From 52896c481e016c0b6ad7769eca1b7e6c36cdbbbe Mon Sep 17 00:00:00 2001 From: Retera Date: Mon, 3 Dec 2018 22:30:36 -0600 Subject: [PATCH 001/116] Initial commit --- README.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..e759a51 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# Warsmash +Greatest game ever From c2934e7fa9a330813ee1895e85ad4ec5e2b0fb92 Mon Sep 17 00:00:00 2001 From: Retera Date: Mon, 3 Dec 2018 22:35:14 -0600 Subject: [PATCH 002/116] Initial commit --- build.gradle | 74 ++++++++ core/assets/badlogic.jpg | Bin 0 -> 68465 bytes core/build.gradle | 11 ++ .../etheller/warsmash/WarsmashGdxGame.java | 30 +++ .../warsmash/parsers/mdlx/AnimatedObject.java | 103 +++++++++++ .../warsmash/parsers/mdlx/AnimationMap.java | 92 ++++++++++ .../etheller/warsmash/parsers/mdlx/Chunk.java | 5 + .../warsmash/parsers/mdlx/GenericObject.java | 107 +++++++++++ .../parsers/mdlx/InterpolationType.java | 11 ++ .../parsers/mdlx/MdlTokenInputStream.java | 19 ++ .../parsers/mdlx/MdlTokenOutputStream.java | 21 +++ .../parsers/mdlx/TimelineDescriptor.java | 38 ++++ .../mdlx/timeline/FloatArrayKeyFrame.java | 99 ++++++++++ .../mdlx/timeline/FloatArrayTimeline.java | 20 ++ .../parsers/mdlx/timeline/FloatKeyFrame.java | 93 ++++++++++ .../parsers/mdlx/timeline/FloatTimeline.java | 15 ++ .../parsers/mdlx/timeline/KeyFrame.java | 21 +++ .../parsers/mdlx/timeline/Timeline.java | 151 +++++++++++++++ .../parsers/mdlx/timeline/UInt32KeyFrame.java | 93 ++++++++++ .../parsers/mdlx/timeline/UInt32Timeline.java | 15 ++ .../com/etheller/warsmash/util/MdlUtils.java | 8 + .../etheller/warsmash/util/ParseUtils.java | 26 +++ .../etheller/warsmash/util/RawcodeUtils.java | 62 +++++++ .../com/etheller/warsmash/util/War3ID.java | 83 +++++++++ desktop/build.gradle | 55 ++++++ .../warsmash/desktop/DesktopLauncher.java | 12 ++ gradle.properties | 3 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54208 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 172 ++++++++++++++++++ gradlew.bat | 84 +++++++++ settings.gradle | 1 + 32 files changed, 1530 insertions(+) create mode 100644 build.gradle create mode 100644 core/assets/badlogic.jpg create mode 100644 core/build.gradle create mode 100644 core/src/com/etheller/warsmash/WarsmashGdxGame.java create mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/AnimatedObject.java create mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/AnimationMap.java create mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/Chunk.java create mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/GenericObject.java create mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/InterpolationType.java create mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/MdlTokenInputStream.java create mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/MdlTokenOutputStream.java create mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/TimelineDescriptor.java create mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatArrayKeyFrame.java create mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatArrayTimeline.java create mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatKeyFrame.java create mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatTimeline.java create mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/timeline/KeyFrame.java create mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/timeline/Timeline.java create mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/timeline/UInt32KeyFrame.java create mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/timeline/UInt32Timeline.java create mode 100644 core/src/com/etheller/warsmash/util/MdlUtils.java create mode 100644 core/src/com/etheller/warsmash/util/ParseUtils.java create mode 100644 core/src/com/etheller/warsmash/util/RawcodeUtils.java create mode 100644 core/src/com/etheller/warsmash/util/War3ID.java create mode 100644 desktop/build.gradle create mode 100644 desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..66947a9 --- /dev/null +++ b/build.gradle @@ -0,0 +1,74 @@ +buildscript { + + + repositories { + mavenLocal() + mavenCentral() + maven { url "https://plugins.gradle.org/m2/" } + maven { url "https://oss.sonatype.org/content/repositories/snapshots/" } + jcenter() + google() + } + dependencies { + + + } +} + +allprojects { + apply plugin: "eclipse" + apply plugin: "idea" + + version = '1.0' + ext { + appName = "warsmash" + gdxVersion = '1.9.8' + roboVMVersion = '2.3.5' + box2DLightsVersion = '1.4' + ashleyVersion = '1.7.0' + aiVersion = '1.8.0' + } + + repositories { + mavenLocal() + mavenCentral() + google() + maven { url "https://oss.sonatype.org/content/repositories/snapshots/" } + maven { url "https://oss.sonatype.org/content/repositories/releases/" } + maven { url 'https://jitpack.io' } + } +} + +project(":desktop") { + apply plugin: "java" + + + dependencies { + compile project(":core") + compile "com.badlogicgames.gdx:gdx-backend-lwjgl:$gdxVersion" + compile "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-desktop" + compile "com.badlogicgames.gdx:gdx-box2d-platform:$gdxVersion:natives-desktop" + compile "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-desktop" + compile "com.google.guava:guava:23.5-jre" + implementation 'com.github.inwc3:wc3libs:-SNAPSHOT' + + } +} + +project(":core") { + apply plugin: "java" + + + dependencies { + compile "com.badlogicgames.gdx:gdx:$gdxVersion" + compile "com.badlogicgames.gdx:gdx-box2d:$gdxVersion" + compile "com.badlogicgames.gdx:gdx-freetype:$gdxVersion" + compile "com.google.guava:guava:23.5-jre" + implementation 'com.github.inwc3:wc3libs:-SNAPSHOT' + + } +} + +tasks.eclipse.doLast { + delete ".project" +} \ No newline at end of file diff --git a/core/assets/badlogic.jpg b/core/assets/badlogic.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4390da6e0f6d041590c6313d2b4c978abc00a342 GIT binary patch literal 68465 zcmbq)^Lrgmu=k14*yf3?6E`;6*tTsowrx8(v2ELIY&2}rB+c9J``r5%+}Y>ZUuJe@ zHH**Aey)G+0nlV6Wh4O*5D)(5YSNn@d5us2mm;EL>SmF&`^;7 z2`HldC-AQc@DLEtkjQYz02nA}SU3nscmx07(ntY=-`zO+9n1HCdK=ttGSPgaMd6-S zdD9t!!mq@QU~+7or#N{(H=pM7;_|xVqVUz6-6>kx63a~pF9x#nS|XSp{W{tyF1i)D zWLEYLZuF;RUKcVXup4ZtEP%y7QBtTp8Y{F|N9=p!Grl7w(XY~I?E7k@k<((`AuB}| zD|>DD>ry4@;%jVuC=HzjQuNT|48}Y7J3WtvC0@mWX)9Lo_;WFu9(aqXWm=c#UE`}O zKS9)T&H2Vk^HV1L=Id^n%^duZb`*^oS|5 z7}x}JPjNFDEG%Cwhdz$M<8C92%G21hX`J=^${)m_$*( zjhrE|7!h$VU}ZA8W?09}6gonXEaE$Q#FCm)tb~a%OcocF#LMI9SzY4anQBv`RMe}~qfRtNL=#xWJ`?(*o9sgExm&%d zp8&{TEs^Wp(vaWa+YeLH?M7|Pg4Y~3r2s*c1s_tur+8m zuhMi68B!nqA}*=Gvi>u?>@(xrx|{qpN2+vP{c+oVob00C$&ejNoN%bF> z#$;A1A}3Jg#U)ATaNo7M?3&H>h7##xnAe>^{8$`Qxo?<##lOohsx)g_AqU~VmO;zb zkVe??Y_{;f%8PkVGPBLEvi-I#x!_Ai2lj2lkCz`s{0LS@je(qSUkH0FWaKb3XLx^+*4}mmQ6^gYY%Z_Hs^_T zeC$aF^KGZ3c}99vb)Z$d791B*h3>9qHmP4mj0EVa@7qn*#A>hxiR{^qiTs4sQLhWd zvxSc;ZNp`WmxT$;kSYz@4lZiqC=-Kcd0 zm$sVXkrfwV6l|EekR>OH8I}~_8%d@)Hi`x9Uvkf)li+db8l3GYt6VV!vSyNJDY)Y( zc;VFU;F{C9YO|#vGsAa-4Xq|<6x0BSY-Hh74+K~5i4<2Gbp*`6@5MdF;0g9Hchj9y zP84i(l@Ui=DRD#8d+5Yh3rrGV^mTory$M8P63!2CoU4TslWzvm@0K_cMl&-fJ`lwH z7GHkz^y_eVv4^TUoebkDHBm)~P;6T6yC%O@0rOPg%UoI`PY>t%@W#5{KP(N>@8bW; zNPPm%1ROpAj^4J$4pyfyjfQ)f0^_$Dl&DxJ32+R=n~zqaaBP!nlvzho^eq#EMJxh} z)t`XS8Fe~yYx(kO!!3sSqZ=^c;b_+ti<-P4@%_dZn&0xJ_PLst|7R~l9rKiYx)g{XvGl)Zwa0@n&m zq=|%Q|Lb2B7s5qpo~+#i41($vAg7&_;Is=M08?K*bP%0ny{+#9^9D3#yti$s1&iC+ zr9nM)Y_N&5vGcdEtSV2ro{hrP))4l>d72H5h#lI7pmjz-_CX8LbzSL5C|!fnh1DN5 zT%T*cnqSU9F15wROfY?%5~WaKOFC!s) ze?ou%^tvB%{nG~bWD~2()O!151>W+-v*YcRn388${+`Dge$KOU*f+kM7ijGyrE_(+ zd#D@YEbsFi5x?#}PS&L^B)d;jyS5(f##&5s+qPjZZfjo4C?aR&`ILmWn^uD8f{YmA zJkCzz5>A{aM37iaa~B0s#f7lDT3p_fRr{0iZr!ZxWKTy8&$X3XsKNtwqlGK!CmX~Z2v;NRkx}*TD8ZD{b}id?5MfepgB#zQxv$FYOgP7p!n+*kp- z8dt(7C0!(ur08A2FWUH7Okr`mW=l-G_Jrcis)gYnHi_d=|0v&8Rn_1mDyOLEVGc}c?4Hsbo$&@BhS#}I~!}zaz9Lmf5I?HU*JfKgw$o5`!!DIF>95@ z3GGgFi`(x*f>TxQzkYwO%yK8%?3|_!l!P#QQ_Lt(@WZGZ(&P>?cQ2kDrDO?xBX;*0 za^jvT@oqG?fa`PNK@RGRLpvECBG<&o*5al39F8GMjqJ=r?;^e*@ z8Dt$%Ya_{wh!d!`7t$Vxuzh^gGA7c(6X#Xk;)@a*8dpq)Y~ia5xS$Zl&Oq^X`PTWN z(kui_kn&>kM{3q+yvSvT97Kq4dq7Qwz?euXcmMd#lI~iSty+^_?zV5fd z>(32N4JHX4ekG7x4IF5b35_7AeO^2u^q|CZ=c9ZrnSX|B>GfRqDdvjR~<4MSVyVw)fq1IK31lUXEiPjx=SeUr4u&i`B2`jwXze zP5&Z3{~dusfEVP@`9p={ZA}I=Mm0U`n6`LAK;;e1wCS=KCc%pDkw)?N=(fGDOg=uY z3a&>IgVmzlCR7*xmev#|C<@KQ3*U$r_x!^w72AY z(i}I&Fc9~Bm2qakN@T)v18!^Ji!$%3U*F<8x4Dc;n&hQU$nYqmI~Kuk3pw9333eS2 zyO6+e`@79Ze)Avl_-aZAaw^g5RJ~}!2g!F>PlQEIRY8{ zoc0Qw4C z3B#VETG|@X08*Rb624yU_HV7MAVMKHK3R5>5VP(o2IYYgE3r=iE?wj+H%NEnD9>MD zBaM+r*m&SjiRs4#5T=yUXh-5W?;u2y$;p9kQ*nXDh1SMK-NH&kPQFa#5k*reTkn!i z<`|h%HCnHR$JAJ(tV4n8?*)11MNJtyxXLP3C^as$)X2gZooB|Wn%Ep;W5v~U#e$HZ zrpT-*5xIXVc2QZmW;;EIkC9W?!jv_-d_qcb=wJyi%aFRrEs^1R(MC}`J?XXb6A&M1 zwt|gpULGLKM-f|BCN4)3%b?K{rkkMOnc6^#9?2SAN@MM)*eoD3P)WB@2pd9#>r1Pu z$xhkI9Powzn8a=_-V*t@0c`VQTYy7Tt?g+t=3V&rnQa?2JZp(8S6n4sEeJAYG#S{|Ww_JHKMc%k%9_c5IBzBHQO;4*m_>|_# zoNOs((}f0*>3%^H0k;e%=hxc<`!dHo*JrC^0?#`3Pk{Lc?_ut9;79h^-={y-yy^aO zFB*IKjh_Irx3#`!q0LXgun^@RgW%iz0|SvepQK02wZZpeup{~iQKpxw5#w`mWlyZX z+HdWpp@pLUNm*;_!$fmw3~-Rt!GPWfg||>I6(pJ&B|itBj);OHA3cLcbz@<-y6K?kZC*d;?GO^XR1-cS5c(E_#3P+_pC z{lyCKb$2kFoTfGdne|1^Q@o_a+^k^(+Sj%uqyIjB+Eot$ zSfT6E-i7ApXQYf)%>-_Ze0k5a>*~-#An3X=jZtPmilc0;$!d^jA0ShaRxwR7KB`Il z!+aF_8~+oKb`#P7Ib^cL6RZ9Ni@DB}Xv`~F8yn3s-#B?5cTn{0cFA~)gUz^X+UAv> zD!b~%S-HqX#{EtXtbYF(-%b;l_qC$+{J#Cf*h z__NDz;~6Wm^cC_52e&C8R+$loe`EG*JOQoc0?H-dVt6yvF4xg(%>}c4xQ@UdLL3`{ zXtjfehL~UvIIU&oQ&iLqXF~XWx-awx2!z7J_fyH!k$lsBO_>O|HJTSI_*|>MtlzwE zR)n~Yt=ap2X7FD?NUbJ%eu(yvOqyt7gz1+t$(pZm>-YZHQ16|$RZ*3coi*cV>%m2n zyykiN=qZ%rBl!MZqUY`Bcy1e>8KCQrPI--`QtBq2EV;Qp&h#dQQGb$RHXB2;yB0k? zTF~JaHt%4KH+D5ipjMXxc9QFcu2ogWwf+7247=l)@FRaP7i)|5eEIHHt9D5>T9z5@ zVVE04=#dUz5Zt%BW?{xvv?DpJUohnSG+-FZ4JgT&nz8(fYW~SNp)Y=F>WO$%#p;ed zKTguYuqWgOB)RL`BEs5VG{{Vbq-}ka+^_4sgi*wycvAkVUs&GNBZ#UD*_QBquyB93 zGt^C8UILq)?AS`F=c^8k_kSY-m>j)88yi4Bgw-M@-!{;h(-oSDGi(tNiQWkIX*10CS12ssB7_6s*>lPYL+H)j8uiTg@ivZnUN6q zs`_HxiPQnotL*!M0=Y7+<5o#@l)1jJb^34duFB7q*$f4tkC(>hz@9isC*#Im^h9QC z9%}*ykgFO4Hb=G8iY#sVum-d$VwGX?tmbX8LM%Z&k$c(c3cr6(oXl%yFz{Ou?;SoEe!LNG{$DDNP`Uk}z;DY=Y7%?D*nc$?0!--B zP*8a2o%61jGNG^XG$R6KaIc^KWOVnt{n0io!#m_>`~;{|Rlo3gdB^#Gug&}5$niDP zKGdDT-q4p;YPLY7&Hh=rp}mGr+O^JN-DSROB-knsB`WHn{2=A{ih;oZU*U={Ib$jP zr;H?CJ|`xxx;Xq8S1Tc1Yi*=yku@@3blTz>CPx3FhAUa_8bKh+jF0{ub0D#yk~B$< z6f+?~la<^Sz$Tx3+=d7Ieuw-kni^TBxO|-FmAH>??hWKYI^oCW|MT+oFXyrCL6tpt zhl0L{*&&Led|&1C=@Ss}3Fu77{jmbqSCQ4Rq&Kf`EGOai6K_lRGU3hQb<`;*0DqL@ z6CmmIZ(Lb)>`WZ)*Z9BJw~bb9f;St@*G3Zf4&oFI*|M?jUFF%aW$B>5e3>7uz^q!y z&9AqA$FDaUEp!R`wwLfok-R>b$ic&Wu~Dzx>pZNZBa(3%gU7Gc-y=PaAhooxS+UR6 zs-RRlY=7um9AiRPRkV3t_q%0D_mtl(=kJ_v@+V;1Dd59><)1Vxf({Tq^pY-s)84 zNChYbToYJ%sxKLKCb2r=7*)|8p{UkWwIV2VK($FEI@(wN<-K@D==%FG;J#;U)YmZt4*Yw0fvP>X* zjggEb3MiGiMmQt91Sda5HxlOsI`H&rrzwtB*kM9U-R4K^nES%w4UU1?;!oW&sV+tm zDzpe)Q`T^cvPB(H5F|Vvznwk_t!XE4y@b;T_;EyBCT5mhKcp^G+j&A1BIoMTJAPX{ zrMuSrE@#1@UQiU9OBNTKn}o4XNi}bMYkMj=tHA3j-Wr>Gv&HWln)hB#4;p=4P}sW6 zC7^MEU9PiRSlhWI@uMv$WUpr!Q^N-9(V%k9By(pGQuAi<@i`02*AgEyE=)!VQjDwU zh1tp&dO^iAt@?JvX+X}#Wqmg{!s;O2kq{4$@bFU0Cl9@5zQh=wL02pxA zAM5dD^}u)&NkR>x$2^GezA|j+-|YUtqA+q%q>nQUPhTmHL1q8YQJe#{mZZLSpVGCYs5+5qxhI%pY1E3A^MBU7DEJqW2Dac-r*KqBqo4NH0lDa9B01idiMFUJ2B^3~w zWLtd3Wf}Ixgx*%rg9W+hDOb_+B9T^iRCqi##4nr1ehn8HNg^(vDPiDR-&V}Gx+_Q_ zlX~f`Brx(Df60mm;W?#1yqf{)cFHwj8>c)g-%3Fwf;Jgf3W+~L9%MyE;0R<1~s+X4CVp=~!UcNiOxuQyAC%F)wYSC&@Q! zH=1GE1S6PA9j>Msv}s{)wq%Y11L!6^IxD!I;mGEszLgIUzA=Z6m*fF_h4dQS z9XyVHDB;dR?pK_{SQu_PS_Ic0^3{cJ#=wDO={19VEci-pWJq{(mA8(438`Y;R-@BB z+n)W%AjiWkg~dDhMp+z1Q_L^v(r`NEwTacBiOFLeI&gw=7?0CxIbUy(<4B8b5BU#& z2GpVI#c|Yo;nX!W?{v9{1IRWdsj%IC3f~9nR8Y$^A(>5;lZsDoac?52ZDw^jbDZVz zigbdV<5F|);8VL^{P_bN^44yc_xB#GCwE%sh9bX$KP@2sEZ^ z7-9y;_|MHK-*SN!>TtC^d8J!<1&s(RTz6)BK7H`~9l`RhB*r}WB3K(-z8*wKBAqzm zjf@fqcoh8=J*Ak#po$giZzno~1{|I@F+*Txzg%6#O}v&Oh@3&&U?myhfh>|l+D;j% z=!l4sSS~!OPH#5z<%di*wZ(4|`MK_=Csh^WR|RFJ35b9BiO>|q$OT=+L<0e6E{!LO z__R2>!PA$B24mP;A!PkZ=T`|ezA@2r+RwaTQl*40XCp6`y! z`)sjN@?Vu<$0qkUC zVs?JJO*gj6%w~~_ka5VcX|3+-!8EKQ<5FUlPt=tlx0J&OyLzYd6b%iO+yrSKox6$1rlj4=BI6cAV&vF0&+S%OEgZ7z zsl}$GZn_s5#Xjc}KMCX938!+-u5?dYUf?OUB$GZ>YRUycWp?OxZw)Fp7GWU;q^rc_ zALcnERuGL7w@tBvif^-82%qU*pwhJPDy%k{{17*k;a@sp0SDy#GvAN+jpN+f^_I?%1K}YPOuU9lLn4X>!AO zH7WnGbxJCqo!az(7nMlSC6U9WC*fhp(`AOW^ALutPyg3*d&Iz8sYeAPS9yi8$apUM z8sBq@T9;W=c_4x$97UQRSac?o>JQ1*HJ$qX@{qotXnH;0cFeJH0U6JB!m?*b!2lV; zJd$i@3(llWA-fyz2|KHuM1<`p`I`k&`U)2Tzl7=6+(mj|)`Wc+x`wW88rMo^U6)W* z2fK%(a7R-Ki#I!~_HI6(wvgHmj-E65odA|JyF*@cq|7k4zm`Z5O#FKCwYvX)7p%gv zb6Ev>SrfPI?51xM-;lAOfdZ_ljy4tQa);W9b8WC& z53&bGxqY&Cb|)UD6NkL^DfBkKBv%kfgBv+*9jgO6VJr@Brk;N7XO{y&!zfg;3c zMl$NVdsQDcadpFM|LR?7e<+B*y?Eo_x?EqG(8dO8+4k=#fL~R6M2ch^j$JCd4!z-j z96RAsdC?oA*U!bc=*xwctxYysayG6%J?8lYbQPif>CNZ4o;7Vd;Is7lgyj+hSH zaHNU?AJb15Xk5cG5kika!K)=$;)H3YGc{;PX<^B5!p!LTc_FB9Pbbwj{ShME2%4MV zlfREt|6nOT~io2Y6_U$1H3NAu|iAASK8`X zq1AE2^>tIvN^8ilvthWiFy(NPS7m#Ht-9Pb#V#FZG+JSL{Sj?mebdCwb2TbMo|;QA z%Qt5|*lkKcV=%VI7TOYlnlK(TUQVB$j!u?(%%R3zSg>4dEn1qWhUq}`fx3jzRe7v} z&SKNrVY6;G#2qIq{^(-ZIb+;kEQp}Brd!kEkEqCA=O4J(;tM7)f67eA{EC8xvUs2I zHNuyojW#?<%J@*h54u=gFyi+>UfnF7zZi@U$JqR{DjNc#B-DQGMC3Ka*<- zNKPSChXOZ}tV$yVPG7n>*r#4-LCX9I0ir7&$UBbYMCn9{L!`#7=nTb_zqD6d$VvOH z)7(8$=|U00FiE1WDq-L#75HAw*=?%sSXA<5aOi7Nu!kt_!&w(AN?462X>inV5y}Ld zvoD5_Gp|$DDSGratu#`*0l6dYbsB5z)7e$~U|X-vd4fNVG>5>#sAo`NZL#CI;dQBO zqH2m+!V;5568)RohI2kaqXL6=Kn}TkpJ3SHwAnYKX>C^C1jWNk zZNKf~jW<3S6H*!q3!VN$h3Prl4b|o1?y+pXL@fW2xLD=g?re=axc8-bQ-y~W1u3Fa zl%J6g#{#8p@}Xr@{B=iMYr+YQ)e}>9!BRx+vnwc8oq%3yoZ{D(30xPaR3}44g>$P> zE^APof9rj)xw=xq@9T#(m#S9BvNDrFrc3X$G~qJ2(QOWun;}?*qY#rMs(yaoG2cGx z;1TE!&$WP@9Su4Q&+PdR(iNO;{jRv()!H`0QF8R-!4H~k+^ zy)`)UIv`e3XlXz_S>({-wWCa|BmNQ>Pk{SQ!LUTq>d|o*H5iG5gX3-))&N%=c_c; z=ALH^60Eg~^Sr-0Vm^~#Ef6U^Z6&hcu;j0pDOHoVc@@Wpb+$MvF9b)Vob50__r-C9 z%;ILwCD#Wa2N@&tEn2&OEuq#baPK@iCMhbQT1>!PsE+r-sFT<{Q4CBTr9tyuwHONt zAgPDeN}z1?K9T#9SVzlB6di)fC1>GSbZl=rzE`JR37{q;Q`7 z_#~lAMd%B%$K#AhfqQoSzWt1gpEMSKDg{$AGbR-&5EV%kjG=jD`zx2Q$F6IBM1tt8 z{6z2DisRH*m8u!i$(l|4;rvq}-Q-u_MS5m_EFp7Q>GrW@%*=Jkt#KMJZg!8%-8Ds_GPyR z3D0jhO%q2Z9^= zyMF5SC?qN!q_kvP{IU|@y>rQK-oF!w>tB;WQ#I)zg>!+}Z@+$C?U6Ad+zYfm6ox6((Fi=zI!;AZ4PtDcVLdf0w ztF;Un0G>)z=i367q#9YVZ^{@?0;32b<5D2j+7;kq8AfrtXDz?SyQHmjZl4?Y{bs3T zyyX~t4w_>gz24uys_TuW#IynWwxO4R(}^P;d=M>l2z<4~d<0oDG+N-n?5rsGN;d`; z44Gp?(sLapcYGw{@s~+Oilg5MeaSVDRczBL`T*~%u9k85+>S}i6!9jgOJtWfB}BL{ zM5sQxQH57wBT-r_RC~OYS^_BQ{w>)zc`?7n=jPj$n26i-!XgT*SM9gzswqseKx+gI zXl~KDKWjV3r*xody9+k@*P^BQCP8St0>_mWiBV_PX03R~*jYUiRUt|)A=s~SA7UOz z8aN6xcG-S(VFXvLTSJUY2uI#$hYyX$7_Y7w)k|@DSSq^X=J(~1?>s7!TfC0v6+KvT z7H05Y{l(m?czhd6^b>qXf-Z>@M%(g&zobXm&t9oJkm$-qgLbi~HRf>?{)IHlb4)#vW1^gv>6DYN(Fp8yYuo2-g5cipP3E3ivW z<+&zt$!P1ED|a!yTP?d8|4Q0I>#l+(Qq7o?IZ#fgamw+>YDxBc;N5lEXv{gYtM`#q zd6L*jN!`$IBMt;DiE`zEEu9WcW~k6pjV@vd8~Sb)m+tZ6){?PmhSfnGtXObmyqzk4 zG>&M<))YZC{F0Ax`@vf9<2%uz>h6#DO_+_^yZ1IxhKD0Ux|HP|Dz>64Wi8_StjdbC zf1dv4i)huG`P&Zc6f?R5i>vGN9HRT@@XyM~n*IyxjZ4;6_MdY)op=cInn^J0# z>MnxNW?ShQner{DE)&YQ>?}NK&Bc7(b3e@4S3VOl7!1elK~wQ&)?g;_gpIsEn}pM- zQj$FBaq6vmTnPwBH=-AU_~G#sW4!R0uavRq5_cb3yv7PjCL5NoH&+rYg^+V-=H%R> z%Ki-vv2o}2&W1W|ZCge%&7srboew`TA;t}FOf{V>T3YREee6US(5>jHhJ7?;_7jfb z@tzZuQ8;hE)eIdltmbX$Z5#Upiv=3OPfA$Xm~)X-%DQUZzHrb5}mWvaV$ zFCS9vQgbY`Vz)Y!D9o<@ErxmT4eOvWBI$8XBe;gVYTM-uwNdo73nFgog_kJ*yH+ z^C^1?S&Eq^G3+|6z#nCs-Q;Bmm?Ep{TYmW>1{8N%{2T7GPp(NMJU(VYl&>Ll(xH0sUp+e>*+pb*-8cJ_)hDi%aCx?Sy}3>E!|b&qqb{U6k549*00tfKMZ_~ytpO5t|-roT}e?9?Us{?>rdQ7|QKWz^QF1Ox)wr>1e z^lfT#Z(MRt-y32~c<0KTzpeUZ9uYT1-R#yEyosMrV2MnKYyR-x*cdWVAX1BdOqrYw z=q(a8C^&U(#hMdez`YkcwtyzOIMbd#*KKJ=z9Rc|wq4fObl`3%T1vq9S|xr!m#S$G zvW{~~b)QdewdW{%9n>k=(F4u(`T0?Tq|D3zJ;Jworp+CDAtO~!W+QE?rEE6NCh9?c z)zH$pPtU0fqNMMJ-+;CRSg?L`psiCg=G+0=fliJE$)1$8W9XM}c4TY(>l}g}RsbFH z@1mEC<>LbosD>b@T*oKmuf%Rk@C8A9wq+f9m%pvs47rjF zFq43KoFo%myL=+pw14?K1j-Q!BGLp|^eHAf`3jN(z)ra>-}IcoYAq5T3-xD&h1BiD;rK+kM z>uLXmYCua>tEphiPw%eV-^N9x_4_=uFmzCD`Yc`$n4zdsc>haK*w%3Rp}6?mzJk&*gN)aO6E18?1$8`svndC zu(WN>v=BV)4(ieG;0CauXx}t%cCkc2T#L-lBUtkIl4U|9cJw4fG~OO4;eq2-OrP2t z7M#SVbBGo?z1%-E+kbBg;lve-8I~ckG|45td@Kz*BrwZjAEII1uvw-+!EW?>mLw6< z*4jB)K(e`YU0UZR&KQg8R2tW7nP9k}kSk90ZOffws7PO);aky`Ki9TzZSj{*O6%kc zS-MPTM zge56HIT6LbQaCWEnXot#r7K#~i)hwuw!X6*81eav!O=z}laj4Hj^o&sd|TFh5l0pnMi$5nvODu{D~K zj5gU@(@Xmaf1Y!=1I`ZkdW|{vPGsS5L-X#de=YdB>a^D}OV!~Si{cTq+dttXkkzg3 z+Qv0!;i zu5n|EK=Sh{LFc4+q;GzE)tQiPpR$5PNsqwg9mmDh3sAg|>p=yaet$44TW>d4tMrm+-5 zCgkK`jo~*BTP`sZ-!K}A_dR?s;5ed|)!Cq72h1>lZb-WZP0e3;twlkM(P5)A5Y7?o zUF6h6Ow%5I8^@?!MnhqdJnA;TRubdoo*gD1@U{5Af(~P1UT#h{gT5FXEXTAX*2zj4 zV99rMm-xC4<0l`j@O1^M5Z&3?{P!N5;XMbkS2|r^uWch-*x2L`9JNjYK#su5$a%Al^YbuLCE_gC>|tB9IV6j8;BG4QJBX2sl0va<=?s%d1ly21%> zf!WA`f0HUNH>O9X2WhA%drGVE8`>JiY6o&D&hxG$zj%esY&F_L^E@j+vrd8jAe)`3 zKe7Oy`WU4R(HZ`tGF6qhjFU})xQQ|?Gdf@s29prP)jBW>JX{(q1F=jUE5AECMY@nF z7tD!v3^>N9s1<=hd9C}3eJAaV-a1|*ZYaNWH4s z`As1aNfF+P;8wiB8cBHYCCVWJEn}mt0s+6D&3bpRIo*OX-&#?$K*{p@u%0gapw0!>G=qx%TQbw%M;6K>bTy52mBQ^p@>yId>DI>6^VXoiUKCty7ScdY>)#<<`|`N z#d}s?{hZX!MUK9CzpRP&go(2eh8fAwd8%%>O16!HHK`S@3?oeCqncQe-l%5fu}02^VBsFx`-XwBGpB6MJD)~P4g$_ z7*V(Wt#!MCv9BnLSNO|rE6J+6YNxgp(Z15hXU>f244EKyonIQ5a7f&$XsQ@FT^iCULNQR=Y2=xm?$9L`nB^q0D=L z19p5faGtrg2Ed4opmJrwOp`}GMl0mwNETHuEO`=J%#wq*(n%OgrkM@uW-2}E zTcA+ypz|cxe*Li*{Ue_xYSC^{DI&Ha)sG-ozw~zsqB98sOWgrCXCurPFTCKeOqsMF zQq($zkNqX|&6%b8BZNAJ7QNoIFwOdD)qBb(O%>eys?65QG@`K513z-{cs=ye7aE=R zo%fYqmDvi)neUyHn1PAwuP$8x{qJXJdh#GN{(&vsHJp*r z>7tva=fS$eq7TtMCs?-zPVY&Ni82hM(h z)7r;#?5;JSxrC;?iAWGeTw_+rSm|a)mP>y)rPFrbv~VG5{9>cZGE7YUs4PTV=zD~F z2>?YTt$IR<9(bevb+*m`A)-MUY8|C;e$kp)kqs|l5q+Zp;zu51ao`)RYRycN%<;YK zQT!wUrwvz&TX9cjoAbUaIyf=)J;^h>4ZDu&s_v_5F36=a)#kNgOnZR3HiOT71oKQ1 zmCP92d>EjTgp4N>&XpXbrm%a)S*AL<@nnZetiQ6NBj^ZQcTWgKEKjP0j0ZZS?=~9Z zQaPn1`|Zk|qakhWZUeQkTN{qDT%%;QCuJd1arff=mvO~|(fYLZhudN7bjyr8Gl;t# z?+ZTh$>5$zN+w7~0v61XN;Nj@>>W*ggu_8Co2G?vH~6iaY*9dDB@W5F;>dmH#*Z3V zK{|sg&8lrHW^4oHH}BZ}*0#1?2>xtbZDq7g^ybPGww9oPM;_A6crhm%aS88H?zKh0 z!p+Uk*L$RY0bGx`p8(=N24}xL9Nu4H19rT>d;;P=^n!3%L)P!4-*0+SMrj-)4O`0& z9`Kw7oGqIZICj38u=krn=zYb|wJG@o^zQunZBNKlcc0Z|g8$U?Ueb4D^Y05=m8#Hi zbme$Br6OXyz?zyLMHBK#mF2|H4(&FJ%UN5x4M4ZaW0de~!A?tDX*cZ?QxT%#{CgPvLfH$i;_^mJtp0V#r6vN(-Npho+1&e^wyEtcYw zGDhVLPevy#NNkjEF;7(8VU*&*%u73gdYqxl# zLS=06e;RHcvnP5fh`$}BG@fN<_Ky~RGYRE-F=vP}N;ZRzwUHxwxQo)RER9ods}BT* zBV7F<8L{s82MV6xxB52-8CZCoxR0Yq4d6IrA7@4sqW8RGkTu$58;y8-Aq@2|bO^-n z|5SdEkAB>~_ZTm+#3lro8Ounx4x)U~%o?nCcTiY49d! zTkQ`dR_Lzb{zuZ9f!>OoMuNKVBJZc3E!D{K1ByT~fvnxgK zQrpVbu~ihKvn+h$U&o;r==U+2u98oJuOw7@F950sjoT=!9En*}79{`X*Z@-8r_akLz zny#A?ESQnqyCI`pK6W^MuZcKdio@I;FC#uUpCu2HyZ0J9DI{*UIJl{rKhE#uAz7)ErVSOqME)9Q(j4GrIP1ES4;L} zr7+r!M0M1e`7MRD#!YK;lU&}t33GLlT-sz8R%C2(#AD~%+{>F~EyIc-M-!w#cW z8>{eXo%4+SclLU_v0XM#J<2;8sjk{*I&%~uE%A-xrwJ z_b)2vDyplva<4jZ<8N;G!iF07d*{9pVMYTZM~NGGw?Gj=9Be_10E`^5k;DOwsKy0@ zXxPL?wW!oY)emAm%$HHt?#PpYb|#>o$dE%am0i(+qzIZK-P`9WKhc?62WIRro*yWh zDEu#7qq>ij!99?DmTmXH^n1Iv{(OT!?!5*70LOl=`t`u!jNOqW;h6E`+PHfC`{mJS z$&)6C?%~tNj+}0q4Vtz=H5YKJj%QJuQp`;! zLZm%7wxi^AG!^D^yJb_V#;Tj4R>X;~`(uP9Z*sk0!DQH~#kG*ppcrmmYHV~VMY zt>xT}v&X?rk8cj`Vp#M-%rqJtYjrmcinWp_OD%-wL8qt5p+Xs^&XReP3OMZZOORP| zh@~kC;nmXqF6>@AjwdMTk1JJ0K*iHH55x?VbKQUWY?Z4`l+n)d7%F(Xr?wO=BChuo`a~hD1v4%4x}PcgxBxi}lrVkw z`K~bCu7+Rbvo*AAo?2_J?e%qF6~AwJL+Op9@NicrUUbEfP;#y8n0n29gb}5kU@I4*G`3ABK?y-apeT#H+PT)BHdTftR1RU zO<>FGp7RxC;RnoC2)4mYiws)om9#;Nm0v%f4PjiNi3@=3>jI@C0jx4t`?7mSnBetd zm%16vCg5fV02V=LcIO4b@#={UPV%yulvFWqrK=p}2d=ZV@yQUzanx3o&l)wEw^5tV zoj@%u_Ui7f)d^ATHC9c!8TL;OP~X~)Y?v3B6#Dly=+^fS(!1N~bKZ!Hs$zK4@C)Qa z$>Jd9^@VcMqvtYimDhfdy7zw*uZgr`^>#$Iu0$fq^wQUMn}!9&H+>BN_7v7iDa3MD zIElN_MNxdbwD_*My`Fxg75N#mMtFnn2eQlIUFf1TU2;{_@ z3SeIzTuE`t*NQ}WARz*9m97~?_c85rT&S#rNcO#?Yn<1y&2t{r?Q+@`_J1)YM?}LJ^}!LXhRRwHRkJQ88fJSwl`nc>_Qg}7`1ET32NjF z$~U1@z1hw&vf!m7SidyVF*Xk*i)e7q*}mxSrT2n~An8|$4jU*ehNPP|sa?j@ajsR{ z36X})<*@Su^Od#Ne~3w7PlfG_V<~I1JnDwqqlEX)7tIfgn5UDDRw)z`%vi3Dt2HMy z=ee@U2xPjFM8>*F^<^WFJs#!zO7_IpEt{D^o6+d7lM9GT9qjQ4%7Vyp`_cDR#^|%D zoyOTbqRx8k9Np2&FxyC$B_TgU+f(GTG5XBZEGt}4G*RG&7Fq##mTht}d6^<@ED*iq zzN1Wro+XG_)U<3MNMf-#^(-J-SI1TMMde(s08lTgT+@PTLrh$*zkMB^_lUYKd6bCpy`el$Y~qB%RD%tVRIEk@%21T50uA z_p<1@?pP<|&oGUSSR3L_81;cFw8fXkp! ziz;!%RBtA4&3J`agQoF$onKH?oXsMf9IzZkcc)#}ui#X!w!C%Jc5-gQerj$WrZZee zSPYO_BXTznT=HDvAJA5qOa3>s^RcR08kliRxr5s%oS{gFw>j?NwI=pl-)I-$(l|t9 zw>2}3Lw2aj*mJ84O_IW1atT_Pi($~3&tfS zw<`E0V`g5oaG=vhXKKiOvQ8o%I`0z00a(PPrDG=S%AzY}h6eXl;*(1SfFTOY8iAG> z3Hoaqm-qFL%up!b2G|wnSabd1D&(jbxT*Tm4u8lSRLAoE7wdZT99ZcS%Zu5OR)m8rhLg+k2cVWQY z)3A!357A;l#q6!8;6jNZWy_0oQHGDhRORg2?j(g!_1jxgTAbik1wihi;1k^ECZHqE zuDY(v6MR<;EMRkb?Bt>B2rEUd8&;Jn7@^rrQYzuY*u1i&k|unpfn;LpC5>H1k4Xdl zF?XAtmExw)j`Up@%~3lJzPV*LxcdFnE=O3BlRJPGcV$Xm#v<%R9ht@9g-9Z+lv&}? zjpM9?ZsE$?gnDoiOe7@@8lOD}<%5`RwzF5PYx`z7DC(Pz0c7=oH!_%kaiGLU%@UPO zK+xg!jg40hX7hPb$#Cb>vb*9KK06YFrKEuevPo0DcJA^$$)fCTJiU3&J#uMla*3)F zAlchlgSdqo2S%KIF&63mu{BdOgJh*Gqlvj_l8KDKTo~61=_Y8ZR5IuIjn>N>|h>w?u-Uvd7N$s_sjZ=^6xOFNMyagQJY72sT_xBTErsDy`&F zAD3F59L2zUQr#aQa*R-L`55Nf@0B_N$*k+*5>pXi8%7z&AImoxx#xSIhyy(B!5_3U zU^O3sXXG|&*|T+aXx%2PmUuyQmngZTQ40LB+S%3-B7mI9HiQZJf?%WN4p_J-FbCB* zey)q)Vn`23QU%_UDymJiQ{??_t+#aV>Gg5Fr~QisA@SD7hWU7}UUXbaplF-l zvTVfUwruQkl$t(g5Q`5{^W@krt1>uDnbJE7iiT6|&r(Rq@$0=!5y?Dw*t>fx-5j4f z`IDnU79i9#d!r&2S2jr9KYYM?Ur{R#gxU-*8xT)Axq5Qht}FQX_6=OJe(;Gls54RG z+iD<|k~Tw8Ga$Fc?i&P0yskc!R2B1yUn=UnQMP>=P>CF3#{J7)_DlE%mhkcf4zj>VH^=`_~X zguYolkC~hYL$_~>GexY3dJW7Pk#en&d8*bx93guM%R;d?69*U~>pO@n%!T zsf9^Kv#U7tkugGgC;DEude-ZThZA`w?;jFY+=?{>BPfZo6)04F$;)>zL#<+uO^kNY zxzX}S0qqAGucxUdH6c#qfYXl5nWWhM>g4M z2S3;*25ljDiU>DV(Yn<7KS;vKjHIHuQb3)^KC85sNvHMU@pSVf2P88)R;P-55?B zZOExe_h3nyiMx@;$268aft_0XKDJ+~Sd(uH2sjSxco)#_Ed(JUY!1dXfd*w#H^z^X z#6}EZY@?Dr+%Yg?g=#pgBr~b#vYQH5G+oOo4=LRcu3i|N@~a$+%7xzQy{L*ODyq4P zyg53_`d+Bp;v?FEU7jMG>*U0%m&on5!D3y0)skDdZLW6H?1HbT*k&ng<+@3H(Hh$^ zmTQpPWWlXq#~&)ZzS2tXarpolXb_U9sj(7hyqG}Q1ae6qB*@4a023^bBpld716Fc4 zuyVtkwtg_12M!rOWZ?&GS5*a5Q*K|nis^%Qx?jv4iSC9z=%AGO4#zUe#Vp)QEQcNa zu9rn@;DsJWHNzyTslkPHT*F6og>+WPj^ezPH!v8FH6$&Xwvl8-4{(&&ee3%>APn@YkIo947ura#z^OO z<3+GXZ4pT{(m^*5$`&N?X2O7g4$Oh+8MmXs02oNg7A#ydeUaQe&%sqG8;_=Jp)h#* zNWV@sAJcS*PV8fhkWF1;wah^!lP4)YzU~>Mt{dS1;L&!Gc~N-$c^9S{^EdMA<|gm* zk!j^OTNbUkf(~6*6%7)JKbqY)2&CzbmMxI0J+);evdS@Z=_Hi*E*=gc>xkS}s>`-> z&FE^a6Il*R5{xR;RvvrX{1}MucJiLZk=yPuC$ge@JgfANgzkGJr4}6mfe|OwZmIFP14~DqK8+!5o^K-nN z3vFC0CNP=228+lIcZ2@`WfDjP-Edv5kC2C3VvvBx4~c+E>y z=n(Q*wo}U1B)f~N0`n@?-g1qLDs-|uipB;oP%z_0j21$C`F2uCE@50_6Y{>zV^jPi z8x!eZ+y3iXh@7gqd2+c{7w|WYem?Q1R3}sNjaEJ)gUwJ@Wnh}h2km<-nmGOn*b~Lt zph(WzDYB~%2-884DE?eIMll@-(QVu2czRx!r#D_bFI@FKx88zo_I<*9sxXdpbsLCU zodq=$mh2S6mh)plE1JNLj<;#CMww)#e{Qx>yI07tBZuiI`Jk~Z2Azd zl>Y!VmYWJq+eu(kHno4p{O`MBN|g$2oKTwz(SdsgsyZob}Ss|fSFP{ zA-P|ZhwpsTCPl7?F2ZzyM1=-1=1i3#?HD6Pj3=5zgAvM&7=ji&gnN}5#x>4WL`BwG z*FDW^QC#OLHLXPDT;*O75kD%bsED7wuJ!Effi)cP*TtjHihddMffq^S8Io$VHwy)n z^o_?Sjg?Im^wWLV9gde2a8s>ulwS)X&Fg*CZs9HVHGZ2qRB9UZ;d3SIEo zulkaRp^a`B(6SEBYoMf*+IJ9<%1#Z&j*!#K>?a4BZFWgsI3m8pIcD6>!D+vv-Fm#d~{^Z+>8w zAj2tIP(ov4M9A*CP2)BQ&z5qzmh9o9XO9umF*h0r%bYc8&+Dn(w`%|{cWToyhtpe<8P8DzqRS%3e3eQvAvy$eu}ibYO1RkTy&dBiY>soa zSpHEts&igV-nFl6(HCz;*R;h;(FL5#BIK*q5lX&mR8A!)YH(>s2AxJpXUgb`!dHfJ zg35`nlvZyOAGD-Npk-s4OfQ4Ec5lxm1IOLlAKFqK%k}S5pP3esL%&8G2RMzwZ8x=e zn)#d$794!^W}&j9ifLrMv!sKhHk^J#3EHxbL(B}c1GC*>#6104Q~bkc(z1J2Qrg=a zJyKrg$&AEdtG6u#CI~Cmz#_3(H02@VzUbiI8a^XFG~~#=1H4#lW#`w9&8le=r_ZE% zyT_Hc2<6DUcy~;)+J%h`tKK^~y<5JtvY~{y zE1!kS%wfp0{G9S^9?0ZN*PP?GHf-KoUXm0wjjg;DSb5mBIzXh2tCx9-@ArhylaOcu z6JwB;q`1u7YA z%CiLwt{BVenD8sC1GM9@xmnwQoVKYjgt(l74>FGLUCSd6d2q)rSF~-DJ?DoPOCtR3 z(D80@w6?d+z|JzVo>*}UfczfIug5}SQFj%4U%2y)xYk=%$qn0zf@FHOP%XPGUox+t zG;2ZV4=3na(2A-t9OViomMKgHYZ|I-N|r^}Z0GPe>Na%d;#gQG)*n?^@y80fu&k+> z@5oL?%u9x3vMi(WAnb#LZa48~d5F5D&LQui72Qoj$^%?rx*|&~q+*g7;p-;+ilLOD zw!FP5WWsO&Nr*eA(&Er~by^CysNw~zO9|w5w;B06l#?%gx6NF<3@=i^0CV+k5go*r z{c{0;&7xaM1WYYxi5V>7y^#U5O}&y887VVnGlDaKlwzaUKa0g@==*93);k{INjs?F9%JJ@|I7-a#&EL*T*^JgZ4Z&i$%a3_-qgs!pOxCtFM&{5uirkp6vWrYkySqPhg zyJ?gZcfRF^NAm1+c$dWQPq`=|`VKW}qKh^{<(w%}=8faNa;z0@qapw*$mtX8RoCFz zl)7S?vx%!uhd<76gu+U5OmCcPvbe|1+eG%?I%+t)-gxqu-}E_t)T)GTW<$#Xh7GsD zuwbK-!^7$jnS4QE0w9_xDZyLm2#>sr5L zP>Wr)=xJi0lOvML!li!(<0qBbB6%JwQYLGuy9op&xdcZvXh4V;511vE4qBd|cV5d4-Z}wy{YY1~jYHs=cXD1wr4edC~5yi{1 za3BoQ64??g<`Me~sNG#_R#$WqJ7`n31U}HNWy}#N8Js}mLgg`Yj8@Yd=(yuSojYRV z8vI`Z$>_JuZnnFi;WU;{boIF%SN6C608PcaP*wNkZ84{=h#Y3jtLy--W78N!yjybs z4+?eg=VTqUajt#^+pnT8M?Y<{1O*ao(3rIV{2hJvPyDvKF9bQb|1M%XhkaQMcRGh1%`@=3_P4RxNNo4+e16 z4HipS_Q$iTnk8h)n`mlBJ{6sL@-OHV8U71q>dFVX35j$qveqEh(3?V8Z~*~dIABTNDBW{( zE0%PNlSZ_!l`kvV+@g6R;yHQP6tb?KfqbRKve8BuV}k54#B)KP7$L97Ax1RFVTUF? zja75qjpU%c3z+t_764*6;7)rU-ci1AD)Oo&a-unoRd}4J*DIQ=d*pAZ1>}#&Z8zg% znx!|*qUoV&9_^lnsA|NDq&7os*t@jZK{0V^WxM6ahHS3rkeSM`G=q4Yb4Mg{#Gyq) zgD)g{FQO_AQKl!C{qHeh1*_|j`xlWNeetTU*->Pfj1C-nIN9N;5%P{%g^vOrlEe%c ze9ePIwpDz4xlMQ1YjkgyzSPXcDBQL}`e7z%S#uvgMH{}8zlz367}1123mOb96;_Z= zWe-rOa)H(?@e&~QV?9epPEnOy3HEgUe~hOhWgJvsRj;s;*CcVT3*m3NERr>O`dYrj zYm1$W6k7> z>5ZB`E~YjjKF7Jj4ck=kX*G6)Kn}hq_>);d;8ANayqNZVeVNr8s=!x)>%Z2bzKohE*KM^6JX&{wso8}RZwz*uOKiNnS|y?BeN{|>#aO< z9HGQ0YB79XqP~M2ps$TpOG{N0Q0uEJ;f@&m>@$NC8pbGmR!73OuQoZeQ?xJb{aY^WP+#-62<8UE!!(x&f@m6ykB!fG~0fA znV9Vhrw!I@0u@(+l1D4C6n_E!9SI=%Xk=WWi!yZu+nt0*eJQDify$z}Z|2%>F}(Pdkz zgVbvHac$*=8#i66@sepJVoOyNXS=5BLa;GEBOcPXf1IAn2{}=+&YL%t&m0Ultp&;S z{*N9y2?mM}!pb0q$v4RjVbF95=4EF}b~P)M7*%Dbli|G^IBSb3t^wxQCp6L`@`Q3I zf}|u;SFPHmC9$Vv6K|P4g)wr~Juvn_eRj>!(;IA}0RWE&6%+CRN5nI0QlAvAu`%7L z+N_6SaNK}0H*az)8&7o-x#(lz*e$CZ+o@Kk8~Y$xWOa&4intHvde_*>xJE`8tIs8M zh0qpk(OJk6q!^F#3C~UvM3yh`(TJ(b*cbl*5${i@y^SB#s`PBS^t~@j*DhLlw7qjF z_MbT{M>zQ=FJ#1+{S-&5;Nz~<(AE2A2(z-aC`u`3Hf+yzb}rR27hc^!w+eaMienkhzdm~p zs)y*Faju)?@5Pldx=E(3iF__MSo>ww_6b~BZv`Ib#}7mPs3J)H zSy@XIPj>|vX3A(>s#l1qOwtNWp_KG*C5Eh)HRF{;8@o;K_eAhKj$S>LTHaIJl&4Kc zZO5U*+VC-dub0WP01eGI0pDoU)cWh#nYSqr_Gy`Y1?t$Q%wY3z0s`qi9t*#Kb21DG!g}l7&o~>zhngnM;Gq zmD^{VY+lPZ4`)*)F3s*qx5+mj1Ao0Hd4-~WFUG$+>#Mx$j={;mMuDqdpe&o)cwXM!7N$*jd4oI^gvTya-hOB^L@}1tgw3uR~qJi1Y*4Q$?d^TF_GQ zeTm~n?(O;}QC!evx8w+g{{SYj=u(u`G}}j(IY~Xa046+iA{k%BUS>_9w;*=H`tGAdfm zu?I3P;G3{${c{Gdo*Qth*CG)5wy7$jqtW@vqo=D{c3^(XGXtkubnGs{u=~{;-ry-w z#OL+6%XZ2mOo4~AJz_eHS6PkAfylY!6wqM_4MT+EgsCA61leb~$41eCmZQe?((zXp zcQ5h}%i@_gXz6BixAL8ndzA2&jiAvP9M$WT@~*q<^^BQE+&mxd#uUr+8(|&OTj1YR zZsOg)8QbjdBpG}J$MD>q?S}91cwbdXI(tjxMMWQt(b4_rsoBSODaMpog=22A%wC=B z@-o{Zq^x-R5S{IxWg07)*r=%f;*GjN@$MIQM7J-SkPgV|{ZNo+t0e#i`+2=O5>dR35gv=l-naJ+Y7av+I!k<>fa1 z+qaZYQCH6%-pOSbkN6{zyni%JTVD98{{Uwf9CPm`r@Q&5%_6VA^6o_c0FN3M{!u-g z?o{sGq?XMuk^H~Yu7^BbIRsE66lxnJ_7ua!RJEW&*`TX(gw@q%4(upW*U27Xd^Ht*bz)s9%+Z-D?gVD)=4*;(9<-GwmyQu;%(a6~$yl}!^x9zZ zR8&gA%x+rjTQrC%8$~v|{Eo1mzAhWLcHMT4&_&BvNUGV>X9?)DcP-b`WR^j6RM%wI zMP&BbHBwVuv{OyC(|vQUbB|7kOkWfI{ORrmekFbBw|{P+sD&1TF0h#&BBg>MxNL)s zW=v$+P@%OvO%xlN?1ls|0g>wB*D>T*S0&CHS8Deheb6Plm2p7UsEFlWdyO4?TIYS$ zTsMjCRaJYR!g0^njD4~H03Z}dArRiBt!qBa04Ys$)?`J-^tI6ChBBH-u_r8sgfU+j zOnXGMO`G?1GICeWHgNJuBs%TJTqb&HA8j+objVoruPO_usToquaIq~T;vg3E1&aC% zV!G_w%9%`Ia}o;fT|z_HDy3Y(yM`?olzgDING>d40lj=Y_`>M@zJ000)LP_seB_g} z_55jiT2fi_&Dtp8$?0t@`o5D$ciU!2V@w<@Lqv4h(rD_MZ=UMlYgm5Dhl;tXn(rG! zqpB@%lXUWxcW|>Lp4+0crBquvwjp+c*j3(+5^UodtExJp#jed!Y~y#X9$KCs@u2Ch z;y4+X;d`rwSiAmmf~ug&X+z4YCx*ri{5x43B69BRc0U}VH^1M6RE@p28n1#(84Ub# z@`Q{1Sm8N|{J1N>pBgMsKWU4?J%a6+@%_Udk3N3w!!#0}RTsn&qITU&%}Uiky#X&HGQuQm%9OQY&h^GCly zqs5+Uh(T#v33GK2Y{A>@(sU68e%yw{ai&=3M;lX0!}d8TvTl^#r|vs463Zostc!aN zn2TBtv<9lGY$S4zBXg>OB}+0xDJDeDtWjoBT@+cc5c(;rbfW}^*yg(%B405+-m48R zijMFBITb@Kj3P-L7vEMJm|SGOU$`|Kw!&(=Nt1aFte4w%=&b!cFzHDJOYuSIg5*MS z?>bb-Cu%w!)(g0>N(FoS8ikUWp{rNlhdIHXRt=v~9K=Ob)e{WJ#;g9%@*;hlIWZiI z$L!xaKm6F7{xoYnpq~uf{{ZC@1uyJA29#3r9@?pcHhX1(tmUyoTlB(Pa-(&NoB&;nSt>bd!3Al_IgA3^Bm{&Q@ z2F9eZ2^4m*u^hBZZM5AIa{<#j-4ZG9qPbPP36@j(Y_@UrOs2la=WFL}ako!$&NcVr z;?BEUdu@$<{{YT)*EQa_+c?L+IM+LCjOX~y*#5Y%XNHdgQNI?m>0S3Oy-8e`jLx_x zS1ia3X$XyD6^a{@MI|NEV$_49Qh7XqVvvw}w2?Z08e-IH#Mo~WUCQVaZyP%B>qn#A z)9HJ2cKVBSDl=@SB7t4{N}`&VA(1F~6?0b7!M>*>5`-8(O8C@vb4sH0__%mrPumBO_x%MS=RS!5{KOpD5r@VW0P(P?j3QbSE;S7@gdB9h zq5-6X+r1<=4i_9ji}0n)f-XFWQsh`Us`T&J+<=gj6L(qn)WRbRRc!1UKRf4OwltxG ze~WL0B69fs#a42 zL>4PN;S0p!v$0cykZe{j2=rDtSC_dJslvH!894Ti{3lnfm^(e+HOusfuB&$XLE!g2 zn&mYSPgOVB`zqSBmOK=O+U<-a=$Z`MI8z%Wf~qT~`fCbpA8Y>r!?Xh=u@gPg7ls&+ zL6cpo>NRmVGTCiDB?}Gx(^>?z4nmCM#@+eW3>ejyI%f6Q)tTgu3fXcaP<8S(;<^_M(OTQ&Ch@W@&Hemt&KWLu z>tAJ0#Y`2(i%J=ERm`5QHeY6zUBrO4>Ls#SPYn0ko`_WL@ZU}I^YzITB8%S-J>L{Jgv51MS3wD^rJUx z9Ot86=AhlM4&Pjpg*1o&Z$9XyF$jsua;Vk=v3p8TjIn{ z=KU+$mZHi#BN~FR(p5fb7(;gK+LwP1ts>1fh}U4SgI2C&F5^+S=rsNXOBz#M_jL1P zd*h$kXzEM8P7-z9J1NZ6i$&FP`T#h(-WmeHD&KDbj##^hlF|Nv>1}ZdAq?FFO z>x}N3O?J*T#xtLGG3mnf1-HlJExY-w3px#(!l|~Rx^M{*xiyUE<(aTV^eYJ}u#Ssc zK}#f}dl9JmtFyv;Tv(P3I*7nh*6sWV?qO^s5_8(U-WE5(8C5t`N1{39A|j&y0KPPG zdpVqPipulfI#OJzR7L3yd#+=(bzu){hT)?z*6QO;*WX06cwo^-t2;F%k`>t<92}HL z$d!3XMYOIth3Dfc&4Gp+6ICCHy=YiTY-`x_b3%Hv9#I&`8I zOu2GQ_muJ^6oxSz=aKGnl~sJkFl=bX#*A!gHH>3XtYcc7HLYt=8rGsBA|fIpC$c7& zrRp)e?Cx1((!)6E)!zqCr;M^tfa+ThoGUb4no#043X5#fCcCz8?u3eeCcrFX@)nx@B@((NB4q2I{e91SvkIYvV{{OBkM5 zavlRTCVGkJ+%Tgre~gSLjE)gI9@qHF&K2W-YOiS;jzx1F!<))-iOOku7oKY>)NUUX zo!t^$xc4cEpd~hz6DIhe=Gv`BF2d|=;Wq*F-P(j2Ij@p-A&R)bvV~A&Fy`rmctd?k zAEvJLguJq;8pMxl!$YOiV9w_U|$Yy*Y6#>k`R?e=38(N*nt7QJc<+iXRiABmAhSqwViSMQ=L% zW(6d~l^s*L4;bNA@y{qxIf|TqdG6zn&bY%@^0_ovp$tY}ziYAaJA^XLlcx>RG9yA_ z&uTP^C@7wf*(6{Xg8DUcs;H(!TL=0ltENqsNSNGSPkWZ(thT7^+ZFlvF&weZ;1-KR z2e#U#b`S-1#pvSx3Q~WpT{aP2uH#T)+jxpow9_8j+O1WuP zjI)DI%Dd&K)MPfjK!n~K46e1B3y;>2I5aiw~=Fy=*4KiN^w%g-Ivw>+sjmqFom9BGF+}=W>AQ0c?4;@VKap zDz_S1fT|&~2|=n5tu0+LixWag5)yp@ ztGG6(%G33Z%BQ7?9?YOV4znjX|}!R)V)j7-a0zAMbLD` z;wp%fa(XuKTI#^kd0><$T39da%LKy1h_Pp}al{)3cE!Yj^X;@CeDLHQ>%ZJqUsFbb zJ@LeSzd{y=MlGs(!-|=(Qzf)WBgp%=ZlolDk^4-D6DH#{eTAj*g1R?jDv0lmWpQ^- zh3I@h+>#mfUk?ulMMr#(b72E6zKx@Y)?g%7`gcx#GO~(d#*Bh%^4Xx_U4FRPaqP0= zboFY}4nC>!?U^Fs%0=$&Emp;8YSWw|J!whU_95HC%)8s#F$sLUWGWL8X<3Mhi6bGI zN+PlM;AZzDxU~3?MPE=^vmoTI` zUWn!@u@Gvm;q6}Dey07?p~tEF!en*P{gfwlHqOo(MToXb{{XR#S*&@p)iL~k!rL#H zYoC69oN14CKaBc-UZp=BpVU3d)PDQt8cHL^!fCE^zsvfBG;bkIOye2&Fs2g(x+!d@ z@sFt8Ju~sCjdtYYb74Be1Ia+tRdC=oZ?bZz+SwqndZ+EfBD=X$-?O3Uw5qehpd>3Uv`8eW&B>3TG2dR~{Pq1HXe$~-#?{PU=;$Zb?D$}SZK z9HYfZ&86G6+eFrF3xNbgl*U-HDyoWZ_5>B{VY;oOg02{}#_lxSg?A&-al~yw;TcFT z2Qi{reVNf_$+E#H7dppqqy5uf$vG>2To!g3Y*fO$Fx%#6NW67>FjY4yw32tBZ+55UxS2`mgmDKP8kmnoE$xSTDnc^%}(HY*t<;%b4iijD|-nUxoxFspBjR!h^XlmHPX+0A&U63 z4M1VNgl_M%u#Jz4O1@JsEp+EIfQ#_H20b||)A0AT1fI@n6&KpPKF^cev#`ADY0|em zjzcZAA+bJ&y{&_0eub0Gou7xhqJG@Cr)x0CU|>mGpri5loMe6*#Rd)v1V}+~P|ILn zNe@)-_Evz*ZwbeeuOXHmiYp@MTh#zrc$0;))k%qx@|(Wi(Oq`obeS_q=c>8xbKZeRxy#C`k$`LYRaIW@ zSKU`j)(%l72@8!#rZQK#9UO^*EhdlJHXweX611RJBM=Z=2Rw!wR5<4(vQ~iCea}T6J+I+#gI8C za3nrZYW0(q_I;{IJZh%;uABhGXp59(c*ZgY6o$yM8d@N*3W#@>-pVz2*cEWfh*v&B zAk{=vN2)4~>GZ0k#Xtn6UN{JdRp;%%+OPNbPj7F3boTtFxbVfBk#u-^0&}*cH`#xt z&Yu4OiyLUwefRf!zn}hVRXzUz^n3mP0LQ79XHnfxQ%5V(mW5|NuL9W2mn&SonnR7m zb(e!}qZ}(CiT(6=<&MVG$u{kq$i(R68|PSh#hdlrk0OI+FPw}=QpX zC9#zp9gVgqC9B7XG&dJG^-LOL8>?-Ks=$HHO;L>hSEpl4e}&5s`- zZrIwaw{RQkhokpq8!;+7S3_U0cH+rB9e4BWn@*dS!n#(YIPbo?vbwQqILk9qVTI_( z^ScIdV>^*uu1Llhh^&>S5wXS9DP@5Yj)-N<^wbk)&;TkHH|n!38YYzLZyy~~Fz6~y z?+;8p=783~$OQiY6UpPVaA|pY^y)(7bK%XEE-}V~oFM8#?c$k!e9+`vrl#Z+TSG>G zmq>W_x;e^QM?=XXJFbSb72ekuL)b|`Z3USOq%K2@6(9l;ZcS}K z6@_phanyv(^$~7GCUY)-d5XTze2uxVm`P$FdLzc99&ALc2Q2lE;!`y~dPZ@lo-UTbsmbVt<)OKKY|idE1M+_+oAZ z{2CH>q(Dac8>M2s7kHkPR+U_g?MJP954b|YE6SJvi>fX<@6HSKO{%Q2#F@!C^Y4QL zSDS~d;%?BBHz;V(*Ry1Ieu0dBV|$%kI3uUi*q%hkk?IV2)5!fHkJWI z0fcW6WeJ}LFDjZ7-Gn+e#gW6V&u<&veNf?aQobyqC3Pi-@`iY@N$;`rZAP+c8ON$*P?Xe<|< zsyX};&84fVZ71*=W0`AyX_K3X&f+~Oe(r?Aa2gOy{CSDG`*3g)XGh?hn-!P`OeA2J zt(B#n+@=Z8l8Xvq0w%?yq|90HD9vpv$sHDU*Jh3)(K|xk4uTCuHxiy7Au(Gd9p&Qx z0B;|sz>lw&v4mrWMz!pFT*tBPM=|VjD_qw#$f)MI6;aJpR8#=ZKrp{mMNSu4Yn;b5 z>~giuRC8R_R=KWf@{Ve!3W|z|sHmu@smg1iMQ1!6YF5?Vq8Yv#hO*$0`v^O2RdBwj z37bbo$%ZAW&ln1JuDUTOhBZ;xv97n*7}>z~OJ{b;JJ6XTu<3+iRE{^>X!+5FR7pH* zo!mBN?Im6`$Hn}6o;1~Vn`ar3mX*oomu_)Os znV>`MTD#I+6d>qxY$dWx_XQ_F3nE)KPg2ffWYkr|(yfa)*u80{+wZQLW8&X^wbNXC z@sEDnXBgKW{O1_Qzd6P|`OYzq^Ug8rmW|rMe?o2seYev@<&Tf#HuFj+V7^=&T+Q8 zV>r%npI>i6&&FSpo<{BnTl)5hyTMpC6+RiBvUtAW=8)+QknNsiXbC9Lb_8lHP~av> z>@)$Qz}u5#7{a3vHn`QKK#_h^JywQ$g5^7HmRw=El?CQAeNQ9DBC8^)pGj!g=4(#^ zS!=AL#+D$@gg{M-@&&ClbBwH2W07@UGsO}-`O%=*Lbiz(WT?^XtEESq3TS|5iIQVU zib8;+Gg^;DVIAAu+U^{Rh`ldM()5qlmrK(0y)Q|%=2{&FE4ts1OKsIufDJ;<$MsQ7b{QM{@y zzavNlW_QRqK+k`y#9*22$3Iit)b`~$_xz@a7p3Z}oeS-1@FjEi5wtC5c=UnB*&+7w zzi1mz*5vTp@6DKJ@)?@XtR`6`_;@#1`Sl*(M;nm+#ea%ICFDbKt#|q0^O11QscPDr za?Rl15v}xl10GCIaVgl$(rmRLIwsI;F&SPG)7)%YF8YLwNwvClp$Z_R|EJxW|@@Lv8kCORN@`8IQ zw=toy)pTsM`=mD%p3MG6+BJDLlGjzuY;`=oF5enLV)JmQv{U0n+S`_TgA?_~{>?9l zemzuaq1@t^L{7!IUBh|*09KT3a=p$KXQM<@+P_#x8bvi&Df7sVCf2HMZIMS9dWlRb zJU`oPw`e{JSVd7|S#N&n+-1cX1Y%o~GqvShfxa}W?lb}ziE^%<`5+ zF9BW8erWAhFG~~YeVUg)#2Cd8Cy2dboa8G4_orhrQ3XC3uC~@ z(yVLYdW?d|Bkfi)-zTwr+=}5E=A+t{c9M~=S3RiLIaN^e?QCq{-#THWaH_3i-N2Zu z_fdt2(GQ=p_A$KvbbNWYl{v?cYhm>lFw*t?BVG^rDt+uqSL?=)-zuFJduUch1p|ID9~0bI2HEU-83s8}-e5&Ua23^C#xVoxEVw=8C%K z?tZyHO-Yvm(d)mt*!d`nzO1s;VAWPC7Hv?%VnyO%b$X(0#cv0m91d-eS+nPva`HTa zs}sksvYY8mgGY^1T4Wb9UW%-TRZRr;HZq=x?yZjwF^c3*bw)0{VG#~d08Q>Gh`|l1 z_i+g1V;Y~;%8CB~9aw+BPc~w`ia#-g8-1-$PtE1*MLx=V1^9{5NiOzL$GEjKyggljut3b_UW`9 zG-&pTqS1>6EfGblHf>T#yJqPof+^^pn(L2$t`_%0JhI&#$j>(-TCh zNW{Ihm12;_zh;6HBr+s+H4%dVMlrRlYf&VeD9nWtEJ6POf;4dftNWms)^{)ScOA;V zbyZbgeJ@r0_L}bcXRcyQ~ zvS63QojBSj2v7F;w{{WAFzc|Ky*v>Jx zxV%_iE$#R<9P-Q|YSqT1u=v$&HnwPhM`o&Bw7e$RPB3h1QHRJTbYr;BND){ zO9FE+tY#Ke1{Z5aVG`481Yr?5x$ifHsHWxYIgOPZxKKLzXn07~Sr?hYWy4S%ANvC- zT-3$u1~CBDi9O}JF6<3iKXS4!S#GLtG4flDGBz)fzDk?d_MC~LmqQD`sVk~Fox4LX z41w=c)-jkA{-Cq0X}wT$OG9aun08+>=i1q5JPKtWs|Ubi2d7b41&d{tMn6B-vRyD5 z82u09?(64?MHdn)D1%uAZ))H@lMB|2o(a0esDV2s#HiJXBPOpDITe%522ZN_P$!J* zvy)&h+$O;=vNFzGuGOyF*giPy&S|XkV7j=e+i-kxd-{&lWl+nd5?7S9uBZpHAy6v; zSsW@J9IL4t2>kUpoYJT*XH?SXwSAJ-F=3Y4D(iJ;75&$|QraiTavCDgzNL1TCqswC zW5R9TVy&Q1DXOV-{o~R3keodT?F%+Z97%@vDp7C6N5>7QZ}FANeq|iVp0Ol+H zQ)1VG#52^a=v9>XO}|Y^&h0rvw#A(13o&Vt?Gr^! z8RI+ptxqjb3rRuZ+;YSo8x@$_!mpl$i^C?C8#HbUKumgC5MtwB9xotsS;FJ4EQ?5_ zN+B85#zSvo9vbx7u|uM*BgTzuGXDS=FwOhUx8;iF+b(*xu;QHQJRfk&tMubWSU~Q+ zcC30BV*W=%&fT2iOGFEVwiTp(Xl<+P%^~_x}JP)3r@~S6_VIddf9X z*i!38xLkF(4lo*X9h_9n>tm88i|q|IbP<5hA@@y8ZJAVZGj_&|$|E~7dfqP5Ng|c^ z2bLIVTdI)1sorr1R@pTBDtr#BmYhqhIK;{mN!3ugY9R@fMv34y%7rHaf{F<-RCz`u zWKA9y#*YUs{{UAAG94A<0-?P^#_I9B+8Vv2VXSQ;QF_!hgE~6xy^(FB7G))}4NYYZ ziX1XsAl3bE8DcRshKt00zK!8@SCFWFL~PNmtHBRQsZ@1I8dSpg)+Ny3b`Xcma^WP0 z*f6%l&&1yRy>wl=x)KzmqBpu5B-v%i7z(}!q>BYv5V^*i?WX$grhXmt+gbZ|HBzJkr}l0{#sQrW29SSNPVp8e))bA-H!27puc!kqf-#MO!ZalS~{#H*pv zSVe&O_z;g|c7qMldlqLJ%d++5ha@wg!8t@(!-n*42PuLJT4Foe9@J+8w*v?2UmQ|i z!is`zx=trh+Vqv5I3NsWil~FKc9$s{oWe%xkhHbKlT*s+#F5?*MdXdr$5bf0-G9nQ z2BF)uUWr55k4pQi)OdQOB>1MGnV3!fS>jb+7<%JX2ByYX!GmRkJtM0m#Y@D;)o~yL z8hI?)^zQar{MQq?;>|o;b}5GO&WoWzX%*)qa+KS}4%rozcsU?WsYBtPAU#Fx`dD~% zHED$O{DNU!v3B6lZDbX+zF97jn>rnf>yK%wLA_WeC7x%vOenbSPV1Nj zS9=JtvN$j@Fu1bhtSRu~CFrdLDDCv3F(Y>O;1?S~oJ>k*N{NeKl-79hgsxbu8{F5+ zs3Tm@0-mVPK#Xoe5o@0W#Cp8XYh#1oYOQz-NeM0W;`gU29J(=(t=v77ZoEmu)!RCH zHG&e^m0er>;}2PnB<~`03zf8NJ=260c#;k_MaKUCOUK5?{{Vtr#s2`SS{MHSiGNp; z^u1_peiI!P*p&$n2+HVKk1M@BR^a7E610lW!K<$)+@G)=u0bjt2;?LD*htH06Ij+M2DV=+ zN0}ErC{^lkr}u*FV)59JmE@%)`k?D(Oq!Qih-L!?Zdu31CG9iyUGy!V_EYe;(icnF z3`NlZCZbZS9>W);iGmA`KD#j0$21#SWf6%=&~i1?_Hsze;|MTa7pTL@Dx+pMgy?9& z`LzRh!y;@pD=C)K_FQH(Ge>NgZbm?Q7HC*zIzA-nh~`_nB4KyA|k{q?|RweJ4+QT;hd$gCh4Bhn7hb(a83YQgXDqkTS8zio&psBRZzCJ zLs=fze-I2(SY_&b^W$QDE@K!TUpWX0#hJP3sHvpOh;-f32{|=d*fG+NR27kgpX7Wc zSHzr-5rJ#$b@8iN`d*i>E9cBbvUo_F#-oQ0YT@w*@0=vjlNK!z-NUz!9WT0QHfq%b z)LFxYl+h$o({&WzT{X_wG-|qD)ptUzLwIVuf{$|9k=}}&)tvMh(}f!4O%agD_OK=m znlXA9yHZX{*A=YLHdEO^?uE7@;zTUVa^V;@c3?OkpVZ|(BIK@acFhJxDNc8&tGP~% z+p%k_xvX*y6_n(%t;QFS4N2L_hK9_))N$iXKDGOdvn)lnQ+Bl$J**EZG`B=s{AtQO ze>R5nX(|HDcXe50Aqus#>mv5E)c^pg>cGUJl1HIuk^#*zHqywCvk*t^yUOD6VRBaT zKhNVsEOXEF=akPW^%P3P@+oGmz1cRUM!fz~{H*NO4bYUA7tdWXPJ)jWGv0l<4^&gg z7Z=yUQ}-RLYpG-Wtr?zob*Arj)paRSgjxH@T=qtR>GQ~jBX69GtiQ5b$Z5)LzMS#^ z{w%ak76ljdJs}^_RmuZIw8W74wTqGEj5={5aN@X&kAcL75`3%8O){RyaQ)K7MX9oH zN3^?kSCPeeTiNaLw_U|x%zrnpj$3t2S}7>dkIg2^2vUZu4lLhfbu3is(p2utB+Q>A z-xVVIw40ldJTak{8d^JtIXV@?yrW6-><{F*7ga-9i!gB}`!?QPHX^FV=d(vBsk*A7 zw6bDI(cM}ovf7Po^|+?{;hPrvX;VKJKB0lOI|ePGrk2*`JJ@H|$za~qJ!S<>CM3L) zYucUuHM*1NF`zKxMoWV__>qX&6{@XVV3kWU(Pxpr-RqOR6;s*rh}SeNPv6Gg^D3h9 z*EC3}!}TluFH6$(%Wj^HV-1=W8w_4Tb@Bqj0T}$rf=;&1t%r*i)mOwHINMxpwx~2{ zg3GJ6y6v1Mp4rB^&++Syw~&6aJKUX{fAsN$)ok9*p1xin9Yx)`*adb47V(PWKb0N) zJMQRQC^$C6);})H2AGe2mJP)@ z*{<6mWgU9i1(jWkYawhSQwvDD6qrfl49GG?p z^Ns8}Vf2%H`4ylQewV|)$s@#oB~(H+_>oA@;7VhN({2r-8cb@TKbFN$Q6&*mk2zbw zSih8Tg06c06(lF+zQodP!*IB#Bl8{m7_v=EaQ$FoA^c|<7?SnM-Jxss-nQ0 z+D>WI;gi-q1lg?i)jH#>1g<{qGDAghAK5<*d;nAohr(f+r5RF ziO0uPZ-ZHKdcGWd6>4dOl#H2k6@13?tD=q`9~6@d3q)s9!CJ*eg#;=_%FH~B&@v8C ziSyu|P)HlNOoo3sicYn+ri301uq^n^KNXrt+y!zwL8O{J8%RSk$O_8n%xd^0x=N|_ znZ}=MH_Mu{nvCRCr6CTCTULA?w5@MTuyMJg!*-l9sUs6BA7z+{V^3mnGE#!|y;mo* z3xj2q<&!7_6QWXywLdHnZtt|*Y4hm66`c89#qAK#t6IT*X7ZK27^k{-64+oykXKKP z7dZ%(%IYVe>}gV#2MNW@t|Qc-9WpT^F{X5zPvD%kW*kmT|=$93l?$?I{IZI*W1 zh)-&Wy_Py`dJ3Ix;F>a$C-rTscy5x(y5d-(#B)qFP=t-aGCJoZ-;ItqdAw*u&$T?f z3AIElfsS*Cg)ePdu#0D{B;e(9)J$|ic!Q^eUkyW7<;aSv#?NFDDU{}WtUzMw5}uh5 zR2E~A=!iI;j2DnD_N6+;zpdlZ9&Bw=O6!0zI7pdWdp=n(%Ju9_WQns_yMU({w_}%X zh?!tbc^0buexGO6mWcd^?l#oXqVL&!L&vcueJurpQV7DGaQ)8+u1e1%wjAu7&!OT? zC5Gw=``P*vg3~(w4>^!aqwS~hBb9UlFB5?=Dq;g6uk6(o1u>6COH+e6#=`B+0;p<; zWR8}sI<2=a3OL=|#al929A5OaF%K$fSs{SY@du8C6j7ha`+Pnh9U|aTE8;RUcdveA z8?ro__NAB-x)o%b3n>)=lE9`T+nT4Tw|dN_6_j--d<^B-s+Hf*=;zUxhOS~Gx9DXp zEin@@CyTA3pe6F5=5Y77lYXYXo4^IllB`8NuHHx8!Hw2j)6G;&+Bc+Ku_n>cA*>2A zpspv4BH_O7?cn%oa!f=Um`q{)LH+O$G9yhIBo8O=%jc z>#!VeK7|pBG@Zboe$u*7yGq38;hH2`H93(7imwWvwOC0=m0?m44qItXyCUJPZW-j- zXay=dNGX$Nw^^dBJ0`gomq?soU?XT&$NiC1o-2PGpk!5G6CL*nsv z4Am4Ua!_i+@`Kpc9fJe0Y-KCCQJ5hq(fIgNT;I*ED`ygGK|M6Frc#PyYJ(K)`gu7~ zxX03a27LpQP7Icg!_B>rs$vrkd<(;1`BLl)67Q|ACuLy~+ls9=oQ5ekttc*hrsNA0 zPwZ(Tq{+oWRKaw!n6_yi9w~@>IVp%?;MFt%fcC*<5^?4YXN6y~D*ph(T0fB*78Mm? zOyAXoa2BMC+#w|_BsH{5#O^~4kINu(hX9u|5+!BI=X2H}BqCgd`ndeAZ7_N$^H<~< zNmLIa85)Ydz7)HCzd2qG=<5WUO)5L|h@tIBzIBGop;oF}ram-zcMe?W#fUjOHI$1< z)!7YH%gtfMT@qp@Izcya*5mDNg1k-?b!ZBJpUEn#dSGZ>D5@+(IuTuNQFG~rscX=c znA-l8BF;kiw2*wZp^L&M1Eg{}e1Vzt49z9)P7W9BT#j_BM@XoOspmfJRo6Ogoim){ zUA@`HJ^0VNIQQfD#@O3m>x}w9?tb0SZpI0b;29v*)tL+jNA&}1T_U<@p2;<~u{)I$ z)mBdwd7*2S23HHhWOxcjIa7sY*0>%~PnA=8VSK zzY@(~l>H}aZHJ=x%-Jbb$UA9*+U-@G8}O3 zjVJ_3Kd19NVq@AttD4sV)2QwOAxVz(b7VlD9g4n|+Og~fuO2oH)Hd3M&;IdKa3+`~ zVu$9q<=l2FUX6005;dK92=JAviYakQ7U@11xTRpRt8}Bu&P)l)d_EE&fy7@8e$RRbYi|YRXBmO>a zytPWD?jB39pFB+^QVka9N~ElXJT33}X}pc0D#_vQHuq=!Rru!4asFvz`1Q}%$tQ}V zmYmJMTXCs8R-G#wE|lc(kS1S47n{oCo8q5xwU#@B4D0)cm?&04u^`>#g%{$wbt6##?W$v8 zpJ*l~KFp1WMmM&ubSGq|2}~i``K!^`t^UEc>lO{>F)VQ|qX0jtaVb&;`;2;H`D55t zqeinUOjfyT?SLl7m=$UfktVWC7}bfn`%{zcW#KDsqrCN|DLzen0X?<$(b|aQaNMfo zk1%X=Qd|PPOB{&hV~-iZl5T<0>u#y9!KWvlS+(>gHn8Gm5s=HKamd&;Lo}+i6Yii zFYf8ar6V2vk>*>!ekJfCjb1i5EeF9=Su#T~L+-&1upB@v?`K28#n2hfuK0$vjD6(< z9I}wqGCAF7<+h4$le8WP{2E#)9#UC6YmIeKW40@{S8aYq`faXpTR&%8!M7K5j8CgN zKEplHqb#m}?+fRcYLc~;=_aUNMqn@)Bn4y&qav)3i%m9`$r#!Pf=XYfCqPT^t(mZ9 z403KwWV7R@%UuIKFG1xm3B1gf`g{5x$ug*GzT584eMF{A;c; zjgnbC^N-_gw#G5;$T;wI* zSi{#nn#;|p$=MRZ)#B9F&P1ic$GoXkncsT`*ZOWHqC91DUm8#iCju z(UT_WHE7iY@$S(U?U2zlQA8?MFV;J>Qp9O-UQ%^KL(Y?7I68WCdj*+fv zG0#4aVMA3WjN71x+iCqGTH&Mj&Sy1%vVU>T`#e<#nIz*~cRjv$G-#kYg?Y@ru<--; zUkP~li4e;oI%~<;A|1+-@lfMX5bgQ)E81y+<<~-O>rY(?Tt`PV%fvjt`KO=q6<29?b6A&Y}K=L_EmQd7guc(K?D-hbyitj z^-VPO+kN)eU2~ioHA`+t=UPg}_OiNUttU}cRh2X2yOpAm;KHkTyg&>;_Pmm_ub{{_ z2Mmq3i8Y8648qQ`W2?Pl5>~`|wUVuzxfbqD1MKz(^8Wzj>ay`s#*@B+l{pe#)G*cj zE1WAA=(&ESmvfUE=XmQvLzt5a_eca&Z8I&iOeSPD%whuG3=BnSsn~!sb)xWN+B7dW z&^~57+jSJ7j7k&J-RCM59?`pE8%-uJKxrno)F0&O)Unhl}_ zolVI`Vl}y0dNr}=KOiBz*?A|8T%JJfX1wXguty7N6sg!qjG1Q)t4ES%>uTa9wpMSI zEc}?LwFSmSIz5!gw6(JAj|!T)RlU5%L<`I5 zD(c{jvg|A+Ij9J5z{e6qf(&iryOjOo$0U-lBkV^J?P55OXvW@E6S?GiqguxE%bct9 zx7~DdJ)2$kn>I<1?He{t1a(UZYX1QG;?Jus^UZpO-%QhRvuIMOBsy#8=&?CY zD!UHGY07BzSn9C6D`rPW8eP#^IP95!GS+({#B8LD83##O9;UWDNbYG9wF*R$|7s!jFoRaL{W696fg31sq-W*G+cceYMj~HO|}bzHzR)>#j4Mw%csy z7}s5KjORJdag1Xb&U5bd&ZpWy^De`#=UZ`KFV1jZm@3n#KD1`>hnoIBecFN)&dr&O zDA#=NB&usSe-@!v(M=;$N$O4}Oq)mpMQa&)yMB;*T+2GG-O8*Mm~xdvW6>emh?NUoLZ>B&=;^eVnMj7uN!$t|Y=Ic^tkNqW=KnNOM(M z)nCIHh@>mO3{Dg4a~j}t-?Xy68ZNA7>je{W!xb0fs(5?^tTU6^86tp*K?Ud^(RcpV5ckR z5~S(YwW|mv(~6op(mMtTVGlP53vC&(DgFB>|2OX7R>_iLo3K3Mo;@7r?S)Z27tswQ$uJSde(6M6Cav9v166#n~9`Fy~`o3qf^JbOd~a+>G?YTIV5Bd$XMG4 z@yPpud*#FA$E$8?gk@JVk5P$1hwY8RHihxOA5~?eTPSpVmG|K@3a*5OOyGK&E-k9E zJz-K18p>GZ;8`9D11MB$)egTc6#k#%PC^DFsGtzqF*r;93cjON0ecEPVowRpI!-Gd zoXBYV2;G3za$09zz^4+~lvNud9$Kq_Mj{p1^F`~q1PanqU{hcs-^%3&NmXa*Ba{tr zUbBVRoc&)|&Ld}L&#XgDj7nZe)^n25*J5LB3cf{)VeA!h5+x1{>*%ocv}9|CPVla+ zKJtUt_5fPmHDJg9Nrl(4D*jqAnW}o3bNQT>t&E`xug+boZ(aRQNWY825?&lRUWr!I z*AWu(XuA5W0$nN`XG81%03n?N)o{41CgK@SU~EG{3C5kmIdhk(nHaIb2bUZoHri)Q z(^d4(Fm_Fj5LGJuQ|iHYCHC`5$5b#783Cq^S~I>Yf0-2MXyf``If{;_>6%h2z>1+w z$HKIff|NU%G>pS5$uKCmvzEki-=o4rYzd1^Oh~^oE;1KCD(N{al^3#Dr_kK(6g%7g zHRach6BDK=+a+E^3v&*S)odurfIAtjs`d4l4-BKhBXQ{CkHnRd5iG9T;!5ELgruim z5-;~c?g9Oe4DuCP!sz+kp&#gITp474BIlWm+$E10i92ua1bsu)ypa1=0UkHkI>t>d zRtWV}w9Y^fRmL;0)w87Cwr6e;%wivUN^Z4Uy+wr9G#1Y>H{FZI+h|Z=`EY^qw;xR9 zzEjxP)^Y|(D3)=O>`{6AJxd0SKNhjxitC<&B<3$OCYR~SEC;}l0^Q6_Q8+*l^LqIm%lNSotkQ@%pdHtb+>5uUyYF5 zxY_kR^A&Jj4rSwM0f!XN8|0L856Z zq9uy1(MKa+WzN-nJGhVE?qKztj?K%bK;yd$ios+OCh9zV=@%JS41)+)e7JPJ68QGj zvs|pEb=!TuKX!BJny(;TF6#L}3>G?<29O~Lh-`4k%YtlA9Xn2;+6&%Ye zLCzJpmi4LVY zZN@r;#ZoMl1wL;~1G^K-kV@x8Bl#n$ZJIPv%c7c8PcYFQcm{W_f{IU38e}xtz2m#u zYsENFmKfEE1l#I0dLkW-`VxEvdZQ!SMq;mx>a7%^r=^sr-wC7iv-CZ?o5uwQ+`=0@ znc?gb+5|OGkvoDe-3JEPvchjRXbM&h%FS3!;w?!#)TV)*I}mM>$vJNsmPQdYewM_(#&t0d za`lU{G1!AkVELmzGCcM_%+$oT(Mh}2$89Ef=Mf01YGZxUMdpNEu_L}4ZMN*!`DT)i z@3TigF-Hx)SNG}*3+dab{10(uzZa>u!}44{N~5y2SVOCzNap3OIIpaV3QH=RDCq9! zvR+V30+J-?env=&%NUoqJ5#wh{dG@b=K;%F(Zj?mDeSNESyG0rS4S2*O&VqB?4cD~ z!C^*HfSrepAz0v@*=}d=Z!o^kVFB@D!#3&;4Q(Vvs!-`EbJ7~DKxNNr`Qu97Zrf5) z9$uEplrB$kq7H?31T!ljc0h~<#(FS={{Tkg*-h;pN|4yTOv8?zL8&9%GuuTi&V8Wl zdk8B#DgALXAtPQZ7zL^08Ou^_;Z|Yi`E1YEcL{3(^n0)-3JIPv-ayEwo_5FoiBiqN0@-`#67?wR`@%lKMnS*6mlLlj}IE4!(x~GFhFsyM#&p|qD8+iUu z1c8`WILq2U()G;mnR^e~Iq}&M4JD-IsCh!<{S#uSgphY7>#)p#?j3f*QE1#zAYEZt z$A(6m4lE%Z^&M1)#s%t%MA7@O*9HcI_Rslfp?478$>clpm?i_ht(hg_Bt+bR2NPh$ zS2;#NIKbMLdmU7o$m-3I_K^8XjGsSbi8rYX(dov|&Lg;3E_wr>49ju+l_EJuJQWs^ zCC#imUD!yr)$Av)shB7K01WW#qeYYgax(>wMQ)=M+UfPCbbAPLVHvB!jz|a>`=U8G zLN&aiwNX)1lvGqyUKdi&$N`Z6y{BIWuZ@)&9g@_CVh2UpN6m+}RJ4YdyMO=|n(oy_ zB?z24OlVN-bl)yXmLSAyB$s&Y1XQ$ETqBR;Dr=1Y0Mnmb<-E~(xa4cQxqMdjZxXvh zqhy?+Cg>B#+s5J3SVXo`#zp$Qopl#~nZytzwYyXd0I=Vi`dX74O_Wv z&*eQ?vzLl)EpjRL@^?3`R*7`wcU0p2t!C4|bzGNM3NG2`*JVSwViq8QQSDBA9g*i}8h zoNuqY{y%;({{XlD0Qd3gtbWWl<+F_WiRhU}JHHL}tvk#n%kU zlOn2!_lRthWkj_}YXUonVGW0s3_^Rz#W>wlkmlGmQTL?DPoy^m$h2 zE(PM-`nxx4M;rB3+ecY28X~6_;i=ieayisUv{*%kdOVnUD^M`#qk?sWaN8FbR?8elR|mTWr!N0zuCKFo+ddf@}M!UmKEdK!VH=)Zm9F`4f98rcB+1rM=(e_E<1UAm9 zZsk2w+ih&v8`>qAI)noO2jDr3cDg`AR*^{LdCW#N92Fl;ENC^LAPj~KU@$(7u%5!( zu^!cr(B-KZ4CP#6DZ1vtynf?hs6P+t9*}cZdp!}-&@MW^w9Qi@0haPKL z7*2sSr?ZUkneTLSBZjQAKZRCr+W7F~th~`pbzhw+7Y)8VXCD()t0=my_SfJ2blDx# z&Yth%;rC}B$Gg!_lS*UI(C&}P%d!d~p18`-9;@(ao%Y7rO#~Cy8?Noz+1G7bvuKj; z-0zQ;-4#^MHOMOBk1n;v-O1pt(6Ei+P9BV!6EHelV0#GM6l5|*B?v^+qD!2!Y_lC0 zN4Jx@-3&Eg&{FE^MBw$>INw^n`EB#pKDo`vDXwFjJ=iiBGas4VqrO{I9en)k#n>7v zMvk{`7CWPCDue4Y2x}yfJi-p5PZ+Qi!ec9@x-N@%)2UvhHx8)Sv+!ZJIKPpY%CL|* zaIs${yrq{M))7WX(q1yOv7rzwd2HU#Q(p>HHk`q3!BJa5iq~big_p{xCPe{hVaGue zBFY-!4dZPsVH}>GF$Ctkww5X=aYjLrt(NDE1ehLreEpJ>x0`WK9-LH2*GQ9!UG{+2 zDQ@{DtLeKFRpHs!a!%SL zaI;OO6D>o%Fc!0cw_8=_He9ntSx@O=uxRQ;q{p4Pbjh+K1-emZjZ)Kf(`SOk+*}!0nwrQ3PC-Bs^*xsP*eq6_GXA z3)&%A*b|t8Nw8|kx`4%5Qh^O5D8o@)iq8^@3Y(c8kJ!*yn~%7o7!+;BiV=79$cRD9 zt(wIkdKdD5gQyG z&Ql!mw<*cxP^JH=O19q)X4_|9@FqLiExT*G#?^Jkq9(n!{#G!a7Ca&nqAW5_FdEFUMdtCiTC zxsYrjRhk_0asL3b!aRfWtxq2=L{k3%)HHmiUE}AFWv{uwGz)s}hGO74Cfch7*z4F49wY+$D+F?q18ycVkTNsU5$Tz% zMly_6636I%(~U*cf!WjR!HiAYO~hnTX}p}oK#MjiCfn_p*)t2rp0i6SC4||hyOcF= zvlWxmEnK!+X6T*E(V>~cEBOqD$^u3*Hb%zJ~b;?U^nn51q#EdsLH3?Nhp zTeVE6`g1yJsKRE78z@%<-N(;0bkwYNIoj5DLx-jV%6aCm+|bx1IL4l>1kTSbH9kr_ zDm@;L0*;L|rinj%<)VqJBrEi<&L4DGvV}^xjJAotCeiBptFSG{eNY0uoTw^oPt5F1 zt+24^s=+?mbWFOolF9JU9HBtpULeCM3{mU+jmyzw5O#)LvsqRg^%LWQHMAn+I|{@4UOhOP$68L0KN*De=Z-G1zwSy}>WbE;IUT=37K!ywI(1!;RKyO!b3L^5 zl|$=$j?A$o$h(b+Fp*h-VJ~4v3=th3eJoham{TGk7o#|1ra6XJS3>Oejarz;xiwp$x*2)>FUvPMIzI7A!LNt zgi7@eXwhk$nwCH!boD?Ftf{GQRl4c>it2>jb-!xVnF|UG{tPU9&cZ9zMr~NEQl^u1 zDai72Cc!0h_?@#daSjrltrUz*3e}+{&|9V(HU9u*4(Bq$UjUu3lu{y2G?P~rm33}` zA@HAwg?qD!O=l~Z1W2x=!@k3zugRig2xX}?S^c!2$B$rqkVggQ3GrxRg~0J?k+)x?Iu7s`3424pOmhIz!CTxy>8KWEH+|knoa_j z6h-TOx_|TsCjS7tpZ@^m7N!S5AN9G|@BVa&{{WVksRhcf=7PML$I3#jDD^_1&~OKm z*<7Oiuer=R_2$wMOeVlxO>CP6w*+~yknfakP5{)=BC?Q}DTL#Ng|ID_q#KtaQ(9BZ zSc_Ia(wzBSR_?ks17+H?c&~r*bb1>m=F< z*ch?$?*cL(C-L|%+kM}Z{?GSSc#r;{O2mC1?If8cy}0{{R4YK#0Fu zjr#up>ZWo30PI#8oILXijn+;2*P0L6eCrpuE$XVf-XkZ|s5zbeQ!0%{ENJ|`X;BgA z*sZc=kpNOoh=8*FA=9fKI~g=awPMK2=C%SAly*$d_17oX-EVo@6FTX2^k9cC=FP==cfb23&ySB}cqMcGk(0zHF1DC}eUt z_lV>0pUtBoGj5bZH z;mpcz;j2AV7dP1MWfzbpQqb+e(xOHhIV_VMXCGqiq>+}yB7+*}`O3?al^t#GQKV#6 zr3hsvCkbz^0e~z`p4@pYIj?2oVwTt3!hkC&5qxG&$fY2L*ZEXvCo5cd>h?5>=C(kc z!t&&6bvIS-R+$~Ky9{Dmd?Vd@())5-lqP(EXjt9up23C0wM~o<+0?NDC=pQ`$k@cjwkI%-0Y5dG5@o4f<1mqj z$&Ym}JivQ~ixgrn=d97;h}d8D*$l!cDC0c>v>T_GWXH%zbK|(3lGD^tw5E+1@`B?I zU4+yN#8Dj2Rqvqr4*RoArm~hOX5^Qv+hfZdc`LJF2#K z3$dCtTMr?!##?K%jkRI2%aMz}bb~@{h-kwq7jKkOZtW2&ra(bL?GbqS0app0*|eO^ zIV=uQF0Cg()NsdgAjlGq8*8rHYMB7!v`2Q!EWzFr`eNY;B&z}q{$nys+&A4sc91sr zVbAY{VY2%YJq=pfuFH)g9?*^oN+eXn7VBu`$f-aOx*!0OYnIWuR$pdm9BLIs3!*uK zZOgh(8aoZ3zXj>p4lX!KDGFm!U(v0GIhJC!DctxXpA3`b)8utNL>Qz^ib|Vswf*3T zXR%yH=WRYC8=GA!12-Qfy4+l1OXJt>2Ht12^chwynPTQ}g?6>GmYy}vJ$T3~CPjQ> zt)&EsEMTmFU=c>r+vAI(?Oq}2z#RJaXm@J9z}SQ8noE+~jC&n^o{mVq1e@l|w)qJZ zQ59Og5|Wy?aC32l2MlMKOXO81K5aXVQkcRjgneIQW^%TpqxlLqFSxp0NtF69|8gArpvhFgwcPa0HNv5J7BV~6@{Oo5+%DV%bkuLKt2ZgMr5>U+4 z+g;l(LF;Tc%a-}~<2&uLE!e-+M6M0GekClZp>LlImh8dUdR@ZMer6dKoi$|DT z-}-3Yh}Rh&1r2QB%u~f^p3&X5O*hVfHx4wiiolWvs%I;neeUV*pA_E}%E)RwVXR}`juqZIhO6$n zI(;Ix_Dz#RMyX>MO+7u?$H9;1{tj{O&;Hzd{{ZRrqv{KNOSW;SUdmW` zR(YAh{YPVWl8$7$x~k&Ll@=9cU1WJsK=OE^p-N(@ffbKrIDs2RO8KVL9$) zJZCeDI+B{Use2Np5ePz9)pnxgq8>ApRuUBQqDVSw<<4Fj8MyD3x>zcjrJg(yqrs{l z4#-h02_*2Ics9F&NQ(&u#R0ImvGc8C!xC;0j>v)*^tMK|(W_0{4AIWbwnyfq#a$ch zu2%YGv#wLzy!lwAE2+ma*)C)am^F3^G-gmJrs*ZB!i>g-yDB)EOJN09M?_R_vLrIs z8o6{G$sxE?M7;6Cj71X8!?%%1X;gDWYosdJ3e_j5<2+9_FU*g!qp+82My79%VJtFP3p)nQOfAXKOTo6aDFMJW)oxa zsBCa`L0m3YQ7bSmIs|fh!UQg=#7&rT$ZI5%MI~2NXHJsQG*VSv_tiZ&+g&#Q0P{J* zl+FkU4K^owN*Uy{PqR~^Q#uhM3~_{*{Z#bJpm2wQNj%$aH7^h|Fi{q5+&<8AofI62 z7^9xtZvN%(_8B*BFgqE)*dAeSxQ$jLMPo9T)+LyWGGRjm%CfpDpHV)<_`W*yhaL+>4i#lB9X)Um5fSBhK~G!< zJkB(uAxiVT!H)pDjy@eVW&D2O%l2qzB}y^4tHF$1EvLnm93V3;4ldVCwZ=1)zT)@$ z-u<;x;oDs`{ZqCxi$XJrBe?Eju8=-d8=%`4OyjXxZ4@SAJkrEx*fakaYTq4~@Q3o5Ju-0je{KZgrW zInHyO=Q+-E>Uv7Ub6VZMHb=PFUKNGpQ!VSo3ab&f$ZZP_DdWv7(AzqF<-H>erWjfP z)0RjCF}YhUTK@nOXD33vTy8e;^$rcUo4Z+N;^7sWdyv@`*+wU~l+KlDQ)7%ntD6?a zgAB*=sZJbevprF+=BZ|s{#jAVc6r=5%k*WuMvIB_zOu(dDk%GcWK@X<#z!`i5q~Hn zCd0XM=WpeXjLu9EA8hlZI*PT&oMas%A&5BwMnbO^Bb<% zfmBfuqm?MYd&domPR|7Vkbu_jRWP(_Dv_y6(SzS)Kw>X-Y{|=)&D=kD^)yz-msNp* zlLt;#bDLgrv6-YUCc9{i0y8Dh!~7S7L{8zR93Bwm7dMjD~N4+1f-q&BuGimF^@@&?TvLv z1aJgwskwRB;OsGQ_H@=&Rn)R$D(m6bwtbZQS;-DCqx#EB2@X8#30@{OPcm6oyz)w~ zDL865>ejUro_ksIrPk2hjz~~P9uk52cHisGKsM=;f_se-;JBPk1CoE zvsCDDCweBbN}&2`%TmtWC%&$>TkDvrd2N}w%Krc_M-7{)qhy*VqXgUC*2t`CDl2N~ zWGqW;n%*reX^Gs|LllKnkt9S!Uc(}|A!E~m4W}W5=9ElwPH%_y&EZ>*xVffvxwQ?* zw)bPU^B~;&ylLn%hbEm$!M>uFY*H#Dn&&l^R&szud|76zhg~>EgFZl?;WyHZQ#_?- zM8#t=7cuk}X|aP6SiA@2x=!$CZjEM`V2kpOpS__pdwo(Ue97%vDiM6KAX5_uyBGbCm*M9);+>bqFVYQDqjKv~z7>$X$m8D0X$7ACXJ6|7@#oU@AL66JWQ1j1TjBw>4CQ=%G zu^|j!H2Wt%wyKqW>-f=3=^NjV;<1h^m{eUErL$o2Y&cv$Xo)(iWe;O9c9&OQ-4pku zkCJ%014gqQZixcrn(6LA!?8pcYn&vje0tcf{;J`*P+5I{T;7v+0D6C^;2jw_lFce* zgRetIYxsTq!ZQaXEF7{xt&KEPk4sAi%SgqxWfwt z9_U47wgnAfLaQ#J3n8paLm{<%f>D>C%o~8sMoXus?l?RG#SCJ*m=n^sujVfZ>KaR2 zz$`dPGLkslc((jrzKitFHS@JYY{%}B9Wc(K#d|So4o_diNSnE)D?;JTQr2kAq^+Pg zPqr5AV=`#0RI?`JmPnLYii1?$&U9)}Tm8S!(Z1TFE-DP2iO@NN)O_n!Ok?pnxS*4* z!cyF!RP$`a;CvY^jJ_a!2$`uoVXm!|WIsjOG?G4QqH1WoH>?qITcJtIm_+TM(cP)H z;1w%p*Be*+xI3kY#DWbNHg6!6uv10A<(u3kvAv$^??VmrZnH7T5qYkq3p>gayA0K6 z4n=?StfETJB^S7{6l+_YF(8hFOscrn8tzLjDiBlr8F;u>a!2fmoR8`*K;e{LLf%mW z6Sm1ZYC#W>I*Q6Ds{|a4mZ)y*IR`?JeBW0sQF`BH5zg7pxK*QsTxtCO04b@a-{yro#wFY?e`4Vkuf8oyi!70)@dcJ~{}f^>Po+k5-k9ZKZTUOVTD2 zCvLa5ar%j$i*8;olGs^H+u~strLKxN5@kfI0WP6dTC&JiHSUjW`Or!R3DZ7U_{@=C zAcf10ot>yUg20x%t!;T!B7I1gtH~R#AdY3Ph3_B;<5uKHw{rXta*7Q!+*ZU!s(g0q zjmzW`dd=p|*3wXK-+OHf%{#9@bocA*@qhMR{KGV|SefL<_R0R;nUH=diPl44Zfzg_ zOnxuRvg`04_hI(`0LXvb Tq2=M{#H!Mq{{UmLu>;B9LkYv&E-5o?WkX9U3HTQc zGq=T|7N!tba>#rA`npea{r>!W{e7F?(|2xWtlM+Vmb0qmhQ#McXsEoY79Xs#KLy#@ zjC^0}sN!KNjz@F8eg6RBh8Va0t7ACP(Oz!F6+LY@Fc`)aY&h`PVp>FN%Glyyn)kJ{ zRjqTH&RmW5MPF}iI-9Sx-w$2b+kR(7ks(^)7)l*ID$luG{q1+pS>LA51w0gt`0q0S-&jPDe0q9k4}$48~c zG*!tJpEztenZld38B9Ofw)o%^8gmwhSkzZU*o|%ya%-ID0Ol;BE+*}CIbGIM zD9ecHkc^CmAc)fOyI}bw3NYRB>70*-5W6v5M4L|nzrhmIB(8!ni0_d0$P@feYfScNe5E>y z77Z?-iK~~aM?IA36+qU_+bt=9nqAt@7%bJhrF7huiMoD3qgD=g(<})qyL@br=g#w- zuMOEejXbPENkq(So??MpWG$gMh5;OEP#BbndfmLOo0Jt?b94wu8P>3nT5MtKNdExO zLsP)g+4_g0uCX+?1vi#eQrrGza}uS4!>mZ{eP5gj2zy`}KVrWcR!-$-%!W!M4x(^q z7*TAC%w?%8c3*{LG3Ir(ab-HvUX)LCEk5Zkf0|m=CHqa7m71VsGDvNyFRpaSeIm-j zX9?EQLeex@ps1aPr#5sxIMDl-F%Ztmk_IZxUx>+d4 zLh|vcyp4pOT^=nLuLe47LqW16vY9!GiSC=m%+X}KW{y#hIc%aIT18a1P19u+WF6y| z3cGwJWvAA#H%$jD$}?+|aA$NIixS2V>2gHfxNwV1k7149CN=ZaT)5G?$h9Q4o2`wJ zYOAGpLq+k7RqKw5H4=eUb-J|jHbhEWD~QRak6pDlD;*Kic7l!LOC&p8!>=Kb*Rn`3 zmySjKv8=7COB$I(KqN@>4-xWB)XM;Bs=P7Shp9!6Chp$J3g}`Ya6&^8!GLEQv$>2Y zBTjJDRqR0#7*VOJwo9OHpRMb2{QIeq^%?SK4(69@_TvaxBFO9^xrB!cB%IuQ+rv(_2k<(_$So@!mci$*}H`7k5WzW+fIfHT0SY1}QmU_6}`u zMdWI+lF4QP;{(W=ZaI+0Dj^7GF^VdQ-7u8hLStW#%#$$Qq+$OcQkQqF+ zTGOGq;PW>#*go43jy!1PG|`dA-{nM|kJU%YH*T8jwpfc-f?s56%C#bVW91Vji$*@S zDA$}W=d0MM7L!Ej;>1v0Wi{T%uIHnUvPC>-!z1-VH9d$m%VzGGI@=066}#e)O(e;Vj2g5MZa!b~HqCqP&WF&%QzjF?bt&*HI(8$tsL5Zqhx z&Ek3sEbM!S2s1V%xO48e{W_*NE!mLM3jW`MNTY9e7bFCJ4*3UR%c8o?9gTW|EAIJ= z#x5J>yT--`iguFu!Wy?zbk@RAyDHkC3}L0qwx*AFs4EnV#_Z&@W4^3{X+)Ij)0k1D z%Rr7qf)xfR=^MDEaL?j$M~h=_&3bO?%E_Ck&Z8!Df4ujQ;16|ii)wOQ?dDB+TU!#e zc}6yAH<;WJK71GhjAWCM{Ke@OD`!(A_8R8*S+jdDwtR&%+65sU5`v?zZ^&_uD?BMT6JUU6{5?yR77bl`zwH zY~>bP6v7TojyG`CVhoyS`*H74MZP^A@2ee#zy1QogPpCl$mq|fcuPVr;M>j!2 z$0@1P*Za0dR_JWL(Sy2YDIuIrp{+#jn9YA6GZMykB{cfzS zpT4=w_eRo+O+Gf#Qw&c@7&EU2Wi(Jva0${RAtYWE&|<)Z90|WZb5RW* zzARZ0gIX{YgU4>(qBWMNqlDTp(ZY%(#h#w+eiTr`NNJ2?KMpaE7`Rsd}Dv164}CAy?KPSS9DnA!)?~xJZrkBC%4YjtHSsmzm(U!v2DM; zg+i)p!L+I~G@ngib6pmy?5dU@U(78SZt(i$y`LnTG}b+8Rg2cru&vn&n3A6jA2CL& zLOnbo6W1jC{{U(6XK8eeO&5!}y;;zcYnr8)90oh=?;OYKn-GLdoq#!;mqgYG+0%&I zQoAr+(G(JBK|rqF;l^2*(q!0{Yzld1r>+#);EGbhu0skCq{#Pr4NUf+hva&>>gRSN z-l~fBEBo8e-VIfWWP6E@)NTeI$RttJkc=Fe?5c?Lmp8yPM)pTT z5KL7&BTDf4V#mtvpM`*%#>!6Wo}-yW1wW1vI3WsIQ7cH2Me|p3KwnPUaFXKMDAQLW zhm}S`<;%Qd1lArecNiYZZ*pi7@v5-_#{wIUy4F`Aa+vuTmthI$#doQw(RJ89*Hu#0 zRDIiHZFW^G=Nn-)l*YN+K86<^nozxZ0Dm=FZp~X6V~W^nI}6>^&m`uitC9ZzVNc@s zy!yG!bmUzhhdB7li+0=krJUzo6VdXMqRtu*LD-~3#=hJ4_wAet-LRj$INhMXtW)B~ z68h&U?u&nazvEL};~&%1a?RbknYB7o%)!M7ZI}sYU|BAw6S<>S=1?g{cpSd{iRa0_Vq{l5W#jLi%GFXv(Hj=8P5=x#XG~=m6>Y=ra{{RA^ zh_2;Py2EhQv_qO!D)M_wbaUn3Qr$mNB-SHMDsc?wp{OgCmO#3$O-kZQC!~2xNdqgk z(W=`zNOOkiE2iotwq7cqs_A&Wa7Uc)<~P+b0W)`lwWGEY^s_PrABy}~q&{#R+)?#} z9}SLuxj>Ji3$r*#D#XwrGEVd;@(3Ois_k>xTASWCw>DZ>Je(&sLV~lnzpT~?ir4oR zWm!w&_x+z0cl){euYbe%_xkU<;r{Qa{{TLh*}Yd8Hvz5b;z=}&uyJ8cO`9kaJAjQu z^Nop;YKw&$V?`O^>RDfQ4xJZt(N{xR3vq{P?S~SBC)~+)@S@t_(O}k|M-yf;+A_K* zU&_4fXPoXTjaRv$Xw~KR5fXKTSa49# z{b#IY;&5lDmjmZCGP%G;)I{Oa-d>JrgL+CEhMS@u6N_ zwV+wuzkI)@Tw51Gj(?)q3aSWNO(ixoS~UrgO^Mk=8BL+?>k7^xwY^FWe~}OwrLq}D<3PFM+^spJVL>-1IsWP z1P)0*kSA9~7Z4K9$zyCz5u6ExR$q_K=sTRgBcZo`j zMJI%T>dm%bQ z*%h{MjTSW6gOcEeXjw9#vfv8LlR=|U9T|1<1lG9ConOi(v5F40pFt{PHTYKs#S+?5 zPTQ5o*htAdeA8AUaXYt8J|N>nkT%*p(IE_Di@Dtr$vG~%=aWovKk3)E36|C#wkG#t zU>2$+sx$lgb-I!5AXz%yb%)_aOk)i-k>lbYt9|hIcG}|`BZ&PL>1!}7d1Dr(D=m8- z4)}0$HYI*JQeYbqG&I==*z%2>Z-GLLNEyZCWSKU5Ct$HQVB{|b2TcGZ5JAU7tRM^M z+dpQrl2ypf%xKkEIc?nNITlNibnk37-NFhsWXY?yF5Wg>!xWHc<<72@YL3hLL9TDQ z6zGvHw&1rC2g7}R9hx>Ij^CwBf)pEX8kJMO#`i~X)p+m?9F1(TA4?qxZb2WA?f(E; z(Dvq@TZnJcd#QuCxdk$nL{`?mY}~JJ*)_H8BF0IP!)_U9-sW0MaETy}1kcDY>F^gu zX^0J>C9~$-k4^Hii09!6=>Y-yilwUg{nW2XlUWW-p5| z4;5zOPX#TAh4llfqH_8DqoH);h8AX(aN|aM3j&Ot;*MUo3Pl$iH-7c%&C+m9pyB9Y zzM8vu(1WB|ZnCfBldf>uJjzQMr!7*V3KBR7T#8B?5s0A!k@Nt&EHHe59nZFY_(1s} zQU%88sxR0eW<9H<2L5v$L}kd265r zsxdw*9m+Y<7A!Zl$kGhtYh4(P=H%`7LmM%-`Gs{Z>EvBXZsp}Gt1$U^-A$ekg5b7B z@fDd>f&FygUdRt)hAMVnWHE>*nk34ohA=K78Ovk2IV!^ib{ZdS%J!W1mqJ4x{Di>Ti*tIqN11O20KS2ml8 zmX(W6VyniA+tbTlmwts)cPhE|wY)mx9@4SbcXS#=rtt9u9kl0!=kLeA_0@PU3aY4_ zCo95z_)dM3pU)a|FTXE!UEtP6;YtQm5Iuj{kP9cTP6@hM5Y3>xY0HyR&~%W~w9y7! z_Qt`QnUL5|oyoQ6N0F&c@sY88hSs4E*Hc|`>++97KAdvJzt0qXsaITFutJ;K48I@0X`5+HY3)+37t7EBgkd0gXjK&8)L`0eAU088o> z1cc7w^4=@Cp|!P#J6?9uP=)0iAtaDiljDd)-NnVVfyo$8BF$t~<1vNZh(;SGFC3U) z(a<8~#A6kyeWSzFWfrB260G{CUUM9gt3bqlXEzSw_p`T$sBFTqSj-+auHsq} zV&%9IL7O7Uqa=m6Gaw+FsWEKp8qll{B3z#;-oCEm9KmJRT;d{oG_&Vb$gY$NAhTsM z$rv3##K%SMiFY@f7Y2aVEZMMU8jlvJtSNc%Po>D%HcY(vXmpyp@?X8T!Pl=tvbYFy zH$%t76%_TX>Lw?zD%GErCeS2c3^nXJOz(xZYY$)@pmPgL1)Pf54rUIF3*0G)3P4^V zB!BF_THz^-2BGK}eOYhRbz57!-8X2#N7-O(NvxJDh}g-@CTSLt$e{qlgRoo*cOZ?< zS-ckd6yeaQ%z`cqKCjLN*Gnnm-=73|Bb3sbj-G2l*EQvla1#jPQX%DwB8O3&r6}o% zG*9!Cq2nX%rIV(R60v0E?Q7;pgs-N-JyFBs-#50cLJJ-Z{ zdi-asy2qN`vReJ0TUL6by|*m15VD)N2Pa>LmB_lhRvk!zIDo&6oR*Zs@MTf8#9#pd za(z)>BOJ&lp>9d6Z=CF0(j6IHNg)??Qzz(1$;s3A=WL+&Ao&$xamM+&3M#wSBWA#A z8ANb2j-RxXahO5CQt}emwDITw(wLtS@dU=vYp3O&utzfL1RtZ(xv=7R6F{5}|cRh{G7HKCkO0r|m zwY);K=}66W7_KN2+^SoRBarfn&H0Aylh3B`dP(s1!z_nVDB>q2;Tp+hP{TUDEHUd# z#QdX2y%*5eUF8ZhWW{M{q&q;Bn2A!bM5Qx}I#|jk8RV8q&^dz5VsSA40F%iV7z073 z4oO(W1#Vr41-cWiP$aujz5 zc=E(fB%09*@l7G@Yn`P;ku585L?Z-Ji51TY`+u+WiJ?_j?yA4ldDLO4e2ViNIi-hJ zO?k4$v$@2@nHsg=b<@6!6B4l^l29X_0yHjFa7DTCpy}K;!;d}4S*W2^Wp^oPn26*0 z)^l5wZ!38<-XeZYxrRjFtYtkF&zBXKZ1x_~+}rZn>!j<7dMJ+azKgCpVdMA=4nxP+ zQ|PI^r!|<7PFZJ}KhkNDJ`tH!kw@N`s#C7f04JD~*erhCNJ1AyF$4 zI?HJA+(ybyYzfN7a}$*=PFRq}k=W&UF~S0Ey3EuN+4woyQIPa2Eu~Rfk_{tDA^5pWSOnbJQx~#%`1592FmVBk z2aQCw10+6HQZ^^ac{iIMc=(oGF;MnuSL_F(Xxjc{K&klBwR_lQ$p!1FTM_XXlL)#} zW~T(}L`2*glAW3mWIC`rkZXl9{WpQ$KzEW&1l554QgR zx!heicmC<|>M`YN1p1bCFl8eAFGa{`3ip?#_jcB1=072^?H480G|$fUK9) z)qP#m5=m>DT>Me&dIn2NRK~h61s_53{UbhtW(F!;TtSO&?}gkdG7T;{se^BfIF9qRA~WMf`&?+zT!-Bq zTdLiM+Dt!q-A`GNtuJP53+pSXxYp8>N?g4oHJ{mMy23#*n3*E=>-VNeuF}d93>($l zzgnU%S&q@^RXBh7xy#oGw{bOxg`P>Fd{;Or1)YN)I1}1>;sZ9Hv4weupgE>AugR2v~iSdX1#sI}GJ-3a}4_ z%cUMT+<9TcNUR$PX;)y&KV9<=n0mC32LWI@8Ca_%Y>hJhYq+fW8VgJ1u()g~7VX(K zia});1ks?8jkZe~2@}9LA2b2+OZ6s%^Il=Dt`cq@?P)N^m@JA33VmGF^d*9y5rca~S3Y ztvg@BH_QX)ZItK0nl0B?Fa+g!gNwcTn$;@RHlZat_akU5v_%9 zbO?j91erjUW>ZuWH@VwiVK{YG6*^=x-mfu6QIVtE<9EH}YFKrbBX)WhZZ-;5S#4Na zKwvfnl;-?7%(g9JCK!;~R36qu>RZk-wQWtT zD}HG@m7Hx4g3@S)8!6T;71Tt05GIV;h<8zQwO=EjvSUP8jyY=buiCM95t($X2`d@t zs`_D-8QK+JM33e8q!He+sZdhUr(%|(>pzc|uMq>!HcZ$B6lyc7`cC2P6 z9gR&nZoToj(>_FN9(%E)kkShcnliGlsI{3^%&dsqLN0F0ttK;ChFatf)(FWw+BIHE z!oUg3r6b}3R@YtRKKJPx2a-IPSRG2spZd~ez!euTT^w^5vboZei`5+McGzdSwyPFO zxD5xb1s-7vfFlHiW;ut%9@yH(MjDQ?o(9@V>H9;3{geHF=%3}RcRTlA^IZT2J>y8i zFDnk+ytyvh!8yelAgXF;3^x4}P=99a!-hL(RUTr&GW%toHeb|CH)&IJ;3 zbsB9Ul*Ub}^q{xQk1}>=6jdazucqbBjh45yqmx(&lw0-XTm@K#VJZ>>M~w=-Q-x1A zl1~Pg5@?>Y*6kuOeS;Jl9a_aFU>?ZiN9U`_TK&`G6Rzc5t7vP`_QxJ{pN{FzH`{B~ z^)**5hQ@nuZhwec6{5)1(HQ$TwfU%R##u)opKko4v%{ixq>jzsr8Zk#IDkGa6P4A%r{{zrg{=Iu zH-Zg`%&oe0&CPI9mbB97=PPU-o-P#-idphkBjxQMS#U_ja(G3(7>8Rz!rSq<)M`^u zx>U->qEi_Z!m=g~7ZRJtT7XaGpv z!@$;+-n4D39sHeG(G%9>G3yZ!@d&v?Q^#;2oImM)YMJ+kH{D%V(|i?ZaxYhLpjb@8 z_`TuWiFtE%C*3@-`cU9ilQ@*sc70AI=w6cM$VQp7WF=!HWf(CR7a$Tm(y&!y@#?VF zG#cQYvBrCH+`pfU^Cv*aq|=FQUu4MV0s6wRjg3ZePD8HrW%m${my>&_1{PU+v#^t6=Q*nB}T^TgS zX!>niM{4rG9@^0=HpZ5~7|l|+qVZ_lRMc*BB&CV2Ee()^~ESvDdr zyJgT0qBkm*q@o@(ou<>{_08Wi=Nh>BYP1afh*L6Jk5?v}%4LURHjJGyHLJLLaE~0T zCOC83{{Y%EsxVA_jScE*f#6}T7)B!eTgKd}ee$eTQE!ba3XH&`FnXh_dK~p&{x%#u zO|z*K>JKuB?pS#V7*HsTT^IOPwSCnQSNBv!*VA71{{V&VZ~d1lf8eRAw_4q8#F8Fd zj8JjTeYcy+Yx!lNWq`1B?5c9X6qM%Liqa7IE2&f}H^xz?<{%@qqoYqFF;TPlxyBCU zGDL}32Q+dwQQk}MyZG9B&GoH0TJweZ2Rl;YMWhN_H0s*o%MzM%S!lTgxjD2(J;jw1 z=}yXyX8c5LNc7qR_BXFt8!4=z0jq*7G`yJi>K*Gv(RAg%&9VC9vH9BK=qLR#Vg;uJ z(P>S!u?0X?f=%64XWqKE;q!ZTIemym?ecrg*6>B4 z$n!Mj4-2&J(Kr79OAC(lTW8W4Npy%toU@gELBL#f6_4~hA`J$gMW#GAd&cT3B7W+2&Q9L>1FXJFQ`oXLu?JCc zZD0}x)(klbEpEVRhoVq~%1XwjTOB?CY@nM@FY%Yo)85$P+A@+>#fep&Fdm0nC2aF? z>2&OBmkj;b1ltxZqWg*OGt<&?n+xa#-e?j<>R9*0dU}R zxQ$x%A@kTRk6|p0T^trt{{WR&EVmUAb92r$bW` z{{WFA`@a0DT{ixeAq480imsH?g`p)Yx#dd$RueXkAzx@yc$hKgkuLE>L5Lz#Q*D}P zj;=H`EIa*OYqK47-cj`YI)k~TzA|MEs>-jNA=gJd2HwYGQ8jrKjSglXx;pBZbv1f^ z%s*kBAtYyA*Rj+~&aBBq$fLXgXzD;k`xZ&HN?rCvdk7y|S@L=JCel^!_Gj-J`kJdE z(du>8AkuMD6o+2LEgFW=HC{b|S$5iRo#~1v1n078)BcHxr*$8AcHh*kmkuT*(V;Oi z^3QV5W_6)CElWae9Ip#bWhhj^X?Vm^tCi1ek$MQ2*fThERS>Bils{y~G>D(|wxD8Y zL!&HW0KkrD`I!ubwdIcco?eybKr^kX7zLRIa$R){LMs8R8cx`q1STp(1(pQL$3aeF z&LU%7#T`aWj#Mc)#EMW1!!jeyx^J23C8Fq?Z_5uq19MSVkq9-RQ*fn@>X@#>s|~LX z&Mcl7=gVcqbPNFJ`y5PNv1-T_0{B2Psn`;Sa~I7)N)er!X(H1l(Tc31blkzh@{10gUIf+$Cx{|Ct}(*sam#?uqPI488AZzrnO+#xC0H9*5xVl zHS9Dq(>z6S$dm``aau_o~zp-DIW+GCUG=}L$vi52Q$IrOctM+S4mi zx>{i}pCWR?jN0#+N{=dOqNG~p!-hsBdSfZ3zuiPOT}9Q#Ro8i!!Vm$lE^eG$b&Um8 z^d-JarE6NH48 zIr{P)#=)w2z7jXg_dmkvnyBzPQPi4AOI`(1KQFexMPWWhd0vZ!2#U;J%HBg6Mv2z5 ziAsf}QMg#?q~r4vNXX{rA}L2>flV44RNs{zim4$7qL7%qa-jWX(%I#5XuW3}KPRH4 zlc5d3%Z<`fVvO9Ef;uiRRBLoFi>Jv)4#VQJD4Jj;)Mn-Z=4h=j35|)-TlW66{nl>uToHP>u09NT}hNs>9ZPd@p&V{4Ltq)qO zS8;?p6kS(nLHgIAY#Pt0XOmi@BVo%R&^E8(aVawhEuKgl*#|p5rRnKePL0-21Then zh$2XJg=!s@RH9;WK+sT#8lq3*I?rjXBUHzy^=Dvm(4X7l0myc?rGst?m%GkMqms!K zf>3#bDAN^cr~@HUI1nf-Z-2zbo{|TVP8KUHB52bSk0}-w+{vDCZO?r1h&}^-9;WrG zv-yAI--Uh&i!Txk8^+(VKH2^ui5^<~AAJ7+68aB+`&M+q-c&jf^$UtI=y)?`FCo#2 zz_pUryNx!vPt=g(Ef2;jlJ;(_Ga+{h4p>B6X2@YwjVKZm3WYi}yb>}pFo~hBXsX@6 zJi+Ql^fgx@r>4F;It!$wmxJn>CRWsIc0nN*4E8G`718a&X)}t5k6+cLb<3MqK;;7K zB211JttBwiSB%Jv(eVEO=*aC$FyT%xk;kb@s*!Q2HxAye?V3i)zNGIGbfNAi8%dyB1=XCfY$Z%=;b{2u%0ZB%-w^3SRLuZB&?ddbpgJdQQZUGfC9Kw3fyy zq~AxkG-!-ZY<{fq@4%ix5b_1pXK8!)r0NcNg7Pmr67nB?bstW!pGQnshpu^4T_Scx znuoE3sCP%Yn#b!cN6yql_}l14EIR(L1K7 z`#ZaQe#XneVfA%w)cbP;g7<`sVcTSQM7a>blw~h139P<&KfazNzuFWpWifuvHvj})tym;(_6D+T*Me8{! zvQ$K@!x+xfR_B>q!qJ^(}BTaq?x) z5fMGSz{BG2VDabeO3qP`Ee{s9gw8ZqUqRa9kYix*Z1%$CerY8(=wyokokZNzp)082 zk-OYPYv^_CItf#$S`Hs4c-tpJj4UCWdX`9Tx69H{BeJp6@-hQPi+_nOch+qR2+0nSeE#NkzVg!%dEHBnDC3?8~((>i%8J9Pml~ z=gts0l)fB(wN0q%R&H9YDPlc$Tvua>2#dl006cEMEr6s_R~C{)7!0vqkgVen*pY~P zFs6+WjtZ}OGqSvR`Fn*!VR0!BMKJ5GI3Pm1Ju<7tK-4|R-2q<9EJ!;kylz19oZcWb z9WNsZ4=Em#VgOP;+zv#NWDVcM{GM!L2#{tfUoklJCAEm}CwkkTO zs^ELV>2tKj(rPBkdl`*T*Q_oz4P@@@9W0K3t=EASk-RG`8cbNVVV@S&ksT=Rleq)Y zH5IRBKy7|zt>bKLS~|SC07Gi4HJ?EgpUKdG&Em3nx(jJJtm!JzjHtLrW8-PsuA_qW zV=taWiVq|c9&m$mgRYT3&b;qf+0&f$To#&hR7`4M*VszRIVFi7lC4C>pi*J-GVupf zOCbpzK7j|zFMJ|8t6C8Vuz<{1+Jxn1jZL?-U$p3#)+rt})3%U>nKUC3pVPg+$Y*M) z(6T|96cTnZAhImFo^h}u3OmX=JS$p{Q!Uc_g9J*mU9zjQUHMq~T<6X@=rY~i<))yg z@h7I{f&g)R$DrhP?b+1P*;!>Pf_0dI5r~vC0T;6(uBC_sBNC0>0S3NYQ(5Ua9Ce)P zX9*wWL{-#h`!94|9UW&GiSv=^#QvDEAyca$ERQT>z3IxF#S~Ld!{>r((gO)pvhvPf zNd(vI;$+NbI%M-J9o!>Kv9j_!`= zxrD+VMqM8ky|WdruIXsQ8=H!k9}pPTE9MCS<`U7yEEvX1B3F9E_;NQX-^v#>Yi#n@ zDnDIu%`p)2A8uFIAD8HNLd~1Ql7&m_8w%#5cp!yxl22+L#g;c%GPZIbpolP0IbPm;&nae_`aoO7w}+AWLHBw`mDHa%X<--S;-_d z&egbSRUsaM*7dvS_oGIGy@Tb$p=?mF;1gJlR`muG*e#P{xJIpwiZ9TT%H@vYqED~W zV%sw@VkFFK=_(~#D)e$vqH{@fu33Y#Q#(l#^j>~m?pq;BoU1z0o2sSM(Ku-e{&Q8p zHik@qvwH literal 0 HcmV?d00001 diff --git a/core/build.gradle b/core/build.gradle new file mode 100644 index 0000000..42ff9fd --- /dev/null +++ b/core/build.gradle @@ -0,0 +1,11 @@ +apply plugin: "java" + +sourceCompatibility = 1.7 +[compileJava, compileTestJava]*.options*.encoding = 'UTF-8' + +sourceSets.main.java.srcDirs = [ "src/" ] + + +eclipse.project { + name = appName + "-core" +} diff --git a/core/src/com/etheller/warsmash/WarsmashGdxGame.java b/core/src/com/etheller/warsmash/WarsmashGdxGame.java new file mode 100644 index 0000000..b9a6a16 --- /dev/null +++ b/core/src/com/etheller/warsmash/WarsmashGdxGame.java @@ -0,0 +1,30 @@ +package com.etheller.warsmash; + +import java.nio.charset.Charset; + +import com.badlogic.gdx.ApplicationAdapter; +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.graphics.GL20; +import com.etheller.warsmash.util.War3ID; + +public class WarsmashGdxGame extends ApplicationAdapter { + + @Override + public void create() { + final War3ID id = War3ID.fromString("ipea"); + System.out.println(id.getValue()); + for (final byte b : "Hello World".getBytes(Charset.forName("utf-8"))) { + System.out.println(b + " - " + (char) b); + } + } + + @Override + public void render() { + Gdx.gl.glClearColor(1, 0, 0, 1); + Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); + } + + @Override + public void dispose() { + } +} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/AnimatedObject.java b/core/src/com/etheller/warsmash/parsers/mdlx/AnimatedObject.java new file mode 100644 index 0000000..2af1da3 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/mdlx/AnimatedObject.java @@ -0,0 +1,103 @@ +package com.etheller.warsmash.parsers.mdlx; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import com.etheller.warsmash.parsers.mdlx.timeline.Timeline; +import com.etheller.warsmash.util.War3ID; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +/** + * Based on the works of Chananya Freiman. + * + */ +public class AnimatedObject implements Chunk { + private final List timelines; + + public AnimatedObject() { + this.timelines = new ArrayList<>(); + } + + public void readTimelines(final LittleEndianDataInputStream stream, long size) throws IOException { + while (size > 0) { + final War3ID name = new War3ID(Integer.reverseBytes(stream.readInt())); + final Timeline timeline = AnimationMap.ID_TO_TAG.get(name).getImplementation().createTimeline(); + + timeline.readMdx(stream, name); + + size -= timeline.getByteLength(); + + this.timelines.add(timeline); + } + } + + public void writeTimelines(final LittleEndianDataOutputStream stream) throws IOException { + for (final Timeline timeline : this.timelines) { + timeline.writeMdx(stream); + } + } + + public Iterator readAnimatedBlock(final MdlTokenInputStream stream) { + return new TransformedAnimatedBlockIterator(stream.readBlock()); + } + + public void readTimeline(final MdlTokenInputStream stream, final War3ID name) throws IOException { + final Timeline timeline = AnimationMap.ID_TO_TAG.get(name).getImplementation().createTimeline(); + + timeline.readMdl(stream, name); + + this.timelines.add(timeline); + } + + public boolean writeTimeline(final MdlTokenOutputStream stream, final War3ID name) throws IOException { + for (final Timeline timeline : this.timelines) { + if (timeline.getName().equals(name)) { + timeline.writeMdl(stream); + return true; + } + } + return false; + } + + @Override + public long getByteLength() { + long size = 0; + for (final Timeline timeline : this.timelines) { + size += timeline.getByteLength(); + } + return size; + } + + /** + * TODO: This code uses StringBuilder implicitly during string concat. This + * should be upgraded for performance. + * + * @author micro + * + */ + private static final class TransformedAnimatedBlockIterator implements Iterator { + private final Iterator delegate; + + public TransformedAnimatedBlockIterator(final Iterator delegate) { + this.delegate = delegate; + } + + @Override + public boolean hasNext() { + return this.delegate.hasNext(); + } + + @Override + public String next() { + final String token = this.delegate.next(); + if (token.equals("static") && hasNext()) { + return "static " + this.delegate.next(); + } + return token; + } + + } +} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/AnimationMap.java b/core/src/com/etheller/warsmash/parsers/mdlx/AnimationMap.java new file mode 100644 index 0000000..a8b871e --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/mdlx/AnimationMap.java @@ -0,0 +1,92 @@ +package com.etheller.warsmash.parsers.mdlx; + +import java.util.HashMap; +import java.util.Map; + +import com.etheller.warsmash.util.War3ID; + +/** + * A map from MDX animation tags to their equivalent MDL tokens, and the + * implementation objects. + * + *

+ * Based on the works of Chananya Freiman. + * + */ +public enum AnimationMap { + // Layer + KMTF("TextureId", TimelineDescriptor.UINT32_TIMELINE), + KMTA("Alpha", TimelineDescriptor.FLOAT_TIMELINE), + // TextureAnimation + KTAT("Translation", TimelineDescriptor.VECTOR3_TIMELINE), + KTAR("Rotation", TimelineDescriptor.VECTOR4_TIMELINE), + KTAS("Scaling", TimelineDescriptor.VECTOR3_TIMELINE), + // GeosetAnimation + KGAO("Alpha", TimelineDescriptor.FLOAT_TIMELINE), + KGAC("Color", TimelineDescriptor.VECTOR3_TIMELINE), + // Light + KLAS("AttenuationStart", TimelineDescriptor.FLOAT_TIMELINE), + KLAE("AttenuationEnd", TimelineDescriptor.FLOAT_TIMELINE), + KLAC("Color", TimelineDescriptor.VECTOR3_TIMELINE), + KLAI("Intensity", TimelineDescriptor.FLOAT_TIMELINE), + KLBI("AmbientIntensity", TimelineDescriptor.FLOAT_TIMELINE), + KLBC("AmbientColor", TimelineDescriptor.VECTOR3_TIMELINE), + KLAV("Visibility", TimelineDescriptor.FLOAT_TIMELINE), + // Attachment + KATV("Visibility", TimelineDescriptor.FLOAT_TIMELINE), + // ParticleEmitter + KPEE("EmissionRate", TimelineDescriptor.FLOAT_TIMELINE), + KPEG("Gravity", TimelineDescriptor.FLOAT_TIMELINE), + KPLN("Longitude", TimelineDescriptor.FLOAT_TIMELINE), + KPLT("Latitude", TimelineDescriptor.FLOAT_TIMELINE), + KPEL("LifeSpan", TimelineDescriptor.FLOAT_TIMELINE), + KPES("Speed", TimelineDescriptor.FLOAT_TIMELINE), + KPEV("Visibility", TimelineDescriptor.FLOAT_TIMELINE), + // ParticleEmitter2 + KP2S("Speed", TimelineDescriptor.FLOAT_TIMELINE), + KP2R("Variation", TimelineDescriptor.FLOAT_TIMELINE), + KP2L("Latitude", TimelineDescriptor.FLOAT_TIMELINE), + KP2G("Gravity", TimelineDescriptor.FLOAT_TIMELINE), + KP2E("EmissionRate", TimelineDescriptor.FLOAT_TIMELINE), + KP2N("Length", TimelineDescriptor.FLOAT_TIMELINE), + KP2W("Width", TimelineDescriptor.FLOAT_TIMELINE), + KP2V("Visibility", TimelineDescriptor.FLOAT_TIMELINE), + // RibbonEmitter + KRHA("HeightAbove", TimelineDescriptor.FLOAT_TIMELINE), + KRHB("HeightBelow", TimelineDescriptor.FLOAT_TIMELINE), + KRAL("Alpha", TimelineDescriptor.FLOAT_TIMELINE), + KRCO("Color", TimelineDescriptor.VECTOR3_TIMELINE), + KRTX("TextureSlot", TimelineDescriptor.UINT32_TIMELINE), + KRVS("Visibility", TimelineDescriptor.FLOAT_TIMELINE), + // Camera + KCTR("Translation", TimelineDescriptor.VECTOR3_TIMELINE), + KTTR("Translation", TimelineDescriptor.VECTOR3_TIMELINE), + KCRL("Rotation", TimelineDescriptor.UINT32_TIMELINE), + // GenericObject + KGTR("Translation", TimelineDescriptor.VECTOR3_TIMELINE), + KGRT("Rotation", TimelineDescriptor.VECTOR4_TIMELINE), + KGSC("Scaling", TimelineDescriptor.VECTOR3_TIMELINE); + private final String mdlToken; + private final TimelineDescriptor implementation; + + private AnimationMap(final String mdlToken, final TimelineDescriptor implementation) { + this.mdlToken = mdlToken; + this.implementation = implementation; + } + + public String getMdlToken() { + return this.mdlToken; + } + + public TimelineDescriptor getImplementation() { + return this.implementation; + } + + public static final Map ID_TO_TAG = new HashMap<>(); + + static { + for (final AnimationMap tag : AnimationMap.values()) { + ID_TO_TAG.put(War3ID.fromString(tag.name()), tag); + } + } +} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/Chunk.java b/core/src/com/etheller/warsmash/parsers/mdlx/Chunk.java new file mode 100644 index 0000000..0f109ba --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/mdlx/Chunk.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.parsers.mdlx; + +public interface Chunk { + long getByteLength(); +} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/GenericObject.java b/core/src/com/etheller/warsmash/parsers/mdlx/GenericObject.java new file mode 100644 index 0000000..45353a2 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/mdlx/GenericObject.java @@ -0,0 +1,107 @@ +package com.etheller.warsmash.parsers.mdlx; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.Iterator; + +import com.etheller.warsmash.parsers.mdlx.timeline.Timeline; +import com.etheller.warsmash.util.ParseUtils; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +/** + * A generic object. + * + * The parent class for all objects that exist in the world, and may contain + * spatial animations. This includes bones, particle emitters, and many other + * things. + * + * Based on the works of Chananya Freiman. + */ +public abstract class GenericObject extends AnimatedObject implements Chunk { + private static final Charset UTF8 = Charset.forName("utf-8"); + private String name; + private int objectId; + private int parentId; + private int flags; + + /** + * Restricts us to only be able to parse models on one thread at a time, in + * return for high performance. + */ + private static final byte[] NAME_BYTES_HEAP = new byte[80]; + + public GenericObject(final int flags) { + this.name = ""; + this.objectId = -1; + this.parentId = -1; + this.flags = flags; + } + + public void readMdx(final LittleEndianDataInputStream stream) throws IOException { + final long size = ParseUtils.parseUInt32(stream); + stream.read(NAME_BYTES_HEAP); + this.name = new String(NAME_BYTES_HEAP, UTF8); + this.objectId = stream.readInt(); + this.parentId = stream.readInt(); + this.flags = stream.readInt(); + readTimelines(stream, size - 96); + } + + public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException { + stream.writeInt((int) getGenericByteLength()); + final byte[] bytes = this.name.getBytes(UTF8); + for (int i = 0; i < (80 - bytes.length); i++) { + stream.write((byte) 0); + } + stream.writeInt(this.objectId); + stream.writeInt(this.parentId); + stream.writeInt(this.flags); // UInt32 in ghostwolf JS, shouldn't matter for Java + + for (final Timeline timeline : eachTimeline(true)) { + timeline.writeMdx(stream); + } + } + + public void writeNonGenericAnimationChunks(final LittleEndianDataOutputStream stream) throws IOException { + for (final Timeline timeline : eachTimeline(false)) { + timeline.writeMdx(stream); + } + } + + protected abstract Iterable eachTimeline(boolean generic); + + public long getGenericByteLength() { + long size = 96; + for (final Chunk animation : eachTimeline(true)) { + size += animation.getByteLength(); + } + return size; + } + + @Override + public long getByteLength() { + return 96 + super.getByteLength(); + } + + private static final class WrappedMdlTokenIterator implements Iterator { + private final Iterator delegate; + + public WrappedMdlTokenIterator(final Iterator delegate) { + this.delegate = delegate; + } + + @Override + public boolean hasNext() { + return this.delegate.hasNext(); + } + + @Override + public String next() { + final String token = this.delegate.next(); + + return null; + } + + } +} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/InterpolationType.java b/core/src/com/etheller/warsmash/parsers/mdlx/InterpolationType.java new file mode 100644 index 0000000..962ff58 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/mdlx/InterpolationType.java @@ -0,0 +1,11 @@ +package com.etheller.warsmash.parsers.mdlx; + +public enum InterpolationType { + DONT_INTERP, LINEAR, BEZIER, HERMITE; + + public static final InterpolationType[] VALUES = values(); + + public boolean tangential() { + return ordinal() > 1; + } +} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/MdlTokenInputStream.java b/core/src/com/etheller/warsmash/parsers/mdlx/MdlTokenInputStream.java new file mode 100644 index 0000000..bb01f82 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/mdlx/MdlTokenInputStream.java @@ -0,0 +1,19 @@ +package com.etheller.warsmash.parsers.mdlx; + +import java.util.Iterator; + +public interface MdlTokenInputStream { + String read(); + + long readUInt32(); + + int readInt(); + + float readFloat(); + + void readKeyframe(float[] values); + + String peek(); + + Iterator readBlock(); +} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/MdlTokenOutputStream.java b/core/src/com/etheller/warsmash/parsers/mdlx/MdlTokenOutputStream.java new file mode 100644 index 0000000..a2a07b9 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/mdlx/MdlTokenOutputStream.java @@ -0,0 +1,21 @@ +package com.etheller.warsmash.parsers.mdlx; + +public interface MdlTokenOutputStream { + void writeKeyframe(String prefix, long uInt32Value); + + void writeKeyframe(String prefix, float floatValue); + + void writeKeyframe(String prefix, float[] floatArrayValues); + + void indent(); + + void unindent(); + + void startBlock(String name, int blockSize); + + void writeFlag(String token); + + void writeAttrib(String string, int globalSequenceId); + + void endBlock(); +} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/TimelineDescriptor.java b/core/src/com/etheller/warsmash/parsers/mdlx/TimelineDescriptor.java new file mode 100644 index 0000000..1de3e7d --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/mdlx/TimelineDescriptor.java @@ -0,0 +1,38 @@ +package com.etheller.warsmash.parsers.mdlx; + +import com.etheller.warsmash.parsers.mdlx.timeline.FloatArrayTimeline; +import com.etheller.warsmash.parsers.mdlx.timeline.FloatTimeline; +import com.etheller.warsmash.parsers.mdlx.timeline.Timeline; +import com.etheller.warsmash.parsers.mdlx.timeline.UInt32Timeline; + +public interface TimelineDescriptor { + Timeline createTimeline(); + + public static final TimelineDescriptor UINT32_TIMELINE = new TimelineDescriptor() { + @Override + public Timeline createTimeline() { + return new UInt32Timeline(); + } + }; + + public static final TimelineDescriptor FLOAT_TIMELINE = new TimelineDescriptor() { + @Override + public Timeline createTimeline() { + return new FloatTimeline(); + } + }; + + public static final TimelineDescriptor VECTOR3_TIMELINE = new TimelineDescriptor() { + @Override + public Timeline createTimeline() { + return new FloatArrayTimeline(3); + } + }; + + public static final TimelineDescriptor VECTOR4_TIMELINE = new TimelineDescriptor() { + @Override + public Timeline createTimeline() { + return new FloatArrayTimeline(4); + } + }; +} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatArrayKeyFrame.java b/core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatArrayKeyFrame.java new file mode 100644 index 0000000..3af812e --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatArrayKeyFrame.java @@ -0,0 +1,99 @@ +package com.etheller.warsmash.parsers.mdlx.timeline; + +import java.io.IOException; + +import com.etheller.warsmash.parsers.mdlx.InterpolationType; +import com.etheller.warsmash.parsers.mdlx.MdlTokenInputStream; +import com.etheller.warsmash.parsers.mdlx.MdlTokenOutputStream; +import com.etheller.warsmash.util.ParseUtils; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +/** + * A UInt32 animation keyframe in a time track. + * + * Based on the works of Chananya Freiman. I changed the name of Track to + * KeyFrame. A possible future optimization would be to make 2 subclasses, one + * with inTan and one without, so that non-Hermite/non-Bezier animation data + * would use less memory. + * + */ +public class FloatArrayKeyFrame implements KeyFrame { + private long time; + private final float[] value; + private final float[] inTan; + private final float[] outTan; + + public FloatArrayKeyFrame(final int arraySize) { + this.value = new float[arraySize]; + this.inTan = new float[arraySize]; + this.outTan = new float[arraySize]; + } + + /** + * Restricts us to only be able to parse models on one thread at a time, in + * return for high performance. + */ + private static StringBuffer STRING_BUFFER_HEAP = new StringBuffer(); + + @Override + public void readMdx(final LittleEndianDataInputStream stream, final InterpolationType interpolationType) + throws IOException { + this.time = ParseUtils.parseUInt32(stream); + ParseUtils.readFloatArray(stream, this.value); + if (interpolationType.tangential()) { + ParseUtils.readFloatArray(stream, this.inTan); + ParseUtils.readFloatArray(stream, this.outTan); + } + } + + @Override + public void writeMdx(final LittleEndianDataOutputStream stream, final InterpolationType interpolationType) + throws IOException { + stream.writeInt((int) this.time); + ParseUtils.writeFloatArray(stream, this.value); + if (interpolationType.tangential()) { + ParseUtils.writeFloatArray(stream, this.inTan); + ParseUtils.writeFloatArray(stream, this.outTan); + } + } + + @Override + public void readMdl(final MdlTokenInputStream stream, final InterpolationType interpolationType) + throws IOException { + this.time = stream.readUInt32(); + stream.readKeyframe(this.value); + if (interpolationType.tangential()) { + stream.read(); // "InTan" + stream.readKeyframe(this.inTan); + stream.read(); // "OutTan" + stream.readKeyframe(this.outTan); + } + } + + @Override + public void writeMdl(final MdlTokenOutputStream stream, final InterpolationType interpolationType) + throws IOException { + STRING_BUFFER_HEAP.setLength(0); + STRING_BUFFER_HEAP.append(this.time); + STRING_BUFFER_HEAP.append(':'); + stream.writeKeyframe(STRING_BUFFER_HEAP.toString(), this.value); + if (interpolationType.tangential()) { + stream.indent(); + stream.writeKeyframe("InTan", this.inTan); + stream.writeKeyframe("OutTan", this.outTan); + stream.unindent(); + } + } + + @Override + public long getByteLength(final InterpolationType interpolationType) { + final long valueSize = Float.BYTES * this.value.length; // unsigned + long size = 4 + valueSize; + if (interpolationType.tangential()) { + size += valueSize * 2; + } + return size; + } + +} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatArrayTimeline.java b/core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatArrayTimeline.java new file mode 100644 index 0000000..20c4ca2 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatArrayTimeline.java @@ -0,0 +1,20 @@ +package com.etheller.warsmash.parsers.mdlx.timeline; + +public final class FloatArrayTimeline extends Timeline { + private final int arraySize; + + public FloatArrayTimeline(final int arraySize) { + this.arraySize = arraySize; + } + + @Override + protected KeyFrame newKeyFrame() { + return new FloatArrayKeyFrame(this.arraySize); + } + + @Override + protected int size() { + return this.arraySize; + } + +} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatKeyFrame.java b/core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatKeyFrame.java new file mode 100644 index 0000000..b03788b --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatKeyFrame.java @@ -0,0 +1,93 @@ +package com.etheller.warsmash.parsers.mdlx.timeline; + +import java.io.IOException; + +import com.etheller.warsmash.parsers.mdlx.InterpolationType; +import com.etheller.warsmash.parsers.mdlx.MdlTokenInputStream; +import com.etheller.warsmash.parsers.mdlx.MdlTokenOutputStream; +import com.etheller.warsmash.util.ParseUtils; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +/** + * A UInt32 animation keyframe in a time track. + * + * Based on the works of Chananya Freiman. I changed the name of Track to + * KeyFrame. A possible future optimization would be to make 2 subclasses, one + * with inTan and one without, so that non-Hermite/non-Bezier animation data + * would use less memory. + * + */ +public class FloatKeyFrame implements KeyFrame { + private long time; + private float value; + private float inTan; + private float outTan; + + /** + * Restricts us to only be able to parse models on one thread at a time, in + * return for high performance. + */ + private static StringBuffer STRING_BUFFER_HEAP = new StringBuffer(); + + @Override + public void readMdx(final LittleEndianDataInputStream stream, final InterpolationType interpolationType) + throws IOException { + this.time = ParseUtils.parseUInt32(stream); + this.value = stream.readFloat(); + if (interpolationType.tangential()) { + this.inTan = stream.readFloat(); + this.outTan = stream.readFloat(); + } + } + + @Override + public void writeMdx(final LittleEndianDataOutputStream stream, final InterpolationType interpolationType) + throws IOException { + stream.writeInt((int) this.time); + stream.writeFloat(this.value); + if (interpolationType.tangential()) { + stream.writeFloat(this.inTan); + stream.writeFloat(this.outTan); + } + } + + @Override + public void readMdl(final MdlTokenInputStream stream, final InterpolationType interpolationType) + throws IOException { + this.time = stream.readUInt32(); + this.value = stream.readFloat(); + if (interpolationType.tangential()) { + stream.read(); // "InTan" + this.inTan = stream.readFloat(); + stream.read(); // "OutTan" + this.outTan = stream.readFloat(); + } + } + + @Override + public void writeMdl(final MdlTokenOutputStream stream, final InterpolationType interpolationType) + throws IOException { + STRING_BUFFER_HEAP.setLength(0); + STRING_BUFFER_HEAP.append(this.time); + STRING_BUFFER_HEAP.append(':'); + stream.writeKeyframe(STRING_BUFFER_HEAP.toString(), this.value); + if (interpolationType.tangential()) { + stream.indent(); + stream.writeKeyframe("InTan", this.inTan); + stream.writeKeyframe("OutTan", this.outTan); + stream.unindent(); + } + } + + @Override + public long getByteLength(final InterpolationType interpolationType) { + final long valueSize = Float.BYTES; // unsigned + long size = 4 + valueSize; + if (interpolationType.tangential()) { + size += valueSize * 2; + } + return size; + } + +} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatTimeline.java b/core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatTimeline.java new file mode 100644 index 0000000..a38091a --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatTimeline.java @@ -0,0 +1,15 @@ +package com.etheller.warsmash.parsers.mdlx.timeline; + +public final class FloatTimeline extends Timeline { + + @Override + protected KeyFrame newKeyFrame() { + return new FloatKeyFrame(); + } + + @Override + protected int size() { + return 1; + } + +} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/timeline/KeyFrame.java b/core/src/com/etheller/warsmash/parsers/mdlx/timeline/KeyFrame.java new file mode 100644 index 0000000..bd4c9b4 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/mdlx/timeline/KeyFrame.java @@ -0,0 +1,21 @@ +package com.etheller.warsmash.parsers.mdlx.timeline; + +import java.io.IOException; + +import com.etheller.warsmash.parsers.mdlx.InterpolationType; +import com.etheller.warsmash.parsers.mdlx.MdlTokenInputStream; +import com.etheller.warsmash.parsers.mdlx.MdlTokenOutputStream; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +public interface KeyFrame { + void readMdx(LittleEndianDataInputStream stream, InterpolationType interpolationType) throws IOException; + + void writeMdx(LittleEndianDataOutputStream stream, InterpolationType interpolationType) throws IOException; + + void readMdl(MdlTokenInputStream stream, InterpolationType interpolationType) throws IOException; + + void writeMdl(MdlTokenOutputStream stream, InterpolationType interpolationType) throws IOException; + + long getByteLength(InterpolationType interpolationType); +} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/timeline/Timeline.java b/core/src/com/etheller/warsmash/parsers/mdlx/timeline/Timeline.java new file mode 100644 index 0000000..8f51d85 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/mdlx/timeline/Timeline.java @@ -0,0 +1,151 @@ +package com.etheller.warsmash.parsers.mdlx.timeline; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import com.etheller.warsmash.parsers.mdlx.AnimationMap; +import com.etheller.warsmash.parsers.mdlx.Chunk; +import com.etheller.warsmash.parsers.mdlx.InterpolationType; +import com.etheller.warsmash.parsers.mdlx.MdlTokenInputStream; +import com.etheller.warsmash.parsers.mdlx.MdlTokenOutputStream; +import com.etheller.warsmash.util.MdlUtils; +import com.etheller.warsmash.util.ParseUtils; +import com.etheller.warsmash.util.War3ID; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +public abstract class Timeline implements Chunk { + private War3ID name; + private InterpolationType interpolationType; + private int globalSequenceId = -1; + private final List keyFrames; + + public War3ID getName() { + return this.name; + } + + public Timeline() { + this.keyFrames = new ArrayList<>(); + } + + public void readMdx(final LittleEndianDataInputStream stream, final War3ID name) throws IOException { + this.name = name; + + final long keyFrameCount = ParseUtils.parseUInt32(stream); + + this.interpolationType = InterpolationType.VALUES[stream.readInt()]; + this.globalSequenceId = stream.readInt(); + + for (int i = 0; i < keyFrameCount; i++) { + final KeyFrame keyFrame = newKeyFrame(); + + keyFrame.readMdx(stream, this.interpolationType); + + this.keyFrames.add(keyFrame); + } + } + + public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException { + stream.writeInt(this.name.getValue()); + stream.writeInt(this.keyFrames.size()); + stream.writeInt(this.interpolationType.ordinal()); + stream.writeInt(this.globalSequenceId); + + for (final KeyFrame keyFrame : this.keyFrames) { + keyFrame.writeMdx(stream, this.interpolationType); + } + } + + public void readMdl(final MdlTokenInputStream stream, final War3ID name) throws IOException { + this.name = name; + + final int keyFrameCount = stream.readInt(); + + stream.read(); // { + + final String token = stream.read(); + final InterpolationType interpolationType; + switch (token) { + case MdlUtils.TOKEN_DONT_INTERP: + interpolationType = InterpolationType.DONT_INTERP; + break; + case MdlUtils.TOKEN_LINEAR: + interpolationType = InterpolationType.LINEAR; + break; + case MdlUtils.TOKEN_HERMITE: + interpolationType = InterpolationType.HERMITE; + break; + case MdlUtils.TOKEN_BEZIER: + interpolationType = InterpolationType.BEZIER; + break; + default: + interpolationType = InterpolationType.DONT_INTERP; + break; + } + + this.interpolationType = interpolationType; + + if (stream.peek().equals("GlobalSeqId")) { + stream.read(); + this.globalSequenceId = stream.readInt(); + } + else { + this.globalSequenceId = -1; + } + + for (int i = 0; i < keyFrameCount; i++) { + final KeyFrame keyFrame = newKeyFrame(); + + keyFrame.readMdl(stream, interpolationType); + + this.keyFrames.add(keyFrame); + } + + stream.read(); // } + } + + public void writeMdl(final MdlTokenOutputStream stream) throws IOException { + stream.startBlock(AnimationMap.ID_TO_TAG.get(this.name).getMdlToken(), this.keyFrames.size()); + + String token; + switch (this.interpolationType) { + case DONT_INTERP: + token = MdlUtils.TOKEN_DONT_INTERP; + break; + case LINEAR: + token = MdlUtils.TOKEN_LINEAR; + break; + case HERMITE: + token = MdlUtils.TOKEN_HERMITE; + break; + case BEZIER: + token = MdlUtils.TOKEN_BEZIER; + break; + default: + token = MdlUtils.TOKEN_DONT_INTERP; + break; + } + + stream.writeFlag(token); + + if (this.globalSequenceId != -1) { + stream.writeAttrib("GlobalSeqId", this.globalSequenceId); + } + + for (final KeyFrame keyFrame : this.keyFrames) { + keyFrame.writeMdl(stream, this.interpolationType); + } + + stream.endBlock(); + } + + @Override + public long getByteLength() { + return 16 + (this.keyFrames.size() * (4 + (4 * (this.size() * (this.interpolationType.tangential() ? 3 : 1))))); + } + + protected abstract KeyFrame newKeyFrame(); + + protected abstract int size(); +} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/timeline/UInt32KeyFrame.java b/core/src/com/etheller/warsmash/parsers/mdlx/timeline/UInt32KeyFrame.java new file mode 100644 index 0000000..0f74c21 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/mdlx/timeline/UInt32KeyFrame.java @@ -0,0 +1,93 @@ +package com.etheller.warsmash.parsers.mdlx.timeline; + +import java.io.IOException; + +import com.etheller.warsmash.parsers.mdlx.InterpolationType; +import com.etheller.warsmash.parsers.mdlx.MdlTokenInputStream; +import com.etheller.warsmash.parsers.mdlx.MdlTokenOutputStream; +import com.etheller.warsmash.util.ParseUtils; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +/** + * A UInt32 animation keyframe in a time track. + * + * Based on the works of Chananya Freiman. I changed the name of Track to + * KeyFrame. A possible future optimization would be to make 2 subclasses, one + * with inTan and one without, so that non-Hermite/non-Bezier animation data + * would use less memory. + * + */ +public class UInt32KeyFrame implements KeyFrame { + private long time; + private long value; + private long inTan; + private long outTan; + + /** + * Restricts us to only be able to parse models on one thread at a time, in + * return for high performance. + */ + private static StringBuffer STRING_BUFFER_HEAP = new StringBuffer(); + + @Override + public void readMdx(final LittleEndianDataInputStream stream, final InterpolationType interpolationType) + throws IOException { + this.time = ParseUtils.parseUInt32(stream); + this.value = ParseUtils.parseUInt32(stream); + if (interpolationType.tangential()) { + this.inTan = ParseUtils.parseUInt32(stream); + this.outTan = ParseUtils.parseUInt32(stream); + } + } + + @Override + public void writeMdx(final LittleEndianDataOutputStream stream, final InterpolationType interpolationType) + throws IOException { + stream.writeInt((int) this.time); + stream.writeInt((int) this.value); + if (interpolationType.tangential()) { + stream.writeInt((int) this.inTan); + stream.writeInt((int) this.outTan); + } + } + + @Override + public void readMdl(final MdlTokenInputStream stream, final InterpolationType interpolationType) + throws IOException { + this.time = stream.readUInt32(); + this.value = stream.readUInt32(); + if (interpolationType.tangential()) { + stream.read(); // "InTan" + this.inTan = stream.readUInt32(); + stream.read(); // "OutTan" + this.outTan = stream.readUInt32(); + } + } + + @Override + public void writeMdl(final MdlTokenOutputStream stream, final InterpolationType interpolationType) + throws IOException { + STRING_BUFFER_HEAP.setLength(0); + STRING_BUFFER_HEAP.append(this.time); + STRING_BUFFER_HEAP.append(':'); + stream.writeKeyframe(STRING_BUFFER_HEAP.toString(), this.value); + if (interpolationType.tangential()) { + stream.indent(); + stream.writeKeyframe("InTan", this.inTan); + stream.writeKeyframe("OutTan", this.outTan); + stream.unindent(); + } + } + + @Override + public long getByteLength(final InterpolationType interpolationType) { + final long valueSize = Integer.BYTES; // unsigned + long size = 4 + valueSize; + if (interpolationType.tangential()) { + size += valueSize * 2; + } + return size; + } + +} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/timeline/UInt32Timeline.java b/core/src/com/etheller/warsmash/parsers/mdlx/timeline/UInt32Timeline.java new file mode 100644 index 0000000..4d349f4 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/mdlx/timeline/UInt32Timeline.java @@ -0,0 +1,15 @@ +package com.etheller.warsmash.parsers.mdlx.timeline; + +public final class UInt32Timeline extends Timeline { + + @Override + protected KeyFrame newKeyFrame() { + return new UInt32KeyFrame(); + } + + @Override + protected int size() { + return 1; + } + +} diff --git a/core/src/com/etheller/warsmash/util/MdlUtils.java b/core/src/com/etheller/warsmash/util/MdlUtils.java new file mode 100644 index 0000000..08eb95b --- /dev/null +++ b/core/src/com/etheller/warsmash/util/MdlUtils.java @@ -0,0 +1,8 @@ +package com.etheller.warsmash.util; + +public class MdlUtils { + public static final String TOKEN_DONT_INTERP = "DontInterp"; + public static final String TOKEN_LINEAR = "Linear"; + public static final String TOKEN_HERMITE = "Hermite"; + public static final String TOKEN_BEZIER = "Bezier"; +} diff --git a/core/src/com/etheller/warsmash/util/ParseUtils.java b/core/src/com/etheller/warsmash/util/ParseUtils.java new file mode 100644 index 0000000..933b6cb --- /dev/null +++ b/core/src/com/etheller/warsmash/util/ParseUtils.java @@ -0,0 +1,26 @@ +package com.etheller.warsmash.util; + +import java.io.IOException; + +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +public class ParseUtils { + public static long parseUInt32(final LittleEndianDataInputStream stream) throws IOException { + return stream.readInt() & 0xFFFFFFFF; + } + + public static void readFloatArray(final LittleEndianDataInputStream stream, final float[] array) + throws IOException { + for (int i = 0; i < array.length; i++) { + array[i] = stream.readFloat(); + } + } + + public static void writeFloatArray(final LittleEndianDataOutputStream stream, final float[] array) + throws IOException { + for (int i = 0; i < array.length; i++) { + stream.writeFloat(array[i]); + } + } +} diff --git a/core/src/com/etheller/warsmash/util/RawcodeUtils.java b/core/src/com/etheller/warsmash/util/RawcodeUtils.java new file mode 100644 index 0000000..4a85638 --- /dev/null +++ b/core/src/com/etheller/warsmash/util/RawcodeUtils.java @@ -0,0 +1,62 @@ +package com.etheller.warsmash.util; + +import java.nio.charset.StandardCharsets; + +public final class RawcodeUtils { + private RawcodeUtils() { + } + + /** + * Convert a four character string into an integer.
+ * Do not use characters outside the ASCII character set. + * + * @param id the string to convert + * @return integer representation of the string. + */ + public final static int toInt(final String id) { + final byte[] bytes = id.getBytes(StandardCharsets.US_ASCII); + int result = 0; + if (bytes.length >= 4) { + result |= (bytes[3] << 0) & 0x000000FF; + result |= (bytes[2] << 8) & 0x0000FF00; + result |= (bytes[1] << 16) & 0x00FF0000; + result |= (bytes[0] << 24) & 0xFF000000; + } + else { + for (int i = 0; i < bytes.length; i++) { + result |= (bytes[i] << (24 - (i << 3))); + } + } + + return result; + } + + /** + * Convert an integer into a four character string. + * + * @param id the integer to convert + * @return four character string representing the integer. + */ + public final static String toString(final int id) { + final StringBuffer result = new StringBuffer(4); + + return toString(id, result); + } + + /** + * Convert an integer into a four character string using an existing + * StringBuffer. + * + * @param id + * @param result + * @return + */ + public static String toString(final int id, final StringBuffer result) { + result.append((char) ((id >> 24) & 0xFF)); + result.append((char) ((id >> 16) & 0xFF)); + result.append((char) ((id >> 8) & 0xFF)); + result.append((char) ((id >> 0) & 0xFF)); + + return result.toString(); + } +} diff --git a/core/src/com/etheller/warsmash/util/War3ID.java b/core/src/com/etheller/warsmash/util/War3ID.java new file mode 100644 index 0000000..e42e3cb --- /dev/null +++ b/core/src/com/etheller/warsmash/util/War3ID.java @@ -0,0 +1,83 @@ +package com.etheller.warsmash.util; + +public final class War3ID implements Comparable { + public static final War3ID NONE = new War3ID(0); + private final int value; + + public War3ID(final int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static War3ID fromString(String idString) { + if (idString.length() == 3) { + System.err.println( + "Loaded custom data for the ability CURSE whose MetaData field, 'Crs', is the only 3 letter War3ID in the game. This might cause unexpected errors, so watch your % chance to miss in custom curse abilities carefully."); + idString += '\0'; + } + if (idString.length() != 4) { + throw new IllegalArgumentException( + "A War3ID must be 4 ascii characters in length (got " + idString.length() + ") '" + idString + "'"); + } + return new War3ID(RawcodeUtils.toInt(idString)); + } + + public String asStringValue() { + String string = RawcodeUtils.toString(this.value); + if (((string.charAt(3) == '\0') || (string.charAt(3) == ' ')) && (string.charAt(2) != '\0')) { + string = string.substring(0, 3); + } + return string; + } + + public War3ID set(final int index, final char c) { + final String asStringValue = asStringValue(); + String result = asStringValue.substring(0, index); + result += c; + result += asStringValue.substring(index + 1, asStringValue.length()); + return War3ID.fromString(result); + } + + public char charAt(final int index) { + return (char) ((this.value >>> ((3 - index) * 8)) & 0xFF); + } + + @Override + public String toString() { + return asStringValue(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = (prime * result) + this.value; + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final War3ID other = (War3ID) obj; + if (this.value != other.value) { + return false; + } + return true; + } + + @Override + public int compareTo(final War3ID o) { + return Integer.compare(this.value, o.value); + } +} diff --git a/desktop/build.gradle b/desktop/build.gradle new file mode 100644 index 0000000..509f1ad --- /dev/null +++ b/desktop/build.gradle @@ -0,0 +1,55 @@ +apply plugin: "java" + +sourceCompatibility = 1.7 +sourceSets.main.java.srcDirs = [ "src/" ] + +project.ext.mainClassName = "com.etheller.warsmash.desktop.DesktopLauncher" +project.ext.assetsDir = new File("../core/assets"); + +task run(dependsOn: classes, type: JavaExec) { + main = project.mainClassName + classpath = sourceSets.main.runtimeClasspath + standardInput = System.in + workingDir = project.assetsDir + ignoreExitValue = true +} + +task debug(dependsOn: classes, type: JavaExec) { + main = project.mainClassName + classpath = sourceSets.main.runtimeClasspath + standardInput = System.in + workingDir = project.assetsDir + ignoreExitValue = true + debug = true +} + +task dist(type: Jar) { + from files(sourceSets.main.output.classesDir) + from files(sourceSets.main.output.resourcesDir) + from {configurations.compile.collect {zipTree(it)}} + from files(project.assetsDir); + + manifest { + attributes 'Main-Class': project.mainClassName + } +} + +dist.dependsOn classes + +eclipse { + project { + name = appName + "-desktop" + linkedResource name: 'assets', type: '2', location: 'PARENT-1-PROJECT_LOC/core/assets' + } +} + +task afterEclipseImport(description: "Post processing after project generation", group: "IDE") { + doLast { + def classpath = new XmlParser().parse(file(".classpath")) + new Node(classpath, "classpathentry", [ kind: 'src', path: 'assets' ]); + def writer = new FileWriter(file(".classpath")) + def printer = new XmlNodePrinter(new PrintWriter(writer)) + printer.setPreserveWhitespace(true) + printer.print(classpath) + } +} diff --git a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java new file mode 100644 index 0000000..dc45a62 --- /dev/null +++ b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java @@ -0,0 +1,12 @@ +package com.etheller.warsmash.desktop; + +import com.badlogic.gdx.backends.lwjgl.LwjglApplication; +import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration; +import com.etheller.warsmash.WarsmashGdxGame; + +public class DesktopLauncher { + public static void main (String[] arg) { + LwjglApplicationConfiguration config = new LwjglApplicationConfiguration(); + new LwjglApplication(new WarsmashGdxGame(), config); + } +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..ff329ac --- /dev/null +++ b/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.daemon=true +org.gradle.jvmargs=-Xms128m -Xmx1500m +org.gradle.configureondemand=false diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..bafc550b87fe65a78f9b11ebb03e513ae6e0a2dd GIT binary patch literal 54208 zcmaI7W3XjgkTrT(b!^+VZQHhOvyN@swr$(CZTqW^?*97Se)qi=aD0)rp{0Dyr3KzI>K0Q|jx{^R!d0{?5$!b<$q;xZz%zyNapa9sZMd*c1;p!C=N zhX0SFG{20vh_Ip(jkL&v^yGw;BsI+(v?Mjf^yEx~0^K6x?$P}u^{Dui^c1By6(GcU zuu<}1p$2&?Dsk~)p}}Z>6UGJlgTtKz;Q!-+U!MPbGmyUzv~@83$4mWhAISgmF?G;4 zvNHbvbw&KAtE+>)ot?46|0`tuI|Oh93;-bUuRqzphp7H%sIZ%{p|g{%1C61TzN2H3 zYM3YD3j9x19F@B|)F@gleHZ|+Ks>!`YdjLB;^w;?HKxVFu)3tBXILe21@bPFxqwIE znf7`kewVDrNTc3dD>!$a^vws)PpnUtdq<^;LEhuT$;)?hVxq?Fxhdi{Y)ru*}G{?0XIuDTgbgDhU{TZBc@$+^ay*JK-Y1#a7SpO zHyWJnsR7T|T~Bv6T*n>U;oojNGn}}GOCkMk$tSQ6w{djY2X8sv z`d;xTvUj&RwNbF9%Uq2O~P)32M5LhEvu)YifH{1z#~{bWNWb@jLMh zVUJV2#fMpMrGIr%9Y7o#C)zVd+KQX8Z)V`&oL^y}Ut?pT;i8{o%0fdIdjtoI5(~Y{ zl$R_`XQt0k0VLP&_!>>&wg55P~iFB}0=c!p}&pO(~&fo}p9!sAW37Mf!kAsUZ4@ zwYFm>c`ib_KqQ|-f1mK47)b3M%)Z2KT)vjM>^`gn=~VsD%Iyl77GI{(9#eGF0Ao6S(TAGLd+S<_FpyMWx={C_7^bT$Bbrg{4Bex-6CxC+|3- zq-eUnX4He-g``+N04TM@rr|3$bFmDJz_Oxtgj-HMLL}x?xt0LJZOW+8cgLnDeSviP z+~H_$+_wl(UWUCKktl{p{0p7l8GOP((+bpm>KqIG{0Nc^gP2jVEgeGC1)41Qmf$GA ztV|uyJTjG?BbIT|YCPeWKDTUGMHyo??xB-yw_N?@6)--PTy6=|ge97~FsHIA6+Zlj z?>&AY_|8}uVjW^javZJ#ZHh9@$;1T%RK%qs3oX3Q{|U=4C0pAP;TvE&B?eaxJ+_g}vtIrE=zaCbk^9am`Fyhw!*X zf(5y2gXmQUWg)$8X>C~+g}k_F8P+fni0nq}RN_pq`P0P^!I*Mp(gK0|RlKIWBA6z+ zZvXp_Hp8KRiwNMwLun?;)l})q>G{HkK^3t@znN?AGnI5!^ogl;>Cq#F|Orith$uD5^dob0h8vyOzOu2MKJUyq{(MIx-^e>y#K0oqJug- znT^aGBM&`u6gvDu6;_!pIhv`i?^JJ3pDprdv}(_9;+=Ub<&Vj_z7nL#{lzISdygW$ zS;Mm_eAx{{ZeO`u(NFR~UdmTUQehNB{7>b+o!b|<@4Vfd*OWj(U=bxEug6FmX;Iuc zldB0@l*UM&GRw8n>=)-VlXN+q$~%nY>?zH2by=_U&1$aGwXNL`A>|})<{n{soC{$f z6i{}Rq~K;U@!0~l0*!C)-VOGv&L>;)DIe{~MOx}*9-Ilor5hAU<|QurOl76NzoN3V zFz=oQ*mRGk@zvH6bG=PAVuhP#vQ)|NqkokQjR$y!VE`vqM(9pk6O3%eF#5L)yu2A+ zs*{Pv!F6}w4%j=vsHRJRBQFSruEA8b+xm116n3s9l*X^2CIqvWhj3h>YKD7;Vodb*~~wfg>xvIfk;u|-e5I|v(RV` zfVcu;xAAxGfjJ}RpiGe>hrN<&TjLbp$?XY{pD8hDB;3DtAmV zOU8|p1xwqShBr-NT}{v1+|S!xNU5h>%WD}IS5wdewOiX8W;fOdo*A_H&U|h?L(e>Y z+pdZ5JuYFFG5hLVA*lzhsL6A!QJrgiynro+pe}MwuJMaD?c>~oZ86oJv^p`~seL|~ z1ArVq0QgvgpqnwMr|XIY4uJWp1|TCsL??Ec(|na|KJjYy28(mJ+-pqtRmNvp*i%Bn>YoSNj+$8+o{rJE{3LOmHi-8jE|VJk_ot%f8pC+4sRyV(3# zW3O2ekaOSg_hUNR7YtwtYU4(m-K}~6*>ToXhNBN4SJ^3&JH}VFGf2J)odBc@>*Gl- zu!@kC8GN(Z%CmDFt?t)BFVTrrZ!TnsPU=#=U$g_cdL4gn$zU5h5vGgRrg@pWEHx`Y z|LMgbYmX`<5rDTUZj18LN6hc9Y_ch?Mvg14mUt;M@RzemPs;Q4n8`|C<7dRgZGJHI zwVvX>w5PjdBjX<^bnISW$31*#3Mt_V3Ao-Pm*S)!i<{%`o-C~T>iy;u%@3-6-z`da z;}xiz)MqEgBfPGcZ39Q~i%t-b3?ye+s zkV{&6m%A-gUR^>9Cg;E*M8+;83~U?~k$A^f&yHwE4pT*`ItMWs>*JDDl0*7UOs3rb z{N%7rt%axd2NKO377KmHN-?%orIejNHen&@RYXd9e{|0?3Z@QR&K_88nhI*wn zl_95|n6VThK4AIQu(kAlGG#LYNFwEsi~vd_%0*~WeMfzssz;mj4JG${`-^wNa@^*u z?1Se|Y4gsSwq$N7$s7O8lxI5YL)Oh?M$6Cl%*79o9n4SU9#^DbV)ckzuSjG(`2aL} zwyJ#Mm9)AVg#`Ve-l&XvA!>fDv5SG+-nff!a0Z3VkR6sLz14*8$!#4O56%GT?HC$Q z5UTKdWBAPI=Ng*Kfg^*L&X6^-Zs>jlJ<+WKk}kp#?ZhoI{iAYRH_Fh8@wW)lPUOBO zy%**V{0Xh--4K$N^hncGQ@CX^6{yB?J(OpDDQEN^8Jn}a zkClUmg|oT7h0oKtm5qh7zC918qdLFWd$5n<43cw2ta>hB1zq{>t``4oEHts?wEyHs=F{&{>VYY$DN|T5^;50-h$n*X8tDV$ zVr~9Nk&!g~n6K}EH8Uk&F@*5|$fEErn^6)H8!_VPoN7$moX&?~o% z!6kGR_z~thhh53cpJ1*`T)(qa+tG*IhNzCAH3wpZPe@O&rOclYvKv_ z$Hytrd^BA-$jHy+Y|Qan157h8Y#;?EzO(dW?&*I);tr@ysC4#JwcOXX^jUhA$=kjE zJfioI8g;!`WvNYLW4-xBl{dVBfX8L;w$#Wu$YH1zDokI{a0e!=41*dG;R1vbHGEHp z88sW%D^$I^8JgM;&}_x0%tdqs#BdypVQMz43>ih(iH+fx)VuUpW=ol9ek9@GA_dT18;t9-Mb&B2VurL628tpA$#ZPxIjlxWVD(7rsfn(hajk_}%sP9xNhl zrJ{)y=?ZENjKlW>@fHaLx`TaX7bSGN=!p~g5#y22p|5_@a+hV=mdqo3 zCuyRIO;)UZ1<=N0Ml8GsSAZ+d8gPqO2u%0N1Y#K13SxsT46W@7M`X^-G#AdceVFsls%T{Z^LV&`j4|WDsRZ{7y557 z5BiXpTcO`?X(K>&nMIwU#I)&g9PjW{o~Ij!#IUhElGfxc)lQ#Q$iOjA+x%=@2{t!X z`&-aD`#Mar42lblnS=)o**}54&DVL5xKCWAi)ww!HKT85aIf`c)Gi*QBZ6)C;(fhE zJRDf-=;x5!szU?NF{J3|Xp*V+W|4&ns|StSqY|=Pmay6SSXTCIe#$ilOgaR2wCa1V z;=4b@*@z+}3wK7y0X2B(?GepcPFzP-97U%GXP$aA!LCHq{9S{hYNR@IM%Stzp4(;u z?@Sj@=pNq5>}tl&r=HbUM%ZUW%l=T6o+l5Jxk}i&A}ZJ&<3In4q%mB*PPhMCE8(C3 z02u$hRtmcrS~)wKyBLd@TN(2k8X7w~O6%L`oBmJX)O5r&Mfc%RpI^Ut!nfI1VXsc$ zBPMN*M-hvYE-e`556f(=GdOQ%(w5Y{j8g3|Xp%6%LxM18Pga!NfJ@yA)}fo6MK33E z3$_Dg)Ec;jY`uhLowVb3>(*YoBfnl`{EoiabKiM++g{rFei`8fWDD0lbHgfv@j^gd zq^sJC;MjMQ8HkJ~lCXH_)aaUxMqT&*6*^pP62#?kg%POWZPqiHB zjK-Gm`fY`sQkQFkg{|Crb(`3w!P&hDj_ZsKh`~|4YXNj#b27M))fy}etvh$C46TcJ zN}WBC)5fMlmfgwbtnbx%o5`npSMNMD&XLTSk_F+lk%b9=I__!1UAw8b?tr0?OITYm zZwZ3v3@8tGTJ0XKXa{_zTZiSGiq)je$wm_^h6<5p?&r2$Ay-#o)^TrDz(M&H&wL?v zG()L5-FUQNvBMGh`+=p(C?cCTCF`LooUlRFyFw+w=lQUyexY`Lp-*=GxT%AC59vYJ&WHijkfN>?*}Xx%{_#wN<6Q3-=x z#yg8RzNweQR4j?ybGpetSoSMyPQk`7KgPFGL0E0 zg|d`R9ScEK^)03o*8-GQ-qY{-RbB`#JXlx*w?%|i?OFj27IiqI6cxuB)g`4fznbzQ z=t66!^#15RjJ#FZ2tt?};n9t1Lvg$-&Fr?zHbGC@Z$lGK+=00=CYmemy!LIt1$6N6 zS=qh(HuL0F;=w2%Vu!KYjDf-8V};oV&rXfQ$Q~@o#|6*Bgs)C4KwHTfHYF2gt%E=~ z1sYV844uKUAgBvGoU}I6YG$3AD{(Z-e_)Ah5bT^9QoJK+x7jaE@7NJ8N%yod&;##c zq~7YbR?2tUslO(C5u(9&5D%{RzJ(3ls*N@$ScyA-r5s*V?|D9^#?tJMPRr~5-f&|| z5hG4_qe_t?&JYXofBA`%*zTKF@&}e~+-eQbzS;U|V4!bYf3kU3qDfy}Xi2#cwA91u zj_?Lz=NH$77i>?Pf1aOj}Wer%O5^pQg2XI&tg@}X|aQ9xmEwfVE_C@_)0A@ zSGbHYe0oR3Gf4i43Hljw_0hu?@Ie-iHVqD)AY?D`Sb*oU*SI=y?DNMJeH**aXfzIW zEEVH=en4^dv`L(oJv;9AMCYDGAdYbBJ63c8>xcQn1DBAQA>FTxCXeW`yB zVT|dk=M&LV6!Mh4MYhG<2jZ*1=nl}&+nl-lSJ*9#SxOy z?b$iv;=He)Bb670FaOG}HWrc_?A`tcSF~bngbktNmslVzr3`Y`*o^@}`<;VXcMii= z=FGm2$Z2w-t{?Y9bN!c3eTM3yvIysmd zI6Il!+WZ&kub?T3$&d6sZL+oGRAJxLysp{k9%^~9zOO0Cj{t(-7=(iBMJ5%GFVnsT zogf|YBhe>!o5$OWtIWk1JYNduwVLMmLF2eO(Szy>&^c7WKB-p)1}iK5IEgjm-T5d_ z@@maI8l#j$w{sevL!hGGS%dKAvsq3leS2@nTzUz|f{}JTh)um77U^p~cO!}I3;%Yv zt%v71C1f$|j;mCD9~0Ph{&*)oH)iz^ySrT9Ohm<`M8ON~DP7hB{tKaBWEo*BZ+86f zAm1_)0mZsz`nkyh#xbcVa2HRysG8Wn$lb`bylI>o!AEm7?(K)TBU{1w;rKe7YebV7 zom96W&t~j`C=+gtr4>M!3k*(=yBEs@_%-#Zj^EAIH|BC!LtJP*jF+{eJ_!**xncaC ziKX%(XYY!$@Wo1Avwzn^ zPfE}$xxI4jvV^r|P&w5rGW2kuo|IImxq`L9 zyCnpoTEiCp0N#LriHe0Nio6-=zo=rPncSuGj1@+m5CtzTfZ9zJI4YTL!-s_C|powj7a%txF*KQ(sgv@^^Fq6{h218-K34C$?^mfUa*|L-w z?9l+DEk8JVrcj#Pj>?DOyTZivZ6|Rr!O?m%`kW(CV35Nos1;(Ij2fs}S#FWLOpe-i z2&lK72Yv1-iGGA`i6|fz7<$NsAX}|3worY-PRsm!L(~& zF%V64k%>!j>#dHjkdkS<=~pPQVH&tG1iZ$Sot>eD&DJj;mzN`v!q<7}_YB8o%^CEV zRJ$5ar>Yh74Ew$1ho)*4iZ%#w#!z+PQCZ;<-UnrZ%{LB*^u@G_RWK6t4k6dm8^vOi zs*+pOUb+hHwACR}wc4+6@b6R7U=4h8DPJ!LwOy8C`H^d3rg%!QFf8|*SdK-48Bz~x z_C4vZpU3(Fr;N2963h1zueM5{oDJIkGr^2JCU@fhCKvZ#p_T666HL+F(aG5QZ+89F zBc05R9mVu*{)(CZMKMLGXew$dBYm@ov*BZncQJ`+7B&THD$t4%H&P%GAp;SE73rMg zXOe^jJMNE(1KK{lYv^K`o(I^%OtVcdrqGQ>dcTO4?Z^-uE{_}4Kd)PQdtNp5G_A;d zzkkH=0(OSldY=vz`jg|H)13`COHroY^$|wdzUAtv$Pg%W%Cpmm z)sYQJ<0?^!yH&zZxRt}qerk7WQqzHlUubrT5*JxYd21*th(^py+7g5K zbrD{*0kGDNd<3{(b%~OONM{9sUm=9xuuYA;gWvVRU`lB}I20DBI`T_i#p*B& zt;lg`Zmz#JGVTE)a?U;@a?XKYIPGnbe~pq?lr6|F*=+?N>ZBAkKI)<&wlT8D8H{m*1(^qX#M5Zs~^uY9_HY(sgHR5yrRiBe_-U6uCrAQc64e zU@d95dqi)+O9UxR6|!e00zhixU>_U_+A~NiuD=MF)g6cr z!)U%>KSa}*le&IsOYJ&Fg#|t$))2q~6`k4T z8N6{9<2Cl)J{A3=Kn+0mhd&w`t)EU_i>f;yLu|K2aIxxYfSENl;6v0c7zejsQ1I&$ zKapAFStLZ%!EAS><+t-DHFD3#7>-9lh};UyoX}%g^D&kNT0V0~bDVc0FZy)e0YDbe zTpVyFid*1?Qai}-mX9lp>G~(T6L0_R++iD*$1t}KY*WrG`{B!>w&@vnFFUHr%Qrik z2Ndetsc3B2Z+mv$cluy^rg=hGTw%^5bvJvMsl&P?sP{2lT=k0+)6hl`_Go!bQfhsK zhH&`RMjpHZSoEjg-}-N$HM^>j$KqNBjXX{W$cHrgk8rMO>w->*YoZ?3o#83B4CG68 z0hFR=#7&LS_K*9fT78yOLAX1PD|C`{@>DW?u1V`nUVyqK&muaW54!){-?A#uUKjt8 z0W7fp-x7h1qm#as6%qY^f~Ks$)B}<#x{vHL!-UBnI1M{ZvpJDfDrm?&IdDG+aBIO7 zK1=}+L+5%3#c_47lN5t(D z72Y$f_o_$49UxP>fnm>nhbChvPEC(QJu?vbQv>ei8-c~VLV#=Y`{ zyiB$E@}}T@gQ+3)3)RM`Mvv2u#x|MAM14TDE$H1Qpb|Hm!}yqZzMj6~6wPO-V8uHE zIekC2?=Ac!EjkC=;2T7&qt?)7Xd**j;!$I{B@_eFvv+L6ChdsF=zW1kb7;khE2icG zt=A^&t4Mdm1^s#e2Ak8qC;CM%C7RzWpgUdg?3DyZNo_--;0t+zCN(=c!i|5V<83q^$>9^jYxY_Y&AT@s7w(?6IR>jTJ}ovoqtf{CONXPfB(nIXG?*K zv_iwOtk!4D0KsU$D4Pqyb(0OI@0fex7C4;p(qcnoo#l_Pt_~43wx0XkV+$o%oBK$WL#QLM z{dERKhszLa4B9snqT%6#Nt(7B<%ivM@`q)HHIsw0DW+*ucY*i}`U@3H|6~92=7tBu z5M;kZgP%)AuC?wk$9glV>NGV<8%mZj~TT znW@zaG*6L;2x8FNNQb6Edo7bcCI54Lov1d>C-or0_@ch;&rYpoBx()nqXl>;zJpHs26q$+#~UgR2JePYBZWD2A;z** zDuXm7FO<7UWwRQ&24Gmb$OW9pADw8A+fMioI;ggQJF$F}E?2IgR5w*xUD18FV+f9N zH5cr$1Jyb7>PL!X*P30qq4A2&FFA}dgC*h09WCJ(;mSO|FgmX~511Bh80rq)KPX*+ zW=60pbL^Wu?bie{wCJW&UYUMo6dFV8;CDPBu8T??ib|&y`!E#B_NK26S*^0dHTvEl zWoD;W)nOc!?3>(hokwq6aFRpSds*SA(cJfsG(oJfXrV12Z6W*$_SeKhijaxnGkK=_ z^S(MY?$OG3*Ax}~Zl8BY#VD-i=^~Naqd{5p!SB2tCLzg zoN?jWFst}W-dL9G&xF!4R|Gi@M)O4ON_Zi~WBDhCI3h6G`bj&5Lpyc2KfQ3@LHbQN zzZXe#BpBS(p!agicj27@Llz&CJ-}mrRi+Ixyt@Oy(#s?!XWY@{?7xz#Gx-M? z!MH0PC~0tqiN31nD_|3)3m&TSUyYEZ;piW>*riHEGYnIB+>~4yGV28245RIl5z9*q zcRa`CjR*w)(v7QSO)ks7xkq@6Udo;9*kgk~?SUN$cmvtS?aUbboeFX5t2{Kr^!h>j z&zgASp^dSPfDuA+VKzL(TuAN5~HWY?N7u* z;U*hv^(l9EA`U{76b7`C?6n7yqi?At*$EDJjEc3k{r*x*u%irpX>Hr^a?hc4^_MfQ zB&5Vg1vwb$j1(jjTZMyTD?m@@ChbLys)B$^Fo^~~l`;RNNrSqQ<}9tf5{4j=rmn23 zOdYjjDKxh|D*g(+)_n30#e; zrlB&+&Yg&THMR9hn%4bm%49}r(thGWQ@z>TvRFPoSDySnJx;RBn6RUd>i48wBf0F< z=uqdel4w(9fstNSPz_@MT7Ui@m?#*Bb*jHnyJkTf$TZW`WNiNOpp1BkA3CudfD+uI zecGD|xs+u6v3eA%gTEoDy0HKO8<7+3b^Cy=;ORU>>{~4CyMoz#`r01UkgN^_!?R1W z^_Y!i`$S*W_-1I{#^1He0|RA|yuxQnqjfOi+tm#^!60}>N>LrCc^ARko2Lgp1o~25 zCHe%tr2lNS7I(E4A0W1nQ6>l4B6&sJoFZR(=#XPJs~B-6A<^Y9O?c24q`C-|yy!KA zcJ&d^G>4ipI-G4v2r+Uw$P_S`T^QToGw`Tj#8AHC@ZQe)AklsEdPb+4veveTem1*% z2kG$1GO6tRj%bJ?)~XaQ)*wapnxEG1D@G6%kNRS{&(GNf%2e^dC zBi=B5tzIw{_&#f(iO_+9o>LLEi0m8^`Xjt?LkxQXgkEe3!Az?dg0O=}O%WnX($gPh zfhp_kK}#a%@?^-A7mmAayl}C^1*4#Dyrx8zF~dL46SDNFX;4=c2EL$sMP;Ur-HQ8v z+)hm+rJzGe-F{J^L135e?h=CZf9v9g_tXA-KOluL4Sa$;P^+&Gh7H7^I?c!K@CXa)ja&8#UC-etu4?M+p4Do7U+ zo1ps5jBU-`Oy^`771U@XfkDpUl%x>U?iWJZk|Vyp6_Ee}4s;^zQ7GGzvSOSVEB$0X z?Me)`U=O^pPUvvlUM0AJvjk8AB51#GL!t(tovE?C|CfAPBlWB&dQU!$}YoI8d9Rx zK5L8CKckM5!?+(4TIzzLgi*@*qYfNAY~b~wNM4)bJ!!EGIEG?UGN!OJkXs_<r2(QEvMBbQX}G>ErdB+ZtJRo;yuUZJpc_U$E!yQ21mXP!KAU^ChICNq zE0XyLwJdHj#vu^s!>8~KPLkq-cb`-V#v)ctC~?nVuu38U&pvbC8J7H;OIpr6YgGVW zuNx{={f(0#C+;)Y%sY6Mp%nz&c)o__PlKafvP?6#9Xu!Ct1`g!+ioIkbWchTRUTzv zw+#LV)&R1^b-@InMgfiC*NGsmo*^M2H7{BmQ;HXw>SBJr{DGye$_G{x}_3CIE#f~E!)cd{c zssrB)IXbxM%zqYPeUI~zerpUsVr-l0F;}CR^?gA9rQ8!oaN`F;oV^BnMepd@y*7JE zZ^eOg`b&;((?~4dDx+u6U%9$-|IP<=8{vi1{?7Y`5_R?(>Q%jC{q>EayAT&2(UTz1 zP2<{Ky@xp;Xgj_q%>LPh)lD2?JF&;<@LJ7ufa~;G;D_%eJM!ZE$u|HCeL1Aa@h#5t zqaObmk@-~taP{ zmP;ehKFgGMkw4aJuYYO~L?bnhOlclwwmd|k-FRxyMAP4{RuIwDu0{&lXkpMr!eT~1 z0079CJ+*G5JABWzfe04UK0Wj%=ZOFfHg&TVY5ae+H_dUafCDm~r7 zI;K6tQatQE@#^i&O5DYfnzrtuC$--3K6a8ig5yAa$E86fc=&K@5}_=>$a31V+0$&8 z#yz!G_PC^^h!j)iWj@==$7V9Qxn{g=I+CesW=t|KGR83R{LtHPxt^ZToj2trtiyUr z-s2Cz+$uD)2D*YeCowg#uweSh#rWr)6?4b2`oeQ-2FhwDNE^1~+}_iC`l^^_s9w!c zk)mW*T>;JOgmt_Pox%|_HW_}nX$ki6T;b7Lht1hcu@ckP>fiGu=b$bVkyof`oA?_! z&Y>s66dWtr({h@wcae|9RiUWnP5bjz(iw4Mjz;l3iJmRdtzXF*;*#ag%1TGIYDAmb z!f5gI1f&-gY)WZpO1}@)r!K{g7?W*dQuJG^yIC!6D)lDHjaD2J-TLg^lkB3{kllbR zH_j#K4z~ldvf_`-h3(}jU@9m@ll=GGhSui~-Ig*!HW#Uah%-Ag>W!OgE2&BBrN-&) zX^*9i=u8P9M}%ZxQ0Zj{O}u$gC&n(5pDhd$$gBGZf$A!hf-#d*RLkL3EDRdRn?p-U zn$!0=?7PTq;5MYV{(MM(lK4y@v4&q!QAD)ORv^q}mrs))D>!ef;))|%JFMn~xhOh? z${^N^*k-s<;+#Acy=g<(N;{z=Wk}18i(R!pef{euv#k7*BBOcCZ`R&NL(G8mF0`?WHAR3J4z*$uD&Vs zF-TS@;A<#rO)I-FjYJ?{6!fW2H5W-N7hCJRu+XkIPi>TZUzMh(8z>ZtIV3R*Dkz*V z>9BV{TQFOZ2C0%78}M9cqE=|hWB-20wryak(i5wHmXGGG*+x)R&fRXTGRBr%mmg^O z8hCC@nz;q7D?1NT6f7}HT_TQqBdw~{nnzlpj<8LUXh2HuFr~QiC>Q1&dVR)z22f5+ z`ZjakxF?~WSLxX)TUFRMO@@!O(p6@xvkwbTHz{rU1}BWyi(Gp-UISFQ-O?%fDBbyF zL5wS(4ks>yh+j{(l+Ln#wy!=146rWobRD$R@-=97Ym5(466kKN_AWwoCHFC2k5Ju) zUdq}jtpu5vDqS!3QKlJHuDOYieoNZ{cWTozDZ4MWIPO-TkQUQxAnz!SVlON`S^=n1 z*PPj6I`PkVM%Tm84;v{0jQWJy_n|m&tB1wE3|p+ER@6H9EIoJ|S|hWJf#`NKw|<*+ z&1yJs*F@n@69=wlW-NIx*qk{!JL0_i!OiFt56x9Ww*_A=N>)6UTA5k;NY-(#$9|l! z#c-E>O3u%*>=&}WrX03ZMx|i1L050%*H(S`b2>qxsL*irL+2u2_qb}X;O&W>y)fZc zUPNVi!1`IqxSuhd?Ru@RcUcv1bH)+7V);oN+x5`>S!i43D)-~CjO{vopQ4oqqu^XEm*20FDU1b#;=dYdK554TnG0xMJ)>N8!>{IY zni*o8P@T>GWJNI5WykKJ^;QUd+m`1InBR4P&eZ726EOT-Z3?%maw|?eb=^3|&l^%AT_0=4K-|c&-N^h`O?jJE(yQk;m zms4(!1sg(y$Wu@&scQ=hH$)K{eMP_(E`Mj)z4hB;pk^%*CiLz0KNs1S%*)K&MprBv zQBAEr)n`w(g_k9BaN8=qQKU=7T^pz2r%@N_5Uby-vN)n3xCLJw`@fh(ZfUSa8qf-c z@x3xVbN04T+g_Bfy%TU!XeRYRpSl5iB7dV-u`X2W>UWwiy8eRQLw0%r5xJ|FOdvVu z71plt$JbVMd5+jKK?k$WB#R&z2a9_P|ko=t69ab}>GjRiRC) zHQ)*xvemft;tPxmy}K!(9b)x~EZk;On$;!vMQeEb5Xhtd17dY&yXgY^zJK9r<27@M!LsJkn7P0(H@pS`nap9Cz7WhG^0OLk3L5nK`knIwlcb60>(; ziXm@jV{}|pcMsf(m9Nv|Bu}?9dXbPqF46VhN}b$)&psq%@9>3--g$!LWi;KrutVCJ z0)O+dUt#G}UvrCz_JI42s{6a&iDr%gJ=&pfhae|<+0q;QpxLU_jo!Q}Y@Jgw46e&C^DaRD``Hf$5s}}NgM^4bG(WOwnL8F zcZ>c87Ib4Vm*k078x>~sCx(weoR%~`PmC^Zkswb<;YN%|Qy>egv3ihr^J_4^)|-0D z1N+c-H!uwk{+D6ms_a8doA))K{EfNjPY!#PsdT##$5K~&o#3wq$%;Q5Pz|3)Me+j4=#tiuF8JDVu zL?OH2o;zUr)B&*8xG`Y)fx}y6Y_URmxmWcuM$pNJyI((~@o+xC)WOhv&)|&YQJd5t zx8m?LgdF|KyL%g#>fzm5CqwVaZ5v?c5_u;D-$XB@;nO^m*a8`n3S`j3XQzlqIueiW z-pp&;+KgpU0WsgnJ%{=7?^mGhTszA@%eQX4wuvVs=H)=0X)R=4dHvQ5=6}DwYX)e# z6^5{dm8-b5-i!F^6y%|aE0)lw=Cj_cwiEr+Y~PVH;IsU-Nq+BgWY3D3zf|P2O+FI} zhN#Sjk}IQzAkCHI`O07}6@&=5J{C2v#z0?oOB3V?yh!MHut^H}E<85@{Hfk8z*7_3 zLODdLO6G-(NM9yhmuj;t+9)I-O9zUHp}JyivE5pbSLS>WT&$eI!ct|qR@ZHFfKl9k zEZL;3AuSZ)yws>s41b|9%~Z{UBdMk_xn3z8KYL_BqD!>BRFomLka1w5DxFdmMCc)1 zQ}*WV&B-+q^foIUjO^|rfO0AZ|{X3%g%o{t- zsDHJnhK0aGTQnqFta8a9omw*rGidmL27rABg3v^bGL44j3#5xjJpnO7yE$!46BqVE z3Nbw@bvr(?`QlgvI$+<=Ed*t)GA-DvgriHP1#o7{?ue>8ObE|AcVLlO(v}VZWkJ0f z!^%F}&a7lEiHUh4bR;>2U50g^*#OaASoE1qaZNnIUqru_HR`$0%a(yq>Hzzmeye<~ zF%MiZyuPH-#S$`w%34|^jYLG~DY%k9sD|J5;nb#hh_vy3lfI%?9ex@*I1S!H&2-76 zd+9XJb`^nb&eKR;U~i_68tqa{L~onQ?<6t0P~jMbJKLr!CJg$Mxi2A$x!|1kDW zQJQthzIRsIwr$(4v~AmVR%WGb+qNog+qP}<#?^q47}~AMXi&C`()sm#Ybsc~_IhTYnNR+VvBI)uvlWik#~q%MF$hQK>jbXkDKys1)#IMY8yRh{!JQ%TNuy2b6()&oc!C-Zr}GhI zLuPX3_nc*2>V|{LT{k*+01BIOi7d1d-9Kd*JD+;)ZDLAV#3y4J4I!prCyWOowwo1R zG=6}xOfO`s7?a5X*A{a5+@&6ktTj@aGO|9nb=sxE9peF+fxx-R`mDh2SJFOBOJ6T^ zr~$Qfw_z^WQHnGXCJrtUE{EYGgqPY)Fve# zPud^{Udiq(xbjmrZ7~mNj#J-8d`^S9p-d)ladBrr(&z?+toB*y&O&A@PoGvYaO_sm z#nq*uK%9ol*xJ~>JaZDKzr56afl<2f=-54RvskyBnctuCBjQ)ptl~FkU}=`G#0kb* zrZD&fA@T9LQO`>PrHC3Za%%2@@}lSrd9(7?`Q1IS`iKY8M}W7pI+Z_$%*65#7 zFRt%~gIygaa*fFSIMg7n@GeG*9JDS>|Tl1F&Q3bHKiEHe$mhgaxLRw3E0y zt3bh(KtVGdaRVK4>?NdJwROnc_XcJn)LDa%6cdB`NJ+qQSe7D}%@`CoXTtE{dtR&A z*w1Od@%B%PdGx;brAFN_n?$_*4}%&YN}up225Y`5c#2JknvmeUY#G2ryj|P!hUiO` z7knSlgR5T3b?anxk>E^6p_|E=bm&Y>Y-HX_ViiP7AQ9~&;l@w7KTVQwjb|RzM&>iP zD>XtLK?~a2i1knoOqg}8EKrfSX-671Q&0~n_S6lpLN!iZ*A6i%iGmu=7T6ZS1!gc9 z5a>h5I6Emd)DY&R!ji^Jdi^HJ8n~y-dowYpb>l{Y=Lg7g3wdhfZL`q1MP)FF#1aN4 z4d`(WazPoF5d&NbjoOtLWKN9g!nR)YW34ST<3@QE6!uCl4t5Jq4p5UCD ze2XC(=!;?Rn(lB)Uf~$UT-s zE&pP^Nu-n||3c1Je*L8M+38#BW>ry09;D$61unVdkejt*Ks%4YW+{Z|%_sNFk(hl1 zbW(z&IIuH*RVT}3NZHj*7p6ofes>EFWn9LcsJp{MPTr4)C|O-p99glb^h>&E;&tCI zvb3EyDbBXA#?ngODiXg5Lz%fCZoJkCtYAZnWqg&{pH20Xzn zk27dh<^b>Z4Dw6t0PhZq@+)AgU#(gZwCo-AOX=Xx3(kB_Rb#Y7*HJdbyJO-OiqpH_ zmZYYKRAkXD-HzdBqMqrXnP~-V?x207`kfNd1+1QMyFsgY!#>dvF&p+plr^L!L8yqelQe-7F zjZd}UNLlM@(OigQZwytWzxABpIQBz3R#kF#uVh+A+uhI))*l8q(>}k)dfLx{*$Cpb zX3=I5aP@oko0N^Er^#247O5$GrgysM(PTomX=viH;zEg-;=LtPYzLO0b(4@2SzC4| zg7+kn7p#YVUn6pjoj7=ye=NVGz9o+Cot?67*bdA&MBu4!3Q-WvpkLJ5@!mVHny>Ko zN91-|S9oeYP&mX(U6LRT9?<84(P9}!M6`Lo8jJOW$}7#D?~7ez6l5M(TgvtmiAyHC zVYY}r<}>=@@hlV8O?{maOkAtG#7VM^&k*S%w5ZO$L9g{i4c!+;Tjv# zYTZT(3$^O`gKMBqa)0zcY3s=YWS%yvaR({T?vk?<&L4nwPbTwsm}@ew#q^=!Aq_c= z4i;dbHtD>nIVxO>>(&5Ads-#lxoGJb2OFqBqnH|($3BHCZooa|EfnnJ&a=eczmj05 zU$o_*6bFnmut~(xF`==>@hlcgC>Jrwj1rH{u{#2aDg0TNv$mLc4<@qIYsmyk+v^a^ zAZHG8H=43P$j$Maep__LCCf-VZ>tU1`?W-sr)S;-A)+&a+yaYV(AwC)+FZ&ea!=04 z1Q3rm_f|1~bPU6UR1Z0RtmXKU$CX*Wyj_Dev_3y?w5HcjGk zRl9huBzrW3JlW3)L|a@+b%!drsz{JSbFV`VcJ&cS)aWhrjxj5q-WAUK#|7GrGYq-g zO@=0~nEQbcvKiHQwiq2uoJY!FqAE6NVf!up%V;_5+_MmCFxIpT5#B0?8b;oT6Q@y% zWPJ&+t?6_mI)$s*Z1VA#@MHRL|6{sXqG4C47ViD8z|Jt-*h6p-u^va`0RU;W@S>c; zcYDm}?uenWYm_If!Y4R*c67J!_5)!9POvC)0PZtw{BU z)6lP=n_lDf0wbw!(cWqt{Ph;O2j@)!kPDPqg`b2z(@*0a%szxT zP_JR{;Z>Z1#S4cZcc5lbPd1})lpuFt$M-Y>KU)uNRxXY{hIHU4fs`1nk`|Z|E&}1( zB1xxJ_zkhN+z=*;E|{ZfgK}M_Q|DnF15UVS&4HX}N#=ioI?ow9QREZ@naQsOWXfG5 zR&;`ijOO2&Lu^Ps#p)(ZraW-A;)w|M>n#A?;}@jxx0&(b_^Lxu2yFF2(wPY#6TGsH zw<2o6eQ(wyiC0)}G@DV@>%Mz2NP1a);haSU*tWwaB_07&dM{?@ki$llB#-Q(I#yZZ zGX%g^swjg7#8M+&i)M@anj?s^$y{V#Zgl|08B+Xukm*Z6FOO1OR&-DgNs&2JEOe_b z9KW9qH4ZR564Adm_l}jVsl=xA?~TsBg93`otRRp8OTz^yC0!j3F_y+nN`a4eE;9sx zT0O}f!2#5cyvB*}sGpVAEy|VFojIyXr4!x>s8Cr+Zqd`TJ1LolTn7^L?P<3N(eVhe z0>XQ#@Sj>CTL9-AbUq0Zw^fb(I6yxMJB&uFxjI6%nmrmh zQ>*0L=lwqyf2`Jlxc@}#4WxN959@QG(z(lA3fBN=tFt;>6J<*7=?%Ye0B=Pj z$b-X=9=>DPM*y=zQ)F0e)Bo_)t9`3ES&znmnxpo*gx_h)FLfo< z&+SXj4!{Z5vl+ep!Jzg^Z(s;+#|??!3AX(KTZ6du2$0bcGKhBkQ|$xOijQt)Y`Zzw zWR}V|4{u${BT>gc+0vZsBSt4U8LxL8Zzg)ib@`WPU(ll{#*~jRUo8(`=w|;_W>b*u zv?gnV<31x*qrJ^Qa`!KdohTxwk^BM}IZwx*`a=MLj+ez+R{~Q#QpYH(+);phQ?tl9 z)|7HYm{RuS1#accS(~+el%h6cie9+B34RmCC@$Ped%4vQ6&dQG(%TIVSUQPJXn?x@ z`-w37u%i#y>ld+VJ@X)ag6ub6gwXehY8?@JZXl$dC=}-`#P7-G1juN)sQ%gzCLNMp zzRPp#u$z?`MN8Iqp{_m^Hr_{?Bej}IC(NFSFPAa&XOLi#5`DT zEeZM&nXv0be-vxY6e#fIj~V$Ha_%Px!hm*ptceCePwE61@W)s0*K}Qgq$)4ue z!JbEQ9Gt#t(*sUuPwv-j1-@p4rp>rm>E~ollRlvF@g%gJcr5bHM6F}5^zOAOeK!Tn zc+ogj1jp)6fQ-iB1Wt&iUx5Zr@B~iaO8P#*HSqGQUYN+eBfMT^Q;C_;)-J&Av6fx9 znpU<98VjB~Ft{#3Dl#Jt=}I8aA!E{g;L31^YrwES!B^&58e#T)0Kv%qZ2I#478?S8efz>410xbZ0KN^Pf-W8+Erzq^+XK`dLIAkFxWNu_B9(sWbk#B2@$}r)R!=P%d{fQ0eX{w~`Qd%_);Sda<^Ie7 zklv4q!e#d-Y{D&6ONTN!nSwn(Ps}g;+5x2cdN1);yTqkV^TuI3Qn6eQ)K^N)4EkO(S`A`C0bjkIee2b4%4+l#0 zULPf|Uv$|sI&al3lAB-;8H$(004sOt?%Z<(UUnjL_TAncWG6mf7dc#ZT(E9jMAq%z zSlo>2`*WFJwYcVG(%8~Rv(V?SzG&OBXVlKhZLVKls)#%QwxT|Hj8a4}T+N{LHX_~v11vu^ z5jA|20abDCXUD7_7pk6$J|I+0*TP721~Kz%S7GlC&<_NA<9w4PqyA7*(cgVGl+t|3 zl*T|)Zk0n(*Aee-bsl- zw)G2NRZh^>&J*URFCXP|d=TFrom5#WRHLSBr1RMx=4V)!`7_sNEH_izf3h?^c$@GzkoQ zmHC4HH#)RdfJWS5)%v1BY8xZ3SDFo074TZ$(xh};=A~S#G>Y)J3&Eey%<{xxEV=Y~ zy|N3!5H_Y5ElE2vRVd^WBnV~XiB6bf16~&Ggrm&zw3Nv5rJ+9wb3!PkmBI(Y)bc_x zYZGMB_c~{{m|kX+Wz=SxV|fxRfKh6tkkG`vy+zH7NRz@*0J&E0g?k$Wi9k0HObG)B z8F&&gi%o?@Cya)b+4?6DIMbN-a>3Kr5qOLPES3r_(oG7@uVM{F`e*wkY9%C~%?%on z(V*AZ+zn@2M(e#AM6|}IA5#dhNcQsripqhN#mGd+3s=hvEDb8vibEgrRJIv!?JT9q z_0iJhEY?GWqeUWP<(TbpKc&M;=7f2w4Ba2e=_0h!Q%N_h;H2OB6PJi1t>uLCNm)Z8 z+oSxf`qG+#|4pm}ij=C1{Uis!QxqnnnpKS^q<$0|HX!DU7Ru|E0Kl8|%F1Ts>8Z4_! z-wWxy>`?TcaAle5c=seZ)*hK9UHO5+CB1mNuql#|4rNmwZU>rn_d?e>s>9EnQYQJLge*V(hP&T@uV`l94)IBn8c z7TIcs)k=y~&h2<%hiP-L1?_>oj5-9-@lHcFPiDkz&E93!CdDeMx^zy+49hrPSfpk_ ztn*058P}bl>W!+qnOD_=4#pjdzx393#E%usL1_9Ijn{194&F52=69hU#c|Oz6n^3( zxE<_q?zshu(!;t>yMZ{=f>nA4p99woX4pNTKp#BlI2~ckdrwX`HB8=VNl;}{bQHhr z^YC4*jH4vyAp;cw$k!I^S zrMzXM>ExeRsb4MA&b2e}OtR18RN(bmSPjAg@B%Xg0AUAJ@7Vm1XvUjdDPPAMUrDz2 zAve{Pfh54A*QzEXhUQQM`U!&s54TDl+=9B+o!I=l{1Bgi2;nmc-w(kcRxKm9S)ms< zyWg*BP@MYwaQ7@#aON5~EZti`7j*P@PW7?;b1)jH#A~qkk48TKS?C4~yHwz0$?M+~ zN-=eHE#zv%=4c?^Fc`pT;big)6~HKh;l*;&2?H3^BRQnQ@r4tgIX-*Deh&2&Ek=FB zv=%D<7JbM`aA1-}HGYpeWmDs#P z+r3(1P*xYprI()mA#k2f*V=2L*u z?8P`xfL7%LVOx!gt>+PgQEc)MYr3LVL`rW-&LP|9C(0G-ES)~HCdR5JGtMa+KLG2R zNyhRP2FhzuCiQ^6tf84fdNH&Ze@nldw>mB_7_HnSUe>imSH*i=mG&M&HyPEi_)9W1 zTU~vSpQZIS?F>R_*+(&^0nuPsb)iX;(AyPW$)BU^EKl==mXlsbI94%MA~nBO(3Hn@ zwyZB0kr)Gf1i&D0`dUCUI>XY3R_$Eyq&(=b2)STo{d|=mov6RT?)|t`K0keB7EkyASRR?*SXdB~cKN<+VOpN+(8n~a?*G2a$ghetO+SD+g?yd7 zXq@tJoA8{9eWPrc?wK92ex$QQiSJ6^@;uia%9^+*d;ac^A5#OcND(Vf3A0R{jJ&r_ z(dqP)x7A<0)bG7Cu9LvRBF~LY+7wtbjS?!pT z(SEHZkc;c-^pv|Greb?zI*#Yf7XFgj&pdA+Cx|qb`bvdXGuOo$+33}#eX^!~x}|`Q zF~=a0(xc~#wi(?~xO6~hw?I4_`1&_8C2*<7hSqnxxcs-E=zkFt{T=BlI~qHP*;*S* z+1gq<+x;EvMk;E`VtxZkL}IlU9~3Ic8=EXNfi+h&E|ll`$I3#L!0{nujRGO6Xxog` zt=?5Th%GE;hj{NrS$O&ssD}O9Mp`CZI~@{ zh-f{B!i&`4@3i>E0Cd26$creLN%u-ZNJ7VJzCOMRQ0lIZRM{5Z&kD#)CArLHI|bRD zF0->RkJXfGOgc)pwT{wnL{fcww}`9>G)Yg7Sbej(TC6O6Pmn$fhuyBgr6(v}=4O-C zqNmtgzASQjVAf1Xl86GS^eZ;Y;PnZtU{o}3cH=%u^eT#X7y50SRG1*)QTuX@1r|!w zCEhlXj!A9n;sadf=C-qWw^4hUG-nI%=2Zk!^hmOInzX1UYmE&0Ta6V9*TVgbBF#gC z-vq1SOcZg-!t?@KyzX`4A^Qjd#O(^T5h$P!CNMvIq^~b)OWgcXP@dpTQjW9UMCKYO z*Nwro=gQr}UFWNl?xD)vqT!(LT(QBNue-!vuTzpcqU0_sc5X2H^b$QWmIyGfA_!2s zyh#u{Y)0JZ@H6dWj+?zDg3KnW=&3hD>v#a{`Lp(d(JzNQ=Le}bUgbS-K0?CG<4^|B z&3ofFM17FIo2&2%QrU&#;*n>>m}Y^X(DZaQW5`GJsMw>xh?VhtDY%JodYN$><7G9B z?wR|%laJ{xKm0rb`D05!I|KZaV>pF+pF!1AmI4Wdp$Sz&T%e=HC-H+?&Uz71$w?nc z=1#k+k|{L36ji}d=yC$UNAA4=iNdz5=lwBVGP4hMmqazagZKf~Z zTJZnHO#hjR3EA41n43B~=>IoICoPjn+XC=nL!yE zMa)a6$}WlMAZlHkVszf-JkwgOKS_{V zW79;8n)6d>mhE!XLzCxxUHg+sInw6EWooANT>XnWF;dU(3#NI@swLLdtd_0Xh^Z`h zFDv&!nSE95qx_9a4^mTtb+0wZMcVduxyljSsW%73T94Y``lLennK{bhJ=&_$^YXOd zvaiQ75z)3dQ{fea(m$ptAAp` zpg_;)=-SX$vz)eRPP`somPfKV!}t#~L1+9T_@ugFL5^9H+btT84Eh1{bCdlcTQ{+a zQ+HS7YNu9fI`SkDDuGbMJ^qpJ7Sb-sY1EC4_bYI!V}e#nCjP{PU9a6d3F);M)YhmS4jVGQJ%*721f#$n z%J;7V5zG!a@GtuJT}_FY0%*p3;Fd~I@lkxog48P@1$g{;iI@uLx*Xt^e9)0m{AlsJ z0yr^wUnvR!1;$}V5;0|%xHy3%@%mY?0%Cp(iI@gx1y#S}Zx|GGolM%2H~%Q05$F8+ z2h{&8HtYpX>*9VF8L+>fzf?(oPn)3m=LiX!f6RZd`=$fa+WmhF7b^16DG6y>iY93~ z38@kB1?kC=eM-s+s*!Q&Mv#9I1U>xQ(2H-1!as&y{Bxj%p_Tdnm{9T8>!LFz=W*XV zE#q51^l$jZzg`!zwYL5S$Vi#n7|ZE9e4h!#vUY#%G{tXrm5u4&$3mjwg$&X+v1ksi zDWOq&G?_fjPkEKbm|~YKWDpaH=m!!s=oid|T9TD(`o_R<{xk4rqA>nUKiG9{gliF% z;2Q9=pcB)z0 zvv#_DKtb$J>Ci2WJfE?eu&(KgCdX?wj;Z?HmcdO&arFjmF3qF#n&&)A=@ixs#1=Y2 z^hQfosufp%Tmrt5uGj@#Zco=&b~|bI$Wy^xFMI{In;nd?PM>xhrdRkN`3?s30Ch}x(x#a zEuqc2^JbT&{XC!ZV^%gt#ehWXVSv8z&;}OBZEfJc*0_l~eS?&?^?3WG-QI98J>*F_ zE*TP~kIw0U9(x!YMGbABQ)=c`VTeHmjkHmieYGYd^vs#1r#u8B#ZVI#b(S)FosjE5 zaSA>7^@_#inTN|bp25fDG4_+gCO;kL1Xl1exQB~t-5CAMv8C|oe$>56VQV1Le9*qXNlU5%lOC{_|ze;cakm*5(& zh(wTof@uRb!3RqG7i-X@l^53zGrnc5{(#Wce54!w3vyl-YNZ36Ij+DJXmmCp8JC_= z*o5ddOq^(MZt6jcVLxo^cA8&$CJ`CaG(FA)e_uq}?|YkE-{#m}>-7_Tk=@o*bJG;* z@>zy)O3nU));RQyOCGJCm~7^Ov9JHK;r=plT{zy^{BIMd0Q-M5aRHNW{q)~saCbQ=VTJ>&GDNF~#w;zQu90>A05N)%gJ+Hy8$rGKX20azZAq%1}-a=?+7R zs+6Ei&A5O1tA2#1eAkV&&ust=rksqRfG zk)Y#L6PQk{@71N=B)qu&FwVGncd145pf}dTND53-CY-?M$XG9Y$QE$usi5`Hy-Cg4 zz1%q70yhFX9D|gAboY$n%pkt2dIjqTn!wsHJ)^e!z?Q?@fll8#c)%WuiU})*f)=xp zgLXVLP$!yDNpmm#eA1e{Ib#kct7nX7zXWYwIL*^m^zGEkX6w~QDe03csH^8f5;h&K z_<%AfeZ_Y-MEuA>4N5{L$O|Qt6t*#hf76a_c@*#Qz>wI80@6dgydIB@l2$WbKlC7Z_dwaqO5QG#0#7IR9Qj z0gtN!dY@!Hj3EJ5h+wQVh9RgPVGp4)=a}3}^tC0|M?}J8`RN3p1_MyidI`1${zsux z6mj7GT{C*_l?aPvoQ2mMvAdJos zbDN>-w5>o=GOnV^M6*eRWu#{q6H+NkJbJ}gzn$L#rHKtT1N#; zD3AmH!!PDrATE^ivsPJDDOOAUaQ3a^1FHSL@}Ll|L9w@B-08Jn$n=%$RcQ5>sEW}_ zon%pb=w#MH)`qQX7tbx8&$qMkO}??l=AtJt?x`SBn zr@3*H99)A~527>_5aErQJT3K$VJ7GxD#&xA9?TiC6D8k@?13*Mv0p@nlN1pj^h7i& z-#<=LPnu@=CE8JbNEv0bU&L&xCODL!!>n9vV2Sv+*o9MS1G7MVScI*~7T!nZE+~It zU@Xp*c>+d)y9!@}$ujSdN}7)8OoU<2C_g>wuIbt%CKj}zs6H*xl%yIsQelxkFA;KP z(pkr!xh%#8-fE_qI9qW^Ey2DHzFHUFl2?feO_R)azh2VVP>>dAzcEj`F>Hf4gRn85 z8IP!N0uaF4D$aP-ipo5J&V0s*GN82>TmX4P zwfqvHm4Q4>_G2@VJ~w4Q4upr$jjZVh&M=FJ*l3zXMRCfLs=uQl5HZdao9zz z=riLcu7$ic$VdGyKiTV2KOn(Z=}^%5JZDkSM%Cw=MFe6laZRF zY|L9v!M3RqggNcg;6ljI;H4#bU-SjP979ekDsUWSNs@_z9=$npa~>OcA*OJ@o{FB7 zfQyrvuevA>6=f1aR7h+BSjU*k{3Lz&_?!Z$vBji{HcXehyEgx=SMoSNW4-)l%luAh z_=&BjyX*|R1E9^(Do1HZ+E*9#UxOrw?lHFn7QaNf2({>pvjj)Eh1S*;8~6l>@0b>O z1R9EB>#0J-n;q;xa1e0~umYR=??OYz=|Z5Z_|5yy^S|kip_{9*dya4hUY7-5$gR`i zxQBJ=YC)j~+=UDp?ZV;EG(oZ3SE(P|sfX#Rb}7#xkfQX!&9gGtB)5hMC{@Z6_I%Z< z6qz~67AhQ<0TY}*E@~}f9K*>I-qv%J$2=p9SiEmmY;EUS1vn^tMmWfH24lMih`mL_2&Y5Nx2;t_6(0Ut{)4CSoN9e~zL<` zA`U^;-rRI+foNa?vPQmGRU%W>jYx+VzfcRPEb+3eusNWKWtuzky62TR%c9!)`7del zUtXQjO0`MiJCXtZ_Ut168QcG7ur8$UX#6b-Ft%|tclze1{~fh|zh4Yie=aNT<5VQ6_CnoCppyOO$BCV**PnGbv_ zS;rj4IKBrxfU9*-r^Sx)M_Gj;y|oWh~rW{N2@sZO&yRr3a+$17c&xF?FjPi z?Xwgcc;X<$2;-st^$DO-$f03XLOV{8u#5|~*EJ1|9Rn}o3ek|t;tL;L#{gRVg~TYpVs z8Bx&2g9U??Nc7?IMFh@Ld@FC?V;EQgSei}_M%dZ0IHEr<+h`sfJ#3Y8UZyx#I5iAj z=&9;8-M*cXx%4T%>@MfaA+|5fer`5|I66r*I1X8Q^#UC{*Xm0||D@F0&59pIH3D}a zu`E#^6MYLtoyt)vLiuBpJUG>XeLS~}E4@9`AB3@vyfoLmG+TsxyqLWhFA(s$sq&(>_O^xDWNe36o0Uz<@OCmRMcv<E&}=w2K4{^TmKHb{{HZ9Vw02cKXYjX?Y|h%JoW1JF4EEsX}hiw6e1Kh z$hyRYX8g#0kg?p)tl~iz!zL;wWF%ktT?Mj%yw5Ut%J@>m1Z*-jLJN%LH{5;0Sk3fBsOE*a|v$U$q1(on5-Yj zr(2p|?G;#djs)oMJdO;jZP;gmZ!oS;SFblJ2(l4o5&Mx3O{fJ6l(^F&3b4g}!&#qN zPFHyITSvKKIs3dS$mb75peI^jc@i)VH}6Z8pGYOUP#z3_YWR1`1?}XmdhKty!`q{P z(&QIHo+(mI2KQ>+>?GmA1D$>T-Wpg1Z|ueUG%kX1Ta-FD18P?M{3;gyABjK zNK$m}VJ|~CrU)zw1@4%=D$^tDXt!Q)hta~kIAbQGkH(AYlS>n}ka+aco+k$yni8t= zw1NZ}F_=91^t_1w_FqXb^8We_hkPUg{QL~w+`vj*&>SL5L95R(kT-!w?PyH>OYk^i zV5MsyoTyifJ5r@KDXFsf9mWD~)cDv+fAS%gj2iwIsj&XzzbLc*GW2i(7Avps#fSP{ ze9r%L%ikoui=X~3U%GsAdjAX8l^G`~+sls}I0XVM?8PV7mv`O`jEUsD zMyt%1o0)IN=p0w6vrfTULAf?!v@eN}p=)winuCh^IVw=>EDJ^-hf?yXc>xD6nZB7fbS9+$yq z*b=6<#|Jjjj@>`g6-=Xci(QG{^pXz~L+)O`Xfi$3Iw4~6g2z8=TnG|Gu^!102dW6Q z_(y&?k{84ngI4s;y~e3MD2=z!obIs%U|QDCvCv}+z_iq#R1hUEu4JVTaR1YJkpYWA zV|=fv>0gC||6J4meF-Dwr6v3L;l1Y;2j{EH$fgLHAw{aCDa7QF0U;qa|D3d1iL=#h zBz&^MeFFF-G)w0K#|xq*WxCg2eWSyUp3bnkc_wk3a54}xh!vr#U~;#himiIy6DW4N z(5qJ14+J1Qab(>M0IMMpIHSh`d@xf>Tl|^)u*7pyMp($!7a-sy)QlRG2+=|9vE3dK zvpn^S0_m933)W>7PP!O)j^gE6(-~MG3Rhd|&u|J@JF7AWgOPu(siGK!DwrL2dy?IQ z+ILxSS7a(A9B}T)GB&=Vk+jTsKxl1MsRfK(Or}={T>3!uPPpv)qrOB?)vqX}^PA~8 zr_l%^(WGCjR2bi|Vq>w?=qjzJNerpL+Nt$h?t>2vc;5aCo9VAT<3_rxr1yOZh50>n zm+L?OUjc)^cy|A9o2F7l(-rd@Y7Gl5#h7~Nm&-z0DGrSS2vgZ)PQxrQH?KGHvozG4 z%EcEV71_kjBt-bj|ElW1Q}+zYT1!$j`vd0_);aq(zEMq~dhf2*%eP%?o@de-hgh*mWT= zToY&wPk_DG02x=iJN_=g)|XiS5}^b1XF-wWBceYW_KE>~Qe@sJecX(bbBD@E`Jp$7 zE~z-aA#%cPl7WTSCL-ixmI;H_6uJq84r8K$dL-JY26y5gD@BUs^dfm>X-&mS<9r4A zdqTE0t79-?r3v6ZHE|vl&h?Vjv|Of$V4_s-1OCutln&&n)uN(gG3VYw579=H$_iAB zB997n5JgLMY-;q^DwVQSU=Cznh$f)bA_I+paHO4TPQ##;rL*{^8HaCm5GmsaplC^0nUPk=!qzhg~-|5Xx%VK4kQ=gM$Qgc_Lhk!L9 z@(qkJTX~|>fJ@!m9@gDT@!Bv&Pt_yL@JdVUmMWAB;V!ED=xMUMVX3BVRaFZR&XH^l&w+vp6YHI3|0&17<=CrvWM=KX=aG z#gv-Jk682uV@4-=_`wA`7WH>y0@dYO>T_>l^rFF0Gj^-&IoFC4j%I0Kk~oRkdl>?4{3X{BHZ{ zsDi;+VA)Pm7$NywT=+iP`rwZB7c#}46qh?s^NP?GUI%G~YS2*3KZ)nf-Xd!}U9$&F zrps=Gq#xbLPn@R6IM6Ri&`gfM1~{&x!3S-58n33QWq3BEpAWPBKLml`NJ}5Mdhv_8 zuPXC>@0tO?0qJ05_~uSc-DNqi^s9^;Bvy4!=|sG{dg}KwZM)Mq5K55hV4fEZV4jx@ zm{G9Mmp_**0RS80ft|uSj}Qo>v3s26G?0EXLC!?SZh|Z4&|jFeyTzbBeUiC9DQ1T| zbiqKg;^XLt=zq*27zJh52>LTY)9tiSNP+*}0Tn^@7TB6X51(~L>;2Ne8(t==YaqiuQgTM|{=A#)H=+-937xGO!M;x;h{ z;Ycr$+97?`i}?|84+c2Czyi1iuy!QpQL zL&!(q!FO^ALkJ5Cm60_9>-3h0759#fg3_cCbgy-_#89Fs(SG@UZ4WN>Mq;tG*0l4a zLLvx~*zX)}Uamc5bb4P-?0;PSxdPa?*A#%>gXE;25h%}~kMG?d=t=N19~ZV~3A2QD zSlP?M9l#cPM{pf$Z6gJQJ_TA^+%OJL9`i`mHyE&w%-FfjD?EZsO4W3cAhAJHmC~%< z6*=9$gC@AdgdRyWeFvFRUuSi&%(7es#TkGKRtwt6ALo^=jmpN41({>*_zBA6ol(mn z;5lHrh|xPH6B~AhN>QFTTXe~Ln4Uzdvya@|IH|38?ytA(X%Qy|Bzu0;bT|8}`5-mw zBRPX6!45GcYs>g}(_2T!AyPv8503&{=1NYDp<>Wk<>}gHT#P4UruiS)FhjiAP4gU^ zwFm~CJtBwE%{nIr12**T>r+1F8h4jX+qwoG3Mriw3jHDs5se>nV~ZJKn$uUQc^{>Q z97wy7lpZr=aok5mF5KOzSke=O8eF$m-J!oI2n#UR7vDl0S$Kh2Ze zB8cUAGuM7JP|eUvb?O>|#Wd9N1T>uE_O3qT?&EOA#1N+YNilsQFunl?dW*2V`SCuY z6dy~KBkBQ|0{D>78huJ=QM^#eONHc_+S4|3O6nMi?<_TX5)$@yzO-9BFmD^PNB01v zLdDcIMGvPFZC^R-wSac=k1F*z?ia>)^Lg2orOA25MudNcr=VZ?n#4Nvqd-_E&#(S8 z!;^QoCCDdKTbAu#scwx!R8~0^qoW1W!YaT&2~S~7!r=p0<4{-t!{bw&C{;%3OXNR7 z7XivN6noxVR z*iB3(?)QjPN-BVSN!~o=gM4|Op0{dgrOHq75c!JAD+B9t?+sq7tBZ$C%{5P3&ovKA z&6BRj)YNe)SklM6y>lMV>W;U-FkPUhO280U*CeLAU&%#Y?7=|h}HCraHxGB4bMd$F7-HznMY zM}FM2`%L>x8heD9u-E8#(F^9>(R0hybHun;drSvUz%NqBVd9+HeevE})I_EureP6M z4>!zaBXizfO@mBMko4jEh>?=cWd@J-sSO9W5W``RFG`U9lsjCCy!FDejW#a0*?o@t zia9r0nW&D9gLh6EqjxMiIrfnXvbaz)iIktF?BOU&)f>5&sc0?E-4XOR);KwuOz+J$-9;; zyh>$M!S|fC@H-xM!+h@nF?A33NLQ9XGd0}v?^$2m>eY@MGXGqoaHh8}3{B)gywBv- z4^;Bn#E-Z{`b+g2Re%RqnrRP53{;@cr6_0K=n=1@M}ziRJI6-JFj))|$w&TSkgj4f zTnw`thaB>|*_NS7524u7$?UY@nroKqTkDI}*7tO1#E4X%8EnS*!wf61J5Zc@rblUq z4$FkH0A|P#(qw9xZ*2kTS!x}rDeuW#WFKJOfXTs!9yx&3)+AUB%d`#%I##hLHb08F z)XZe;yQ*z6KN=IxJv@fq{VUSRk|DF!;$an~9J7geevxjguGQsY^&pv<{zcV>$u&(` z`$n&X(xOqltz0GD-V8-&n3>Xms;z=+#83&-xnl()ZGBKrb2-BGXKmj>YJK>5HUZPR ziZPQ~Gb5sPxkY#y4MBMs2XckPxwSF9)ygQX7GM^L2|4nLGTyp%Bk}k^KUNJ8OV$qE zIC7I(rhNH|Ql~F6IULq%oqsGPO9L-vKfPKugR~$;SyC2SM5?9`D)pr{GBntpWQrC^ z;aSSMb1bSPD^w$9D`%6&Ors#UJQdM|iCHEF%;;5r4%a4b0Hz|ZzHO7Ku$Q<*b$|pR z9iL~+$Q*@a%3-1vw$;F_m3)|wWE#KSuqEy@L=UVLK<1b$o92jbKki|2fqbPeXs4-l#TcsToBj}~h@98k&Jyq(foKD{W6QqgWRWZS)F=SYd9`oUv zh7hGUfkiqg7*iW0`=!(l2CzSz);g+CNbWiu_lrzyJfuuztz7Z32m3I=1#t=L99FCP z?vA(opn$&-W0A{Y;P&?#;shcx0CiL&R0ujWgR#bCtkzAKAzfRARM4db99gZr99~Is zNKmK&G5yv08D}bI!VG&jQi;NYf^|KL^(G4$>S1K=i#>~)>X8s^Oi>WGLX7b5kHs1W z!bszXaZwrpY%51mMq=NY8&yCJ^GYq-7GRc_&4XI;=M4k*bLbnq$~& z_PCrLir?dWY7&D-XeuGL_SPmwu1iZC$`oAvQNhhl+COq4)?{(UN{_Iv7+;$}RcG9d z!a$`w?Dof{u_;V;5C*Y9Y9gdrg#wRp>gh*N_^6SgWTq=|eBb(f@#L`*<*A8dJxaKA zI+r8q+^9SI z&0{%z?MQeYa=cFf@L;TNxfqs1r1ra9$K+71=Iv|SHl4FM!6ytwySY*R0_U-Vn7YQ- zxSLead_>vhsb#_3kJx7#>fVuqZ_u4d)pKrLJ=q6mFrV0402yOZH2${xKq3BNkp6sA zY~RgW6wDo`sOoHc=p`k~ZZEqN2cTQMV9=e3U3%Bn??3%*kGKHLNF)slA;Ja{jX}3Y zzygnH{jUy%0IXT)<`^Y|`_0`$Yr`fIjm5^8*`-y|$MR>y=C}Lu?w5Piv7j5p1eqS4 z;e1B6JzseJuh4|JyIs-W@%fCd`@Dv?>E?JqzlSSYc=c~rga5GtgB@k{$J?tW1tLW1 zBKg&sxwG9UKj!D3Y64U5`+q9?3aG5Mt!=uM?nXMK8>G8LI;Fe2yE~-2yO9RzkZzz( z^TnHpq)ZrezAlLnrC9@)tyIpR&4kwVM-O9up8*P_ZjS*J)Yyc^q7M;)*=P9>G}_># zFUH69#MmZ#Lso1^v@B|>A#aRLrAl9si6omRD=&uihB|_89P}_!8pvbW!7de_3_^VA ztT4Gx*6Y5I#10&&VncdukIpL6Y0Zcckezm5fUyt^krJA+uvTT@rwn&OQ7vDiFT65BKz^Ppi@l}-HpogVwp?onP0FD7E00*j7P){h67|<3xG_fk+rHoe z)oe==omT5hH)-C+mjz!$UO|S)oC(uAI$3e-4Cpl6q&`@6pj*G?C#=z zpnYiBjp!+h;Dd+0v~ID=EFI-2R8Zk@8Kd#^ZQ3%-Li@^Hfr-2y17ozY+Ei(0mBd2? z+SJaAdcVq4o_lVWm-EErU$VnAbJH7{bkIfa7tkgSh}%ui%SC-X19BlxXRQxeYhXru zK>3l)38i7tq8F=l$@(JGw`m+#kw-Y;69?+b4X(kJVq>ew#=wG}Y_NbzGv6|qgI!XV zG8*5627xktzsXaJ6jSQ>@WJ}xqVnvFGEsJ@xFTEp`Wxb%$-2r8ZP=%wgN2hc13msk z43*Z|A)H=Y-kF*}G`M=@=1qcQ;0h}iQ04lOn9c%9t<+jH`~j}R=+LZ!onnw% zRA0Zd#D{v6vlI=DID~K)v`d4+Doiw5rN2p>&yFr1x!{XdOMLz6^gE$7tM1N_jGz%( zHSwr8^R|EG{QDi`Rmk0JU4f!NU!O3XdVDQm1ENgxv9nTTTc$*}gCC#B!fsx(VCKiQ zERXvUgRtHy#a>)ay?}ll6}H=&v6&t6Izb@?viSt9hT1p3J&OrEL1d(P}P>g6SS3wKDoY=ePnB-TE8M6Tc3|ON;o#9 zZyfJ3QsHqF2h(1t%!&>q6p8m-Fc?eh2jXKaf~_n>ryMzI>rgm2EQn&YQPalWl0XST{;q7uR#m~L2L64VbGii{A2>X zp=nx9q0Qje$jN>@tPA;&GNs*m%5VjX5QVqg{E<43P`A|w6{}BO=NOHCML!76M?tY2 zc{W)u&mK1Qt5AT`oJ8--jXUi9G`{3e{Z#j02 zCF8#`DPV}VPIjN(3C+!Pg0OitnM2vRotIIJa zgni%+JE8ZxSKnz5vua9OS@aRf4WP=lX78^z+xW|9Im@OO~`oe0_2+dd2SOp%zvns0l z8291AaYmIr=y;cq&BcI+Mz`-%gCa&0b^~Hctn{9K`S5uO2)keP&#)rgxN5Qkw;+kr z)hW{>J^-zRGexkB%Xd4`Xh@@vqp3 zuqI+m%M-EEXN1oqGka1}o3WCQeGS?a?J2{0hQF)$NVU|9KY73&^N4iEEm~=z*QK;; zdc7V#*oj)*p=@8mL(0sSZ?RXIq@!zzzB~&YU7wmN%94sg+;cKc*W#o z=47UC>r7zU1MrLs(ihVkb{7b4__#C9%5HkG3miuV_;FXT+qIeYFG3c$BJt9jA7$~m zAycl%=!IpuGos5_x?8;-p`Ic({PFe~C33G>Z>pv+dAu*Gr2LEqAVt3#%#e23Gq zrd3|tZr8z&c_2vvZ_a8AsFL!*ZmrF1Ysp#{{JfxWKyixFh|e#M=4>$Q1Uj%-Jka5Q zzBP9HfY*IQ3*ufIRFu^TfsCaV-5%59L+6dBYV&>SP9Kz>9ejqAAPK%M39NZYQ0*0M zsW<*i*C_TX>$oHGJu|emmaC8mu_tngdiyETEt=7yJn!UM23chnbEZ?0X2+ZkZ~665 z9vg9$Eplsd2&yKf7vmB>w}fVz+_*(h55_*z&}VEu<-DrH53!oH9XeAl5< z2_Bj;d)xT92$oUSXhK8eEfMdM)J}$Q`N&N78JmBG zTqBfti`$aR_*pMaIV*CeWK;Y)$-8rKk@me@EQrxmQ#f}mBDN}m=%FEU-3PF-1$C#! zRtgyi=UC2;aZRT^QoptM0y#n_5)cVqQgrBj5ts!2=5L~_(@?O;#@NEihy2On@k{V! z(gf#^;J}x*e&csg2`hwt@A${uB*|0?>Vbk+*4$iDw#OWA)Nk=-$FCa0m)A7T->!)B zd{^4(yXScDlkt&2T7I3a!$zScGz*kSqGwBZNACjL!YukFtxdr44cAJ6F;FuZ{T-*@ z<<(Tk^w!_>Q~?%STY}st>MPzAR3N7DX7iR(ixmNz3JKD z&dqyEI~aVsonVv&sWBy^)ShI}>MC|)`kh9-rn)q;E+3tgiCZ>t%83*&e-UWpe(M65 zeP1IRkD*ZD=d)|i$#dB{`B3t5Iw6O|cyr3&N18AjSVlnL9sQip=tiL!|DDk% z43TO26kj+5qftK33LgjbeYZRx2iDhqh?M0^Y_AIPG9LtC=4FU8AJDFqY>3EK0wr&b zx_6cR6}jG^Iq;+_7%CN{h2q`fYX|i_Nqlis-v9b`A`ShiL)K>GnD`0*VO*OYUVvf$ zqF}15P<0p~oX6MU{5^{D;X8hdSw?w93{L42L-O<5VnT=zKa=nMT<4Fa#O^_*Xkj=) z8Vwb}I_TL;;hbu$^8n+TfGg@ex6zePpW&Eh*?oiWWutu)+JQYrhMxn^{AhM7-kFdQ zPv9j)Eo+nQ4$%Cl?;~j~tFHy_*g3A{A!p=pRP^Wn|5tb(ym@|{Y&S9E3unGuZtUZn5({7mpen7x8>jswuE7Y-8?t6rO0WuP za7Tu~ARPO!I798iVy`ZddmSI=9{Ab1jCm6)c;;P~^{!cu7rl;&#aGe}X4nYF+dDsl zh+2rNLmlhG!nLVdjmNDvaLLw5x>xlQtNiZPr~1NK4cnodF3uxn7${H|%GRedsFD*I zSe&^FkhKEPor1TeiSZayX1-Uz4Bg8}%8s$dQi*K}?Bk1?i(Z|j-_^b`G#^AyC0aCr zH1C~tO;;|;Lz`kcN4r^rl+0ZvA6)(rQU5?QrufASw?x>^F>-52O;dHns>Bg~;WXPrVLbXecIH#W z8z81lgqw1tck0^oZfMu8kRCHvBlmrI*7zfi9!_PC8JZtPorF4sS|}?$2z$yuMF;(0 zxtIgZX)0c2zV}a<1!u{f#*Dz4jjh9blhHkGK;!~iA?hU8p+3SOmLo$PV4U7wBE{K#56T;OjBTnF0ry!Bx}=4>@8VDwD-pvG&W$$d z48zf)-Kh0@3b&3I1t3zE4b9yJJU&?WubQIBzi z#+&HJWSv)}WY@FmDIYm`@)}uIOY$W^Q^T+6Wvr@bO%<6aVugzHh;f*ZS+)^$$|WH0 zl=HvJ>*&0jFk5psKFqv1a`BOaBQDCQn+sTlD}?d{gC!)kK|Gbe%n2ua z9r_$mitMZMyGtGXRX#JvV92V?JiP9@K(3%N+BZKgH{>v}Ng~^LIKo2$D~(@&wbIky zaW2Jr8=mgZWc6BUDD$+Z*SJ&~U*t3r`mJSZPcjl*bmr#*#`?K_JOmj&B$?QYf-03% zz62+<*7Z_HU;DPpp;xyj#&A*QAdptJS{n!|G8-%=ViHbR3bBO-?lsC}bir}9$~;3O zOI9387nE&nTE%DYdu-D=deBh{k-119#5uU~GL5dq(}(&g3|pKiLU~fxzL1#bI(gxU zNu%lS_lr&GtL(UzHD%5!9zWu++^Vu(+?e5KkK1G6UwV!DOXpg8=yQN5p+>Y*FCn|8 z7@Qy~stL7E`xsQ1-eRW|$lC5bc;4uk&}RB(#@Nnr1#*b@-HLWGWmB7Dg-4U(8}R$& z^R>`Cw%MbZtH7}2qSjuWNd=W}Ox-;ZzfXtnv`2vmf>nN_nP1u}TIGpaA394%sM%+I z0zul7;-uW-AG>a@j+Ahm?gaUSc=Un#@sPbGbkQ@_#v!pf8|6w1*`kLp<^d0UjZrh; z$(ORHFZ#^4-EJJ?T4fPJ7P?r32C7P`n)tO&ZBuKBK}G3;mIz8O3^C9 zbp3Ir@_TKVbv}QJF<%%tt3V}fxNxnhACLhBYjp4SNL1MC)-cNb!y+fIkO?2L_c8R{ zp)Cq%DZ7+qRw}%oj5!1P_)ni+tPo;>IFo+*ecc0&gW3POLU@^p{gatO{RqwnfZ|gyZzCl(9l27zTfpo#+!^LMkbx)Ulv#LfyHkRL?SarDB|l@ zNo716eHu<}Z1HSq18xU4OCW#`Co)6HQt=xGF+_RO#E~k;|YeqO7)$qo29eD1nY&0kVW~iuI`$|Z=ep`T4`o+!3R^2lEmr-v zm0%Kz)UU4Pm7NB%LoU92G7top&jb3-j*gyOvcEN)(I_C}eC`C?lM$$qgaI|vdylD%Gyl|+dg zXM0D>3s`1Ci&JjtR=w6orLy(?N=4rWGYxh~Y?a@3UhBp3B`b;6js|>~I-9e|2=M3I zY*m=zcc%NQPhkJ;_9a{gb;S*Eb*k9sI5;I&9o!eGL zXj(#{Acv-xlZjznji2xuG>_Oo>U=6%b-p%BnoaKET2M{ffgMGMkSo>7K80|;QF}% zJWZsSlru4$R8u^?ewU;l*#VT6rv5Qu#RJN0L-i9P9FBm8j;ALlmHe9vSM@Gp`#2PRf@Z$*ja0H`i`RBfx zeDSCEf)ykq2OU0N-y0QNNLK_F{Q*wHu^QH4XsLpKP{=UV?I zE&NZPo7l7#)E2-bO8|&_p#JMb`>&y>_siJ)HKy&SXa&H*=CQ7x=71SLZqV5d;eZmlF)}A{+2Eydov&;MT|N($}7E>hMLq`&Eu%Bf$GcE_v+mB zwh%}dB-bG`YbCz?>cPvzxqZ(^Z-UNG z8yvL2LT1BC&6Fd8T1hD9DRRslG9##*DVU>(VJg z<&OP{fx4exjuOwr2`q*St5eLme4K%MoXOeC4qCNSw~1>>n)%a-V3!<)(dARGbAZ0C zYr_Ob+u55k`uc=Mg0@|?o@X9%-DBHr!iRE$xs5R$(UW`9#V3Bpyb zGqsK1^*O2p(}o813!#VCvMzC5^&VjiPv}PkPJVbycl-c3vb7z=3B-G!%OVZyL?$1T zBKe$*>%Jm>JY?MIg!$<{3cQn&A9gWM>oFIfsMjD1IAb=nZN{Q-dPwO}Rh@(pjg?}F z&qQ{@SM43CwxUSc-?Y8Q$37icVlr^Uv@;aw{XkBIeZCsw16o)GuXnGT(0lVb{99cI zJAT~Li#TRACyT^S0E0vy;%|Lt{xS;wj3`k0=83I@`Y626KOtD9&=;{psxZkGug@Mp zJmypsxlB93PvrY(jLsg6>HVX9vVPy0=+-1TcUa4+mgCdoFsPK zB)HmW@GJ+eBm52wz5y~Q*yuUW)Y;|qrxk_n#c(KpzL;38RmF=QV<=zsU zQDjM9j5);jZF~PGV^qk{cvW&^-!l^TW9#W+BY$XHYguL(xu&c%8|sKKM0SO`+7N@e zL&d!D>rw-`&5qtQAm7(fc{IrqsvVHx#!6-qxb-2^LhH6UpI;k_S3vEwV%M>BM1=1J zSVW5L49!raRx(R)f1D7$9T5$ZOazy58a*~B4&7${0evFH@A5TONy1QG0^QWIre`yi z)Q>+_3YyTp!tavf2uHWh_$1|n!?hh~T+|ZfQdrmd$o@AcQc+GF+MPI{B~rJ&uLj|l zmjnUCc1#nM>gBT5Y`mW5hljznVPN{Y5{5#(>vbwr*Oz7=-y?mJPyjfmhj-={!Djo$ z;@dGOo)C2Ov>2j|p|7oTtGs%L9$)k7<&B`N!UWhhcv z?WevPC{SyecZeLzVk)7w_&VylDRo>O zyMyzz!;|P8Zm}}fF)O0nL-E9)AhUD}AL`%BcZ?p}LPNG%v!(79eP;|uk8YlIQVVW{ zXqVfjt&Tz+TK(jMdheq&N-F2;DD*C8HQ^dHP`JW}LXs*G=;nc0QU6}JgN(jlwf-7c z#Ca)9s{m#C!*EHC(iU@MU|P9M(sH`EWiQ=klzY6A z;qNT;WQ3`x_R$vUvZ-B7j-RXKv6Ax-ze=zk*(lrG4n?U!rj_B+rwVP*IR+C8`lS6B zO|OFxI2;W#e}(y-UPR4_-|8_V#elS^98Q(43 z*X*o>9lo@<-E?f)w&K$ZOIr)(M#@vRpH1(QO7M&)gtd#^l{IQY= znWvyg6dEo(w6D7VF=6?)oyS{(*+7=kpBIG0H}Ap-zjk>sdC}<|w!}1pQ#fL0HL5|a z@cQX|(%FajZci^=<*&025%XaBo>+2wwo*4vgP%@5@&Bieh<& z9T~p6(g)wsg=$n*VbiXAwD`ekl8WVUS*HSj6J65|BMc}Q4;4A4f9y*nS!attu#CAz zcKYOi*V&g=LC6Ojv5v7S8i^>>3H=pj8o!beHyZM*xSN zctD6ar(DL+WUg`)=Jvp( zB!AFO-wBv8>?|U6?Z`D)MqW4HKBAisClQ}{<6}yHD(XJNEhNA4g|Q?XOlO)1P9Mi2 zXdANm&=tlSaq1n=oDfZ-6$E-bM4P!&kLpI6a(uADf6Sex=1+BaLD(PQcI=J0$?uJr1*36(SB()a<6 zMAJd@9yQy~Kf*|WqnTB3TeaZKvuk8wCrRQclx=#E7UmMo%VerADm?n`enviCA`tpxN1CEOmlK z(fSwaD^cwX5_caoKu}lsz=itw=zx|4m|DONEmrk~KWQu{8M!mjR~}k^g;teJr7W0; z$)F*+c5vXF6t&5P?%PID#TD=V7J6N3c=u}b^1Y|I!|g?ESqXj#-cNgIe^^$W$efwGJ9qsku!8*D797R5u>lZ~yfwn<`h#VpT5=h$<&;Q z54}u+HU{)htpvJ3US6a=wDi(U9a=t0@TE!2OL7xvE3_>qz1R-~nxffnPCDUN0~yi_ zXl$`1dgDnC*kwj<(q?P_mF7Fs4;7XEyF#~YP%IP4bO|L=V!WXc#jqefb`LW|&%FIB z2|@Zky7Rf%46B9lgI5X79KM&lP)nMOjT<|!yVSo`m-G}5Q{`(e(uc1nE0kEvQeg96 zJ&;E5##4L^A%wd^>*BA&=e39>tTs>}&)_p|Xj594IVf;j$c%VpjG$twpsNjzOPj~v|q+XjJR)?*F11Z{(A zZrZTD&pH+PunB}q!&#Z#NdSNgdCVe2k(ptT}V&&fwJ7z$U5(G1Nw3F z@JL4;F|>}ds^Qth40GDprK7=QVw8nvjl;ml@_>rJ!`chBF+0J0|KMr1PW~#whmq}v zwUGqKh(L%8CPC7Zw-qj^e-X#0Bl89sytfC~ELH`7ZkT`1DZ4(t4nhl7oy+}n+# z>8_WL7e|(~K)Kc*dsT+gvJEtaF>G-#F_F;psaI8jBpOCef)lB2OQGgoVKOMP&p=d; zSj+W7yo;j`l8Q(TL#Sgr#i_@u?x_qHdKw1@A=?ZqFSszEvHhWC>OqzYGG8b zP*Sdf$xjPdFw)YO%D8lW6k*$1Vo^6RN#XmN+>F(QsXb>hC7x_ALZdK%^fgKUb5ogW zQzC14Oy(eh=J;Vsd|kepee)POvpWMhc0iWOw_?=_Q?QgXV$6fRAZaXeeBS1uNoTYG zzDe@AV*PFWZ%xKlZX{RnxiX5*=Uwcd! zL+x_hkJmHeas_{Xy$GJX1urGn3Sq&HXA(=f5@xN=(wGP*;tdQ3zamcQTqDi7yXDI8 zCb`zg05iLFUpETYpo>y2IS2>muw4?i5jC|d$VaOkOauQ65-qNa0GWQWd9?K$TN%ELe>BiI$)xeb0+_FObhv?k$-^L(}|Uh0lP@ZPJi zYK5>WX5u*x1~cE~Q1-w_{RHLEwg6~JBy;-Y>}Yu4eS!cmT=!4k#dNSfAP}oGIX7^X ziB8#y&^Tl&ezEF8OQWMlpMdG2m)K)8v>OJ~snJLREA|j=+jc%D%Kcg8QQs8`CKmiUmECrm z&5Cw*Z2*VS4|D8{4CLY`WT{Hm z4u7nMBktDg@TA0e3vzf^6(0ppWR^=cDOgwM{T!n#p*gjD?!&_suZY|2Ljs}}Wsg(8 zvYz23@^~{}SBy|2t9)83eMBFXwJ%hl56X1sP)^W}b2iGS!cof)z#G_95N3}CwXt9O ztI}mal*>U#8TsfTD61rS=Tr5Kb_^&kaJOdF=u+s1gpp#}yXUbEy)mqC;dNF6$pt<} zh|KOMR}CMT8*s`Ek$Y1c^$&}!OT_o)mL+{ZMaej4&R|NA1r(8r{BSESuj z%bpW(M~z=2NO*_--`%REREpuJ5~(l1^!SgwK>k>j{a$G$O@8!Wwn&2}eQoos(;ThO zKB`&o^(Y8L#e;H#p{o#);ewa*5Axwu90m^KuPfRIQXpMVK!QnoYdk-l3_FzZo0_oM zEvH}(yjoZoD8)iY`wxT8L!IJ9rp?#`JBiRuaIme+ZPg{5a3O-+pm>E z7@xtTHTKnFNe81yw9jRlt6X&%TlO;rgQ~S@=R1US`8)DL@W2T}(W5l53HwV>8IJI3 zS2rRq#0ES8omp$@3NzT1dZ>C8>(+p8$AU|BL&-E!op`VX<;ksR>6Xro%W>jxE;Ng> zlZ|ehqNy;GXwqF~ZzPtYZ!|bI0;WSk+|e^*H3<>#1!iHP`j#J-OVv$lmAI=-_=-5Q zu}-cAm0Shc;|TL+F%aT^+}-rVH2Ez)0bvGQ>USaX$pu$m&=wE#&Trw9)HnIh<$vgH zTR1nFfi1FNUYfQL!xbm+)&r5LD%bU0bN(2izoyn4VaeVG_q}ME8*kDbp?D()j5NwX zRAYO%(z?sI=|d?ET9*^;XAHc{FVM*t3pQ9C+SdU_SO&Lg9Sq$3zQXHh+$yisp(UEN z=ack|RS`ZmfIUgR?t>}=rRq8wBvkY)bl9-;NIw1-gDWF2W^B4-fj1D;Z{HlXZ-HOZ4!T zZf@`9g3(;7KqZD;I&e7VQ;H%TGqdcPR5e#j>_t4|5`-O0Z;@8SDLvQglbS?Wb39!= zMihL0;GFN=1ff#|OIpA(Q8zDiEb7`TZ4&`~y%?|&`Tywae2&^Sf2xE2GMknu08~L` z5xDCC8XXQ*s97GXkUEG>C@{?Z1u#hT#IKU4m^wV`4^+|Xo3{>UB1KN1?>FG31jC8n zc>%O|)#6nrl7-eYMn;B`Z1Wwr4j=C?9w5D(OUa_TU%ld}J~igg$wrOzXT6zHji zKm|l5FcZ@i=x7Q>6ROyzNF7c|#OpGIC8&>+Gl5ks7-Si!`S+f1pC&ZXb(9(Fo8-!11WI;dqESg4#j zF>vpw6lK2guZ^ft9-|Lpj{O2CIkto6^TEn7sJWveME+tOR70soFP25)w)Mm4o5YDZS ztKqax{tGl`w1e`yd3&-2NoT6V=Pmo4I2wz=$m&9kxwMaiaooG#%&rR4(oMN=3c|** zKNL6`f_2&Sc-yJIPYW-WM)q5OUN8f7m? zIyYRctk4EywjeWayt}|YE(7Fy$2>B|Dd&6c50Ik!5apLuIYnX+bwO-udlNJccA?%D zV6zL%8x6cO1e?U}A4jMVIRh0u1dE&qFZ-+A-uR0ME@F9GQG z^nbf*0PM5v&Gjwpgq(Es|0RL@r+GbkSR9ld#b4%@G3RrgsyWqO=V7e^cPCFIk`lUs;YxM3uiIR@T zcJ^(b0&bt%EKeEyB6L|qmj`)kM2E-#FnY{#SH`7 zI)rJ*eyiOHl;`|HeTZj1L9Pi55k(l-{r)gDiNWW4>{{>?3E2{>z0_hxMnzxL5o!~h z?(*SC#or~}%vjN9s$`2@_pV@4(SQmv&nWKA{rP+HeeQOGM(>q&>%cQ2xZaZJ&wh5;?I0GNna z|F%{Bw21ui(FIsatbP?Xi&OZQYO9CE?6@okhNavwxF8(1rM?#d9Ac^t8aiDP;fXHh zF!iqLghO}68vI)5$97Sj>-|Wg^aU2%O7S%TSHRwneYEkarPj0D;{oD*dqf!1mfrcP z68shkbw5HCxi0h|lBT$FboBZiil&(I#<4xL5HvQDCZnA>M*NyN1F_AGJ4BTp{vMn= zYS)BgN;v4!O(||-E@t5z^YG#`2qX}zTa+~gP zUB820#`Y8p!;aE1gc?#ErsB~Y5?}m63Kh2b>b)G&G9~#MuKngPKfPH`0JU+sW}W(y z&8tziaZcUH9s-oGRqie)^%*vcPgzz+jSUV}nKp0&vUxdZk(RKO8X8wV1Wbj1Pz^O~ zdHdy<`b82gZ48S@%VfKJueW@@e8!^++56+Kl!ipYdp?iDY?sT$(&~D*S++89xu4sk z5FW?pECC(Js~VR_rM?S1_5}m>JwIF*ckm~Si39S|<^s#$rIg*dPwS7VEgwoHv<5zb zHKfvE2Dl@8{Z5W@INZg6@4Y~jB+IZ;t2Q}Up2cNT~97Xk~Z|Jo|u|(nGJk_$d zd_~GVJ1!TUyL}>4LFpWHP=dj6Ug!GT*;iFzMjd#(*Q`g1*MwB1@> z;_>u+gs=*F0}8#rGsle35dn-l8h6F-%#Q1f3yv!k;M8-WuA(2bby@(Yx^!d}FdgvY zBv!j(SZL715n7DZZDB86wNv2^x^Q6h&?{@|*k6~UbI-2P*ioZq22WJ`TlL|UOZ=>? zp8X2vHouLm!Cb@8#pkDtqa9MgIK>im5|$;rH*kH8y-D^KNg9K;L-i=x%7ct^&6k+< z`t0}tqM;->6V-J=KILK)rf;XYsr$nL{w=FM+NPTALmexS^eC-6pW-k}Dg1x1d)JX0 z>(ObtS2=%dYGWO%>a!}@` zzeef8{NuD(XS;d8ko|0&AoQJBBAe(s-fPSd)2ITI)><4)Y)opoMQ-T7zKnJA4%mYaXwF-o2uMW5%coztRNCf z3}-QmHDjp=vnVzI-SJ7II2wgRYGF~;lJ)^B3x(`2Nr)y>=Zuuerf1&?E52#IfsKwt z4@yT7e`DnT!P;+b8S3O{5{62T&l$RO(&J5`JjS*(C52_$a%Fq7jErBloRe4Jr;?Fq zXf_tIZHzvi&d z;xyIFGmH=b|2#;jjm4?}fXhA*;FKKjcVG|{AVBQ^&@gUq{sj#fHUBCA53d(RN=TlM zMod~5pt||rx6RypU;Na1kq7*KKI(J%DGm0Y+obrU#e{|A6Q$G?dI z28v%wGT{2BhRomH0WgdHQriB!<4@I@zq|40dw4FpfXiRxYW~bP@E7L!$3WmdC?KFu zfV+R{Ha-^|z)I;i07^F6R#y5pLQeX+_Ww~8eOayTeP!EX0Mr8DNAwH{xT1jhEvlFx zKsCqAQd{qjh1N?vooi#N9l-hk>-8Vr5YLdvfY8M`?uWQOHAFl z!(?j!CIZ|aO7YK_Jb>Q)8%)4r(9GTzpsXrvY^KkrtE+EoE2V9rZTP-QFo@{>i@^-{w+t_RVMPH+CH`kK zq?KS?PXU^N1(3huzq^Vg;IF@F27tt^owk`7@t*>sFKc_`qMC37sO>bMwm-AUJQpp% z`27tYzon(!PyIQopNT#Jd)jA##+T4WK6(Z@0K93y@69tV;41m;Z=wJ5s{cn_^PK6L z6aj4q1o*K3oK2sL2kmbGB`ppAXLnn=U7^4LD8MfRIv3$H65t9EU?Tm^ga5mBzaMfC z=J?6DfJtE=@YV?a3(g2IJN^dl&vyAe+RLP?&wyPYev2mZ`xLA%o8)DRt7qIYjlaSD zUBauE&@a;xJwwZD{TBMqnejJCie5s$%nI`iO{e>xZ|a{j_HVzdzXJaBnfwXxvmgB| zfN}Ioi39-t(pW$BX~PNCcl?;f2oW6%mQNn7nc7ImE4y+FSS~qd1f5`!t+aK zda)&X3Hwr_^cj}L=`XPV-f{gCmEV^{FC_(^iH_X#O|CgitOXQdC!q3RYIsZ5E?;HB1tI{(jU&&uE z|N2_~#&PLo?O(ctJTnQD{{yDKaS(aQ^zz{NnJKXPA29u&v*61U#Ap2F+JAumcjt*O zALZo%&NJ25`hQII`{SIKR4)zZ&s42Ve@FE{%;+!SUs`~l;pJNX4*t*a_j`NrOOlsH zv}Y3gFMlEV_XPh-ul_&gke7y$XLz8_|19bLmzm_>b^W(dZgeiU?c<>M2Z3b7YOM80USuVL;wH) literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..9f7b58b --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jun 09 23:06:52 EDT 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-bin.zip diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..4453cce --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## 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 + +# Escape application args +save ( ) { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat 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..74fc652 --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +include 'desktop', 'core' \ No newline at end of file From f0da595d203bd7dd178bc6583c1cd7ade3563822 Mon Sep 17 00:00:00 2001 From: Retera Date: Sun, 8 Sep 2019 16:39:20 -0500 Subject: [PATCH 003/116] Upgrade code --- .gitignore | 128 ++++ build.gradle | 5 + core/build.gradle | 2 +- .../etheller/warsmash/WarsmashGdxGame.java | 45 +- .../warsmash/parsers/mdlx/AnimatedObject.java | 21 +- .../warsmash/parsers/mdlx/AnimationMap.java | 65 +- .../warsmash/parsers/mdlx/Attachment.java | 99 +++ .../etheller/warsmash/parsers/mdlx/Bone.java | 88 +++ .../warsmash/parsers/mdlx/Camera.java | 128 ++++ .../warsmash/parsers/mdlx/CollisionShape.java | 167 +++++ .../warsmash/parsers/mdlx/EventObject.java | 77 +++ .../warsmash/parsers/mdlx/Extent.java | 47 ++ .../warsmash/parsers/mdlx/GenericObject.java | 263 ++++++- .../warsmash/parsers/mdlx/Geoset.java | 319 +++++++++ .../parsers/mdlx/GeosetAnimation.java | 102 +++ .../warsmash/parsers/mdlx/Helper.java | 29 + .../etheller/warsmash/parsers/mdlx/Layer.java | 195 ++++++ .../etheller/warsmash/parsers/mdlx/Light.java | 166 +++++ .../warsmash/parsers/mdlx/Material.java | 121 ++++ .../parsers/mdlx/MdlTokenInputStream.java | 22 +- .../parsers/mdlx/MdlTokenOutputStream.java | 38 ++ .../warsmash/parsers/mdlx/MdlxBlock.java | 16 + .../parsers/mdlx/MdlxBlockDescriptor.java | 125 ++++ .../warsmash/parsers/mdlx/MdlxModel.java | 642 ++++++++++++++++++ .../warsmash/parsers/mdlx/MdlxTest.java | 64 ++ .../parsers/mdlx/ParticleEmitter.java | 193 ++++++ .../parsers/mdlx/ParticleEmitter2.java | 425 ++++++++++++ .../warsmash/parsers/mdlx/RibbonEmitter.java | 176 +++++ .../warsmash/parsers/mdlx/Sequence.java | 104 +++ .../warsmash/parsers/mdlx/Texture.java | 81 +++ .../parsers/mdlx/TextureAnimation.java | 57 ++ .../warsmash/parsers/mdlx/UnknownChunk.java | 35 + .../mdlx/mdl/GhostwolfTokenInputStream.java | 232 +++++++ .../mdlx/mdl/GhostwolfTokenOutputStream.java | 261 +++++++ .../mdlx/timeline/FloatArrayKeyFrame.java | 2 +- .../parsers/mdlx/timeline/FloatKeyFrame.java | 2 +- .../parsers/mdlx/timeline/Timeline.java | 8 +- .../parsers/mdlx/timeline/UInt32KeyFrame.java | 8 +- .../warsmash/parsers/terrain/Corner.java | 27 + .../warsmash/parsers/terrain/Terrain.java | 13 + .../warsmash/parsers/terrain/TilePathing.java | 15 + .../etheller/warsmash/util/Descriptor.java | 6 + .../etheller/warsmash/util/ImageUtils.java | 85 +++ .../com/etheller/warsmash/util/MdlUtils.java | 190 ++++++ .../etheller/warsmash/util/ParseUtils.java | 119 +++- .../warsmash/util/RenderMathUtils.java | 290 ++++++++ .../com/etheller/warsmash/util/Vector4.java | 573 ++++++++++++++++ .../warsmash/viewer/BoundingShape.java | 116 ++++ .../com/etheller/warsmash/viewer/Bucket.java | 28 + .../com/etheller/warsmash/viewer/Camera.java | 314 +++++++++ .../com/etheller/warsmash/viewer/Model.java | 9 + .../warsmash/viewer/ModelInstance.java | 5 + .../etheller/warsmash/viewer/ModelView.java | 68 ++ .../com/etheller/warsmash/viewer/Scene.java | 5 + .../etheller/warsmash/viewer/SceneNode.java | 245 +++++++ .../warsmash/viewer/SkeletalNode.java | 118 ++++ .../com/etheller/warsmash/viewer/Viewer.java | 7 + .../etheller/warsmash/viewer/ViewerNode.java | 65 ++ .../com/hiveworkshop/wc3/mpq/Codebase.java | 12 + .../hiveworkshop/wc3/mpq/FileCodebase.java | 35 + jars/blp-iio-plugin.jar | Bin 0 -> 63630 bytes 61 files changed, 6825 insertions(+), 78 deletions(-) create mode 100644 .gitignore create mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/Attachment.java create mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/Bone.java create mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/Camera.java create mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/CollisionShape.java create mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/EventObject.java create mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/Extent.java create mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/Geoset.java create mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/GeosetAnimation.java create mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/Helper.java create mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/Layer.java create mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/Light.java create mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/Material.java create mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/MdlxBlock.java create mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/MdlxBlockDescriptor.java create mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/MdlxModel.java create mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/MdlxTest.java create mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/ParticleEmitter.java create mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/ParticleEmitter2.java create mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/RibbonEmitter.java create mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/Sequence.java create mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/Texture.java create mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/TextureAnimation.java create mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/UnknownChunk.java create mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/mdl/GhostwolfTokenInputStream.java create mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/mdl/GhostwolfTokenOutputStream.java create mode 100644 core/src/com/etheller/warsmash/parsers/terrain/Corner.java create mode 100644 core/src/com/etheller/warsmash/parsers/terrain/Terrain.java create mode 100644 core/src/com/etheller/warsmash/parsers/terrain/TilePathing.java create mode 100644 core/src/com/etheller/warsmash/util/Descriptor.java create mode 100644 core/src/com/etheller/warsmash/util/ImageUtils.java create mode 100644 core/src/com/etheller/warsmash/util/RenderMathUtils.java create mode 100644 core/src/com/etheller/warsmash/util/Vector4.java create mode 100644 core/src/com/etheller/warsmash/viewer/BoundingShape.java create mode 100644 core/src/com/etheller/warsmash/viewer/Bucket.java create mode 100644 core/src/com/etheller/warsmash/viewer/Camera.java create mode 100644 core/src/com/etheller/warsmash/viewer/Model.java create mode 100644 core/src/com/etheller/warsmash/viewer/ModelInstance.java create mode 100644 core/src/com/etheller/warsmash/viewer/ModelView.java create mode 100644 core/src/com/etheller/warsmash/viewer/Scene.java create mode 100644 core/src/com/etheller/warsmash/viewer/SceneNode.java create mode 100644 core/src/com/etheller/warsmash/viewer/SkeletalNode.java create mode 100644 core/src/com/etheller/warsmash/viewer/Viewer.java create mode 100644 core/src/com/etheller/warsmash/viewer/ViewerNode.java create mode 100644 core/src/com/hiveworkshop/wc3/mpq/Codebase.java create mode 100644 core/src/com/hiveworkshop/wc3/mpq/FileCodebase.java create mode 100644 jars/blp-iio-plugin.jar diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8d97c25 --- /dev/null +++ b/.gitignore @@ -0,0 +1,128 @@ +## Java + +*.class +*.war +*.ear +hs_err_pid* + +## Robovm +/ios/robovm-build/ + +## GWT +/html/war/ +/html/gwt-unitCache/ +.apt_generated/ +.gwt/ +gwt-unitCache/ +www-test/ +.gwt-tmp/ + +## Android Studio and Intellij and Android in general +/android/libs/armeabi/ +/android/libs/armeabi-v7a/ +/android/libs/arm64-v8a/ +/android/libs/x86/ +/android/libs/x86_64/ +/android/gen/ +.idea/ +*.ipr +*.iws +*.iml +/android/out/ +com_crashlytics_export_strings.xml + +## Eclipse + +.classpath +.project +.metadata/ +/android/bin/ +/core/bin/ +/desktop/bin/ +/html/bin/ +/ios/bin/ +/ios-moe/bin/ +*.tmp +*.bak +*.swp +*~.nib +.settings/ +.loadpath +.externalToolBuilders/ +*.launch + +## NetBeans + +/nbproject/private/ +/android/nbproject/private/ +/core/nbproject/private/ +/desktop/nbproject/private/ +/html/nbproject/private/ +/ios/nbproject/private/ +/ios-moe/nbproject/private/ + +/build/ +/android/build/ +/core/build/ +/desktop/build/ +/html/build/ +/ios/build/ +/ios-moe/build/ + +/nbbuild/ +/android/nbbuild/ +/core/nbbuild/ +/desktop/nbbuild/ +/html/nbbuild/ +/ios/nbbuild/ +/ios-moe/nbbuild/ + +/dist/ +/android/dist/ +/core/dist/ +/desktop/dist/ +/html/dist/ +/ios/dist/ +/ios-moe/dist/ + +/nbdist/ +/android/nbdist/ +/core/nbdist/ +/desktop/nbdist/ +/html/nbdist/ +/ios/nbdist/ +/ios-moe/nbdist/ + +nbactions.xml +nb-configuration.xml + +## Gradle + +/local.properties +.gradle/ +gradle-app.setting +/build/ +/android/build/ +/core/build/ +/desktop/build/ +/html/build/ +/ios/build/ +/ios-moe/build/ + +## OS Specific +.DS_Store +Thumbs.db + +## iOS +/ios/xcode/*.xcodeproj/* +!/ios/xcode/*.xcodeproj/xcshareddata +!/ios/xcode/*.xcodeproj/project.pbxproj +/ios/xcode/native/ + +/ios-moe/xcode/*.xcodeproj/* +!/ios-moe/xcode/*.xcodeproj/xcshareddata +!/ios-moe/xcode/*.xcodeproj/project.pbxproj +/ios-moe/xcode/native/ + +## data +/core/data diff --git a/build.gradle b/build.gradle index 66947a9..5a5d981 100644 --- a/build.gradle +++ b/build.gradle @@ -3,6 +3,9 @@ buildscript { repositories { mavenLocal() + flatDir { + dirs "$rootProject.projectDir/jars" + } mavenCentral() maven { url "https://plugins.gradle.org/m2/" } maven { url "https://oss.sonatype.org/content/repositories/snapshots/" } @@ -51,6 +54,7 @@ project(":desktop") { compile "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-desktop" compile "com.google.guava:guava:23.5-jre" implementation 'com.github.inwc3:wc3libs:-SNAPSHOT' + compile files(fileTree(dir:'../jars', includes: ['*.jar'])) } } @@ -65,6 +69,7 @@ project(":core") { compile "com.badlogicgames.gdx:gdx-freetype:$gdxVersion" compile "com.google.guava:guava:23.5-jre" implementation 'com.github.inwc3:wc3libs:-SNAPSHOT' + compile files(fileTree(dir:'../jars', includes: ['*.jar'])) } } diff --git a/core/build.gradle b/core/build.gradle index 42ff9fd..13c049a 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -1,6 +1,6 @@ apply plugin: "java" -sourceCompatibility = 1.7 +sourceCompatibility = 1.8 [compileJava, compileTestJava]*.options*.encoding = 'UTF-8' sourceSets.main.java.srcDirs = [ "src/" ] diff --git a/core/src/com/etheller/warsmash/WarsmashGdxGame.java b/core/src/com/etheller/warsmash/WarsmashGdxGame.java index b9a6a16..c3260a0 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxGame.java @@ -1,27 +1,62 @@ package com.etheller.warsmash; -import java.nio.charset.Charset; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; + +import javax.imageio.ImageIO; import com.badlogic.gdx.ApplicationAdapter; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.GL20; +import com.badlogic.gdx.graphics.Texture; +import com.badlogic.gdx.graphics.Texture.TextureFilter; +import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.etheller.warsmash.util.ImageUtils; import com.etheller.warsmash.util.War3ID; +import com.hiveworkshop.wc3.mpq.Codebase; +import com.hiveworkshop.wc3.mpq.FileCodebase; public class WarsmashGdxGame extends ApplicationAdapter { + private SpriteBatch batch; + private BitmapFont font; + private Codebase codebase; + private Texture texture; @Override public void create() { + this.codebase = new FileCodebase(new File("C:/MPQBuild/War3.mpq/war3.mpq")); + final War3ID id = War3ID.fromString("ipea"); - System.out.println(id.getValue()); - for (final byte b : "Hello World".getBytes(Charset.forName("utf-8"))) { - System.out.println(b + " - " + (char) b); + try { + final String path = "terrainart\\lordaeronsummer\\lords_dirt.blp"; + final boolean has = this.codebase.has(path); + final BufferedImage img = ImageIO.read(this.codebase.getResourceAsStream(path)); + this.texture = ImageUtils.getTexture(ImageUtils.forceBufferedImagesRGB(img)); + this.texture.setFilter(TextureFilter.Linear, TextureFilter.Linear); } + catch (final IOException e) { + throw new RuntimeException(e); + } + this.batch = new SpriteBatch(); + this.font = new BitmapFont(); } @Override public void render() { - Gdx.gl.glClearColor(1, 0, 0, 1); + Gdx.gl.glClearColor(0, 1, 0, 1); Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); + final int srcFunc = this.batch.getBlendSrcFunc(); + final int dstFunc = this.batch.getBlendDstFunc(); + + this.batch.enableBlending(); + this.batch.begin(); +// this.font.draw(this.batch, "Hello World", 100, 100); + this.batch.setBlendFunction(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA); + this.batch.draw(this.texture, 0, 0); + this.batch.end(); + this.batch.setBlendFunction(srcFunc, dstFunc); } @Override diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/AnimatedObject.java b/core/src/com/etheller/warsmash/parsers/mdlx/AnimatedObject.java index 2af1da3..c15855a 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/AnimatedObject.java +++ b/core/src/com/etheller/warsmash/parsers/mdlx/AnimatedObject.java @@ -6,6 +6,7 @@ import java.util.Iterator; import java.util.List; import com.etheller.warsmash.parsers.mdlx.timeline.Timeline; +import com.etheller.warsmash.util.MdlUtils; import com.etheller.warsmash.util.War3ID; import com.google.common.io.LittleEndianDataInputStream; import com.google.common.io.LittleEndianDataOutputStream; @@ -14,8 +15,8 @@ import com.google.common.io.LittleEndianDataOutputStream; * Based on the works of Chananya Freiman. * */ -public class AnimatedObject implements Chunk { - private final List timelines; +public abstract class AnimatedObject implements Chunk, MdlxBlock { + protected final List timelines; public AnimatedObject() { this.timelines = new ArrayList<>(); @@ -41,20 +42,20 @@ public class AnimatedObject implements Chunk { } public Iterator readAnimatedBlock(final MdlTokenInputStream stream) { - return new TransformedAnimatedBlockIterator(stream.readBlock()); + return new TransformedAnimatedBlockIterator(stream.readBlock().iterator()); } - public void readTimeline(final MdlTokenInputStream stream, final War3ID name) throws IOException { - final Timeline timeline = AnimationMap.ID_TO_TAG.get(name).getImplementation().createTimeline(); + public void readTimeline(final MdlTokenInputStream stream, final AnimationMap name) throws IOException { + final Timeline timeline = name.getImplementation().createTimeline(); - timeline.readMdl(stream, name); + timeline.readMdl(stream, name.getWar3id()); this.timelines.add(timeline); } - public boolean writeTimeline(final MdlTokenOutputStream stream, final War3ID name) throws IOException { + public boolean writeTimeline(final MdlTokenOutputStream stream, final AnimationMap name) throws IOException { for (final Timeline timeline : this.timelines) { - if (timeline.getName().equals(name)) { + if (timeline.getName().equals(name.getWar3id())) { timeline.writeMdl(stream); return true; } @@ -93,8 +94,8 @@ public class AnimatedObject implements Chunk { @Override public String next() { final String token = this.delegate.next(); - if (token.equals("static") && hasNext()) { - return "static " + this.delegate.next(); + if (token.equals(MdlUtils.TOKEN_STATIC) && hasNext()) { + return MdlUtils.TOKEN_STATIC + " " + this.delegate.next(); } return token; } diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/AnimationMap.java b/core/src/com/etheller/warsmash/parsers/mdlx/AnimationMap.java index a8b871e..ec9110f 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/AnimationMap.java +++ b/core/src/com/etheller/warsmash/parsers/mdlx/AnimationMap.java @@ -3,6 +3,7 @@ package com.etheller.warsmash.parsers.mdlx; import java.util.HashMap; import java.util.Map; +import com.etheller.warsmash.util.MdlUtils; import com.etheller.warsmash.util.War3ID; /** @@ -15,33 +16,33 @@ import com.etheller.warsmash.util.War3ID; */ public enum AnimationMap { // Layer - KMTF("TextureId", TimelineDescriptor.UINT32_TIMELINE), - KMTA("Alpha", TimelineDescriptor.FLOAT_TIMELINE), + KMTF(MdlUtils.TOKEN_TEXTURE_ID, TimelineDescriptor.UINT32_TIMELINE), + KMTA(MdlUtils.TOKEN_ALPHA, TimelineDescriptor.FLOAT_TIMELINE), // TextureAnimation - KTAT("Translation", TimelineDescriptor.VECTOR3_TIMELINE), - KTAR("Rotation", TimelineDescriptor.VECTOR4_TIMELINE), - KTAS("Scaling", TimelineDescriptor.VECTOR3_TIMELINE), + KTAT(MdlUtils.TOKEN_TRANSLATION, TimelineDescriptor.VECTOR3_TIMELINE), + KTAR(MdlUtils.TOKEN_ROTATION, TimelineDescriptor.VECTOR4_TIMELINE), + KTAS(MdlUtils.TOKEN_SCALING, TimelineDescriptor.VECTOR3_TIMELINE), // GeosetAnimation - KGAO("Alpha", TimelineDescriptor.FLOAT_TIMELINE), - KGAC("Color", TimelineDescriptor.VECTOR3_TIMELINE), + KGAO(MdlUtils.TOKEN_ALPHA, TimelineDescriptor.FLOAT_TIMELINE), + KGAC(MdlUtils.TOKEN_COLOR, TimelineDescriptor.VECTOR3_TIMELINE), // Light - KLAS("AttenuationStart", TimelineDescriptor.FLOAT_TIMELINE), - KLAE("AttenuationEnd", TimelineDescriptor.FLOAT_TIMELINE), - KLAC("Color", TimelineDescriptor.VECTOR3_TIMELINE), - KLAI("Intensity", TimelineDescriptor.FLOAT_TIMELINE), - KLBI("AmbientIntensity", TimelineDescriptor.FLOAT_TIMELINE), - KLBC("AmbientColor", TimelineDescriptor.VECTOR3_TIMELINE), - KLAV("Visibility", TimelineDescriptor.FLOAT_TIMELINE), + KLAS(MdlUtils.TOKEN_ATTENUATION_START, TimelineDescriptor.FLOAT_TIMELINE), + KLAE(MdlUtils.TOKEN_ATTENUATION_END, TimelineDescriptor.FLOAT_TIMELINE), + KLAC(MdlUtils.TOKEN_COLOR, TimelineDescriptor.VECTOR3_TIMELINE), + KLAI(MdlUtils.TOKEN_INTENSITY, TimelineDescriptor.FLOAT_TIMELINE), + KLBI(MdlUtils.TOKEN_AMB_INTENSITY, TimelineDescriptor.FLOAT_TIMELINE), + KLBC(MdlUtils.TOKEN_AMB_COLOR, TimelineDescriptor.VECTOR3_TIMELINE), + KLAV(MdlUtils.TOKEN_VISIBILITY, TimelineDescriptor.FLOAT_TIMELINE), // Attachment - KATV("Visibility", TimelineDescriptor.FLOAT_TIMELINE), + KATV(MdlUtils.TOKEN_VISIBILITY, TimelineDescriptor.FLOAT_TIMELINE), // ParticleEmitter - KPEE("EmissionRate", TimelineDescriptor.FLOAT_TIMELINE), - KPEG("Gravity", TimelineDescriptor.FLOAT_TIMELINE), - KPLN("Longitude", TimelineDescriptor.FLOAT_TIMELINE), - KPLT("Latitude", TimelineDescriptor.FLOAT_TIMELINE), - KPEL("LifeSpan", TimelineDescriptor.FLOAT_TIMELINE), - KPES("Speed", TimelineDescriptor.FLOAT_TIMELINE), - KPEV("Visibility", TimelineDescriptor.FLOAT_TIMELINE), + KPEE(MdlUtils.TOKEN_EMISSION_RATE, TimelineDescriptor.FLOAT_TIMELINE), + KPEG(MdlUtils.TOKEN_GRAVITY, TimelineDescriptor.FLOAT_TIMELINE), + KPLN(MdlUtils.TOKEN_LONGITUDE, TimelineDescriptor.FLOAT_TIMELINE), + KPLT(MdlUtils.TOKEN_LATITUDE, TimelineDescriptor.FLOAT_TIMELINE), + KPEL(MdlUtils.TOKEN_LIFE_SPAN, TimelineDescriptor.FLOAT_TIMELINE), + KPES(MdlUtils.TOKEN_INIT_VELOCITY, TimelineDescriptor.FLOAT_TIMELINE), + KPEV(MdlUtils.TOKEN_VISIBILITY, TimelineDescriptor.FLOAT_TIMELINE), // ParticleEmitter2 KP2S("Speed", TimelineDescriptor.FLOAT_TIMELINE), KP2R("Variation", TimelineDescriptor.FLOAT_TIMELINE), @@ -59,19 +60,21 @@ public enum AnimationMap { KRTX("TextureSlot", TimelineDescriptor.UINT32_TIMELINE), KRVS("Visibility", TimelineDescriptor.FLOAT_TIMELINE), // Camera - KCTR("Translation", TimelineDescriptor.VECTOR3_TIMELINE), - KTTR("Translation", TimelineDescriptor.VECTOR3_TIMELINE), - KCRL("Rotation", TimelineDescriptor.UINT32_TIMELINE), + KCTR(MdlUtils.TOKEN_TRANSLATION, TimelineDescriptor.VECTOR3_TIMELINE), + KTTR(MdlUtils.TOKEN_TRANSLATION, TimelineDescriptor.VECTOR3_TIMELINE), + KCRL(MdlUtils.TOKEN_ROTATION, TimelineDescriptor.UINT32_TIMELINE), // GenericObject - KGTR("Translation", TimelineDescriptor.VECTOR3_TIMELINE), - KGRT("Rotation", TimelineDescriptor.VECTOR4_TIMELINE), - KGSC("Scaling", TimelineDescriptor.VECTOR3_TIMELINE); + KGTR(MdlUtils.TOKEN_TRANSLATION, TimelineDescriptor.VECTOR3_TIMELINE), + KGRT(MdlUtils.TOKEN_ROTATION, TimelineDescriptor.VECTOR4_TIMELINE), + KGSC(MdlUtils.TOKEN_SCALING, TimelineDescriptor.VECTOR3_TIMELINE); private final String mdlToken; private final TimelineDescriptor implementation; + private final War3ID war3id; private AnimationMap(final String mdlToken, final TimelineDescriptor implementation) { this.mdlToken = mdlToken; this.implementation = implementation; + this.war3id = War3ID.fromString(this.name()); } public String getMdlToken() { @@ -82,11 +85,15 @@ public enum AnimationMap { return this.implementation; } + public War3ID getWar3id() { + return this.war3id; + } + public static final Map ID_TO_TAG = new HashMap<>(); static { for (final AnimationMap tag : AnimationMap.values()) { - ID_TO_TAG.put(War3ID.fromString(tag.name()), tag); + ID_TO_TAG.put(tag.getWar3id(), tag); } } } diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/Attachment.java b/core/src/com/etheller/warsmash/parsers/mdlx/Attachment.java new file mode 100644 index 0000000..85d65b0 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/mdlx/Attachment.java @@ -0,0 +1,99 @@ +package com.etheller.warsmash.parsers.mdlx; + +import java.io.IOException; + +import com.etheller.warsmash.util.MdlUtils; +import com.etheller.warsmash.util.ParseUtils; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +public class Attachment extends GenericObject { + private String path = ""; + private int attachmentId; + + public Attachment() { + super(0x800); + } + + /** + * Restricts us to only be able to parse models on one thread at a time, in + * return for high performance. + */ + private static final byte[] PATH_BYTES_HEAP = new byte[260]; + + @Override + public void readMdx(final LittleEndianDataInputStream stream) throws IOException { + final long size = ParseUtils.readUInt32(stream); + + super.readMdx(stream); + + this.path = ParseUtils.readString(stream, PATH_BYTES_HEAP); + this.attachmentId = stream.readInt(); + + this.readTimelines(stream, size - this.getByteLength()); + } + + @Override + public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException { + ParseUtils.writeUInt32(stream, getByteLength()); + + super.writeMdx(stream); + + final byte[] bytes = this.path.getBytes(ParseUtils.UTF8); + stream.write(bytes); + for (int i = 0; i < (260 - bytes.length); i++) { + stream.write((byte) 0); + } + stream.writeInt(this.attachmentId); // Used to be Int32 in JS + + this.writeNonGenericAnimationChunks(stream); + } + + @Override + public void readMdl(final MdlTokenInputStream stream) throws IOException { + for (final String token : super.readMdlGeneric(stream)) { + if (MdlUtils.TOKEN_ATTACHMENT_ID.equals(token)) { + this.attachmentId = stream.readInt(); + } + else if (MdlUtils.TOKEN_PATH.equals(token)) { + this.path = stream.read(); + } + else if (MdlUtils.TOKEN_VISIBILITY.equals(token)) { + this.readTimeline(stream, AnimationMap.KATV); + } + else { + throw new IOException("Unknown token in Attachment " + this.name + ": " + token); + } + } + } + + @Override + public void writeMdl(final MdlTokenOutputStream stream) throws IOException { + stream.startObjectBlock(MdlUtils.TOKEN_ATTACHMENT, this.name); + this.writeGenericHeader(stream); + + // flowtsohg asks in his JS: + // Is this needed? MDX supplies it, but MdlxConv does not use it. + // Retera: + // I tried to preserve it when it was shown, but omit it when it was omitted + // for MDL in Matrix Eater. Matrix Eater's MDL -> MDX is generating them + // and discarding what was read from the MDL. The Matrix Eater is notably + // buggy from a cursory read through, and would always omit AttachmentID 0 + // in MDL output. + stream.writeAttrib(MdlUtils.TOKEN_ATTACHMENT_ID, this.attachmentId); + + if ((this.path != null) && (this.path.length() > 0)) { + stream.writeStringAttrib(MdlUtils.TOKEN_PATH, this.path); + } + + this.writeTimeline(stream, AnimationMap.KATV); + + this.writeGenericTimelines(stream); + stream.endBlock(); + } + + @Override + public long getByteLength() { + return 268 + super.getByteLength(); + } +} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/Bone.java b/core/src/com/etheller/warsmash/parsers/mdlx/Bone.java new file mode 100644 index 0000000..8c4fe7a --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/mdlx/Bone.java @@ -0,0 +1,88 @@ +package com.etheller.warsmash.parsers.mdlx; + +import java.io.IOException; + +import com.etheller.warsmash.util.MdlUtils; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +public class Bone extends GenericObject { + private int geosetId = -1; + private int geosetAnimationId = -1; + + public Bone() { + super(0x100); + } + + @Override + public void readMdx(final LittleEndianDataInputStream stream) throws IOException { + super.readMdx(stream); + + this.geosetId = stream.readInt(); + this.geosetAnimationId = stream.readInt(); + } + + @Override + public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException { + super.writeMdx(stream); + stream.writeInt(this.geosetId); + stream.writeInt(this.geosetAnimationId); + } + + @Override + public void readMdl(final MdlTokenInputStream stream) { + for (String token : super.readMdlGeneric(stream)) { + if (MdlUtils.TOKEN_GEOSETID.equals(token)) { + token = stream.read(); + + if (MdlUtils.TOKEN_MULTIPLE.equals(token)) { + this.geosetId = -1; + } + else { + this.geosetId = Integer.parseInt(token); + } + } + else if (MdlUtils.TOKEN_GEOSETANIMID.equals(token)) { + token = stream.read(); + + if (MdlUtils.TOKEN_NONE.equals(token)) { + this.geosetAnimationId = -1; + } + else { + this.geosetAnimationId = Integer.parseInt(token); + } + } + else { + throw new RuntimeException("Unknown token in Bone " + this.name + ": " + token); + } + } + } + + @Override + public void writeMdl(final MdlTokenOutputStream stream) throws IOException { + stream.startObjectBlock(MdlUtils.TOKEN_BONE, this.name); + this.writeGenericHeader(stream); + + if (this.geosetId == -1) { + stream.writeAttrib(MdlUtils.TOKEN_GEOSETID, MdlUtils.TOKEN_MULTIPLE); + } + else { + stream.writeAttrib(MdlUtils.TOKEN_GEOSETID, this.geosetId); + } + if (this.geosetAnimationId == -1) { + stream.writeAttrib(MdlUtils.TOKEN_GEOSETANIMID, MdlUtils.TOKEN_NONE); + } + else { + stream.writeAttrib(MdlUtils.TOKEN_GEOSETANIMID, this.geosetAnimationId); + } + + this.writeGenericTimelines(stream); + stream.endBlock(); + } + + @Override + public long getByteLength() { + return 8 + super.getByteLength(); + } + +} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/Camera.java b/core/src/com/etheller/warsmash/parsers/mdlx/Camera.java new file mode 100644 index 0000000..1c3c363 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/mdlx/Camera.java @@ -0,0 +1,128 @@ +package com.etheller.warsmash.parsers.mdlx; + +import java.io.IOException; + +import com.etheller.warsmash.util.MdlUtils; +import com.etheller.warsmash.util.ParseUtils; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +public class Camera extends AnimatedObject { + protected String name; + private final float[] position; + private float fieldOfView; + private float farClippingPlane; + private float nearClippingPlane; + private final float[] targetPosition; + + public Camera() { + this.position = new float[3]; + this.targetPosition = new float[3]; + } + + /** + * Restricts us to only be able to parse models on one thread at a time, in + * return for high performance. + */ + private static final byte[] NAME_BYTES_HEAP = new byte[80]; + + @Override + public void readMdx(final LittleEndianDataInputStream stream) throws IOException { + final long size = ParseUtils.readUInt32(stream); + + this.name = ParseUtils.readString(stream, NAME_BYTES_HEAP); + ParseUtils.readFloatArray(stream, this.position); + this.fieldOfView = stream.readFloat(); + this.farClippingPlane = stream.readFloat(); + this.nearClippingPlane = stream.readFloat(); + ParseUtils.readFloatArray(stream, this.targetPosition); + + readTimelines(stream, size - 120); + } + + @Override + public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException { + ParseUtils.writeUInt32(stream, getByteLength()); + final byte[] bytes = this.name.getBytes(ParseUtils.UTF8); + stream.write(bytes); + for (int i = 0; i < (80 - bytes.length); i++) { + stream.write((byte) 0); + } + ParseUtils.writeFloatArray(stream, this.position); + stream.writeFloat(this.fieldOfView); + stream.writeFloat(this.farClippingPlane); + stream.writeFloat(this.nearClippingPlane); + ParseUtils.writeFloatArray(stream, this.targetPosition); + + writeTimelines(stream); + } + + @Override + public void readMdl(final MdlTokenInputStream stream) throws IOException { + this.name = stream.read(); + + for (final String token : stream.readBlock()) { + switch (token) { + case MdlUtils.TOKEN_POSITION: + stream.readFloatArray(this.position); + break; + case MdlUtils.TOKEN_TRANSLATION: + readTimeline(stream, AnimationMap.KCTR); + break; + case MdlUtils.TOKEN_ROTATION: + readTimeline(stream, AnimationMap.KCRL); + break; + case MdlUtils.TOKEN_FIELDOFVIEW: + this.fieldOfView = stream.readFloat(); + break; + case MdlUtils.TOKEN_FARCLIP: + this.farClippingPlane = stream.readFloat(); + break; + case MdlUtils.TOKEN_NEARCLIP: + this.nearClippingPlane = stream.readFloat(); + break; + case MdlUtils.TOKEN_TARGET: + for (final String subToken : stream.readBlock()) { + switch (subToken) { + case MdlUtils.TOKEN_POSITION: + stream.readFloatArray(this.targetPosition); + break; + case MdlUtils.TOKEN_TRANSLATION: + readTimeline(stream, AnimationMap.KTTR); + break; + default: + throw new IllegalStateException( + "Unknown token in Camera " + this.name + "'s Target: " + subToken); + } + } + break; + default: + throw new IllegalStateException("Unknown token in Camera " + this.name + ": " + token); + } + } + } + + @Override + public void writeMdl(final MdlTokenOutputStream stream) throws IOException { + stream.startObjectBlock(MdlUtils.TOKEN_CAMERA, this.name); + + stream.writeFloatArrayAttrib(MdlUtils.TOKEN_POSITION, this.position); + writeTimeline(stream, AnimationMap.KCTR); + writeTimeline(stream, AnimationMap.KCRL); + stream.writeFloatAttrib(MdlUtils.TOKEN_FIELDOFVIEW, this.fieldOfView); + stream.writeFloatAttrib(MdlUtils.TOKEN_FARCLIP, this.farClippingPlane); + stream.writeFloatAttrib(MdlUtils.TOKEN_NEARCLIP, this.nearClippingPlane); + + stream.startBlock(MdlUtils.TOKEN_TARGET); + stream.writeFloatArrayAttrib(MdlUtils.TOKEN_POSITION, this.targetPosition); + writeTimeline(stream, AnimationMap.KTTR); + stream.endBlock(); + + stream.endBlock(); + } + + @Override + public long getByteLength() { + return 120 + super.getByteLength(); + } +} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/CollisionShape.java b/core/src/com/etheller/warsmash/parsers/mdlx/CollisionShape.java new file mode 100644 index 0000000..7e82a21 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/mdlx/CollisionShape.java @@ -0,0 +1,167 @@ +package com.etheller.warsmash.parsers.mdlx; + +import java.io.IOException; + +import com.etheller.warsmash.util.MdlUtils; +import com.etheller.warsmash.util.ParseUtils; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +public class CollisionShape extends GenericObject { + public static enum Type { + PLANE, + BOX, + SPHERE, + CYLINDER; + + private static final Type[] VALUES = values(); + + private final boolean boundsRadius; + + private Type() { + this.boundsRadius = false; + } + + private Type(final boolean boundsRadius) { + this.boundsRadius = boundsRadius; + } + + public boolean isBoundsRadius() { + return this.boundsRadius; + } + + public static Type from(final int index) { + return VALUES[index]; + } + } + + private CollisionShape.Type type; + private final float[][] vertices = { new float[3], new float[3] }; + private float boundsRadius; + + public CollisionShape() { + super(0x2000); + } + + @Override + public void readMdx(final LittleEndianDataInputStream stream) throws IOException { + super.readMdx(stream); + + final long typeIndex = ParseUtils.readUInt32(stream); + this.type = CollisionShape.Type.from((int) typeIndex); + ParseUtils.readFloatArray(stream, this.vertices[0]); + + if (this.type != Type.SPHERE) { + ParseUtils.readFloatArray(stream, this.vertices[1]); + } + if ((this.type == Type.SPHERE) || (this.type == Type.CYLINDER)) { + this.boundsRadius = stream.readFloat(); + } + } + + @Override + public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException { + super.writeMdx(stream); + + ParseUtils.writeUInt32(stream, this.type == null ? -1 : this.type.ordinal()); + ParseUtils.writeFloatArray(stream, this.vertices[0]); + if (this.type != CollisionShape.Type.SPHERE) { + ParseUtils.writeFloatArray(stream, this.vertices[1]); + } + if ((this.type == Type.SPHERE) || (this.type == Type.CYLINDER)) { + stream.writeFloat(this.boundsRadius); + } + } + + @Override + public void readMdl(final MdlTokenInputStream stream) { + for (final String token : super.readMdlGeneric(stream)) { + switch (token) { + case MdlUtils.TOKEN_PLANE: + this.type = Type.PLANE; + break; + case MdlUtils.TOKEN_BOX: + this.type = Type.BOX; + break; + case MdlUtils.TOKEN_SPHERE: + this.type = Type.SPHERE; + break; + case MdlUtils.TOKEN_CYLINDER: + this.type = Type.CYLINDER; + break; + case MdlUtils.TOKEN_VERTICES: + final int count = stream.readInt(); + stream.read(); // { + + stream.readFloatArray(this.vertices[0]); + if (count == 2) { + stream.readFloatArray(this.vertices[1]); + } + + stream.read(); // } + break; + case MdlUtils.TOKEN_BOUNDSRADIUS: + this.boundsRadius = stream.readFloat(); + break; + default: + throw new RuntimeException("Unknown token in CollisionShape " + this.name + ": " + token); + } + } + } + + @Override + public void writeMdl(final MdlTokenOutputStream stream) throws IOException { + stream.startObjectBlock(MdlUtils.TOKEN_COLLISION_SHAPE, this.name); + writeGenericHeader(stream); + String type; + int vertices = 2; + switch (this.type) { + case PLANE: + type = MdlUtils.TOKEN_PLANE; + break; + case BOX: + type = MdlUtils.TOKEN_BOX; + break; + case SPHERE: + type = MdlUtils.TOKEN_SPHERE; + vertices = 1; + break; + case CYLINDER: + type = MdlUtils.TOKEN_CYLINDER; + break; + default: + throw new IllegalStateException("Invalid type in CollisionShape " + this.name + ": " + this.type); + } + + stream.writeFlag(type); + stream.startBlock(MdlUtils.TOKEN_VERTICES, vertices); + stream.writeFloatArray(this.vertices[0]); + if (vertices == 2) { + stream.writeFloatArray(this.vertices[1]); + } + stream.endBlock(); + + if (this.type.boundsRadius) { + stream.writeFloatAttrib(MdlUtils.TOKEN_BOUNDSRADIUS, this.boundsRadius); + } + + writeGenericTimelines(stream); + stream.endBlock(); + } + + @Override + public long getByteLength() { + long size = 16 + super.getByteLength(); + + if (this.type != Type.SPHERE) { + size += 12; + } + + if ((this.type == Type.SPHERE) || (this.type == Type.CYLINDER)) { + size += 4; + } + + return size; + } + +} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/EventObject.java b/core/src/com/etheller/warsmash/parsers/mdlx/EventObject.java new file mode 100644 index 0000000..5e4a62a --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/mdlx/EventObject.java @@ -0,0 +1,77 @@ +package com.etheller.warsmash.parsers.mdlx; + +import java.io.IOException; + +import com.etheller.warsmash.util.MdlUtils; +import com.etheller.warsmash.util.ParseUtils; +import com.etheller.warsmash.util.War3ID; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +public class EventObject extends GenericObject { + private static final War3ID KEVT = War3ID.fromString("KEVT"); + private int globalSequenceId = -1; + private long[] keyFrames = { 1 }; + + public EventObject() { + super(0x400); + } + + @Override + public void readMdx(final LittleEndianDataInputStream stream) throws IOException { + super.readMdx(stream); + stream.readInt(); // KEVT skipped + final long count = ParseUtils.readUInt32(stream); + this.globalSequenceId = stream.readInt(); + + this.keyFrames = new long[(int) count]; + for (int i = 0; i < count; i++) { + this.keyFrames[i] = stream.readInt(); + } + } + + @Override + public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException { + super.writeMdx(stream); + ParseUtils.writeWar3ID(stream, KEVT); + ParseUtils.writeUInt32(stream, this.keyFrames.length); + stream.writeInt(this.globalSequenceId); + for (int i = 0; i < this.keyFrames.length; i++) { + ParseUtils.writeUInt32(stream, this.keyFrames[i]); + } + } + + @Override + public void readMdl(final MdlTokenInputStream stream) { + for (final String token : super.readMdlGeneric(stream)) { + if (MdlUtils.TOKEN_EVENT_TRACK.equals(token)) { + this.keyFrames = new long[stream.readInt()]; + stream.readIntArray(this.keyFrames); + } + else { + throw new RuntimeException("Unknown token in EventObject " + this.name + ": " + token); + } + } + } + + @Override + public void writeMdl(final MdlTokenOutputStream stream) throws IOException { + stream.startObjectBlock(MdlUtils.TOKEN_EVENT_OBJECT, this.name); + writeGenericHeader(stream); + stream.startBlock(MdlUtils.TOKEN_EVENT_TRACK, this.keyFrames.length); + + for (final long keyFrame : this.keyFrames) { + stream.writeFlagUInt32(keyFrame); + } + + stream.endBlock(); + + writeGenericTimelines(stream); + stream.endBlock(); + } + + @Override + public long getByteLength() { + return 12 + (this.keyFrames.length * 4) + super.getByteLength(); + } +} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/Extent.java b/core/src/com/etheller/warsmash/parsers/mdlx/Extent.java new file mode 100644 index 0000000..3be2e48 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/mdlx/Extent.java @@ -0,0 +1,47 @@ +package com.etheller.warsmash.parsers.mdlx; + +import java.io.IOException; + +import com.etheller.warsmash.util.MdlUtils; +import com.etheller.warsmash.util.ParseUtils; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +public class Extent { + protected float boundsRadius = 0; + protected final float[] min = new float[3]; + protected final float[] max = new float[3]; + + public void readMdx(final LittleEndianDataInputStream stream) throws IOException { + this.boundsRadius = stream.readFloat(); + ParseUtils.readFloatArray(stream, this.min); + ParseUtils.readFloatArray(stream, this.max); + } + + public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException { + stream.writeFloat(this.boundsRadius); + ParseUtils.writeFloatArray(stream, this.min); + ParseUtils.writeFloatArray(stream, this.max); + } + + public void writeMdl(final MdlTokenOutputStream stream) { + if ((this.min[0] != 0) || (this.min[1] != 0) || (this.min[2] != 0)) { + stream.writeFloatArrayAttrib(MdlUtils.TOKEN_MINIMUM_EXTENT, this.min); + } + if ((this.max[0] != 0) || (this.max[1] != 0) || (this.max[2] != 0)) { + stream.writeFloatArrayAttrib(MdlUtils.TOKEN_MAXIMUM_EXTENT, this.max); + } + + if (this.boundsRadius != 0) { + stream.writeFloatAttrib(MdlUtils.TOKEN_BOUNDSRADIUS, this.boundsRadius); + } + } + + public void setBoundsRadius(final float boundsRadius) { + this.boundsRadius = boundsRadius; + } + + public float getBoundsRadius() { + return this.boundsRadius; + } +} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/GenericObject.java b/core/src/com/etheller/warsmash/parsers/mdlx/GenericObject.java index 45353a2..b5018b0 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/GenericObject.java +++ b/core/src/com/etheller/warsmash/parsers/mdlx/GenericObject.java @@ -1,11 +1,13 @@ package com.etheller.warsmash.parsers.mdlx; import java.io.IOException; -import java.nio.charset.Charset; import java.util.Iterator; +import java.util.List; import com.etheller.warsmash.parsers.mdlx.timeline.Timeline; +import com.etheller.warsmash.util.MdlUtils; import com.etheller.warsmash.util.ParseUtils; +import com.etheller.warsmash.util.War3ID; import com.google.common.io.LittleEndianDataInputStream; import com.google.common.io.LittleEndianDataOutputStream; @@ -19,11 +21,10 @@ import com.google.common.io.LittleEndianDataOutputStream; * Based on the works of Chananya Freiman. */ public abstract class GenericObject extends AnimatedObject implements Chunk { - private static final Charset UTF8 = Charset.forName("utf-8"); - private String name; + protected String name; private int objectId; private int parentId; - private int flags; + protected int flags; /** * Restricts us to only be able to parse models on one thread at a time, in @@ -38,19 +39,21 @@ public abstract class GenericObject extends AnimatedObject implements Chunk { this.flags = flags; } + @Override public void readMdx(final LittleEndianDataInputStream stream) throws IOException { - final long size = ParseUtils.parseUInt32(stream); - stream.read(NAME_BYTES_HEAP); - this.name = new String(NAME_BYTES_HEAP, UTF8); + final long size = ParseUtils.readUInt32(stream); + this.name = ParseUtils.readString(stream, NAME_BYTES_HEAP); this.objectId = stream.readInt(); this.parentId = stream.readInt(); - this.flags = stream.readInt(); + this.flags = stream.readInt(); // Used to be Int32 in JS readTimelines(stream, size - 96); } + @Override public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException { - stream.writeInt((int) getGenericByteLength()); - final byte[] bytes = this.name.getBytes(UTF8); + ParseUtils.writeUInt32(stream, getGenericByteLength()); + final byte[] bytes = this.name.getBytes(ParseUtils.UTF8); + stream.write(bytes); for (int i = 0; i < (80 - bytes.length); i++) { stream.write((byte) 0); } @@ -69,7 +72,66 @@ public abstract class GenericObject extends AnimatedObject implements Chunk { } } - protected abstract Iterable eachTimeline(boolean generic); + protected final Iterable readMdlGeneric(final MdlTokenInputStream stream) { + this.name = stream.read(); + return new Iterable() { + @Override + public Iterator iterator() { + return new WrappedMdlTokenIterator(GenericObject.this.readAnimatedBlock(stream), GenericObject.this, + stream); + } + }; + } + + public void writeGenericHeader(final MdlTokenOutputStream stream) { + stream.writeAttrib(MdlUtils.TOKEN_OBJECTID, this.objectId); + + if (this.parentId != -1) { + stream.writeAttrib("Parent", this.parentId); + } + + if ((this.flags & 0x40) != 0) { + stream.writeFlag(MdlUtils.TOKEN_BILLBOARDED_LOCK_Z); + } + + if ((this.flags & 0x20) != 0) { + stream.writeFlag(MdlUtils.TOKEN_BILLBOARDED_LOCK_Y); + } + + if ((this.flags & 0x10) != 0) { + stream.writeFlag(MdlUtils.TOKEN_BILLBOARDED_LOCK_X); + } + + if ((this.flags & 0x8) != 0) { + stream.writeFlag(MdlUtils.TOKEN_BILLBOARDED); + } + + if ((this.flags & 0x80) != 0) { + stream.writeFlag(MdlUtils.TOKEN_CAMERA_ANCHORED); + } + + if ((this.flags & 0x2) != 0) { + stream.writeFlag(MdlUtils.TOKEN_DONT_INHERIT + " { " + MdlUtils.TOKEN_ROTATION + " }"); + } + + if ((this.flags & 0x1) != 0) { + stream.writeFlag(MdlUtils.TOKEN_DONT_INHERIT + " { " + MdlUtils.TOKEN_TRANSLATION + " }"); + } + + if ((this.flags & 0x4) != 0) { + stream.writeFlag(MdlUtils.TOKEN_DONT_INHERIT + " { " + MdlUtils.TOKEN_SCALING + " }"); + } + } + + public void writeGenericTimelines(final MdlTokenOutputStream stream) throws IOException { + this.writeTimeline(stream, AnimationMap.KGTR); + this.writeTimeline(stream, AnimationMap.KGRT); + this.writeTimeline(stream, AnimationMap.KGSC); + } + + public Iterable eachTimeline(final boolean generic) { + return new TimelineMaskingIterable(generic); + } public long getGenericByteLength() { long size = 96; @@ -84,23 +146,188 @@ public abstract class GenericObject extends AnimatedObject implements Chunk { return 96 + super.getByteLength(); } - private static final class WrappedMdlTokenIterator implements Iterator { - private final Iterator delegate; + private final class TimelineMaskingIterable implements Iterable { + private final boolean generic; - public WrappedMdlTokenIterator(final Iterator delegate) { - this.delegate = delegate; + private TimelineMaskingIterable(final boolean generic) { + this.generic = generic; + } + + @Override + public Iterator iterator() { + return new TimelineMaskingIterator(this.generic, GenericObject.this.timelines); + } + } + + private static final class TimelineMaskingIterator implements Iterator { + private final boolean wantGeneric; + private final Iterator delegate; + private boolean hasNext; + private Timeline next; + + public TimelineMaskingIterator(final boolean wantGeneric, final List timelines) { + this.wantGeneric = wantGeneric; + this.delegate = timelines.iterator(); + scanUntilNext(); + } + + private boolean isGeneric(final Timeline timeline) { + final War3ID name = timeline.getName(); + final boolean generic = AnimationMap.KGTR.getWar3id().equals(name) + || AnimationMap.KGRT.getWar3id().equals(name) || AnimationMap.KGSC.getWar3id().equals(name); + return generic; + } + + private void scanUntilNext() { + boolean hasNext = false; + if (hasNext = this.delegate.hasNext()) { + do { + this.next = this.delegate.next(); + } + while ((isGeneric(this.next) != this.wantGeneric) && (hasNext = this.delegate.hasNext())); + } + if (!hasNext) { + this.next = null; + } } @Override public boolean hasNext() { - return this.delegate.hasNext(); + return this.next != null; + } + + @Override + public Timeline next() { + final Timeline last = this.next; + scanUntilNext(); + return last; + } + + @Override + public void remove() { + throw new UnsupportedOperationException("Remove is not supported"); + } + + } + + private static final class WrappedMdlTokenIterator implements Iterator { + private final Iterator delegate; + private final GenericObject updatingObject; + private final MdlTokenInputStream stream; + private String next; + private boolean hasLoaded = false; + + public WrappedMdlTokenIterator(final Iterator delegate, final GenericObject updatingObject, + final MdlTokenInputStream stream) { + this.delegate = delegate; + this.updatingObject = updatingObject; + this.stream = stream; + } + + @Override + public boolean hasNext() { + if (this.delegate.hasNext()) { + this.next = read(); + this.hasLoaded = true; + return this.next != null; + } + return false; } @Override public String next() { - final String token = this.delegate.next(); + if (!this.hasLoaded) { + this.next = read(); + } + this.hasLoaded = false; + return this.next; + } - return null; + private String read() { + String token; + InteriorParsing: do { + token = this.delegate.next(); + if (token == null) { + break; + } + switch (token) { + case MdlUtils.TOKEN_OBJECTID: + this.updatingObject.objectId = Integer.parseInt(this.delegate.next()); + token = null; + break; + case MdlUtils.TOKEN_PARENT: + this.updatingObject.parentId = Integer.parseInt(this.delegate.next()); + token = null; + break; + case MdlUtils.TOKEN_BILLBOARDED_LOCK_Z: + this.updatingObject.flags |= 0x40; + token = null; + break; + case MdlUtils.TOKEN_BILLBOARDED_LOCK_Y: + this.updatingObject.flags |= 0x20; + token = null; + break; + case MdlUtils.TOKEN_BILLBOARDED_LOCK_X: + this.updatingObject.flags |= 0x10; + token = null; + break; + case MdlUtils.TOKEN_BILLBOARDED: + this.updatingObject.flags |= 0x8; + token = null; + break; + case MdlUtils.TOKEN_CAMERA_ANCHORED: + this.updatingObject.flags |= 0x80; + token = null; + break; + case MdlUtils.TOKEN_DONT_INHERIT: + for (final String subToken : this.stream.readBlock()) { + switch (subToken) { + case MdlUtils.TOKEN_ROTATION: + this.updatingObject.flags |= 0x2; + break; + case MdlUtils.TOKEN_TRANSLATION: + this.updatingObject.flags |= 0x1; + break; + case MdlUtils.TOKEN_SCALING: + this.updatingObject.flags |= 0x0; + break; + } + } + token = null; + break; + case MdlUtils.TOKEN_TRANSLATION: + try { + this.updatingObject.readTimeline(this.stream, AnimationMap.KGTR); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + token = null; + break; + case MdlUtils.TOKEN_ROTATION: + try { + this.updatingObject.readTimeline(this.stream, AnimationMap.KGRT); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + token = null; + break; + case MdlUtils.TOKEN_SCALING: + try { + this.updatingObject.readTimeline(this.stream, AnimationMap.KGSC); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + token = null; + break; + default: + break InteriorParsing; + } + } + while (this.delegate.hasNext()); + return token; } } diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/Geoset.java b/core/src/com/etheller/warsmash/parsers/mdlx/Geoset.java new file mode 100644 index 0000000..eb8b55d --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/mdlx/Geoset.java @@ -0,0 +1,319 @@ +package com.etheller.warsmash.parsers.mdlx; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import com.etheller.warsmash.util.MdlUtils; +import com.etheller.warsmash.util.ParseUtils; +import com.etheller.warsmash.util.War3ID; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +public class Geoset implements MdlxBlock, Chunk { + private static final War3ID VRTX = War3ID.fromString("VRTX"); + private static final War3ID NRMS = War3ID.fromString("NRMS"); + private static final War3ID PTYP = War3ID.fromString("PTYP"); + private static final War3ID PCNT = War3ID.fromString("PCNT"); + private static final War3ID PVTX = War3ID.fromString("PVTX"); + private static final War3ID GNDX = War3ID.fromString("GNDX"); + private static final War3ID MTGC = War3ID.fromString("MTGC"); + private static final War3ID MATS = War3ID.fromString("MATS"); + private static final War3ID UVAS = War3ID.fromString("UVAS"); + private static final War3ID UVBS = War3ID.fromString("UVBS"); + private float[] vertices; + private float[] normals; + private long[] faceTypeGroups; // unsigned int[] + private long[] faceGroups; // unsigned int[] + private int[] faces; // unsigned short[] + private short[] vertexGroups; // unsigned byte[] + private long[] matrixGroups; // unsigned int[] + private long[] matrixIndices; // unsigned int[] + private long materialId = 0; + private long selectionGroup = 0; + private long selectionFlags = 0; + private final Extent extent = new Extent(); + private Extent[] sequenceExtents; + private float[][] uvSets; + + @Override + public void readMdx(final LittleEndianDataInputStream stream) throws IOException { + final long mySize = ParseUtils.readUInt32(stream); + final int vrtx = stream.readInt(); // skip VRTX + this.vertices = ParseUtils.readFloatArray(stream, (int) (ParseUtils.readUInt32(stream) * 3)); + final int nrms = stream.readInt(); // skip NRMS + this.normals = ParseUtils.readFloatArray(stream, (int) (ParseUtils.readUInt32(stream) * 3)); + final int ptyp = stream.readInt(); // skip PTYP + this.faceTypeGroups = ParseUtils.readUInt32Array(stream, (int) ParseUtils.readUInt32(stream)); + stream.readInt(); // skip PCNT + this.faceGroups = ParseUtils.readUInt32Array(stream, (int) ParseUtils.readUInt32(stream)); + stream.readInt(); // skip PVTX + this.faces = ParseUtils.readUInt16Array(stream, (int) ParseUtils.readUInt32(stream)); + stream.readInt(); // skip GNDX + this.vertexGroups = ParseUtils.readUInt8Array(stream, (int) ParseUtils.readUInt32(stream)); + stream.readInt(); // skip MTGC + this.matrixGroups = ParseUtils.readUInt32Array(stream, (int) ParseUtils.readUInt32(stream)); + stream.readInt(); // skip MATS + this.matrixIndices = ParseUtils.readUInt32Array(stream, (int) ParseUtils.readUInt32(stream)); + this.materialId = ParseUtils.readUInt32(stream); + this.selectionGroup = ParseUtils.readUInt32(stream); + this.selectionFlags = ParseUtils.readUInt32(stream); + this.extent.readMdx(stream); + + final long numExtents = ParseUtils.readUInt32(stream); + this.sequenceExtents = new Extent[(int) numExtents]; + for (int i = 0; i < numExtents; i++) { + final Extent extent = new Extent(); + extent.readMdx(stream); + this.sequenceExtents[i] = extent; + } + + stream.readInt(); // skip UVAS + + final long numUVLayers = ParseUtils.readUInt32(stream); + this.uvSets = new float[(int) numUVLayers][]; + for (int i = 0; i < numUVLayers; i++) { + stream.readInt(); // skip UVBS + this.uvSets[i] = ParseUtils.readFloatArray(stream, (int) (ParseUtils.readUInt32(stream) * 2)); + } + } + + @Override + public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException { + ParseUtils.writeUInt32(stream, this.getByteLength()); + ParseUtils.writeWar3ID(stream, VRTX); + ParseUtils.writeUInt32(stream, this.vertices.length / 3); + ParseUtils.writeFloatArray(stream, this.vertices); + ParseUtils.writeWar3ID(stream, NRMS); + ParseUtils.writeUInt32(stream, this.normals.length / 3); + ParseUtils.writeFloatArray(stream, this.normals); + ParseUtils.writeWar3ID(stream, PTYP); + ParseUtils.writeUInt32(stream, this.faceTypeGroups.length); + ParseUtils.writeUInt32Array(stream, this.faceTypeGroups); + ParseUtils.writeWar3ID(stream, PCNT); + ParseUtils.writeUInt32(stream, this.faceGroups.length); + ParseUtils.writeUInt32Array(stream, this.faceGroups); + ParseUtils.writeWar3ID(stream, PVTX); + ParseUtils.writeUInt32(stream, this.faces.length); + ParseUtils.writeUInt16Array(stream, this.faces); + ParseUtils.writeWar3ID(stream, GNDX); + ParseUtils.writeUInt32(stream, this.vertexGroups.length); + ParseUtils.writeUInt8Array(stream, this.vertexGroups); + ParseUtils.writeWar3ID(stream, MTGC); + ParseUtils.writeUInt32(stream, this.matrixGroups.length); + ParseUtils.writeUInt32Array(stream, this.matrixGroups); + ParseUtils.writeWar3ID(stream, MATS); + ParseUtils.writeUInt32(stream, this.matrixIndices.length); + ParseUtils.writeUInt32Array(stream, this.matrixIndices); + ParseUtils.writeUInt32(stream, this.materialId); + ParseUtils.writeUInt32(stream, this.selectionGroup); + ParseUtils.writeUInt32(stream, this.selectionFlags); + this.extent.writeMdx(stream); + ParseUtils.writeUInt32(stream, this.sequenceExtents.length); + + for (final Extent sequenceExtent : this.sequenceExtents) { + sequenceExtent.writeMdx(stream); + } + + ParseUtils.writeWar3ID(stream, UVAS); + ParseUtils.writeUInt32(stream, this.uvSets.length); + for (final float[] uvSet : this.uvSets) { + ParseUtils.writeWar3ID(stream, UVBS); + ParseUtils.writeUInt32(stream, uvSet.length / 2); + ParseUtils.writeFloatArray(stream, uvSet); + } + } + + @Override + public void readMdl(final MdlTokenInputStream stream) { + this.uvSets = new float[0][]; + final List sequenceExtents = new ArrayList<>(); + for (final String token : stream.readBlock()) { + switch (token) { + case MdlUtils.TOKEN_VERTICES: + this.vertices = stream.readVectorArray(new float[stream.readInt() * 3], 3); + break; + case MdlUtils.TOKEN_NORMALS: + this.normals = stream.readVectorArray(new float[stream.readInt() * 3], 3); + break; + case MdlUtils.TOKEN_TVERTICES: + this.uvSets = Arrays.copyOf(this.uvSets, this.uvSets.length + 1); + this.uvSets[this.uvSets.length - 1] = stream.readVectorArray(new float[stream.readInt() * 2], 2); + break; + case MdlUtils.TOKEN_VERTEX_GROUP: { + // Vertex groups are stored in a block with no count, can't allocate the buffer + // yet. + final List vertexGroups = new ArrayList<>(); + for (final String vertexGroup : stream.readBlock()) { + vertexGroups.add(Short.valueOf(vertexGroup)); + } + + this.vertexGroups = new short[vertexGroups.size()]; + int i = 0; + for (final Short vertexGroup : vertexGroups) { + this.vertexGroups[i++] = vertexGroup.shortValue(); + } + } + break; + case MdlUtils.TOKEN_FACES: + // For now hardcoded for triangles, until I see a model with something + // different. + this.faceTypeGroups = new long[] { 4L }; + + stream.readInt(); // number of groups + + final int count = stream.readInt(); + + stream.read(); // { + stream.read(); // Triangles + stream.read(); // { + + this.faces = stream.readUInt16Array(new int[count]); + this.faceGroups = new long[] { count }; + + stream.read(); // } + stream.read(); // } + break; + case MdlUtils.TOKEN_GROUPS: { + final List indices = new ArrayList<>(); + final List groups = new ArrayList<>(); + + stream.readInt(); // matrices count + stream.readInt(); // total indices + + // eslint-disable-next-line no-unused-vars + for (final String matrix : stream.readBlock()) { + int size = 0; + + for (final String index : stream.readBlock()) { + indices.add(Integer.valueOf(index)); + size += 1; + } + groups.add(size); + } + + this.matrixIndices = new long[indices.size()]; + int i = 0; + for (final Integer index : indices) { + this.matrixIndices[i++] = index.intValue(); + } + this.matrixGroups = new long[groups.size()]; + i = 0; + for (final Integer group : groups) { + this.matrixGroups[i++] = group.intValue(); + } + } + break; + case MdlUtils.TOKEN_MINIMUM_EXTENT: + stream.readFloatArray(this.extent.min); + break; + case MdlUtils.TOKEN_MAXIMUM_EXTENT: + stream.readFloatArray(this.extent.max); + break; + case MdlUtils.TOKEN_BOUNDSRADIUS: + this.extent.boundsRadius = stream.readFloat(); + break; + case MdlUtils.TOKEN_ANIM: + final Extent extent = new Extent(); + + for (final String subToken : stream.readBlock()) { + switch (subToken) { + case MdlUtils.TOKEN_MINIMUM_EXTENT: + stream.readFloatArray(extent.min); + break; + case MdlUtils.TOKEN_MAXIMUM_EXTENT: + stream.readFloatArray(extent.max); + break; + case MdlUtils.TOKEN_BOUNDSRADIUS: + extent.boundsRadius = stream.readFloat(); + break; + } + } + + sequenceExtents.add(extent); + break; + case MdlUtils.TOKEN_MATERIAL_ID: + this.materialId = stream.readInt(); + break; + case MdlUtils.TOKEN_SELECTION_GROUP: + this.selectionGroup = stream.readInt(); + break; + case MdlUtils.TOKEN_UNSELECTABLE: + this.selectionFlags = 4; + break; + default: + throw new RuntimeException("Unknown token in Geoset: " + token); + } + } + this.sequenceExtents = sequenceExtents.toArray(new Extent[sequenceExtents.size()]); + } + + @Override + public void writeMdl(final MdlTokenOutputStream stream) { + stream.startBlock(MdlUtils.TOKEN_GEOSET); + + stream.writeVectorArray(MdlUtils.TOKEN_VERTICES, this.vertices, 3); + stream.writeVectorArray(MdlUtils.TOKEN_NORMALS, this.normals, 3); + + for (final float[] uvSet : this.uvSets) { + stream.writeVectorArray(MdlUtils.TOKEN_TVERTICES, uvSet, 2); + } + + stream.startBlock(MdlUtils.TOKEN_VERTEX_GROUP); + for (int i = 0; i < this.vertexGroups.length; i++) { + stream.writeLine(this.vertexGroups[i] + ","); + } + stream.endBlock(); + + // For now hardcoded for triangles, until I see a model with something + // different. + stream.startBlock(MdlUtils.TOKEN_FACES, 1, this.faces.length); + stream.startBlock(MdlUtils.TOKEN_TRIANGLES); + final StringBuffer facesBuffer = new StringBuffer(); + for (final int faceValue : this.faces) { + if (facesBuffer.length() > 0) { + facesBuffer.append(", "); + } + facesBuffer.append(faceValue); + } + stream.writeLine("{ " + facesBuffer.toString() + " },"); + stream.endBlock(); + stream.endBlock(); + + stream.startBlock(MdlUtils.TOKEN_GROUPS, this.matrixGroups.length, this.matrixIndices.length); + int index = 0; + for (final long groupSize : this.matrixGroups) { + stream.writeLongSubArrayAttrib(MdlUtils.TOKEN_MATRICES, this.matrixIndices, index, + (int) (index + groupSize)); + index += groupSize; + } + stream.endBlock(); + + this.extent.writeMdl(stream); + + for (final Extent sequenceExtent : this.sequenceExtents) { + stream.startBlock(MdlUtils.TOKEN_ANIM); + sequenceExtent.writeMdl(stream); + stream.endBlock(); + } + + stream.writeAttribUInt32("MaterialID", this.materialId); + stream.writeAttribUInt32("SelectionGroup", this.selectionGroup); + if (this.selectionFlags == 4) { + stream.writeFlag("Unselectable"); + } + stream.endBlock(); + } + + @Override + public long getByteLength() { + long size = 120 + (this.vertices.length * 4) + (this.normals.length * 4) + (this.faceTypeGroups.length * 4) + + (this.faceGroups.length * 4) + (this.faces.length * 2) + this.vertexGroups.length + + (this.matrixGroups.length * 4) + (this.matrixIndices.length * 4) + (this.sequenceExtents.length * 28); + for (final float[] uvSet : this.uvSets) { + size += 8 + (uvSet.length * 4); + } + return size; + } +} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/GeosetAnimation.java b/core/src/com/etheller/warsmash/parsers/mdlx/GeosetAnimation.java new file mode 100644 index 0000000..9375a6e --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/mdlx/GeosetAnimation.java @@ -0,0 +1,102 @@ +package com.etheller.warsmash.parsers.mdlx; + +import java.io.IOException; +import java.util.Iterator; + +import com.etheller.warsmash.util.MdlUtils; +import com.etheller.warsmash.util.ParseUtils; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +public class GeosetAnimation extends AnimatedObject { + private float alpha = 1; + private int flags = 0; + private final float[] color = { 1, 1, 1 }; + private int geosetId = -1; + + @Override + public void readMdx(final LittleEndianDataInputStream stream) throws IOException { + final long size = ParseUtils.readUInt32(stream); + + this.alpha = stream.readFloat(); + this.flags = stream.readInt();// ParseUtils.readUInt32(stream); + ParseUtils.readFloatArray(stream, this.color); + this.geosetId = stream.readInt(); + + readTimelines(stream, size - 28); + } + + @Override + public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException { + ParseUtils.writeUInt32(stream, getByteLength()); + stream.writeFloat(this.alpha); + stream.writeInt(this.flags);// ParseUtils.writeUInt32(stream, this.flags); + ParseUtils.writeFloatArray(stream, this.color); + stream.writeInt(this.geosetId); + + writeTimelines(stream); + } + + @Override + public void readMdl(final MdlTokenInputStream stream) throws IOException { + final Iterator blockIterator = readAnimatedBlock(stream); + while (blockIterator.hasNext()) { + final String token = blockIterator.next(); + switch (token) { + case MdlUtils.TOKEN_DROP_SHADOW: + this.flags |= 0x1; + break; + case MdlUtils.TOKEN_STATIC_ALPHA: + this.alpha = stream.readFloat(); + break; + case MdlUtils.TOKEN_ALPHA: + this.readTimeline(stream, AnimationMap.KGAO); + break; + case MdlUtils.TOKEN_STATIC_COLOR: + this.flags |= 0x2; + stream.readColor(this.color); + break; + case MdlUtils.TOKEN_COLOR: + this.flags |= 0x2; + readTimeline(stream, AnimationMap.KGAC); + break; + case MdlUtils.TOKEN_GEOSETID: + this.geosetId = stream.readInt(); + break; + default: + throw new IllegalStateException("Unknown token in GeosetAnimation: " + token); + } + } + } + + @Override + public void writeMdl(final MdlTokenOutputStream stream) throws IOException { + stream.startBlock(MdlUtils.TOKEN_GEOSETANIM); + + if ((this.flags & 0x1) != 0) { + stream.writeFlag(MdlUtils.TOKEN_DROP_SHADOW); + } + + if (!this.writeTimeline(stream, AnimationMap.KGAO)) { + stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_ALPHA, this.alpha); + } + + if ((this.flags & 0x2) != 0) { + if (!this.writeTimeline(stream, AnimationMap.KGAC) + && ((this.color[0] != 0) || (this.color[1] != 0) || (this.color[2] != 0))) { + stream.writeColor(MdlUtils.TOKEN_STATIC_COLOR + " ", this.color); // TODO why the space? + } + } + + if (this.geosetId != -1) { // TODO Retera added -1 check here, why wasn't it there before in JS??? + stream.writeAttrib(MdlUtils.TOKEN_GEOSETID, this.geosetId); + } + + stream.endBlock(); + } + + @Override + public long getByteLength() { + return 28 + super.getByteLength(); + } +} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/Helper.java b/core/src/com/etheller/warsmash/parsers/mdlx/Helper.java new file mode 100644 index 0000000..6426f28 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/mdlx/Helper.java @@ -0,0 +1,29 @@ +package com.etheller.warsmash.parsers.mdlx; + +import java.io.IOException; + +import com.etheller.warsmash.util.MdlUtils; + +public class Helper extends GenericObject { + + public Helper() { + super(0x0); // NOTE: ghostwolf JS didn't pass the 0x1 flag???? + // ANOTHER NOTE: setting the 0x1 flag causes other fan programs to spam error + // messages + } + + @Override + public void readMdl(final MdlTokenInputStream stream) { + for (final String token : readMdlGeneric(stream)) { + throw new IllegalStateException("Unknown token in Helper: " + token); + } + } + + @Override + public void writeMdl(final MdlTokenOutputStream stream) throws IOException { + stream.startObjectBlock(MdlUtils.TOKEN_HELPER, this.name); + writeGenericHeader(stream); + writeGenericTimelines(stream); + stream.endBlock(); + } +} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/Layer.java b/core/src/com/etheller/warsmash/parsers/mdlx/Layer.java new file mode 100644 index 0000000..3ba2673 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/mdlx/Layer.java @@ -0,0 +1,195 @@ +package com.etheller.warsmash.parsers.mdlx; + +import java.io.IOException; +import java.util.Iterator; + +import com.etheller.warsmash.util.MdlUtils; +import com.etheller.warsmash.util.ParseUtils; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +public class Layer extends AnimatedObject { + // 0: none + // 1: transparent + // 2: blend + // 3: additive + // 4: add alpha + // 5: modulate + // 6: modulate 2x + public static enum FilterMode { + NONE("None"), + TRANSPARENT("Transparent"), + BLEND("Blend"), + ADDITIVE("Additive"), + ADDALPHA("AddAlpha"), + MODULATE("Modulate"), + MODULATE2X("Modulate2x"); + + String mdlText; + + FilterMode(final String str) { + this.mdlText = str; + } + + public String getMdlText() { + return this.mdlText; + } + + public static FilterMode fromId(final int id) { + return values()[id]; + } + + public static int nameToId(final String name) { + for (final FilterMode mode : values()) { + if (mode.getMdlText().equals(name)) { + return mode.ordinal(); + } + } + return -1; + } + + @Override + public String toString() { + return getMdlText(); + } + } + + private FilterMode filterMode; + private int flags = 0; + private int textureId = -1; + private int textureAnimationId = -1; + private long coordId = 0; + private float alpha = 1; + + @Override + public void readMdx(final LittleEndianDataInputStream stream) throws IOException { + final long size = ParseUtils.readUInt32(stream); + + this.filterMode = FilterMode.fromId((int) ParseUtils.readUInt32(stream)); + this.flags = stream.readInt(); // UInt32 in JS + this.textureId = stream.readInt(); + this.textureAnimationId = stream.readInt(); + this.coordId = ParseUtils.readUInt32(stream); + this.alpha = stream.readFloat(); + + readTimelines(stream, size - 28); + } + + @Override + public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException { + ParseUtils.writeUInt32(stream, getByteLength()); + ParseUtils.writeUInt32(stream, this.filterMode.ordinal()); + ParseUtils.writeUInt32(stream, this.flags); + stream.writeInt(this.textureId); + stream.writeInt(this.textureAnimationId); + ParseUtils.writeUInt32(stream, this.coordId); + stream.writeFloat(this.alpha); + + writeTimelines(stream); + } + + @Override + public void readMdl(final MdlTokenInputStream stream) throws IOException { + final Iterator iterator = readAnimatedBlock(stream); + while (iterator.hasNext()) { + final String token = iterator.next(); + switch (token) { + case MdlUtils.TOKEN_FILTER_MODE: + this.filterMode = FilterMode.fromId(FilterMode.nameToId(stream.read())); + break; + case MdlUtils.TOKEN_UNSHADED: + this.flags |= 0x1; + break; + case MdlUtils.TOKEN_SPHERE_ENV_MAP: + this.flags |= 0x2; + break; + case MdlUtils.TOKEN_TWO_SIDED: + this.flags |= 0x10; + break; + case MdlUtils.TOKEN_UNFOGGED: + this.flags |= 0x20; + break; + case MdlUtils.TOKEN_NO_DEPTH_TEST: + this.flags |= 0x40; + break; + case MdlUtils.TOKEN_NO_DEPTH_SET: + this.flags |= 0x100; + break; + case MdlUtils.TOKEN_STATIC_TEXTURE_ID: + this.textureId = stream.readInt(); + break; + case MdlUtils.TOKEN_TEXTURE_ID: + readTimeline(stream, AnimationMap.KMTF); + break; + case MdlUtils.TOKEN_TVERTEX_ANIM_ID: + this.textureAnimationId = stream.readInt(); + break; + case MdlUtils.TOKEN_COORD_ID: + this.coordId = stream.readInt(); + break; + case MdlUtils.TOKEN_STATIC_ALPHA: + this.alpha = stream.readFloat(); + break; + case MdlUtils.TOKEN_ALPHA: + readTimeline(stream, AnimationMap.KMTA); + break; + default: + throw new IllegalStateException("Unknown token in Layer: " + token); + } + } + } + + @Override + public void writeMdl(final MdlTokenOutputStream stream) throws IOException { + stream.startBlock(MdlUtils.TOKEN_LAYER); + + stream.writeAttrib(MdlUtils.TOKEN_FILTER_MODE, this.filterMode.getMdlText()); + + if ((this.flags & 0x1) != 0) { + stream.writeFlag(MdlUtils.TOKEN_UNSHADED); + } + + if ((this.flags & 0x2) != 0) { + stream.writeFlag(MdlUtils.TOKEN_SPHERE_ENV_MAP); + } + + if ((this.flags & 0x10) != 0) { + stream.writeFlag(MdlUtils.TOKEN_TWO_SIDED); + } + + if ((this.flags & 0x20) != 0) { + stream.writeFlag(MdlUtils.TOKEN_UNFOGGED); + } + + if ((this.flags & 0x40) != 0) { + stream.writeFlag(MdlUtils.TOKEN_NO_DEPTH_TEST); + } + + if ((this.flags & 0x100) != 0) { + stream.writeFlag(MdlUtils.TOKEN_NO_DEPTH_SET); + } + + if (!writeTimeline(stream, AnimationMap.KMTF)) { + stream.writeAttrib(MdlUtils.TOKEN_STATIC_TEXTURE_ID, this.textureId); + } + + if (this.textureAnimationId != -1) { + stream.writeAttrib(MdlUtils.TOKEN_TVERTEX_ANIM_ID, this.textureAnimationId); + } + + if (this.coordId != 0) { + stream.writeAttribUInt32(MdlUtils.TOKEN_COORD_ID, this.coordId); + } + + if (!writeTimeline(stream, AnimationMap.KMTA) && (this.alpha != 1)) { + stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_ALPHA, this.alpha); + } + + stream.endBlock(); + } + + @Override + public long getByteLength() { + return 28 + super.getByteLength(); + } +} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/Light.java b/core/src/com/etheller/warsmash/parsers/mdlx/Light.java new file mode 100644 index 0000000..6b802e0 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/mdlx/Light.java @@ -0,0 +1,166 @@ +package com.etheller.warsmash.parsers.mdlx; + +import java.io.IOException; + +import com.etheller.warsmash.util.MdlUtils; +import com.etheller.warsmash.util.ParseUtils; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +public class Light extends GenericObject { + + private int type = -1; + private final float[] attenuation = new float[2]; + private final float[] color = new float[3]; + private float intensity = 0; + private final float[] ambientColor = new float[3]; + private float ambientIntensity = 0; + + public Light() { + super(0x200); + } + + @Override + public void readMdx(final LittleEndianDataInputStream stream) throws IOException { + final long size = ParseUtils.readUInt32(stream); + + super.readMdx(stream); + + this.type = stream.readInt(); // UInt32 in JS + ParseUtils.readFloatArray(stream, this.attenuation); + ParseUtils.readFloatArray(stream, this.color); + this.intensity = stream.readFloat(); + ParseUtils.readFloatArray(stream, this.ambientColor); + this.ambientIntensity = stream.readFloat(); + + readTimelines(stream, size - this.getByteLength()); + } + + @Override + public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException { + ParseUtils.writeUInt32(stream, getByteLength()); + + super.writeMdx(stream); + + ParseUtils.writeUInt32(stream, this.type); + ParseUtils.writeFloatArray(stream, this.attenuation); + ParseUtils.writeFloatArray(stream, this.color); + stream.writeFloat(this.intensity); + ParseUtils.writeFloatArray(stream, this.ambientColor); + stream.writeFloat(this.ambientIntensity); + + writeNonGenericAnimationChunks(stream); + } + + @Override + public void readMdl(final MdlTokenInputStream stream) throws IOException { + for (final String token : super.readMdlGeneric(stream)) { + switch (token) { + case MdlUtils.TOKEN_OMNIDIRECTIONAL: + this.type = 0; + break; + case MdlUtils.TOKEN_DIRECTIONAL: + this.type = 1; + break; + case MdlUtils.TOKEN_AMBIENT: + this.type = 2; + break; + case MdlUtils.TOKEN_STATIC_ATTENUATION_START: + this.attenuation[0] = stream.readFloat(); + break; + case MdlUtils.TOKEN_ATTENUATION_START: + readTimeline(stream, AnimationMap.KLAS); + break; + case MdlUtils.TOKEN_STATIC_ATTENUATION_END: + this.attenuation[1] = stream.readFloat(); + break; + case MdlUtils.TOKEN_ATTENUATION_END: + readTimeline(stream, AnimationMap.KLAE); + break; + case MdlUtils.TOKEN_STATIC_INTENSITY: + this.intensity = stream.readFloat(); + break; + case MdlUtils.TOKEN_INTENSITY: + readTimeline(stream, AnimationMap.KLAI); + break; + case MdlUtils.TOKEN_STATIC_COLOR: + stream.readColor(this.color); + break; + case MdlUtils.TOKEN_COLOR: + readTimeline(stream, AnimationMap.KLAC); + break; + case MdlUtils.TOKEN_STATIC_AMB_INTENSITY: + this.ambientIntensity = stream.readFloat(); + break; + case MdlUtils.TOKEN_AMB_INTENSITY: + readTimeline(stream, AnimationMap.KLBI); + break; + case MdlUtils.TOKEN_STATIC_AMB_COLOR: + stream.readColor(this.ambientColor); + break; + case MdlUtils.TOKEN_AMB_COLOR: + readTimeline(stream, AnimationMap.KLBC); + break; + case MdlUtils.TOKEN_VISIBILITY: + readTimeline(stream, AnimationMap.KLAV); + break; + default: + throw new IllegalStateException("Unknown token in Light: " + token); + } + } + } + + @Override + public void writeMdl(final MdlTokenOutputStream stream) throws IOException { + stream.startObjectBlock(MdlUtils.TOKEN_LIGHT, this.name); + writeGenericHeader(stream); + + switch (this.type) { + case 0: + stream.writeFlag(MdlUtils.TOKEN_OMNIDIRECTIONAL); + break; + case 1: + stream.writeFlag(MdlUtils.TOKEN_DIRECTIONAL); + break; + case 2: + stream.writeFlag(MdlUtils.TOKEN_AMBIENT); + break; + default: + throw new IllegalStateException("Unable to save Light of type: " + this.type); + } + + if (!writeTimeline(stream, AnimationMap.KLAS)) { + stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_ATTENUATION_START, this.attenuation[0]); + } + + if (!writeTimeline(stream, AnimationMap.KLAE)) { + stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_ATTENUATION_END, this.attenuation[1]); + } + + if (!writeTimeline(stream, AnimationMap.KLAI)) { + stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_INTENSITY, this.intensity); + } + + if (!writeTimeline(stream, AnimationMap.KLAC)) { + stream.writeColor(MdlUtils.TOKEN_STATIC_COLOR, this.color); + } + + if (!writeTimeline(stream, AnimationMap.KLBI)) { + stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_AMB_INTENSITY, this.ambientIntensity); + } + + if (!writeTimeline(stream, AnimationMap.KLBC)) { + stream.writeColor(MdlUtils.TOKEN_STATIC_AMB_COLOR, this.ambientColor); + } + + writeTimeline(stream, AnimationMap.KLAV); + + writeGenericTimelines(stream); + stream.endBlock(); + } + + @Override + public long getByteLength() { + return 48 + super.getByteLength(); + } +} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/Material.java b/core/src/com/etheller/warsmash/parsers/mdlx/Material.java new file mode 100644 index 0000000..46cb097 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/mdlx/Material.java @@ -0,0 +1,121 @@ +package com.etheller.warsmash.parsers.mdlx; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import com.etheller.warsmash.util.MdlUtils; +import com.etheller.warsmash.util.ParseUtils; +import com.etheller.warsmash.util.War3ID; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +public class Material implements MdlxBlock, Chunk { + private static final War3ID LAYS = War3ID.fromString("LAYS"); + private int priorityPlane = 0; + private int flags; + private final List layers = new ArrayList<>(); + + @Override + public void readMdx(final LittleEndianDataInputStream stream) throws IOException { + ParseUtils.readUInt32(stream); // Don't care about the size + + this.priorityPlane = stream.readInt();// ParseUtils.readUInt32(stream); + this.flags = stream.readInt();// ParseUtils.readUInt32(stream); + + stream.readInt(); // skip LAYS + + final long layerCount = ParseUtils.readUInt32(stream); + for (int i = 0; i < layerCount; i++) { + final Layer layer = new Layer(); + layer.readMdx(stream); + this.layers.add(layer); + } + } + + @Override + public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException { + ParseUtils.writeUInt32(stream, getByteLength()); + stream.writeInt(this.priorityPlane); // was UInt32 in JS, but I *really* thought I used -1 in a past model + stream.writeInt(this.flags); // UInt32 in JS + ParseUtils.writeWar3ID(stream, LAYS); + ParseUtils.writeUInt32(stream, this.layers.size()); + + for (final Layer layer : this.layers) { + layer.writeMdx(stream); + } + } + + @Override + public void readMdl(final MdlTokenInputStream stream) throws IOException { + for (final String token : stream.readBlock()) { + switch (token) { + case MdlUtils.TOKEN_CONSTANT_COLOR: + this.flags |= 0x1; + break; + case MdlUtils.TOKEN_SORT_PRIMS_NEAR_Z: + this.flags |= 0x8; + break; + case MdlUtils.TOKEN_SORT_PRIMS_FAR_Z: + this.flags |= 0x10; + break; + case MdlUtils.TOKEN_FULL_RESOLUTION: + this.flags |= 0x20; + break; + case MdlUtils.TOKEN_PRIORITY_PLANE: + this.priorityPlane = stream.readInt(); + break; + case MdlUtils.TOKEN_LAYER: { + final Layer layer = new Layer(); + layer.readMdl(stream); + this.layers.add(layer); + break; + } + default: + throw new IllegalStateException("Unknown token in Material: " + token); + } + } + } + + @Override + public void writeMdl(final MdlTokenOutputStream stream) throws IOException { + stream.startBlock(MdlUtils.TOKEN_MATERIAL); + + if ((this.flags & 0x1) != 0) { + stream.writeFlag(MdlUtils.TOKEN_CONSTANT_COLOR); + } + + if ((this.flags & 0x8) != 0) { + stream.writeFlag(MdlUtils.TOKEN_SORT_PRIMS_NEAR_Z); + } + + if ((this.flags & 0x10) != 0) { + stream.writeFlag(MdlUtils.TOKEN_SORT_PRIMS_FAR_Z); + } + + if ((this.flags & 0x20) != 0) { + stream.writeFlag(MdlUtils.TOKEN_FULL_RESOLUTION); + } + + if (this.priorityPlane != 0) { + stream.writeAttrib(MdlUtils.TOKEN_PRIORITY_PLANE, this.priorityPlane); + } + + for (final Layer layer : this.layers) { + layer.writeMdl(stream); + } + + stream.endBlock(); + } + + @Override + public long getByteLength() { + long size = 20; + + for (final Layer layer : this.layers) { + size += layer.getByteLength(); + } + + return size; + } +} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/MdlTokenInputStream.java b/core/src/com/etheller/warsmash/parsers/mdlx/MdlTokenInputStream.java index bb01f82..0049b8e 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/MdlTokenInputStream.java +++ b/core/src/com/etheller/warsmash/parsers/mdlx/MdlTokenInputStream.java @@ -1,7 +1,5 @@ package com.etheller.warsmash.parsers.mdlx; -import java.util.Iterator; - public interface MdlTokenInputStream { String read(); @@ -11,9 +9,27 @@ public interface MdlTokenInputStream { float readFloat(); + void readIntArray(long[] values); + + float[] readFloatArray(float[] values); // is this same as read keyframe??? + void readKeyframe(float[] values); + float[] readVectorArray(float[] array, int vectorLength); + + int[] readUInt16Array(int[] values); + + short[] readUInt8Array(short[] values); + String peek(); - Iterator readBlock(); + // needs crazy generator function behavior that I can call this multiple times + // and it allocates a new iterator that is changing the same underlying + // stream position, and needs nesting of blocks within blocks + // (see crazy transcribed generator in GenericObject, only makes good sense + // in JS) + Iterable readBlock(); + + void readColor(float[] color); + } diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/MdlTokenOutputStream.java b/core/src/com/etheller/warsmash/parsers/mdlx/MdlTokenOutputStream.java index a2a07b9..511430c 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/MdlTokenOutputStream.java +++ b/core/src/com/etheller/warsmash/parsers/mdlx/MdlTokenOutputStream.java @@ -11,11 +11,49 @@ public interface MdlTokenOutputStream { void unindent(); + void startObjectBlock(String name, String objectName); + void startBlock(String name, int blockSize); + void startBlock(String name); + void writeFlag(String token); + void writeFlagUInt32(long flag); + void writeAttrib(String string, int globalSequenceId); + void writeAttribUInt32(String attribName, long uInt); + + void writeAttrib(String string, String value); + + void writeFloatAttrib(String attribName, float value); + + // if this matches writeAttrib(String,String), + // then remove it + void writeStringAttrib(String attribName, String value); + + void writeFloatArrayAttrib(String attribName, float[] floatArray); + + void writeLongSubArrayAttrib(String attribName, long[] array, int startIndexInclusive, int endIndexExclusive); + + void writeFloatArray(float[] floatArray); + + void writeVectorArray(String token, float[] vectors, int vectorLength); + void endBlock(); + + void endBlockComma(); + + void writeLine(String string); + + void startBlock(String tokenFaces, int sizeNumberProbably, int length); + + void writeColor(String tokenStaticColor, float[] color); + + void writeArrayAttrib(String tokenAlpha, short[] uint8Array); + + void writeArrayAttrib(String tokenAlpha, int[] uint16Array); + + void writeArrayAttrib(String tokenAlpha, long[] uint32Array); } diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/MdlxBlock.java b/core/src/com/etheller/warsmash/parsers/mdlx/MdlxBlock.java new file mode 100644 index 0000000..680cbec --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/mdlx/MdlxBlock.java @@ -0,0 +1,16 @@ +package com.etheller.warsmash.parsers.mdlx; + +import java.io.IOException; + +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +public interface MdlxBlock { + void readMdx(final LittleEndianDataInputStream stream) throws IOException; + + void writeMdx(final LittleEndianDataOutputStream stream) throws IOException; + + void readMdl(final MdlTokenInputStream stream) throws IOException; + + void writeMdl(final MdlTokenOutputStream stream) throws IOException; +} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/MdlxBlockDescriptor.java b/core/src/com/etheller/warsmash/parsers/mdlx/MdlxBlockDescriptor.java new file mode 100644 index 0000000..2844003 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/mdlx/MdlxBlockDescriptor.java @@ -0,0 +1,125 @@ +package com.etheller.warsmash.parsers.mdlx; + +import com.etheller.warsmash.util.Descriptor; + +public interface MdlxBlockDescriptor extends Descriptor { + + public static final MdlxBlockDescriptor ATTACHMENT = new MdlxBlockDescriptor() { + @Override + public Attachment create() { + return new Attachment(); + } + }; + + public static final MdlxBlockDescriptor BONE = new MdlxBlockDescriptor() { + @Override + public Bone create() { + return new Bone(); + } + }; + + public static final MdlxBlockDescriptor CAMERA = new MdlxBlockDescriptor() { + @Override + public Camera create() { + return new Camera(); + } + }; + + public static final MdlxBlockDescriptor COLLISION_SHAPE = new MdlxBlockDescriptor() { + @Override + public CollisionShape create() { + return new CollisionShape(); + } + }; + + public static final MdlxBlockDescriptor EVENT_OBJECT = new MdlxBlockDescriptor() { + @Override + public EventObject create() { + return new EventObject(); + } + }; + + public static final MdlxBlockDescriptor GEOSET = new MdlxBlockDescriptor() { + @Override + public Geoset create() { + return new Geoset(); + } + }; + + public static final MdlxBlockDescriptor GEOSET_ANIMATION = new MdlxBlockDescriptor() { + @Override + public GeosetAnimation create() { + return new GeosetAnimation(); + } + }; + + public static final MdlxBlockDescriptor HELPER = new MdlxBlockDescriptor() { + @Override + public Helper create() { + return new Helper(); + } + }; + + public static final MdlxBlockDescriptor LIGHT = new MdlxBlockDescriptor() { + @Override + public Light create() { + return new Light(); + } + }; + + public static final MdlxBlockDescriptor LAYER = new MdlxBlockDescriptor() { + @Override + public Layer create() { + return new Layer(); + } + }; + + public static final MdlxBlockDescriptor MATERIAL = new MdlxBlockDescriptor() { + @Override + public Material create() { + return new Material(); + } + }; + + public static final MdlxBlockDescriptor PARTICLE_EMITTER = new MdlxBlockDescriptor() { + @Override + public ParticleEmitter create() { + return new ParticleEmitter(); + } + }; + + public static final MdlxBlockDescriptor PARTICLE_EMITTER2 = new MdlxBlockDescriptor() { + @Override + public ParticleEmitter2 create() { + return new ParticleEmitter2(); + } + }; + + public static final MdlxBlockDescriptor RIBBON_EMITTER = new MdlxBlockDescriptor() { + @Override + public RibbonEmitter create() { + return new RibbonEmitter(); + } + }; + + public static final MdlxBlockDescriptor SEQUENCE = new MdlxBlockDescriptor() { + @Override + public Sequence create() { + return new Sequence(); + } + }; + + public static final MdlxBlockDescriptor TEXTURE = new MdlxBlockDescriptor() { + @Override + public Texture create() { + return new Texture(); + } + }; + + public static final MdlxBlockDescriptor TEXTURE_ANIMATION = new MdlxBlockDescriptor() { + @Override + public TextureAnimation create() { + return new TextureAnimation(); + } + }; +} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/MdlxModel.java b/core/src/com/etheller/warsmash/parsers/mdlx/MdlxModel.java new file mode 100644 index 0000000..e5bc993 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/mdlx/MdlxModel.java @@ -0,0 +1,642 @@ +package com.etheller.warsmash.parsers.mdlx; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +import com.badlogic.gdx.utils.StreamUtils; +import com.etheller.warsmash.parsers.mdlx.mdl.GhostwolfTokenInputStream; +import com.etheller.warsmash.parsers.mdlx.mdl.GhostwolfTokenOutputStream; +import com.etheller.warsmash.util.MdlUtils; +import com.etheller.warsmash.util.ParseUtils; +import com.etheller.warsmash.util.War3ID; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +/** + * A Warcraft 3 model. Supports loading from and saving to both the binary MDX + * and text MDL file formats. + */ +public class MdlxModel { + // Below, these can't call a function on a string to make their value because + // switch/case statements require the value to be compile-time defined in order + // to be legal, and it appears to only allow basic binary operators for that. + // I would love a clearer way to just type 'MDLX' in a character constant in + // Java for this + private static final int MDLX = ('M' << 24) | ('D' << 16) | ('L' << 8) | ('X');// War3ID.fromString("MDLX").getValue(); + private static final int VERS = ('V' << 24) | ('E' << 16) | ('R' << 8) | ('S');// War3ID.fromString("VERS").getValue(); + private static final int MODL = ('M' << 24) | ('O' << 16) | ('D' << 8) | ('L');// War3ID.fromString("MODL").getValue(); + private static final int SEQS = ('S' << 24) | ('E' << 16) | ('Q' << 8) | ('S');// War3ID.fromString("SEQS").getValue(); + private static final int GLBS = ('G' << 24) | ('L' << 16) | ('B' << 8) | ('S');// War3ID.fromString("GLBS").getValue(); + private static final int MTLS = ('M' << 24) | ('T' << 16) | ('L' << 8) | ('S');// War3ID.fromString("MTLS").getValue(); + private static final int TEXS = ('T' << 24) | ('E' << 16) | ('X' << 8) | ('S');// War3ID.fromString("TEXS").getValue(); + private static final int TXAN = ('T' << 24) | ('X' << 16) | ('A' << 8) | ('N');// War3ID.fromString("TXAN").getValue(); + private static final int GEOS = ('G' << 24) | ('E' << 16) | ('O' << 8) | ('S');// War3ID.fromString("GEOS").getValue(); + private static final int GEOA = ('G' << 24) | ('E' << 16) | ('O' << 8) | ('A');// War3ID.fromString("GEOA").getValue(); + private static final int BONE = ('B' << 24) | ('O' << 16) | ('N' << 8) | ('E');// War3ID.fromString("BONE").getValue(); + private static final int LITE = ('L' << 24) | ('I' << 16) | ('T' << 8) | ('E');// War3ID.fromString("LITE").getValue(); + private static final int HELP = ('H' << 24) | ('E' << 16) | ('L' << 8) | ('P');// War3ID.fromString("HELP").getValue(); + private static final int ATCH = ('A' << 24) | ('T' << 16) | ('C' << 8) | ('H');// War3ID.fromString("ATCH").getValue(); + private static final int PIVT = ('P' << 24) | ('I' << 16) | ('V' << 8) | ('T');// War3ID.fromString("PIVT").getValue(); + private static final int PREM = ('P' << 24) | ('R' << 16) | ('E' << 8) | ('M');// War3ID.fromString("PREM").getValue(); + private static final int PRE2 = ('P' << 24) | ('R' << 16) | ('E' << 8) | ('2');// War3ID.fromString("PRE2").getValue(); + private static final int RIBB = ('R' << 24) | ('I' << 16) | ('B' << 8) | ('B');// War3ID.fromString("RIBB").getValue(); + private static final int CAMS = ('C' << 24) | ('A' << 16) | ('M' << 8) | ('S');// War3ID.fromString("CAMS").getValue(); + private static final int EVTS = ('E' << 24) | ('V' << 16) | ('T' << 8) | ('S');// War3ID.fromString("EVTS").getValue(); + private static final int CLID = ('C' << 24) | ('L' << 16) | ('I' << 8) | ('D');// War3ID.fromString("CLID").getValue(); + private int version = 800; + private String name = ""; + /** + * (Comment copied from Ghostwolf JS) To the best of my knowledge, this should + * always be left empty. This is probably a leftover from the Warcraft 3 beta. + * (WS game note: No, I never saw any animation files in the RoC 2001-2002 Beta. + * So it must be from the Alpha) + * + * @member {string} + */ + private String animationFile = ""; + private final Extent extent = new Extent(); + private long blendTime = 0; + private final List sequences = new ArrayList(); + private final List globalSequences = new ArrayList<>(); + private final List materials = new ArrayList<>(); + private final List textures = new ArrayList<>(); + private final List textureAnimations = new ArrayList<>(); + private final List geosets = new ArrayList<>(); + private final List geosetAnimations = new ArrayList<>(); + private final List bones = new ArrayList<>(); + private final List lights = new ArrayList<>(); + private final List helpers = new ArrayList<>(); + private final List attachments = new ArrayList<>(); + private final List pivotPoints = new ArrayList<>(); + private final List particleEmitters = new ArrayList<>(); + private final List particleEmitters2 = new ArrayList<>(); + private final List ribbonEmitters = new ArrayList<>(); + private final List cameras = new ArrayList<>(); + private final List eventObjects = new ArrayList<>(); + private final List collisionShapes = new ArrayList<>(); + private final List unknownChunks = new ArrayList<>(); + + public MdlxModel(final InputStream buffer) throws IOException { + if (buffer != null) { + // In ghostwolf JS, this function called load() + // which decided whether the buffer was an MDL. + loadMdx(buffer); + } + } + + public void loadMdx(final InputStream buffer) throws IOException { + final LittleEndianDataInputStream stream = new LittleEndianDataInputStream(buffer); + if (Integer.reverseBytes(stream.readInt()) != MDLX) { + throw new IllegalStateException("WrongMagicNumber"); + } + + while (stream.available() > 0) { + final int tag = Integer.reverseBytes(stream.readInt()); + final long size = ParseUtils.readUInt32(stream); + + switch (tag) { + case VERS: + loadVersionChunk(stream); + break; + case MODL: + loadModelChunk(stream); + break; + case SEQS: + loadStaticObjects(this.sequences, MdlxBlockDescriptor.SEQUENCE, stream, size / 132); + break; + case GLBS: + loadGlobalSequenceChunk(stream, size); + break; + case MTLS: + loadDynamicObjects(this.materials, MdlxBlockDescriptor.MATERIAL, stream, size); + break; + case TEXS: + loadStaticObjects(this.textures, MdlxBlockDescriptor.TEXTURE, stream, size / 268); + break; + case TXAN: + loadDynamicObjects(this.textureAnimations, MdlxBlockDescriptor.TEXTURE_ANIMATION, stream, size); + break; + case GEOS: + loadDynamicObjects(this.geosets, MdlxBlockDescriptor.GEOSET, stream, size); + break; + case GEOA: + loadDynamicObjects(this.geosetAnimations, MdlxBlockDescriptor.GEOSET_ANIMATION, stream, size); + break; + case BONE: + loadDynamicObjects(this.bones, MdlxBlockDescriptor.BONE, stream, size); + break; + case LITE: + loadDynamicObjects(this.lights, MdlxBlockDescriptor.LIGHT, stream, size); + break; + case HELP: + loadDynamicObjects(this.helpers, MdlxBlockDescriptor.HELPER, stream, size); + break; + case ATCH: + loadDynamicObjects(this.attachments, MdlxBlockDescriptor.ATTACHMENT, stream, size); + break; + case PIVT: + loadPivotPointChunk(stream, size); + break; + case PREM: + loadDynamicObjects(this.particleEmitters, MdlxBlockDescriptor.PARTICLE_EMITTER, stream, size); + break; + case PRE2: + loadDynamicObjects(this.particleEmitters2, MdlxBlockDescriptor.PARTICLE_EMITTER2, stream, size); + break; + case RIBB: + loadDynamicObjects(this.ribbonEmitters, MdlxBlockDescriptor.RIBBON_EMITTER, stream, size); + break; + case CAMS: + loadDynamicObjects(this.cameras, MdlxBlockDescriptor.CAMERA, stream, size); + break; + case EVTS: + loadDynamicObjects(this.eventObjects, MdlxBlockDescriptor.EVENT_OBJECT, stream, size); + break; + case CLID: + loadDynamicObjects(this.collisionShapes, MdlxBlockDescriptor.COLLISION_SHAPE, stream, size); + break; + default: + this.unknownChunks.add(new UnknownChunk(stream, size, new War3ID(tag))); + } + } + + } + + private void loadVersionChunk(final LittleEndianDataInputStream stream) throws IOException { + this.version = (int) ParseUtils.readUInt32(stream); + } + + /** + * Restricts us to only be able to parse models on one thread at a time, in + * return for high performance. + */ + private static final byte[] NAME_BYTES_HEAP = new byte[80]; + private static final byte[] ANIMATION_FILE_BYTES_HEAP = new byte[260]; + + private void loadModelChunk(final LittleEndianDataInputStream stream) throws IOException { + this.name = ParseUtils.readString(stream, NAME_BYTES_HEAP); + this.animationFile = ParseUtils.readString(stream, ANIMATION_FILE_BYTES_HEAP); + this.extent.readMdx(stream); + this.blendTime = ParseUtils.readUInt32(stream); + } + + private void loadStaticObjects(final List out, final MdlxBlockDescriptor constructor, + final LittleEndianDataInputStream stream, final long count) throws IOException { + for (int i = 0; i < count; i++) { + final E object = constructor.create(); + + object.readMdx(stream); + + out.add(object); + } + } + + private void loadGlobalSequenceChunk(final LittleEndianDataInputStream stream, final long size) throws IOException { + for (long i = 0, l = size / 4; i < l; i++) { + this.globalSequences.add(ParseUtils.readUInt32(stream)); + } + } + + private void loadDynamicObjects(final List out, + final MdlxBlockDescriptor constructor, final LittleEndianDataInputStream stream, final long size) + throws IOException { + long totalSize = 0; + while (totalSize < size) { + final E object = constructor.create(); + + object.readMdx(stream); + + totalSize += object.getByteLength(); + + out.add(object); + } + } + + private void loadPivotPointChunk(final LittleEndianDataInputStream stream, final long size) throws IOException { + for (long i = 0, l = size / 12; i < l; i++) { + this.pivotPoints.add(ParseUtils.readFloatArray(stream, 3)); + } + } + + public void saveMdx(final OutputStream outputStream) throws IOException { + final LittleEndianDataOutputStream stream = new LittleEndianDataOutputStream(outputStream); + stream.writeInt(Integer.reverseBytes(MDLX)); + this.saveVersionChunk(stream); + this.saveModelChunk(stream); + this.saveStaticObjectChunk(stream, SEQS, this.sequences, 132); + this.saveGlobalSequenceChunk(stream); + this.saveDynamicObjectChunk(stream, MTLS, this.materials); + this.saveStaticObjectChunk(stream, TEXS, this.textures, 268); + this.saveDynamicObjectChunk(stream, TXAN, this.textureAnimations); + this.saveDynamicObjectChunk(stream, GEOS, this.geosets); + this.saveDynamicObjectChunk(stream, GEOA, this.geosetAnimations); + this.saveDynamicObjectChunk(stream, BONE, this.bones); + this.saveDynamicObjectChunk(stream, LITE, this.lights); + this.saveDynamicObjectChunk(stream, HELP, this.helpers); + this.saveDynamicObjectChunk(stream, ATCH, this.attachments); + this.savePivotPointChunk(stream); + this.saveDynamicObjectChunk(stream, PREM, this.particleEmitters); + this.saveDynamicObjectChunk(stream, PRE2, this.particleEmitters2); + this.saveDynamicObjectChunk(stream, RIBB, this.ribbonEmitters); + this.saveDynamicObjectChunk(stream, CAMS, this.cameras); + this.saveDynamicObjectChunk(stream, EVTS, this.eventObjects); + this.saveDynamicObjectChunk(stream, CLID, this.collisionShapes); + + for (final UnknownChunk chunk : this.unknownChunks) { + chunk.writeMdx(stream); + } + } + + private void saveVersionChunk(final LittleEndianDataOutputStream stream) throws IOException { + stream.writeInt(Integer.reverseBytes(VERS)); + ParseUtils.writeUInt32(stream, 4); + ParseUtils.writeUInt32(stream, this.version); + } + + private void saveModelChunk(final LittleEndianDataOutputStream stream) throws IOException { + stream.writeInt(Integer.reverseBytes(MODL)); + ParseUtils.writeUInt32(stream, 372); + final byte[] bytes = this.name.getBytes(ParseUtils.UTF8); + stream.write(bytes); + for (int i = 0; i < (NAME_BYTES_HEAP.length - bytes.length); i++) { + stream.write((byte) 0); + } + final byte[] animationFileBytes = this.animationFile.getBytes(ParseUtils.UTF8); + stream.write(animationFileBytes); + for (int i = 0; i < (ANIMATION_FILE_BYTES_HEAP.length - animationFileBytes.length); i++) { + stream.write((byte) 0); + } + this.extent.writeMdx(stream); + ParseUtils.writeUInt32(stream, this.blendTime); + } + + private void saveStaticObjectChunk(final LittleEndianDataOutputStream stream, final int name, + final List objects, final long size) throws IOException { + if (!objects.isEmpty()) { + stream.writeInt(Integer.reverseBytes(name)); + ParseUtils.writeUInt32(stream, objects.size() * size); + + for (final E object : objects) { + object.writeMdx(stream); + } + } + } + + private void saveGlobalSequenceChunk(final LittleEndianDataOutputStream stream) throws IOException { + if (!this.globalSequences.isEmpty()) { + stream.writeInt(Integer.reverseBytes(GLBS)); + ParseUtils.writeUInt32(stream, this.globalSequences.size() * 4); + + for (final Long globalSequence : this.globalSequences) { + ParseUtils.writeUInt32(stream, globalSequence); + } + } + } + + private void saveDynamicObjectChunk(final LittleEndianDataOutputStream stream, + final int name, final List objects) throws IOException { + if (!objects.isEmpty()) { + stream.writeInt(Integer.reverseBytes(name)); + ParseUtils.writeUInt32(stream, getObjectsByteLength(objects)); + + for (final E object : objects) { + object.writeMdx(stream); + } + } + } + + private void savePivotPointChunk(final LittleEndianDataOutputStream stream) throws IOException { + if (this.pivotPoints.size() > 0) { + stream.writeInt(Integer.reverseBytes(PIVT)); + ParseUtils.writeUInt32(stream, this.pivotPoints.size() * 12); + + for (final float[] pivotPoint : this.pivotPoints) { + ParseUtils.writeFloatArray(stream, pivotPoint); + } + } + } + + public void loadMdl(final InputStream inputStream) throws IOException { + final byte[] array = StreamUtils.copyStreamToByteArray(inputStream); + loadMdl(ByteBuffer.wrap(array)); + } + + public void loadMdl(final ByteBuffer inputStream) throws IOException { + String token; + final MdlTokenInputStream stream = new GhostwolfTokenInputStream(inputStream); + + while ((token = stream.read()) != null) { + switch (token) { + case MdlUtils.TOKEN_VERSION: + this.loadVersionBlock(stream); + break; + case MdlUtils.TOKEN_MODEL: + this.loadModelBlock(stream); + break; + case MdlUtils.TOKEN_SEQUENCES: + this.loadNumberedObjectBlock(this.sequences, MdlxBlockDescriptor.SEQUENCE, MdlUtils.TOKEN_ANIM, stream); + break; + case MdlUtils.TOKEN_GLOBAL_SEQUENCES: + this.loadGlobalSequenceBlock(stream); + break; + case MdlUtils.TOKEN_TEXTURES: + this.loadNumberedObjectBlock(this.textures, MdlxBlockDescriptor.TEXTURE, MdlUtils.TOKEN_BITMAP, stream); + break; + case MdlUtils.TOKEN_MATERIALS: + this.loadNumberedObjectBlock(this.materials, MdlxBlockDescriptor.MATERIAL, MdlUtils.TOKEN_MATERIAL, + stream); + break; + case MdlUtils.TOKEN_TEXTURE_ANIMS: + this.loadNumberedObjectBlock(this.textureAnimations, MdlxBlockDescriptor.TEXTURE_ANIMATION, + MdlUtils.TOKEN_TEXTURE_ANIM, stream); + break; + case MdlUtils.TOKEN_GEOSET: + this.loadObject(this.geosets, MdlxBlockDescriptor.GEOSET, stream); + break; + case MdlUtils.TOKEN_GEOSETANIM: + this.loadObject(this.geosetAnimations, MdlxBlockDescriptor.GEOSET_ANIMATION, stream); + break; + case MdlUtils.TOKEN_BONE: + this.loadObject(this.bones, MdlxBlockDescriptor.BONE, stream); + break; + case MdlUtils.TOKEN_LIGHT: + this.loadObject(this.lights, MdlxBlockDescriptor.LIGHT, stream); + break; + case MdlUtils.TOKEN_HELPER: + this.loadObject(this.helpers, MdlxBlockDescriptor.HELPER, stream); + break; + case MdlUtils.TOKEN_ATTACHMENT: + this.loadObject(this.attachments, MdlxBlockDescriptor.ATTACHMENT, stream); + break; + case MdlUtils.TOKEN_PIVOT_POINTS: + this.loadPivotPointBlock(stream); + break; + case MdlUtils.TOKEN_PARTICLE_EMITTER: + this.loadObject(this.particleEmitters, MdlxBlockDescriptor.PARTICLE_EMITTER, stream); + break; + case MdlUtils.TOKEN_PARTICLE_EMITTER2: + this.loadObject(this.particleEmitters2, MdlxBlockDescriptor.PARTICLE_EMITTER2, stream); + break; + case MdlUtils.TOKEN_RIBBON_EMITTER: + this.loadObject(this.ribbonEmitters, MdlxBlockDescriptor.RIBBON_EMITTER, stream); + break; + case MdlUtils.TOKEN_CAMERA: + this.loadObject(this.cameras, MdlxBlockDescriptor.CAMERA, stream); + break; + case MdlUtils.TOKEN_EVENT_OBJECT: + this.loadObject(this.eventObjects, MdlxBlockDescriptor.EVENT_OBJECT, stream); + break; + case MdlUtils.TOKEN_COLLISION_SHAPE: + this.loadObject(this.collisionShapes, MdlxBlockDescriptor.COLLISION_SHAPE, stream); + break; + default: + throw new IllegalStateException("Unsupported block: " + token); + } + } + } + + private void loadVersionBlock(final MdlTokenInputStream stream) { + for (final String token : stream.readBlock()) { + switch (token) { + case MdlUtils.TOKEN_FORMAT_VERSION: + this.version = stream.readInt(); + break; + default: + throw new IllegalStateException("Unknown token in Version: " + token); + } + } + } + + private void loadModelBlock(final MdlTokenInputStream stream) { + this.name = stream.read(); + for (final String token : stream.readBlock()) { + if (token.startsWith("Num")) { + /*- + * Don't care about the number of things, the arrays will grow as they wish. + * This includes: + * NumGeosets + * NumGeosetAnims + * NumHelpers + * NumLights + * NumBones + * NumAttachments + * NumParticleEmitters + * NumParticleEmitters2 + * NumRibbonEmitters + * NumEvents + */ + stream.read(); + } + else { + switch (token) { + case MdlUtils.TOKEN_BLEND_TIME: + this.blendTime = stream.readUInt32(); + break; + case MdlUtils.TOKEN_MINIMUM_EXTENT: + stream.readFloatArray(this.extent.min); + break; + case MdlUtils.TOKEN_MAXIMUM_EXTENT: + stream.readFloatArray(this.extent.max); + break; + case MdlUtils.TOKEN_BOUNDSRADIUS: + this.extent.boundsRadius = stream.readFloat(); + break; + default: + throw new IllegalStateException("Unknown token in Model: " + token); + } + } + } + } + + private void loadNumberedObjectBlock(final List out, + final MdlxBlockDescriptor constructor, final String name, final MdlTokenInputStream stream) + throws IOException { + stream.read(); // Don't care about the number, the array will grow + + for (final String token : stream.readBlock()) { + if (token.equals(name)) { + final E object = constructor.create(); + + object.readMdl(stream); + + out.add(object); + } + else { + throw new IllegalStateException("Unknown token in " + name + ": " + token); + } + } + } + + private void loadGlobalSequenceBlock(final MdlTokenInputStream stream) { + stream.read(); // Don't care about the number, the array will grow + + for (final String token : stream.readBlock()) { + if (token.equals(MdlUtils.TOKEN_DURATION)) { + this.globalSequences.add(stream.readUInt32()); + } + else { + throw new IllegalStateException("Unknown token in GlobalSequences: " + token); + } + } + } + + private void loadObject(final List out, final MdlxBlockDescriptor descriptor, + final MdlTokenInputStream stream) throws IOException { + final E object = descriptor.create(); + + object.readMdl(stream); + + out.add(object); + } + + private void loadPivotPointBlock(final MdlTokenInputStream stream) { + final int count = stream.readInt(); + + stream.read(); // { + + for (int i = 0; i < count; i++) { + this.pivotPoints.add(stream.readFloatArray(new float[3])); + } + + stream.read(); // } + } + + public void saveMdl(final OutputStream outputStream) throws IOException { + try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream))) { + final MdlTokenOutputStream stream = new GhostwolfTokenOutputStream(writer); + this.saveVersionBlock(stream); + this.saveModelBlock(stream); + this.saveStaticObjectsBlock(stream, MdlUtils.TOKEN_SEQUENCES, this.sequences); + this.saveGlobalSequenceBlock(stream); + this.saveStaticObjectsBlock(stream, MdlUtils.TOKEN_TEXTURES, this.textures); + this.saveStaticObjectsBlock(stream, MdlUtils.TOKEN_MATERIALS, this.materials); + this.saveStaticObjectsBlock(stream, MdlUtils.TOKEN_TEXTURE_ANIMS, this.textureAnimations); + this.saveObjects(stream, this.geosets); + this.saveObjects(stream, this.geosetAnimations); + this.saveObjects(stream, this.bones); + this.saveObjects(stream, this.lights); + this.saveObjects(stream, this.helpers); + this.saveObjects(stream, this.attachments); + this.savePivotPointBlock(stream); + this.saveObjects(stream, this.particleEmitters); + this.saveObjects(stream, this.particleEmitters2); + this.saveObjects(stream, this.ribbonEmitters); + this.saveObjects(stream, this.cameras); + this.saveObjects(stream, this.eventObjects); + this.saveObjects(stream, this.collisionShapes); + } + } + + private void saveVersionBlock(final MdlTokenOutputStream stream) { + stream.startBlock(MdlUtils.TOKEN_VERSION); + stream.writeAttrib(MdlUtils.TOKEN_FORMAT_VERSION, this.version); + stream.endBlock(); + } + + private void saveModelBlock(final MdlTokenOutputStream stream) { + stream.startObjectBlock(MdlUtils.TOKEN_MODEL, this.name); + stream.writeAttribUInt32(MdlUtils.TOKEN_BLEND_TIME, this.blendTime); + this.extent.writeMdl(stream); + stream.endBlock(); + } + + private void saveStaticObjectsBlock(final MdlTokenOutputStream stream, final String name, + final List objects) throws IOException { + if (!objects.isEmpty()) { + stream.startBlock(name, objects.size()); + + for (final MdlxBlock object : objects) { + object.writeMdl(stream); + } + + stream.endBlock(); + } + } + + private void saveGlobalSequenceBlock(final MdlTokenOutputStream stream) { + if (!this.globalSequences.isEmpty()) { + stream.startBlock(MdlUtils.TOKEN_GLOBAL_SEQUENCES, this.globalSequences.size()); + + for (final Long globalSequence : this.globalSequences) { + stream.writeAttribUInt32(MdlUtils.TOKEN_DURATION, globalSequence); + } + + stream.endBlock(); + } + } + + private void saveObjects(final MdlTokenOutputStream stream, final List objects) + throws IOException { + for (final MdlxBlock object : objects) { + object.writeMdl(stream); + } + } + + private void savePivotPointBlock(final MdlTokenOutputStream stream) { + if (!this.pivotPoints.isEmpty()) { + stream.startBlock(MdlUtils.TOKEN_PIVOT_POINTS, this.pivotPoints.size()); + + for (final float[] pivotPoint : this.pivotPoints) { + stream.writeFloatArray(pivotPoint); + } + + stream.endBlock(); + } + } + + private long getByteLength() { + long size = 396; + + size += getStaticObjectsChunkByteLength(this.sequences, 132); + size += this.getStaticObjectsChunkByteLength(this.globalSequences, 4); + size += this.getDynamicObjectsChunkByteLength(this.materials); + size += this.getStaticObjectsChunkByteLength(this.textures, 268); + size += this.getDynamicObjectsChunkByteLength(this.textureAnimations); + size += this.getDynamicObjectsChunkByteLength(this.geosets); + size += this.getDynamicObjectsChunkByteLength(this.geosetAnimations); + size += this.getDynamicObjectsChunkByteLength(this.bones); + size += this.getDynamicObjectsChunkByteLength(this.lights); + size += this.getDynamicObjectsChunkByteLength(this.helpers); + size += this.getDynamicObjectsChunkByteLength(this.attachments); + size += this.getStaticObjectsChunkByteLength(this.pivotPoints, 12); + size += this.getDynamicObjectsChunkByteLength(this.particleEmitters); + size += this.getDynamicObjectsChunkByteLength(this.particleEmitters2); + size += this.getDynamicObjectsChunkByteLength(this.ribbonEmitters); + size += this.getDynamicObjectsChunkByteLength(this.cameras); + size += this.getDynamicObjectsChunkByteLength(this.eventObjects); + size += this.getDynamicObjectsChunkByteLength(this.collisionShapes); + size += this.getObjectsByteLength(this.unknownChunks); + + return size; + } + + private long getObjectsByteLength(final List objects) { + long size = 0; + for (final E object : objects) { + size += object.getByteLength(); + } + return size; + } + + private long getDynamicObjectsChunkByteLength(final List objects) { + if (!objects.isEmpty()) { + return 8 + this.getObjectsByteLength(objects); + } + + return 0; + } + + private long getStaticObjectsChunkByteLength(final List objects, final long size) { + if (!objects.isEmpty()) { + return 8 + (objects.size() * size); + } + + return 0; + } +} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/MdlxTest.java b/core/src/com/etheller/warsmash/parsers/mdlx/MdlxTest.java new file mode 100644 index 0000000..ca2e8da --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/mdlx/MdlxTest.java @@ -0,0 +1,64 @@ +package com.etheller.warsmash.parsers.mdlx; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; + +public class MdlxTest { + + public static void main(final String[] args) { + try (FileInputStream stream = new FileInputStream( + new File("C:\\Users\\micro\\OneDrive\\Documents\\Warcraft III\\Models\\ArcaneEpic13.mdx"))) { + final MdlxModel model = new MdlxModel(stream); + try (FileOutputStream mdlStream = new FileOutputStream(new File( + "C:\\Users\\micro\\OneDrive\\Documents\\Warcraft III\\Models\\Test\\MyOutAutomated.mdl"))) { + + model.saveMdl(mdlStream); + } + } + catch (final FileNotFoundException e) { + e.printStackTrace(); + } + catch (final IOException e) { + e.printStackTrace(); + } + + System.out.println("Created MDL, now reparsing to MDX"); + + try (FileInputStream stream = new FileInputStream( + new File("C:\\Users\\micro\\OneDrive\\Documents\\Warcraft III\\Models\\Test\\MyOutAutomated.mdl"))) { + final MdlxModel model = new MdlxModel(null); + model.loadMdl(stream); + try (FileOutputStream mdlStream = new FileOutputStream(new File( + "C:\\Users\\micro\\OneDrive\\Documents\\Warcraft III\\Models\\Test\\MyOutAutomatedMDX.mdx"))) { + + model.saveMdx(mdlStream); + } + } + catch (final FileNotFoundException e) { + e.printStackTrace(); + } + catch (final IOException e) { + e.printStackTrace(); + } + + try (FileInputStream stream = new FileInputStream( + new File("C:\\Users\\micro\\OneDrive\\Documents\\Warcraft III\\Models\\Test\\MyOutAutomatedMDX.mdx"))) { + final MdlxModel model = new MdlxModel(stream); + try (FileOutputStream mdlStream = new FileOutputStream(new File( + "C:\\Users\\micro\\OneDrive\\Documents\\Warcraft III\\Models\\Test\\MyOutAutomatedMDXBack2MDL.mdl"))) { + + model.saveMdl(mdlStream); + } + } + catch (final FileNotFoundException e) { + e.printStackTrace(); + } + catch (final IOException e) { + e.printStackTrace(); + } + } + +} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/ParticleEmitter.java b/core/src/com/etheller/warsmash/parsers/mdlx/ParticleEmitter.java new file mode 100644 index 0000000..37d00c5 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/mdlx/ParticleEmitter.java @@ -0,0 +1,193 @@ +package com.etheller.warsmash.parsers.mdlx; + +import java.io.IOException; +import java.util.Iterator; + +import com.etheller.warsmash.util.MdlUtils; +import com.etheller.warsmash.util.ParseUtils; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +public class ParticleEmitter extends GenericObject { + private float emissionRate = 0; + private float gravity = 0; + private float longitude = 0; + private float latitude = 0; + private String path = ""; + private float lifeSpan = 0; + private float speed = 0; + + public ParticleEmitter() { + super(0x1000); + } + + /** + * Restricts us to only be able to parse models on one thread at a time, in + * return for high performance. + */ + private static final byte[] PATH_BYTES_HEAP = new byte[260]; + + @Override + public void readMdx(final LittleEndianDataInputStream stream) throws IOException { + final long size = ParseUtils.readUInt32(stream); + + super.readMdx(stream); + + this.emissionRate = stream.readFloat(); + this.gravity = stream.readFloat(); + this.longitude = stream.readFloat(); + this.latitude = stream.readFloat(); + this.path = ParseUtils.readString(stream, PATH_BYTES_HEAP); + this.lifeSpan = stream.readFloat(); + this.speed = stream.readFloat(); + + readTimelines(stream, size - this.getByteLength()); + } + + @Override + public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException { + ParseUtils.writeUInt32(stream, getByteLength()); + + super.writeMdx(stream); + + stream.writeFloat(this.emissionRate); + stream.writeFloat(this.gravity); + stream.writeFloat(this.longitude); + stream.writeFloat(this.latitude); + final byte[] bytes = this.path.getBytes(ParseUtils.UTF8); + stream.write(bytes); + for (int i = 0; i < (PATH_BYTES_HEAP.length - bytes.length); i++) { + stream.write((byte) 0); + } + stream.writeFloat(this.lifeSpan); + stream.writeFloat(this.speed); + + writeNonGenericAnimationChunks(stream); + } + + @Override + public void readMdl(final MdlTokenInputStream stream) throws IOException { + for (final String token : super.readMdlGeneric(stream)) { + switch (token) { + case MdlUtils.TOKEN_EMITTER_USES_MDL: + this.flags |= 0x8000; + break; + case MdlUtils.TOKEN_EMITTER_USES_TGA: + this.flags |= 0x10000; + break; + case MdlUtils.TOKEN_STATIC_EMISSION_RATE: + this.emissionRate = stream.readFloat(); + break; + case MdlUtils.TOKEN_EMISSION_RATE: + readTimeline(stream, AnimationMap.KPEE); + break; + case MdlUtils.TOKEN_STATIC_GRAVITY: + this.gravity = stream.readFloat(); + break; + case MdlUtils.TOKEN_GRAVITY: + readTimeline(stream, AnimationMap.KPEG); + break; + case MdlUtils.TOKEN_STATIC_LONGITUDE: + this.longitude = stream.readFloat(); + break; + case MdlUtils.TOKEN_LONGITUDE: + readTimeline(stream, AnimationMap.KPLN); + break; + case MdlUtils.TOKEN_STATIC_LATITUDE: + this.latitude = stream.readFloat(); + break; + case MdlUtils.TOKEN_LATITUDE: + readTimeline(stream, AnimationMap.KPLT); + break; + case MdlUtils.TOKEN_VISIBILITY: + readTimeline(stream, AnimationMap.KPEV); + break; + case MdlUtils.TOKEN_PARTICLE: + final Iterator iterator = readAnimatedBlock(stream); + while (iterator.hasNext()) { + final String subToken = iterator.next(); + switch (subToken) { + case MdlUtils.TOKEN_STATIC_LIFE_SPAN: + this.lifeSpan = stream.readFloat(); + break; + case MdlUtils.TOKEN_LIFE_SPAN: + readTimeline(stream, AnimationMap.KPEL); + break; + case MdlUtils.TOKEN_STATIC_INIT_VELOCITY: + this.speed = stream.readFloat(); + break; + case MdlUtils.TOKEN_INIT_VELOCITY: + readTimeline(stream, AnimationMap.KPES); + break; + case MdlUtils.TOKEN_PATH: + this.path = stream.read(); + break; + default: + throw new IllegalStateException( + "Unknown token in ParticleEmitter " + this.name + "'s Particle: " + subToken); + } + } + break; + default: + throw new IllegalStateException("Unknown token in ParticleEmitter " + this.name + ": " + token); + } + } + } + + @Override + public void writeMdl(final MdlTokenOutputStream stream) throws IOException { + stream.startObjectBlock(MdlUtils.TOKEN_PARTICLE_EMITTER, this.name); + writeGenericHeader(stream); + + if ((this.flags & 0x8000) != 0) { + stream.writeFlag(MdlUtils.TOKEN_EMITTER_USES_MDL); + } + + if ((this.flags & 0x10000) != 0) { + stream.writeFlag(MdlUtils.TOKEN_EMITTER_USES_TGA); + } + + if (!this.writeTimeline(stream, AnimationMap.KPEE)) { + stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_EMISSION_RATE, this.emissionRate); + } + + if (!this.writeTimeline(stream, AnimationMap.KPEG)) { + stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_GRAVITY, this.gravity); + } + + if (!this.writeTimeline(stream, AnimationMap.KPLN)) { + stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_LONGITUDE, this.longitude); + } + + if (!this.writeTimeline(stream, AnimationMap.KPLT)) { + stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_LATITUDE, this.latitude); + } + + this.writeTimeline(stream, AnimationMap.KPEV); + + stream.startBlock(MdlUtils.TOKEN_PARTICLE); + + if (!this.writeTimeline(stream, AnimationMap.KPEL)) { + stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_LIFE_SPAN, this.lifeSpan); + } + + if (!this.writeTimeline(stream, AnimationMap.KPES)) { + stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_INIT_VELOCITY, this.speed); + } + + if (((this.flags & 0x8000) != 0) || ((this.flags & 0x10000) != 0)) { + stream.writeAttrib(MdlUtils.TOKEN_PATH, this.path); + } + + stream.endBlock(); + + writeGenericTimelines(stream); + + stream.endBlock(); + } + + @Override + public long getByteLength() { + return 288 + super.getByteLength(); + } +} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/ParticleEmitter2.java b/core/src/com/etheller/warsmash/parsers/mdlx/ParticleEmitter2.java new file mode 100644 index 0000000..dd321be --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/mdlx/ParticleEmitter2.java @@ -0,0 +1,425 @@ +package com.etheller.warsmash.parsers.mdlx; + +import java.io.IOException; + +import com.etheller.warsmash.util.MdlUtils; +import com.etheller.warsmash.util.ParseUtils; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +public class ParticleEmitter2 extends GenericObject { + // 0: blend + // 1: additive + // 2: modulate + // 3: modulate 2x + // 4: alphakey + public static enum FilterMode { + BLEND("Blend"), + ADDITIVE("Additive"), + MODULATE("Modulate"), + MODULATE2X("Modulate2x"), + ALPHAKEY("AlphaKey"); + + String mdlText; + + FilterMode(final String str) { + this.mdlText = str; + } + + public String getMdlText() { + return this.mdlText; + } + + public static FilterMode fromId(final int id) { + return values()[id]; + } + + public static int nameToId(final String name) { + for (final FilterMode mode : values()) { + if (mode.getMdlText().equals(name)) { + return mode.ordinal(); + } + } + return -1; + } + + @Override + public String toString() { + return getMdlText(); + } + } + + private float speed = 0; + private float variation = 0; + private float latitude = 0; + private float gravity = 0; + private float lifeSpan = 0; + private float emissionRate = 0; + private float length; + private float width; + private FilterMode filterMode = FilterMode.BLEND; + private long rows = 0; + private long columns = 0; + private long headOrTail = 0; + private float tailLength = 0; + private float timeMiddle = 0; + private final float[][] segmentColors = new float[3][3]; + private final short[] segmentAlphas = new short[3]; // unsigned byte[] + private final float[] segmentScaling = new float[3]; + private final long[][] headIntervals = new long[2][3]; + private final long[][] tailIntervals = new long[2][3]; + private int textureId = -1; + private long squirt = 0; + private int priorityPlane = 0; + private long replaceableId = 0; + + public ParticleEmitter2() { + super(0x1000); + } + + @Override + public void readMdx(final LittleEndianDataInputStream stream) throws IOException { + final long size = ParseUtils.readUInt32(stream); + + super.readMdx(stream); + + this.speed = stream.readFloat(); + this.variation = stream.readFloat(); + this.latitude = stream.readFloat(); + this.gravity = stream.readFloat(); + this.lifeSpan = stream.readFloat(); + this.emissionRate = stream.readFloat(); + this.length = stream.readFloat(); + this.width = stream.readFloat(); + this.filterMode = FilterMode.fromId((int) (ParseUtils.readUInt32(stream))); + this.rows = ParseUtils.readUInt32(stream); + this.columns = ParseUtils.readUInt32(stream); + this.headOrTail = ParseUtils.readUInt32(stream); + this.tailLength = stream.readFloat(); + this.timeMiddle = stream.readFloat(); + ParseUtils.readFloatArray(stream, this.segmentColors[0]); + ParseUtils.readFloatArray(stream, this.segmentColors[1]); + ParseUtils.readFloatArray(stream, this.segmentColors[2]); + ParseUtils.readUInt8Array(stream, this.segmentAlphas); + ParseUtils.readFloatArray(stream, this.segmentScaling); + ParseUtils.readUInt32Array(stream, this.headIntervals[0]); + ParseUtils.readUInt32Array(stream, this.headIntervals[1]); + ParseUtils.readUInt32Array(stream, this.tailIntervals[0]); + ParseUtils.readUInt32Array(stream, this.tailIntervals[1]); + this.textureId = stream.readInt(); + this.squirt = ParseUtils.readUInt32(stream); + this.priorityPlane = stream.readInt(); + this.replaceableId = ParseUtils.readUInt32(stream); + + readTimelines(stream, size - this.getByteLength()); + } + + @Override + public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException { + ParseUtils.writeUInt32(stream, getByteLength()); + + super.writeMdx(stream); + + stream.writeFloat(this.speed); + stream.writeFloat(this.variation); + stream.writeFloat(this.latitude); + stream.writeFloat(this.gravity); + stream.writeFloat(this.lifeSpan); + stream.writeFloat(this.emissionRate); + stream.writeFloat(this.length); + stream.writeFloat(this.width); + ParseUtils.writeUInt32(stream, this.filterMode.ordinal()); + ParseUtils.writeUInt32(stream, this.rows); + ParseUtils.writeUInt32(stream, this.columns); + ParseUtils.writeUInt32(stream, this.headOrTail); + stream.writeFloat(this.tailLength); + stream.writeFloat(this.timeMiddle); + ParseUtils.writeFloatArray(stream, this.segmentColors[0]); + ParseUtils.writeFloatArray(stream, this.segmentColors[1]); + ParseUtils.writeFloatArray(stream, this.segmentColors[2]); + ParseUtils.writeUInt8Array(stream, this.segmentAlphas); + ParseUtils.writeFloatArray(stream, this.segmentScaling); + ParseUtils.writeUInt32Array(stream, this.headIntervals[0]); + ParseUtils.writeUInt32Array(stream, this.headIntervals[1]); + ParseUtils.writeUInt32Array(stream, this.tailIntervals[0]); + ParseUtils.writeUInt32Array(stream, this.tailIntervals[1]); + stream.writeInt(this.textureId); + ParseUtils.writeUInt32(stream, this.squirt); + stream.writeInt(this.priorityPlane); + ParseUtils.writeUInt32(stream, this.replaceableId); + + writeNonGenericAnimationChunks(stream); + } + + @Override + public void readMdl(final MdlTokenInputStream stream) throws IOException { + for (final String token : super.readMdlGeneric(stream)) { + switch (token) { + case MdlUtils.TOKEN_SORT_PRIMS_FAR_Z: + this.flags |= 0x10000; + break; + case MdlUtils.TOKEN_UNSHADED: + this.flags |= 0x8000; + break; + case MdlUtils.TOKEN_LINE_EMITTER: + this.flags |= 0x20000; + break; + case MdlUtils.TOKEN_UNFOGGED: + this.flags |= 0x40000; + break; + case MdlUtils.TOKEN_MODEL_SPACE: + this.flags |= 0x80000; + break; + case MdlUtils.TOKEN_XY_QUAD: + this.flags |= 0x100000; + break; + case MdlUtils.TOKEN_STATIC_SPEED: + this.speed = stream.readFloat(); + break; + case MdlUtils.TOKEN_SPEED: + readTimeline(stream, AnimationMap.KP2S); + break; + case MdlUtils.TOKEN_STATIC_VARIATION: + this.variation = stream.readFloat(); + break; + case MdlUtils.TOKEN_VARIATION: + readTimeline(stream, AnimationMap.KP2R); + break; + case MdlUtils.TOKEN_STATIC_LATITUDE: + this.latitude = stream.readFloat(); + break; + case MdlUtils.TOKEN_LATITUDE: + readTimeline(stream, AnimationMap.KP2L); + break; + case MdlUtils.TOKEN_STATIC_GRAVITY: + this.gravity = stream.readFloat(); + break; + case MdlUtils.TOKEN_GRAVITY: + readTimeline(stream, AnimationMap.KP2G); + break; + case MdlUtils.TOKEN_VISIBILITY: + readTimeline(stream, AnimationMap.KP2V); + break; + case MdlUtils.TOKEN_SQUIRT: + this.squirt = 1; + break; + case MdlUtils.TOKEN_LIFE_SPAN: + this.lifeSpan = stream.readFloat(); + break; + case MdlUtils.TOKEN_STATIC_EMISSION_RATE: + this.emissionRate = stream.readFloat(); + break; + case MdlUtils.TOKEN_EMISSION_RATE: + readTimeline(stream, AnimationMap.KP2E); + break; + case MdlUtils.TOKEN_STATIC_WIDTH: + this.width = stream.readFloat(); + break; + case MdlUtils.TOKEN_WIDTH: + readTimeline(stream, AnimationMap.KP2W); + break; + case MdlUtils.TOKEN_STATIC_LENGTH: + this.length = stream.readFloat(); + break; + case MdlUtils.TOKEN_LENGTH: + readTimeline(stream, AnimationMap.KP2N); + break; + case MdlUtils.TOKEN_BLEND: + this.filterMode = FilterMode.BLEND; + break; + case MdlUtils.TOKEN_ADDITIVE: + this.filterMode = FilterMode.ADDITIVE; + break; + case MdlUtils.TOKEN_MODULATE: + this.filterMode = FilterMode.MODULATE; + break; + case MdlUtils.TOKEN_MODULATE2X: + this.filterMode = FilterMode.MODULATE2X; + break; + case MdlUtils.TOKEN_ALPHAKEY: + this.filterMode = FilterMode.ALPHAKEY; + break; + case MdlUtils.TOKEN_ROWS: + this.rows = stream.readUInt32(); + break; + case MdlUtils.TOKEN_COLUMNS: + this.columns = stream.readUInt32(); + break; + case MdlUtils.TOKEN_HEAD: + this.headOrTail = 0; + break; + case MdlUtils.TOKEN_TAIL: + this.headOrTail = 1; + break; + case MdlUtils.TOKEN_BOTH: + this.headOrTail = 2; + break; + case MdlUtils.TOKEN_TAIL_LENGTH: + this.tailLength = stream.readFloat(); + break; + case MdlUtils.TOKEN_TIME: + this.timeMiddle = stream.readFloat(); + break; + case MdlUtils.TOKEN_SEGMENT_COLOR: + stream.read(); // { + + for (int i = 0; i < 3; i++) { + stream.read(); // Color + stream.readColor(this.segmentColors[i]); + } + + stream.read(); // } + break; + case MdlUtils.TOKEN_ALPHA: + stream.readUInt8Array(this.segmentAlphas); + break; + case MdlUtils.TOKEN_PARTICLE_SCALING: + stream.readFloatArray(this.segmentScaling); + break; + case MdlUtils.TOKEN_LIFE_SPAN_UV_ANIM: + stream.readIntArray(this.headIntervals[0]); + break; + case MdlUtils.TOKEN_DECAY_UV_ANIM: + stream.readIntArray(this.headIntervals[1]); + break; + case MdlUtils.TOKEN_TAIL_UV_ANIM: + stream.readIntArray(this.tailIntervals[0]); + break; + case MdlUtils.TOKEN_TAIL_DECAY_UV_ANIM: + stream.readIntArray(this.tailIntervals[1]); + break; + case MdlUtils.TOKEN_TEXTURE_ID: + this.textureId = stream.readInt(); + break; + case MdlUtils.TOKEN_REPLACEABLE_ID: + this.replaceableId = stream.readUInt32(); + break; + case MdlUtils.TOKEN_PRIORITY_PLANE: + this.priorityPlane = stream.readInt(); + break; + + default: + throw new IllegalStateException("Unknown token in ParticleEmitter2 " + this.name + ": " + token); + } + } + } + + @Override + public void writeMdl(final MdlTokenOutputStream stream) throws IOException { + stream.startObjectBlock(MdlUtils.TOKEN_PARTICLE_EMITTER2, this.name); + writeGenericHeader(stream); + + if ((this.flags & 0x10000) != 0) { + stream.writeFlag(MdlUtils.TOKEN_SORT_PRIMS_FAR_Z); + } + + if ((this.flags & 0x8000) != 0) { + stream.writeFlag(MdlUtils.TOKEN_UNSHADED); + } + + if ((this.flags & 0x20000) != 0) { + stream.writeFlag(MdlUtils.TOKEN_LINE_EMITTER); + } + + if ((this.flags & 0x40000) != 0) { + stream.writeFlag(MdlUtils.TOKEN_UNFOGGED); + } + + if ((this.flags & 0x80000) != 0) { + stream.writeFlag(MdlUtils.TOKEN_MODEL_SPACE); + } + + if ((this.flags & 0x100000) != 0) { + stream.writeFlag(MdlUtils.TOKEN_XY_QUAD); + } + + if (!this.writeTimeline(stream, AnimationMap.KP2S)) { + stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_SPEED, this.speed); + } + + if (!this.writeTimeline(stream, AnimationMap.KP2R)) { + stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_VARIATION, this.variation); + } + + if (!this.writeTimeline(stream, AnimationMap.KP2L)) { + stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_LATITUDE, this.latitude); + } + + if (!this.writeTimeline(stream, AnimationMap.KP2G)) { + stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_GRAVITY, this.gravity); + } + + writeTimeline(stream, AnimationMap.KP2V); + + if (this.squirt != 0) { + stream.writeFlag(MdlUtils.TOKEN_SQUIRT); + } + + stream.writeFloatAttrib(MdlUtils.TOKEN_LIFE_SPAN, this.lifeSpan); + + if (!this.writeTimeline(stream, AnimationMap.KP2E)) { + stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_EMISSION_RATE, this.emissionRate); + } + + if (!this.writeTimeline(stream, AnimationMap.KP2W)) { + stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_WIDTH, this.width); + } + + if (!this.writeTimeline(stream, AnimationMap.KP2N)) { + stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_LENGTH, this.length); + } + + stream.writeFlag(this.filterMode.getMdlText()); + + stream.writeAttribUInt32(MdlUtils.TOKEN_ROWS, this.rows); + stream.writeAttribUInt32(MdlUtils.TOKEN_COLUMNS, this.columns); + + switch ((int) this.headOrTail) { + case 0: + stream.writeFlag(MdlUtils.TOKEN_HEAD); + break; + case 1: + stream.writeFlag(MdlUtils.TOKEN_TAIL); + break; + case 2: + stream.writeFlag(MdlUtils.TOKEN_BOTH); + break; + default: + throw new IllegalStateException("Bad headOrTail value when saving MDL: " + this.headOrTail); + } + + stream.writeFloatAttrib(MdlUtils.TOKEN_TAIL_LENGTH, this.tailLength); + stream.writeFloatAttrib(MdlUtils.TOKEN_TIME, this.timeMiddle); + + stream.startBlock(MdlUtils.TOKEN_SEGMENT_COLOR); + stream.writeColor(MdlUtils.TOKEN_COLOR, this.segmentColors[0]); + stream.writeColor(MdlUtils.TOKEN_COLOR, this.segmentColors[1]); + stream.writeColor(MdlUtils.TOKEN_COLOR, this.segmentColors[2]); + stream.endBlockComma(); + + stream.writeArrayAttrib(MdlUtils.TOKEN_ALPHA, this.segmentAlphas); + stream.writeFloatArrayAttrib(MdlUtils.TOKEN_PARTICLE_SCALING, this.segmentScaling); + stream.writeArrayAttrib(MdlUtils.TOKEN_LIFE_SPAN_UV_ANIM, this.headIntervals[0]); + stream.writeArrayAttrib(MdlUtils.TOKEN_DECAY_UV_ANIM, this.headIntervals[1]); + stream.writeArrayAttrib(MdlUtils.TOKEN_TAIL_UV_ANIM, this.tailIntervals[0]); + stream.writeArrayAttrib(MdlUtils.TOKEN_TAIL_DECAY_UV_ANIM, this.tailIntervals[1]); + stream.writeAttrib(MdlUtils.TOKEN_TEXTURE_ID, this.textureId); + + if (this.replaceableId != 0) { + stream.writeAttribUInt32(MdlUtils.TOKEN_REPLACEABLE_ID, this.replaceableId); + } + + if (this.priorityPlane != 0) { + stream.writeAttrib(MdlUtils.TOKEN_PRIORITY_PLANE, this.priorityPlane); + } + + writeGenericTimelines(stream); + + stream.endBlock(); + } + + @Override + public long getByteLength() { + return 175 + super.getByteLength(); + } +} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/RibbonEmitter.java b/core/src/com/etheller/warsmash/parsers/mdlx/RibbonEmitter.java new file mode 100644 index 0000000..f5a756a --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/mdlx/RibbonEmitter.java @@ -0,0 +1,176 @@ +package com.etheller.warsmash.parsers.mdlx; + +import java.io.IOException; + +import com.etheller.warsmash.util.MdlUtils; +import com.etheller.warsmash.util.ParseUtils; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +public class RibbonEmitter extends GenericObject { + private float heightAbove = 0; + private float heightBelow = 0; + private float alpha = 0; + private final float[] color = new float[3]; + private float lifeSpan = 0; + private long textureSlot = 0; + private long emissionRate = 0; + private long rows = 0; + private long columns = 0; + private int materialId = 0; + private float gravity = 0; + + public RibbonEmitter() { + super(0x4000); + } + + @Override + public void readMdx(final LittleEndianDataInputStream stream) throws IOException { + final long size = ParseUtils.readUInt32(stream); + + super.readMdx(stream); + + this.heightAbove = stream.readFloat(); + this.heightBelow = stream.readFloat(); + this.alpha = stream.readFloat(); + ParseUtils.readFloatArray(stream, this.color); + this.lifeSpan = stream.readFloat(); + this.textureSlot = ParseUtils.readUInt32(stream); + this.emissionRate = ParseUtils.readUInt32(stream); + this.rows = ParseUtils.readUInt32(stream); + this.columns = ParseUtils.readUInt32(stream); + this.materialId = stream.readInt(); + this.gravity = stream.readFloat(); + + readTimelines(stream, size - getByteLength()); + } + + @Override + public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException { + ParseUtils.writeUInt32(stream, getByteLength()); + + super.writeMdx(stream); + + stream.writeFloat(this.heightAbove); + stream.writeFloat(this.heightBelow); + stream.writeFloat(this.alpha); + ParseUtils.writeFloatArray(stream, this.color); + stream.writeFloat(this.lifeSpan); + ParseUtils.writeUInt32(stream, this.textureSlot); + ParseUtils.writeUInt32(stream, this.emissionRate); + ParseUtils.writeUInt32(stream, this.rows); + ParseUtils.writeUInt32(stream, this.columns); + stream.writeInt(this.materialId); + stream.writeFloat(this.gravity); + + writeNonGenericAnimationChunks(stream); + } + + @Override + public void readMdl(final MdlTokenInputStream stream) throws IOException { + for (final String token : super.readMdlGeneric(stream)) { + switch (token) { + case MdlUtils.TOKEN_STATIC_HEIGHT_ABOVE: + this.heightAbove = stream.readFloat(); + break; + case MdlUtils.TOKEN_HEIGHT_ABOVE: + readTimeline(stream, AnimationMap.KRHA); + break; + case MdlUtils.TOKEN_STATIC_HEIGHT_BELOW: + this.heightBelow = stream.readFloat(); + break; + case MdlUtils.TOKEN_HEIGHT_BELOW: + readTimeline(stream, AnimationMap.KRHB); + break; + case MdlUtils.TOKEN_STATIC_ALPHA: + this.alpha = stream.readFloat(); + break; + case MdlUtils.TOKEN_ALPHA: + readTimeline(stream, AnimationMap.KRAL); + break; + case MdlUtils.TOKEN_STATIC_COLOR: + stream.readColor(this.color); + break; + case MdlUtils.TOKEN_COLOR: + readTimeline(stream, AnimationMap.KRCO); + break; + case MdlUtils.TOKEN_STATIC_TEXTURE_SLOT: + this.textureSlot = stream.readUInt32(); + break; + case MdlUtils.TOKEN_TEXTURE_SLOT: + readTimeline(stream, AnimationMap.KRTX); + break; + case MdlUtils.TOKEN_VISIBILITY: + readTimeline(stream, AnimationMap.KRVS); + break; + case MdlUtils.TOKEN_EMISSION_RATE: + this.emissionRate = stream.readUInt32(); + break; + case MdlUtils.TOKEN_LIFE_SPAN: + this.lifeSpan = stream.readFloat(); + break; + case MdlUtils.TOKEN_GRAVITY: + this.gravity = stream.readFloat(); + break; + case MdlUtils.TOKEN_ROWS: + this.rows = stream.readUInt32(); + break; + case MdlUtils.TOKEN_COLUMNS: + this.columns = stream.readUInt32(); + break; + case MdlUtils.TOKEN_MATERIAL_ID: + this.materialId = stream.readInt(); + break; + default: + throw new IllegalStateException("Unknown token in RibbonEmitter " + this.name + ": " + token); + } + } + } + + @Override + public void writeMdl(final MdlTokenOutputStream stream) throws IOException { + stream.startObjectBlock(MdlUtils.TOKEN_RIBBON_EMITTER, this.name); + writeGenericHeader(stream); + + if (!writeTimeline(stream, AnimationMap.KRHA)) { + stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_HEIGHT_ABOVE, this.heightAbove); + } + + if (!writeTimeline(stream, AnimationMap.KRHB)) { + stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_HEIGHT_BELOW, this.heightBelow); + } + + if (!writeTimeline(stream, AnimationMap.KRAL)) { + stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_ALPHA, this.alpha); + } + + if (!writeTimeline(stream, AnimationMap.KRCO)) { + stream.writeColor(MdlUtils.TOKEN_STATIC_COLOR, this.color); + } + + if (!writeTimeline(stream, AnimationMap.KRTX)) { + stream.writeAttribUInt32(MdlUtils.TOKEN_STATIC_TEXTURE_SLOT, this.textureSlot); + } + + writeTimeline(stream, AnimationMap.KRVS); + + stream.writeAttribUInt32(MdlUtils.TOKEN_EMISSION_RATE, this.emissionRate); + stream.writeFloatAttrib(MdlUtils.TOKEN_LIFE_SPAN, this.lifeSpan); + + if (this.gravity != 0) { + stream.writeFloatAttrib(MdlUtils.TOKEN_GRAVITY, this.gravity); + } + + stream.writeAttribUInt32(MdlUtils.TOKEN_ROWS, this.rows); + stream.writeAttribUInt32(MdlUtils.TOKEN_COLUMNS, this.columns); + stream.writeAttrib(MdlUtils.TOKEN_MATERIAL_ID, this.materialId); + + writeGenericTimelines(stream); + stream.endBlock(); + } + + @Override + public long getByteLength() { + return 56 + super.getByteLength(); + } +} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/Sequence.java b/core/src/com/etheller/warsmash/parsers/mdlx/Sequence.java new file mode 100644 index 0000000..18f2b1a --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/mdlx/Sequence.java @@ -0,0 +1,104 @@ +package com.etheller.warsmash.parsers.mdlx; + +import java.io.IOException; + +import com.etheller.warsmash.util.MdlUtils; +import com.etheller.warsmash.util.ParseUtils; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +public class Sequence implements MdlxBlock { + private String name = ""; + private final long[] interval = new long[2]; + private float moveSpeed = 0; + private int flags = 0; + private float rarity = 0; + private long syncPoint = 0; + private final Extent extent = new Extent(); + + /** + * Restricts us to only be able to parse models on one thread at a time, in + * return for high performance. + */ + private static final byte[] NAME_BYTES_HEAP = new byte[80]; + + @Override + public void readMdx(final LittleEndianDataInputStream stream) throws IOException { + this.name = ParseUtils.readString(stream, NAME_BYTES_HEAP); + ParseUtils.readUInt32Array(stream, this.interval); + this.moveSpeed = stream.readFloat(); + this.flags = (int) ParseUtils.readUInt32(stream); + this.rarity = stream.readFloat(); + this.syncPoint = ParseUtils.readUInt32(stream); + this.extent.readMdx(stream); + } + + @Override + public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException { + final byte[] bytes = this.name.getBytes(ParseUtils.UTF8); + stream.write(bytes); + for (int i = 0; i < (NAME_BYTES_HEAP.length - bytes.length); i++) { + stream.write((byte) 0); + } + ParseUtils.writeUInt32Array(stream, this.interval); + stream.writeFloat(this.moveSpeed); + ParseUtils.writeUInt32(stream, this.flags); + stream.writeFloat(this.rarity); + ParseUtils.writeUInt32(stream, this.syncPoint); + this.extent.writeMdx(stream); + } + + @Override + public void readMdl(final MdlTokenInputStream stream) { + this.name = stream.read(); + + for (final String token : stream.readBlock()) { + switch (token) { + case MdlUtils.TOKEN_INTERVAL: + stream.readIntArray(this.interval); + break; + case MdlUtils.TOKEN_NONLOOPING: + this.flags = 1; + break; + case MdlUtils.TOKEN_MOVESPEED: + this.moveSpeed = stream.readFloat(); + break; + case MdlUtils.TOKEN_RARITY: + this.rarity = stream.readFloat(); + break; + case MdlUtils.TOKEN_MINIMUM_EXTENT: + stream.readFloatArray(this.extent.min); + break; + case MdlUtils.TOKEN_MAXIMUM_EXTENT: + stream.readFloatArray(this.extent.max); + break; + case MdlUtils.TOKEN_BOUNDSRADIUS: + this.extent.boundsRadius = stream.readFloat(); + break; + default: + throw new IllegalStateException("Unknown token in Sequence \"" + this.name + "\": " + token); + } + } + } + + @Override + public void writeMdl(final MdlTokenOutputStream stream) { + stream.startObjectBlock(MdlUtils.TOKEN_ANIM, this.name); + stream.writeArrayAttrib(MdlUtils.TOKEN_INTERVAL, this.interval); + + if (this.flags == 1) { + stream.writeFlag(MdlUtils.TOKEN_NONLOOPING); + } + + if (this.moveSpeed != 0) { + stream.writeFloatAttrib(MdlUtils.TOKEN_MOVESPEED, this.moveSpeed); + } + + if (this.rarity != 0) { + stream.writeFloatAttrib(MdlUtils.TOKEN_RARITY, this.rarity); + } + + this.extent.writeMdl(stream); + stream.endBlock(); + } +} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/Texture.java b/core/src/com/etheller/warsmash/parsers/mdlx/Texture.java new file mode 100644 index 0000000..68ea94b --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/mdlx/Texture.java @@ -0,0 +1,81 @@ +package com.etheller.warsmash.parsers.mdlx; + +import java.io.IOException; + +import com.etheller.warsmash.util.MdlUtils; +import com.etheller.warsmash.util.ParseUtils; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +public class Texture implements MdlxBlock { + private int replaceableId = 0; + private String path = ""; + private int flags = 0; + + /** + * Restricts us to only be able to parse models on one thread at a time, in + * return for high performance. + */ + private static final byte[] PATH_BYTES_HEAP = new byte[260]; + + @Override + public void readMdx(final LittleEndianDataInputStream stream) throws IOException { + this.replaceableId = (int) ParseUtils.readUInt32(stream); + this.path = ParseUtils.readString(stream, PATH_BYTES_HEAP); + this.flags = (int) ParseUtils.readUInt32(stream); + } + + @Override + public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException { + ParseUtils.writeUInt32(stream, this.replaceableId); + final byte[] bytes = this.path.getBytes(ParseUtils.UTF8); + stream.write(bytes); + for (int i = 0; i < (PATH_BYTES_HEAP.length - bytes.length); i++) { + stream.write((byte) 0); + } + ParseUtils.writeUInt32(stream, this.flags); + } + + @Override + public void readMdl(final MdlTokenInputStream stream) throws IOException { + for (final String token : stream.readBlock()) { + switch (token) { + case MdlUtils.TOKEN_IMAGE: + this.path = stream.read(); + break; + case MdlUtils.TOKEN_REPLACEABLE_ID: + this.replaceableId = stream.readInt(); + break; + case MdlUtils.TOKEN_WRAP_WIDTH: + this.flags |= 0x1; + break; + case MdlUtils.TOKEN_WRAP_HEIGHT: + this.flags |= 0x2; + break; + default: + throw new IllegalStateException("Unknown token in Texture: " + token); + } + } + } + + @Override + public void writeMdl(final MdlTokenOutputStream stream) throws IOException { + stream.startBlock(MdlUtils.TOKEN_BITMAP); + stream.writeStringAttrib(MdlUtils.TOKEN_IMAGE, this.path); + + if (this.replaceableId != 0) { + stream.writeAttrib(MdlUtils.TOKEN_REPLACEABLE_ID, this.replaceableId); + } + + if ((this.flags & 0x1) != 0) { + stream.writeFlag(MdlUtils.TOKEN_WRAP_WIDTH); + } + + if ((this.flags & 0x2) != 0) { + stream.writeFlag(MdlUtils.TOKEN_WRAP_HEIGHT); + } + + stream.endBlock(); + } + +} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/TextureAnimation.java b/core/src/com/etheller/warsmash/parsers/mdlx/TextureAnimation.java new file mode 100644 index 0000000..a5b55c1 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/mdlx/TextureAnimation.java @@ -0,0 +1,57 @@ +package com.etheller.warsmash.parsers.mdlx; + +import java.io.IOException; + +import com.etheller.warsmash.util.MdlUtils; +import com.etheller.warsmash.util.ParseUtils; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +public class TextureAnimation extends AnimatedObject { + + @Override + public void readMdx(final LittleEndianDataInputStream stream) throws IOException { + final long size = ParseUtils.readUInt32(stream); + this.readTimelines(stream, size - 4); + } + + @Override + public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException { + ParseUtils.writeUInt32(stream, this.getByteLength()); + this.writeTimelines(stream); + } + + @Override + public void readMdl(final MdlTokenInputStream stream) throws IOException { + for (final String token : stream.readBlock()) { + switch (token) { + case MdlUtils.TOKEN_TRANSLATION: + this.readTimeline(stream, AnimationMap.KTAT); + break; + case MdlUtils.TOKEN_ROTATION: + this.readTimeline(stream, AnimationMap.KTAR); + break; + case MdlUtils.TOKEN_SCALING: + this.readTimeline(stream, AnimationMap.KTAS); + break; + default: + throw new IllegalStateException("Unknown token in TextureAnimation: " + token); + } + } + } + + @Override + public void writeMdl(final MdlTokenOutputStream stream) throws IOException { + stream.startBlock(MdlUtils.TOKEN_TVERTEX_ANIM_SPACE); + this.writeTimeline(stream, AnimationMap.KTAT); + this.writeTimeline(stream, AnimationMap.KTAR); + this.writeTimeline(stream, AnimationMap.KTAS); + stream.endBlock(); + } + + @Override + public long getByteLength() { + return 4 + super.getByteLength(); + } + +} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/UnknownChunk.java b/core/src/com/etheller/warsmash/parsers/mdlx/UnknownChunk.java new file mode 100644 index 0000000..a46fdcb --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/mdlx/UnknownChunk.java @@ -0,0 +1,35 @@ +package com.etheller.warsmash.parsers.mdlx; + +import java.io.IOException; + +import com.etheller.warsmash.util.ParseUtils; +import com.etheller.warsmash.util.War3ID; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +public class UnknownChunk implements Chunk { + private final short[] chunk; + private final War3ID tag; + + public UnknownChunk(final LittleEndianDataInputStream stream, final long size, final War3ID tag) + throws IOException { + System.err.println("Loading unknown chunk: " + tag); + this.chunk = ParseUtils.readUInt8Array(stream, (int) size); + this.tag = tag; + } + + public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException { + ParseUtils.writeWar3ID(stream, this.tag); + // Below: Byte.BYTES used because it's mean as a UInt8 array. This is + // not using Short.BYTES, deliberately, despite using a short[] as the + // type for the array. This is a Java problem that did not exist in the original + // JavaScript implementation by Ghostwolf + ParseUtils.writeUInt32(stream, this.chunk.length * Byte.BYTES); + ParseUtils.writeUInt8Array(stream, this.chunk); + } + + @Override + public long getByteLength() { + return 8 + (this.chunk.length * Byte.BYTES); + } +} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/mdl/GhostwolfTokenInputStream.java b/core/src/com/etheller/warsmash/parsers/mdlx/mdl/GhostwolfTokenInputStream.java new file mode 100644 index 0000000..21076c5 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/mdlx/mdl/GhostwolfTokenInputStream.java @@ -0,0 +1,232 @@ +package com.etheller.warsmash.parsers.mdlx.mdl; + +import java.nio.ByteBuffer; +import java.util.Iterator; + +import com.etheller.warsmash.parsers.mdlx.MdlTokenInputStream; + +public class GhostwolfTokenInputStream implements MdlTokenInputStream { + private final ByteBuffer buffer; + private int index; + private final int ident; + private final int fractionDigits; + + public GhostwolfTokenInputStream(final ByteBuffer buffer) { + this.buffer = buffer; + this.index = 0; + this.ident = 0; // Used for writing blocks nicely. + this.fractionDigits = 6; // The number of fraction digits when writing floats. + } + + @Override + public String read() { + boolean inComment = false; + boolean inString = false; + final StringBuilder token = new StringBuilder(); + final int length = this.buffer.remaining(); + + while (this.index < length) { + // Note: cast from 'byte' to 'char' will cause Java incompatibility with Chinese + // and Russian/Cyrillic and others + final char c = (char) this.buffer.get(this.buffer.position() + this.index++); + + if (inComment) { + if (c == '\n') { + inComment = false; + } + } + else if (inString) { + if (c == '"') { + return token.toString(); + } + else { + token.append(c); + } + } + else if ((c == ' ') || (c == ',') || (c == '\t') || (c == '\n') || (c == ':') || (c == '\r')) { + if (token.length() > 0) { + return token.toString(); + } + } + else if ((c == '{') || (c == '}')) { + if (token.length() > 0) { + this.index--; + return token.toString(); + } + else { + return Character.toString(c); + } + } + else if ((c == '/') && (this.buffer.get(this.buffer.position() + this.index) == '/')) { + if (token.length() > 0) { + this.index--; + return token.toString(); + } + else { + inComment = true; + } + } + else if (c == '"') { + if (token.length() > 0) { + this.index--; + return token.toString(); + } + else { + inString = true; + } + } + else { + token.append(c); + } + } + return null; + } + + @Override + public String peek() { + final int index = this.index; + final String value = this.read(); + + this.index = index; + return value; + } + + @Override + public long readUInt32() { + return Long.parseLong(this.read()); + } + + @Override + public int readInt() { + return Integer.parseInt(this.read()); + } + + @Override + public float readFloat() { + return Float.parseFloat(this.read()); + } + + @Override + public void readIntArray(final long[] values) { + this.read(); // { + + for (int i = 0, l = values.length; i < l; i++) { + values[i] = this.readInt(); + } + + this.read(); // } + } + + @Override + public float[] readFloatArray(final float[] values) { + this.read(); // { + + for (int i = 0, l = values.length; i < l; i++) { + values[i] = this.readFloat(); + } + + this.read(); // } + return values; + } + + /** + * Read an MDL keyframe value. If the value is a scalar, it is just the number. + * If the value is a vector, it is enclosed with curly braces. + * + * @param {Float32Array|Uint32Array} value + */ + @Override + public void readKeyframe(final float[] values) { + if (values.length == 1) { + values[0] = this.readFloat(); + } + else { + this.readFloatArray(values); + } + } + + @Override + public float[] readVectorArray(final float[] array, final int vectorLength) { + this.read(); // { + + for (int i = 0, l = array.length / vectorLength; i < l; i++) { + this.read(); // { + + for (int j = 0; j < vectorLength; j++) { + array[(i * vectorLength) + j] = this.readFloat(); + } + + this.read(); // } + } + + this.read(); // } + return array; + } + + @Override + public Iterable readBlock() { + this.read(); // { + return new Iterable() { + @Override + public Iterator iterator() { + return new Iterator() { + String current; + private boolean hasLoaded = false; + + @Override + public String next() { + if (!this.hasLoaded) { + hasNext(); + } + this.hasLoaded = false; + return this.current; + } + + @Override + public boolean hasNext() { + this.current = read(); + this.hasLoaded = true; + return (this.current != null) && !this.current.equals("}"); + } + }; + } + }; + } + + @Override + public int[] readUInt16Array(final int[] values) { + this.read(); // { + + for (int i = 0, l = values.length; i < l; i++) { + values[i] = this.readInt(); + } + + this.read(); // } + + return values; + } + + @Override + public short[] readUInt8Array(final short[] values) { + this.read(); // { + + for (int i = 0, l = values.length; i < l; i++) { + values[i] = Short.parseShort(this.read()); + } + + this.read(); // } + + return values; + } + + @Override + public void readColor(final float[] color) { + this.read(); // { + + color[2] = this.readFloat(); + color[1] = this.readFloat(); + color[0] = this.readFloat(); + + this.read(); // } + } +} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/mdl/GhostwolfTokenOutputStream.java b/core/src/com/etheller/warsmash/parsers/mdlx/mdl/GhostwolfTokenOutputStream.java new file mode 100644 index 0000000..d154b20 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/mdlx/mdl/GhostwolfTokenOutputStream.java @@ -0,0 +1,261 @@ +package com.etheller.warsmash.parsers.mdlx.mdl; + +import java.io.IOException; + +import com.etheller.warsmash.parsers.mdlx.MdlTokenOutputStream; + +public class GhostwolfTokenOutputStream implements MdlTokenOutputStream { + private final Appendable buffer; + private final int index; + private int ident; + private final int fractionDigits; + + public GhostwolfTokenOutputStream(final Appendable appendable) { + this.buffer = appendable; + this.index = 0; + this.ident = 0; // Used for writing blocks nicely. + this.fractionDigits = 6; // The number of fraction digits when writing floats. + } + + @Override + public void writeKeyframe(final String prefix, final long uInt32Value) { + writeAttribUInt32(prefix, uInt32Value); + } + + @Override + public void writeKeyframe(final String prefix, final float floatValue) { + writeFloatAttrib(prefix, floatValue); + } + + @Override + public void writeKeyframe(final String prefix, final float[] floatArrayValues) { + writeFloatArrayAttrib(prefix, floatArrayValues); + } + + @Override + public void indent() { + this.ident += 1; + } + + @Override + public void unindent() { + this.ident -= 1; + } + + @Override + public void startObjectBlock(final String name, final String objectName) { + this.writeLine(name + " \"" + objectName + "\" {"); + this.ident += 1; + } + + @Override + public void startBlock(final String name, final int blockSize) { + this.writeLine(name + " " + blockSize + " {" + ""); + this.ident += 1; + } + + @Override + public void startBlock(final String name) { + this.writeLine(name + " {" + ""); + this.ident += 1; + } + + @Override + public void writeFlag(final String token) { + this.writeLine(token + ","); + } + + @Override + public void writeFlagUInt32(final long flag) { + this.writeLine(flag + ","); + } + + @Override + public void writeAttrib(final String string, final int globalSequenceId) { + writeLine(string + " " + globalSequenceId + ","); + } + + @Override + public void writeAttribUInt32(final String attribName, final long uInt) { + writeLine(attribName + " " + uInt + ","); + } + + @Override + public void writeAttrib(final String string, final String value) { + writeLine(string + " " + value + ","); + } + + @Override + public void writeFloatAttrib(final String attribName, final float value) { + writeLine(attribName + " " + value + ","); + } + + @Override + public void writeStringAttrib(final String attribName, final String value) { + writeLine(attribName + " \"" + value + "\","); + + } + + @Override + public void writeFloatArrayAttrib(final String attribName, final float[] floatArray) { + this.writeLine(attribName + " { " + formatFloatArray(floatArray) + " },"); + } + + @Override + public void writeLongSubArrayAttrib(final String attribName, final long[] array, final int startIndexInclusive, + final int endIndexExclusive) { + this.writeLine(attribName + " { " + formatLongSubArray(array, startIndexInclusive, endIndexExclusive) + " },"); + } + + @Override + public void writeFloatArray(final float[] floatArray) { + this.writeLine("{ " + formatFloatArray(floatArray) + " },"); + } + + public void writeFloatSubArray(final float[] floatArray, final int startIndexInclusive, + final int endIndexExclusive) { + this.writeLine("{ " + formatFloatSubArray(floatArray, startIndexInclusive, endIndexExclusive) + " },"); + } + + @Override + public void writeVectorArray(final String token, final float[] vectors, final int vectorLength) { + this.startBlock(token, vectors.length / vectorLength); + + for (int i = 0, l = vectors.length; i < l; i += vectorLength) { + this.writeFloatSubArray(vectors, i, i + vectorLength); + } + + this.endBlock(); + } + + @Override + public void endBlock() { + this.ident -= 1; + this.writeLine("}"); + } + + @Override + public void endBlockComma() { + this.ident -= 1; + this.writeLine("},"); + } + + @Override + public void writeLine(final String string) { + try { + for (int i = 0; i < this.ident; i++) { + this.buffer.append('\t'); + } + this.buffer.append(string); + this.buffer.append('\n'); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public void startBlock(final String tokenFaces, final int sizeNumberProbably, final int length) { + this.writeLine(tokenFaces + " " + sizeNumberProbably + " " + length + " {" + ""); + this.ident += 1; + } + + @Override + public void writeColor(final String tokenStaticColor, final float[] color) { + this.writeLine(tokenStaticColor + " { " + color[2] + ", " + color[1] + ", " + color[0] + " },"); + } + + @Override + public void writeArrayAttrib(final String tokenAlpha, final short[] uint8Array) { + this.writeLine(tokenAlpha + " { " + formatShortArray(uint8Array) + " },"); + } + + @Override + public void writeArrayAttrib(final String tokenAlpha, final int[] uint16Array) { + this.writeLine(tokenAlpha + " { " + formatIntArray(uint16Array) + " },"); + } + + @Override + public void writeArrayAttrib(final String tokenAlpha, final long[] uint32Array) { + this.writeLine(tokenAlpha + " { " + formatLongArray(uint32Array) + " },"); + } + + private String formatFloat(final float value) { + final String s = Float.toString(value); + final String f = String.format("%." + this.fractionDigits + "f", value); + if (s.length() > f.length()) { + return f; + } + else { + return s; + } + } + + private String formatFloatArray(final float[] value) { + final StringBuilder stringBuilder = new StringBuilder(); + for (int i = 0, l = value.length; i < l; i++) { + if (stringBuilder.length() > 0) { + stringBuilder.append(", "); + } + stringBuilder.append(formatFloat(value[i])); + } + return stringBuilder.toString(); + } + + private String formatLongArray(final long[] value) { + final StringBuilder stringBuilder = new StringBuilder(); + for (int i = 0, l = value.length; i < l; i++) { + if (stringBuilder.length() > 0) { + stringBuilder.append(", "); + } + stringBuilder.append(value[i]); + } + return stringBuilder.toString(); + } + + private String formatShortArray(final short[] value) { + final StringBuilder stringBuilder = new StringBuilder(); + for (int i = 0, l = value.length; i < l; i++) { + if (stringBuilder.length() > 0) { + stringBuilder.append(", "); + } + stringBuilder.append(value[i]); + } + return stringBuilder.toString(); + } + + private String formatIntArray(final int[] value) { + final StringBuilder stringBuilder = new StringBuilder(); + for (int i = 0, l = value.length; i < l; i++) { + if (stringBuilder.length() > 0) { + stringBuilder.append(", "); + } + stringBuilder.append(value[i]); + } + return stringBuilder.toString(); + } + + private String formatLongSubArray(final long[] value, final int startIndexInclusive, final int endIndexExclusive) { + final StringBuilder stringBuilder = new StringBuilder(); + for (int i = startIndexInclusive; i < endIndexExclusive; i++) { + if (stringBuilder.length() > 0) { + stringBuilder.append(", "); + } + stringBuilder.append(value[i]); + } + return stringBuilder.toString(); + } + + private String formatFloatSubArray(final float[] value, final int startIndexInclusive, + final int endIndexExclusive) { + final StringBuilder stringBuilder = new StringBuilder(); + for (int i = startIndexInclusive; i < endIndexExclusive; i++) { + if (stringBuilder.length() > 0) { + stringBuilder.append(", "); + } + stringBuilder.append(formatFloat(value[i])); + } + return stringBuilder.toString(); + } + +} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatArrayKeyFrame.java b/core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatArrayKeyFrame.java index 3af812e..41120ba 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatArrayKeyFrame.java +++ b/core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatArrayKeyFrame.java @@ -39,7 +39,7 @@ public class FloatArrayKeyFrame implements KeyFrame { @Override public void readMdx(final LittleEndianDataInputStream stream, final InterpolationType interpolationType) throws IOException { - this.time = ParseUtils.parseUInt32(stream); + this.time = ParseUtils.readUInt32(stream); ParseUtils.readFloatArray(stream, this.value); if (interpolationType.tangential()) { ParseUtils.readFloatArray(stream, this.inTan); diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatKeyFrame.java b/core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatKeyFrame.java index b03788b..5bb3254 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatKeyFrame.java +++ b/core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatKeyFrame.java @@ -33,7 +33,7 @@ public class FloatKeyFrame implements KeyFrame { @Override public void readMdx(final LittleEndianDataInputStream stream, final InterpolationType interpolationType) throws IOException { - this.time = ParseUtils.parseUInt32(stream); + this.time = ParseUtils.readUInt32(stream); this.value = stream.readFloat(); if (interpolationType.tangential()) { this.inTan = stream.readFloat(); diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/timeline/Timeline.java b/core/src/com/etheller/warsmash/parsers/mdlx/timeline/Timeline.java index 8f51d85..97d8f77 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/timeline/Timeline.java +++ b/core/src/com/etheller/warsmash/parsers/mdlx/timeline/Timeline.java @@ -32,7 +32,7 @@ public abstract class Timeline implements Chunk { public void readMdx(final LittleEndianDataInputStream stream, final War3ID name) throws IOException { this.name = name; - final long keyFrameCount = ParseUtils.parseUInt32(stream); + final long keyFrameCount = ParseUtils.readUInt32(stream); this.interpolationType = InterpolationType.VALUES[stream.readInt()]; this.globalSequenceId = stream.readInt(); @@ -47,7 +47,7 @@ public abstract class Timeline implements Chunk { } public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException { - stream.writeInt(this.name.getValue()); + stream.writeInt(Integer.reverseBytes(this.name.getValue())); stream.writeInt(this.keyFrames.size()); stream.writeInt(this.interpolationType.ordinal()); stream.writeInt(this.globalSequenceId); @@ -86,7 +86,7 @@ public abstract class Timeline implements Chunk { this.interpolationType = interpolationType; - if (stream.peek().equals("GlobalSeqId")) { + if (stream.peek().equals(MdlUtils.TOKEN_GLOBAL_SEQ_ID)) { stream.read(); this.globalSequenceId = stream.readInt(); } @@ -130,7 +130,7 @@ public abstract class Timeline implements Chunk { stream.writeFlag(token); if (this.globalSequenceId != -1) { - stream.writeAttrib("GlobalSeqId", this.globalSequenceId); + stream.writeAttrib(MdlUtils.TOKEN_GLOBAL_SEQ_ID, this.globalSequenceId); } for (final KeyFrame keyFrame : this.keyFrames) { diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/timeline/UInt32KeyFrame.java b/core/src/com/etheller/warsmash/parsers/mdlx/timeline/UInt32KeyFrame.java index 0f74c21..a858378 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/timeline/UInt32KeyFrame.java +++ b/core/src/com/etheller/warsmash/parsers/mdlx/timeline/UInt32KeyFrame.java @@ -33,11 +33,11 @@ public class UInt32KeyFrame implements KeyFrame { @Override public void readMdx(final LittleEndianDataInputStream stream, final InterpolationType interpolationType) throws IOException { - this.time = ParseUtils.parseUInt32(stream); - this.value = ParseUtils.parseUInt32(stream); + this.time = ParseUtils.readUInt32(stream); + this.value = ParseUtils.readUInt32(stream); if (interpolationType.tangential()) { - this.inTan = ParseUtils.parseUInt32(stream); - this.outTan = ParseUtils.parseUInt32(stream); + this.inTan = ParseUtils.readUInt32(stream); + this.outTan = ParseUtils.readUInt32(stream); } } diff --git a/core/src/com/etheller/warsmash/parsers/terrain/Corner.java b/core/src/com/etheller/warsmash/parsers/terrain/Corner.java new file mode 100644 index 0000000..f3f71f2 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/terrain/Corner.java @@ -0,0 +1,27 @@ +package com.etheller.warsmash.parsers.terrain; + +public class Corner { + boolean mapEdge; + + int groundTexture; + float height; + float waterHeight; + boolean ramp; + boolean blight; + boolean water; + boolean boundary; + boolean cliff; + boolean romp; + int groundVariation; + int cliffVariation; + int cliffTexture; + int layerHeight; + + public float finalGroundHeight() { + return 0; + } + + public float finalWaterHeight() { + return 0; + } +} diff --git a/core/src/com/etheller/warsmash/parsers/terrain/Terrain.java b/core/src/com/etheller/warsmash/parsers/terrain/Terrain.java new file mode 100644 index 0000000..950474b --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/terrain/Terrain.java @@ -0,0 +1,13 @@ +package com.etheller.warsmash.parsers.terrain; + +import com.badlogic.gdx.utils.FloatArray; + +public class Terrain { + private FloatArray groundHeights; + private FloatArray groundCornerHeights; + + private FloatArray waterHeights; + + public Terrain() { + } +} diff --git a/core/src/com/etheller/warsmash/parsers/terrain/TilePathing.java b/core/src/com/etheller/warsmash/parsers/terrain/TilePathing.java new file mode 100644 index 0000000..1c2d661 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/terrain/TilePathing.java @@ -0,0 +1,15 @@ +package com.etheller.warsmash.parsers.terrain; + +public class TilePathing { + boolean unwalkable = false; + boolean unflyable = false; + boolean unbuildable = false; + + public byte mask() { + byte mask = 0; + mask |= this.unwalkable ? 0b00000010 : 0; + mask |= this.unflyable ? 0b00000100 : 0; + mask |= this.unbuildable ? 0b00001000 : 0; + return mask; + } +} \ No newline at end of file diff --git a/core/src/com/etheller/warsmash/util/Descriptor.java b/core/src/com/etheller/warsmash/util/Descriptor.java new file mode 100644 index 0000000..ff608fe --- /dev/null +++ b/core/src/com/etheller/warsmash/util/Descriptor.java @@ -0,0 +1,6 @@ +package com.etheller.warsmash.util; + +public interface Descriptor { + E create(); + +} diff --git a/core/src/com/etheller/warsmash/util/ImageUtils.java b/core/src/com/etheller/warsmash/util/ImageUtils.java new file mode 100644 index 0000000..71dbb0b --- /dev/null +++ b/core/src/com/etheller/warsmash/util/ImageUtils.java @@ -0,0 +1,85 @@ +package com.etheller.warsmash.util; + +import java.awt.Graphics2D; +import java.awt.Transparency; +import java.awt.color.ColorSpace; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.ComponentColorModel; +import java.awt.image.DataBuffer; + +import com.badlogic.gdx.graphics.Pixmap; +import com.badlogic.gdx.graphics.Pixmap.Format; +import com.badlogic.gdx.graphics.Texture; + +/** + * Uses AWT stuff + * + */ +public final class ImageUtils { + private static final int BYTES_PER_PIXEL = 4; + + public static Texture getTexture(final BufferedImage image) { + final int[] pixels = new int[image.getWidth() * image.getHeight()]; + image.getRGB(0, 0, image.getWidth(), image.getHeight(), pixels, 0, image.getWidth()); + + // 4 + // for + // RGBA, + // 3 + // for + // RGB + + final Pixmap pixmap = new Pixmap(image.getWidth(), image.getHeight(), Format.RGBA8888); + for (int y = 0; y < image.getHeight(); y++) { + for (int x = 0; x < image.getWidth(); x++) { + final int pixel = pixels[(y * image.getWidth()) + x]; + pixmap.drawPixel(x, y, (pixel << 8) | (pixel >>> 24)); + } + } + return new Texture(pixmap); + } + + /** + * Convert an input buffered image into sRGB color space using component values + * directly instead of performing a color space conversion. + * + * @param in Input image to be converted. + * @return Resulting sRGB image. + */ + public static BufferedImage forceBufferedImagesRGB(final BufferedImage in) { + // Resolve input ColorSpace. + final ColorSpace inCS = in.getColorModel().getColorSpace(); + final ColorSpace sRGBCS = ColorSpace.getInstance(ColorSpace.CS_sRGB); + if (inCS == sRGBCS) { + // Already is sRGB. + return in; + } + if (inCS.getNumComponents() != sRGBCS.getNumComponents()) { + throw new IllegalArgumentException("Input color space has different number of components from sRGB."); + } + + // Draw input. + final ColorModel lRGBModel = new ComponentColorModel(inCS, true, false, Transparency.TRANSLUCENT, + DataBuffer.TYPE_BYTE); + final ColorModel sRGBModel = new ComponentColorModel(sRGBCS, true, false, Transparency.TRANSLUCENT, + DataBuffer.TYPE_BYTE); + final BufferedImage lRGB = new BufferedImage(lRGBModel, + lRGBModel.createCompatibleWritableRaster(in.getWidth(), in.getHeight()), false, null); + final Graphics2D graphic = lRGB.createGraphics(); + try { + graphic.drawImage(in, 0, 0, null); + } + finally { + graphic.dispose(); + } + + // Convert to sRGB. + final BufferedImage sRGB = new BufferedImage(sRGBModel, lRGB.getRaster(), false, null); + + return sRGB; + } + + private ImageUtils() { + } +} diff --git a/core/src/com/etheller/warsmash/util/MdlUtils.java b/core/src/com/etheller/warsmash/util/MdlUtils.java index 08eb95b..7712229 100644 --- a/core/src/com/etheller/warsmash/util/MdlUtils.java +++ b/core/src/com/etheller/warsmash/util/MdlUtils.java @@ -1,8 +1,198 @@ package com.etheller.warsmash.util; +/** + * Constants for the tokens were used to prevent typos in token literals. It + * would be very easy for me to type "Interval" in one place and "Intreval" in + * another by mistake. With this paradigm, that mistake causes a compile error, + * since TOKEN_INTREVAL does not exist. + */ public class MdlUtils { + public static final String TOKEN_VERSION = "Version"; + + public static final String TOKEN_MODEL = "Model"; + + public static final String TOKEN_SEQUENCES = "Sequences"; + + public static final String TOKEN_GLOBAL_SEQUENCES = "GlobalSequences"; + + public static final String TOKEN_INTERVAL = "Interval"; + public static final String TOKEN_NONLOOPING = "NonLooping"; + public static final String TOKEN_MOVESPEED = "MoveSpeed"; + public static final String TOKEN_RARITY = "Rarity"; + + public static final String TOKEN_FORMAT_VERSION = "FormatVersion"; + public static final String TOKEN_BLEND_TIME = "BlendTime"; + public static final String TOKEN_DURATION = "Duration"; + + public static final String TOKEN_IMAGE = "Image"; + public static final String TOKEN_WRAP_WIDTH = "WrapWidth"; + public static final String TOKEN_WRAP_HEIGHT = "WrapHeight"; + public static final String TOKEN_BITMAP = "Bitmap"; + + public static final String TOKEN_TVERTEX_ANIM_SPACE = "TVertexAnim "; + public static final String TOKEN_DONT_INTERP = "DontInterp"; public static final String TOKEN_LINEAR = "Linear"; public static final String TOKEN_HERMITE = "Hermite"; public static final String TOKEN_BEZIER = "Bezier"; + public static final String TOKEN_GLOBAL_SEQ_ID = "GlobalSeqId"; + + public static final String TOKEN_PLANE = "Plane"; + public static final String TOKEN_BOX = "Box"; + public static final String TOKEN_SPHERE = "Sphere"; + public static final String TOKEN_CYLINDER = "Cylinder"; + + public static final String TOKEN_GEOSETID = "GeosetId"; + public static final String TOKEN_MULTIPLE = "Multiple"; + public static final String TOKEN_GEOSETANIMID = "GeosetAnimId"; + public static final String TOKEN_NONE = "None"; + public static final String TOKEN_OBJECTID = "ObjectId"; + public static final String TOKEN_PARENT = "Parent"; + public static final String TOKEN_BILLBOARDED_LOCK_Z = "BillboardedLockZ"; + public static final String TOKEN_BILLBOARDED_LOCK_Y = "BillboardedLockY"; + public static final String TOKEN_BILLBOARDED_LOCK_X = "BillboardedLockX"; + public static final String TOKEN_BILLBOARDED = "Billboarded"; + public static final String TOKEN_CAMERA_ANCHORED = "CameraAnchored"; + public static final String TOKEN_DONT_INHERIT = "DontInherit"; + public static final String TOKEN_ROTATION = "Rotation"; + public static final String TOKEN_TRANSLATION = "Translation"; + public static final String TOKEN_SCALING = "Scaling"; + public static final String TOKEN_STATIC = "static"; + public static final String TOKEN_ATTACHMENT_ID = "AttachmentID"; + public static final String TOKEN_PATH = "Path"; + public static final String TOKEN_VISIBILITY = "Visibility"; + public static final String TOKEN_POSITION = "Position"; + public static final String TOKEN_FIELDOFVIEW = "FieldOfView"; + public static final String TOKEN_FARCLIP = "FarClip"; + public static final String TOKEN_NEARCLIP = "NearClip"; + public static final String TOKEN_TARGET = "Target"; + public static final String TOKEN_VERTICES = "Vertices"; + public static final String TOKEN_BOUNDSRADIUS = "BoundsRadius"; + public static final String TOKEN_EVENT_TRACK = "EventTrack"; + public static final String TOKEN_MAXIMUM_EXTENT = "MaximumExtent"; + public static final String TOKEN_MINIMUM_EXTENT = "MinimumExtent"; + public static final String TOKEN_NORMALS = "Normals"; + public static final String TOKEN_TVERTICES = "TVertices"; + public static final String TOKEN_VERTEX_GROUP = "VertexGroup"; + public static final String TOKEN_FACES = "Faces"; + public static final String TOKEN_GROUPS = "Groups"; + public static final String TOKEN_ANIM = "Anim"; + public static final String TOKEN_MATERIAL_ID = "MaterialID"; + public static final String TOKEN_SELECTION_GROUP = "SelectionGroup"; + public static final String TOKEN_UNSELECTABLE = "Unselectable"; + public static final String TOKEN_TRIANGLES = "Triangles"; + public static final String TOKEN_MATRICES = "Matrices"; + public static final String TOKEN_DROP_SHADOW = "DropShadow"; + public static final String TOKEN_ALPHA = "Alpha"; + public static final String TOKEN_COLOR = "Color"; + public static final String TOKEN_STATIC_ALPHA = TOKEN_STATIC + " " + TOKEN_ALPHA; + public static final String TOKEN_STATIC_COLOR = TOKEN_STATIC + " " + TOKEN_COLOR; + public static final String TOKEN_FILTER_MODE = "FilterMode"; + public static final String TOKEN_UNSHADED = "Unshaded"; + public static final String TOKEN_SPHERE_ENV_MAP = "SphereEnvMap"; + public static final String TOKEN_TWO_SIDED = "TwoSided"; + public static final String TOKEN_UNFOGGED = "Unfogged"; + public static final String TOKEN_NO_DEPTH_TEST = "NoDepthTest"; + public static final String TOKEN_NO_DEPTH_SET = "NoDepthSet"; + public static final String TOKEN_TEXTURE_ID = "TextureID"; + public static final String TOKEN_STATIC_TEXTURE_ID = TOKEN_STATIC + " " + TOKEN_TEXTURE_ID; + public static final String TOKEN_TVERTEX_ANIM_ID = "TVertexAnimId"; + public static final String TOKEN_COORD_ID = "CoordId"; + + public static final String TOKEN_OMNIDIRECTIONAL = "Omnidirectional"; + public static final String TOKEN_DIRECTIONAL = "Directional"; + public static final String TOKEN_AMBIENT = "Ambient"; + public static final String TOKEN_ATTENUATION_START = "AttenuationStart"; + public static final String TOKEN_STATIC_ATTENUATION_START = TOKEN_STATIC + " " + TOKEN_ATTENUATION_START; + public static final String TOKEN_ATTENUATION_END = "AttenuationEnd"; + public static final String TOKEN_STATIC_ATTENUATION_END = TOKEN_STATIC + " " + TOKEN_ATTENUATION_END; + public static final String TOKEN_INTENSITY = "Intensity"; + public static final String TOKEN_STATIC_INTENSITY = TOKEN_STATIC + " " + TOKEN_INTENSITY; + public static final String TOKEN_AMB_INTENSITY = "AmbIntensity"; + public static final String TOKEN_STATIC_AMB_INTENSITY = TOKEN_STATIC + " " + TOKEN_AMB_INTENSITY; + public static final String TOKEN_AMB_COLOR = "AmbColor"; + public static final String TOKEN_STATIC_AMB_COLOR = TOKEN_STATIC + " " + TOKEN_AMB_COLOR; + + public static final String TOKEN_CONSTANT_COLOR = "ConstantColor"; + public static final String TOKEN_SORT_PRIMS_NEAR_Z = "SortPrimsNearZ"; + public static final String TOKEN_SORT_PRIMS_FAR_Z = "SortPrimsFarZ"; + public static final String TOKEN_FULL_RESOLUTION = "FullResolution"; + public static final String TOKEN_PRIORITY_PLANE = "PriorityPlane"; + + public static final String TOKEN_EMITTER_USES_MDL = "EmitterUsesMDL"; + public static final String TOKEN_EMITTER_USES_TGA = "EmitterUsesTGA"; + public static final String TOKEN_EMISSION_RATE = "EmissionRate"; + public static final String TOKEN_STATIC_EMISSION_RATE = TOKEN_STATIC + " " + TOKEN_EMISSION_RATE; + public static final String TOKEN_GRAVITY = "Gravity"; + public static final String TOKEN_STATIC_GRAVITY = TOKEN_STATIC + " " + TOKEN_GRAVITY; + public static final String TOKEN_LONGITUDE = "Longitude"; + public static final String TOKEN_STATIC_LONGITUDE = TOKEN_STATIC + " " + TOKEN_LONGITUDE; + public static final String TOKEN_LATITUDE = "Latitude"; + public static final String TOKEN_STATIC_LATITUDE = TOKEN_STATIC + " " + TOKEN_LATITUDE; + public static final String TOKEN_PARTICLE = "Particle"; + public static final String TOKEN_LIFE_SPAN = "LifeSpan"; + public static final String TOKEN_STATIC_LIFE_SPAN = TOKEN_STATIC + " " + TOKEN_LIFE_SPAN; + public static final String TOKEN_INIT_VELOCITY = "InitVelocity"; + public static final String TOKEN_STATIC_INIT_VELOCITY = TOKEN_STATIC + " " + TOKEN_INIT_VELOCITY; + + public static final String TOKEN_LINE_EMITTER = "LineEmitter"; + public static final String TOKEN_MODEL_SPACE = "ModelSpace"; + public static final String TOKEN_XY_QUAD = "XYQuad"; + public static final String TOKEN_SPEED = "Speed"; + public static final String TOKEN_STATIC_SPEED = TOKEN_STATIC + " " + TOKEN_SPEED; + public static final String TOKEN_VARIATION = "Variation"; + public static final String TOKEN_STATIC_VARIATION = TOKEN_STATIC + " " + TOKEN_VARIATION; + public static final String TOKEN_SQUIRT = "Squirt"; + public static final String TOKEN_WIDTH = "Width"; + public static final String TOKEN_STATIC_WIDTH = TOKEN_STATIC + " " + TOKEN_WIDTH; + public static final String TOKEN_LENGTH = "Length"; + public static final String TOKEN_STATIC_LENGTH = TOKEN_STATIC + " " + TOKEN_LENGTH; + public static final String TOKEN_ROWS = "Rows"; + public static final String TOKEN_COLUMNS = "Columns"; + public static final String TOKEN_HEAD = "Head"; + public static final String TOKEN_TAIL = "Tail"; + public static final String TOKEN_BOTH = "Both"; + public static final String TOKEN_TAIL_LENGTH = "TailLength"; + public static final String TOKEN_TIME = "Time"; + public static final String TOKEN_SEGMENT_COLOR = "SegmentColor"; + public static final String TOKEN_PARTICLE_SCALING = "ParticleScaling"; + public static final String TOKEN_LIFE_SPAN_UV_ANIM = "LifeSpanUVAnim"; + public static final String TOKEN_DECAY_UV_ANIM = "DecayUVAnim"; + public static final String TOKEN_TAIL_UV_ANIM = "TailUVAnim"; + public static final String TOKEN_TAIL_DECAY_UV_ANIM = "TailDecayUVAnim"; + public static final String TOKEN_REPLACEABLE_ID = "ReplaceableId"; + public static final String TOKEN_BLEND = "Blend";// ParticleEmitter2.FilterMode.BLEND.getMdlText(); + public static final String TOKEN_ADDITIVE = "Additive";// ParticleEmitter2.FilterMode.ADDITIVE.getMdlText(); + public static final String TOKEN_MODULATE = "Modulate";// ParticleEmitter2.FilterMode.MODULATE.getMdlText(); + public static final String TOKEN_MODULATE2X = "Modulate2x";// ParticleEmitter2.FilterMode.MODULATE2X.getMdlText(); + public static final String TOKEN_ALPHAKEY = "AlphaKey";// ParticleEmitter2.FilterMode.ALPHAKEY.getMdlText(); + + public static final String TOKEN_HEIGHT_ABOVE = "HeightAbove"; + public static final String TOKEN_STATIC_HEIGHT_ABOVE = TOKEN_STATIC + " " + TOKEN_HEIGHT_ABOVE; + public static final String TOKEN_HEIGHT_BELOW = "HeightBelow"; + public static final String TOKEN_STATIC_HEIGHT_BELOW = TOKEN_STATIC + " " + TOKEN_HEIGHT_BELOW; + public static final String TOKEN_TEXTURE_SLOT = "TextureSlot"; + public static final String TOKEN_STATIC_TEXTURE_SLOT = TOKEN_STATIC + " " + TOKEN_TEXTURE_SLOT; + + public static final String TOKEN_TEXTURES = "Textures"; + public static final String TOKEN_MATERIALS = "Materials"; + public static final String TOKEN_TEXTURE_ANIMS = "TextureAnims"; + public static final String TOKEN_TEXTURE_ANIM = "TextureAnim"; + public static final String TOKEN_PIVOT_POINTS = "PivotPoints"; + + public static final String TOKEN_ATTACHMENT = "Attachment"; + public static final String TOKEN_BONE = "Bone"; + public static final String TOKEN_CAMERA = "Camera"; + public static final String TOKEN_COLLISION_SHAPE = "CollisionShape"; + public static final String TOKEN_EVENT_OBJECT = "EventObject"; + public static final String TOKEN_GEOSET = "Geoset"; + public static final String TOKEN_GEOSETANIM = "GeosetAnim"; + public static final String TOKEN_HELPER = "Helper"; + public static final String TOKEN_LAYER = "Layer"; + public static final String TOKEN_LIGHT = "Light"; + public static final String TOKEN_MATERIAL = "Material"; + public static final String TOKEN_PARTICLE_EMITTER = "ParticleEmitter"; + public static final String TOKEN_PARTICLE_EMITTER2 = "ParticleEmitter2"; + public static final String TOKEN_RIBBON_EMITTER = "RibbonEmitter"; + } diff --git a/core/src/com/etheller/warsmash/util/ParseUtils.java b/core/src/com/etheller/warsmash/util/ParseUtils.java index 933b6cb..951df08 100644 --- a/core/src/com/etheller/warsmash/util/ParseUtils.java +++ b/core/src/com/etheller/warsmash/util/ParseUtils.java @@ -1,13 +1,24 @@ package com.etheller.warsmash.util; import java.io.IOException; +import java.nio.charset.Charset; import com.google.common.io.LittleEndianDataInputStream; import com.google.common.io.LittleEndianDataOutputStream; public class ParseUtils { - public static long parseUInt32(final LittleEndianDataInputStream stream) throws IOException { - return stream.readInt() & 0xFFFFFFFF; + public static final Charset UTF8 = Charset.forName("utf-8"); + + public static long readUInt32(final LittleEndianDataInputStream stream) throws IOException { + return stream.readInt() & 0xFFFFFFFFL; + } + + public static int readUInt16(final LittleEndianDataInputStream stream) throws IOException { + return stream.readShort() & 0xFFFF; + } + + public static short readUInt8(final LittleEndianDataInputStream stream) throws IOException { + return (short) (stream.readByte() & (short) 0xFF); } public static void readFloatArray(final LittleEndianDataInputStream stream, final float[] array) @@ -17,10 +28,114 @@ public class ParseUtils { } } + public static float[] readFloatArray(final LittleEndianDataInputStream stream, final int length) + throws IOException { + final float[] array = new float[length]; + readFloatArray(stream, array); + return array; + } + + public static void readUInt32Array(final LittleEndianDataInputStream stream, final long[] array) + throws IOException { + for (int i = 0; i < array.length; i++) { + array[i] = readUInt32(stream); + } + } + + public static long[] readUInt32Array(final LittleEndianDataInputStream stream, final int length) + throws IOException { + final long[] array = new long[length]; + readUInt32Array(stream, array); + return array; + } + + public static void readUInt16Array(final LittleEndianDataInputStream stream, final int[] array) throws IOException { + for (int i = 0; i < array.length; i++) { + array[i] = readUInt16(stream); + } + } + + public static int[] readUInt16Array(final LittleEndianDataInputStream stream, final int length) throws IOException { + final int[] array = new int[length]; + readUInt16Array(stream, array); + return array; + } + + public static void readUInt8Array(final LittleEndianDataInputStream stream, final short[] array) + throws IOException { + for (int i = 0; i < array.length; i++) { + array[i] = readUInt8(stream); + } + } + + public static short[] readUInt8Array(final LittleEndianDataInputStream stream, final int length) + throws IOException { + final short[] array = new short[length]; + readUInt8Array(stream, array); + return array; + } + + public static War3ID readWar3ID(final LittleEndianDataInputStream stream) throws IOException { +// final int value = stream.readInt(); +// return new War3ID(((value & 0xFF000000) >>> 24) | ((value & 0x00FF0000) >>> 8) | ((value & 0x0000FF00) << 8) +// | ((value & 0x000000FF) << 24)); + return new War3ID(Integer.reverseBytes(stream.readInt())); + } + + public static void writeWar3ID(final LittleEndianDataOutputStream stream, final War3ID id) throws IOException { +// final int value = id.getValue(); +// stream.writeInt(((value & 0xFF000000) >>> 24) | ((value & 0x00FF0000) >>> 8) | ((value & 0x0000FF00) << 8) +// | ((value & 0x000000FF) << 24)); + stream.writeInt(Integer.reverseBytes(id.getValue())); + } + public static void writeFloatArray(final LittleEndianDataOutputStream stream, final float[] array) throws IOException { for (int i = 0; i < array.length; i++) { stream.writeFloat(array[i]); } } + + public static void writeUInt32(final LittleEndianDataOutputStream stream, final long uInt) throws IOException { + stream.writeInt((int) (uInt & 0xFFFFFFFF)); + } + + public static void writeUInt16(final LittleEndianDataOutputStream stream, final int uInt) throws IOException { + stream.writeShort((short) (uInt & 0xFFFF)); + } + + public static void writeUInt8(final LittleEndianDataOutputStream stream, final short uInt) throws IOException { + stream.writeByte((byte) (uInt & 0xFF)); + } + + public static void writeUInt32Array(final LittleEndianDataOutputStream stream, final long[] array) + throws IOException { + for (int i = 0; i < array.length; i++) { + writeUInt32(stream, array[i]); + } + } + + public static void writeUInt16Array(final LittleEndianDataOutputStream stream, final int[] array) + throws IOException { + for (int i = 0; i < array.length; i++) { + writeUInt16(stream, array[i]); + } + } + + public static void writeUInt8Array(final LittleEndianDataOutputStream stream, final short[] array) + throws IOException { + for (int i = 0; i < array.length; i++) { + writeUInt8(stream, array[i]); + } + } + + public static String readString(final LittleEndianDataInputStream stream, final byte[] recycleByteArray) + throws IOException { + stream.read(recycleByteArray); + int i; + for (i = 0; (i < recycleByteArray.length) && (recycleByteArray[i] != 0); i++) { + } + final String name = new String(recycleByteArray, 0, i, ParseUtils.UTF8); + return name; + } } diff --git a/core/src/com/etheller/warsmash/util/RenderMathUtils.java b/core/src/com/etheller/warsmash/util/RenderMathUtils.java new file mode 100644 index 0000000..96feea1 --- /dev/null +++ b/core/src/com/etheller/warsmash/util/RenderMathUtils.java @@ -0,0 +1,290 @@ +package com.etheller.warsmash.util; + +import com.badlogic.gdx.math.Matrix4; +import com.badlogic.gdx.math.Quaternion; +import com.badlogic.gdx.math.Rectangle; +import com.badlogic.gdx.math.Vector3; + +public enum RenderMathUtils { + ; + public static final Quaternion QUAT_DEFAULT = new Quaternion(0, 0, 0, 1); + public static final Vector3 VEC3_ONE = new Vector3(1, 1, 1); + public static final Vector3 VEC3_UNIT_X = new Vector3(1, 0, 0); + public static final Vector3 VEC3_UNIT_Y = new Vector3(0, 1, 0); + public static final Vector3 VEC3_UNIT_Z = new Vector3(0, 0, 1); + + // copied from ghostwolf and + // https://www.blend4web.com/api_doc/libs_gl-matrix2.js.html + public static void fromRotationTranslationScaleOrigin(final Quaternion q, final Vector3 v, final Vector3 s, + final Matrix4 out, final Vector3 pivot) { + final float x = q.x; + final float y = q.y; + final float z = q.z; + final float w = q.w; + final float x2 = x + x; + final float y2 = y + y; + final float z2 = z + z; + final float xx = x * x2; + final float xy = x * y2; + final float xz = x * z2; + final float yy = y * y2; + final float yz = y * z2; + final float zz = z * z2; + final float wx = w * x2; + final float wy = w * y2; + final float wz = w * z2; + final float sx = s.x; + final float sy = s.y; + final float sz = s.z; + out.val[Matrix4.M00] = (1 - (yy + zz)) * sx; + out.val[Matrix4.M01] = (xy + wz) * sx; + out.val[Matrix4.M02] = (xz - wy) * sx; + out.val[Matrix4.M03] = 0; + out.val[Matrix4.M10] = (xy - wz) * sy; + out.val[Matrix4.M11] = (1 - (xx + zz)) * sy; + out.val[Matrix4.M12] = (yz + wx) * sy; + out.val[Matrix4.M13] = 0; + out.val[Matrix4.M20] = (xz + wy) * sz; + out.val[Matrix4.M21] = (yz - wx) * sz; + out.val[Matrix4.M22] = (1 - (xx + yy)) * sz; + out.val[Matrix4.M23] = 0; + out.val[Matrix4.M30] = (v.x + pivot.x) - ((out.val[Matrix4.M00] * pivot.x) + (out.val[Matrix4.M10] * pivot.y) + + (out.val[Matrix4.M20] * pivot.z)); + out.val[Matrix4.M31] = (v.y + pivot.y) - ((out.val[Matrix4.M01] * pivot.x) + (out.val[Matrix4.M11] * pivot.y) + + (out.val[Matrix4.M21] * pivot.z)); + out.val[Matrix4.M32] = (v.z + pivot.z) - ((out.val[Matrix4.M02] * pivot.x) + (out.val[Matrix4.M12] * pivot.y) + + (out.val[Matrix4.M22] * pivot.z)); + out.val[Matrix4.M33] = 1; + } + + // copied from + // https://www.blend4web.com/api_doc/libs_gl-matrix2.js.html + public static void fromRotationTranslationScale(final Quaternion q, final Vector3 v, final Vector3 s, + final Matrix4 out) { + final float x = q.x; + final float y = q.y; + final float z = q.z; + final float w = q.w; + final float x2 = x + x; + final float y2 = y + y; + final float z2 = z + z; + final float xx = x * x2; + final float xy = x * y2; + final float xz = x * z2; + final float yy = y * y2; + final float yz = y * z2; + final float zz = z * z2; + final float wx = w * x2; + final float wy = w * y2; + final float wz = w * z2; + final float sx = s.x; + final float sy = s.y; + final float sz = s.z; + out.val[Matrix4.M00] = (1 - (yy + zz)) * sx; + out.val[Matrix4.M01] = (xy + wz) * sx; + out.val[Matrix4.M02] = (xz - wy) * sx; + out.val[Matrix4.M03] = 0; + out.val[Matrix4.M10] = (xy - wz) * sy; + out.val[Matrix4.M11] = (1 - (xx + zz)) * sy; + out.val[Matrix4.M12] = (yz + wx) * sy; + out.val[Matrix4.M13] = 0; + out.val[Matrix4.M20] = (xz + wy) * sz; + out.val[Matrix4.M21] = (yz - wx) * sz; + out.val[Matrix4.M22] = (1 - (xx + yy)) * sz; + out.val[Matrix4.M23] = 0; + out.val[Matrix4.M30] = v.x; + out.val[Matrix4.M31] = v.y; + out.val[Matrix4.M32] = v.z; + out.val[Matrix4.M33] = 1; + } + + public static void mul(final Matrix4 dest, final Matrix4 left, final Matrix4 right) { + dest.set(left); // TODO better performance here, remove the extra copying + dest.mul(right); + } + + public static void mul(final Quaternion dest, final Quaternion left, final Quaternion right) { + dest.set(left); // TODO better performance here, remove the extra copying + dest.mul(right); + } + + public static Quaternion rotateX(final Quaternion out, final Quaternion a, float rad) { + rad *= 0.5; + + final float ax = a.x, ay = a.y, az = a.z, aw = a.w; + final float bx = (float) Math.sin(rad), bw = (float) Math.cos(rad); + + out.x = (ax * bw) + (aw * bx); + out.y = (ay * bw) + (az * bx); + out.z = (az * bw) - (ay * bx); + out.w = (aw * bw) - (ax * bx); + return out; + } + + public static Quaternion rotateY(final Quaternion out, final Quaternion a, float rad) { + rad *= 0.5; + + final float ax = a.x, ay = a.y, az = a.z, aw = a.w; + final float by = (float) Math.sin(rad), bw = (float) Math.cos(rad); + + out.x = (ax * bw) - (az * by); + out.y = (ay * bw) + (aw * by); + out.z = (az * bw) + (ax * by); + out.w = (aw * bw) - (ay * by); + return out; + } + + public static Quaternion rotateZ(final Quaternion out, final Quaternion a, float rad) { + rad *= 0.5; + + final float ax = a.x, ay = a.y, az = a.z, aw = a.w; + final float bz = (float) Math.sin(rad), bw = (float) Math.cos(rad); + + out.x = (ax * bw) + (ay * bz); + out.y = (ay * bw) - (ax * bz); + out.z = (az * bw) + (aw * bz); + out.w = (aw * bw) - (az * bz); + return out; + } + + public static Matrix4 perspective(final Matrix4 out, final float fovy, final float aspect, final float near, + final float far) { + final float f = 1.0f / (float) Math.tan(fovy / 2), nf; + out.val[Matrix4.M00] = f / aspect; + out.val[Matrix4.M01] = 0; + out.val[Matrix4.M02] = 0; + out.val[Matrix4.M03] = 0; + out.val[Matrix4.M10] = 0; + out.val[Matrix4.M11] = f; + out.val[Matrix4.M12] = 0; + out.val[Matrix4.M13] = 0; + out.val[Matrix4.M20] = 0; + out.val[Matrix4.M21] = 0; + out.val[Matrix4.M23] = -1; + out.val[Matrix4.M30] = 0; + out.val[Matrix4.M31] = 0; + out.val[Matrix4.M33] = 0; + if (!Double.isNaN(far) && !Double.isInfinite(far)) { + nf = 1 / (near - far); + out.val[Matrix4.M22] = (far + near) * nf; + out.val[Matrix4.M32] = (2 * far * near) * nf; + } + else { + out.val[Matrix4.M22] = -1; + out.val[Matrix4.M32] = -2 * near; + } + return out; + } + + public static Matrix4 ortho(final Matrix4 out, final float left, final float right, final float bottom, + final float top, final float near, final float far) { + final float lr = 1 / (left - right); + final float bt = 1 / (bottom - top); + final float nf = 1 / (near - far); + out.val[Matrix4.M00] = -2 * lr; + out.val[Matrix4.M01] = 0; + out.val[Matrix4.M02] = 0; + out.val[Matrix4.M03] = 0; + + out.val[Matrix4.M10] = 0; + out.val[Matrix4.M11] = -2 * bt; + out.val[Matrix4.M12] = 0; + out.val[Matrix4.M13] = 0; + + out.val[Matrix4.M20] = 0; + out.val[Matrix4.M21] = 0; + out.val[Matrix4.M22] = 2 * nf; + out.val[Matrix4.M23] = 0; + + out.val[Matrix4.M30] = (left + right) * lr; + out.val[Matrix4.M31] = (top + bottom) * bt; + out.val[Matrix4.M32] = (far + near) * nf; + out.val[Matrix4.M33] = 1; + return out; + } + + public static void unpackPlanes(final Vector4[] planes, final Matrix4 m) { + final float a00 = m.val[Matrix4.M00], a01 = m.val[Matrix4.M01], a02 = m.val[Matrix4.M02], + a03 = m.val[Matrix4.M03], a10 = m.val[Matrix4.M10], a11 = m.val[Matrix4.M11], a12 = m.val[Matrix4.M12], + a13 = m.val[Matrix4.M13], a20 = m.val[Matrix4.M20], a21 = m.val[Matrix4.M21], a22 = m.val[Matrix4.M22], + a23 = m.val[Matrix4.M23], a30 = m.val[Matrix4.M30], a31 = m.val[Matrix4.M31], a32 = m.val[Matrix4.M32], + a33 = m.val[Matrix4.M33]; + + // Left clipping plane + Vector4 plane = planes[0]; + plane.x = a30 + a00; + plane.y = a31 + a01; + plane.z = a32 + a02; + plane.w = a33 + a03; + + // Right clipping plane + plane = planes[1]; + plane.x = a30 - a00; + plane.y = a31 - a01; + plane.z = a32 - a02; + plane.w = a33 - a03; + + // Top clipping plane + plane = planes[2]; + plane.x = a30 - a10; + plane.y = a31 - a11; + plane.z = a32 - a12; + plane.w = a33 - a13; + + // Bottom clipping plane + plane = planes[3]; + plane.x = a30 + a10; + plane.y = a31 + a11; + plane.z = a32 + a12; + plane.w = a33 + a13; + + // Near clipping plane + plane = planes[4]; + plane.x = a30 + a20; + plane.y = a31 + a21; + plane.z = a32 + a22; + plane.w = a33 + a23; + + // Far clipping plane + plane = planes[5]; + plane.x = a30 - a20; + plane.y = a31 - a21; + plane.z = a32 - a22; + plane.w = a33 - a23; + + normalizePlane(planes[0], planes[0]); + normalizePlane(planes[1], planes[1]); + normalizePlane(planes[2], planes[2]); + normalizePlane(planes[3], planes[3]); + normalizePlane(planes[4], planes[4]); + normalizePlane(planes[5], planes[5]); + } + + public static void normalizePlane(final Vector4 out, final Vector4 plane) { + final float len = Vector3.len(plane.x, plane.y, plane.z); + + out.x = plane.x / len; + out.y = plane.y / len; + out.z = plane.z / len; + out.w = plane.w / len; + } + + public static float distanceToPlane(final Vector4 plane, final Vector3 point) { + return (plane.x * point.x) + (plane.y * point.y) + (plane.z * point.z) + plane.w; + } + + private static final Vector4 heap = new Vector4(); + + public static Vector3 unproject(final Vector3 out, final Vector3 v, final Matrix4 inverseMatrix, + final Rectangle viewport) { + final float x = ((2 * (v.x - viewport.x)) / viewport.width) - 1; + final float y = 1 - ((2 * (v.y - viewport.y)) / viewport.height); + final float z = (2 * v.z) - 1; + + heap.set(x, y, z, 1); + Vector4.transformMat4(heap, heap, inverseMatrix); + out.set(heap.x / heap.w, heap.y / heap.w, heap.z / heap.w); + + return out; + } +} diff --git a/core/src/com/etheller/warsmash/util/Vector4.java b/core/src/com/etheller/warsmash/util/Vector4.java new file mode 100644 index 0000000..f80af83 --- /dev/null +++ b/core/src/com/etheller/warsmash/util/Vector4.java @@ -0,0 +1,573 @@ +package com.etheller.warsmash.util; + +import java.io.Serializable; + +import com.badlogic.gdx.math.Interpolation; +import com.badlogic.gdx.math.MathUtils; +import com.badlogic.gdx.math.Matrix4; +import com.badlogic.gdx.math.Vector; +import com.badlogic.gdx.utils.NumberUtils; + +/** + * Encapsulates a 4D vector. Allows chaining operations by returning a reference + * to itself in all modification methods. + * + * @author intrigus + */ +public class Vector4 implements Serializable, Vector { + + /** the x-component of this vector **/ + public float x; + /** the y-component of this vector **/ + public float y; + /** the z-component of this vector **/ + public float z; + /** the w-component of this vector **/ + public float w; + + public final static Vector4 X = new Vector4(1, 0, 0, 0); + public final static Vector4 Y = new Vector4(0, 1, 0, 0); + public final static Vector4 Z = new Vector4(0, 0, 1, 0); + public final static Vector4 W = new Vector4(0, 0, 0, 1); + public final static Vector4 Zero = new Vector4(0, 0, 0, 0); + + private final static Matrix4 tmpMat = new Matrix4(); + + /** Constructs a vector at (0,0,0,0) */ + public Vector4() { + } + + /** + * Creates a vector with the given components + * + * @param x The x-component + * @param y The y-component + * @param z The z-component + * @param w The w-component + */ + public Vector4(final float x, final float y, final float z, final float w) { + this.set(x, y, z, w); + } + + /** + * Creates a vector from the given vector + * + * @param vector The vector + */ + public Vector4(final Vector4 vector) { + this.set(vector); + } + + /** + * Creates a vector from the given array. The array must have at least 4 + * elements. + * + * @param values The array + */ + public Vector4(final float[] values) { + this.set(values[0], values[1], values[2], values[3]); + } + + /** + * Sets the vector to the given components + * + * @param x The x-component + * @param y The y-component + * @param z The z-component + * @param w The w-component + * @return this vector for chaining + */ + public Vector4 set(final float x, final float y, final float z, final float w) { + this.x = x; + this.y = y; + this.z = z; + this.w = w; + return this; + } + + @Override + public Vector4 set(final Vector4 vector) { + return this.set(vector.x, vector.y, vector.z, vector.w); + } + + /** + * Sets the components from the array. The array must have at least 4 elements + * + * @param values The array + * @return this vector for chaining + */ + public Vector4 set(final float[] values) { + return this.set(values[0], values[1], values[2], values[3]); + } + + @Override + public Vector4 cpy() { + return new Vector4(this); + } + + @Override + public Vector4 add(final Vector4 vector) { + return this.add(vector.x, vector.y, vector.z, vector.w); + } + + /** + * Adds the given vector to this component + * + * @param x The x-component of the other vector + * @param y The y-component of the other vector + * @param z The z-component of the other vector + * @param w The w-component of the other vector + * @return This vector for chaining. + */ + public Vector4 add(final float x, final float y, final float z, final float w) { + return this.set(this.x + x, this.y + y, this.z + z, this.w + w); + } + + /** + * Adds the given value to all four components of the vector. + * + * @param values The value + * @return This vector for chaining + */ + public Vector4 add(final float values) { + return this.set(this.x + values, this.y + values, this.z + values, this.w + values); + } + + @Override + public Vector4 sub(final Vector4 a_vec) { + return this.sub(a_vec.x, a_vec.y, a_vec.z, a_vec.w); + } + + /** + * Subtracts the other vector from this vector. + * + * @param x The x-component of the other vector + * @param y The y-component of the other vector + * @param z The z-component of the other vector + * @param w The w-component of the other vector + * @return This vector for chaining + */ + public Vector4 sub(final float x, final float y, final float z, final float w) { + return this.set(this.x - x, this.y - y, this.z - z, this.w - w); + } + + /** + * Subtracts the given value from all components of this vector + * + * @param value The value + * @return This vector for chaining + */ + public Vector4 sub(final float value) { + return this.set(this.x - value, this.y - value, this.z - value, this.w - value); + } + + @Override + public Vector4 scl(final float scalar) { + return this.set(this.x * scalar, this.y * scalar, this.z * scalar, this.w * scalar); + } + + @Override + public Vector4 scl(final Vector4 other) { + return this.set(this.x * other.x, this.y * other.y, this.z * other.z, this.w * other.w); + } + + /** + * Scales this vector by the given values + * + * @param vx X value + * @param vy Y value + * @param vz Z value + * @param vw W value + * @return This vector for chaining + */ + public Vector4 scl(final float vx, final float vy, final float vz, final float vw) { + return this.set(this.x * vx, this.y * vy, this.z * vz, this.z * vw); + } + + @Override + public Vector4 mulAdd(final Vector4 vec, final float scalar) { + this.x += vec.x * scalar; + this.y += vec.y * scalar; + this.z += vec.z * scalar; + this.w += vec.w * scalar; + return this; + } + + @Override + public Vector4 mulAdd(final Vector4 vec, final Vector4 mulVec) { + this.x += vec.x * mulVec.x; + this.y += vec.y * mulVec.y; + this.z += vec.z * mulVec.z; + this.w += vec.w * mulVec.w; + return this; + } + + /** @return The euclidian length */ + public static float len(final float x, final float y, final float z, final float w) { + return (float) Math.sqrt((x * x) + (y * y) + (z * z) + (w * w)); + } + + @Override + public float len() { + return (float) Math.sqrt((this.x * this.x) + (this.y * this.y) + (this.z * this.z) + (this.w * this.w)); + } + + /** @return The squared euclidian length */ + public static float len2(final float x, final float y, final float z, final float w) { + return (x * x) + (y * y) + (z * z) + (w * w); + } + + @Override + public float len2() { + return (this.x * this.x) + (this.y * this.y) + (this.z * this.z) + (this.w * this.w); + } + + /** + * @param vector The other vector + * @return Whether this and the other vector are equal + */ + public boolean idt(final Vector4 vector) { + return (this.x == vector.x) && (this.y == vector.y) && (this.z == vector.z) && (this.w == vector.w); + } + + /** @return The euclidian distance between the two specified vectors */ + public static float dst(final float x1, final float y1, final float z1, final float w1, final float x2, + final float y2, final float z2, final float w2) { + final float a = x2 - x1; + final float b = y2 - y1; + final float c = z2 - z1; + final float d = w2 - w1; + return (float) Math.sqrt((a * a) + (b * b) + (c * c) + (d * d)); + } + + @Override + public float dst(final Vector4 vector) { + final float a = vector.x - this.x; + final float b = vector.y - this.y; + final float c = vector.z - this.z; + final float d = vector.w - this.w; + return (float) Math.sqrt((a * a) + (b * b) + (c * c) + (d * d)); + } + + /** @return the distance between this point and the given point */ + public float dst(final float x, final float y, final float z, final float w) { + final float a = x - this.x; + final float b = y - this.y; + final float c = z - this.z; + final float d = w - this.w; + return (float) Math.sqrt((a * a) + (b * b) + (c * c) + (d * d)); + } + + /** @return the squared distance between the given points */ + public static float dst2(final float x1, final float y1, final float z1, final float w1, final float x2, + final float y2, final float z2, final float w2) { + final float a = x2 - x1; + final float b = y2 - y1; + final float c = z2 - z1; + final float d = w2 - w1; + return (a * a) + (b * b) + (c * c) + (d * d); + } + + @Override + public float dst2(final Vector4 point) { + final float a = point.x - this.x; + final float b = point.y - this.y; + final float c = point.z - this.z; + final float d = point.w - this.w; + return (a * a) + (b * b) + (c * c) + (d * d); + } + + /** + * Returns the squared distance between this point and the given point + * + * @param x The x-component of the other point + * @param y The y-component of the other point + * @param z The z-component of the other point + * @param w The w-component of the other point + * @return The squared distance + */ + public float dst2(final float x, final float y, final float z, final float w) { + final float a = x - this.x; + final float b = y - this.y; + final float c = z - this.z; + final float d = w - this.w; + return (a * a) + (b * b) + (c * c) + (d * d); + } + + @Override + public Vector4 nor() { + final float len2 = this.len2(); + if ((len2 == 0f) || (len2 == 1f)) { + return this; + } + return this.scl(1f / (float) Math.sqrt(len2)); + } + + /** @return The dot product between the two vectors */ + public static float dot(final float x1, final float y1, final float z1, final float w1, final float x2, + final float y2, final float z2, final float w2) { + return (x1 * x2) + (y1 * y2) + (z1 * z2) + (w1 * w2); + } + + @Override + public float dot(final Vector4 vector) { + return (this.x * vector.x) + (this.y * vector.y) + (this.z * vector.z) + (this.w * vector.w); + } + + /** + * Returns the dot product between this and the given vector. + * + * @param x The x-component of the other vector + * @param y The y-component of the other vector + * @param z The z-component of the other vector + * @param w The w-component of the other vector + * @return The dot product + */ + public float dot(final float x, final float y, final float z, final float w) { + return (this.x * x) + (this.y * y) + (this.z * z) + (this.w * w); + } + + @Override + public boolean isUnit() { + return isUnit(0.000000001f); + } + + @Override + public boolean isUnit(final float margin) { + return Math.abs(len2() - 1f) < margin; + } + + @Override + public boolean isZero() { + return (this.x == 0) && (this.y == 0) && (this.z == 0); + } + + @Override + public boolean isZero(final float margin) { + return len2() < margin; + } + + @Override + public boolean isOnLine(final Vector4 other, final float epsilon) { + throw new UnsupportedOperationException(); + // TODO +// return len2((this.y * other.z) - (this.z * other.y), (this.z * other.x) - (this.x * other.z), +// (this.x * other.y) - (this.y * other.x)) <= epsilon; + } + + @Override + public boolean isOnLine(final Vector4 other) { + throw new UnsupportedOperationException(); + // TODO +// return len2((this.y * other.z) - (this.z * other.y), (this.z * other.x) - (this.x * other.z), +// (this.x * other.y) - (this.y * other.x)) <= MathUtils.FLOAT_ROUNDING_ERROR; + } + + @Override + public boolean isCollinear(final Vector4 other, final float epsilon) { + return isOnLine(other, epsilon) && hasSameDirection(other); + } + + @Override + public boolean isCollinear(final Vector4 other) { + return isOnLine(other) && hasSameDirection(other); + } + + @Override + public boolean isCollinearOpposite(final Vector4 other, final float epsilon) { + return isOnLine(other, epsilon) && hasOppositeDirection(other); + } + + @Override + public boolean isCollinearOpposite(final Vector4 other) { + return isOnLine(other) && hasOppositeDirection(other); + } + + @Override + public boolean isPerpendicular(final Vector4 vector) { + return MathUtils.isZero(dot(vector)); + } + + @Override + public boolean isPerpendicular(final Vector4 vector, final float epsilon) { + return MathUtils.isZero(dot(vector), epsilon); + } + + @Override + public boolean hasSameDirection(final Vector4 vector) { + return dot(vector) > 0; + } + + @Override + public boolean hasOppositeDirection(final Vector4 vector) { + return dot(vector) < 0; + } + + @Override + public Vector4 lerp(final Vector4 target, final float alpha) { + // TODO + this.x += alpha * (target.x - this.x); + this.y += alpha * (target.y - this.y); + this.z += alpha * (target.z - this.z); + return this; + } + + @Override + public Vector4 interpolate(final Vector4 target, final float alpha, final Interpolation interpolator) { + // TODO + return lerp(target, interpolator.apply(0f, 1f, alpha)); + } + + @Override + public String toString() { + return "[" + this.x + ", " + this.y + ", " + this.z + ", " + this.w + "]"; + } + + @Override + public Vector4 limit(final float limit) { +// TODO + return limit2(limit * limit); + } + + @Override + public Vector4 limit2(final float limit2) { + // TODO + final float len2 = len2(); + if (len2 > limit2) { + scl((float) Math.sqrt(limit2 / len2)); + } + return this; + } + + @Override + public Vector4 setLength(final float len) { + return setLength2(len * len); + } + + @Override + public Vector4 setLength2(final float len2) { + final float oldLen2 = len2(); + return ((oldLen2 == 0) || (oldLen2 == len2)) ? this : scl((float) Math.sqrt(len2 / oldLen2)); + } + + @Override + public Vector4 clamp(final float min, final float max) { + final float len2 = len2(); + if (len2 == 0f) { + return this; + } + final float max2 = max * max; + if (len2 > max2) { + return scl((float) Math.sqrt(max2 / len2)); + } + final float min2 = min * min; + if (len2 < min2) { + return scl((float) Math.sqrt(min2 / len2)); + } + return this; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = (prime * result) + NumberUtils.floatToIntBits(this.x); + result = (prime * result) + NumberUtils.floatToIntBits(this.y); + result = (prime * result) + NumberUtils.floatToIntBits(this.z); + result = (prime * result) + NumberUtils.floatToIntBits(this.w); + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final Vector4 other = (Vector4) obj; + if (NumberUtils.floatToIntBits(this.x) != NumberUtils.floatToIntBits(other.x)) { + return false; + } + if (NumberUtils.floatToIntBits(this.y) != NumberUtils.floatToIntBits(other.y)) { + return false; + } + if (NumberUtils.floatToIntBits(this.z) != NumberUtils.floatToIntBits(other.z)) { + return false; + } + if (NumberUtils.floatToIntBits(this.w) != NumberUtils.floatToIntBits(other.w)) { + return false; + } + return true; + } + + @Override + public boolean epsilonEquals(final Vector4 other, final float epsilon) { + if (other == null) { + return false; + } + if (Math.abs(other.x - this.x) > epsilon) { + return false; + } + if (Math.abs(other.y - this.y) > epsilon) { + return false; + } + if (Math.abs(other.z - this.z) > epsilon) { + return false; + } + if (Math.abs(other.w - this.w) > epsilon) { + return false; + } + return true; + } + + /** + * Compares this vector with the other vector, using the supplied epsilon for + * fuzzy equality testing. + * + * @return whether the vectors are the same. + */ + public boolean epsilonEquals(final float x, final float y, final float z, final float w, final float epsilon) { + if (Math.abs(x - this.x) > epsilon) { + return false; + } + if (Math.abs(y - this.y) > epsilon) { + return false; + } + if (Math.abs(z - this.z) > epsilon) { + return false; + } + if (Math.abs(w - this.w) > epsilon) { + return false; + } + return true; + } + + @Override + public Vector4 setZero() { + this.x = 0; + this.y = 0; + this.z = 0; + this.w = 0; + return this; + } + + @Override + public Vector4 setToRandomDirection() { + throw new UnsupportedOperationException(); + } + + public static Vector4 transformMat4(final Vector4 out, final Vector4 a, final Matrix4 matrix) { + final float x = a.x, y = a.y, z = a.z, w = a.w; + final float[] m = matrix.val; + out.x = (m[Matrix4.M00] * x) + (m[Matrix4.M10] * y) + (m[Matrix4.M20] * z) + (m[Matrix4.M30] * w); + out.y = (m[Matrix4.M01] * x) + (m[Matrix4.M11] * y) + (m[Matrix4.M21] * z) + (m[Matrix4.M31] * w); + out.z = (m[Matrix4.M02] * x) + (m[Matrix4.M12] * y) + (m[Matrix4.M22] * z) + (m[Matrix4.M32] * w); + out.w = (m[Matrix4.M03] * x) + (m[Matrix4.M13] * y) + (m[Matrix4.M23] * z) + (m[Matrix4.M33] * w); + return out; + } +} \ No newline at end of file diff --git a/core/src/com/etheller/warsmash/viewer/BoundingShape.java b/core/src/com/etheller/warsmash/viewer/BoundingShape.java new file mode 100644 index 0000000..d0d379b --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer/BoundingShape.java @@ -0,0 +1,116 @@ +package com.etheller.warsmash.viewer; + +import com.badlogic.gdx.math.Quaternion; +import com.badlogic.gdx.math.Vector3; + +public class BoundingShape extends SceneNode { + private final float[] min = new float[] { -1, -1, -1 }; + private final float[] max = new float[] { 1, 1, 1 }; + private float radius = (float) Math.sqrt(2); + + public void fromBounds(final float[] min, final float[] max) { + System.arraycopy(min, 0, this.min, 0, this.min.length); + System.arraycopy(max, 0, this.max, 0, this.max.length); + + final float dX = max[0] - min[0]; + final float dY = max[1] - min[1]; + final float dZ = max[2] - min[2]; + + this.radius = (float) Math.sqrt((dX * dX) + (dY * dY) + (dZ * dZ)) / 2; + } + + public void fromRadius(final float radius) { + final float s = (float) (radius * Math.cos(radius)); + this.min[0] = this.min[1] = this.min[2] = s; + this.max[0] = this.max[1] = this.max[2] = s; + this.radius = radius; + } + + public void fromVertices(final float[] vertices) { + final float[] min = new float[] { 1E9f, 1E9f, 1E9f }; + final float[] max = new float[] { -1E9f, -1E9f, -1E9f }; + + for (int i = 0, l = vertices.length; i < l; i += 3) { + final float x = vertices[i]; + final float y = vertices[i + 1]; + final float z = vertices[i + 2]; + + if (x > max[0]) { + max[0] = x; + } + if (x < min[0]) { + min[0] = x; + } + if (y > max[1]) { + max[1] = y; + } + if (y < min[1]) { + min[1] = y; + } + if (z > max[2]) { + max[2] = z; + } + if (z < min[2]) { + min[2] = z; + } + } + + fromBounds(min, max); + } + + public Vector3 getPositiveVertex(final Vector3 out, final Vector3 normal) { + if (normal.x >= 0) { + out.x = this.max[0]; + } + else { + out.x = this.min[0]; + } + if (normal.y >= 0) { + out.y = this.max[1]; + } + else { + out.y = this.min[1]; + } + if (normal.z >= 0) { + out.z = this.max[2]; + } + else { + out.z = this.min[2]; + } + + return out; + } + + public Vector3 getNegativeVertex(final Vector3 out, final Vector3 normal) { + if (normal.x >= 0) { + out.x = this.min[0]; + } + else { + out.x = this.max[0]; + } + if (normal.y >= 0) { + out.y = this.min[1]; + } + else { + out.y = this.max[1]; + } + if (normal.z >= 0) { + out.z = this.min[2]; + } + else { + out.z = this.max[2]; + } + + return out; + } + + @Override + protected void updateObject(final Scene scene) { + } + + @Override + protected void convertBasis(final Quaternion computedRotation) { + // TODO ??? + } + +} diff --git a/core/src/com/etheller/warsmash/viewer/Bucket.java b/core/src/com/etheller/warsmash/viewer/Bucket.java new file mode 100644 index 0000000..98955a1 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer/Bucket.java @@ -0,0 +1,28 @@ +package com.etheller.warsmash.viewer; + +import com.badlogic.gdx.graphics.GL20; +import com.etheller.warsmash.viewer.ModelView.SceneData; + +public class Bucket { + private final ModelView modelView; + private final Model model; + private final int count; + + public Bucket(final ModelView modelView) { + final Model model = modelView.model; + final GL20 gl = model.getViewer().gl; + + this.modelView = modelView; + this.model = model; + this.count = 0; + +// this.instanceIdBuffer = + } + + public int fill(final SceneData data, final int baseInstance, final Scene scene) { + // Make believe the bucket is now filled with data for all instances. + // This is because if a non-specific bucket implementation is supplied, + // instancing isn't used, so batching is irrelevant. + return data.instances.size(); + } +} diff --git a/core/src/com/etheller/warsmash/viewer/Camera.java b/core/src/com/etheller/warsmash/viewer/Camera.java new file mode 100644 index 0000000..db37d39 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer/Camera.java @@ -0,0 +1,314 @@ +package com.etheller.warsmash.viewer; + +import com.badlogic.gdx.math.Matrix4; +import com.badlogic.gdx.math.Quaternion; +import com.badlogic.gdx.math.Rectangle; +import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.math.Vector3; +import com.etheller.warsmash.util.RenderMathUtils; +import com.etheller.warsmash.util.Vector4; + +public class Camera { + private static final Vector3 vectorHeap = new Vector3(); + private static final Vector3 vectorHeap2 = new Vector3(); + private static final Vector3 vectorHeap3 = new Vector3(); + private static final Quaternion quatHeap = new Quaternion(); + private static final Matrix4 matHeap = new Matrix4(); + + private final Rectangle rect; + + private boolean isPerspective; + private float fov; + private float aspect; + + private boolean isOrtho; + private float leftClipPlane; + private float rightClipPlane; + private float bottomClipPlane; + private float topClipPlane; + + private float nearClipPlane; + private float farClipPlane; + + private final Vector3 location; + private final Quaternion rotation; + + public Quaternion inverseRotation; + private final Matrix4 worldMatrix; + private final Matrix4 projectionMatrix; + private final Matrix4 worldProjectionMatrix; + private final Matrix4 inverseWorldMatrix; + private final Matrix4 inverseRotationMatrix; + private final Matrix4 inverseWorldProjectionMatrix; + private final Vector3 directionX; + private final Vector3 directionY; + private final Vector3 directionZ; + private final Vector3[] vectors; + private final Vector3[] billboardedVectors; + + private final Vector4[] planes; + private boolean dirty; + + public Camera() { + // rencered viewport + this.rect = new Rectangle(); + + // perspective values + this.isPerspective = true; + this.fov = 0; + this.aspect = 0; + + // Orthogonal values + this.isOrtho = false; + this.leftClipPlane = 0f; + this.rightClipPlane = 0f; + this.bottomClipPlane = 0f; + this.topClipPlane = 0f; + + // Shared values + this.nearClipPlane = 0f; + this.farClipPlane = 0f; + + // World values + this.location = new Vector3(); + this.rotation = new Quaternion(); + + // Derived values. + this.inverseRotation = new Quaternion(); + this.worldMatrix = new Matrix4(); + this.projectionMatrix = new Matrix4(); + this.worldProjectionMatrix = new Matrix4(); + this.inverseWorldMatrix = new Matrix4(); + this.inverseRotationMatrix = new Matrix4(); + this.inverseWorldProjectionMatrix = new Matrix4(); + this.directionX = new Vector3(); + this.directionY = new Vector3(); + this.directionZ = new Vector3(); + + // First four vectors are the corners of a 2x2 rectangle, the last three vectors + // are the unit axes + this.vectors = new Vector3[] { new Vector3(-1, -1, 0), new Vector3(-1, 1, 0), new Vector3(1, 1, 0), + new Vector3(1, -1, 0), new Vector3(1, 0, 0), new Vector3(0, 1, 0), new Vector3(0, 0, 1) }; + + // First four vectors are the corners of a 2x2 rectangle billboarded to the + // camera, the last three vectors are the unit axes billboarded + this.billboardedVectors = new Vector3[] { new Vector3(), new Vector3(), new Vector3(), new Vector3(), + new Vector3(), new Vector3(), new Vector3() }; + + // Left, right, top, bottom, near, far + this.planes = new Vector4[] { new Vector4(), new Vector4(), new Vector4(), new Vector4(), new Vector4(), + new Vector4() }; + + this.dirty = true; + } + + public void perspective(final float fov, final float aspect, final float near, final float far) { + this.isPerspective = true; + this.isOrtho = false; + this.fov = fov; + this.aspect = aspect; + this.nearClipPlane = near; + this.farClipPlane = far; + + this.dirty = true; + } + + public void ortho(final float left, final float right, final float bottom, final float top, final float near, + final float far) { + this.isPerspective = false; + this.isOrtho = true; + this.leftClipPlane = left; + this.rightClipPlane = right; + this.bottomClipPlane = bottom; + this.topClipPlane = top; + this.nearClipPlane = near; + this.farClipPlane = far; + } + + public void viewport(final Rectangle viewport) { + this.rect.set(viewport); + + this.aspect = viewport.width / viewport.height; + + this.dirty = true; + } + + public void setLocation(final Vector3 location) { + this.location.set(location); + + this.dirty = true; + } + + public void move(final Vector3 offset) { + this.location.add(offset); + + this.dirty = true; + } + + public void setRotation(final Quaternion rotation) { + this.rotation.set(rotation); + + this.dirty = true; + } + + public void rotate(final Quaternion rotation) { + this.rotation.mul(rotation); + + this.dirty = true; + } + + public void setRotationAngles(final float horizontalAngle, final float verticalAngle) { + this.rotation.idt(); +// this.rotateAngles(horizontalAngle, verticalAngle); + throw new UnsupportedOperationException( + "Ghostwolf called a function that does not exist, so I did not know what to do here"); + } + + public void rotateAround(final Quaternion rotation, final Vector3 point) { + this.rotate(rotation); + + quatHeap.conjugate(); // TODO ????????? + vectorHeap.set(this.location); + vectorHeap.sub(point); + rotation.transform(vectorHeap); + vectorHeap.add(point); + this.location.set(vectorHeap); + } + + public void setRotationAround(final Quaternion rotation, final Vector3 point) { + this.setRotation(rotation); + ; + + final float length = vectorHeap.set(this.location).sub(point).len(); + + quatHeap.conjugate(); // TODO ????????? + vectorHeap.set(RenderMathUtils.VEC3_UNIT_Z); + quatHeap.transform(vectorHeap); + vectorHeap.scl(length); + this.location.set(vectorHeap.add(point)); + } + + public void setRotationAroundAngles(final float horizontalAngle, final float verticalAngle, final Vector3 point) { + quatHeap.idt(); + RenderMathUtils.rotateX(quatHeap, quatHeap, verticalAngle); + RenderMathUtils.rotateY(quatHeap, quatHeap, horizontalAngle); + + this.setRotationAround(quatHeap, point); + } + + public void face(final Vector3 point, final Vector3 worldUp) { + matHeap.setToLookAt(this.location, point, worldUp); + matHeap.getRotation(this.rotation); + + this.dirty = true; + } + + public void moveToAndFace(final Vector3 location, final Vector3 target, final Vector3 worldUp) { + this.location.set(location); + this.face(target, worldUp); + } + + public void reset() { + this.location.set(0, 0, 0); + this.rotation.idt(); + + this.dirty = true; + } + + public void update() { + if (this.dirty) { + this.dirty = true; + + final Vector3 location = this.location; + final Quaternion rotation = this.rotation; + final Quaternion inverseRotation = this.inverseRotation; + final Matrix4 worldMatrix = this.worldMatrix; + final Matrix4 projectionMatrix = this.projectionMatrix; + final Matrix4 worldProjectionMatrix = this.worldProjectionMatrix; + final Vector3[] vectors = this.vectors; + final Vector3[] billboardedVectors = this.billboardedVectors; + + if (this.isPerspective) { + RenderMathUtils.perspective(projectionMatrix, this.fov, this.aspect, this.nearClipPlane, + this.farClipPlane); + } + else { + RenderMathUtils.ortho(projectionMatrix, this.leftClipPlane, this.rightClipPlane, this.bottomClipPlane, + this.topClipPlane, this.nearClipPlane, this.farClipPlane); + } + + rotation.toMatrix(projectionMatrix.val); + worldMatrix.translate(vectorHeap.set(location).scl(-1)); + inverseRotation.set(rotation).conjugate(); + + // World projection matrix + // World space -> NDC space + worldProjectionMatrix.set(projectionMatrix).mul(worldMatrix); + + // Recalculate the camera's frustum planes + RenderMathUtils.unpackPlanes(this.planes, worldProjectionMatrix); + + // Inverse world matrix + // Camera space -> world space + this.inverseWorldMatrix.set(worldMatrix).inv(); + + this.directionX.set(RenderMathUtils.VEC3_UNIT_X); + inverseRotation.transform(this.directionX); + this.directionY.set(RenderMathUtils.VEC3_UNIT_Y); + inverseRotation.transform(this.directionY); + this.directionZ.set(RenderMathUtils.VEC3_UNIT_Z); + inverseRotation.transform(this.directionZ); + + // Inverse world projection matrix + // NDC space -> World space + this.inverseWorldProjectionMatrix.set(worldProjectionMatrix); + this.inverseWorldProjectionMatrix.inv(); + + for (int i = 0; i < 7; i++) { + billboardedVectors[i].set(vectors[i]); + inverseRotation.transform(billboardedVectors[i]); + } + } + } + + public boolean testSphere(final Vector3 center, final float radius) { + for (final Vector4 plane : this.planes) { + if (RenderMathUtils.distanceToPlane(plane, center) <= -radius) { + return false; + } + } + return true; + } + + public Vector3 cameraToWorld(final Vector3 out, final Vector3 v) { + return out.set(v).prj(this.inverseWorldMatrix); + } + + public Vector3 worldToCamera(final Vector3 out, final Vector3 v) { + return out.set(v).prj(this.worldMatrix); + } + + public float[] screenToWorldRay(final float[] out, final Vector2 v) { + final Vector3 a = vectorHeap; + final Vector3 b = vectorHeap2; + final Vector3 c = vectorHeap3; + final float x = v.x; + final float y = v.y; + final Rectangle viewport = this.rect; + + // Intersection on the near-plane + RenderMathUtils.unproject(a, c.set(x, y, 0), this.inverseWorldProjectionMatrix, viewport); + + // Intersection on the far-plane + RenderMathUtils.unproject(b, c.set(x, y, 1), this.inverseWorldProjectionMatrix, viewport); + + out[0] = a.x; + out[1] = a.y; + out[2] = a.z; + out[3] = b.x; + out[4] = b.y; + out[5] = b.z; + + return out; + } +} diff --git a/core/src/com/etheller/warsmash/viewer/Model.java b/core/src/com/etheller/warsmash/viewer/Model.java new file mode 100644 index 0000000..5c5d35a --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer/Model.java @@ -0,0 +1,9 @@ +package com.etheller.warsmash.viewer; + +public abstract class Model { + private ModelView modelView; + + public boolean ok; + + public abstract Viewer getViewer(); +} diff --git a/core/src/com/etheller/warsmash/viewer/ModelInstance.java b/core/src/com/etheller/warsmash/viewer/ModelInstance.java new file mode 100644 index 0000000..9404c32 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer/ModelInstance.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.viewer; + +public class ModelInstance { + +} diff --git a/core/src/com/etheller/warsmash/viewer/ModelView.java b/core/src/com/etheller/warsmash/viewer/ModelView.java new file mode 100644 index 0000000..e4004a6 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer/ModelView.java @@ -0,0 +1,68 @@ +package com.etheller.warsmash.viewer; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; + +public abstract class ModelView { + protected final Model model; + protected final HashSet instanceSet; + protected final HashMap sceneData; + protected final int renderedInstances; + protected final int renderedParticles; + protected final int renderedBuckets; + protected final int renderedCalls; + + public ModelView(final Model model) { + this.model = model; + + this.instanceSet = new HashSet<>(); + this.sceneData = new HashMap<>(); + + this.renderedInstances = 0; + this.renderedParticles = 0; + this.renderedBuckets = 0; + this.renderedCalls = 0; + } + + public abstract Object getShallowCopy(); + + public abstract void applyShallowCopy(final Object view); + + @Override + public abstract boolean equals(Object view); + +// public boo + public void addSceneData(final ModelInstance instance, final Scene scene) { + if (this.model.ok && (scene != null)) { + SceneData data = this.sceneData.get(scene); + + if (data == null) { + data = this.createSceneData(scene); + + this.sceneData.put(scene, data); + } + + } + } + + private SceneData createSceneData(final Scene scene) { + return new SceneData(scene, this); + } + + public static final class SceneData { + public final Scene scene; + public final ModelView modelView; + public final int baseIndex = 0; + public final List instances = new ArrayList<>(); + public final List buckets = new ArrayList<>(); + public final int usedBuckets = 0; + + public SceneData(final Scene scene, final ModelView modelView) { + this.scene = scene; + this.modelView = modelView; + } + + } +} diff --git a/core/src/com/etheller/warsmash/viewer/Scene.java b/core/src/com/etheller/warsmash/viewer/Scene.java new file mode 100644 index 0000000..f1dcf53 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer/Scene.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.viewer; + +public abstract class Scene { + public Camera camera; +} diff --git a/core/src/com/etheller/warsmash/viewer/SceneNode.java b/core/src/com/etheller/warsmash/viewer/SceneNode.java new file mode 100644 index 0000000..cc89faf --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer/SceneNode.java @@ -0,0 +1,245 @@ +package com.etheller.warsmash.viewer; + +import com.badlogic.gdx.math.Matrix4; +import com.badlogic.gdx.math.Quaternion; +import com.badlogic.gdx.math.Vector3; +import com.etheller.warsmash.util.RenderMathUtils; + +public abstract class SceneNode extends ViewerNode { + + public SceneNode() { + } + + public SceneNode setPivot(final float[] pivot) { + this.pivot.set(pivot); + this.dirty = true; + return this; + } + + public SceneNode setLocation(final float[] location) { + this.localLocation.set(location); + this.dirty = true; + return this; + } + + public SceneNode setRotation(final float[] rotation) { + this.localRotation.set(rotation[0], rotation[1], rotation[2], rotation[3]); + this.dirty = true; + return this; + } + + public SceneNode setScale(final float[] varying) { + this.localScale.set(varying); + this.dirty = true; + return this; + } + + public SceneNode setUniformScale(final float uniform) { + this.localScale.set(uniform, uniform, uniform); + this.dirty = true; + return this; + } + + public SceneNode setTransformation(final Vector3 location, final Quaternion rotation, final Vector3 scale) { + // TODO for performance, Ghostwolf did a direct field write on everything here. + // I'm hoping we can get Java's JIT to just figure it out and do it on its own + this.localLocation.set(location); + this.localRotation.set(rotation); + this.localScale.set(scale); + this.dirty = true; + return this; + } + + public SceneNode resetTransformation() { + this.pivot.set(Vector3.Zero); + this.localLocation.set(Vector3.Zero); + this.localRotation.set(RenderMathUtils.QUAT_DEFAULT); + this.localScale.set(RenderMathUtils.VEC3_ONE); + + this.dirty = true; + return this; + } + + public SceneNode movePivot(final float[] offset) { + this.pivot.add(offset[0], offset[1], offset[2]); + + this.dirty = true; + + return this; + } + + public SceneNode move(final float[] offset) { + this.localLocation.add(offset[0], offset[1], offset[2]); + + this.dirty = true; + + return this; + } + + public SceneNode rotate(final Quaternion rotation) { + RenderMathUtils.mul(this.localRotation, this.localRotation, rotation); + + this.dirty = true; + + return this; + } + + public SceneNode rotateLocal(final Quaternion rotation) { + RenderMathUtils.mul(this.localRotation, rotation, this.localRotation); + + this.dirty = true; + + return this; + } + + public SceneNode scale(final float[] scale) { + this.localScale.x *= scale[0]; + this.localScale.y *= scale[1]; + this.localScale.z *= scale[2]; + + this.dirty = true; + + return this; + } + + public SceneNode uniformScale(final float scale) { + this.localScale.x *= scale; + this.localScale.y *= scale; + this.localScale.z *= scale; + + this.dirty = true; + + return this; + } + + public SceneNode setParent(final ViewerNode parent) { + if (this.parent != null) { + this.parent.children.remove(this); + } + + this.parent = parent; + + if (parent != null) { + parent.children.add(this); + } + + this.dirty = true; + + return this; + } + + public void recalculateTransformation() { + boolean dirty = this.dirty; + final ViewerNode parent = this.parent; + + this.wasDirty = this.dirty; + + if (parent != null) { + dirty = dirty || parent.wasDirty; + } + + this.wasDirty = dirty; + + if (dirty) { + this.dirty = false; + + if (parent != null) { + Vector3 computedLocation; + Vector3 computedScaling; + + final Vector3 parentPivot = parent.pivot; + + computedLocation = locationHeap; + computedLocation.x = this.localLocation.x + parentPivot.x; + computedLocation.y = this.localLocation.y + parentPivot.y; + computedLocation.z = this.localLocation.z + parentPivot.z; + + if (this.dontInheritScaling) { + computedScaling = scalingHeap; + + final Vector3 parentInverseScale = parent.inverseWorldScale; + computedScaling.x = parentInverseScale.x * this.localScale.x; + computedScaling.y = parentInverseScale.y * this.localScale.y; + computedScaling.z = parentInverseScale.z * this.localScale.z; + + this.worldScale.x = this.localScale.x; + this.worldScale.y = this.localScale.y; + this.worldScale.z = this.localScale.z; + } + else { + computedScaling = this.localScale; + + final Vector3 parentScale = parent.worldScale; + this.worldScale.x = parentScale.x * this.localScale.x; + this.worldScale.y = parentScale.y * this.localScale.y; + this.worldScale.z = parentScale.z * this.localScale.z; + } + + RenderMathUtils.fromRotationTranslationScale(this.localRotation, computedLocation, computedScaling, + this.localMatrix); + + RenderMathUtils.mul(this.worldMatrix, parent.worldMatrix, this.localMatrix); + + RenderMathUtils.mul(this.worldRotation, parent.worldRotation, this.localRotation); + } + else { + RenderMathUtils.fromRotationTranslationScale(this.localRotation, this.localLocation, this.localScale, + this.localMatrix); + + this.worldMatrix.set(this.localMatrix); + + this.worldRotation.set(this.localRotation); + + this.worldScale.set(this.localScale); + } + } + + // Inverse world rotation + this.inverseWorldRotation.x = -this.worldRotation.x; + this.inverseWorldRotation.y = -this.worldRotation.y; + this.inverseWorldRotation.z = -this.worldRotation.z; + this.inverseWorldRotation.w = this.worldRotation.w; + + // Inverse world scale + this.inverseWorldScale.x = 1 / this.worldScale.x; + this.inverseWorldScale.y = 1 / this.worldScale.y; + this.inverseWorldScale.z = 1 / this.worldScale.z; + + // World location + this.worldLocation.x = this.worldMatrix.val[Matrix4.M30]; + this.worldLocation.y = this.worldMatrix.val[Matrix4.M31]; + this.worldLocation.z = this.worldMatrix.val[Matrix4.M32]; + + // Inverse world location + this.inverseWorldLocation.x = -this.worldLocation.x; + this.inverseWorldLocation.y = -this.worldLocation.y; + this.inverseWorldLocation.z = -this.worldLocation.z; + + } + + @Override + public void update(final Scene scene) { + if (this.dirty || ((this.parent != null) && this.parent.wasDirty)) { + this.dirty = true; + this.wasDirty = true; + this.recalculateTransformation(); + } + else { + this.wasDirty = false; + } + + this.updateObject(scene); + this.updateChildren(scene); + } + + protected abstract void updateObject(Scene scene); + + protected void updateChildren(final Scene scene) { + for (int i = 0, l = this.children.size(); i < l; i++) { + this.children.get(i).update(scene); + } + } + + protected abstract void convertBasis(Quaternion computedRotation); + +} diff --git a/core/src/com/etheller/warsmash/viewer/SkeletalNode.java b/core/src/com/etheller/warsmash/viewer/SkeletalNode.java new file mode 100644 index 0000000..bd2a1fd --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer/SkeletalNode.java @@ -0,0 +1,118 @@ +package com.etheller.warsmash.viewer; + +import java.util.ArrayList; +import java.util.List; + +import com.badlogic.gdx.math.Matrix4; +import com.badlogic.gdx.math.Quaternion; +import com.badlogic.gdx.math.Vector3; +import com.etheller.warsmash.util.Descriptor; +import com.etheller.warsmash.util.RenderMathUtils; + +public abstract class SkeletalNode extends ViewerNode { + + private final Object object; + + private final boolean billboarded = false; + private final boolean billboardedX = false; + private final boolean billboardedY = false; + private final boolean billboardedZ = false; + + public SkeletalNode() { + this.object = null; + } + + public void recalculateTransformation(final Scene scene) { + final Quaternion computedRotation; + Vector3 computedScaling; + + if (this.dontInheritScaling) { + computedScaling = scalingHeap; + + final Vector3 parentInverseScale = this.parent.inverseWorldScale; + computedScaling.x = parentInverseScale.x * this.localScale.x; + computedScaling.y = parentInverseScale.y * this.localScale.y; + computedScaling.z = parentInverseScale.z * this.localScale.z; + + this.worldScale.x = this.localScale.x; + this.worldScale.y = this.localScale.y; + this.worldScale.z = this.localScale.z; + } + else { + computedScaling = this.localScale; + + final Vector3 parentScale = this.parent.worldScale; + this.worldScale.x = parentScale.x * this.worldScale.x; + this.worldScale.y = parentScale.y * this.worldScale.y; + this.worldScale.z = parentScale.z * this.worldScale.z; + } + + if (this.billboarded) { + computedRotation = rotationHeap; + + computedRotation.set(this.parent.inverseWorldRotation); + computedRotation.mul(scene.camera.inverseRotation); + + this.convertBasis(computedRotation); + } + else { + computedRotation = this.localRotation; + } + + RenderMathUtils.fromRotationTranslationScaleOrigin(computedRotation, this.localLocation, computedScaling, + this.localMatrix, this.pivot); + + RenderMathUtils.mul(this.worldMatrix, this.parent.worldMatrix, this.localMatrix); + + RenderMathUtils.mul(this.worldRotation, this.parent.worldRotation, computedRotation); + + // Inverse world rotation + this.inverseWorldRotation.x = -this.worldRotation.x; + this.inverseWorldRotation.y = -this.worldRotation.y; + this.inverseWorldRotation.z = -this.worldRotation.z; + this.inverseWorldRotation.w = this.worldRotation.w; + + // Inverse world scale + this.inverseWorldScale.x = 1 / this.worldScale.x; + this.inverseWorldScale.y = 1 / this.worldScale.y; + this.inverseWorldScale.z = 1 / this.worldScale.z; + + // World location + final float x = this.pivot.x; + final float y = this.pivot.y; + final float z = this.pivot.z; + this.worldLocation.x = (this.worldMatrix.val[Matrix4.M00] * x) + (this.worldMatrix.val[Matrix4.M10] * y) + + (this.worldMatrix.val[Matrix4.M20] * z) + this.worldMatrix.val[Matrix4.M30]; + this.worldLocation.y = (this.worldMatrix.val[Matrix4.M01] * x) + (this.worldMatrix.val[Matrix4.M11] * y) + + (this.worldMatrix.val[Matrix4.M21] * z) + this.worldMatrix.val[Matrix4.M31]; + this.worldLocation.z = (this.worldMatrix.val[Matrix4.M02] * x) + (this.worldMatrix.val[Matrix4.M12] * y) + + (this.worldMatrix.val[Matrix4.M22] * z) + this.worldMatrix.val[Matrix4.M32]; + + // Inverse world location + this.inverseWorldLocation.x = -this.worldLocation.x; + this.inverseWorldLocation.y = -this.worldLocation.y; + this.inverseWorldLocation.z = -this.worldLocation.z; + } + + protected void updateChildren(final Scene scene) { + for (int i = 0, l = this.children.size(); i < l; i++) { + this.children.get(i).update(scene); + } + } + + protected abstract void convertBasis(Quaternion computedRotation); + + public static Object[] createSkeletalNodes(final int count, + final Descriptor nodeDescriptor) { + final List nodes = new ArrayList<>(); + final List worldMatrices = new ArrayList<>(); + for (int i = 0; i < count; i++) { + final NODE node = nodeDescriptor.create(); + nodes.add(node); + worldMatrices.add(node.worldMatrix); + } + final Object[] data = { nodes, worldMatrices }; + return data; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer/Viewer.java b/core/src/com/etheller/warsmash/viewer/Viewer.java new file mode 100644 index 0000000..f1d0232 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer/Viewer.java @@ -0,0 +1,7 @@ +package com.etheller.warsmash.viewer; + +import com.badlogic.gdx.graphics.GL20; + +public abstract class Viewer { + public GL20 gl; +} diff --git a/core/src/com/etheller/warsmash/viewer/ViewerNode.java b/core/src/com/etheller/warsmash/viewer/ViewerNode.java new file mode 100644 index 0000000..5bdd33d --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer/ViewerNode.java @@ -0,0 +1,65 @@ +package com.etheller.warsmash.viewer; + +import java.util.ArrayList; +import java.util.List; + +import com.badlogic.gdx.math.Matrix4; +import com.badlogic.gdx.math.Quaternion; +import com.badlogic.gdx.math.Vector3; + +public abstract class ViewerNode { + protected static final Vector3 locationHeap = new Vector3(); + protected static final Quaternion rotationHeap = new Quaternion(); + protected static final Vector3 scalingHeap = new Vector3(); + + protected final Vector3 pivot; + protected final Vector3 localLocation; + protected final Quaternion localRotation; + protected final Vector3 localScale; + protected final Vector3 worldLocation; + protected final Quaternion worldRotation; + protected final Vector3 worldScale; + protected final Vector3 inverseWorldLocation; + protected final Quaternion inverseWorldRotation; + protected final Vector3 inverseWorldScale; + protected final Matrix4 localMatrix; + protected final Matrix4 worldMatrix; + protected final boolean dontInheritTranslation; + protected final boolean dontInheritRotation; + protected final boolean dontInheritScaling; + protected boolean visible; + protected boolean wasDirty; + protected boolean dirty; + + protected ViewerNode parent; + + protected final List children; + + public ViewerNode() { + this.pivot = new Vector3(); + this.localLocation = new Vector3(); + this.localRotation = new Quaternion(0, 0, 0, 1); + this.localScale = new Vector3(1, 1, 1); + this.worldLocation = new Vector3(); + this.worldRotation = new Quaternion(); + this.worldScale = new Vector3(); + this.inverseWorldLocation = new Vector3(); + this.inverseWorldRotation = new Quaternion(); + this.inverseWorldScale = new Vector3(); + this.localMatrix = new Matrix4(); + this.localMatrix.val[0] = 1; + this.localMatrix.val[5] = 1; + this.localMatrix.val[10] = 1; + this.localMatrix.val[15] = 1; + this.worldMatrix = new Matrix4(); + this.dontInheritTranslation = false; + this.dontInheritRotation = false; + this.dontInheritScaling = false; + this.visible = true; + this.wasDirty = false; + this.dirty = true; + this.children = new ArrayList<>(); + } + + public abstract void update(Scene scene); +} diff --git a/core/src/com/hiveworkshop/wc3/mpq/Codebase.java b/core/src/com/hiveworkshop/wc3/mpq/Codebase.java new file mode 100644 index 0000000..61a7d5c --- /dev/null +++ b/core/src/com/hiveworkshop/wc3/mpq/Codebase.java @@ -0,0 +1,12 @@ +package com.hiveworkshop.wc3.mpq; + +import java.io.File; +import java.io.InputStream; + +public interface Codebase { + InputStream getResourceAsStream(String filepath); + + File getFile(String filepath); + + boolean has(String filepath); +} diff --git a/core/src/com/hiveworkshop/wc3/mpq/FileCodebase.java b/core/src/com/hiveworkshop/wc3/mpq/FileCodebase.java new file mode 100644 index 0000000..ae5d443 --- /dev/null +++ b/core/src/com/hiveworkshop/wc3/mpq/FileCodebase.java @@ -0,0 +1,35 @@ +package com.hiveworkshop.wc3.mpq; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; + +public class FileCodebase implements Codebase { + private final File sourceDirectory; + + public FileCodebase(final File sourceDirectory) { + this.sourceDirectory = sourceDirectory; + } + + @Override + public InputStream getResourceAsStream(final String filepath) { + try { + return new FileInputStream(new File(this.sourceDirectory.getPath() + File.separatorChar + filepath)); + } + catch (final FileNotFoundException e) { + throw new RuntimeException(e); + } + } + + @Override + public File getFile(final String filepath) { + return new File(this.sourceDirectory.getPath() + File.separatorChar + filepath); + } + + @Override + public boolean has(final String filepath) { + return new File(this.sourceDirectory.getPath() + File.separatorChar + filepath).exists(); + } + +} diff --git a/jars/blp-iio-plugin.jar b/jars/blp-iio-plugin.jar new file mode 100644 index 0000000000000000000000000000000000000000..f2d085a59d48ce8564683c2ef4a699344ebccf4f GIT binary patch literal 63630 zcmb@uW2`RS)-AfGy{2v3_L{bBKhw5t+qP}2Y1_7K z{d$m@oRF5HrJIA3qNSRioM}{KSY+Nk+CKvRB`*a5+29L&js4H3F#oyb|E~oA|E~q; z|9NXtN|i5^oYw$&T*^eOrm zx=YEfH2)(6$2ZxCqb(EE?+ET&|L&I@$DEV)lN`tU5xrVnkhW-YtYHi)S(G$fTe(k! zp-u_%^9<{qjh&NvsmPjkLw*mMHtPNPh7g>AtEy|6TI^dJ-+?$eZe7I=0VfwxKdiu( zk*@@FP*~S*@^NS-#L&%~L^k(PS5ZibO9q;Cxi>stfCo@|*QoTo`fYF1;WBh*x04XA z{A5v7yn|7_O#h90eyY#-Z9$gCTlpYhF;zpjcqpz?tcC+7q`)8Q@W^7=FNE3Qsl>l= z_UJszU*ga?>VOsm{222Z&~wSZtr0J~p-X}H9MBTR?!JLOXfF2qMT_V8(8n$W(I}hX38dkyJpufA7h#WWPlLv*JEU;OB_LMMX zE*L3HYAsrgh4qT+gbnsD=d{xsuM$X!>Op)m6sz)RU?>S0>!2en%fre)5DQ1YW)WEr z*M?h+iNr*<*>&_1fb|*NYVKpeqL-TAmqaNhiJULhN5+M(tH|%ObALS7Z9~6;UN{!m zI653{ZBG=va|F>dj1CcD;4*w|w8D?P46MDIeG*Y@C^Tzbsm8T0!Ox5 zy;RD<(c-kJcHG&yRaD5FE}Uj?eU$tX&8MT^r^bDmW4T4RVENNwe;7uxwr?+0tJEx^_l=KdHE6avH+B|iUFnmoJ(NJ3na|a)o$3SkL@&-A7X8#WC%_NCK9Atg1IYFO_dP+eOG-Dj2z0%>w6_(~ zL)!l@$!cm^a%4MssGIKvDpfCG%@-SJuHv{4Q8WJmk2mR9yU(kiaE;AATHnG9=QU2D zB|^X>J?z=YSAE!IbcEBOp5kzzbBj(k$6Zrl=f0h5D>1vPO6uEhzjLjndfRChh~`$ z@yg{%jPE#@3|RK;fk@Z(8ajHw1-t7)F54B4e}x#Ic6T9trGrNt9n$#xk-m=oTL)wq4GjA|PAVtyVuC3z@9y`KPYagF;8SPFoC> zRj{zu@Xeg<$;5-~Qygjeg9~BqPf^9_NnUm+74hZEP2p9XgcuWjK0FcnmK~ZLPlP=Cg6LvUFBF#Oi!AHzN+slprGd7-@fW4La5Wd~D-Elqn z^HR1Y&DU;kz(fn67o@7LIu(AE|Igi4r5VFKi*gWa54PK=w{Kj4+l(F1BmG%Cp|>)c zlp?Y_K}A?entsM&gfUo}{dOj7uNaBI>v1mkZ0>YQC+l27)*@U zzYAA3LB%koyftp8KgT$ZCqw&~3)j9klz>XvbaDYBVCVyT%#LMSF0VI4$bkDy82PTr zpEE!bT~wu=wv~v&LQ44ZSJ;p+>#>6tje!W8Y0+cwxl6Lc9We3`1(ZG?8b%rEgm2-v z42^Jm?+pphb6Lo=+2%DC)@8F2clCA}ZP}@GQ1vPuHR{sMq;Whmf(*=gNu_o3mCBsk zq6W1(5*3RLQ^t+h)|i>pR?ROjb;Erk8dZVR42Inmh3s&qG-WL!)T`KG+EA+naEO8$ zL%IyCW+Dw6Loh6`A%!Q>KU7HgQhi{sQ-F8*pgx0yf$D30 za?YBiNP~weagqUUOZsYz5TB&D)J=<`y{1zmYV6=5YGA%Zt7nw1?LPSC z=xNCy;>-i+(*f`y&)UpDAH_A8BOq_b#bvF$DU&~qkPCK}DOHZnT5Pjk$)5U{w6R(> zi$oDfc(ywB0#4Yx?5t#Q0vMy$XUSXI&fz^G2iQ&!PphQ7@JuT+9wS##5Bs-FS*-wx z%B33AaWEOQm`KhyHcLB`A4&+y5s?4WPstcmyK6c(Nt&Mg+QWG~zL>hE#PNJyCl0LE zojwsB_aqPcV!yFimz|11V7RjU!6wWyDiF?~#nZw{nj5X|$e)s2o3K!1MA3=q0g3lh zlYDcOgil)O{nmXpkXlo!pG>w4jtF)J1?@)ffs%4+FG3X%TcXCq8-+=*Y=3Cq&hXaV zYUxuHA8#5?{bteZChU@Gpe{t$vhtl8%qk)3k7~Ela97GwQ8BfT5MJzcxGt?aiwchL z)zHgPj&s-j-~8Jn%U4{-ImS%!4rVoW0Sc(D0l&ctqp$mU!LM@^v%%U}J-4V=S+;^$ zMMM|pKLwCq_Iy0TOCF=c7X=cwMdgr3w|~X#4S?gNDIz1gx|8u0aIw7`3Z409Llr+O zAQwZ*-$=Bksmh>qHYsX)D(X4e$=AK#NU9c9;3|z@6XSVuxsm-=>63OL*!$9|HZDQV zM8&Hc_+ZW``C3}TW4yzxhCFg&v-Q<7q`1a#Q?BEvEmnB77MgD9QKb``ci-dde?Pi{ z9liu{p}~+?Jj~L=+|520Aq-l!E(zIVa!c1dZ`4;=}lA~0-|O! zw!K(5E++mfHtkp6#24+$aNEv!+8r`B$iI)7^3Wq}F>6@eG%fXgYn9!l&@?nxGXBmnVR2!n%$#jgLH#elK_^*HY3MI&{1&E)G2dT$ zBQP29J_KetfBF`b-spIHK1T;*?~paQ&W@s_Q=4dU@Fa^b!}O0phpd?p!&75$uVTp+ zU7j(70vVAi9CZM?M-CfbdTFolp9$MevxVCd=q(3U$sEb42~HWD-~8@T7%MNea! zL)l#zZ??Q%B_Dwb;KTAX!zAz2RdfTkcb*`BiDQirGhYKkj1@A7C}(q}KQVm`*z(q& z%oxZJ98BYW4A?o?nwhW;V$e*d%sWTM3md+d27fB5jx;n>qFvVWVwmO*>knli>{J11 z`-wy|-&ZcVtSFB6(>3nBQE9bOP_a>h#;HNWwQ>l?CTTTptn3`04ENP}L23i-fCOu$q*$Zk7cqT?Qz3q-hJxHL%%7*~%b^cwq zr>;PIf(PlW-i`FE;^8E!@)AbK<8jaF$W%c0E)IEMx`+wcO@T4MrSX~6e2O0T4g5m? zst)HxwxB7q-jGv>vSEXXaZv?XLjcj0{K~G0UNU{<++GYV-66i=n*3>NE{wUiM}Usa zBwV{6^2FG+rF-<`DnNeZEp5T3%+hQ&Shlj|dHJqO9WZ1V9=O$#4BbdJqtIn!L%aA< zQeH~Ls|K|k%r$*tzEPc&Q^fOK6&M3Cx5~A+Vp_@+&{UQcU7}5us*r!_y)Ezoed1tv?$LT>7TEWy&J!$4@k1x{45&>^`u=T^ogQDV>+vW}@6b)j zJ{p}A(_(DffA!^oP8lW}?(J~o5<||lhl34LPhur*I}CeLXeKFq(P0Vx0+?D#DGBfJ z#*HZ4NdgKk!@1zeQEc}~r2J4>TjGTE>=4TO}kfh&-C=8{=;l*$g)m8z9Hit+R~%)HdarYZ*VZ% zKP2G{DY|<|C;0^TyQP!VaJWa+=WchIaW!|#Rd?kmokZ}*UO=jSBr<%{-LC4VK}B(w zCMx$9^Pl}qYhs*=^Q}`@5cLkmjb+4NY_T3 zr%Hqw*Vwi$>$^Q3^&v7?>ojM!Y2fhHiv#qc)6|y2QrDwxuv)ldeKUZaP5@ zKs^X;d~z~&8C=t78LD#9{2*ZdS?-S`XJUU-8n+o$a(WsQz(m1gK)Vy(1(6TJd{-lh z0i5D%Y`RhcUw6Wi?KDDiA|HL`9Q7~s&Q3f5PRoHAF2v+De4!sg6de1vUYPhmyg;&p zb|PR<#{)&;lKSpM)F(^#&j^e9RafY-`-YzGDUQz$ki3Irj}fLxGkcr8Y6$Kp#-t%C z>IOvkR)pdNY6?AQh~F{aR~iL}z4;=f1Wf)iebGe7-f!LF0Alv%b0U(7)t`!-&KDQd z9ofg7A5oT_@J7J450l%+s6*!x{IydvwOgM!G*fsZkDdzEcN#BY*FlL_i-5EHNi~>O zC@`}Ln%Ec0nc<2}XO9Lbj$fkm#a8a$N9vFD!r#jSxK|5 zYHis>8E2JdYO#z!*;&!?#dtB*86x`dDp=;UnIe*EVteOA=DAmu?A<`w`6Z%ieL!2i zlE(3eusvVjjuE?}J@{j|Y_D*)*R#``U)n73LSCrA#}O<>8{T|;ymxJqmQg-J?(*M{ zEXFhT#qRPl=1wy1+^Y^BJl@ygSOP>A9t*(PEapN)6!ni^7gnQlr z^M&c{`s57ZAKra%==P7eY4g!B-^QiACN1tSAoUM4;E)@m?pWF>X557}9VA`$LVA`h zI1u9{LW&-d@mzOoBSs^#VdY#bjl1lBVdz-1AANC+!K~ zM~tZegnbIq+e__$_yp1=DF$eGgt5R$32I+iPVmTv$R_V+e4rfRs>&-g4ZDIAK`wOr z0U!_PS)?gQApNYe4k4Yf@nh@rZ-@Tm^U|O>yItV&spv!Gmi$rc3R&mn7g)kIbD30N0md)IKJT@JlB7PC|OypD)d_P_aJ);6rO14 z7FIAnBWLmYj)^*MS3IeD6z*u{3Ni`|yh=SmckqYd9mj@cxSdo|SSAY_0H&*YXC-~M z@)A48D6XUqol>M-f&{pbzwPX=JA zmwGj0ce22V49BGx7K)hF!IvXDf@0lL#;wwYFgNV{mBmS$U+OX^BEOh_A{^rX z8kYN`!)1NJ$PjzPGS!edn^;H$Yb9EU8P{|;UDavd%GbmtS#B9cp+X(VMBMY#5RI6L z3?B#|GgG4|YNX~HwxJZ|okDFX0K1z-<7g6xraag(eR16^-yU7lu9_0%8{6hp_dkC1Y>3x8z@Fb3vh^~((j;;EM28e(#C!6^MMIZ z5>>77Y?wnglDImW_G?m24(w{joZyNzHiOq;knRe)Qu0|gm`<-29^vq4SYAm7JCnwt zWDIV>OMd!e_Fx`A+XwAga-m_eGhr?9XWPZ@aQN{Sp=iBXR_m(TAdWOCsZENtsK%D5 z!1g_5)KX?j3aUghBb+zxL(U7%_oYRWQ;ritOcj=Ho9YJ2EZiiQ;U?ZY5z8pepYca( zu~AGrZ8&JLGr!a1$-JZ}~pw#ptKqegp<=PwC%k;?8?l9oYbC#bPPj*_QL zyq;Mzy+kts_n4McYRcgm^GXTJBS79b%OjQEJsAxK?m7=NqzPqE6vBT9fuRv^38aJ5 zXr~;#WKc)ymsgbCaV9EERn>m8sHmi>sll?StV~waRMY@!e?pEqSsN>^X6LPz7l=sgIzw zr}np~)Qlp#a8|}VHYuv>is7JoaL;-P+reAM&^GC^-AbMI=+;6!Uwiawh5~6`$zQ*t zE(Z@BaC72yUa_$KY{zuF2tIC=+`)V(*>_j0Ag9Nvhv?ZyrR&pLJuo*6pd^B>cc>>g zS17(36u}Rn!^bt-CD=xN&XUqD;{?u*J!1#kiK!&|yF}-Cp1EsceRdw&t707v-zdcH zW!=%cog%dB(JV5>P^!q@Sh8fALD-uFN;(#xfthk=9a$bSd39q`4DCif)T_4tA`7QIq& z4$Rn|{)*t~duI|?)@#^u%M;S7SGN+BCBeC+d!-dpoqObX1r=JbcaqCvRd7YpGOU({ z5vCBrpL@c{JERnMHrQqSz@6Fq@{j9fcZv8?f~b2(n4Cp2G~ZEX>z8JV#wbt%P zk$r@2_4l!2?roZL6332Fn9>y>LhODOG2Ivid2~?_Fpi6}%^3T+gKqRRP^+K7JvTbTtV}aL zv;V39777oa&Op5Xs5P(E+XVIiIwn%8VNcFY85zW$NFD32hq8 z6v=dL6rP`Z9nc0=`Rt7O`{|J8w=nN(fcOw(<7p(l3D2L1ia#ntc0+XmO2|*;1h&g%rK23Eh;OBad_{~z z^46nn6lQMW!x@GSTS&VNOl)_o=4JNXG@MGRIGqkD|)E7))E_7Cz8ocdHLi35oEN+f9YSN;L|Lfi6--VTuplBe_L1aLQZmgpOEXu5 zRI@Khx7JD_bf(8`t{jj>!QhM=>?)CFwU1FrQiqBsBAho=PpIp!xB=<3P9Ln#));V& zy{T8gQO{jOjED#V32PF*48K7K52P#o+`P?Q=J3kpo_*fO7it2J)ekuj)i72I)^-GU zA=N-`+H?onk8mvawxZGacm@{eO28Lob6+ z9F03IO-<8UnSUhG!TO&@5%~)1gda&$Fz4rb#|abo4L(ulwfv>A2U$1B^l=QnS|PoC zhHil!NY111_TxG3%iO}M(Qo++^JKBLgUlj+)X}})pq3$lu*v0-L8&D#Gy+)U{<3a1 zLV<4hae*%14U(OX0QV5y;k+)RC432qWh12pVTRklkDhQ-=%AdKi;3??N8u9g2;NoM zz{{Q;Ull#MIBoRVsLkR-6-6F1a-Ui}y1JEW&9ddnh-KF*SoUr5DCjTJeX&KyN<@-l zkl^_Ua!CgNHK8^RJukxJmZeqoTrEAcfz6jFTsXc4`hf8%kl~ZFW`g(u{ukHvf4*i0 z|D#lm|3~@rzvnthc@eRH^PIect&xeNqwW7~kWjXkTaf?9AdyLJwJ=2)5kq;t$f$1u zOl9~<0K}{mWDG7SE`7ZY)4AH*cm=+vmo6TL^#$~ez@PWG&&{S4)H$pVzWe0yxr^t7 z=j3rB7Csk9+DKIhe<10M?Xklu5_iS(YGtcLxAABj?}bJ&v-%>mHkZobPD0u zAC7*bFfiw{6LP3vdkYLl*l@Pi2j^?`7U&daLE-8@>+gUGMvybE_pXcA>Ja(m`jPqg zKI9OKJ^qGl3@@i-LarN4!(XuFBrK2AS6)aUVIzor!6Zm+qX?7~3rU2ycLhsjP&cHt zYXO8tZDg%#?c__IFH9930}PHHO>ay~nd@gwo@*>xIpj1U4t=lLIS~njtlQc{97Ii6 zZinI~<`{sq(=mLk;JBllIy9ZyJ`e-vu6O(BJ?Qo%A7@H1Lv{+pO`j+PduT^jto_mb z5{nty5V2rggl=~1;oL$oapbn>PONJNV>x}Qa9~k$ls5ASKre$j2OS#btIY&+c;F=z zu2ER%3hD?_Cu~)Jp0j5S##Nup^Af{lf%4;TTp?`5ZKSD~JL_p(1{GLiHpak6L#R&-T*@ku68DCqih0*Vb_d zoln#Zk?1i?K#!}M4Mf~@QFm6ZEH(!;;qi&ayzvo_Fr6yq>OkAx3;%2%h{;xHK!D50 z7}9Hqlq)emr{4rNz9@WP$tH~*g-4P}%`5a#%y@;lee&s)TzHunc?o3GE!GP^CIBFK zAk_a99e`_yu`>WVBpYV~T#U+yMmA-DgG~60BRLD@2%2cn#dIAXWeCmeCa#%vsc{;e z`!9&FsPgD^{iEo)g8hGM2mWUe`9Hk^%GS0>0th~62ozZXX4c6CDhjnp4&L=x)pvn1 zmJEw*h87t<3=>;uEqJbn*LgBO$>B1EqP~9o;~bv)Uik@&@UGmg+z*q?50l%iH@Q8) zZLy(H!Wb#it=3J(ayV_<+<%W;qC#k6W7yO$Qde@rqlwoY}-!6OEnUG2ypfo2Ln?c zBOindTmWd)uxEI!k+lB+1ekhJP;l$d`mpZGzW_2^aq+sntWq{db=AbZ!pBCyy`&>J7B#;ipSfU6SPwVDw!^ngx z@CvG=NRJOGnN*Y!pqh`LP9^D?%~BZ5kv>9UZJ)MGf2a&2 zJ-fOUt7CGmzA5=3-+v7^UT^2*xkDS2NmrlGn&>vS-@xE6u)HXEOcN$dRYd+Wc09+L zr6(7Z!@|nWA-_QM7Binp0>s+qEAxrAI0bBe%-Uyd+dF_R*auso3fH9dl2o%8w6Xb_ zBeexkCh-w`V7_H*6S@*VgUw}|wfh8Q;wwb$qMQ9@jK~#9NM;ncK*@wbmVOZche}fE zw@PdBV>%YBNk$mz<*8<*(T9Q`m5fJ+F}nRfEHj|AwH6is07C0O0)qX&?T`N_fcSTJ z+^hkmue`MMJ)Jq$HFju1$k97$h_D#+OJG)cpa9C!3Q}AhwtPPhc1)j1!j#NP)sp(! zx;a9%PE}s(d>##zTpppX#b&9cMbouveZ5;{n6)_ClUYW_LrYhlsOq}-`G<%Evd|&bN*BH9E_n%wKZvF=0GMJSRtHMQ*u_b+x2Jg zUQ3Gp#=e=QHwBtybcP)a8602r3QAvC@X<+Cg@$~O@pYC8%yG(0dZ)3<(&qV_6gp&) zVjOQLD^er`@!e71@pBvu!#Zet$7@Bz{W)jlcq$ELEh>g{5pF{Z?V`Fuvzv>hqotF9 zrBIm$LxY92olYG|8=o$c4HR35WCwUDj2D-Y0L3ao1XV1wzpJRXBL`B<3{o3(WD3k? zqeLcDpFB+Zt^Hkv9f|VRb^{lR4wm*74$`4o^D;zcDny}yx15UyfMl%=hB`9kV^^@Z zpW%?QG9IlVJ~4cbV|p;H@H|Q@1H0VT8d3}^)y4`&gQ8dcUWF7=vf{gNY{6UGIV<>% zLm&=4CYimJ6^y$74&w>i#;!=me4nK6u4O+XFn%~;{PPhlkd> z8j)_^S(Z<_PZ^mU2H1Sx1J`-Fp@ddlE?atlGN5W$zH7cE)dQ~&lHGZj=7p7LrZCv6Ik;@Qqxn0N8Put zUteoJ%t342=x(U`)A4viRD#P_OD(stH%mW@XIk8*kQdK}t{i$~f1$Ix);xCcqCB}w z>W-jrA<{!hsV&3Khy=qn*5L`RyM3yxGL4mD1k)SUFt`1RU{5AL#Jzehg#eD!9JKL0 zOrJhJ_^pgZ0CMz<(_)XtD@Agbu~MZtW<}R%?z}nw{nZJXlTSH+y`P8%&3KW0Aq#W_ zukUw%g9i~!rgPcU%zWikhaWQO^QQsI=Bg1@~!>*)3r{= z(;iPIU5U&!mx&W^9U)h`@^1f=q<^UiG&9pOBVbNGFnq~rWgGi8AIMTV2<5#aixml; zHKYIyYmS6rh0=yjQ;7enyoLm`JMXvU5)2(e($hhyx99mgRfCy3n&zxUfCnaLn76vw zz)5&?pNB}CZg1T2kr3<^*?u|hM%Q@Z-0pB%u(P@x_*1ntwzCqzKu5$xK_{5&L=-0T zPw!L}yy$Tdq&p$99)Zove!k1$KyaHnJFoUi$6$Zztw}-XfHPEZzGU~frh8?wRf`=a z-N_sTHWoNSX2_nUxdQ(pDut)s2~%c3d<^Znlf zeRF2CUf4VN?TcbUDC=j}5lw|3Rm;}TPV_xeUQ5aop`G@%;hv|0UvX-54Y#~rxd;UY zU6BG+hJ}Sy=lU?{)R**`r!FYTSx8aLivR=kYj%5l=BA!PG`!i@-53NH8J;w0y7w(V z_|7>41Nd>MNl^4oR|$raf-@}sraHNi)9reT%q^h+BhUH^fU7PK4M%xLGr=-mOeHqj zX+5fwRy{#gg~{?#FOLO(M7NtTLo+<8NqEyR%<_enz#>{%mA($e{45r2nuc8fnL+2B z5e&b=1BPfZT|@}>0PKx2W@D)H9{ASgmD^upE7mqH>9q^QkydVtyp2_hqV_jq`bIaePQW1>aYNu?n&$auk$f1d3v;4`yJ7)9w z0rZf;*qQrW7jQ7P7PYC})ctUGWXX@NXxsfa=KV1zDhvTZSc;jv$(-UQCaEluS_AP- zd=Lt~m9K@t{ChLg@!Sqet8cNLb!9b*D_@ntfA?-!&M}-Pl=fZT{Y(;aMK4`%;gN4# zkP7f+Q?u@4Bj zi2;w)X1>7o9{&okN=07m#v|=iJ-6IrCZjo{!zobzRp^dKHGq*AgrxWgSXE zkTes>h0Jm*N|&F_x~;JpWuz~f_DNFnQ*Z+Jns5`$3{Xwl|9s9J%V(qb0crR4^UqsWvAgu>8ZR;uRgtk%jO&|uP6 z$$asK&e)hPM%gwYKe|i0CXfdA-!I@_h zEk(Nh5GL60^Kyvr5_NUPTIllE?$}{!h*XoJz_Zf#DVIhk+ZcmO)?m^B%tBw4B~3rZ zFtxTyk(lsm0w2?GP9!kLi6xmqj;LdVHWX`u}c;;R2EA*J5Q8JZa}8@SUCYbCt$l}hb(u9faD%oJ>qW4Fje zN}DT}QM{DE77eY+!A6c_BA8#u)ys3i77nLbPg6gk-)R`{qG3DU;ZL2yQY})rkyfLg zbzP`R^Ba@7sPjlrs%N$k!+zKUg}6j}(|2!o_Wh8keK3LktfvtZu6gB-tyY^$A8L2< zN)_1chzZLQKfw5T=8E+(_tWu-I_*;`N^`)7uU)gpaGaI*;W;`>?DM0-ONO3o3H1pn zsB~inRm_B6CnIUhxtyiDQ%X&=qgZ)m{0Ym!rmis&I@^(W;2-IG%Hwv8Xh)up9Y)lt z7_ddbLS(nGLDMeMQ$AYs(r4`f!qk-uSFK&1;B}#70-@yXP`S#rZ7_D#kHxhcWXGG$ zhuCG@3%L#N?tr}0a>(7VATg5=ZoFP-_5*@#M-|;AwJ_S5Xv&O`P5DxCZshDL^HiP> zD1mwzeNkEA7uR*BQww>~aQMs9%fylGL(HkQp}CLzd$aZq3mfsm!D z&iM-d?kCSAZ!)F}cf*g8#bD=kYQTyzHJ9=^nt(qc zSN2oG4ga*bCbM@8E)cJipsc#Ag4ez2A&RFY!)H?(58@lk1)ZC`#o!~ruxuQ=hUFuk z({xyRpX3N}C~Qe&p=u!6-yvE18HJ%Nuoc1{lyN2Zn{RQ9Ub)5;QY82ExYev(%|`^T zf`1aKHHgS%A1?+G?n0xghj>Qg-qJnCJ-(1>Y9jECVd%0QwU48@tnmB_*L^M_wMl*S zpeIeW=E9uzBy3a$av6aNJ?U;o|_uX)^O z8M!wZb5qiJD0~@EE}0W0)5N!AatkSQTFp>$g<|85Toym6Yt7q&=T@y@r!mXZM8&rJ zQV}2NI&$LKMi`ytS@!WXhJKkIXOfPLRvBLxSr*DkAz!$#o?!EU?1Ud7&+2x_)&Za_ zkH7NTo}EMtwp)u~QwLv*4-v)5)X(5^{W9~`N2Z)K$POVJ^=$*&Xp`VzA(oxG)Q!JsV{F3 znC2)WSbl?s3Wu}#iuTH5)tPddT3I!9UU61v%mG@XAjH`j=6pAgB5IW9Ecqtw(x|2= z1!%L9^JXTbl^(_v`z(b&^h-VNS5s5iiTEuTPTj+IDB?h^M4Re~pB)DU+=<5BiEOjr z8HxBIOelh{lMRu77wb`d_XQl$>$NKVNp@SJ|v2`udj5kvN5$BuBdxr5%rIm-}(+S~)9kM3@ev_CD z&EP8tAu1gQVSo9B!OG-+&`RiuFIzQ5f7u%pDt7GNI^c8@w884w&nI1hyZ=6cmlWj? zy$-!FVAEO&Qpy*$;G~e+eQG=oS5@M5B7f`wD*_@)97I&Lr%o&@f(#&xTJy>cZEmpx zi=m(xaPm}pU>rc}fiz-BTm+xiztJ{!N=P;czzCXTE>29;c^@7)8Hj}X+!;jKgH$92 z%pj)hav&c`&`JlbaxrbbJkM(mqD?x8CI&!JQMCATy6k8l=}#V$F~9k=tu~`Sz~Tt2 z0K>d#@t*xD?{uS|MoR*;NtIX48~!C1Q37;Gc5@>_rup17Nqly~@%?R0%9!51ME$#R zvpx5=42XT@{f@%TmV4b2ifZ9iS3Q63e3Zrw;SW?igO(8op#E?XhQgGjvH)#RRX+X(>uYKt<(toUSvBnloU#7t^AId|*Z5 z*}|3VI8$G}Ryx+#3TQ@2F3Sz~0lu{gNN?(oK= zxx-&H$$N)jvlgL%48NZOYU7RaHl_;9J#NOM^x%5kr(=T2%}5qkk6xwu+t4+gd{=^? z9<|3$EWE+ltX5y^WY6z|(fQ@8`3-{0?sm^F(`mylYs+zzk@o6C0XTN~vM*6mDXW)$ zoCz2c>T})TQ?k4~c15b2ZoS%_{~mr6@3VzQ$@9l9Z}H^Ce8;6GvwY#Z0Xfwybf*+N zo7I3_R%-XEdi256{N;BV9axIugi1y?@;|jcD?9CoYHBN7!C%@phJPa$d(XX_3 zh5aOgePY_QHoAZw|%GAA1O{iH2t=3lI%l z!z2wL*Ir3+@K);WhRZ)}CK6$&qtoKzJ_Ou^-wj!Rexu@za^BL`@y8uxWW@CjiZKEV z1;auI-ZM-qY68G1k61j{O1xr9T8{}lm;f$eS`W26S zI%>F?^{9S3?M$Pd&M^;{JH{N-mI}UT@}Kz)BHLa zpj;1}HU}_YkcxXk!7jA~XEgmG5C&}mZUW5&0(3Y1BiDLhKTcUc%n#rVsDIl92Oi!s zeC;Ao;_Yq3-eUMfk)I{c>?w2&x9;4%xq6)bJvIFqPHei#9k=5f**E7zL6lztkA*^uDP zXF){eY-OSVe=>T4Q9{!dX|h=!O8DVDnf$Oi-Zz3lUb(uTu}XL%g`iQ?E}<&LOLKl8 zX`VN^wkRqs*bu^uVi{8GS|8kLXKY)P6~T~cKZzK{T>k;dBy-92EM$BfbwL6!UQ2t3 zqFiIUykxuUlE7_@J)(%wr>VXmP9qYFGx&*qMIeS0NxvCb2&X#y&9pO!s5I^@p5e!c zr9IHlLT_d-23j4JSr+ZpqbmY|$#@sXVk`1I7&xC5H`RB}D6e7wgl-mk4WkX7$ z3*ho2%N*~5Hq(WeJtPv80toh$`K6Q#uW==TvOF*zQh327({F?U66~;{vudU!#Enjl zpxT+h%!Bfbo^Jl!`9EN?Pjn5{__43mi`A@?uPlRi;od(rOnNs2>uwOTC%*v$dQVCg zY~a#94wu)V{G)!i(-LkZzh#?f_|stj>A85ZA_X9rU6T1@P~4EU_T_mlJ8+q$+?csT+s;qv(d0G5xbHsbeF7# zYlh#Likf>KSYQs62(R%~))LHVZp{<4cqK%7*$kL4ak18Y(D$sp@5giJBn-uL1YXmT z@5o3m_Xx~HV9ZR554HZP19%zpV z$UDsS_JrXGtvnqVVunmW1*lCxZV~`_C0=nGvs@=LvTV++(vIvAQuYpb@ev(BCLP=b zWc}Ht9^6n2crk|NIx@DOV8Ns+9XI2Y8J;*(6dt8aPIm~IOpyGhOMw<#wBn4-Yf?Ry zT;>%?^rwFsVjNfQe$Aw_=fu ztd}Jk7V***?3gH?3OOTJ*%(%#Oan+MQO==}dI|x>RI@`x({(;+aqB2?1z*EPF=-yU z2C2ZuFfS{v?;t0fAVU89L3lC51bT|SoG8-GP2^vbSYK$+`=;?V*nx4#{Z#oDP4khf zA^*qFJ&Q{_b?@q)nb(*E@g7rAcW?h^4f9 z7{dpl19!UjF+87u`H?qK-M3-aTsh%MnUcDa>`4~<;eaw6@lQ)-o{VxDpygl-ek@*d zh3~s4%l5D=$ZOL8gVi%Cs#~-~U#&wHtB|y^Do|{hSdXnvS~oV{3x8mhPj=av`f|9G z`T6eIK5}dRfQ>-3C4PZD0;9df7+<~tmQM|B$M*1wH3XAl+Y*O5jn(6C>!c2j9WyU) z-3rDOO)l7cHihJ}h){GrCfhN`>Ateu7Icvxw+hZyYK>Kig>xT87NwqGju)m+dlJxt z1KtB_8i^Vbcum2Fp1;K#bjrAz0<~V_i(3S-fk!NLv^@(?z1G211qr+mUlg=mf-^v>Q{2@Ti!Q9D2 z-oU}YnncOSz{*6<&dJ=`+{3`h+}7sb5=XI;wGxs7icbv;&F(P_T8QSeA(&Z0F&lDG zo)EJ%oVaD{9!9D56Y%grq!>0E%=Zqug$HU5s z(yvH$RmR@i1{-!o&;8z4V{UvcPZwqt=Ak`8R~HKNlI4iZ8=O6?%+0-M%8fik2hOZk741crRVFu%zXmLLQG1|9SSMKd<*z}i0Rf_@l^HImw1Ox!9r ztWY21nf;4!BnggPqqTekn%v_BR}?0X@|_=Sj^l!PyQt6r@gSz)roCIK4LdjN*h9`l zvv=Wj35*&J>fu#+&g4A=_*X=w z(k{!t4EDMSV_kGEwAU@TL|yHR1Pi5ssET>0G!u3_W1><+L<5(;EG8IVmDVo)DN^~a z>#5OWw4f9f-55#s#)39E#t3*pqMw#QTtP?VFK|^@n2IrsUO|lTo44)+|AV!63KFf` zwl!CBt+Z|1wr$(CZQHhO+qP}n#>%YR=dX%7Q4x3VQ#bD0jF_*ZkKUs7*1vJ?m*EXF z2)F}ArWJXnq=O>Yb$)|ijH7oC!GzXJ_1+~F;CI4!2@)l0@KbcXPu4k$0~M-zu?$s!%8mEFqmB}$6U zxYX1*M7i+_l@Ux6aOhz_!v_{oUGU02%}T&~$(CO11C{;sUp z{2Hc1;sqM8?V}uC84Ui~Y|LS^C`Qs@9z6cZQJCrvnKVukXtcTqZ*-#i#^kwV+1#So z$sJa-(;k?axd@=LUr!gqN2p()_3QIXoM1E5A_{2CQ?gOhw7FR)I_)$*ACL&>Zq2JS zBrsF_z6pCRftDIiay=_3mP3P*;>0nM0;T`QjJRytL1WZu=*(dyaDdi6 z6@2tVdui(bl1ZB`Sv^BgZ2!8Sne$~H$*HnUvJIL-w zvnB5>b2Y)~Ep(4_2XbbYC6K(q_tA3MjCWH75D^jmTraj9;?TUG1-adPS`d7zrD7`q{`l%f=(0|u4{&RRr%=&*B6s=O$ zu*Fb8`-*mUs%sRt9Fb3FL?MY4ZmtzF%}3-QtjlaI9Zk_Uu-z$(F*>uczS=hqiv~sdDLOitvW0LLO&S6?sd6W~ ziBwHRtyO3u^VES9HU{h!dKF}Yi!^sA9TenIn#2SoPcO?4m!Bz@3`qO+uyWhChu(q~ zmN%;RJyNUIeH?*6*{&CJOk*>_uHM*tmU7)*3+ep2Rm$Uo{#&H=YLU&j4QUN-fr-Ni z5%5e^Sf4hlmZWvc8J-A96`v~Qtlx00N^m_}`2*f)Wtv2IBB;`WBS^4Bfi#Jvw9AZD zC^cHzq)O~9|8PXv59%D)sn`Q$=g;Efh5^U|6$;wiWr_aZUUObWaim~L_38;S>D20^ z0{RZxWpCN`3nCxe5S}YY9CIQPZ%)G5d!920T;kd=5%m-tGp8!EmZY~oxoMT+X>HRW z@E~_=jqh{()Kqsg2Z+Ovgtu-0-)0Ot&F&&$x|emulUp9^0k441?{~GlTk{xc4#sNa zt0b^dMJYHH-G^gZt2AJ5p}ZQFYr*8y6+{DRNg(svjHvt=(64;LT&_8VsITyr)oQO>bAH_rRo0Q3oL7Z#i$`tAH!Xu;nss?zB zkRbx)buYXiLPs!{9Do6d>;;R}hsVg0M)Ni@i**Y**~vTNA1Q3=G8sj{^Icvd(#C?a z=N`_Gqj_6*m>Npjr^JkUV@Ot2#a~P*t6nIsw(C18n?n?&0suxy$>WE?;iIY#7^7DW zs`xDXS`Vwb{DqZT2!Bh9p&x-@t7pi$JoF&#GvAQ2V&CJbUeM;^w?3(*>fQ(E{ zt-x7VS9!6#GB2x)v>&Ip)a{kCW9%`2yE#||L{D*k;G(ONXPDB7m&wifN4feE)R0ay zT?4p4TyGRNTBa~_d6@cOt>Ccq1U+e}&#>g)O?qcVv4Syvc_w+yxIDB5Y19$lwe^5ggc)=R%a!cqvqTqAf9iv+gnOVT3 zuRi6Q$u-@=KlPqw5|i!~h0b*S>AP;ONbDoA#bhXob0n~dvg_W?z7cHE$lUufa<)L5 zt$U2Q?h#)POEmJv(wmuWgc)OxJgg9*8QUx^TQc^B_j)rM_N(n=MDMHREFQU40OlGV z9Nea0J0?!%wTj%jLFN-fuZ<-FQ$Q6zk?^KRRFr4LfT|@P=}Yi&JJcKGXTf-quV(Eg z%6*Kqp41aTBlx`P0@Xujj{3YSx0!kHXxlAw5!NPcB=Gl?0HWaIgK^Sly0{qy5U8=~ zU$b>?0h#PRlRL_z89koqgI^qqDA^|PI=HSh<%yVzK`xebL?gFS{=|!xctj(z_?75S z(B!I4=G=wx2THR+c{{^E`?NVw@66cFW06U`qAiZm8wU@pRI_uf?8n0$`DKasMENdg z7j{8M?+(NtP)AM$=C~UuUEA73@i`V}DI#x2Kn)FI?X(d#RraS`X{ymDz2ccnHXLlV2O>?cTYHda?O*PjI42G}wds`m~h^Y_F}T1DFyg%>upKsIo1 z6@p*v&wIo4g$WVdBq(D!mH1T;63=SxHFrKJ4$Qp3%n8#bNN+}ho-bgKT9%KuyKNr1 zpEA=#TlIGqtUNI&T(O&KHDe=HZk?4CPFb2OO3O@G>hd`Pl0wxg1TW(+nqAShixdlo z5$+t*XZZONkYC;S3P{^qg)S|rMK5!KI|S4BTLC<=BtC{~Utw_Gyw{%~1h2Arbt*gG5+aRfy|n*p9H2T2;e#;o{lCYsw~Hy_!g6%Y@n( zJrgP+8h44JO-?_C=Ce?IyG|f_>;dR?-pR(uL2>84S|%?%gWK>Ud3R&=7OCo#s3`(` z0PRP)Cu;e`?KFE-8o_;@Q{ytmpl0XByV8-1s#w=oGEfx1;>}Mt1LAqR+T`K!=&=^FHTv3lG?e9U% zxwn=^-kn=5wkqvXkITBQR?DwK&1NUTRjdmT)GD{e#@eAGL_)n?-0*f4^$D$|oBb3q zly&XpcmcuFpFBYZ+vG_=toPMr53;Ak5&3o`I{8HrL5W;$rL5<$pbOz~G-ypd)2MAp zG`Eq$XfnV4rWz+mqx^eg40c)y1L#AV5~!0;s*%)Mp_a?X-}*_>@^vr|NHY_z09K8D zmDV&ZxL{iS5lc{|`_u*iWbLnXLJRtHhKz7>KPVe53_J|_YCI`T@Qc>VXBKtX)MQ=< z10Ah*LE?S)bxE@10lgF>!&sx(QZMRwsYZh$B#F_xh76O{0~PcT(;z4j{yaugW|{?c z9rZr)r7>pkw#jljt5uM3#sNMbue=pHENIfqODVJ#S7$C@UD%~#e(ND=vid$~;t|EG0){#fvI zI0y=mD$oLpig5dfI_ahI?oYn!sK1N>T8~7>{n)OvlIeo#2%Uusxg=W1@6PjPvI&=Y zE*x{hHlBrAz8If3x(@=4Gi{z&=b0NV8d(po?Tb0H2i~D0WADIYA3lh@zt38dvU}ki z^I<{)k$_!V0nH1t2;Vs`U$E}pB2H=W6$cZzZQ!_sE?to9>O&WVAW`VV#alx~;ZVX` z9bxXi^BotQeZBk9aou7q1qwmzF!A7&sE2w9vk~q}a_7aP3|Y)l*btC$(+UiKfANmE zh{AkEN5AfRP0|vc(#ia74y|~pTMhiJnoh9b1X{lt;IzDT^o2&l>+fYd@O>HW+9TBkoj^CgWM)VqUEl>)B(O`S4o_Y!}tze9`OTM>912KpO|X` z=S%++OkW2PEtrkiX{;$&*iGYKqWCxL?HVauI)||PX3pNMwl9y@+YoWDbfGTc{n#E$K9}_>ujx*%H zYZUbVu~CRw{j4tjGzkG4E35yDod#vU9Mlmf<>>$#g7PN9%9!@ zC03rrkyNK=TqTw?(+mC)6`0F!9?Vn^KeklLo|@tKVbZo`hhIJ7;_+0K#m$>l z|M)Sy#)lCC%^*s+scle8g8azknUWJhIN?hhF)YbL77!Q+a<~X(5sPzZGj&AN_fcB+`Y(} zHN#xx$z9DRJ(@bwPpRdA)^qi6wHMK8t2}K4GHmm^6JdR_*1RP{iNJvU1X|09B=ha< zKryGm?JueT3t%CL*ad&+;gX{PF+) z0|fa0v+4Gac4|`Au)z>Q{2F%NAGTYFFD~X)muzDb%Q*6G5W}Mq+GrOGl!Fxnlv+kG zS?@A)ZU%EZKC9WZ%w59O=)o7c%KW5*ftbyBV4vAHO}kV-%)DTY;G?0iL>Ovz)QhFsB2zWn zK$7rAa*FvGhgI6?ajz!<6c<&&Y#Vxz82X>3Of+pNbw@**Lv|%W4-eF;rsh^#LVXLL zAZ0U(@J+a_i#YIBoch5AJ;5Og)F~>0-99;<_xx3s;>3|@q)!#6yf7rTm?KfZ~F@4-u#5D~+vWGy-o(bH-FNeki}8w=t}4 zI4Iqjl{*7NJ;&a}GG@Lh5|q}k<5CgYGx!dU4DM((tC38d;h;yUbqD;{8CM_2qt0Q& zLbYV?$gPoX^sLNi1m|ddEPtE}b;=V`*y%qg$_JK;ftI2TJ^wsQy>qX@d@#Fh_jMSK z9pB7-dU@LRjuX47zs6E}UDrlP|?-a$dwar#%qu8!J`fBT9oquUn)afbltvWN3~)h~#m6 zRQWqLyYDdF<12uitb;^f}b2k=DDF zRUY+1^rU_yrev4_ahSc`{rOT13cHzpZ1GfedR=K$m5qnduP8)bL>{ZGpeXv%)O_{4 z)RhKSvgAI^@VQk9@%HitxilW4OF%%VVvs1ub6;yGamqwOqm^fVt1HLH>M!6r^ZB2? z2y-AlPTbv-3G43r0%hGc`V5Jq5RP^SHVf9I4R&r$I`bn54&vWcb@I)zFuBsX)f_&A zNfXBEnuK?-=Cy+7j3ASuGnx5@W}F!~+pEhs{6N@`0xYwkMS*Oo6o*uPL~+gGTU}=Y z%erO;uz*sap0h=blfPy_X7O)%i;na)yS1!we`)0wlVr3w%S0Ae-E}Lni!3>D1@$Rb z^Dt#6%}nPjqmRvVz`Shwm@<3K;wDNXQE`AfR%qh11Ot~R5S_0j zp;H8)H;W6laVd#kjjGMJ2p&W=I!RDlKvfC^nm-^EXgQ-=w0>VtZJ^c%QD3B`(BDh0 z49>A6aUXHk@acbeA|LydH!fnB&TC)UEH&Wk}D0=K8tT6!A5ygq0*47Q2p1uv` zeZ@{s1v6L^OIu+YdXrq)W?AqSJGBFn(Io*xI{8DT;^sY5FiGvM8v|@m0+)A}h?Ry7 z)M04oDbkPzAE0x=E>R4|kZDM;HBM#rQ6*4NDW3A{@T0s=9AasxBWbbKeWFb9s`^qK z2}Z(O2IF)XrSLJ*Z|gK^9so#ZFomBr6o4li!SqwG7*zh$D3~4gXpaxb<46x+g11M} z6?OnrxEJyT<;p9X;gqOvR8hmF0Rnj z1gQ34I;`}5q~UjXmeb!PHGQC2d@FlKQ}|{_!Iv&9JSU$8UTlzI8nskZoJQ=K@piLM z)|ZhY;>P>Pl!568_O&vyr7`ZRYk(7K!_Zd^5Lak>72k^Q2=@W0sP&FN%;pUWw?oDz zx~>S_WR;<5-LCvBPFI-xO#li$KuapJ4K>ztkjUTHjWVg5p4Zbm`E_QCIsyYmfdzGH zR=eh>^yx{#s*fzD%HoOSJ%3A~Qr%n41|MS<-+!9}wfSkQtmL3KVW^^IbfK^3l0hv+ zrJpf)JwpR>x|{@%Sz0byMobqY*(dP3Bj>GFmgTtkavuUJiR{EMo2SgVi@BMB_&of1 za3M+F6{8e_QD0TkuiSg`+#UzN=EH%orGdU(=~rNlEV-l4(K#4*xuxu$8UMt~9*A-4 z2?h8}ANoX|Z;j^L6a@9zEAzR1>!I9+i5(JG=ZG>tbMgeu0ZD8GhZ`0Yw@Ty`kB56C z7aNv7sExj->3>2Vze;qxJP3*EGS)*=u4o!?mF;>upp-nhVya%9ms(87*+eP11}g0u zxB5~&ZNs0# z?^Q6fSg=Q|kfL@s+VUZN;SaItay{LnkFupoqaYssXcT5nQX5KD1Tb>?S4{d11u-ZlKatyFh5XfO#Mm9Cs0!FFJP%~i;0_0a;pT@E96B*H&BzY%-| zU7%wdz=&9utGA9gYLs#DPTEOpqb~dKh>s2VBs%vF`)cqUHM? zDKafCa)x}Je7kB?%7htxa%|zHWmeVV(xxu3TF~fz*y!X)R3?$IoibI-hHX7g<@>5t zL}_m0kfd~13RM?wWb;uWjuyN7Ow9;ujhdk--$$2>4vURYeceEf=Byat&q{+d6Ii-y|@*tT4QJMSjBFD#k(gt3>cU^cc`&60BofOFL&9qJ^ z$h1ScvLnUcBKBHPZ2e9YB$!KCjKC*^2d6Ivawz0(JPIl8E}cco#Ng<^KV}B0WlvUZ zBm}^N=a&ONJM-sAYI$X5ZqS3m!kNiiEFC4SG6l6fVlqUBY}`OvIgyM+Yg{bFe!eJk z5KlZkp3n`)%1Y(XNqw_&^ux@CO2aKsT4(BgS`K{P8N$Oz5X#mYjrDHp05ZIt0`{X{ zir%0w!I;+BPJ3EX1<^PwqInvN$7%W%4%H;15vwQC8DqYzP5!#iUNKi^&TGR^94-`ERa0t^?miL>hgE23w1Vj79(AI$KVS z1ay7XEjxbNASU{D_%E#@itT=V=_Xol8Frh5QK$AZtmZl%Tm7C1ZQFo`D=SF5?JC7B zAvs$zo*H9vo?y1Rm_1gq7F`|{CnJ*#bANQEYPp8_lJz{Wo&6ZulMx8}*DYaH+vBn7 zuY8Wf<%iZsgCNp&Ab6y zC(k5<-etSsRHImxGCj2OJL~NJcxM)-^nJ%R5yrKFBc3Y<=;o3Moiv{txx0@~Lid>S zZ|yji3QPxY?hnLF+zW#8`(k@@#!z*TvdwvH2laG0P`GFs+bJFj_w+r`iQW|>?`3PF zX`g|)=J7N>?`J==PWBxhezJSgGbSRhi|>C)vTPp0QmP+01_uA{B-#Jit^TiA)jv`! zDsD^~kPkNaI~V{_#BK=W#T2l>j#^uDzmF+B438R<)fD}dmx46`O>>{YZC21=zwh^L zUgQ!%pIwA@dAr5e`^80i%1VjJuLsl^yg^;Wt8}$El>K>UnXd4w)S&Xt?qlmP=;$_c zqZeQrqqYePI#b92l23n~wEE!*{`KSLaVZdi%R#s!FA;i4^_-+^0-nWwqA7TRPC_lY z$_jX(357h$bB?trsvUjuthbo3X5$n=3e=p2SuHB$=-P7hv{F>ud zilr{O#dD-Hx=HnK9Y6kOz`%vG9smY5P%ZZhzm(vGv`)b56*vJ1zXX%pj^ycz>eG78 zgeN4OoCtzQI4l8CxPfl3YviF~#hFuZz-cl}$yk?EZ~H&#>wmHsnL_OYyBqPP>!PfQ zjf%^MxgIro@&fc1X^dlYOM4(@!>Gp_#U2^cRYbAm%?*wL-nAMyu2oPas9;(C&oR9ql%y-TQU69An#we6?fFC9Zh7yKh z=+BYtFiYx%8$zVA_ctE_=oCb-S-e5@_^;p*4V))#+Yc5`DrAv!2BDczL3J`Rj8E8b$`TM{dp6;>;MVOc!3Jg z+!*_PEhH^5Tf(pc8rE3tB-hoeq#&ImbLs3dsim*joiiGp)(6EFEVb&_rPB9>{?2&W zAcqMl?yxxQY`WaAeY2hLoMcz1Y=7TF{5q~!vpytcd>Z_~_`v_&-!g*m@%v*RC~9kN zbZ;O}kOl`7U4vugw?WrlK*j!?tG-ny3#QY6Ea?^sJFGe6R<>p2W5{zb215-J`1(4B zvi0?5qlJ=+$x5)6g-W%`=o;E#M5rkEi!n|18k@Ad1|i(Fm~k?t-0kD3P98weSOw`c zd}nh-RhL4ZXQwZnhJ^l|pid}h*z%X3D zB$sx%Qd%!SU2VxBMwamUtUN#UKg`g9?)}8!a{0R(7}B5y0b{b2A>kAi0ZdscTlIr} za=ifF9&*2W!oU^DTa(vmO;mIYY-GQjfi0R3`SpUuz_{Nl8Q>vmFwSqFNtlD$3j}3X z(Qd$mS_0wW-mGM##R7jH%mM95g~?jl;wJtXafhD60iKvW3eE@e%3Bt-m*u;2*&a45 zHQ^D^w{cdwft0UGF$W^H?xm!WDn+<_0%U7taD`gra0a#-jUvvp9b3>uAilS!gJJ0y zQ(BY}U3gZN>12W5=s27G^>;fG1pIopz4_Vd$_jDw?R&jvP+}-Tk`k0H5eH2(t==1= zZPCKbCF;YtPS+5c@&5t-o=qt}oBe&lMl- zIxEiEH~>YWPJVN-T-H!T8Db+<5_ST9g35|`^VW6FPh(S6O3-~vmLA?pGfgxQ8Y=zI zK2RjTjOVWG3O0K6Kpg|=4|C#Ya?dl+cybVJg7KbTaP+Y3Itou@~^iXbP$I{?$BQ0-p zE%UHj%apL40Z=t$`nf3V`1pzXA=f~ceNZ()>B~OtApJNeH@RK=8dfWl-z<)!!J%PZ zt2y!M_!ay@PfhhI<@`NisQZt?^xb++71GhrRFBM%?Qx<*GvN<;eTJzR@u=~X38vz( zX~Jukr{~)v>kWjiL1np$RiCRy+>b-`K2m3B2*6W6Hjq441Bqf_>2LhsMf5W0l9yN0 z$$Q~9YYeE;?Qte-MNEhnP2=H#2Lr^{!(5K;I-}w!Si+z)8wo@OkKoE`boK|_Wn)$H zS=1W*D~>qg!B&RFq%bh*oQ<0pm1an=FsZBlWAUUg>8#NRjsZ#V7^IYxk-PF>wRS67 z1qIrk@Hx&03XMz!c2=ZoCi@Josgu~9RB?#@ElZ6>nMqS9X{SH$Pi>sC+MsO-ibl~l z3O&=3DTVaH*_1G&b-fE&uM0eKBT$R&NaaUa&J) zY8ReD?QPp7u{H^)#P!hMF-`8zi%M=vQnZ;k;bZ>LyL;&F;yaxn_zDEtm;Y9Ur5H&g zpLfQNtw?s34}}gdRnb4a5NoHDX5EMP z-*ml_cy2_os18@R%?|QN>>&v(3+rzfZn#K#vRGJmOnS`>z8FbIZlIq@J&zs2NPcEW z9^2n1|7@h_$Q%0@?*H&y74rpCL-@tF%DY7yRr(7xP(W?)Ax;|uucFQnsH++i!9@7S z{_I3*<=Fx~NnbJq<)po`dG}d6boNH&efgQLS{s{aim5Myhrc6j+aIie8>#3R?OT1a zFbvBRFLE^wJ4qH zRL6-Kj7N}<4Z3yqepkKv{ekqkB0n*JOP^}z3~sSzq^j^}a`#>}PM0Krru-fmCUbQ3 zg@J)pq*-iF0kaWk*<}zY*;8CV39PN~MhN1me1K0vG@)>(B-^n>_-usGY;>_^7vj;7 z2)6C1fF!y$I&@iE~SM3=csoUi${PgtEQ8J zL8)!@XK_jf_}=oo`SP}i9p8(Hq1-8?NqdTRNm6OZh)XUXcdoof)u;>OKp-sJRz&a` zYAB^81FHVFLz-hJ@EyA>sR|&sR1$WoL0)46ro@r+0G$XmI3~rLGd!__4!2L$k;Yqv zD>ldm1SU_1FWDPWP*IQ(2&xmS=84=Fk))5XP0rYjnIa%#KQBVi<|n#v*+>1@6}3-_QN=f0Ym^=; zbjUZLIp&ER2B~c8DOX6aFMCL_iCuyZFoX>LjT_~bcIzdlvL{pQ75TUIhFb7^$_mHhrlarv>uA|?F$K9|9~Z7_HK9y+%j~AUYkFFRtb89(UW4Z`(eXB6{lzN&lci8soJ- zrb?9Vg}lY)8veA_1Hapj5K6&u&#Bf)&#Ag4YkJmzDi*1IHfszFQ-@)lXSn85G~A_g zB08pfi#q+gcoduPuI?u5WahgU zU>5=rE58el5EOSLsPp>+S|jqNgtAXyUKh;8TW6TnnY3pl-(S*zSG&hOzV>}qc+8O2 z6Kl>wEQboC`m0pW`Q2YYWu*o@)+=~bc)25Q0Ob|Bvdil1_ZQ@v9TN{+AxB0kTWgK= z^jeG9M_)7Y%LSA!IU;P>CUUR-ps=-sC2NSY)Uxkqs4vH-BzArR0}i}O7Pc?zyVJ71 zi$!itH2VY*P*8D#X)a2MMZt77oa5wL{vl;IV#|XisPzku&k2;;CaO3y^rAE7jSo~x zW{nS_&oGSZZa*KEjns<=Fk*ND2hY4+5pCA<E*ld{)|6gn`CAIs7$T>Hvhh`P9I9>dR^~1-gp-JUtmAB46w2fdE!G=m zDW8!uhK{?hgSHL0l@2NFv(UX7RzG?{JojJuX>{6o{;Iu(b52d`-|)ZC#gN~xqA^T9 zPQ)6){QcGZ4$9$y(#;p%BR*I|7hThU*fsp>ouK`WO!f79_zWtWVP_c7^LNCWQcZ8h z>#y#S@n>R+sh2d|ouW=0S~u^(z*jo27lis}sLhz~#ceL*H~g!u1J^(Xah|M2u*8u^ zcSUmSC+t}=ec2D(HX<-bg6A_q2e>h8Pv{srzjL(!mTw4$8rMcatc3On1A zKbqR>R;@gSl0?5(7QL{v^Etfc8k(ciY7&Yyqjt}Rgu4VbuA#+-$DrQnwaHERIi`spbEB24(3ti+Wh-5C^=&WK+(4Qj@a;??~)s;P%Jj_jH ze4l^ft)PkJ!rIX2v;hZ_(G5$Qi)9(6GoKZ!3H!VF838LsGb^ScMl;CW$;#H{nLHLx zcM2z|BLODBK51sTo%pI${t8sn=HJD|dXhsr7XgD~$l{9PR&HnRj-Fnf(MOAQ`|VDh zo?PtOXf*{{?CjMVBNb*p*h#933c^Z~J>xL8EM*lFtclApk=rQAII_h2JoY_h3$#P( zsrQ*EB}~;ni9Os-9+m2WbFyTj>#hxLt0EN;?z}+&#hL7*mcQ+ z7F-!CBZaOg%niFF`AqvkOAKjjnQ}?1i6D0v1~KBIwZ!&7r_nC4O6(2usJ-~&iuI(W zNR;9wFG(;CdaYiR(HV zq9irTIi+oDh7pFLc?XQNcwB{eHFxp_cXBH(gVs2C*A%Il^hi7^WCrk0HSw6{Kj7b} z=Bkujf?XkUrPsP|Z441bYa-c2J0gTTk~$BMd@KT$-^d%J5R-7#aF%wCbpcPJymb-wmyEuo-6-|+|VVemIiU475?yg>Z#P1Jy5@E z{E>W~;4u=K{)NA6a%#BL8du3tKC_&^2vsjW_0IDz11xGL_$wIp*RMOgf9G`k=hVT^ zOj5|&z{b$b+C?Xg9k?)mcQ>cs8!WrcyiJ&fYGa&BvB9pC~hLQ>LrH zsOWGK@SC*VYwIo$q3UXflTymqfV?j_S`-K_b*8RF4TztnF3v7SHfu`O8vl8T49&A$ zX=gE^*uccmBf_r;t#iY;Nte;GLAmHL%U7@oBdW^0D%EY`C@S8+m^IZ*3au{bqj+$gX|S1-hzI*ePYWA>cCQ>LnM48Rb6_^d9^d zV&!^VV|c}irMGdA)5T^uExpcy5TE%VMCN=$wvqJ$A{r0{{7?*MdC{-;(c0VNr%rPI z{;PCR2$M+Jc`J(X%hY$78OM23i_H_0s)9K+4wl=hvF z(kT*BSzv!tRNo<0(qnS*O^>qaC^P4tZrj4dM=Ml^$p;?AIzc2_u2$8+w!6*>b6IpO zn8$~K5@gPM%OG3aSd%hXSjyWMEM-Oa5d}XpGf(s%5*Q=lz3LL*^t`Kn)r)Vqw+i9l zc<6ZqUY#UBJVxJA49m!C()<;7iE!45VCbE<(H;7VkjF8Yr-1AnjJ1s%^}5ubpd-iR zOSy#KpnJh-OnIhDq#6G}okG$2K%M9re3b!S38Hw!E-=jW^|upuMH6tGsn!M5lQf9u z#0eGsntdshI<*-Ugq_7_q3-Hm621MkLICjgYj68*hpU0wY?x|_lt-76)aMDybD-My z1A_&Ew1V$#qRkiI=j$pG{_|@}Cr&Q~(H3Wa7=W0KK#Vq>2+lV|@v($ZCY{wZku3l& zeG1op&;f1bHkGw{mM4f)|L_AC1pS=E|0*+9Vj!#8G96X}zM`Ws9?UBJ%X2XkPEs$} z)0#heADbH=>ssNEwvZ)pw7{1v)hKe?Zul3^ceA%7nL58IAq2_@&Kuf_$ct+e^;^(m zzCuI>pkMqh(<=DY?LC48w2O`)0}j@7~IFsI?7jP%FWC^$Tr@1>0y{18<)sORsK_FCd?5Od-dnQxGPU0hcO!qTcgaPY6+ z|L)@A`=3~}3Xb+hdR9_Kj(UcAj(UUwHa{zG){g)0HCCix@k2mHm`w>)0ZQi}Aw|!-H zab>hgi>A$*@)jw*rJcOI)yk%CU6Vwb)XwO8urIjv-;*lvd}Qr#dI zsgM!Z2m&8C-=K5zS_{t0Xm1$L9o$!%&uD25f=K|NoizWm4@oowjkl+I*2=xVG-X_j zTJsL(p*%p?$z3`RW+A(mwCo1O{TfzYn4w>_QVrpeumaTwORffGt=Lv2v7-ga+eay8 ztL~mse{EktGCDf6e~^UMYow`U7lg-(-?QT+s4>7Ki1xYJ?AD{_^bQ#|hre>ck-ujc zUP6gBg2;Qnp%%E$fzljxwlQ~vAw%xecb5i+ie zL_-Et`We2>*tXCB*68g|$e|2XQOHM;URR@J08KhkmpOGT< zd$w|%trfQ;%9)e#H?0tBRHw_~1kD>#C33nlG;Kpn>_z57>-6RiA1o1oKPlbBw{4so zKZi^o@Zk|g0eKgX&X_ie65llvgfkZp&VH5w?N~g4zD!Drk_JonE_G1i7i0kAigF#H zP{J-mD=TD=OqrpptFP!)Qao$9QBi$;y=$lhMUHfB-46WcA~8!aCYX9$haap2=Kiru ztlFIoN}*K^zDZZ61y(of=~%API`JW=&-hcmGSdvH(AqljA*|8TflROUBO^>nI>zBbJ>RGt)$ zK#cE_;Uw zJWBLFv!D=GshO?1M{hK{K=)R^l4!ClwU2Y)^K74jNV`U4`r`THdLEusy~zwIl1bsu z^+&;0qiF6KQ-kVWRIw0W8iNpZ;|pDWE-?ktKs_6V6PjG`Est^`owPai)U3SNV_!!y z=#Vy`;Zd}cOJ#`xlAcG%AgFwBK(>xB%h3z$JFlwd5RGnoiwgT-642vX?v7pn00tzR z6|H5!1vN(G`fjmh0NslUzdVLl`n9P6??RRpq(ys0l$=z`2I>Md?N^w(R=-ud3<8#J z)F@HdG{Yl76rSPk2!y0=fc7i$-;5*JfM{0uRs)l89Itw;dj>*3SUzZ}XiWY6!qy1D zSuuGl9bw*5-gIEN^FkinBf<8XD7(LHi0sP+`oWEio`GX!F(&18I*6Q$uFVPrf+YK8 z`$l;Lo8O?%FPvfQ;5T^KYtMAZg2Y0!g0-!TuL%F@0(Q}Qm4iPOGxUGFV*bh2{kMuq zv~pMwh8_71!IVg*2}@5d1e=|Y=eLxP6u=~9b|aK!iAIpspY4@wV>Qpm<%y&$VOp4O zTnITEF`I%&b=Gy=V)-kab)Cf&JO3yYW?ZiqnSq`@6e{nEMIb_ADWP!F&1al&?&Fo zp)mcd52IR~?@M-+qDgI29j$^ao-x5Z(BvUPu|TTmRdXHG0J$z$&Tm1()T{;MMT{!U zx;;d?q#CLjOd?)WEM(U8F5R74PB{7kEP}HS>IimYcM1;)hGUBg4-Y@g1`jU@cETzN zCI)5}T?kCQX8=u|r_(PK?F|M5Pi+S&ike6}Ko{D&SZ%B78lhKvxU^;8(R>7op+Dh* z7AziBeoKLWOGr@w21T%F-g?ng+Tpd=q9uP!EH9E?9Og!OuBuxLEm^hzzJ)#lU(G-$ z2pcNhGiC!1E4PLGC)d+FbL)U+-@$l_*w~IaH24Wx;4HN2=!-N|o|g z?b6rV&@37Kl`uzXI}0bTLOLL0?iXI1U$-RYG^kdVMJXoh{O^09kn$?-A z$>`@yWPMZ$+ew3CDdYPT;fcsmAE@Yg93y^OXd&8|A)y=L2LT)G7#~>`otr$FL*zZ= zo|XCumhTkY#Kp``UuNFHIszomaH@X-9Y_gc=T6CPqd&|6_%!LOsEVy}Utk_?(QfC` zLx!Sb-#MExqES`$Nn*YkMINb1+DvE@^{^$$@(|g^*~Nb2)nEs!0f#$^iPgv{z7Yp< z8nIX`m4JVw0wKKC;pIr{Cq5i@`y>j3YndYoxItjqvNL5d}~*VUsPX~B<$q)Uk5hfN&jsmh03K!h-!btK;`PI!sW+5T3WT57q**^TQ1d0w$*jL-(GfqyMl?J zeLon!-`(=)d+aZ7DS)I4bio4U|KdZXxPh-&T#(E+*UyNnd1x`%pOy6d$)nIYT6USl zK2Vs((5x8t@zvWZ>l46qs<+Fr6Y?NDyqE({xQq8ty?SILy+R6IS*FX>TkP;W`#`J2 za8FxVVfVy2hdLyKt%wLVa?s=WdkN!eW#RuzK%-2=C_p?tejOibwAsGP!YEDjPX)IK z^5`;Y-YjujCoqm+4;{$br4o3{lqaphuy;XCzH3JvcJ(SO^&rOO`GX3?J|ns$>)_SH za4L8QX2zDfgt#8T%_uk2jOX!Im1^}875L9hg6$#71%z$r3mHs>Zp_!?*NI@akFtpQH;~#O$W35{H=PeEU z0lf+%Qqmf^ktUCWI7jCxs(=axyOiyOiZO}U40$Cc;MGta;25+1BURW-RxPSz2Vn&h zMK=%2KN;$+5iAvg%FX*rra9tjV?bIxZWpl8ZX8;{G&D83h_JmE0=2WTe!xGBaAOo)y3k8LRuimFf#bXtGGimw--q~=0Wbl4vM#gJHHE-#-q@fUnw zZkTC2kQ$$HC2{Qt5L%EBpA`wJs;Wu3lCZ!Afl9j91@v!s39qA#GwXf;8m)SF_u!$q z)k+7~Srh{e!Q5`azL!J;cc$V(8q-2o&&vpd!NykJXlu2{l}b$6h_{X8OiffjeF{=; zx}6m2A7E>2%0Cny%!-41T$#O?i}abiY!G3 zKqo*x=@H=OVIz|?)DMrC=7wtb8MuMg3}ZZ}Me!VYWE~V4!<0&OvYMy0GdECL-f((M z;RG2rXDRV;1ZZFo?ng7tWqY`S18;`cv-fQZYDSM;wT<5}5$U+mS#jv3$BiB0-DaFN z3Vb$agX*luQk#t5IyIGS`O}8Jiqrti2*XQp=byJueTQD_yyq3%czMnH6^?!{4qJa! zQzOoDRqvQ*Y*QJDvv&xK2_2jjL);fafH;zc)*mtZjx5$DAp&HZ`Vyt)K^C*AycMXW zp_xFra}VZn>&|S?-9p`Vz`~Lty$!e;(id&k7g;o>y6#lYCSq5vj>GEI;GT|%9Kma( zEsoFBS(6xJ$BOm<@Z5%%G4%jn5NrPB=Cah{r zb@2y_q4G#`kd(id^thKrZm*FA#n^#}iT3`}k=H7#_tuuSi&C%`2kus^dROKl)N409 z&e-C~FBFSp3nNf}Nqb`h72GDm@Pr!d+H+aJ{rSLwNahotAKbZs}H*uQkQdpvpQ$VL;aAX#WrAU*Kqg>ZHa1j z6;(169yhMXUVPN7iJHZ^I*-MPToJ0@bc{+5G|LddW9GkOQ>VasLDQAL;W<(MWSJUW z#yQXwTB3(~XYU#lsF~SZQdaBiXz>kj^Zh|T$#rp2qtO4z=(+~n-=e4$?r7gLrX!Ax z{&jUxiv4u9syEmM%ICOsORGrcW(tzfzGeF^+X}^VU5b~z{aFjkJNuwohca+mF*^qc z(0>iszcu-P9*2Snb&+njVn0yKD3QPmmPw3}+$w+ZIgZNoq)$IY(T9|T)lwvkub~>{ z*}Z-SZqaiM6iCb5_+W+O5wEt5X`<&|<*qmcj>DQH9SEEWo>=sqigx=wl94IIiA%(Z zeeJTeTPqAcgRF|D*J*a>udpK1g-ke{l+dqU0N_&f#MT_>=_)EHOfh+yzjbVulz|sgsDwEhb;ckU@WS`6iKe~$hl36I%DPwmh~o=K)MOj*yxcvQe3zao)E|P zA!jNWHf-z>g^E8LvV}Ye)i6huZIFBzX!g$oqUn?w>LMHyu)BYvL>rA?#pMppv_BmP z`FyjTARRWFvqr|N8jK}wkue-jSc#BUTnMDWpVQ5{dX2kw2D#<~>zQ6V#7hicxnfW{ zq9i%Z8Ih^g+ANS$sFhd3@;bt+a`}s($7hkb`R{^5l*c3ThcTk8tU7{Pn$pmtTAVQ7 zLIrYpr#@QxqJIo)0s1qoFd*5`9~;Eu%6?uQI|L6|KO?>-ldm)YEH}*qD z!rkek7|WL~rJHN)TvwMU0!)grA9$Go4^_%k$DrBTzKChbCKTV&<|D{ZOA`ms_55*s z1txo;jJaKhrHC_!TNp9I`)_73Uu_yrRqnJk@yfb>hLxQl0xNk@D@Z(Piio%;cyVgTdTW#-Mbl4^60#4&Oex%&x?~-ZBt&r*hC9l^q79gsm4o z_EVG?)9J;L1c9B80$IVaQ%PxL+f>pIacI+{-6? z$!EF=dN0AHff%vkV0D((Mj>#Ke5Geyl|i7sS>(-RHaZ(utLmP>G{5>F4`Azg@gSRI zt&1Qu{1zIg9f)#8%bI87^yaW2Mdtf(&Ah$^o}8J<$gFh(?9sBCYe0VuOOOR&+w@tb*B#;ekZ_rN`Ya(p>A2UtMDh9ua$8V~+0Y;LI>G z9C>1i1b<#b`W+hKy=Z5HyOiE=nefG6P`lpYA4M>_XLc5xoEEM5jTgo9rLV`2y zPL=Cmy=gPCTxsWH0GxaqROoINX2b|!B3PVbOKZ}B-Cpueaq#YL?lp?C#tLY$=4CBOq;ATU@v4Z% zWDf6WP8Yh!ll6M6)pT-U5Q5B>NW_m~dO$(-hxS75-+uDa-?+TYxRZ3gfC;5R{MV z!|Dlo%aIjhbj`uUt|v}~AdE@@{u8YaeBv8Va^ma6SJTeVMTjb+R(kj&w$|Y5R>`oZ zf&U(X=^lac9)bBjE<5;j3nl%!K3&f+^l$&dBQAFVEYezzk9%v#G3?oD69FC9uM{U) zMcg2pgk+(Pt&b3}*6FE~kOherBlT1<%`jAC983D#o9n>AgrUUI0WdrR)7+y!&cVF* z8m6b=a9Ra$T3o{YJXBp~9C6SyMTyM&+7s!E2z7DU{r20qA$i=vX`qtu2kg})VK-X9 zc6dWL-R@fCcE{>l)MOvJR!@HTLwR{jSIUq3^p7MIyyF9y9vG#LQ#oh5ot5xL7FJJT z>jY)|0pGSVIO|bzI9hnGvH0G9-4XS02lzR%?#|s+#)}hDm~fdt9&$J;hkki6haDv9 z22PoHS1#c9x2EA2vg}iOc;B-?>||u*UX024-SMGH?mLd1%{d%%kuK;uN*GGry(YpB zyHD-f$Y*yuk4TfPX4EnyD}W~xk)xhWcPASK>_xTDNMC06rL6AF=vSI z=1faZ5Qd3zxX6Rk=QZLQ>s}Qk#8fXtxjx7j$dEIooxj>yZZtV_Co7ymQD%{fRL8Q6 z>+KszWG1H*@n~p}nWikTI{!w~3QatTJd?Fh9a&1r>LHuWI5{t2Y~2dUk}jfSdm*qZ zsANbsOD`gu=Q(k9%eCszOgu4W=yqOtM7GJD)KuPxpjU7<_qfu{9ILxi_K|MQ=SG>M z^@a7qv|(f#&R!?WbxyRfy%&J%G_LWYFfEap+yZ;dl%8SN*CxALHmS{I(1o^$1yKtFPPVYyyp?M zGa~5@qCV5nmb*5<`Gn#=n{^NW3fz9+^E?CL5g^!y^a(yWGwSL;=zM@;56s(Vexr_^ zKe5&oqe}V`4LXCto<0D5<5rnVQ?Tp`JN zN>e(gpY)>AaS< z5SoT77#3jCVh?8+reKl`7*?%K+-D7eI)qUobBmO((N7F777BJv)as(7?7KL$xJyYR z`5nH#8)m*JOUpThTUcNjhT<1`a$q|RNepeVu;<7qaS%@<)-EW=_5iEwlVyr7Y9P3z z3!m7x?*FF8kYj#X3ZQ69&Xj26sKXls&Ybp=l{^rwiiAN6A-j*(m9EE?pD~F_stk53 zPmaRie2?d0$*XuBR+uQk&TSu3t||oMabotNRi0#4;en&4J;boR#V%bS&NVsjcZP8U z^cq^}^CWpFj-yjoFQgK^^gfw z^f2U@{@L0h6)NcIgkhG9L2N!p^ci(;7|(R*(zKzSK)y2nr>w3H_(v%^nM1Xb@oyVC zYqlVBQZIac4AF^EQMpK+rq8$@(P@}i8HOHP7*A6KWF`D)jWE+8#k@bJ5Un*_rA2<1$4{IXrPb{S}eF&|{q!NC4E^b=!mWfBJ2HHR2MquPv-{q*A zO{O~WWd5a@l9F4;n({npjncz-@{43&LSVLYX4-S+@*plUP3XB{iRMMk%E^>}!50i6 znh1);{8Qo8NA?;FI^@!v7ll&BCPE68UbQULIZ6GOE%oOOfbO4w-wwZxDvKIHDQ4q_ zPyC9M&}ji2XH$6?o0QNz#ttib%G#mgA|X(R?|*Edx6D!*I-qVba$z#7JRP znC(P?&4^*7uOl_R=jjtK`kCh}HQg|S1J(>uR-LwXZ2np_wLAUz%rZE)2HmTlM*5{S z`+E6?m|GJc%xG?^ZpZz&Ufb+ZVR2U0+yi*a5&COD5Jv2fAuoOQNZ})6hYGm|(1}N&idSD}C%qWZ2iFPRp$<&<)eqzaHjEJ# zN<1T`!&|?%PM2*y5G%>+G z8Y&4lYKdMI!6?&?qF_j998U+K+S8-L2}sutOeWT%W#YhK)1R|v)tTx~?E^Jgjy91X zSaI1Wmc#aGq-ar%{}}OqtwZBw0fcz zkqqwrQrhr?A#k2PQK%R2RnC|8t!#X>bdjzd2l0Y*Seq*45n6Xc&7n$+fDgbh0R|Ri zV`M|ql{=gl@eJz9!Dvfg9qN^x7^sYI?PGjR}uL*G#5e|Eh1L@J|aa6v6P!_3v@mEyt=XORo zO;I=gT(199PPq0eK6pS3ai9wlGbQA}fc7`p=`(B_m6=mcE~@q(kjX56_8$bu;j#cX z{npch*&LtD1$j<8?AY^k7V#GsLKciQdeeeEuJJs43IRN!TGkV~b-6goEgmf(h@s(Bx_#_gSa4X>?pdx69 zdso~VEsi@2kDK-J-ZL5>g~p1EQz4M^@qUMXb+c}jU|^8AD+@mZC$~qD*diVwRb29i zNbX-7_Jd#Q`8VcVD0U z{zLxQF=Vpi__4M){g0dw`u~mm@n26RQpW$rgD7pWJ09qjmH5;C%E-yLF%vN{& zyg$M9Q7*t0gDv@QE(L;J_B+RsnLgF(PaI@i}da4OV8kKjPC2967lbXf0( z%+V1WZskRe!e~}C;`O8SrGvFnbH&_0@>Kp&)!u|~qx^%`yQF|!H=9tqbqf4LQ{@FH zKc^#Fga&7V7~q7F8nbM&?(f4wvB++X7fQG>HX!kHW;LRpsZCeTY7!<%=DKjSCO5cQpi&$|yA+-6Rnbnr3Og}Zzi`7GajP(Hwj)Kx5 zwZK{$eVB~)!ELeHB;1`N;WgW$sjE)OB!D@&Zkn(JEo;Z|bs-Mw==A{IqaP9PE;b=v zi%;*ih*W;P)CUF(4Dv)(v}-^xtV>W*VO+#rmCh_@ORD%<2Rfci=pe3!+%v9jv8`Vf zdsJeBes`YI#;(GYU5A9Uqt7O5jv}ZMd{h_DNum+gGMRpJ$_4qgNb;2DQpL<4$NG04 z8lrNXD=s0LpO~uE4$c2Qap)QY-&0t*?Xs&18W9+ z)8;FDM70XAMnB;Hm_al{jZumpVLj<*4*&lXu>UVJ`2W?(JE5qc`pPD<$u!VfKsN;fTl#k` z(IpoeQVUAXS3{jir{4u&ncB9dnPy$uu5F`&KL&k)^S?rTOht?A1@ZS0l;nCfDp_2d z!e^}ibY)N8XM0_>pTzO&eZ%WR;b;^?s%*MV*ECY(y$R4?zJWMZUi0|T1=^s4n-;a- z6511EC1W&>A*LpFv}OlTgBQUOo!I@8ZEux8FhhL z{ic|>;}}5sR6wTPi@=Lv=-GFdbxtA#M0o5(R&+>=RlRNg%qJ9xhBT$c#&7sno`rNq z`96t(n4t*V3QCmdPqc5;o9qC_Xl9kg+r#dHPO_kP!%BLnl2W^lcLlCes7z&tywl%%w z+;bd2XS@NkMIsNjnPi~RBXdsdt+!lAEH`9<-TFCqL=FXy2`|<)e6(_eQa0Ky)`I5- zK{$;E2q0R2U6yF2@km?pAzxIgJzVp$CN?d`|6JJpe2E~d%3JF_6Ei5h10%S>9f z8hX5jBV;)jjy_l}U*Zsl+fCH*$bnb-AcZF4Ou0KD?*j9GvmiXMC|ZL58ZN|U?@C>& zGmr}H1%`dfG9EXSJL1=>yf&&}>y0yv@f9g|MJ{_{d=HeN>Kgo3nuu|A2=wHbzQ9_c zLWCdI=$fQvC!MK(_K*?GjwF7GJwmO0+@Mc4nIrHs41J^a{;mI!$5;+mD=!fgV)#jR ztati>c828wjGg~m&OQ=&N?|NOXvtiT!rl`Uk0QUgQr-C6iYsI(8#`3$J8{Wo<GNhsm>Bd^c? zkJd@9|A+nZU(C7xS~mZ^Y7}McepZdoMH>`rKq+mq-6_x#Dyg?bxjbSRcsy}CC>15I z{YG1*sg{?`%0SV2kl!zUXvWSK;$TET^I=X#Yp2~`ocGb$Ym8r>>URA`r^6c9 zGl%sY;|~gqlXv!+V~xx;&s5`sIj4)csiLQ4olJ5bx@fd7usK4$Q-i^kLG~=XsPzWs zJqePTt*|jy$HDys#_z3FHlfVWm zPXQZm!k&hW&|6251d!7=g*Y1hWd+hu@fzxwwC>pOHk<$%-OPHSH&xG@FBbIngbaYD zx`Dcj>Dh`s%MMW%6G=`QIeF%Qy$!&`=(ZUZ&>}=B9MgReieOs8RjA<_rsrCfyt-XE zZQz9LZ;;Rx-KaRaAJo*?&B_0{P+@A0kb>+Pe^ZEf_{caoLbf@-y@T_1u~Y-}PSe*& zkbRD>LL_23f3jo$FB=&Fs9FZ%$v$Vr)y01wF0vR|%DFbXqM8mdEU#9{k;9TM~h&WXt>Y9JD9@v z2IfCD0hF(~#>fu~A?tr+A<+Ksr{%u}q?sGa8};b%+J~FFaa~%lhRhls5}6c2;*cRV zCJ+=D>M+qB3{YoGF+PFs;5K;VBVYelta&$J7s$Liumk9}uV~G)N!6g(!lwS-QJVpM$F;|bDRh>K3tgJo)RQhH8 z)P^PFygoh6`b1J{BG2EBMS$GIVJHcS)@B`RTx+WBy>jp}H}w=oOBkc|m%T2$>1=3$ zSBQ$ig@C5dY~}=I)0hv1)8+Y3b!mN4m5Ki7x}~kIqiq*E+9QrE4TQG2fSRQPm2vLc zEd~nk2UK8!Dhus^fs%~>Ltt|W>*x@I6nXT~FqpHnA4)fh7o1ew<{Rigc@SRRM-FUW zIE!ql*1eW4Z25~s;S|~if+MQjxha>bEMxMpk3~)g- zur-)0?099iOq@-*f@N#e@Jnz%r$0lX5X|(_M$UGwycEAo#o6q@-{ChTRkUZEXVFyErDfLPCMh6@ z@_vW&Vzkn}a&N-2ANW%w27m#sz}Xyj-l1_oIlV@Zb&BwxD@2)F*hlEx`AsUr2R52% z|Br_+A3YMPNx7-moru0h$pC(;gk<3r1@>&~@g0_mn&w=tV+I@F`fOoi>z^$bIyKAh zB;XiQ`Jic)0<4+e;sxENKkcc%LB@+Ai1-_1LY!pA)OF^$rK4?#*0GZ*I;t;qQbA$# z6M#uyO@ioR)M(Mrg{T7+S2-F@9j2%HX|;MEhhj_YOM&^L*Peq}%IjcLN!G#xTlLOWFKd)N0-Wh@lWQJEMjF}ot=}WFuB#+6GvkUH-@j|uZl=P zm6;kztRwytfc4MPSlIz5jaO>uv;pOR#w-*qTt{|nskXFtJrk7-a1!T=lK>WUtu>h+ z^@NiW6C7N?sQeB&Gcdyrkyczjey&~t2ARt4@AXNl^hC@uq ziNYWsoUW4y$WxSGVUJccV9sKv5{JAm)>3JMv!zfWYhkSWi5%O@P|q%G2pS(<6V9%t zd2LtNRq+i+qw#JRiM8{3(EQBAnhax$$A8e_&z&2IZfVjsop705COqV`~ zWJ;4d5U$jA%_2TV>t%&Q@&-YS=LCI=WCE1uf1eJ6yk@{^>8C+=?h!@iuRLHxLy5&H z0IRq}$cC-SjAqRF;VBi4xqF4*9;T}3wX&0oqnamq)wvVfv@DJ~sDr9JofbPWaQNdj zonY&R)LnX=dFUj~#0oTLr5naX@I;I&p(>t_-5i##g`?#L=uzI#P+9KBD0B3|(?{U` z`HZ_Y zmiUdT0exYg%_}9`_5lOa-#jdBPZZ2IE}q%HrA@?- zuot~wo6#c8?Z0p7PqXeO;|b2ug5(CpHY?&{5SL=daQG9iO15un2K} z1qbG9y!i^Z&(fxBH`?@!ycv`CFM{$LikEbEl)HOt#m7IuVyo0QW(%DKN6F|f)Di+) z0V13!un>E8fDb%Alf7kcyaHKXmMo)yStt`DmjK_;zcq6mj;Wb`e{RS=7Q@$$El{}= z?#{Ut?Rgl+{xJ(ZXX=&PV_6@Dzwu9H)~=V~dZCHkUhHY1!S}lj)$0dR`u>hg@1tiL zjT7da@tP)qA}P2t9}hQIlqWpIgF!+gYk5T{_D^mfrIcSWYKbJ7!*_XN%w=nCg;||sji(+VWh|2 z#DS!@J=4W<_Uz;_M(`LBM-zL@ncuJ*F-Mc@V;o9ei?%GAEitnhmb!2sQAd-D?myM( zTKgSqhFY$ktuI>1ND|SeT(R#SKVDONq}m>d=4pl3O8$oqc@>}m-i zG42@^&Cg%ylrTfUGg*}lGmIp#2&WT3t-5Cct;l06#UU_UE-lcIKz2q`w|iFXxh$(D zAb%p&FL_04CcX@q`8~0kxG7FAv=J@aIy!u=RQ63<&5eyNkse+0F;}oup&sT$i4gg_ zpejl>M-j-CBpVsCQoY>y>>onYHA~KrCmK%OJfeZAQjFL=wudfKKc@K#3aFT>^@s;_ zE|P?3Y)4)sEk~X!^wSO%_pL0<877t6s0c_(XGdo@Z|PR@kwImo)Y_RzdkkqUXTArO zw_}pJ1syfr5J7`(Uc?+zpW(2HdeDuPgtoQ(1+442k@W#&PXqyzhD34K3ve3))TX>r zJp60Z!5AjQ-_i25SX7D(vn4q@bJ28Hp4XG#=D7PsTEmU@DF$4~Ti-JD*ykmy3hV;( zZ)gu9aAm#~aEAmcmjjKQ4^A~?iKk1CTEMUsn1(w0ZEi#v!n|V{ft4!SgWDN2nYM{& z_~!OCE?%A8Q>QKf;{!};#y4EF*`tg|{#|0BQv=W$wOe2#=2rNB#Ax&*Qe#^XMaO^J z%GAe#aFjog5HtgkAPo9rAYBcGJa}*>gf=$hIqUlZ`N?%wG8p6zga!Mliv)bKVYO~8 zyHxwF#sEWLg2qqW>Nqt*6PC%#vU05}OEYvua>qy+BL#epr9sJO9LUC8AYQ5D3uLBi z(|`}m8hwH7;(gPu;Ts=?EJja#NQORLmIE8>KuRraB^K282E=Q52Ws1&M4R-=rGI1P z;&v&u&(@v!^H7Eg^k0!6htSMzjsz7cKAQ9&BoHsab`)=Dp`EpQ@&fy0b;`=NB9E`P zCZbB+_fr8KeP($@<@Vf5r7KQobs%K%0%Pexr?;!#^sOs1^;LOgx~AGT)2sySnfcl; zmZAPMy$vmdHDO3{o_xo|5P3uS05S|2kSdqXC+iJpye6pr&#L zgOq)x^vuI54(mkn$E3ml7RH!ot&>^fAJ+6|IQH=7vu9GBDRv|1t$0|JQO2adG7cD?Zqe81&o@N zH@3_;n#3blYuAo`SHE4m{{)d7ZxesHidDA*^LZ3bmY@KyZXf6aEpPjt#wucNZ}&G*f>V<7O2$@oBd>i+IG<*`AjxVL$NwWHPS=AeX-;_?LknqSJCAJ%E=l6419K@{=jIZFUjkj!> zNH`TF5^MIx-j2!#D*u5NKt^jg)-txgnO^w=!dIe4Y}N}-DoCE|P%YV7{43;J^`=sQ zGx_DSBJz9hlX>N#Cm=ezGH^#2F2}aD6ask0#d~K z`7YKaCE5kexqa0#mk@}%a}#ea(8qJKdVshpQvCd&nIz4aID-wWU8mPmFg>yFg8Z%? z#4JHOQdtG|V2+sCv9euQO z6rU=9vcUX1+K9TneH%so*jJhyUZ%vIk%r`%hDmt}& zx`-3RSXej(ARF@#Usk$c%NRZcAU1Hrlg9G!OaIemN$Ytj0_LvBfaNrSlhaE*+?t6wshi)DlYHWq8{{YtSS^fuHX` z?c21LN-nPBDV>%=8BFZ6)a49J;a7_prWXfq;`Y*}FF5VZezE*s*+XIK42DmyWP z!9Ij(wsitwTASux45@ecxmaKOfQD&68aqGhj=Vv@1W%5nERx2tN!)REd9G<%w?FOJ z!K=DPzw*bhTHUO;9nKiiStq*tZ^SbWx&wUs1Xtk<0{|CF37>MNXiX?Pj=@&kgRocr zH?8ZP&kpfv4^_HwoD^kDC{nU$6kNqiLR++Y=}9pu^RmdW)c0hkKWU&>JHU4;4O7ub z$q3F(yF9Y;K`X{1+l^{JbIhkVz_+Nkq_f&7RqMXZ5*X;qcXFVI5tAl6e`g0b&ka{l zen`75U_fjim9&Za|)nzRn38yz(7>8AT|0F8RY7&KUdD9W7Y*X5*4)1*H za&>uEeOI$Ud*qT&eRVa{8Y56Ii}V_DO*FEz!x)IVZNSj5_*F}mbT%It^62|raa98f z?9mj$b=|X?Uw>4;ijIJrv-jYx87`wTXk1S#lRe#Gp9f%z(h8;8|I6YaReUX=vQ_%b zJGc294zOK)$1mk;5OCom!19h`#x#zRkh3o+9T}aC%va`l#$<;wZapmzr$8EkkC=~i z2fUd~DU>D3;v^Zc3~!MC5(n?!Syhm^S#|Nu09Eal23Mh1B2=Ld6Ufvp6`Oz^I}F#U z-8^N_eqXGz7JXQlA$uh9n-rVnvD6W|2Te(ml=LF(9eRL_2K0;Rv+$4H7}4;L-2P_d zmu%M9_-FopeY+ZP0Dg^J}8O_Mauw=E`*c~Wm1PsF3A1qAHUdI z;+F*UurV$#Ax`86WJnlxA+VrP?@1tS@h_kvbfHZDocxq#8$k(YG%aNBfnln+aD`~S z1Z2)SZ9x8-VRKrpze|R}@WVFUu1pm(HP~R-#lYSs?3QP(9ZJz-uGnq8+!KBF&K6iI z(}Y``JeT`RU}L>_kv&Wu#vUDS6i_%V)Z_uhPmuk~7ZXB9_;&rLwn8?9a7kVRhN!Ru zCw0J59o#DXyqU!a?lHG=k#9L4Ao^|@=)g8_O_UDg3B6$e)(<`ZMbXC-*p$hk0N)5X zqPc&^rP&BOC8kd|1}%@@q8mU3Ev=F-P#{Be0GZ=hh*Z#uj$7~;5h^pn@D=tXGVs_H z9-obmBpZM*GNWC}=O5+b2^~F;$qPI~5v@?mXp$|i`wK%ff4Z1$Hce_IY_ISOk$prU z*$C034U}$q^ayhZ?xqNGh*Mb->b6reOw}-YWDg_6R3BiN&a|C|1P{Iy@y7Qn)rTSF z@cQm+&Tg)tawHqlIpW%pv`k=0RvUsjYWG@1KkuJJ2PC6~sPCvuY zb-sE4E%eqSYX(j5=pGdCWAO|#DRhLMV}F?YOLDlKAbJaE6%o13O+D9TVksgguk{&$G+~35PwaW$rG?P;bTU! z5!m{n9Gv*pVElwnnH{PaZ+FSX=^4rKiu?Z|v3wy57;Wjk1nKw;D+6~3>vH^Ptw_#WQ&7gwuwa#)2K);kp#m_^-15_ z2%?HDZeS?os&c&m5^v6GM;d{JPz${<;!I)Uy{AU=MCYJ+psA8BJ!z9%wzc)al(HVD z#_vb&)-_cs*DYO@Yar{y?@xMoBh8Y0lyGHsQ7g4)EN5D#Tc7r-SM)bZ_ff~hOR;dB z1WGrrkluk0agCzw?qV8Db%0T~v1p0rCQ1?EMk%Ye*2|1-aqc{ds22O%gkWS(_CeWL zvZfeeE`?df9jYLtOVn=NP?p$X1HFodV8cC?`@pbrW0XoOQd?VBaz2e_ko`|wOf0mV z$%$KRPM(Y;1mTWlNtnJJ7-KKSRcXn0e8z7&-l8MWI z@lrS^m;LwdkqV4&GF{a+?wIsT)GZ9U6O?wL+P00)lhMzwV5_--N_lpm71P6;Kv%6c9A!!m4@p$=S68w}qr4^Ti+#)4KRAP>FM_qatcyImKAR}*7Ho&VH zU1r10Ju#oT9VGyUTzoWzf48zGBC{f98;-9AL8_m&@IOjOYK;kc<}?jwFZ$CcFW2EY z-)~G5yVY>pS?&Nab4{xpgbUA~K_qSnJ=3UNIJ~VI4})Xv^{*z^j8Q8+tClpc`4Hd2 z^-He6^#}-0u3sY8(k4*}5q>e6^~K#U>?k*+wjsApfDz1PzlFD2l8fGd{e!sCSTPgU z$DOoZR;XJEkZtpC9*|YI^P3DR8xfUH+~O`_o1)eMNsnX$*4a*n!ej@+XTkpxQ1JVY z@C#1k;l!zwqnpiAQg{HA8?aF{V(`zwBC!snCQsSj#Cm;&{}{3|(XtB0FbTS$L>*&Zke=lk@Uiopn|0C=Tb+xeK6x-rZ=c% z1yV;1U4t)gf`c})QHF@&7i;&VD)@!#2L})T5)j>^mF_l&6$bL6N?d|KaDHSQ-eDw8 z>PcSppyU?j-&jBo{FRfu!D*b7%67RN3eFnVX)CA1lc)#GX_hGoxet_78`t4QUpxFlb9mu{Qls_*(Y<61#!B zTfJ6zMasN!O1?n^3doLTOcn09e^&fWSdtEOO&>AtS^NtNk`2YXa=a1!t*RU1LfLw zpq6E^={KJ_W*fXg&9LwWvzGEM`9@ECvOK}gFm?Mk%Fr8CpP>QtitcF7y$CL(Ask}Q zP_nJwP%mWkD=+knCsf+dYQWh$ezwm1l z|MpoGyWMJ@%_{o_u1e(X$1JSAC^bT{XDB%|uE(}!itXDR69c3iDm|ENne#&3GOcd$ z?}4{$btAZ^blMhi2i~Vn_GYJAce{@_%e&zS7Jvwawc8ZU*~ zVvnNU5_}+jB(3|Cj@)0W@cYzvoG%q!KzJA)7P`dtB$;WShn-@@pjUTTq- zawZ^tCE0>ImJjXnVftkBkFW;JUggw1{uEmPeP3-d!9i~d&W)A{Grr>0&~-9iU=l%J zZ@noscTvW#WmFOeMc2s1A!T@#KeOW5y2(38i$iCqQ-0Ln+R_-y$xyhd*C@=C2!y8N z+(Xh8RFvaxQP>Ue=+X&xXpKYG;sesJ9!rct65CgI-qDTNX&LLHP0dUP}X zJMuf2fA(k5iIh-ptb0-lM=ZN?mak`01QgBMnBqyymSrXocOr(AVX^%WaYWOCBz6)kr(j3|F_@$< zAX&+WI3?*%ucGmQbOP9YFdTZs-fr;%nPVTVV?V_mA?2MxH73+ago1rNrT^rR`9eL) zE>{CWx0?LwsI5TF$+uR2IoDwn%utB&`^woWU+FOPaY%;NKN|!&BwMe=m7aA0D8A+dw+iggpjw*Jg)5_VzkOhit?a1Si!bX z+D2jAwmtz{vB?39bxO48k;vgT2$VDiaRxKe`XON4Mx6S3j@2N^6 zcjYvwSPhf6rQtY5*B`(6lc9W<5e1w8{d+FS_hNVJIzHorIu6fkE9vR#!`0pzqII_1 z@aosFz!y)LRTYplCB@35zaBJj3CY048Gj|zt`FP~;~7>G?HNLRx<=+Dupey94%5|Z z)$)=K6pCHW(+ikbli0O9J97{Bz7h0bQ+h*Yj;9CD_3(=Es z`V&j=>({Q}|77|4UkUyHcNb@fl8zk8f6%jRTBWG;j)d~n1(Y>^w&L2x0ryyZ0c;;S`9S%4T$A=@AbzCBgcIF@{8>4|KnB)he+6f!2CS3<#omBcJ=jf z{|~hf89xk3KmGrd_SI2UZrj_GG)N=e-6cpWNK1FKfembG)7_F%(w)-X-6`GDji8`( zD4=}XbMEgP@p`V_`z^*E;~o6*JZsH0-?`RYYrb<9H90wrTO@;Z53{OvNsHB%`1v_o zNjTAG>pVI3-|UxTOT5hXSu-anw&rWulW9Phd@!D!X%8%Fd@@)FGMCdg5W9A#xfOV36UP6^@<&dT3zhoDhrNq9Lq0^ zaEwt;WDuv4m=WSTGAMqLK0XeYah|i;?c>Xc#V1{S9||L#`ChP(DYi&# z0LhDh?%YJpOJ^244q;no8At}}&z`HySPFAI+0#elbOW5gD6$8Yqj(409H4mPY~A=h z&G#2Rn;wO7OU3EwaQlp4q%-BBZ6rqbV6wr#n-t$!Y94#}tA8(@AD^^k_=GQL@sL9L zn)sv2WXON4)Ie1PO9v&VW-q{(RRsz0k`$qRm_(=84$xY6O&F~wT zFS%(W#l^ijIOMM$caU>D65tS0SFE0|QO>H#b)6)YDl96M(vj5IC$LrAdh|5pf)CW@!(Z1QcU+xFNq8&WuU0xWiNbJCYtedI{W zu^5hv0pw4}bK_S>mZ|Y~W|N5!7vC(3YuB-;FCB=s$W}FfxTq>Ar6ngUEAg^+vHsZJ zyi|Tr61C#ouyQb6ym0%-g_diFBfElZW7T2YrTx%z{Py#h=lMAuEwmL*8KEu=5!ImU zX^7LjV9YbJp>S%I(dQid^-JN7anDs9_%L|}M$Dt)Ul0|gzc=X}F^K`8k@}tYJu<6A zWUeCCB3Tg-h^;bdrX%1nhM9B3PwaUs6Dp{X>gzKh_4K-QX(Zg&lAKwwx=%*)LLFK` zuhILyLAox1FA=piI$mgdF_L0yP66T1t}FUkEMW)XBKfiAFi7 zIF^Yc`$hm%Z!$AB{DN2E%6t)xi*XuhPR+!fOnY?7jQa>{n^;A>I(r?h@XQFnVTV%x;RT z>O@B+VHFi!zwP2YWgMj$t~(4fmMZYsK#a`Qp|zp;!p{$~%G#i^}Mm10z2+DrKd0Ux&LgnwsWSq_MR5${zz-)p~T` z62?1ZBzfJ5W5Jwd8<;v>(wZk1d-Dt+;i(I4C6A{Gv#cau^KwBU(etK5+$JdpiT7$G z2lPK+)2CnfJ(E|^`|?<`$J;_cEL4kUE~??ZYs)gnW6bRYa{7Y%?_3d3MPtKG2;_ON>$&bDLfl9fY8xAZWcw7y2l0-|@ov@?h`%%9~jh zMLlj4Ohr>&3xeEdn|?XYv|B|c{)h?DOfme*u4b$x_Bign?6zsu#j*HP&+sMdyS(~1GjJ8e%(NfeO7q_#lHA5G}lV59&q%MZ27AG7j3dG6x zTb|-!2;G0B-qYgB&w>~x5Gu%=>sb$HE3iuCcZ_cOSTE@fpB>_sJjr1X7~>NaD0Y?( z&0fZGjPiA?8s>7|7T@Z)@eIJ*_yQ$-L^QUWn~hRHVKKQD%Xz6gkGOw@{~&sj#VKd0 ztA~$jSEO)D>{BJSikU}p##j|zg2Zyc7FFXK1C5y$|Cs{%;pt!&!jvJ;s`AThR_z6 zL^i0AWk3LWXi+7e5Fb5PY~+WP_}E7s)9LZ)LSrpahbcxIw9DZ~B9^ZLv|}xQus&J)7>2Ng~T=Xf7*G<@#GxmKmcL(o3~};Cv6W7P3(* z_KeBKbNVbX;~|$|{m6#`hl|Sx$zsbdXIc7RXYJS%)SN}IAS{(zEmdu4c$iwavZlJ6 z(moGjn}Qa1o;`ZI@LCrU)qRHfgE9IHn&U{kPs=l^y<7u6-Bv9v`q9H{8kbM^+@m%b zr3JsJkoeF-OX9VQ#dqdx>^txr_0^wlh+m~L%LOva)ju-jty1;;gaG8C^12t8xygrKt1e(cs9x( zgY0Vx-MM7&(kQ?mQ`|CEBRs}+`3X3&_aMGcCEnHTsWZ@oXwccF9F@a!t9!0qYnMOp z^GFp=8>l+fUL1jeJ6*^tap;uU1xS6B4?tsvM$+W zeGs#I@H`dVq-S;@boZ;&NsJ871U+SL6gyarn5 zQIb2##Z?D-P>JIM{paj6p*#k>dVKg4n zPR&<{POD4jxLzI{l*8%?)CVrPRPOI* zw8f2~g<8llo3XPFV(HC<;0h|w>&c;DHkR73F7)Ebbm-#O8R1eW1sPtOGdrthi_JHE zY;luE(RB*7q29%$O3%WdcN3ohsk=YTh-;d7h=(F|SeTAa6JL^;&x@$*1`><`q_e(Z zvYe?0%+@r%;!I|9GuzZ>Os=XCk>D$+hLr@DrzT$~+N1AkAgtqor~2UScn)=rl@6lU zBv*JZhjCLKtCVzyQ{Kr&=2PuB%UPxLa>E7&$BT1^ANRqXC<$l6k zuff5{%WdMjt`9jxdNS5N3&n9_J z#muh5WKSgYQYQbadWWvakx3R0#z`>DR^z!KypHTRpi%AqJ=#YGc#I?i6zD@MEG7m7 zj40{>XNn+tj8b#t_?Cg_^;^foD&!}kh9@I4!;z#w@i@cn`_UnbAHI_eQW@>Zr%XbPs!gQM{6~Fg>|hBj|!R zv#ZF?^8_;Ffvpn>I5JXRaRsr(#-vaIaYF>&!l+V}f>(;mcjSOum6j;~U|qsE}RP zs9P*jj5X?87y4a~)|46mBI$9tW+dvZvha^;S7V+)Ga=QY{R7sLyYeB zaMGO(FD&u+`V;DxW?fH|Okkj;CXIgYF5k)ID~fc44@;Km^?*#)8X(E)^M>aPQ3RLgiewxRlBeVcjq@zT$5Dckn{{ ze!)k~A8Cz3UC6onYLVz|7fUyycmyl5Rm7f(Y1-Pz;T4o`W%{$>=zU<(W!CotBkL$V zViOb5D5?k{w90ZHE>H}6A-nUs_!9NDdhZKy>#C@}QQiHoZb{G`yH4B=r5mzo9Zy<^ zh@;0}zK$?}z24B+czcVUqOZT_e(xlBzGA(^!S_>U;px~qvKu@}LQ*~DyW}A5dm9Fp zlZ1577eS#5_{X9?v%JOa@4b+HCJ$}W1fx3k_AlXhK4)~gu&x8~3E%FDI2rU7eeLfb zM7ohJVN=CAsZ#cy+3N@y{eokFdD7C<6BU}^2O{2J&=}k$F#Y^|B!)|~cSy6pxG{~O zF@dKlbk-JjKyPHq#`H#b=Z4%tpubf9C>=mMJk9Y|lgo>rv|r&klXIWX9pg4{Y^>)K z*fdus;%hKf6yc!unY{t_ZP=}`HH$HBTh5*hnfj(#DH55BuD?c6VCHp`nmEFVTbYTV zEWrtN{+fYUg9IGqdmhnfru_?symb46Ju92HRrI67bp0wD`}FHuePATJZx6 z)-n5-h3aAm{nRwOuTUyahO``bhz+`4lx(%0iI!1$E~1>yHvHNuPUe!pSOnw<>&Uu1vjA@rM;E)5Bz19>Q&mh*=MUVIPOXA8d+MUdE733YcwCVrgGPo zHd2h{D8rPoDb&Q8u=K_{tU}wPKd?}9>2J!|;r6}NqtfH-t#Y)8*oxuDH|d;jJ>xES zsCcD9DCFwMFQqp065sr2E?IeP9dKmy#LyxitrSx(C)s1-;}Ek3u;qhSx+#u^U7c

RBtHeS{8k3%Xk6rDF^ zhPXIzWm!^Wie!RtIGMgt^}@w+Ar+7fU8z+|?h2*F&3eUg2`%N*SKiZ*>UwM(d+7_- z&^kH!AIH*@1$NUsc5FJnc-|s)BHA#R8$B4(4xIO|8p+hm>pStU!{ItN62@z&ZUzZ% zHEEToPFQEiF3`M6(pU$Y`;@Zn%P*df7-!TGcoMXY#Jc-&!EF*Pjb$^yk5l>#O%x}d z#KW*H0)tjuwtTu3^&uIx&F804W1pY1pEIGci-O<}#hC`wTq{+czj&BKhMI@_!RX!6 z>kgw7!HU#fJ!vi`t#%V4^~vBB)Ry9NxCr}7O^#6j?#Fi+w&|!OXuW|`!XK!>g^Ko^ z8Ul03K{jZk=9o%R@O`1^=11A_*(^3VZq>@KkES36VPs$fFF|c4Ax$ms>hMwRS@3Qv z_9-QCH87sMyoTFQSa5gd$Yzr^xhL76Ykr<0y)fz1?60VuXF=JWOfrK||F)D1wQ+!{ z9>9O~Nwx56>x&1GOnm*7V5%>8Y&!AE2?m8Y4zzQ&Fmy#8C(F)w55}xZ%AoaNrKAOl z0jOK(N14S9ahe_5J#b>+F8V|e9&^)t*RG`UWY(!iwh@#Hq5KwV-8q#T3o16+IuVbZ zSAu`d>_z-JHSvLkR^0^+r;56P2R5$&C1GgA%a;k^`zDtcq#W!re8jf`40#6xR<2DN zwsKvw7Gc&gITVp_&t+B(Hlxm7fWY1J%v!Z`PH0M~O3pGTV}zn^;}7LTn8|HJBHiBk zj6L^t{Y;M>qM;CZwmJE(Pj<34u3+*53(y@Th^PNtIJcFD6X&0-6(x zFgg5+!6If7AbK{Tg&{x926M(}hz!5&f^jsBgiF!jUlPE;co9gYR3B8S_=Y;Z2#Zbr zoQ~RKVz8v@+Tw$7VmeZX-tu8B)67`lS`JfnEwbYtwceR8ydH~-^x6?hMpG^?Cy)sjh>gH0Ko z$;a*?Zw3ln-oPm!@_=e|z!y%5^!DARnJ;|Hsc2sekqX+8-~z z9KZ}@EN$cX=bIj(zMz3^M%)$~h{T79`xJ-X*ltiXRHyC*B}QUDe4&4%p(Pza3clxA z+B3~XVfU-WY2n3bV=r^BjjoH@&LU^G{YPIM+^^;rSHYYCeTp7@HJ)pp9Ug~Zw2qEX z$7WD;>})-B8+D~Tv9F10uF`;OOmEAKaI%-K38 ze-1-HffNDinF$`|B3L0U>HH|8qgBRUz7VA8kqS)*j-xJoe~Y)ql+6<()50&;BZJDy zcNHR3wA40v3=b)GNC^3aovSwZf7y}y@vEXEY_y(xia6dbuZz6B;S8h1J_QmQ;bw?s`_*1eB%+CZh zjEqk+lzI9-b~)YKYJ8$PT3>fBo1UM8zIGL>7U4NOe;1R zjZU$VZY)?QlmKmM+9V$qt)wx}UX=Q_m#xhOj`9QSVLbzW-g`L%pk9<0{@z!bDZh5Wnxk}@fGsgK1Hte z)*hDU4J@J2u%eN*NY=4Vx##@gwezCC7=tgXHZeKOgz}>ZT5i~b)W`9-_But1msu;A)x_cgnd!tF{z_$rY6NOgO0DPQGH_k# zM~x~OB7j7Jc7G}!U4Dfe?~rnR2L@dl#Mt!!lp%uME>;Hx&5MDAqx>7hvhfKI%ekgb zg`>5T0SOITZ-POC?cMC!+huI-{w3p&vW=e>=3z5uSVg|>-ba>3s=yE3aEU)_Y&@Kb zFHz-`IpAPy3To4JS?5>bjTCEqGSF{_%sUsDvo?#6@vavmsHigR4b{^V%=^9PE;kV` z!%L{~rhSUBL4d213iTnIt_rBCy%FTwKJ@Unb&+h{`m{So_9+it=Ibee{OX>5 z|7d~Q;I-mF%NUfLUHYqK!MvSM#wLv?M!1d@W8FBK(G9T$w6?(~DX#K}=PjZ=M{SQ$ zoQ7N#ux_DSqqs6=0(6ugvtI}i;+MQHap09plq(`Wnxfrc6+Q|lJFP`jRbc*#=I__{ z35;^dzV}pN1SEzN1pb*=v}~9Jgse&_GOd963MJ#{M$a=^&{1 z0Pm|osc;!V35==W7SRmrP>0-1z+5^^a3AI+zY|*Zl+b;G47LX$!TZexWqY2fOL3x@ z0~wN28YG-gBTrM7nNLetf@Gt?Q<2a{E0>iyr0QPJwr!CvOz8&$Pnr_9R)6=NnYZ@ESBDK3mQ=rm5+Z;~j+_C)_T# z-p15Wdo}6UZFGNzV`$IQBB1NAY=UE~hiMFWv)XtH?Hi5aIi zn#)3!>|JYWG&!e*(uGc2eB2c?up4mPHB7PH_;@_Sr!LhuKWFiA>knCbsV?T>Pd3^$ zCB@LwWEsviadQgMy--wX*2Xv>l3ThAh7G(0utC1W={ToluQUIeXSuixt*hcBhY~ zcNweXp& zJaguw8XKpB)d4%>@-qo@=$1&c9Yf8Jz;l$XsFM*cu8J{WUX%)Wx*dnxD2 zTvX*1YRs;g$kZ`@LE~qR?>^G>MRB2`(9=K9!(|>@+`cLYU*hx*VGY3 z{dlrXG>0d)<3 z_YN8c2MPxY5fKr}S?;A=wGYez5v11ykRJ{dFtog%ke@THMtUXm10s`f~tskbDwmz<*u!|2P6}bwRyWh?HC)>-1e3SBhWJ$S8_S z{t!>_PqPijQejaHq0#v_npEolh2|gTodqg+&He!tloaIji1&`rO6mRwnxD^%1JYDy zAY?2?kg#D@vVaPtTq(|MzS^me%``?fa~}-->+w-RvOYY`-*IcSZSgmHR%$=3RweO#Kat zzxHE4NB=%~;@wS(ZPst2|I?cIefGAy;Ac6%5B?7;;rGeD?(Tpl@_!%vPs{bEkbW+q z?_-Vc?ts1){x;9gp}&vjybHbi`Zu8elQsW7sPeAXtPlSR`rm^?e^mSLFSzc?zINnS z=>I89^GBZV&(rUk!l0#}@cjDn>_>v{4{#vcwC|eF+CLH8J*590@tfoHy~B4G5&ZTi zh(9?7`VsMaC++USAN~G+aT|Zc|K82Ii_f Date: Sun, 24 Nov 2019 16:02:20 -0600 Subject: [PATCH 004/116] More progress, working on getting viewer --- .../warsmash/datasources/DataSource.java | 46 +++ .../datasources/DataSourceDescriptor.java | 9 + .../datasources/FolderDataSource.java | 68 ++++ .../FolderDataSourceDescriptor.java | 57 +++ .../warsmash/util/RenderMathUtils.java | 44 +++ .../warsmash/viewer5/AudioContext.java | 47 +++ .../com/etheller/warsmash/viewer5/Bounds.java | 5 + .../com/etheller/warsmash/viewer5/Camera.java | 326 ++++++++++++++++++ .../warsmash/viewer5/CanvasProvider.java | 7 + .../warsmash/viewer5/EmittedObject.java | 11 + .../viewer5/EmittedObjectUpdater.java | 40 +++ .../etheller/warsmash/viewer5/Emitter.java | 75 ++++ .../com/etheller/warsmash/viewer5/Grid.java | 119 +++++++ .../etheller/warsmash/viewer5/GridCell.java | 44 +++ .../com/etheller/warsmash/viewer5/Model.java | 9 + .../warsmash/viewer5/ModelInstance.java | 47 +++ .../warsmash/viewer5/ModelViewer.java | 169 +++++++++ .../etheller/warsmash/viewer5/Resource.java | 12 + .../warsmash/viewer5/ResourceLoader.java | 4 + .../com/etheller/warsmash/viewer5/Scene.java | 290 ++++++++++++++++ .../warsmash/viewer5/TextureMapper.java | 25 ++ .../viewer5/deprecated/ShaderProgram.java | 15 + .../deprecated/ShaderUnitDeprecated.java | 31 ++ .../etheller/warsmash/viewer5/gl/WebGL.java | 139 ++++++++ .../warsmash/viewer5/handlers/Batch.java | 22 ++ .../viewer5/handlers/BatchDescriptor.java | 9 + .../viewer5/handlers/EmitterObject.java | 5 + .../warsmash/viewer5/handlers/Handler.java | 5 + 28 files changed, 1680 insertions(+) create mode 100644 core/src/com/etheller/warsmash/datasources/DataSource.java create mode 100644 core/src/com/etheller/warsmash/datasources/DataSourceDescriptor.java create mode 100644 core/src/com/etheller/warsmash/datasources/FolderDataSource.java create mode 100644 core/src/com/etheller/warsmash/datasources/FolderDataSourceDescriptor.java create mode 100644 core/src/com/etheller/warsmash/viewer5/AudioContext.java create mode 100644 core/src/com/etheller/warsmash/viewer5/Bounds.java create mode 100644 core/src/com/etheller/warsmash/viewer5/Camera.java create mode 100644 core/src/com/etheller/warsmash/viewer5/CanvasProvider.java create mode 100644 core/src/com/etheller/warsmash/viewer5/EmittedObject.java create mode 100644 core/src/com/etheller/warsmash/viewer5/EmittedObjectUpdater.java create mode 100644 core/src/com/etheller/warsmash/viewer5/Emitter.java create mode 100644 core/src/com/etheller/warsmash/viewer5/Grid.java create mode 100644 core/src/com/etheller/warsmash/viewer5/GridCell.java create mode 100644 core/src/com/etheller/warsmash/viewer5/Model.java create mode 100644 core/src/com/etheller/warsmash/viewer5/ModelInstance.java create mode 100644 core/src/com/etheller/warsmash/viewer5/ModelViewer.java create mode 100644 core/src/com/etheller/warsmash/viewer5/Resource.java create mode 100644 core/src/com/etheller/warsmash/viewer5/ResourceLoader.java create mode 100644 core/src/com/etheller/warsmash/viewer5/Scene.java create mode 100644 core/src/com/etheller/warsmash/viewer5/TextureMapper.java create mode 100644 core/src/com/etheller/warsmash/viewer5/deprecated/ShaderProgram.java create mode 100644 core/src/com/etheller/warsmash/viewer5/deprecated/ShaderUnitDeprecated.java create mode 100644 core/src/com/etheller/warsmash/viewer5/gl/WebGL.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/Batch.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/BatchDescriptor.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/EmitterObject.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/Handler.java diff --git a/core/src/com/etheller/warsmash/datasources/DataSource.java b/core/src/com/etheller/warsmash/datasources/DataSource.java new file mode 100644 index 0000000..8a279d1 --- /dev/null +++ b/core/src/com/etheller/warsmash/datasources/DataSource.java @@ -0,0 +1,46 @@ +package com.etheller.warsmash.datasources; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.Collection; + +public interface DataSource { + /** + * Efficiently return a stream instance that will read the data source file's + * contents directly from the data source. For example, this will read a file + * within an MPQ or CASC storage without extracting it. + * + * @param filepath + * @return + * @throws IOException + */ + InputStream getResourceAsStream(String filepath) throws IOException; + + /** + * Inefficiently copy a file from the data source onto the Hard Drive of the + * computer, and then return a java File instance pointed at the file. + * + * @param filepath + * @return + * @throws IOException + */ + File getFile(String filepath) throws IOException; + + /** + * Returns true if the data source contains a valid entry for a particular file. + * Some data sources (MPQs) may contain files for which this returns true, even + * though they cannot list the file in their Listfile. + * + * @param filepath + * @return + */ + boolean has(String filepath); + + /** + * @return a list of data source contents, or null if no list is provided + */ + Collection getListfile(); + + void close() throws IOException; +} diff --git a/core/src/com/etheller/warsmash/datasources/DataSourceDescriptor.java b/core/src/com/etheller/warsmash/datasources/DataSourceDescriptor.java new file mode 100644 index 0000000..2b4fd30 --- /dev/null +++ b/core/src/com/etheller/warsmash/datasources/DataSourceDescriptor.java @@ -0,0 +1,9 @@ +package com.etheller.warsmash.datasources; + +import java.io.Serializable; + +public interface DataSourceDescriptor extends Serializable { + DataSource createDataSource(); + + String getDisplayName(); +} diff --git a/core/src/com/etheller/warsmash/datasources/FolderDataSource.java b/core/src/com/etheller/warsmash/datasources/FolderDataSource.java new file mode 100644 index 0000000..7fac978 --- /dev/null +++ b/core/src/com/etheller/warsmash/datasources/FolderDataSource.java @@ -0,0 +1,68 @@ +package com.etheller.warsmash.datasources; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import java.util.function.Consumer; + +public class FolderDataSource implements DataSource { + + private final Path folderPath; + private final Set listfile; + + public FolderDataSource(final Path folderPath) { + this.folderPath = folderPath; + this.listfile = new HashSet<>(); + try { + Files.walk(folderPath).filter(Files::isRegularFile).forEach(new Consumer() { + @Override + public void accept(final Path t) { + FolderDataSource.this.listfile.add(folderPath.relativize(t).toString()); + } + }); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public InputStream getResourceAsStream(final String filepath) throws IOException { + if (!has(filepath)) { + return null; + } + return Files.newInputStream(this.folderPath.resolve(filepath), StandardOpenOption.READ); + } + + @Override + public File getFile(final String filepath) throws IOException { + if (!has(filepath)) { + return null; + } + return new File(this.folderPath.toString() + File.separatorChar + filepath); + } + + @Override + public boolean has(final String filepath) { + if ("".equals(filepath)) { + return false; // special case for folder data source, dont do this + } + return Files.exists(this.folderPath.resolve(filepath)); + } + + @Override + public Collection getListfile() { + return this.listfile; + } + + @Override + public void close() { + } + +} diff --git a/core/src/com/etheller/warsmash/datasources/FolderDataSourceDescriptor.java b/core/src/com/etheller/warsmash/datasources/FolderDataSourceDescriptor.java new file mode 100644 index 0000000..174aad3 --- /dev/null +++ b/core/src/com/etheller/warsmash/datasources/FolderDataSourceDescriptor.java @@ -0,0 +1,57 @@ +package com.etheller.warsmash.datasources; + +import java.nio.file.Paths; + +public class FolderDataSourceDescriptor implements DataSourceDescriptor { + /** + * Generated serial id + */ + private static final long serialVersionUID = -476724730967709309L; + private final String folderPath; + + public FolderDataSourceDescriptor(final String folderPath) { + this.folderPath = folderPath; + } + + @Override + public DataSource createDataSource() { + return new FolderDataSource(Paths.get(this.folderPath)); + } + + @Override + public String getDisplayName() { + return "Folder: " + this.folderPath; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = (prime * result) + ((this.folderPath == null) ? 0 : this.folderPath.hashCode()); + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final FolderDataSourceDescriptor other = (FolderDataSourceDescriptor) obj; + if (this.folderPath == null) { + if (other.folderPath != null) { + return false; + } + } + else if (!this.folderPath.equals(other.folderPath)) { + return false; + } + return true; + } + +} diff --git a/core/src/com/etheller/warsmash/util/RenderMathUtils.java b/core/src/com/etheller/warsmash/util/RenderMathUtils.java index 96feea1..8ba3a8c 100644 --- a/core/src/com/etheller/warsmash/util/RenderMathUtils.java +++ b/core/src/com/etheller/warsmash/util/RenderMathUtils.java @@ -1,5 +1,7 @@ package com.etheller.warsmash.util; +import java.util.List; + import com.badlogic.gdx.math.Matrix4; import com.badlogic.gdx.math.Quaternion; import com.badlogic.gdx.math.Rectangle; @@ -287,4 +289,46 @@ public enum RenderMathUtils { return out; } + + public static int testCell(final List planes, final int left, final int right, final int bottom, + final int top, int first) { + if (first == -1) { + first = 0; + } + + for (int i = 0; i < 6; i++) { + final int index = (first + i) % 6; + final Vector4 plane = planes.get(index); + + if ((distance2Plane2(plane, left, bottom) < 0) && (distance2Plane2(plane, left, top) < 0) + && (distance2Plane2(plane, right, top) < 0) && (distance2Plane2(plane, right, bottom) < 0)) { + return index; + } + } + + return -1; + } + + public static int testCell(final Vector4[] planes, final int left, final int right, final int bottom, final int top, + int first) { + if (first == -1) { + first = 0; + } + + for (int i = 0; i < 6; i++) { + final int index = (first + i) % 6; + final Vector4 plane = planes[index]; + + if ((distance2Plane2(plane, left, bottom) < 0) && (distance2Plane2(plane, left, top) < 0) + && (distance2Plane2(plane, right, top) < 0) && (distance2Plane2(plane, right, bottom) < 0)) { + return index; + } + } + + return -1; + } + + public static float distance2Plane2(final Vector4 plane, final int px, final int py) { + return (plane.x * px) + (plane.y * py) + plane.w; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/AudioContext.java b/core/src/com/etheller/warsmash/viewer5/AudioContext.java new file mode 100644 index 0000000..77ffc6d --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/AudioContext.java @@ -0,0 +1,47 @@ +package com.etheller.warsmash.viewer5; + +public class AudioContext { + private boolean running = false; + public Listener listener = new Listener(); + + public void suspend() { + this.running = false; + } + + public boolean isRunning() { + return this.running; + } + + public void resume() { + this.running = true; + } + + public static class Listener { + private float x; + private float y; + private float z; + private float forwardX; + private float forwardY; + private float forwardZ; + private float upX; + private float upY; + private float upZ; + + public void setPosition(final float x, final float y, final float z) { + this.x = x; + this.y = y; + this.z = z; + } + + public void setOrientation(final float forwardX, final float forwardY, final float forwardZ, final float upX, + final float upY, final float upZ) { + this.forwardX = forwardX; + this.forwardY = forwardY; + this.forwardZ = forwardZ; + this.upX = upX; + this.upY = upY; + this.upZ = upZ; + + } + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/Bounds.java b/core/src/com/etheller/warsmash/viewer5/Bounds.java new file mode 100644 index 0000000..cc99170 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/Bounds.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.viewer5; + +public class Bounds { + public int x, y, r; +} diff --git a/core/src/com/etheller/warsmash/viewer5/Camera.java b/core/src/com/etheller/warsmash/viewer5/Camera.java new file mode 100644 index 0000000..cbd9fc3 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/Camera.java @@ -0,0 +1,326 @@ +package com.etheller.warsmash.viewer5; + +import com.badlogic.gdx.math.Matrix4; +import com.badlogic.gdx.math.Quaternion; +import com.badlogic.gdx.math.Rectangle; +import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.math.Vector3; +import com.etheller.warsmash.util.RenderMathUtils; +import com.etheller.warsmash.util.Vector4; + +public class Camera { + private static final Vector3 vectorHeap = new Vector3(); + private static final Vector3 vectorHeap2 = new Vector3(); + private static final Vector3 vectorHeap3 = new Vector3(); + private static final Quaternion quatHeap = new Quaternion(); + private static final Matrix4 matHeap = new Matrix4(); + + public final Rectangle rect; + + private boolean isPerspective; + private float fov; + private float aspect; + + private boolean isOrtho; + private float leftClipPlane; + private float rightClipPlane; + private float bottomClipPlane; + private float topClipPlane; + + private float nearClipPlane; + private float farClipPlane; + + public final Vector3 location; + public final Quaternion rotation; + + public Quaternion inverseRotation; + private final Matrix4 worldMatrix; + private final Matrix4 projectionMatrix; + private final Matrix4 worldProjectionMatrix; + private final Matrix4 inverseWorldMatrix; + private final Matrix4 inverseRotationMatrix; + private final Matrix4 inverseWorldProjectionMatrix; + public final Vector3 directionX; + public final Vector3 directionY; + public final Vector3 directionZ; + private final Vector3[] vectors; + private final Vector3[] billboardedVectors; + + public final Vector4[] planes; + private boolean dirty; + + public Camera() { + // rencered viewport + this.rect = new Rectangle(); + + // perspective values + this.isPerspective = true; + this.fov = 0; + this.aspect = 0; + + // Orthogonal values + this.isOrtho = false; + this.leftClipPlane = 0f; + this.rightClipPlane = 0f; + this.bottomClipPlane = 0f; + this.topClipPlane = 0f; + + // Shared values + this.nearClipPlane = 0f; + this.farClipPlane = 0f; + + // World values + this.location = new Vector3(); + this.rotation = new Quaternion(); + + // Derived values. + this.inverseRotation = new Quaternion(); + this.worldMatrix = new Matrix4(); + this.projectionMatrix = new Matrix4(); + this.worldProjectionMatrix = new Matrix4(); + this.inverseWorldMatrix = new Matrix4(); + this.inverseRotationMatrix = new Matrix4(); + this.inverseWorldProjectionMatrix = new Matrix4(); + this.directionX = new Vector3(); + this.directionY = new Vector3(); + this.directionZ = new Vector3(); + + // First four vectors are the corners of a 2x2 rectangle, the last three vectors + // are the unit axes + this.vectors = new Vector3[] { new Vector3(-1, -1, 0), new Vector3(-1, 1, 0), new Vector3(1, 1, 0), + new Vector3(1, -1, 0), new Vector3(1, 0, 0), new Vector3(0, 1, 0), new Vector3(0, 0, 1) }; + + // First four vectors are the corners of a 2x2 rectangle billboarded to the + // camera, the last three vectors are the unit axes billboarded + this.billboardedVectors = new Vector3[] { new Vector3(), new Vector3(), new Vector3(), new Vector3(), + new Vector3(), new Vector3(), new Vector3() }; + + // Left, right, top, bottom, near, far + this.planes = new Vector4[] { new Vector4(), new Vector4(), new Vector4(), new Vector4(), new Vector4(), + new Vector4() }; + + this.dirty = true; + } + + public void perspective(final float fov, final float aspect, final float near, final float far) { + this.isPerspective = true; + this.isOrtho = false; + this.fov = fov; + this.aspect = aspect; + this.nearClipPlane = near; + this.farClipPlane = far; + + this.dirty = true; + } + + public void ortho(final float left, final float right, final float bottom, final float top, final float near, + final float far) { + this.isPerspective = false; + this.isOrtho = true; + this.leftClipPlane = left; + this.rightClipPlane = right; + this.bottomClipPlane = bottom; + this.topClipPlane = top; + this.nearClipPlane = near; + this.farClipPlane = far; + } + + public void viewport(final Rectangle viewport) { + this.rect.set(viewport); + + this.aspect = viewport.width / viewport.height; + + this.dirty = true; + } + + public void setLocation(final Vector3 location) { + this.location.set(location); + + this.dirty = true; + } + + public void move(final Vector3 offset) { + this.location.add(offset); + + this.dirty = true; + } + + public void setRotation(final Quaternion rotation) { + this.rotation.set(rotation); + + this.dirty = true; + } + + public void rotate(final Quaternion rotation) { + this.rotation.mul(rotation); + + this.dirty = true; + } + + public void setRotationAngles(final float horizontalAngle, final float verticalAngle) { + this.rotation.idt(); +// this.rotateAngles(horizontalAngle, verticalAngle); + throw new UnsupportedOperationException( + "Ghostwolf called a function that does not exist, so I did not know what to do here"); + } + + public void rotateAround(final Quaternion rotation, final Vector3 point) { + this.rotate(rotation); + + quatHeap.conjugate(); // TODO ????????? + vectorHeap.set(this.location); + vectorHeap.sub(point); + rotation.transform(vectorHeap); + vectorHeap.add(point); + this.location.set(vectorHeap); + } + + public void setRotationAround(final Quaternion rotation, final Vector3 point) { + this.setRotation(rotation); + ; + + final float length = vectorHeap.set(this.location).sub(point).len(); + + quatHeap.conjugate(); // TODO ????????? + vectorHeap.set(RenderMathUtils.VEC3_UNIT_Z); + quatHeap.transform(vectorHeap); + vectorHeap.scl(length); + this.location.set(vectorHeap.add(point)); + } + + public void setRotationAroundAngles(final float horizontalAngle, final float verticalAngle, final Vector3 point) { + quatHeap.idt(); + RenderMathUtils.rotateX(quatHeap, quatHeap, verticalAngle); + RenderMathUtils.rotateY(quatHeap, quatHeap, horizontalAngle); + + this.setRotationAround(quatHeap, point); + } + + public void face(final Vector3 point, final Vector3 worldUp) { + matHeap.setToLookAt(this.location, point, worldUp); + matHeap.getRotation(this.rotation); + + this.dirty = true; + } + + public void moveToAndFace(final Vector3 location, final Vector3 target, final Vector3 worldUp) { + this.location.set(location); + this.face(target, worldUp); + } + + public void reset() { + this.location.set(0, 0, 0); + this.rotation.idt(); + + this.dirty = true; + } + + public void update() { + if (this.dirty) { + this.dirty = true; + + final Vector3 location = this.location; + final Quaternion rotation = this.rotation; + final Quaternion inverseRotation = this.inverseRotation; + final Matrix4 worldMatrix = this.worldMatrix; + final Matrix4 projectionMatrix = this.projectionMatrix; + final Matrix4 worldProjectionMatrix = this.worldProjectionMatrix; + final Vector3[] vectors = this.vectors; + final Vector3[] billboardedVectors = this.billboardedVectors; + + if (this.isPerspective) { + RenderMathUtils.perspective(projectionMatrix, this.fov, this.aspect, this.nearClipPlane, + this.farClipPlane); + } + else { + RenderMathUtils.ortho(projectionMatrix, this.leftClipPlane, this.rightClipPlane, this.bottomClipPlane, + this.topClipPlane, this.nearClipPlane, this.farClipPlane); + } + + rotation.toMatrix(projectionMatrix.val); + worldMatrix.translate(vectorHeap.set(location).scl(-1)); + inverseRotation.set(rotation).conjugate(); + + // World projection matrix + // World space -> NDC space + worldProjectionMatrix.set(projectionMatrix).mul(worldMatrix); + + // Recalculate the camera's frustum planes + RenderMathUtils.unpackPlanes(this.planes, worldProjectionMatrix); + + // Inverse world matrix + // Camera space -> world space + this.inverseWorldMatrix.set(worldMatrix).inv(); + + this.directionX.set(RenderMathUtils.VEC3_UNIT_X); + inverseRotation.transform(this.directionX); + this.directionY.set(RenderMathUtils.VEC3_UNIT_Y); + inverseRotation.transform(this.directionY); + this.directionZ.set(RenderMathUtils.VEC3_UNIT_Z); + inverseRotation.transform(this.directionZ); + + // Inverse world projection matrix + // NDC space -> World space + this.inverseWorldProjectionMatrix.set(worldProjectionMatrix); + this.inverseWorldProjectionMatrix.inv(); + + for (int i = 0; i < 7; i++) { + billboardedVectors[i].set(vectors[i]); + inverseRotation.transform(billboardedVectors[i]); + } + } + } + + public boolean testSphere(final Vector3 center, final float radius) { + for (final Vector4 plane : this.planes) { + if (RenderMathUtils.distanceToPlane(plane, center) <= -radius) { + return false; + } + } + return true; + } + + public Vector3 cameraToWorld(final Vector3 out, final Vector3 v) { + return out.set(v).prj(this.inverseWorldMatrix); + } + + public Vector3 worldToCamera(final Vector3 out, final Vector3 v) { + return out.set(v).prj(this.worldMatrix); + } + + public Vector2 worldToScreen(final Vector2 out, final Vector3 v) { + final Rectangle viewport = this.rect; + + vectorHeap.set(v); + vectorHeap.prj(this.inverseWorldMatrix); + + out.x = Math.round(((vectorHeap.x + 1) / 2) * viewport.width); + out.y = Math.round(((vectorHeap.y + 1) / 2) * viewport.height); + + return out; + } + + public float[] screenToWorldRay(final float[] out, final Vector2 v) { + final Vector3 a = vectorHeap; + final Vector3 b = vectorHeap2; + final Vector3 c = vectorHeap3; + final float x = v.x; + final float y = v.y; + final Rectangle viewport = this.rect; + + // Intersection on the near-plane + RenderMathUtils.unproject(a, c.set(x, y, 0), this.inverseWorldProjectionMatrix, viewport); + + // Intersection on the far-plane + RenderMathUtils.unproject(b, c.set(x, y, 1), this.inverseWorldProjectionMatrix, viewport); + + out[0] = a.x; + out[1] = a.y; + out[2] = a.z; + out[3] = b.x; + out[4] = b.y; + out[5] = b.z; + + return out; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/CanvasProvider.java b/core/src/com/etheller/warsmash/viewer5/CanvasProvider.java new file mode 100644 index 0000000..7d0aaab --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/CanvasProvider.java @@ -0,0 +1,7 @@ +package com.etheller.warsmash.viewer5; + +public interface CanvasProvider { + float getWidth(); + + float getHeight(); +} diff --git a/core/src/com/etheller/warsmash/viewer5/EmittedObject.java b/core/src/com/etheller/warsmash/viewer5/EmittedObject.java new file mode 100644 index 0000000..b39a1e3 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/EmittedObject.java @@ -0,0 +1,11 @@ +package com.etheller.warsmash.viewer5; + +public abstract class EmittedObject { + abstract void update(float dt); + + public float health; + public Emitter emitter; + public int index; + + protected abstract void bind(int flags); +} diff --git a/core/src/com/etheller/warsmash/viewer5/EmittedObjectUpdater.java b/core/src/com/etheller/warsmash/viewer5/EmittedObjectUpdater.java new file mode 100644 index 0000000..b9d4086 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/EmittedObjectUpdater.java @@ -0,0 +1,40 @@ +package com.etheller.warsmash.viewer5; + +import java.util.ArrayList; +import java.util.List; + +public class EmittedObjectUpdater { + final List objects; + private int alive; + + public EmittedObjectUpdater() { + this.objects = new ArrayList<>(); + this.alive = 0; + } + + public void add(final EmittedObject object) { + this.objects.add(object); + this.alive++; + } + + public void update(final float dt) { + for (int i = 0; i < this.alive; i++) { + final EmittedObject object = this.objects.get(i); + + object.update(dt); + + if (object.health <= 0) { + this.alive -= 1; + + object.emitter.kill(object); + + // Swap between this object and the last living object. + // Decrement the iterator so the swapped object is updated this frame. + if (i != this.alive) { + this.objects.set(i, this.objects.remove(this.alive)); + i -= 1; + } + } + } + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/Emitter.java b/core/src/com/etheller/warsmash/viewer5/Emitter.java new file mode 100644 index 0000000..6f8a63b --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/Emitter.java @@ -0,0 +1,75 @@ +package com.etheller.warsmash.viewer5; + +import java.util.ArrayList; +import java.util.List; + +import com.etheller.warsmash.viewer5.handlers.EmitterObject; + +public abstract class Emitter { + + private final ModelInstance instance; + private final EmitterObject emitterObject; + private final List objects; + private int alive; + private int currentEmission; + + public Emitter(final ModelInstance instance, final EmitterObject emitterObject) { + this.instance = instance; + this.emitterObject = emitterObject; + this.objects = new ArrayList<>(); + this.alive = 0; + this.currentEmission = 0; + } + + public final EmittedObject emitObject(final int flags) { + if (this.alive == this.objects.size()) { + this.objects.add(this.createObject()); + } + + final EmittedObject object = this.objects.get(this.alive); + object.index = this.alive; + object.bind(flags); + + this.alive += 1; + this.currentEmission -= 1; + + this.instance.scene.emitterObjectUpdater.add(object); + + return object; + } + + public final void update(final float dt) { + this.updateEmission(dt); + + final int currentEmission = this.currentEmission; + if (currentEmission >= 1) { + for (int i = 0; i < currentEmission; i += 1) { + this.emit(); + } + } + } + + public final void kill(final EmittedObject object) { + this.alive -= 1; + + final EmittedObject otherObject = this.objects.get(this.alive); + this.objects.set(object.index, otherObject); + this.objects.set(this.alive, object); + + otherObject.index = object.index; + object.index = -1; + } + + public final void clear() { + for (int i = 0; i < this.alive; i++) { + this.objects.get(i).health = 0; + } + this.currentEmission = 0; + } + + protected abstract void updateEmission(float dt); + + protected abstract void emit(); + + protected abstract EmittedObject createObject(); +} diff --git a/core/src/com/etheller/warsmash/viewer5/Grid.java b/core/src/com/etheller/warsmash/viewer5/Grid.java new file mode 100644 index 0000000..448ad83 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/Grid.java @@ -0,0 +1,119 @@ +package com.etheller.warsmash.viewer5; + +import com.badlogic.gdx.math.Vector3; + +public class Grid { + private final int x; + private final int y; + private final int width; + private final int depth; + private final int cellWidth; + private final int cellDepth; + private final int columns; + private final int rows; + final GridCell[] cells; + + public Grid(final int x, int y, final int width, final int depth, final int cellWidth, final int cellDepth) { + final int columns = width / cellWidth; + final int rows = depth / cellDepth; + + this.x = x; + this.y = y; + this.width = width; + this.depth = depth; + this.cellWidth = cellWidth; + this.cellDepth = cellDepth; + this.columns = columns; + this.rows = rows; + this.cells = new GridCell[rows * columns]; + + for (int row = 0; row < rows; row++) { + for (int column = 0; column < columns; column++) { + final int left = x + (columns * cellWidth); + final int right = left + cellWidth; + final int bottom = y = row * cellDepth; + final int top = bottom + cellDepth; + + this.cells[(row * columns) + column] = new GridCell(left, right, bottom, top); + } + } + } + + public void add(final ModelInstance instance) { + final int left = instance.left; + final int right = instance.right + 1; + final int bottom = instance.bottom; + final int top = instance.top + 1; + + if (left != -1) { + for (int y = bottom; y < top; y++) { + for (int x = left; x < right; x++) { + this.cells[(y * this.columns) + x].add(instance); + } + } + } + } + + public void remove(final ModelInstance instance) { + final int left = instance.left; + final int right = instance.right + 1; + final int bottom = instance.bottom; + final int top = instance.top + 1; + + if (left != -1) { + instance.left = -1; + + for (int y = bottom; y < top; y++) { + for (int x = left; x < right; x++) { + this.cells[(y * this.columns) + x].remove(instance); + } + } + } + } + + public void moved(final ModelInstance instance) { + final Bounds bounds = instance.model.bounds; + final float x = (instance.worldLocation.x + bounds.x) - this.x; + final float y = (instance.worldLocation.y + bounds.y) - this.y; + final float r = bounds.r; + final Vector3 s = instance.worldScale; + int left = (int) (Math.floor((x - (r * s.x)) / this.cellWidth)); + int right = (int) (Math.floor((x + (r * s.x)) / this.cellWidth)); + int bottom = (int) (Math.floor((y - (r * s.y)) / this.cellDepth)); + int top = (int) (Math.floor((y + (r * s.y)) / this.cellDepth)); + + if ((right < 0) || (left > (this.columns - 1)) || (top < 0) || (bottom > (this.rows - 1))) { + // The instance is outside of the grid, so remove it. + this.remove(instance); + } + else { + // Clamp the values so they are in the grid. + left = Math.max(left, 0); + right = Math.min(right, this.columns - 1); + bottom = Math.max(bottom, 0); + top = Math.min(top, this.rows - 1); + + // If the values actually changed, update the cells. + if ((left != instance.left) || (right != instance.right) || (bottom != instance.bottom) + || (top != instance.top)) { + /// TODO: This can be optimized by checking if there are shared cells. + /// That can be done in precisely the same way as done a few lines above, i.e. + /// simple rectangle intersection. + this.remove(instance); + + instance.left = left; + instance.right = right; + instance.bottom = bottom; + instance.top = top; + + this.add(instance); + } + } + } + + public void clear() { + for (final GridCell cell : this.cells) { + cell.clear(); + } + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/GridCell.java b/core/src/com/etheller/warsmash/viewer5/GridCell.java new file mode 100644 index 0000000..dc48f23 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/GridCell.java @@ -0,0 +1,44 @@ +package com.etheller.warsmash.viewer5; + +import java.util.ArrayList; +import java.util.List; + +import com.etheller.warsmash.util.RenderMathUtils; + +public class GridCell { + public final int left; + public final int right; + public final int bottom; + public final int top; + public int plane; + final List instances; + public final boolean visible; + + public GridCell(final int left, final int right, final int bottom, final int top) { + this.left = left; + this.right = right; + this.bottom = bottom; + this.top = top; + this.plane = -1; + this.instances = new ArrayList(); + this.visible = false; + } + + public void add(final ModelInstance instance) { + this.instances.add(instance); + } + + public void remove(final ModelInstance instance) { + this.instances.remove(instance); + } + + public void clear() { + this.instances.clear(); + } + + public boolean isVisible(final Camera camera) { + this.plane = RenderMathUtils.testCell(camera.planes, this.left, this.right, this.bottom, this.top, this.plane); + + return this.plane == -1; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/Model.java b/core/src/com/etheller/warsmash/viewer5/Model.java new file mode 100644 index 0000000..c7096f8 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/Model.java @@ -0,0 +1,9 @@ +package com.etheller.warsmash.viewer5; + +import com.etheller.warsmash.viewer5.handlers.Handler; + +public class Model { + public Bounds bounds; + public boolean ok; + public Handler handler; +} diff --git a/core/src/com/etheller/warsmash/viewer5/ModelInstance.java b/core/src/com/etheller/warsmash/viewer5/ModelInstance.java new file mode 100644 index 0000000..1113241 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/ModelInstance.java @@ -0,0 +1,47 @@ +package com.etheller.warsmash.viewer5; + +import com.badlogic.gdx.math.Vector3; + +public class ModelInstance { + public Model model; + public TextureMapper textureMapper; + + public int left = -1; + public int right = -1; + public int bottom = -1; + public int top = -1; + public int plane = -1; + public int depth = 0; + + public Vector3 worldLocation; + public Vector3 worldScale; + public Scene scene; + public boolean rendered; + public int cullFrame; + public int updateFrame; + + public boolean isVisible(final Camera camera) { + // TODO Auto-generated method stub + return false; + } + + public void update(final float dt, final Scene scene) { + // TODO Auto-generated method stub + + } + + public boolean isBatched() { + // TODO Auto-generated method stub + return false; + } + + public void renderOpaque() { + // TODO Auto-generated method stub + + } + + public void renderTranslucent() { + // TODO Auto-generated method stub + + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/ModelViewer.java b/core/src/com/etheller/warsmash/viewer5/ModelViewer.java new file mode 100644 index 0000000..6fbad31 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/ModelViewer.java @@ -0,0 +1,169 @@ +package com.etheller.warsmash.viewer5; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.graphics.GL20; +import com.badlogic.gdx.graphics.Texture; +import com.etheller.warsmash.datasources.DataSource; +import com.etheller.warsmash.viewer5.gl.WebGL; + +public class ModelViewer { + private final DataSource dataSource; + public final CanvasProvider canvas; + public List resources; + public Map fetchCache; + public int frameTime; + public GL20 gl; + public WebGL webGL; + public List scenes; + private int visibleCells; + private int visibleInstances; + private int updatedParticles; + public int frame; + private final int rectBuffer; + private final boolean enableAudio; + private final Map> textureMappers; + + public ModelViewer(final DataSource dataSource, final CanvasProvider canvas) { + this.dataSource = dataSource; + this.canvas = canvas; + this.resources = new ArrayList<>(); + this.fetchCache = new HashMap<>(); + this.frameTime = 1000 / 60; + this.gl = Gdx.gl; + this.webGL = new WebGL(this.gl); + this.scenes = new ArrayList<>(); + this.visibleCells = 0; + this.visibleInstances = 0; + this.updatedParticles = 0; + + this.frame = 0; + + this.rectBuffer = this.gl.glGenBuffer(); + this.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.rectBuffer); + final ByteBuffer temp = ByteBuffer.allocate(6); + temp.put((byte) 0); + temp.put((byte) 1); + temp.put((byte) 2); + temp.put((byte) 0); + temp.put((byte) 2); + temp.put((byte) 3); + temp.clear(); + this.gl.glBufferData(GL20.GL_ARRAY_BUFFER, temp.capacity(), temp, GL20.GL_STATIC_DRAW); + this.enableAudio = false; + this.textureMappers = new HashMap>(); + } + + public Scene addScene() { + final Scene scene = new Scene(this); + + this.scenes.add(scene); + + return scene; + } + + public boolean removeScene(final Scene scene) { + return this.scenes.remove(scene); + } + + public void clear() { + this.scenes.clear(); + } + + public boolean has(final String key) { + return this.fetchCache.containsKey(key); + } + + public Resource get(final String key) { + return this.fetchCache.get(key); + } + + public void updateAndRender() { + update(); + } + +// public Resource loadGeneric(String path, String dataType, ) + public void update() { + final float dt = this.frameTime * 0.001f; + + this.frame += 1; + + this.visibleCells = 0; + this.visibleInstances = 0; + this.updatedParticles = 0; + + for (final Scene scene : this.scenes) { + scene.update(dt); + + this.visibleCells += scene.visibleCells; + this.visibleInstances += scene.visibleInstances; + this.updatedParticles += scene.updatedParticles; + } + } + + public void startFrame() { + this.gl.glDepthMask(true); + this.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT); + } + + public void render() { + this.renderOpaque(); + this.renderTranslucent(); + } + + private void renderOpaque() { + for (final Scene scene : this.scenes) { + scene.renderOpaque(); + } + } + + private void renderTranslucent() { + for (final Scene scene : this.scenes) { + scene.renderTranslucent(); + } + } + + public TextureMapper baseTextureMapper(final ModelInstance instance) { + final Model model = instance.model; + List mappers = this.textureMappers.get(model); + if (mappers == null) { + mappers = new ArrayList<>(); + this.textureMappers.put(model, mappers); + } + if (mappers.isEmpty()) { + mappers.add(new TextureMapper(model)); + } + return mappers.get(0); + } + + public TextureMapper changeTextureMapper(final ModelInstance instance, final Object key, final Texture texture) { + final Map map = new HashMap<>(instance.textureMapper.textures); + + if (texture instanceof Texture) { // not null? + map.put(key, texture); + } + else { + map.remove(key); + } + + final Model model = instance.model; + final List mappers = this.textureMappers.get(model); + + for (final TextureMapper mapper : mappers) { + if (mapper.textures.equals(map)) { + return mapper; + } + } + + final TextureMapper mapper = new TextureMapper(model, map); + + mappers.add(mapper); + + return mapper; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/Resource.java b/core/src/com/etheller/warsmash/viewer5/Resource.java new file mode 100644 index 0000000..2761957 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/Resource.java @@ -0,0 +1,12 @@ +package com.etheller.warsmash.viewer5; + +public abstract class Resource { + public ModelViewer viewer; + + public Resource(final ModelViewer viewer) { + this.viewer = viewer; + } + + public abstract void bind(int unit); + +} diff --git a/core/src/com/etheller/warsmash/viewer5/ResourceLoader.java b/core/src/com/etheller/warsmash/viewer5/ResourceLoader.java new file mode 100644 index 0000000..ce56cf8 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/ResourceLoader.java @@ -0,0 +1,4 @@ +package com.etheller.warsmash.viewer5; + +public interface ResourceLoader { +} diff --git a/core/src/com/etheller/warsmash/viewer5/Scene.java b/core/src/com/etheller/warsmash/viewer5/Scene.java new file mode 100644 index 0000000..77caad9 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/Scene.java @@ -0,0 +1,290 @@ +package com.etheller.warsmash.viewer5; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.badlogic.gdx.math.Rectangle; +import com.etheller.warsmash.viewer5.handlers.Batch; +import com.etheller.warsmash.viewer5.handlers.BatchDescriptor; + +/** + * A scene. + * + * Every scene has its own list of model instances, and its own camera and + * viewport. + * + * In addition, in Ghostwolf's original code every scene may have its own + * AudioContext if enableAudio() is called. If audo is enabled, the + * AudioContext's listener's location will be updated automatically. Note that + * due to browser policies, this may be done only after user interaction with + * the web page. + * + * In "Warsmash", we are starting from an attempt to replicate Ghostwolf, but + * audio is always on in LibGDX generally. So we will probably simplify or skip + * over those behaviors other than a boolean on/off toggle for audio. + */ +public class Scene { + + private final ModelViewer viewer; + private final Camera camera; + private final Grid grid; + public int visibleCells; + public int visibleInstances; + public int updatedParticles; + private boolean audioEnabled; + private AudioContext audioContext; + + private final List instances; + private final int currentInstance; + private final List batchedInstances; + private final int currentBatchedInstance; + public final EmittedObjectUpdater emitterObjectUpdater; + private final Map batches; + private final Comparator instanceDepthComparator; + + public Scene(final ModelViewer viewer) { + final CanvasProvider canvas = viewer.canvas; + this.viewer = viewer; + this.camera = new Camera(); + this.grid = new Grid(-100000, -100000, 200000, 200000, 200000, 200000); + + this.visibleCells = 0; + this.visibleInstances = 0; + this.updatedParticles = 0; + + this.audioEnabled = false; + this.audioContext = null; + + // Use the whole canvas, and standard perspective projection values. + this.camera.viewport(new Rectangle(0, 0, canvas.getWidth(), canvas.getHeight())); + this.camera.perspective((float) (Math.PI / 4), canvas.getWidth() / canvas.getHeight(), 8, 10000); + + this.instances = new ArrayList<>(); + this.currentInstance = 0; + + this.batchedInstances = new ArrayList<>(); + this.currentBatchedInstance = 0; + + this.emitterObjectUpdater = new EmittedObjectUpdater(); + + this.batches = new HashMap<>(); + this.instanceDepthComparator = new InstanceDepthComparator(); + } + + public boolean enableAudio() { + if (this.audioContext == null) { + this.audioContext = new AudioContext(); + } + if (!this.audioContext.isRunning()) { + this.audioContext.resume(); + } + this.audioEnabled = this.audioContext.isRunning(); + return this.audioEnabled; + } + + public void disableAudio() { + if (this.audioContext != null) { + this.audioContext.suspend(); + } + this.audioEnabled = false; + } + + public boolean addInstance(final ModelInstance instance) { + if (instance.scene != this) { + if (instance.scene != null) { + instance.scene.removeInstance(instance); + } + + instance.scene = this; + + // Only allow instances that are actually ok to be added the scene. + if (instance.model.ok) { + this.grid.moved(instance); + + return true; + } + } + + return false; + } + + public boolean removeInstance(final ModelInstance instance) { + if (instance.scene == this) { + this.grid.remove(instance); + + instance.scene = null; + + return true; + } + return false; + } + + public void clear() { + // First remove references to this scene stored in the instances. + for (final GridCell cell : this.grid.cells) { + for (final ModelInstance instance : cell.instances) { + instance.scene = null; + } + } + + // Then remove references to the instances. + this.grid.clear(); + } + + public boolean detach() { + if (this.viewer != null) { + return this.viewer.removeScene(this); + } + return false; + } + + public void addToBatch(final ModelInstance instance) { + final TextureMapper textureMapper = instance.textureMapper; + Batch batch = this.batches.get(textureMapper); + + if (batch == null) { + final Model model = instance.model; + final BatchDescriptor batchDescriptor = model.handler.batchDescriptor; + batch = batchDescriptor.create(this, model, textureMapper); + + this.batches.put(textureMapper, batch); + } + + batch.add(instance); + } + + public void update(final float dt) { + this.camera.update(); + + if (this.audioEnabled) { + final float x = this.camera.location.x; + final float y = this.camera.location.y; + final float z = this.camera.location.z; + final float forwardX = this.camera.directionY.x; + final float forwardY = this.camera.directionY.y; + final float forwardZ = this.camera.directionY.z; + final float upX = this.camera.directionZ.x; + final float upY = this.camera.directionZ.y; + final float upZ = this.camera.directionZ.z; + final AudioContext.Listener listener = this.audioContext.listener; + + listener.setPosition(-x, -y, -z); + listener.setOrientation(forwardX, forwardY, forwardZ, upX, upY, upZ); + } + + final int frame = this.viewer.frame; + + int currentInstance = 0; + int currentBatchedInstance = 0; + + this.visibleCells = 0; + this.visibleInstances = 0; + + // Update and collect all of the visible instances. + for (final GridCell cell : this.grid.cells) { + if (cell.isVisible(this.camera)) { + this.visibleCells += 1; + + for (final ModelInstance instance : cell.instances) { + if (instance.rendered && (instance.cullFrame < frame) && instance.isVisible(this.camera)) { + instance.cullFrame = frame; + + if (instance.updateFrame < frame) { + instance.update(dt, this); + } + + if (instance.isBatched()) { + if (currentBatchedInstance < this.batchedInstances.size()) { + this.batchedInstances.set(currentBatchedInstance++, instance); + } + else { + this.batchedInstances.add(instance); + currentBatchedInstance++; + } + } + else { + if (currentInstance < this.instances.size()) { + this.instances.set(currentInstance++, instance); + } + else { + this.instances.add(instance); + currentInstance++; + } + } + + this.visibleInstances += 1; + } + } + } + } + + for (int i = this.batchedInstances.size() - 1; i >= currentBatchedInstance; i--) { + this.batchedInstances.remove(i); + } + + for (int i = this.instances.size() - 1; i >= currentInstance; i--) { + this.instances.remove(i); + } + Collections.sort(this.instances, this.instanceDepthComparator); + + this.emitterObjectUpdater.update(dt); + this.updatedParticles = this.emitterObjectUpdater.objects.size(); + } + + public void renderOpaque() { + final Rectangle viewport = this.camera.rect; + this.viewer.gl.glViewport((int) viewport.x, (int) viewport.y, (int) viewport.width, (int) viewport.height); + + // Clear all of the batches. + for (final Batch batch : this.batches.values()) { + batch.clear(); + } + + // Add all of the batched instances to batches. + for (final ModelInstance instance : this.batchedInstances) { + this.addToBatch(instance); + } + + // Render all of the batches. + for (final Batch batch : this.batches.values()) { + batch.render(); + } + + // Render all of the opaque things of non-batched instances. + for (final ModelInstance instance : this.instances) { + instance.renderOpaque(); + } + } + + /** + * Renders all translucent things in this scene. Automatically applies the + * camera's viewport. + */ + public void renderTranslucent() { + final Rectangle viewport = this.camera.rect; + + this.viewer.gl.glViewport((int) viewport.x, (int) viewport.y, (int) viewport.width, (int) viewport.height); + + for (final ModelInstance instance : this.instances) { + instance.renderTranslucent(); + } + + } + + public void clearEmitterObjects() { + for (final EmittedObject object : this.emitterObjectUpdater.objects) { + object.health = 0; + } + } + + private static final class InstanceDepthComparator implements Comparator { + @Override + public int compare(final ModelInstance o1, final ModelInstance o2) { + return o2.depth - o1.depth; + } + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/TextureMapper.java b/core/src/com/etheller/warsmash/viewer5/TextureMapper.java new file mode 100644 index 0000000..82915fd --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/TextureMapper.java @@ -0,0 +1,25 @@ +package com.etheller.warsmash.viewer5; + +import java.util.HashMap; +import java.util.Map; + +import com.badlogic.gdx.graphics.Texture; + +public class TextureMapper { + public final Model model; + public final Map textures; + + public TextureMapper(final Model model) { + this.model = model; + this.textures = new HashMap<>(); + } + + public TextureMapper(final Model model, final Map textures) { + this.model = model; + this.textures = new HashMap<>(textures); + } + + public Texture get(final Object key) { + return this.textures.get(key); + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/deprecated/ShaderProgram.java b/core/src/com/etheller/warsmash/viewer5/deprecated/ShaderProgram.java new file mode 100644 index 0000000..51117e5 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/deprecated/ShaderProgram.java @@ -0,0 +1,15 @@ +package com.etheller.warsmash.viewer5.deprecated; + +import com.etheller.warsmash.viewer5.gl.WebGL; + +public class ShaderProgram { + + public boolean ok; + public int attribsCount; + public int webglResource; + + public ShaderProgram(final WebGL webGL, final ShaderUnitDeprecated vertexShader, final ShaderUnitDeprecated fragmentShader) { + // TODO Auto-generated constructor stub + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/deprecated/ShaderUnitDeprecated.java b/core/src/com/etheller/warsmash/viewer5/deprecated/ShaderUnitDeprecated.java new file mode 100644 index 0000000..75897cd --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/deprecated/ShaderUnitDeprecated.java @@ -0,0 +1,31 @@ +package com.etheller.warsmash.viewer5.deprecated; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.IntBuffer; + +import com.badlogic.gdx.graphics.GL20; + +public class ShaderUnitDeprecated { + + public boolean ok; + private final int webglResource; + private final String src; + private final int shaderType; + + public ShaderUnitDeprecated(final GL20 gl, final String src, final int type) { + final int id = gl.glCreateShader(type); + this.ok = false; + this.webglResource = id; + this.src = src; + this.shaderType = type; + + gl.glShaderSource(id, src); + gl.glCompileShader(id); + + final IntBuffer success = ByteBuffer.allocateDirect(8).order(ByteOrder.nativeOrder()).asIntBuffer(); + gl.glGetShaderiv(id, GL20.GL_COMPILE_STATUS, success); + throw new UnsupportedOperationException("Not yet implemented, probably using library instead"); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/gl/WebGL.java b/core/src/com/etheller/warsmash/viewer5/gl/WebGL.java new file mode 100644 index 0000000..e9f081b --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/gl/WebGL.java @@ -0,0 +1,139 @@ +package com.etheller.warsmash.viewer5.gl; + +import java.util.HashMap; +import java.util.Map; + +import com.badlogic.gdx.graphics.GL20; +import com.badlogic.gdx.graphics.Pixmap; +import com.badlogic.gdx.graphics.Texture; +import com.badlogic.gdx.graphics.glutils.ShaderProgram; +import com.etheller.warsmash.viewer5.deprecated.ShaderUnitDeprecated; + +/** + * This needs a rename. Just a ripoff of ghostwolf's wrapper utility class, it's + * a utility, not a webgl + */ +public class WebGL { + public GL20 gl; + public Map shaderUnits; + public Map shaderPrograms; + public ShaderProgram currentShaderProgram; + public String floatPrecision; + public final Texture emptyTexture; + + public WebGL(final GL20 gl) { + gl.glDepthFunc(GL20.GL_LEQUAL); + gl.glEnable(GL20.GL_DEPTH_TEST); + + // TODO here ghostwolf throws exceptions for unsupported versions of opengl + + this.gl = gl; + + this.shaderUnits = new HashMap<>(); + + this.shaderPrograms = new HashMap<>(); + + this.currentShaderProgram = null; + this.floatPrecision = "precision mediump float;\n"; + + final Pixmap imageData = new Pixmap(2, 2, Pixmap.Format.RGBA8888); + for (int i = 0; i < 2; i++) { + for (int j = 0; j < 2; j++) { + imageData.drawPixel(i, j, 0x000000FF); + } + } + this.emptyTexture = new Texture(imageData); + } + + public ShaderUnitDeprecated createShaderUnit(final String src, final int type) { + final int hash = stringHash(src); // TODO: why on earth are we doing this, what about hash collisions? + if (!this.shaderUnits.containsKey(hash)) { + this.shaderUnits.put(hash, new ShaderUnitDeprecated(this.gl, src, type)); + } + return this.shaderUnits.get(hash); + } + + public ShaderProgram createShaderProgram(final String vertexSrc, final String fragmentSrc) { + final Map shaderPrograms = this.shaderPrograms; + + final int hash = stringHash(vertexSrc + fragmentSrc); + if (!shaderPrograms.containsKey(hash)) { + shaderPrograms.put(hash, new ShaderProgram(vertexSrc, fragmentSrc)); + } + + final ShaderProgram shaderProgram = shaderPrograms.get(hash); + + if (shaderProgram.isCompiled()) { + return shaderProgram; + } + return null; + } + + public void enableVertexAttribs(final int start, final int end) { + final GL20 gl = this.gl; + + for (int i = start; i < end; i++) { + gl.glEnableVertexAttribArray(i); + } + } + + public void disableVertexAttribs(final int start, final int end) { + final GL20 gl = this.gl; + + for (int i = start; i < end; i++) { + gl.glDisableVertexAttribArray(i); + } + } + + public void useShaderProgram(final ShaderProgram shaderProgram) { + final ShaderProgram currentShaderProgram = this.currentShaderProgram; + + if ((shaderProgram != null) && shaderProgram.isCompiled() && (shaderProgram != currentShaderProgram)) { + int oldAttribs = 0; + final int newAttribs = shaderProgram.getAttributes().length; + + if (currentShaderProgram != null) { + oldAttribs = currentShaderProgram.getAttributes().length; + } + + shaderProgram.begin(); + + if (newAttribs > oldAttribs) { + this.enableVertexAttribs(oldAttribs, newAttribs); + } + else if (newAttribs < oldAttribs) { + this.disableVertexAttribs(newAttribs, oldAttribs); + } + + this.currentShaderProgram = shaderProgram; + } + } + + public void bindTexture(final Texture texture, final int unit) { + final GL20 gl = this.gl; + + gl.glActiveTexture(GL20.GL_TEXTURE0 + unit); + + if (texture != null /* && texture.ok */) { + texture.bind(); + } + else { + this.emptyTexture.bind(); + } + } + + public void setTextureMode(final int wrapS, final int wrapT, final int magFilter, final int minFilter) { + final GL20 gl = this.gl; + + // TODO make sure we dont assign this parameter doubly if we're already using + // libgdx texture, which does do some wrapS and wrapT stuff already + gl.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_WRAP_S, wrapS); + gl.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_WRAP_T, wrapT); + gl.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_MAG_FILTER, magFilter); + gl.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_MIN_FILTER, minFilter); + } + + private int stringHash(final String src) { + return src.hashCode(); + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/Batch.java b/core/src/com/etheller/warsmash/viewer5/handlers/Batch.java new file mode 100644 index 0000000..c107087 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/Batch.java @@ -0,0 +1,22 @@ +package com.etheller.warsmash.viewer5.handlers; + +import com.etheller.warsmash.viewer5.ModelInstance; + +public class Batch { + + public void add(final ModelInstance instance) { + // TODO Auto-generated method stub + + } + + public void clear() { + // TODO Auto-generated method stub + + } + + public void render() { + // TODO Auto-generated method stub + + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/BatchDescriptor.java b/core/src/com/etheller/warsmash/viewer5/handlers/BatchDescriptor.java new file mode 100644 index 0000000..0ec39b5 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/BatchDescriptor.java @@ -0,0 +1,9 @@ +package com.etheller.warsmash.viewer5.handlers; + +import com.etheller.warsmash.viewer5.Model; +import com.etheller.warsmash.viewer5.Scene; +import com.etheller.warsmash.viewer5.TextureMapper; + +public interface BatchDescriptor { + Batch create(Scene scene, Model model, TextureMapper textureMapper); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/EmitterObject.java b/core/src/com/etheller/warsmash/viewer5/handlers/EmitterObject.java new file mode 100644 index 0000000..d2cc087 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/EmitterObject.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.viewer5.handlers; + +public class EmitterObject { + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/Handler.java b/core/src/com/etheller/warsmash/viewer5/handlers/Handler.java new file mode 100644 index 0000000..f77aaba --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/Handler.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.viewer5.handlers; + +public class Handler { + public BatchDescriptor batchDescriptor; +} From eb49c33eef02f6523458b3f872c52e34c274bb00 Mon Sep 17 00:00:00 2001 From: Retera Date: Tue, 26 Nov 2019 00:58:02 -0600 Subject: [PATCH 005/116] More work on initial setup, working towards hopefully a prototype --- core/assets/shaders/BoneTexture.vs | 16 + .../warsmash/util/RenderMathUtils.java | 21 ++ .../com/etheller/warsmash/viewer5/Model.java | 31 +- .../warsmash/viewer5/ModelInstance.java | 125 +++++-- .../warsmash/viewer5/ModelViewer.java | 52 ++- .../com/etheller/warsmash/viewer5/Node.java | 319 ++++++++++++++++++ .../etheller/warsmash/viewer5/Resource.java | 42 ++- .../com/etheller/warsmash/viewer5/Scene.java | 6 +- .../etheller/warsmash/viewer5/Shaders.java | 58 ++++ .../warsmash/viewer5/SkeletalNode.java | 168 +++++++++ .../warsmash/viewer5/handlers/Handler.java | 5 - .../viewer5/handlers/ModelHandler.java | 6 + .../handlers/ModelInstanceDescriptor.java | 8 + .../viewer5/handlers/ResourceHandler.java | 10 + 14 files changed, 824 insertions(+), 43 deletions(-) create mode 100644 core/assets/shaders/BoneTexture.vs create mode 100644 core/src/com/etheller/warsmash/viewer5/Node.java create mode 100644 core/src/com/etheller/warsmash/viewer5/Shaders.java create mode 100644 core/src/com/etheller/warsmash/viewer5/SkeletalNode.java delete mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/Handler.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/ModelHandler.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/ModelInstanceDescriptor.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/ResourceHandler.java diff --git a/core/assets/shaders/BoneTexture.vs b/core/assets/shaders/BoneTexture.vs new file mode 100644 index 0000000..5dfa5b7 --- /dev/null +++ b/core/assets/shaders/BoneTexture.vs @@ -0,0 +1,16 @@ +uniform sampler2D u_boneMap; +uniform float u_vectorSize; +uniform float u_rowSize; +mat4 fetchMatrix(float column, float row) { + column *= u_vectorSize * 4.0; + row *= u_rowSize; + // Add in half texel to sample in the middle of the texel. + // Otherwise, since the sample is directly on the boundry, small floating point errors can cause the sample to get the wrong pixel. + // This is mostly noticable with NPOT textures, which the bone maps are. + column += 0.5 * u_vectorSize; + row += 0.5 * u_rowSize; + return mat4(texture2D(u_boneMap, vec2(column, row)), + texture2D(u_boneMap, vec2(column + u_vectorSize, row)), + texture2D(u_boneMap, vec2(column + u_vectorSize * 2.0, row)), + texture2D(u_boneMap, vec2(column + u_vectorSize * 3.0, row))); +} \ No newline at end of file diff --git a/core/src/com/etheller/warsmash/util/RenderMathUtils.java b/core/src/com/etheller/warsmash/util/RenderMathUtils.java index 8ba3a8c..da0c6fc 100644 --- a/core/src/com/etheller/warsmash/util/RenderMathUtils.java +++ b/core/src/com/etheller/warsmash/util/RenderMathUtils.java @@ -331,4 +331,25 @@ public enum RenderMathUtils { public static float distance2Plane2(final Vector4 plane, final int px, final int py) { return (plane.x * px) + (plane.y * py) + plane.w; } + + public static int testSphere(final Vector4[] planes, final float x, final float y, final float z, final int r, + int first) { + if (first == -1) { + first = 0; + } + + for (int i = 0; i < 6; i++) { + final int index = (first + i) % 6; + + if (distanceToPlane3(planes[index], x, y, z) <= -r) { + return index; + } + } + + return -1; + } + + public static float distanceToPlane3(final Vector4 plane, final float px, final float py, final float pz) { + return (plane.x * px) + (plane.y * py) + (plane.z * pz) + plane.w; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/Model.java b/core/src/com/etheller/warsmash/viewer5/Model.java index c7096f8..814465d 100644 --- a/core/src/com/etheller/warsmash/viewer5/Model.java +++ b/core/src/com/etheller/warsmash/viewer5/Model.java @@ -1,9 +1,32 @@ package com.etheller.warsmash.viewer5; -import com.etheller.warsmash.viewer5.handlers.Handler; +import java.util.ArrayList; +import java.util.List; -public class Model { +import com.etheller.warsmash.viewer5.handlers.ModelHandler; +import com.etheller.warsmash.viewer5.handlers.ModelInstanceDescriptor; + +public abstract class Model extends Resource { public Bounds bounds; - public boolean ok; - public Handler handler; + public List preloadedInstances; + + public Model(final ModelHandler handler, final ModelViewer viewer, final String extension, final String fetchUrl) { + super(viewer, handler, extension, fetchUrl); + this.bounds = new Bounds(); + this.preloadedInstances = new ArrayList<>(); + } + + public ModelInstance addInstance(final int type) { + final ModelInstanceDescriptor instanceDescriptor = this.handler.instanceDescriptor; + final ModelInstance instance = instanceDescriptor.create(this); + + if (this.ok) { + instance.load(); + } + else { + this.preloadedInstances.add(instance); + } + + return instance; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/ModelInstance.java b/core/src/com/etheller/warsmash/viewer5/ModelInstance.java index 1113241..d4eb710 100644 --- a/core/src/com/etheller/warsmash/viewer5/ModelInstance.java +++ b/core/src/com/etheller/warsmash/viewer5/ModelInstance.java @@ -1,47 +1,126 @@ package com.etheller.warsmash.viewer5; +import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.math.Vector3; +import com.etheller.warsmash.util.RenderMathUtils; +import com.etheller.warsmash.util.Vector4; -public class ModelInstance { +public abstract class ModelInstance extends Node { + + public int left; + public int right; + public int bottom; + public int top; + public int plane; + public float depth; + public int updateFrame; + public int cullFrame; public Model model; public TextureMapper textureMapper; - - public int left = -1; - public int right = -1; - public int bottom = -1; - public int top = -1; - public int plane = -1; - public int depth = 0; + public boolean paused; + public boolean rendered; public Vector3 worldLocation; public Vector3 worldScale; public Scene scene; - public boolean rendered; - public int cullFrame; - public int updateFrame; - public boolean isVisible(final Camera camera) { - // TODO Auto-generated method stub + public ModelInstance(final Model model) { + this.scene = null; + this.left = -1; + this.right = -1; + this.bottom = -1; + this.top = -1; + this.plane = -1; + this.depth = 0; + this.updateFrame = 0; + this.cullFrame = 0; + this.model = model; + this.textureMapper = model.viewer.baseTextureMapper(this); + this.paused = false; + this.rendered = true; + } + + public void setTexture(final int index, final Texture texture) { + this.textureMapper = this.model.viewer.changeTextureMapper(this, index, texture); + } + + public void show() { + this.rendered = true; + } + + public void hide() { + this.rendered = false; + } + + public boolean shown() { + return this.rendered; + } + + public boolean hidden() { + return !this.rendered; + } + + public boolean detach() { + if (this.scene != null) { + return this.scene.removeInstance(this); + } + return false; } - public void update(final float dt, final Scene scene) { - // TODO Auto-generated method stub + public abstract void updateAnimations(float dt); + public abstract void clearEmittedObjects(); + + @Override + protected final void updateObject(final float dt, final Scene scene) { + if (this.updateFrame < this.model.viewer.frame) { + if (this.rendered && !this.paused) { + this.updateAnimations(dt); + } + } + + this.updateFrame = this.model.viewer.frame; + } + + public boolean setScene(final Scene scene) { + return scene.addInstance(this); + } + + @Override + public void recalculateTransformation() { + super.recalculateTransformation(); + + if (this.scene != null) { + this.scene.grid.moved(this); + } + } + + public boolean isVisible(final Camera camera) { + final float x = this.worldLocation.x; + final float y = this.worldLocation.y; + final float z = this.worldLocation.z; + final Bounds bounds = this.model.bounds; + final Vector4[] planes = camera.planes; + + this.plane = RenderMathUtils.testSphere(planes, x + bounds.x, y + bounds.y, z, bounds.r, this.plane); + + if (this.plane == -1) { + this.depth = RenderMathUtils.distanceToPlane3(planes[4], x, y, z); + + return true; + } + + return false; } public boolean isBatched() { - // TODO Auto-generated method stub return false; } - public void renderOpaque() { - // TODO Auto-generated method stub + public abstract void renderOpaque(); - } + public abstract void renderTranslucent(); - public void renderTranslucent() { - // TODO Auto-generated method stub - - } + public abstract void load(); } diff --git a/core/src/com/etheller/warsmash/viewer5/ModelViewer.java b/core/src/com/etheller/warsmash/viewer5/ModelViewer.java index 6fbad31..1815863 100644 --- a/core/src/com/etheller/warsmash/viewer5/ModelViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/ModelViewer.java @@ -3,20 +3,23 @@ package com.etheller.warsmash.viewer5; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.Texture; import com.etheller.warsmash.datasources.DataSource; import com.etheller.warsmash.viewer5.gl.WebGL; +import com.etheller.warsmash.viewer5.handlers.ResourceHandler; public class ModelViewer { private final DataSource dataSource; public final CanvasProvider canvas; - public List resources; - public Map fetchCache; + public List> resources; + public Map> fetchCache; public int frameTime; public GL20 gl; public WebGL webGL; @@ -28,12 +31,14 @@ public class ModelViewer { private final int rectBuffer; private final boolean enableAudio; private final Map> textureMappers; + private final Set handlers; public ModelViewer(final DataSource dataSource, final CanvasProvider canvas) { this.dataSource = dataSource; this.canvas = canvas; this.resources = new ArrayList<>(); this.fetchCache = new HashMap<>(); + this.handlers = new HashSet(); this.frameTime = 1000 / 60; this.gl = Gdx.gl; this.webGL = new WebGL(this.gl); @@ -59,6 +64,29 @@ public class ModelViewer { this.textureMappers = new HashMap>(); } + public boolean addHandler(ResourceHandler handler) { + if (handler != null) { + + // Allow to pass also the handler's module for convenience. + if (handler.handler != null) { + handler = handler.handler; + } + + if (!this.handlers.contains(handler)) { + // Check if the handler has a loader, and if so load it. + if (handler.load && !handler.load(this)) { + onResourceLoadError(); + return false; + } + + this.handlers.add(handler); + + return true; + } + } + return false; + } + public Scene addScene() { final Scene scene = new Scene(this); @@ -79,15 +107,27 @@ public class ModelViewer { return this.fetchCache.containsKey(key); } - public Resource get(final String key) { + public Resource get(final String key) { return this.fetchCache.get(key); } public void updateAndRender() { - update(); + this.update(); + this.startFrame(); + this.render(); } // public Resource loadGeneric(String path, String dataType, ) + + public boolean unload(final Resource resource) { + // TODO Auto-generated method stub + final String fetchUrl = resource.fetchUrl; + if (!"".equals(fetchUrl)) { + this.fetchCache.remove(fetchUrl); + } + return this.resources.remove(resource); + } + public void update() { final float dt = this.frameTime * 0.001f; @@ -166,4 +206,8 @@ public class ModelViewer { return mapper; } + + private void onResourceLoadError() { + System.err.println("error, this, InvalidHandler, FailedToLoad"); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/Node.java b/core/src/com/etheller/warsmash/viewer5/Node.java new file mode 100644 index 0000000..076679b --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/Node.java @@ -0,0 +1,319 @@ +package com.etheller.warsmash.viewer5; + +import java.util.ArrayList; +import java.util.List; + +import com.badlogic.gdx.math.Matrix4; +import com.badlogic.gdx.math.Quaternion; +import com.badlogic.gdx.math.Vector3; +import com.etheller.warsmash.util.Descriptor; +import com.etheller.warsmash.util.RenderMathUtils; + +public abstract class Node { + protected static final Vector3 locationHeap = new Vector3(); + protected static final Quaternion rotationHeap = new Quaternion(); + protected static final Vector3 scalingHeap = new Vector3(); + + protected final Vector3 pivot; + protected final Vector3 localLocation; + protected final Quaternion localRotation; + protected final Vector3 localScale; + protected final Vector3 worldLocation; + protected final Quaternion worldRotation; + protected final Vector3 worldScale; + protected final Vector3 inverseWorldLocation; + protected final Quaternion inverseWorldRotation; + protected final Vector3 inverseWorldScale; + protected final Matrix4 localMatrix; + protected final Matrix4 worldMatrix; + protected Node parent; + protected final List children; + protected final boolean dontInheritTranslation; + protected final boolean dontInheritRotation; + protected final boolean dontInheritScaling; + protected boolean visible; + protected boolean wasDirty; + protected boolean dirty; + + public Node() { + this.pivot = new Vector3(); + this.localLocation = new Vector3(); + this.localRotation = new Quaternion(0, 0, 0, 1); + this.localScale = new Vector3(1, 1, 1); + this.worldLocation = new Vector3(); + this.worldRotation = new Quaternion(); + this.worldScale = new Vector3(); + this.inverseWorldLocation = new Vector3(); + this.inverseWorldRotation = new Quaternion(); + this.inverseWorldScale = new Vector3(); + this.localMatrix = new Matrix4(); +// this.localMatrix.val[0] = 1; +// this.localMatrix.val[5] = 1; +// this.localMatrix.val[10] = 1; +// this.localMatrix.val[15] = 1; + this.worldMatrix = new Matrix4(); + this.parent = null; + this.children = new ArrayList<>(); + this.dontInheritTranslation = false; + this.dontInheritRotation = false; + this.dontInheritScaling = false; + + this.visible = true; + this.wasDirty = false; + this.dirty = true; + } + + public Node setPivot(final float[] pivot) { + this.pivot.set(pivot); + this.dirty = true; + return this; + } + + public Node setLocation(final float[] location) { + this.localLocation.set(location); + this.dirty = true; + return this; + } + + public Node setRotation(final float[] rotation) { + this.localRotation.set(rotation[0], rotation[1], rotation[2], rotation[3]); + this.dirty = true; + return this; + } + + public Node setScale(final float[] varying) { + this.localScale.set(varying); + this.dirty = true; + return this; + } + + public Node setUniformScale(final float uniform) { + this.localScale.set(uniform, uniform, uniform); + this.dirty = true; + return this; + } + + public Node setTransformation(final Vector3 location, final Quaternion rotation, final Vector3 scale) { + // TODO for performance, Ghostwolf did a direct field write on everything here. + // I'm hoping we can get Java's JIT to just figure it out and do it on its own + this.localLocation.set(location); + this.localRotation.set(rotation); + this.localScale.set(scale); + this.dirty = true; + return this; + } + + public Node resetTransformation() { + this.pivot.set(Vector3.Zero); + this.localLocation.set(Vector3.Zero); + this.localRotation.set(RenderMathUtils.QUAT_DEFAULT); + this.localScale.set(RenderMathUtils.VEC3_ONE); + + this.dirty = true; + return this; + } + + public Node movePivot(final float[] offset) { + this.pivot.add(offset[0], offset[1], offset[2]); + + this.dirty = true; + + return this; + } + + public Node move(final float[] offset) { + this.localLocation.add(offset[0], offset[1], offset[2]); + + this.dirty = true; + + return this; + } + + public Node rotate(final Quaternion rotation) { + RenderMathUtils.mul(this.localRotation, this.localRotation, rotation); + + this.dirty = true; + + return this; + } + + public Node rotateLocal(final Quaternion rotation) { + RenderMathUtils.mul(this.localRotation, rotation, this.localRotation); + + this.dirty = true; + + return this; + } + + public Node scale(final float[] scale) { + this.localScale.x *= scale[0]; + this.localScale.y *= scale[1]; + this.localScale.z *= scale[2]; + + this.dirty = true; + + return this; + } + + public Node uniformScale(final float scale) { + this.localScale.x *= scale; + this.localScale.y *= scale; + this.localScale.z *= scale; + + this.dirty = true; + + return this; + } + + public Node setParent(final Node parent) { + if (this.parent != null) { + this.parent.children.remove(this); + } + + this.parent = parent; + + if (parent != null) { + parent.children.add(this); + } + + this.dirty = true; + + return this; + } + + public void recalculateTransformation() { + boolean dirty = this.dirty; + final Node parent = this.parent; + + this.wasDirty = this.dirty; + + if (parent != null) { + dirty = dirty || parent.wasDirty; + } + + this.wasDirty = dirty; + + if (dirty) { + this.dirty = false; + + if (parent != null) { + Vector3 computedLocation; + Quaternion computedRotation; + Vector3 computedScaling; + + final Vector3 parentPivot = parent.pivot; + + computedLocation = locationHeap; + computedLocation.x = this.localLocation.x + parentPivot.x; + computedLocation.y = this.localLocation.y + parentPivot.y; + computedLocation.z = this.localLocation.z + parentPivot.z; + + if (this.dontInheritRotation) { + computedRotation = rotationHeap; + + computedRotation.set(this.localRotation); + computedRotation.mul(parent.inverseWorldRotation); + } + else { + computedRotation = this.localRotation; + } + + if (this.dontInheritScaling) { + computedScaling = scalingHeap; + + final Vector3 parentInverseScale = parent.inverseWorldScale; + computedScaling.x = parentInverseScale.x * this.localScale.x; + computedScaling.y = parentInverseScale.y * this.localScale.y; + computedScaling.z = parentInverseScale.z * this.localScale.z; + + this.worldScale.x = this.localScale.x; + this.worldScale.y = this.localScale.y; + this.worldScale.z = this.localScale.z; + } + else { + computedScaling = this.localScale; + + final Vector3 parentScale = parent.worldScale; + this.worldScale.x = parentScale.x * this.localScale.x; + this.worldScale.y = parentScale.y * this.localScale.y; + this.worldScale.z = parentScale.z * this.localScale.z; + } + + RenderMathUtils.fromRotationTranslationScale(computedRotation, computedLocation, computedScaling, + this.localMatrix); + + RenderMathUtils.mul(this.worldMatrix, parent.worldMatrix, this.localMatrix); + + RenderMathUtils.mul(this.worldRotation, parent.worldRotation, computedRotation); + } + else { + RenderMathUtils.fromRotationTranslationScale(this.localRotation, this.localLocation, this.localScale, + this.localMatrix); + + this.worldMatrix.set(this.localMatrix); + + this.worldRotation.set(this.localRotation); + + this.worldScale.set(this.localScale); + } + } + + // Inverse world rotation + this.inverseWorldRotation.x = -this.worldRotation.x; + this.inverseWorldRotation.y = -this.worldRotation.y; + this.inverseWorldRotation.z = -this.worldRotation.z; + this.inverseWorldRotation.w = this.worldRotation.w; + + // Inverse world scale + this.inverseWorldScale.x = 1 / this.worldScale.x; + this.inverseWorldScale.y = 1 / this.worldScale.y; + this.inverseWorldScale.z = 1 / this.worldScale.z; + + // World location + this.worldLocation.x = this.worldMatrix.val[Matrix4.M30]; + this.worldLocation.y = this.worldMatrix.val[Matrix4.M31]; + this.worldLocation.z = this.worldMatrix.val[Matrix4.M32]; + + // Inverse world location + this.inverseWorldLocation.x = -this.worldLocation.x; + this.inverseWorldLocation.y = -this.worldLocation.y; + this.inverseWorldLocation.z = -this.worldLocation.z; + + } + + public void update(final float dt, final Scene scene) { + if (this.dirty || ((this.parent != null) && this.parent.wasDirty)) { + this.dirty = true; // in case this node isn't dirty, but the parent was + this.wasDirty = true; + this.recalculateTransformation(); + } + else { + this.wasDirty = false; + } + + this.updateObject(dt, scene); + this.updateChildren(dt, scene); + } + + protected abstract void updateObject(float dt, Scene scene); + + private void updateChildren(final float dt, final Scene scene) { + final int childrenSize = this.children.size(); + for (int i = 0; i < childrenSize; i++) { + this.children.get(i).update(dt, scene); + } + } + + public static Object[] createSkeletalNodes(final int count, + final Descriptor nodeDescriptor) { + final List nodes = new ArrayList<>(); + final List worldMatrices = new ArrayList<>(); + for (int i = 0; i < count; i++) { + final NODE node = nodeDescriptor.create(); + nodes.add(node); + worldMatrices.add(node.worldMatrix); + } + final Object[] data = { nodes, worldMatrices }; + return data; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/Resource.java b/core/src/com/etheller/warsmash/viewer5/Resource.java index 2761957..80db618 100644 --- a/core/src/com/etheller/warsmash/viewer5/Resource.java +++ b/core/src/com/etheller/warsmash/viewer5/Resource.java @@ -1,12 +1,46 @@ package com.etheller.warsmash.viewer5; -public abstract class Resource { - public ModelViewer viewer; +import java.io.InputStream; - public Resource(final ModelViewer viewer) { +import com.etheller.warsmash.viewer5.handlers.ResourceHandler; + +public abstract class Resource { + public final ModelViewer viewer; + public final HANDLER handler; + public final String extension; + public final String fetchUrl; + public boolean ok; + public boolean loaded; + + public Resource(final ModelViewer viewer, final HANDLER handler, final String extension, final String fetchUrl) { this.viewer = viewer; + this.handler = handler; + this.extension = extension; + this.fetchUrl = fetchUrl; + this.ok = false; + this.loaded = false; } - public abstract void bind(int unit); + public void loadData(final InputStream src, final Object options) { + this.loaded = true; + try { + this.load(src, options); + this.ok = true; + this.lateLoad(); + } + catch (final Exception e) { + this.error(e); + } + } + + public boolean detach() { + return this.viewer.unload(this); + } + + protected abstract void lateLoad(); + + protected abstract void load(InputStream src, Object options); + + protected abstract void error(Exception e); } diff --git a/core/src/com/etheller/warsmash/viewer5/Scene.java b/core/src/com/etheller/warsmash/viewer5/Scene.java index 77caad9..102eb13 100644 --- a/core/src/com/etheller/warsmash/viewer5/Scene.java +++ b/core/src/com/etheller/warsmash/viewer5/Scene.java @@ -30,8 +30,8 @@ import com.etheller.warsmash.viewer5.handlers.BatchDescriptor; public class Scene { private final ModelViewer viewer; - private final Camera camera; - private final Grid grid; + public final Camera camera; + final Grid grid; public int visibleCells; public int visibleInstances; public int updatedParticles; @@ -284,7 +284,7 @@ public class Scene { private static final class InstanceDepthComparator implements Comparator { @Override public int compare(final ModelInstance o1, final ModelInstance o2) { - return o2.depth - o1.depth; + return (int) Math.signum(o2.depth - o1.depth); } } } diff --git a/core/src/com/etheller/warsmash/viewer5/Shaders.java b/core/src/com/etheller/warsmash/viewer5/Shaders.java new file mode 100644 index 0000000..aa59c1e --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/Shaders.java @@ -0,0 +1,58 @@ +package com.etheller.warsmash.viewer5; + +public class Shaders { + public static final String boneTexture = ""// + + " uniform sampler2D u_boneMap;\r\n" + // + " uniform float u_vectorSize;\r\n" + // + " uniform float u_rowSize;\r\n" + // + " mat4 fetchMatrix(float column, float row) {\r\n" + // + " column *= u_vectorSize * 4.0;\r\n" + // + " row *= u_rowSize;\r\n" + // + " // Add in half texel to sample in the middle of the texel.\r\n" + // + " // Otherwise, since the sample is directly on the boundry, small floating point errors can cause the sample to get the wrong pixel.\r\n" + + // + " // This is mostly noticable with NPOT textures, which the bone maps are.\r\n" + // + " column += 0.5 * u_vectorSize;\r\n" + // + " row += 0.5 * u_rowSize;\r\n" + // + " return mat4(texture2D(u_boneMap, vec2(column, row)),\r\n" + // + " texture2D(u_boneMap, vec2(column + u_vectorSize, row)),\r\n" + // + " texture2D(u_boneMap, vec2(column + u_vectorSize * 2.0, row)),\r\n" + // + " texture2D(u_boneMap, vec2(column + u_vectorSize * 3.0, row)));\r\n" + // + " }"; + + public static final String decodeFloat = "\r\n" + // + " vec2 decodeFloat2(float f) {\r\n" + // + " vec2 v;\r\n" + // + " v[1] = floor(f / 256.0);\r\n" + // + " v[0] = floor(f - v[1] * 256.0);\r\n" + // + " return v;\r\n" + // + " }\r\n" + // + " vec3 decodeFloat3(float f) {\r\n" + // + " vec3 v;\r\n" + // + " v[2] = floor(f / 65536.0);\r\n" + // + " v[1] = floor((f - v[2] * 65536.0) / 256.0);\r\n" + // + " v[0] = floor(f - v[2] * 65536.0 - v[1] * 256.0);\r\n" + // + " return v;\r\n" + // + " }\r\n" + // + " vec4 decodeFloat4(float v) {\r\n" + // + " vec4 enc = vec4(1.0, 255.0, 65025.0, 16581375.0) * v;\r\n" + // + " enc = fract(enc);\r\n" + // + " enc -= enc.yzww * vec4(1.0 / 255.0, 1.0 / 255.0, 1.0 / 255.0, 0.0);\r\n" + // + " return enc;\r\n" + // + " }"; + + public static final String quadTransform = "\r\n" + // + " // A 2D quaternion*vector.\r\n" + // + " // q is the zw components of the original quaternion.\r\n" + // + " vec2 quat_transform(vec2 q, vec2 v) {\r\n" + // + " vec2 uv = vec2(-q.x * v.y, q.x * v.x);\r\n" + // + " vec2 uuv = vec2(-q.x * uv.y, q.x * uv.x);\r\n" + // + " return v + 2.0 * (uv * q.y + uuv);\r\n" + // + " }\r\n" + // + " // A 2D quaternion*vector.\r\n" + // + " // q is the zw components of the original quaternion.\r\n" + // + " vec3 quat_transform(vec2 q, vec3 v) {\r\n" + // + " return vec3(quat_transform(q, v.xy), v.z);\r\n" + // + " }\r\n" + // + " `,"; +} diff --git a/core/src/com/etheller/warsmash/viewer5/SkeletalNode.java b/core/src/com/etheller/warsmash/viewer5/SkeletalNode.java new file mode 100644 index 0000000..2e8b677 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/SkeletalNode.java @@ -0,0 +1,168 @@ +package com.etheller.warsmash.viewer5; + +import java.util.ArrayList; +import java.util.List; + +import com.badlogic.gdx.math.Matrix4; +import com.badlogic.gdx.math.Quaternion; +import com.badlogic.gdx.math.Vector3; +import com.etheller.warsmash.util.RenderMathUtils; + +public abstract class SkeletalNode { + protected static final Vector3 locationHeap = new Vector3(); + protected static final Quaternion rotationHeap = new Quaternion(); + protected static final Vector3 scalingHeap = new Vector3(); + + protected final Vector3 pivot; + protected final Vector3 localLocation; + protected final Quaternion localRotation; + protected final Vector3 localScale; + protected final Vector3 worldLocation; + protected final Quaternion worldRotation; + protected final Vector3 worldScale; + protected final Vector3 inverseWorldLocation; + protected final Quaternion inverseWorldRotation; + protected final Vector3 inverseWorldScale; + protected final Matrix4 localMatrix; + protected final Matrix4 worldMatrix; + protected SkeletalNode parent; + protected final List children; + protected final boolean dontInheritTranslation; + protected final boolean dontInheritRotation; + protected final boolean dontInheritScaling; + protected boolean visible; + protected boolean wasDirty; + protected boolean dirty; + + public Object object; + + public final boolean billboarded; + public final boolean billboardedX; + public final boolean billboardedY; + public final boolean billboardedZ; + + public SkeletalNode() { + this.pivot = new Vector3(); + this.localLocation = new Vector3(); + this.localRotation = new Quaternion(0, 0, 0, 1); + this.localScale = new Vector3(1, 1, 1); + this.worldLocation = new Vector3(); + this.worldRotation = new Quaternion(); + this.worldScale = new Vector3(); + this.inverseWorldLocation = new Vector3(); + this.inverseWorldRotation = new Quaternion(); + this.inverseWorldScale = new Vector3(); + this.localMatrix = new Matrix4(); + this.worldMatrix = new Matrix4(); + this.dontInheritTranslation = false; + this.dontInheritRotation = false; + this.dontInheritScaling = false; + this.children = new ArrayList<>(); + + this.visible = true; + this.wasDirty = false; + + /** + * The object associated with this node, if there is any. + * + * @member {?} + */ + this.object = null; + + this.localRotation.w = 1; + + this.localScale.set(1, 1, 1); + + this.localMatrix.val[0] = 1; + this.localMatrix.val[5] = 1; + this.localMatrix.val[10] = 1; + this.localMatrix.val[15] = 1; + + this.dirty = true; + + this.billboarded = false; + this.billboardedX = false; + this.billboardedY = false; + this.billboardedZ = false; + } + + public void recalculateTransformation(final Scene scene) { + final Quaternion computedRotation; + Vector3 computedScaling; + + if (this.dontInheritScaling) { + computedScaling = scalingHeap; + + final Vector3 parentInverseScale = this.parent.inverseWorldScale; + computedScaling.x = parentInverseScale.x * this.localScale.x; + computedScaling.y = parentInverseScale.y * this.localScale.y; + computedScaling.z = parentInverseScale.z * this.localScale.z; + + this.worldScale.x = this.localScale.x; + this.worldScale.y = this.localScale.y; + this.worldScale.z = this.localScale.z; + } + else { + computedScaling = this.localScale; + + final Vector3 parentScale = this.parent.worldScale; + this.worldScale.x = parentScale.x * this.worldScale.x; + this.worldScale.y = parentScale.y * this.worldScale.y; + this.worldScale.z = parentScale.z * this.worldScale.z; + } + + if (this.billboarded) { + computedRotation = rotationHeap; + + computedRotation.set(this.parent.inverseWorldRotation); + computedRotation.mul(scene.camera.inverseRotation); + + this.convertBasis(computedRotation); + } + else { + computedRotation = this.localRotation; + } + + RenderMathUtils.fromRotationTranslationScaleOrigin(computedRotation, this.localLocation, computedScaling, + this.localMatrix, this.pivot); + + RenderMathUtils.mul(this.worldMatrix, this.parent.worldMatrix, this.localMatrix); + + RenderMathUtils.mul(this.worldRotation, this.parent.worldRotation, computedRotation); + + // Inverse world rotation + this.inverseWorldRotation.x = -this.worldRotation.x; + this.inverseWorldRotation.y = -this.worldRotation.y; + this.inverseWorldRotation.z = -this.worldRotation.z; + this.inverseWorldRotation.w = this.worldRotation.w; + + // Inverse world scale + this.inverseWorldScale.x = 1 / this.worldScale.x; + this.inverseWorldScale.y = 1 / this.worldScale.y; + this.inverseWorldScale.z = 1 / this.worldScale.z; + + // World location + final float x = this.pivot.x; + final float y = this.pivot.y; + final float z = this.pivot.z; + this.worldLocation.x = (this.worldMatrix.val[Matrix4.M00] * x) + (this.worldMatrix.val[Matrix4.M10] * y) + + (this.worldMatrix.val[Matrix4.M20] * z) + this.worldMatrix.val[Matrix4.M30]; + this.worldLocation.y = (this.worldMatrix.val[Matrix4.M01] * x) + (this.worldMatrix.val[Matrix4.M11] * y) + + (this.worldMatrix.val[Matrix4.M21] * z) + this.worldMatrix.val[Matrix4.M31]; + this.worldLocation.z = (this.worldMatrix.val[Matrix4.M02] * x) + (this.worldMatrix.val[Matrix4.M12] * y) + + (this.worldMatrix.val[Matrix4.M22] * z) + this.worldMatrix.val[Matrix4.M32]; + + // Inverse world location + this.inverseWorldLocation.x = -this.worldLocation.x; + this.inverseWorldLocation.y = -this.worldLocation.y; + this.inverseWorldLocation.z = -this.worldLocation.z; + } + + protected void updateChildren(final float dt, final Scene scene) { + for (int i = 0, l = this.children.size(); i < l; i++) { + this.children.get(i).update(dt, scene); + } + } + + protected abstract void convertBasis(Quaternion computedRotation); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/Handler.java b/core/src/com/etheller/warsmash/viewer5/handlers/Handler.java deleted file mode 100644 index f77aaba..0000000 --- a/core/src/com/etheller/warsmash/viewer5/handlers/Handler.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.etheller.warsmash.viewer5.handlers; - -public class Handler { - public BatchDescriptor batchDescriptor; -} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/ModelHandler.java b/core/src/com/etheller/warsmash/viewer5/handlers/ModelHandler.java new file mode 100644 index 0000000..59ea3a0 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/ModelHandler.java @@ -0,0 +1,6 @@ +package com.etheller.warsmash.viewer5.handlers; + +public abstract class ModelHandler extends ResourceHandler { + public BatchDescriptor batchDescriptor; + public ModelInstanceDescriptor instanceDescriptor; +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/ModelInstanceDescriptor.java b/core/src/com/etheller/warsmash/viewer5/handlers/ModelInstanceDescriptor.java new file mode 100644 index 0000000..336e9ed --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/ModelInstanceDescriptor.java @@ -0,0 +1,8 @@ +package com.etheller.warsmash.viewer5.handlers; + +import com.etheller.warsmash.viewer5.Model; +import com.etheller.warsmash.viewer5.ModelInstance; + +public interface ModelInstanceDescriptor { + ModelInstance create(Model model); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/ResourceHandler.java b/core/src/com/etheller/warsmash/viewer5/handlers/ResourceHandler.java new file mode 100644 index 0000000..c27bb52 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/ResourceHandler.java @@ -0,0 +1,10 @@ +package com.etheller.warsmash.viewer5.handlers; + +import com.etheller.warsmash.viewer5.ModelViewer; + +public abstract class ResourceHandler { + public ResourceHandler handler; + public boolean load; + + public abstract boolean load(ModelViewer modelViewer); +} From 6acbd13f27eda0ff2cc111601d41613088ba1ed7 Mon Sep 17 00:00:00 2001 From: Retera Date: Fri, 29 Nov 2019 01:41:09 -0600 Subject: [PATCH 006/116] Working towards mdx handler --- .../etheller/warsmash/parsers/mdlx/Layer.java | 25 +++ .../warsmash/parsers/mdlx/MdlxModel.java | 29 ++-- .../warsmash/parsers/mdlx/Sequence.java | 4 + .../mdlx/timeline/FloatArrayKeyFrame.java | 25 +++ .../parsers/mdlx/timeline/FloatKeyFrame.java | 23 +++ .../parsers/mdlx/timeline/KeyFrame.java | 6 + .../parsers/mdlx/timeline/Timeline.java | 12 ++ .../parsers/mdlx/timeline/UInt32KeyFrame.java | 22 +++ .../etheller/warsmash/util/Interpolator.java | 72 +++++++++ .../warsmash/util/RenderMathUtils.java | 92 ++++++++++- .../etheller/warsmash/viewer/ModelView.java | 5 +- .../viewer5/handlers/mdx/AnimatedObject.java | 6 + .../warsmash/viewer5/handlers/mdx/Layer.java | 30 ++++ .../viewer5/handlers/mdx/Material.java | 16 ++ .../warsmash/viewer5/handlers/mdx/Model.java | 25 +++ .../warsmash/viewer5/handlers/mdx/Sd.java | 130 +++++++++++++++ .../viewer5/handlers/mdx/SdSequence.java | 150 ++++++++++++++++++ 17 files changed, 660 insertions(+), 12 deletions(-) create mode 100644 core/src/com/etheller/warsmash/util/Interpolator.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/AnimatedObject.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/Layer.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/Material.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/Model.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/Sd.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/SdSequence.java diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/Layer.java b/core/src/com/etheller/warsmash/parsers/mdlx/Layer.java index 3ba2673..4621ed5 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/Layer.java +++ b/core/src/com/etheller/warsmash/parsers/mdlx/Layer.java @@ -192,4 +192,29 @@ public class Layer extends AnimatedObject { public long getByteLength() { return 28 + super.getByteLength(); } + + public FilterMode getFilterMode() { + return filterMode; + } + + public int getFlags() { + return flags; + } + + public int getTextureId() { + return textureId; + } + + public int getTextureAnimationId() { + return textureAnimationId; + } + + public long getCoordId() { + return coordId; + } + + public float getAlpha() { + return alpha; + } + } diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/MdlxModel.java b/core/src/com/etheller/warsmash/parsers/mdlx/MdlxModel.java index e5bc993..0c6bf12 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/MdlxModel.java +++ b/core/src/com/etheller/warsmash/parsers/mdlx/MdlxModel.java @@ -23,9 +23,12 @@ import com.google.common.io.LittleEndianDataOutputStream; * and text MDL file formats. */ public class MdlxModel { - // Below, these can't call a function on a string to make their value because - // switch/case statements require the value to be compile-time defined in order - // to be legal, and it appears to only allow basic binary operators for that. + // Below, these can't call a function on a string to make their value + // because + // switch/case statements require the value to be compile-time defined in + // order + // to be legal, and it appears to only allow basic binary operators for + // that. // I would love a clearer way to just type 'MDLX' in a character constant in // Java for this private static final int MDLX = ('M' << 24) | ('D' << 16) | ('L' << 8) | ('X');// War3ID.fromString("MDLX").getValue(); @@ -52,10 +55,10 @@ public class MdlxModel { private int version = 800; private String name = ""; /** - * (Comment copied from Ghostwolf JS) To the best of my knowledge, this should - * always be left empty. This is probably a leftover from the Warcraft 3 beta. - * (WS game note: No, I never saw any animation files in the RoC 2001-2002 Beta. - * So it must be from the Alpha) + * (Comment copied from Ghostwolf JS) To the best of my knowledge, this + * should always be left empty. This is probably a leftover from the + * Warcraft 3 beta. (WS game note: No, I never saw any animation files in + * the RoC 2001-2002 Beta. So it must be from the Alpha) * * @member {string} */ @@ -205,7 +208,7 @@ public class MdlxModel { private void loadDynamicObjects(final List out, final MdlxBlockDescriptor constructor, final LittleEndianDataInputStream stream, final long size) - throws IOException { + throws IOException { long totalSize = 0; while (totalSize < size) { final E object = constructor.create(); @@ -456,7 +459,7 @@ public class MdlxModel { private void loadNumberedObjectBlock(final List out, final MdlxBlockDescriptor constructor, final String name, final MdlTokenInputStream stream) - throws IOException { + throws IOException { stream.read(); // Don't care about the number, the array will grow for (final String token : stream.readBlock()) { @@ -639,4 +642,12 @@ public class MdlxModel { return 0; } + + public List getGlobalSequences() { + return globalSequences; + } + + public List getSequences() { + return sequences; + } } diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/Sequence.java b/core/src/com/etheller/warsmash/parsers/mdlx/Sequence.java index 18f2b1a..37cab80 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/Sequence.java +++ b/core/src/com/etheller/warsmash/parsers/mdlx/Sequence.java @@ -101,4 +101,8 @@ public class Sequence implements MdlxBlock { this.extent.writeMdl(stream); stream.endBlock(); } + + public long[] getInterval() { + return interval; + } } diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatArrayKeyFrame.java b/core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatArrayKeyFrame.java index 41120ba..5c5ed84 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatArrayKeyFrame.java +++ b/core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatArrayKeyFrame.java @@ -1,6 +1,7 @@ package com.etheller.warsmash.parsers.mdlx.timeline; import java.io.IOException; +import java.util.Arrays; import com.etheller.warsmash.parsers.mdlx.InterpolationType; import com.etheller.warsmash.parsers.mdlx.MdlTokenInputStream; @@ -96,4 +97,28 @@ public class FloatArrayKeyFrame implements KeyFrame { return size; } + @Override + public long getTime() { + return time; + } + + @Override + public boolean matchingValue(final KeyFrame other) { + if (other instanceof FloatArrayKeyFrame) { + final FloatArrayKeyFrame otherFrame = (FloatArrayKeyFrame) other; + return Arrays.equals(value, otherFrame.value); + } + return false; + } + + @Override + public KeyFrame clone(final long time) { + final FloatArrayKeyFrame newKeyFrame = new FloatArrayKeyFrame(value.length); + System.arraycopy(value, 0, newKeyFrame.value, 0, value.length); + // if (inTan != null) { + System.arraycopy(inTan, 0, newKeyFrame.inTan, 0, inTan.length); + System.arraycopy(outTan, 0, newKeyFrame.outTan, 0, outTan.length); + // } + return newKeyFrame; + } } diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatKeyFrame.java b/core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatKeyFrame.java index 5bb3254..e3dbfab 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatKeyFrame.java +++ b/core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatKeyFrame.java @@ -6,6 +6,7 @@ import com.etheller.warsmash.parsers.mdlx.InterpolationType; import com.etheller.warsmash.parsers.mdlx.MdlTokenInputStream; import com.etheller.warsmash.parsers.mdlx.MdlTokenOutputStream; import com.etheller.warsmash.util.ParseUtils; +import com.etheller.warsmash.util.RenderMathUtils; import com.google.common.io.LittleEndianDataInputStream; import com.google.common.io.LittleEndianDataOutputStream; @@ -90,4 +91,26 @@ public class FloatKeyFrame implements KeyFrame { return size; } + @Override + public long getTime() { + return time; + } + + @Override + public boolean matchingValue(final KeyFrame other) { + if (other instanceof FloatKeyFrame) { + final FloatKeyFrame otherFrame = (FloatKeyFrame) other; + return Math.abs(value - otherFrame.value) <= RenderMathUtils.EPSILON; + } + return false; + } + + @Override + public KeyFrame clone(final long time) { + final FloatKeyFrame newKeyFrame = new FloatKeyFrame(); + newKeyFrame.value = value; + newKeyFrame.inTan = inTan; + newKeyFrame.outTan = outTan; + return newKeyFrame; + } } diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/timeline/KeyFrame.java b/core/src/com/etheller/warsmash/parsers/mdlx/timeline/KeyFrame.java index bd4c9b4..f80078b 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/timeline/KeyFrame.java +++ b/core/src/com/etheller/warsmash/parsers/mdlx/timeline/KeyFrame.java @@ -18,4 +18,10 @@ public interface KeyFrame { void writeMdl(MdlTokenOutputStream stream, InterpolationType interpolationType) throws IOException; long getByteLength(InterpolationType interpolationType); + + long getTime(); + + boolean matchingValue(KeyFrame other); + + KeyFrame clone(long time); } diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/timeline/Timeline.java b/core/src/com/etheller/warsmash/parsers/mdlx/timeline/Timeline.java index 97d8f77..8564937 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/timeline/Timeline.java +++ b/core/src/com/etheller/warsmash/parsers/mdlx/timeline/Timeline.java @@ -148,4 +148,16 @@ public abstract class Timeline implements Chunk { protected abstract KeyFrame newKeyFrame(); protected abstract int size(); + + public int getGlobalSequenceId() { + return globalSequenceId; + } + + public List getKeyFrames() { + return keyFrames; + } + + public InterpolationType getInterpolationType() { + return interpolationType; + } } diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/timeline/UInt32KeyFrame.java b/core/src/com/etheller/warsmash/parsers/mdlx/timeline/UInt32KeyFrame.java index a858378..e5401e5 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/timeline/UInt32KeyFrame.java +++ b/core/src/com/etheller/warsmash/parsers/mdlx/timeline/UInt32KeyFrame.java @@ -90,4 +90,26 @@ public class UInt32KeyFrame implements KeyFrame { return size; } + @Override + public long getTime() { + return time; + } + + @Override + public boolean matchingValue(final KeyFrame other) { + if (other instanceof UInt32KeyFrame) { + final UInt32KeyFrame otherFrame = (UInt32KeyFrame) other; + return value == otherFrame.value; + } + return false; + } + + @Override + public KeyFrame clone(final long time) { + final UInt32KeyFrame newKeyFrame = new UInt32KeyFrame(); + newKeyFrame.value = value; + newKeyFrame.inTan = inTan; + newKeyFrame.outTan = outTan; + return newKeyFrame; + } } diff --git a/core/src/com/etheller/warsmash/util/Interpolator.java b/core/src/com/etheller/warsmash/util/Interpolator.java new file mode 100644 index 0000000..94d9943 --- /dev/null +++ b/core/src/com/etheller/warsmash/util/Interpolator.java @@ -0,0 +1,72 @@ +package com.etheller.warsmash.util; + +public class Interpolator { + public void interpolateScalar(final float[] out, final float[] a, final float[] b, final float[] c, final float[] d, + final float t, final int type) { + switch (type) { + case 0: { + out[0] = a[0]; + break; + } + case 1: { + out[0] = RenderMathUtils.lerp(a[0], d[0], t); + break; + } + case 2: { + out[0] = RenderMathUtils.hermite(a[0], b[0], c[0], d[0], t); + break; + } + case 3: { + out[0] = RenderMathUtils.bezier(a[0], b[0], c[0], d[0], t); + break; + } + } + } + + public void interpolateVector(final float[] out, final float[] a, final float[] b, final float[] c, final float[] d, + final float t, final int type) { + switch (type) { + case 0: { + System.arraycopy(a, 0, out, 0, a.length); + break; + } + case 1: { + out[0] = RenderMathUtils.lerp(a[0], d[0], t); + out[1] = RenderMathUtils.lerp(a[1], d[1], t); + out[2] = RenderMathUtils.lerp(a[2], d[2], t); + break; + } + case 2: { + out[0] = RenderMathUtils.hermite(a[0], b[0], c[0], d[0], t); + out[1] = RenderMathUtils.hermite(a[1], b[1], c[1], d[1], t); + out[2] = RenderMathUtils.hermite(a[2], b[2], c[2], d[2], t); + break; + } + case 3: { + out[0] = RenderMathUtils.bezier(a[0], b[0], c[0], d[0], t); + out[1] = RenderMathUtils.bezier(a[1], b[1], c[1], d[1], t); + out[2] = RenderMathUtils.bezier(a[2], b[2], c[2], d[2], t); + break; + } + } + } + + public void interpolateQuaternion(final float[] out, final float[] a, final float[] b, final float[] c, + final float[] d, final float t, final int type) { + switch (type) { + case 0: { + System.arraycopy(a, 0, out, 0, a.length); + break; + } + case 1: { + RenderMathUtils.slerp(out, a, d, t); + break; + } + case 2: + case 3: { + RenderMathUtils.sqlerp(out, a, b, c, d, t); + break; + } + } + } +} diff --git a/core/src/com/etheller/warsmash/util/RenderMathUtils.java b/core/src/com/etheller/warsmash/util/RenderMathUtils.java index da0c6fc..ec09fe4 100644 --- a/core/src/com/etheller/warsmash/util/RenderMathUtils.java +++ b/core/src/com/etheller/warsmash/util/RenderMathUtils.java @@ -101,12 +101,14 @@ public enum RenderMathUtils { } public static void mul(final Matrix4 dest, final Matrix4 left, final Matrix4 right) { - dest.set(left); // TODO better performance here, remove the extra copying + dest.set(left); // TODO better performance here, remove the extra + // copying dest.mul(right); } public static void mul(final Quaternion dest, final Quaternion left, final Quaternion right) { - dest.set(left); // TODO better performance here, remove the extra copying + dest.set(left); // TODO better performance here, remove the extra + // copying dest.mul(right); } @@ -352,4 +354,90 @@ public enum RenderMathUtils { public static float distanceToPlane3(final Vector4 plane, final float px, final float py, final float pz) { return (plane.x * px) + (plane.y * py) + (plane.z * pz) + plane.w; } + + public static float randomInRange(final float a, final float b) { + return (float) (a + Math.random() * (b - a)); + } + + public static float clamp(final float x, final float minVal, final float maxVal) { + return Math.min(Math.max(x, minVal), maxVal); + } + + public static float lerp(final float a, final float b, final float t) { + return a + t * (b - a); + } + + public static float hermite(final float a, final float b, final float c, final float d, final float t) { + final float factorTimes2 = t * t; + final float factor1 = factorTimes2 * (2 * t - 3) + 1; + final float factor2 = factorTimes2 * (t - 2) + t; + final float factor3 = factorTimes2 * (t - 1); + final float factor4 = factorTimes2 * (3 - 2 * t); + return (a * factor1) + (b * factor2) + (c * factor3) + (d * factor4); + } + + public static float bezier(final float a, final float b, final float c, final float d, final float t) { + final float invt = 1 - t; + final float factorTimes2 = t * t; + final float inverseFactorTimesTwo = invt * invt; + final float factor1 = inverseFactorTimesTwo * invt; + final float factor2 = 3 * t * inverseFactorTimesTwo; + final float factor3 = 3 * factorTimes2 * invt; + final float factor4 = factorTimes2 * t; + + return (a * factor1) + (b * factor2) + (c * factor3) + (d * factor4); + } + + public static final float EPSILON = 0.000001f; + + public static float[] slerp(final float[] out, final float[] a, final float[] b, final float t) { + final float ax = a[0], ay = a[1], az = a[2], aw = a[3]; + float bx = b[0], by = b[1], bz = b[2], bw = b[3]; + + float omega, cosom, sinom, scale0, scale1; + + // calc cosine + cosom = ax * bx + ay * by + az * bz + aw * bw; + // adjust signs (if necessary) + if (cosom < 0.0) { + cosom = -cosom; + bx = -bx; + by = -by; + bz = -bz; + bw = -bw; + } + // calculate coefficients + if ((1.0 - cosom) > EPSILON) { + // standard case (slerp) + omega = (float) Math.acos(cosom); + sinom = (float) Math.sin(omega); + scale0 = (float) (Math.sin((1.0 - t) * omega) / sinom); + scale1 = (float) (Math.sin(t * omega) / sinom); + } + else { + // "from" and "to" quaternions are very close + // ... so we can do a linear interpolation + scale0 = 1.0f - t; + scale1 = t; + } + // calculate final values + out[0] = scale0 * ax + scale1 * bx; + out[1] = scale0 * ay + scale1 * by; + out[2] = scale0 * az + scale1 * bz; + out[3] = scale0 * aw + scale1 * bw; + + return out; + } + + private static final float[] sqlerpHeap1 = new float[4]; + private static final float[] sqlerpHeap2 = new float[4]; + + public static float[] sqlerp(final float[] out, final float[] a, final float[] b, final float[] c, final float[] d, + final float t) { + slerp(sqlerpHeap1, a, d, t); + slerp(sqlerpHeap2, b, c, t); + slerp(out, sqlerpHeap1, sqlerpHeap2, 2 * t * (1 - t)); + return out; + } + } diff --git a/core/src/com/etheller/warsmash/viewer/ModelView.java b/core/src/com/etheller/warsmash/viewer/ModelView.java index e4004a6..c2c24f6 100644 --- a/core/src/com/etheller/warsmash/viewer/ModelView.java +++ b/core/src/com/etheller/warsmash/viewer/ModelView.java @@ -33,7 +33,10 @@ public abstract class ModelView { @Override public abstract boolean equals(Object view); -// public boo + @Override + public abstract int hashCode(); + + // public boo public void addSceneData(final ModelInstance instance, final Scene scene) { if (this.model.ok && (scene != null)) { SceneData data = this.sceneData.get(scene); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/AnimatedObject.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/AnimatedObject.java new file mode 100644 index 0000000..75aac83 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/AnimatedObject.java @@ -0,0 +1,6 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +public class AnimatedObject { + public Model model; + public +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Layer.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Layer.java new file mode 100644 index 0000000..95639c2 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Layer.java @@ -0,0 +1,30 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +import com.etheller.warsmash.parsers.mdlx.Layer.FilterMode; + +public class Layer { + public Model model; + public com.etheller.warsmash.parsers.mdlx.Layer layer; + public int layerId; + public int priorityPlane; + + public int filterMode; + public int textureId; + public int coordId; + public float alpha; + + public Layer(final Model model, final com.etheller.warsmash.parsers.mdlx.Layer layer, final int layerId, + final int priorityPlane) { + super(model, layer); + this.model = model; + this.layer = layer; + this.layerId = layerId; + this.priorityPlane = priorityPlane; + + final FilterMode filterMode2 = layer.getFilterMode(); + this.filterMode = filterMode2.ordinal(); + this.textureId = layer.getTextureId(); + // this.coo + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Material.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Material.java new file mode 100644 index 0000000..fe589c6 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Material.java @@ -0,0 +1,16 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +import java.util.List; + +public class Material { + public final Model model; + public final String shader; + public final List layers; + + public Material(final Model model, final String shader, final List layers) { + this.model = model; + this.shader = shader; + this.layers = layers; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Model.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Model.java new file mode 100644 index 0000000..a695149 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Model.java @@ -0,0 +1,25 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +import java.io.InputStream; + +public class Model extends com.etheller.warsmash.viewer5.Model { + + @Override + protected void lateLoad() { + // TODO Auto-generated method stub + + } + + @Override + protected void load(final InputStream src, final Object options) { + // TODO Auto-generated method stub + + } + + @Override + protected void error(final Exception e) { + // TODO Auto-generated method stub + + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Sd.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Sd.java new file mode 100644 index 0000000..b3b2325 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Sd.java @@ -0,0 +1,130 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.etheller.warsmash.parsers.mdlx.MdlxModel; +import com.etheller.warsmash.parsers.mdlx.Sequence; +import com.etheller.warsmash.parsers.mdlx.timeline.KeyFrame; +import com.etheller.warsmash.parsers.mdlx.timeline.Timeline; +import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.ModelInstance; + +public abstract class Sd { + public MdlxModel model; + public int interpolationType; + public War3ID name; + public float[] defval; + public SdSequence globalSequence; + public List> sequences; + + public static Map forcedInterpMap = new HashMap<>(); + + static { + forcedInterpMap.put(War3ID.fromString("KLAV"), 0); + forcedInterpMap.put(War3ID.fromString("KATV"), 0); + forcedInterpMap.put(War3ID.fromString("KPEV"), 0); + forcedInterpMap.put(War3ID.fromString("KP2V"), 0); + forcedInterpMap.put(War3ID.fromString("KRVS"), 0); + } + + public static Map defVals = new HashMap<>(); + + static { + // LAYS + defVals.put(War3ID.fromString("KMTF"), new float[] { 0 }); + defVals.put(War3ID.fromString("KMTA"), new float[] { 1 }); + // TXAN + defVals.put(War3ID.fromString("KTAT"), new float[] { 0, 0, 0 }); + defVals.put(War3ID.fromString("KTAR"), new float[] { 0, 0, 0, 1 }); + defVals.put(War3ID.fromString("KTAS"), new float[] { 1, 1, 1 }); + // GEOA + defVals.put(War3ID.fromString("KGAO"), new float[] { 1 }); + defVals.put(War3ID.fromString("KGAC"), new float[] { 0, 0, 0 }); + // LITE + defVals.put(War3ID.fromString("KLAS"), new float[] { 0 }); + defVals.put(War3ID.fromString("KLAE"), new float[] { 0 }); + defVals.put(War3ID.fromString("KLAC"), new float[] { 0, 0, 0 }); + defVals.put(War3ID.fromString("KLAI"), new float[] { 0 }); + defVals.put(War3ID.fromString("KLBI"), new float[] { 0 }); + defVals.put(War3ID.fromString("KLBC"), new float[] { 0, 0, 0 }); + defVals.put(War3ID.fromString("KLAV"), new float[] { 1 }); + // ATCH + defVals.put(War3ID.fromString("KATV"), new float[] { 1 }); + // PREM + defVals.put(War3ID.fromString("KPEE"), new float[] { 0 }); + defVals.put(War3ID.fromString("KPEG"), new float[] { 0 }); + defVals.put(War3ID.fromString("KPLN"), new float[] { 0 }); + defVals.put(War3ID.fromString("KPLT"), new float[] { 0 }); + defVals.put(War3ID.fromString("KPEL"), new float[] { 0 }); + defVals.put(War3ID.fromString("KPES"), new float[] { 0 }); + defVals.put(War3ID.fromString("KPEV"), new float[] { 1 }); + // PRE2 + defVals.put(War3ID.fromString("KP2S"), new float[] { 0 }); + defVals.put(War3ID.fromString("KP2R"), new float[] { 0 }); + defVals.put(War3ID.fromString("KP2L"), new float[] { 0 }); + defVals.put(War3ID.fromString("KP2G"), new float[] { 0 }); + defVals.put(War3ID.fromString("KP2E"), new float[] { 0 }); + defVals.put(War3ID.fromString("KP2N"), new float[] { 0 }); + defVals.put(War3ID.fromString("KP2W"), new float[] { 0 }); + defVals.put(War3ID.fromString("KP2V"), new float[] { 1 }); + // RIBB + defVals.put(War3ID.fromString("KRHA"), new float[] { 0 }); + defVals.put(War3ID.fromString("KRHB"), new float[] { 0 }); + defVals.put(War3ID.fromString("KRAL"), new float[] { 1 }); + defVals.put(War3ID.fromString("KRCO"), new float[] { 0, 0, 0 }); + defVals.put(War3ID.fromString("KRTX"), new float[] { 0 }); + defVals.put(War3ID.fromString("KRVS"), new float[] { 1 }); + // CAMS + defVals.put(War3ID.fromString("KCTR"), new float[] { 0, 0, 0 }); + defVals.put(War3ID.fromString("KTTR"), new float[] { 0, 0, 0 }); + defVals.put(War3ID.fromString("KCRL"), new float[] { 0 }); + // NODE + defVals.put(War3ID.fromString("KGTR"), new float[] { 0, 0, 0 }); + defVals.put(War3ID.fromString("KGRT"), new float[] { 0, 0, 0, 1 }); + defVals.put(War3ID.fromString("KGSC"), new float[] { 1, 1, 1 }); + + } + + public Sd(final MdlxModel model, final Timeline timeline) { + final List globalSequences = model.getGlobalSequences(); + final int globalSequenceId = timeline.getGlobalSequenceId(); + final List keyFrames = timeline.getKeyFrames(); + final Integer forcedInterp = forcedInterpMap.get(timeline.getName()); + + this.model = model; + this.name = timeline.getName(); + this.defval = defVals.get(timeline.getName()); + this.globalSequence = null; + this.sequences = new ArrayList<>(); + + // Allow to force an interpolation type. + // The game seems to do this with visibility tracks, where the type is + // forced to None. + // It came up as a bug report by a user who used the wrong interpolation + // type. + this.interpolationType = forcedInterp != null ? forcedInterp : timeline.getInterpolationType().ordinal(); + + if (globalSequenceId != -1 && globalSequences.size() > 0) { + this.globalSequence = newSequenceTyped(this, 0, globalSequences.get(globalSequenceId).longValue(), + keyFrames, true); + } + else { + for (final Sequence sequence : model.getSequences()) { + final long[] interval = sequence.getInterval(); + this.sequences.add(newSequenceTyped(this, interval[0], interval[1], keyFrames, false)); + } + } + } + + public int getValue(final TYPE out, final ModelInstance instance) { + if(this.globalSequence != null) { + return this.globalSequence + } + } + + protected abstract SdSequence newSequenceTyped(final Sd parent, final long start, final long end, + final List keyframes, final boolean isGlobalSequence); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SdSequence.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SdSequence.java new file mode 100644 index 0000000..3f2ee00 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SdSequence.java @@ -0,0 +1,150 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +import java.util.ArrayList; +import java.util.List; + +import com.etheller.warsmash.parsers.mdlx.timeline.KeyFrame; +import com.etheller.warsmash.util.RenderMathUtils; + +public abstract class SdSequence { + + private final Sd sd; + private final long start; // UInt32 + private final long end; // UInt32 + private final List keyframes; + private boolean constant; + + public SdSequence(final Sd sd, final long start, final long end, final List keyframes, + final boolean isGlobalSequence) { + final TYPE defval = convertDefaultValue(sd.defval); + + this.sd = sd; + this.start = start; + this.end = end; + this.keyframes = new ArrayList<>(); + + // When using a global sequence, where the first key is outside of the + // sequence's length, it becomes its constant value. + // When having one key in the sequence's range, and one key outside of + // it, results seem to be non-deterministic. + // Sometimes the second key is used too, sometimes not. + // It also differs depending where the model is viewed - the WE + // previewer, the WE itself, or the game. + // All three show different results, none of them make sense. + // Therefore, only handle the case where the first key is outside. + // This fixes problems spread over many models, e.g. HeroMountainKing + // (compare in WE and in Magos). + if (isGlobalSequence && keyframes.size() > 0 && keyframes.get(0).getTime() > end) { + this.keyframes.add(keyframes.get(0)); + } + + // Go over the keyframes, and add all of the ones that are in this + // sequence (start <= frame <= end). + for (int i = 0, l = keyframes.size(); i < l; i++) { + final KeyFrame keyFrame = keyframes.get(i); + final long frame = keyFrame.getTime(); + + if (frame >= start && frame <= end) { + this.keyframes.add(keyFrame); + } + } + + final int keyframeCount = this.keyframes.size(); + + if (keyframeCount == 0) { + // if there are no keys, use the default value directly. + this.constant = true; + this.keyframes.add(createKeyFrame(start, defval)); + } + else if (keyframeCount == 1) { + // If there's only one key, use it directly + this.constant = true; + } + else { + final KeyFrame firstFrame = this.keyframes.get(0); + + // If all of the values in this sequence are the same, might as well + // make it constant. + boolean allFramesMatch = true; + for (final KeyFrame frame : this.keyframes) { + if (!frame.matchingValue(firstFrame)) { + allFramesMatch = false; + } + } + this.constant = allFramesMatch; + + if (!this.constant) { + // If there is no opening keyframe for this sequence, inject one + // with the default value. + if (this.keyframes.get(0).getTime() != start) { + this.keyframes.add(0, createKeyFrame(start, defval)); + } + + // If there is no closing keyframe for this sequence, inject one + // with the default value. + if (this.keyframes.get(this.keyframes.size() - 1).getTime() != end) { + this.keyframes.add(this.keyframes.get(0).clone(end)); + } + } + } + } + + public int getValue(final TYPE out, final long frame) { + final int index = this.getKeyframe(frame); + final int size = keyframes.size(); + + if (index == -1) { + set(out, keyframes.get(0)); + + return 0; + } + else if (index == size) { + set(out, keyframes.get(size - 1)); + + return size - 1; + } + else { + final KeyFrame start = keyframes.get(index - 1); + final KeyFrame end = keyframes.get(index); + final float t = RenderMathUtils.clamp((frame - start.getTime()) / (end.getTime() - start.getTime()), 0, 1); + + interpolate(out, start, end, t); + + return index; + } + } + + public int getKeyframe(final long frame) { + if (this.constant) { + return -1; + } + else { + final int l = keyframes.size(); + + if (frame < this.start) { + return -1; + } + else if (frame >= this.end) { + return 1; + } + else { + for (int i = 1; i < l; i++) { + final KeyFrame keyframe = keyframes.get(i); + + if (keyframe.getTime() > frame) { + return i; + } + } + } + } + return -1; + } + + protected abstract void set(TYPE out, KeyFrame frameForValue); + + protected abstract TYPE convertDefaultValue(float[] defaultValue); + + protected abstract KeyFrame createKeyFrame(long time, TYPE value); + + protected abstract void interpolate(TYPE out, KeyFrame a, KeyFrame b, float t); +} From dca8289d1408de53848e5cf8b5955381e12d33e9 Mon Sep 17 00:00:00 2001 From: Retera Date: Sun, 8 Dec 2019 11:04:05 -0600 Subject: [PATCH 007/116] Changed sd --- core/src/com/etheller/warsmash/viewer5/handlers/mdx/Sd.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Sd.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Sd.java index b3b2325..6e5843b 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Sd.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Sd.java @@ -121,7 +121,7 @@ public abstract class Sd { public int getValue(final TYPE out, final ModelInstance instance) { if(this.globalSequence != null) { - return this.globalSequence + return this.globalSequence.getValue(out, instance.cout) } } From 005dd375ad26fb6455861069cc18dff4bd940786 Mon Sep 17 00:00:00 2001 From: Retera Date: Tue, 17 Dec 2019 02:00:19 -0600 Subject: [PATCH 008/116] More updates for handlers mdx --- .../warsmash/parsers/mdlx/AnimatedObject.java | 16 +- .../warsmash/parsers/mdlx/Attachment.java | 8 + .../etheller/warsmash/parsers/mdlx/Bone.java | 3 + .../warsmash/parsers/mdlx/Camera.java | 24 ++ .../warsmash/parsers/mdlx/EventObject.java | 17 + .../warsmash/parsers/mdlx/GenericObject.java | 40 +- .../parsers/mdlx/GeosetAnimation.java | 12 + .../warsmash/parsers/mdlx/MdlxModel.java | 20 +- .../parsers/mdlx/ParticleEmitter2.java | 93 ++++ .../warsmash/parsers/mdlx/RibbonEmitter.java | 45 ++ .../mdlx/timeline/FloatArrayKeyFrame.java | 124 ------ .../mdlx/timeline/FloatArrayTimeline.java | 41 +- .../parsers/mdlx/timeline/FloatKeyFrame.java | 116 ----- .../parsers/mdlx/timeline/FloatTimeline.java | 32 +- .../parsers/mdlx/timeline/KeyFrame.java | 27 -- .../parsers/mdlx/timeline/Timeline.java | 129 ++++-- .../parsers/mdlx/timeline/UInt32KeyFrame.java | 115 ----- .../parsers/mdlx/timeline/UInt32Timeline.java | 33 +- .../etheller/warsmash/util/Interpolator.java | 10 +- .../warsmash/util/RenderMathUtils.java | 23 +- .../com/etheller/warsmash/viewer5/Camera.java | 6 +- .../warsmash/viewer5/EmittedObject.java | 6 +- .../etheller/warsmash/viewer5/Emitter.java | 33 +- .../warsmash/viewer5/GenericNode.java | 33 ++ .../com/etheller/warsmash/viewer5/Model.java | 11 +- .../warsmash/viewer5/ModelInstance.java | 3 +- .../warsmash/viewer5/ModelViewer.java | 76 +++- .../com/etheller/warsmash/viewer5/Node.java | 28 +- .../etheller/warsmash/viewer5/PathSolver.java | 5 + .../etheller/warsmash/viewer5/Resource.java | 6 +- .../com/etheller/warsmash/viewer5/Scene.java | 2 +- .../etheller/warsmash/viewer5/Shaders.java | 2 +- .../warsmash/viewer5/SkeletalNode.java | 42 +- .../etheller/warsmash/viewer5/SolvedPath.java | 26 ++ .../etheller/warsmash/viewer5/Texture.java | 42 ++ .../warsmash/viewer5/TextureMapper.java | 2 - .../etheller/warsmash/viewer5/gl/WebGL.java | 8 +- .../viewer5/handlers/EmitterObject.java | 4 +- .../viewer5/handlers/ResourceHandler.java | 6 + .../ResourceHandlerConstructionParams.java | 42 ++ .../viewer5/handlers/blp/BlpHandler.java | 28 ++ .../viewer5/handlers/blp/BlpTexture.java | 43 ++ .../viewer5/handlers/mdx/AnimatedObject.java | 107 ++++- .../viewer5/handlers/mdx/Attachment.java | 30 ++ .../handlers/mdx/AttachmentInstance.java | 49 +++ .../warsmash/viewer5/handlers/mdx/Batch.java | 16 + .../viewer5/handlers/mdx/BatchGroup.java | 124 ++++++ .../warsmash/viewer5/handlers/mdx/Bone.java | 24 ++ .../warsmash/viewer5/handlers/mdx/Camera.java | 37 ++ .../viewer5/handlers/mdx/CollisionShape.java | 10 + .../viewer5/handlers/mdx/EmitterGroup.java | 64 +++ .../handlers/mdx/EventObjectEmitter.java | 31 ++ .../mdx/EventObjectEmitterObject.java | 69 +++ .../handlers/mdx/EventObjectSplUbr.java | 12 + .../viewer5/handlers/mdx/FilterMode.java | 46 ++ .../viewer5/handlers/mdx/GenericObject.java | 159 +++++++ .../handlers/mdx/GeometryEmitterFuncs.java | 287 ++++++++++++ .../warsmash/viewer5/handlers/mdx/Geoset.java | 150 +++++++ .../viewer5/handlers/mdx/GeosetAnimation.java | 37 ++ .../warsmash/viewer5/handlers/mdx/Layer.java | 13 +- .../viewer5/handlers/mdx/Material.java | 4 +- .../handlers/mdx/MdxComplexInstance.java | 70 +++ .../viewer5/handlers/mdx/MdxEmitter.java | 25 ++ .../viewer5/handlers/mdx/MdxHandler.java | 64 +++ .../viewer5/handlers/mdx/MdxModel.java | 73 ++++ .../viewer5/handlers/mdx/MdxNode.java | 22 + .../viewer5/handlers/mdx/MdxShaders.java | 408 ++++++++++++++++++ .../handlers/mdx/MdxSimpleInstance.java | 37 ++ .../warsmash/viewer5/handlers/mdx/Model.java | 25 -- .../viewer5/handlers/mdx/Particle2.java | 111 +++++ .../handlers/mdx/ParticleEmitter2.java | 54 +++ .../handlers/mdx/ParticleEmitter2Object.java | 151 +++++++ .../viewer5/handlers/mdx/QuaternionSd.java | 29 ++ .../viewer5/handlers/mdx/ReplaceableIds.java | 22 + .../warsmash/viewer5/handlers/mdx/Ribbon.java | 89 ++++ .../viewer5/handlers/mdx/RibbonEmitter.java | 73 ++++ .../handlers/mdx/RibbonEmitterObject.java | 77 ++++ .../viewer5/handlers/mdx/ScalarSd.java | 44 ++ .../warsmash/viewer5/handlers/mdx/Sd.java | 52 ++- .../viewer5/handlers/mdx/SdSequence.java | 182 ++++---- .../handlers/mdx/TextureAnimation.java | 37 ++ .../viewer5/handlers/mdx/UInt32Sd.java | 48 +++ .../viewer5/handlers/mdx/VectorSd.java | 29 ++ 83 files changed, 3692 insertions(+), 681 deletions(-) delete mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatArrayKeyFrame.java delete mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatKeyFrame.java delete mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/timeline/KeyFrame.java delete mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/timeline/UInt32KeyFrame.java create mode 100644 core/src/com/etheller/warsmash/viewer5/GenericNode.java create mode 100644 core/src/com/etheller/warsmash/viewer5/PathSolver.java create mode 100644 core/src/com/etheller/warsmash/viewer5/SolvedPath.java create mode 100644 core/src/com/etheller/warsmash/viewer5/Texture.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/ResourceHandlerConstructionParams.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/blp/BlpHandler.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/blp/BlpTexture.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/Attachment.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/AttachmentInstance.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/Batch.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/BatchGroup.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/Bone.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/Camera.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/CollisionShape.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/EmitterGroup.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectEmitter.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectEmitterObject.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectSplUbr.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/FilterMode.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/GenericObject.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/GeometryEmitterFuncs.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/Geoset.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/GeosetAnimation.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxEmitter.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxHandler.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxNode.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxShaders.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxSimpleInstance.java delete mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/Model.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/Particle2.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/ParticleEmitter2.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/ParticleEmitter2Object.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/QuaternionSd.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/ReplaceableIds.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/Ribbon.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/RibbonEmitter.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/RibbonEmitterObject.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/ScalarSd.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/TextureAnimation.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/UInt32Sd.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/VectorSd.java diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/AnimatedObject.java b/core/src/com/etheller/warsmash/parsers/mdlx/AnimatedObject.java index c15855a..e665208 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/AnimatedObject.java +++ b/core/src/com/etheller/warsmash/parsers/mdlx/AnimatedObject.java @@ -16,7 +16,7 @@ import com.google.common.io.LittleEndianDataOutputStream; * */ public abstract class AnimatedObject implements Chunk, MdlxBlock { - protected final List timelines; + protected final List> timelines; public AnimatedObject() { this.timelines = new ArrayList<>(); @@ -25,7 +25,7 @@ public abstract class AnimatedObject implements Chunk, MdlxBlock { public void readTimelines(final LittleEndianDataInputStream stream, long size) throws IOException { while (size > 0) { final War3ID name = new War3ID(Integer.reverseBytes(stream.readInt())); - final Timeline timeline = AnimationMap.ID_TO_TAG.get(name).getImplementation().createTimeline(); + final Timeline timeline = AnimationMap.ID_TO_TAG.get(name).getImplementation().createTimeline(); timeline.readMdx(stream, name); @@ -36,7 +36,7 @@ public abstract class AnimatedObject implements Chunk, MdlxBlock { } public void writeTimelines(final LittleEndianDataOutputStream stream) throws IOException { - for (final Timeline timeline : this.timelines) { + for (final Timeline timeline : this.timelines) { timeline.writeMdx(stream); } } @@ -46,7 +46,7 @@ public abstract class AnimatedObject implements Chunk, MdlxBlock { } public void readTimeline(final MdlTokenInputStream stream, final AnimationMap name) throws IOException { - final Timeline timeline = name.getImplementation().createTimeline(); + final Timeline timeline = name.getImplementation().createTimeline(); timeline.readMdl(stream, name.getWar3id()); @@ -54,7 +54,7 @@ public abstract class AnimatedObject implements Chunk, MdlxBlock { } public boolean writeTimeline(final MdlTokenOutputStream stream, final AnimationMap name) throws IOException { - for (final Timeline timeline : this.timelines) { + for (final Timeline timeline : this.timelines) { if (timeline.getName().equals(name.getWar3id())) { timeline.writeMdl(stream); return true; @@ -66,7 +66,7 @@ public abstract class AnimatedObject implements Chunk, MdlxBlock { @Override public long getByteLength() { long size = 0; - for (final Timeline timeline : this.timelines) { + for (final Timeline timeline : this.timelines) { size += timeline.getByteLength(); } return size; @@ -101,4 +101,8 @@ public abstract class AnimatedObject implements Chunk, MdlxBlock { } } + + public List> getTimelines() { + return this.timelines; + } } diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/Attachment.java b/core/src/com/etheller/warsmash/parsers/mdlx/Attachment.java index 85d65b0..bf12d25 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/Attachment.java +++ b/core/src/com/etheller/warsmash/parsers/mdlx/Attachment.java @@ -96,4 +96,12 @@ public class Attachment extends GenericObject { public long getByteLength() { return 268 + super.getByteLength(); } + + public String getPath() { + return this.path; + } + + public int getAttachmentId() { + return this.attachmentId; + } } diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/Bone.java b/core/src/com/etheller/warsmash/parsers/mdlx/Bone.java index 8c4fe7a..ff69b00 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/Bone.java +++ b/core/src/com/etheller/warsmash/parsers/mdlx/Bone.java @@ -85,4 +85,7 @@ public class Bone extends GenericObject { return 8 + super.getByteLength(); } + public int getGeosetAnimationId() { + return this.geosetAnimationId; + } } diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/Camera.java b/core/src/com/etheller/warsmash/parsers/mdlx/Camera.java index 1c3c363..75c8770 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/Camera.java +++ b/core/src/com/etheller/warsmash/parsers/mdlx/Camera.java @@ -125,4 +125,28 @@ public class Camera extends AnimatedObject { public long getByteLength() { return 120 + super.getByteLength(); } + + public String getName() { + return this.name; + } + + public float[] getPosition() { + return this.position; + } + + public float getFieldOfView() { + return this.fieldOfView; + } + + public float getFarClippingPlane() { + return this.farClippingPlane; + } + + public float getNearClippingPlane() { + return this.nearClippingPlane; + } + + public float[] getTargetPosition() { + return this.targetPosition; + } } diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/EventObject.java b/core/src/com/etheller/warsmash/parsers/mdlx/EventObject.java index 5e4a62a..987d359 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/EventObject.java +++ b/core/src/com/etheller/warsmash/parsers/mdlx/EventObject.java @@ -74,4 +74,21 @@ public class EventObject extends GenericObject { public long getByteLength() { return 12 + (this.keyFrames.length * 4) + super.getByteLength(); } + + public int getGlobalSequenceId() { + return this.globalSequenceId; + } + + public void setGlobalSequenceId(final int globalSequenceId) { + this.globalSequenceId = globalSequenceId; + } + + public long[] getKeyFrames() { + return this.keyFrames; + } + + public void setKeyFrames(final long[] keyFrames) { + this.keyFrames = keyFrames; + } + } diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/GenericObject.java b/core/src/com/etheller/warsmash/parsers/mdlx/GenericObject.java index b5018b0..66171fb 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/GenericObject.java +++ b/core/src/com/etheller/warsmash/parsers/mdlx/GenericObject.java @@ -61,13 +61,13 @@ public abstract class GenericObject extends AnimatedObject implements Chunk { stream.writeInt(this.parentId); stream.writeInt(this.flags); // UInt32 in ghostwolf JS, shouldn't matter for Java - for (final Timeline timeline : eachTimeline(true)) { + for (final Timeline timeline : eachTimeline(true)) { timeline.writeMdx(stream); } } public void writeNonGenericAnimationChunks(final LittleEndianDataOutputStream stream) throws IOException { - for (final Timeline timeline : eachTimeline(false)) { + for (final Timeline timeline : eachTimeline(false)) { timeline.writeMdx(stream); } } @@ -129,7 +129,7 @@ public abstract class GenericObject extends AnimatedObject implements Chunk { this.writeTimeline(stream, AnimationMap.KGSC); } - public Iterable eachTimeline(final boolean generic) { + public Iterable> eachTimeline(final boolean generic) { return new TimelineMaskingIterable(generic); } @@ -146,7 +146,7 @@ public abstract class GenericObject extends AnimatedObject implements Chunk { return 96 + super.getByteLength(); } - private final class TimelineMaskingIterable implements Iterable { + private final class TimelineMaskingIterable implements Iterable> { private final boolean generic; private TimelineMaskingIterable(final boolean generic) { @@ -154,24 +154,24 @@ public abstract class GenericObject extends AnimatedObject implements Chunk { } @Override - public Iterator iterator() { + public Iterator> iterator() { return new TimelineMaskingIterator(this.generic, GenericObject.this.timelines); } } - private static final class TimelineMaskingIterator implements Iterator { + private static final class TimelineMaskingIterator implements Iterator> { private final boolean wantGeneric; - private final Iterator delegate; + private final Iterator> delegate; private boolean hasNext; - private Timeline next; + private Timeline next; - public TimelineMaskingIterator(final boolean wantGeneric, final List timelines) { + public TimelineMaskingIterator(final boolean wantGeneric, final List> timelines) { this.wantGeneric = wantGeneric; this.delegate = timelines.iterator(); scanUntilNext(); } - private boolean isGeneric(final Timeline timeline) { + private boolean isGeneric(final Timeline timeline) { final War3ID name = timeline.getName(); final boolean generic = AnimationMap.KGTR.getWar3id().equals(name) || AnimationMap.KGRT.getWar3id().equals(name) || AnimationMap.KGSC.getWar3id().equals(name); @@ -197,8 +197,8 @@ public abstract class GenericObject extends AnimatedObject implements Chunk { } @Override - public Timeline next() { - final Timeline last = this.next; + public Timeline next() { + final Timeline last = this.next; scanUntilNext(); return last; } @@ -331,4 +331,20 @@ public abstract class GenericObject extends AnimatedObject implements Chunk { } } + + public String getName() { + return this.name; + } + + public int getObjectId() { + return this.objectId; + } + + public int getParentId() { + return this.parentId; + } + + public int getFlags() { + return this.flags; + } } diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/GeosetAnimation.java b/core/src/com/etheller/warsmash/parsers/mdlx/GeosetAnimation.java index 9375a6e..6ea4fc1 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/GeosetAnimation.java +++ b/core/src/com/etheller/warsmash/parsers/mdlx/GeosetAnimation.java @@ -99,4 +99,16 @@ public class GeosetAnimation extends AnimatedObject { public long getByteLength() { return 28 + super.getByteLength(); } + + public float[] getColor() { + return this.color; + } + + public float getAlpha() { + return this.alpha; + } + + public int getGeosetId() { + return this.geosetId; + } } diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/MdlxModel.java b/core/src/com/etheller/warsmash/parsers/mdlx/MdlxModel.java index 0c6bf12..c6f0629 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/MdlxModel.java +++ b/core/src/com/etheller/warsmash/parsers/mdlx/MdlxModel.java @@ -55,10 +55,10 @@ public class MdlxModel { private int version = 800; private String name = ""; /** - * (Comment copied from Ghostwolf JS) To the best of my knowledge, this - * should always be left empty. This is probably a leftover from the - * Warcraft 3 beta. (WS game note: No, I never saw any animation files in - * the RoC 2001-2002 Beta. So it must be from the Alpha) + * (Comment copied from Ghostwolf JS) To the best of my knowledge, this should + * always be left empty. This is probably a leftover from the Warcraft 3 beta. + * (WS game note: No, I never saw any animation files in the RoC 2001-2002 Beta. + * So it must be from the Alpha) * * @member {string} */ @@ -208,7 +208,7 @@ public class MdlxModel { private void loadDynamicObjects(final List out, final MdlxBlockDescriptor constructor, final LittleEndianDataInputStream stream, final long size) - throws IOException { + throws IOException { long totalSize = 0; while (totalSize < size) { final E object = constructor.create(); @@ -459,7 +459,7 @@ public class MdlxModel { private void loadNumberedObjectBlock(final List out, final MdlxBlockDescriptor constructor, final String name, final MdlTokenInputStream stream) - throws IOException { + throws IOException { stream.read(); // Don't care about the number, the array will grow for (final String token : stream.readBlock()) { @@ -644,10 +644,14 @@ public class MdlxModel { } public List getGlobalSequences() { - return globalSequences; + return this.globalSequences; } public List getSequences() { - return sequences; + return this.sequences; + } + + public List getPivotPoints() { + return this.pivotPoints; } } diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/ParticleEmitter2.java b/core/src/com/etheller/warsmash/parsers/mdlx/ParticleEmitter2.java index dd321be..07423fb 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/ParticleEmitter2.java +++ b/core/src/com/etheller/warsmash/parsers/mdlx/ParticleEmitter2.java @@ -422,4 +422,97 @@ public class ParticleEmitter2 extends GenericObject { public long getByteLength() { return 175 + super.getByteLength(); } + + public float getSpeed() { + return this.speed; + } + + public float getVariation() { + return this.variation; + } + + public float getLatitude() { + return this.latitude; + } + + public float getGravity() { + return this.gravity; + } + + public float getLifeSpan() { + return this.lifeSpan; + } + + public float getEmissionRate() { + return this.emissionRate; + } + + public float getLength() { + return this.length; + } + + public float getWidth() { + return this.width; + } + + public FilterMode getFilterMode() { + return this.filterMode; + } + + public long getRows() { + return this.rows; + } + + public long getColumns() { + return this.columns; + } + + public long getHeadOrTail() { + return this.headOrTail; + } + + public float getTailLength() { + return this.tailLength; + } + + public float getTimeMiddle() { + return this.timeMiddle; + } + + public float[][] getSegmentColors() { + return this.segmentColors; + } + + public short[] getSegmentAlphas() { + return this.segmentAlphas; + } + + public float[] getSegmentScaling() { + return this.segmentScaling; + } + + public long[][] getHeadIntervals() { + return this.headIntervals; + } + + public long[][] getTailIntervals() { + return this.tailIntervals; + } + + public int getTextureId() { + return this.textureId; + } + + public long getSquirt() { + return this.squirt; + } + + public int getPriorityPlane() { + return this.priorityPlane; + } + + public long getReplaceableId() { + return this.replaceableId; + } + } diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/RibbonEmitter.java b/core/src/com/etheller/warsmash/parsers/mdlx/RibbonEmitter.java index f5a756a..4b0e53b 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/RibbonEmitter.java +++ b/core/src/com/etheller/warsmash/parsers/mdlx/RibbonEmitter.java @@ -173,4 +173,49 @@ public class RibbonEmitter extends GenericObject { public long getByteLength() { return 56 + super.getByteLength(); } + + public float getHeightAbove() { + return this.heightAbove; + } + + public float getHeightBelow() { + return this.heightBelow; + } + + public float getAlpha() { + return this.alpha; + } + + public float[] getColor() { + return this.color; + } + + public float getLifeSpan() { + return this.lifeSpan; + } + + public long getTextureSlot() { + return this.textureSlot; + } + + public long getEmissionRate() { + return this.emissionRate; + } + + public long getRows() { + return this.rows; + } + + public long getColumns() { + return this.columns; + } + + public int getMaterialId() { + return this.materialId; + } + + public float getGravity() { + return this.gravity; + } + } diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatArrayKeyFrame.java b/core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatArrayKeyFrame.java deleted file mode 100644 index 5c5ed84..0000000 --- a/core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatArrayKeyFrame.java +++ /dev/null @@ -1,124 +0,0 @@ -package com.etheller.warsmash.parsers.mdlx.timeline; - -import java.io.IOException; -import java.util.Arrays; - -import com.etheller.warsmash.parsers.mdlx.InterpolationType; -import com.etheller.warsmash.parsers.mdlx.MdlTokenInputStream; -import com.etheller.warsmash.parsers.mdlx.MdlTokenOutputStream; -import com.etheller.warsmash.util.ParseUtils; -import com.google.common.io.LittleEndianDataInputStream; -import com.google.common.io.LittleEndianDataOutputStream; - -/** - * A UInt32 animation keyframe in a time track. - * - * Based on the works of Chananya Freiman. I changed the name of Track to - * KeyFrame. A possible future optimization would be to make 2 subclasses, one - * with inTan and one without, so that non-Hermite/non-Bezier animation data - * would use less memory. - * - */ -public class FloatArrayKeyFrame implements KeyFrame { - private long time; - private final float[] value; - private final float[] inTan; - private final float[] outTan; - - public FloatArrayKeyFrame(final int arraySize) { - this.value = new float[arraySize]; - this.inTan = new float[arraySize]; - this.outTan = new float[arraySize]; - } - - /** - * Restricts us to only be able to parse models on one thread at a time, in - * return for high performance. - */ - private static StringBuffer STRING_BUFFER_HEAP = new StringBuffer(); - - @Override - public void readMdx(final LittleEndianDataInputStream stream, final InterpolationType interpolationType) - throws IOException { - this.time = ParseUtils.readUInt32(stream); - ParseUtils.readFloatArray(stream, this.value); - if (interpolationType.tangential()) { - ParseUtils.readFloatArray(stream, this.inTan); - ParseUtils.readFloatArray(stream, this.outTan); - } - } - - @Override - public void writeMdx(final LittleEndianDataOutputStream stream, final InterpolationType interpolationType) - throws IOException { - stream.writeInt((int) this.time); - ParseUtils.writeFloatArray(stream, this.value); - if (interpolationType.tangential()) { - ParseUtils.writeFloatArray(stream, this.inTan); - ParseUtils.writeFloatArray(stream, this.outTan); - } - } - - @Override - public void readMdl(final MdlTokenInputStream stream, final InterpolationType interpolationType) - throws IOException { - this.time = stream.readUInt32(); - stream.readKeyframe(this.value); - if (interpolationType.tangential()) { - stream.read(); // "InTan" - stream.readKeyframe(this.inTan); - stream.read(); // "OutTan" - stream.readKeyframe(this.outTan); - } - } - - @Override - public void writeMdl(final MdlTokenOutputStream stream, final InterpolationType interpolationType) - throws IOException { - STRING_BUFFER_HEAP.setLength(0); - STRING_BUFFER_HEAP.append(this.time); - STRING_BUFFER_HEAP.append(':'); - stream.writeKeyframe(STRING_BUFFER_HEAP.toString(), this.value); - if (interpolationType.tangential()) { - stream.indent(); - stream.writeKeyframe("InTan", this.inTan); - stream.writeKeyframe("OutTan", this.outTan); - stream.unindent(); - } - } - - @Override - public long getByteLength(final InterpolationType interpolationType) { - final long valueSize = Float.BYTES * this.value.length; // unsigned - long size = 4 + valueSize; - if (interpolationType.tangential()) { - size += valueSize * 2; - } - return size; - } - - @Override - public long getTime() { - return time; - } - - @Override - public boolean matchingValue(final KeyFrame other) { - if (other instanceof FloatArrayKeyFrame) { - final FloatArrayKeyFrame otherFrame = (FloatArrayKeyFrame) other; - return Arrays.equals(value, otherFrame.value); - } - return false; - } - - @Override - public KeyFrame clone(final long time) { - final FloatArrayKeyFrame newKeyFrame = new FloatArrayKeyFrame(value.length); - System.arraycopy(value, 0, newKeyFrame.value, 0, value.length); - // if (inTan != null) { - System.arraycopy(inTan, 0, newKeyFrame.inTan, 0, inTan.length); - System.arraycopy(outTan, 0, newKeyFrame.outTan, 0, outTan.length); - // } - return newKeyFrame; - } -} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatArrayTimeline.java b/core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatArrayTimeline.java index 20c4ca2..6bba03d 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatArrayTimeline.java +++ b/core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatArrayTimeline.java @@ -1,20 +1,49 @@ package com.etheller.warsmash.parsers.mdlx.timeline; -public final class FloatArrayTimeline extends Timeline { +import java.io.IOException; + +import com.etheller.warsmash.parsers.mdlx.MdlTokenInputStream; +import com.etheller.warsmash.parsers.mdlx.MdlTokenOutputStream; +import com.etheller.warsmash.util.ParseUtils; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +public final class FloatArrayTimeline extends Timeline { private final int arraySize; public FloatArrayTimeline(final int arraySize) { this.arraySize = arraySize; } - @Override - protected KeyFrame newKeyFrame() { - return new FloatArrayKeyFrame(this.arraySize); - } - @Override protected int size() { return this.arraySize; } + @Override + protected float[] readMdxValue(final LittleEndianDataInputStream stream) throws IOException { + return ParseUtils.readFloatArray(stream, this.arraySize); + } + + @Override + protected float[] readMdlValue(final MdlTokenInputStream stream) { + final float[] output = new float[this.arraySize]; + stream.readKeyframe(output); + return output; + } + + @Override + protected void writeMdxValue(final LittleEndianDataOutputStream stream, final float[] value) throws IOException { + ParseUtils.writeFloatArray(stream, value); + } + + @Override + protected void writeMdlValue(final MdlTokenOutputStream stream, final String prefix, final float[] value) { + stream.writeKeyframe(prefix, value); + } + + public int getArraySize() { + return this.arraySize; + } + } diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatKeyFrame.java b/core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatKeyFrame.java deleted file mode 100644 index e3dbfab..0000000 --- a/core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatKeyFrame.java +++ /dev/null @@ -1,116 +0,0 @@ -package com.etheller.warsmash.parsers.mdlx.timeline; - -import java.io.IOException; - -import com.etheller.warsmash.parsers.mdlx.InterpolationType; -import com.etheller.warsmash.parsers.mdlx.MdlTokenInputStream; -import com.etheller.warsmash.parsers.mdlx.MdlTokenOutputStream; -import com.etheller.warsmash.util.ParseUtils; -import com.etheller.warsmash.util.RenderMathUtils; -import com.google.common.io.LittleEndianDataInputStream; -import com.google.common.io.LittleEndianDataOutputStream; - -/** - * A UInt32 animation keyframe in a time track. - * - * Based on the works of Chananya Freiman. I changed the name of Track to - * KeyFrame. A possible future optimization would be to make 2 subclasses, one - * with inTan and one without, so that non-Hermite/non-Bezier animation data - * would use less memory. - * - */ -public class FloatKeyFrame implements KeyFrame { - private long time; - private float value; - private float inTan; - private float outTan; - - /** - * Restricts us to only be able to parse models on one thread at a time, in - * return for high performance. - */ - private static StringBuffer STRING_BUFFER_HEAP = new StringBuffer(); - - @Override - public void readMdx(final LittleEndianDataInputStream stream, final InterpolationType interpolationType) - throws IOException { - this.time = ParseUtils.readUInt32(stream); - this.value = stream.readFloat(); - if (interpolationType.tangential()) { - this.inTan = stream.readFloat(); - this.outTan = stream.readFloat(); - } - } - - @Override - public void writeMdx(final LittleEndianDataOutputStream stream, final InterpolationType interpolationType) - throws IOException { - stream.writeInt((int) this.time); - stream.writeFloat(this.value); - if (interpolationType.tangential()) { - stream.writeFloat(this.inTan); - stream.writeFloat(this.outTan); - } - } - - @Override - public void readMdl(final MdlTokenInputStream stream, final InterpolationType interpolationType) - throws IOException { - this.time = stream.readUInt32(); - this.value = stream.readFloat(); - if (interpolationType.tangential()) { - stream.read(); // "InTan" - this.inTan = stream.readFloat(); - stream.read(); // "OutTan" - this.outTan = stream.readFloat(); - } - } - - @Override - public void writeMdl(final MdlTokenOutputStream stream, final InterpolationType interpolationType) - throws IOException { - STRING_BUFFER_HEAP.setLength(0); - STRING_BUFFER_HEAP.append(this.time); - STRING_BUFFER_HEAP.append(':'); - stream.writeKeyframe(STRING_BUFFER_HEAP.toString(), this.value); - if (interpolationType.tangential()) { - stream.indent(); - stream.writeKeyframe("InTan", this.inTan); - stream.writeKeyframe("OutTan", this.outTan); - stream.unindent(); - } - } - - @Override - public long getByteLength(final InterpolationType interpolationType) { - final long valueSize = Float.BYTES; // unsigned - long size = 4 + valueSize; - if (interpolationType.tangential()) { - size += valueSize * 2; - } - return size; - } - - @Override - public long getTime() { - return time; - } - - @Override - public boolean matchingValue(final KeyFrame other) { - if (other instanceof FloatKeyFrame) { - final FloatKeyFrame otherFrame = (FloatKeyFrame) other; - return Math.abs(value - otherFrame.value) <= RenderMathUtils.EPSILON; - } - return false; - } - - @Override - public KeyFrame clone(final long time) { - final FloatKeyFrame newKeyFrame = new FloatKeyFrame(); - newKeyFrame.value = value; - newKeyFrame.inTan = inTan; - newKeyFrame.outTan = outTan; - return newKeyFrame; - } -} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatTimeline.java b/core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatTimeline.java index a38091a..6e23287 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatTimeline.java +++ b/core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatTimeline.java @@ -1,15 +1,37 @@ package com.etheller.warsmash.parsers.mdlx.timeline; -public final class FloatTimeline extends Timeline { +import java.io.IOException; - @Override - protected KeyFrame newKeyFrame() { - return new FloatKeyFrame(); - } +import com.etheller.warsmash.parsers.mdlx.MdlTokenInputStream; +import com.etheller.warsmash.parsers.mdlx.MdlTokenOutputStream; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +public final class FloatTimeline extends Timeline { @Override protected int size() { return 1; } + @Override + protected float[] readMdxValue(final LittleEndianDataInputStream stream) throws IOException { + return new float[] { stream.readFloat() }; + } + + @Override + protected float[] readMdlValue(final MdlTokenInputStream stream) { + return new float[] { stream.readFloat() }; + } + + @Override + protected void writeMdxValue(final LittleEndianDataOutputStream stream, final float[] value) throws IOException { + stream.writeFloat(value[0]); + } + + @Override + protected void writeMdlValue(final MdlTokenOutputStream stream, final String prefix, final float[] value) { + stream.writeKeyframe(prefix, value[0]); + } + } diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/timeline/KeyFrame.java b/core/src/com/etheller/warsmash/parsers/mdlx/timeline/KeyFrame.java deleted file mode 100644 index f80078b..0000000 --- a/core/src/com/etheller/warsmash/parsers/mdlx/timeline/KeyFrame.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.etheller.warsmash.parsers.mdlx.timeline; - -import java.io.IOException; - -import com.etheller.warsmash.parsers.mdlx.InterpolationType; -import com.etheller.warsmash.parsers.mdlx.MdlTokenInputStream; -import com.etheller.warsmash.parsers.mdlx.MdlTokenOutputStream; -import com.google.common.io.LittleEndianDataInputStream; -import com.google.common.io.LittleEndianDataOutputStream; - -public interface KeyFrame { - void readMdx(LittleEndianDataInputStream stream, InterpolationType interpolationType) throws IOException; - - void writeMdx(LittleEndianDataOutputStream stream, InterpolationType interpolationType) throws IOException; - - void readMdl(MdlTokenInputStream stream, InterpolationType interpolationType) throws IOException; - - void writeMdl(MdlTokenOutputStream stream, InterpolationType interpolationType) throws IOException; - - long getByteLength(InterpolationType interpolationType); - - long getTime(); - - boolean matchingValue(KeyFrame other); - - KeyFrame clone(long time); -} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/timeline/Timeline.java b/core/src/com/etheller/warsmash/parsers/mdlx/timeline/Timeline.java index 8564937..0c06b87 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/timeline/Timeline.java +++ b/core/src/com/etheller/warsmash/parsers/mdlx/timeline/Timeline.java @@ -1,8 +1,6 @@ package com.etheller.warsmash.parsers.mdlx.timeline; import java.io.IOException; -import java.util.ArrayList; -import java.util.List; import com.etheller.warsmash.parsers.mdlx.AnimationMap; import com.etheller.warsmash.parsers.mdlx.Chunk; @@ -15,18 +13,27 @@ import com.etheller.warsmash.util.War3ID; import com.google.common.io.LittleEndianDataInputStream; import com.google.common.io.LittleEndianDataOutputStream; -public abstract class Timeline implements Chunk { +public abstract class Timeline implements Chunk { private War3ID name; private InterpolationType interpolationType; private int globalSequenceId = -1; - private final List keyFrames; + + private long[] frames; + private TYPE[] values; + private TYPE[] inTans; + private TYPE[] outTans; + + /** + * Restricts us to only be able to parse models on one thread at a time, in + * return for high performance. + */ + private static StringBuffer STRING_BUFFER_HEAP = new StringBuffer(); public War3ID getName() { return this.name; } public Timeline() { - this.keyFrames = new ArrayList<>(); } public void readMdx(final LittleEndianDataInputStream stream, final War3ID name) throws IOException { @@ -37,23 +44,38 @@ public abstract class Timeline implements Chunk { this.interpolationType = InterpolationType.VALUES[stream.readInt()]; this.globalSequenceId = stream.readInt(); + this.frames = new long[(int) keyFrameCount]; + this.values = (TYPE[]) new Object[(int) keyFrameCount]; + if (this.interpolationType.tangential()) { + this.inTans = (TYPE[]) new Object[(int) keyFrameCount]; + this.outTans = (TYPE[]) new Object[(int) keyFrameCount]; + } + for (int i = 0; i < keyFrameCount; i++) { - final KeyFrame keyFrame = newKeyFrame(); + this.frames[i] = (stream.readInt()); // TODO autoboxing is slow + this.values[i] = (this.readMdxValue(stream)); - keyFrame.readMdx(stream, this.interpolationType); - - this.keyFrames.add(keyFrame); + if (this.interpolationType.tangential()) { + this.inTans[i] = (this.readMdxValue(stream)); + this.outTans[i] = (this.readMdxValue(stream)); + } } } public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException { stream.writeInt(Integer.reverseBytes(this.name.getValue())); - stream.writeInt(this.keyFrames.size()); + final int keyframeCount = this.frames.length; + stream.writeInt(keyframeCount); stream.writeInt(this.interpolationType.ordinal()); stream.writeInt(this.globalSequenceId); - for (final KeyFrame keyFrame : this.keyFrames) { - keyFrame.writeMdx(stream, this.interpolationType); + for (int i = 0; i < keyframeCount; i++) { + stream.writeInt((int) this.frames[i]); + writeMdxValue(stream, this.values[i]); + if (this.interpolationType.tangential()) { + writeMdxValue(stream, this.inTans[i]); + writeMdxValue(stream, this.outTans[i]); + } } } @@ -94,19 +116,29 @@ public abstract class Timeline implements Chunk { this.globalSequenceId = -1; } + this.frames = new long[keyFrameCount]; + this.values = (TYPE[]) new Object[keyFrameCount]; + if (this.interpolationType.tangential()) { + this.inTans = (TYPE[]) new Object[keyFrameCount]; + this.outTans = (TYPE[]) new Object[keyFrameCount]; + } for (int i = 0; i < keyFrameCount; i++) { - final KeyFrame keyFrame = newKeyFrame(); - - keyFrame.readMdl(stream, interpolationType); - - this.keyFrames.add(keyFrame); + this.frames[i] = (stream.readInt()); + this.values[i] = (this.readMdlValue(stream)); + if (interpolationType.tangential()) { + stream.read(); // InTan + this.inTans[i] = (this.readMdlValue(stream)); + stream.read(); // OutTan + this.outTans[i] = (this.readMdlValue(stream)); + } } stream.read(); // } } public void writeMdl(final MdlTokenOutputStream stream) throws IOException { - stream.startBlock(AnimationMap.ID_TO_TAG.get(this.name).getMdlToken(), this.keyFrames.size()); + final int tracksCount = this.frames.length; + stream.startBlock(AnimationMap.ID_TO_TAG.get(this.name).getMdlToken(), tracksCount); String token; switch (this.interpolationType) { @@ -133,8 +165,17 @@ public abstract class Timeline implements Chunk { stream.writeAttrib(MdlUtils.TOKEN_GLOBAL_SEQ_ID, this.globalSequenceId); } - for (final KeyFrame keyFrame : this.keyFrames) { - keyFrame.writeMdl(stream, this.interpolationType); + for (int i = 0; i < tracksCount; i++) { + STRING_BUFFER_HEAP.setLength(0); + STRING_BUFFER_HEAP.append(this.frames[i]); + STRING_BUFFER_HEAP.append(':'); + this.writeMdlValue(stream, STRING_BUFFER_HEAP.toString(), this.values[i]); + if (this.interpolationType.tangential()) { + stream.indent(); + this.writeMdlValue(stream, "InTan", this.inTans[i]); + this.writeMdlValue(stream, "OutTan", this.outTans[i]); + stream.unindent(); + } } stream.endBlock(); @@ -142,22 +183,52 @@ public abstract class Timeline implements Chunk { @Override public long getByteLength() { - return 16 + (this.keyFrames.size() * (4 + (4 * (this.size() * (this.interpolationType.tangential() ? 3 : 1))))); - } + final int tracksCount = this.frames.length; + int size = 16; - protected abstract KeyFrame newKeyFrame(); + if (tracksCount > 0) { + final int bytesPerValue = size() * 4; + int valuesPerTrack = 1; + if (this.interpolationType.tangential()) { + valuesPerTrack = 3; + } + + size += (4 + (valuesPerTrack * bytesPerValue)) * tracksCount; + } + return size; + } protected abstract int size(); - public int getGlobalSequenceId() { - return globalSequenceId; - } + protected abstract TYPE readMdxValue(LittleEndianDataInputStream stream) throws IOException; - public List getKeyFrames() { - return keyFrames; + protected abstract TYPE readMdlValue(MdlTokenInputStream stream); + + protected abstract void writeMdxValue(LittleEndianDataOutputStream stream, TYPE value) throws IOException; + + protected abstract void writeMdlValue(MdlTokenOutputStream stream, String prefix, TYPE value); + + public int getGlobalSequenceId() { + return this.globalSequenceId; } public InterpolationType getInterpolationType() { - return interpolationType; + return this.interpolationType; + } + + public long[] getFrames() { + return this.frames; + } + + public TYPE[] getValues() { + return this.values; + } + + public TYPE[] getInTans() { + return this.inTans; + } + + public TYPE[] getOutTans() { + return this.outTans; } } diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/timeline/UInt32KeyFrame.java b/core/src/com/etheller/warsmash/parsers/mdlx/timeline/UInt32KeyFrame.java deleted file mode 100644 index e5401e5..0000000 --- a/core/src/com/etheller/warsmash/parsers/mdlx/timeline/UInt32KeyFrame.java +++ /dev/null @@ -1,115 +0,0 @@ -package com.etheller.warsmash.parsers.mdlx.timeline; - -import java.io.IOException; - -import com.etheller.warsmash.parsers.mdlx.InterpolationType; -import com.etheller.warsmash.parsers.mdlx.MdlTokenInputStream; -import com.etheller.warsmash.parsers.mdlx.MdlTokenOutputStream; -import com.etheller.warsmash.util.ParseUtils; -import com.google.common.io.LittleEndianDataInputStream; -import com.google.common.io.LittleEndianDataOutputStream; - -/** - * A UInt32 animation keyframe in a time track. - * - * Based on the works of Chananya Freiman. I changed the name of Track to - * KeyFrame. A possible future optimization would be to make 2 subclasses, one - * with inTan and one without, so that non-Hermite/non-Bezier animation data - * would use less memory. - * - */ -public class UInt32KeyFrame implements KeyFrame { - private long time; - private long value; - private long inTan; - private long outTan; - - /** - * Restricts us to only be able to parse models on one thread at a time, in - * return for high performance. - */ - private static StringBuffer STRING_BUFFER_HEAP = new StringBuffer(); - - @Override - public void readMdx(final LittleEndianDataInputStream stream, final InterpolationType interpolationType) - throws IOException { - this.time = ParseUtils.readUInt32(stream); - this.value = ParseUtils.readUInt32(stream); - if (interpolationType.tangential()) { - this.inTan = ParseUtils.readUInt32(stream); - this.outTan = ParseUtils.readUInt32(stream); - } - } - - @Override - public void writeMdx(final LittleEndianDataOutputStream stream, final InterpolationType interpolationType) - throws IOException { - stream.writeInt((int) this.time); - stream.writeInt((int) this.value); - if (interpolationType.tangential()) { - stream.writeInt((int) this.inTan); - stream.writeInt((int) this.outTan); - } - } - - @Override - public void readMdl(final MdlTokenInputStream stream, final InterpolationType interpolationType) - throws IOException { - this.time = stream.readUInt32(); - this.value = stream.readUInt32(); - if (interpolationType.tangential()) { - stream.read(); // "InTan" - this.inTan = stream.readUInt32(); - stream.read(); // "OutTan" - this.outTan = stream.readUInt32(); - } - } - - @Override - public void writeMdl(final MdlTokenOutputStream stream, final InterpolationType interpolationType) - throws IOException { - STRING_BUFFER_HEAP.setLength(0); - STRING_BUFFER_HEAP.append(this.time); - STRING_BUFFER_HEAP.append(':'); - stream.writeKeyframe(STRING_BUFFER_HEAP.toString(), this.value); - if (interpolationType.tangential()) { - stream.indent(); - stream.writeKeyframe("InTan", this.inTan); - stream.writeKeyframe("OutTan", this.outTan); - stream.unindent(); - } - } - - @Override - public long getByteLength(final InterpolationType interpolationType) { - final long valueSize = Integer.BYTES; // unsigned - long size = 4 + valueSize; - if (interpolationType.tangential()) { - size += valueSize * 2; - } - return size; - } - - @Override - public long getTime() { - return time; - } - - @Override - public boolean matchingValue(final KeyFrame other) { - if (other instanceof UInt32KeyFrame) { - final UInt32KeyFrame otherFrame = (UInt32KeyFrame) other; - return value == otherFrame.value; - } - return false; - } - - @Override - public KeyFrame clone(final long time) { - final UInt32KeyFrame newKeyFrame = new UInt32KeyFrame(); - newKeyFrame.value = value; - newKeyFrame.inTan = inTan; - newKeyFrame.outTan = outTan; - return newKeyFrame; - } -} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/timeline/UInt32Timeline.java b/core/src/com/etheller/warsmash/parsers/mdlx/timeline/UInt32Timeline.java index 4d349f4..2d56ee7 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/timeline/UInt32Timeline.java +++ b/core/src/com/etheller/warsmash/parsers/mdlx/timeline/UInt32Timeline.java @@ -1,15 +1,38 @@ package com.etheller.warsmash.parsers.mdlx.timeline; -public final class UInt32Timeline extends Timeline { +import java.io.IOException; - @Override - protected KeyFrame newKeyFrame() { - return new UInt32KeyFrame(); - } +import com.etheller.warsmash.parsers.mdlx.MdlTokenInputStream; +import com.etheller.warsmash.parsers.mdlx.MdlTokenOutputStream; +import com.etheller.warsmash.util.ParseUtils; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +public final class UInt32Timeline extends Timeline { @Override protected int size() { return 1; } + @Override + protected long[] readMdxValue(final LittleEndianDataInputStream stream) throws IOException { + return new long[] { ParseUtils.readUInt32(stream) }; + } + + @Override + protected long[] readMdlValue(final MdlTokenInputStream stream) { + return new long[] { stream.readUInt32() }; + } + + @Override + protected void writeMdxValue(final LittleEndianDataOutputStream stream, final long[] uint32) throws IOException { + ParseUtils.writeUInt32(stream, uint32[0]); + } + + @Override + protected void writeMdlValue(final MdlTokenOutputStream stream, final String prefix, final long[] uint32) { + stream.writeKeyframe(prefix, uint32[0]); + } + } diff --git a/core/src/com/etheller/warsmash/util/Interpolator.java b/core/src/com/etheller/warsmash/util/Interpolator.java index 94d9943..ab6dd42 100644 --- a/core/src/com/etheller/warsmash/util/Interpolator.java +++ b/core/src/com/etheller/warsmash/util/Interpolator.java @@ -1,8 +1,8 @@ package com.etheller.warsmash.util; public class Interpolator { - public void interpolateScalar(final float[] out, final float[] a, final float[] b, final float[] c, final float[] d, - final float t, final int type) { + public static void interpolateScalar(final float[] out, final float[] a, final float[] b, final float[] c, + final float[] d, final float t, final int type) { switch (type) { case 0: { out[0] = a[0]; @@ -23,8 +23,8 @@ public class Interpolator { } } - public void interpolateVector(final float[] out, final float[] a, final float[] b, final float[] c, final float[] d, - final float t, final int type) { + public static void interpolateVector(final float[] out, final float[] a, final float[] b, final float[] c, + final float[] d, final float t, final int type) { switch (type) { case 0: { System.arraycopy(a, 0, out, 0, a.length); @@ -51,7 +51,7 @@ public class Interpolator { } } - public void interpolateQuaternion(final float[] out, final float[] a, final float[] b, final float[] c, + public static void interpolateQuaternion(final float[] out, final float[] a, final float[] b, final float[] c, final float[] d, final float t, final int type) { switch (type) { case 0: { diff --git a/core/src/com/etheller/warsmash/util/RenderMathUtils.java b/core/src/com/etheller/warsmash/util/RenderMathUtils.java index ec09fe4..acf0574 100644 --- a/core/src/com/etheller/warsmash/util/RenderMathUtils.java +++ b/core/src/com/etheller/warsmash/util/RenderMathUtils.java @@ -14,6 +14,9 @@ public enum RenderMathUtils { public static final Vector3 VEC3_UNIT_X = new Vector3(1, 0, 0); public static final Vector3 VEC3_UNIT_Y = new Vector3(0, 1, 0); public static final Vector3 VEC3_UNIT_Z = new Vector3(0, 0, 1); + public static final float[] FLOAT_VEC3_ZERO = new float[] { 0, 0, 0 }; + public static final float[] FLOAT_QUAT_DEFAULT = new float[] { 0, 0, 0, 1 }; + public static final float[] FLOAT_VEC3_ONE = new float[] { 1, 1, 1 }; // copied from ghostwolf and // https://www.blend4web.com/api_doc/libs_gl-matrix2.js.html @@ -356,7 +359,7 @@ public enum RenderMathUtils { } public static float randomInRange(final float a, final float b) { - return (float) (a + Math.random() * (b - a)); + return (float) (a + (Math.random() * (b - a))); } public static float clamp(final float x, final float minVal, final float maxVal) { @@ -364,15 +367,15 @@ public enum RenderMathUtils { } public static float lerp(final float a, final float b, final float t) { - return a + t * (b - a); + return a + (t * (b - a)); } public static float hermite(final float a, final float b, final float c, final float d, final float t) { final float factorTimes2 = t * t; - final float factor1 = factorTimes2 * (2 * t - 3) + 1; - final float factor2 = factorTimes2 * (t - 2) + t; + final float factor1 = (factorTimes2 * ((2 * t) - 3)) + 1; + final float factor2 = (factorTimes2 * (t - 2)) + t; final float factor3 = factorTimes2 * (t - 1); - final float factor4 = factorTimes2 * (3 - 2 * t); + final float factor4 = factorTimes2 * (3 - (2 * t)); return (a * factor1) + (b * factor2) + (c * factor3) + (d * factor4); } @@ -397,7 +400,7 @@ public enum RenderMathUtils { float omega, cosom, sinom, scale0, scale1; // calc cosine - cosom = ax * bx + ay * by + az * bz + aw * bw; + cosom = (ax * bx) + (ay * by) + (az * bz) + (aw * bw); // adjust signs (if necessary) if (cosom < 0.0) { cosom = -cosom; @@ -421,10 +424,10 @@ public enum RenderMathUtils { scale1 = t; } // calculate final values - out[0] = scale0 * ax + scale1 * bx; - out[1] = scale0 * ay + scale1 * by; - out[2] = scale0 * az + scale1 * bz; - out[3] = scale0 * aw + scale1 * bw; + out[0] = (scale0 * ax) + (scale1 * bx); + out[1] = (scale0 * ay) + (scale1 * by); + out[2] = (scale0 * az) + (scale1 * bz); + out[3] = (scale0 * aw) + (scale1 * bw); return out; } diff --git a/core/src/com/etheller/warsmash/viewer5/Camera.java b/core/src/com/etheller/warsmash/viewer5/Camera.java index cbd9fc3..7b875ce 100644 --- a/core/src/com/etheller/warsmash/viewer5/Camera.java +++ b/core/src/com/etheller/warsmash/viewer5/Camera.java @@ -36,15 +36,15 @@ public class Camera { public Quaternion inverseRotation; private final Matrix4 worldMatrix; private final Matrix4 projectionMatrix; - private final Matrix4 worldProjectionMatrix; + public final Matrix4 worldProjectionMatrix; private final Matrix4 inverseWorldMatrix; private final Matrix4 inverseRotationMatrix; private final Matrix4 inverseWorldProjectionMatrix; public final Vector3 directionX; public final Vector3 directionY; public final Vector3 directionZ; - private final Vector3[] vectors; - private final Vector3[] billboardedVectors; + public final Vector3[] vectors; + public final Vector3[] billboardedVectors; public final Vector4[] planes; private boolean dirty; diff --git a/core/src/com/etheller/warsmash/viewer5/EmittedObject.java b/core/src/com/etheller/warsmash/viewer5/EmittedObject.java index b39a1e3..cd9ed80 100644 --- a/core/src/com/etheller/warsmash/viewer5/EmittedObject.java +++ b/core/src/com/etheller/warsmash/viewer5/EmittedObject.java @@ -1,10 +1,10 @@ package com.etheller.warsmash.viewer5; -public abstract class EmittedObject { - abstract void update(float dt); +public abstract class EmittedObject>> { + public abstract void update(float dt); public float health; - public Emitter emitter; + public EMITTER emitter; public int index; protected abstract void bind(int flags); diff --git a/core/src/com/etheller/warsmash/viewer5/Emitter.java b/core/src/com/etheller/warsmash/viewer5/Emitter.java index 6f8a63b..7b60b12 100644 --- a/core/src/com/etheller/warsmash/viewer5/Emitter.java +++ b/core/src/com/etheller/warsmash/viewer5/Emitter.java @@ -3,31 +3,29 @@ package com.etheller.warsmash.viewer5; import java.util.ArrayList; import java.util.List; -import com.etheller.warsmash.viewer5.handlers.EmitterObject; +public abstract class Emitter>> { -public abstract class Emitter { + public final MODEL_INSTANCE instance; + public final List objects; + public int alive; + protected float currentEmission; - private final ModelInstance instance; - private final EmitterObject emitterObject; - private final List objects; - private int alive; - private int currentEmission; - - public Emitter(final ModelInstance instance, final EmitterObject emitterObject) { + public Emitter(final MODEL_INSTANCE instance) { this.instance = instance; - this.emitterObject = emitterObject; this.objects = new ArrayList<>(); this.alive = 0; this.currentEmission = 0; } - public final EmittedObject emitObject(final int flags) { + public final EMITTED_OBJECT emitObject(final int flags) { if (this.alive == this.objects.size()) { this.objects.add(this.createObject()); } - final EmittedObject object = this.objects.get(this.alive); + final EMITTED_OBJECT object = this.objects.get(this.alive); + object.index = this.alive; + object.bind(flags); this.alive += 1; @@ -38,10 +36,11 @@ public abstract class Emitter { return object; } - public final void update(final float dt) { + public void update(final float dt) { this.updateEmission(dt); - final int currentEmission = this.currentEmission; + final float currentEmission = this.currentEmission; + if (currentEmission >= 1) { for (int i = 0; i < currentEmission; i += 1) { this.emit(); @@ -49,10 +48,10 @@ public abstract class Emitter { } } - public final void kill(final EmittedObject object) { + public void kill(final EMITTED_OBJECT object) { this.alive -= 1; - final EmittedObject otherObject = this.objects.get(this.alive); + final EMITTED_OBJECT otherObject = this.objects.get(this.alive); this.objects.set(object.index, otherObject); this.objects.set(this.alive, object); @@ -71,5 +70,5 @@ public abstract class Emitter { protected abstract void emit(); - protected abstract EmittedObject createObject(); + protected abstract EMITTED_OBJECT createObject(); } diff --git a/core/src/com/etheller/warsmash/viewer5/GenericNode.java b/core/src/com/etheller/warsmash/viewer5/GenericNode.java new file mode 100644 index 0000000..2772813 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/GenericNode.java @@ -0,0 +1,33 @@ +package com.etheller.warsmash.viewer5; + +import java.util.List; + +import com.badlogic.gdx.math.Matrix4; +import com.badlogic.gdx.math.Quaternion; +import com.badlogic.gdx.math.Vector3; + +public abstract class GenericNode { + + protected Vector3 pivot; + protected Vector3 localLocation; + protected Quaternion localRotation; + protected Vector3 localScale; + protected Vector3 worldLocation; + protected Quaternion worldRotation; + protected Vector3 worldScale; + protected Vector3 inverseWorldLocation; + protected Quaternion inverseWorldRotation; + protected Vector3 inverseWorldScale; + protected Matrix4 localMatrix; + protected Matrix4 worldMatrix; + protected GenericNode parent; + protected List children; + public boolean dontInheritTranslation; + public boolean dontInheritRotation; + public boolean dontInheritScaling; + protected boolean visible; + protected boolean wasDirty; + protected boolean dirty; + + protected abstract void update(float dt, Scene scene); +} diff --git a/core/src/com/etheller/warsmash/viewer5/Model.java b/core/src/com/etheller/warsmash/viewer5/Model.java index 814465d..8552f8a 100644 --- a/core/src/com/etheller/warsmash/viewer5/Model.java +++ b/core/src/com/etheller/warsmash/viewer5/Model.java @@ -6,16 +6,21 @@ import java.util.List; import com.etheller.warsmash.viewer5.handlers.ModelHandler; import com.etheller.warsmash.viewer5.handlers.ModelInstanceDescriptor; -public abstract class Model extends Resource { +public abstract class Model extends Resource { public Bounds bounds; public List preloadedInstances; - public Model(final ModelHandler handler, final ModelViewer viewer, final String extension, final String fetchUrl) { - super(viewer, handler, extension, fetchUrl); + public Model(final HANDLER handler, final ModelViewer viewer, final String extension, final PathSolver pathSolver, + final String fetchUrl) { + super(viewer, handler, extension, pathSolver, fetchUrl); this.bounds = new Bounds(); this.preloadedInstances = new ArrayList<>(); } + public ModelInstance addInstance() { + return addInstance(0); + } + public ModelInstance addInstance(final int type) { final ModelInstanceDescriptor instanceDescriptor = this.handler.instanceDescriptor; final ModelInstance instance = instanceDescriptor.create(this); diff --git a/core/src/com/etheller/warsmash/viewer5/ModelInstance.java b/core/src/com/etheller/warsmash/viewer5/ModelInstance.java index d4eb710..5080ae8 100644 --- a/core/src/com/etheller/warsmash/viewer5/ModelInstance.java +++ b/core/src/com/etheller/warsmash/viewer5/ModelInstance.java @@ -1,6 +1,5 @@ package com.etheller.warsmash.viewer5; -import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.math.Vector3; import com.etheller.warsmash.util.RenderMathUtils; import com.etheller.warsmash.util.Vector4; @@ -15,7 +14,7 @@ public abstract class ModelInstance extends Node { public float depth; public int updateFrame; public int cullFrame; - public Model model; + public Model model; public TextureMapper textureMapper; public boolean paused; public boolean rendered; diff --git a/core/src/com/etheller/warsmash/viewer5/ModelViewer.java b/core/src/com/etheller/warsmash/viewer5/ModelViewer.java index 1815863..5e60545 100644 --- a/core/src/com/etheller/warsmash/viewer5/ModelViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/ModelViewer.java @@ -1,5 +1,6 @@ package com.etheller.warsmash.viewer5; +import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.HashMap; @@ -10,10 +11,10 @@ import java.util.Set; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.GL20; -import com.badlogic.gdx.graphics.Texture; import com.etheller.warsmash.datasources.DataSource; import com.etheller.warsmash.viewer5.gl.WebGL; import com.etheller.warsmash.viewer5.handlers.ResourceHandler; +import com.etheller.warsmash.viewer5.handlers.ResourceHandlerConstructionParams; public class ModelViewer { private final DataSource dataSource; @@ -28,7 +29,7 @@ public class ModelViewer { private int visibleInstances; private int updatedParticles; public int frame; - private final int rectBuffer; + public final int rectBuffer; private final boolean enableAudio; private final Map> textureMappers; private final Set handlers; @@ -103,6 +104,77 @@ public class ModelViewer { this.scenes.clear(); } + public Object[] findHandler(final String ext) { + for (final ResourceHandler handler : this.handlers) { + for (final String[] extension : handler.extensions) { + if (extension[0].equals(ext)) { + return new Object[] { handler, extension[1] }; + } + } + } + return null; + } + + public Resource load(final String src, final PathSolver pathSolver, final Object solverParams) { + String finalSrc = src; + String extension = ""; + boolean isFetch = false; + final boolean resolved = false; + + // If a given path solver, resolve. + if (pathSolver != null) { + final SolvedPath solved = pathSolver.solve(src, solverParams); + + finalSrc = solved.getFinalSrc(); + extension = solved.getExtension(); + isFetch = solved.isFetch(); + } + + // Built-in texture sources + // ---- TODO not using JS code here + + if (resolved) { + final Object[] handlerAndDataType = this.findHandler(extension.toLowerCase()); + + // Is there a handler for this file type? + if (handlerAndDataType != null) { + if (isFetch) { + final Resource resource = this.fetchCache.get(finalSrc); + + if (resource != null) { + return resource; + } + } + + final ResourceHandler handler = (ResourceHandler) handlerAndDataType[0]; + final Resource resource = handler.construct(new ResourceHandlerConstructionParams(this, handler, + extension, pathSolver, isFetch ? finalSrc : "")); + + this.resources.add(resource); + +// if (isFetch) { + this.fetchCache.put(finalSrc, resource); +// } + + // TODO this is a synchronous hack, skipped some Ghostwolf code + try { + resource.loadData(this.dataSource.getResourceAsStream(finalSrc), null); + } + catch (final IOException e) { + throw new IllegalStateException("Unable to load data: " + finalSrc); + } + + return resource; + } + else { + throw new IllegalStateException("Missing handler for: " + finalSrc); + } + } + else { + throw new IllegalStateException("Load unresolved: " + finalSrc); + } + } + public boolean has(final String key) { return this.fetchCache.containsKey(key); } diff --git a/core/src/com/etheller/warsmash/viewer5/Node.java b/core/src/com/etheller/warsmash/viewer5/Node.java index 076679b..789aa84 100644 --- a/core/src/com/etheller/warsmash/viewer5/Node.java +++ b/core/src/com/etheller/warsmash/viewer5/Node.java @@ -9,32 +9,11 @@ import com.badlogic.gdx.math.Vector3; import com.etheller.warsmash.util.Descriptor; import com.etheller.warsmash.util.RenderMathUtils; -public abstract class Node { +public abstract class Node extends GenericNode { protected static final Vector3 locationHeap = new Vector3(); protected static final Quaternion rotationHeap = new Quaternion(); protected static final Vector3 scalingHeap = new Vector3(); - protected final Vector3 pivot; - protected final Vector3 localLocation; - protected final Quaternion localRotation; - protected final Vector3 localScale; - protected final Vector3 worldLocation; - protected final Quaternion worldRotation; - protected final Vector3 worldScale; - protected final Vector3 inverseWorldLocation; - protected final Quaternion inverseWorldRotation; - protected final Vector3 inverseWorldScale; - protected final Matrix4 localMatrix; - protected final Matrix4 worldMatrix; - protected Node parent; - protected final List children; - protected final boolean dontInheritTranslation; - protected final boolean dontInheritRotation; - protected final boolean dontInheritScaling; - protected boolean visible; - protected boolean wasDirty; - protected boolean dirty; - public Node() { this.pivot = new Vector3(); this.localLocation = new Vector3(); @@ -165,7 +144,7 @@ public abstract class Node { return this; } - public Node setParent(final Node parent) { + public Node setParent(final GenericNode parent) { if (this.parent != null) { this.parent.children.remove(this); } @@ -183,7 +162,7 @@ public abstract class Node { public void recalculateTransformation() { boolean dirty = this.dirty; - final Node parent = this.parent; + final GenericNode parent = this.parent; this.wasDirty = this.dirty; @@ -281,6 +260,7 @@ public abstract class Node { } + @Override public void update(final float dt, final Scene scene) { if (this.dirty || ((this.parent != null) && this.parent.wasDirty)) { this.dirty = true; // in case this node isn't dirty, but the parent was diff --git a/core/src/com/etheller/warsmash/viewer5/PathSolver.java b/core/src/com/etheller/warsmash/viewer5/PathSolver.java new file mode 100644 index 0000000..6aace1f --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/PathSolver.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.viewer5; + +public interface PathSolver { + SolvedPath solve(String src, Object solverParams); +} diff --git a/core/src/com/etheller/warsmash/viewer5/Resource.java b/core/src/com/etheller/warsmash/viewer5/Resource.java index 80db618..7016b3a 100644 --- a/core/src/com/etheller/warsmash/viewer5/Resource.java +++ b/core/src/com/etheller/warsmash/viewer5/Resource.java @@ -11,11 +11,15 @@ public abstract class Resource { public final String fetchUrl; public boolean ok; public boolean loaded; + public final PathSolver pathSolver; + public final Object solverParams = null; - public Resource(final ModelViewer viewer, final HANDLER handler, final String extension, final String fetchUrl) { + public Resource(final ModelViewer viewer, final HANDLER handler, final String extension, + final PathSolver pathSolver, final String fetchUrl) { this.viewer = viewer; this.handler = handler; this.extension = extension; + this.pathSolver = pathSolver; this.fetchUrl = fetchUrl; this.ok = false; this.loaded = false; diff --git a/core/src/com/etheller/warsmash/viewer5/Scene.java b/core/src/com/etheller/warsmash/viewer5/Scene.java index 102eb13..44bb954 100644 --- a/core/src/com/etheller/warsmash/viewer5/Scene.java +++ b/core/src/com/etheller/warsmash/viewer5/Scene.java @@ -147,7 +147,7 @@ public class Scene { Batch batch = this.batches.get(textureMapper); if (batch == null) { - final Model model = instance.model; + final Model model = instance.model; final BatchDescriptor batchDescriptor = model.handler.batchDescriptor; batch = batchDescriptor.create(this, model, textureMapper); diff --git a/core/src/com/etheller/warsmash/viewer5/Shaders.java b/core/src/com/etheller/warsmash/viewer5/Shaders.java index aa59c1e..1a2d3b9 100644 --- a/core/src/com/etheller/warsmash/viewer5/Shaders.java +++ b/core/src/com/etheller/warsmash/viewer5/Shaders.java @@ -41,7 +41,7 @@ public class Shaders { " return enc;\r\n" + // " }"; - public static final String quadTransform = "\r\n" + // + public static final String quatTransform = "\r\n" + // " // A 2D quaternion*vector.\r\n" + // " // q is the zw components of the original quaternion.\r\n" + // " vec2 quat_transform(vec2 q, vec2 v) {\r\n" + // diff --git a/core/src/com/etheller/warsmash/viewer5/SkeletalNode.java b/core/src/com/etheller/warsmash/viewer5/SkeletalNode.java index 2e8b677..4958705 100644 --- a/core/src/com/etheller/warsmash/viewer5/SkeletalNode.java +++ b/core/src/com/etheller/warsmash/viewer5/SkeletalNode.java @@ -8,31 +8,31 @@ import com.badlogic.gdx.math.Quaternion; import com.badlogic.gdx.math.Vector3; import com.etheller.warsmash.util.RenderMathUtils; -public abstract class SkeletalNode { +public abstract class SkeletalNode extends GenericNode { protected static final Vector3 locationHeap = new Vector3(); protected static final Quaternion rotationHeap = new Quaternion(); protected static final Vector3 scalingHeap = new Vector3(); - protected final Vector3 pivot; - protected final Vector3 localLocation; - protected final Quaternion localRotation; - protected final Vector3 localScale; - protected final Vector3 worldLocation; - protected final Quaternion worldRotation; - protected final Vector3 worldScale; - protected final Vector3 inverseWorldLocation; - protected final Quaternion inverseWorldRotation; - protected final Vector3 inverseWorldScale; - protected final Matrix4 localMatrix; - protected final Matrix4 worldMatrix; - protected SkeletalNode parent; - protected final List children; - protected final boolean dontInheritTranslation; - protected final boolean dontInheritRotation; - protected final boolean dontInheritScaling; - protected boolean visible; - protected boolean wasDirty; - protected boolean dirty; + public final Vector3 pivot; + public final Vector3 localLocation; + public final Quaternion localRotation; + public final Vector3 localScale; + public final Vector3 worldLocation; + public final Quaternion worldRotation; + public final Vector3 worldScale; + public final Vector3 inverseWorldLocation; + public final Quaternion inverseWorldRotation; + public final Vector3 inverseWorldScale; + public final Matrix4 localMatrix; + public final Matrix4 worldMatrix; + public SkeletalNode parent; + public final List children; + public final boolean dontInheritTranslation; + public final boolean dontInheritRotation; + public final boolean dontInheritScaling; + public boolean visible; + public boolean wasDirty; + public boolean dirty; public Object object; diff --git a/core/src/com/etheller/warsmash/viewer5/SolvedPath.java b/core/src/com/etheller/warsmash/viewer5/SolvedPath.java new file mode 100644 index 0000000..537e89e --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/SolvedPath.java @@ -0,0 +1,26 @@ +package com.etheller.warsmash.viewer5; + +public class SolvedPath { + public String finalSrc; + public String extension; + public boolean fetch; + + public SolvedPath(final String finalSrc, final String extension, final boolean fetch) { + this.finalSrc = finalSrc; + this.extension = extension; + this.fetch = fetch; + } + + public String getFinalSrc() { + return this.finalSrc; + } + + public String getExtension() { + return this.extension; + } + + public boolean isFetch() { + return this.fetch; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/Texture.java b/core/src/com/etheller/warsmash/viewer5/Texture.java new file mode 100644 index 0000000..60b9002 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/Texture.java @@ -0,0 +1,42 @@ +package com.etheller.warsmash.viewer5; + +import com.etheller.warsmash.viewer5.handlers.ResourceHandler; + +public abstract class Texture extends Resource { + private com.badlogic.gdx.graphics.Texture gdxTexture; + + public Texture(final ModelViewer viewer, final ResourceHandler handler, final String extension, + final PathSolver pathSolver, final String fetchUrl) { + super(viewer, handler, extension, pathSolver, fetchUrl); + } + + public void setGdxTexture(final com.badlogic.gdx.graphics.Texture gdxTexture) { + this.gdxTexture = gdxTexture; + } + + @Override + protected void error(final Exception e) { + throw new RuntimeException(e); + } + + public void bind(final int unit) { + this.viewer.webGL.bindTexture(this, unit); + } + + public void internalBind() { + this.gdxTexture.bind(); + } + + public int getWidth() { + return this.gdxTexture.getWidth(); + } + + public int getHeight() { + return this.gdxTexture.getHeight(); + } + + public int getGlTarget() { + return this.gdxTexture.glTarget; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/TextureMapper.java b/core/src/com/etheller/warsmash/viewer5/TextureMapper.java index 82915fd..5f9eead 100644 --- a/core/src/com/etheller/warsmash/viewer5/TextureMapper.java +++ b/core/src/com/etheller/warsmash/viewer5/TextureMapper.java @@ -3,8 +3,6 @@ package com.etheller.warsmash.viewer5; import java.util.HashMap; import java.util.Map; -import com.badlogic.gdx.graphics.Texture; - public class TextureMapper { public final Model model; public final Map textures; diff --git a/core/src/com/etheller/warsmash/viewer5/gl/WebGL.java b/core/src/com/etheller/warsmash/viewer5/gl/WebGL.java index e9f081b..8de52db 100644 --- a/core/src/com/etheller/warsmash/viewer5/gl/WebGL.java +++ b/core/src/com/etheller/warsmash/viewer5/gl/WebGL.java @@ -5,8 +5,8 @@ import java.util.Map; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.Pixmap; -import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.glutils.ShaderProgram; +import com.etheller.warsmash.viewer5.Texture; import com.etheller.warsmash.viewer5.deprecated.ShaderUnitDeprecated; /** @@ -19,7 +19,7 @@ public class WebGL { public Map shaderPrograms; public ShaderProgram currentShaderProgram; public String floatPrecision; - public final Texture emptyTexture; + public final com.badlogic.gdx.graphics.Texture emptyTexture; public WebGL(final GL20 gl) { gl.glDepthFunc(GL20.GL_LEQUAL); @@ -42,7 +42,7 @@ public class WebGL { imageData.drawPixel(i, j, 0x000000FF); } } - this.emptyTexture = new Texture(imageData); + this.emptyTexture = new com.badlogic.gdx.graphics.Texture(imageData); } public ShaderUnitDeprecated createShaderUnit(final String src, final int type) { @@ -115,7 +115,7 @@ public class WebGL { gl.glActiveTexture(GL20.GL_TEXTURE0 + unit); if (texture != null /* && texture.ok */) { - texture.bind(); + texture.internalBind(); } else { this.emptyTexture.bind(); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/EmitterObject.java b/core/src/com/etheller/warsmash/viewer5/handlers/EmitterObject.java index d2cc087..e9300b7 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/EmitterObject.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/EmitterObject.java @@ -1,5 +1,7 @@ package com.etheller.warsmash.viewer5.handlers; -public class EmitterObject { +public interface EmitterObject { + boolean ok(); + int getGeometryEmitterType(); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/ResourceHandler.java b/core/src/com/etheller/warsmash/viewer5/handlers/ResourceHandler.java index c27bb52..0aa0061 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/ResourceHandler.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/ResourceHandler.java @@ -1,10 +1,16 @@ package com.etheller.warsmash.viewer5.handlers; +import java.util.List; + import com.etheller.warsmash.viewer5.ModelViewer; +import com.etheller.warsmash.viewer5.Resource; public abstract class ResourceHandler { public ResourceHandler handler; public boolean load; + public List extensions; public abstract boolean load(ModelViewer modelViewer); + + public abstract Resource construct(ResourceHandlerConstructionParams params); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/ResourceHandlerConstructionParams.java b/core/src/com/etheller/warsmash/viewer5/handlers/ResourceHandlerConstructionParams.java new file mode 100644 index 0000000..1eb95b3 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/ResourceHandlerConstructionParams.java @@ -0,0 +1,42 @@ +package com.etheller.warsmash.viewer5.handlers; + +import com.etheller.warsmash.viewer5.ModelViewer; +import com.etheller.warsmash.viewer5.PathSolver; + +public class ResourceHandlerConstructionParams { + public final ModelViewer viewer; + public final ResourceHandler handler; + public final String extension; + public final PathSolver pathSolver; + public final String fetchUrl; + + public ResourceHandlerConstructionParams(final ModelViewer viewer, final ResourceHandler handler, + final String extension, final PathSolver pathSolver, final String fetchUrl) { + this.viewer = viewer; + this.handler = handler; + this.extension = extension; + this.pathSolver = pathSolver; + this.fetchUrl = fetchUrl; + } + + public ModelViewer getViewer() { + return this.viewer; + } + + public ResourceHandler getHandler() { + return this.handler; + } + + public String getExtension() { + return this.extension; + } + + public PathSolver getPathSolver() { + return this.pathSolver; + } + + public String getFetchUrl() { + return this.fetchUrl; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/blp/BlpHandler.java b/core/src/com/etheller/warsmash/viewer5/handlers/blp/BlpHandler.java new file mode 100644 index 0000000..632d9c4 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/blp/BlpHandler.java @@ -0,0 +1,28 @@ +package com.etheller.warsmash.viewer5.handlers.blp; + +import java.util.ArrayList; + +import com.etheller.warsmash.viewer5.ModelViewer; +import com.etheller.warsmash.viewer5.Resource; +import com.etheller.warsmash.viewer5.handlers.ResourceHandler; +import com.etheller.warsmash.viewer5.handlers.ResourceHandlerConstructionParams; + +public class BlpHandler extends ResourceHandler { + + public BlpHandler() { + this.extensions = new ArrayList<>(); + this.extensions.add(new String[] { ".blp", "arrayBuffer" }); + } + + @Override + public boolean load(final ModelViewer modelViewer) { + return true; + } + + @Override + public Resource construct(final ResourceHandlerConstructionParams params) { + return new BlpTexture(params.getViewer(), params.getHandler(), params.getExtension(), params.getPathSolver(), + params.getFetchUrl()); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/blp/BlpTexture.java b/core/src/com/etheller/warsmash/viewer5/handlers/blp/BlpTexture.java new file mode 100644 index 0000000..8bbe141 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/blp/BlpTexture.java @@ -0,0 +1,43 @@ +package com.etheller.warsmash.viewer5.handlers.blp; + +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.InputStream; + +import javax.imageio.ImageIO; + +import com.badlogic.gdx.graphics.Texture.TextureFilter; +import com.etheller.warsmash.util.ImageUtils; +import com.etheller.warsmash.viewer5.ModelViewer; +import com.etheller.warsmash.viewer5.PathSolver; +import com.etheller.warsmash.viewer5.Texture; +import com.etheller.warsmash.viewer5.handlers.ResourceHandler; + +public class BlpTexture extends Texture { + + public BlpTexture(final ModelViewer viewer, final ResourceHandler handler, final String extension, + final PathSolver pathSolver, final String fetchUrl) { + super(viewer, handler, extension, pathSolver, fetchUrl); + } + + @Override + protected void lateLoad() { + + } + + @Override + protected void load(final InputStream src, final Object options) { + BufferedImage img; + try { + img = ImageIO.read(src); + final com.badlogic.gdx.graphics.Texture texture = ImageUtils + .getTexture(ImageUtils.forceBufferedImagesRGB(img)); + texture.setFilter(TextureFilter.Linear, TextureFilter.Linear); + setGdxTexture(texture); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/AnimatedObject.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/AnimatedObject.java index 75aac83..12ab2ee 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/AnimatedObject.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/AnimatedObject.java @@ -1,6 +1,109 @@ package com.etheller.warsmash.viewer5.handlers.mdx; +import java.util.HashMap; +import java.util.Map; + +import com.etheller.warsmash.parsers.mdlx.timeline.FloatArrayTimeline; +import com.etheller.warsmash.parsers.mdlx.timeline.FloatTimeline; +import com.etheller.warsmash.parsers.mdlx.timeline.Timeline; +import com.etheller.warsmash.parsers.mdlx.timeline.UInt32Timeline; +import com.etheller.warsmash.util.War3ID; + public class AnimatedObject { - public Model model; - public + public MdxModel model; + public Map> timelines; + + public AnimatedObject(final MdxModel model, final com.etheller.warsmash.parsers.mdlx.AnimatedObject object) { + this.model = model; + this.timelines = new HashMap<>(); + + for (final Timeline timeline : object.getTimelines()) { + this.timelines.put(timeline.getName(), createTypedSd(model, timeline)); + } + } + + public int getScalarValue(final float[] out, final War3ID name, final MdxComplexInstance instance, + final float defaultValue) { + final Sd animation = this.timelines.get(name); + + if (animation instanceof ScalarSd) { + return ((ScalarSd) animation).getValue(out, instance); + } + + out[0] = defaultValue; + + return -1; + } + + public int getScalarValue(final long[] out, final War3ID name, final MdxComplexInstance instance, + final long defaultValue) { + final Sd animation = this.timelines.get(name); + + if (animation instanceof UInt32Sd) { + return ((UInt32Sd) animation).getValue(out, instance); + } + + out[0] = defaultValue; + + return -1; + } + + public int getVectorValue(final float[] out, final War3ID name, final MdxComplexInstance instance, + final float[] defaultValue) { + final Sd animation = this.timelines.get(name); + + if (animation instanceof VectorSd) { + return ((VectorSd) animation).getValue(out, instance); + } + + System.arraycopy(defaultValue, 0, out, 0, 3); + + return -1; + } + + public int getQuadValue(final float[] out, final War3ID name, final MdxComplexInstance instance, + final float[] defaultValue) { + final Sd animation = this.timelines.get(name); + + if (animation instanceof QuaternionSd) { + return ((QuaternionSd) animation).getValue(out, instance); + } + + System.arraycopy(defaultValue, 0, out, 0, 4); + + return -1; + } + + public boolean isVariant(final War3ID name, final int sequence) { + final Sd timeline = this.timelines.get(name); + + if (timeline != null) { + return timeline.isVariant(sequence); + } + + return false; + } + + private Sd createTypedSd(final MdxModel model, final Timeline timeline) { + if (timeline instanceof UInt32Timeline) { + return new UInt32Sd(model, (UInt32Timeline) timeline); + } + else if (timeline instanceof FloatTimeline) { + return new ScalarSd(model, (FloatTimeline) timeline); + } + else if (timeline instanceof FloatArrayTimeline) { + final FloatArrayTimeline faTimeline = (FloatArrayTimeline) timeline; + final int arraySize = faTimeline.getArraySize(); + if (arraySize == 3) { + return new VectorSd(model, faTimeline); + } + else if (arraySize == 4) { + return new QuaternionSd(model, faTimeline); + } + else { + throw new IllegalStateException("Unsupported arraySize = " + arraySize); + } + } + throw new IllegalStateException("Unsupported timeline type " + timeline.getClass()); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Attachment.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Attachment.java new file mode 100644 index 0000000..705335b --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Attachment.java @@ -0,0 +1,30 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +import com.etheller.warsmash.parsers.mdlx.AnimationMap; + +public class Attachment extends GenericObject { + protected final String path; + protected final int attachmentId; + protected MdxModel internalModel; + + public Attachment(final MdxModel model, final com.etheller.warsmash.parsers.mdlx.Attachment attachment, + final int index) { + super(model, attachment, index); + + final String path = attachment.getPath().replace("\\", "/").toLowerCase().replace(".mdl", ".mdx"); + + this.path = path; + this.attachmentId = attachment.getAttachmentId(); + this.internalModel = null; + + // Second condition is against custom resources using arbitrary paths + if (!path.equals("") && (path.indexOf(".mdx") != -1)) { + this.internalModel = (MdxModel) model.viewer.load(path, model.pathSolver, model.solverParams); + } + } + + @Override + public int getVisibility(final float[] out, final MdxComplexInstance instance) { + return this.getScalarValue(out, AnimationMap.KATV.getWar3id(), instance, 1); + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/AttachmentInstance.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/AttachmentInstance.java new file mode 100644 index 0000000..f8e8b3b --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/AttachmentInstance.java @@ -0,0 +1,49 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +public class AttachmentInstance { + private static final float[] visbilityHeap = new float[1]; + + private final MdxComplexInstance instance; + private final Attachment attachment; + private final MdxComplexInstance internalInstance; + + public AttachmentInstance(final MdxComplexInstance instance, final Attachment attachment) { + final MdxModel internalModel = attachment.internalModel; + final MdxComplexInstance internalInstance = (MdxComplexInstance) internalModel.addInstance(); + + internalInstance.setSequenceLoopMode(2); + internalInstance.dontInheritScaling = false; + internalInstance.hide(); + internalInstance.setParent(instance.nodes[attachment.objectId]); + + this.instance = instance; + this.attachment = attachment; + this.internalInstance = internalInstance; + } + + public void update() { + final MdxComplexInstance internalInstance = this.internalInstance; + + if (internalInstance.model.ok) { + this.attachment.getVisibility(visbilityHeap, this.instance); + + if (visbilityHeap[0] > 0.1) { + // The parent instance might not actually be in a scene. + // This happens if loading a local model, where loading is instant and adding to + // a scene always comes afterwards. + // Therefore, do it here dynamically. + this.instance.scene.addInstance(internalInstance); + + if (internalInstance.hidden()) { + internalInstance.show(); + + // Every time the attachment becomes visible again, restart its first sequence. + internalInstance.setSequence(0); + } + } + else { + internalInstance.hide(); + } + } + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Batch.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Batch.java new file mode 100644 index 0000000..930b5b3 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Batch.java @@ -0,0 +1,16 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +public class Batch { + public int index; + public Geoset geoset; + public Layer layer; + public boolean isExtended; + + public Batch(final int index, final Geoset geoset, final Layer layer, final boolean isExtended) { + this.index = index; + this.geoset = geoset; + this.layer = layer; + this.isExtended = isExtended; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/BatchGroup.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/BatchGroup.java new file mode 100644 index 0000000..de99033 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/BatchGroup.java @@ -0,0 +1,124 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +import java.util.ArrayList; +import java.util.List; + +import com.badlogic.gdx.graphics.GL20; +import com.badlogic.gdx.graphics.glutils.ShaderProgram; +import com.etheller.warsmash.viewer5.ModelViewer; +import com.etheller.warsmash.viewer5.Scene; +import com.etheller.warsmash.viewer5.Texture; + +public class BatchGroup { + + private final MdxModel model; + private final boolean isExtended; + private final List objects; + + public BatchGroup(final MdxModel model, final boolean isExtended) { + this.model = model; + this.isExtended = isExtended; + this.objects = new ArrayList<>(); // TODO IntArrayList + } + + public void render(final MdxComplexInstance instance) { + final Scene scene = instance.scene; + final MdxModel model = this.model; + final List textures = model.getTextures(); + final MdxHandler handler = model.handler; + final List teamColors = MdxHandler.teamColors; + final List teamGlows = MdxHandler.teamGlows; + final List batches = model.batches; + final List replaceables = model.replaceables; + final ModelViewer viewer = model.viewer; + final GL20 gl = viewer.gl; + final boolean isExtended = this.isExtended; + final ShaderProgram shader; + + if (isExtended) { + shader = MdxHandler.Shaders.extended; + } + else { + shader = MdxHandler.Shaders.complex; + } + + shader.begin(); + + shader.setUniformMatrix("u_mvp", scene.camera.worldProjectionMatrix); + + final Texture boneTexture = instance.boneTexture; + + // Instances of models with no bones don't have a bone texture. + if (boneTexture != null) { + boneTexture.bind(15); + + shader.setUniformf("u_hasBones", 1); + shader.setUniformf("u_boneMap", 15); + shader.setUniformf("u_vectorSize", 1f / boneTexture.getWidth()); + shader.setUniformf("u_rowSize", 1); + } + else { + shader.setUniformf("u_hasBones", 0); + } + + shader.setUniformi("u_texture", 0); + + gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, model.arrayBuffer); + gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, model.elementBuffer); + + shader.setUniform4fv("u_vertexColor", instance.vertexColor, 0, instance.vertexColor.length); + + for (final int index : this.objects) { + final Batch batch = batches.get(index); + final Geoset geoset = batch.geoset; + final Layer layer = batch.layer; + final int geosetIndex = geoset.index; + final int layerIndex = layer.index; + final float[] geosetColor = instance.geosetColors[geosetIndex]; + final float layerAlpha = instance.layerAlphas[layerIndex]; + + if ((geosetColor[3] > 0) && (layerAlpha > 0)) { + final int layerTexture = instance.layerTextures[layerIndex]; + final float[] uvAnim = instance.uvAnims[layerIndex]; + + shader.setUniform4fv("u_geosetColor", geosetColor, 0, geosetColor.length); + + shader.setUniformf("u_layerAlpha", layerAlpha); + + shader.setUniform2fv("u_uvTrans", uvAnim, 0, 2); + shader.setUniform2fv("u_uvRot", uvAnim, 2, 2); + shader.setUniform1fv("u_uvRot", uvAnim, 4, 1); + + layer.bind(shader); + + final Integer replaceable = replaceables.get(layerTexture); // TODO is this OK? + Texture texture; + + if (replaceable == 1) { + texture = teamColors.get(instance.teamColor); + } + else if (replaceable == 2) { + texture = teamGlows.get(instance.teamColor); + } + else { + texture = textures.get(layerTexture); + } + + Texture textureLookup = instance.textureMapper.get(texture); + if (textureLookup == null) { + textureLookup = texture; + } + viewer.webGL.bindTexture(textureLookup, 0); + + if (isExtended) { + geoset.bindExtended(shader, layer.coordId); + } + else { + geoset.bind(shader, layer.coordId); + } + + geoset.render(); + } + } + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Bone.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Bone.java new file mode 100644 index 0000000..e3b7f19 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Bone.java @@ -0,0 +1,24 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +public class Bone extends GenericObject { + + private final GeosetAnimation geosetAnimation; + + public Bone(final MdxModel model, final com.etheller.warsmash.parsers.mdlx.Bone bone, final int index) { + super(model, bone, index); + + this.geosetAnimation = model.getGeosetAnimations().get(bone.getGeosetAnimationId()); + } + + @Override + public int getVisibility(final float[] out, final MdxComplexInstance instance) { + if (this.geosetAnimation != null) { + return this.geosetAnimation.getAlpha(out, instance); + } + + out[0] = 1; + + return -1; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Camera.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Camera.java new file mode 100644 index 0000000..2dd4db7 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Camera.java @@ -0,0 +1,37 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +import com.etheller.warsmash.parsers.mdlx.AnimationMap; + +public class Camera extends AnimatedObject { + + private final String name; + private final float[] position; + private final float fieldOfView; + private final float farClippingPlane; + private final float nearClippingPlane; + private final float[] targetPosition; + + public Camera(final MdxModel model, final com.etheller.warsmash.parsers.mdlx.Camera camera) { + super(model, camera); + + this.name = camera.getName(); + this.position = camera.getPosition(); + this.fieldOfView = camera.getFieldOfView(); + this.farClippingPlane = camera.getFarClippingPlane(); + this.nearClippingPlane = camera.getNearClippingPlane(); + this.targetPosition = camera.getTargetPosition(); + } + + public int getPositionTranslation(final float[] out, final MdxComplexInstance instance) { + return this.getVectorValue(out, AnimationMap.KCTR.getWar3id(), instance, this.position); + } + + public int getTargetTranslation(final float[] out, final MdxComplexInstance instance) { + return this.getVectorValue(out, AnimationMap.KTTR.getWar3id(), instance, this.targetPosition); + } + + public int getRotation(final float[] out, final MdxComplexInstance instance) { + return this.getScalarValue(out, AnimationMap.KCRL.getWar3id(), instance, 0); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/CollisionShape.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/CollisionShape.java new file mode 100644 index 0000000..7ae765c --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/CollisionShape.java @@ -0,0 +1,10 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +public class CollisionShape extends GenericObject { + + public CollisionShape(final MdxModel model, final com.etheller.warsmash.parsers.mdlx.CollisionShape object, + final int index) { + super(model, object, index); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EmitterGroup.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EmitterGroup.java new file mode 100644 index 0000000..9e6088b --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EmitterGroup.java @@ -0,0 +1,64 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +import java.util.ArrayList; +import java.util.List; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.graphics.GL20; +import com.badlogic.gdx.graphics.glutils.ShaderProgram; +import com.etheller.warsmash.viewer5.Model; +import com.etheller.warsmash.viewer5.ModelViewer; +import com.etheller.warsmash.viewer5.Scene; +import com.etheller.warsmash.viewer5.SkeletalNode; +import com.etheller.warsmash.viewer5.handlers.EmitterObject; + +public class EmitterGroup { + private final MdxModel model; + private final List objects; + + public EmitterGroup(final MdxModel model) { + this.model = model; + this.objects = new ArrayList<>(); + } + + public void render(final MdxComplexInstance instance) { + final Scene scene = instance.scene; + final SkeletalNode[] nodes = instance.nodes; + final Model model = instance.model; + final ModelViewer viewer = model.viewer; + final GL20 gl = viewer.gl; + final ShaderProgram shader = MdxHandler.Shaders.particles; + + gl.glDepthMask(false); + gl.glEnable(GL20.GL_BLEND); + gl.glDisable(GL20.GL_CULL_FACE); + gl.glEnable(GL20.GL_DEPTH_TEST); + + shader.begin(); + + shader.setUniformMatrix("u_mvp", scene.camera.worldProjectionMatrix); + shader.setUniformf("u_texture", 0); + + final int a_position = shader.getAttributeLocation("a_position"); + Gdx.gl30.glVertexAttribDivisor(a_position, 0); + + gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, viewer.rectBuffer); + gl.glVertexAttribPointer(a_position, 1, GL20.GL_UNSIGNED_BYTE, false, 0, 0); + + Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_p0"), 1); + Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_p1"), 1); + Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_p2"), 1); + Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_p3"), 1); + Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_health"), 1); + Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_color"), 1); + Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_tail"), 1); + Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_leftRightTop"), 1); + + for (final int index : this.objects) { + + } + + } + + protected abstract void renderEmitter(EmitterObject emitter, ShaderProgram shader); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectEmitter.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectEmitter.java new file mode 100644 index 0000000..34fe433 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectEmitter.java @@ -0,0 +1,31 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +import com.etheller.warsmash.viewer5.EmittedObject; +import com.etheller.warsmash.viewer5.handlers.EmitterObject; + +public abstract class EventObjectEmitter>> + extends MdxEmitter { + private final int number = 0; + + public EventObjectEmitter(final MdxComplexInstance instance, final EMITTER_OBJECT emitterObject) { + super(instance, emitterObject); + } + + @Override + protected void updateEmission(final float dt) { + final MdxComplexInstance instance = this.instance; + + if (instance.allowParticleSpawn) { + final EMITTER_OBJECT emitterObject = this.emitterObject; + + emitterObject.getV + } + + } + + @Override + protected void emit() { + this.emitObject(0); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectEmitterObject.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectEmitterObject.java new file mode 100644 index 0000000..f46fece --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectEmitterObject.java @@ -0,0 +1,69 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +import com.etheller.warsmash.viewer5.ModelViewer; +import com.etheller.warsmash.viewer5.Texture; +import com.etheller.warsmash.viewer5.handlers.EmitterObject; + +public abstract class EventObjectEmitterObject extends GenericObject implements EmitterObject { + private int geometryEmitterType = -1; + private final String type; + private final String id; + private final long[] keyFrames; + private long globalSequence; + private final long[] defval = { 1 }; + private MdxModel internalModel; + private Texture internalTexture; + private float[][] colors; + private float[] intervalTimes; + private float scale; + private int columns; + private int rows; + private float lifeSpan; + private int blendSrc; + private int blendDst; + private float[][] intervals; + private float distanceCutoff; + private float maxDistance; + private float minDistance; + private float pitch; + private float pitchVariance; + private float volume; +// private AudioBuffer[] decodedBuffers; + /** + * If this is an SPL/UBR emitter object, ok will be set to true if the tables + * are loaded. + * + * This is because, like the other geometry emitters, it is fine to use them + * even if the textures don't load. + * + * The particles will simply be black. + */ + private final boolean ok = false; + + public EventObjectEmitterObject(final MdxModel model, + final com.etheller.warsmash.parsers.mdlx.EventObject eventObject, final int index) { + super(model, eventObject, index); + + final ModelViewer viewer = model.viewer; + final String name = eventObject.getName(); + String type = name.substring(0, 3); + final String id = name.substring(4); + + // Same thing + if ("FPT".equals(type)) { + type = "SPL"; + } + + if ("SPL".equals(type)) { + this.geometryEmitterType = GeometryEmitterFuncs.EMITTER_SPLAT; + } + else if ("UBR".equals(type)) { + this.geometryEmitterType = GeometryEmitterFuncs.EMITTER_UBERSPLAT; + } + + this.type = type; + this.id = id; + this.keyFrames = eventObject.getKeyFrames(); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectSplUbr.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectSplUbr.java new file mode 100644 index 0000000..d268c3c --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectSplUbr.java @@ -0,0 +1,12 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +import com.etheller.warsmash.viewer5.EmittedObject; + +public class EventObjectSplUbr> extends EmittedObject { + private final float[] vertices = new float[12]; + + @Override + protected void bind(final int flags) { + + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/FilterMode.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/FilterMode.java new file mode 100644 index 0000000..2709f75 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/FilterMode.java @@ -0,0 +1,46 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +import com.badlogic.gdx.graphics.GL20; + +public class FilterMode { + private static final int[] ERROR_DEFAULT = new int[] { 0, 0 }; + private static final int[] MODULATE_2X = new int[] { GL20.GL_DST_COLOR, GL20.GL_SRC_COLOR }; + private static final int[] MODULATE = new int[] { GL20.GL_ZERO, GL20.GL_SRC_COLOR }; + private static final int[] ADDITIVE_ALPHA = new int[] { GL20.GL_ALPHA, GL20.GL_ONE }; + private static final int[] BLEND = new int[] { GL20.GL_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA }; + + public static int[] layerFilterMode(final com.etheller.warsmash.parsers.mdlx.Layer.FilterMode filterMode) { + switch (filterMode) { + case BLEND: + return BLEND; // Blend + case ADDITIVE: + return ADDITIVE_ALPHA; // Additive + case ADDALPHA: + return ADDITIVE_ALPHA; // Add alpha + case MODULATE: + return MODULATE; // Modulate + case MODULATE2X: + return MODULATE_2X; // Modulate 2x + default: + return ERROR_DEFAULT; + } + } + + public static int[] emitterFilterMode( + final com.etheller.warsmash.parsers.mdlx.ParticleEmitter2.FilterMode filterMode) { + switch (filterMode) { + case BLEND: + return BLEND; // Blend + case ADDITIVE: + return ADDITIVE_ALPHA; // Add alpha + case MODULATE: + return MODULATE; // Modulate + case MODULATE2X: + return MODULATE_2X; // Modulate 2x + case ALPHAKEY: + return ADDITIVE_ALPHA; // Add alpha + default: + return ERROR_DEFAULT; + } + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GenericObject.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GenericObject.java new file mode 100644 index 0000000..71f02c1 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GenericObject.java @@ -0,0 +1,159 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +import com.etheller.warsmash.parsers.mdlx.AnimationMap; +import com.etheller.warsmash.util.RenderMathUtils; + +public class GenericObject extends AnimatedObject { + + public final int index; + public final String name; + public final int objectId; + public final int parentId; + public final float[] pivot; + public final int dontInheritTranslation; + public final int dontInheritRotation; + public final int dontInheritScaling; + public final int billboarded; + public final int billboardedX; + public final int billboardedY; + public final int billboardedZ; + public final int cameraAnchored; + public final int bone; + public final int light; + public final int eventObject; + public final int attachment; + public final int particleEmitter; + public final int collisionShape; + public final int ribbonEmitter; + public final int emitterUsesMdlOrUnshaded; + public final int emitterUsesTgaOrSortPrimitivesFarZ; + public final int lineEmitter; + public final int unfogged; + public final int modelSpace; + public final int xYQuad; + public final boolean anyBillboarding; + public final Variants variants; + public final boolean hasTranslationAnim; + public final boolean hasRotationAnim; + public final boolean hasScaleAnim; + public final boolean hasGenericAnim; + + public GenericObject(final MdxModel model, final com.etheller.warsmash.parsers.mdlx.GenericObject object, + final int index) { + super(model, object); + + this.index = index; + this.name = object.getName(); + this.objectId = object.getObjectId(); + int parentId = object.getParentId(); + this.pivot = (this.objectId < model.getPivotPoints().size()) ? model.getPivotPoints().get(this.objectId) + : new float[] { 0, 0, 0 }; + + final int flags = object.getFlags(); + + this.dontInheritTranslation = flags & 0x1; + this.dontInheritRotation = flags & 0x2; + this.dontInheritScaling = flags & 0x4; + this.billboarded = flags & 0x8; + this.billboardedX = flags & 0x10; + this.billboardedY = flags & 0x20; + this.billboardedZ = flags & 0x40; + this.cameraAnchored = flags & 0x80; + this.bone = flags & 0x100; + this.light = flags & 0x200; + this.eventObject = flags & 0x400; + this.attachment = flags & 0x800; + this.particleEmitter = flags & 0x1000; + this.collisionShape = flags & 0x2000; + this.ribbonEmitter = flags & 0x4000; + this.emitterUsesMdlOrUnshaded = flags & 0x8000; + this.emitterUsesTgaOrSortPrimitivesFarZ = flags & 0x10000; + this.lineEmitter = flags & 0x20000; + this.unfogged = flags & 0x40000; + this.modelSpace = flags & 0x80000; + this.xYQuad = flags & 0x100000; + + this.anyBillboarding = (this.billboarded != 0) || (this.billboardedX != 0) || (this.billboardedY != 0) + || (this.billboardedZ != 0); + + if (object.getObjectId() == object.getParentId()) { + parentId = -1; // + } + this.parentId = parentId; + + final Variants variants = new Variants(model.getSequences().size()); + + boolean hasTranslationAnim = false; + boolean hasRotationAnim = false; + boolean hasScaleAnim = false; + + for (int i = 0; i < model.getSequences().size(); i++) { + final boolean translation = this.isTranslationVariant(i); + final boolean rotation = this.isRotationVariant(i); + final boolean scale = this.isScaleVariant(i); + + variants.translation[i] = translation; + variants.rotation[i] = rotation; + variants.scale[i] = scale; + variants.generic[i] = translation || rotation || scale; + + hasTranslationAnim = hasTranslationAnim || translation; + hasRotationAnim = hasRotationAnim || rotation; + hasScaleAnim = hasScaleAnim || scale; + } + + this.variants = variants; + this.hasTranslationAnim = hasTranslationAnim; + this.hasRotationAnim = hasRotationAnim; + this.hasScaleAnim = hasScaleAnim; + this.hasGenericAnim = hasTranslationAnim || hasRotationAnim || hasScaleAnim; + } + + /** + * Many of the generic objects have animated visibilities. This is a generic + * getter to allow the code to be consistent. + */ + public int getVisibility(final float[] out, final MdxComplexInstance instance) { + out[0] = 1; + return -1; + } + + public int getTranslation(final float[] out, final MdxComplexInstance instance) { + return this.getVectorValue(out, AnimationMap.KGTR.getWar3id(), instance, RenderMathUtils.FLOAT_VEC3_ZERO); + } + + public int getRotation(final float[] out, final MdxComplexInstance instance) { + return this.getQuadValue(out, AnimationMap.KGRT.getWar3id(), instance, RenderMathUtils.FLOAT_QUAT_DEFAULT); + } + + public int getScale(final float[] out, final MdxComplexInstance instance) { + return this.getVectorValue(out, AnimationMap.KGSC.getWar3id(), instance, RenderMathUtils.FLOAT_VEC3_ONE); + } + + public boolean isTranslationVariant(final int sequence) { + return this.isVariant(AnimationMap.KGTR.getWar3id(), sequence); + } + + public boolean isRotationVariant(final int sequence) { + return this.isVariant(AnimationMap.KGRT.getWar3id(), sequence); + } + + public boolean isScaleVariant(final int sequence) { + return this.isVariant(AnimationMap.KGSC.getWar3id(), sequence); + } + + private static final class Variants { + boolean[] translation; + boolean[] rotation; + boolean[] scale; + boolean[] generic; + + public Variants(final int sequencesCount) { + this.translation = new boolean[sequencesCount]; + this.rotation = new boolean[sequencesCount]; + this.scale = new boolean[sequencesCount]; + this.generic = new boolean[sequencesCount]; + } + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GeometryEmitterFuncs.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GeometryEmitterFuncs.java new file mode 100644 index 0000000..32d3123 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GeometryEmitterFuncs.java @@ -0,0 +1,287 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.util.List; + +import com.badlogic.gdx.graphics.GL20; +import com.badlogic.gdx.graphics.glutils.ShaderProgram; +import com.badlogic.gdx.math.Vector3; +import com.etheller.warsmash.viewer5.Camera; +import com.etheller.warsmash.viewer5.ModelViewer; +import com.etheller.warsmash.viewer5.Scene; +import com.etheller.warsmash.viewer5.Texture; +import com.etheller.warsmash.viewer5.TextureMapper; + +//The total storage that emitted objects can use. +//This is enough to support all of the MDX geometry emitters. +//The memory layout is the same as this C struct: +// +//struct { +// float p0[3] +// float p1[3] +// float p2[3] +// float p3[3] +// float health +// byte color[4] +// byte tail +// byte leftRightTop[3] +//} +// +public class GeometryEmitterFuncs { + public static final int BYTES_PER_OBJECT = 60; + public static final int FLOATS_PER_OBJECT = BYTES_PER_OBJECT >> 2; + + // Offsets into the emitted object structure + public static final int BYTE_OFFSET_P0 = 0; + public static final int BYTE_OFFSET_P1 = 12; + public static final int BYTE_OFFSET_P2 = 24; + public static final int BYTE_OFFSET_P3 = 36; + public static final int BYTE_OFFSET_HEALTH = 48; + public static final int BYTE_OFFSET_COLOR = 52; + public static final int BYTE_OFFSET_TAIL = 56; + public static final int BYTE_OFFSET_LEFT_RIGHT_TOP = 57; + + // Offset aliases + public static final int FLOAT_OFFSET_P0 = BYTE_OFFSET_P0 >> 2; + public static final int FLOAT_OFFSET_P1 = BYTE_OFFSET_P1 >> 2; + public static final int FLOAT_OFFSET_P2 = BYTE_OFFSET_P2 >> 2; + public static final int FLOAT_OFFSET_P3 = BYTE_OFFSET_P3 >> 2; + public static final int FLOAT_OFFSET_HEALTH = BYTE_OFFSET_HEALTH >> 2; + public static final int BYTE_OFFSET_TEAM_COLOR = BYTE_OFFSET_LEFT_RIGHT_TOP; + + // Head or tail. + public static final int HEAD = 0; + public static final int TAIL = 1; + + // Emitter types + public static final int EMITTER_PARTICLE2 = 0; + public static final int EMITTER_RIBBON = 1; + public static final int EMITTER_SPLAT = 2; + public static final int EMITTER_UBERSPLAT = 3; + + private static final Vector3 locationHeap = new Vector3(); + private static final Vector3 startHeap = new Vector3(); + private static final Vector3 endHeap = new Vector3(); + private static final float[] vectorTemp = new float[3]; + + public static void bindParticleEmitter2Buffer(final ParticleEmitter2 emitter, final ByteBuffer buffer) { + final MdxComplexInstance instance = emitter.instance; + final List objects = emitter.objects; + final ByteBuffer byteView = buffer; + final FloatBuffer floatView = buffer.asFloatBuffer(); + final ParticleEmitter2Object emitterObject = emitter.emitterObject; + final int modelSpace = emitterObject.modelSpace; + final float tailLength = emitterObject.tailLength; + final int teamColor = instance.teamColor; + int offset = 0; + + for (final Particle2 object : objects) { + final int byteOffset = offset * BYTES_PER_OBJECT; + final int floatOffset = offset * FLOATS_PER_OBJECT; + final int p0Offset = floatOffset + FLOAT_OFFSET_P0; + Vector3 location = object.location; + final Vector3 scale = object.scale; + final int tail = object.tail; + + if (tail == HEAD) { + // If this is a model space emitter, the location is in local space, so convert + // it to world space. + if (modelSpace != 0) { + location = locationHeap.set(location).prj(emitter.node.worldMatrix); + } + + floatView.put(p0Offset + 0, location.x); + floatView.put(p0Offset + 1, location.y); + floatView.put(p0Offset + 2, location.z); + } + else { + final Vector3 velocity = object.velocity; + final Vector3 start = startHeap; + Vector3 end = location; + + start.x = end.x - (tailLength * velocity.x); + start.y = end.y - (tailLength * velocity.y); + start.z = end.z - (tailLength * velocity.z); + + // If this is a model space emitter, the start and end are in local space, so + // convert them to world space. + if (modelSpace != 0) { + start.prj(emitter.node.worldMatrix); + end = endHeap.set(end).prj(emitter.node.worldMatrix); + } + + floatView.put(p0Offset + 0, start.x); + floatView.put(p0Offset + 1, start.y); + floatView.put(p0Offset + 2, start.z); + floatView.put(p0Offset + 3, end.x); + floatView.put(p0Offset + 4, end.y); + floatView.put(p0Offset + 5, end.z); + } + + floatView.put(p0Offset + 6, scale.x); + floatView.put(p0Offset + 7, scale.y); + floatView.put(p0Offset + 8, scale.z); + + floatView.put(floatOffset + FLOAT_OFFSET_HEALTH, object.health); + byteView.put(byteOffset + BYTE_OFFSET_TAIL, (byte) tail); + byteView.put(byteOffset + BYTE_OFFSET_TEAM_COLOR, (byte) teamColor); + + offset += 1; + } + + } + + public static void bindParticleEmitter2Shader(final ParticleEmitter2 emitter, final ShaderProgram shader) { + final MdxComplexInstance instance = emitter.instance; + final Scene scene = instance.scene; + final Camera camera = scene.camera; + final ParticleEmitter2Object emitterObject = emitter.emitterObject; + final MdxModel model = emitterObject.model; + final ModelViewer viewer = model.viewer; + final GL20 gl = viewer.gl; + final float[][] colors = emitterObject.colors; + final float[][] intervals = emitterObject.intervals; + final long replaceableId = emitterObject.replaceableId; + Vector3[] vectors; + Texture texture; + + gl.glBlendFunc(emitterObject.blendSrc, emitterObject.blendDst); + + if (replaceableId == 1) { + final List teamColors = model.reforged ? MdxHandler.reforgedTeamColors : MdxHandler.teamColors; + + texture = teamColors.get(instance.teamColor); + } + else if (replaceableId == 2) { + final List teamGlows = model.reforged ? MdxHandler.reforgedTeamGlows : MdxHandler.teamGlows; + + texture = teamGlows.get(instance.teamColor); + } + else { + texture = emitterObject.internalTexture; + } + + viewer.webGL.bindTexture(texture, 0); + + // Choose between a default rectangle and a billboarded one + if (emitterObject.xYQuad != 0) { + vectors = camera.vectors; + } + else { + vectors = camera.billboardedVectors; + } + + shader.setUniformf("u_emitter", EMITTER_PARTICLE2); + + shader.setUniformf("u_lifeSpan", emitterObject.lifeSpan); + shader.setUniformf("u_timeMiddle", emitterObject.timeMiddle); + shader.setUniformf("u_columns", emitterObject.columns); + shader.setUniformf("u_rows", emitterObject.rows); + shader.setUniformf("u_teamColored", emitterObject.teamColored); + + shader.setUniform3fv("u_intervals[0]", intervals[0], 0, 3); + shader.setUniform3fv("u_intervals[1]", intervals[1], 0, 3); + shader.setUniform3fv("u_intervals[2]", intervals[2], 0, 3); + shader.setUniform3fv("u_intervals[3]", intervals[3], 0, 3); + + shader.setUniform4fv("u_colors[0]", colors[0], 0, 3); + shader.setUniform4fv("u_colors[1]", colors[1], 0, 3); + shader.setUniform4fv("u_colors[2]", colors[2], 0, 3); + + shader.setUniform3fv("u_scaling", emitterObject.scaling, 0, 3); + + if (emitterObject.head) { + shader.setUniform3fv("u_vertices[0]", asFloatArray(vectors[0]), 0, 3); + shader.setUniform3fv("u_vertices[1]", asFloatArray(vectors[1]), 0, 3); + shader.setUniform3fv("u_vertices[2]", asFloatArray(vectors[2]), 0, 3); + shader.setUniform3fv("u_vertices[3]", asFloatArray(vectors[3]), 0, 3); + } + + if (emitterObject.tail) { + shader.setUniform3fv("u_cameraZ", asFloatArray(camera.billboardedVectors[6]), 0, 3); + } + } + + public static void bindRibbonEmitterBuffer(final RibbonEmitter emitter, final ByteBuffer buffer) { + Ribbon object = emitter.first; + final ByteBuffer byteView = buffer; + final FloatBuffer floatView = buffer.asFloatBuffer(); + final RibbonEmitterObject emitterObject = emitter.emitterObject; + final long columns = emitterObject.columns; + final int alive = emitter.alive; + final float chainLengthFactor = 1 / (float) (alive - 1); + int offset = 0; + + while (object.next != null) { + final float[] next = object.next.vertices; + final int byteOffset = offset * BYTES_PER_OBJECT; + final int floatOffset = offset * FLOATS_PER_OBJECT; + final int p0Offset = floatOffset + FLOAT_OFFSET_P0; + final int colorOffset = byteOffset + BYTE_OFFSET_COLOR; + final int leftRightTopOffset = byteOffset + BYTE_OFFSET_LEFT_RIGHT_TOP; + final float left = ((object.slot % columns) + (1 - (offset * chainLengthFactor) - chainLengthFactor)) + / columns; + final float top = object.slot / (float) columns; + final float right = left + chainLengthFactor; + final float[] vertices = object.vertices; + final byte[] color = object.color; + + floatView.put(p0Offset + 0, vertices[0]); + floatView.put(p0Offset + 1, vertices[1]); + floatView.put(p0Offset + 2, vertices[2]); + floatView.put(p0Offset + 3, vertices[3]); + floatView.put(p0Offset + 4, vertices[4]); + floatView.put(p0Offset + 5, vertices[5]); + floatView.put(p0Offset + 6, next[3]); + floatView.put(p0Offset + 7, next[4]); + floatView.put(p0Offset + 8, next[5]); + floatView.put(p0Offset + 9, next[0]); + floatView.put(p0Offset + 10, next[1]); + floatView.put(p0Offset + 11, next[2]); + + byteView.put(colorOffset + 0, color[0]); + byteView.put(colorOffset + 1, color[1]); + byteView.put(colorOffset + 2, color[2]); + byteView.put(colorOffset + 3, color[3]); + + byteView.put(leftRightTopOffset + 0, (byte) (left * 255)); + byteView.put(leftRightTopOffset + 1, (byte) (right * 255)); + byteView.put(leftRightTopOffset + 2, (byte) (top * 255)); + + object = object.next; + offset += 1; + + } + } + + public static void bindRibbonEmitterShader(final RibbonEmitter emitter, final ShaderProgram shader) { + final TextureMapper textureMapper = emitter.instance.textureMapper; + final RibbonEmitterObject emitterObject = emitter.emitterObject; + final Layer layer = emitterObject.layer; + final MdxModel model = emitterObject.model; + final GL20 gl = model.viewer.gl; + final Texture texture = model.getTextures().get(layer.textureId); + + layer.bind(shader); + + Texture mappedTexture = textureMapper.get(texture); + if (mappedTexture == null) { + mappedTexture = texture; + } + model.viewer.webGL.bindTexture(mappedTexture, 0); + + shader.setUniformf("u_emitter", EMITTER_RIBBON); + + shader.setUniformf("u_columns", emitterObject.columns); + shader.setUniformf("u_rows", emitterObject.rows); + } + + private static final float[] asFloatArray(final Vector3 vec) { + vectorTemp[0] = vec.x; + vectorTemp[1] = vec.y; + vectorTemp[2] = vec.z; + return vectorTemp; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Geoset.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Geoset.java new file mode 100644 index 0000000..ef010e7 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Geoset.java @@ -0,0 +1,150 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +import java.util.Arrays; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.graphics.GL20; +import com.badlogic.gdx.graphics.GL30; +import com.badlogic.gdx.graphics.glutils.ShaderProgram; + +public class Geoset { + public MdxModel model; + public int index; + public int positionOffset; + public int normalOffset; + public int uvOffset; + public int skinOffset; + public int faceOffset; + public int vertices; + public int elements; + public GeosetAnimation geosetAnimation; + public Variants variants; + public boolean hasAlphaAnim; + public boolean hasColorAnim; + public boolean hasObjectAnim; + + public Geoset(final MdxModel model, final int index, final int positionOffset, final int normalOffset, + final int uvOffset, final int skinOffset, final int faceOffset, final int vertices, final int elements) { + this.model = model; + this.index = index; + this.positionOffset = positionOffset; + this.normalOffset = normalOffset; + this.uvOffset = uvOffset; + this.skinOffset = skinOffset; + this.faceOffset = faceOffset; + this.vertices = vertices; + this.elements = elements; + + for (final GeosetAnimation geosetAnimation : model.getGeosetAnimations()) { + if (geosetAnimation.geosetId == index) { + this.geosetAnimation = geosetAnimation; + } + } + + final Variants variants = new Variants(model.getSequences().size()); + + final GeosetAnimation geosetAnimation = this.geosetAnimation; + boolean hasAlphaAnim = false; + boolean hasColorAnim = false; + + if (geosetAnimation != null) { + for (int i = 0, l = model.getSequences().size(); i < l; i++) { + final boolean alpha = geosetAnimation.isAlphaVariant(i); + final boolean color = geosetAnimation.isColorVariant(i); + + variants.alpha[i] = alpha; + variants.color[i] = color; + variants.object[i] = alpha || color; + + hasAlphaAnim = hasAlphaAnim || alpha; + hasColorAnim = hasColorAnim || color; + } + } + else { + for (int i = 0, l = model.getSequences().size(); i < l; i++) { + variants.alpha[i] = false; + variants.color[i] = false; + variants.object[i] = false; + } + } + + this.variants = variants; + this.hasAlphaAnim = hasAlphaAnim; + this.hasColorAnim = hasColorAnim; + this.hasObjectAnim = hasAlphaAnim || hasColorAnim; + } + + public int getAlpha(final float[] out, final MdxComplexInstance instance) { + if (this.geosetAnimation != null) { + return this.geosetAnimation.getAlpha(out, instance); + } + + out[0] = 1; + return -1; + } + + public int getColor(final float[] out, final MdxComplexInstance instance) { + if (this.geosetAnimation != null) { + return this.geosetAnimation.getAlpha(out, instance); + } + + Arrays.fill(out, 1); + return -1; + } + + public void bind(final ShaderProgram shader, final int coordId) { + // TODO use indices instead of strings for attributes + shader.setVertexAttribute("a_position", 3, GL20.GL_FLOAT, false, 0, this.positionOffset); + shader.setVertexAttribute("a_normal", 3, GL20.GL_FLOAT, false, 0, this.normalOffset); + shader.setVertexAttribute("a_uv", 2, GL20.GL_FLOAT, false, 0, this.uvOffset + (coordId * this.vertices * 8)); + shader.setVertexAttribute("a_bones", 4, GL20.GL_UNSIGNED_BYTE, false, 5, this.skinOffset); + shader.setVertexAttribute("a_boneNumber", 1, GL20.GL_UNSIGNED_BYTE, false, 5, this.skinOffset + 4); + } + + public void bindExtended(final ShaderProgram shader, final int coordId) { + // TODO use indices instead of strings for attributes + shader.setVertexAttribute("a_position", 3, GL20.GL_FLOAT, false, 0, this.positionOffset); + shader.setVertexAttribute("a_normal", 3, GL20.GL_FLOAT, false, 0, this.normalOffset); + shader.setVertexAttribute("a_uv", 2, GL20.GL_FLOAT, false, 0, this.uvOffset + (coordId * this.vertices * 8)); + shader.setVertexAttribute("a_bones", 4, GL20.GL_UNSIGNED_BYTE, false, 9, this.skinOffset); + shader.setVertexAttribute("a_extendedBones", 4, GL20.GL_UNSIGNED_BYTE, false, 9, this.skinOffset + 4); + shader.setVertexAttribute("a_boneNumber", 1, GL20.GL_UNSIGNED_BYTE, false, 9, this.skinOffset + 8); + } + + public void render() { + final GL20 gl = this.model.viewer.gl; + + gl.glDrawElements(GL20.GL_TRIANGLES, this.elements, GL20.GL_UNSIGNED_SHORT, this.faceOffset); + } + + public void bindSimple(final ShaderProgram shader) { + shader.setVertexAttribute("a_position", 3, GL20.GL_FLOAT, false, 0, this.positionOffset); + shader.setVertexAttribute("a_uv", 2, GL20.GL_FLOAT, false, 0, this.uvOffset); + } + + public void renderSimple(final int instances) { + Gdx.gl30.glDrawElementsInstanced(GL30.GL_TRIANGLES, this.elements, GL30.GL_UNSIGNED_SHORT, this.faceOffset, + instances); + } + + public void bindHd(final ShaderProgram shader, final int coordId) { + shader.setVertexAttribute("a_position", 3, GL20.GL_FLOAT, false, 0, this.positionOffset); + shader.setVertexAttribute("a_normal", 3, GL20.GL_FLOAT, false, 0, this.positionOffset); + shader.setVertexAttribute("a_uv", 2, GL20.GL_FLOAT, false, 0, this.uvOffset + (coordId * this.vertices * 8)); + shader.setVertexAttribute("a_bones", 4, GL20.GL_UNSIGNED_BYTE, false, 8, this.skinOffset); + shader.setVertexAttribute("a_weights", 4, GL20.GL_UNSIGNED_BYTE, false, 8, this.skinOffset + 4); + } + + private static final class Variants { + private final boolean[] alpha; + private final boolean[] color; + private final boolean[] object; + + public Variants(final int size) { + this.alpha = new boolean[size]; + this.color = new boolean[size]; + this.object = new boolean[size]; + } + + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GeosetAnimation.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GeosetAnimation.java new file mode 100644 index 0000000..b4ee688 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GeosetAnimation.java @@ -0,0 +1,37 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +import com.etheller.warsmash.parsers.mdlx.AnimationMap; + +public class GeosetAnimation extends AnimatedObject { + + private final float alpha; + private final float[] color; + public final int geosetId; + + public GeosetAnimation(final MdxModel model, + final com.etheller.warsmash.parsers.mdlx.GeosetAnimation geosetAnimation) { + super(model, geosetAnimation); + + final float[] color = geosetAnimation.getColor(); + + this.alpha = geosetAnimation.getAlpha(); + this.color = new float[] { color[2], color[1], color[0] }; + this.geosetId = geosetAnimation.getGeosetId(); + } + + public int getAlpha(final float[] out, final MdxComplexInstance instance) { + return this.getScalarValue(out, AnimationMap.KGAO.getWar3id(), instance, this.alpha); + } + + public int getColor(final float[] out, final MdxComplexInstance instance) { + return this.getVectorValue(out, AnimationMap.KGAC.getWar3id(), instance, this.color); + } + + public boolean isAlphaVariant(final int sequence) { + return this.isVariant(AnimationMap.KGAO.getWar3id(), sequence); + } + + public boolean isColorVariant(final int sequence) { + return this.isVariant(AnimationMap.KGAC.getWar3id(), sequence); + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Layer.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Layer.java index 95639c2..378c35e 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Layer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Layer.java @@ -1,9 +1,10 @@ package com.etheller.warsmash.viewer5.handlers.mdx; +import com.badlogic.gdx.graphics.glutils.ShaderProgram; import com.etheller.warsmash.parsers.mdlx.Layer.FilterMode; public class Layer { - public Model model; + public MdxModel model; public com.etheller.warsmash.parsers.mdlx.Layer layer; public int layerId; public int priorityPlane; @@ -13,7 +14,9 @@ public class Layer { public int coordId; public float alpha; - public Layer(final Model model, final com.etheller.warsmash.parsers.mdlx.Layer layer, final int layerId, + public int index = -666; + + public Layer(final MdxModel model, final com.etheller.warsmash.parsers.mdlx.Layer layer, final int layerId, final int priorityPlane) { super(model, layer); this.model = model; @@ -25,6 +28,12 @@ public class Layer { this.filterMode = filterMode2.ordinal(); this.textureId = layer.getTextureId(); // this.coo + this.index + } + + public void bind(final ShaderProgram shader) { + // TODO Auto-generated method stub + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Material.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Material.java index fe589c6..a07ca08 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Material.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Material.java @@ -3,11 +3,11 @@ package com.etheller.warsmash.viewer5.handlers.mdx; import java.util.List; public class Material { - public final Model model; + public final MdxModel model; public final String shader; public final List layers; - public Material(final Model model, final String shader, final List layers) { + public Material(final MdxModel model, final String shader, final List layers) { this.model = model; this.shader = shader; this.layers = layers; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java new file mode 100644 index 0000000..3f3a499 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java @@ -0,0 +1,70 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +import com.etheller.warsmash.viewer5.ModelInstance; +import com.etheller.warsmash.viewer5.SkeletalNode; +import com.etheller.warsmash.viewer5.Texture; + +public class MdxComplexInstance extends ModelInstance { + + public MdxNode[] nodes; + public SkeletalNode[] sortedNodes; + + public int frame; + public int counter; + public int sequence; + public int sequenceLoopMode; + public boolean sequenceEnded; + public int teamColor; + public Texture boneTexture; + // TODO more fields, these few are to make related classes compile + public float[] vertexColor; + public float[][] geosetColors; + public float[] layerAlphas; + public int[] layerTextures; + public float[][] uvAnims; + public boolean allowParticleSpawn; + + public MdxComplexInstance(final MdxModel model) { + super(model); + } + + @Override + public void updateAnimations(final float dt) { + // TODO Auto-generated method stub + + } + + @Override + public void clearEmittedObjects() { + // TODO Auto-generated method stub + + } + + @Override + public void renderOpaque() { + // TODO Auto-generated method stub + + } + + @Override + public void renderTranslucent() { + // TODO Auto-generated method stub + + } + + @Override + public void load() { + // TODO Auto-generated method stub + + } + + public MdxComplexInstance setSequenceLoopMode(final int mode) { + this.sequenceLoopMode = mode; + return this; + } + + public void setSequence(final int sequence) { + this.sequence = sequence; + throw new UnsupportedOperationException("Not yet implemented"); + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxEmitter.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxEmitter.java new file mode 100644 index 0000000..0b1b9eb --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxEmitter.java @@ -0,0 +1,25 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +import com.etheller.warsmash.viewer5.EmittedObject; +import com.etheller.warsmash.viewer5.Emitter; +import com.etheller.warsmash.viewer5.ModelInstance; +import com.etheller.warsmash.viewer5.handlers.EmitterObject; + +public abstract class MdxEmitter>> + extends Emitter { + + protected final EMITTER_OBJECT emitterObject; + + public MdxEmitter(final MODEL_INSTANCE instance, final EMITTER_OBJECT emitterObject) { + super(instance); + + this.emitterObject = emitterObject; + } + + @Override + public void update(final float dt) { + if (this.emitterObject.ok()) { + super.update(dt); + } + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxHandler.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxHandler.java new file mode 100644 index 0000000..2acd4f9 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxHandler.java @@ -0,0 +1,64 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +import java.util.ArrayList; +import java.util.List; + +import com.badlogic.gdx.graphics.glutils.ShaderProgram; +import com.etheller.warsmash.viewer5.ModelViewer; +import com.etheller.warsmash.viewer5.Resource; +import com.etheller.warsmash.viewer5.Texture; +import com.etheller.warsmash.viewer5.handlers.ModelHandler; +import com.etheller.warsmash.viewer5.handlers.ResourceHandlerConstructionParams; +import com.etheller.warsmash.viewer5.handlers.blp.BlpHandler; + +public class MdxHandler extends ModelHandler { + + // Team color/glow textures, shared between all models, but loaded with the + // first model that uses them. + public static final List teamColors = new ArrayList<>(); + public static final List teamGlows = new ArrayList<>(); + + public static final List reforgedTeamColors = new ArrayList<>(); + public static final List reforgedTeamGlows = new ArrayList<>(); + + public MdxHandler() { + this.extensions = new ArrayList<>(); + this.extensions.add(new String[] { ".mdx", "arrayBuffer" }); + this.extensions.add(new String[] { ".mdl", "text" }); + } + + @Override + public boolean load(final ModelViewer viewer) { + viewer.addHandler(new BlpHandler()); + + Shaders.complex = viewer.webGL.createShaderProgram(MdxShaders.vsComplex, MdxShaders.fsComplex); + Shaders.extended = viewer.webGL.createShaderProgram("#define EXTENDED_BONES\r\n" + MdxShaders.vsComplex, + MdxShaders.fsComplex); + Shaders.particles = viewer.webGL.createShaderProgram(MdxShaders.vsParticles, MdxShaders.fsParticles); + Shaders.simple = viewer.webGL.createShaderProgram(MdxShaders.vsSimple, MdxShaders.fsSimple); + Shaders.hd = viewer.webGL.createShaderProgram(MdxShaders.vsHd, MdxShaders.fsHd); + + // If a shader failed to compile, don't allow the handler to be registered, and + // send an error instead. + return Shaders.complex.isCompiled() && Shaders.extended.isCompiled() && Shaders.particles.isCompiled() + && Shaders.simple.isCompiled() && Shaders.hd.isCompiled(); + } + + @Override + public Resource construct(final ResourceHandlerConstructionParams params) { + return new MdxModel((MdxHandler) params.getHandler(), params.getViewer(), params.getExtension(), + params.getPathSolver(), params.getFetchUrl()); + } + + public static final class Shaders { + private Shaders() { + + } + + public static ShaderProgram complex; + public static ShaderProgram extended; + public static ShaderProgram simple; + public static ShaderProgram particles; + public static ShaderProgram hd; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java new file mode 100644 index 0000000..a390c34 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java @@ -0,0 +1,73 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +import com.etheller.warsmash.parsers.mdlx.MdlxModel; +import com.etheller.warsmash.parsers.mdlx.Sequence; +import com.etheller.warsmash.viewer5.ModelViewer; +import com.etheller.warsmash.viewer5.PathSolver; +import com.etheller.warsmash.viewer5.Texture; + +public class MdxModel extends com.etheller.warsmash.viewer5.Model { + private MdlxModel model; + + public int arrayBuffer; + public int elementBuffer; + + public List batches = new ArrayList<>(); // TODO?? + + public List replaceables = new ArrayList<>(); + + public boolean reforged = false; + + public MdxModel(final MdxHandler handler, final ModelViewer viewer, final String extension, + final PathSolver pathSolver, final String fetchUrl) { + super(handler, viewer, extension, pathSolver, fetchUrl); + } + + @Override + protected void lateLoad() { + // TODO Auto-generated method stub + + } + + @Override + protected void load(final InputStream src, final Object options) { + // TODO Auto-generated method stub + + } + + @Override + protected void error(final Exception e) { + // TODO Auto-generated method stub + + } + + // TODO typing + public List getGlobalSequences() { + return this.model.getGlobalSequences(); + } + + public List getSequences() { + return this.model.getSequences(); + } + + public List getPivotPoints() { + return this.model.getPivotPoints(); + } + + public List getGeosetAnimations() { + throw new UnsupportedOperationException("NYI"); + } + + public List getTextures() { + throw new UnsupportedOperationException("NYI"); + } + + public List getMaterials() { + throw new UnsupportedOperationException("NYI"); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxNode.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxNode.java new file mode 100644 index 0000000..c958764 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxNode.java @@ -0,0 +1,22 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +import com.badlogic.gdx.math.Quaternion; +import com.etheller.warsmash.viewer5.Scene; +import com.etheller.warsmash.viewer5.SkeletalNode; + +public class MdxNode extends SkeletalNode { + private static final Quaternion HALF_PI_X = new Quaternion().setFromAxisRad(1, 0, 0, (float) (-Math.PI / 2)); + private static final Quaternion HALF_PI_Y = new Quaternion().setFromAxisRad(0, 1, 0, (float) (-Math.PI / 2)); + + @Override + protected void convertBasis(final Quaternion computedRotation) { + computedRotation.mulLeft(HALF_PI_Y); + computedRotation.mulLeft(HALF_PI_X); + } + + @Override + protected void update(final float dt, final Scene scene) { + + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxShaders.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxShaders.java new file mode 100644 index 0000000..bb65b5f --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxShaders.java @@ -0,0 +1,408 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +import com.etheller.warsmash.viewer5.Shaders; + +public class MdxShaders { + public static final String vsHd = Shaders.boneTexture + "\r\n" + // + " uniform mat4 u_mvp;\r\n" + // + " uniform float u_layerAlpha;\r\n" + // + " attribute vec3 a_position;\r\n" + // + " attribute vec3 a_normal;\r\n" + // + " attribute vec2 a_uv;\r\n" + // + " attribute vec4 a_bones;\r\n" + // + " attribute vec4 a_weights;\r\n" + // + " varying vec3 v_normal;\r\n" + // + " varying vec2 v_uv;\r\n" + // + " varying float v_layerAlpha;\r\n" + // + " void transform(inout vec3 position, inout vec3 normal) {\r\n" + // + " mat4 bone;\r\n" + // + " bone += fetchMatrix(a_bones[0], 0.0) * a_weights[0];\r\n" + // + " bone += fetchMatrix(a_bones[1], 0.0) * a_weights[1];\r\n" + // + " bone += fetchMatrix(a_bones[2], 0.0) * a_weights[2];\r\n" + // + " bone += fetchMatrix(a_bones[3], 0.0) * a_weights[3];\r\n" + // + " position = vec3(bone * vec4(position, 1.0));\r\n" + // + " normal = mat3(bone) * normal;\r\n" + // + " }\r\n" + // + " void main() {\r\n" + // + " vec3 position = a_position;\r\n" + // + " vec3 normal = a_normal;\r\n" + // + " transform(position, normal);\r\n" + // + " v_normal = normal;\r\n" + // + " v_uv = a_uv;\r\n" + // + " v_layerAlpha = u_layerAlpha;\r\n" + // + " gl_Position = u_mvp * vec4(position, 1.0);\r\n" + // + " }"; + + public static final String fsHd = "\r\n" + // + " uniform sampler2D u_diffuseMap;\r\n" + // + " uniform sampler2D u_ormMap;\r\n" + // + " uniform sampler2D u_teamColorMap;\r\n" + // + " uniform float u_filterMode;\r\n" + // + " varying vec3 v_normal;\r\n" + // + " varying vec2 v_uv;\r\n" + // + " varying float v_layerAlpha;\r\n" + // + " void main() {\r\n" + // + " vec4 texel = texture2D(u_diffuseMap, v_uv);\r\n" + // + " vec4 color = vec4(texel.rgb, texel.a * v_layerAlpha);\r\n" + // + " vec4 orma = texture2D(u_ormMap, v_uv);\r\n" + // + " if (orma.a > 0.1) {\r\n" + // + " color *= texture2D(u_teamColorMap, v_uv) * orma.a;\r\n" + // + " }\r\n" + // + " // 1bit Alpha\r\n" + // + " if (u_filterMode == 1.0 && color.a < 0.75) {\r\n" + // + " discard;\r\n" + // + " }\r\n" + // + " gl_FragColor = color;\r\n" + // + " }"; + + public static final String vsSimple = "\r\n" + // + " uniform mat4 u_mvp;\r\n" + // + " attribute vec3 a_m0;\r\n" + // + " attribute vec3 a_m1;\r\n" + // + " attribute vec3 a_m2;\r\n" + // + " attribute vec3 a_m3;\r\n" + // + " attribute vec3 a_position;\r\n" + // + " attribute vec2 a_uv;\r\n" + // + " varying vec2 v_uv;\r\n" + // + " void main() {\r\n" + // + " v_uv = a_uv;\r\n" + // + " gl_Position = u_mvp * mat4(a_m0, 0.0, a_m1, 0.0, a_m2, 0.0, a_m3, 1.0) * vec4(a_position, 1.0);\r\n" + + // + " }"; + + public static final String fsSimple = "\r\n" + // + " uniform sampler2D u_texture;\r\n" + // + " uniform float u_filterMode;\r\n" + // + " varying vec2 v_uv;\r\n" + // + " void main() {\r\n" + // + " vec4 color = texture2D(u_texture, v_uv);\r\n" + // + " // 1bit Alpha\r\n" + // + " if (u_filterMode == 1.0 && color.a < 0.75) {\r\n" + // + " discard;\r\n" + // + " }\r\n" + // + " gl_FragColor = color;\r\n" + // + " }"; + + public static final String vsComplex = Shaders.boneTexture + "\r\n" + // + " uniform mat4 u_mvp;\r\n" + // + " uniform vec4 u_vertexColor;\r\n" + // + " uniform vec4 u_geosetColor;\r\n" + // + " uniform float u_layerAlpha;\r\n" + // + " uniform vec2 u_uvTrans;\r\n" + // + " uniform vec2 u_uvRot;\r\n" + // + " uniform float u_uvScale;\r\n" + // + " uniform bool u_hasBones;\r\n" + // + " attribute vec3 a_position;\r\n" + // + " attribute vec3 a_normal;\r\n" + // + " attribute vec2 a_uv;\r\n" + // + " attribute vec4 a_bones;\r\n" + // + " #ifdef EXTENDED_BONES\r\n" + // + " attribute vec4 a_extendedBones;\r\n" + // + " #endif\r\n" + // + " attribute float a_boneNumber;\r\n" + // + " varying vec2 v_uv;\r\n" + // + " varying vec4 v_color;\r\n" + // + " varying vec4 v_uvTransRot;\r\n" + // + " varying float v_uvScale;\r\n" + // + " void transform(inout vec3 position, inout vec3 normal) {\r\n" + // + " // For the broken models out there, since the game supports this.\r\n" + // + " if (a_boneNumber > 0.0) {\r\n" + // + " vec4 position4 = vec4(position, 1.0);\r\n" + // + " vec4 normal4 = vec4(normal, 0.0);\r\n" + // + " mat4 bone;\r\n" + // + " vec4 p;\r\n" + // + " vec4 n;\r\n" + // + " for (int i = 0; i < 4; i++) {\r\n" + // + " if (a_bones[i] > 0.0) {\r\n" + // + " bone = fetchMatrix(a_bones[i] - 1.0, 0.0);\r\n" + // + " p += bone * position4;\r\n" + // + " n += bone * normal4;\r\n" + // + " }\r\n" + // + " }\r\n" + // + " #ifdef EXTENDED_BONES\r\n" + // + " for (int i = 0; i < 4; i++) {\r\n" + // + " if (a_extendedBones[i] > 0.0) {\r\n" + // + " bone = fetchMatrix(a_extendedBones[i] - 1.0, 0.0);\r\n" + // + " p += bone * position4;\r\n" + // + " n += bone * normal4;\r\n" + // + " }\r\n" + // + " }\r\n" + // + " #endif\r\n" + // + " position = p.xyz / a_boneNumber;\r\n" + // + " normal = normalize(n.xyz);\r\n" + // + " }\r\n" + // + " }\r\n" + // + " void main() {\r\n" + // + " vec3 position = a_position;\r\n" + // + " vec3 normal = a_normal;\r\n" + // + " if (u_hasBones) {\r\n" + // + " transform(position, normal);\r\n" + // + " }\r\n" + // + " v_uv = a_uv;\r\n" + // + " v_color = u_vertexColor * u_geosetColor.bgra * vec4(1.0, 1.0, 1.0, u_layerAlpha);\r\n" + // + " v_uvTransRot = vec4(u_uvTrans, u_uvRot);\r\n" + // + " v_uvScale = u_uvScale;\r\n" + // + " gl_Position = u_mvp * vec4(position, 1.0);\r\n" + // + " }"; + + public static final String fsComplex = Shaders.quatTransform + "\r\n\r\n" + // + " uniform sampler2D u_texture;\r\n" + // + " uniform float u_filterMode;\r\n" + // + " varying vec2 v_uv;\r\n" + // + " varying vec4 v_color;\r\n" + // + " varying vec4 v_uvTransRot;\r\n" + // + " varying float v_uvScale;\r\n" + // + " void main() {\r\n" + // + " vec2 uv = v_uv;\r\n" + // + " // Translation animation\r\n" + // + " uv += v_uvTransRot.xy;\r\n" + // + " // Rotation animation\r\n" + // + " uv = quat_transform(v_uvTransRot.zw, uv - 0.5) + 0.5;\r\n" + // + " // Scale animation\r\n" + // + " uv = v_uvScale * (uv - 0.5) + 0.5;\r\n" + // + " vec4 texel = texture2D(u_texture, uv);\r\n" + // + " vec4 color = texel * v_color;\r\n" + // + " // 1bit Alpha\r\n" + // + " if (u_filterMode == 1.0 && color.a < 0.75) {\r\n" + // + " discard;\r\n" + // + " }\r\n" + // + " // \"Close to 0 alpha\"\r\n" + // + " if (u_filterMode >= 5.0 && color.a < 0.02) {\r\n" + // + " discard;\r\n" + // + " }\r\n" + // + " // if (!u_unshaded) {\r\n" + // + " // color *= clamp(dot(v_normal, lightDirection) + 0.45, 0.0, 1.0);\r\n" + // + " // }\r\n" + // + " gl_FragColor = color;\r\n" + // + " }"; + + public static final String vsParticles = "\r\n" + // + " #define EMITTER_PARTICLE2 0.0\r\n" + // + " #define EMITTER_RIBBON 1.0\r\n" + // + " #define EMITTER_SPLAT 2.0\r\n" + // + " #define EMITTER_UBERSPLAT 3.0\r\n" + // + " #define HEAD 0.0\r\n" + // + " uniform mat4 u_mvp;\r\n" + // + " uniform mediump float u_emitter;\r\n" + // + " // Shared\r\n" + // + " uniform vec4 u_colors[3];\r\n" + // + " uniform vec3 u_vertices[4];\r\n" + // + " uniform vec3 u_intervals[4];\r\n" + // + " uniform float u_lifeSpan;\r\n" + // + " uniform float u_columns;\r\n" + // + " uniform float u_rows;\r\n" + // + " // Particle2\r\n" + // + " uniform vec3 u_scaling;\r\n" + // + " uniform vec3 u_cameraZ;\r\n" + // + " uniform float u_timeMiddle;\r\n" + // + " uniform bool u_teamColored;\r\n" + // + " // Splat and Uber.\r\n" + // + " uniform vec3 u_intervalTimes;\r\n" + // + " // Vertices\r\n" + // + " attribute float a_position;\r\n" + // + " // Instances\r\n" + // + " attribute vec3 a_p0;\r\n" + // + " attribute vec3 a_p1;\r\n" + // + " attribute vec3 a_p2;\r\n" + // + " attribute vec3 a_p3;\r\n" + // + " attribute float a_health;\r\n" + // + " attribute vec4 a_color;\r\n" + // + " attribute float a_tail;\r\n" + // + " attribute vec3 a_leftRightTop;\r\n" + // + " varying vec2 v_texcoord;\r\n" + // + " varying vec4 v_color;\r\n" + // + " float getCell(vec3 interval, float factor) {\r\n" + // + " float start = interval[0];\r\n" + // + " float end = interval[1];\r\n" + // + " float repeat = interval[2];\r\n" + // + " float spriteCount = end - start;\r\n" + // + " if (spriteCount > 0.0) {\r\n" + // + " // Repeating speeds up the sprite animation, which makes it effectively run N times in its interval.\r\n" + + // + " // E.g. if repeat is 4, the sprite animation will be seen 4 times, and thus also run 4 times as fast.\r\n" + + // + " // The sprite index is limited to the number of actual sprites.\r\n" + // + " return min(start + mod(floor(spriteCount * repeat * factor), spriteCount), u_columns * u_rows - 1.0);\r\n" + + // + " }\r\n" + // + " return 0.0;\r\n" + // + " }\r\n" + // + " void particle2() {\r\n" + // + " float factor = (u_lifeSpan - a_health) / u_lifeSpan;\r\n" + // + " int index = 0;\r\n" + // + " if (factor < u_timeMiddle) {\r\n" + // + " factor = factor / u_timeMiddle;\r\n" + // + " index = 0;\r\n" + // + " } else {\r\n" + // + " factor = (factor - u_timeMiddle) / (1.0 - u_timeMiddle);\r\n" + // + " index = 1;\r\n" + // + " }\r\n" + // + " factor = min(factor, 1.0);\r\n" + // + " float scale = mix(u_scaling[index], u_scaling[index + 1], factor);\r\n" + // + " vec4 color = mix(u_colors[index], u_colors[index + 1], factor);\r\n" + // + " float cell = 0.0;\r\n" + // + " if (u_teamColored) {\r\n" + // + " cell = a_leftRightTop[0];\r\n" + // + " } else {\r\n" + // + " vec3 interval;\r\n" + // + " if (a_tail == HEAD) {\r\n" + // + " interval = u_intervals[index];\r\n" + // + " } else {\r\n" + // + " interval = u_intervals[index + 2];\r\n" + // + " }\r\n" + // + " cell = getCell(interval, factor);\r\n" + // + " }\r\n" + // + " float left = floor(mod(cell, u_columns));\r\n" + // + " float top = floor(cell / u_columns);\r\n" + // + " float right = left + 1.0;\r\n" + // + " float bottom = top + 1.0;\r\n" + // + " left /= u_columns;\r\n" + // + " right /= u_columns;\r\n" + // + " top /= u_rows;\r\n" + // + " bottom /= u_rows;\r\n" + // + " if (a_position == 0.0) {\r\n" + // + " v_texcoord = vec2(right, top);\r\n" + // + " } else if (a_position == 1.0) {\r\n" + // + " v_texcoord = vec2(left, top);\r\n" + // + " } else if (a_position == 2.0) {\r\n" + // + " v_texcoord = vec2(left, bottom);\r\n" + // + " } else if (a_position == 3.0) {\r\n" + // + " v_texcoord = vec2(right, bottom);\r\n" + // + " }\r\n" + // + " v_color = color;\r\n" + // + " \r\n" + // + " if (a_tail == HEAD) {\r\n" + // + " gl_Position = u_mvp * vec4(a_p0 + (u_vertices[int(a_position)] * scale), 1.0);\r\n" + // + " } else {\r\n" + // + " // Get the normal to the tail in camera space.\r\n" + // + " // This allows to build a 2D rectangle around the 3D tail.\r\n" + // + " vec3 normal = cross(u_cameraZ, normalize(a_p1 - a_p0));\r\n" + // + " vec3 boundary = normal * scale * a_p2[0];\r\n" + // + " vec3 position;\r\n" + // + " if (a_position == 0.0) {\r\n" + // + " position = a_p0 - boundary;\r\n" + // + " } else if (a_position == 1.0) {\r\n" + // + " position = a_p1 - boundary;\r\n" + // + " } else if (a_position == 2.0) {\r\n" + // + " position = a_p1 + boundary;\r\n" + // + " } else if (a_position == 3.0) {\r\n" + // + " position = a_p0 + boundary;\r\n" + // + " }\r\n" + // + " gl_Position = u_mvp * vec4(position, 1.0);\r\n" + // + " }\r\n" + // + " }\r\n" + // + " void ribbon() {\r\n" + // + " vec3 position;\r\n" + // + " float left = a_leftRightTop[0] / 255.0;\r\n" + // + " float right = a_leftRightTop[1] / 255.0;\r\n" + // + " float top = a_leftRightTop[2] / 255.0;\r\n" + // + " float bottom = top + 1.0;\r\n" + // + " if (a_position == 0.0) {\r\n" + // + " v_texcoord = vec2(right, top);\r\n" + // + " position = a_p0;\r\n" + // + " } else if (a_position == 1.0) {\r\n" + // + " v_texcoord = vec2(right, bottom);\r\n" + // + " position = a_p1;\r\n" + // + " } else if (a_position == 2.0) {\r\n" + // + " v_texcoord = vec2(left, bottom);\r\n" + // + " position = a_p2;\r\n" + // + " } else if (a_position == 3.0) {\r\n" + // + " v_texcoord = vec2(left, top);\r\n" + // + " position = a_p3;\r\n" + // + " }\r\n" + // + " v_texcoord[0] /= u_columns;\r\n" + // + " v_texcoord[1] /= u_rows;\r\n" + // + " v_color = a_color;\r\n" + // + " gl_Position = u_mvp * vec4(position, 1.0);\r\n" + // + " }\r\n" + // + " void splat() {\r\n" + // + " float factor = u_lifeSpan - a_health;\r\n" + // + " int index;\r\n" + // + " if (factor < u_intervalTimes[0]) {\r\n" + // + " factor = factor / u_intervalTimes[0];\r\n" + // + " index = 0;\r\n" + // + " } else {\r\n" + // + " factor = (factor - u_intervalTimes[0]) / u_intervalTimes[1];\r\n" + // + " index = 1;\r\n" + // + " }\r\n" + // + " float cell = getCell(u_intervals[index], factor);\r\n" + // + " float left = floor(mod(cell, u_columns));\r\n" + // + " float top = floor(cell / u_columns);\r\n" + // + " float right = left + 1.0;\r\n" + // + " float bottom = top + 1.0;\r\n" + // + " vec3 position;\r\n" + // + " if (a_position == 0.0) {\r\n" + // + " v_texcoord = vec2(left, top);\r\n" + // + " position = a_p0;\r\n" + // + " } else if (a_position == 1.0) {\r\n" + // + " v_texcoord = vec2(left, bottom);\r\n" + // + " position = a_p1;\r\n" + // + " } else if (a_position == 2.0) {\r\n" + // + " v_texcoord = vec2(right, bottom);\r\n" + // + " position = a_p2;\r\n" + // + " } else if (a_position == 3.0) {\r\n" + // + " v_texcoord = vec2(right, top);\r\n" + // + " position = a_p3;\r\n" + // + " }\r\n" + // + " v_texcoord[0] /= u_columns;\r\n" + // + " v_texcoord[1] /= u_rows;\r\n" + // + " v_color = mix(u_colors[index], u_colors[index + 1], factor) / 255.0;\r\n" + // + " gl_Position = u_mvp * vec4(position, 1.0);\r\n" + // + " }\r\n" + // + " void ubersplat() {\r\n" + // + " float factor = u_lifeSpan - a_health;\r\n" + // + " vec4 color;\r\n" + // + " if (factor < u_intervalTimes[0]) {\r\n" + // + " color = mix(u_colors[0], u_colors[1], factor / u_intervalTimes[0]);\r\n" + // + " } else if (factor < u_intervalTimes[0] + u_intervalTimes[1]) {\r\n" + // + " color = u_colors[1];\r\n" + // + " } else {\r\n" + // + " color = mix(u_colors[1], u_colors[2], (factor - u_intervalTimes[0] - u_intervalTimes[1]) / u_intervalTimes[2]);\r\n" + + // + " }\r\n" + // + " vec3 position;\r\n" + // + " if (a_position == 0.0) {\r\n" + // + " v_texcoord = vec2(0.0, 0.0);\r\n" + // + " position = a_p0;\r\n" + // + " } else if (a_position == 1.0) {\r\n" + // + " v_texcoord = vec2(0.0, 1.0);\r\n" + // + " position = a_p1;\r\n" + // + " } else if (a_position == 2.0) {\r\n" + // + " v_texcoord = vec2(1.0, 1.0);\r\n" + // + " position = a_p2;\r\n" + // + " } else if (a_position == 3.0) {\r\n" + // + " v_texcoord = vec2(1.0, 0.0);\r\n" + // + " position = a_p3;\r\n" + // + " }\r\n" + // + " v_color = color / 255.0;\r\n" + // + " gl_Position = u_mvp * vec4(position, 1.0);\r\n" + // + " }\r\n" + // + " void main() {\r\n" + // + " if (u_emitter == EMITTER_PARTICLE2) {\r\n" + // + " particle2();\r\n" + // + " } else if (u_emitter == EMITTER_RIBBON) {\r\n" + // + " ribbon();\r\n" + // + " } else if (u_emitter == EMITTER_SPLAT) {\r\n" + // + " splat();\r\n" + // + " } else if (u_emitter == EMITTER_UBERSPLAT) {\r\n" + // + " ubersplat();\r\n" + // + " }\r\n" + // + " }"; + + public static final String fsParticles = "\r\n" + // + " #define EMITTER_RIBBON 1.0\r\n" + // + " uniform sampler2D u_texture;\r\n" + // + " uniform mediump float u_emitter;\r\n" + // + " uniform float u_filterMode;\r\n" + // + " varying vec2 v_texcoord;\r\n" + // + " varying vec4 v_color;\r\n" + // + " void main() {\r\n" + // + " vec4 texel = texture2D(u_texture, v_texcoord);\r\n" + // + " vec4 color = texel * v_color;\r\n" + // + " // 1bit Alpha, used by ribbon emitters.\r\n" + // + " if (u_emitter == EMITTER_RIBBON && u_filterMode == 1.0 && color.a < 0.75) {\r\n" + // + " discard;\r\n" + // + " }\r\n" + // + " gl_FragColor = color;\r\n" + // + " }"; +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxSimpleInstance.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxSimpleInstance.java new file mode 100644 index 0000000..b110969 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxSimpleInstance.java @@ -0,0 +1,37 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +import com.etheller.warsmash.viewer5.Model; +import com.etheller.warsmash.viewer5.ModelInstance; + +public class MdxSimpleInstance extends ModelInstance { + + public MdxSimpleInstance(final Model model) { + super(model); + } + + @Override + public boolean isBatched() { + return true; + } + + @Override + public void updateAnimations(final float dt) { + } + + @Override + public void clearEmittedObjects() { + } + + @Override + public void renderOpaque() { + } + + @Override + public void renderTranslucent() { + } + + @Override + public void load() { + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Model.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Model.java deleted file mode 100644 index a695149..0000000 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Model.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.etheller.warsmash.viewer5.handlers.mdx; - -import java.io.InputStream; - -public class Model extends com.etheller.warsmash.viewer5.Model { - - @Override - protected void lateLoad() { - // TODO Auto-generated method stub - - } - - @Override - protected void load(final InputStream src, final Object options) { - // TODO Auto-generated method stub - - } - - @Override - protected void error(final Exception e) { - // TODO Auto-generated method stub - - } - -} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Particle2.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Particle2.java new file mode 100644 index 0000000..55eb990 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Particle2.java @@ -0,0 +1,111 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +import com.badlogic.gdx.math.Quaternion; +import com.badlogic.gdx.math.Vector3; +import com.etheller.warsmash.util.RenderMathUtils; +import com.etheller.warsmash.viewer5.EmittedObject; + +public class Particle2 extends EmittedObject { + private static final Quaternion HALF_PI_Z = new Quaternion().setFromAxisRad(0, 0, 1, (float) (Math.PI / 2)); + public int tail = 0; + private float gravity = 0; + public final Vector3 location = new Vector3(); + public final Vector3 velocity = new Vector3(); + public final Vector3 scale = new Vector3(); + private final ParticleEmitter2 emitter2; + + private static final Quaternion rotationHeap = new Quaternion(); + private static final Quaternion rotationHeap2 = new Quaternion(); + private static final float[] widthHeap = new float[1]; + private static final float[] lengthHeap = new float[1]; + private static final float[] latitudeHeap = new float[1]; + private static final float[] variationHeap = new float[1]; + private static final float[] speedHeap = new float[1]; + private static final float[] gravityHeap = new float[1]; + + public Particle2(final ParticleEmitter2 emitter) { + this.emitter2 = emitter; + } + + @Override + protected void bind(final int flags) { + final MdxComplexInstance instance = this.emitter.instance; + final ParticleEmitter2Object emitterObject = this.emitter.emitterObject; + + emitterObject.getWidth(widthHeap, instance); + emitterObject.getLength(lengthHeap, instance); + emitterObject.getLatitude(latitudeHeap, instance); + emitterObject.getVariation(variationHeap, instance); + emitterObject.getSpeed(speedHeap, instance); + emitterObject.getGravity(gravityHeap, instance); + + final MdxNode node = this.emitter.node; + final Vector3 pivot = node.pivot; + final Vector3 scale = node.worldScale; + final float width = widthHeap[0] * 0.5f; + final float length = lengthHeap[0] * 0.5f; + final float latitude = (float) Math.toRadians(latitudeHeap[0]); + final float variation = variationHeap[0]; + final float speed = speedHeap[0]; + final Vector3 location = this.location; + final Vector3 velocity = this.velocity; + + this.health = emitterObject.lifeSpan; + this.tail = flags; + this.gravity = gravityHeap[0] * scale.z; + + this.scale.set(scale); + + // Local location + location.x = pivot.x + RenderMathUtils.randomInRange(-width, width); + location.y = pivot.y + RenderMathUtils.randomInRange(-length, length); + location.z = pivot.z; + + // World location + if (emitterObject.modelSpace == 0) { + location.prj(node.worldMatrix); + } + + // Local rotation + rotationHeap.idt(); + rotationHeap.mulLeft(HALF_PI_Z); + rotationHeap.mulLeft(rotationHeap2.setFromAxisRad(0, 1, 0, RenderMathUtils.randomInRange(-latitude, latitude))); + + // If this is not a line emitter, emit in a sphere rather than a circle + if (emitterObject.lineEmitter == 0) { + rotationHeap + .mulLeft(rotationHeap2.setFromAxisRad(1, 0, 0, RenderMathUtils.randomInRange(-latitude, latitude))); + } + + // World rotation + if (emitterObject.modelSpace == 0) { + rotationHeap.mulLeft(node.worldRotation); + } + + // Apply the rotation + velocity.set(RenderMathUtils.VEC3_UNIT_Z); + rotationHeap.transform(velocity); + + // Apply speed + velocity.scl(speed + RenderMathUtils.randomInRange(-variation, variation)); + + // Apply the parent's scale + if (emitterObject.modelSpace == 0) { + velocity.scl(scale); + } + } + + @Override + public void update(final float dt) { + this.health -= dt; + + if (this.health > 0) { + this.velocity.z -= this.gravity * dt; + + this.location.x += this.velocity.x * dt; + this.location.y += this.velocity.y * dt; + this.location.z += this.velocity.z * dt; + } + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ParticleEmitter2.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ParticleEmitter2.java new file mode 100644 index 0000000..2c2427d --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ParticleEmitter2.java @@ -0,0 +1,54 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +public class ParticleEmitter2 extends MdxEmitter { + private static final float[] emissionRateHeap = new float[1]; + + protected final MdxNode node; + private int lastEmissionKey; + + public ParticleEmitter2(final MdxComplexInstance instance, final ParticleEmitter2Object emitterObject) { + super(instance, emitterObject); + + this.node = instance.nodes[emitterObject.index]; + this.lastEmissionKey = -1; + } + + @Override + protected void updateEmission(final float dt) { + final MdxComplexInstance instance = this.instance; + + if (instance.allowParticleSpawn) { + final ParticleEmitter2Object emitterObject = this.emitterObject; + final int keyframe = emitterObject.getEmissionRate(emissionRateHeap, instance); + + if (emitterObject.squirt != 0) { + if (keyframe != this.lastEmissionKey) { + this.currentEmission += emissionRateHeap[0]; + } + + this.lastEmissionKey = keyframe; + } + else { + this.currentEmission += emissionRateHeap[0] * dt; + } + } + } + + @Override + protected void emit() { + if (this.emitterObject.head) { + this.emitObject(0); + } + + if (this.emitterObject.tail) { + this.emitObject(1); + } + + } + + @Override + protected Particle2 createObject() { + return new Particle2(this); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ParticleEmitter2Object.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ParticleEmitter2Object.java new file mode 100644 index 0000000..84bd9e0 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ParticleEmitter2Object.java @@ -0,0 +1,151 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +import com.etheller.warsmash.parsers.mdlx.AnimationMap; +import com.etheller.warsmash.viewer5.Texture; +import com.etheller.warsmash.viewer5.handlers.EmitterObject; + +public class ParticleEmitter2Object extends GenericObject implements EmitterObject { + public float width; + public float length; + public float speed; + public float latitude; + public float gravity; + public float emissionRate; + public long squirt; + public float lifeSpan; + public float variation; + public float tailLength; + public float timeMiddle; + public long columns; + public long rows; + public int teamColored = 0; + public Texture internalTexture; + public long replaceableId; + public boolean head; + public boolean tail; + public float cellWidth; + public float cellHeight; + public float[][] colors; + public float[] scaling; + public float[][] intervals; + public int blendSrc; + public int blendDst; + public int priorityPlane; + + public ParticleEmitter2Object(final MdxModel model, + final com.etheller.warsmash.parsers.mdlx.ParticleEmitter2 emitter, final int index) { + super(model, emitter, index); + + this.width = emitter.getWidth(); + this.length = emitter.getLength(); + this.speed = emitter.getSpeed(); + this.latitude = emitter.getLatitude(); + this.gravity = emitter.getGravity(); + this.emissionRate = emitter.getEmissionRate(); + this.squirt = emitter.getSquirt(); + this.lifeSpan = emitter.getLifeSpan(); + this.variation = emitter.getVariation(); + this.tailLength = emitter.getTailLength(); + this.timeMiddle = emitter.getTimeMiddle(); + + final long replaceableId = emitter.getReplaceableId(); + + this.columns = emitter.getColumns(); + this.rows = emitter.getRows(); + + if (this.replaceableId == 0) { + this.internalTexture = model.getTextures().get(emitter.getTextureId()); + } + else if ((replaceableId == 1) || (replaceableId == 2)) { + this.teamColored = 1; + } + else { + this.internalTexture = (Texture) model.viewer.load( + "ReplaceableTextures\\" + ReplaceableIds.get(replaceableId) + ".blp", model.pathSolver, + model.solverParams); + } + + this.replaceableId = emitter.getReplaceableId(); + + final long headOrTail = emitter.getHeadOrTail(); + + this.head = ((headOrTail == 0) || (headOrTail == 2)); + this.tail = ((headOrTail == 1) || (headOrTail == 2)); + + this.cellWidth = 1f / emitter.getColumns(); + this.cellHeight = 1f / emitter.getRows(); + this.colors = new float[3][0]; + + final float[][] colors = emitter.getSegmentColors(); + final short[] alpha = emitter.getSegmentAlphas(); + + for (int i = 0; i < 3; i++) { + final float[] color = colors[i]; + + this.colors[i] = new float[] { color[0], color[1], color[2], alpha[i] / 255f }; + } + + this.scaling = emitter.getSegmentScaling(); + + final long[][] headIntervals = emitter.getHeadIntervals(); + final long[][] tailIntervals = emitter.getTailIntervals(); + + // Change to Float32Array instead of Uint32Array to be able to pass the + // intervals directly using uniform3fv(). + this.intervals = new float[][] { { headIntervals[0][0], headIntervals[0][1], headIntervals[0][2] }, + { headIntervals[1][0], headIntervals[1][1], headIntervals[1][2] }, + { tailIntervals[0][0], tailIntervals[0][1], tailIntervals[0][2] }, + { tailIntervals[1][0], tailIntervals[1][1], tailIntervals[1][2] }, }; + + final int[] blendModes = FilterMode.emitterFilterMode(emitter.getFilterMode()); + + this.blendSrc = blendModes[0]; + this.blendDst = blendModes[1]; + + this.priorityPlane = emitter.getPriorityPlane(); + } + + public int getWidth(final float[] out, final MdxComplexInstance instance) { + return this.getScalarValue(out, AnimationMap.KP2N.getWar3id(), instance, this.width); + } + + public int getLength(final float[] out, final MdxComplexInstance instance) { + return this.getScalarValue(out, AnimationMap.KP2W.getWar3id(), instance, this.length); + } + + public int getSpeed(final float[] out, final MdxComplexInstance instance) { + return this.getScalarValue(out, AnimationMap.KP2S.getWar3id(), instance, this.speed); + } + + public int getLatitude(final float[] out, final MdxComplexInstance instance) { + return this.getScalarValue(out, AnimationMap.KP2L.getWar3id(), instance, this.latitude); + } + + public int getGravity(final float[] out, final MdxComplexInstance instance) { + return this.getScalarValue(out, AnimationMap.KP2G.getWar3id(), instance, this.gravity); + } + + public int getEmissionRate(final float[] out, final MdxComplexInstance instance) { + return this.getScalarValue(out, AnimationMap.KP2E.getWar3id(), instance, this.emissionRate); + } + + @Override + public int getVisibility(final float[] out, final MdxComplexInstance instance) { + return this.getScalarValue(out, AnimationMap.KP2V.getWar3id(), instance, 1); + } + + public int getVariation(final float[] out, final MdxComplexInstance instance) { + return this.getScalarValue(out, AnimationMap.KP2R.getWar3id(), instance, this.variation); + } + + @Override + public boolean ok() { + return true; + } + + @Override + public int getGeometryEmitterType() { + return GeometryEmitterFuncs.EMITTER_PARTICLE2; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/QuaternionSd.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/QuaternionSd.java new file mode 100644 index 0000000..f239b87 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/QuaternionSd.java @@ -0,0 +1,29 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +import com.etheller.warsmash.parsers.mdlx.timeline.Timeline; +import com.etheller.warsmash.util.Interpolator; + +public class QuaternionSd extends Sd { + + public QuaternionSd(final MdxModel model, final Timeline timeline) { + super(model, timeline); + } + + @Override + protected float[] convertDefaultValue(final float[] defaultValue) { + return defaultValue; + } + + @Override + protected void copy(final float[] out, final float[] value) { + System.arraycopy(value, 0, out, 0, value.length); + } + + @Override + protected void interpolate(final float[] out, final float[][] values, final float[][] inTans, + final float[][] outTans, final int start, final int end, final float t) { + Interpolator.interpolateQuaternion(out, values[start], outTans[start], inTans[end], values[end], t, + this.interpolationType); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ReplaceableIds.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ReplaceableIds.java new file mode 100644 index 0000000..860a3bd --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ReplaceableIds.java @@ -0,0 +1,22 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +import java.util.HashMap; +import java.util.Map; + +public class ReplaceableIds { + private static final Map ID_TO_STR = new HashMap<>(); + + static { + for (int i = 0; i < 28; i++) { + ID_TO_STR.put(Long.valueOf(i), String.format("%2d", i).replace(' ', '0')); + } + } + + public static void main(final String[] args) { + System.out.println(ID_TO_STR); + } + + public static String get(final long replaceableId) { + return ID_TO_STR.get(replaceableId); + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Ribbon.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Ribbon.java new file mode 100644 index 0000000..f397ba1 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Ribbon.java @@ -0,0 +1,89 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +import com.badlogic.gdx.math.Matrix4; +import com.badlogic.gdx.math.Vector3; +import com.etheller.warsmash.viewer5.EmittedObject; + +public class Ribbon extends EmittedObject { + private static final float[] vectorHeap = new float[3]; + private static final Vector3 belowHeap = new Vector3(); + private static final Vector3 aboveHeap = new Vector3(); + private static final float[] colorHeap = new float[3]; + private static final float[] alphaHeap = new float[1]; + private static final long[] slotHeap = new long[1]; + + public float[] vertices = new float[6]; + public byte[] color = new byte[4]; + public int slot; + public Ribbon prev; + public Ribbon next; + + @Override + protected void bind(final int flags) { + final RibbonEmitter emitter = this.emitter; + final MdxComplexInstance instance = emitter.instance; + final RibbonEmitterObject emitterObject = emitter.emitterObject; + final MdxNode node = instance.nodes[emitterObject.index]; + final Vector3 pivot = node.pivot; + final float x = pivot.x, y = pivot.y, z = pivot.z; + final Matrix4 worldMatrix = node.worldMatrix; + final float[] vertices = this.vertices; + + this.health = emitter.emitterObject.lifeSpan; + + emitterObject.getHeightBelow(vectorHeap, instance); + belowHeap.set(vectorHeap); + emitterObject.getHeightAbove(vectorHeap, instance); + aboveHeap.set(vectorHeap); + + belowHeap.y = y - belowHeap.x; + belowHeap.x = x; + belowHeap.z = z; + + aboveHeap.y = y + aboveHeap.x; + aboveHeap.x = x; + aboveHeap.z = z; + + belowHeap.prj(worldMatrix); + aboveHeap.prj(worldMatrix); + + vertices[0] = aboveHeap.x; + vertices[1] = aboveHeap.y; + vertices[2] = aboveHeap.z; + vertices[3] = belowHeap.x; + vertices[4] = belowHeap.y; + vertices[5] = belowHeap.z; + } + + public Ribbon(final RibbonEmitter emitter) { + } + + @Override + public void update(final float dt) { + this.health -= dt; + + if (this.health > 0) { + final RibbonEmitter emitter = this.emitter; + final MdxComplexInstance instance = emitter.instance; + final RibbonEmitterObject emitterObject = emitter.emitterObject; + final byte[] color = this.color; + final float[] vertices = this.vertices; + final float gravity = emitterObject.gravity * dt * dt; + + emitterObject.getColor(colorHeap, instance); + emitterObject.getAlpha(alphaHeap, instance); + emitterObject.getTextureSlot(slotHeap, instance); + + vertices[1] -= gravity; + vertices[4] -= gravity; + + color[0] = (byte) (colorHeap[0] * 255); + color[1] = (byte) (colorHeap[1] * 255); + color[2] = (byte) (colorHeap[2] * 255); + color[3] = (byte) (alphaHeap[0] * 255); + + this.slot = (int) slotHeap[0]; + } + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/RibbonEmitter.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/RibbonEmitter.java new file mode 100644 index 0000000..e815f21 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/RibbonEmitter.java @@ -0,0 +1,73 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +public class RibbonEmitter extends MdxEmitter { + public Ribbon first; + public Ribbon last; + + public RibbonEmitter(final MdxComplexInstance instance, final RibbonEmitterObject emitterObject) { + super(instance, emitterObject); + } + + @Override + protected void updateEmission(final float dt) { + final MdxComplexInstance instance = this.instance; + + if (instance.allowParticleSpawn) { + final RibbonEmitterObject emitterObject = this.emitterObject; + + // It doesn't make sense to emit more than 1 ribbon at the same time. + this.currentEmission = Math.min(this.currentEmission + (emitterObject.emissionRate * dt), 1); + } + + } + + @Override + protected void emit() { + final Ribbon ribbon = this.emitObject(0); + final Ribbon last = this.last; + + if (last != null) { + last.next = ribbon; + ribbon.prev = last; + } + else { + this.first = ribbon; + } + + this.last = ribbon; + } + + @Override + public void kill(final Ribbon object) { + super.kill(object); + + final Ribbon prev = object.prev; + final Ribbon next = object.next; + + if (object == this.first) { + this.first = next; + } + + if (object == this.last) { + this.first = null; + this.last = null; + } + + if (prev != null) { + prev.next = next; + } + + if (next != null) { + next.prev = prev; + } + + object.prev = null; + object.next = null; + } + + @Override + protected Ribbon createObject() { + return new Ribbon(this); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/RibbonEmitterObject.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/RibbonEmitterObject.java new file mode 100644 index 0000000..41bcca8 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/RibbonEmitterObject.java @@ -0,0 +1,77 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +import com.etheller.warsmash.parsers.mdlx.AnimationMap; +import com.etheller.warsmash.viewer5.handlers.EmitterObject; + +public class RibbonEmitterObject extends GenericObject implements EmitterObject { + public Layer layer; + public float heightAbove; + public float heightBelow; + public float alpha; + public float[] color; + public float lifeSpan; + public long textureSlot; + public long emissionRate; + public float gravity; + public long columns; + public long rows; + /** + * Even if the internal texture isn't loaded, it's fine to run emitters based on + * this emitter object. + * + * The ribbons will simply be black. + */ + public boolean ok = true; + + public RibbonEmitterObject(final MdxModel model, final com.etheller.warsmash.parsers.mdlx.RibbonEmitter emitter, + final int index) { + super(model, emitter, index); + + this.layer = model.getMaterials().get(emitter.getMaterialId()).layers.get(0); + this.heightAbove = emitter.getHeightAbove(); + this.heightBelow = emitter.getHeightBelow(); + this.alpha = emitter.getAlpha(); + this.color = emitter.getColor(); + this.lifeSpan = emitter.getLifeSpan(); + this.textureSlot = emitter.getTextureSlot(); + this.emissionRate = emitter.getEmissionRate(); + this.gravity = emitter.getGravity(); + this.columns = emitter.getColumns(); + this.rows = emitter.getRows(); + } + + public int getHeightBelow(final float[] out, final MdxComplexInstance instance) { + return this.getScalarValue(out, AnimationMap.KRHB.getWar3id(), instance, this.heightBelow); + } + + public int getHeightAbove(final float[] out, final MdxComplexInstance instance) { + return this.getScalarValue(out, AnimationMap.KRHA.getWar3id(), instance, this.heightAbove); + } + + public int getTextureSlot(final long[] out, final MdxComplexInstance instance) { + return this.getScalarValue(out, AnimationMap.KRTX.getWar3id(), instance, 0); + } + + public int getColor(final float[] out, final MdxComplexInstance instance) { + return this.getVectorValue(out, AnimationMap.KRCO.getWar3id(), instance, this.color); + } + + public int getAlpha(final float[] out, final MdxComplexInstance instance) { + return this.getScalarValue(out, AnimationMap.KRAL.getWar3id(), instance, this.alpha); + } + + @Override + public int getVisibility(final float[] out, final MdxComplexInstance instance) { + return this.getScalarValue(out, AnimationMap.KRVS.getWar3id(), instance, 1f); + } + + @Override + public int getGeometryEmitterType() { + return GeometryEmitterFuncs.EMITTER_RIBBON; + } + + @Override + public boolean ok() { + return this.ok; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ScalarSd.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ScalarSd.java new file mode 100644 index 0000000..161497c --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ScalarSd.java @@ -0,0 +1,44 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +import com.etheller.warsmash.parsers.mdlx.timeline.Timeline; +import com.etheller.warsmash.util.RenderMathUtils; + +public class ScalarSd extends Sd { + + public ScalarSd(final MdxModel model, final Timeline timeline) { + super(model, timeline); + } + + @Override + protected float[] convertDefaultValue(final float[] defaultValue) { + return defaultValue; + } + + @Override + protected void copy(final float[] out, final float[] value) { + out[0] = value[0]; + } + + @Override + protected void interpolate(final float[] out, final float[][] values, final float[][] inTans, + final float[][] outTans, final int start, final int end, final float t) { + final float startValue = values[start][0]; + + switch (this.interpolationType) { + case 0: + out[0] = startValue; + break; + case 1: + out[0] = RenderMathUtils.lerp(startValue, values[end][0], t); + break; + case 2: + out[0] = RenderMathUtils.hermite(startValue, outTans[start][0], inTans[end][0], values[end][0], t); + break; + case 3: + out[0] = RenderMathUtils.bezier(startValue, outTans[start][0], inTans[end][0], values[end][0], t); + break; + } + + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Sd.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Sd.java index 6e5843b..7688267 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Sd.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Sd.java @@ -5,18 +5,15 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import com.etheller.warsmash.parsers.mdlx.MdlxModel; import com.etheller.warsmash.parsers.mdlx.Sequence; -import com.etheller.warsmash.parsers.mdlx.timeline.KeyFrame; import com.etheller.warsmash.parsers.mdlx.timeline.Timeline; import com.etheller.warsmash.util.War3ID; -import com.etheller.warsmash.viewer5.ModelInstance; public abstract class Sd { - public MdlxModel model; + public MdxModel model; public int interpolationType; public War3ID name; - public float[] defval; + public TYPE defval; public SdSequence globalSequence; public List> sequences; @@ -88,15 +85,14 @@ public abstract class Sd { } - public Sd(final MdlxModel model, final Timeline timeline) { + public Sd(final MdxModel model, final Timeline timeline) { final List globalSequences = model.getGlobalSequences(); final int globalSequenceId = timeline.getGlobalSequenceId(); - final List keyFrames = timeline.getKeyFrames(); final Integer forcedInterp = forcedInterpMap.get(timeline.getName()); this.model = model; this.name = timeline.getName(); - this.defval = defVals.get(timeline.getName()); + this.defval = convertDefaultValue(defVals.get(timeline.getName())); this.globalSequence = null; this.sequences = new ArrayList<>(); @@ -107,24 +103,46 @@ public abstract class Sd { // type. this.interpolationType = forcedInterp != null ? forcedInterp : timeline.getInterpolationType().ordinal(); - if (globalSequenceId != -1 && globalSequences.size() > 0) { - this.globalSequence = newSequenceTyped(this, 0, globalSequences.get(globalSequenceId).longValue(), - keyFrames, true); + if ((globalSequenceId != -1) && (globalSequences.size() > 0)) { + this.globalSequence = new SdSequence(this, 0, globalSequences.get(globalSequenceId).longValue(), + timeline, true); } else { for (final Sequence sequence : model.getSequences()) { final long[] interval = sequence.getInterval(); - this.sequences.add(newSequenceTyped(this, interval[0], interval[1], keyFrames, false)); + + this.sequences.add(new SdSequence(this, interval[0], interval[1], timeline, false)); } } } - public int getValue(final TYPE out, final ModelInstance instance) { - if(this.globalSequence != null) { - return this.globalSequence.getValue(out, instance.cout) + public int getValue(final TYPE out, final MdxComplexInstance instance) { + if (this.globalSequence != null) { + return this.globalSequence.getValue(out, instance.counter % this.globalSequence.end); + } + else if (instance.sequence != -1) { + return this.sequences.get(instance.sequence).getValue(out, instance.frame); + } + else { + this.copy(out, this.defval); + + return -1; } } - protected abstract SdSequence newSequenceTyped(final Sd parent, final long start, final long end, - final List keyframes, final boolean isGlobalSequence); + public boolean isVariant(final int sequence) { + if (this.globalSequence != null) { + return !this.globalSequence.constant; + } + else { + return !this.sequences.get(sequence).constant; + } + } + + protected abstract TYPE convertDefaultValue(float[] defaultValue); + + protected abstract void copy(TYPE out, TYPE value); + + protected abstract void interpolate(TYPE out, TYPE[] values, TYPE[] inTans, TYPE[] outTans, int start, int end, + float t); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SdSequence.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SdSequence.java index 3f2ee00..14b229b 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SdSequence.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SdSequence.java @@ -1,27 +1,39 @@ package com.etheller.warsmash.viewer5.handlers.mdx; import java.util.ArrayList; -import java.util.List; +import java.util.Arrays; -import com.etheller.warsmash.parsers.mdlx.timeline.KeyFrame; +import com.etheller.warsmash.parsers.mdlx.timeline.Timeline; import com.etheller.warsmash.util.RenderMathUtils; -public abstract class SdSequence { +public final class SdSequence { private final Sd sd; - private final long start; // UInt32 - private final long end; // UInt32 - private final List keyframes; - private boolean constant; + public final long start; // UInt32 + public final long end; // UInt32 + public long[] frames; + public TYPE[] values; + public TYPE[] inTans; + public TYPE[] outTans; + public boolean constant; - public SdSequence(final Sd sd, final long start, final long end, final List keyframes, + public SdSequence(final Sd sd, final long start, final long end, final Timeline timeline, final boolean isGlobalSequence) { - final TYPE defval = convertDefaultValue(sd.defval); - this.sd = sd; this.start = start; this.end = end; - this.keyframes = new ArrayList<>(); + final ArrayList framesBuilder = new ArrayList<>(); + final ArrayList valuesBuilder = new ArrayList<>(); + final ArrayList inTansBuilder = new ArrayList<>(); + final ArrayList outTansBuilder = new ArrayList<>(); + this.constant = false; + + final int interpolationType = sd.interpolationType; + final long[] frames = timeline.getFrames(); + final TYPE[] values = timeline.getValues(); + final TYPE[] inTans = timeline.getInTans(); + final TYPE[] outTans = timeline.getOutTans(); + final TYPE defval = sd.defval; // When using a global sequence, where the first key is outside of the // sequence's length, it becomes its constant value. @@ -34,40 +46,47 @@ public abstract class SdSequence { // Therefore, only handle the case where the first key is outside. // This fixes problems spread over many models, e.g. HeroMountainKing // (compare in WE and in Magos). - if (isGlobalSequence && keyframes.size() > 0 && keyframes.get(0).getTime() > end) { - this.keyframes.add(keyframes.get(0)); + if (isGlobalSequence && (frames.length > 0) && (frames[0] > end)) { + this.frames[0] = frames[0]; + this.values[0] = values[0]; } // Go over the keyframes, and add all of the ones that are in this // sequence (start <= frame <= end). - for (int i = 0, l = keyframes.size(); i < l; i++) { - final KeyFrame keyFrame = keyframes.get(i); - final long frame = keyFrame.getTime(); + for (int i = 0, l = frames.length; i < l; i++) { + final long frame = frames[i]; - if (frame >= start && frame <= end) { - this.keyframes.add(keyFrame); + if ((frame >= start) && (frame <= end)) { + framesBuilder.add(frame); + valuesBuilder.add(values[i]); + + if (interpolationType > 1) { + inTansBuilder.add(inTans[i]); + outTansBuilder.add(outTans[i]); + } } } - final int keyframeCount = this.keyframes.size(); + final int keyframeCount = framesBuilder.size(); if (keyframeCount == 0) { // if there are no keys, use the default value directly. this.constant = true; - this.keyframes.add(createKeyFrame(start, defval)); + framesBuilder.add(start); + valuesBuilder.add(defval); } else if (keyframeCount == 1) { // If there's only one key, use it directly this.constant = true; } else { - final KeyFrame firstFrame = this.keyframes.get(0); + final TYPE firstValue = valuesBuilder.get(0); // If all of the values in this sequence are the same, might as well // make it constant. boolean allFramesMatch = true; - for (final KeyFrame frame : this.keyframes) { - if (!frame.matchingValue(firstFrame)) { + for (final TYPE value : valuesBuilder) { + if (!equals(firstValue, value)) { allFramesMatch = false; } } @@ -76,75 +95,78 @@ public abstract class SdSequence { if (!this.constant) { // If there is no opening keyframe for this sequence, inject one // with the default value. - if (this.keyframes.get(0).getTime() != start) { - this.keyframes.add(0, createKeyFrame(start, defval)); + if (framesBuilder.get(0) != start) { + framesBuilder.add(start); + valuesBuilder.add(defval); + + if (interpolationType > 1) { + inTansBuilder.add(defval); + outTansBuilder.add(defval); + } } // If there is no closing keyframe for this sequence, inject one // with the default value. - if (this.keyframes.get(this.keyframes.size() - 1).getTime() != end) { - this.keyframes.add(this.keyframes.get(0).clone(end)); - } - } - } - } + if (framesBuilder.get(framesBuilder.size() - 1) != end) { + framesBuilder.add(end); + valuesBuilder.add(valuesBuilder.get(0)); - public int getValue(final TYPE out, final long frame) { - final int index = this.getKeyframe(frame); - final int size = keyframes.size(); - - if (index == -1) { - set(out, keyframes.get(0)); - - return 0; - } - else if (index == size) { - set(out, keyframes.get(size - 1)); - - return size - 1; - } - else { - final KeyFrame start = keyframes.get(index - 1); - final KeyFrame end = keyframes.get(index); - final float t = RenderMathUtils.clamp((frame - start.getTime()) / (end.getTime() - start.getTime()), 0, 1); - - interpolate(out, start, end, t); - - return index; - } - } - - public int getKeyframe(final long frame) { - if (this.constant) { - return -1; - } - else { - final int l = keyframes.size(); - - if (frame < this.start) { - return -1; - } - else if (frame >= this.end) { - return 1; - } - else { - for (int i = 1; i < l; i++) { - final KeyFrame keyframe = keyframes.get(i); - - if (keyframe.getTime() > frame) { - return i; + if (interpolationType > 1) { + inTansBuilder.add(inTansBuilder.get(0)); + outTansBuilder.add(outTansBuilder.get(0)); } } } } - return -1; + this.frames = new long[framesBuilder.size()]; + for (int i = 0; i < framesBuilder.size(); i++) { + frames[i] = framesBuilder.get(i); + } + this.values = valuesBuilder.toArray((TYPE[]) new Object[valuesBuilder.size()]); + this.inTans = inTansBuilder.toArray((TYPE[]) new Object[inTansBuilder.size()]); + this.outTans = outTansBuilder.toArray((TYPE[]) new Object[outTansBuilder.size()]); } - protected abstract void set(TYPE out, KeyFrame frameForValue); + public int getValue(final TYPE out, final long frame) { + final int l = this.frames.length; - protected abstract TYPE convertDefaultValue(float[] defaultValue); + if (this.constant || (frame < this.start)) { + this.sd.copy(out, this.values[0]); - protected abstract KeyFrame createKeyFrame(long time, TYPE value); + return -1; + } + else if (frame >= this.end) { + this.sd.copy(out, this.values[l - 1]); - protected abstract void interpolate(TYPE out, KeyFrame a, KeyFrame b, float t); + return l - 1; + } + else { + for (int i = 1; i < l; i++) { + if (this.frames[i] > frame) { + final long start = this.frames[i = 1]; + final long end = this.frames[i]; + final float t = RenderMathUtils.clamp((frame - start) / (end - start), 0, 1); + + this.sd.interpolate(out, this.values, this.inTans, this.outTans, i - 1, i, t); + + return i; + } + } + + return -1; + } + } + + protected final boolean equals(final TYPE a, final TYPE b) { + if ((a instanceof Float) && (b instanceof Float)) { + return a.equals(b); + } + else if ((a instanceof Long) && (b instanceof Long)) { + return a.equals(b); + } + else if ((a instanceof float[]) && (b instanceof float[])) { + return Arrays.equals(((float[]) a), (float[]) b); + } + return false; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/TextureAnimation.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/TextureAnimation.java new file mode 100644 index 0000000..aec7adf --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/TextureAnimation.java @@ -0,0 +1,37 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +import com.etheller.warsmash.parsers.mdlx.AnimationMap; +import com.etheller.warsmash.util.RenderMathUtils; + +public class TextureAnimation extends AnimatedObject { + + public TextureAnimation(final MdxModel model, + final com.etheller.warsmash.parsers.mdlx.TextureAnimation textureAnimation) { + super(model, textureAnimation); + } + + public int getTranslation(final float[] out, final MdxComplexInstance instance) { + return this.getVectorValue(out, AnimationMap.KTAT.getWar3id(), instance, RenderMathUtils.FLOAT_VEC3_ZERO); + } + + public int getRotation(final float[] out, final MdxComplexInstance instance) { + return this.getVectorValue(out, AnimationMap.KTAR.getWar3id(), instance, RenderMathUtils.FLOAT_QUAT_DEFAULT); + } + + public int getScale(final float[] out, final MdxComplexInstance instance) { + return this.getVectorValue(out, AnimationMap.KTAS.getWar3id(), instance, RenderMathUtils.FLOAT_VEC3_ONE); + } + + public boolean isTranslationVariant(final int sequence) { + return this.isVariant(AnimationMap.KTAT.getWar3id(), sequence); + } + + public boolean isRotationVariant(final int sequence) { + return this.isVariant(AnimationMap.KTAR.getWar3id(), sequence); + } + + public boolean isScaleVariant(final int sequence) { + return this.isVariant(AnimationMap.KTAS.getWar3id(), sequence); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/UInt32Sd.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/UInt32Sd.java new file mode 100644 index 0000000..ae358c6 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/UInt32Sd.java @@ -0,0 +1,48 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +import com.etheller.warsmash.parsers.mdlx.timeline.Timeline; +import com.etheller.warsmash.util.RenderMathUtils; + +public class UInt32Sd extends Sd { + + public UInt32Sd(final MdxModel model, final Timeline timeline) { + super(model, timeline); + } + + @Override + protected long[] convertDefaultValue(final float[] defaultValue) { + final long[] returnValue = new long[defaultValue.length]; + for (int i = 0; i < defaultValue.length; i++) { + returnValue[i] = (long) defaultValue[i]; + } + return returnValue; + } + + @Override + protected void copy(final long[] out, final long[] value) { + out[0] = value[0]; + } + + @Override + protected void interpolate(final long[] out, final long[][] values, final long[][] inTans, final long[][] outTans, + final int start, final int end, final float t) { + final long startValue = values[start][0]; + + switch (this.interpolationType) { + case 0: + out[0] = startValue; + break; + case 1: + out[0] = (long) RenderMathUtils.lerp(startValue, values[end][0], t); + break; + case 2: + out[0] = (long) RenderMathUtils.hermite(startValue, outTans[start][0], inTans[end][0], values[end][0], t); + break; + case 3: + out[0] = (long) RenderMathUtils.bezier(startValue, outTans[start][0], inTans[end][0], values[end][0], t); + break; + } + + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/VectorSd.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/VectorSd.java new file mode 100644 index 0000000..94c8917 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/VectorSd.java @@ -0,0 +1,29 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +import com.etheller.warsmash.parsers.mdlx.timeline.Timeline; +import com.etheller.warsmash.util.Interpolator; + +public class VectorSd extends Sd { + + public VectorSd(final MdxModel model, final Timeline timeline) { + super(model, timeline); + } + + @Override + protected float[] convertDefaultValue(final float[] defaultValue) { + return defaultValue; + } + + @Override + protected void copy(final float[] out, final float[] value) { + System.arraycopy(value, 0, out, 0, value.length); + } + + @Override + protected void interpolate(final float[] out, final float[][] values, final float[][] inTans, + final float[][] outTans, final int start, final int end, final float t) { + Interpolator.interpolateVector(out, values[start], outTans[start], inTans[end], values[end], t, + this.interpolationType); + } + +} From c132b0d9841b9cf5abdefd48d4e4f81af64fcdcb Mon Sep 17 00:00:00 2001 From: Retera Date: Mon, 13 Jan 2020 01:28:46 -0600 Subject: [PATCH 009/116] More work on handler code --- .../etheller/warsmash/WarsmashGdxGame.java | 10 +- .../warsmash/common/FetchDataTypeName.java | 13 + .../warsmash/common/LoadGenericCallback.java | 7 + .../datasources/CompoundDataSource.java | 153 +++++ .../warsmash/parsers/mdlx/Geoset.java | 57 ++ .../etheller/warsmash/parsers/mdlx/Light.java | 25 + .../parsers/mdlx/ParticleEmitter.java | 29 + .../etheller/warsmash/units/DataTable.java | 303 +++++++++ .../com/etheller/warsmash/units/Element.java | 217 +++++++ .../etheller/warsmash/units/GameObject.java | 30 + .../warsmash/units/HashedGameObject.java | 241 +++++++ .../com/etheller/warsmash/units/LMUnit.java | 12 + .../etheller/warsmash/units/ObjectData.java | 13 + .../warsmash/units/StandardObjectData.java | 610 ++++++++++++++++++ .../etheller/warsmash/units/StringKey.java | 54 ++ .../com/etheller/warsmash/util/IniFile.java | 84 +++ .../etheller/warsmash/util/MappedData.java | 92 +++ .../etheller/warsmash/util/MappedDataRow.java | 7 + .../com/etheller/warsmash/util/SlkFile.java | 70 ++ core/src/com/etheller/warsmash/util/Test.java | 20 + .../warsmash/util/WorldEditStrings.java | 71 ++ .../warsmash/viewer5/AudioBufferSource.java | 17 + .../warsmash/viewer5/AudioContext.java | 20 + .../warsmash/viewer5/AudioDestination.java | 5 + .../warsmash/viewer5/AudioPanner.java | 10 + .../warsmash/viewer5/EmittedObject.java | 4 + .../warsmash/viewer5/GenericResource.java | 35 + .../warsmash/viewer5/HandlerResource.java | 14 + .../com/etheller/warsmash/viewer5/Model.java | 4 +- .../warsmash/viewer5/ModelViewer.java | 73 ++- .../etheller/warsmash/viewer5/Resource.java | 10 +- .../com/etheller/warsmash/viewer5/Scene.java | 4 +- .../etheller/warsmash/viewer5/Texture.java | 4 +- .../warsmash/viewer5/gl/ClientBuffer.java | 54 ++ .../viewer5/handlers/ResourceHandler.java | 4 +- .../viewer5/handlers/blp/BlpHandler.java | 4 +- .../viewer5/handlers/mdx/AnimatedObject.java | 82 ++- .../viewer5/handlers/mdx/Attachment.java | 4 +- .../handlers/mdx/AttachmentInstance.java | 3 +- .../warsmash/viewer5/handlers/mdx/Batch.java | 7 +- .../viewer5/handlers/mdx/BatchGroup.java | 7 +- .../warsmash/viewer5/handlers/mdx/Bone.java | 4 +- .../warsmash/viewer5/handlers/mdx/Camera.java | 12 +- .../viewer5/handlers/mdx/EmitterGroup.java | 21 +- .../handlers/mdx/EventObjectEmitter.java | 15 +- .../mdx/EventObjectEmitterObject.java | 264 +++++++- .../viewer5/handlers/mdx/EventObjectSnd.java | 55 ++ .../handlers/mdx/EventObjectSndEmitter.java | 14 + .../handlers/mdx/EventObjectSplEmitter.java | 12 + .../handlers/mdx/EventObjectSplUbr.java | 55 +- .../viewer5/handlers/mdx/EventObjectSpn.java | 48 ++ .../handlers/mdx/EventObjectSpnEmitter.java | 14 + .../handlers/mdx/EventObjectUbrEmitter.java | 12 + .../viewer5/handlers/mdx/GenericGroup.java | 13 + .../viewer5/handlers/mdx/GenericIndexed.java | 5 + .../viewer5/handlers/mdx/GenericObject.java | 24 +- .../handlers/mdx/GeometryEmitterFuncs.java | 164 ++++- .../warsmash/viewer5/handlers/mdx/Geoset.java | 8 +- .../viewer5/handlers/mdx/GeosetAnimation.java | 8 +- .../warsmash/viewer5/handlers/mdx/Helper.java | 12 + .../warsmash/viewer5/handlers/mdx/Layer.java | 116 +++- .../warsmash/viewer5/handlers/mdx/Light.java | 48 ++ .../viewer5/handlers/mdx/MdxHandler.java | 4 +- .../viewer5/handlers/mdx/MdxModel.java | 44 +- .../viewer5/handlers/mdx/Particle.java | 103 +++ .../viewer5/handlers/mdx/Particle2.java | 15 +- .../viewer5/handlers/mdx/ParticleEmitter.java | 34 + .../handlers/mdx/ParticleEmitter2.java | 3 +- .../handlers/mdx/ParticleEmitter2Object.java | 32 +- .../handlers/mdx/ParticleEmitterObject.java | 83 +++ .../warsmash/viewer5/handlers/mdx/Ribbon.java | 17 +- .../handlers/mdx/RibbonEmitterObject.java | 24 +- .../warsmash/viewer5/handlers/mdx/Sd.java | 8 +- .../viewer5/handlers/mdx/SetupGeosets.java | 186 ++++++ .../viewer5/handlers/mdx/SetupGroups.java | 121 ++++ .../handlers/mdx/TextureAnimation.java | 15 +- .../com/hiveworkshop/wc3/mpq/Codebase.java | 12 - .../hiveworkshop/wc3/mpq/FileCodebase.java | 35 - 78 files changed, 3899 insertions(+), 244 deletions(-) create mode 100644 core/src/com/etheller/warsmash/common/FetchDataTypeName.java create mode 100644 core/src/com/etheller/warsmash/common/LoadGenericCallback.java create mode 100644 core/src/com/etheller/warsmash/datasources/CompoundDataSource.java create mode 100644 core/src/com/etheller/warsmash/units/DataTable.java create mode 100644 core/src/com/etheller/warsmash/units/Element.java create mode 100644 core/src/com/etheller/warsmash/units/GameObject.java create mode 100644 core/src/com/etheller/warsmash/units/HashedGameObject.java create mode 100644 core/src/com/etheller/warsmash/units/LMUnit.java create mode 100644 core/src/com/etheller/warsmash/units/ObjectData.java create mode 100644 core/src/com/etheller/warsmash/units/StandardObjectData.java create mode 100644 core/src/com/etheller/warsmash/units/StringKey.java create mode 100644 core/src/com/etheller/warsmash/util/IniFile.java create mode 100644 core/src/com/etheller/warsmash/util/MappedData.java create mode 100644 core/src/com/etheller/warsmash/util/MappedDataRow.java create mode 100644 core/src/com/etheller/warsmash/util/SlkFile.java create mode 100644 core/src/com/etheller/warsmash/util/Test.java create mode 100644 core/src/com/etheller/warsmash/util/WorldEditStrings.java create mode 100644 core/src/com/etheller/warsmash/viewer5/AudioBufferSource.java create mode 100644 core/src/com/etheller/warsmash/viewer5/AudioDestination.java create mode 100644 core/src/com/etheller/warsmash/viewer5/AudioPanner.java create mode 100644 core/src/com/etheller/warsmash/viewer5/GenericResource.java create mode 100644 core/src/com/etheller/warsmash/viewer5/HandlerResource.java create mode 100644 core/src/com/etheller/warsmash/viewer5/gl/ClientBuffer.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectSnd.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectSndEmitter.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectSplEmitter.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectSpn.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectSpnEmitter.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectUbrEmitter.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/GenericGroup.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/GenericIndexed.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/Helper.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/Light.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/Particle.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/ParticleEmitter.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/ParticleEmitterObject.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/SetupGeosets.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/SetupGroups.java delete mode 100644 core/src/com/hiveworkshop/wc3/mpq/Codebase.java delete mode 100644 core/src/com/hiveworkshop/wc3/mpq/FileCodebase.java diff --git a/core/src/com/etheller/warsmash/WarsmashGdxGame.java b/core/src/com/etheller/warsmash/WarsmashGdxGame.java index c3260a0..3f12574 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxGame.java @@ -1,8 +1,8 @@ package com.etheller.warsmash; import java.awt.image.BufferedImage; -import java.io.File; import java.io.IOException; +import java.nio.file.Paths; import javax.imageio.ImageIO; @@ -13,20 +13,20 @@ import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.Texture.TextureFilter; import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.etheller.warsmash.datasources.DataSource; +import com.etheller.warsmash.datasources.FolderDataSource; import com.etheller.warsmash.util.ImageUtils; import com.etheller.warsmash.util.War3ID; -import com.hiveworkshop.wc3.mpq.Codebase; -import com.hiveworkshop.wc3.mpq.FileCodebase; public class WarsmashGdxGame extends ApplicationAdapter { private SpriteBatch batch; private BitmapFont font; - private Codebase codebase; + private DataSource codebase; private Texture texture; @Override public void create() { - this.codebase = new FileCodebase(new File("C:/MPQBuild/War3.mpq/war3.mpq")); + this.codebase = new FolderDataSource(Paths.get("C:/MPQBuild/War3.mpq/war3.mpq")); final War3ID id = War3ID.fromString("ipea"); try { diff --git a/core/src/com/etheller/warsmash/common/FetchDataTypeName.java b/core/src/com/etheller/warsmash/common/FetchDataTypeName.java new file mode 100644 index 0000000..1d4b35b --- /dev/null +++ b/core/src/com/etheller/warsmash/common/FetchDataTypeName.java @@ -0,0 +1,13 @@ +package com.etheller.warsmash.common; + +/** + * These will probably change the further I get from the source material I am + * copying from. + */ +public enum FetchDataTypeName { + IMAGE, + TEXT, + SLK, + ARRAY_BUFFER, + BLOB; +} diff --git a/core/src/com/etheller/warsmash/common/LoadGenericCallback.java b/core/src/com/etheller/warsmash/common/LoadGenericCallback.java new file mode 100644 index 0000000..d7c4c29 --- /dev/null +++ b/core/src/com/etheller/warsmash/common/LoadGenericCallback.java @@ -0,0 +1,7 @@ +package com.etheller.warsmash.common; + +import java.io.InputStream; + +public interface LoadGenericCallback { + Object call(InputStream data); // TODO typing +} diff --git a/core/src/com/etheller/warsmash/datasources/CompoundDataSource.java b/core/src/com/etheller/warsmash/datasources/CompoundDataSource.java new file mode 100644 index 0000000..2c1a975 --- /dev/null +++ b/core/src/com/etheller/warsmash/datasources/CompoundDataSource.java @@ -0,0 +1,153 @@ +package com.etheller.warsmash.datasources; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +public class CompoundDataSource implements DataSource { + private final List mpqList = new ArrayList<>(); + + public CompoundDataSource(final List dataSourceDescriptors) { + if (dataSourceDescriptors != null) { + for (final DataSourceDescriptor descriptor : dataSourceDescriptors) { + this.mpqList.add(descriptor.createDataSource()); + } + } + } + + Map cache = new HashMap<>(); + + @Override + public File getFile(final String filepath) { + if (this.cache.containsKey(filepath)) { + return this.cache.get(filepath); + } + try { + for (int i = this.mpqList.size() - 1; i >= 0; i--) { + final DataSource mpq = this.mpqList.get(i); + final File tempProduct = mpq.getFile(filepath); + if (tempProduct != null) { + this.cache.put(filepath, tempProduct); + return tempProduct; + } + } + } + catch (final IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + if (filepath.toLowerCase(Locale.US).endsWith(".blp")) { + return getFile(filepath.substring(0, filepath.lastIndexOf(".")) + ".dds"); + } + if (filepath.toLowerCase(Locale.US).endsWith(".tif")) { + return getFile(filepath.substring(0, filepath.lastIndexOf(".")) + ".dds"); + } + return null; + } + + @Override + public InputStream getResourceAsStream(final String filepath) { + try { + for (int i = this.mpqList.size() - 1; i >= 0; i--) { + final DataSource mpq = this.mpqList.get(i); + final InputStream resourceAsStream = mpq.getResourceAsStream(filepath); + if (resourceAsStream != null) { + return resourceAsStream; + } + } + } + catch (final IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + if (filepath.toLowerCase(Locale.US).endsWith(".blp")) { + return getResourceAsStream(filepath.substring(0, filepath.lastIndexOf(".")) + ".dds"); + } + if (filepath.toLowerCase(Locale.US).endsWith(".tif")) { + return getResourceAsStream(filepath.substring(0, filepath.lastIndexOf(".")) + ".dds"); + } + return null; + } + + @Override + public boolean has(final String filepath) { + if (this.cache.containsKey(filepath)) { + return true; + } + for (int i = this.mpqList.size() - 1; i >= 0; i--) { + final DataSource mpq = this.mpqList.get(i); + if (mpq.has(filepath)) { + return true; + } + } + if (filepath.toLowerCase(Locale.US).endsWith(".blp")) { + return has(filepath.substring(0, filepath.lastIndexOf(".")) + ".dds"); + } + if (filepath.toLowerCase(Locale.US).endsWith(".tif")) { + return has(filepath.substring(0, filepath.lastIndexOf(".")) + ".dds"); + } + return false; + } + + public void refresh(final List dataSourceDescriptors) { + for (final DataSource dataSource : this.mpqList) { + try { + dataSource.close(); + } + catch (final NullPointerException e) { + e.printStackTrace(); + } + catch (final IOException e) { + e.printStackTrace(); + } + } + this.cache.clear(); + this.mpqList.clear(); + if (dataSourceDescriptors != null) { + for (final DataSourceDescriptor descriptor : dataSourceDescriptors) { + this.mpqList.add(descriptor.createDataSource()); + } + } + } + + public interface LoadedMPQ { + void unload(); + + boolean hasListfile(); + + boolean has(String path); + } + + public Set getMergedListfile() { + final Set listfile = new HashSet<>(); + for (final DataSource mpqGuy : this.mpqList) { + final Collection dataSourceListfile = mpqGuy.getListfile(); + if (dataSourceListfile != null) { + for (final String element : dataSourceListfile) { + listfile.add(element); + } + } + } + return listfile; + } + + @Override + public Collection getListfile() { + return getMergedListfile(); + } + + @Override + public void close() throws IOException { + for (final DataSource mpqGuy : this.mpqList) { + mpqGuy.close(); + } + } +} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/Geoset.java b/core/src/com/etheller/warsmash/parsers/mdlx/Geoset.java index eb8b55d..6c3d7df 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/Geoset.java +++ b/core/src/com/etheller/warsmash/parsers/mdlx/Geoset.java @@ -316,4 +316,61 @@ public class Geoset implements MdlxBlock, Chunk { } return size; } + + public float[] getVertices() { + return this.vertices; + } + + public float[] getNormals() { + return this.normals; + } + + public long[] getFaceTypeGroups() { + return this.faceTypeGroups; + } + + public long[] getFaceGroups() { + return this.faceGroups; + } + + public int[] getFaces() { + return this.faces; + } + + public short[] getVertexGroups() { + return this.vertexGroups; + } + + public long[] getMatrixGroups() { + return this.matrixGroups; + } + + public long[] getMatrixIndices() { + return this.matrixIndices; + } + + public long getMaterialId() { + return this.materialId; + } + + public long getSelectionGroup() { + return this.selectionGroup; + } + + public long getSelectionFlags() { + return this.selectionFlags; + } + + public Extent getExtent() { + return this.extent; + } + + public Extent[] getSequenceExtents() { + return this.sequenceExtents; + } + + public float[][] getUvSets() { + return this.uvSets; + } + } diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/Light.java b/core/src/com/etheller/warsmash/parsers/mdlx/Light.java index 6b802e0..19f8dd5 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/Light.java +++ b/core/src/com/etheller/warsmash/parsers/mdlx/Light.java @@ -163,4 +163,29 @@ public class Light extends GenericObject { public long getByteLength() { return 48 + super.getByteLength(); } + + public int getType() { + return this.type; + } + + public float[] getAttenuation() { + return this.attenuation; + } + + public float[] getColor() { + return this.color; + } + + public float getIntensity() { + return this.intensity; + } + + public float[] getAmbientColor() { + return this.ambientColor; + } + + public float getAmbientIntensity() { + return this.ambientIntensity; + } + } diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/ParticleEmitter.java b/core/src/com/etheller/warsmash/parsers/mdlx/ParticleEmitter.java index 37d00c5..fb8a3d1 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/ParticleEmitter.java +++ b/core/src/com/etheller/warsmash/parsers/mdlx/ParticleEmitter.java @@ -190,4 +190,33 @@ public class ParticleEmitter extends GenericObject { public long getByteLength() { return 288 + super.getByteLength(); } + + public float getEmissionRate() { + return this.emissionRate; + } + + public float getGravity() { + return this.gravity; + } + + public float getLongitude() { + return this.longitude; + } + + public float getLatitude() { + return this.latitude; + } + + public String getPath() { + return this.path; + } + + public float getLifeSpan() { + return this.lifeSpan; + } + + public float getSpeed() { + return this.speed; + } + } diff --git a/core/src/com/etheller/warsmash/units/DataTable.java b/core/src/com/etheller/warsmash/units/DataTable.java new file mode 100644 index 0000000..7ec7c66 --- /dev/null +++ b/core/src/com/etheller/warsmash/units/DataTable.java @@ -0,0 +1,303 @@ +package com.etheller.warsmash.units; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +import com.etheller.warsmash.util.WorldEditStrings; + +public class DataTable implements ObjectData { + private static final boolean DEBUG = false; + + Map dataTable = new LinkedHashMap<>(); + + private final WorldEditStrings worldEditStrings; + + public DataTable(final WorldEditStrings worldEditStrings) { + this.worldEditStrings = worldEditStrings; + } + + @Override + public String getLocalizedString(final String key) { + return this.worldEditStrings.getString(key); + } + + @Override + public Set keySet() { + final Set outputKeySet = new HashSet<>(); + final Set internalKeySet = this.dataTable.keySet(); + for (final StringKey key : internalKeySet) { + outputKeySet.add(key.getString()); + } + return outputKeySet; + } + + public void readTXT(final InputStream inputStream) { + try { + readTXT(inputStream, false); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + } + + public void readTXT(final File f) { + readTXT(f, false); + } + + public void readTXT(final File f, final boolean canProduce) { + try { + readTXT(new FileInputStream(f), canProduce); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + } + + public void readSLK(final File f) { + try { + readSLK(new FileInputStream(f)); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + } + + public void readTXT(final InputStream txt, final boolean canProduce) throws IOException { + final BufferedReader reader = new BufferedReader(new InputStreamReader(txt, "utf-8")); + // BOM marker will only appear on the very beginning + reader.mark(4); + if ('\ufeff' != reader.read()) { + reader.reset(); // not the BOM marker + } + + String input = ""; + Element currentUnit = null; + final boolean first = true; + while ((input = reader.readLine()) != null) { + if (DEBUG) { + System.out.println(input); + } + if (input.startsWith("//")) { + continue; + } + if (input.startsWith("[") && input.contains("]")) { + final int start = input.indexOf("[") + 1; + final int end = input.indexOf("]"); + final String newKey = input.substring(start, end); + final String newKeyBase = newKey; + currentUnit = this.dataTable.get(new StringKey(newKey)); + if (currentUnit == null) { + currentUnit = new Element(newKey, this); + if (canProduce) { + currentUnit = new LMUnit(newKey, this); + this.dataTable.put(new StringKey(newKey), currentUnit); + } + } + } + else if (input.contains("=")) { + final int eIndex = input.indexOf("="); + final String fieldValue = input.substring(eIndex + 1); + int fieldIndex = 0; + final StringBuilder builder = new StringBuilder(); + boolean withinQuotedString = false; + final String fieldName = input.substring(0, eIndex); + for (int i = 0; i < fieldValue.length(); i++) { + final char c = fieldValue.charAt(i); + if (c == '\"') { + withinQuotedString = !withinQuotedString; + } + else if (!withinQuotedString && (c == ',')) { + currentUnit.setField(fieldName, builder.toString().trim(), fieldIndex++); + builder.setLength(0); // empty buffer + } + else { + builder.append(c); + } + } + if (builder.length() > 0) { + if (currentUnit == null) { + System.out.println("null for " + input); + } + currentUnit.setField(fieldName, builder.toString().trim(), fieldIndex++); + } + } + } + + reader.close(); + } + + public void readSLK(final InputStream txt) throws IOException { + final BufferedReader reader = new BufferedReader(new InputStreamReader(txt, "utf-8")); + + String input = ""; + Element currentUnit = null; + input = reader.readLine(); + if (!input.contains("ID")) { + System.err.println("Formatting of SLK is unusual."); + } + input = reader.readLine(); + while (input.startsWith("P;") || input.startsWith("F;")) { + input = reader.readLine(); + } + final int yIndex = input.indexOf("Y") + 1; + final int xIndex = input.indexOf("X") + 1; + int colCount = 0; + int rowCount = 0; + boolean flipMode = false; + if (xIndex > yIndex) { + colCount = Integer.parseInt(input.substring(xIndex, input.lastIndexOf(";"))); + rowCount = Integer.parseInt(input.substring(yIndex, xIndex - 2)); + } + else { + rowCount = Integer.parseInt(input.substring(yIndex, input.lastIndexOf(";"))); + colCount = Integer.parseInt(input.substring(xIndex, yIndex - 2)); + flipMode = true; + } + int rowStartCount = 0; + final String[] dataNames = new String[colCount]; + int col = 0; + int lastFieldId = 0; + while ((input = reader.readLine()) != null) { + if (DEBUG) { + System.out.println(input); + } + if (input.startsWith("E")) { + break; + } + if (input.startsWith("O;")) { + continue; + } + if (input.contains("X1;")) { + rowStartCount++; + col = 0; + } + else { + col++; + } + String kInput; + if (input.startsWith("F;")) { + kInput = reader.readLine(); + if (DEBUG) { + System.out.println(kInput); + } + } + else { + kInput = input; + } + if (rowStartCount <= 1) { + final int subXIndex = input.indexOf("X"); + final int subYIndex = input.indexOf("Y"); + if ((subYIndex >= 0) && (subYIndex < subXIndex)) { + final int eIndex = kInput.indexOf("K"); + final int fieldIdEndIndex = kInput != input ? input.length() : eIndex - 1; + if ((eIndex == -1) || (kInput.charAt(eIndex - 1) != ';')) { + continue; + } + final int fieldId; + if (subXIndex < 0) { + if (lastFieldId == 0) { + rowStartCount++; + } + fieldId = lastFieldId + 1; + } + else { + fieldId = Integer.parseInt(input.substring(subXIndex + 1, fieldIdEndIndex)); + } + + final int quotationIndex = kInput.indexOf("\""); + if (quotationIndex == -1) { + dataNames[fieldId - 1] = kInput.substring(eIndex + 1); + } + else { + dataNames[fieldId - 1] = kInput.substring(quotationIndex + 1, kInput.lastIndexOf("\"")); + } + lastFieldId = fieldId; + continue; + } + else { + int eIndex = kInput.indexOf("K"); + if ((eIndex == -1) || (kInput.charAt(eIndex - 1) != ';')) { + continue; + } + final int fieldId; + if (subXIndex < 0) { + if (lastFieldId == 0) { + rowStartCount++; + } + fieldId = lastFieldId + 1; + } + else { + if (flipMode && input.contains("Y") && (input == kInput)) { + eIndex = Math.min(subYIndex, eIndex); + } + final int fieldIdEndIndex = kInput != input ? input.length() : eIndex - 1; + fieldId = Integer.parseInt(input.substring(subXIndex + 1, fieldIdEndIndex)); + } + + final int quotationIndex = kInput.indexOf("\""); + if (quotationIndex == -1) { + dataNames[fieldId - 1] = kInput.substring(eIndex + 1); + } + else { + dataNames[fieldId - 1] = kInput.substring(quotationIndex + 1, kInput.lastIndexOf("\"")); + } + lastFieldId = fieldId; + continue; + } + } + if (input.contains("X1;") || ((input != kInput) && input.endsWith("X1"))) { + final int start = kInput.indexOf("\"") + 1; + final int end = kInput.lastIndexOf("\""); + if ((start - 1) != end) { + final String newKey = kInput.substring(start, end); + currentUnit = this.dataTable.get(new StringKey(newKey)); + if (currentUnit == null) { + currentUnit = new Element(newKey, this); + this.dataTable.put(new StringKey(newKey), currentUnit); + } + } + } + else if (kInput.contains("K")) { + final int subXIndex = input.indexOf("X"); + int eIndex = kInput.indexOf("K"); + if (flipMode && kInput.contains("Y")) { + eIndex = Math.min(kInput.indexOf("Y"), eIndex); + } + final int fieldIdEndIndex = kInput != input ? input.length() : eIndex - 1; + final int fieldId = (subXIndex == -1) || (subXIndex > fieldIdEndIndex) ? 1 + : Integer.parseInt(input.substring(subXIndex + 1, fieldIdEndIndex)); + String fieldValue = kInput.substring(eIndex + 1); + if ((fieldValue.length() > 1) && fieldValue.startsWith("\"") && fieldValue.endsWith("\"")) { + fieldValue = fieldValue.substring(1, fieldValue.length() - 1); + } + if (dataNames[fieldId - 1] != null) { + currentUnit.setField(dataNames[fieldId - 1], fieldValue); + } + } + } + + reader.close(); + } + + @Override + public Element get(final String id) { + return this.dataTable.get(new StringKey(id)); + } + + @Override + public void setValue(final String id, final String field, final String value) { + get(id).setField(field, value); + } + + public void put(final String id, final Element e) { + this.dataTable.put(new StringKey(id), e); + } +} \ No newline at end of file diff --git a/core/src/com/etheller/warsmash/units/Element.java b/core/src/com/etheller/warsmash/units/Element.java new file mode 100644 index 0000000..15de6fd --- /dev/null +++ b/core/src/com/etheller/warsmash/units/Element.java @@ -0,0 +1,217 @@ +package com.etheller.warsmash.units; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; + +public class Element extends HashedGameObject { + + public Element(final String id, final DataTable table) { + super(id, table); + } + + public List builds() { + return getFieldAsList("Builds", this.parentTable); + } + + public List requires() { + final List requirements = getFieldAsList("Requires", this.parentTable); + final List reqLvls = requiresLevels(); + return requirements; + } + + public List requiresLevels() { + final String stringList = getField("Requiresamount"); + final String[] listAsArray = stringList.split(","); + final LinkedList output = new LinkedList<>(); + if ((listAsArray != null) && (listAsArray.length > 0) && !listAsArray[0].equals("")) { + for (final String levelString : listAsArray) { + final Integer level = Integer.parseInt(levelString); + if (level != null) { + output.add(level); + } + } + } + return output; + } + + public List parents() { + return getFieldAsList("Parents", this.parentTable); + } + + public List children() { + return getFieldAsList("Children", this.parentTable); + } + + public List requiredBy() { + return getFieldAsList("RequiredBy", this.parentTable); + } + + public List trains() { + return getFieldAsList("Trains", this.parentTable); + } + + public List upgrades() { + return getFieldAsList("Upgrade", this.parentTable); + } + + public List researches() { + return getFieldAsList("Researches", this.parentTable); + } + + public List dependencyOr() { + return getFieldAsList("DependencyOr", this.parentTable); + } + + public List abilities() { + return getFieldAsList("abilList", this.parentTable); + } + + HashMap> hashedLists = new HashMap<>(); + + @Override + public String toString() { + return getField("Name"); + } + + public int getTechTier() { + final String tier = getField("Custom Field: TechTier"); + if (tier == null) { + return -1; + } + return Integer.parseInt(tier); + } + + public void setTechTier(final int i) { + setField("Custom Field: TechTier", i + ""); + } + + public int getTechDepth() { + final String tier = getField("Custom Field: TechDepth"); + if (tier == null) { + return -1; + } + return Integer.parseInt(tier); + } + + public void setTechDepth(final int i) { + setField("Custom Field: TechDepth", i + ""); + } + + public String getIconPath() { + String artField = getField("Art"); + if (artField.indexOf(',') != -1) { + artField = artField.substring(0, artField.indexOf(',')); + } + return artField; + } + + public String getUnitId() { + return this.id; + } + + @Override + public String getName() { + String name = getField("Name"); + boolean nameKnown = name.length() >= 1; + if (!nameKnown && !getField("code").equals(this.id) && (getField("code").length() >= 4)) { + final Element other = (Element) this.parentTable.get(getField("code").substring(0, 4)); + if (other != null) { + name = other.getName(); + nameKnown = true; + } + } + if (!nameKnown && (getField("EditorName").length() > 1)) { + name = getField("EditorName"); + nameKnown = true; + } + if (!nameKnown && (getField("Editorname").length() > 1)) { + name = getField("Editorname"); + nameKnown = true; + } + if (!nameKnown && (getField("BuffTip").length() > 1)) { + name = getField("BuffTip"); + nameKnown = true; + } + if (!nameKnown && (getField("Bufftip").length() > 1)) { + name = getField("Bufftip"); + nameKnown = true; + } + if (nameKnown && name.startsWith("WESTRING")) { + if (!name.contains(" ")) { + name = this.parentTable.getLocalizedString(name); + } + else { + final String[] names = name.split(" "); + name = ""; + for (final String subName : names) { + if (name.length() > 0) { + name += " "; + } + if (subName.startsWith("WESTRING")) { + name += this.parentTable.getLocalizedString(subName); + } + else { + name += subName; + } + } + } + if (name.startsWith("\"") && name.endsWith("\"")) { + name = name.substring(1, name.length() - 1); + } + setField("Name", name); + } + if (!nameKnown) { + name = this.parentTable.getLocalizedString("WESTRING_UNKNOWN") + " '" + getUnitId() + "'"; + } + if (getField("campaign").startsWith("1") && Character.isUpperCase(getUnitId().charAt(0))) { + name = getField("Propernames"); + if (name.contains(",")) { + name = name.split(",")[0]; + } + } + String suf = getField("EditorSuffix"); + if ((suf.length() > 0) && !suf.equals("_")) { + if (suf.startsWith("WESTRING")) { + suf = this.parentTable.getLocalizedString(suf); + } + if (!suf.startsWith(" ")) { + name += " "; + } + name += suf; + } + return name; + } + + public void addParent(final String parentId) { + String parentField = getField("Parents"); + if (!parentField.contains(parentId)) { + parentField = parentField + "," + parentId; + setField("Parents", parentField); + } + } + + public void addChild(final String parentId) { + String parentField = getField("Children"); + if (!parentField.contains(parentId)) { + parentField = parentField + "," + parentId; + setField("Children", parentField); + } + } + + public void addRequiredBy(final String parentId) { + String parentField = getField("RequiredBy"); + if (!parentField.contains(parentId)) { + parentField = parentField + "," + parentId; + setField("RequiredBy", parentField); + } + } + + public void addResearches(final String parentId) { + String parentField = getField("Researches"); + if (!parentField.contains(parentId)) { + parentField = parentField + "," + parentId; + setField("Researches", parentField); + } + } +} diff --git a/core/src/com/etheller/warsmash/units/GameObject.java b/core/src/com/etheller/warsmash/units/GameObject.java new file mode 100644 index 0000000..d3b9de9 --- /dev/null +++ b/core/src/com/etheller/warsmash/units/GameObject.java @@ -0,0 +1,30 @@ +package com.etheller.warsmash.units; + +import java.util.List; +import java.util.Set; + +public interface GameObject { + + public void setField(String field, String value); + + public void setField(String field, String value, int index); + + public String getField(String field); + + public String getField(String field, int index); + + public int getFieldValue(String field); + + public int getFieldValue(String field, int index); + + public List getFieldAsList(String field, ObjectData objectData); + + public String getId(); + + public ObjectData getTable(); + + public String getName(); + + public Set keySet(); + +} \ No newline at end of file diff --git a/core/src/com/etheller/warsmash/units/HashedGameObject.java b/core/src/com/etheller/warsmash/units/HashedGameObject.java new file mode 100644 index 0000000..396449a --- /dev/null +++ b/core/src/com/etheller/warsmash/units/HashedGameObject.java @@ -0,0 +1,241 @@ +package com.etheller.warsmash.units; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public abstract class HashedGameObject implements GameObject { + HashMap> fields = new HashMap<>(); + String id; + ObjectData parentTable; + + transient HashMap> hashedLists = new HashMap<>(); + + public HashedGameObject(final String id, final ObjectData table) { + this.id = id; + this.parentTable = table; + } + + @Override + public void setField(final String field, final String value) { + final StringKey key = new StringKey(field); + List list = this.fields.get(key); + if (list == null) { + list = new ArrayList<>(); + this.fields.put(key, list); + list.add(value); + } + else { + list.set(0, value); + } + } + + @Override + public String getField(final String field) { + final String value = ""; + if (this.fields.get(new StringKey(field)) != null) { + final List list = this.fields.get(new StringKey(field)); + final StringBuilder sb = new StringBuilder(); + if (list != null) { + for (final String str : list) { + if (sb.length() != 0) { + sb.append(','); + } + sb.append(str); + } + return sb.toString(); + } + } + return value; + } + + public boolean hasField(final String field) { + return this.fields.containsKey(new StringKey(field)); + } + + @Override + public int getFieldValue(final String field) { + int i = 0; + try { + i = Integer.parseInt(getField(field)); + } + catch (final NumberFormatException e) { + + } + return i; + } + + @Override + public void setField(final String field, final String value, final int index) { + final StringKey key = new StringKey(field); + List list = this.fields.get(key); + if (list == null) { + if (index == 0) { + list = new ArrayList<>(); + this.fields.put(key, list); + list.add(value); + } + else { + throw new IndexOutOfBoundsException(); + } + } + else { + if (list.size() == index) { + list.add(value); + } + else { + list.set(index, value); + } + } + } + + @Override + public String getField(final String field, final int index) { + String value = ""; + if (this.fields.get(new StringKey(field)) != null) { + final List list = this.fields.get(new StringKey(field)); + if (list != null) { + if (list.size() > index) { + value = list.get(index); + } + } + } + return value; + } + + @Override + public int getFieldValue(final String field, final int index) { + int i = 0; + try { + i = Integer.parseInt(getField(field, index)); + } + catch (final NumberFormatException e) { + + } + return i; + } + + @Override + public List getFieldAsList(final String field, final ObjectData parentTable) { + List fieldAsList; + fieldAsList = new ArrayList<>(); + final String stringList = getField(field); + final String[] listAsArray = stringList.split(","); + if ((listAsArray != null) && (listAsArray.length > 0)) { + for (final String buildingId : listAsArray) { + final GameObject referencedUnit = parentTable.get(buildingId); + if (referencedUnit != null) { + fieldAsList.add(referencedUnit); + } + } + } + return fieldAsList; + } + + @Override + public String toString() { + return getField("Name"); + } + + @Override + public String getId() { + return this.id; + } + + @Override + public String getName() { + String name = getField("Name"); + boolean nameKnown = name.length() >= 1; + if (!nameKnown && !getField("code").equals(this.id) && (getField("code").length() >= 4)) { + final GameObject other = this.parentTable.get(getField("code").substring(0, 4)); + if (other != null) { + name = other.getName(); + nameKnown = true; + } + } + if (!nameKnown && (getField("EditorName").length() > 1)) { + name = getField("EditorName"); + nameKnown = true; + } + if (!nameKnown && (getField("Editorname").length() > 1)) { + name = getField("Editorname"); + nameKnown = true; + } + if (!nameKnown && (getField("BuffTip").length() > 1)) { + name = getField("BuffTip"); + nameKnown = true; + } + if (!nameKnown && (getField("Bufftip").length() > 1)) { + name = getField("Bufftip"); + nameKnown = true; + } + if (nameKnown && name.startsWith("WESTRING")) { + if (!name.contains(" ")) { + name = this.parentTable.getLocalizedString(name); + } + else { + final String[] names = name.split(" "); + name = ""; + for (final String subName : names) { + if (name.length() > 0) { + name += " "; + } + if (subName.startsWith("WESTRING")) { + name += this.parentTable.getLocalizedString(subName); + } + else { + name += subName; + } + } + } + if (name.startsWith("\"") && name.endsWith("\"")) { + name = name.substring(1, name.length() - 1); + } + setField("Name", name); + } + if (!nameKnown) { + name = this.parentTable.getLocalizedString("WESTRING_UNKNOWN") + " '" + getId() + "'"; + } + if (getField("campaign").startsWith("1") && Character.isUpperCase(getId().charAt(0))) { + name = getField("Propernames"); + if (name.contains(",")) { + name = name.split(",")[0]; + } + } + String suf = getField("EditorSuffix"); + if ((suf.length() > 0) && !suf.equals("_")) { + if (suf.startsWith("WESTRING")) { + suf = this.parentTable.getLocalizedString(suf); + } + if (!suf.startsWith(" ")) { + name += " "; + } + name += suf; + } + return name; + } + + public void addToList(final String parentId, final String list) { + String parentField = getField(list); + if (!parentField.contains(parentId)) { + parentField = parentField + "," + parentId; + setField(list, parentField); + } + } + + @Override + public ObjectData getTable() { + return this.parentTable; + } + + @Override + public Set keySet() { + final Set keySet = new HashSet<>(); + for (final StringKey key : this.fields.keySet()) { + keySet.add(key.getString()); + } + return keySet; + } +} diff --git a/core/src/com/etheller/warsmash/units/LMUnit.java b/core/src/com/etheller/warsmash/units/LMUnit.java new file mode 100644 index 0000000..39d29a1 --- /dev/null +++ b/core/src/com/etheller/warsmash/units/LMUnit.java @@ -0,0 +1,12 @@ +package com.etheller.warsmash.units; + +import java.util.LinkedHashMap; + +public class LMUnit extends Element { + + public LMUnit(final String id, final DataTable table) { + super(id, table); + this.fields = new LinkedHashMap<>(); + } + +} diff --git a/core/src/com/etheller/warsmash/units/ObjectData.java b/core/src/com/etheller/warsmash/units/ObjectData.java new file mode 100644 index 0000000..0ff7c61 --- /dev/null +++ b/core/src/com/etheller/warsmash/units/ObjectData.java @@ -0,0 +1,13 @@ +package com.etheller.warsmash.units; + +import java.util.Set; + +public interface ObjectData { + GameObject get(String id); + + void setValue(String id, String field, String value); + + Set keySet(); + + String getLocalizedString(String key); +} diff --git a/core/src/com/etheller/warsmash/units/StandardObjectData.java b/core/src/com/etheller/warsmash/units/StandardObjectData.java new file mode 100644 index 0000000..845a6d1 --- /dev/null +++ b/core/src/com/etheller/warsmash/units/StandardObjectData.java @@ -0,0 +1,610 @@ +package com.etheller.warsmash.units; + +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.etheller.warsmash.datasources.DataSource; +import com.etheller.warsmash.util.WorldEditStrings; + +public class StandardObjectData { + private WorldEditStrings worldEditStrings; + private DataSource source; + + public StandardObjectData(final DataSource dataSource) { + this.source = dataSource; + this.worldEditStrings = new WorldEditStrings(dataSource); + } + + public WarcraftData getStandardUnits() { + + final DataTable profile = new DataTable(this.worldEditStrings); + final DataTable unitAbilities = new DataTable(this.worldEditStrings); + final DataTable unitBalance = new DataTable(this.worldEditStrings); + final DataTable unitData = new DataTable(this.worldEditStrings); + final DataTable unitUI = new DataTable(this.worldEditStrings); + final DataTable unitWeapons = new DataTable(this.worldEditStrings); + + try { + profile.readTXT(this.source.getResourceAsStream("Units\\CampaignUnitFunc.txt"), true); + profile.readTXT(this.source.getResourceAsStream("Units\\CampaignUnitStrings.txt"), true); + profile.readTXT(this.source.getResourceAsStream("Units\\HumanUnitFunc.txt"), true); + profile.readTXT(this.source.getResourceAsStream("Units\\HumanUnitStrings.txt"), true); + profile.readTXT(this.source.getResourceAsStream("Units\\NeutralUnitFunc.txt"), true); + profile.readTXT(this.source.getResourceAsStream("Units\\NeutralUnitStrings.txt"), true); + profile.readTXT(this.source.getResourceAsStream("Units\\NightElfUnitFunc.txt"), true); + profile.readTXT(this.source.getResourceAsStream("Units\\NightElfUnitStrings.txt"), true); + profile.readTXT(this.source.getResourceAsStream("Units\\OrcUnitFunc.txt"), true); + profile.readTXT(this.source.getResourceAsStream("Units\\OrcUnitStrings.txt"), true); + profile.readTXT(this.source.getResourceAsStream("Units\\UndeadUnitFunc.txt"), true); + profile.readTXT(this.source.getResourceAsStream("Units\\UndeadUnitStrings.txt"), true); + + unitAbilities.readSLK(this.source.getResourceAsStream("Units\\UnitAbilities.slk")); + + unitBalance.readSLK(this.source.getResourceAsStream("Units\\UnitBalance.slk")); + + unitData.readSLK(this.source.getResourceAsStream("Units\\UnitData.slk")); + + unitUI.readSLK(this.source.getResourceAsStream("Units\\UnitUI.slk")); + + unitWeapons.readSLK(this.source.getResourceAsStream("Units\\UnitWeapons.slk")); + final InputStream unitSkin = this.source.getResourceAsStream("Units\\UnitSkin.txt"); + if (unitSkin != null) { + profile.readTXT(unitSkin, true); + } + } + catch (final IOException e) { + throw new RuntimeException(e); + } + + final WarcraftData units = new WarcraftData(); + + units.add(profile, "Profile", false); + units.add(unitAbilities, "UnitAbilities", true); + units.add(unitBalance, "UnitBalance", true); + units.add(unitData, "UnitData", true); + units.add(unitUI, "UnitUI", true); + units.add(unitWeapons, "UnitWeapons", true); + + return units; + } + + public WarcraftData getStandardItems() { + final DataTable profile = new DataTable(this.worldEditStrings); + final DataTable itemData = new DataTable(this.worldEditStrings); + + try { + profile.readTXT(this.source.getResourceAsStream("Units\\ItemFunc.txt"), true); + profile.readTXT(this.source.getResourceAsStream("Units\\ItemStrings.txt"), true); + itemData.readSLK(this.source.getResourceAsStream("Units\\ItemData.slk")); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + + final WarcraftData units = new WarcraftData(); + + units.add(profile, "Profile", false); + units.add(itemData, "ItemData", true); + + return units; + } + + public WarcraftData getStandardDestructables() { + final DataTable destructableData = new DataTable(this.worldEditStrings); + + try { + destructableData.readSLK(this.source.getResourceAsStream("Units\\DestructableData.slk")); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + + final WarcraftData units = new WarcraftData(); + + units.add(destructableData, "DestructableData", true); + + return units; + } + + public WarcraftData getStandardDoodads() { + + final DataTable destructableData = new DataTable(this.worldEditStrings); + + try { + destructableData.readSLK(this.source.getResourceAsStream("Doodads\\Doodads.slk")); + final InputStream unitSkin = this.source.getResourceAsStream("Doodads\\DoodadSkins.txt"); + if (unitSkin != null) { + destructableData.readTXT(unitSkin, true); + } + } + catch (final IOException e) { + throw new RuntimeException(e); + } + + final WarcraftData units = new WarcraftData(); + + units.add(destructableData, "DoodadData", true); + + return units; + } + + public DataTable getStandardUnitMeta() { + final DataTable unitMetaData = new DataTable(this.worldEditStrings); + try { + unitMetaData.readSLK(this.source.getResourceAsStream("Units\\UnitMetaData.slk")); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + return unitMetaData; + } + + public DataTable getStandardDestructableMeta() { + final DataTable unitMetaData = new DataTable(this.worldEditStrings); + try { + unitMetaData.readSLK(this.source.getResourceAsStream("Units\\DestructableMetaData.slk")); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + return unitMetaData; + } + + public DataTable getStandardDoodadMeta() { + final DataTable unitMetaData = new DataTable(this.worldEditStrings); + try { + unitMetaData.readSLK(this.source.getResourceAsStream("Doodads\\DoodadMetaData.slk")); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + return unitMetaData; + } + + public WarcraftData getStandardAbilities() { + + final DataTable profile = new DataTable(this.worldEditStrings); + final DataTable abilityData = new DataTable(this.worldEditStrings); + + try { + profile.readTXT(this.source.getResourceAsStream("Units\\CampaignAbilityFunc.txt"), true); + profile.readTXT(this.source.getResourceAsStream("Units\\CampaignAbilityStrings.txt"), true); + profile.readTXT(this.source.getResourceAsStream("Units\\CommonAbilityFunc.txt"), true); + profile.readTXT(this.source.getResourceAsStream("Units\\CommonAbilityStrings.txt"), true); + profile.readTXT(this.source.getResourceAsStream("Units\\HumanAbilityFunc.txt"), true); + profile.readTXT(this.source.getResourceAsStream("Units\\HumanAbilityStrings.txt"), true); + profile.readTXT(this.source.getResourceAsStream("Units\\NeutralAbilityFunc.txt"), true); + profile.readTXT(this.source.getResourceAsStream("Units\\NeutralAbilityStrings.txt"), true); + profile.readTXT(this.source.getResourceAsStream("Units\\NightElfAbilityFunc.txt"), true); + profile.readTXT(this.source.getResourceAsStream("Units\\NightElfAbilityStrings.txt"), true); + profile.readTXT(this.source.getResourceAsStream("Units\\OrcAbilityFunc.txt"), true); + profile.readTXT(this.source.getResourceAsStream("Units\\OrcAbilityStrings.txt"), true); + profile.readTXT(this.source.getResourceAsStream("Units\\UndeadAbilityFunc.txt"), true); + profile.readTXT(this.source.getResourceAsStream("Units\\UndeadAbilityStrings.txt"), true); + profile.readTXT(this.source.getResourceAsStream("Units\\ItemAbilityFunc.txt"), true); + profile.readTXT(this.source.getResourceAsStream("Units\\ItemAbilityStrings.txt"), true); + + abilityData.readSLK(this.source.getResourceAsStream("Units\\AbilityData.slk")); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + + final WarcraftData abilities = new WarcraftData(); + + abilities.add(profile, "Profile", false); + abilities.add(abilityData, "AbilityData", true); + + return abilities; + } + + public WarcraftData getStandardAbilityBuffs() { + final DataTable profile = new DataTable(this.worldEditStrings); + final DataTable abilityData = new DataTable(this.worldEditStrings); + + try { + profile.readTXT(this.source.getResourceAsStream("Units\\CampaignAbilityFunc.txt"), true); + profile.readTXT(this.source.getResourceAsStream("Units\\CampaignAbilityStrings.txt"), true); + profile.readTXT(this.source.getResourceAsStream("Units\\CommonAbilityFunc.txt"), true); + profile.readTXT(this.source.getResourceAsStream("Units\\CommonAbilityStrings.txt"), true); + profile.readTXT(this.source.getResourceAsStream("Units\\HumanAbilityFunc.txt"), true); + profile.readTXT(this.source.getResourceAsStream("Units\\HumanAbilityStrings.txt"), true); + profile.readTXT(this.source.getResourceAsStream("Units\\NeutralAbilityFunc.txt"), true); + profile.readTXT(this.source.getResourceAsStream("Units\\NeutralAbilityStrings.txt"), true); + profile.readTXT(this.source.getResourceAsStream("Units\\NightElfAbilityFunc.txt"), true); + profile.readTXT(this.source.getResourceAsStream("Units\\NightElfAbilityStrings.txt"), true); + profile.readTXT(this.source.getResourceAsStream("Units\\OrcAbilityFunc.txt"), true); + profile.readTXT(this.source.getResourceAsStream("Units\\OrcAbilityStrings.txt"), true); + profile.readTXT(this.source.getResourceAsStream("Units\\UndeadAbilityFunc.txt"), true); + profile.readTXT(this.source.getResourceAsStream("Units\\UndeadAbilityStrings.txt"), true); + profile.readTXT(this.source.getResourceAsStream("Units\\ItemAbilityFunc.txt"), true); + profile.readTXT(this.source.getResourceAsStream("Units\\ItemAbilityStrings.txt"), true); + + abilityData.readSLK(this.source.getResourceAsStream("Units\\AbilityBuffData.slk")); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + + final WarcraftData abilities = new WarcraftData(); + + abilities.add(profile, "Profile", false); + abilities.add(abilityData, "AbilityData", true); + + return abilities; + } + + public WarcraftData getStandardUpgrades() { + final DataTable profile = new DataTable(this.worldEditStrings); + final DataTable upgradeData = new DataTable(this.worldEditStrings); + + try { + profile.readTXT(this.source.getResourceAsStream("Units\\CampaignUpgradeFunc.txt"), true); + profile.readTXT(this.source.getResourceAsStream("Units\\CampaignUpgradeStrings.txt"), true); + profile.readTXT(this.source.getResourceAsStream("Units\\HumanUpgradeFunc.txt"), true); + profile.readTXT(this.source.getResourceAsStream("Units\\HumanUpgradeStrings.txt"), true); + profile.readTXT(this.source.getResourceAsStream("Units\\NeutralUpgradeFunc.txt"), true); + profile.readTXT(this.source.getResourceAsStream("Units\\NeutralUpgradeStrings.txt"), true); + profile.readTXT(this.source.getResourceAsStream("Units\\NightElfUpgradeFunc.txt"), true); + profile.readTXT(this.source.getResourceAsStream("Units\\NightElfUpgradeStrings.txt"), true); + profile.readTXT(this.source.getResourceAsStream("Units\\OrcUpgradeFunc.txt"), true); + profile.readTXT(this.source.getResourceAsStream("Units\\OrcUpgradeStrings.txt"), true); + profile.readTXT(this.source.getResourceAsStream("Units\\UndeadUpgradeFunc.txt"), true); + profile.readTXT(this.source.getResourceAsStream("Units\\UndeadUpgradeStrings.txt"), true); + + upgradeData.readSLK(this.source.getResourceAsStream("Units\\UpgradeData.slk")); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + + final WarcraftData units = new WarcraftData(); + + units.add(profile, "Profile", false); + units.add(upgradeData, "UpgradeData", true); + + return units; + } + + public DataTable getStandardUpgradeMeta() { + final DataTable unitMetaData = new DataTable(this.worldEditStrings); + try { + unitMetaData.readSLK(this.source.getResourceAsStream("Units\\UpgradeMetaData.slk")); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + return unitMetaData; + } + + public DataTable getStandardUpgradeEffectMeta() { + final DataTable unitMetaData = new DataTable(this.worldEditStrings); + try { + unitMetaData.readSLK(this.source.getResourceAsStream("Units\\UpgradeEffectMetaData.slk")); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + return unitMetaData; + } + + public DataTable getStandardAbilityMeta() { + final DataTable unitMetaData = new DataTable(this.worldEditStrings); + try { + unitMetaData.readSLK(this.source.getResourceAsStream("Units\\AbilityMetaData.slk")); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + return unitMetaData; + } + + public DataTable getStandardAbilityBuffMeta() { + final DataTable unitMetaData = new DataTable(this.worldEditStrings); + try { + unitMetaData.readSLK(this.source.getResourceAsStream("Units\\AbilityBuffMetaData.slk")); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + return unitMetaData; + } + + public DataTable getUnitEditorData() { + final DataTable unitMetaData = new DataTable(this.worldEditStrings); + try { + unitMetaData.readTXT(this.source.getResourceAsStream("UI\\UnitEditorData.txt"), true); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + return unitMetaData; + } + + public DataTable getWorldEditData() { + final DataTable unitMetaData = new DataTable(this.worldEditStrings); + try { + unitMetaData.readTXT(this.source.getResourceAsStream("UI\\WorldEditData.txt"), true); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + return unitMetaData; + } + + public static class WarcraftData implements ObjectData { + WorldEditStrings worldEditStrings; + List tables = new ArrayList<>(); + Map tableMap = new HashMap<>(); + Map units = new HashMap<>(); + + public WarcraftData(final WorldEditStrings worldEditStrings) { + this.worldEditStrings = worldEditStrings; + } + + @Override + public String getLocalizedString(final String key) { + return this.worldEditStrings.getString(key); + } + + public void add(final DataTable data, final String name, final boolean canMake) { + this.tableMap.put(new StringKey(name), data); + this.tables.add(data); + if (canMake) { + for (final String id : data.keySet()) { + if (!this.units.containsKey(new StringKey(id))) { + this.units.put(new StringKey(id), new WarcraftObject(data.get(id).getId(), this)); + } + } + } + } + + public WarcraftData() { + } + + public List getTables() { + return this.tables; + } + + public void setTables(final List tables) { + this.tables = tables; + } + + public DataTable getTable(final String tableName) { + return this.tableMap.get(new StringKey(tableName)); + } + + @Override + public GameObject get(final String id) { + return this.units.get(new StringKey(id)); + } + + @Override + public void setValue(final String id, final String field, final String value) { + get(id).setField(field, value); + } + + @Override + public Set keySet() { + final Set keySet = new HashSet<>(); + for (final StringKey key : this.units.keySet()) { + keySet.add(key.getString()); + } + return keySet; + } + + public void cloneUnit(final String parentId, final String cloneId) { + for (final DataTable table : this.tables) { + final Element parentEntry = table.get(parentId); + final LMUnit cloneUnit = new LMUnit(cloneId, table); + for (final String key : parentEntry.keySet()) { + cloneUnit.setField(key, parentEntry.getField(key)); + } + table.put(cloneId, cloneUnit); + } + this.units.put(new StringKey(cloneId), new WarcraftObject(cloneId, this)); + } + } + + public static class WarcraftObject implements GameObject { + String id; + WarcraftData dataSource; + + public WarcraftObject(final String id, final WarcraftData dataSource) { + this.id = id; + this.dataSource = dataSource; + } + + @Override + public void setField(final String field, final String value, final int index) { + for (final DataTable table : this.dataSource.getTables()) { + final Element element = table.get(this.id); + if ((element != null) && element.hasField(field)) { + element.setField(field, value, index); + return; + } + } + } + + @Override + public String getField(final String field, final int index) { + for (final DataTable table : this.dataSource.getTables()) { + final Element element = table.get(this.id); + if ((element != null) && element.hasField(field)) { + return element.getField(field, index); + } + } + return ""; + } + + @Override + public int getFieldValue(final String field, final int index) { + for (final DataTable table : this.dataSource.getTables()) { + final Element element = table.get(this.id); + if ((element != null) && element.hasField(field)) { + return element.getFieldValue(field, index); + } + } + return 0; + } + + @Override + public void setField(final String field, final String value) { + for (final DataTable table : this.dataSource.getTables()) { + final Element element = table.get(this.id); + if ((element != null) && element.hasField(field)) { + element.setField(field, value); + return; + } + } + throw new IllegalArgumentException("no field"); + } + + @Override + public String getField(final String field) { + for (final DataTable table : this.dataSource.getTables()) { + final Element element = table.get(this.id); + if ((element != null) && element.hasField(field)) { + return element.getField(field); + } + } + return ""; + } + + @Override + public int getFieldValue(final String field) { + for (final DataTable table : this.dataSource.getTables()) { + final Element element = table.get(this.id); + if ((element != null) && element.hasField(field)) { + return element.getFieldValue(field); + } + } + return 0; + } + + /* + * (non-Javadoc) I'm not entirely sure this is still safe to use + * + * @see com.hiveworkshop.wc3.units.GameObject#getFieldAsList(java.lang. String) + */ + @Override + public List getFieldAsList(final String field, final ObjectData objectData) { + for (final DataTable table : this.dataSource.getTables()) { + final Element element = table.get(this.id); + if ((element != null) && element.hasField(field)) { + return element.getFieldAsList(field, objectData); + } + } + return new ArrayList<>();// empty list if not found + } + + @Override + public String getId() { + return this.id; + } + + @Override + public ObjectData getTable() { + return this.dataSource; + } + + // @Override + // public String getName() { + // return dataSource.profile.get(id).getName(); + // } + @Override + public String getName() { + String name = getField("Name"); + boolean nameKnown = name.length() >= 1; + if (!nameKnown && !getField("code").equals(this.id) && (getField("code").length() >= 4)) { + final WarcraftObject other = (WarcraftObject) this.dataSource.get(getField("code").substring(0, 4)); + if (other != null) { + name = other.getName(); + nameKnown = true; + } + } + if (!nameKnown && (getField("EditorName").length() > 1)) { + name = getField("EditorName"); + nameKnown = true; + } + if (!nameKnown && (getField("Editorname").length() > 1)) { + name = getField("Editorname"); + nameKnown = true; + } + if (!nameKnown && (getField("BuffTip").length() > 1)) { + name = getField("BuffTip"); + nameKnown = true; + } + if (!nameKnown && (getField("Bufftip").length() > 1)) { + name = getField("Bufftip"); + nameKnown = true; + } + if (nameKnown && name.startsWith("WESTRING")) { + if (!name.contains(" ")) { + name = this.dataSource.getLocalizedString(name); + } + else { + final String[] names = name.split(" "); + name = ""; + for (final String subName : names) { + if (name.length() > 0) { + name += " "; + } + if (subName.startsWith("WESTRING")) { + name += this.dataSource.getLocalizedString(subName); + } + else { + name += subName; + } + } + } + if (name.startsWith("\"") && name.endsWith("\"")) { + name = name.substring(1, name.length() - 1); + } + setField("Name", name); + } + if (!nameKnown) { + name = this.dataSource.getLocalizedString("WESTRING_UNKNOWN") + " '" + getId() + "'"; + } + if (getField("campaign").startsWith("1") && Character.isUpperCase(getId().charAt(0))) { + name = getField("Propernames"); + if (name.contains(",")) { + name = name.split(",")[0]; + } + } + String suf = getField("EditorSuffix"); + if ((suf.length() > 0) && !suf.equals("_")) { + if (suf.startsWith("WESTRING")) { + suf = this.dataSource.getLocalizedString(suf); + } + if (!suf.startsWith(" ")) { + name += " "; + } + name += suf; + } + return name; + } + + BufferedImage storedImage = null; + String storedImagePath = null; + + @Override + public Set keySet() { + final Set keySet = new HashSet<>(); + for (final DataTable table : this.dataSource.tables) { + keySet.addAll(table.get(this.id).keySet()); + } + return keySet; + } + } + + private StandardObjectData() { + } +} diff --git a/core/src/com/etheller/warsmash/units/StringKey.java b/core/src/com/etheller/warsmash/units/StringKey.java new file mode 100644 index 0000000..c78e16e --- /dev/null +++ b/core/src/com/etheller/warsmash/units/StringKey.java @@ -0,0 +1,54 @@ +package com.etheller.warsmash.units; + +/** + * A hashable wrapper object for a String that can be used as the key in a + * hashtable, but which disregards case as a key -- except that it will remember + * case if directly asked for its value. The game needs this to be able to show + * the original case of a string to the user in the editor, while still doing + * map lookups in a case insensitive way. + * + * @author Eric + * + */ +public final class StringKey { + private final String string; + + public StringKey(final String string) { + this.string = string; + } + + public String getString() { + return this.string; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = (prime * result) + ((this.string.toLowerCase() == null) ? 0 : this.string.toLowerCase().hashCode()); + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final StringKey other = (StringKey) obj; + if (this.string == null) { + if (other.string != null) { + return false; + } + } + else if (!this.string.equalsIgnoreCase(other.string)) { + return false; + } + return true; + } +} \ No newline at end of file diff --git a/core/src/com/etheller/warsmash/util/IniFile.java b/core/src/com/etheller/warsmash/util/IniFile.java new file mode 100644 index 0000000..5c6fcff --- /dev/null +++ b/core/src/com/etheller/warsmash/util/IniFile.java @@ -0,0 +1,84 @@ +package com.etheller.warsmash.util; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class IniFile { + private static final Pattern NAME_PATTERN = Pattern.compile("^\\[(.+?)\\].*"); + private static final Pattern DATA_PATTERN = Pattern.compile("^(.+?)=(.*?)$"); + public final Map properties = new HashMap<>(); + public final Map> sections = new HashMap<>(); + + public IniFile(final String buffer) { + if (buffer != null) { + this.load(buffer); + } + } + + public void load(final String buffer) { + // All properties added until a section is reached are added to the properties + // map. + // Once a section is reached, any further properties will be added to it until + // matching another section, etc. + Map section = this.properties; + + // Below: using \n instead of \r\n because its not reading directly from the + // actual file, but instead from a Java translated thing + for (final String line : buffer.split("\n")) { + // INI defines comments as starting with a semicolon ';'. + // However, Warcraft 3 INI files use normal C comments '//'. + // In addition, Warcraft 3 files have empty lines. + // Therefore, ignore any line matching any of these conditions. + + if ((line.length() != 0) && !line.startsWith("//") && !line.startsWith(";")) { + final Matcher matcher = NAME_PATTERN.matcher(line); + + if (matcher.matches()) { + final String name = matcher.group(1).trim().toLowerCase(); + + section = this.sections.get(name); + + if (section == null) { + section = new HashMap<>(); + + this.sections.put(name, section); + } + } + else { + final Matcher dataMatcher = DATA_PATTERN.matcher(line); + if (dataMatcher.matches()) { + section.put(dataMatcher.group(1).toLowerCase(), dataMatcher.group(2)); + } + } + } + + } + + } + + public String save() { + final List lines = new ArrayList<>(); + + for (final Map.Entry entry : this.properties.entrySet()) { + lines.add(entry.getKey() + "=" + entry.getValue()); + } + + for (final Map.Entry> sectionData : this.sections.entrySet()) { + lines.add("[" + sectionData.getKey() + "]"); + + for (final Map.Entry entry : sectionData.getValue().entrySet()) { + lines.add(entry.getKey() + "=" + entry.getValue()); + } + } + + return String.join("\r\n", lines); + } + + public Map getSection(final String name) { + return this.sections.get(name.toLowerCase()); + } +} diff --git a/core/src/com/etheller/warsmash/util/MappedData.java b/core/src/com/etheller/warsmash/util/MappedData.java new file mode 100644 index 0000000..97b4915 --- /dev/null +++ b/core/src/com/etheller/warsmash/util/MappedData.java @@ -0,0 +1,92 @@ +package com.etheller.warsmash.util; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * A structure that holds mapped data from INI and SLK files. + * + * In the case of SLK files, the first row is expected to hold the names of the + * columns. + */ +public class MappedData { + private final Map map = new HashMap<>(); + + public MappedData(final String buffer) { + if (buffer != null) { + this.load(buffer); + } + } + + /** + * Load data from an SLK file or an INI file. + * + * Note that this may override previous properties! + */ + public void load(final String buffer) { + if (buffer.startsWith("ID;")) { + final SlkFile file = new SlkFile(buffer); + final List> rows = file.rows; + final List header = rows.get(0); + + for (int i = 1, l = rows.size(); i < l; i++) { + final List row = rows.get(i); + if (row != null) { + String name = (String) row.get(0); + + if (name != null) { + name = name.toLowerCase(); + + if (!this.map.containsKey(name)) { + this.map.put(name, new MappedDataRow()); + } + + final MappedDataRow mapped = this.map.get(name); + + for (int j = 0, k = header.size(); j < k; j++) { + String key = (String) header.get(j); + + // UnitBalance.slk doesn't define the name of one row. + if (key == null) { + key = "column" + j; + } + + mapped.put(key, row.get(j)); + } + } + } + } + } + else { + final IniFile file = new IniFile(buffer); + final Map> sections = file.sections; + + for (final Map.Entry> rowAndProperties : sections.entrySet()) { + final String row = rowAndProperties.getKey(); + + if (!this.map.containsKey(row)) { + this.map.put(row, new MappedDataRow()); + } + + final MappedDataRow mapped = this.map.get(row); + + for (final Map.Entry nameAndProperty : rowAndProperties.getValue().entrySet()) { + mapped.put(nameAndProperty.getKey(), nameAndProperty.getValue()); + } + } + } + } + + public MappedDataRow getRow(final String key) { + return this.map.get(key.toLowerCase()); + } + + public Object getProperty(final String key, final String name) { + return this.map.get(key.toLowerCase()).get(name); + } + + public void setRow(final String key, final MappedDataRow values) { + this.map.put(key.toLowerCase(), values); + } +} diff --git a/core/src/com/etheller/warsmash/util/MappedDataRow.java b/core/src/com/etheller/warsmash/util/MappedDataRow.java new file mode 100644 index 0000000..f22a20f --- /dev/null +++ b/core/src/com/etheller/warsmash/util/MappedDataRow.java @@ -0,0 +1,7 @@ +package com.etheller.warsmash.util; + +import java.util.HashMap; + +public class MappedDataRow extends HashMap { + +} diff --git a/core/src/com/etheller/warsmash/util/SlkFile.java b/core/src/com/etheller/warsmash/util/SlkFile.java new file mode 100644 index 0000000..1c37a21 --- /dev/null +++ b/core/src/com/etheller/warsmash/util/SlkFile.java @@ -0,0 +1,70 @@ +package com.etheller.warsmash.util; + +import java.util.ArrayList; +import java.util.List; + +public class SlkFile { + public List> rows; + + public SlkFile(final String buffer) { + if (buffer != null) { + this.load(buffer); + } + } + + public void load(final String buffer) { + if (!buffer.startsWith("ID")) { + throw new RuntimeException("WrongMagicNumber"); + } + + int x = 0; + int y = 0; + for (final String line : buffer.split("\n")) { + // The B command is supposed to define the total number of columns and rows, + // however in UbetSplatData.slk it gives wrong information + // Therefore, just ignore it, since JavaScript arrays grow as they want either + // way + if (line.charAt(0) != 'B') { + for (final String token : line.split(";")) { + final char op = token.charAt(0); + final String valueString = token.substring(1).trim(); + final Object value; + + if (op == 'X') { + x = Integer.parseInt(valueString, 10) - 1; + } + else if (op == 'Y') { + y = Integer.parseInt(valueString, 10) - 1; + } + else if (op == 'K') { + while (y >= this.rows.size()) { + this.rows.add(null); + } + if (this.rows.get(y) == null) { + this.rows.set(y, new ArrayList<>()); + } + + if (valueString.charAt('0') == '"') { + value = valueString.substring(1, valueString.length() - 1); + } + else if ("TRUE".equals(valueString)) { + value = true; + } + else if ("FALSE".equals(valueString)) { + value = false; + } + else { + value = Float.parseFloat(valueString); + } + + final List row = this.rows.get(y); + while (x >= row.size()) { + row.add(null); + } + row.set(x, value); + } + } + } + } + } +} diff --git a/core/src/com/etheller/warsmash/util/Test.java b/core/src/com/etheller/warsmash/util/Test.java new file mode 100644 index 0000000..8c0a983 --- /dev/null +++ b/core/src/com/etheller/warsmash/util/Test.java @@ -0,0 +1,20 @@ +package com.etheller.warsmash.util; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class Test { + + public static void main(final String[] args) { + final Pattern pattern = Pattern.compile("^\\[(.+?)\\]"); + final Matcher matcher = pattern.matcher("[boat] // ocean"); + if (matcher.matches()) { + final String name = matcher.group(1).trim().toLowerCase(); + System.out.println(name); + } + else { + System.out.println("no match"); + } + } + +} diff --git a/core/src/com/etheller/warsmash/util/WorldEditStrings.java b/core/src/com/etheller/warsmash/util/WorldEditStrings.java new file mode 100644 index 0000000..bc8eb41 --- /dev/null +++ b/core/src/com/etheller/warsmash/util/WorldEditStrings.java @@ -0,0 +1,71 @@ +package com.etheller.warsmash.util; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.MissingResourceException; +import java.util.PropertyResourceBundle; +import java.util.ResourceBundle; + +import com.etheller.warsmash.datasources.DataSource; + +public class WorldEditStrings { + private ResourceBundle bundle; + private ResourceBundle bundlegs; + + public WorldEditStrings(final DataSource dataSource) { + try (InputStream fis = dataSource.getResourceAsStream("UI\\WorldEditStrings.txt"); + InputStreamReader reader = new InputStreamReader(fis, "utf-8")) { + this.bundle = new PropertyResourceBundle(reader); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + try (InputStream fis = dataSource.getResourceAsStream("UI\\WorldEditGameStrings.txt"); + InputStreamReader reader = new InputStreamReader(fis, "utf-8")) { + this.bundlegs = new PropertyResourceBundle(reader); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + } + + public String getString(String string) { + try { + while (string.toUpperCase().startsWith("WESTRING")) { + string = internalGetString(string); + } + return string; + } + catch (final MissingResourceException exc) { + try { + return this.bundlegs.getString(string.toUpperCase()); + } + catch (final MissingResourceException exc2) { + return string; + } + } + } + + private String internalGetString(final String key) { + try { + String string = this.bundle.getString(key.toUpperCase()); + if ((string.charAt(0) == '"') && (string.length() >= 2) && (string.charAt(string.length() - 1) == '"')) { + string = string.substring(1, string.length() - 1); + } + return string; + } + catch (final MissingResourceException exc) { + return this.bundlegs.getString(key.toUpperCase()); + } + } + + public String getStringCaseSensitive(final String key) { + try { + return this.bundle.getString(key); + } + catch (final MissingResourceException exc) { + return this.bundlegs.getString(key); + } + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/AudioBufferSource.java b/core/src/com/etheller/warsmash/viewer5/AudioBufferSource.java new file mode 100644 index 0000000..7a2976f --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/AudioBufferSource.java @@ -0,0 +1,17 @@ +package com.etheller.warsmash.viewer5; + +import com.badlogic.gdx.audio.Sound; + +public class AudioBufferSource { + public Sound buffer; + + public void connect(final AudioPanner panner) { + + } + + public void start(final int value) { + if (this.buffer != null) { + this.buffer.play(1); + } + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/AudioContext.java b/core/src/com/etheller/warsmash/viewer5/AudioContext.java index 77ffc6d..77e5154 100644 --- a/core/src/com/etheller/warsmash/viewer5/AudioContext.java +++ b/core/src/com/etheller/warsmash/viewer5/AudioContext.java @@ -3,6 +3,8 @@ package com.etheller.warsmash.viewer5; public class AudioContext { private boolean running = false; public Listener listener = new Listener(); + public AudioDestination destination = new AudioDestination() { + }; public void suspend() { this.running = false; @@ -44,4 +46,22 @@ public class AudioContext { } } + + public AudioPanner createPanner() { + return new AudioPanner() { + @Override + public void setPosition(final float x, final float y, final float z) { + System.err.println("audio panner set position not implemented"); + } + + @Override + public void connect(final AudioDestination destination) { + System.err.println("audio panner connect dest not implemented"); + } + }; + } + + public AudioBufferSource createBufferSource() { + return new AudioBufferSource(); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/AudioDestination.java b/core/src/com/etheller/warsmash/viewer5/AudioDestination.java new file mode 100644 index 0000000..d028de9 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/AudioDestination.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.viewer5; + +public interface AudioDestination { + +} diff --git a/core/src/com/etheller/warsmash/viewer5/AudioPanner.java b/core/src/com/etheller/warsmash/viewer5/AudioPanner.java new file mode 100644 index 0000000..463b874 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/AudioPanner.java @@ -0,0 +1,10 @@ +package com.etheller.warsmash.viewer5; + +public abstract class AudioPanner { + public abstract void setPosition(float x, float y, float z); + + public float maxDistance; + public float refDistance; + + public abstract void connect(AudioDestination destination); +} diff --git a/core/src/com/etheller/warsmash/viewer5/EmittedObject.java b/core/src/com/etheller/warsmash/viewer5/EmittedObject.java index cd9ed80..24eec9d 100644 --- a/core/src/com/etheller/warsmash/viewer5/EmittedObject.java +++ b/core/src/com/etheller/warsmash/viewer5/EmittedObject.java @@ -7,5 +7,9 @@ public abstract class EmittedObject extends Resource { + public final HANDLER handler; + + public HandlerResource(final ModelViewer viewer, final String extension, final PathSolver pathSolver, + final String fetchUrl, final HANDLER handler) { + super(viewer, extension, pathSolver, fetchUrl); + this.handler = handler; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/Model.java b/core/src/com/etheller/warsmash/viewer5/Model.java index 8552f8a..d5aba41 100644 --- a/core/src/com/etheller/warsmash/viewer5/Model.java +++ b/core/src/com/etheller/warsmash/viewer5/Model.java @@ -6,13 +6,13 @@ import java.util.List; import com.etheller.warsmash.viewer5.handlers.ModelHandler; import com.etheller.warsmash.viewer5.handlers.ModelInstanceDescriptor; -public abstract class Model extends Resource { +public abstract class Model extends HandlerResource { public Bounds bounds; public List preloadedInstances; public Model(final HANDLER handler, final ModelViewer viewer, final String extension, final PathSolver pathSolver, final String fetchUrl) { - super(viewer, handler, extension, pathSolver, fetchUrl); + super(viewer, extension, pathSolver, fetchUrl, handler); this.bounds = new Bounds(); this.preloadedInstances = new ArrayList<>(); } diff --git a/core/src/com/etheller/warsmash/viewer5/ModelViewer.java b/core/src/com/etheller/warsmash/viewer5/ModelViewer.java index 5e60545..d80f031 100644 --- a/core/src/com/etheller/warsmash/viewer5/ModelViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/ModelViewer.java @@ -11,7 +11,10 @@ import java.util.Set; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.GL20; +import com.etheller.warsmash.common.FetchDataTypeName; +import com.etheller.warsmash.common.LoadGenericCallback; import com.etheller.warsmash.datasources.DataSource; +import com.etheller.warsmash.viewer5.gl.ClientBuffer; import com.etheller.warsmash.viewer5.gl.WebGL; import com.etheller.warsmash.viewer5.handlers.ResourceHandler; import com.etheller.warsmash.viewer5.handlers.ResourceHandlerConstructionParams; @@ -19,8 +22,8 @@ import com.etheller.warsmash.viewer5.handlers.ResourceHandlerConstructionParams; public class ModelViewer { private final DataSource dataSource; public final CanvasProvider canvas; - public List> resources; - public Map> fetchCache; + public List resources; + public Map fetchCache; public int frameTime; public GL20 gl; public WebGL webGL; @@ -30,7 +33,8 @@ public class ModelViewer { private int updatedParticles; public int frame; public final int rectBuffer; - private final boolean enableAudio; + public ClientBuffer buffer; + public boolean audioEnabled; private final Map> textureMappers; private final Set handlers; @@ -51,6 +55,7 @@ public class ModelViewer { this.frame = 0; this.rectBuffer = this.gl.glGenBuffer(); + this.buffer = new ClientBuffer(this.gl); this.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.rectBuffer); final ByteBuffer temp = ByteBuffer.allocate(6); temp.put((byte) 0); @@ -61,10 +66,15 @@ public class ModelViewer { temp.put((byte) 3); temp.clear(); this.gl.glBufferData(GL20.GL_ARRAY_BUFFER, temp.capacity(), temp, GL20.GL_STATIC_DRAW); - this.enableAudio = false; + this.audioEnabled = false; this.textureMappers = new HashMap>(); } + public boolean enableAudio() { + this.audioEnabled = true; + return this.audioEnabled; + } + public boolean addHandler(ResourceHandler handler) { if (handler != null) { @@ -115,7 +125,7 @@ public class ModelViewer { return null; } - public Resource load(final String src, final PathSolver pathSolver, final Object solverParams) { + public Resource load(final String src, final PathSolver pathSolver, final Object solverParams) { String finalSrc = src; String extension = ""; boolean isFetch = false; @@ -139,7 +149,7 @@ public class ModelViewer { // Is there a handler for this file type? if (handlerAndDataType != null) { if (isFetch) { - final Resource resource = this.fetchCache.get(finalSrc); + final Resource resource = this.fetchCache.get(finalSrc); if (resource != null) { return resource; @@ -147,7 +157,7 @@ public class ModelViewer { } final ResourceHandler handler = (ResourceHandler) handlerAndDataType[0]; - final Resource resource = handler.construct(new ResourceHandlerConstructionParams(this, handler, + final Resource resource = handler.construct(new ResourceHandlerConstructionParams(this, handler, extension, pathSolver, isFetch ? finalSrc : "")); this.resources.add(resource); @@ -179,10 +189,55 @@ public class ModelViewer { return this.fetchCache.containsKey(key); } - public Resource get(final String key) { + public Resource get(final String key) { return this.fetchCache.get(key); } + /** + * Load something generic. + * + * Unlike load(), this does not use handlers or construct any internal objects. + * + * `dataType` can be one of: `"image"`, `"string"`, `"arrayBuffer"`, `"blob"`. + * + * If `callback` isn't given, the resource's `data` is the fetch data, according + * to `dataType`. + * + * If `callback` is given, the resource's `data` is the value returned by it + * when called with the fetch data. + * + * If `callback` returns a promise, the resource's `data` will be whatever the + * promise resolved to. + */ + public GenericResource loadGeneric(final String path, final FetchDataTypeName dataType, + final LoadGenericCallback callback) { + final Resource cachedResource = this.fetchCache.get(path); + + if (cachedResource != null) { + // Technically also non-generic resources can be returned here, since the fetch + // cache is shared. + // That being said, this should be used for generic resources, and it makes the + // typing a lot easier. + return (GenericResource) cachedResource; + } + + final GenericResource resource = new GenericResource(this, null, null, path, callback); + + this.resources.add(resource); + this.fetchCache.put(path, resource); + + // TODO this is a synchronous hack, skipped some Ghostwolf code + try { + resource.loadData(this.dataSource.getResourceAsStream(path), null); + } + catch (final IOException e) { + throw new IllegalStateException("Unable to load data: " + path); + } + + return resource; + + } + public void updateAndRender() { this.update(); this.startFrame(); @@ -191,7 +246,7 @@ public class ModelViewer { // public Resource loadGeneric(String path, String dataType, ) - public boolean unload(final Resource resource) { + public boolean unload(final Resource resource) { // TODO Auto-generated method stub final String fetchUrl = resource.fetchUrl; if (!"".equals(fetchUrl)) { diff --git a/core/src/com/etheller/warsmash/viewer5/Resource.java b/core/src/com/etheller/warsmash/viewer5/Resource.java index 7016b3a..cc9f619 100644 --- a/core/src/com/etheller/warsmash/viewer5/Resource.java +++ b/core/src/com/etheller/warsmash/viewer5/Resource.java @@ -2,11 +2,8 @@ package com.etheller.warsmash.viewer5; import java.io.InputStream; -import com.etheller.warsmash.viewer5.handlers.ResourceHandler; - -public abstract class Resource { +public abstract class Resource { public final ModelViewer viewer; - public final HANDLER handler; public final String extension; public final String fetchUrl; public boolean ok; @@ -14,10 +11,9 @@ public abstract class Resource { public final PathSolver pathSolver; public final Object solverParams = null; - public Resource(final ModelViewer viewer, final HANDLER handler, final String extension, - final PathSolver pathSolver, final String fetchUrl) { + public Resource(final ModelViewer viewer, final String extension, final PathSolver pathSolver, + final String fetchUrl) { this.viewer = viewer; - this.handler = handler; this.extension = extension; this.pathSolver = pathSolver; this.fetchUrl = fetchUrl; diff --git a/core/src/com/etheller/warsmash/viewer5/Scene.java b/core/src/com/etheller/warsmash/viewer5/Scene.java index 44bb954..b23c227 100644 --- a/core/src/com/etheller/warsmash/viewer5/Scene.java +++ b/core/src/com/etheller/warsmash/viewer5/Scene.java @@ -35,8 +35,8 @@ public class Scene { public int visibleCells; public int visibleInstances; public int updatedParticles; - private boolean audioEnabled; - private AudioContext audioContext; + public boolean audioEnabled; + public AudioContext audioContext; private final List instances; private final int currentInstance; diff --git a/core/src/com/etheller/warsmash/viewer5/Texture.java b/core/src/com/etheller/warsmash/viewer5/Texture.java index 60b9002..e171ae7 100644 --- a/core/src/com/etheller/warsmash/viewer5/Texture.java +++ b/core/src/com/etheller/warsmash/viewer5/Texture.java @@ -2,12 +2,12 @@ package com.etheller.warsmash.viewer5; import com.etheller.warsmash.viewer5.handlers.ResourceHandler; -public abstract class Texture extends Resource { +public abstract class Texture extends HandlerResource { private com.badlogic.gdx.graphics.Texture gdxTexture; public Texture(final ModelViewer viewer, final ResourceHandler handler, final String extension, final PathSolver pathSolver, final String fetchUrl) { - super(viewer, handler, extension, pathSolver, fetchUrl); + super(viewer, extension, pathSolver, fetchUrl, handler); } public void setGdxTexture(final com.badlogic.gdx.graphics.Texture gdxTexture) { diff --git a/core/src/com/etheller/warsmash/viewer5/gl/ClientBuffer.java b/core/src/com/etheller/warsmash/viewer5/gl/ClientBuffer.java new file mode 100644 index 0000000..ce16b5f --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/gl/ClientBuffer.java @@ -0,0 +1,54 @@ +package com.etheller.warsmash.viewer5.gl; + +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; + +import com.badlogic.gdx.graphics.GL20; + +public class ClientBuffer { + private final GL20 gl; + private final int buffer; + private int size; + private ByteBuffer arrayBuffer; + public ByteBuffer byteView; + public FloatBuffer floatView; + + public ClientBuffer(final GL20 gl) { + this(gl, 4); + } + + public ClientBuffer(final GL20 gl, final int size) { + this.gl = gl; + this.buffer = gl.glGenBuffer(); + this.arrayBuffer = null; + + this.reserve(size); + } + + public void reserve(final int size) { + if (this.size < size) { + + // Ensure the size is on a 4 byte boundary. + this.size = (int) Math.ceil(size / 4.) * 4; + + this.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.buffer); + + this.arrayBuffer = ByteBuffer.allocate(this.size); + this.gl.glBufferData(GL20.GL_ARRAY_BUFFER, this.size, this.arrayBuffer, GL20.GL_DYNAMIC_DRAW); + this.byteView = this.arrayBuffer; + this.floatView = this.arrayBuffer.asFloatBuffer(); + + } + } + + public void bindAndUpdate() { + bindAndUpdate(this.size); + } + + public void bindAndUpdate(final int size) { + final GL20 gl = this.gl; + + gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.buffer); + gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, 0, size, this.byteView); + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/ResourceHandler.java b/core/src/com/etheller/warsmash/viewer5/handlers/ResourceHandler.java index 0aa0061..2571736 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/ResourceHandler.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/ResourceHandler.java @@ -3,7 +3,7 @@ package com.etheller.warsmash.viewer5.handlers; import java.util.List; import com.etheller.warsmash.viewer5.ModelViewer; -import com.etheller.warsmash.viewer5.Resource; +import com.etheller.warsmash.viewer5.HandlerResource; public abstract class ResourceHandler { public ResourceHandler handler; @@ -12,5 +12,5 @@ public abstract class ResourceHandler { public abstract boolean load(ModelViewer modelViewer); - public abstract Resource construct(ResourceHandlerConstructionParams params); + public abstract HandlerResource construct(ResourceHandlerConstructionParams params); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/blp/BlpHandler.java b/core/src/com/etheller/warsmash/viewer5/handlers/blp/BlpHandler.java index 632d9c4..478871f 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/blp/BlpHandler.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/blp/BlpHandler.java @@ -3,7 +3,7 @@ package com.etheller.warsmash.viewer5.handlers.blp; import java.util.ArrayList; import com.etheller.warsmash.viewer5.ModelViewer; -import com.etheller.warsmash.viewer5.Resource; +import com.etheller.warsmash.viewer5.HandlerResource; import com.etheller.warsmash.viewer5.handlers.ResourceHandler; import com.etheller.warsmash.viewer5.handlers.ResourceHandlerConstructionParams; @@ -20,7 +20,7 @@ public class BlpHandler extends ResourceHandler { } @Override - public Resource construct(final ResourceHandlerConstructionParams params) { + public HandlerResource construct(final ResourceHandlerConstructionParams params) { return new BlpTexture(params.getViewer(), params.getHandler(), params.getExtension(), params.getPathSolver(), params.getFetchUrl()); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/AnimatedObject.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/AnimatedObject.java index 12ab2ee..6a43a20 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/AnimatedObject.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/AnimatedObject.java @@ -12,22 +12,26 @@ import com.etheller.warsmash.util.War3ID; public class AnimatedObject { public MdxModel model; public Map> timelines; + public Map variants; public AnimatedObject(final MdxModel model, final com.etheller.warsmash.parsers.mdlx.AnimatedObject object) { this.model = model; this.timelines = new HashMap<>(); + this.variants = new HashMap<>(); for (final Timeline timeline : object.getTimelines()) { this.timelines.put(timeline.getName(), createTypedSd(model, timeline)); } } - public int getScalarValue(final float[] out, final War3ID name, final MdxComplexInstance instance, - final float defaultValue) { - final Sd animation = this.timelines.get(name); + public int getScalarValue(final float[] out, final War3ID name, final int sequence, final int frame, + final int counter, final float defaultValue) { + if (sequence != -1) { + final Sd animation = this.timelines.get(name); - if (animation instanceof ScalarSd) { - return ((ScalarSd) animation).getValue(out, instance); + if (animation instanceof ScalarSd) { + return ((ScalarSd) animation).getValue(out, sequence, frame, counter); + } } out[0] = defaultValue; @@ -35,12 +39,14 @@ public class AnimatedObject { return -1; } - public int getScalarValue(final long[] out, final War3ID name, final MdxComplexInstance instance, - final long defaultValue) { - final Sd animation = this.timelines.get(name); + public int getScalarValue(final long[] out, final War3ID name, final int sequence, final int frame, + final int counter, final long defaultValue) { + if (sequence != -1) { + final Sd animation = this.timelines.get(name); - if (animation instanceof UInt32Sd) { - return ((UInt32Sd) animation).getValue(out, instance); + if (animation instanceof UInt32Sd) { + return ((UInt32Sd) animation).getValue(out, sequence, frame, counter); + } } out[0] = defaultValue; @@ -48,12 +54,14 @@ public class AnimatedObject { return -1; } - public int getVectorValue(final float[] out, final War3ID name, final MdxComplexInstance instance, - final float[] defaultValue) { - final Sd animation = this.timelines.get(name); + public int getVectorValue(final float[] out, final War3ID name, final int sequence, final int frame, + final int counter, final float[] defaultValue) { + if (sequence != -1) { + final Sd animation = this.timelines.get(name); - if (animation instanceof VectorSd) { - return ((VectorSd) animation).getValue(out, instance); + if (animation instanceof VectorSd) { + return ((VectorSd) animation).getValue(out, sequence, frame, counter); + } } System.arraycopy(defaultValue, 0, out, 0, 3); @@ -61,12 +69,14 @@ public class AnimatedObject { return -1; } - public int getQuadValue(final float[] out, final War3ID name, final MdxComplexInstance instance, - final float[] defaultValue) { - final Sd animation = this.timelines.get(name); + public int getQuadValue(final float[] out, final War3ID name, final int sequence, final int frame, + final int counter, final float[] defaultValue) { + if (sequence != -1) { + final Sd animation = this.timelines.get(name); - if (animation instanceof QuaternionSd) { - return ((QuaternionSd) animation).getValue(out, instance); + if (animation instanceof QuaternionSd) { + return ((QuaternionSd) animation).getValue(out, sequence, frame, counter); + } } System.arraycopy(defaultValue, 0, out, 0, 4); @@ -74,6 +84,38 @@ public class AnimatedObject { return -1; } + public void addVariants(final War3ID name, final String variantName) { + final Sd timeline = this.timelines.get(name); + final int sequences = this.model.getSequences().size(); + final byte[] variants = new byte[sequences]; + + if (timeline != null) { + for (int i = 0; i < sequences; i++) { + if (timeline.isVariant(i)) { + variants[i] = 1; + } + } + } + + this.variants.put(variantName, variants); + } + + public void addVariantIntersection(final String[] names, final String variantName) { + final int sequences = this.model.getSequences().size(); + final byte[] variants = new byte[sequences]; + + for (int i = 0; i < sequences; i++) { + for (final String name : names) { + final byte[] variantsAtName = this.variants.get(name); + if ((variantsAtName != null) && (variantsAtName[i] != 0)) { + variants[i] = 1; + } + } + } + + this.variants.put(variantName, variants); + } + public boolean isVariant(final War3ID name, final int sequence) { final Sd timeline = this.timelines.get(name); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Attachment.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Attachment.java index 705335b..0bb591f 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Attachment.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Attachment.java @@ -24,7 +24,7 @@ public class Attachment extends GenericObject { } @Override - public int getVisibility(final float[] out, final MdxComplexInstance instance) { - return this.getScalarValue(out, AnimationMap.KATV.getWar3id(), instance, 1); + public int getVisibility(final float[] out, final int sequence, final int frame, final int counter) { + return this.getScalarValue(out, AnimationMap.KATV.getWar3id(), sequence, frame, counter, 1); } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/AttachmentInstance.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/AttachmentInstance.java index f8e8b3b..9588b85 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/AttachmentInstance.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/AttachmentInstance.java @@ -25,7 +25,8 @@ public class AttachmentInstance { final MdxComplexInstance internalInstance = this.internalInstance; if (internalInstance.model.ok) { - this.attachment.getVisibility(visbilityHeap, this.instance); + this.attachment.getVisibility(visbilityHeap, this.instance.sequence, this.instance.frame, + this.instance.counter); if (visbilityHeap[0] > 0.1) { // The parent instance might not actually be in a scene. diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Batch.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Batch.java index 930b5b3..02dc3b1 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Batch.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Batch.java @@ -1,6 +1,6 @@ package com.etheller.warsmash.viewer5.handlers.mdx; -public class Batch { +public class Batch implements GenericIndexed { public int index; public Geoset geoset; public Layer layer; @@ -13,4 +13,9 @@ public class Batch { this.isExtended = isExtended; } + @Override + public int getIndex() { + return this.index; + } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/BatchGroup.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/BatchGroup.java index de99033..35e9e6a 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/BatchGroup.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/BatchGroup.java @@ -1,6 +1,5 @@ package com.etheller.warsmash.viewer5.handlers.mdx; -import java.util.ArrayList; import java.util.List; import com.badlogic.gdx.graphics.GL20; @@ -9,16 +8,14 @@ import com.etheller.warsmash.viewer5.ModelViewer; import com.etheller.warsmash.viewer5.Scene; import com.etheller.warsmash.viewer5.Texture; -public class BatchGroup { +public class BatchGroup extends GenericGroup { private final MdxModel model; - private final boolean isExtended; - private final List objects; + public final boolean isExtended; public BatchGroup(final MdxModel model, final boolean isExtended) { this.model = model; this.isExtended = isExtended; - this.objects = new ArrayList<>(); // TODO IntArrayList } public void render(final MdxComplexInstance instance) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Bone.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Bone.java index e3b7f19..10167f6 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Bone.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Bone.java @@ -11,9 +11,9 @@ public class Bone extends GenericObject { } @Override - public int getVisibility(final float[] out, final MdxComplexInstance instance) { + public int getVisibility(final float[] out, final int sequence, final int frame, final int counter) { if (this.geosetAnimation != null) { - return this.geosetAnimation.getAlpha(out, instance); + return this.geosetAnimation.getAlpha(out, sequence, frame, counter); } out[0] = 1; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Camera.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Camera.java index 2dd4db7..d074e4b 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Camera.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Camera.java @@ -22,16 +22,16 @@ public class Camera extends AnimatedObject { this.targetPosition = camera.getTargetPosition(); } - public int getPositionTranslation(final float[] out, final MdxComplexInstance instance) { - return this.getVectorValue(out, AnimationMap.KCTR.getWar3id(), instance, this.position); + public int getPositionTranslation(final float[] out, final int sequence, final int frame, final int counter) { + return this.getVectorValue(out, AnimationMap.KCTR.getWar3id(), sequence, frame, counter, this.position); } - public int getTargetTranslation(final float[] out, final MdxComplexInstance instance) { - return this.getVectorValue(out, AnimationMap.KTTR.getWar3id(), instance, this.targetPosition); + public int getTargetTranslation(final float[] out, final int sequence, final int frame, final int counter) { + return this.getVectorValue(out, AnimationMap.KTTR.getWar3id(), sequence, frame, counter, this.targetPosition); } - public int getRotation(final float[] out, final MdxComplexInstance instance) { - return this.getScalarValue(out, AnimationMap.KCRL.getWar3id(), instance, 0); + public int getRotation(final float[] out, final int sequence, final int frame, final int counter) { + return this.getScalarValue(out, AnimationMap.KCRL.getWar3id(), sequence, frame, counter, 0); } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EmitterGroup.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EmitterGroup.java index 9e6088b..af7e109 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EmitterGroup.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EmitterGroup.java @@ -1,8 +1,5 @@ package com.etheller.warsmash.viewer5.handlers.mdx; -import java.util.ArrayList; -import java.util.List; - import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.glutils.ShaderProgram; @@ -10,15 +7,12 @@ import com.etheller.warsmash.viewer5.Model; import com.etheller.warsmash.viewer5.ModelViewer; import com.etheller.warsmash.viewer5.Scene; import com.etheller.warsmash.viewer5.SkeletalNode; -import com.etheller.warsmash.viewer5.handlers.EmitterObject; -public class EmitterGroup { +public class EmitterGroup extends GenericGroup { private final MdxModel model; - private final List objects; public EmitterGroup(final MdxModel model) { this.model = model; - this.objects = new ArrayList<>(); } public void render(final MdxComplexInstance instance) { @@ -55,10 +49,17 @@ public class EmitterGroup { Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_leftRightTop"), 1); for (final int index : this.objects) { - + GeometryEmitterFuncs.renderEmitter((MdxEmitter) nodes[index].object, shader); } - } + Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_leftRightTop"), 0); + Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_tail"), 0); + Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_color"), 0); + Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_health"), 0); + Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_p3"), 0); + Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_p2"), 0); + Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_p1"), 0); + Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_p0"), 0); - protected abstract void renderEmitter(EmitterObject emitter, ShaderProgram shader); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectEmitter.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectEmitter.java index 34fe433..16fabda 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectEmitter.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectEmitter.java @@ -1,11 +1,12 @@ package com.etheller.warsmash.viewer5.handlers.mdx; import com.etheller.warsmash.viewer5.EmittedObject; -import com.etheller.warsmash.viewer5.handlers.EmitterObject; public abstract class EventObjectEmitter>> extends MdxEmitter { - private final int number = 0; + private static final long[] valueHeap = { 0L }; + + private long lastValue = 0; public EventObjectEmitter(final MdxComplexInstance instance, final EMITTER_OBJECT emitterObject) { super(instance, emitterObject); @@ -18,7 +19,15 @@ public abstract class EventObjectEmitter decodedBuffers; /** * If this is an SPL/UBR emitter object, ok will be set to true if the tables * are loaded. @@ -38,7 +89,7 @@ public abstract class EventObjectEmitterObject extends GenericObject implements * * The particles will simply be black. */ - private final boolean ok = false; + private boolean ok = false; public EventObjectEmitterObject(final MdxModel model, final com.etheller.warsmash.parsers.mdlx.EventObject eventObject, final int index) { @@ -64,6 +115,189 @@ public abstract class EventObjectEmitterObject extends GenericObject implements this.type = type; this.id = id; this.keyFrames = eventObject.getKeyFrames(); + + final int globalSequenceId = eventObject.getGlobalSequenceId(); + if (globalSequenceId != -1) { + this.globalSequence = model.getGlobalSequences().get(globalSequenceId); + } + + final List tables = new ArrayList<>(); + final PathSolver pathSolver = model.pathSolver; + final Object solverParams = model.solverParams; + + if ("SPN".equals(type)) { + tables.add(viewer.loadGeneric(pathSolver.solve("Splats\\SpawnData.slk", solverParams).finalSrc, + FetchDataTypeName.SLK, mappedDataCallback)); + } + else if ("SPL".equals(type)) { + tables.add(viewer.loadGeneric(pathSolver.solve("Splats\\SpawnData.slk", solverParams).finalSrc, + FetchDataTypeName.SLK, mappedDataCallback)); + } + else if ("UBR".equals(type)) { + tables.add(viewer.loadGeneric(pathSolver.solve("Splats\\UberSplatData.slk", solverParams).finalSrc, + FetchDataTypeName.SLK, mappedDataCallback)); + } + else if ("SND".equals(type)) { + if (!model.reforged) { + tables.add(viewer.loadGeneric(pathSolver.solve("UI\\SoundInfo\\AnimLookups.slk", solverParams).finalSrc, + FetchDataTypeName.SLK, mappedDataCallback)); + } + tables.add(viewer.loadGeneric(pathSolver.solve("UI\\SoundInfo\\AnimSounds.slk", solverParams).finalSrc, + FetchDataTypeName.SLK, mappedDataCallback)); + } + else { + // Units\Critters\BlackStagMale\BlackStagMale.mdx has an event object named + // "Point01". + return; + } + + // TODO I am scrapping some async stuff with promises here from the JS and + // calling load + this.load(tables); + } + + private void load(final List tables) { + final MappedData firstTable = (MappedData) tables.get(0).data; + final MappedDataRow row = firstTable.getRow(this.id); + + if (row != null) { + final MdxModel model = this.model; + final ModelViewer viewer = model.viewer; + final PathSolver pathSolver = model.pathSolver; + + if ("SPN".equals(this.type)) { + this.internalModel = (MdxModel) viewer.load(((String) row.get("Model")).replace(".mdl", ".mdx"), + pathSolver, model.solverParams); + + if (this.internalModel != null) { + // TODO javascript async code removed here +// this.internalModel.whenLoaded((model) => this.ok = model.ok) + this.ok = this.internalModel.ok; + } + } + else if ("SPL".equals(this.type) || "UBR".equals(this.type)) { + final String texturesExt = model.reforged ? ".dds" : ".blp"; + + this.internalTexture = (Texture) viewer.load( + "ReplaceableTextures\\Splats\\" + row.get("file") + texturesExt, pathSolver, + model.solverParams); + + this.scale = (Float) row.get("Scale"); + this.colors = new float[][] { + { ((Float) row.get("StartR")).floatValue(), ((Float) row.get("StartG")).floatValue(), + ((Float) row.get("StartB")).floatValue(), ((Float) row.get("StartA")).floatValue() }, + { ((Float) row.get("MiddleR")).floatValue(), ((Float) row.get("MiddleG")).floatValue(), + ((Float) row.get("MiddleB")).floatValue(), ((Float) row.get("MiddleA")).floatValue() }, + { ((Float) row.get("EndR")).floatValue(), ((Float) row.get("EndG")).floatValue(), + ((Float) row.get("EndB")).floatValue(), ((Float) row.get("EndA")).floatValue() } }; + + if ("SPL".equals(this.type)) { + this.columns = ((Number) row.get("Columns")).intValue(); + this.rows = ((Number) row.get("Rows")).intValue(); + this.lifeSpan = ((Number) row.get("Lifespan")).floatValue() + + ((Number) row.get("Decay")).floatValue(); + this.intervals = new float[][] { + { ((Float) row.get("UVLifespanStart")).floatValue(), + ((Float) row.get("UVLifespanEnd")).floatValue(), + ((Float) row.get("LifespanRepeat")).floatValue() }, + { ((Float) row.get("UVDecayStart")).floatValue(), + ((Float) row.get("UVDecayEnd")).floatValue(), + ((Float) row.get("DecayRepeat")).floatValue() }, }; + } + else { + this.columns = 1; + this.rows = 1; + this.lifeSpan = ((Number) row.get("BirthTime")).floatValue() + + ((Number) row.get("PauseTime")).floatValue() + ((Number) row.get("Decay")).floatValue(); + this.intervalTimes = new float[] { ((Number) row.get("BirthTime")).floatValue(), + ((Number) row.get("PauseTime")).floatValue(), ((Number) row.get("Decay")).floatValue() }; + } + + final int[] blendModes = FilterMode + .emitterFilterMode(com.etheller.warsmash.parsers.mdlx.ParticleEmitter2.FilterMode + .fromId(((Number) row.get("BlendMode")).intValue())); + + this.blendSrc = blendModes[0]; + this.blendDst = blendModes[1]; + + this.ok = true; + } + else if ("SND".equals(this.type)) { + // Only load sounds if audio is enabled. + // This is mostly to save on bandwidth and loading time, especially when loading + // full maps. + if (viewer.audioEnabled) { + final MappedData animSounds = (MappedData) tables.get(1).data; + + final MappedDataRow animSoundsRow = animSounds.getRow((String) row.get("SoundLabel")); + + if (animSoundsRow != null) { + this.distanceCutoff = ((Number) animSoundsRow.get("DistanceCutoff")).floatValue(); + this.maxDistance = ((Number) animSoundsRow.get("MaxDistance")).floatValue(); + this.minDistance = ((Number) animSoundsRow.get("MinDistance")).floatValue(); + this.pitch = ((Number) animSoundsRow.get("Pitch")).floatValue(); + this.pitchVariance = ((Number) animSoundsRow.get("PitchVariance")).floatValue(); + this.volume = ((Number) animSoundsRow.get("Volume")).floatValue(); + + final String[] fileNames = ((String) animSoundsRow.get("FileNames")).split(","); + final GenericResource[] resources = new GenericResource[fileNames.length]; + for (int i = 0; i < fileNames.length; i++) { + resources[i] = viewer.loadGeneric( + pathSolver.solve(((String) animSoundsRow.get("DirectoryBase")) + fileNames[i], + model.solverParams).finalSrc, + FetchDataTypeName.ARRAY_BUFFER, decodedDataCallback); + } + + // TODO JS async removed + for (final GenericResource resource : resources) { + this.decodedBuffers.add((Sound) resource.data); + } + this.ok = true; + } + } + } + else { + System.err.println("Unknown event object ID: " + this.type + this.id); + } + } + } + + public int getValue(final long[] out, final MdxComplexInstance instance) { + if (this.globalSequence != -1) { + + return this.getValueAtTime(out, instance.counter % this.globalSequence, 0, this.globalSequence); + } + else if (instance.sequence != -1) { + final long[] interval = this.model.getSequences().get(instance.sequence).getInterval(); + + return this.getValueAtTime(out, instance.frame, interval[0], interval[1]); + } + else { + out[0] = this.defval[0]; + + return -1; + } + } + + public int getValueAtTime(final long[] out, final long frame, final long start, final long end) { + if ((frame >= start) && (frame <= end)) { + for (int i = this.keyFrames.length - 1; i > -1; i--) { + if (this.keyFrames[i] < start) { + out[0] = 0; + + return i; + } + else if (this.keyFrames[i] <= frame) { + out[0] = 1; + + return i; + } + } + } + + out[0] = 0; + + return -1; } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectSnd.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectSnd.java new file mode 100644 index 0000000..f7bf38a --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectSnd.java @@ -0,0 +1,55 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +import java.util.List; + +import com.badlogic.gdx.audio.Sound; +import com.badlogic.gdx.math.Vector3; +import com.etheller.warsmash.viewer5.AudioBufferSource; +import com.etheller.warsmash.viewer5.AudioContext; +import com.etheller.warsmash.viewer5.AudioPanner; +import com.etheller.warsmash.viewer5.EmittedObject; +import com.etheller.warsmash.viewer5.ModelViewer; +import com.etheller.warsmash.viewer5.Scene; + +public class EventObjectSnd extends EmittedObject { + public EventObjectSnd(final EventObjectSndEmitter emitter) { + super(emitter); + } + + @Override + protected void bind(final int flags) { + final EventObjectSndEmitter emitter = this.emitter; + final MdxComplexInstance instance = emitter.instance; + final ModelViewer viewer = instance.model.viewer; + final Scene scene = instance.scene; + + // Is audio enabled both viewer-wide and in this scene? + if (viewer.audioEnabled && scene.audioEnabled) { + final EventObjectEmitterObject emitterObject = emitter.emitterObject; + final MdxNode node = instance.nodes[emitterObject.index]; + final AudioContext audioContext = scene.audioContext; + final List decodedBuffers = emitterObject.decodedBuffers; + final AudioPanner panner = audioContext.createPanner(); + final AudioBufferSource source = audioContext.createBufferSource(); + final Vector3 location = node.worldLocation; + + // Panner settings + panner.setPosition(location.x, location.y, location.z); + panner.maxDistance = emitterObject.distanceCutoff; + panner.refDistance = emitterObject.minDistance; + panner.connect(audioContext.destination); + + // Source. + source.buffer = decodedBuffers.get((int) (Math.random() * decodedBuffers.size())); + source.connect(panner); + + // Make a sound. + source.start(0); + } + } + + @Override + public void update(final float dt) { + + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectSndEmitter.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectSndEmitter.java new file mode 100644 index 0000000..6eba361 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectSndEmitter.java @@ -0,0 +1,14 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +public class EventObjectSndEmitter extends EventObjectEmitter { + + public EventObjectSndEmitter(final MdxComplexInstance instance, final EventObjectEmitterObject emitterObject) { + super(instance, emitterObject); + } + + @Override + protected EventObjectSnd createObject() { + return new EventObjectSnd(this); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectSplEmitter.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectSplEmitter.java new file mode 100644 index 0000000..e112023 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectSplEmitter.java @@ -0,0 +1,12 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +public class EventObjectSplEmitter extends EventObjectEmitter { + public EventObjectSplEmitter(final MdxComplexInstance instance, final EventObjectEmitterObject emitterObject) { + super(instance, emitterObject); + } + + @Override + protected EventObjectSplUbr createObject() { + return new EventObjectSplUbr(this); + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectSplUbr.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectSplUbr.java index d268c3c..ac7b8d7 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectSplUbr.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectSplUbr.java @@ -1,12 +1,63 @@ package com.etheller.warsmash.viewer5.handlers.mdx; +import com.badlogic.gdx.math.Matrix4; +import com.badlogic.gdx.math.Vector3; import com.etheller.warsmash.viewer5.EmittedObject; -public class EventObjectSplUbr> extends EmittedObject { - private final float[] vertices = new float[12]; +public class EventObjectSplUbr + extends EmittedObject> { + private static final Vector3 vertexHeap = new Vector3(); + + public final float[] vertices = new float[12]; + + public EventObjectSplUbr(final EventObjectEmitter emitter) { + super(emitter); + } @Override protected void bind(final int flags) { + final EventObjectEmitter emitter = this.emitter; + final MdxComplexInstance instance = emitter.instance; + final EventObjectEmitterObject emitterObject = emitter.emitterObject; + final float[] vertices = this.vertices; + final float scale = emitterObject.scale; + final MdxNode node = instance.nodes[emitterObject.index]; + final Matrix4 worldMatrix = node.worldMatrix; + + this.health = emitterObject.lifeSpan; + + vertexHeap.x = scale; + vertexHeap.y = scale; + vertexHeap.prj(worldMatrix); + vertices[0] = vertexHeap.x; + vertices[1] = vertexHeap.y; + vertices[2] = vertexHeap.z; + + vertexHeap.x = -scale; + vertexHeap.y = scale; + vertexHeap.prj(worldMatrix); + vertices[3] = vertexHeap.x; + vertices[4] = vertexHeap.y; + vertices[5] = vertexHeap.z; + + vertexHeap.x = -scale; + vertexHeap.y = -scale; + vertexHeap.prj(worldMatrix); + vertices[6] = vertexHeap.x; + vertices[7] = vertexHeap.y; + vertices[8] = vertexHeap.z; + + vertexHeap.x = scale; + vertexHeap.y = -scale; + vertexHeap.prj(worldMatrix); + vertices[9] = vertexHeap.x; + vertices[10] = vertexHeap.y; + vertices[11] = vertexHeap.z; } + + @Override + public void update(final float dt) { + this.health -= dt; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectSpn.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectSpn.java new file mode 100644 index 0000000..d59b650 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectSpn.java @@ -0,0 +1,48 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +import com.etheller.warsmash.viewer5.EmittedObject; +import com.etheller.warsmash.viewer5.Scene; + +public class EventObjectSpn extends EmittedObject { + private final MdxComplexInstance internalInstance; + + public EventObjectSpn(final EventObjectSpnEmitter emitter) { + super(emitter); + + final EventObjectEmitterObject emitterObject = emitter.emitterObject; + final MdxModel internalModel = emitterObject.internalModel; + + this.internalInstance = (MdxComplexInstance) internalModel.addInstance(); + } + + @Override + protected void bind(final int flags) { + final EventObjectSpnEmitter emitter = this.emitter; + final MdxComplexInstance instance = emitter.instance; + final Scene scene = instance.scene; + final MdxNode node = instance.nodes[emitter.emitterObject.index]; + final MdxComplexInstance internalInstance = this.internalInstance; + + internalInstance.setSequence(0); + internalInstance.setTransformation(node.worldLocation, node.worldRotation, node.worldScale); + internalInstance.show(); + + scene.addInstance(internalInstance); + + this.health = 1; + } + + @Override + public void update(final float dt) { + final MdxComplexInstance instance = this.internalInstance; + final MdxModel model = (MdxModel) instance.model; + + // Once the sequence finishes, this event object dies + if (instance.frame >= model.getSequences().get(0).getInterval()[1]) { + this.health = 0; + + instance.hide(); + } + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectSpnEmitter.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectSpnEmitter.java new file mode 100644 index 0000000..786e5ba --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectSpnEmitter.java @@ -0,0 +1,14 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +public class EventObjectSpnEmitter extends EventObjectEmitter { + + public EventObjectSpnEmitter(final MdxComplexInstance instance, final EventObjectEmitterObject emitterObject) { + super(instance, emitterObject); + } + + @Override + protected EventObjectSpn createObject() { + return new EventObjectSpn(this); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectUbrEmitter.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectUbrEmitter.java new file mode 100644 index 0000000..7589e78 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectUbrEmitter.java @@ -0,0 +1,12 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +public class EventObjectUbrEmitter extends EventObjectEmitter { + public EventObjectUbrEmitter(final MdxComplexInstance instance, final EventObjectEmitterObject emitterObject) { + super(instance, emitterObject); + } + + @Override + protected EventObjectSplUbr createObject() { + return new EventObjectSplUbr(this); + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GenericGroup.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GenericGroup.java new file mode 100644 index 0000000..e4393af --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GenericGroup.java @@ -0,0 +1,13 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +import java.util.ArrayList; +import java.util.List; + +public class GenericGroup { + public final List objects; + + public GenericGroup() { + this.objects = new ArrayList<>(); // TODO IntArrayList + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GenericIndexed.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GenericIndexed.java new file mode 100644 index 0000000..f5151a5 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GenericIndexed.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +public interface GenericIndexed { + public int getIndex(); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GenericObject.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GenericObject.java index 71f02c1..5ca6b42 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GenericObject.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GenericObject.java @@ -3,7 +3,7 @@ package com.etheller.warsmash.viewer5.handlers.mdx; import com.etheller.warsmash.parsers.mdlx.AnimationMap; import com.etheller.warsmash.util.RenderMathUtils; -public class GenericObject extends AnimatedObject { +public class GenericObject extends AnimatedObject implements GenericIndexed { public final int index; public final String name; @@ -113,21 +113,24 @@ public class GenericObject extends AnimatedObject { * Many of the generic objects have animated visibilities. This is a generic * getter to allow the code to be consistent. */ - public int getVisibility(final float[] out, final MdxComplexInstance instance) { + public int getVisibility(final float[] out, final int sequence, final int frame, final int counter) { out[0] = 1; return -1; } - public int getTranslation(final float[] out, final MdxComplexInstance instance) { - return this.getVectorValue(out, AnimationMap.KGTR.getWar3id(), instance, RenderMathUtils.FLOAT_VEC3_ZERO); + public int getTranslation(final float[] out, final int sequence, final int frame, final int counter) { + return this.getVectorValue(out, AnimationMap.KGTR.getWar3id(), sequence, frame, counter, + RenderMathUtils.FLOAT_VEC3_ZERO); } - public int getRotation(final float[] out, final MdxComplexInstance instance) { - return this.getQuadValue(out, AnimationMap.KGRT.getWar3id(), instance, RenderMathUtils.FLOAT_QUAT_DEFAULT); + public int getRotation(final float[] out, final int sequence, final int frame, final int counter) { + return this.getQuadValue(out, AnimationMap.KGRT.getWar3id(), sequence, frame, counter, + RenderMathUtils.FLOAT_QUAT_DEFAULT); } - public int getScale(final float[] out, final MdxComplexInstance instance) { - return this.getVectorValue(out, AnimationMap.KGSC.getWar3id(), instance, RenderMathUtils.FLOAT_VEC3_ONE); + public int getScale(final float[] out, final int sequence, final int frame, final int counter) { + return this.getVectorValue(out, AnimationMap.KGSC.getWar3id(), sequence, frame, counter, + RenderMathUtils.FLOAT_VEC3_ONE); } public boolean isTranslationVariant(final int sequence) { @@ -156,4 +159,9 @@ public class GenericObject extends AnimatedObject { } } + @Override + public int getIndex() { + return this.index; + } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GeometryEmitterFuncs.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GeometryEmitterFuncs.java index 32d3123..5dadb82 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GeometryEmitterFuncs.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GeometryEmitterFuncs.java @@ -4,7 +4,9 @@ import java.nio.ByteBuffer; import java.nio.FloatBuffer; import java.util.List; +import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.GL20; +import com.badlogic.gdx.graphics.GL30; import com.badlogic.gdx.graphics.glutils.ShaderProgram; import com.badlogic.gdx.math.Vector3; import com.etheller.warsmash.viewer5.Camera; @@ -12,6 +14,8 @@ import com.etheller.warsmash.viewer5.ModelViewer; import com.etheller.warsmash.viewer5.Scene; import com.etheller.warsmash.viewer5.Texture; import com.etheller.warsmash.viewer5.TextureMapper; +import com.etheller.warsmash.viewer5.gl.ClientBuffer; +import com.etheller.warsmash.viewer5.handlers.EmitterObject; //The total storage that emitted objects can use. //This is enough to support all of the MDX geometry emitters. @@ -65,11 +69,11 @@ public class GeometryEmitterFuncs { private static final Vector3 endHeap = new Vector3(); private static final float[] vectorTemp = new float[3]; - public static void bindParticleEmitter2Buffer(final ParticleEmitter2 emitter, final ByteBuffer buffer) { + public static void bindParticleEmitter2Buffer(final ParticleEmitter2 emitter, final ClientBuffer buffer) { final MdxComplexInstance instance = emitter.instance; final List objects = emitter.objects; - final ByteBuffer byteView = buffer; - final FloatBuffer floatView = buffer.asFloatBuffer(); + final ByteBuffer byteView = buffer.byteView; + final FloatBuffer floatView = buffer.floatView; final ParticleEmitter2Object emitterObject = emitter.emitterObject; final int modelSpace = emitterObject.modelSpace; final float tailLength = emitterObject.tailLength; @@ -203,10 +207,10 @@ public class GeometryEmitterFuncs { } } - public static void bindRibbonEmitterBuffer(final RibbonEmitter emitter, final ByteBuffer buffer) { + public static void bindRibbonEmitterBuffer(final RibbonEmitter emitter, final ClientBuffer buffer) { Ribbon object = emitter.first; - final ByteBuffer byteView = buffer; - final FloatBuffer floatView = buffer.asFloatBuffer(); + final ByteBuffer byteView = buffer.byteView; + final FloatBuffer floatView = buffer.floatView; final RibbonEmitterObject emitterObject = emitter.emitterObject; final long columns = emitterObject.columns; final int alive = emitter.alive; @@ -277,6 +281,154 @@ public class GeometryEmitterFuncs { shader.setUniformf("u_rows", emitterObject.rows); } + public static void bindEventObjectEmitterBuffer( + final EventObjectEmitter emitter, final ClientBuffer buffer) { + final List objects = emitter.objects; + final FloatBuffer floatView = buffer.floatView; + int offset = 0; + + for (final EventObjectSplUbr object : objects) { + final int floatOffset = offset * FLOATS_PER_OBJECT; + final int p0Offset = floatOffset + FLOAT_OFFSET_P0; + final float[] vertices = object.vertices; + + floatView.put(p0Offset + 0, vertices[0]); + floatView.put(p0Offset + 1, vertices[1]); + floatView.put(p0Offset + 2, vertices[2]); + floatView.put(p0Offset + 3, vertices[3]); + floatView.put(p0Offset + 4, vertices[4]); + floatView.put(p0Offset + 5, vertices[5]); + floatView.put(p0Offset + 6, vertices[6]); + floatView.put(p0Offset + 7, vertices[7]); + floatView.put(p0Offset + 8, vertices[8]); + floatView.put(p0Offset + 9, vertices[9]); + floatView.put(p0Offset + 10, vertices[10]); + floatView.put(p0Offset + 11, vertices[11]); + + floatView.put(floatOffset + FLOAT_OFFSET_HEALTH, object.health); + + offset += 1; + } + } + + public static void bindEventObjectSplEmitterShader(final EventObjectSplEmitter emitter, + final ShaderProgram shader) { + final TextureMapper textureMapper = emitter.instance.textureMapper; + final EventObjectEmitterObject emitterObject = emitter.emitterObject; + final float[] intervalTimes = emitterObject.intervalTimes; + final float[][] intervals = emitterObject.intervals; + final float[][] colors = emitterObject.colors; + final MdxModel model = emitterObject.model; + final GL20 gl = model.viewer.gl; + final Texture texture = emitterObject.internalTexture; + + gl.glBlendFunc(emitterObject.blendSrc, emitterObject.blendDst); + + Texture finalTexture = textureMapper.get(texture); + if (finalTexture == null) { + finalTexture = texture; + } + model.viewer.webGL.bindTexture(finalTexture, 0); + + shader.setUniformf("u_lifeSpan", emitterObject.lifeSpan); + shader.setUniformf("u_columns", emitterObject.columns); + shader.setUniformf("rows", emitterObject.rows); + + // 3 because the uniform is shared with UBR, which has 3 values. + vectorTemp[0] = intervalTimes[0]; + vectorTemp[1] = intervalTimes[1]; + vectorTemp[2] = 0; + shader.setUniform3fv("u_intervalTimes", vectorTemp, 0, 3); + + shader.setUniform3fv("u_intervals[0]", intervals[0], 0, 3); + shader.setUniform3fv("u_intervals[1]", intervals[1], 0, 3); + + shader.setUniform3fv("u_colors[0]", colors[0], 0, 3); + shader.setUniform3fv("u_colors[1]", colors[1], 0, 3); + shader.setUniform3fv("u_colors[2]", colors[2], 0, 3); + } + + public static void bindEventObjectUbrEmitterShader(final EventObjectUbrEmitter emitter, + final ShaderProgram shader) { + final TextureMapper textureMapper = emitter.instance.textureMapper; + final EventObjectEmitterObject emitterObject = emitter.emitterObject; + final float[] intervalTimes = emitterObject.intervalTimes; + final float[][] colors = emitterObject.colors; + final MdxModel model = emitterObject.model; + final GL20 gl = model.viewer.gl; + final Texture texture = emitterObject.internalTexture; + + gl.glBlendFunc(emitterObject.blendSrc, emitterObject.blendDst); + + Texture finalTexture = textureMapper.get(texture); + if (finalTexture == null) { + finalTexture = texture; + } + model.viewer.webGL.bindTexture(finalTexture, 0); + + shader.setUniformf("u_lifeSpan", emitterObject.lifeSpan); + shader.setUniformf("u_columns", emitterObject.columns); + shader.setUniformf("rows", emitterObject.rows); + + shader.setUniform3fv("u_intervalTimes", intervalTimes, 0, 3); + + shader.setUniform3fv("u_colors[0]", colors[0], 0, 3); + shader.setUniform3fv("u_colors[1]", colors[1], 0, 3); + shader.setUniform3fv("u_colors[2]", colors[2], 0, 3); + } + + public static void renderEmitter(final MdxEmitter emitter, final ShaderProgram shader) { + int alive = emitter.alive; + final EmitterObject emitterObject = emitter.emitterObject; + final int emitterType = emitterObject.getGeometryEmitterType(); + + if (emitterType == EMITTER_RIBBON) { + alive -= 1; + } + + if (alive > 0) { + final ModelViewer viewer = emitter.instance.model.viewer; + final ClientBuffer buffer = viewer.buffer; + final GL20 gl = viewer.gl; + final int size = alive * BYTES_PER_OBJECT; + + switch (emitterType) { + case EMITTER_PARTICLE2: + bindParticleEmitter2Buffer((ParticleEmitter2) emitter, buffer); + bindParticleEmitter2Shader((ParticleEmitter2) emitter, shader); + break; + case EMITTER_RIBBON: + bindRibbonEmitterBuffer((RibbonEmitter) emitter, buffer); + bindRibbonEmitterShader((RibbonEmitter) emitter, shader); + break; + case EMITTER_SPLAT: + bindEventObjectEmitterBuffer((EventObjectSplEmitter) emitter, buffer); + bindEventObjectSplEmitterShader((EventObjectSplEmitter) emitter, shader); + break; + default: + bindEventObjectEmitterBuffer((EventObjectUbrEmitter) emitter, buffer); + bindEventObjectUbrEmitterShader((EventObjectUbrEmitter) emitter, shader); + break; + } + + buffer.bindAndUpdate(size); + + shader.setUniformi("u_emitter", emitterType); + + shader.setVertexAttribute("a_p0", 3, GL20.GL_FLOAT, false, BYTES_PER_OBJECT, BYTE_OFFSET_P0); + shader.setVertexAttribute("a_p1", 3, GL20.GL_FLOAT, false, BYTES_PER_OBJECT, BYTE_OFFSET_P1); + shader.setVertexAttribute("a_p2", 3, GL20.GL_FLOAT, false, BYTES_PER_OBJECT, BYTE_OFFSET_P2); + shader.setVertexAttribute("a_p3", 3, GL20.GL_FLOAT, false, BYTES_PER_OBJECT, BYTE_OFFSET_P3); + shader.setVertexAttribute("a_health", 1, GL20.GL_FLOAT, false, BYTES_PER_OBJECT, BYTE_OFFSET_HEALTH); + shader.setVertexAttribute("a_color", 4, GL20.GL_UNSIGNED_BYTE, true, BYTES_PER_OBJECT, BYTE_OFFSET_COLOR); + shader.setVertexAttribute("a_tail", 1, GL20.GL_UNSIGNED_BYTE, false, BYTES_PER_OBJECT, BYTE_OFFSET_TAIL); + shader.setVertexAttribute("a_leftRightTop", 3, GL20.GL_UNSIGNED_BYTE, false, BYTES_PER_OBJECT, + BYTE_OFFSET_LEFT_RIGHT_TOP); + + Gdx.gl30.glDrawArraysInstanced(GL30.GL_TRIANGLES, 0, 6, alive); + } + } + private static final float[] asFloatArray(final Vector3 vec) { vectorTemp[0] = vec.x; vectorTemp[1] = vec.y; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Geoset.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Geoset.java index ef010e7..247b013 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Geoset.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Geoset.java @@ -74,18 +74,18 @@ public class Geoset { this.hasObjectAnim = hasAlphaAnim || hasColorAnim; } - public int getAlpha(final float[] out, final MdxComplexInstance instance) { + public int getAlpha(final float[] out, final int sequence, final int frame, final int counter) { if (this.geosetAnimation != null) { - return this.geosetAnimation.getAlpha(out, instance); + return this.geosetAnimation.getAlpha(out, sequence, frame, counter); } out[0] = 1; return -1; } - public int getColor(final float[] out, final MdxComplexInstance instance) { + public int getColor(final float[] out, final int sequence, final int frame, final int counter) { if (this.geosetAnimation != null) { - return this.geosetAnimation.getAlpha(out, instance); + return this.geosetAnimation.getAlpha(out, sequence, frame, counter); } Arrays.fill(out, 1); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GeosetAnimation.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GeosetAnimation.java index b4ee688..7e3206e 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GeosetAnimation.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GeosetAnimation.java @@ -19,12 +19,12 @@ public class GeosetAnimation extends AnimatedObject { this.geosetId = geosetAnimation.getGeosetId(); } - public int getAlpha(final float[] out, final MdxComplexInstance instance) { - return this.getScalarValue(out, AnimationMap.KGAO.getWar3id(), instance, this.alpha); + public int getAlpha(final float[] out, final int sequence, final int frame, final int counter) { + return this.getScalarValue(out, AnimationMap.KGAO.getWar3id(), sequence, frame, counter, this.alpha); } - public int getColor(final float[] out, final MdxComplexInstance instance) { - return this.getVectorValue(out, AnimationMap.KGAC.getWar3id(), instance, this.color); + public int getColor(final float[] out, final int sequence, final int frame, final int counter) { + return this.getVectorValue(out, AnimationMap.KGAC.getWar3id(), sequence, frame, counter, this.color); } public boolean isAlphaVariant(final int sequence) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Helper.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Helper.java new file mode 100644 index 0000000..2cd24b3 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Helper.java @@ -0,0 +1,12 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +/** + * An MDX helper. + */ +public class Helper extends GenericObject { + public Helper(final MdxModel model, final com.etheller.warsmash.parsers.mdlx.GenericObject object, + final int index) { + super(model, object, index); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Layer.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Layer.java index 378c35e..359df40 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Layer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Layer.java @@ -1,39 +1,121 @@ package com.etheller.warsmash.viewer5.handlers.mdx; +import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.glutils.ShaderProgram; -import com.etheller.warsmash.parsers.mdlx.Layer.FilterMode; +import com.etheller.warsmash.parsers.mdlx.AnimationMap; -public class Layer { - public MdxModel model; - public com.etheller.warsmash.parsers.mdlx.Layer layer; - public int layerId; +/** + * An MDX layer. + */ +public class Layer extends AnimatedObject { + public int index; public int priorityPlane; - public int filterMode; public int textureId; public int coordId; public float alpha; - - public int index = -666; + public int unshaded; + public int sphereEnvironmentMap; + public int twoSided; + public int unfogged; + public int noDepthTest; + public int noDepthSet; + public boolean depthMaskValue; + public int blendSrc; + public int blendDst; + public boolean blended; + public TextureAnimation textureAnimation; public Layer(final MdxModel model, final com.etheller.warsmash.parsers.mdlx.Layer layer, final int layerId, final int priorityPlane) { super(model, layer); - this.model = model; - this.layer = layer; - this.layerId = layerId; - this.priorityPlane = priorityPlane; - final FilterMode filterMode2 = layer.getFilterMode(); - this.filterMode = filterMode2.ordinal(); + final com.etheller.warsmash.parsers.mdlx.Layer.FilterMode filterMode = layer.getFilterMode(); + final int textureAnimationId = layer.getTextureAnimationId(); + final GL20 gl = model.viewer.gl; + + this.index = layerId; + this.priorityPlane = priorityPlane; + this.filterMode = filterMode.ordinal(); this.textureId = layer.getTextureId(); - // this.coo - this.index + this.coordId = (int) layer.getCoordId(); + this.alpha = layer.getAlpha(); + + final int flags = layer.getFlags(); + + this.unshaded = flags & 0x1; + this.sphereEnvironmentMap = flags & 0x2; + this.twoSided = flags & 0x10; + this.unfogged = flags & 0x20; + this.noDepthTest = flags & 0x40; + this.noDepthSet = flags & 0x80; + + this.depthMaskValue = ((filterMode == com.etheller.warsmash.parsers.mdlx.Layer.FilterMode.NONE) + || (filterMode == com.etheller.warsmash.parsers.mdlx.Layer.FilterMode.TRANSPARENT)); + + this.blendSrc = 0; + this.blendDst = 0; + this.blended = (filterMode.ordinal() > 1); + + if (this.blended) { + final int[] result = FilterMode.layerFilterMode(filterMode); + this.blendSrc = result[0]; + this.blendDst = result[1]; + } + + if (textureAnimationId != -1) { + final TextureAnimation textureAnimation = model.getTextureAnimations().get(textureAnimationId); + + if (textureAnimation != null) { + this.textureAnimation = textureAnimation; + } + } + + this.addVariants(AnimationMap.KMTA.getWar3id(), "alpha"); + this.addVariants(AnimationMap.KMTF.getWar3id(), "textureId"); } public void bind(final ShaderProgram shader) { - // TODO Auto-generated method stub + final GL20 gl = this.model.viewer.gl; + // gl.uniform1f(shader.uniforms.u_unshaded, this.unshaded); + shader.setUniformf("u_filterMode", this.filterMode); + + if (this.blended) { + gl.glEnable(GL20.GL_BLEND); + gl.glBlendFunc(this.blendSrc, this.blendDst); + } + else { + gl.glDisable(GL20.GL_BLEND); + } + + if (this.twoSided != 0) { + gl.glEnable(GL20.GL_CULL_FACE); + } + else { + gl.glDisable(GL20.GL_CULL_FACE); + } + + if (this.noDepthTest != 0) { + gl.glDisable(GL20.GL_DEPTH_TEST); + } + else { + gl.glEnable(GL20.GL_DEPTH_TEST); + } + + if (this.noDepthSet != 0) { + gl.glDepthMask(false); + } + else { + gl.glDepthMask(this.depthMaskValue); + } } + public int getAlpha(final float[] out, final int sequence, final int frame, final int counter) { + return this.getScalarValue(out, AnimationMap.KMTA.getWar3id(), sequence, frame, counter, this.alpha); + } + + public int getTextureId(final long[] out, final int sequence, final int frame, final int counter) { + return this.getScalarValue(out, AnimationMap.KMTF.getWar3id(), sequence, frame, counter, this.textureId); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Light.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Light.java new file mode 100644 index 0000000..c3a9179 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Light.java @@ -0,0 +1,48 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +import com.etheller.warsmash.parsers.mdlx.AnimationMap; + +public class Light extends GenericObject { + + private final int type; + private final float[] attenuation; + private final float[] color; + private final float intensity; + private final float[] ambientColor; + private final float ambientIntensity; + + public Light(final MdxModel model, final com.etheller.warsmash.parsers.mdlx.Light light, final int index) { + super(model, light, index); + + this.type = light.getType(); + this.attenuation = light.getAttenuation(); + this.color = light.getColor(); + this.intensity = light.getIntensity(); + this.ambientColor = light.getAmbientColor(); + this.ambientIntensity = light.getAmbientIntensity(); + } + + public int getAttenuationStart(final float[] out, final int sequence, final int frame, final int counter) { + return this.getScalarValue(out, AnimationMap.KLAS.getWar3id(), sequence, frame, counter, this.attenuation[0]); + } + + public int getAttenuationEnd(final float[] out, final int sequence, final int frame, final int counter) { + return this.getScalarValue(out, AnimationMap.KLAE.getWar3id(), sequence, frame, counter, this.attenuation[1]); + } + + public int getIntensity(final float[] out, final int sequence, final int frame, final int counter) { + return this.getScalarValue(out, AnimationMap.KLAI.getWar3id(), sequence, frame, counter, this.intensity); + } + + public int getColor(final float[] out, final int sequence, final int frame, final int counter) { + return this.getVectorValue(out, AnimationMap.KLAC.getWar3id(), sequence, frame, counter, this.color); + } + + public int getAmbientIntensity(final float[] out, final int sequence, final int frame, final int counter) { + return this.getScalarValue(out, AnimationMap.KLBI.getWar3id(), sequence, frame, counter, this.ambientIntensity); + } + + public int getAmbientColor(final float[] out, final int sequence, final int frame, final int counter) { + return this.getVectorValue(out, AnimationMap.KLBC.getWar3id(), sequence, frame, counter, this.ambientColor); + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxHandler.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxHandler.java index 2acd4f9..889f8a5 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxHandler.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxHandler.java @@ -4,8 +4,8 @@ import java.util.ArrayList; import java.util.List; import com.badlogic.gdx.graphics.glutils.ShaderProgram; +import com.etheller.warsmash.viewer5.HandlerResource; import com.etheller.warsmash.viewer5.ModelViewer; -import com.etheller.warsmash.viewer5.Resource; import com.etheller.warsmash.viewer5.Texture; import com.etheller.warsmash.viewer5.handlers.ModelHandler; import com.etheller.warsmash.viewer5.handlers.ResourceHandlerConstructionParams; @@ -45,7 +45,7 @@ public class MdxHandler extends ModelHandler { } @Override - public Resource construct(final ResourceHandlerConstructionParams params) { + public HandlerResource construct(final ResourceHandlerConstructionParams params) { return new MdxModel((MdxHandler) params.getHandler(), params.getViewer(), params.getExtension(), params.getPathSolver(), params.getFetchUrl()); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java index a390c34..f96195a 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java @@ -11,6 +11,31 @@ import com.etheller.warsmash.viewer5.PathSolver; import com.etheller.warsmash.viewer5.Texture; public class MdxModel extends com.etheller.warsmash.viewer5.Model { + public boolean reforged = false; + public boolean hd = false; + public SolverParams solverParams = new SolverParams(); + public String name = ""; + public List sequences = new ArrayList<>(); + public List globalSequences = new ArrayList<>(); + public List materials = new ArrayList<>(); + public List layers = new ArrayList<>(); + public List replaceables = new ArrayList<>(); + public List textures = new ArrayList<>(); + public List textureAnimations = new ArrayList<>(); + public List geosets = new ArrayList<>(); + public List geosetAnimations = new ArrayList<>(); + public List bones = new ArrayList<>(); + public List lights = new ArrayList<>(); + public List helpers = new ArrayList<>(); + public List attachments = new ArrayList<>(); + public List pivotPoints = new ArrayList<>(); + public List particleEmitters = new ArrayList<>(); + public List particleEmitters2 = new ArrayList<>(); + public List ribbonEmitters = new ArrayList<>(); + public List cameras = new ArrayList<>(); + public List eventObjects = new ArrayList<>(); + public + private MdlxModel model; public int arrayBuffer; @@ -18,9 +43,8 @@ public class MdxModel extends com.etheller.warsmash.viewer5.Model { public List batches = new ArrayList<>(); // TODO?? - public List replaceables = new ArrayList<>(); - - public boolean reforged = false; + public List opaqueGroups; + public List translucentGroups; public MdxModel(final MdxHandler handler, final ModelViewer viewer, final String extension, final PathSolver pathSolver, final String fetchUrl) { @@ -70,4 +94,18 @@ public class MdxModel extends com.etheller.warsmash.viewer5.Model { throw new UnsupportedOperationException("NYI"); } + public List getTextureAnimations() { + throw new UnsupportedOperationException("NYI"); + } + + public List getGeosets() { + throw new UnsupportedOperationException("NYI"); + } + + private static final class SolverParams { + public boolean reforged; + public boolean hd; + + } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Particle.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Particle.java new file mode 100644 index 0000000..ff01c77 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Particle.java @@ -0,0 +1,103 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +import com.badlogic.gdx.math.Quaternion; +import com.badlogic.gdx.math.Vector3; +import com.etheller.warsmash.util.RenderMathUtils; +import com.etheller.warsmash.viewer5.EmittedObject; +import com.etheller.warsmash.viewer5.Scene; + +/** + * A spawned model particle. + */ +public class Particle extends EmittedObject { + private static final Quaternion rotationHeap = new Quaternion(); + private static final Quaternion rotationHeap2 = new Quaternion(); + private static final Vector3 velocityHeap = new Vector3(); + private static final float[] latitudeHeap = new float[1]; +// private static final float[] longitudeHeap = new float[1]; + private static final float[] lifeSpanHeap = new float[1]; + private static final float[] gravityHeap = new float[1]; + private static final float[] speedHeap = new float[1]; + private static final float[] tempVector = new float[3]; + + private final MdxComplexInstance internalInstance; + private final Vector3 velocity = new Vector3(); + private float gravity; + + public Particle(final ParticleEmitter emitter) { + super(emitter); + + final ParticleEmitterObject emitterObject = emitter.emitterObject; + + this.internalInstance = (MdxComplexInstance) emitterObject.internalModel.addInstance(); + } + + @Override + protected void bind(final int flags) { + final ParticleEmitter emitter = this.emitter; + final MdxComplexInstance instance = emitter.instance; + final int sequence = instance.sequence; + final int frame = instance.frame; + final int counter = instance.counter; + final Scene scene = instance.scene; + final ParticleEmitterObject emitterObject = emitter.emitterObject; + final MdxNode node = instance.nodes[emitterObject.index]; + final MdxComplexInstance internalInstance = this.internalInstance; + final Vector3 scale = node.worldScale; + final Vector3 velocity = this.velocity; + + emitterObject.getLatitude(latitudeHeap, sequence, frame, counter); + // longitude?? commented in ghostwolf JS + emitterObject.getLifeSpan(lifeSpanHeap, sequence, frame, counter); + emitterObject.getGravity(gravityHeap, sequence, frame, counter); + emitterObject.getSpeed(speedHeap, sequence, frame, counter); + + this.health = lifeSpanHeap[0]; + this.gravity = gravityHeap[0] * scale.z; + + // Local rotation + rotationHeap.idt(); + rotationHeap.mulLeft(rotationHeap2.setFromAxisRad(0, 0, 1, + RenderMathUtils.randomInRange((float) -Math.PI, (float) Math.PI))); + rotationHeap.mulLeft(rotationHeap2.setFromAxisRad(0, 1, 0, + RenderMathUtils.randomInRange(-latitudeHeap[0], latitudeHeap[0]))); + velocity.set(RenderMathUtils.VEC3_UNIT_Z); + rotationHeap.transform(velocity); + + // World rotation + node.worldRotation.transform(velocity); + + // Apply speed + velocity.scl(speedHeap[0]); + + // Apply the parent's scale + velocity.scl(scale); + + scene.addInstance(internalInstance); + + internalInstance.setTransformation(node.worldLocation, rotationHeap.setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, + RenderMathUtils.randomInRange(0, (float) Math.PI * 2)), node.worldScale); + internalInstance.setSequence(0); + internalInstance.show(); + } + + @Override + public void update(final float dt) { + final MdxComplexInstance internalInstance = this.internalInstance; + + internalInstance.paused = false; /// Why is this here? + + this.health -= dt; + + if (this.health > 0) { + final Vector3 velocity = this.velocity; + + velocity.z -= this.gravity * dt; + + tempVector[0] = velocity.x * dt; + tempVector[1] = velocity.y * dt; + tempVector[2] = velocity.z * dt; + internalInstance.move(tempVector); + } + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Particle2.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Particle2.java index 55eb990..c83bb22 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Particle2.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Particle2.java @@ -12,7 +12,6 @@ public class Particle2 extends EmittedObject { + + private static final float[] emissionRateHeap = new float[1]; + + public ParticleEmitter(final MdxComplexInstance instance, final ParticleEmitterObject emitterObject) { + super(instance, emitterObject); + } + + @Override + protected void updateEmission(final float dt) { + final MdxComplexInstance instance = this.instance; + + if (instance.allowParticleSpawn) { + final ParticleEmitterObject emitterObject = this.emitterObject; + + emitterObject.getEmissionRate(emissionRateHeap, instance.sequence, instance.frame, instance.counter); + + this.currentEmission += emissionRateHeap[0] * dt; + } + } + + @Override + protected void emit() { + this.emitObject(0); + } + + @Override + protected Particle createObject() { + return new Particle(this); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ParticleEmitter2.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ParticleEmitter2.java index 2c2427d..0394053 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ParticleEmitter2.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ParticleEmitter2.java @@ -19,7 +19,8 @@ public class ParticleEmitter2 extends MdxEmitter { public Ribbon prev; public Ribbon next; + public Ribbon(final RibbonEmitter emitter) { + super(emitter); + } + @Override protected void bind(final int flags) { final RibbonEmitter emitter = this.emitter; @@ -31,9 +35,9 @@ public class Ribbon extends EmittedObject { this.health = emitter.emitterObject.lifeSpan; - emitterObject.getHeightBelow(vectorHeap, instance); + emitterObject.getHeightBelow(vectorHeap, instance.sequence, instance.frame, instance.counter); belowHeap.set(vectorHeap); - emitterObject.getHeightAbove(vectorHeap, instance); + emitterObject.getHeightAbove(vectorHeap, instance.sequence, instance.frame, instance.counter); aboveHeap.set(vectorHeap); belowHeap.y = y - belowHeap.x; @@ -55,9 +59,6 @@ public class Ribbon extends EmittedObject { vertices[5] = belowHeap.z; } - public Ribbon(final RibbonEmitter emitter) { - } - @Override public void update(final float dt) { this.health -= dt; @@ -70,9 +71,9 @@ public class Ribbon extends EmittedObject { final float[] vertices = this.vertices; final float gravity = emitterObject.gravity * dt * dt; - emitterObject.getColor(colorHeap, instance); - emitterObject.getAlpha(alphaHeap, instance); - emitterObject.getTextureSlot(slotHeap, instance); + emitterObject.getColor(colorHeap, instance.sequence, instance.frame, instance.counter); + emitterObject.getAlpha(alphaHeap, instance.sequence, instance.frame, instance.counter); + emitterObject.getTextureSlot(slotHeap, instance.sequence, instance.frame, instance.counter); vertices[1] -= gravity; vertices[4] -= gravity; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/RibbonEmitterObject.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/RibbonEmitterObject.java index 41bcca8..fe07765 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/RibbonEmitterObject.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/RibbonEmitterObject.java @@ -40,29 +40,29 @@ public class RibbonEmitterObject extends GenericObject implements EmitterObject this.rows = emitter.getRows(); } - public int getHeightBelow(final float[] out, final MdxComplexInstance instance) { - return this.getScalarValue(out, AnimationMap.KRHB.getWar3id(), instance, this.heightBelow); + public int getHeightBelow(final float[] out, final int sequence, final int frame, final int counter) { + return this.getScalarValue(out, AnimationMap.KRHB.getWar3id(), sequence, frame, counter, this.heightBelow); } - public int getHeightAbove(final float[] out, final MdxComplexInstance instance) { - return this.getScalarValue(out, AnimationMap.KRHA.getWar3id(), instance, this.heightAbove); + public int getHeightAbove(final float[] out, final int sequence, final int frame, final int counter) { + return this.getScalarValue(out, AnimationMap.KRHA.getWar3id(), sequence, frame, counter, this.heightAbove); } - public int getTextureSlot(final long[] out, final MdxComplexInstance instance) { - return this.getScalarValue(out, AnimationMap.KRTX.getWar3id(), instance, 0); + public int getTextureSlot(final long[] out, final int sequence, final int frame, final int counter) { + return this.getScalarValue(out, AnimationMap.KRTX.getWar3id(), sequence, frame, counter, 0); } - public int getColor(final float[] out, final MdxComplexInstance instance) { - return this.getVectorValue(out, AnimationMap.KRCO.getWar3id(), instance, this.color); + public int getColor(final float[] out, final int sequence, final int frame, final int counter) { + return this.getVectorValue(out, AnimationMap.KRCO.getWar3id(), sequence, frame, counter, this.color); } - public int getAlpha(final float[] out, final MdxComplexInstance instance) { - return this.getScalarValue(out, AnimationMap.KRAL.getWar3id(), instance, this.alpha); + public int getAlpha(final float[] out, final int sequence, final int frame, final int counter) { + return this.getScalarValue(out, AnimationMap.KRAL.getWar3id(), sequence, frame, counter, this.alpha); } @Override - public int getVisibility(final float[] out, final MdxComplexInstance instance) { - return this.getScalarValue(out, AnimationMap.KRVS.getWar3id(), instance, 1f); + public int getVisibility(final float[] out, final int sequence, final int frame, final int counter) { + return this.getScalarValue(out, AnimationMap.KRVS.getWar3id(), sequence, frame, counter, 1f); } @Override diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Sd.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Sd.java index 7688267..07810ce 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Sd.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Sd.java @@ -116,12 +116,12 @@ public abstract class Sd { } } - public int getValue(final TYPE out, final MdxComplexInstance instance) { + public int getValue(final TYPE out, final int sequence, final int frame, final int counter) { if (this.globalSequence != null) { - return this.globalSequence.getValue(out, instance.counter % this.globalSequence.end); + return this.globalSequence.getValue(out, counter % this.globalSequence.end); } - else if (instance.sequence != -1) { - return this.sequences.get(instance.sequence).getValue(out, instance.frame); + else if (sequence != -1) { + return this.sequences.get(sequence).getValue(out, frame); } else { this.copy(out, this.defval); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SetupGeosets.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SetupGeosets.java new file mode 100644 index 0000000..37fe299 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SetupGeosets.java @@ -0,0 +1,186 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import com.badlogic.gdx.graphics.GL20; + +public class SetupGeosets { + private static final int NORMAL_BATCH = 0; + private static final int EXTENDED_BATCH = 0; + private static final int REFORGED_BATCH = 0; + + public static void setupGeosets(final MdxModel model, + final List geosets) { + if (geosets.size() > 0) { + final GL20 gl = model.viewer.gl; + int positionBytes = 0; + int normalBytes = 0; + int uvBytes = 0; + int skinBytes = 0; + int faceBytes = 0; + final int[] batchTypes = new int[geosets.size()]; + + for (int i = 0, l = geosets.size(); i < l; i++) { + final com.etheller.warsmash.parsers.mdlx.Geoset geoset = geosets.get(i); + + if (true /* geoset.getLod() == 0 */) { + final int vertices = geoset.getVertices().length / 3; + + positionBytes += vertices * 12; + normalBytes += vertices * 12; + uvBytes += geoset.getUvSets().length * vertices * 8; + + if (false /* geoset.skin.length */) { + skinBytes += vertices * 8; + + batchTypes[i] = REFORGED_BATCH; + } + else { + long biggestGroup = 0; + + for (final long group : geoset.getMatrixGroups()) { + if (group > biggestGroup) { + biggestGroup = group; + } + } + + if (biggestGroup > 4) { + skinBytes += vertices * 9; + + batchTypes[i] = EXTENDED_BATCH; + } + else { + batchTypes[i] = NORMAL_BATCH; + } + } + + faceBytes += geoset.getFaces().length * 4; + } + } + + int positionOffset = 0; + int normalOffset = positionOffset + positionBytes; + int uvOffset = normalOffset + normalBytes; + int skinOffset = uvOffset + uvBytes; + int faceOffset = 0; + + model.arrayBuffer = gl.glGenBuffer(); + gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, model.arrayBuffer); + gl.glBufferData(GL20.GL_ARRAY_BUFFER, skinOffset + skinBytes, null, GL20.GL_STATIC_DRAW); + + model.elementBuffer = gl.glGenBuffer(); + gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, model.elementBuffer); + gl.glBufferData(GL20.GL_ELEMENT_ARRAY_BUFFER, faceBytes, null, GL20.GL_STATIC_DRAW); + + for (int i = 0, l = geosets.size(); i < l; i++) { + final com.etheller.warsmash.parsers.mdlx.Geoset geoset = geosets.get(i); + + if (true /* geoset.lod == 0 */) { + final float[] positions = geoset.getVertices(); + final float[] normals = geoset.getNormals(); + final float[][] uvSets = geoset.getUvSets(); + final int[] faces = geoset.getFaces(); + byte[] skin = null; + final int vertices = geoset.getVertices().length / 3; + final int batchType = batchTypes[i]; + + if (batchType == REFORGED_BATCH) { + // skin = geoset.skin; + } + else { + final long[] matrixIndices = geoset.getMatrixIndices(); + final short[] vertexGroups = geoset.getVertexGroups(); + final List matrixGroups = new ArrayList<>(); + int offset = 0; + // Normally the shader supports up to 4 bones per vertex. + // This is enough for almost every existing Warcraft 3 model. + // That being said, there are a few models with geosets that need more, for + // example the Water Elemental. + // These geosets use a different shader, which support up to 8 bones per vertex. + int maxBones = 4; + if (batchType == EXTENDED_BATCH) { + maxBones = 8; + } + + skin = new byte[vertices * (maxBones + 1)]; + + // Slice the matrix groups + for (final long size : geoset.getMatrixGroups()) { + matrixGroups.add(Arrays.copyOfRange(matrixIndices, offset, (int) (offset + size))); + offset += size; + } + + // Parse the skinning. + for (int si = 0; si < vertices; si++) { + final short vertexGroup = vertexGroups[si]; + final long[] matrixGroup = (vertexGroup >= matrixGroups.size()) ? null + : matrixGroups.get(vertexGroup); + + offset = si * (maxBones + 1); + + // Somehow in some bad models a vertex group index refers to an invalid matrix + // group. + // Such models are still loaded by the game. + if (matrixGroup != null) { + final int bones = Math.min(matrixGroup.length, maxBones); + + for (int j = 0; j < bones; j++) { + skin[offset + j] = (byte) (matrixGroup[j] + 1); // 1 is added to diffrentiate + // between matrix 0, and no matrix. + } + + skin[offset + maxBones] = (byte) bones; + } + } + } + + final Geoset vGeoset = new Geoset(model, model.getGeosets().size(), positionOffset, normalOffset, + uvOffset, skinOffset, faceOffset, vertices, faces.length); + + model.getGeosets().add(vGeoset); + + if (batchType == REFORGED_BATCH) { + throw new UnsupportedOperationException("NYI"); +// model.batches.add(new Reforged) + } + else { + final boolean isExtended = batchType == EXTENDED_BATCH; + + for (final Layer layer : model.getMaterials().get((int) geoset.getMaterialId()).layers) { + model.batches.add(new Batch(model.batches.size(), vGeoset, layer, isExtended)); + } + } + + // Positions. + gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, positionOffset, positions.length, + FloatBuffer.wrap(positions)); + positionOffset += positions.length * 4; + + // Normals. + gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, normalOffset, normals.length, FloatBuffer.wrap(normals)); + normalOffset += normals.length * 4; + + // Texture coordinates. + for (final float[] uvSet : uvSets) { + gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, uvOffset, uvSet.length, FloatBuffer.wrap(uvSet)); + uvOffset += uvSet.length * 4; + } + + // Skin. + gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, skinOffset, skin.length, ByteBuffer.wrap(skin)); + skinOffset += skin.length * 1; + + // Faces. + gl.glBufferSubData(GL20.GL_ELEMENT_ARRAY_BUFFER, faceOffset, faces.length, IntBuffer.wrap(faces)); + faceOffset += faces.length * 4; + } + + } + } + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SetupGroups.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SetupGroups.java new file mode 100644 index 0000000..6eb35d0 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SetupGroups.java @@ -0,0 +1,121 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import com.etheller.warsmash.viewer5.handlers.EmitterObject; + +public class SetupGroups { + public static int getPrio(final Batch object) { + return object.layer.priorityPlane; + } + + public static int getPrio(final ParticleEmitter2Object object) { + return object.priorityPlane; + } + + public static int getPrio(final RibbonEmitterObject object) { + return object.layer.priorityPlane; + } + + public static int getPrio(final Object object) { + if (object instanceof Batch) { + return getPrio((Batch) object); + } + else if (object instanceof RibbonEmitterObject) { + return getPrio((RibbonEmitterObject) object); + } + else if (object instanceof ParticleEmitter2Object) { + return getPrio((ParticleEmitter2Object) object); + } + else { + throw new IllegalArgumentException(object.getClass().getName()); + } + } + + public static boolean matchingGroup(final Object group, final Object object) { + if (group instanceof BatchGroup) { + return (object instanceof Batch) && (((Batch) object).isExtended == ((BatchGroup) group).isExtended); +// } else if(group instanceof ReforgedBatch) { TODO +// return (object instanceof ReforgedBatch) && (object.material.shader === group.shader); + } + else { + // All of the emitter objects are generic objects. + return (object instanceof GenericObject); + } + } + + public static GenericGroup createMatchingGroup(final MdxModel model, final Object object) { + if (object instanceof Batch) { + return new BatchGroup(model, ((Batch) object).isExtended); +// } else if(object instanceof ReforgedBatch) { TODO +// return new ReforgedBatchGroup(model, ((ReforgedBatch)object).material.shader); + } + else { + return new EmitterGroup(model); + } + } + + public static void setupGroups(final MdxModel model) { + final List opaqueBatches = new ArrayList<>(); + final List translucentBatches = new ArrayList<>(); + + for (final Batch batch : model.batches) {// TODO reforged + if (/* batch instanceof ReforgedBatch || */batch.layer.filterMode < 2) { + opaqueBatches.add(batch); + } + else { + translucentBatches.add(batch); + } + } + + final List opaqueGroups = model.opaqueGroups; + final List translucentGroups = model.translucentGroups; + GenericGroup currentGroup = null; + + for (final Batch object : opaqueBatches) { + if ((currentGroup == null) || !matchingGroup(currentGroup, object)) { + currentGroup = createMatchingGroup(model, object); + + opaqueGroups.add(currentGroup); + } + + currentGroup.objects.add(object.index); + } + + // Sort between all of the translucent batches and emitters that have priority + // planes + final List sorted = new ArrayList<>(); + sorted.addAll(translucentBatches); + sorted.addAll(model.particleEmitters2); + sorted.addAll(model.ribbonEmitters); + Collections.sort(sorted, new Comparator() { + @Override + public int compare(final Object o1, final Object o2) { + return getPrio(o1) - getPrio(o2); + } + }); + + // Event objects have no priority planes, so they might as well always be last. + final List objects = new ArrayList<>(); + objects.addAll(sorted); + objects.addAll(model.eventObjects); + + currentGroup = null; + + for (final Object object : objects) { // TODO reforged + if ((object instanceof Batch /* || object instanceof ReforgedBatch */) + || (object instanceof EmitterObject)) { + if ((currentGroup == null) || !matchingGroup(currentGroup, objects)) { + currentGroup = createMatchingGroup(model, objects); + + translucentGroups.add(currentGroup); + } + + currentGroup.objects.add(((GenericIndexed) object).getIndex()); + } + } + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/TextureAnimation.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/TextureAnimation.java index aec7adf..1fcdd04 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/TextureAnimation.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/TextureAnimation.java @@ -10,16 +10,19 @@ public class TextureAnimation extends AnimatedObject { super(model, textureAnimation); } - public int getTranslation(final float[] out, final MdxComplexInstance instance) { - return this.getVectorValue(out, AnimationMap.KTAT.getWar3id(), instance, RenderMathUtils.FLOAT_VEC3_ZERO); + public int getTranslation(final float[] out, final int sequence, final int frame, final int counter) { + return this.getVectorValue(out, AnimationMap.KTAT.getWar3id(), sequence, frame, counter, + RenderMathUtils.FLOAT_VEC3_ZERO); } - public int getRotation(final float[] out, final MdxComplexInstance instance) { - return this.getVectorValue(out, AnimationMap.KTAR.getWar3id(), instance, RenderMathUtils.FLOAT_QUAT_DEFAULT); + public int getRotation(final float[] out, final int sequence, final int frame, final int counter) { + return this.getVectorValue(out, AnimationMap.KTAR.getWar3id(), sequence, frame, counter, + RenderMathUtils.FLOAT_QUAT_DEFAULT); } - public int getScale(final float[] out, final MdxComplexInstance instance) { - return this.getVectorValue(out, AnimationMap.KTAS.getWar3id(), instance, RenderMathUtils.FLOAT_VEC3_ONE); + public int getScale(final float[] out, final int sequence, final int frame, final int counter) { + return this.getVectorValue(out, AnimationMap.KTAS.getWar3id(), sequence, frame, counter, + RenderMathUtils.FLOAT_VEC3_ONE); } public boolean isTranslationVariant(final int sequence) { diff --git a/core/src/com/hiveworkshop/wc3/mpq/Codebase.java b/core/src/com/hiveworkshop/wc3/mpq/Codebase.java deleted file mode 100644 index 61a7d5c..0000000 --- a/core/src/com/hiveworkshop/wc3/mpq/Codebase.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.hiveworkshop.wc3.mpq; - -import java.io.File; -import java.io.InputStream; - -public interface Codebase { - InputStream getResourceAsStream(String filepath); - - File getFile(String filepath); - - boolean has(String filepath); -} diff --git a/core/src/com/hiveworkshop/wc3/mpq/FileCodebase.java b/core/src/com/hiveworkshop/wc3/mpq/FileCodebase.java deleted file mode 100644 index ae5d443..0000000 --- a/core/src/com/hiveworkshop/wc3/mpq/FileCodebase.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.hiveworkshop.wc3.mpq; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.InputStream; - -public class FileCodebase implements Codebase { - private final File sourceDirectory; - - public FileCodebase(final File sourceDirectory) { - this.sourceDirectory = sourceDirectory; - } - - @Override - public InputStream getResourceAsStream(final String filepath) { - try { - return new FileInputStream(new File(this.sourceDirectory.getPath() + File.separatorChar + filepath)); - } - catch (final FileNotFoundException e) { - throw new RuntimeException(e); - } - } - - @Override - public File getFile(final String filepath) { - return new File(this.sourceDirectory.getPath() + File.separatorChar + filepath); - } - - @Override - public boolean has(final String filepath) { - return new File(this.sourceDirectory.getPath() + File.separatorChar + filepath).exists(); - } - -} From c157b0e368b015b685f3e9c6f3a1ec97ae93a872 Mon Sep 17 00:00:00 2001 From: Retera Date: Fri, 17 Jan 2020 02:13:56 -0600 Subject: [PATCH 010/116] More work but no prototype yet --- .../etheller/warsmash/WarsmashGdxGame.java | 171 +++-- .../warsmash/parsers/mdlx/Extent.java | 8 + .../warsmash/parsers/mdlx/Material.java | 20 + .../warsmash/parsers/mdlx/MdlxModel.java | 84 +++ .../warsmash/parsers/mdlx/Sequence.java | 6 +- .../warsmash/parsers/mdlx/Texture.java | 12 + .../warsmash/util/RenderMathUtils.java | 2 +- .../com/etheller/warsmash/util/SlkFile.java | 4 +- .../warsmash/viewer5/BatchedInstance.java | 16 + .../com/etheller/warsmash/viewer5/Bounds.java | 16 +- .../com/etheller/warsmash/viewer5/Camera.java | 59 +- .../etheller/warsmash/viewer5/Emitter.java | 4 +- .../warsmash/viewer5/GenericNode.java | 34 +- .../warsmash/viewer5/GenericResource.java | 2 +- .../etheller/warsmash/viewer5/GridCell.java | 3 + .../com/etheller/warsmash/viewer5/Model.java | 6 +- .../warsmash/viewer5/ModelInstance.java | 8 +- .../warsmash/viewer5/ModelViewer.java | 21 +- .../warsmash/viewer5/RenderBatch.java | 40 ++ .../com/etheller/warsmash/viewer5/Scene.java | 28 +- .../etheller/warsmash/viewer5/Shaders.java | 2 +- .../warsmash/viewer5/SkeletalNode.java | 28 +- .../etheller/warsmash/viewer5/Texture.java | 11 +- .../warsmash/viewer5/UpdatableObject.java | 5 + .../viewer5/gl/ANGLEInstancedArrays.java | 13 + .../warsmash/viewer5/gl/ClientBuffer.java | 2 +- .../warsmash/viewer5/gl/DataTexture.java | 67 ++ .../warsmash/viewer5/gl/Extensions.java | 5 + .../etheller/warsmash/viewer5/gl/WebGL.java | 13 +- .../warsmash/viewer5/handlers/Batch.java | 22 - .../viewer5/handlers/BatchDescriptor.java | 9 - .../viewer5/handlers/ModelHandler.java | 3 +- .../handlers/mdx/AttachmentInstance.java | 9 +- .../viewer5/handlers/mdx/BatchGroup.java | 6 +- .../warsmash/viewer5/handlers/mdx/Bone.java | 8 +- .../viewer5/handlers/mdx/EmitterGroup.java | 40 +- .../handlers/mdx/EventObjectEmitter.java | 4 + .../mdx/EventObjectEmitterObject.java | 93 ++- .../viewer5/handlers/mdx/GenericGroup.java | 4 +- .../viewer5/handlers/mdx/GenericObject.java | 2 +- .../handlers/mdx/GeometryEmitterFuncs.java | 9 +- .../warsmash/viewer5/handlers/mdx/Geoset.java | 8 +- .../viewer5/handlers/mdx/GeosetAnimation.java | 6 +- .../handlers/mdx/MdxComplexInstance.java | 634 +++++++++++++++++- .../viewer5/handlers/mdx/MdxHandler.java | 6 +- .../viewer5/handlers/mdx/MdxModel.java | 285 +++++++- .../handlers/mdx/MdxNodeDescriptor.java | 13 + .../viewer5/handlers/mdx/MdxRenderBatch.java | 128 ++++ .../viewer5/handlers/mdx/MdxShaders.java | 16 +- .../handlers/mdx/MdxSimpleInstance.java | 16 +- .../handlers/mdx/ParticleEmitter2Object.java | 2 +- .../viewer5/handlers/mdx/QuaternionSd.java | 2 +- .../viewer5/handlers/mdx/ReplaceableIds.java | 19 +- .../viewer5/handlers/mdx/ScalarSd.java | 2 +- .../warsmash/viewer5/handlers/mdx/Sd.java | 7 +- .../handlers/mdx/SdArrayDescriptor.java | 24 + .../viewer5/handlers/mdx/SdSequence.java | 17 +- .../viewer5/handlers/mdx/SetupGeosets.java | 40 +- .../viewer5/handlers/mdx/SetupGroups.java | 12 +- .../handlers/mdx/SetupSimpleGroups.java | 62 ++ .../handlers/mdx/TextureAnimation.java | 4 + .../viewer5/handlers/mdx/UInt32Sd.java | 2 +- .../viewer5/handlers/mdx/VectorSd.java | 2 +- .../warsmash/desktop/DesktopLauncher.java | 29 +- 64 files changed, 1892 insertions(+), 343 deletions(-) create mode 100644 core/src/com/etheller/warsmash/viewer5/BatchedInstance.java create mode 100644 core/src/com/etheller/warsmash/viewer5/RenderBatch.java create mode 100644 core/src/com/etheller/warsmash/viewer5/UpdatableObject.java create mode 100644 core/src/com/etheller/warsmash/viewer5/gl/ANGLEInstancedArrays.java create mode 100644 core/src/com/etheller/warsmash/viewer5/gl/DataTexture.java create mode 100644 core/src/com/etheller/warsmash/viewer5/gl/Extensions.java delete mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/Batch.java delete mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/BatchDescriptor.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxNodeDescriptor.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxRenderBatch.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/SdArrayDescriptor.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/SetupSimpleGroups.java diff --git a/core/src/com/etheller/warsmash/WarsmashGdxGame.java b/core/src/com/etheller/warsmash/WarsmashGdxGame.java index 3f12574..2e72845 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxGame.java @@ -1,65 +1,154 @@ package com.etheller.warsmash; -import java.awt.image.BufferedImage; -import java.io.IOException; -import java.nio.file.Paths; - -import javax.imageio.ImageIO; +import java.util.Arrays; import com.badlogic.gdx.ApplicationAdapter; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.GL20; -import com.badlogic.gdx.graphics.Texture; -import com.badlogic.gdx.graphics.Texture.TextureFilter; -import com.badlogic.gdx.graphics.g2d.BitmapFont; -import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.badlogic.gdx.math.Quaternion; +import com.badlogic.gdx.math.Vector3; +import com.etheller.warsmash.datasources.CompoundDataSource; import com.etheller.warsmash.datasources.DataSource; -import com.etheller.warsmash.datasources.FolderDataSource; -import com.etheller.warsmash.util.ImageUtils; -import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.datasources.DataSourceDescriptor; +import com.etheller.warsmash.datasources.FolderDataSourceDescriptor; +import com.etheller.warsmash.viewer5.Camera; +import com.etheller.warsmash.viewer5.CanvasProvider; +import com.etheller.warsmash.viewer5.ModelViewer; +import com.etheller.warsmash.viewer5.PathSolver; +import com.etheller.warsmash.viewer5.Scene; +import com.etheller.warsmash.viewer5.SolvedPath; +import com.etheller.warsmash.viewer5.handlers.mdx.MdxHandler; +import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; +import com.etheller.warsmash.viewer5.handlers.mdx.MdxSimpleInstance; -public class WarsmashGdxGame extends ApplicationAdapter { - private SpriteBatch batch; - private BitmapFont font; +public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvider { private DataSource codebase; - private Texture texture; + private ModelViewer viewer; + private MdxModel model; + private CameraManager cameraManager; @Override public void create() { - this.codebase = new FolderDataSource(Paths.get("C:/MPQBuild/War3.mpq/war3.mpq")); + final String renderer = Gdx.gl.glGetString(GL20.GL_RENDERER); + System.err.println("Renderer: " + renderer); + + final FolderDataSourceDescriptor war3mpq = new FolderDataSourceDescriptor( + "D:\\NEEDS_ORGANIZING\\MPQBuild\\War3.mpq\\war3.mpq"); + final FolderDataSourceDescriptor testingFolder = new FolderDataSourceDescriptor( + "D:\\NEEDS_ORGANIZING\\MPQBuild\\Test"); + this.codebase = new CompoundDataSource(Arrays.asList(war3mpq, testingFolder)); + this.viewer = new ModelViewer(this.codebase, this); + + this.viewer.addHandler(new MdxHandler()); + + final Scene scene = this.viewer.addScene(); + + this.cameraManager = new CameraManager(); + this.cameraManager.setupCamera(scene); + +// this.model = (MdxModel) this.viewer.load("units\\human\\footman\\footman.mdx", new PathSolver() { + this.model = (MdxModel) this.viewer.load("Cube.mdx", new PathSolver() { + @Override + public SolvedPath solve(final String src, final Object solverParams) { + return new SolvedPath(src, src.substring(src.lastIndexOf('.')), true); + } + }, null); + + final MdxSimpleInstance instance = (MdxSimpleInstance) this.model.addInstance(1); + + instance.setScene(scene); + +// instance.setSequence(1); +// +// instance.setSequenceLoopMode(2); - final War3ID id = War3ID.fromString("ipea"); - try { - final String path = "terrainart\\lordaeronsummer\\lords_dirt.blp"; - final boolean has = this.codebase.has(path); - final BufferedImage img = ImageIO.read(this.codebase.getResourceAsStream(path)); - this.texture = ImageUtils.getTexture(ImageUtils.forceBufferedImagesRGB(img)); - this.texture.setFilter(TextureFilter.Linear, TextureFilter.Linear); - } - catch (final IOException e) { - throw new RuntimeException(e); - } - this.batch = new SpriteBatch(); - this.font = new BitmapFont(); } @Override public void render() { - Gdx.gl.glClearColor(0, 1, 0, 1); - Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); - final int srcFunc = this.batch.getBlendSrcFunc(); - final int dstFunc = this.batch.getBlendDstFunc(); + this.viewer.updateAndRender(); - this.batch.enableBlending(); - this.batch.begin(); -// this.font.draw(this.batch, "Hello World", 100, 100); - this.batch.setBlendFunction(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA); - this.batch.draw(this.texture, 0, 0); - this.batch.end(); - this.batch.setBlendFunction(srcFunc, dstFunc); +// gl.glDrawElements(GL20.GL_TRIANGLES, this.elements, GL20.GL_UNSIGNED_SHORT, this.faceOffset); } @Override public void dispose() { } + + @Override + public float getWidth() { + return Gdx.graphics.getWidth(); + } + + @Override + public float getHeight() { + return Gdx.graphics.getHeight(); + } + + class CameraManager { + private CanvasProvider canvas; + private Camera camera; + private float moveSpeed; + private float rotationSpeed; + private float zoomFactor; + private float horizontalAngle; + private float verticalAngle; + private float distance; + private Vector3 position; + private Vector3 target; + private Vector3 worldUp; + private Vector3 vecHeap; + private Quaternion quatHeap; + private Quaternion quatHeap2; + + // An orbit camera setup example. + // Left mouse button controls the orbit itself. + // The right mouse button allows to move the camera and the point it's looking + // at on the XY plane. + // Scrolling zooms in and out. + private void setupCamera(final Scene scene) { + this.canvas = scene.viewer.canvas; + this.camera = scene.camera; + this.moveSpeed = 2; + this.rotationSpeed = (float) (Math.PI / 180); + this.zoomFactor = 0.1f; + this.horizontalAngle = (float) (Math.PI / 2); + this.verticalAngle = (float) (Math.PI / 4); + this.distance = 500; + this.position = new Vector3(); + this.target = new Vector3(); + this.worldUp = new Vector3(0, 0, 1); + this.vecHeap = new Vector3(); + this.quatHeap = new Quaternion(); + this.quatHeap2 = new Quaternion(); + + updateCamera(); + +// cameraUpdate(); + } + + private void updateCamera() { + // Limit the vertical angle so it doesn't flip. + // Since the camera uses a quaternion, flips don't matter to it, but this feels + // better. + this.verticalAngle = (float) Math.min(Math.max(0.01, this.verticalAngle), Math.PI - 0.01); + + this.quatHeap.idt(); + this.quatHeap.setFromAxisRad(0, 0, 1, this.horizontalAngle); + this.quatHeap2.idt(); + this.quatHeap2.setFromAxisRad(1, 0, 0, this.verticalAngle); + this.quatHeap.mul(this.quatHeap2); + + this.position.set(0, 0, 1); + this.quatHeap.transform(this.position); + this.position.scl(this.distance); + this.position = this.position.add(this.target); + + this.camera.moveToAndFace(this.position, this.target, this.worldUp); + } + +// private void cameraUpdate() { +// +// } + } } diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/Extent.java b/core/src/com/etheller/warsmash/parsers/mdlx/Extent.java index 3be2e48..17b15fe 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/Extent.java +++ b/core/src/com/etheller/warsmash/parsers/mdlx/Extent.java @@ -44,4 +44,12 @@ public class Extent { public float getBoundsRadius() { return this.boundsRadius; } + + public float[] getMin() { + return this.min; + } + + public float[] getMax() { + return this.max; + } } diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/Material.java b/core/src/com/etheller/warsmash/parsers/mdlx/Material.java index 46cb097..7060a6e 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/Material.java +++ b/core/src/com/etheller/warsmash/parsers/mdlx/Material.java @@ -118,4 +118,24 @@ public class Material implements MdlxBlock, Chunk { return size; } + + public int getPriorityPlane() { + return this.priorityPlane; + } + + public void setPriorityPlane(final int priorityPlane) { + this.priorityPlane = priorityPlane; + } + + public int getFlags() { + return this.flags; + } + + public void setFlags(final int flags) { + this.flags = flags; + } + + public List getLayers() { + return this.layers; + } } diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/MdlxModel.java b/core/src/com/etheller/warsmash/parsers/mdlx/MdlxModel.java index c6f0629..8c7ef42 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/MdlxModel.java +++ b/core/src/com/etheller/warsmash/parsers/mdlx/MdlxModel.java @@ -654,4 +654,88 @@ public class MdlxModel { public List getPivotPoints() { return this.pivotPoints; } + + public int getVersion() { + return this.version; + } + + public String getName() { + return this.name; + } + + public String getAnimationFile() { + return this.animationFile; + } + + public Extent getExtent() { + return this.extent; + } + + public long getBlendTime() { + return this.blendTime; + } + + public List getMaterials() { + return this.materials; + } + + public List getTextures() { + return this.textures; + } + + public List getTextureAnimations() { + return this.textureAnimations; + } + + public List getGeosets() { + return this.geosets; + } + + public List getGeosetAnimations() { + return this.geosetAnimations; + } + + public List getBones() { + return this.bones; + } + + public List getLights() { + return this.lights; + } + + public List getHelpers() { + return this.helpers; + } + + public List getAttachments() { + return this.attachments; + } + + public List getParticleEmitters() { + return this.particleEmitters; + } + + public List getParticleEmitters2() { + return this.particleEmitters2; + } + + public List getRibbonEmitters() { + return this.ribbonEmitters; + } + + public List getCameras() { + return this.cameras; + } + + public List getEventObjects() { + return this.eventObjects; + } + + public List getCollisionShapes() { + return this.collisionShapes; + } + + public List getUnknownChunks() { + return this.unknownChunks; + } } diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/Sequence.java b/core/src/com/etheller/warsmash/parsers/mdlx/Sequence.java index 37cab80..a2811ce 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/Sequence.java +++ b/core/src/com/etheller/warsmash/parsers/mdlx/Sequence.java @@ -103,6 +103,10 @@ public class Sequence implements MdlxBlock { } public long[] getInterval() { - return interval; + return this.interval; + } + + public int getFlags() { + return this.flags; } } diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/Texture.java b/core/src/com/etheller/warsmash/parsers/mdlx/Texture.java index 68ea94b..d55b505 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/Texture.java +++ b/core/src/com/etheller/warsmash/parsers/mdlx/Texture.java @@ -78,4 +78,16 @@ public class Texture implements MdlxBlock { stream.endBlock(); } + public int getReplaceableId() { + return this.replaceableId; + } + + public String getPath() { + return this.path; + } + + public int getFlags() { + return this.flags; + } + } diff --git a/core/src/com/etheller/warsmash/util/RenderMathUtils.java b/core/src/com/etheller/warsmash/util/RenderMathUtils.java index acf0574..5ce1055 100644 --- a/core/src/com/etheller/warsmash/util/RenderMathUtils.java +++ b/core/src/com/etheller/warsmash/util/RenderMathUtils.java @@ -337,7 +337,7 @@ public enum RenderMathUtils { return (plane.x * px) + (plane.y * py) + plane.w; } - public static int testSphere(final Vector4[] planes, final float x, final float y, final float z, final int r, + public static int testSphere(final Vector4[] planes, final float x, final float y, final float z, final float r, int first) { if (first == -1) { first = 0; diff --git a/core/src/com/etheller/warsmash/util/SlkFile.java b/core/src/com/etheller/warsmash/util/SlkFile.java index 1c37a21..da5e6d6 100644 --- a/core/src/com/etheller/warsmash/util/SlkFile.java +++ b/core/src/com/etheller/warsmash/util/SlkFile.java @@ -4,7 +4,7 @@ import java.util.ArrayList; import java.util.List; public class SlkFile { - public List> rows; + public List> rows = new ArrayList<>(); public SlkFile(final String buffer) { if (buffer != null) { @@ -44,7 +44,7 @@ public class SlkFile { this.rows.set(y, new ArrayList<>()); } - if (valueString.charAt('0') == '"') { + if (valueString.charAt(0) == '"') { value = valueString.substring(1, valueString.length() - 1); } else if ("TRUE".equals(valueString)) { diff --git a/core/src/com/etheller/warsmash/viewer5/BatchedInstance.java b/core/src/com/etheller/warsmash/viewer5/BatchedInstance.java new file mode 100644 index 0000000..dca3214 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/BatchedInstance.java @@ -0,0 +1,16 @@ +package com.etheller.warsmash.viewer5; + +/** + * A batched model instance. + */ +public abstract class BatchedInstance extends ModelInstance { + + public BatchedInstance(final Model model) { + super(model); + } + + @Override + public boolean isBatched() { + return true; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/Bounds.java b/core/src/com/etheller/warsmash/viewer5/Bounds.java index cc99170..0de9b71 100644 --- a/core/src/com/etheller/warsmash/viewer5/Bounds.java +++ b/core/src/com/etheller/warsmash/viewer5/Bounds.java @@ -1,5 +1,19 @@ package com.etheller.warsmash.viewer5; public class Bounds { - public int x, y, r; + public float x, y, z, r; + + public void fromExtents(final float[] min, final float[] max) { + final float x = min[0]; + final float y = min[1]; + final float z = min[2]; + final float w = max[0] - x; + final float d = max[1] - y; + final float h = max[2] - z; + + this.x = x + (w / 2f); + this.y = y + (d / 2f); + this.z = z + (h / 2f); + this.r = (float) (Math.max(Math.max(w, d), h) / 2.); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/Camera.java b/core/src/com/etheller/warsmash/viewer5/Camera.java index 7b875ce..79dfa53 100644 --- a/core/src/com/etheller/warsmash/viewer5/Camera.java +++ b/core/src/com/etheller/warsmash/viewer5/Camera.java @@ -34,12 +34,26 @@ public class Camera { public final Quaternion rotation; public Quaternion inverseRotation; - private final Matrix4 worldMatrix; + /** + * World -> View. + */ + private final Matrix4 viewMatrix; + /** + * View -> Clip. + */ private final Matrix4 projectionMatrix; - public final Matrix4 worldProjectionMatrix; - private final Matrix4 inverseWorldMatrix; - private final Matrix4 inverseRotationMatrix; - private final Matrix4 inverseWorldProjectionMatrix; + /** + * World -> Clip. + */ + public final Matrix4 viewProjectionMatrix; + /** + * View -> World. + */ + private final Matrix4 inverseViewMatrix; + /** + * Clip -> World. + */ + private final Matrix4 inverseViewProjectionMatrix; public final Vector3 directionX; public final Vector3 directionY; public final Vector3 directionZ; @@ -75,12 +89,11 @@ public class Camera { // Derived values. this.inverseRotation = new Quaternion(); - this.worldMatrix = new Matrix4(); + this.viewMatrix = new Matrix4(); this.projectionMatrix = new Matrix4(); - this.worldProjectionMatrix = new Matrix4(); - this.inverseWorldMatrix = new Matrix4(); - this.inverseRotationMatrix = new Matrix4(); - this.inverseWorldProjectionMatrix = new Matrix4(); + this.viewProjectionMatrix = new Matrix4(); + this.inverseViewMatrix = new Matrix4(); + this.inverseViewProjectionMatrix = new Matrix4(); this.directionX = new Vector3(); this.directionY = new Vector3(); this.directionZ = new Vector3(); @@ -222,9 +235,9 @@ public class Camera { final Vector3 location = this.location; final Quaternion rotation = this.rotation; final Quaternion inverseRotation = this.inverseRotation; - final Matrix4 worldMatrix = this.worldMatrix; + final Matrix4 viewMatrix = this.viewMatrix; final Matrix4 projectionMatrix = this.projectionMatrix; - final Matrix4 worldProjectionMatrix = this.worldProjectionMatrix; + final Matrix4 viewProjectionMatrix = this.viewProjectionMatrix; final Vector3[] vectors = this.vectors; final Vector3[] billboardedVectors = this.billboardedVectors; @@ -238,19 +251,19 @@ public class Camera { } rotation.toMatrix(projectionMatrix.val); - worldMatrix.translate(vectorHeap.set(location).scl(-1)); + viewMatrix.translate(vectorHeap.set(location).scl(-1)); inverseRotation.set(rotation).conjugate(); // World projection matrix // World space -> NDC space - worldProjectionMatrix.set(projectionMatrix).mul(worldMatrix); + viewProjectionMatrix.set(projectionMatrix).mul(viewMatrix); // Recalculate the camera's frustum planes - RenderMathUtils.unpackPlanes(this.planes, worldProjectionMatrix); + RenderMathUtils.unpackPlanes(this.planes, viewProjectionMatrix); // Inverse world matrix // Camera space -> world space - this.inverseWorldMatrix.set(worldMatrix).inv(); + this.inverseViewMatrix.set(viewMatrix).inv(); this.directionX.set(RenderMathUtils.VEC3_UNIT_X); inverseRotation.transform(this.directionX); @@ -261,8 +274,8 @@ public class Camera { // Inverse world projection matrix // NDC space -> World space - this.inverseWorldProjectionMatrix.set(worldProjectionMatrix); - this.inverseWorldProjectionMatrix.inv(); + this.inverseViewProjectionMatrix.set(viewProjectionMatrix); + this.inverseViewProjectionMatrix.inv(); for (int i = 0; i < 7; i++) { billboardedVectors[i].set(vectors[i]); @@ -281,18 +294,18 @@ public class Camera { } public Vector3 cameraToWorld(final Vector3 out, final Vector3 v) { - return out.set(v).prj(this.inverseWorldMatrix); + return out.set(v).prj(this.inverseViewMatrix); } public Vector3 worldToCamera(final Vector3 out, final Vector3 v) { - return out.set(v).prj(this.worldMatrix); + return out.set(v).prj(this.viewMatrix); } public Vector2 worldToScreen(final Vector2 out, final Vector3 v) { final Rectangle viewport = this.rect; vectorHeap.set(v); - vectorHeap.prj(this.inverseWorldMatrix); + vectorHeap.prj(this.inverseViewMatrix); out.x = Math.round(((vectorHeap.x + 1) / 2) * viewport.width); out.y = Math.round(((vectorHeap.y + 1) / 2) * viewport.height); @@ -309,10 +322,10 @@ public class Camera { final Rectangle viewport = this.rect; // Intersection on the near-plane - RenderMathUtils.unproject(a, c.set(x, y, 0), this.inverseWorldProjectionMatrix, viewport); + RenderMathUtils.unproject(a, c.set(x, y, 0), this.inverseViewProjectionMatrix, viewport); // Intersection on the far-plane - RenderMathUtils.unproject(b, c.set(x, y, 1), this.inverseWorldProjectionMatrix, viewport); + RenderMathUtils.unproject(b, c.set(x, y, 1), this.inverseViewProjectionMatrix, viewport); out[0] = a.x; out[1] = a.y; diff --git a/core/src/com/etheller/warsmash/viewer5/Emitter.java b/core/src/com/etheller/warsmash/viewer5/Emitter.java index 7b60b12..4d966f8 100644 --- a/core/src/com/etheller/warsmash/viewer5/Emitter.java +++ b/core/src/com/etheller/warsmash/viewer5/Emitter.java @@ -3,7 +3,8 @@ package com.etheller.warsmash.viewer5; import java.util.ArrayList; import java.util.List; -public abstract class Emitter>> { +public abstract class Emitter>> + implements UpdatableObject { public final MODEL_INSTANCE instance; public final List objects; @@ -36,6 +37,7 @@ public abstract class Emitter children; + public Vector3 pivot; + public Vector3 localLocation; + public Quaternion localRotation; + public Vector3 localScale; + public Vector3 worldLocation; + public Quaternion worldRotation; + public Vector3 worldScale; + public Vector3 inverseWorldLocation; + public Quaternion inverseWorldRotation; + public Vector3 inverseWorldScale; + public Matrix4 localMatrix; + public Matrix4 worldMatrix; + public GenericNode parent; + public List children; public boolean dontInheritTranslation; public boolean dontInheritRotation; public boolean dontInheritScaling; - protected boolean visible; - protected boolean wasDirty; - protected boolean dirty; + public boolean visible; + public boolean wasDirty; + public boolean dirty; protected abstract void update(float dt, Scene scene); } diff --git a/core/src/com/etheller/warsmash/viewer5/GenericResource.java b/core/src/com/etheller/warsmash/viewer5/GenericResource.java index f8954a9..79ff358 100644 --- a/core/src/com/etheller/warsmash/viewer5/GenericResource.java +++ b/core/src/com/etheller/warsmash/viewer5/GenericResource.java @@ -29,7 +29,7 @@ public final class GenericResource extends Resource { @Override protected void error(final Exception e) { - + e.printStackTrace(); } } diff --git a/core/src/com/etheller/warsmash/viewer5/GridCell.java b/core/src/com/etheller/warsmash/viewer5/GridCell.java index dc48f23..194c2a9 100644 --- a/core/src/com/etheller/warsmash/viewer5/GridCell.java +++ b/core/src/com/etheller/warsmash/viewer5/GridCell.java @@ -37,6 +37,9 @@ public class GridCell { } public boolean isVisible(final Camera camera) { + if (true) { + return true; + } this.plane = RenderMathUtils.testCell(camera.planes, this.left, this.right, this.bottom, this.top, this.plane); return this.plane == -1; diff --git a/core/src/com/etheller/warsmash/viewer5/Model.java b/core/src/com/etheller/warsmash/viewer5/Model.java index d5aba41..379781d 100644 --- a/core/src/com/etheller/warsmash/viewer5/Model.java +++ b/core/src/com/etheller/warsmash/viewer5/Model.java @@ -4,7 +4,6 @@ import java.util.ArrayList; import java.util.List; import com.etheller.warsmash.viewer5.handlers.ModelHandler; -import com.etheller.warsmash.viewer5.handlers.ModelInstanceDescriptor; public abstract class Model extends HandlerResource { public Bounds bounds; @@ -17,13 +16,14 @@ public abstract class Model extends HandlerResourc this.preloadedInstances = new ArrayList<>(); } + protected abstract ModelInstance createInstance(int type); + public ModelInstance addInstance() { return addInstance(0); } public ModelInstance addInstance(final int type) { - final ModelInstanceDescriptor instanceDescriptor = this.handler.instanceDescriptor; - final ModelInstance instance = instanceDescriptor.create(this); + final ModelInstance instance = createInstance(type); if (this.ok) { instance.load(); diff --git a/core/src/com/etheller/warsmash/viewer5/ModelInstance.java b/core/src/com/etheller/warsmash/viewer5/ModelInstance.java index 5080ae8..96a7afe 100644 --- a/core/src/com/etheller/warsmash/viewer5/ModelInstance.java +++ b/core/src/com/etheller/warsmash/viewer5/ModelInstance.java @@ -1,6 +1,5 @@ package com.etheller.warsmash.viewer5; -import com.badlogic.gdx.math.Vector3; import com.etheller.warsmash.util.RenderMathUtils; import com.etheller.warsmash.util.Vector4; @@ -19,8 +18,6 @@ public abstract class ModelInstance extends Node { public boolean paused; public boolean rendered; - public Vector3 worldLocation; - public Vector3 worldScale; public Scene scene; public ModelInstance(final Model model) { @@ -96,6 +93,9 @@ public abstract class ModelInstance extends Node { } public boolean isVisible(final Camera camera) { + if (true) { + return true; + } final float x = this.worldLocation.x; final float y = this.worldLocation.y; final float z = this.worldLocation.z; @@ -122,4 +122,6 @@ public abstract class ModelInstance extends Node { public abstract void renderTranslucent(); public abstract void load(); + + protected abstract RenderBatch getBatch(TextureMapper textureMapper2); } diff --git a/core/src/com/etheller/warsmash/viewer5/ModelViewer.java b/core/src/com/etheller/warsmash/viewer5/ModelViewer.java index d80f031..28e27ae 100644 --- a/core/src/com/etheller/warsmash/viewer5/ModelViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/ModelViewer.java @@ -57,7 +57,7 @@ public class ModelViewer { this.rectBuffer = this.gl.glGenBuffer(); this.buffer = new ClientBuffer(this.gl); this.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.rectBuffer); - final ByteBuffer temp = ByteBuffer.allocate(6); + final ByteBuffer temp = ByteBuffer.allocateDirect(6); temp.put((byte) 0); temp.put((byte) 1); temp.put((byte) 2); @@ -129,7 +129,6 @@ public class ModelViewer { String finalSrc = src; String extension = ""; boolean isFetch = false; - final boolean resolved = false; // If a given path solver, resolve. if (pathSolver != null) { @@ -138,12 +137,17 @@ public class ModelViewer { finalSrc = solved.getFinalSrc(); extension = solved.getExtension(); isFetch = solved.isFetch(); - } - // Built-in texture sources - // ---- TODO not using JS code here + if (!(extension instanceof String)) { + throw new IllegalStateException("The path solver did not return an extension!"); + } + + if (extension.charAt(0) != '.') { + extension = '.' + extension; + } + // Built-in texture sources + // ---- TODO not using JS code here - if (resolved) { final Object[] handlerAndDataType = this.findHandler(extension.toLowerCase()); // Is there a handler for this file type? @@ -181,8 +185,10 @@ public class ModelViewer { } } else { - throw new IllegalStateException("Load unresolved: " + finalSrc); + throw new IllegalStateException( + "Could not resolve " + finalSrc + ". Did you forget to pass a path solver?"); } + } public boolean has(final String key) { @@ -276,6 +282,7 @@ public class ModelViewer { public void startFrame() { this.gl.glDepthMask(true); this.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT); + this.gl.glClearColor(0.5f, 0.5f, 0.5f, 1); // TODO remove white background } public void render() { diff --git a/core/src/com/etheller/warsmash/viewer5/RenderBatch.java b/core/src/com/etheller/warsmash/viewer5/RenderBatch.java new file mode 100644 index 0000000..83dbb4d --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/RenderBatch.java @@ -0,0 +1,40 @@ +package com.etheller.warsmash.viewer5; + +import java.util.ArrayList; +import java.util.List; + +/** + * A render batch. + */ +public abstract class RenderBatch { + public Scene scene; + public Model model; + public TextureMapper textureMapper; + public List instances = new ArrayList<>(); + public int count = 0; + + public abstract void render(); + + public RenderBatch(final Scene scene, final Model model, final TextureMapper textureMapper) { + this.scene = scene; + this.model = model; + this.textureMapper = textureMapper; + } + + public void clear() { + this.count = 0; + } + + public void add(final ModelInstance instance) { + if (this.count == this.instances.size()) { + this.instances.add(instance); + } + else if (this.count > this.instances.size()) { + throw new IllegalStateException("count > size"); + } + else { + this.instances.set(this.count, instance); + } + this.count++; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/Scene.java b/core/src/com/etheller/warsmash/viewer5/Scene.java index b23c227..423662d 100644 --- a/core/src/com/etheller/warsmash/viewer5/Scene.java +++ b/core/src/com/etheller/warsmash/viewer5/Scene.java @@ -8,8 +8,6 @@ import java.util.List; import java.util.Map; import com.badlogic.gdx.math.Rectangle; -import com.etheller.warsmash.viewer5.handlers.Batch; -import com.etheller.warsmash.viewer5.handlers.BatchDescriptor; /** * A scene. @@ -29,22 +27,22 @@ import com.etheller.warsmash.viewer5.handlers.BatchDescriptor; */ public class Scene { - private final ModelViewer viewer; + public final ModelViewer viewer; public final Camera camera; - final Grid grid; + public final Grid grid; public int visibleCells; public int visibleInstances; public int updatedParticles; public boolean audioEnabled; public AudioContext audioContext; - private final List instances; - private final int currentInstance; - private final List batchedInstances; - private final int currentBatchedInstance; + public final List instances; + public final int currentInstance; + public final List batchedInstances; + public final int currentBatchedInstance; public final EmittedObjectUpdater emitterObjectUpdater; - private final Map batches; - private final Comparator instanceDepthComparator; + public final Map batches; + public final Comparator instanceDepthComparator; public Scene(final ModelViewer viewer) { final CanvasProvider canvas = viewer.canvas; @@ -144,12 +142,10 @@ public class Scene { public void addToBatch(final ModelInstance instance) { final TextureMapper textureMapper = instance.textureMapper; - Batch batch = this.batches.get(textureMapper); + RenderBatch batch = this.batches.get(textureMapper); if (batch == null) { - final Model model = instance.model; - final BatchDescriptor batchDescriptor = model.handler.batchDescriptor; - batch = batchDescriptor.create(this, model, textureMapper); + batch = instance.getBatch(textureMapper); this.batches.put(textureMapper, batch); } @@ -240,7 +236,7 @@ public class Scene { this.viewer.gl.glViewport((int) viewport.x, (int) viewport.y, (int) viewport.width, (int) viewport.height); // Clear all of the batches. - for (final Batch batch : this.batches.values()) { + for (final RenderBatch batch : this.batches.values()) { batch.clear(); } @@ -250,7 +246,7 @@ public class Scene { } // Render all of the batches. - for (final Batch batch : this.batches.values()) { + for (final RenderBatch batch : this.batches.values()) { batch.render(); } diff --git a/core/src/com/etheller/warsmash/viewer5/Shaders.java b/core/src/com/etheller/warsmash/viewer5/Shaders.java index 1a2d3b9..16d4c77 100644 --- a/core/src/com/etheller/warsmash/viewer5/Shaders.java +++ b/core/src/com/etheller/warsmash/viewer5/Shaders.java @@ -54,5 +54,5 @@ public class Shaders { " vec3 quat_transform(vec2 q, vec3 v) {\r\n" + // " return vec3(quat_transform(q, v.xy), v.z);\r\n" + // " }\r\n" + // - " `,"; + " "; } diff --git a/core/src/com/etheller/warsmash/viewer5/SkeletalNode.java b/core/src/com/etheller/warsmash/viewer5/SkeletalNode.java index 4958705..ee340d8 100644 --- a/core/src/com/etheller/warsmash/viewer5/SkeletalNode.java +++ b/core/src/com/etheller/warsmash/viewer5/SkeletalNode.java @@ -1,7 +1,6 @@ package com.etheller.warsmash.viewer5; import java.util.ArrayList; -import java.util.List; import com.badlogic.gdx.math.Matrix4; import com.badlogic.gdx.math.Quaternion; @@ -13,30 +12,9 @@ public abstract class SkeletalNode extends GenericNode { protected static final Quaternion rotationHeap = new Quaternion(); protected static final Vector3 scalingHeap = new Vector3(); - public final Vector3 pivot; - public final Vector3 localLocation; - public final Quaternion localRotation; - public final Vector3 localScale; - public final Vector3 worldLocation; - public final Quaternion worldRotation; - public final Vector3 worldScale; - public final Vector3 inverseWorldLocation; - public final Quaternion inverseWorldRotation; - public final Vector3 inverseWorldScale; - public final Matrix4 localMatrix; - public final Matrix4 worldMatrix; - public SkeletalNode parent; - public final List children; - public final boolean dontInheritTranslation; - public final boolean dontInheritRotation; - public final boolean dontInheritScaling; - public boolean visible; - public boolean wasDirty; - public boolean dirty; + public UpdatableObject object; - public Object object; - - public final boolean billboarded; + public boolean billboarded; public final boolean billboardedX; public final boolean billboardedY; public final boolean billboardedZ; @@ -158,7 +136,7 @@ public abstract class SkeletalNode extends GenericNode { this.inverseWorldLocation.z = -this.worldLocation.z; } - protected void updateChildren(final float dt, final Scene scene) { + public void updateChildren(final float dt, final Scene scene) { for (int i = 0, l = this.children.size(); i < l; i++) { this.children.get(i).update(dt, scene); } diff --git a/core/src/com/etheller/warsmash/viewer5/Texture.java b/core/src/com/etheller/warsmash/viewer5/Texture.java index e171ae7..0043ae2 100644 --- a/core/src/com/etheller/warsmash/viewer5/Texture.java +++ b/core/src/com/etheller/warsmash/viewer5/Texture.java @@ -1,5 +1,6 @@ package com.etheller.warsmash.viewer5; +import com.badlogic.gdx.graphics.Texture.TextureWrap; import com.etheller.warsmash.viewer5.handlers.ResourceHandler; public abstract class Texture extends HandlerResource { @@ -16,7 +17,7 @@ public abstract class Texture extends HandlerResource { @Override protected void error(final Exception e) { - throw new RuntimeException(e); + e.printStackTrace(); } public void bind(final int unit) { @@ -39,4 +40,12 @@ public abstract class Texture extends HandlerResource { return this.gdxTexture.glTarget; } + public void setWrapS(final boolean wrapS) { + this.gdxTexture.setWrap(wrapS ? TextureWrap.Repeat : TextureWrap.ClampToEdge, this.gdxTexture.getVWrap()); + } + + public void setWrapT(final boolean wrapT) { + this.gdxTexture.setWrap(this.gdxTexture.getUWrap(), wrapT ? TextureWrap.Repeat : TextureWrap.ClampToEdge); + } + } diff --git a/core/src/com/etheller/warsmash/viewer5/UpdatableObject.java b/core/src/com/etheller/warsmash/viewer5/UpdatableObject.java new file mode 100644 index 0000000..52f57fe --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/UpdatableObject.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.viewer5; + +public interface UpdatableObject { + void update(float dt); +} diff --git a/core/src/com/etheller/warsmash/viewer5/gl/ANGLEInstancedArrays.java b/core/src/com/etheller/warsmash/viewer5/gl/ANGLEInstancedArrays.java new file mode 100644 index 0000000..c1219af --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/gl/ANGLEInstancedArrays.java @@ -0,0 +1,13 @@ +package com.etheller.warsmash.viewer5.gl; + +/** + * TODO what is this? + */ +public interface ANGLEInstancedArrays { + + void glVertexAttribDivisorANGLE(int index, int divisor); + + void glDrawArraysInstancedANGLE(int mode, int first, int count, int instanceCount); + + void glDrawElementsInstancedANGLE(int mode, int count, int type, int indicesOffset, int instanceCount); +} diff --git a/core/src/com/etheller/warsmash/viewer5/gl/ClientBuffer.java b/core/src/com/etheller/warsmash/viewer5/gl/ClientBuffer.java index ce16b5f..22ff687 100644 --- a/core/src/com/etheller/warsmash/viewer5/gl/ClientBuffer.java +++ b/core/src/com/etheller/warsmash/viewer5/gl/ClientBuffer.java @@ -33,7 +33,7 @@ public class ClientBuffer { this.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.buffer); - this.arrayBuffer = ByteBuffer.allocate(this.size); + this.arrayBuffer = ByteBuffer.allocateDirect(this.size); this.gl.glBufferData(GL20.GL_ARRAY_BUFFER, this.size, this.arrayBuffer, GL20.GL_DYNAMIC_DRAW); this.byteView = this.arrayBuffer; this.floatView = this.arrayBuffer.asFloatBuffer(); diff --git a/core/src/com/etheller/warsmash/viewer5/gl/DataTexture.java b/core/src/com/etheller/warsmash/viewer5/gl/DataTexture.java new file mode 100644 index 0000000..4ba4a49 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/gl/DataTexture.java @@ -0,0 +1,67 @@ +package com.etheller.warsmash.viewer5.gl; + +import java.nio.Buffer; + +import com.badlogic.gdx.graphics.GL20; + +public class DataTexture { + public GL20 gl; + public int texture; + public int format; + public int width = 0; + public int height = 0; + + public DataTexture(final GL20 gl, final int channels, final int width, final int height) { + this.gl = gl; + this.texture = gl.glGenTexture(); + this.format = (channels == 3 ? GL20.GL_RGB : GL20.GL_RGBA); + + gl.glBindTexture(GL20.GL_TEXTURE_2D, this.texture); + gl.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_WRAP_S, GL20.GL_CLAMP_TO_EDGE); + gl.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_WRAP_T, GL20.GL_CLAMP_TO_EDGE); + gl.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_MAG_FILTER, GL20.GL_NEAREST); + gl.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_MAG_FILTER, GL20.GL_NEAREST); + + this.reserve(width, height); + } + + private void reserve(final int width, final int height) { + if ((this.width < width) || (this.height < height)) { + final GL20 gl = this.gl; + + this.width = Math.max(this.width, width); + this.height = Math.max(this.height, height); + + gl.glBindTexture(GL20.GL_TEXTURE_2D, this.texture); + gl.glTexImage2D(GL20.GL_TEXTURE_2D, 0, this.format, this.width, this.height, 0, this.format, GL20.GL_FLOAT, + null); + } + + } + + public void bindAndUpdate(final Buffer buffer) { + bindAndUpdate(buffer, this.width, this.height); + } + + public void bindAndUpdate(final Buffer buffer, final int width, final int height) { + final GL20 gl = this.gl; + + gl.glBindTexture(GL20.GL_TEXTURE_2D, this.texture); + gl.glTexSubImage2D(GL20.GL_TEXTURE_2D, 0, 0, 0, width, height, this.format, GL20.GL_FLOAT, buffer); + } + + public void bind(final int unit) { + final GL20 gl = this.gl; + + gl.glActiveTexture(GL20.GL_TEXTURE0 + unit); + gl.glBindTexture(GL20.GL_TEXTURE_2D, this.texture); + } + + public int getWidth() { + return this.width; + } + + public int getHeight() { + return this.height; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/gl/Extensions.java b/core/src/com/etheller/warsmash/viewer5/gl/Extensions.java new file mode 100644 index 0000000..9bec9da --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/gl/Extensions.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.viewer5.gl; + +public class Extensions { + public static ANGLEInstancedArrays angleInstancedArrays; +} diff --git a/core/src/com/etheller/warsmash/viewer5/gl/WebGL.java b/core/src/com/etheller/warsmash/viewer5/gl/WebGL.java index 8de52db..b9e3eb0 100644 --- a/core/src/com/etheller/warsmash/viewer5/gl/WebGL.java +++ b/core/src/com/etheller/warsmash/viewer5/gl/WebGL.java @@ -20,6 +20,7 @@ public class WebGL { public ShaderProgram currentShaderProgram; public String floatPrecision; public final com.badlogic.gdx.graphics.Texture emptyTexture; + public ANGLEInstancedArrays instancedArrays; public WebGL(final GL20 gl) { gl.glDepthFunc(GL20.GL_LEQUAL); @@ -43,6 +44,7 @@ public class WebGL { } } this.emptyTexture = new com.badlogic.gdx.graphics.Texture(imageData); + this.instancedArrays = Extensions.angleInstancedArrays; } public ShaderUnitDeprecated createShaderUnit(final String src, final int type) { @@ -53,10 +55,13 @@ public class WebGL { return this.shaderUnits.get(hash); } - public ShaderProgram createShaderProgram(final String vertexSrc, final String fragmentSrc) { + public ShaderProgram createShaderProgram(String vertexSrc, String fragmentSrc) { + vertexSrc = vertexSrc.replace("mediump", ""); + fragmentSrc = fragmentSrc.replace("mediump", ""); final Map shaderPrograms = this.shaderPrograms; final int hash = stringHash(vertexSrc + fragmentSrc); + ShaderProgram.pedantic = false; if (!shaderPrograms.containsKey(hash)) { shaderPrograms.put(hash, new ShaderProgram(vertexSrc, fragmentSrc)); } @@ -66,6 +71,12 @@ public class WebGL { if (shaderProgram.isCompiled()) { return shaderProgram; } + else { + System.err.println(shaderProgram.getLog()); + if (true) { + throw new IllegalStateException("Bad shader"); + } + } return null; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/Batch.java b/core/src/com/etheller/warsmash/viewer5/handlers/Batch.java deleted file mode 100644 index c107087..0000000 --- a/core/src/com/etheller/warsmash/viewer5/handlers/Batch.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.etheller.warsmash.viewer5.handlers; - -import com.etheller.warsmash.viewer5.ModelInstance; - -public class Batch { - - public void add(final ModelInstance instance) { - // TODO Auto-generated method stub - - } - - public void clear() { - // TODO Auto-generated method stub - - } - - public void render() { - // TODO Auto-generated method stub - - } - -} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/BatchDescriptor.java b/core/src/com/etheller/warsmash/viewer5/handlers/BatchDescriptor.java deleted file mode 100644 index 0ec39b5..0000000 --- a/core/src/com/etheller/warsmash/viewer5/handlers/BatchDescriptor.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.etheller.warsmash.viewer5.handlers; - -import com.etheller.warsmash.viewer5.Model; -import com.etheller.warsmash.viewer5.Scene; -import com.etheller.warsmash.viewer5.TextureMapper; - -public interface BatchDescriptor { - Batch create(Scene scene, Model model, TextureMapper textureMapper); -} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/ModelHandler.java b/core/src/com/etheller/warsmash/viewer5/handlers/ModelHandler.java index 59ea3a0..6598bdc 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/ModelHandler.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/ModelHandler.java @@ -1,6 +1,5 @@ package com.etheller.warsmash.viewer5.handlers; public abstract class ModelHandler extends ResourceHandler { - public BatchDescriptor batchDescriptor; - public ModelInstanceDescriptor instanceDescriptor; + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/AttachmentInstance.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/AttachmentInstance.java index 9588b85..5549044 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/AttachmentInstance.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/AttachmentInstance.java @@ -1,11 +1,13 @@ package com.etheller.warsmash.viewer5.handlers.mdx; -public class AttachmentInstance { +import com.etheller.warsmash.viewer5.UpdatableObject; + +public class AttachmentInstance implements UpdatableObject { private static final float[] visbilityHeap = new float[1]; private final MdxComplexInstance instance; private final Attachment attachment; - private final MdxComplexInstance internalInstance; + public final MdxComplexInstance internalInstance; public AttachmentInstance(final MdxComplexInstance instance, final Attachment attachment) { final MdxModel internalModel = attachment.internalModel; @@ -21,7 +23,8 @@ public class AttachmentInstance { this.internalInstance = internalInstance; } - public void update() { + @Override + public void update(final float dt) { final MdxComplexInstance internalInstance = this.internalInstance; if (internalInstance.model.ok) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/BatchGroup.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/BatchGroup.java index 35e9e6a..78e4335 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/BatchGroup.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/BatchGroup.java @@ -7,6 +7,7 @@ import com.badlogic.gdx.graphics.glutils.ShaderProgram; import com.etheller.warsmash.viewer5.ModelViewer; import com.etheller.warsmash.viewer5.Scene; import com.etheller.warsmash.viewer5.Texture; +import com.etheller.warsmash.viewer5.gl.DataTexture; public class BatchGroup extends GenericGroup { @@ -18,6 +19,7 @@ public class BatchGroup extends GenericGroup { this.isExtended = isExtended; } + @Override public void render(final MdxComplexInstance instance) { final Scene scene = instance.scene; final MdxModel model = this.model; @@ -41,9 +43,9 @@ public class BatchGroup extends GenericGroup { shader.begin(); - shader.setUniformMatrix("u_mvp", scene.camera.worldProjectionMatrix); + shader.setUniformMatrix("u_mvp", scene.camera.viewProjectionMatrix); - final Texture boneTexture = instance.boneTexture; + final DataTexture boneTexture = instance.boneTexture; // Instances of models with no bones don't have a bone texture. if (boneTexture != null) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Bone.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Bone.java index 10167f6..63de3dd 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Bone.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Bone.java @@ -7,7 +7,13 @@ public class Bone extends GenericObject { public Bone(final MdxModel model, final com.etheller.warsmash.parsers.mdlx.Bone bone, final int index) { super(model, bone, index); - this.geosetAnimation = model.getGeosetAnimations().get(bone.getGeosetAnimationId()); + final int geosetAnimationId = bone.getGeosetAnimationId(); + if (geosetAnimationId != -1) { + this.geosetAnimation = model.getGeosetAnimations().get(geosetAnimationId); + } + else { + this.geosetAnimation = null; + } } @Override diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EmitterGroup.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EmitterGroup.java index af7e109..046c9b1 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EmitterGroup.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EmitterGroup.java @@ -1,12 +1,12 @@ package com.etheller.warsmash.viewer5.handlers.mdx; -import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.glutils.ShaderProgram; import com.etheller.warsmash.viewer5.Model; import com.etheller.warsmash.viewer5.ModelViewer; import com.etheller.warsmash.viewer5.Scene; import com.etheller.warsmash.viewer5.SkeletalNode; +import com.etheller.warsmash.viewer5.gl.ANGLEInstancedArrays; public class EmitterGroup extends GenericGroup { private final MdxModel model; @@ -15,12 +15,14 @@ public class EmitterGroup extends GenericGroup { this.model = model; } + @Override public void render(final MdxComplexInstance instance) { final Scene scene = instance.scene; final SkeletalNode[] nodes = instance.nodes; final Model model = instance.model; final ModelViewer viewer = model.viewer; final GL20 gl = viewer.gl; + final ANGLEInstancedArrays instancedArrays = viewer.webGL.instancedArrays; final ShaderProgram shader = MdxHandler.Shaders.particles; gl.glDepthMask(false); @@ -30,36 +32,36 @@ public class EmitterGroup extends GenericGroup { shader.begin(); - shader.setUniformMatrix("u_mvp", scene.camera.worldProjectionMatrix); + shader.setUniformMatrix("u_mvp", scene.camera.viewProjectionMatrix); shader.setUniformf("u_texture", 0); final int a_position = shader.getAttributeLocation("a_position"); - Gdx.gl30.glVertexAttribDivisor(a_position, 0); + instancedArrays.glVertexAttribDivisorANGLE(a_position, 0); gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, viewer.rectBuffer); gl.glVertexAttribPointer(a_position, 1, GL20.GL_UNSIGNED_BYTE, false, 0, 0); - Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_p0"), 1); - Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_p1"), 1); - Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_p2"), 1); - Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_p3"), 1); - Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_health"), 1); - Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_color"), 1); - Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_tail"), 1); - Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_leftRightTop"), 1); + instancedArrays.glVertexAttribDivisorANGLE(shader.getAttributeLocation("a_p0"), 1); + instancedArrays.glVertexAttribDivisorANGLE(shader.getAttributeLocation("a_p1"), 1); + instancedArrays.glVertexAttribDivisorANGLE(shader.getAttributeLocation("a_p2"), 1); + instancedArrays.glVertexAttribDivisorANGLE(shader.getAttributeLocation("a_p3"), 1); + instancedArrays.glVertexAttribDivisorANGLE(shader.getAttributeLocation("a_health"), 1); + instancedArrays.glVertexAttribDivisorANGLE(shader.getAttributeLocation("a_color"), 1); + instancedArrays.glVertexAttribDivisorANGLE(shader.getAttributeLocation("a_tail"), 1); + instancedArrays.glVertexAttribDivisorANGLE(shader.getAttributeLocation("a_leftRightTop"), 1); for (final int index : this.objects) { GeometryEmitterFuncs.renderEmitter((MdxEmitter) nodes[index].object, shader); } - Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_leftRightTop"), 0); - Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_tail"), 0); - Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_color"), 0); - Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_health"), 0); - Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_p3"), 0); - Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_p2"), 0); - Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_p1"), 0); - Gdx.gl30.glVertexAttribDivisor(shader.getAttributeLocation("a_p0"), 0); + instancedArrays.glVertexAttribDivisorANGLE(shader.getAttributeLocation("a_leftRightTop"), 0); + instancedArrays.glVertexAttribDivisorANGLE(shader.getAttributeLocation("a_tail"), 0); + instancedArrays.glVertexAttribDivisorANGLE(shader.getAttributeLocation("a_color"), 0); + instancedArrays.glVertexAttribDivisorANGLE(shader.getAttributeLocation("a_health"), 0); + instancedArrays.glVertexAttribDivisorANGLE(shader.getAttributeLocation("a_p3"), 0); + instancedArrays.glVertexAttribDivisorANGLE(shader.getAttributeLocation("a_p2"), 0); + instancedArrays.glVertexAttribDivisorANGLE(shader.getAttributeLocation("a_p1"), 0); + instancedArrays.glVertexAttribDivisorANGLE(shader.getAttributeLocation("a_p0"), 0); } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectEmitter.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectEmitter.java index 16fabda..17c3b96 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectEmitter.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectEmitter.java @@ -32,6 +32,10 @@ public abstract class EventObjectEmitter tables) { final MappedData firstTable = (MappedData) tables.get(0).data; final MappedDataRow row = firstTable.getRow(this.id); @@ -182,40 +206,36 @@ public abstract class EventObjectEmitterObject extends GenericObject implements "ReplaceableTextures\\Splats\\" + row.get("file") + texturesExt, pathSolver, model.solverParams); - this.scale = (Float) row.get("Scale"); + this.scale = getFloat(row, "Scale"); this.colors = new float[][] { - { ((Float) row.get("StartR")).floatValue(), ((Float) row.get("StartG")).floatValue(), - ((Float) row.get("StartB")).floatValue(), ((Float) row.get("StartA")).floatValue() }, - { ((Float) row.get("MiddleR")).floatValue(), ((Float) row.get("MiddleG")).floatValue(), - ((Float) row.get("MiddleB")).floatValue(), ((Float) row.get("MiddleA")).floatValue() }, - { ((Float) row.get("EndR")).floatValue(), ((Float) row.get("EndG")).floatValue(), - ((Float) row.get("EndB")).floatValue(), ((Float) row.get("EndA")).floatValue() } }; + { getFloat(row, "StartR"), getFloat(row, "StartG"), getFloat(row, "StartB"), + getFloat(row, "StartA") }, + { getFloat(row, "MiddleR"), getFloat(row, "MiddleG"), getFloat(row, "MiddleB"), + getFloat(row, "MiddleA") }, + { getFloat(row, "EndR"), getFloat(row, "EndG"), getFloat(row, "EndB"), + getFloat(row, "EndA") } }; if ("SPL".equals(this.type)) { - this.columns = ((Number) row.get("Columns")).intValue(); - this.rows = ((Number) row.get("Rows")).intValue(); - this.lifeSpan = ((Number) row.get("Lifespan")).floatValue() - + ((Number) row.get("Decay")).floatValue(); + this.columns = getInt(row, "Columns"); + this.rows = getInt(row, "Rows"); + this.lifeSpan = getFloat(row, "Lifespan") + getFloat(row, "Decay"); this.intervals = new float[][] { - { ((Float) row.get("UVLifespanStart")).floatValue(), - ((Float) row.get("UVLifespanEnd")).floatValue(), - ((Float) row.get("LifespanRepeat")).floatValue() }, - { ((Float) row.get("UVDecayStart")).floatValue(), - ((Float) row.get("UVDecayEnd")).floatValue(), - ((Float) row.get("DecayRepeat")).floatValue() }, }; + { getFloat(row, "UVLifespanStart"), getFloat(row, "UVLifespanEnd"), + getFloat(row, "LifespanRepeat") }, + { getFloat(row, "UVDecayStart"), getFloat(row, "UVDecayEnd"), + getFloat(row, "DecayRepeat") }, }; } else { this.columns = 1; this.rows = 1; - this.lifeSpan = ((Number) row.get("BirthTime")).floatValue() - + ((Number) row.get("PauseTime")).floatValue() + ((Number) row.get("Decay")).floatValue(); - this.intervalTimes = new float[] { ((Number) row.get("BirthTime")).floatValue(), - ((Number) row.get("PauseTime")).floatValue(), ((Number) row.get("Decay")).floatValue() }; + this.lifeSpan = getFloat(row, "BirthTime") + getFloat(row, "PauseTime") + getFloat(row, "Decay"); + this.intervalTimes = new float[] { getFloat(row, "BirthTime"), getFloat(row, "PauseTime"), + getFloat(row, "Decay") }; } final int[] blendModes = FilterMode .emitterFilterMode(com.etheller.warsmash.parsers.mdlx.ParticleEmitter2.FilterMode - .fromId(((Number) row.get("BlendMode")).intValue())); + .fromId(getInt(row, "BlendMode"))); this.blendSrc = blendModes[0]; this.blendDst = blendModes[1]; @@ -232,12 +252,12 @@ public abstract class EventObjectEmitterObject extends GenericObject implements final MappedDataRow animSoundsRow = animSounds.getRow((String) row.get("SoundLabel")); if (animSoundsRow != null) { - this.distanceCutoff = ((Number) animSoundsRow.get("DistanceCutoff")).floatValue(); - this.maxDistance = ((Number) animSoundsRow.get("MaxDistance")).floatValue(); - this.minDistance = ((Number) animSoundsRow.get("MinDistance")).floatValue(); - this.pitch = ((Number) animSoundsRow.get("Pitch")).floatValue(); - this.pitchVariance = ((Number) animSoundsRow.get("PitchVariance")).floatValue(); - this.volume = ((Number) animSoundsRow.get("Volume")).floatValue(); + this.distanceCutoff = getFloat(animSoundsRow, "DistanceCutoff"); + this.maxDistance = getFloat(animSoundsRow, "MaxDistance"); + this.minDistance = getFloat(animSoundsRow, "MinDistance"); + this.pitch = getFloat(animSoundsRow, "Pitch"); + this.pitchVariance = getFloat(animSoundsRow, "PitchVariance"); + this.volume = getFloat(animSoundsRow, "Volume"); final String[] fileNames = ((String) animSoundsRow.get("FileNames")).split(","); final GenericResource[] resources = new GenericResource[fileNames.length]; @@ -300,4 +320,13 @@ public abstract class EventObjectEmitterObject extends GenericObject implements return -1; } + @Override + public boolean ok() { + return this.ok; + } + + @Override + public int getGeometryEmitterType() { + return this.geometryEmitterType; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GenericGroup.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GenericGroup.java index e4393af..69621f5 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GenericGroup.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GenericGroup.java @@ -3,9 +3,11 @@ package com.etheller.warsmash.viewer5.handlers.mdx; import java.util.ArrayList; import java.util.List; -public class GenericGroup { +public abstract class GenericGroup { public final List objects; + public abstract void render(MdxComplexInstance instance); + public GenericGroup() { this.objects = new ArrayList<>(); // TODO IntArrayList } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GenericObject.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GenericObject.java index 5ca6b42..ff04b5e 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GenericObject.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GenericObject.java @@ -145,7 +145,7 @@ public class GenericObject extends AnimatedObject implements GenericIndexed { return this.isVariant(AnimationMap.KGSC.getWar3id(), sequence); } - private static final class Variants { + public static final class Variants { boolean[] translation; boolean[] rotation; boolean[] scale; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GeometryEmitterFuncs.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GeometryEmitterFuncs.java index 5dadb82..23bc7f6 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GeometryEmitterFuncs.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GeometryEmitterFuncs.java @@ -4,9 +4,7 @@ import java.nio.ByteBuffer; import java.nio.FloatBuffer; import java.util.List; -import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.GL20; -import com.badlogic.gdx.graphics.GL30; import com.badlogic.gdx.graphics.glutils.ShaderProgram; import com.badlogic.gdx.math.Vector3; import com.etheller.warsmash.viewer5.Camera; @@ -14,6 +12,7 @@ import com.etheller.warsmash.viewer5.ModelViewer; import com.etheller.warsmash.viewer5.Scene; import com.etheller.warsmash.viewer5.Texture; import com.etheller.warsmash.viewer5.TextureMapper; +import com.etheller.warsmash.viewer5.gl.ANGLEInstancedArrays; import com.etheller.warsmash.viewer5.gl.ClientBuffer; import com.etheller.warsmash.viewer5.handlers.EmitterObject; @@ -378,6 +377,9 @@ public class GeometryEmitterFuncs { } public static void renderEmitter(final MdxEmitter emitter, final ShaderProgram shader) { + if (emitter == null) { + System.err.println("NULL EMITTER"); + } int alive = emitter.alive; final EmitterObject emitterObject = emitter.emitterObject; final int emitterType = emitterObject.getGeometryEmitterType(); @@ -388,6 +390,7 @@ public class GeometryEmitterFuncs { if (alive > 0) { final ModelViewer viewer = emitter.instance.model.viewer; + final ANGLEInstancedArrays instancedArrays = viewer.webGL.instancedArrays; final ClientBuffer buffer = viewer.buffer; final GL20 gl = viewer.gl; final int size = alive * BYTES_PER_OBJECT; @@ -425,7 +428,7 @@ public class GeometryEmitterFuncs { shader.setVertexAttribute("a_leftRightTop", 3, GL20.GL_UNSIGNED_BYTE, false, BYTES_PER_OBJECT, BYTE_OFFSET_LEFT_RIGHT_TOP); - Gdx.gl30.glDrawArraysInstanced(GL30.GL_TRIANGLES, 0, 6, alive); + instancedArrays.glDrawArraysInstancedANGLE(GL20.GL_TRIANGLES, 0, 6, alive); } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Geoset.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Geoset.java index 247b013..5588e38 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Geoset.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Geoset.java @@ -2,10 +2,9 @@ package com.etheller.warsmash.viewer5.handlers.mdx; import java.util.Arrays; -import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.GL20; -import com.badlogic.gdx.graphics.GL30; import com.badlogic.gdx.graphics.glutils.ShaderProgram; +import com.etheller.warsmash.viewer5.gl.ANGLEInstancedArrays; public class Geoset { public MdxModel model; @@ -123,8 +122,9 @@ public class Geoset { } public void renderSimple(final int instances) { - Gdx.gl30.glDrawElementsInstanced(GL30.GL_TRIANGLES, this.elements, GL30.GL_UNSIGNED_SHORT, this.faceOffset, - instances); + final ANGLEInstancedArrays instancedArrays = this.model.viewer.webGL.instancedArrays; + instancedArrays.glDrawElementsInstancedANGLE(GL20.GL_TRIANGLES, this.elements, GL20.GL_UNSIGNED_SHORT, + this.faceOffset, instances); } public void bindHd(final ShaderProgram shader, final int coordId) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GeosetAnimation.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GeosetAnimation.java index 7e3206e..4828ee9 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GeosetAnimation.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GeosetAnimation.java @@ -15,8 +15,12 @@ public class GeosetAnimation extends AnimatedObject { final float[] color = geosetAnimation.getColor(); this.alpha = geosetAnimation.getAlpha(); - this.color = new float[] { color[2], color[1], color[0] }; + this.color = new float[] { color[2], color[1], color[0] }; // Stored as RGB, but animated colors are stored as + // BGR, so sizzle. this.geosetId = geosetAnimation.getGeosetId(); + + this.addVariants(AnimationMap.KGAO.getWar3id(), "alpha"); + this.addVariants(AnimationMap.KGAC.getWar3id(), "color"); } public int getAlpha(final float[] out, final int sequence, final int frame, final int counter) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java index 3f3a499..4792e88 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java @@ -1,70 +1,654 @@ package com.etheller.warsmash.viewer5.handlers.mdx; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.util.ArrayList; +import java.util.List; + +import com.badlogic.gdx.math.Matrix4; +import com.badlogic.gdx.math.Quaternion; +import com.badlogic.gdx.math.Vector3; +import com.etheller.warsmash.parsers.mdlx.Sequence; +import com.etheller.warsmash.viewer5.GenericNode; import com.etheller.warsmash.viewer5.ModelInstance; +import com.etheller.warsmash.viewer5.Node; +import com.etheller.warsmash.viewer5.RenderBatch; +import com.etheller.warsmash.viewer5.Scene; import com.etheller.warsmash.viewer5.SkeletalNode; -import com.etheller.warsmash.viewer5.Texture; +import com.etheller.warsmash.viewer5.TextureMapper; +import com.etheller.warsmash.viewer5.UpdatableObject; +import com.etheller.warsmash.viewer5.gl.DataTexture; public class MdxComplexInstance extends ModelInstance { + private static final float[] visibilityHeap = new float[1]; + private static final float[] translationHeap = new float[3]; + private static final float[] rotationHeap = new float[4]; + private static final float[] scaleHeap = new float[3]; + private static final float[] colorHeap = new float[3]; + private static final float[] alphaHeap = new float[1]; + private static final long[] textureIdHeap = new long[1]; + public List attachments = new ArrayList<>(); + public List particleEmitters = new ArrayList<>(); + public List particleEmitters2 = new ArrayList<>(); + public List ribbonEmitters = new ArrayList<>(); + public List> eventObjectEmitters = new ArrayList<>(); public MdxNode[] nodes; public SkeletalNode[] sortedNodes; - - public int frame; - public int counter; - public int sequence; - public int sequenceLoopMode; - public boolean sequenceEnded; - public int teamColor; - public Texture boneTexture; - // TODO more fields, these few are to make related classes compile - public float[] vertexColor; + public int frame = 0; + // Global sequences + public int counter = 0; + public int sequence = -1; + public int sequenceLoopMode = 0; + public boolean sequenceEnded = false; + public int teamColor = 0; + public float[] vertexColor = { 1, 1, 1, 1 }; + // Particles do not spawn when the sequence is -1, or when the sequence finished + // and it's not repeating + public boolean allowParticleSpawn = false; + // If forced is true, everything will update regardless of variancy. + // Any later non-forced update can then use variancy to skip updating things. + // It is set to true every time the sequence is set with setSequence(). + public boolean forced = true; public float[][] geosetColors; public float[] layerAlphas; public int[] layerTextures; public float[][] uvAnims; - public boolean allowParticleSpawn; + public Matrix4[] worldMatrices; + public FloatBuffer worldMatricesCopyHeap; + public DataTexture boneTexture; public MdxComplexInstance(final MdxModel model) { super(model); } @Override - public void updateAnimations(final float dt) { - // TODO Auto-generated method stub + public void load() { + final MdxModel model = (MdxModel) this.model; + + this.geosetColors = new float[model.geosets.size()][]; + for (int i = 0, l = model.geosets.size(); i < l; i++) { + this.geosetColors[i] = new float[4]; + } + + this.layerAlphas = new float[model.layers.size()]; + this.layerTextures = new int[model.layers.size()]; + this.uvAnims = new float[model.layers.size()][]; + for (int i = 0, l = model.layers.size(); i < l; i++) { + this.layerAlphas[i] = 0; + this.layerTextures[i] = 0; + this.uvAnims[i] = new float[5]; + } + + // Create the needed amount of shared nodes. + final Object[] sharedNodeData = Node.createSkeletalNodes(model.genericObjects.size(), + MdxNodeDescriptor.INSTANCE); + final List nodes = (List) sharedNodeData[0]; + int nodeIndex = 0; + this.nodes = nodes.toArray(new MdxNode[nodes.size()]); + + // A shared typed array for all world matrices of the internal nodes. + this.worldMatrices = ((List) sharedNodeData[1]).toArray(new Matrix4[0]); + this.worldMatricesCopyHeap = ByteBuffer.allocateDirect(16 * this.worldMatrices.length * 4).asFloatBuffer(); + + // And now initialize all of the nodes and objects + for (final Bone bone : model.bones) { + this.initNode(this.nodes, this.nodes[nodeIndex++], bone); + } + + for (final Light light : model.lights) { + this.initNode(this.nodes, this.nodes[nodeIndex++], light); + } + + for (final Helper helper : model.helpers) { + this.initNode(this.nodes, this.nodes[nodeIndex++], helper); + } + + for (final Attachment attachment : model.attachments) { + AttachmentInstance attachmentInstance = null; + + // Attachments may have game models attached to them, such as Undead and + // Nightelf building animations. + if (attachment.internalModel != null) { + attachmentInstance = new AttachmentInstance(this, attachment); + + this.attachments.add(attachmentInstance); + } + + this.initNode(this.nodes, this.nodes[nodeIndex++], attachment, attachmentInstance); + } + + for (final ParticleEmitterObject emitterObject : model.particleEmitters) { + final ParticleEmitter emitter = new ParticleEmitter(this, emitterObject); + + this.particleEmitters.add(emitter); + + this.initNode(this.nodes, this.nodes[nodeIndex++], emitterObject, emitter); + } + + for (final ParticleEmitter2Object emitterObject : model.particleEmitters2) { + final ParticleEmitter2 emitter = new ParticleEmitter2(this, emitterObject); + + this.particleEmitters2.add(emitter); + + this.initNode(this.nodes, this.nodes[nodeIndex++], emitterObject, emitter); + } + + for (final RibbonEmitterObject emitterObject : model.ribbonEmitters) { + final RibbonEmitter emitter = new RibbonEmitter(this, emitterObject); + + this.ribbonEmitters.add(emitter); + + this.initNode(this.nodes, this.nodes[nodeIndex++], emitterObject, emitter); + } + + for (final EventObjectEmitterObject emitterObject : model.eventObjects) { + final String type = emitterObject.type; + EventObjectEmitter emitter; + + if ("SPN".equals(type)) { + emitter = new EventObjectSpnEmitter(this, emitterObject); + } + else if ("SPL".equals(type)) { + emitter = new EventObjectSplEmitter(this, emitterObject); + } + else if ("UBR".equals(type)) { + emitter = new EventObjectUbrEmitter(this, emitterObject); + } + else { + emitter = new EventObjectSndEmitter(this, emitterObject); + } + + this.eventObjectEmitters.add(emitter); + + this.initNode(this.nodes, this.nodes[nodeIndex++], emitterObject, emitter); + } + + for (final CollisionShape collisionShape : model.collisionShapes) { + this.initNode(this.nodes, this.nodes[nodeIndex++], collisionShape); + } + + // Save a sorted array of all of the nodes, such that every child node comes + // after its parent. + // This allows for flat iteration when updating. + final List hierarchy = model.hierarchy; + + this.sortedNodes = new SkeletalNode[nodes.size()]; + for (int i = 0, l = nodes.size(); i < l; i++) { + this.sortedNodes[i] = this.nodes[hierarchy.get(i)]; + } + + // If the sequence was changed before the model was loaded, reset it now that + // the model loaded. + this.setSequence(this.sequence); + + if (model.bones.size() != 0) { + this.boneTexture = new DataTexture(model.viewer.gl, 4, model.bones.size() * 4, 1); + } + } + + /* + * Clear all of the emitted objects that belong to this instance. + */ + @Override + public void clearEmittedObjects() { + for (final ParticleEmitter emitter : this.particleEmitters) { + emitter.clear(); + } + + for (final ParticleEmitter2 emitter : this.particleEmitters2) { + emitter.clear(); + } + + for (final RibbonEmitter emitter : this.ribbonEmitters) { + emitter.clear(); + } + + for (final EventObjectEmitter emitter : this.eventObjectEmitters) { + emitter.clear(); + } + } + + private void initNode(final MdxNode[] nodes, final SkeletalNode node, final GenericObject genericObject) { + initNode(nodes, node, genericObject, null); + } + + /** + * Initialize a skeletal node. + */ + private void initNode(final MdxNode[] nodes, final SkeletalNode node, final GenericObject genericObject, + final UpdatableObject object) { + node.pivot.set(genericObject.pivot); + + if (genericObject.parentId == -1) { + node.parent = this; + } + else { + node.parent = nodes[genericObject.parentId]; + } + + /// TODO: single-axis billboarding + if (genericObject.billboarded != 0) { + node.billboarded = true; + } // else if (genericObject.billboardedX) { + // node.billboardedX = true; + // } else if (genericObject.billboardedY) { + // node.billboardedY = true; + // } else if (genericObject.billboardedZ) { + // node.billboardedZ = true; + // } + + if (object != null) { + node.object = object; + } } + /* + * Overriden to hide also attachment models. + */ @Override - public void clearEmittedObjects() { - // TODO Auto-generated method stub + public void hide() { + super.hide(); + for (final AttachmentInstance attachment : this.attachments) { + attachment.internalInstance.hide(); + } + } + + /** + * Updates all of this instance internal nodes and objects. Nodes that are + * determined to not be visible will not be updated, nor will any of their + * children down the hierarchy. + */ + public void updateNodes(final float dt, final boolean forced) { + final int sequence = this.sequence; + final int frame = this.frame; + final int counter = this.counter; + final SkeletalNode[] sortedNodes = this.sortedNodes; + final MdxModel model = (MdxModel) this.model; + final List sortedGenericObjects = model.sortedGenericObjects; + final Scene scene = this.scene; + + // Update the nodes + for (int i = 0, l = sortedNodes.length; i < l; i++) { + final GenericObject genericObject = sortedGenericObjects.get(i); + final SkeletalNode node = sortedNodes[i]; + final GenericNode parent = node.parent; + + genericObject.getVisibility(visibilityHeap, sequence, frame, counter); + + final boolean objectVisible = visibilityHeap[0] > 0; + final boolean nodeVisible = forced || (parent.visible && objectVisible); + + node.visible = nodeVisible; + + // Every node only needs to be updated if this is a forced update, or if both + // the parent node and the generic object corresponding to this node are + // visible. + // Incoming messy code for optimizations! + if (nodeVisible) { + boolean wasDirty = false; + final GenericObject.Variants variants = genericObject.variants; + final Vector3 localLocation = node.localLocation; + final Quaternion localRotation = node.localRotation; + final Vector3 localScale = node.localScale; + + // Only update the local node data if there is a need to + if (forced || variants.generic[sequence]) { + wasDirty = true; + + // Translation + if (forced || variants.translation[sequence]) { + genericObject.getTranslation(translationHeap, sequence, frame, counter); + + localLocation.x = translationHeap[0]; + localLocation.y = translationHeap[1]; + localLocation.z = translationHeap[2]; + } + + // Rotation + if (forced || variants.rotation[sequence]) { + genericObject.getRotation(rotationHeap, sequence, frame, counter); + + localRotation.x = rotationHeap[0]; + localRotation.y = rotationHeap[1]; + localRotation.z = rotationHeap[2]; + localRotation.w = rotationHeap[3]; + } + + // Scale + if (forced || variants.scale[sequence]) { + genericObject.getScale(scaleHeap, sequence, frame, counter); + + localScale.x = scaleHeap[0]; + localScale.y = scaleHeap[1]; + localScale.z = scaleHeap[2]; + } + } + + final boolean wasReallyDirty = forced || wasDirty || parent.wasDirty || genericObject.anyBillboarding; + + node.wasDirty = wasReallyDirty; + + // If this is a forced update, or this node's local data was updated, or the + // parent node was updated, do a full world update. + if (wasReallyDirty) { + node.recalculateTransformation(scene); + } + + // If there is an instance object associated with this node, and the node is + // visible (which might not be the case for a forced update!), update the + // object. + // This includes attachments and emitters. + final UpdatableObject object = node.object; + + if ((object != null) && objectVisible) { + object.update(dt); + } + + // Update all of the node's non-skeletal children, which will update their + // children, and so on. + node.updateChildren(dt, scene); + } + } + } + + /** + * Update the batch data. + */ + public void updateBatches(final boolean forced) { + final int sequence = this.sequence; + final int frame = this.frame; + final int counter = this.counter; + final MdxModel model = (MdxModel) this.model; + final List geosets = model.geosets; + final List layers = model.layers; + final float[][] geosetColors = this.geosetColors; + final float[] layerAlphas = this.layerAlphas; + final int[] layerTextures = this.layerTextures; + final float[][] uvAnims = this.uvAnims; + + // Geoset + for (int i = 0, l = geosets.size(); i < l; i++) { + final Geoset geoset = geosets.get(i); + final GeosetAnimation geosetAnimation = geoset.geosetAnimation; + final float[] geosetColor = geosetColors[i]; + + if (geosetAnimation != null) { + // Color + if (forced || (geosetAnimation.variants.get("color")[sequence] != 0)) { + geosetAnimation.getColor(colorHeap, sequence, frame, counter); + + geosetColor[0] = colorHeap[0]; + geosetColor[1] = colorHeap[1]; + geosetColor[2] = colorHeap[2]; + } + + // Alpha + if (forced || (geosetAnimation.variants.get("alpha")[sequence] != 0)) { + geosetAnimation.getAlpha(alphaHeap, sequence, frame, counter); + + geosetColor[3] = alphaHeap[0]; + } + } + else if (forced) { + geosetColor[0] = 1; + geosetColor[1] = 1; + geosetColor[2] = 1; + geosetColor[3] = 1; + } + } + + // Layers + for (int i = 0, l = layers.size(); i < l; i++) { + final Layer layer = layers.get(i); + final TextureAnimation textureAnimation = layer.textureAnimation; + final float[] uvAnim = uvAnims[i]; + + // Alpha + if (forced || (layer.variants.get("alpha")[sequence] != 0)) { + layer.getAlpha(alphaHeap, sequence, frame, counter); + + layerAlphas[i] = alphaHeap[0]; + } + + // Sprite animation + if (forced || (layer.variants.get("textureId")[sequence] != 0)) { + layer.getTextureId(textureIdHeap, sequence, frame, counter); + + layerTextures[i] = (int) textureIdHeap[0]; + } + + if (textureAnimation != null) { + // UV translation animation + if (forced || (textureAnimation.variants.get("translation")[sequence] != 0)) { + textureAnimation.getTranslation(translationHeap, sequence, frame, counter); + + uvAnim[0] = translationHeap[0]; + uvAnim[1] = translationHeap[1]; + } + + // UV rotation animation + if (forced || (textureAnimation.variants.get("rotation")[sequence] != 0)) { + textureAnimation.getRotation(rotationHeap, sequence, frame, counter); + + uvAnim[2] = rotationHeap[2]; + uvAnim[3] = rotationHeap[3]; + } + + // UV scale animation + if (forced || (textureAnimation.variants.get("scale")[sequence] != 0)) { + textureAnimation.getScale(scaleHeap, sequence, frame, counter); + + uvAnim[4] = scaleHeap[0]; + } + } + else if (forced) { + uvAnim[0] = 0; + uvAnim[1] = 0; + uvAnim[2] = 0; + uvAnim[3] = 1; + uvAnim[4] = 1; + } + } + } + + public void updateBoneTexture() { + if (this.boneTexture != null) { + this.worldMatricesCopyHeap.clear(); + for (int i = 0, l = this.worldMatrices.length; i < l; i++) { + final Matrix4 worldMatrix = this.worldMatrices[i]; + this.worldMatricesCopyHeap.put((i * 16) + 0, worldMatrix.val[Matrix4.M00]); + this.worldMatricesCopyHeap.put((i * 16) + 1, worldMatrix.val[Matrix4.M01]); + this.worldMatricesCopyHeap.put((i * 16) + 2, worldMatrix.val[Matrix4.M02]); + this.worldMatricesCopyHeap.put((i * 16) + 3, worldMatrix.val[Matrix4.M03]); + this.worldMatricesCopyHeap.put((i * 16) + 4, worldMatrix.val[Matrix4.M10]); + this.worldMatricesCopyHeap.put((i * 16) + 5, worldMatrix.val[Matrix4.M11]); + this.worldMatricesCopyHeap.put((i * 16) + 6, worldMatrix.val[Matrix4.M12]); + this.worldMatricesCopyHeap.put((i * 16) + 7, worldMatrix.val[Matrix4.M13]); + this.worldMatricesCopyHeap.put((i * 16) + 8, worldMatrix.val[Matrix4.M20]); + this.worldMatricesCopyHeap.put((i * 16) + 9, worldMatrix.val[Matrix4.M21]); + this.worldMatricesCopyHeap.put((i * 16) + 10, worldMatrix.val[Matrix4.M22]); + this.worldMatricesCopyHeap.put((i * 16) + 11, worldMatrix.val[Matrix4.M23]); + this.worldMatricesCopyHeap.put((i * 16) + 12, worldMatrix.val[Matrix4.M30]); + this.worldMatricesCopyHeap.put((i * 16) + 13, worldMatrix.val[Matrix4.M31]); + this.worldMatricesCopyHeap.put((i * 16) + 14, worldMatrix.val[Matrix4.M32]); + this.worldMatricesCopyHeap.put((i * 16) + 15, worldMatrix.val[Matrix4.M33]); + } + this.boneTexture.bindAndUpdate(this.worldMatricesCopyHeap); + } } @Override public void renderOpaque() { - // TODO Auto-generated method stub + final MdxModel model = (MdxModel) this.model; + for (final GenericGroup group : model.opaqueGroups) { + group.render(this); + } } @Override public void renderTranslucent() { - // TODO Auto-generated method stub + final MdxModel model = (MdxModel) this.model; + for (final GenericGroup group : model.translucentGroups) { + group.render(this); + } } @Override - public void load() { - // TODO Auto-generated method stub + public void updateAnimations(final float dt) { + final MdxModel model = (MdxModel) this.model; + final int sequenceId = this.sequence; + + if (sequenceId != -1) { + final Sequence sequence = model.sequences.get(sequenceId); + final long[] interval = sequence.getInterval(); + final int frameTime = model.viewer.frameTime; + + this.frame += frameTime; + this.counter += frameTime; + this.allowParticleSpawn = true; + + if (this.frame >= interval[1]) { + if ((this.sequenceLoopMode == 2) || ((this.sequenceLoopMode == 0) && (sequence.getFlags() == 0))) { + this.frame = (int) interval[0]; // TODO not cast + + this.resetEventEmitters(); + } + else { + this.frame = (int) interval[1]; // TODO not cast + this.counter -= frameTime; + this.allowParticleSpawn = false; + } + + this.sequenceEnded = true; + } + else { + this.sequenceEnded = false; + } + } + + final boolean forced = this.forced; + + if (sequenceId == -1) { + if (forced) { + // Update the nodes + this.updateNodes(dt, forced); + + this.updateBoneTexture(); + + // Update the batches + this.updateBatches(forced); + } + } + else { + // let variants = model.variants; + + // if (forced || variants.nodes[sequenceId]) { + // Update the nodes + this.updateNodes(dt, forced); + + this.updateBoneTexture(); + // } + + // if (forced || variants.batches[sequenceId]) { + // Update the batches + this.updateBatches(forced); + // } + } + + this.forced = false; } - public MdxComplexInstance setSequenceLoopMode(final int mode) { - this.sequenceLoopMode = mode; + /** + * Set the team color of this instance. + */ + public MdxComplexInstance setTeamColor(final int id) { + this.teamColor = id; + return this; } - public void setSequence(final int sequence) { - this.sequence = sequence; - throw new UnsupportedOperationException("Not yet implemented"); + /** + * Set the vertex color of this instance. + */ + public MdxComplexInstance setVertexColor(final float[] color) { + System.arraycopy(color, 0, this.vertexColor, 0, color.length); + + return this; + } + + /** + * Set the sequence of this instance. + */ + public MdxComplexInstance setSequence(final int id) { + final MdxModel model = (MdxModel) this.model; + + this.sequence = id; + + if (model.ok) { + final List sequences = model.sequences; + + if ((id < 0) || (id > (sequences.size() - 1))) { + this.sequence = -1; + this.frame = 0; + this.allowParticleSpawn = false; + } + else { + this.frame = (int) sequences.get(id).getInterval()[0]; // TODO not cast + } + + this.resetEventEmitters(); + + this.forced = true; + } + + return this; + } + + /** + * Set the seuqnece loop mode. 0 to never loop, 1 to loop based on the model, + * and 2 to always loop. + */ + public MdxComplexInstance setSequenceLoopMode(final int mode) { + this.sequenceLoopMode = mode; + + return this; + } + + /** + * Get an attachment node. + */ + public MdxNode getAttachment(final int id) { + final MdxModel model = (MdxModel) this.model; + final Attachment attachment = model.attachments.get(id); + + if (attachment != null) { + return this.nodes[attachment.index]; + } + + return null; + } + + /** + * Event emitters depend on keyframe index changes to emit, rather than only + * values. To work, they need to check what the last keyframe was, and only if + * it's a different one, do something. When changing sequences, these states + * need to be reset, so they can immediately emit things if needed. + */ + private void resetEventEmitters() { + /// TODO: Update this. Said Ghostwolf. + for (final EventObjectEmitter eventObjectEmitter : this.eventObjectEmitters) { + eventObjectEmitter.reset(); + } + } + + @Override + protected RenderBatch getBatch(final TextureMapper textureMapper2) { + throw new UnsupportedOperationException("NOT API"); } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxHandler.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxHandler.java index 889f8a5..482dc87 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxHandler.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxHandler.java @@ -25,6 +25,7 @@ public class MdxHandler extends ModelHandler { this.extensions = new ArrayList<>(); this.extensions.add(new String[] { ".mdx", "arrayBuffer" }); this.extensions.add(new String[] { ".mdl", "text" }); + this.load = true; } @Override @@ -36,12 +37,13 @@ public class MdxHandler extends ModelHandler { MdxShaders.fsComplex); Shaders.particles = viewer.webGL.createShaderProgram(MdxShaders.vsParticles, MdxShaders.fsParticles); Shaders.simple = viewer.webGL.createShaderProgram(MdxShaders.vsSimple, MdxShaders.fsSimple); - Shaders.hd = viewer.webGL.createShaderProgram(MdxShaders.vsHd, MdxShaders.fsHd); +// Shaders.hd = viewer.webGL.createShaderProgram(MdxShaders.vsHd, MdxShaders.fsHd); + // TODO HD reforged // If a shader failed to compile, don't allow the handler to be registered, and // send an error instead. return Shaders.complex.isCompiled() && Shaders.extended.isCompiled() && Shaders.particles.isCompiled() - && Shaders.simple.isCompiled() && Shaders.hd.isCompiled(); + && Shaders.simple.isCompiled() /* && Shaders.hd.isCompiled() */; } @Override diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java index f96195a..64cf26c 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java @@ -1,11 +1,15 @@ package com.etheller.warsmash.viewer5.handlers.mdx; +import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; +import com.badlogic.gdx.graphics.GL20; +import com.etheller.warsmash.parsers.mdlx.Extent; import com.etheller.warsmash.parsers.mdlx.MdlxModel; import com.etheller.warsmash.parsers.mdlx.Sequence; +import com.etheller.warsmash.viewer5.ModelInstance; import com.etheller.warsmash.viewer5.ModelViewer; import com.etheller.warsmash.viewer5.PathSolver; import com.etheller.warsmash.viewer5.Texture; @@ -16,7 +20,7 @@ public class MdxModel extends com.etheller.warsmash.viewer5.Model { public SolverParams solverParams = new SolverParams(); public String name = ""; public List sequences = new ArrayList<>(); - public List globalSequences = new ArrayList<>(); + public List globalSequences = new ArrayList<>(); public List materials = new ArrayList<>(); public List layers = new ArrayList<>(); public List replaceables = new ArrayList<>(); @@ -34,72 +38,305 @@ public class MdxModel extends com.etheller.warsmash.viewer5.Model { public List ribbonEmitters = new ArrayList<>(); public List cameras = new ArrayList<>(); public List eventObjects = new ArrayList<>(); - public - - private MdlxModel model; - + public List collisionShapes = new ArrayList<>(); + public boolean hasLayerAnims = false; + public boolean hasGeosetAnims = false; + public List batches = new ArrayList<>(); + public List genericObjects = new ArrayList<>(); + public List sortedGenericObjects = new ArrayList<>(); + public List hierarchy = new ArrayList<>(); + public List opaqueGroups = new ArrayList<>(); + public List translucentGroups = new ArrayList<>(); + public List simpleGroups = new ArrayList<>(); public int arrayBuffer; public int elementBuffer; - public List batches = new ArrayList<>(); // TODO?? - - public List opaqueGroups; - public List translucentGroups; - public MdxModel(final MdxHandler handler, final ModelViewer viewer, final String extension, final PathSolver pathSolver, final String fetchUrl) { super(handler, viewer, extension, pathSolver, fetchUrl); } + public ModelInstance createInstance(final int type) { + if (type == 1) { + return new MdxSimpleInstance(this); + } + else { + return new MdxComplexInstance(this); + } + } + + public void load(final Object bufferOrParser) throws IOException { + MdlxModel parser; + + if (bufferOrParser instanceof MdlxModel) { + parser = (MdlxModel) bufferOrParser; + } + else { + parser = new MdlxModel((InputStream) bufferOrParser); + } + + final ModelViewer viewer = this.viewer; + final PathSolver pathSolver = this.pathSolver; + final SolverParams solverParams = this.solverParams; + final boolean reforged = parser.getVersion() > 800; + final String texturesExt = reforged ? ".dds" : ".blp"; + + this.reforged = reforged; + this.name = parser.getName(); + + // Initialize the bounds. + final Extent extent = parser.getExtent(); + this.bounds.fromExtents(extent.getMin(), extent.getMax()); + + // Sequences + this.sequences.addAll(parser.getSequences()); + + // Global sequences + this.globalSequences.addAll(parser.getGlobalSequences()); + + // Texture animations + for (final com.etheller.warsmash.parsers.mdlx.TextureAnimation textureAnimation : parser + .getTextureAnimations()) { + this.textureAnimations.add(new TextureAnimation(this, textureAnimation)); + } + + // Materials + int layerId = 0; + for (final com.etheller.warsmash.parsers.mdlx.Material material : parser.getMaterials()) { + final List layers = new ArrayList<>(); + + for (final com.etheller.warsmash.parsers.mdlx.Layer layer : material.getLayers()) { + final Layer vLayer = new Layer(this, layer, layerId++, material.getPriorityPlane()); + + layers.add(vLayer); + + this.layers.add(vLayer); + } + + this.materials.add(new Material(this, "" /* material.shader */, layers)); + + if (false /* !"".equals(material.shader) */) { + this.hd = true; + } + } + + if (reforged) { + solverParams.reforged = true; + } + + if (this.hd) { + solverParams.hd = true; + } + + final GL20 gl = viewer.gl; + boolean usingTeamTextures = false; + + // Textures. + for (final com.etheller.warsmash.parsers.mdlx.Texture texture : parser.getTextures()) { + String path = texture.getPath(); + final int replaceableId = texture.getReplaceableId(); + final int flags = texture.getFlags(); + + if (replaceableId != 0) { + path = "ReplaceableTextures\\" + ReplaceableIds.getPathString(replaceableId) + ".blp"; + + if ((replaceableId == 1) || (replaceableId == 2)) { + usingTeamTextures = true; + } + } + + if (reforged && !path.endsWith(".dds")) { + path = path.substring(0, path.length() - 4) + ".dds"; + } + + final Texture viewerTexture = (Texture) viewer.load(path, pathSolver, solverParams); + + // When the texture will load, it will apply its wrap modes. + if (!viewerTexture.loaded) { + if ((flags & 0x1) != 0) { + viewerTexture.setWrapS(true); + } + + if ((flags & 0x2) != 0) { + viewerTexture.setWrapT(true); + } + } + + this.replaceables.add(replaceableId); + this.textures.add(viewerTexture); + } + + // Start loading the team color and glow textures if this model uses them and + // they weren't loaded previously. + if (usingTeamTextures) { + final List teamColors = reforged ? MdxHandler.reforgedTeamColors : MdxHandler.teamColors; + final List teamGlows = reforged ? MdxHandler.reforgedTeamGlows : MdxHandler.teamGlows; + + if (teamColors.isEmpty()) { + for (int i = 0; i < 28; i++) { + final String id = ReplaceableIds.getIdString(i); + + teamColors.add((Texture) viewer.load("ReplaceableTextures\\TeamColor\\TeamColor" + id + texturesExt, + pathSolver, solverParams)); + teamGlows.add((Texture) viewer.load("ReplaceableTextures\\TeamGlow\\TeamGlow" + id + texturesExt, + pathSolver, solverParams)); + } + } + } + + // Geoset animations + for (final com.etheller.warsmash.parsers.mdlx.GeosetAnimation geosetAnimation : parser.getGeosetAnimations()) { + this.geosetAnimations.add(new GeosetAnimation(this, geosetAnimation)); + } + + // Geosets + SetupGeosets.setupGeosets(this, parser.getGeosets()); + + this.pivotPoints = parser.getPivotPoints(); + + // Tracks the IDs of all generic objects + int objectId = 0; + + // Bones + for (final com.etheller.warsmash.parsers.mdlx.Bone bone : parser.getBones()) { + this.bones.add(new Bone(this, bone, objectId++)); + } + + // Lights + for (final com.etheller.warsmash.parsers.mdlx.Light light : parser.getLights()) { + this.lights.add(new Light(this, light, objectId++)); + } + + // Helpers + for (final com.etheller.warsmash.parsers.mdlx.Helper helper : parser.getHelpers()) { + this.helpers.add(new Helper(this, helper, objectId++)); + } + + // Attachments + for (final com.etheller.warsmash.parsers.mdlx.Attachment attachment : parser.getAttachments()) { + this.attachments.add(new Attachment(this, attachment, objectId++)); + } + + // Particle Emitters + for (final com.etheller.warsmash.parsers.mdlx.ParticleEmitter particleEmitter : parser.getParticleEmitters()) { + this.particleEmitters.add(new ParticleEmitterObject(this, particleEmitter, objectId++)); + } + + // Particle Emitters 2 + for (final com.etheller.warsmash.parsers.mdlx.ParticleEmitter2 particleEmitter2 : parser + .getParticleEmitters2()) { + this.particleEmitters2.add(new ParticleEmitter2Object(this, particleEmitter2, objectId++)); + } + + // Ribbon emitters + for (final com.etheller.warsmash.parsers.mdlx.RibbonEmitter ribbonEmitter : parser.getRibbonEmitters()) { + this.ribbonEmitters.add(new RibbonEmitterObject(this, ribbonEmitter, objectId++)); + } + + // Camera + for (final com.etheller.warsmash.parsers.mdlx.Camera camera : parser.getCameras()) { + this.cameras.add(new Camera(this, camera)); + } + + // Event objects + for (final com.etheller.warsmash.parsers.mdlx.EventObject eventObject : parser.getEventObjects()) { + this.eventObjects.add(new EventObjectEmitterObject(this, eventObject, objectId++)); + } + + // Collision shapes + for (final com.etheller.warsmash.parsers.mdlx.CollisionShape collisionShape : parser.getCollisionShapes()) { + this.collisionShapes.add(new CollisionShape(this, collisionShape, objectId++)); + } + + // One array for all generic objects. + this.genericObjects.addAll(this.bones); + this.genericObjects.addAll(this.lights); + this.genericObjects.addAll(this.helpers); + this.genericObjects.addAll(this.attachments); + this.genericObjects.addAll(this.particleEmitters); + this.genericObjects.addAll(this.particleEmitters2); + this.genericObjects.addAll(this.ribbonEmitters); + this.genericObjects.addAll(this.eventObjects); + this.genericObjects.addAll(this.collisionShapes); + + // Render groups. + SetupGroups.setupGroups(this); + + // SimpleInstance render group. + SetupSimpleGroups.setupSimpleGroups(this); + + // Creates the sorted indices array of the generic objects + this.setupHierarchy(-1); + + // Keep a sorted array. + for (int i = 0, l = this.genericObjects.size(); i < l; i++) { + this.sortedGenericObjects.add(this.genericObjects.get(this.hierarchy.get(i))); + } + + } + + private void setupHierarchy(final int parent) { + for (int i = 0, l = this.genericObjects.size(); i < l; i++) { + final GenericObject object = this.genericObjects.get(i); + + if (object.parentId == parent) { + this.hierarchy.add(i); + + this.setupHierarchy(object.objectId); + } + } + } + @Override protected void lateLoad() { - // TODO Auto-generated method stub - } @Override protected void load(final InputStream src, final Object options) { - // TODO Auto-generated method stub - + try { + this.load(src); + } + catch (final IOException e) { + throw new RuntimeException(e); + } } @Override protected void error(final Exception e) { - // TODO Auto-generated method stub - + e.printStackTrace(); } // TODO typing public List getGlobalSequences() { - return this.model.getGlobalSequences(); + return this.globalSequences; } public List getSequences() { - return this.model.getSequences(); + return this.sequences; } public List getPivotPoints() { - return this.model.getPivotPoints(); + return this.pivotPoints; } public List getGeosetAnimations() { - throw new UnsupportedOperationException("NYI"); + return this.geosetAnimations; } public List getTextures() { - throw new UnsupportedOperationException("NYI"); + return this.textures; } public List getMaterials() { - throw new UnsupportedOperationException("NYI"); + return this.materials; } public List getTextureAnimations() { - throw new UnsupportedOperationException("NYI"); + return this.textureAnimations; } public List getGeosets() { - throw new UnsupportedOperationException("NYI"); + return this.geosets; } private static final class SolverParams { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxNodeDescriptor.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxNodeDescriptor.java new file mode 100644 index 0000000..72c3665 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxNodeDescriptor.java @@ -0,0 +1,13 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +import com.etheller.warsmash.util.Descriptor; + +public class MdxNodeDescriptor implements Descriptor { + public static final MdxNodeDescriptor INSTANCE = new MdxNodeDescriptor(); + + @Override + public MdxNode create() { + return new MdxNode(); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxRenderBatch.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxRenderBatch.java new file mode 100644 index 0000000..2e4a332 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxRenderBatch.java @@ -0,0 +1,128 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +import java.nio.FloatBuffer; +import java.util.List; + +import com.badlogic.gdx.graphics.GL20; +import com.badlogic.gdx.graphics.glutils.ShaderProgram; +import com.badlogic.gdx.math.Matrix4; +import com.etheller.warsmash.viewer5.Model; +import com.etheller.warsmash.viewer5.ModelInstance; +import com.etheller.warsmash.viewer5.ModelViewer; +import com.etheller.warsmash.viewer5.RenderBatch; +import com.etheller.warsmash.viewer5.Scene; +import com.etheller.warsmash.viewer5.Texture; +import com.etheller.warsmash.viewer5.TextureMapper; +import com.etheller.warsmash.viewer5.gl.ANGLEInstancedArrays; +import com.etheller.warsmash.viewer5.gl.ClientBuffer; +import com.etheller.warsmash.viewer5.gl.WebGL; + +public class MdxRenderBatch extends RenderBatch { + + public MdxRenderBatch(final Scene scene, final Model model, final TextureMapper textureMapper) { + super(scene, model, textureMapper); + } + + private void bindAndUpdateBuffer(final ClientBuffer buffer) { + final int count = this.count; + final List instances = this.instances; + + // Ensure there is enough memory for all of the instances data. + buffer.reserve(count * 48); + + final FloatBuffer floatView = buffer.floatView; + + // "Copy" the instances into the buffer + for (int i = 0; i < count; i++) { + final ModelInstance instance = instances.get(i); + final Matrix4 worldMatrix = instance.worldMatrix; + final int offset = i * 12; + + floatView.put(offset + 0, worldMatrix.val[Matrix4.M00]); + floatView.put(offset + 1, worldMatrix.val[Matrix4.M01]); + floatView.put(offset + 2, worldMatrix.val[Matrix4.M02]); + floatView.put(offset + 3, worldMatrix.val[Matrix4.M03]); + floatView.put(offset + 4, worldMatrix.val[Matrix4.M10]); + floatView.put(offset + 5, worldMatrix.val[Matrix4.M11]); + floatView.put(offset + 6, worldMatrix.val[Matrix4.M12]); + floatView.put(offset + 7, worldMatrix.val[Matrix4.M13]); + floatView.put(offset + 8, worldMatrix.val[Matrix4.M20]); + floatView.put(offset + 9, worldMatrix.val[Matrix4.M21]); + floatView.put(offset + 10, worldMatrix.val[Matrix4.M22]); + floatView.put(offset + 11, worldMatrix.val[Matrix4.M23]); + } + + // Update the buffer. + buffer.bindAndUpdate(count * 48); + } + + @Override + public void render() { + final int count = this.count; + + if (count != 0) { + final MdxModel model = (MdxModel) this.model; + final List batches = model.batches; + final List textures = model.textures; + final ModelViewer viewer = model.viewer; + final GL20 gl = viewer.gl; + final WebGL webGL = viewer.webGL; + final ANGLEInstancedArrays instancedArrays = webGL.instancedArrays; + final ShaderProgram shader = MdxHandler.Shaders.simple; + final int m0 = shader.getAttributeLocation("a_m0"); + final int m1 = shader.getAttributeLocation("a_m1"); + final int m2 = shader.getAttributeLocation("a_m2"); + final int m3 = shader.getAttributeLocation("a_m3"); + final ClientBuffer buffer = viewer.buffer; + final TextureMapper textureMapper = this.textureMapper; + + webGL.useShaderProgram(shader); + + this.bindAndUpdateBuffer(buffer); + + shader.setVertexAttribute(m0, 3, GL20.GL_FLOAT, false, 48, 0); + shader.setVertexAttribute(m1, 3, GL20.GL_FLOAT, false, 48, 12); + shader.setVertexAttribute(m2, 3, GL20.GL_FLOAT, false, 48, 24); + shader.setVertexAttribute(m3, 3, GL20.GL_FLOAT, false, 48, 36); + + shader.setUniformMatrix4fv("u_VP", this.scene.camera.viewProjectionMatrix.val, 0, + this.scene.camera.viewProjectionMatrix.val.length); + + gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, model.arrayBuffer); + gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, model.elementBuffer); + + instancedArrays.glVertexAttribDivisorANGLE(m0, 1); + instancedArrays.glVertexAttribDivisorANGLE(m1, 1); + instancedArrays.glVertexAttribDivisorANGLE(m2, 1); + instancedArrays.glVertexAttribDivisorANGLE(m3, 1); + + for (final GenericGroup group : model.simpleGroups) { + for (final Integer object : group.objects) { + final Batch batch = batches.get(object); + final Geoset geoset = batch.geoset; + final Layer layer = batch.layer; + final Texture texture = textures.get(layer.textureId); + + shader.setUniformi("u_texture", 0); + + Texture mappedTexture = textureMapper.get(texture); + if (mappedTexture == null) { + mappedTexture = texture; + } + viewer.webGL.bindTexture(mappedTexture, 0); + + layer.bind(shader); + + geoset.bindSimple(shader); + geoset.renderSimple(count); + } + } + + instancedArrays.glVertexAttribDivisorANGLE(m3, 0); + instancedArrays.glVertexAttribDivisorANGLE(m2, 0); + instancedArrays.glVertexAttribDivisorANGLE(m1, 0); + instancedArrays.glVertexAttribDivisorANGLE(m0, 0); + } + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxShaders.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxShaders.java index bb65b5f..d1360ba 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxShaders.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxShaders.java @@ -56,7 +56,7 @@ public class MdxShaders { " }"; public static final String vsSimple = "\r\n" + // - " uniform mat4 u_mvp;\r\n" + // + " uniform mat4 u_VP;\r\n" + // " attribute vec3 a_m0;\r\n" + // " attribute vec3 a_m1;\r\n" + // " attribute vec3 a_m2;\r\n" + // @@ -66,22 +66,22 @@ public class MdxShaders { " varying vec2 v_uv;\r\n" + // " void main() {\r\n" + // " v_uv = a_uv;\r\n" + // - " gl_Position = u_mvp * mat4(a_m0, 0.0, a_m1, 0.0, a_m2, 0.0, a_m3, 1.0) * vec4(a_position, 1.0);\r\n" - + // - " }"; + " gl_Position = u_VP * mat4(a_m0, 0.0, a_m1, 0.0, a_m2, 0.0, a_m3, 1.0) * vec4(a_position, 1.0);\r\n" + // + " }\r\n"; public static final String fsSimple = "\r\n" + // + " precision mediump float;\r\n" + // " uniform sampler2D u_texture;\r\n" + // " uniform float u_filterMode;\r\n" + // " varying vec2 v_uv;\r\n" + // " void main() {\r\n" + // " vec4 color = texture2D(u_texture, v_uv);\r\n" + // " // 1bit Alpha\r\n" + // - " if (u_filterMode == 1.0 && color.a < 0.75) {\r\n" + // - " discard;\r\n" + // - " }\r\n" + // + " //if (u_filterMode == 1.0 && color.a < 0.75) {\r\n" + // + " //discard;\r\n" + // + " //}\r\n" + // " gl_FragColor = color;\r\n" + // - " }"; + " }\r\n"; public static final String vsComplex = Shaders.boneTexture + "\r\n" + // " uniform mat4 u_mvp;\r\n" + // diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxSimpleInstance.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxSimpleInstance.java index b110969..8a075f2 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxSimpleInstance.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxSimpleInstance.java @@ -1,19 +1,16 @@ package com.etheller.warsmash.viewer5.handlers.mdx; +import com.etheller.warsmash.viewer5.BatchedInstance; import com.etheller.warsmash.viewer5.Model; -import com.etheller.warsmash.viewer5.ModelInstance; +import com.etheller.warsmash.viewer5.RenderBatch; +import com.etheller.warsmash.viewer5.TextureMapper; -public class MdxSimpleInstance extends ModelInstance { +public class MdxSimpleInstance extends BatchedInstance { public MdxSimpleInstance(final Model model) { super(model); } - @Override - public boolean isBatched() { - return true; - } - @Override public void updateAnimations(final float dt) { } @@ -34,4 +31,9 @@ public class MdxSimpleInstance extends ModelInstance { public void load() { } + @Override + public RenderBatch getBatch(final TextureMapper textureMapper) { + return new MdxRenderBatch(this.scene, this.model, textureMapper); + } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ParticleEmitter2Object.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ParticleEmitter2Object.java index 45ae024..af7936b 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ParticleEmitter2Object.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ParticleEmitter2Object.java @@ -61,7 +61,7 @@ public class ParticleEmitter2Object extends GenericObject implements EmitterObje } else { this.internalTexture = (Texture) model.viewer.load( - "ReplaceableTextures\\" + ReplaceableIds.get(replaceableId) + ".blp", model.pathSolver, + "ReplaceableTextures\\" + ReplaceableIds.getPathString(replaceableId) + ".blp", model.pathSolver, model.solverParams); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/QuaternionSd.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/QuaternionSd.java index f239b87..48cd8c4 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/QuaternionSd.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/QuaternionSd.java @@ -6,7 +6,7 @@ import com.etheller.warsmash.util.Interpolator; public class QuaternionSd extends Sd { public QuaternionSd(final MdxModel model, final Timeline timeline) { - super(model, timeline); + super(model, timeline, SdArrayDescriptor.FLOAT_ARRAY); } @Override diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ReplaceableIds.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ReplaceableIds.java index 860a3bd..2f2371e 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ReplaceableIds.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ReplaceableIds.java @@ -5,18 +5,35 @@ import java.util.Map; public class ReplaceableIds { private static final Map ID_TO_STR = new HashMap<>(); + private static final Map REPLACEABLE_ID_TO_STR = new HashMap<>(); static { for (int i = 0; i < 28; i++) { ID_TO_STR.put(Long.valueOf(i), String.format("%2d", i).replace(' ', '0')); } + REPLACEABLE_ID_TO_STR.put(Long.valueOf(1), "TeamColor\\TeamColor00"); + REPLACEABLE_ID_TO_STR.put(Long.valueOf(2), "TeamGlow\\TeamGlow00"); + REPLACEABLE_ID_TO_STR.put(Long.valueOf(11), "Cliff\\Cliff0"); + REPLACEABLE_ID_TO_STR.put(Long.valueOf(21), ""); // Used by all cursor models (HumanCursor, OrcCursor, + // UndeadCursor, NightElfCursor) + REPLACEABLE_ID_TO_STR.put(Long.valueOf(31), "LordaeronTree\\LordaeronSummerTree"); + REPLACEABLE_ID_TO_STR.put(Long.valueOf(32), "AshenvaleTree\\AshenTree"); + REPLACEABLE_ID_TO_STR.put(Long.valueOf(33), "BarrensTree\\BarrensTree"); + REPLACEABLE_ID_TO_STR.put(Long.valueOf(34), "NorthrendTree\\NorthTree"); + REPLACEABLE_ID_TO_STR.put(Long.valueOf(35), "Mushroom\\MushroomTree"); + REPLACEABLE_ID_TO_STR.put(Long.valueOf(36), "RuinsTree\\RuinsTree"); + REPLACEABLE_ID_TO_STR.put(Long.valueOf(37), "OutlandMushroomTree\\MushroomTree"); } public static void main(final String[] args) { System.out.println(ID_TO_STR); } - public static String get(final long replaceableId) { + public static String getIdString(final long replaceableId) { return ID_TO_STR.get(replaceableId); } + + public static String getPathString(final long replaceableId) { + return REPLACEABLE_ID_TO_STR.get(replaceableId); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ScalarSd.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ScalarSd.java index 161497c..7690972 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ScalarSd.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ScalarSd.java @@ -6,7 +6,7 @@ import com.etheller.warsmash.util.RenderMathUtils; public class ScalarSd extends Sd { public ScalarSd(final MdxModel model, final Timeline timeline) { - super(model, timeline); + super(model, timeline, SdArrayDescriptor.FLOAT_ARRAY); } @Override diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Sd.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Sd.java index 07810ce..9c72446 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Sd.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Sd.java @@ -85,7 +85,7 @@ public abstract class Sd { } - public Sd(final MdxModel model, final Timeline timeline) { + public Sd(final MdxModel model, final Timeline timeline, final SdArrayDescriptor arrayDescriptor) { final List globalSequences = model.getGlobalSequences(); final int globalSequenceId = timeline.getGlobalSequenceId(); final Integer forcedInterp = forcedInterpMap.get(timeline.getName()); @@ -105,13 +105,14 @@ public abstract class Sd { if ((globalSequenceId != -1) && (globalSequences.size() > 0)) { this.globalSequence = new SdSequence(this, 0, globalSequences.get(globalSequenceId).longValue(), - timeline, true); + timeline, true, arrayDescriptor); } else { for (final Sequence sequence : model.getSequences()) { final long[] interval = sequence.getInterval(); - this.sequences.add(new SdSequence(this, interval[0], interval[1], timeline, false)); + this.sequences + .add(new SdSequence(this, interval[0], interval[1], timeline, false, arrayDescriptor)); } } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SdArrayDescriptor.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SdArrayDescriptor.java new file mode 100644 index 0000000..ff20f4b --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SdArrayDescriptor.java @@ -0,0 +1,24 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +public interface SdArrayDescriptor { + SdArrayDescriptor GENERIC = new SdArrayDescriptor() { + @Override + public Object[] create(final int size) { + return new Object[size]; + } + }; + SdArrayDescriptor FLOAT_ARRAY = new SdArrayDescriptor() { + @Override + public float[][] create(final int size) { + return new float[size][]; + } + }; + SdArrayDescriptor LONG_ARRAY = new SdArrayDescriptor() { + @Override + public long[][] create(final int size) { + return new long[size][]; + } + }; + + TYPE[] create(int size); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SdSequence.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SdSequence.java index 14b229b..a877bce 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SdSequence.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SdSequence.java @@ -18,7 +18,7 @@ public final class SdSequence { public boolean constant; public SdSequence(final Sd sd, final long start, final long end, final Timeline timeline, - final boolean isGlobalSequence) { + final boolean isGlobalSequence, final SdArrayDescriptor arrayDescriptor) { this.sd = sd; this.start = start; this.end = end; @@ -120,13 +120,17 @@ public final class SdSequence { } this.frames = new long[framesBuilder.size()]; for (int i = 0; i < framesBuilder.size(); i++) { - frames[i] = framesBuilder.get(i); + this.frames[i] = framesBuilder.get(i); } - this.values = valuesBuilder.toArray((TYPE[]) new Object[valuesBuilder.size()]); - this.inTans = inTansBuilder.toArray((TYPE[]) new Object[inTansBuilder.size()]); - this.outTans = outTansBuilder.toArray((TYPE[]) new Object[outTansBuilder.size()]); + this.values = valuesBuilder.toArray(arrayDescriptor.create(valuesBuilder.size())); + this.inTans = inTansBuilder.toArray(arrayDescriptor.create(inTansBuilder.size())); + this.outTans = outTansBuilder.toArray(arrayDescriptor.create(outTansBuilder.size())); } +// private TYPE[] makeArray(final int size) { +// return (TYPE[]) new Object[size]; +// } + public int getValue(final TYPE out, final long frame) { final int l = this.frames.length; @@ -145,7 +149,8 @@ public final class SdSequence { if (this.frames[i] > frame) { final long start = this.frames[i = 1]; final long end = this.frames[i]; - final float t = RenderMathUtils.clamp((frame - start) / (end - start), 0, 1); + final float t = RenderMathUtils.clamp(((end - start) == 0 ? 0 : ((frame - start) / (end - start))), + 0, 1); this.sd.interpolate(out, this.values, this.inTans, this.outTans, i - 1, i, t); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SetupGeosets.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SetupGeosets.java index 37fe299..ee38440 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SetupGeosets.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SetupGeosets.java @@ -2,7 +2,7 @@ package com.etheller.warsmash.viewer5.handlers.mdx; import java.nio.ByteBuffer; import java.nio.FloatBuffer; -import java.nio.IntBuffer; +import java.nio.ShortBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -11,8 +11,8 @@ import com.badlogic.gdx.graphics.GL20; public class SetupGeosets { private static final int NORMAL_BATCH = 0; - private static final int EXTENDED_BATCH = 0; - private static final int REFORGED_BATCH = 0; + private static final int EXTENDED_BATCH = 1; + private static final int REFORGED_BATCH = 2; public static void setupGeosets(final MdxModel model, final List geosets) { @@ -157,30 +157,52 @@ public class SetupGeosets { } // Positions. - gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, positionOffset, positions.length, - FloatBuffer.wrap(positions)); + gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, positionOffset, positions.length, wrap(positions)); positionOffset += positions.length * 4; // Normals. - gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, normalOffset, normals.length, FloatBuffer.wrap(normals)); + gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, normalOffset, normals.length, wrap(normals)); normalOffset += normals.length * 4; // Texture coordinates. for (final float[] uvSet : uvSets) { - gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, uvOffset, uvSet.length, FloatBuffer.wrap(uvSet)); + gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, uvOffset, uvSet.length, wrap(uvSet)); uvOffset += uvSet.length * 4; } // Skin. - gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, skinOffset, skin.length, ByteBuffer.wrap(skin)); + gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, skinOffset, skin.length, wrap(skin)); skinOffset += skin.length * 1; // Faces. - gl.glBufferSubData(GL20.GL_ELEMENT_ARRAY_BUFFER, faceOffset, faces.length, IntBuffer.wrap(faces)); + gl.glBufferSubData(GL20.GL_ELEMENT_ARRAY_BUFFER, faceOffset, faces.length, wrapFaces(faces)); faceOffset += faces.length * 4; } } } } + + private static ShortBuffer wrapFaces(final int[] faces) { + final ShortBuffer wrapper = ByteBuffer.allocateDirect(faces.length * 2).asShortBuffer(); + for (final int face : faces) { + wrapper.put((short) face); + } + wrapper.clear(); + return wrapper; + } + + private static ByteBuffer wrap(final byte[] skin) { + final ByteBuffer wrapper = ByteBuffer.allocateDirect(skin.length); + wrapper.put(skin); + wrapper.clear(); + return wrapper; + } + + private static FloatBuffer wrap(final float[] positions) { + final FloatBuffer wrapper = ByteBuffer.allocateDirect(positions.length * 4).asFloatBuffer(); + wrapper.put(positions); + wrapper.clear(); + return wrapper; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SetupGroups.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SetupGroups.java index 6eb35d0..d502fe4 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SetupGroups.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SetupGroups.java @@ -71,8 +71,8 @@ public class SetupGroups { } } - final List opaqueGroups = model.opaqueGroups; - final List translucentGroups = model.translucentGroups; + final List opaqueGroups = model.opaqueGroups; + final List translucentGroups = model.translucentGroups; GenericGroup currentGroup = null; for (final Batch object : opaqueBatches) { @@ -82,7 +82,8 @@ public class SetupGroups { opaqueGroups.add(currentGroup); } - currentGroup.objects.add(object.index); + final int index = object.index; + currentGroup.objects.add(index); } // Sort between all of the translucent batches and emitters that have priority @@ -109,12 +110,13 @@ public class SetupGroups { if ((object instanceof Batch /* || object instanceof ReforgedBatch */) || (object instanceof EmitterObject)) { if ((currentGroup == null) || !matchingGroup(currentGroup, objects)) { - currentGroup = createMatchingGroup(model, objects); + currentGroup = createMatchingGroup(model, object); translucentGroups.add(currentGroup); } - currentGroup.objects.add(((GenericIndexed) object).getIndex()); + final int index = ((GenericIndexed) object).getIndex(); + currentGroup.objects.add(index); } } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SetupSimpleGroups.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SetupSimpleGroups.java new file mode 100644 index 0000000..ebb45ce --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SetupSimpleGroups.java @@ -0,0 +1,62 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +import java.util.List; + +public class SetupSimpleGroups { + private static final float[] alphaHeap = new float[1]; + + public static boolean isBatchSimple(final Batch batch) { + final GeosetAnimation geosetAnimation = batch.geoset.geosetAnimation; + + if (geosetAnimation != null) { + geosetAnimation.getAlpha(alphaHeap, 0, 0, 0); + + if (alphaHeap[0] <= 0.01) { + return false; + } + } + + Layer layer; + + if (batch instanceof Batch) { + layer = batch.layer; + } + else { + throw new IllegalStateException("reforged?"); // TODO +// layer = batch.material.layers[0]; + } + + layer.getAlpha(alphaHeap, 0, 0, 0); + + if (alphaHeap[0] < 0.01) { + return false; + } + + return true; + } + + public static void setupSimpleGroups(final MdxModel model) { + final List batches = model.batches; + final List simpleGroups = model.simpleGroups; + + for (final GenericGroup group : model.opaqueGroups) { + GenericGroup simpleGroup; + + if (group instanceof BatchGroup) { + simpleGroup = new BatchGroup(model, ((BatchGroup) group).isExtended); + } + else { + throw new IllegalStateException("reforged?"); // TODO + // simpleGroup = new ReforgedBatchGroup(model, group.shader); + } + + for (final Integer object : group.objects) { + if (isBatchSimple(batches.get(object))) { + simpleGroup.objects.add(object); + } + } + + simpleGroups.add(simpleGroup); + } + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/TextureAnimation.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/TextureAnimation.java index 1fcdd04..0f7fab9 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/TextureAnimation.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/TextureAnimation.java @@ -8,6 +8,10 @@ public class TextureAnimation extends AnimatedObject { public TextureAnimation(final MdxModel model, final com.etheller.warsmash.parsers.mdlx.TextureAnimation textureAnimation) { super(model, textureAnimation); + + this.addVariants(AnimationMap.KTAT.getWar3id(), "translation"); + this.addVariants(AnimationMap.KTAR.getWar3id(), "rotation"); + this.addVariants(AnimationMap.KTAS.getWar3id(), "scale"); } public int getTranslation(final float[] out, final int sequence, final int frame, final int counter) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/UInt32Sd.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/UInt32Sd.java index ae358c6..464b8ee 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/UInt32Sd.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/UInt32Sd.java @@ -6,7 +6,7 @@ import com.etheller.warsmash.util.RenderMathUtils; public class UInt32Sd extends Sd { public UInt32Sd(final MdxModel model, final Timeline timeline) { - super(model, timeline); + super(model, timeline, SdArrayDescriptor.LONG_ARRAY); } @Override diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/VectorSd.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/VectorSd.java index 94c8917..90fbab8 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/VectorSd.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/VectorSd.java @@ -6,7 +6,7 @@ import com.etheller.warsmash.util.Interpolator; public class VectorSd extends Sd { public VectorSd(final MdxModel model, final Timeline timeline) { - super(model, timeline); + super(model, timeline, SdArrayDescriptor.FLOAT_ARRAY); } @Override diff --git a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java index dc45a62..faef1dc 100644 --- a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java +++ b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java @@ -1,12 +1,37 @@ package com.etheller.warsmash.desktop; +import org.lwjgl.opengl.GL31; +import org.lwjgl.opengl.GL33; + import com.badlogic.gdx.backends.lwjgl.LwjglApplication; import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration; import com.etheller.warsmash.WarsmashGdxGame; +import com.etheller.warsmash.viewer5.gl.ANGLEInstancedArrays; +import com.etheller.warsmash.viewer5.gl.Extensions; public class DesktopLauncher { - public static void main (String[] arg) { - LwjglApplicationConfiguration config = new LwjglApplicationConfiguration(); + public static void main(final String[] arg) { + Extensions.angleInstancedArrays = new ANGLEInstancedArrays() { + @Override + public void glVertexAttribDivisorANGLE(final int index, final int divisor) { + GL33.glVertexAttribDivisor(index, divisor); + } + + @Override + public void glDrawElementsInstancedANGLE(final int mode, final int count, final int type, + final int indicesOffset, final int instanceCount) { + GL31.glDrawElementsInstanced(mode, count, type, indicesOffset, instanceCount); + } + + @Override + public void glDrawArraysInstancedANGLE(final int mode, final int first, final int count, + final int instanceCount) { + GL31.glDrawArraysInstanced(mode, first, count, instanceCount); + } + }; + final LwjglApplicationConfiguration config = new LwjglApplicationConfiguration(); + config.useGL30 = true; + config.gles30ContextMinorVersion = 3; new LwjglApplication(new WarsmashGdxGame(), config); } } From d39976fa1819a72ae25f25c3c381d662df18648b Mon Sep 17 00:00:00 2001 From: Retera Date: Fri, 17 Jan 2020 22:49:20 -0600 Subject: [PATCH 011/116] Squashed character test --- .../etheller/warsmash/WarsmashGdxGame.java | 35 ++++- .../etheller/warsmash/WarsmashTestGame.java | 113 ++++++++++++++ .../etheller/warsmash/WarsmashTestGame2.java | 138 +++++++++++++++++ .../etheller/warsmash/WarsmashTestGame3.java | 145 ++++++++++++++++++ .../com/etheller/warsmash/viewer5/Camera.java | 2 +- .../warsmash/viewer5/ModelViewer.java | 5 +- .../warsmash/viewer5/gl/ClientBuffer.java | 3 +- .../handlers/mdx/MdxComplexInstance.java | 4 +- .../viewer5/handlers/mdx/MdxRenderBatch.java | 6 +- .../viewer5/handlers/mdx/MdxShaders.java | 9 +- .../viewer5/handlers/mdx/SetupGeosets.java | 9 +- .../warsmash/desktop/DesktopLauncher.java | 1 + 12 files changed, 453 insertions(+), 17 deletions(-) create mode 100644 core/src/com/etheller/warsmash/WarsmashTestGame.java create mode 100644 core/src/com/etheller/warsmash/WarsmashTestGame2.java create mode 100644 core/src/com/etheller/warsmash/WarsmashTestGame3.java diff --git a/core/src/com/etheller/warsmash/WarsmashGdxGame.java b/core/src/com/etheller/warsmash/WarsmashGdxGame.java index 2e72845..2099f18 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxGame.java @@ -1,5 +1,8 @@ package com.etheller.warsmash; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.IntBuffer; import java.util.Arrays; import com.badlogic.gdx.ApplicationAdapter; @@ -26,9 +29,20 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide private ModelViewer viewer; private MdxModel model; private CameraManager cameraManager; + private static int VAO; @Override public void create() { + + final ByteBuffer tempByteBuffer = ByteBuffer.allocateDirect(4); + tempByteBuffer.order(ByteOrder.LITTLE_ENDIAN); + final IntBuffer temp = tempByteBuffer.asIntBuffer(); +// + Gdx.gl30.glGenVertexArrays(1, temp); + VAO = temp.get(0); + + Gdx.gl30.glBindVertexArray(VAO); + final String renderer = Gdx.gl.glGetString(GL20.GL_RENDERER); System.err.println("Renderer: " + renderer); @@ -46,8 +60,8 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide this.cameraManager = new CameraManager(); this.cameraManager.setupCamera(scene); -// this.model = (MdxModel) this.viewer.load("units\\human\\footman\\footman.mdx", new PathSolver() { - this.model = (MdxModel) this.viewer.load("Cube.mdx", new PathSolver() { + this.model = (MdxModel) this.viewer.load("units\\human\\footman\\footman.mdx", new PathSolver() { +// this.model = (MdxModel) this.viewer.load("Cube.mdx", new PathSolver() { @Override public SolvedPath solve(final String src, final Object solverParams) { return new SolvedPath(src, src.substring(src.lastIndexOf('.')), true); @@ -62,10 +76,25 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide // // instance.setSequenceLoopMode(2); + System.out.println("Loaded"); +// Gdx.gl30.glClearColor(0.5f, 0.5f, 0.5f, 1); // TODO remove white background + } + + public static void bindDefaultVertexArray() { + Gdx.gl30.glBindVertexArray(VAO); } @Override public void render() { +// this.cameraManager.verticalAngle += 0.01; +// if (this.cameraManager.verticalAngle >= (Math.PI)) { +// this.cameraManager.verticalAngle = 0; +// } + this.cameraManager.horizontalAngle += 0.01; + if (this.cameraManager.horizontalAngle > (2 * Math.PI)) { + this.cameraManager.horizontalAngle = 0; + } + this.cameraManager.updateCamera(); this.viewer.updateAndRender(); // gl.glDrawElements(GL20.GL_TRIANGLES, this.elements, GL20.GL_UNSIGNED_SHORT, this.faceOffset); @@ -114,7 +143,7 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide this.zoomFactor = 0.1f; this.horizontalAngle = (float) (Math.PI / 2); this.verticalAngle = (float) (Math.PI / 4); - this.distance = 500; + this.distance = 5; this.position = new Vector3(); this.target = new Vector3(); this.worldUp = new Vector3(0, 0, 1); diff --git a/core/src/com/etheller/warsmash/WarsmashTestGame.java b/core/src/com/etheller/warsmash/WarsmashTestGame.java new file mode 100644 index 0000000..635aac1 --- /dev/null +++ b/core/src/com/etheller/warsmash/WarsmashTestGame.java @@ -0,0 +1,113 @@ +package com.etheller.warsmash; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; + +import com.badlogic.gdx.ApplicationAdapter; +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.graphics.GL20; +import com.badlogic.gdx.graphics.glutils.ShaderProgram; + +public class WarsmashTestGame extends ApplicationAdapter { + private int arrayBuffer; + private int elementBuffer; + private int VAO; + + @Override + public void create() { + Gdx.gl.glClearColor(0.5f, 0.5f, 0.5f, 1.0f); // colour to use when clearing + + final ByteBuffer tempByteBuffer = ByteBuffer.allocateDirect(4); + tempByteBuffer.order(ByteOrder.LITTLE_ENDIAN); + final IntBuffer temp = tempByteBuffer.asIntBuffer(); + + Gdx.gl30.glGenVertexArrays(1, temp); + this.VAO = temp.get(0); + + Gdx.gl30.glBindVertexArray(this.VAO); + + this.shaderProgram = new ShaderProgram(vsSimple, fsSimple); + if (!this.shaderProgram.isCompiled()) { + throw new IllegalStateException(this.shaderProgram.getLog()); + } + + this.arrayBuffer = Gdx.gl.glGenBuffer(); + this.elementBuffer = Gdx.gl.glGenBuffer(); + System.out.println("arrayBuffer: " + this.arrayBuffer + ", elementBuffer: " + this.elementBuffer); + + Gdx.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.arrayBuffer); + + this.shaderProgram.enableVertexAttribute("a_position"); + this.shaderProgram.setVertexAttribute("a_position", 3, GL20.GL_FLOAT, false, 0, 0); + + Gdx.gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, this.elementBuffer); + + final ByteBuffer vertexByteBuffer = ByteBuffer.allocateDirect(4 * 9); + vertexByteBuffer.order(ByteOrder.LITTLE_ENDIAN); + this.vertexBuffer = vertexByteBuffer.asFloatBuffer(); + + this.vertexBuffer.put(0, -1f); + this.vertexBuffer.put(1, -1f); + this.vertexBuffer.put(2, 0); + this.vertexBuffer.put(3, 1f); + this.vertexBuffer.put(4, -1f); + this.vertexBuffer.put(5, 0); + this.vertexBuffer.put(6, 0f); + this.vertexBuffer.put(7, 1f); + this.vertexBuffer.put(8, 0); + + Gdx.gl.glBufferData(GL20.GL_ARRAY_BUFFER, 9 * 4, null, GL20.GL_STATIC_DRAW); + + final ByteBuffer faceByteBuffer = ByteBuffer.allocateDirect(6); + faceByteBuffer.order(ByteOrder.LITTLE_ENDIAN); + this.faceBuffer = faceByteBuffer.asShortBuffer(); + + this.faceBuffer.put(0, (short) 0); + this.faceBuffer.put(1, (short) 1); + this.faceBuffer.put(2, (short) 2); + + Gdx.gl.glBufferData(GL20.GL_ELEMENT_ARRAY_BUFFER, 3 * 2, null, GL20.GL_STATIC_DRAW); + + } + + @Override + public void render() { + Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT); + +// Gdx.gl30.glBindVertexArray(this.VAO); + this.shaderProgram.begin(); + Gdx.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.arrayBuffer); + Gdx.gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, 0, 9 * 4, this.vertexBuffer); + Gdx.gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, this.elementBuffer); + Gdx.gl.glBufferSubData(GL20.GL_ELEMENT_ARRAY_BUFFER, 0, 3 * 2, this.faceBuffer); + Gdx.gl.glDrawElements(GL20.GL_TRIANGLES, 9, GL20.GL_UNSIGNED_SHORT, 0); +// Gdx.gl.glDrawArrays(GL20.GL_TRIANGLES, 0, 3); + this.shaderProgram.end(); + } + + @Override + public void dispose() { + } + + @Override + public void resize(final int width, final int height) { + } + + public static final String vsSimple = "\r\n" + // + " attribute vec3 a_position;\r\n" + // + " void main() {\r\n" + // + " gl_Position = vec4(a_position, 1.0);\r\n" + // + " }\r\n"; + + public static final String fsSimple = "\r\n" + // + " void main() {\r\n" + // + " gl_FragColor = vec4(0.0, 1.0, 1.0, 1.0);\r\n" + // + " }\r\n"; + private ShaderProgram shaderProgram; + private FloatBuffer vertexBuffer; + private ShortBuffer faceBuffer; + +} diff --git a/core/src/com/etheller/warsmash/WarsmashTestGame2.java b/core/src/com/etheller/warsmash/WarsmashTestGame2.java new file mode 100644 index 0000000..8261284 --- /dev/null +++ b/core/src/com/etheller/warsmash/WarsmashTestGame2.java @@ -0,0 +1,138 @@ +package com.etheller.warsmash; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; + +import com.badlogic.gdx.ApplicationAdapter; +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.graphics.GL20; +import com.badlogic.gdx.graphics.GL30; + +public class WarsmashTestGame2 extends ApplicationAdapter { + private int VBO; + private int VAO; + + @Override + public void create() { + final ByteBuffer vertexByteBuffer = ByteBuffer.allocateDirect(4 * 9); + vertexByteBuffer.order(ByteOrder.LITTLE_ENDIAN); + final FloatBuffer vertexBuffer = vertexByteBuffer.asFloatBuffer(); + + vertexBuffer.put(0, -0.5f); + vertexBuffer.put(1, -0.5f); + vertexBuffer.put(2, 0); + vertexBuffer.put(3, 0.5f); + vertexBuffer.put(4, -0.5f); + vertexBuffer.put(5, 0); + vertexBuffer.put(6, 0f); + vertexBuffer.put(7, 0.5f); + vertexBuffer.put(8, 0); + vertexBuffer.clear(); + + Gdx.gl30.glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + Gdx.gl30.glEnable(GL20.GL_DEPTH_TEST); +// Gdx.gl30.glEnable(GL20.GL_CULL_FACE); + + final ByteBuffer tempByteBuffer = ByteBuffer.allocateDirect(4); + System.out.println(tempByteBuffer.order()); + tempByteBuffer.order(ByteOrder.LITTLE_ENDIAN); + final IntBuffer temp = tempByteBuffer.asIntBuffer(); + + Gdx.gl30.glGenBuffers(1, temp); + this.VBO = temp.get(0); + + temp.clear(); + Gdx.gl30.glGenVertexArrays(1, temp); + this.VAO = temp.get(0); + + Gdx.gl30.glBindVertexArray(this.VAO); + + Gdx.gl30.glBindBuffer(GL30.GL_ARRAY_BUFFER, this.VBO); + Gdx.gl30.glBufferData(GL30.GL_ARRAY_BUFFER, 9 * 4, vertexByteBuffer, GL30.GL_STATIC_DRAW); + + Gdx.gl30.glVertexAttribPointer(0, 3, GL30.GL_FLOAT, false, 3 * 4, 0); + Gdx.gl30.glEnableVertexAttribArray(0); + + final int vertexShader = Gdx.gl30.glCreateShader(GL30.GL_VERTEX_SHADER); + Gdx.gl30.glShaderSource(vertexShader, vsSimple); + Gdx.gl30.glCompileShader(vertexShader); + + temp.clear(); + Gdx.gl30.glGetShaderiv(vertexShader, GL30.GL_COMPILE_STATUS, temp); + int success = temp.get(0); + if (success == 0) { + final String infoLog = Gdx.gl30.glGetShaderInfoLog(vertexShader); + System.err.println(infoLog); + throw new IllegalStateException("bad vertex shader"); + } + + final int fragmentShader = Gdx.gl30.glCreateShader(GL30.GL_FRAGMENT_SHADER); + Gdx.gl30.glShaderSource(fragmentShader, fsSimple); + Gdx.gl30.glCompileShader(fragmentShader); + + temp.clear(); + Gdx.gl30.glGetShaderiv(fragmentShader, GL30.GL_COMPILE_STATUS, temp); + success = temp.get(0); + if (success == 0) { + final String infoLog = Gdx.gl30.glGetShaderInfoLog(fragmentShader); + System.err.println(infoLog); + throw new IllegalStateException("bad fragment shader"); + } + + this.shaderProgram = Gdx.gl30.glCreateProgram(); + + Gdx.gl30.glAttachShader(this.shaderProgram, vertexShader); + Gdx.gl30.glAttachShader(this.shaderProgram, fragmentShader); + Gdx.gl30.glLinkProgram(this.shaderProgram); + + temp.clear(); + Gdx.gl30.glGetProgramiv(this.shaderProgram, GL30.GL_LINK_STATUS, temp); + success = temp.get(0); + if (success == 0) { + final String infoLog = Gdx.gl30.glGetProgramInfoLog(this.shaderProgram); + System.err.println(infoLog); + throw new IllegalStateException("bad program"); + } + + Gdx.gl30.glDeleteShader(vertexShader); + Gdx.gl30.glDeleteShader(fragmentShader); + } + + @Override + public void render() { + Gdx.gl30.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT); + Gdx.gl30.glUseProgram(this.shaderProgram); + Gdx.gl30.glBindVertexArray(this.VAO); + + Gdx.gl30.glDrawArrays(GL30.GL_TRIANGLES, 0, 3); + } + + @Override + public void dispose() { + } + + @Override + public void resize(final int width, final int height) { + final int side = Math.min(width, height); + Gdx.gl30.glViewport((width - side) / 2, (height - side) / 2, side, side); + + } + + public static final String vsSimple = "\r\n" + // + "#version 450 core\r\n" + // + " layout(location = 0) in vec3 aPos;\r\n" + // + " void main() {\r\n" + // + " gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\r\n" + // + " }\r\n"; + + public static final String fsSimple = "\r\n" + // + "#version 450 core\r\n" + // + " out vec4 FragColor;\r\n" + // + " void main() {\r\n" + // + " FragColor = vec4(0.2f, 1.0f, 0.2f, 1.0f);\r\n" + // + " }\r\n"; + private int shaderProgram; + +} diff --git a/core/src/com/etheller/warsmash/WarsmashTestGame3.java b/core/src/com/etheller/warsmash/WarsmashTestGame3.java new file mode 100644 index 0000000..f2b0ee7 --- /dev/null +++ b/core/src/com/etheller/warsmash/WarsmashTestGame3.java @@ -0,0 +1,145 @@ +package com.etheller.warsmash; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; + +import com.badlogic.gdx.ApplicationAdapter; +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.graphics.GL20; +import com.badlogic.gdx.graphics.GL30; + +public class WarsmashTestGame3 extends ApplicationAdapter { + private int VBO; + private int VAO; + + @Override + public void create() { + Gdx.gl30.glBindVertexArray(0); + + this.vertexByteBuffer = ByteBuffer.allocateDirect(4 * 9); + this.vertexByteBuffer.order(ByteOrder.LITTLE_ENDIAN); + final FloatBuffer vertexBuffer = this.vertexByteBuffer.asFloatBuffer(); + + vertexBuffer.put(0, -0.5f); + vertexBuffer.put(1, -0.5f); + vertexBuffer.put(2, 0); + vertexBuffer.put(3, 0.5f); + vertexBuffer.put(4, -0.5f); + vertexBuffer.put(5, 0); + vertexBuffer.put(6, 0f); + vertexBuffer.put(7, 0.5f); + vertexBuffer.put(8, 0); + vertexBuffer.clear(); + + Gdx.gl30.glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + Gdx.gl30.glEnable(GL20.GL_DEPTH_TEST); +// Gdx.gl30.glEnable(GL20.GL_CULL_FACE); + + final ByteBuffer tempByteBuffer = ByteBuffer.allocateDirect(4); + tempByteBuffer.order(ByteOrder.LITTLE_ENDIAN); + final IntBuffer temp = tempByteBuffer.asIntBuffer(); + + Gdx.gl30.glGenVertexArrays(1, temp); + this.VAO = temp.get(0); + + Gdx.gl30.glBindVertexArray(this.VAO); + + temp.clear(); + Gdx.gl30.glGenBuffers(1, temp); + this.VBO = temp.get(0); + + Gdx.gl30.glBindBuffer(GL30.GL_ARRAY_BUFFER, this.VBO); + Gdx.gl30.glBufferData(GL30.GL_ARRAY_BUFFER, 9 * 4, this.vertexByteBuffer, GL30.GL_STATIC_DRAW); + + Gdx.gl30.glVertexAttribPointer(0, 3, GL30.GL_FLOAT, false, 3 * 4, 0); + Gdx.gl30.glEnableVertexAttribArray(0); + + final int vertexShader = Gdx.gl30.glCreateShader(GL30.GL_VERTEX_SHADER); + Gdx.gl30.glShaderSource(vertexShader, vsSimple); + Gdx.gl30.glCompileShader(vertexShader); + + temp.clear(); + Gdx.gl30.glGetShaderiv(vertexShader, GL30.GL_COMPILE_STATUS, temp); + int success = temp.get(0); + if (success == 0) { + final String infoLog = Gdx.gl30.glGetShaderInfoLog(vertexShader); + System.err.println(infoLog); + throw new IllegalStateException("bad vertex shader"); + } + + final int fragmentShader = Gdx.gl30.glCreateShader(GL30.GL_FRAGMENT_SHADER); + Gdx.gl30.glShaderSource(fragmentShader, fsSimple); + Gdx.gl30.glCompileShader(fragmentShader); + + temp.clear(); + Gdx.gl30.glGetShaderiv(fragmentShader, GL30.GL_COMPILE_STATUS, temp); + success = temp.get(0); + if (success == 0) { + final String infoLog = Gdx.gl30.glGetShaderInfoLog(fragmentShader); + System.err.println(infoLog); + throw new IllegalStateException("bad fragment shader"); + } + + this.shaderProgram = Gdx.gl30.glCreateProgram(); + + Gdx.gl30.glAttachShader(this.shaderProgram, vertexShader); + Gdx.gl30.glAttachShader(this.shaderProgram, fragmentShader); + Gdx.gl30.glLinkProgram(this.shaderProgram); + + temp.clear(); + Gdx.gl30.glGetProgramiv(this.shaderProgram, GL30.GL_LINK_STATUS, temp); + success = temp.get(0); + if (success == 0) { + final String infoLog = Gdx.gl30.glGetProgramInfoLog(this.shaderProgram); + System.err.println(infoLog); + throw new IllegalStateException("bad program"); + } + + Gdx.gl30.glDeleteShader(vertexShader); + Gdx.gl30.glDeleteShader(fragmentShader); + } + + @Override + public void render() { + + Gdx.gl30.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT); + Gdx.gl30.glBindVertexArray(this.VAO); + Gdx.gl30.glUseProgram(this.shaderProgram); + Gdx.gl30.glBindBuffer(GL30.GL_ARRAY_BUFFER, this.VBO); + Gdx.gl30.glVertexAttribPointer(0, 3, GL30.GL_FLOAT, false, 3 * 4, 0); + Gdx.gl30.glEnableVertexAttribArray(0); + + Gdx.gl30.glDrawArrays(GL30.GL_TRIANGLES, 0, 3); + } + + @Override + public void dispose() { + } + + @Override + public void resize(final int width, final int height) { + final int side = Math.min(width, height); + Gdx.gl30.glViewport((width - side) / 2, (height - side) / 2, side, side); + + } + + public static final String vsSimple = "\r\n" + // + "#version 450 core\r\n" + // + " layout(location = 0) in vec3 aPos;\r\n" + // + " void main() {\r\n" + // + " gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\r\n" + // + " }\r\n"; + + public static final String fsSimple = "\r\n" + // + "#version 450 core\r\n" + // + " out vec4 FragColor;\r\n" + // + " void main() {\r\n" + // + " FragColor = vec4(0.2f, 1.0f, 0.2f, 1.0f);\r\n" + // + " }\r\n"; + private int shaderProgram; + + private ByteBuffer vertexByteBuffer; + +} diff --git a/core/src/com/etheller/warsmash/viewer5/Camera.java b/core/src/com/etheller/warsmash/viewer5/Camera.java index 79dfa53..41925f4 100644 --- a/core/src/com/etheller/warsmash/viewer5/Camera.java +++ b/core/src/com/etheller/warsmash/viewer5/Camera.java @@ -250,7 +250,7 @@ public class Camera { this.topClipPlane, this.nearClipPlane, this.farClipPlane); } - rotation.toMatrix(projectionMatrix.val); + rotation.toMatrix(viewMatrix.val); viewMatrix.translate(vectorHeap.set(location).scl(-1)); inverseRotation.set(rotation).conjugate(); diff --git a/core/src/com/etheller/warsmash/viewer5/ModelViewer.java b/core/src/com/etheller/warsmash/viewer5/ModelViewer.java index 28e27ae..1284991 100644 --- a/core/src/com/etheller/warsmash/viewer5/ModelViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/ModelViewer.java @@ -2,6 +2,7 @@ package com.etheller.warsmash.viewer5; import java.io.IOException; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -57,7 +58,7 @@ public class ModelViewer { this.rectBuffer = this.gl.glGenBuffer(); this.buffer = new ClientBuffer(this.gl); this.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.rectBuffer); - final ByteBuffer temp = ByteBuffer.allocateDirect(6); + final ByteBuffer temp = ByteBuffer.allocateDirect(6).order(ByteOrder.nativeOrder()); temp.put((byte) 0); temp.put((byte) 1); temp.put((byte) 2); @@ -282,7 +283,7 @@ public class ModelViewer { public void startFrame() { this.gl.glDepthMask(true); this.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT); - this.gl.glClearColor(0.5f, 0.5f, 0.5f, 1); // TODO remove white background +// WarsmashGdxGame.bindDefaultVertexArray(); } public void render() { diff --git a/core/src/com/etheller/warsmash/viewer5/gl/ClientBuffer.java b/core/src/com/etheller/warsmash/viewer5/gl/ClientBuffer.java index 22ff687..0c513f6 100644 --- a/core/src/com/etheller/warsmash/viewer5/gl/ClientBuffer.java +++ b/core/src/com/etheller/warsmash/viewer5/gl/ClientBuffer.java @@ -1,6 +1,7 @@ package com.etheller.warsmash.viewer5.gl; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.nio.FloatBuffer; import com.badlogic.gdx.graphics.GL20; @@ -33,7 +34,7 @@ public class ClientBuffer { this.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.buffer); - this.arrayBuffer = ByteBuffer.allocateDirect(this.size); + this.arrayBuffer = ByteBuffer.allocateDirect(this.size).order(ByteOrder.nativeOrder()); this.gl.glBufferData(GL20.GL_ARRAY_BUFFER, this.size, this.arrayBuffer, GL20.GL_DYNAMIC_DRAW); this.byteView = this.arrayBuffer; this.floatView = this.arrayBuffer.asFloatBuffer(); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java index 4792e88..1f22002 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java @@ -1,6 +1,7 @@ package com.etheller.warsmash.viewer5.handlers.mdx; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.nio.FloatBuffer; import java.util.ArrayList; import java.util.List; @@ -89,7 +90,8 @@ public class MdxComplexInstance extends ModelInstance { // A shared typed array for all world matrices of the internal nodes. this.worldMatrices = ((List) sharedNodeData[1]).toArray(new Matrix4[0]); - this.worldMatricesCopyHeap = ByteBuffer.allocateDirect(16 * this.worldMatrices.length * 4).asFloatBuffer(); + this.worldMatricesCopyHeap = ByteBuffer.allocateDirect(16 * this.worldMatrices.length * 4) + .order(ByteOrder.nativeOrder()).asFloatBuffer(); // And now initialize all of the nodes and objects for (final Bone bone : model.bones) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxRenderBatch.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxRenderBatch.java index 2e4a332..f5cf018 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxRenderBatch.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxRenderBatch.java @@ -18,6 +18,7 @@ import com.etheller.warsmash.viewer5.gl.ClientBuffer; import com.etheller.warsmash.viewer5.gl.WebGL; public class MdxRenderBatch extends RenderBatch { + private static final Matrix4 transposeHeap = new Matrix4(); public MdxRenderBatch(final Scene scene, final Model model, final TextureMapper textureMapper) { super(scene, model, textureMapper); @@ -85,8 +86,9 @@ public class MdxRenderBatch extends RenderBatch { shader.setVertexAttribute(m2, 3, GL20.GL_FLOAT, false, 48, 24); shader.setVertexAttribute(m3, 3, GL20.GL_FLOAT, false, 48, 36); - shader.setUniformMatrix4fv("u_VP", this.scene.camera.viewProjectionMatrix.val, 0, - this.scene.camera.viewProjectionMatrix.val.length); + transposeHeap.set(this.scene.camera.viewProjectionMatrix); + transposeHeap.tra(); + shader.setUniformMatrix4fv("u_VP", transposeHeap.val, 0, transposeHeap.val.length); gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, model.arrayBuffer); gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, model.elementBuffer); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxShaders.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxShaders.java index d1360ba..8322d7d 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxShaders.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxShaders.java @@ -66,7 +66,8 @@ public class MdxShaders { " varying vec2 v_uv;\r\n" + // " void main() {\r\n" + // " v_uv = a_uv;\r\n" + // - " gl_Position = u_VP * mat4(a_m0, 0.0, a_m1, 0.0, a_m2, 0.0, a_m3, 1.0) * vec4(a_position, 1.0);\r\n" + // +// " gl_Position = u_VP * mat4(a_m0, 0.0, a_m1, 0.0, a_m2, 0.0, a_m3, 1.0) * vec4(a_position, 1.0);\r\n" + // + " gl_Position = u_VP * vec4(a_position, 1.0);\r\n" + // " }\r\n"; public static final String fsSimple = "\r\n" + // @@ -77,9 +78,9 @@ public class MdxShaders { " void main() {\r\n" + // " vec4 color = texture2D(u_texture, v_uv);\r\n" + // " // 1bit Alpha\r\n" + // - " //if (u_filterMode == 1.0 && color.a < 0.75) {\r\n" + // - " //discard;\r\n" + // - " //}\r\n" + // + " if (u_filterMode == 1.0 && color.a < 0.75) {\r\n" + // + " discard;\r\n" + // + " }\r\n" + // " gl_FragColor = color;\r\n" + // " }\r\n"; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SetupGeosets.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SetupGeosets.java index ee38440..498fc6c 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SetupGeosets.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SetupGeosets.java @@ -1,6 +1,7 @@ package com.etheller.warsmash.viewer5.handlers.mdx; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.nio.FloatBuffer; import java.nio.ShortBuffer; import java.util.ArrayList; @@ -184,7 +185,8 @@ public class SetupGeosets { } private static ShortBuffer wrapFaces(final int[] faces) { - final ShortBuffer wrapper = ByteBuffer.allocateDirect(faces.length * 2).asShortBuffer(); + final ShortBuffer wrapper = ByteBuffer.allocateDirect(faces.length * 2).order(ByteOrder.nativeOrder()) + .asShortBuffer(); for (final int face : faces) { wrapper.put((short) face); } @@ -193,14 +195,15 @@ public class SetupGeosets { } private static ByteBuffer wrap(final byte[] skin) { - final ByteBuffer wrapper = ByteBuffer.allocateDirect(skin.length); + final ByteBuffer wrapper = ByteBuffer.allocateDirect(skin.length).order(ByteOrder.nativeOrder()); wrapper.put(skin); wrapper.clear(); return wrapper; } private static FloatBuffer wrap(final float[] positions) { - final FloatBuffer wrapper = ByteBuffer.allocateDirect(positions.length * 4).asFloatBuffer(); + final FloatBuffer wrapper = ByteBuffer.allocateDirect(positions.length * 4).order(ByteOrder.nativeOrder()) + .asFloatBuffer(); wrapper.put(positions); wrapper.clear(); return wrapper; diff --git a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java index faef1dc..b4b6f7d 100644 --- a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java +++ b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java @@ -31,6 +31,7 @@ public class DesktopLauncher { }; final LwjglApplicationConfiguration config = new LwjglApplicationConfiguration(); config.useGL30 = true; + config.gles30ContextMajorVersion = 3; config.gles30ContextMinorVersion = 3; new LwjglApplication(new WarsmashGdxGame(), config); } From 015b04c3710aada92994e51fe510747f1396314b Mon Sep 17 00:00:00 2001 From: Retera Date: Mon, 20 Jan 2020 19:28:18 -0600 Subject: [PATCH 012/116] More work towards prototype --- .../etheller/warsmash/WarsmashGdxGame.java | 323 +++++- .../warsmash/WarsmashTestGameAttributes.java | 163 ++++ .../warsmash/WarsmashTestGameAttributes2.java | 214 ++++ .../WarsmashTestGameTextureBuffer.java | 244 +++++ .../WarsmashTestGameTextureBuffer2.java | 203 ++++ .../warsmash/WarsmashTestMyTextureGame.java | 56 ++ .../datasources/CompoundDataSource.java | 8 +- .../CompoundDataSourceDescriptor.java | 27 + .../warsmash/datasources/MpqDataSource.java | 143 +++ .../datasources/MpqDataSourceDescriptor.java | 75 ++ .../etheller/warsmash/parsers/mdlx/Bone.java | 4 + .../warsmash/parsers/mdlx/Sequence.java | 20 + .../warsmash/parsers/w3x/War3Map.java | 125 +++ .../warsmash/parsers/w3x/doo/Doodad.java | 157 +++ .../warsmash/parsers/w3x/doo/RandomItem.java | 23 + .../parsers/w3x/doo/RandomItemSet.java | 34 + .../parsers/w3x/doo/TerrainDoodad.java | 54 + .../warsmash/parsers/w3x/doo/War3MapDoo.java | 108 ++ .../objectdata/Warcraft3MapObjectData.java | 166 ++++ .../parsers/w3x/unitsdoo/DroppedItem.java | 26 + .../parsers/w3x/unitsdoo/DroppedItemSet.java | 38 + .../parsers/w3x/unitsdoo/InventoryItem.java | 26 + .../parsers/w3x/unitsdoo/ModifiedAbility.java | 26 + .../parsers/w3x/unitsdoo/RandomUnit.java | 23 + .../warsmash/parsers/w3x/unitsdoo/Unit.java | 428 ++++++++ .../parsers/w3x/unitsdoo/War3MapUnitsDoo.java | 76 ++ .../warsmash/parsers/w3x/w3e/Corner.java | 110 +++ .../warsmash/parsers/w3x/w3e/War3MapW3e.java | 148 +++ .../warsmash/parsers/w3x/w3i/Force.java | 29 + .../warsmash/parsers/w3x/w3i/Player.java | 48 + .../warsmash/parsers/w3x/w3i/RandomItem.java | 39 + .../parsers/w3x/w3i/RandomItemSet.java | 39 + .../parsers/w3x/w3i/RandomItemTable.java | 69 ++ .../warsmash/parsers/w3x/w3i/RandomUnit.java | 31 + .../parsers/w3x/w3i/RandomUnitTable.java | 48 + .../w3x/w3i/TechAvailabilityChange.java | 24 + .../w3x/w3i/UpgradeAvailabilityChange.java | 30 + .../warsmash/parsers/w3x/w3i/War3MapW3i.java | 440 +++++++++ .../warsmash/parsers/w3x/wpm/War3MapWpm.java | 54 + .../warsmash/units/StandardObjectData.java | 4 + .../warsmash/units/custom/Change.java | 105 ++ .../warsmash/units/custom/ChangeMap.java | 55 ++ .../units/custom/ObjectDataChangeEntry.java | 47 + .../warsmash/units/custom/ObjectMap.java | 89 ++ .../etheller/warsmash/units/custom/WTS.java | 5 + .../warsmash/units/custom/WTSFile.java | 94 ++ .../units/custom/War3ObjectDataChangeset.java | 802 +++++++++++++++ .../units/manager/MutableObjectData.java | 922 ++++++++++++++++++ .../MutableObjectDataChangeListener.java | 23 + .../MutableObjectDataChangeNotifier.java | 72 ++ .../etheller/warsmash/util/ImageUtils.java | 22 +- .../etheller/warsmash/util/MappedData.java | 4 + .../etheller/warsmash/util/ParseUtils.java | 37 + .../warsmash/util/RenderMathUtils.java | 148 ++- .../warsmash/util/SubscriberSetNotifier.java | 22 + .../com/etheller/warsmash/util/Vector4.java | 8 +- .../warsmash/util/WarsmashConstants.java | 5 + .../warsmash/viewer/BoundingShape.java | 116 --- .../com/etheller/warsmash/viewer/Bucket.java | 28 - .../com/etheller/warsmash/viewer/Camera.java | 314 ------ .../com/etheller/warsmash/viewer/Model.java | 9 - .../warsmash/viewer/ModelInstance.java | 5 - .../etheller/warsmash/viewer/ModelView.java | 71 -- .../com/etheller/warsmash/viewer/Scene.java | 5 - .../etheller/warsmash/viewer/SceneNode.java | 245 ----- .../warsmash/viewer/SkeletalNode.java | 118 --- .../com/etheller/warsmash/viewer/Viewer.java | 7 - .../etheller/warsmash/viewer/ViewerNode.java | 65 -- .../com/etheller/warsmash/viewer5/Camera.java | 3 +- .../viewer5/EmittedObjectUpdater.java | 3 + .../etheller/warsmash/viewer5/Emitter.java | 8 +- .../warsmash/viewer5/GdxTextureResource.java | 56 ++ .../com/etheller/warsmash/viewer5/Grid.java | 6 +- .../warsmash/viewer5/ModelInstance.java | 3 - .../warsmash/viewer5/ModelViewer.java | 17 +- .../com/etheller/warsmash/viewer5/Node.java | 8 +- .../etheller/warsmash/viewer5/PathSolver.java | 12 + .../viewer5/RawOpenGLTextureResource.java | 138 +++ .../com/etheller/warsmash/viewer5/Scene.java | 5 +- .../warsmash/viewer5/SkeletalNode.java | 15 +- .../etheller/warsmash/viewer5/Texture.java | 43 +- .../warsmash/viewer5/UpdatableObject.java | 2 +- .../warsmash/viewer5/gl/DataTexture.java | 9 +- .../viewer5/handlers/blp/BlpTexture.java | 13 +- .../handlers/mdx/AttachmentInstance.java | 42 +- .../viewer5/handlers/mdx/BatchGroup.java | 20 +- .../warsmash/viewer5/handlers/mdx/Bone.java | 20 +- .../viewer5/handlers/mdx/EmitterGroup.java | 2 +- .../mdx/EventObjectEmitterObject.java | 12 +- .../viewer5/handlers/mdx/FilterMode.java | 6 +- .../handlers/mdx/GeometryEmitterFuncs.java | 19 +- .../warsmash/viewer5/handlers/mdx/Geoset.java | 2 +- .../warsmash/viewer5/handlers/mdx/Layer.java | 4 +- .../handlers/mdx/MdxComplexInstance.java | 30 +- .../viewer5/handlers/mdx/MdxEmitter.java | 7 +- .../viewer5/handlers/mdx/MdxHandler.java | 4 +- .../viewer5/handlers/mdx/MdxModel.java | 4 +- .../viewer5/handlers/mdx/MdxNode.java | 4 +- .../viewer5/handlers/mdx/MdxRenderBatch.java | 23 +- .../viewer5/handlers/mdx/MdxShaders.java | 67 +- .../viewer5/handlers/mdx/Particle2.java | 2 +- .../viewer5/handlers/mdx/QuaternionSd.java | 5 +- .../viewer5/handlers/mdx/ReplaceableIds.java | 4 +- .../viewer5/handlers/mdx/ScalarSd.java | 6 +- .../warsmash/viewer5/handlers/mdx/Sd.java | 3 +- .../viewer5/handlers/mdx/SdSequence.java | 13 +- .../viewer5/handlers/mdx/SetupGeosets.java | 48 +- .../viewer5/handlers/mdx/UInt32Sd.java | 6 +- .../viewer5/handlers/mdx/VectorSd.java | 5 +- .../warsmash/viewer5/handlers/w3x/Doodad.java | 33 + .../viewer5/handlers/w3x/IndexedSequence.java | 13 + .../viewer5/handlers/w3x/StandSequence.java | 64 ++ .../handlers/w3x/StandSequenceComparator.java | 11 + .../viewer5/handlers/w3x/TerrainDoodad.java | 30 + .../viewer5/handlers/w3x/TerrainModel.java | 147 +++ .../warsmash/viewer5/handlers/w3x/Unit.java | 36 + .../viewer5/handlers/w3x/W3xShaders.java | 230 +++++ .../viewer5/handlers/w3x/War3MapViewer.java | 552 +++++++++++ core/src/mpq/ArchivedFile.java | 132 +++ core/src/mpq/ArchivedFileExtractor.java | 66 ++ core/src/mpq/ArchivedFileStream.java | 131 +++ core/src/mpq/BlockTable.java | 78 ++ core/src/mpq/HashLookup.java | 35 + core/src/mpq/HashTable.java | 82 ++ core/src/mpq/MPQArchive.java | 291 ++++++ core/src/mpq/MPQException.java | 29 + core/src/mpq/compression/Compression.java | 397 ++++++++ .../compression/DecompressionException.java | 25 + core/src/mpq/compression/adpcm/ADPCM.java | 118 +++ core/src/mpq/compression/huffman/Huffman.java | 481 +++++++++ .../mpq/compression/pkware/PKException.java | 14 + .../mpq/compression/pkware/PKExploder.java | 200 ++++ core/src/mpq/data/ArchiveHeader.java | 98 ++ core/src/mpq/data/BlockTableEntry.java | 31 + core/src/mpq/data/FileHeader.java | 31 + core/src/mpq/data/HashTableEntry.java | 31 + core/src/mpq/data/Raw.java | 20 + core/src/mpq/data/RawArrays.java | 36 + core/src/mpq/data/UserDataHeader.java | 21 + core/src/mpq/util/Cryption.java | 116 +++ .../warsmash/desktop/DesktopLauncher.java | 4 + 141 files changed, 10344 insertions(+), 1284 deletions(-) create mode 100644 core/src/com/etheller/warsmash/WarsmashTestGameAttributes.java create mode 100644 core/src/com/etheller/warsmash/WarsmashTestGameAttributes2.java create mode 100644 core/src/com/etheller/warsmash/WarsmashTestGameTextureBuffer.java create mode 100644 core/src/com/etheller/warsmash/WarsmashTestGameTextureBuffer2.java create mode 100644 core/src/com/etheller/warsmash/WarsmashTestMyTextureGame.java create mode 100644 core/src/com/etheller/warsmash/datasources/CompoundDataSourceDescriptor.java create mode 100644 core/src/com/etheller/warsmash/datasources/MpqDataSource.java create mode 100644 core/src/com/etheller/warsmash/datasources/MpqDataSourceDescriptor.java create mode 100644 core/src/com/etheller/warsmash/parsers/w3x/War3Map.java create mode 100644 core/src/com/etheller/warsmash/parsers/w3x/doo/Doodad.java create mode 100644 core/src/com/etheller/warsmash/parsers/w3x/doo/RandomItem.java create mode 100644 core/src/com/etheller/warsmash/parsers/w3x/doo/RandomItemSet.java create mode 100644 core/src/com/etheller/warsmash/parsers/w3x/doo/TerrainDoodad.java create mode 100644 core/src/com/etheller/warsmash/parsers/w3x/doo/War3MapDoo.java create mode 100644 core/src/com/etheller/warsmash/parsers/w3x/objectdata/Warcraft3MapObjectData.java create mode 100644 core/src/com/etheller/warsmash/parsers/w3x/unitsdoo/DroppedItem.java create mode 100644 core/src/com/etheller/warsmash/parsers/w3x/unitsdoo/DroppedItemSet.java create mode 100644 core/src/com/etheller/warsmash/parsers/w3x/unitsdoo/InventoryItem.java create mode 100644 core/src/com/etheller/warsmash/parsers/w3x/unitsdoo/ModifiedAbility.java create mode 100644 core/src/com/etheller/warsmash/parsers/w3x/unitsdoo/RandomUnit.java create mode 100644 core/src/com/etheller/warsmash/parsers/w3x/unitsdoo/Unit.java create mode 100644 core/src/com/etheller/warsmash/parsers/w3x/unitsdoo/War3MapUnitsDoo.java create mode 100644 core/src/com/etheller/warsmash/parsers/w3x/w3e/Corner.java create mode 100644 core/src/com/etheller/warsmash/parsers/w3x/w3e/War3MapW3e.java create mode 100644 core/src/com/etheller/warsmash/parsers/w3x/w3i/Force.java create mode 100644 core/src/com/etheller/warsmash/parsers/w3x/w3i/Player.java create mode 100644 core/src/com/etheller/warsmash/parsers/w3x/w3i/RandomItem.java create mode 100644 core/src/com/etheller/warsmash/parsers/w3x/w3i/RandomItemSet.java create mode 100644 core/src/com/etheller/warsmash/parsers/w3x/w3i/RandomItemTable.java create mode 100644 core/src/com/etheller/warsmash/parsers/w3x/w3i/RandomUnit.java create mode 100644 core/src/com/etheller/warsmash/parsers/w3x/w3i/RandomUnitTable.java create mode 100644 core/src/com/etheller/warsmash/parsers/w3x/w3i/TechAvailabilityChange.java create mode 100644 core/src/com/etheller/warsmash/parsers/w3x/w3i/UpgradeAvailabilityChange.java create mode 100644 core/src/com/etheller/warsmash/parsers/w3x/w3i/War3MapW3i.java create mode 100644 core/src/com/etheller/warsmash/parsers/w3x/wpm/War3MapWpm.java create mode 100644 core/src/com/etheller/warsmash/units/custom/Change.java create mode 100644 core/src/com/etheller/warsmash/units/custom/ChangeMap.java create mode 100644 core/src/com/etheller/warsmash/units/custom/ObjectDataChangeEntry.java create mode 100644 core/src/com/etheller/warsmash/units/custom/ObjectMap.java create mode 100644 core/src/com/etheller/warsmash/units/custom/WTS.java create mode 100644 core/src/com/etheller/warsmash/units/custom/WTSFile.java create mode 100644 core/src/com/etheller/warsmash/units/custom/War3ObjectDataChangeset.java create mode 100644 core/src/com/etheller/warsmash/units/manager/MutableObjectData.java create mode 100644 core/src/com/etheller/warsmash/units/manager/MutableObjectDataChangeListener.java create mode 100644 core/src/com/etheller/warsmash/units/manager/MutableObjectDataChangeNotifier.java create mode 100644 core/src/com/etheller/warsmash/util/SubscriberSetNotifier.java create mode 100644 core/src/com/etheller/warsmash/util/WarsmashConstants.java delete mode 100644 core/src/com/etheller/warsmash/viewer/BoundingShape.java delete mode 100644 core/src/com/etheller/warsmash/viewer/Bucket.java delete mode 100644 core/src/com/etheller/warsmash/viewer/Camera.java delete mode 100644 core/src/com/etheller/warsmash/viewer/Model.java delete mode 100644 core/src/com/etheller/warsmash/viewer/ModelInstance.java delete mode 100644 core/src/com/etheller/warsmash/viewer/ModelView.java delete mode 100644 core/src/com/etheller/warsmash/viewer/Scene.java delete mode 100644 core/src/com/etheller/warsmash/viewer/SceneNode.java delete mode 100644 core/src/com/etheller/warsmash/viewer/SkeletalNode.java delete mode 100644 core/src/com/etheller/warsmash/viewer/Viewer.java delete mode 100644 core/src/com/etheller/warsmash/viewer/ViewerNode.java create mode 100644 core/src/com/etheller/warsmash/viewer5/GdxTextureResource.java create mode 100644 core/src/com/etheller/warsmash/viewer5/RawOpenGLTextureResource.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/Doodad.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/IndexedSequence.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/StandSequence.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/StandSequenceComparator.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/TerrainDoodad.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/TerrainModel.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/Unit.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xShaders.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java create mode 100644 core/src/mpq/ArchivedFile.java create mode 100644 core/src/mpq/ArchivedFileExtractor.java create mode 100644 core/src/mpq/ArchivedFileStream.java create mode 100644 core/src/mpq/BlockTable.java create mode 100644 core/src/mpq/HashLookup.java create mode 100644 core/src/mpq/HashTable.java create mode 100644 core/src/mpq/MPQArchive.java create mode 100644 core/src/mpq/MPQException.java create mode 100644 core/src/mpq/compression/Compression.java create mode 100644 core/src/mpq/compression/DecompressionException.java create mode 100644 core/src/mpq/compression/adpcm/ADPCM.java create mode 100644 core/src/mpq/compression/huffman/Huffman.java create mode 100644 core/src/mpq/compression/pkware/PKException.java create mode 100644 core/src/mpq/compression/pkware/PKExploder.java create mode 100644 core/src/mpq/data/ArchiveHeader.java create mode 100644 core/src/mpq/data/BlockTableEntry.java create mode 100644 core/src/mpq/data/FileHeader.java create mode 100644 core/src/mpq/data/HashTableEntry.java create mode 100644 core/src/mpq/data/Raw.java create mode 100644 core/src/mpq/data/RawArrays.java create mode 100644 core/src/mpq/data/UserDataHeader.java create mode 100644 core/src/mpq/util/Cryption.java diff --git a/core/src/com/etheller/warsmash/WarsmashGdxGame.java b/core/src/com/etheller/warsmash/WarsmashGdxGame.java index 2099f18..688611c 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxGame.java @@ -8,28 +8,38 @@ import java.util.Arrays; import com.badlogic.gdx.ApplicationAdapter; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.GL20; +import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.math.Quaternion; +import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Vector3; -import com.etheller.warsmash.datasources.CompoundDataSource; +import com.etheller.warsmash.datasources.CompoundDataSourceDescriptor; import com.etheller.warsmash.datasources.DataSource; import com.etheller.warsmash.datasources.DataSourceDescriptor; import com.etheller.warsmash.datasources.FolderDataSourceDescriptor; +import com.etheller.warsmash.parsers.mdlx.Sequence; import com.etheller.warsmash.viewer5.Camera; import com.etheller.warsmash.viewer5.CanvasProvider; import com.etheller.warsmash.viewer5.ModelViewer; import com.etheller.warsmash.viewer5.PathSolver; import com.etheller.warsmash.viewer5.Scene; import com.etheller.warsmash.viewer5.SolvedPath; +import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxHandler; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; -import com.etheller.warsmash.viewer5.handlers.mdx.MdxSimpleInstance; public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvider { + private static final boolean SPIN = false; + private static final boolean ADVANCE_ANIMS = true; private DataSource codebase; private ModelViewer viewer; private MdxModel model; private CameraManager cameraManager; private static int VAO; + private final Rectangle tempRect = new Rectangle(); + + private BitmapFont font; + private SpriteBatch batch; @Override public void create() { @@ -46,58 +56,320 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide final String renderer = Gdx.gl.glGetString(GL20.GL_RENDERER); System.err.println("Renderer: " + renderer); - final FolderDataSourceDescriptor war3mpq = new FolderDataSourceDescriptor( - "D:\\NEEDS_ORGANIZING\\MPQBuild\\War3.mpq\\war3.mpq"); + final FolderDataSourceDescriptor war3mpq = new FolderDataSourceDescriptor("E:\\Backups\\Warcraft\\Data\\127"); final FolderDataSourceDescriptor testingFolder = new FolderDataSourceDescriptor( "D:\\NEEDS_ORGANIZING\\MPQBuild\\Test"); - this.codebase = new CompoundDataSource(Arrays.asList(war3mpq, testingFolder)); + final FolderDataSourceDescriptor currentFolder = new FolderDataSourceDescriptor("."); + this.codebase = new CompoundDataSourceDescriptor( + Arrays.asList(war3mpq, testingFolder, currentFolder)).createDataSource(); this.viewer = new ModelViewer(this.codebase, this); this.viewer.addHandler(new MdxHandler()); + this.viewer.enableAudio(); final Scene scene = this.viewer.addScene(); + scene.enableAudio(); this.cameraManager = new CameraManager(); this.cameraManager.setupCamera(scene); - this.model = (MdxModel) this.viewer.load("units\\human\\footman\\footman.mdx", new PathSolver() { -// this.model = (MdxModel) this.viewer.load("Cube.mdx", new PathSolver() { + this.mainModel = (MdxModel) this.viewer.load("Buildings\\Undead\\Necropolis\\Necropolis.mdx", new PathSolver() { @Override public SolvedPath solve(final String src, final Object solverParams) { return new SolvedPath(src, src.substring(src.lastIndexOf('.')), true); } }, null); - final MdxSimpleInstance instance = (MdxSimpleInstance) this.model.addInstance(1); + this.mainInstance = (MdxComplexInstance) this.mainModel.addInstance(0); - instance.setScene(scene); + this.mainInstance.setScene(scene); -// instance.setSequence(1); -// -// instance.setSequenceLoopMode(2); + int animIndex = 0; + for (final Sequence s : this.mainModel.getSequences()) { + if (s.getName().toLowerCase().startsWith("walk")) { + animIndex = this.mainModel.getSequences().indexOf(s); + } + } + this.mainInstance.setSequence(animIndex); + + this.mainInstance.setSequenceLoopMode(0); + +// acolytesHarvestingSceneJoke2(scene); System.out.println("Loaded"); -// Gdx.gl30.glClearColor(0.5f, 0.5f, 0.5f, 1); // TODO remove white background + Gdx.gl30.glClearColor(0.5f, 0.5f, 0.5f, 1); // TODO remove white background + + this.font = new BitmapFont(); + this.batch = new SpriteBatch(); + } + + private void makeDruidSquare(final Scene scene) { + final MdxModel model2 = (MdxModel) this.viewer.load("units\\nightelf\\druidoftheclaw\\druidoftheclaw.mdx", + new PathSolver() { + @Override + public SolvedPath solve(final String src, final Object solverParams) { + return new SolvedPath(src, src.substring(src.lastIndexOf('.')), true); + } + }, null); + makePerfectSquare(scene, model2, 15); + } + + private void singleAcolyteScene(final Scene scene) { + final MdxModel model2 = (MdxModel) this.viewer.load("units\\undead\\acolyte\\acolyte.mdx", new PathSolver() { + @Override + public SolvedPath solve(final String src, final Object solverParams) { + return new SolvedPath(src, src.substring(src.lastIndexOf('.')), true); + } + }, null); + + final MdxComplexInstance instance3 = (MdxComplexInstance) model2.addInstance(0); + + instance3.setScene(scene); + + int animIndex = 0; + for (final Sequence s : model2.getSequences()) { + if (s.getName().toLowerCase().startsWith("stand work")) { + animIndex = model2.getSequences().indexOf(s); + } + } + instance3.setSequence(animIndex); + + instance3.setSequenceLoopMode(2); + } + + private void singleModelScene(final Scene scene, final String path, final String animName) { + final MdxModel model2 = (MdxModel) this.viewer.load(path, new PathSolver() { + @Override + public SolvedPath solve(final String src, final Object solverParams) { + return new SolvedPath(src, src.substring(src.lastIndexOf('.')), true); + } + }, null); + + final MdxComplexInstance instance3 = (MdxComplexInstance) model2.addInstance(0); + + instance3.setScene(scene); + + int animIndex = 0; + for (final Sequence s : model2.getSequences()) { + if (s.getName().toLowerCase().startsWith(animName)) { + animIndex = model2.getSequences().indexOf(s); + } + } + instance3.setSequence(animIndex); + + instance3.setSequenceLoopMode(2); + } + + private void acolytesHarvestingScene(final Scene scene) { + + final MdxModel acolyteModel = (MdxModel) this.viewer.load("units\\undead\\acolyte\\acolyte.mdx", + new PathSolver() { + @Override + public SolvedPath solve(final String src, final Object solverParams) { + return new SolvedPath(src, src.substring(src.lastIndexOf('.')), true); + } + }, null); + final MdxModel mineEffectModel = (MdxModel) this.viewer + .load("abilities\\spells\\undead\\undeadmine\\undeadminecircle.mdx", new PathSolver() { + @Override + public SolvedPath solve(final String src, final Object solverParams) { + return new SolvedPath(src, src.substring(src.lastIndexOf('.')), true); + } + }, null); + for (int i = 0; i < 5; i++) { + final MdxComplexInstance acolyteInstance = (MdxComplexInstance) acolyteModel.addInstance(0); + + acolyteInstance.setScene(scene); + + int animIndex = i % acolyteModel.getSequences().size(); + for (final Sequence s : acolyteModel.getSequences()) { + if (s.getName().toLowerCase().startsWith("stand work")) { + animIndex = acolyteModel.getSequences().indexOf(s); + } + } + acolyteInstance.setSequence(animIndex); + + acolyteInstance.setSequenceLoopMode(2); + + final double angle = ((Math.PI * 2) / 5) * i; + acolyteInstance.localLocation.x = (float) Math.cos(angle) * 256; + acolyteInstance.localLocation.y = (float) Math.sin(angle) * 256; + acolyteInstance.localRotation.setFromAxisRad(0, 0, 1, (float) (angle + Math.PI)); + + final MdxComplexInstance effectInstance = (MdxComplexInstance) mineEffectModel.addInstance(0); + + effectInstance.setScene(scene); + + effectInstance.setSequence(1); + + effectInstance.setSequenceLoopMode(2); + effectInstance.localLocation.x = (float) Math.cos(angle) * 256; + effectInstance.localLocation.y = (float) Math.sin(angle) * 256; + effectInstance.localRotation.setFromAxisRad(0, 0, 1, (float) (angle)); + + } + final MdxModel mineModel = (MdxModel) this.viewer.load("buildings\\undead\\hauntedmine\\hauntedmine.mdx", + new PathSolver() { + @Override + public SolvedPath solve(final String src, final Object solverParams) { + return new SolvedPath(src, src.substring(src.lastIndexOf('.')), true); + } + }, null); + final MdxComplexInstance mineInstance = (MdxComplexInstance) mineModel.addInstance(0); + + mineInstance.setScene(scene); + + mineInstance.setSequence(2); + + mineInstance.setSequenceLoopMode(2); + } + + private void acolytesHarvestingSceneJoke2(final Scene scene) { + + final MdxModel acolyteModel = (MdxModel) this.viewer.load("units\\undead\\acolyte\\acolyte.mdx", + new PathSolver() { + @Override + public SolvedPath solve(final String src, final Object solverParams) { + return new SolvedPath(src, src.substring(src.lastIndexOf('.')), true); + } + }, null); + final MdxModel mineEffectModel = (MdxModel) this.viewer + .load("abilities\\spells\\undead\\undeadmine\\undeadminecircle.mdx", new PathSolver() { + @Override + public SolvedPath solve(final String src, final Object solverParams) { + return new SolvedPath(src, src.substring(src.lastIndexOf('.')), true); + } + }, null); + for (int i = 0; i < 5; i++) { + final MdxComplexInstance acolyteInstance = (MdxComplexInstance) acolyteModel.addInstance(0); + + acolyteInstance.setScene(scene); + + int animIndex = i % acolyteModel.getSequences().size(); + for (final Sequence s : acolyteModel.getSequences()) { + if (s.getName().toLowerCase().startsWith("stand work")) { + animIndex = acolyteModel.getSequences().indexOf(s); + } + } + acolyteInstance.setSequence(animIndex); + + acolyteInstance.setSequenceLoopMode(2); + + final double angle = ((Math.PI * 2) / 5) * i; + acolyteInstance.localLocation.x = (float) Math.cos(angle) * 256; + acolyteInstance.localLocation.y = (float) Math.sin(angle) * 256; + acolyteInstance.localRotation.setFromAxisRad(0, 0, 1, (float) (angle + Math.PI)); + + final MdxComplexInstance effectInstance = (MdxComplexInstance) mineEffectModel.addInstance(0); + + effectInstance.setScene(scene); + + effectInstance.setSequence(1); + + effectInstance.setSequenceLoopMode(2); + effectInstance.localLocation.x = (float) Math.cos(angle) * 256; + effectInstance.localLocation.y = (float) Math.sin(angle) * 256; + effectInstance.localRotation.setFromAxisRad(0, 0, 1, (float) (angle)); + + } + final MdxModel mineModel = (MdxModel) this.viewer.load("units\\orc\\spiritwolf\\spiritwolf.mdx", + new PathSolver() { + @Override + public SolvedPath solve(final String src, final Object solverParams) { + return new SolvedPath(src, src.substring(src.lastIndexOf('.')), true); + } + }, null); + final MdxComplexInstance mineInstance = (MdxComplexInstance) mineModel.addInstance(0); + + mineInstance.setScene(scene); + + mineInstance.setSequence(0); + mineInstance.localScale.x = 2; + mineInstance.localScale.y = 2; + mineInstance.localScale.z = 2; + + mineInstance.setSequenceLoopMode(2); + final MdxModel mineModel2 = (MdxModel) this.viewer + .load("abilities\\spells\\undead\\unsummon\\unsummontarget.mdx", new PathSolver() { + @Override + public SolvedPath solve(final String src, final Object solverParams) { + return new SolvedPath(src, src.substring(src.lastIndexOf('.')), true); + } + }, null); + final MdxComplexInstance mineInstance2 = (MdxComplexInstance) mineModel2.addInstance(0); + + mineInstance2.setScene(scene); + + mineInstance2.setSequence(0); + + mineInstance2.setSequenceLoopMode(2); + } + + private void makeFourHundred(final Scene scene, final MdxModel model2) { + for (int i = 0; i < 400; i++) { + final MdxComplexInstance instance3 = (MdxComplexInstance) model2.addInstance(0); + instance3.localLocation.x = (((i % 20) - 10) * 128); + instance3.localLocation.y = (((i / 20) - 10) * 128); + + instance3.setScene(scene); + + final int animIndex = i % model2.getSequences().size(); + instance3.setSequence(animIndex); + + instance3.setSequenceLoopMode(2); + } + } + + private void makePerfectSquare(final Scene scene, final MdxModel model2, final int n) { + final int n2 = n * n; + for (int i = 0; i < n2; i++) { + final MdxComplexInstance instance3 = (MdxComplexInstance) model2.addInstance(0); + instance3.localLocation.x = (((i % n) - (n / 2)) * 128); + instance3.localLocation.y = (((i / n) - (n / 2)) * 128); + + instance3.setScene(scene); + + final int animIndex = i % model2.getSequences().size(); + instance3.setSequence(animIndex); + + instance3.setSequenceLoopMode(2); + } } public static void bindDefaultVertexArray() { Gdx.gl30.glBindVertexArray(VAO); } + private int frame = 0; + private MdxComplexInstance mainInstance; + private MdxModel mainModel; + @Override public void render() { -// this.cameraManager.verticalAngle += 0.01; -// if (this.cameraManager.verticalAngle >= (Math.PI)) { -// this.cameraManager.verticalAngle = 0; -// } - this.cameraManager.horizontalAngle += 0.01; - if (this.cameraManager.horizontalAngle > (2 * Math.PI)) { - this.cameraManager.horizontalAngle = 0; + Gdx.gl30.glBindVertexArray(VAO); + if (SPIN) { + this.cameraManager.horizontalAngle += 0.01; + if (this.cameraManager.horizontalAngle > (2 * Math.PI)) { + this.cameraManager.horizontalAngle = 0; + } } this.cameraManager.updateCamera(); this.viewer.updateAndRender(); // gl.glDrawElements(GL20.GL_TRIANGLES, this.elements, GL20.GL_UNSIGNED_SHORT, this.faceOffset); + +// this.batch.begin(); +// this.font.draw(this.batch, Integer.toString(Gdx.graphics.getFramesPerSecond()), 0, 0); +// this.batch.end(); + + this.frame++; + if ((this.frame % 1000) == 0) { + System.out.println(Integer.toString(Gdx.graphics.getFramesPerSecond())); + } + + if (ADVANCE_ANIMS && this.mainInstance.sequenceEnded) { + this.mainInstance.setSequence((this.mainInstance.sequence + 1) % this.mainModel.getSequences().size()); + } } @Override @@ -114,6 +386,13 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide return Gdx.graphics.getHeight(); } + @Override + public void resize(final int width, final int height) { + this.tempRect.width = width; + this.tempRect.height = height; + this.cameraManager.camera.viewport(this.tempRect); + } + class CameraManager { private CanvasProvider canvas; private Camera camera; @@ -143,9 +422,9 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide this.zoomFactor = 0.1f; this.horizontalAngle = (float) (Math.PI / 2); this.verticalAngle = (float) (Math.PI / 4); - this.distance = 5; + this.distance = 1000; this.position = new Vector3(); - this.target = new Vector3(); + this.target = new Vector3(0, 0, 50); this.worldUp = new Vector3(0, 0, 1); this.vecHeap = new Vector3(); this.quatHeap = new Quaternion(); diff --git a/core/src/com/etheller/warsmash/WarsmashTestGameAttributes.java b/core/src/com/etheller/warsmash/WarsmashTestGameAttributes.java new file mode 100644 index 0000000..36e4fa9 --- /dev/null +++ b/core/src/com/etheller/warsmash/WarsmashTestGameAttributes.java @@ -0,0 +1,163 @@ +package com.etheller.warsmash; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; + +import com.badlogic.gdx.ApplicationAdapter; +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.graphics.GL20; +import com.badlogic.gdx.graphics.glutils.ShaderProgram; + +public class WarsmashTestGameAttributes extends ApplicationAdapter { + private int arrayBuffer; + private int elementBuffer; + private int VAO; + + @Override + public void create() { + Gdx.gl.glClearColor(0.5f, 0.5f, 0.5f, 1.0f); // colour to use when clearing + + final ByteBuffer tempByteBuffer = ByteBuffer.allocateDirect(4); + tempByteBuffer.order(ByteOrder.LITTLE_ENDIAN); + final IntBuffer temp = tempByteBuffer.asIntBuffer(); + + Gdx.gl30.glGenVertexArrays(1, temp); + this.VAO = temp.get(0); + + Gdx.gl30.glBindVertexArray(this.VAO); + + this.shaderProgram = new ShaderProgram(vsSimple, fsSimple); + if (!this.shaderProgram.isCompiled()) { + throw new IllegalStateException(this.shaderProgram.getLog()); + } + + this.arrayBuffer = Gdx.gl.glGenBuffer(); + this.elementBuffer = Gdx.gl.glGenBuffer(); + System.out.println("arrayBuffer: " + this.arrayBuffer + ", elementBuffer: " + this.elementBuffer); + + Gdx.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.arrayBuffer); + + Gdx.gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, this.elementBuffer); + + final ByteBuffer vertexByteBuffer = ByteBuffer.allocateDirect(4 * 9); + vertexByteBuffer.order(ByteOrder.LITTLE_ENDIAN); + this.vertexBuffer = vertexByteBuffer.asFloatBuffer(); + + this.vertexBuffer.put(0, -1f); + this.vertexBuffer.put(1, -1f); + this.vertexBuffer.put(2, 0); + this.vertexBuffer.put(3, 1f); + this.vertexBuffer.put(4, -1f); + this.vertexBuffer.put(5, 0); + this.vertexBuffer.put(6, 0f); + this.vertexBuffer.put(7, 1f); + this.vertexBuffer.put(8, 0); + + Gdx.gl.glBufferData(GL20.GL_ARRAY_BUFFER, ((9 * 4) * 2) + 3, null, GL20.GL_STATIC_DRAW); + Gdx.gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, 0, 9 * 4, this.vertexBuffer); + + final ByteBuffer vertex2ByteBuffer = ByteBuffer.allocateDirect(4 * 9); + vertex2ByteBuffer.order(ByteOrder.LITTLE_ENDIAN); + final FloatBuffer vertexBuffer2 = vertex2ByteBuffer.asFloatBuffer(); + + vertexBuffer2.put(0, -1f); + vertexBuffer2.put(1, -1f); + vertexBuffer2.put(2, 0); + vertexBuffer2.put(3, 1f); + vertexBuffer2.put(4, -1f); + vertexBuffer2.put(5, 0); + vertexBuffer2.put(6, 0f); + vertexBuffer2.put(7, 1f); + vertexBuffer2.put(8, 0); + + Gdx.gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, 9 * 4, 9 * 4, vertexBuffer2); + + final ByteBuffer skinByteBuffer = ByteBuffer.allocateDirect(3); + skinByteBuffer.order(ByteOrder.LITTLE_ENDIAN); + skinByteBuffer.put((byte) 34); + skinByteBuffer.put((byte) 35); + skinByteBuffer.put((byte) 36); + skinByteBuffer.clear(); + Gdx.gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, 9 * 4 * 2, 3, skinByteBuffer); + +// this.shaderProgram.setVertexAttribute("a_position", 3, GL20.GL_FLOAT, false, 0, 0); +// this.shaderProgram.enableVertexAttribute("a_position"); +// this.shaderProgram.setVertexAttribute("a_position2", 3, GL20.GL_FLOAT, false, 0, 4 * 9); +// this.shaderProgram.enableVertexAttribute("a_position2"); +// this.shaderProgram.setVertexAttribute("a_boneNumber", 1, GL20.GL_UNSIGNED_BYTE, false, 1, 4 * 9 * 2); +// this.shaderProgram.enableVertexAttribute("a_boneNumber"); + + final ByteBuffer faceByteBuffer = ByteBuffer.allocateDirect(6); + faceByteBuffer.order(ByteOrder.LITTLE_ENDIAN); + this.faceBuffer = faceByteBuffer.asShortBuffer(); + + this.faceBuffer.put(0, (short) 0); + this.faceBuffer.put(1, (short) 1); + this.faceBuffer.put(2, (short) 2); + + Gdx.gl.glBufferData(GL20.GL_ELEMENT_ARRAY_BUFFER, 3 * 2, null, GL20.GL_STATIC_DRAW); + + final int glGetError = Gdx.gl.glGetError(); + System.out.println(glGetError); + } + + @Override + public void render() { + Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT); + +// Gdx.gl30.glBindVertexArray(this.VAO); + this.shaderProgram.begin(); + + Gdx.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.arrayBuffer); + this.shaderProgram.setVertexAttribute("a_position", 3, GL20.GL_FLOAT, false, 0, 0); + this.shaderProgram.enableVertexAttribute("a_position"); + this.shaderProgram.setVertexAttribute("a_position2", 3, GL20.GL_FLOAT, false, 0, 4 * 9); + this.shaderProgram.enableVertexAttribute("a_position2"); + this.shaderProgram.setVertexAttribute("a_boneNumber", 1, GL20.GL_UNSIGNED_BYTE, false, 1, 4 * 9 * 2); + this.shaderProgram.enableVertexAttribute("a_boneNumber"); + Gdx.gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, this.elementBuffer); + Gdx.gl.glBufferSubData(GL20.GL_ELEMENT_ARRAY_BUFFER, 0, 3 * 2, this.faceBuffer); + Gdx.gl.glDrawElements(GL20.GL_TRIANGLES, 3, GL20.GL_UNSIGNED_SHORT, 0); +// Gdx.gl.glDrawArrays(GL20.GL_TRIANGLES, 0, 3); + this.shaderProgram.end(); + } + + @Override + public void dispose() { + } + + @Override + public void resize(final int width, final int height) { + } + + public static final String vsSimple = "\r\n" + // + " attribute vec3 a_position;\r\n" + // + " attribute vec3 a_position2;\r\n" + // + " attribute float a_boneNumber;\r\n" + // + " varying float fragNumber;\r\n" + // + " void main() {\r\n" + // + " gl_Position = vec4(a_position2.x, a_position2.y, a_position2.z, 1.0);\r\n" + // + " fragNumber = a_boneNumber;\r\n" + // + " }\r\n"; + + public static final String fsSimple = "\r\n" + // + " varying float fragNumber;\r\n" + // + " void main() {\r\n" + // + " if( fragNumber > 35.5 ) {\r\n" + // + " gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\r\n" + // + " } else if( fragNumber > 34.5 ) {\r\n" + // + " gl_FragColor = vec4(0.0, 1.0, 1.0, 1.0);\r\n" + // + " } else if( fragNumber > 33.5 ) {\r\n" + // + " gl_FragColor = vec4(1.0, 0.0, 1.0, 1.0);\r\n" + // + " } else {\r\n" + // + " gl_FragColor = vec4(fragNumber*100.0, fragNumber, fragNumber, 1.0);\r\n" + // + " }\r\n" + // + " }\r\n"; + private ShaderProgram shaderProgram; + private FloatBuffer vertexBuffer; + private ShortBuffer faceBuffer; + +} diff --git a/core/src/com/etheller/warsmash/WarsmashTestGameAttributes2.java b/core/src/com/etheller/warsmash/WarsmashTestGameAttributes2.java new file mode 100644 index 0000000..b125319 --- /dev/null +++ b/core/src/com/etheller/warsmash/WarsmashTestGameAttributes2.java @@ -0,0 +1,214 @@ +package com.etheller.warsmash; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; +import java.util.Arrays; + +import com.badlogic.gdx.ApplicationAdapter; +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.graphics.GL20; +import com.badlogic.gdx.graphics.glutils.ShaderProgram; +import com.etheller.warsmash.datasources.CompoundDataSourceDescriptor; +import com.etheller.warsmash.datasources.DataSource; +import com.etheller.warsmash.datasources.DataSourceDescriptor; +import com.etheller.warsmash.datasources.FolderDataSourceDescriptor; +import com.etheller.warsmash.parsers.mdlx.Geoset; +import com.etheller.warsmash.parsers.mdlx.MdlxModel; + +public class WarsmashTestGameAttributes2 extends ApplicationAdapter { + private int arrayBuffer; + private int elementBuffer; + private int VAO; + private DataSource codebase; + + @Override + public void create() { + final FolderDataSourceDescriptor war3mpq = new FolderDataSourceDescriptor( + "D:\\NEEDS_ORGANIZING\\MPQBuild\\War3.mpq\\war3.mpq"); + final FolderDataSourceDescriptor testingFolder = new FolderDataSourceDescriptor( + "D:\\NEEDS_ORGANIZING\\MPQBuild\\Test"); + this.codebase = new CompoundDataSourceDescriptor(Arrays.asList(war3mpq, testingFolder)) + .createDataSource(); + + final MdlxModel model; + try (InputStream modelStream = this.codebase.getResourceAsStream("Buildings\\Other\\TempArtB\\TempArtB.mdx")) { + model = new MdlxModel(modelStream); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + + Gdx.gl.glClearColor(0.5f, 0.5f, 0.5f, 1.0f); // colour to use when clearing + + final ByteBuffer tempByteBuffer = ByteBuffer.allocateDirect(4); + tempByteBuffer.order(ByteOrder.LITTLE_ENDIAN); + final IntBuffer temp = tempByteBuffer.asIntBuffer(); + + Gdx.gl30.glGenVertexArrays(1, temp); + this.VAO = temp.get(0); + + Gdx.gl30.glBindVertexArray(this.VAO); + + this.shaderProgram = new ShaderProgram(vsSimple, fsSimple); + if (!this.shaderProgram.isCompiled()) { + throw new IllegalStateException(this.shaderProgram.getLog()); + } + + this.arrayBuffer = Gdx.gl.glGenBuffer(); + this.elementBuffer = Gdx.gl.glGenBuffer(); + System.out.println("arrayBuffer: " + this.arrayBuffer + ", elementBuffer: " + this.elementBuffer); + + Gdx.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.arrayBuffer); + + Gdx.gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, this.elementBuffer); + + final Geoset geoset0 = model.getGeosets().get(0); + final float[] vertices = geoset0.getVertices(); + final ByteBuffer vertexByteBuffer = ByteBuffer.allocateDirect(4 * 9); + vertexByteBuffer.order(ByteOrder.LITTLE_ENDIAN); + this.vertexBuffer = vertexByteBuffer.asFloatBuffer(); + + this.vertexBuffer.put(0, -1f); + this.vertexBuffer.put(1, -1f); + this.vertexBuffer.put(2, 0); + this.vertexBuffer.put(3, 1f); + this.vertexBuffer.put(4, -1f); + this.vertexBuffer.put(5, 0); + this.vertexBuffer.put(6, 0f); + this.vertexBuffer.put(7, 1f); + this.vertexBuffer.put(8, 0); + + Gdx.gl.glBufferData(GL20.GL_ARRAY_BUFFER, ((9 * 4) * 2) + 3, null, GL20.GL_STATIC_DRAW); + Gdx.gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, 0, 9 * 4, this.vertexBuffer); + + final ByteBuffer vertex2ByteBuffer = ByteBuffer.allocateDirect(4 * 9); + vertex2ByteBuffer.order(ByteOrder.LITTLE_ENDIAN); + final FloatBuffer vertexBuffer2 = vertex2ByteBuffer.asFloatBuffer(); + + vertexBuffer2.put(0, -1f); + vertexBuffer2.put(1, -1f); + vertexBuffer2.put(2, 0); + vertexBuffer2.put(3, 1f); + vertexBuffer2.put(4, -1f); + vertexBuffer2.put(5, 0); + vertexBuffer2.put(6, 0f); + vertexBuffer2.put(7, 1f); + vertexBuffer2.put(8, 0); + + Gdx.gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, 9 * 4, 9 * 4, vertexBuffer2); + + final ByteBuffer skinByteBuffer = ByteBuffer.allocateDirect(3); + skinByteBuffer.order(ByteOrder.LITTLE_ENDIAN); + skinByteBuffer.put((byte) 34); + skinByteBuffer.put((byte) 35); + skinByteBuffer.put((byte) 36); + skinByteBuffer.clear(); + Gdx.gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, 9 * 4 * 2, 3, skinByteBuffer); + +// this.shaderProgram.setVertexAttribute("a_position", 3, GL20.GL_FLOAT, false, 0, 0); +// this.shaderProgram.enableVertexAttribute("a_position"); +// this.shaderProgram.setVertexAttribute("a_position2", 3, GL20.GL_FLOAT, false, 0, 4 * 9); +// this.shaderProgram.enableVertexAttribute("a_position2"); +// this.shaderProgram.setVertexAttribute("a_boneNumber", 1, GL20.GL_UNSIGNED_BYTE, false, 1, 4 * 9 * 2); +// this.shaderProgram.enableVertexAttribute("a_boneNumber"); + + final ByteBuffer faceByteBuffer = ByteBuffer.allocateDirect(6); + faceByteBuffer.order(ByteOrder.LITTLE_ENDIAN); + this.faceBuffer = faceByteBuffer.asShortBuffer(); + + this.faceBuffer.put(0, (short) 0); + this.faceBuffer.put(1, (short) 1); + this.faceBuffer.put(2, (short) 2); + + Gdx.gl.glBufferData(GL20.GL_ELEMENT_ARRAY_BUFFER, 3 * 2, null, GL20.GL_STATIC_DRAW); + + final int glGetError = Gdx.gl.glGetError(); + System.out.println(glGetError); + } + + @Override + public void render() { + Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT); + +// Gdx.gl30.glBindVertexArray(this.VAO); + this.shaderProgram.begin(); + + Gdx.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.arrayBuffer); + this.shaderProgram.setVertexAttribute("a_position", 3, GL20.GL_FLOAT, false, 0, 0); + this.shaderProgram.enableVertexAttribute("a_position"); + this.shaderProgram.setVertexAttribute("a_position2", 3, GL20.GL_FLOAT, false, 0, 4 * 9); + this.shaderProgram.enableVertexAttribute("a_position2"); + this.shaderProgram.setVertexAttribute("a_boneNumber", 1, GL20.GL_UNSIGNED_BYTE, false, 1, 4 * 9 * 2); + this.shaderProgram.enableVertexAttribute("a_boneNumber"); + Gdx.gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, this.elementBuffer); + Gdx.gl.glBufferSubData(GL20.GL_ELEMENT_ARRAY_BUFFER, 0, 3 * 2, this.faceBuffer); + Gdx.gl.glDrawElements(GL20.GL_TRIANGLES, 3, GL20.GL_UNSIGNED_SHORT, 0); +// Gdx.gl.glDrawArrays(GL20.GL_TRIANGLES, 0, 3); + this.shaderProgram.end(); + } + + @Override + public void dispose() { + } + + @Override + public void resize(final int width, final int height) { + } + + public static final String vsSimple = "\r\n" + // + " attribute vec3 a_position;\r\n" + // + " attribute vec3 a_position2;\r\n" + // + " attribute float a_boneNumber;\r\n" + // + " varying float fragNumber;\r\n" + // + " void main() {\r\n" + // + " gl_Position = vec4(a_position2.x, a_position2.y, a_position2.z, 1.0);\r\n" + // + " fragNumber = a_boneNumber;\r\n" + // + " }\r\n"; + + public static final String fsSimple = "\r\n" + // + " varying float fragNumber;\r\n" + // + " void main() {\r\n" + // + " if( fragNumber > 35.5 ) {\r\n" + // + " gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\r\n" + // + " } else if( fragNumber > 34.5 ) {\r\n" + // + " gl_FragColor = vec4(0.0, 1.0, 1.0, 1.0);\r\n" + // + " } else if( fragNumber > 33.5 ) {\r\n" + // + " gl_FragColor = vec4(1.0, 0.0, 1.0, 1.0);\r\n" + // + " } else {\r\n" + // + " gl_FragColor = vec4(fragNumber*100.0, fragNumber, fragNumber, 1.0);\r\n" + // + " }\r\n" + // + " }\r\n"; + private ShaderProgram shaderProgram; + private FloatBuffer vertexBuffer; + private ShortBuffer faceBuffer; + + private static ShortBuffer wrapFaces(final int[] faces) { + final ShortBuffer wrapper = ByteBuffer.allocateDirect(faces.length * 2).order(ByteOrder.nativeOrder()) + .asShortBuffer(); + for (final int face : faces) { + wrapper.put((short) face); + } + wrapper.clear(); + return wrapper; + } + + private static ByteBuffer wrap(final byte[] skin) { + final ByteBuffer wrapper = ByteBuffer.allocateDirect(skin.length).order(ByteOrder.nativeOrder()); + wrapper.put(skin); + wrapper.clear(); + return wrapper; + } + + private static FloatBuffer wrap(final float[] positions) { + final FloatBuffer wrapper = ByteBuffer.allocateDirect(positions.length * 4).order(ByteOrder.nativeOrder()) + .asFloatBuffer(); + wrapper.put(positions); + wrapper.clear(); + return wrapper; + } +} diff --git a/core/src/com/etheller/warsmash/WarsmashTestGameTextureBuffer.java b/core/src/com/etheller/warsmash/WarsmashTestGameTextureBuffer.java new file mode 100644 index 0000000..c0f7930 --- /dev/null +++ b/core/src/com/etheller/warsmash/WarsmashTestGameTextureBuffer.java @@ -0,0 +1,244 @@ +package com.etheller.warsmash; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; + +import com.badlogic.gdx.ApplicationAdapter; +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.graphics.GL20; +import com.badlogic.gdx.graphics.GL30; +import com.badlogic.gdx.graphics.glutils.ShaderProgram; +import com.etheller.warsmash.viewer5.Shaders; + +public class WarsmashTestGameTextureBuffer extends ApplicationAdapter { + private int arrayBuffer; + private int elementBuffer; + private int VAO; + private ShaderProgram shaderProgram; + private FloatBuffer vertexBuffer; + private ShortBuffer faceBuffer; + + @Override + public void create() { +// ShaderProgram.pedantic = false; + Gdx.gl.glClearColor(0.5f, 0.5f, 0.5f, 1.0f); // colour to use when clearing + + final ByteBuffer tempByteBuffer = ByteBuffer.allocateDirect(4); + tempByteBuffer.order(ByteOrder.LITTLE_ENDIAN); + final IntBuffer temp = tempByteBuffer.asIntBuffer(); + + Gdx.gl30.glGenVertexArrays(1, temp); + this.VAO = temp.get(0); + + Gdx.gl30.glBindVertexArray(this.VAO); + + System.out.println(vsSimple); + this.shaderProgram = new ShaderProgram(vsSimple, fsSimple); + if (!this.shaderProgram.isCompiled()) { + throw new IllegalStateException(this.shaderProgram.getLog()); + } + + this.arrayBuffer = Gdx.gl.glGenBuffer(); + this.elementBuffer = Gdx.gl.glGenBuffer(); + System.out.println("arrayBuffer: " + this.arrayBuffer + ", elementBuffer: " + this.elementBuffer); + + Gdx.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.arrayBuffer); + + Gdx.gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, this.elementBuffer); + + final ByteBuffer vertexByteBuffer = ByteBuffer.allocateDirect(4 * 9); + vertexByteBuffer.order(ByteOrder.LITTLE_ENDIAN); + this.vertexBuffer = vertexByteBuffer.asFloatBuffer(); + + this.vertexBuffer.put(0, -1f); + this.vertexBuffer.put(1, -1f); + this.vertexBuffer.put(2, 0); + this.vertexBuffer.put(3, 1f); + this.vertexBuffer.put(4, -1f); + this.vertexBuffer.put(5, 0); + this.vertexBuffer.put(6, 0f); + this.vertexBuffer.put(7, 1f); + this.vertexBuffer.put(8, 0); + + Gdx.gl.glBufferData(GL20.GL_ARRAY_BUFFER, ((9 * 4) * 2) + 3, null, GL20.GL_STATIC_DRAW); + Gdx.gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, 0, 9 * 4, this.vertexBuffer); + + final ByteBuffer vertex2ByteBuffer = ByteBuffer.allocateDirect(4 * 9); + vertex2ByteBuffer.order(ByteOrder.LITTLE_ENDIAN); + final FloatBuffer vertexBuffer2 = vertex2ByteBuffer.asFloatBuffer(); + + vertexBuffer2.put(0, -1f); + vertexBuffer2.put(1, -1f); + vertexBuffer2.put(2, 0); + vertexBuffer2.put(3, 1f); + vertexBuffer2.put(4, -1f); + vertexBuffer2.put(5, 0); + vertexBuffer2.put(6, 0f); + vertexBuffer2.put(7, 1f); + vertexBuffer2.put(8, 0); + + Gdx.gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, 9 * 4, 9 * 4, vertexBuffer2); + + final ByteBuffer skinByteBuffer = ByteBuffer.allocateDirect(3); + skinByteBuffer.order(ByteOrder.LITTLE_ENDIAN); + skinByteBuffer.put((byte) 34); + skinByteBuffer.put((byte) 35); + skinByteBuffer.put((byte) 36); + skinByteBuffer.clear(); + Gdx.gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, 9 * 4 * 2, 3, skinByteBuffer); + +// this.shaderProgram.setVertexAttribute("a_position", 3, GL20.GL_FLOAT, false, 0, 0); +// this.shaderProgram.enableVertexAttribute("a_position"); +// this.shaderProgram.setVertexAttribute("a_position2", 3, GL20.GL_FLOAT, false, 0, 4 * 9); +// this.shaderProgram.enableVertexAttribute("a_position2"); +// this.shaderProgram.setVertexAttribute("a_boneNumber", 1, GL20.GL_UNSIGNED_BYTE, false, 1, 4 * 9 * 2); +// this.shaderProgram.enableVertexAttribute("a_boneNumber"); +// this.shaderProgram.setUniformi("u_boneMap", 15); + + final ByteBuffer faceByteBuffer = ByteBuffer.allocateDirect(6); + faceByteBuffer.order(ByteOrder.LITTLE_ENDIAN); + this.faceBuffer = faceByteBuffer.asShortBuffer(); + + this.faceBuffer.put(0, (short) 0); + this.faceBuffer.put(1, (short) 1); + this.faceBuffer.put(2, (short) 2); + + Gdx.gl.glBufferData(GL20.GL_ELEMENT_ARRAY_BUFFER, 3 * 2, null, GL20.GL_STATIC_DRAW); + + final int glGetError = Gdx.gl.glGetError(); + System.out.println(glGetError); + + final ByteBuffer vertex3ByteBuffer = ByteBuffer.allocateDirect(4 * 16); + vertex3ByteBuffer.order(ByteOrder.nativeOrder()); + this.vertexTextureBuffer3 = vertex3ByteBuffer.asFloatBuffer(); + + this.vertexTextureBuffer3.put(0, 0.5f); + this.vertexTextureBuffer3.put(1, 0); + this.vertexTextureBuffer3.put(2, 0); + this.vertexTextureBuffer3.put(3, 0); + this.vertexTextureBuffer3.put(4, 0); + this.vertexTextureBuffer3.put(5, 1); + this.vertexTextureBuffer3.put(6, 0); + this.vertexTextureBuffer3.put(7, 0); + this.vertexTextureBuffer3.put(8, 0); + this.vertexTextureBuffer3.put(9, 0); + this.vertexTextureBuffer3.put(10, 1); + this.vertexTextureBuffer3.put(11, 0); + this.vertexTextureBuffer3.put(12, 0.0f); + this.vertexTextureBuffer3.put(13, 0.0f); + this.vertexTextureBuffer3.put(14, 0); + this.vertexTextureBuffer3.put(15, 1); + +// this.vertexTextureBuffer = Gdx.gl.glGenBuffer(); + this.vertexTexture = Gdx.gl.glGenTexture(); + + Gdx.gl.glActiveTexture(GL20.GL_TEXTURE15); +// Gdx.gl.glBindBuffer(GL20.GL_TEXTURE_2D, this.vertexTextureBuffer); +// Gdx.gl.glBindBuffer(GL20.GL_TEXTURE_2D, 0); + + Gdx.gl.glBindTexture(GL20.GL_TEXTURE_2D, this.vertexTexture); + Gdx.gl.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_WRAP_S, GL20.GL_CLAMP_TO_EDGE); + Gdx.gl.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_WRAP_T, GL20.GL_CLAMP_TO_EDGE); + Gdx.gl.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_MIN_FILTER, GL20.GL_NEAREST); + Gdx.gl.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_MAG_FILTER, GL20.GL_NEAREST); + Gdx.gl.glTexImage2D(GL20.GL_TEXTURE_2D, 0, GL30.GL_RGBA, 4, 1, 0, GL30.GL_RGBA, GL20.GL_FLOAT, + this.vertexTextureBuffer3); + } + + @Override + public void render() { + Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT); + +// Gdx.gl30.glBindVertexArray(this.VAO); + this.shaderProgram.begin(); + + Gdx.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.arrayBuffer); + this.shaderProgram.setVertexAttribute("a_position", 3, GL20.GL_FLOAT, false, 0, 0); + this.shaderProgram.enableVertexAttribute("a_position"); + this.shaderProgram.setVertexAttribute("a_position2", 3, GL20.GL_FLOAT, false, 0, 4 * 9); + this.shaderProgram.enableVertexAttribute("a_position2"); + this.shaderProgram.setVertexAttribute("a_boneNumber", 1, GL20.GL_UNSIGNED_BYTE, false, 1, 4 * 9 * 2); + this.shaderProgram.enableVertexAttribute("a_boneNumber"); + + Gdx.gl.glActiveTexture(GL20.GL_TEXTURE15); + Gdx.gl.glBindTexture(GL20.GL_TEXTURE_2D, this.vertexTexture); + Gdx.gl.glTexSubImage2D(GL20.GL_TEXTURE_2D, 0, 0, 0, 4, 1, GL30.GL_RGBA, GL20.GL_FLOAT, + this.vertexTextureBuffer3); + this.shaderProgram.setUniformi("u_boneMap", 15); + this.shaderProgram.setUniformf("u_vectorSize", 1f / 4); + this.shaderProgram.setUniformf("u_rowSize", 1); + + Gdx.gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, this.elementBuffer); + Gdx.gl.glBufferSubData(GL20.GL_ELEMENT_ARRAY_BUFFER, 0, 3 * 2, this.faceBuffer); + Gdx.gl.glDrawElements(GL20.GL_TRIANGLES, 3, GL20.GL_UNSIGNED_SHORT, 0); +// Gdx.gl.glDrawArrays(GL20.GL_TRIANGLES, 0, 3); + this.shaderProgram.end(); + } + + @Override + public void dispose() { + } + + @Override + public void resize(final int width, final int height) { + } + + public static final String boneTexture = ""// + + " uniform samplerBuffer u_boneMap;\r\n" + // + // " uniform uint u_vectorSize;\r\n" + // + " uniform uint u_rowSize;\r\n" + // + " mat4 fetchMatrix(uint column, uint row) {\r\n" + // + // " column *= u_vectorSize * 4.0;\r\n" + // + // " row *= u_rowSize;\r\n" + // + " // Add in half texel to sample in the middle of the texel.\r\n" + // + " // Otherwise, since the sample is directly on the boundry, small floating point errors can cause the sample to get the wrong pixel.\r\n" + + // + " // This is mostly noticable with NPOT textures, which the bone maps are.\r\n" + // + // " column += 0.5 * u_vectorSize;\r\n" + // + // " row += 0.5 * u_rowSize;\r\n" + // + " return mat4(texelFetch(u_boneMap, row * u_rowSize + column * 4),\r\n" + // + " texelFetch(u_boneMap, row * u_rowSize + column * 4 + 1),\r\n" + // + " texelFetch(u_boneMap, row * u_rowSize + column * 4 + 2),\r\n" + // + " texelFetch(u_boneMap, row * u_rowSize + column * 4 + 3);\r\n" + // + " }"; + + public static final String vsSimple = "\r\n" + // + "\r\n" + // + " attribute vec3 a_position;\r\n" + // + " attribute vec3 a_position2;\r\n" + // + " attribute float a_boneNumber;\r\n" + // + " varying float fragNumber;\r\n" + // + Shaders.boneTexture + "\r\n" + // + " void main() {\r\n" + // + " mat4 bone = fetchMatrix(0.0, 0.0);\r\n" + // + " if( a_boneNumber <= 34.5 ) {\r\n" + // + " gl_Position = vec4(a_position2.x * bone[0][0], a_position2.y, a_position2.z, 1.0);\r\n" + // + " } else {\r\n" + // + " gl_Position = vec4(a_position2.x, a_position2.y, a_position2.z, 1.0);\r\n" + // + " }\r\n" + // + " fragNumber = a_boneNumber;\r\n" + // + " }\r\n"; + + public static final String fsSimple = "\r\n" + // + " varying float fragNumber;\r\n" + // + Shaders.boneTexture + "\r\n" + // + " void main() {\r\n" + // + " mat4 bone = fetchMatrix(0.0, 0.0);\r\n" + // + " if( fragNumber > 35.5 ) {\r\n" + // + " gl_FragColor = bone[0];//vec4(1.0, 0.0, 0.0, 1.0);\r\n" + // + " } else if( fragNumber > 34.5 ) {\r\n" + // + " gl_FragColor = bone[1];//vec4(0.0, 1.0, 1.0, 1.0);\r\n" + // + " } else if( fragNumber > 33.5 ) {\r\n" + // + " gl_FragColor = bone[2];//vec4(1.0, 0.0, 1.0, 1.0);\r\n" + // + " } else {\r\n" + // + " gl_FragColor = vec4(fragNumber*100.0, fragNumber, fragNumber, 1.0);\r\n" + // + " }\r\n" + // + " }\r\n"; + private int vertexTexture; + private int vertexTextureBuffer; + private FloatBuffer vertexTextureBuffer3; + +} diff --git a/core/src/com/etheller/warsmash/WarsmashTestGameTextureBuffer2.java b/core/src/com/etheller/warsmash/WarsmashTestGameTextureBuffer2.java new file mode 100644 index 0000000..5a892c6 --- /dev/null +++ b/core/src/com/etheller/warsmash/WarsmashTestGameTextureBuffer2.java @@ -0,0 +1,203 @@ +package com.etheller.warsmash; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; + +import com.badlogic.gdx.ApplicationAdapter; +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.graphics.GL20; +import com.badlogic.gdx.graphics.glutils.ShaderProgram; +import com.etheller.warsmash.viewer5.Shaders; +import com.etheller.warsmash.viewer5.gl.DataTexture; + +public class WarsmashTestGameTextureBuffer2 extends ApplicationAdapter { + private int arrayBuffer; + private int elementBuffer; + private int VAO; + + @Override + public void create() { +// ShaderProgram.pedantic = false; + Gdx.gl.glClearColor(0.5f, 0.5f, 0.5f, 1.0f); // colour to use when clearing + + final ByteBuffer tempByteBuffer = ByteBuffer.allocateDirect(4); + tempByteBuffer.order(ByteOrder.LITTLE_ENDIAN); + final IntBuffer temp = tempByteBuffer.asIntBuffer(); + + Gdx.gl30.glGenVertexArrays(1, temp); + this.VAO = temp.get(0); + + Gdx.gl30.glBindVertexArray(this.VAO); + + System.out.println(vsSimple); + this.shaderProgram = new ShaderProgram(vsSimple, fsSimple); + if (!this.shaderProgram.isCompiled()) { + throw new IllegalStateException(this.shaderProgram.getLog()); + } + + this.arrayBuffer = Gdx.gl.glGenBuffer(); + this.elementBuffer = Gdx.gl.glGenBuffer(); + System.out.println("arrayBuffer: " + this.arrayBuffer + ", elementBuffer: " + this.elementBuffer); + + Gdx.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.arrayBuffer); + + Gdx.gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, this.elementBuffer); + + final ByteBuffer vertexByteBuffer = ByteBuffer.allocateDirect(4 * 9); + vertexByteBuffer.order(ByteOrder.LITTLE_ENDIAN); + this.vertexBuffer = vertexByteBuffer.asFloatBuffer(); + + this.vertexBuffer.put(0, -1f); + this.vertexBuffer.put(1, -1f); + this.vertexBuffer.put(2, 0); + this.vertexBuffer.put(3, 1f); + this.vertexBuffer.put(4, -1f); + this.vertexBuffer.put(5, 0); + this.vertexBuffer.put(6, 0f); + this.vertexBuffer.put(7, 1f); + this.vertexBuffer.put(8, 0); + + Gdx.gl.glBufferData(GL20.GL_ARRAY_BUFFER, ((9 * 4) * 2) + 3, null, GL20.GL_STATIC_DRAW); + Gdx.gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, 0, 9 * 4, this.vertexBuffer); + + final ByteBuffer vertex2ByteBuffer = ByteBuffer.allocateDirect(4 * 9); + vertex2ByteBuffer.order(ByteOrder.LITTLE_ENDIAN); + final FloatBuffer vertexBuffer2 = vertex2ByteBuffer.asFloatBuffer(); + + vertexBuffer2.put(0, -1f); + vertexBuffer2.put(1, -1f); + vertexBuffer2.put(2, 0); + vertexBuffer2.put(3, 1f); + vertexBuffer2.put(4, -1f); + vertexBuffer2.put(5, 0); + vertexBuffer2.put(6, 0f); + vertexBuffer2.put(7, 1f); + vertexBuffer2.put(8, 0); + + Gdx.gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, 9 * 4, 9 * 4, vertexBuffer2); + + final ByteBuffer skinByteBuffer = ByteBuffer.allocateDirect(3); + skinByteBuffer.order(ByteOrder.LITTLE_ENDIAN); + skinByteBuffer.put((byte) 34); + skinByteBuffer.put((byte) 35); + skinByteBuffer.put((byte) 36); + skinByteBuffer.clear(); + Gdx.gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, 9 * 4 * 2, 3, skinByteBuffer); + +// this.shaderProgram.setVertexAttribute("a_position", 3, GL20.GL_FLOAT, false, 0, 0); +// this.shaderProgram.enableVertexAttribute("a_position"); +// this.shaderProgram.setVertexAttribute("a_position2", 3, GL20.GL_FLOAT, false, 0, 4 * 9); +// this.shaderProgram.enableVertexAttribute("a_position2"); +// this.shaderProgram.setVertexAttribute("a_boneNumber", 1, GL20.GL_UNSIGNED_BYTE, false, 1, 4 * 9 * 2); +// this.shaderProgram.enableVertexAttribute("a_boneNumber"); +// this.shaderProgram.setUniformi("u_boneMap", 15); + + final ByteBuffer faceByteBuffer = ByteBuffer.allocateDirect(6); + faceByteBuffer.order(ByteOrder.LITTLE_ENDIAN); + this.faceBuffer = faceByteBuffer.asShortBuffer(); + + this.faceBuffer.put(0, (short) 0); + this.faceBuffer.put(1, (short) 1); + this.faceBuffer.put(2, (short) 2); + + Gdx.gl.glBufferData(GL20.GL_ELEMENT_ARRAY_BUFFER, 3 * 2, null, GL20.GL_STATIC_DRAW); + + final int glGetError = Gdx.gl.glGetError(); + System.out.println(glGetError); + + final ByteBuffer vertex3ByteBuffer = ByteBuffer.allocateDirect(4 * 16); + vertex3ByteBuffer.order(ByteOrder.LITTLE_ENDIAN); + final FloatBuffer vertexBuffer3 = vertex3ByteBuffer.asFloatBuffer(); + + vertexBuffer3.put(0, 1); + vertexBuffer3.put(1, 0); + vertexBuffer3.put(2, 0); + vertexBuffer3.put(3, 0); + vertexBuffer3.put(4, 0); + vertexBuffer3.put(5, 1); + vertexBuffer3.put(6, 0); + vertexBuffer3.put(7, 0); + vertexBuffer3.put(8, 0); + vertexBuffer3.put(9, 0); + vertexBuffer3.put(10, 1); + vertexBuffer3.put(11, 0); + vertexBuffer3.put(12, 0.0f); + vertexBuffer3.put(13, 0.0f); + vertexBuffer3.put(14, 0); + vertexBuffer3.put(15, 1); + + this.dataTexture = new DataTexture(Gdx.gl, 4, 4, 1); + this.dataTexture.bindAndUpdate(vertexBuffer3); + } + + @Override + public void render() { + Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT); + +// Gdx.gl30.glBindVertexArray(this.VAO); + this.shaderProgram.begin(); + + this.dataTexture.bind(15); + + Gdx.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.arrayBuffer); + this.shaderProgram.setVertexAttribute("a_position", 3, GL20.GL_FLOAT, false, 0, 0); + this.shaderProgram.enableVertexAttribute("a_position"); + this.shaderProgram.setVertexAttribute("a_position2", 3, GL20.GL_FLOAT, false, 0, 4 * 9); + this.shaderProgram.enableVertexAttribute("a_position2"); + this.shaderProgram.setVertexAttribute("a_boneNumber", 1, GL20.GL_UNSIGNED_BYTE, false, 1, 4 * 9 * 2); + this.shaderProgram.enableVertexAttribute("a_boneNumber"); + + this.shaderProgram.setUniformi("u_boneMap", 15); + this.shaderProgram.setUniformf("u_vectorSize", 1f / this.dataTexture.getWidth()); + this.shaderProgram.setUniformf("u_rowSize", 1); + + Gdx.gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, this.elementBuffer); + Gdx.gl.glBufferSubData(GL20.GL_ELEMENT_ARRAY_BUFFER, 0, 3 * 2, this.faceBuffer); + Gdx.gl.glDrawElements(GL20.GL_TRIANGLES, 3, GL20.GL_UNSIGNED_SHORT, 0); +// Gdx.gl.glDrawArrays(GL20.GL_TRIANGLES, 0, 3); + this.shaderProgram.end(); + } + + @Override + public void dispose() { + } + + @Override + public void resize(final int width, final int height) { + } + + public static final String vsSimple = "\r\n" + // + "\r\n" + // + " attribute vec3 a_position;\r\n" + // + " attribute vec3 a_position2;\r\n" + // + " attribute float a_boneNumber;\r\n" + // + " varying float fragNumber;\r\n" + // + Shaders.boneTexture + "\r\n" + // + " void main() {\r\n" + // + " mat4 bone = fetchMatrix(0.0, 0.0);\r\n" + // + " gl_Position = bone * vec4(a_position2.x, a_position2.y, a_position2.z, 1.0);\r\n" + // + " fragNumber = a_boneNumber;\r\n" + // + " }\r\n"; + + public static final String fsSimple = "\r\n" + // + " varying float fragNumber;\r\n" + // + " void main() {\r\n" + // + " if( fragNumber > 35.5 ) {\r\n" + // + " gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\r\n" + // + " } else if( fragNumber > 34.5 ) {\r\n" + // + " gl_FragColor = vec4(0.0, 1.0, 1.0, 1.0);\r\n" + // + " } else if( fragNumber > 33.5 ) {\r\n" + // + " gl_FragColor = vec4(1.0, 0.0, 1.0, 1.0);\r\n" + // + " } else {\r\n" + // + " gl_FragColor = vec4(fragNumber*100.0, fragNumber, fragNumber, 1.0);\r\n" + // + " }\r\n" + // + " }\r\n"; + private ShaderProgram shaderProgram; + private FloatBuffer vertexBuffer; + private ShortBuffer faceBuffer; + private DataTexture dataTexture; + +} diff --git a/core/src/com/etheller/warsmash/WarsmashTestMyTextureGame.java b/core/src/com/etheller/warsmash/WarsmashTestMyTextureGame.java new file mode 100644 index 0000000..b7079d2 --- /dev/null +++ b/core/src/com/etheller/warsmash/WarsmashTestMyTextureGame.java @@ -0,0 +1,56 @@ +package com.etheller.warsmash; + +import java.io.IOException; +import java.util.Arrays; + +import javax.imageio.ImageIO; + +import com.badlogic.gdx.ApplicationAdapter; +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.graphics.GL20; +import com.badlogic.gdx.graphics.Texture; +import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.etheller.warsmash.datasources.CompoundDataSourceDescriptor; +import com.etheller.warsmash.datasources.DataSource; +import com.etheller.warsmash.datasources.DataSourceDescriptor; +import com.etheller.warsmash.datasources.FolderDataSourceDescriptor; +import com.etheller.warsmash.util.ImageUtils; + +public class WarsmashTestMyTextureGame extends ApplicationAdapter { + + private DataSource codebase; + private Texture texture; + private SpriteBatch batch; + + @Override + public void create() { + + final FolderDataSourceDescriptor war3mpq = new FolderDataSourceDescriptor("E:\\Backups\\Warcraft\\Data\\127"); + final FolderDataSourceDescriptor testingFolder = new FolderDataSourceDescriptor( + "D:\\NEEDS_ORGANIZING\\MPQBuild\\Test"); + final FolderDataSourceDescriptor currentFolder = new FolderDataSourceDescriptor("."); + this.codebase = new CompoundDataSourceDescriptor( + Arrays.asList(war3mpq, testingFolder, currentFolder)).createDataSource(); + + try { + this.texture = ImageUtils + .getTexture(ImageIO.read(this.codebase.getResourceAsStream("Textures\\Dust3x.blp"))); + } + catch (final IOException e) { + e.printStackTrace(); + } + Gdx.gl.glClearColor(0, 0, 0, 1); + this.batch = new SpriteBatch(); + this.batch.enableBlending(); + } + + @Override + public void render() { + Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT); + + this.batch.begin(); + this.batch.draw(this.texture, 20, 20, 256, 256); + this.batch.end(); + } + +} diff --git a/core/src/com/etheller/warsmash/datasources/CompoundDataSource.java b/core/src/com/etheller/warsmash/datasources/CompoundDataSource.java index 2c1a975..3a33343 100644 --- a/core/src/com/etheller/warsmash/datasources/CompoundDataSource.java +++ b/core/src/com/etheller/warsmash/datasources/CompoundDataSource.java @@ -15,10 +15,10 @@ import java.util.Set; public class CompoundDataSource implements DataSource { private final List mpqList = new ArrayList<>(); - public CompoundDataSource(final List dataSourceDescriptors) { - if (dataSourceDescriptors != null) { - for (final DataSourceDescriptor descriptor : dataSourceDescriptors) { - this.mpqList.add(descriptor.createDataSource()); + public CompoundDataSource(final List dataSources) { + if (dataSources != null) { + for (final DataSource dataSource : dataSources) { + this.mpqList.add(dataSource); } } } diff --git a/core/src/com/etheller/warsmash/datasources/CompoundDataSourceDescriptor.java b/core/src/com/etheller/warsmash/datasources/CompoundDataSourceDescriptor.java new file mode 100644 index 0000000..0c1a957 --- /dev/null +++ b/core/src/com/etheller/warsmash/datasources/CompoundDataSourceDescriptor.java @@ -0,0 +1,27 @@ +package com.etheller.warsmash.datasources; + +import java.util.ArrayList; +import java.util.List; + +public class CompoundDataSourceDescriptor implements DataSourceDescriptor { + private final List dataSourceDescriptors; + + public CompoundDataSourceDescriptor(final List dataSourceDescriptors) { + this.dataSourceDescriptors = dataSourceDescriptors; + } + + @Override + public DataSource createDataSource() { + final List dataSources = new ArrayList<>(); + for (final DataSourceDescriptor descriptor : this.dataSourceDescriptors) { + dataSources.add(descriptor.createDataSource()); + } + return new CompoundDataSource(dataSources); + } + + @Override + public String getDisplayName() { + return "CompoundDataSourceDescriptor"; + } + +} diff --git a/core/src/com/etheller/warsmash/datasources/MpqDataSource.java b/core/src/com/etheller/warsmash/datasources/MpqDataSource.java new file mode 100644 index 0000000..2d168d0 --- /dev/null +++ b/core/src/com/etheller/warsmash/datasources/MpqDataSource.java @@ -0,0 +1,143 @@ +package com.etheller.warsmash.datasources; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.channels.Channels; +import java.nio.channels.SeekableByteChannel; +import java.nio.file.Files; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import mpq.ArchivedFile; +import mpq.ArchivedFileExtractor; +import mpq.ArchivedFileStream; +import mpq.HashLookup; +import mpq.MPQArchive; +import mpq.MPQException; + +public class MpqDataSource implements DataSource { + + private final MPQArchive archive; + private final SeekableByteChannel inputChannel; + private final ArchivedFileExtractor extractor = new ArchivedFileExtractor(); + + public MpqDataSource(final MPQArchive archive, final SeekableByteChannel inputChannel) { + this.archive = archive; + this.inputChannel = inputChannel; + } + + public MPQArchive getArchive() { + return this.archive; + } + + public SeekableByteChannel getInputChannel() { + return this.inputChannel; + } + + @Override + public InputStream getResourceAsStream(final String filepath) throws IOException { + ArchivedFile file = null; + try { + file = this.archive.lookupHash2(new HashLookup(filepath)); + } + catch (final MPQException exc) { + if (exc.getMessage().equals("lookup not found")) { + return null; + } + else { + throw new IOException(exc); + } + } + final ArchivedFileStream stream = new ArchivedFileStream(this.inputChannel, this.extractor, file); + final InputStream newInputStream = Channels.newInputStream(stream); + return newInputStream; + } + + @Override + public File getFile(final String filepath) throws IOException { + // TODO Auto-generated method stub + // System.out.println("getting it from the outside: " + + // filepath); + ArchivedFile file = null; + try { + file = this.archive.lookupHash2(new HashLookup(filepath)); + } + catch (final MPQException exc) { + if (exc.getMessage().equals("lookup not found")) { + return null; + } + else { + throw new IOException(exc); + } + } + final ArchivedFileStream stream = new ArchivedFileStream(this.inputChannel, this.extractor, file); + final InputStream newInputStream = Channels.newInputStream(stream); + String tmpdir = System.getProperty("java.io.tmpdir"); + if (!tmpdir.endsWith(File.separator)) { + tmpdir += File.separator; + } + final String tempDir = tmpdir + "RMSExtract/"; + final File tempProduct = new File(tempDir + filepath.replace('\\', File.separatorChar)); + tempProduct.delete(); + tempProduct.getParentFile().mkdirs(); + Files.copy(newInputStream, tempProduct.toPath()); + tempProduct.deleteOnExit(); + return tempProduct; + } + + @Override + public boolean has(final String filepath) { + try { + this.archive.lookupPath(filepath); + return true; + } + catch (final MPQException exc) { + if (exc.getMessage().equals("lookup not found")) { + return false; + } + else { + throw new RuntimeException(exc); + } + } + } + + @Override + public Collection getListfile() { + try { + final Set listfile = new HashSet<>(); + ArchivedFile listfileContents; + listfileContents = this.archive.lookupHash2(new HashLookup("(listfile)")); + final ArchivedFileStream stream = new ArchivedFileStream(this.inputChannel, this.extractor, + listfileContents); + final InputStream newInputStream = Channels.newInputStream(stream); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(newInputStream))) { + String line; + while ((line = reader.readLine()) != null) { + listfile.add(line); + } + } + catch (final IOException exc) { + throw new RuntimeException(exc); + } + return listfile; + } + catch (final MPQException exc) { + if (exc.getMessage().equals("lookup not found")) { + return null; + } + else { + throw new RuntimeException(exc); + } + } + } + + @Override + public void close() throws IOException { + this.inputChannel.close(); + } + +} diff --git a/core/src/com/etheller/warsmash/datasources/MpqDataSourceDescriptor.java b/core/src/com/etheller/warsmash/datasources/MpqDataSourceDescriptor.java new file mode 100644 index 0000000..ce168e2 --- /dev/null +++ b/core/src/com/etheller/warsmash/datasources/MpqDataSourceDescriptor.java @@ -0,0 +1,75 @@ +package com.etheller.warsmash.datasources; + +import java.io.IOException; +import java.nio.channels.SeekableByteChannel; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.EnumSet; + +import mpq.MPQArchive; +import mpq.MPQException; + +public class MpqDataSourceDescriptor implements DataSourceDescriptor { + /** + * Generated serial id + */ + private static final long serialVersionUID = 8424254987711783598L; + private final String mpqFilePath; + + public MpqDataSourceDescriptor(final String mpqFilePath) { + this.mpqFilePath = mpqFilePath; + } + + @Override + public DataSource createDataSource() { + try { + SeekableByteChannel sbc; + sbc = Files.newByteChannel(Paths.get(this.mpqFilePath), EnumSet.of(StandardOpenOption.READ)); + return new MpqDataSource(new MPQArchive(sbc), sbc); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + catch (final MPQException e) { + throw new RuntimeException(e); + } + } + + @Override + public String getDisplayName() { + return "MPQ Archive: " + this.mpqFilePath; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = (prime * result) + ((this.mpqFilePath == null) ? 0 : this.mpqFilePath.hashCode()); + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final MpqDataSourceDescriptor other = (MpqDataSourceDescriptor) obj; + if (this.mpqFilePath == null) { + if (other.mpqFilePath != null) { + return false; + } + } + else if (!this.mpqFilePath.equals(other.mpqFilePath)) { + return false; + } + return true; + } + +} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/Bone.java b/core/src/com/etheller/warsmash/parsers/mdlx/Bone.java index ff69b00..a1fbb31 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/Bone.java +++ b/core/src/com/etheller/warsmash/parsers/mdlx/Bone.java @@ -88,4 +88,8 @@ public class Bone extends GenericObject { public int getGeosetAnimationId() { return this.geosetAnimationId; } + + public int getGeosetId() { + return this.geosetId; + } } diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/Sequence.java b/core/src/com/etheller/warsmash/parsers/mdlx/Sequence.java index a2811ce..cfa34d6 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/Sequence.java +++ b/core/src/com/etheller/warsmash/parsers/mdlx/Sequence.java @@ -109,4 +109,24 @@ public class Sequence implements MdlxBlock { public int getFlags() { return this.flags; } + + public String getName() { + return this.name; + } + + public float getRarity() { + return this.rarity; + } + + public float getMoveSpeed() { + return this.moveSpeed; + } + + public long getSyncPoint() { + return this.syncPoint; + } + + public Extent getExtent() { + return this.extent; + } } diff --git a/core/src/com/etheller/warsmash/parsers/w3x/War3Map.java b/core/src/com/etheller/warsmash/parsers/w3x/War3Map.java new file mode 100644 index 0000000..f1e1182 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/w3x/War3Map.java @@ -0,0 +1,125 @@ +package com.etheller.warsmash.parsers.w3x; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.channels.SeekableByteChannel; +import java.util.Arrays; +import java.util.Collection; + +import org.apache.commons.compress.utils.IOUtils; +import org.apache.commons.compress.utils.SeekableInMemoryByteChannel; + +import com.etheller.warsmash.datasources.CompoundDataSource; +import com.etheller.warsmash.datasources.DataSource; +import com.etheller.warsmash.datasources.MpqDataSource; +import com.etheller.warsmash.parsers.w3x.doo.War3MapDoo; +import com.etheller.warsmash.parsers.w3x.objectdata.Warcraft3MapObjectData; +import com.etheller.warsmash.parsers.w3x.unitsdoo.War3MapUnitsDoo; +import com.etheller.warsmash.parsers.w3x.w3e.War3MapW3e; +import com.etheller.warsmash.parsers.w3x.w3i.War3MapW3i; +import com.google.common.io.LittleEndianDataInputStream; + +import mpq.MPQArchive; +import mpq.MPQException; + +/** + * Warcraft 3 map (W3X and W3M). + */ +public class War3Map implements DataSource { + + private CompoundDataSource dataSource; + private MpqDataSource internalMpqContentsDataSource; + + public War3Map(final DataSource dataSource, final String mapFileName) { + try { + // Slightly complex. Here's the theory: + // 1.) Copy map into RAM + // 2.) Setup a Data Source that will read assets + // from either the map or the game, giving the map priority. + SeekableByteChannel sbc; + try (InputStream mapStream = dataSource.getResourceAsStream(mapFileName)) { + final byte[] mapData = IOUtils.toByteArray(mapStream); + sbc = new SeekableInMemoryByteChannel(mapData); + this.internalMpqContentsDataSource = new MpqDataSource(new MPQArchive(sbc), sbc); + this.dataSource = new CompoundDataSource(Arrays.asList(dataSource, this.internalMpqContentsDataSource)); + } + } + catch (final IOException e) { + throw new RuntimeException(e); + } + catch (final MPQException e) { + throw new RuntimeException(e); + } + } + + public War3MapW3i readMapInformation() throws IOException { + War3MapW3i mapInfo; + try (LittleEndianDataInputStream stream = new LittleEndianDataInputStream( + this.dataSource.getResourceAsStream("war3map.w3i"))) { + mapInfo = new War3MapW3i(stream); + } + return mapInfo; + } + + public War3MapW3e readEnvironment() throws IOException { + War3MapW3e environment; + try (LittleEndianDataInputStream stream = new LittleEndianDataInputStream( + this.dataSource.getResourceAsStream("war3map.w3e"))) { + environment = new War3MapW3e(stream); + } + return environment; + } + + public War3MapDoo readDoodads() throws IOException { + War3MapDoo doodadsFile; + try (LittleEndianDataInputStream stream = new LittleEndianDataInputStream( + this.dataSource.getResourceAsStream("war3map.doo"))) { + doodadsFile = new War3MapDoo(stream); + } + return doodadsFile; + } + + public War3MapUnitsDoo readUnits() throws IOException { + War3MapUnitsDoo unitsFile; + try (LittleEndianDataInputStream stream = new LittleEndianDataInputStream( + this.dataSource.getResourceAsStream("war3mapUnits.doo"))) { + unitsFile = new War3MapUnitsDoo(stream); + } + return unitsFile; + } + + public Warcraft3MapObjectData readModifications() throws IOException { + final Warcraft3MapObjectData changes = Warcraft3MapObjectData.load(this.dataSource, true); + return changes; + } + + @Override + public InputStream getResourceAsStream(final String filepath) throws IOException { + return this.dataSource.getResourceAsStream(filepath); + } + + @Override + public File getFile(final String filepath) throws IOException { + return this.dataSource.getFile(filepath); + } + + @Override + public boolean has(final String filepath) { + return this.dataSource.has(filepath); + } + + @Override + public Collection getListfile() { + return this.internalMpqContentsDataSource.getListfile(); + } + + @Override + public void close() throws IOException { + this.dataSource.close(); + } + + public CompoundDataSource getCompoundDataSource() { + return this.dataSource; + } +} diff --git a/core/src/com/etheller/warsmash/parsers/w3x/doo/Doodad.java b/core/src/com/etheller/warsmash/parsers/w3x/doo/Doodad.java new file mode 100644 index 0000000..913e388 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/w3x/doo/Doodad.java @@ -0,0 +1,157 @@ +package com.etheller.warsmash.parsers.w3x.doo; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import com.etheller.warsmash.util.ParseUtils; +import com.etheller.warsmash.util.War3ID; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +public class Doodad { + private War3ID id; + private int variation; + private final float[] location = new float[3]; + private float angle; + private final float[] scale = { 1f, 1f, 1f }; + private short flags; // short to store unsigned byte, java problem + private short life; // short to store unsigned byte, java problem + private long itemTable = -1; // long to store unsigned int32, java problem + private final List itemSets = new ArrayList<>(); + private int editorId; + private final short[] u1 = new short[8]; // short to store unsigned byte, java problem + + public void load(final LittleEndianDataInputStream stream, final int version) throws IOException { + this.id = ParseUtils.readWar3ID(stream); + this.variation = stream.readInt(); + ParseUtils.readFloatArray(stream, this.location); + this.angle = stream.readFloat(); + ParseUtils.readFloatArray(stream, this.scale); + this.flags = ParseUtils.readUInt8(stream); + this.life = ParseUtils.readUInt8(stream); + + if (version > 7) { + this.itemTable = ParseUtils.readUInt32(stream); + + for (long i = 0, l = ParseUtils.readUInt32(stream); i < l; i++) { + final RandomItemSet itemSet = new RandomItemSet(); + + itemSet.load(stream); + + this.itemSets.add(itemSet); + } + } + + this.editorId = stream.readInt(); + } + + public void save(final LittleEndianDataOutputStream stream, final int version) throws IOException { + ParseUtils.writeWar3ID(stream, this.id); + ; + stream.writeInt(this.variation); + ParseUtils.writeFloatArray(stream, this.location); + stream.writeFloat(this.angle); + ParseUtils.writeFloatArray(stream, this.scale); + ParseUtils.writeUInt8(stream, this.flags); + ParseUtils.writeUInt8(stream, this.life); + + if (version > 7) { + ParseUtils.writeUInt32(stream, this.itemTable); + ParseUtils.writeUInt32(stream, this.itemSets.size()); + + for (final RandomItemSet itemSet : this.itemSets) { + itemSet.save(stream); + } + } + + stream.writeInt(this.editorId); + } + + public int getByteLength(final int version) { + int size = 42; + + if (version > 7) { + size += 8; + + for (final RandomItemSet itemSet : this.itemSets) { + size += itemSet.getByteLength(); + } + } + + return size; + } + + public War3ID getId() { + return this.id; + } + + public void setId(final War3ID id) { + this.id = id; + } + + public int getVariation() { + return this.variation; + } + + public void setVariation(final int variation) { + this.variation = variation; + } + + public float getAngle() { + return this.angle; + } + + public void setAngle(final float angle) { + this.angle = angle; + } + + public short getFlags() { + return this.flags; + } + + public void setFlags(final short flags) { + this.flags = flags; + } + + public short getLife() { + return this.life; + } + + public void setLife(final short life) { + this.life = life; + } + + public long getItemTable() { + return this.itemTable; + } + + public void setItemTable(final long itemTable) { + this.itemTable = itemTable; + } + + public int getEditorId() { + return this.editorId; + } + + public void setEditorId(final int editorId) { + this.editorId = editorId; + } + + public float[] getLocation() { + return this.location; + } + + public float[] getScale() { + return this.scale; + } + + public List getItemSets() { + return this.itemSets; + } + + public short[] getU1() { + return this.u1; + } + +} diff --git a/core/src/com/etheller/warsmash/parsers/w3x/doo/RandomItem.java b/core/src/com/etheller/warsmash/parsers/w3x/doo/RandomItem.java new file mode 100644 index 0000000..26fd3c2 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/w3x/doo/RandomItem.java @@ -0,0 +1,23 @@ +package com.etheller.warsmash.parsers.w3x.doo; + +import java.io.IOException; + +import com.etheller.warsmash.util.ParseUtils; +import com.etheller.warsmash.util.War3ID; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +public class RandomItem { + private War3ID id; + private int chance; + + public void load(final LittleEndianDataInputStream stream) throws IOException { + this.id = ParseUtils.readWar3ID(stream); + this.chance = stream.readInt(); + } + + public void save(final LittleEndianDataOutputStream stream) throws IOException { + ParseUtils.writeWar3ID(stream, this.id); + stream.writeInt(this.chance); + } +} diff --git a/core/src/com/etheller/warsmash/parsers/w3x/doo/RandomItemSet.java b/core/src/com/etheller/warsmash/parsers/w3x/doo/RandomItemSet.java new file mode 100644 index 0000000..f115b8e --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/w3x/doo/RandomItemSet.java @@ -0,0 +1,34 @@ +package com.etheller.warsmash.parsers.w3x.doo; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import com.etheller.warsmash.util.ParseUtils; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +public class RandomItemSet { + private final List items = new ArrayList<>(); + + public void load(final LittleEndianDataInputStream stream) throws IOException { + for (long i = 0, l = ParseUtils.readUInt32(stream); i < l; i++) { + final RandomItem item = new RandomItem(); + + item.load(stream); + + this.items.add(item); + } + } + + public void save(final LittleEndianDataOutputStream stream) throws IOException { + ParseUtils.writeUInt32(stream, this.items.size()); + for (final RandomItem item : this.items) { + item.save(stream); + } + } + + public int getByteLength() { + return 4 + (this.items.size() * 8); + } +} diff --git a/core/src/com/etheller/warsmash/parsers/w3x/doo/TerrainDoodad.java b/core/src/com/etheller/warsmash/parsers/w3x/doo/TerrainDoodad.java new file mode 100644 index 0000000..5390166 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/w3x/doo/TerrainDoodad.java @@ -0,0 +1,54 @@ +package com.etheller.warsmash.parsers.w3x.doo; + +import java.io.IOException; + +import com.etheller.warsmash.util.ParseUtils; +import com.etheller.warsmash.util.War3ID; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +/** + * A terrain doodad. + * + * This type of doodad works much like cliffs. It uses the height of the + * terrain, and gets affected by the ground heightmap. It cannot be manipulated + * in any way in the World Editor once placed. Indeed, the only way to change it + * is to remove it by changing cliffs around it. + */ +public class TerrainDoodad { + private War3ID id; + private long u1; + private final long[] location = new long[2]; + + public void load(final LittleEndianDataInputStream stream, final int version) throws IOException { + this.id = ParseUtils.readWar3ID(stream); + this.u1 = ParseUtils.readUInt32(stream); + ParseUtils.readUInt32Array(stream, this.location); + } + + public void save(final LittleEndianDataOutputStream stream, final int version) throws IOException { + ParseUtils.writeWar3ID(stream, this.id); + ParseUtils.writeUInt32(stream, this.u1); + ParseUtils.writeUInt32Array(stream, this.location); + } + + public War3ID getId() { + return this.id; + } + + public long getU1() { + return this.u1; + } + + public long[] getLocation() { + return this.location; + } + + public void setId(final War3ID id) { + this.id = id; + } + + public void setU1(final long u1) { + this.u1 = u1; + } +} diff --git a/core/src/com/etheller/warsmash/parsers/w3x/doo/War3MapDoo.java b/core/src/com/etheller/warsmash/parsers/w3x/doo/War3MapDoo.java new file mode 100644 index 0000000..828fca2 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/w3x/doo/War3MapDoo.java @@ -0,0 +1,108 @@ +package com.etheller.warsmash.parsers.w3x.doo; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import com.etheller.warsmash.util.ParseUtils; +import com.etheller.warsmash.util.War3ID; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +/** + * war3map.doo - the doodad and destructible file. + */ +public class War3MapDoo { + private static final War3ID MAGIC_NUMBER = War3ID.fromString("W3do"); + private int version = 0; + private final short[] u1 = new short[4]; + private final List doodads = new ArrayList<>(); + private final short[] u2 = new short[4]; + private final List terrainDoodads = new ArrayList<>(); + + public War3MapDoo(final LittleEndianDataInputStream stream) throws IOException { + if (stream != null) { + this.load(stream); + } + } + + private boolean load(final LittleEndianDataInputStream stream) throws IOException { + final War3ID firstId = ParseUtils.readWar3ID(stream); + if (!MAGIC_NUMBER.equals(firstId)) { + return false; + } + + this.version = stream.readInt(); + ParseUtils.readUInt8Array(stream, this.u1); + + for (int i = 0, l = stream.readInt(); i < l; i++) { + final Doodad doodad = new Doodad(); + + doodad.load(stream, this.version); + + this.doodads.add(doodad); + } + + ParseUtils.readUInt8Array(stream, this.u2); + + for (int i = 0, l = stream.readInt(); i < l; i++) { + final TerrainDoodad terrainDoodad = new TerrainDoodad(); + + terrainDoodad.load(stream, this.version); + + this.terrainDoodads.add(terrainDoodad); + } + + return true; + } + + public void save(final LittleEndianDataOutputStream stream) throws IOException { + + ParseUtils.writeWar3ID(stream, MAGIC_NUMBER); + stream.writeInt(this.version); + ParseUtils.writeUInt8Array(stream, this.u1); + ParseUtils.writeUInt32(stream, this.doodads.size()); + + for (final Doodad doodad : this.doodads) { + doodad.save(stream, this.version); + } + + ParseUtils.writeUInt8Array(stream, this.u2); + ParseUtils.writeUInt32(stream, this.terrainDoodads.size()); + + for (final TerrainDoodad terrainDoodad : this.terrainDoodads) { + terrainDoodad.save(stream, this.version); + } + } + + public int getByteLength() { + int size = 24 + (this.terrainDoodads.size() * 16); + + for (final Doodad doodad : this.doodads) { + size += doodad.getByteLength(this.version); + } + + return size; + } + + public int getVersion() { + return this.version; + } + + public short[] getU1() { + return this.u1; + } + + public List getDoodads() { + return this.doodads; + } + + public short[] getU2() { + return this.u2; + } + + public List getTerrainDoodads() { + return this.terrainDoodads; + } + +} diff --git a/core/src/com/etheller/warsmash/parsers/w3x/objectdata/Warcraft3MapObjectData.java b/core/src/com/etheller/warsmash/parsers/w3x/objectdata/Warcraft3MapObjectData.java new file mode 100644 index 0000000..fa288ba --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/w3x/objectdata/Warcraft3MapObjectData.java @@ -0,0 +1,166 @@ +package com.etheller.warsmash.parsers.w3x.objectdata; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.etheller.warsmash.datasources.DataSource; +import com.etheller.warsmash.units.DataTable; +import com.etheller.warsmash.units.StandardObjectData; +import com.etheller.warsmash.units.StandardObjectData.WarcraftData; +import com.etheller.warsmash.units.custom.WTSFile; +import com.etheller.warsmash.units.custom.War3ObjectDataChangeset; +import com.etheller.warsmash.units.manager.MutableObjectData; +import com.etheller.warsmash.units.manager.MutableObjectData.WorldEditorDataType; +import com.etheller.warsmash.util.WorldEditStrings; +import com.google.common.io.LittleEndianDataInputStream; + +public final class Warcraft3MapObjectData { + private final MutableObjectData units; + private final MutableObjectData items; + private final MutableObjectData destructibles; + private final MutableObjectData doodads; + private final MutableObjectData abilities; + private final MutableObjectData buffs; + private final MutableObjectData upgrades; + private final List datas; + private transient Map typeToData = new HashMap<>(); + + public Warcraft3MapObjectData(final MutableObjectData units, final MutableObjectData items, + final MutableObjectData destructibles, final MutableObjectData doodads, final MutableObjectData abilities, + final MutableObjectData buffs, final MutableObjectData upgrades) { + this.units = units; + this.items = items; + this.destructibles = destructibles; + this.doodads = doodads; + this.abilities = abilities; + this.buffs = buffs; + this.upgrades = upgrades; + this.datas = new ArrayList<>(); + this.datas.add(units); + this.datas.add(items); + this.datas.add(destructibles); + this.datas.add(doodads); + this.datas.add(abilities); + this.datas.add(buffs); + this.datas.add(upgrades); + for (final MutableObjectData data : this.datas) { + this.typeToData.put(data.getWorldEditorDataType(), data); + } + } + + public MutableObjectData getDataByType(final WorldEditorDataType type) { + return this.typeToData.get(type); + } + + public MutableObjectData getUnits() { + return this.units; + } + + public MutableObjectData getItems() { + return this.items; + } + + public MutableObjectData getDestructibles() { + return this.destructibles; + } + + public MutableObjectData getDoodads() { + return this.doodads; + } + + public MutableObjectData getAbilities() { + return this.abilities; + } + + public MutableObjectData getBuffs() { + return this.buffs; + } + + public MutableObjectData getUpgrades() { + return this.upgrades; + } + + public List getDatas() { + return this.datas; + } + + public static Warcraft3MapObjectData load(final DataSource dataSource, final boolean inlineWTS) throws IOException { + + final StandardObjectData standardObjectData = new StandardObjectData(dataSource); + final WarcraftData standardUnits = standardObjectData.getStandardUnits(); + final WarcraftData standardItems = standardObjectData.getStandardItems(); + final WarcraftData standardDoodads = standardObjectData.getStandardDoodads(); + final WarcraftData standardDestructables = standardObjectData.getStandardDestructables(); + final WarcraftData abilities = standardObjectData.getStandardAbilities(); + final WarcraftData standardAbilityBuffs = standardObjectData.getStandardAbilityBuffs(); + final WarcraftData standardUpgrades = standardObjectData.getStandardUpgrades(); + + final DataTable standardUnitMeta = standardObjectData.getStandardUnitMeta(); + final DataTable standardDoodadMeta = standardObjectData.getStandardDoodadMeta(); + final DataTable standardDestructableMeta = standardObjectData.getStandardDestructableMeta(); + final DataTable abilityMeta = standardObjectData.getStandardAbilityMeta(); + final DataTable standardAbilityBuffMeta = standardObjectData.getStandardAbilityBuffMeta(); + final DataTable standardUpgradeMeta = standardObjectData.getStandardUpgradeMeta(); + + final War3ObjectDataChangeset unitChangeset = new War3ObjectDataChangeset('u'); + final War3ObjectDataChangeset itemChangeset = new War3ObjectDataChangeset('t'); + final War3ObjectDataChangeset doodadChangeset = new War3ObjectDataChangeset('d'); + final War3ObjectDataChangeset destructableChangeset = new War3ObjectDataChangeset('b'); + final War3ObjectDataChangeset abilityChangeset = new War3ObjectDataChangeset('a'); + final War3ObjectDataChangeset buffChangeset = new War3ObjectDataChangeset('h'); + final War3ObjectDataChangeset upgradeChangeset = new War3ObjectDataChangeset('q'); + + final WTSFile wts = new WTSFile(dataSource.getResourceAsStream("war3map.wts")); + if (dataSource.has("war3map.w3u")) { + unitChangeset.load(new LittleEndianDataInputStream(dataSource.getResourceAsStream("war3map.w3u")), wts, + inlineWTS); + } + if (dataSource.has("war3map.w3t")) { + itemChangeset.load(new LittleEndianDataInputStream(dataSource.getResourceAsStream("war3map.w3t")), wts, + inlineWTS); + } + if (dataSource.has("war3map.w3d")) { + doodadChangeset.load(new LittleEndianDataInputStream(dataSource.getResourceAsStream("war3map.w3d")), wts, + inlineWTS); + } + if (dataSource.has("war3map.w3b")) { + destructableChangeset.load(new LittleEndianDataInputStream(dataSource.getResourceAsStream("war3map.w3b")), + wts, inlineWTS); + } + if (dataSource.has("war3map.w3a")) { + abilityChangeset.load(new LittleEndianDataInputStream(dataSource.getResourceAsStream("war3map.w3a")), wts, + inlineWTS); + } + if (dataSource.has("war3map.w3h")) { + buffChangeset.load(new LittleEndianDataInputStream(dataSource.getResourceAsStream("war3map.w3h")), wts, + inlineWTS); + } + if (dataSource.has("war3map.w3q")) { + upgradeChangeset.load(new LittleEndianDataInputStream(dataSource.getResourceAsStream("war3map.w3q")), wts, + inlineWTS); + } + + final WorldEditStrings worldEditStrings = standardObjectData.getWorldEditStrings(); + final MutableObjectData unitData = new MutableObjectData(worldEditStrings, WorldEditorDataType.UNITS, + standardUnits, standardUnitMeta, unitChangeset); + final MutableObjectData itemData = new MutableObjectData(worldEditStrings, WorldEditorDataType.ITEM, + standardItems, standardUnitMeta, itemChangeset); + final MutableObjectData doodadData = new MutableObjectData(worldEditStrings, WorldEditorDataType.DOODADS, + standardDoodads, standardDoodadMeta, doodadChangeset); + final MutableObjectData destructableData = new MutableObjectData(worldEditStrings, + WorldEditorDataType.DESTRUCTIBLES, standardDestructables, standardDestructableMeta, + destructableChangeset); + final MutableObjectData abilityData = new MutableObjectData(worldEditStrings, WorldEditorDataType.ABILITIES, + abilities, abilityMeta, abilityChangeset); + final MutableObjectData buffData = new MutableObjectData(worldEditStrings, WorldEditorDataType.BUFFS_EFFECTS, + standardAbilityBuffs, standardAbilityBuffMeta, buffChangeset); + final MutableObjectData upgradeData = new MutableObjectData(worldEditStrings, WorldEditorDataType.UPGRADES, + standardUpgrades, standardUpgradeMeta, upgradeChangeset); + + return new Warcraft3MapObjectData(unitData, itemData, destructableData, doodadData, abilityData, buffData, + upgradeData); + } +} \ No newline at end of file diff --git a/core/src/com/etheller/warsmash/parsers/w3x/unitsdoo/DroppedItem.java b/core/src/com/etheller/warsmash/parsers/w3x/unitsdoo/DroppedItem.java new file mode 100644 index 0000000..28dfa43 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/w3x/unitsdoo/DroppedItem.java @@ -0,0 +1,26 @@ +package com.etheller.warsmash.parsers.w3x.unitsdoo; + +import java.io.IOException; + +import com.etheller.warsmash.util.ParseUtils; +import com.etheller.warsmash.util.War3ID; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +/** + * A dropped item. + */ +public class DroppedItem { + private War3ID id; + private int chance; + + public void load(final LittleEndianDataInputStream stream) throws IOException { + this.id = ParseUtils.readWar3ID(stream); + this.chance = stream.readInt(); + } + + public void save(final LittleEndianDataOutputStream stream) throws IOException { + ParseUtils.writeWar3ID(stream, this.id); + stream.writeInt(this.chance); + } +} diff --git a/core/src/com/etheller/warsmash/parsers/w3x/unitsdoo/DroppedItemSet.java b/core/src/com/etheller/warsmash/parsers/w3x/unitsdoo/DroppedItemSet.java new file mode 100644 index 0000000..d70b57f --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/w3x/unitsdoo/DroppedItemSet.java @@ -0,0 +1,38 @@ +package com.etheller.warsmash.parsers.w3x.unitsdoo; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import com.etheller.warsmash.util.ParseUtils; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +/** + * A dropped item set. + */ +public class DroppedItemSet { + private final List items = new ArrayList<>(); + + public void load(final LittleEndianDataInputStream stream) throws IOException { + for (long i = 0, l = ParseUtils.readUInt32(stream); i < l; i++) { + final DroppedItem item = new DroppedItem(); + + item.load(stream); + + this.items.add(item); + } + } + + public void save(final LittleEndianDataOutputStream stream) throws IOException { + ParseUtils.writeUInt32(stream, this.items.size()); + + for (final DroppedItem item : this.items) { + item.save(stream); + } + } + + public int getByteLength() { + return 4 + (this.items.size() * 8); + } +} diff --git a/core/src/com/etheller/warsmash/parsers/w3x/unitsdoo/InventoryItem.java b/core/src/com/etheller/warsmash/parsers/w3x/unitsdoo/InventoryItem.java new file mode 100644 index 0000000..7912c25 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/w3x/unitsdoo/InventoryItem.java @@ -0,0 +1,26 @@ +package com.etheller.warsmash.parsers.w3x.unitsdoo; + +import java.io.IOException; + +import com.etheller.warsmash.util.ParseUtils; +import com.etheller.warsmash.util.War3ID; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +/** + * An inventory item. + */ +public class InventoryItem { + private int slot; + private War3ID id; + + public void load(final LittleEndianDataInputStream stream) throws IOException { + this.slot = stream.readInt(); + this.id = ParseUtils.readWar3ID(stream); + } + + public void save(final LittleEndianDataOutputStream stream) throws IOException { + stream.writeInt(this.slot); + ParseUtils.writeWar3ID(stream, this.id); + } +} diff --git a/core/src/com/etheller/warsmash/parsers/w3x/unitsdoo/ModifiedAbility.java b/core/src/com/etheller/warsmash/parsers/w3x/unitsdoo/ModifiedAbility.java new file mode 100644 index 0000000..1a89ca5 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/w3x/unitsdoo/ModifiedAbility.java @@ -0,0 +1,26 @@ +package com.etheller.warsmash.parsers.w3x.unitsdoo; + +import java.io.IOException; + +import com.etheller.warsmash.util.ParseUtils; +import com.etheller.warsmash.util.War3ID; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +public class ModifiedAbility { + private War3ID id; + private int activeForAutocast = 0; + private int heroLevel = 1; + + public void load(final LittleEndianDataInputStream stream) throws IOException { + this.id = ParseUtils.readWar3ID(stream); + this.activeForAutocast = stream.readInt(); + this.heroLevel = stream.readInt(); + } + + public void save(final LittleEndianDataOutputStream stream) throws IOException { + ParseUtils.writeWar3ID(stream, this.id); + stream.writeInt(this.activeForAutocast); + stream.writeInt(this.heroLevel); + } +} diff --git a/core/src/com/etheller/warsmash/parsers/w3x/unitsdoo/RandomUnit.java b/core/src/com/etheller/warsmash/parsers/w3x/unitsdoo/RandomUnit.java new file mode 100644 index 0000000..ef0852e --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/w3x/unitsdoo/RandomUnit.java @@ -0,0 +1,23 @@ +package com.etheller.warsmash.parsers.w3x.unitsdoo; + +import java.io.IOException; + +import com.etheller.warsmash.util.ParseUtils; +import com.etheller.warsmash.util.War3ID; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +public class RandomUnit { + private War3ID id; + private int chance; + + public void load(final LittleEndianDataInputStream stream) throws IOException { + this.id = ParseUtils.readWar3ID(stream); + this.chance = stream.readInt(); + } + + public void save(final LittleEndianDataOutputStream stream) throws IOException { + ParseUtils.writeWar3ID(stream, this.id); + stream.writeInt(this.chance); + } +} diff --git a/core/src/com/etheller/warsmash/parsers/w3x/unitsdoo/Unit.java b/core/src/com/etheller/warsmash/parsers/w3x/unitsdoo/Unit.java new file mode 100644 index 0000000..1ce7c1b --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/w3x/unitsdoo/Unit.java @@ -0,0 +1,428 @@ +package com.etheller.warsmash.parsers.w3x.unitsdoo; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import com.etheller.warsmash.util.ParseUtils; +import com.etheller.warsmash.util.War3ID; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +public class Unit { + private War3ID id; + private int variation; + private final float[] location = new float[3]; + private float angle; + private final float[] scale = new float[3]; + private short flags; + private int player; + private int unknown; + private int hitpoints = -1; + private int mana = -1; + /** + * @since 8 + */ + private int droppedItemTable = 0; + private final List droppedItemSets = new ArrayList<>(); + private int goldAmount; + private float targetAcquisition; + private int heroLevel; + /** + * @since 8 + */ + private int heroStrength; + /** + * @since 8 + */ + private int heroAgility; + /** + * @since 8 + */ + private int heroIntelligence; + private final List itemsInInventory = new ArrayList<>(); + private final List modifiedAbilities = new ArrayList<>(); + private int randomFlag; + private final short[] level = new short[3]; + private short itemClass; + private long unitGroup; + private long positionInGroup; + private final List randomUnitTables = new ArrayList<>(); + private int customTeamColor; + private int waygate; + private int creationNumber; + + public void load(final LittleEndianDataInputStream stream, final int version) throws IOException { + this.id = ParseUtils.readWar3ID(stream); + this.variation = stream.readInt(); + ParseUtils.readFloatArray(stream, this.location); + this.angle = stream.readFloat(); + ParseUtils.readFloatArray(stream, this.scale); + this.flags = ParseUtils.readUInt8(stream); + this.player = stream.readInt(); + this.unknown = ParseUtils.readUInt16(stream); + this.hitpoints = stream.readInt(); + this.mana = stream.readInt(); + + if (version > 7) { + this.droppedItemTable = stream.readInt(); + } + + for (int i = 0, l = stream.readInt(); i < l; i++) { + final DroppedItemSet set = new DroppedItemSet(); + + set.load(stream); + + this.droppedItemSets.add(set); + } + + this.goldAmount = stream.readInt(); + this.targetAcquisition = stream.readFloat(); + this.heroLevel = stream.readInt(); + + if (version > 7) { + this.heroStrength = stream.readInt(); + this.heroAgility = stream.readInt(); + this.heroIntelligence = stream.readInt(); + } + + for (int i = 0, l = stream.readInt(); i < l; i++) { + final InventoryItem item = new InventoryItem(); + + item.load(stream); + + this.itemsInInventory.add(item); + } + + for (int i = 0, l = stream.readInt(); i < l; i++) { + final ModifiedAbility modifiedAbility = new ModifiedAbility(); + + modifiedAbility.load(stream); + + this.modifiedAbilities.add(modifiedAbility); + } + + this.randomFlag = stream.readInt(); + + if (this.randomFlag == 0) { + ParseUtils.readUInt8Array(stream, this.level); + this.itemClass = ParseUtils.readUInt8(stream); + } + else if (this.randomFlag == 1) { + this.unitGroup = ParseUtils.readUInt32(stream); + this.positionInGroup = ParseUtils.readUInt32(stream); + } + else if (this.randomFlag == 2) { + for (int i = 0, l = stream.readInt(); i < l; i++) { + final RandomUnit randomUnit = new RandomUnit(); + + randomUnit.load(stream); + + this.randomUnitTables.add(randomUnit); + } + } + + this.customTeamColor = stream.readInt(); + this.waygate = stream.readInt(); + this.creationNumber = stream.readInt(); + } + + public void save(final LittleEndianDataOutputStream stream, final int version) throws IOException { + ParseUtils.writeWar3ID(stream, this.id); + stream.writeInt(this.variation); + ParseUtils.writeFloatArray(stream, this.location); + stream.writeFloat(this.angle); + ParseUtils.writeFloatArray(stream, this.scale); + ParseUtils.writeUInt8(stream, this.flags); + stream.writeInt(this.player); + ParseUtils.writeUInt16(stream, this.unknown); + stream.writeInt(this.hitpoints); + stream.writeInt(this.mana); + + if (version > 7) { + stream.writeInt(this.droppedItemTable); + } + + stream.writeInt(this.droppedItemSets.size()); + + for (final DroppedItemSet droppedItemSet : this.droppedItemSets) { + droppedItemSet.save(stream); + } + + stream.writeInt(this.goldAmount); + stream.writeFloat(this.targetAcquisition); + stream.writeInt(this.heroLevel); + + if (version > 7) { + stream.writeInt(this.heroStrength); + stream.writeInt(this.heroAgility); + stream.writeInt(this.heroIntelligence); + } + + stream.writeInt(this.itemsInInventory.size()); + + for (final InventoryItem itemInInventory : this.itemsInInventory) { + itemInInventory.save(stream); + } + + stream.writeInt(this.modifiedAbilities.size()); + + for (final ModifiedAbility modifiedAbility : this.modifiedAbilities) { + modifiedAbility.save(stream); + } + + stream.writeInt(this.randomFlag); + + if (this.randomFlag == 0) { + ParseUtils.writeUInt8Array(stream, this.level); + ParseUtils.writeUInt8(stream, this.itemClass); + } + else if (this.randomFlag == 1) { + ParseUtils.writeUInt32(stream, this.unitGroup); + ParseUtils.writeUInt32(stream, this.positionInGroup); + } + else if (this.randomFlag == 2) { + stream.writeInt(this.randomUnitTables.size()); + + for (final RandomUnit randomUnitTable : this.randomUnitTables) { + randomUnitTable.save(stream); + } + } + + stream.writeInt(this.customTeamColor); + stream.writeInt(this.waygate); + stream.writeInt(this.creationNumber); + } + + public int getByteLength(final int version) { + int size = 91; + + if (version > 7) { + size += 16; + } + + for (final DroppedItemSet droppedItemSet : this.droppedItemSets) { + size += droppedItemSet.getByteLength(); + } + + size += this.itemsInInventory.size() * 8; + + size += this.modifiedAbilities.size() * 12; + + if (this.randomFlag == 0) { + size += 4; + } + else if (this.randomFlag == 1) { + size += 8; + } + else if (this.randomFlag == 2) { + size += 4 + (this.randomUnitTables.size() * 8); + } + + return size; + } + + public War3ID getId() { + return this.id; + } + + public int getVariation() { + return this.variation; + } + + public float[] getLocation() { + return this.location; + } + + public float getAngle() { + return this.angle; + } + + public float[] getScale() { + return this.scale; + } + + public short getFlags() { + return this.flags; + } + + public int getPlayer() { + return this.player; + } + + public int getUnknown() { + return this.unknown; + } + + public int getHitpoints() { + return this.hitpoints; + } + + public int getMana() { + return this.mana; + } + + public int getDroppedItemTable() { + return this.droppedItemTable; + } + + public List getDroppedItemSets() { + return this.droppedItemSets; + } + + public int getGoldAmount() { + return this.goldAmount; + } + + public float getTargetAcquisition() { + return this.targetAcquisition; + } + + public int getHeroLevel() { + return this.heroLevel; + } + + public int getHeroStrength() { + return this.heroStrength; + } + + public int getHeroAgility() { + return this.heroAgility; + } + + public int getHeroIntelligence() { + return this.heroIntelligence; + } + + public List getItemsInInventory() { + return this.itemsInInventory; + } + + public List getModifiedAbilities() { + return this.modifiedAbilities; + } + + public int getRandomFlag() { + return this.randomFlag; + } + + public short[] getLevel() { + return this.level; + } + + public short getItemClass() { + return this.itemClass; + } + + public long getUnitGroup() { + return this.unitGroup; + } + + public long getPositionInGroup() { + return this.positionInGroup; + } + + public List getRandomUnitTables() { + return this.randomUnitTables; + } + + public int getCustomTeamColor() { + return this.customTeamColor; + } + + public int getWaygate() { + return this.waygate; + } + + public int getCreationNumber() { + return this.creationNumber; + } + + public void setId(final War3ID id) { + this.id = id; + } + + public void setVariation(final int variation) { + this.variation = variation; + } + + public void setAngle(final float angle) { + this.angle = angle; + } + + public void setFlags(final short flags) { + this.flags = flags; + } + + public void setPlayer(final int player) { + this.player = player; + } + + public void setUnknown(final int unknown) { + this.unknown = unknown; + } + + public void setHitpoints(final int hitpoints) { + this.hitpoints = hitpoints; + } + + public void setMana(final int mana) { + this.mana = mana; + } + + public void setDroppedItemTable(final int droppedItemTable) { + this.droppedItemTable = droppedItemTable; + } + + public void setGoldAmount(final int goldAmount) { + this.goldAmount = goldAmount; + } + + public void setTargetAcquisition(final float targetAcquisition) { + this.targetAcquisition = targetAcquisition; + } + + public void setHeroLevel(final int heroLevel) { + this.heroLevel = heroLevel; + } + + public void setHeroStrength(final int heroStrength) { + this.heroStrength = heroStrength; + } + + public void setHeroAgility(final int heroAgility) { + this.heroAgility = heroAgility; + } + + public void setHeroIntelligence(final int heroIntelligence) { + this.heroIntelligence = heroIntelligence; + } + + public void setRandomFlag(final int randomFlag) { + this.randomFlag = randomFlag; + } + + public void setItemClass(final short itemClass) { + this.itemClass = itemClass; + } + + public void setUnitGroup(final long unitGroup) { + this.unitGroup = unitGroup; + } + + public void setPositionInGroup(final long positionInGroup) { + this.positionInGroup = positionInGroup; + } + + public void setCustomTeamColor(final int customTeamColor) { + this.customTeamColor = customTeamColor; + } + + public void setWaygate(final int waygate) { + this.waygate = waygate; + } + + public void setCreationNumber(final int creationNumber) { + this.creationNumber = creationNumber; + } +} diff --git a/core/src/com/etheller/warsmash/parsers/w3x/unitsdoo/War3MapUnitsDoo.java b/core/src/com/etheller/warsmash/parsers/w3x/unitsdoo/War3MapUnitsDoo.java new file mode 100644 index 0000000..d8f6bfe --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/w3x/unitsdoo/War3MapUnitsDoo.java @@ -0,0 +1,76 @@ +package com.etheller.warsmash.parsers.w3x.unitsdoo; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import com.etheller.warsmash.util.ParseUtils; +import com.etheller.warsmash.util.War3ID; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +public class War3MapUnitsDoo { + private static final War3ID MAGIC_NUMBER = War3ID.fromString("W3do"); + private int version = 8; + private long unknown = 11; + private final List units = new ArrayList<>(); + + public War3MapUnitsDoo(final LittleEndianDataInputStream stream) throws IOException { + if (stream != null) { + this.load(stream); + } + } + + private boolean load(final LittleEndianDataInputStream stream) throws IOException { + final War3ID firstId = ParseUtils.readWar3ID(stream); + if (!MAGIC_NUMBER.equals(firstId)) { + return false; + } + + this.version = stream.readInt(); + this.unknown = ParseUtils.readUInt32(stream); + + for (int i = 0, l = stream.readInt(); i < l; i++) { + final Unit unit = new Unit(); + + unit.load(stream, this.version); + + this.units.add(unit); + } + + return true; + } + + public void save(final LittleEndianDataOutputStream stream) throws IOException { + ParseUtils.writeWar3ID(stream, MAGIC_NUMBER); + stream.writeInt(this.version); + ParseUtils.writeUInt32(stream, this.unknown); + stream.writeInt(this.units.size()); + + for (final Unit unit : this.units) { + unit.save(stream, this.version); + } + } + + public int getByteLength() { + int size = 16; + + for (final Unit unit : this.units) { + size += unit.getByteLength(this.version); + } + + return size; + } + + public List getUnits() { + return this.units; + } + + public int getVersion() { + return this.version; + } + + public void setVersion(final int version) { + this.version = version; + } +} diff --git a/core/src/com/etheller/warsmash/parsers/w3x/w3e/Corner.java b/core/src/com/etheller/warsmash/parsers/w3x/w3e/Corner.java new file mode 100644 index 0000000..ea65ac4 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/w3x/w3e/Corner.java @@ -0,0 +1,110 @@ +package com.etheller.warsmash.parsers.w3x.w3e; + +import java.io.IOException; + +import com.etheller.warsmash.util.ParseUtils; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +/** + * A tile corner. + */ +public class Corner { + private int groundHeight; + private int waterHeight; + private int mapEdge; + private int ramp; + private int blight; + private int water; + private int boundary; + private int groundTexture; + private int cliffVariation; + private int groundVariation; + private int cliffTexture; + private int layerHeight; + + public void load(final LittleEndianDataInputStream stream) throws IOException { + this.groundHeight = (stream.readShort() - 8192) / 512; + + final short waterAndEdge = stream.readShort(); + this.waterHeight = ((waterAndEdge & 0x3FFF) - 8192) / 512; + this.mapEdge = waterAndEdge & 0x4000; + + final short textureAndFlags = ParseUtils.readUInt8(stream); + + this.ramp = textureAndFlags & 0b00010000; + this.blight = textureAndFlags & 0b00100000; + this.water = textureAndFlags & 0b01000000; + this.boundary = textureAndFlags & 0b10000000; + + this.groundTexture = textureAndFlags & 0b00001111; + + final short variation = ParseUtils.readUInt8(stream); + + this.cliffVariation = (variation & 0b11100000) >>> 5; + this.groundVariation = variation & 0b00011111; + + final short cliffTextureAndLayer = ParseUtils.readUInt8(stream); + + this.cliffTexture = (cliffTextureAndLayer & 0b11110000) >>> 4; + this.layerHeight = cliffTextureAndLayer & 0b00001111; + + } + + public void save(final LittleEndianDataOutputStream stream) throws IOException { + stream.writeShort((this.groundHeight * 512) + 8192); + stream.writeShort((this.waterHeight + 8192 + this.mapEdge) << 14); + ParseUtils.writeUInt8(stream, (short) ((this.ramp << 4) | (this.blight << 5) | (this.water << 6) + | (this.boundary << 7) | this.groundTexture)); + ParseUtils.writeUInt8(stream, (short) ((this.cliffVariation << 5) | this.groundVariation)); + ParseUtils.writeUInt8(stream, (short) ((this.cliffTexture << 4) + this.layerHeight)); + } + + public int getGroundHeight() { + return this.groundHeight; + } + + public int getWaterHeight() { + return this.waterHeight; + } + + public int getMapEdge() { + return this.mapEdge; + } + + public int getRamp() { + return this.ramp; + } + + public int getBlight() { + return this.blight; + } + + public int getWater() { + return this.water; + } + + public int getBoundary() { + return this.boundary; + } + + public int getGroundTexture() { + return this.groundTexture; + } + + public int getCliffVariation() { + return this.cliffVariation; + } + + public int getGroundVariation() { + return this.groundVariation; + } + + public int getCliffTexture() { + return this.cliffTexture; + } + + public int getLayerHeight() { + return this.layerHeight; + } +} diff --git a/core/src/com/etheller/warsmash/parsers/w3x/w3e/War3MapW3e.java b/core/src/com/etheller/warsmash/parsers/w3x/w3e/War3MapW3e.java new file mode 100644 index 0000000..8494df7 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/w3x/w3e/War3MapW3e.java @@ -0,0 +1,148 @@ +package com.etheller.warsmash.parsers.w3x.w3e; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import com.etheller.warsmash.util.ParseUtils; +import com.etheller.warsmash.util.War3ID; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +/** + * war3map.w3e - the environment file. + */ +public class War3MapW3e { + private static final War3ID MAGIC_NUMBER = War3ID.fromString("W3E!"); + private int version; + private char tileset = 'A'; + private int hasCustomTileset; + private final List groundTiles = new ArrayList<>(); + private final List cliffTiles = new ArrayList<>(); + private final int[] mapSize = new int[2]; + private final float[] centerOffset = new float[2]; + private Corner[][] corners; + + public War3MapW3e(final LittleEndianDataInputStream stream) throws IOException { + if (stream != null) { + this.load(stream); + } + } + + private boolean load(final LittleEndianDataInputStream stream) throws IOException { + final War3ID firstId = ParseUtils.readWar3ID(stream); + if (!MAGIC_NUMBER.equals(firstId)) { + return false; + } + + this.version = stream.readInt(); + this.tileset = (char) stream.read(); + this.hasCustomTileset = stream.readInt(); + + for (int i = 0, l = stream.readInt(); i < l; i++) { + this.groundTiles.add(ParseUtils.readWar3ID(stream)); + } + + for (int i = 0, l = stream.readInt(); i < l; i++) { + this.cliffTiles.add(ParseUtils.readWar3ID(stream)); + } + + ParseUtils.readInt32Array(stream, this.mapSize); + ParseUtils.readFloatArray(stream, this.centerOffset); + + this.corners = new Corner[this.mapSize[1]][]; + for (int row = 0, rows = this.mapSize[1]; row < rows; row++) { + this.corners[row] = new Corner[this.mapSize[0]]; + + for (int column = 0, columns = this.mapSize[0]; column < columns; column++) { + final Corner corner = new Corner(); + + corner.load(stream); + + this.corners[row][column] = corner; + } + } + + return true; + } + + public void save(final LittleEndianDataOutputStream stream) throws IOException { + ParseUtils.writeWar3ID(stream, MAGIC_NUMBER); + stream.writeInt(this.version); + stream.write(this.tileset); + stream.writeInt(this.hasCustomTileset); + ParseUtils.writeUInt32(stream, this.groundTiles.size()); + + for (final War3ID groundTile : this.groundTiles) { + ParseUtils.writeWar3ID(stream, groundTile); + } + + ParseUtils.writeUInt32(stream, this.cliffTiles.size()); + + for (final War3ID cliffTile : this.cliffTiles) { + ParseUtils.writeWar3ID(stream, cliffTile); + } + + ParseUtils.writeInt32Array(stream, this.mapSize); + ParseUtils.writeFloatArray(stream, this.centerOffset); + + for (final Corner[] row : this.corners) { + for (final Corner corner : row) { + corner.save(stream); + } + } + } + + public int getByteLength() { + return 37 + (this.groundTiles.size() * 4) + (this.cliffTiles.size() * 4) + + (this.mapSize[0] * this.mapSize[1] * 7); + } + + public int getVersion() { + return this.version; + } + + public char getTileset() { + return this.tileset; + } + + public int getHasCustomTileset() { + return this.hasCustomTileset; + } + + public List getGroundTiles() { + return this.groundTiles; + } + + public List getCliffTiles() { + return this.cliffTiles; + } + + public int[] getMapSize() { + return this.mapSize; + } + + public float[] getCenterOffset() { + return this.centerOffset; + } + + public Corner[][] getCorners() { + return this.corners; + } + + public void setVersion(final int version) { + this.version = version; + } + + public void setTileset(final char tileset) { + this.tileset = tileset; + } + + public void setHasCustomTileset(final int hasCustomTileset) { + this.hasCustomTileset = hasCustomTileset; + } + + public void setCorners(final Corner[][] corners) { + this.corners = corners; + } +} diff --git a/core/src/com/etheller/warsmash/parsers/w3x/w3i/Force.java b/core/src/com/etheller/warsmash/parsers/w3x/w3i/Force.java new file mode 100644 index 0000000..0d3f8dd --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/w3x/w3i/Force.java @@ -0,0 +1,29 @@ +package com.etheller.warsmash.parsers.w3x.w3i; + +import java.io.IOException; + +import com.etheller.warsmash.util.ParseUtils; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +public class Force { + private long flags; + private long playerMasks; + private String name; + + public void load(final LittleEndianDataInputStream stream) throws IOException { + this.flags = ParseUtils.readUInt32(stream); + this.playerMasks = ParseUtils.readUInt32(stream); + this.name = ParseUtils.readUntilNull(stream); + } + + public void save(final LittleEndianDataOutputStream stream) throws IOException { + ParseUtils.writeUInt32(stream, this.flags); + ParseUtils.writeUInt32(stream, this.playerMasks); + ParseUtils.writeWithNullTerminator(stream, this.name); + } + + public int getByteLength() { + return 9 + this.name.length(); + } +} diff --git a/core/src/com/etheller/warsmash/parsers/w3x/w3i/Player.java b/core/src/com/etheller/warsmash/parsers/w3x/w3i/Player.java new file mode 100644 index 0000000..91b8465 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/w3x/w3i/Player.java @@ -0,0 +1,48 @@ +package com.etheller.warsmash.parsers.w3x.w3i; + +import java.io.IOException; + +import com.etheller.warsmash.util.ParseUtils; +import com.etheller.warsmash.util.War3ID; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +/** + * A player. + */ +public class Player { + private War3ID id; + private int type; + private int race; + private int isFixedStartPosition; + private String name; + private final float[] startLocation = new float[2]; + private long allyLowPriorities; + private long allyHighPriorities; + + public void load(final LittleEndianDataInputStream stream) throws IOException { + this.id = ParseUtils.readWar3ID(stream); + this.type = stream.readInt(); + this.race = stream.readInt(); + this.isFixedStartPosition = stream.readInt(); + this.name = ParseUtils.readUntilNull(stream); + ParseUtils.readFloatArray(stream, this.startLocation); + this.allyLowPriorities = ParseUtils.readUInt32(stream); + this.allyHighPriorities = ParseUtils.readUInt32(stream); + } + + public void save(final LittleEndianDataOutputStream stream) throws IOException { + ParseUtils.writeWar3ID(stream, this.id); + stream.writeInt(this.type); + stream.writeInt(this.race); + stream.writeInt(this.isFixedStartPosition); + ParseUtils.writeWithNullTerminator(stream, this.name); + ParseUtils.writeFloatArray(stream, this.startLocation); + ParseUtils.writeUInt32(stream, this.allyLowPriorities); + ParseUtils.writeUInt32(stream, this.allyHighPriorities); + } + + public int getByteLength() { + return 33 + this.name.length(); + } +} diff --git a/core/src/com/etheller/warsmash/parsers/w3x/w3i/RandomItem.java b/core/src/com/etheller/warsmash/parsers/w3x/w3i/RandomItem.java new file mode 100644 index 0000000..cd332ef --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/w3x/w3i/RandomItem.java @@ -0,0 +1,39 @@ +package com.etheller.warsmash.parsers.w3x.w3i; + +import java.io.IOException; + +import com.etheller.warsmash.util.ParseUtils; +import com.etheller.warsmash.util.War3ID; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +public class RandomItem { + private int chance; + private War3ID id; + + public void load(final LittleEndianDataInputStream stream) throws IOException { + this.chance = stream.readInt(); + this.id = ParseUtils.readWar3ID(stream); + } + + public void save(final LittleEndianDataOutputStream stream) throws IOException { + stream.writeInt(this.chance); + ParseUtils.writeWar3ID(stream, this.id); + } + + public int getChance() { + return this.chance; + } + + public War3ID getId() { + return this.id; + } + + public void setChance(final int chance) { + this.chance = chance; + } + + public void setId(final War3ID id) { + this.id = id; + } +} diff --git a/core/src/com/etheller/warsmash/parsers/w3x/w3i/RandomItemSet.java b/core/src/com/etheller/warsmash/parsers/w3x/w3i/RandomItemSet.java new file mode 100644 index 0000000..4b3e2ea --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/w3x/w3i/RandomItemSet.java @@ -0,0 +1,39 @@ +package com.etheller.warsmash.parsers.w3x.w3i; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import com.etheller.warsmash.util.ParseUtils; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +public class RandomItemSet { + private final List items = new ArrayList<>(); + + public void load(final LittleEndianDataInputStream stream) throws IOException { + for (long i = 0, l = ParseUtils.readUInt32(stream); i < l; i++) { + final RandomItem item = new RandomItem(); + + item.load(stream); + + this.items.add(item); + } + } + + public void save(final LittleEndianDataOutputStream stream) throws IOException { + ParseUtils.writeUInt32(stream, this.items.size()); + + for (final RandomItem item : this.items) { + item.save(stream); + } + } + + public int getByteLength() { + return 4 + (this.items.size() * 8); + } + + public List getItems() { + return this.items; + } +} diff --git a/core/src/com/etheller/warsmash/parsers/w3x/w3i/RandomItemTable.java b/core/src/com/etheller/warsmash/parsers/w3x/w3i/RandomItemTable.java new file mode 100644 index 0000000..634ff0c --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/w3x/w3i/RandomItemTable.java @@ -0,0 +1,69 @@ +package com.etheller.warsmash.parsers.w3x.w3i; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import com.etheller.warsmash.util.ParseUtils; +import com.etheller.warsmash.util.War3ID; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +public class RandomItemTable { + private War3ID id; + private String name; + private final List sets = new ArrayList<>(); + + public void load(final LittleEndianDataInputStream stream) throws IOException { + this.id = ParseUtils.readWar3ID(stream); + this.name = ParseUtils.readUntilNull(stream); + + for (long i = 0, l = ParseUtils.readUInt32(stream); i < l; i++) { + final RandomItemSet set = new RandomItemSet(); + + set.load(stream); + + this.sets.add(set); + } + } + + public void save(final LittleEndianDataOutputStream stream) throws IOException { + ParseUtils.writeWar3ID(stream, this.id); + ParseUtils.writeWithNullTerminator(stream, this.name); + ParseUtils.writeUInt32(stream, this.sets.size()); + + for (final RandomItemSet set : this.sets) { + set.save(stream); + } + } + + public int getByteLength() { + int size = 9 + this.name.length(); + + for (final RandomItemSet set : this.sets) { + size += set.getByteLength(); + } + + return size; + } + + public War3ID getId() { + return this.id; + } + + public String getName() { + return this.name; + } + + public List getSets() { + return this.sets; + } + + public void setId(final War3ID id) { + this.id = id; + } + + public void setName(final String name) { + this.name = name; + } +} diff --git a/core/src/com/etheller/warsmash/parsers/w3x/w3i/RandomUnit.java b/core/src/com/etheller/warsmash/parsers/w3x/w3i/RandomUnit.java new file mode 100644 index 0000000..5ad694d --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/w3x/w3i/RandomUnit.java @@ -0,0 +1,31 @@ +package com.etheller.warsmash.parsers.w3x.w3i; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import com.etheller.warsmash.util.ParseUtils; +import com.etheller.warsmash.util.War3ID; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +public class RandomUnit { + private int chance; + private final List ids = new ArrayList<>(); + + public void load(final LittleEndianDataInputStream stream, final int positions) throws IOException { + this.chance = stream.readInt(); + + for (int i = 0; i < positions; i++) { + this.ids.add(ParseUtils.readWar3ID(stream)); + } + } + + public void save(final LittleEndianDataOutputStream stream) throws IOException { + stream.writeInt(this.chance); + + for (final War3ID id : this.ids) { + ParseUtils.writeWar3ID(stream, id); + } + } +} diff --git a/core/src/com/etheller/warsmash/parsers/w3x/w3i/RandomUnitTable.java b/core/src/com/etheller/warsmash/parsers/w3x/w3i/RandomUnitTable.java new file mode 100644 index 0000000..3295681 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/w3x/w3i/RandomUnitTable.java @@ -0,0 +1,48 @@ +package com.etheller.warsmash.parsers.w3x.w3i; + +import java.io.IOException; +import java.util.List; + +import com.etheller.warsmash.util.ParseUtils; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +public class RandomUnitTable { + private int id; + private String name; + private int positions; + private int[] columnTypes; + private List units; + + public void load(final LittleEndianDataInputStream stream) throws IOException { + this.id = stream.readInt(); // TODO is this a War3ID? + this.name = ParseUtils.readUntilNull(stream); + this.positions = stream.readInt(); + this.columnTypes = ParseUtils.readInt32Array(stream, this.positions); + + for (long i = 0, l = ParseUtils.readUInt32(stream); i < l; i++) { + final RandomUnit unit = new RandomUnit(); + + unit.load(stream, this.positions); + + this.units.add(unit); + } + } + + public void save(final LittleEndianDataOutputStream stream) throws IOException { + stream.writeInt(this.id); + ParseUtils.writeWithNullTerminator(stream, this.name); + stream.writeInt(this.positions); + ParseUtils.writeInt32Array(stream, this.columnTypes); + ParseUtils.writeUInt32(stream, this.units.size()); + + for (final RandomUnit unit : this.units) { + unit.save(stream); + } + } + + public int getByteLength() { + return 13 + this.name.length() + (this.columnTypes.length * 4) + + (this.units.size() * (4 + (4 * this.positions))); + } +} diff --git a/core/src/com/etheller/warsmash/parsers/w3x/w3i/TechAvailabilityChange.java b/core/src/com/etheller/warsmash/parsers/w3x/w3i/TechAvailabilityChange.java new file mode 100644 index 0000000..9c37f84 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/w3x/w3i/TechAvailabilityChange.java @@ -0,0 +1,24 @@ +package com.etheller.warsmash.parsers.w3x.w3i; + +import java.io.IOException; + +import com.etheller.warsmash.util.ParseUtils; +import com.etheller.warsmash.util.War3ID; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +public class TechAvailabilityChange { + private long playerFlags; + private War3ID id; + + public void load(final LittleEndianDataInputStream stream) throws IOException { + this.playerFlags = ParseUtils.readUInt32(stream); + this.id = ParseUtils.readWar3ID(stream); + } + + public void save(final LittleEndianDataOutputStream stream) throws IOException { + ParseUtils.writeUInt32(stream, this.playerFlags); + ParseUtils.writeWar3ID(stream, this.id); + } + +} diff --git a/core/src/com/etheller/warsmash/parsers/w3x/w3i/UpgradeAvailabilityChange.java b/core/src/com/etheller/warsmash/parsers/w3x/w3i/UpgradeAvailabilityChange.java new file mode 100644 index 0000000..ed922c7 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/w3x/w3i/UpgradeAvailabilityChange.java @@ -0,0 +1,30 @@ +package com.etheller.warsmash.parsers.w3x.w3i; + +import java.io.IOException; + +import com.etheller.warsmash.util.ParseUtils; +import com.etheller.warsmash.util.War3ID; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +public class UpgradeAvailabilityChange { + private long playerFlags; + private War3ID id; + private int levelAffected; + private int availability; + + public void load(final LittleEndianDataInputStream stream) throws IOException { + this.playerFlags = ParseUtils.readUInt32(stream); + this.id = ParseUtils.readWar3ID(stream); + this.levelAffected = stream.readInt(); + this.availability = stream.readInt(); + } + + public void save(final LittleEndianDataOutputStream stream) throws IOException { + ParseUtils.writeUInt32(stream, this.playerFlags); + ParseUtils.writeWar3ID(stream, this.id); + stream.writeInt(this.levelAffected); + stream.writeInt(this.availability); + } + +} diff --git a/core/src/com/etheller/warsmash/parsers/w3x/w3i/War3MapW3i.java b/core/src/com/etheller/warsmash/parsers/w3x/w3i/War3MapW3i.java new file mode 100644 index 0000000..266c4e1 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/w3x/w3i/War3MapW3i.java @@ -0,0 +1,440 @@ +package com.etheller.warsmash.parsers.w3x.w3i; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import com.etheller.warsmash.util.ParseUtils; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +/** + * war3map.w3i - the general map information file. + */ +public class War3MapW3i { + private int version; + private int saves; + private int editorVersion; + private final short[] unknown1 = new short[16]; + private String name; + private String author; + private String description; + private String recommendedPlayers; + private final float[] cameraBounds = new float[8]; + private final int[] cameraBoundsComplements = new int[4]; + private final int[] playableSize = new int[2]; + private long flags; + private char tileset = 'A'; + private int campaignBackground; + private String loadingScreenModel; + private String loadingScreenText; + private String loadingScreenTitle; + private String loadingScreenSubtitle; + private int loadingScreen; + private String prologueScreenModel; + private String prologueScreenText; + private String prologueScreenTitle; + private String prologueScreenSubtitle; + private int useTerrainFog; + private final float[] fogHeight = new float[2]; + private float fogDensity; + private final short[] fogColor = new short[4]; + private int globalWeather; + private String soundEnvironment; + private char lightEnvironmentTileset; + private final short[] waterVertexColor = new short[4]; + private final short[] unknown2 = new short[4]; + private final List players = new ArrayList<>(); + private final List forces = new ArrayList<>(); + private final List upgradeAvailabilityChanges = new ArrayList<>(); + private final List techAvailabilityChanges = new ArrayList<>(); + private final List randomUnitTables = new ArrayList<>(); + private final List randomItemTables = new ArrayList<>(); + + public War3MapW3i(final LittleEndianDataInputStream stream) throws IOException { + if (stream != null) { + load(stream); + } + } + + private void load(final LittleEndianDataInputStream stream) throws IOException { + this.version = stream.readInt(); + this.saves = stream.readInt(); + this.editorVersion = stream.readInt(); + + if (this.version > 27) { + ParseUtils.readUInt8Array(stream, this.unknown1); + } + + this.name = ParseUtils.readUntilNull(stream); + this.author = ParseUtils.readUntilNull(stream); + this.description = ParseUtils.readUntilNull(stream); + this.recommendedPlayers = ParseUtils.readUntilNull(stream); + ParseUtils.readFloatArray(stream, this.cameraBounds); + ParseUtils.readInt32Array(stream, this.cameraBoundsComplements); + ParseUtils.readInt32Array(stream, this.playableSize); + this.flags = ParseUtils.readUInt32(stream); + this.tileset = (char) stream.read(); + this.campaignBackground = stream.readInt(); + + if (this.version > 24) { + this.loadingScreenModel = ParseUtils.readUntilNull(stream); + } + + this.loadingScreenText = ParseUtils.readUntilNull(stream); + this.loadingScreenTitle = ParseUtils.readUntilNull(stream); + this.loadingScreenSubtitle = ParseUtils.readUntilNull(stream); + this.loadingScreen = stream.readInt(); + + if (this.version > 24) { + this.prologueScreenModel = ParseUtils.readUntilNull(stream); + } + + this.prologueScreenText = ParseUtils.readUntilNull(stream); + this.prologueScreenTitle = ParseUtils.readUntilNull(stream); + this.prologueScreenSubtitle = ParseUtils.readUntilNull(stream); + + if (this.version > 24) { + this.useTerrainFog = stream.readInt(); + ParseUtils.readFloatArray(stream, this.fogHeight); + this.fogDensity = stream.readFloat(); + ParseUtils.readUInt8Array(stream, this.fogColor); + this.globalWeather = stream.readInt(); // TODO probably war3id, right? + this.soundEnvironment = ParseUtils.readUntilNull(stream); + this.lightEnvironmentTileset = (char) stream.read(); + ParseUtils.readUInt8Array(stream, this.waterVertexColor); + } + + if (this.version > 27) { + ParseUtils.readUInt8Array(stream, this.unknown2); + } + + for (int i = 0, l = stream.readInt(); i < l; i++) { + final Player player = new Player(); + + player.load(stream); + + this.players.add(player); + } + + for (int i = 0, l = stream.readInt(); i < l; i++) { + final Force force = new Force(); + + force.load(stream); + + this.forces.add(force); + } + + for (int i = 0, l = stream.readInt(); i < l; i++) { + final UpgradeAvailabilityChange upgradeAvailabilityChange = new UpgradeAvailabilityChange(); + + upgradeAvailabilityChange.load(stream); + + this.upgradeAvailabilityChanges.add(upgradeAvailabilityChange); + } + + for (int i = 0, l = stream.readInt(); i < l; i++) { + final TechAvailabilityChange techAvailabilityChange = new TechAvailabilityChange(); + + techAvailabilityChange.load(stream); + + this.techAvailabilityChanges.add(techAvailabilityChange); + } + + for (int i = 0, l = stream.readInt(); i < l; i++) { + final RandomUnitTable randomUnitTable = new RandomUnitTable(); + + randomUnitTable.load(stream); + + this.randomUnitTables.add(randomUnitTable); + } + + if (this.version > 24) { + for (int i = 0, l = stream.readInt(); i < l; i++) { + final RandomItemTable randomItemTable = new RandomItemTable(); + + randomItemTable.load(stream); + + this.randomItemTables.add(randomItemTable); + } + } + } + + public void save(final LittleEndianDataOutputStream stream) throws IOException { + stream.writeInt(this.version); + stream.writeInt(this.saves); + stream.writeInt(this.editorVersion); + + if (this.version > 27) { + ParseUtils.writeUInt8Array(stream, this.unknown1); + } + + ParseUtils.writeWithNullTerminator(stream, this.name); + ParseUtils.writeWithNullTerminator(stream, this.author); + ParseUtils.writeWithNullTerminator(stream, this.description); + ParseUtils.writeWithNullTerminator(stream, this.recommendedPlayers); + ParseUtils.writeFloatArray(stream, this.cameraBounds); + ParseUtils.writeInt32Array(stream, this.cameraBoundsComplements); + ParseUtils.writeInt32Array(stream, this.playableSize); + ParseUtils.writeUInt32(stream, this.flags); + stream.write((byte) this.tileset); + stream.writeInt(this.campaignBackground); + + if (this.version > 24) { + ParseUtils.writeWithNullTerminator(stream, this.loadingScreenModel); + } + + ParseUtils.writeWithNullTerminator(stream, this.loadingScreenText); + ParseUtils.writeWithNullTerminator(stream, this.loadingScreenTitle); + ParseUtils.writeWithNullTerminator(stream, this.loadingScreenSubtitle); + stream.writeInt(this.loadingScreen); + + if (this.version > 24) { + ParseUtils.writeWithNullTerminator(stream, this.prologueScreenModel); + } + + ParseUtils.writeWithNullTerminator(stream, this.prologueScreenText); + ParseUtils.writeWithNullTerminator(stream, this.prologueScreenTitle); + ParseUtils.writeWithNullTerminator(stream, this.prologueScreenSubtitle); + + if (this.version > 24) { + stream.writeInt(this.useTerrainFog); + ParseUtils.writeFloatArray(stream, this.fogHeight); + stream.writeFloat(this.fogDensity); + ParseUtils.writeUInt8Array(stream, this.fogColor); + stream.writeInt(this.globalWeather); // TODO War3ID??? + ParseUtils.writeWithNullTerminator(stream, this.soundEnvironment); + stream.write((byte) this.lightEnvironmentTileset); + ParseUtils.writeUInt8Array(stream, this.waterVertexColor); + } + + if (this.version > 27) { + ParseUtils.writeUInt8Array(stream, this.unknown2); + } + + ParseUtils.writeUInt32(stream, this.players.size()); + + for (final Player player : this.players) { + player.save(stream); + } + + ParseUtils.writeUInt32(stream, this.forces.size()); + + for (final Force force : this.forces) { + force.save(stream); + } + + ParseUtils.writeUInt32(stream, this.upgradeAvailabilityChanges.size()); + + for (final UpgradeAvailabilityChange change : this.upgradeAvailabilityChanges) { + change.save(stream); + } + + ParseUtils.writeUInt32(stream, this.techAvailabilityChanges.size()); + + for (final TechAvailabilityChange change : this.techAvailabilityChanges) { + change.save(stream); + } + + ParseUtils.writeUInt32(stream, this.randomUnitTables.size()); + + for (final RandomUnitTable table : this.randomUnitTables) { + table.save(stream); + } + + if (this.version > 24) { + ParseUtils.writeUInt32(stream, this.randomItemTables.size()); + + for (final RandomItemTable table : this.randomItemTables) { + table.save(stream); + } + } + + } + + public int getByteLength() { + int size = 111 + this.name.length() + this.author.length() + this.description.length() + + this.recommendedPlayers.length() + this.loadingScreenText.length() + this.loadingScreenTitle.length() + + this.loadingScreenSubtitle.length() + this.prologueScreenText.length() + + this.prologueScreenTitle.length() + this.prologueScreenSubtitle.length(); + + for (final Player player : this.players) { + size += player.getByteLength(); + } + + for (final Force force : this.forces) { + size += force.getByteLength(); + } + + size += this.upgradeAvailabilityChanges.size() * 16; + + size += this.techAvailabilityChanges.size() * 8; + + for (final RandomUnitTable table : this.randomUnitTables) { + size += table.getByteLength(); + } + + if (this.version > 24) { + size += 36 + this.loadingScreenModel.length() + this.prologueScreenModel.length() + + this.soundEnvironment.length(); + + for (final RandomItemTable table : this.randomItemTables) { + size += table.getByteLength(); + } + } + + return size; + } + + public int getVersion() { + return this.version; + } + + public int getSaves() { + return this.saves; + } + + public int getEditorVersion() { + return this.editorVersion; + } + + public short[] getUnknown1() { + return this.unknown1; + } + + public String getName() { + return this.name; + } + + public String getAuthor() { + return this.author; + } + + public String getDescription() { + return this.description; + } + + public String getRecommendedPlayers() { + return this.recommendedPlayers; + } + + public float[] getCameraBounds() { + return this.cameraBounds; + } + + public int[] getCameraBoundsComplements() { + return this.cameraBoundsComplements; + } + + public int[] getPlayableSize() { + return this.playableSize; + } + + public long getFlags() { + return this.flags; + } + + public char getTileset() { + return this.tileset; + } + + public int getCampaignBackground() { + return this.campaignBackground; + } + + public String getLoadingScreenModel() { + return this.loadingScreenModel; + } + + public String getLoadingScreenText() { + return this.loadingScreenText; + } + + public String getLoadingScreenTitle() { + return this.loadingScreenTitle; + } + + public String getLoadingScreenSubtitle() { + return this.loadingScreenSubtitle; + } + + public int getLoadingScreen() { + return this.loadingScreen; + } + + public String getPrologueScreenModel() { + return this.prologueScreenModel; + } + + public String getPrologueScreenText() { + return this.prologueScreenText; + } + + public String getPrologueScreenTitle() { + return this.prologueScreenTitle; + } + + public String getPrologueScreenSubtitle() { + return this.prologueScreenSubtitle; + } + + public int getUseTerrainFog() { + return this.useTerrainFog; + } + + public float[] getFogHeight() { + return this.fogHeight; + } + + public float getFogDensity() { + return this.fogDensity; + } + + public short[] getFogColor() { + return this.fogColor; + } + + public int getGlobalWeather() { + return this.globalWeather; + } + + public String getSoundEnvironment() { + return this.soundEnvironment; + } + + public char getLightEnvironmentTileset() { + return this.lightEnvironmentTileset; + } + + public short[] getWaterVertexColor() { + return this.waterVertexColor; + } + + public short[] getUnknown2() { + return this.unknown2; + } + + public List getPlayers() { + return this.players; + } + + public List getForces() { + return this.forces; + } + + public List getUpgradeAvailabilityChanges() { + return this.upgradeAvailabilityChanges; + } + + public List getTechAvailabilityChanges() { + return this.techAvailabilityChanges; + } + + public List getRandomUnitTables() { + return this.randomUnitTables; + } + + public List getRandomItemTables() { + return this.randomItemTables; + } +} diff --git a/core/src/com/etheller/warsmash/parsers/w3x/wpm/War3MapWpm.java b/core/src/com/etheller/warsmash/parsers/w3x/wpm/War3MapWpm.java new file mode 100644 index 0000000..78b436f --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/w3x/wpm/War3MapWpm.java @@ -0,0 +1,54 @@ +package com.etheller.warsmash.parsers.w3x.wpm; + +import java.io.IOException; + +import com.etheller.warsmash.util.ParseUtils; +import com.etheller.warsmash.util.War3ID; +import com.google.common.io.LittleEndianDataInputStream; + +public class War3MapWpm { + private static final War3ID MAGIC_NUMBER = War3ID.fromString("MP3W"); + private int version; + private final int[] size = new int[2]; + private short[] pathing; + + public War3MapWpm(final LittleEndianDataInputStream stream) throws IOException { + if (stream != null) { + this.load(stream); + } + } + + private boolean load(final LittleEndianDataInputStream stream) throws IOException { + final War3ID firstId = ParseUtils.readWar3ID(stream); + if (!MAGIC_NUMBER.equals(firstId)) { + return false; + } + + this.version = stream.readInt(); + ParseUtils.readInt32Array(stream, this.size); + this.pathing = ParseUtils.readUInt8Array(stream, this.size[0] * this.size[1]); + + return true; + } + + public int getVersion() { + return this.version; + } + + public int[] getSize() { + return this.size; + } + + public short[] getPathing() { + return this.pathing; + } + + public void setVersion(final int version) { + this.version = version; + } + + public void setPathing(final short[] pathing) { + this.pathing = pathing; + } + +} diff --git a/core/src/com/etheller/warsmash/units/StandardObjectData.java b/core/src/com/etheller/warsmash/units/StandardObjectData.java index 845a6d1..7c9953d 100644 --- a/core/src/com/etheller/warsmash/units/StandardObjectData.java +++ b/core/src/com/etheller/warsmash/units/StandardObjectData.java @@ -339,6 +339,10 @@ public class StandardObjectData { return unitMetaData; } + public WorldEditStrings getWorldEditStrings() { + return this.worldEditStrings; + } + public static class WarcraftData implements ObjectData { WorldEditStrings worldEditStrings; List tables = new ArrayList<>(); diff --git a/core/src/com/etheller/warsmash/units/custom/Change.java b/core/src/com/etheller/warsmash/units/custom/Change.java new file mode 100644 index 0000000..e185128 --- /dev/null +++ b/core/src/com/etheller/warsmash/units/custom/Change.java @@ -0,0 +1,105 @@ +package com.etheller.warsmash.units.custom; + +import com.etheller.warsmash.util.War3ID; + +public final class Change { + private War3ID id; + private int vartype, level, dataptr; + private int longval; + private float realval; + private String strval; + + private boolean boolval; + private War3ID junkDNA; + + public War3ID getId() { + return this.id; + } + + public void setId(final War3ID id) { + this.id = id; + } + + public int getVartype() { + return this.vartype; + } + + public void setVartype(final int vartype) { + this.vartype = vartype; + } + + public int getLevel() { + return this.level; + } + + public void setLevel(final int level) { + this.level = level; + } + + public int getDataptr() { + return this.dataptr; + } + + public void setDataptr(final int dataptr) { + this.dataptr = dataptr; + } + + public int getLongval() { + return this.longval; + } + + public void setLongval(final int longval) { + this.longval = longval; + } + + public float getRealval() { + return this.realval; + } + + public void setRealval(final float realval) { + this.realval = realval; + } + + public String getStrval() { + return this.strval; + } + + public void setStrval(final String strval) { + this.strval = strval; + } + + public boolean isBoolval() { + return this.boolval; + } + + public void setBoolval(final boolean boolval) { + this.boolval = boolval; + } + + public void setJunkDNA(final War3ID junkDNA) { + this.junkDNA = junkDNA; + } + + public War3ID getJunkDNA() { + return this.junkDNA; + } + + public void copyFrom(final Change other) { + this.id = other.id; + this.level = other.level; + this.dataptr = other.dataptr; + this.vartype = other.vartype; + this.longval = other.longval; + this.realval = other.realval; + this.strval = other.strval; + this.boolval = other.boolval; + this.junkDNA = other.junkDNA; + } + + @Override + public Change clone() { + final Change copy = new Change(); + copy.copyFrom(this); + return copy; + } +} diff --git a/core/src/com/etheller/warsmash/units/custom/ChangeMap.java b/core/src/com/etheller/warsmash/units/custom/ChangeMap.java new file mode 100644 index 0000000..5442421 --- /dev/null +++ b/core/src/com/etheller/warsmash/units/custom/ChangeMap.java @@ -0,0 +1,55 @@ +package com.etheller.warsmash.units.custom; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import com.etheller.warsmash.util.War3ID; + +public final class ChangeMap implements Iterable>> { + private final Map> idToChanges = new LinkedHashMap<>(); + + public void add(final War3ID war3Id, final Change change) { + List list = this.idToChanges.get(war3Id); + if (list == null) { + list = new ArrayList<>(); + this.idToChanges.put(war3Id, list); + } + list.add(change); + } + + public void add(final War3ID war3Id, final List changes) { + for (final Change change : changes) { + add(war3Id, change); + } + } + + public List get(final War3ID war3ID) { + return this.idToChanges.get(war3ID); + } + + public void delete(final War3ID war3ID, final Change change) { + if (this.idToChanges.containsKey(war3ID)) { + final List changeList = this.idToChanges.get(war3ID); + changeList.remove(change); + if (changeList.isEmpty()) { + this.idToChanges.remove(war3ID); + } + } + } + + @Override + public Iterator>> iterator() { + return this.idToChanges.entrySet().iterator(); + } + + public int size() { + return this.idToChanges.size(); + } + + public void clear() { + this.idToChanges.clear(); + } +} diff --git a/core/src/com/etheller/warsmash/units/custom/ObjectDataChangeEntry.java b/core/src/com/etheller/warsmash/units/custom/ObjectDataChangeEntry.java new file mode 100644 index 0000000..1acda99 --- /dev/null +++ b/core/src/com/etheller/warsmash/units/custom/ObjectDataChangeEntry.java @@ -0,0 +1,47 @@ +package com.etheller.warsmash.units.custom; + +import java.util.List; +import java.util.Map; + +import com.etheller.warsmash.util.War3ID; + +public final class ObjectDataChangeEntry { + private War3ID oldId; + private War3ID newId; + private final ChangeMap changes; + + public ObjectDataChangeEntry(final War3ID oldId, final War3ID newId) { + this.oldId = oldId; + this.newId = newId; + this.changes = new ChangeMap(); + } + + @Override + public ObjectDataChangeEntry clone() { + final ObjectDataChangeEntry objectDataChangeEntry = new ObjectDataChangeEntry(this.oldId, this.newId); + for (final Map.Entry> entry : this.changes) { + objectDataChangeEntry.getChanges().add(entry.getKey(), entry.getValue()); + } + return objectDataChangeEntry; + } + + public ChangeMap getChanges() { + return this.changes; + } + + public War3ID getOldId() { + return this.oldId; + } + + public void setOldId(final War3ID oldId) { + this.oldId = oldId; + } + + public War3ID getNewId() { + return this.newId; + } + + public void setNewId(final War3ID newId) { + this.newId = newId; + } +} diff --git a/core/src/com/etheller/warsmash/units/custom/ObjectMap.java b/core/src/com/etheller/warsmash/units/custom/ObjectMap.java new file mode 100644 index 0000000..bbece53 --- /dev/null +++ b/core/src/com/etheller/warsmash/units/custom/ObjectMap.java @@ -0,0 +1,89 @@ +package com.etheller.warsmash.units.custom; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; +import java.util.function.BiConsumer; + +import com.etheller.warsmash.util.War3ID; + +public final class ObjectMap implements Iterable> { + private final Map idToDataChangeEntry; + private final Set lowerCaseKeySet; + + public ObjectMap() { + this.idToDataChangeEntry = new LinkedHashMap<>(); + this.lowerCaseKeySet = new HashSet<>(); + } + + public void clear() { + this.idToDataChangeEntry.clear(); + this.lowerCaseKeySet.clear(); + } + + public ObjectDataChangeEntry remove(final War3ID key) { + this.lowerCaseKeySet.remove(War3ID.fromString(key.toString().toLowerCase())); + return this.idToDataChangeEntry.remove(key); + } + + public Set keySet() { + return this.idToDataChangeEntry.keySet(); + } + + public ObjectDataChangeEntry put(final War3ID key, final ObjectDataChangeEntry value) { + this.lowerCaseKeySet.add(War3ID.fromString(key.toString().toLowerCase())); + return this.idToDataChangeEntry.put(key, value); + } + + public Set> entrySet() { + return this.idToDataChangeEntry.entrySet(); + } + + public ObjectDataChangeEntry get(final War3ID key) { + return this.idToDataChangeEntry.get(key); + } + + public boolean containsKey(final War3ID key) { + return this.idToDataChangeEntry.containsKey(key); + } + + public boolean containsKeyCaseInsensitive(final War3ID key) { + return this.lowerCaseKeySet.contains(War3ID.fromString(key.toString().toLowerCase())); + } + + public boolean containsValue(final ObjectDataChangeEntry value) { + return this.idToDataChangeEntry.containsValue(value); + } + + public Collection values() { + return this.idToDataChangeEntry.values(); + } + + public int size() { + return this.idToDataChangeEntry.size(); + } + + public void forEach(final BiConsumer forEach) { + this.idToDataChangeEntry.forEach(forEach); + } + + @Override + public Iterator> iterator() { + return this.idToDataChangeEntry.entrySet().iterator(); + } + + @Override + public ObjectMap clone() { + final ObjectMap clone = new ObjectMap(); + forEach(new BiConsumer() { + @Override + public void accept(final War3ID key, final ObjectDataChangeEntry value) { + clone.put(key, value); + } + }); + return clone; + } +} diff --git a/core/src/com/etheller/warsmash/units/custom/WTS.java b/core/src/com/etheller/warsmash/units/custom/WTS.java new file mode 100644 index 0000000..2ad60bd --- /dev/null +++ b/core/src/com/etheller/warsmash/units/custom/WTS.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.units.custom; + +public interface WTS { + String get(int key); +} diff --git a/core/src/com/etheller/warsmash/units/custom/WTSFile.java b/core/src/com/etheller/warsmash/units/custom/WTSFile.java new file mode 100644 index 0000000..99b7ab3 --- /dev/null +++ b/core/src/com/etheller/warsmash/units/custom/WTSFile.java @@ -0,0 +1,94 @@ +package com.etheller.warsmash.units.custom; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Hashtable; +import java.util.Map; + +/** + * + * @author Deaod + * + */ +public class WTSFile implements WTS { + private final InputStream source; + private final Map trigStrings = new Hashtable<>(); + + private static enum ParseState { + NEXT_TRIGSTR, + START_OF_DATA, + END_OF_DATA; + } + + private void parse() throws IOException { + final BufferedReader sourceReader = new BufferedReader( + new InputStreamReader(this.source, Charset.forName("utf-8"))); + ParseState state = ParseState.NEXT_TRIGSTR; + + // WTS files may start with a Byte Order Mark, which we will have to skip. + sourceReader.mark(4); + if (sourceReader.read() != 0xFEFF) { + // first character not a BOM, unread the character. + sourceReader.reset(); + } + + String currentLine = sourceReader.readLine(); + int id = 0; + StringBuffer data = new StringBuffer(); + + while (currentLine != null) { + switch (state) { + case NEXT_TRIGSTR: + if (currentLine.startsWith("STRING ")) { + id = Integer.parseInt(currentLine.substring(7)); + state = ParseState.START_OF_DATA; + } + break; + + case START_OF_DATA: + if (currentLine.startsWith("{")) { + state = ParseState.END_OF_DATA; + } + break; + + case END_OF_DATA: + if (currentLine.startsWith("}")) { + this.trigStrings.put(id, data.toString()); + data = new StringBuffer(); + state = ParseState.NEXT_TRIGSTR; + } + else { + data.append(currentLine); + } + break; + } + currentLine = sourceReader.readLine(); + } + sourceReader.close(); + } + + public WTSFile(final InputStream inputStream) throws IOException { + this.source = inputStream; + parse(); + } + + public WTSFile(final Path source) throws IOException { + this(Files.newInputStream(source)); + } + + public WTSFile(final String sourcePath) throws IOException { + this(Paths.get(sourcePath)); + } + + @Override + public String get(final int index) { + return this.trigStrings.get(index); + } + +} diff --git a/core/src/com/etheller/warsmash/units/custom/War3ObjectDataChangeset.java b/core/src/com/etheller/warsmash/units/custom/War3ObjectDataChangeset.java new file mode 100644 index 0000000..1f215dd --- /dev/null +++ b/core/src/com/etheller/warsmash/units/custom/War3ObjectDataChangeset.java @@ -0,0 +1,802 @@ +package com.etheller.warsmash.units.custom; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.CharsetEncoder; +import java.nio.charset.CodingErrorAction; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.etheller.warsmash.util.ParseUtils; +import com.etheller.warsmash.util.War3ID; +import com.google.common.io.LittleEndianDataInputStream; +import com.google.common.io.LittleEndianDataOutputStream; + +/** + * Inspired by PitzerMike's obj.h, without a lot of immediate focus on Java + * conventions. I will probably get it converted over to Java conventions once I + * have a working replica of his obj.h code. + * + * @author Eric + * + */ +public final class War3ObjectDataChangeset { + public static final int VAR_TYPE_INT = 0; + public static final int VAR_TYPE_REAL = 1; + public static final int VAR_TYPE_UNREAL = 2; + public static final int VAR_TYPE_STRING = 3; + public static final int VAR_TYPE_BOOLEAN = 4; + public static final int MAX_STR_LEN = 1024; + private static final Set UNIT_ID_SET; + private static final Set ABILITY_ID_SET; + static { + final HashSet unitHashSet = new HashSet<>(); + unitHashSet.add(War3ID.fromString("ubpx")); + unitHashSet.add(War3ID.fromString("ubpy")); + unitHashSet.add(War3ID.fromString("ides")); + unitHashSet.add(War3ID.fromString("uhot")); + unitHashSet.add(War3ID.fromString("unam")); + unitHashSet.add(War3ID.fromString("ureq")); + unitHashSet.add(War3ID.fromString("urqa")); + unitHashSet.add(War3ID.fromString("utip")); + unitHashSet.add(War3ID.fromString("utub")); + UNIT_ID_SET = unitHashSet; + final HashSet abilHashSet = new HashSet<>(); + abilHashSet.add(War3ID.fromString("irc2")); + abilHashSet.add(War3ID.fromString("irc3")); + abilHashSet.add(War3ID.fromString("bsk1")); + abilHashSet.add(War3ID.fromString("bsk2")); + abilHashSet.add(War3ID.fromString("bsk3")); + abilHashSet.add(War3ID.fromString("coau")); + abilHashSet.add(War3ID.fromString("coa1")); + abilHashSet.add(War3ID.fromString("coa2")); + abilHashSet.add(War3ID.fromString("cyc1")); + abilHashSet.add(War3ID.fromString("dcp1")); + abilHashSet.add(War3ID.fromString("dcp2")); + abilHashSet.add(War3ID.fromString("dvm1")); + abilHashSet.add(War3ID.fromString("dvm2")); + abilHashSet.add(War3ID.fromString("dvm3")); + abilHashSet.add(War3ID.fromString("dvm4")); + abilHashSet.add(War3ID.fromString("dvm5")); + abilHashSet.add(War3ID.fromString("exh1")); + abilHashSet.add(War3ID.fromString("exhu")); + abilHashSet.add(War3ID.fromString("fak1")); + abilHashSet.add(War3ID.fromString("fak2")); + abilHashSet.add(War3ID.fromString("fak3")); + abilHashSet.add(War3ID.fromString("hwdu")); + abilHashSet.add(War3ID.fromString("inv1")); + abilHashSet.add(War3ID.fromString("inv2")); + abilHashSet.add(War3ID.fromString("inv3")); + abilHashSet.add(War3ID.fromString("inv4")); + abilHashSet.add(War3ID.fromString("inv5")); + abilHashSet.add(War3ID.fromString("liq1")); + abilHashSet.add(War3ID.fromString("liq2")); + abilHashSet.add(War3ID.fromString("liq3")); + abilHashSet.add(War3ID.fromString("liq4")); + abilHashSet.add(War3ID.fromString("mim1")); + abilHashSet.add(War3ID.fromString("mfl1")); + abilHashSet.add(War3ID.fromString("mfl2")); + abilHashSet.add(War3ID.fromString("mfl3")); + abilHashSet.add(War3ID.fromString("mfl4")); + abilHashSet.add(War3ID.fromString("mfl5")); + abilHashSet.add(War3ID.fromString("tpi1")); + abilHashSet.add(War3ID.fromString("tpi2")); + abilHashSet.add(War3ID.fromString("spl1")); + abilHashSet.add(War3ID.fromString("spl2")); + abilHashSet.add(War3ID.fromString("irl1")); + abilHashSet.add(War3ID.fromString("irl2")); + abilHashSet.add(War3ID.fromString("irl3")); + abilHashSet.add(War3ID.fromString("irl4")); + abilHashSet.add(War3ID.fromString("irl5")); + abilHashSet.add(War3ID.fromString("idc1")); + abilHashSet.add(War3ID.fromString("idc2")); + abilHashSet.add(War3ID.fromString("idc3")); + abilHashSet.add(War3ID.fromString("imo1")); + abilHashSet.add(War3ID.fromString("imo2")); + abilHashSet.add(War3ID.fromString("imo3")); + abilHashSet.add(War3ID.fromString("imou")); + abilHashSet.add(War3ID.fromString("ict1")); + abilHashSet.add(War3ID.fromString("ict2")); + abilHashSet.add(War3ID.fromString("isr1")); + abilHashSet.add(War3ID.fromString("isr2")); + abilHashSet.add(War3ID.fromString("ipv1")); + abilHashSet.add(War3ID.fromString("ipv2")); + abilHashSet.add(War3ID.fromString("ipv3")); + abilHashSet.add(War3ID.fromString("mec1")); + abilHashSet.add(War3ID.fromString("spb1")); + abilHashSet.add(War3ID.fromString("spb2")); + abilHashSet.add(War3ID.fromString("spb3")); + abilHashSet.add(War3ID.fromString("spb4")); + abilHashSet.add(War3ID.fromString("spb5")); + abilHashSet.add(War3ID.fromString("gra1")); + abilHashSet.add(War3ID.fromString("gra2")); + abilHashSet.add(War3ID.fromString("gra3")); + abilHashSet.add(War3ID.fromString("gra4")); + abilHashSet.add(War3ID.fromString("gra5")); + abilHashSet.add(War3ID.fromString("ipmu")); + abilHashSet.add(War3ID.fromString("flk1")); + abilHashSet.add(War3ID.fromString("flk2")); + abilHashSet.add(War3ID.fromString("flk3")); + abilHashSet.add(War3ID.fromString("flk4")); + abilHashSet.add(War3ID.fromString("flk5")); + abilHashSet.add(War3ID.fromString("fbk1")); + abilHashSet.add(War3ID.fromString("fbk2")); + abilHashSet.add(War3ID.fromString("fbk3")); + abilHashSet.add(War3ID.fromString("fbk4")); + abilHashSet.add(War3ID.fromString("nca1")); + abilHashSet.add(War3ID.fromString("pxf1")); + abilHashSet.add(War3ID.fromString("pxf2")); + abilHashSet.add(War3ID.fromString("mls1")); + abilHashSet.add(War3ID.fromString("sla1")); + abilHashSet.add(War3ID.fromString("sla2")); + ABILITY_ID_SET = abilHashSet; + } + + private int version; + private ObjectMap original = new ObjectMap(); + private final ObjectMap custom = new ObjectMap(); + private char expected; + private War3ID lastused; + + public char kind; + public boolean detected; + + public War3ID nameField; + + public War3ObjectDataChangeset() { + this.version = 2; + this.kind = 'u'; + this.expected = 'u'; + this.detected = false; + this.lastused = War3ID.fromString("u~~~"); + } + + public War3ObjectDataChangeset(final char expectedkind) { + this.version = 2; + this.kind = 'u'; + this.expected = expectedkind; + this.detected = false; + this.lastused = War3ID.fromString("u~~~"); + } + + public boolean detectKind(final War3ID chid) { + if (UNIT_ID_SET.contains(chid)) { + this.kind = 'u'; + return false; + } + else if (ABILITY_ID_SET.contains(chid)) { + this.kind = 'a'; + } + else { + switch (chid.asStringValue().charAt(0)) { + case 'f': + this.kind = 'h'; + break; + case 'i': + this.kind = 't'; + break; + case 'g': + this.kind = 'q'; + break; + case 'a': + case 'u': + case 'b': + case 'd': + this.kind = chid.asStringValue().charAt(0); + break; + default: + this.kind = 'a'; + } + } + return true; + } + + public char getExpectedKind() { + return this.expected; + } + + public War3ID getNameField() { + final War3ID field = War3ID.fromString("unam"); + char cmp = this.kind; + if (!this.detected) { + cmp = this.expected; + } + switch (cmp) { + case 'h': + this.nameField = field.set(0, 'f'); + break; + case 't': + this.nameField = field.set(0, 'u'); + break; + case 'q': + this.nameField = field.set(0, 'g'); + break; + default: + this.nameField = field.set(0, cmp); + break; + } + return this.nameField; + } + + public boolean extended() { + char cmp = this.kind; + if (!this.detected) { + cmp = this.expected; + } + switch (cmp) { + case 'u': + case 'h': + case 'b': + case 't': + return false; + } + return true; + } + + public void renameids(final ObjectMap map, final boolean isOriginal) { + final War3ID nameId = getNameField(); + final List idsToRemoveFromMap = new ArrayList<>(); + final Map idsToObjectsForAddingToMap = new HashMap<>(); + for (final Iterator> iterator = map.iterator(); iterator.hasNext();) { + final Map.Entry entry = iterator.next(); + final ObjectDataChangeEntry current = entry.getValue(); + final List nameEntry = current.getChanges().get(nameId); + if ((nameEntry != null) && !nameEntry.isEmpty()) { + final Change firstNameChange = nameEntry.get(0); + int pos = firstNameChange.getStrval().lastIndexOf("::"); + if ((pos != -1) && (firstNameChange.getStrval().length() > (pos + 2))) { + String rest = firstNameChange.getStrval().substring(pos + 2); + if (rest.length() == 4) { + final War3ID newId = War3ID.fromString(rest); + final ObjectDataChangeEntry existingObjectWithMatchingId = map.get(newId); + if (isOriginal) {// obj.cpp: update id and name + current.setOldId(newId); + } + else { + current.setNewId(newId); + } + firstNameChange.setStrval(firstNameChange.getStrval().substring(0, pos)); + if (existingObjectWithMatchingId != null) { + // obj.cpp: carry over all changes + final Iterator>> changeIterator = current.getChanges() + .iterator(); + while (changeIterator.hasNext()) { + final Map.Entry> changeIteratorNext = changeIterator.next(); + final War3ID copiedChangeId = changeIteratorNext.getKey(); + List changeListForFieldToOverwrite = existingObjectWithMatchingId.getChanges() + .get(copiedChangeId); + if (changeListForFieldToOverwrite == null) { + changeListForFieldToOverwrite = new ArrayList<>(); + } + for (final Change changeToCopy : changeIteratorNext.getValue()) { + final Iterator replaceIterator = changeListForFieldToOverwrite.iterator(); + boolean didOverwrite = false; + while (replaceIterator.hasNext()) { + final Change changeToOverwrite = replaceIterator.next(); + if (changeToOverwrite.getLevel() != changeToCopy.getLevel()) { + // obj.cpp: we can only replace + // changes with the same + // level/variation + continue; + } + if (copiedChangeId.equals(nameId)) { + // obj.cpp: carry over further + // references + pos = changeToOverwrite.getStrval().lastIndexOf("::"); + if ((pos != -1) && (changeToOverwrite.getStrval().length() > (pos + 2))) { + rest = changeToOverwrite.getStrval().substring(pos + 2); + if ((rest.length() == 4) || "REMOVE".equals(rest)) { + changeToCopy.setStrval(changeToCopy.getStrval() + "::" + rest); + // so if this is a peasant, whose name was "Peasant::hfoo" + // and when we copied his data onto the footman, we found + // that the footman was named "Footman::hkni", then at that + // point we set the peasant's name to be "Peasant::hkni" + // because we are about to copy it onto the footman. + // And, we already set it to just "Peasant", so + // appending the "::" and the 'rest' variable is enough. + // Then, on a further loop iteration, in theory + // we will copoy the footman who is named Peasant + // onto the knight. + // + // TODO but what if we already copied the footman onto the knight? + // did PitzerMike consider this in obj.cpp? + } + } + } + changeToOverwrite.copyFrom(changeToCopy); + didOverwrite = true; + break; + } + if (!didOverwrite) { + changeListForFieldToOverwrite.add(changeToCopy); + if (changeListForFieldToOverwrite.size() == 1) { + existingObjectWithMatchingId.getChanges().add(copiedChangeId, + changeListForFieldToOverwrite); + } + } + } + } + } + else { // obj.cpp: an object with that id didn't exist + idsToRemoveFromMap.add(entry.getKey()); + idsToObjectsForAddingToMap.put(newId, current.clone()); + } + } + else if ("REMOVE".equals(rest)) { // obj.cpp: want to remove the object + idsToRemoveFromMap.add(entry.getKey()); + } // obj.cpp: in all other cases keep it untouched + } + } + + } + for (final War3ID id : idsToRemoveFromMap) { + map.remove(id); + } + for (final Map.Entry entry : idsToObjectsForAddingToMap.entrySet()) { + map.put(entry.getKey(), entry.getValue()); + } + } + + public void renameIds() { + renameids(this.original, true); + renameids(this.custom, false); + } + + // ' ' - '/' + // ':' - '@' + // '[' - '`' + // '{' - '~' + public char nextchar(final char cur) { + switch (cur) { + case '&': // skip ' because often jass parsers don't handle escaped rawcodes like '\'' + return '('; + case '/': // skip digits + return ':'; + case '@': // skip capital letters + return '['; // skip \ for the sam reason like ' ('\\') + case '[': + return ']'; + case '_': // skip � and lower case letters (� can't be seen very well) + return '{'; + case '~': // close circle and restart at ! + return '!'; + default: + return (char) ((short) cur + 1); + } + } + + // we use only special characters to avoid collisions with existing objects + // the first character must remain unchanged though because it can have a + // special meaning + public War3ID getunusedid(final War3ID substitutefor) { + this.lastused = this.lastused.set(0, substitutefor.charAt(0)); + this.lastused = this.lastused.set(3, nextchar(substitutefor.charAt(3))); + if (this.lastused.charAt(3) == '!') { + this.lastused = this.lastused.set(2, nextchar(substitutefor.charAt(2))); + if (this.lastused.charAt(2) == '!') { + this.lastused = this.lastused.set(1, nextchar(substitutefor.charAt(1))); + } + } + return this.lastused; + } + + public void mergetable(final ObjectMap target, final ObjectMap targetCustom, final ObjectMap source, + final CollisionHandling collisionHandling) { + final Iterator> sourceObjectIterator = source.iterator(); + while (sourceObjectIterator.hasNext()) { + final Map.Entry sourceObject = sourceObjectIterator.next(); + if (target.containsKey(sourceObject.getKey())) { + // obj.cpp: we have a collision + War3ID oldId; + War3ID replacementId; + + switch (collisionHandling) { + case CREATE_NEW_ID: + oldId = sourceObject.getKey(); + // obj.cpp: get new id until we finally have one that isn't used yet, or we're + // out of ids + replacementId = getunusedid(oldId); + while (!((oldId.charAt(1) == '~') && (oldId.charAt(2) == '~') && (oldId.charAt(3) == '~')) + && targetCustom.containsKey(replacementId)) { + oldId = replacementId; + replacementId = getunusedid(oldId); + } + if (!((oldId.charAt(1) == '~') && (oldId.charAt(2) == '~') && (oldId.charAt(3) == '~'))) { + sourceObject.getValue().setNewId(replacementId); + targetCustom.put(replacementId, sourceObject.getValue().clone()); + } + break; + case REPLACE: + // final ObjectDataChangeEntry deleteObject = target.get(sourceObject.getKey()); + target.put(sourceObject.getKey(), sourceObject.getValue().clone()); + break; + default:// merge + final ObjectDataChangeEntry targetObject = target.get(sourceObject.getKey()); + for (final Map.Entry> sourceUnitField : sourceObject.getValue().getChanges()) { + for (final Change sourceChange : sourceUnitField.getValue()) { + List targetChanges = targetObject.getChanges().get(sourceUnitField.getKey()); + if (targetChanges == null) { + targetChanges = new ArrayList<>(); + } + Change bestTargetChange = null; + for (final Change targetChange : targetChanges) { + if (targetChange.getLevel() == sourceChange.getLevel()) { + bestTargetChange = targetChange; + break; + } + } + if (bestTargetChange != null) { + bestTargetChange.copyFrom(sourceChange); + } + else { + targetChanges.add(sourceChange.clone()); + if (targetChanges.size() == 1) { + targetObject.getChanges().add(sourceUnitField.getKey(), targetChanges); + } + } + } + } + break; + } + } + else { + targetCustom.put(sourceObject.getKey(), sourceObject.getValue().clone()); + } + } + } + + public static enum CollisionHandling { + CREATE_NEW_ID, + REPLACE, + MERGE; + } + + public void merge(final War3ObjectDataChangeset obj, final CollisionHandling collisionHandling) { + mergetable(this.original, this.custom, obj.original, collisionHandling); + mergetable(this.original, this.custom, obj.custom, collisionHandling); + } + + public int getvartype(final String name) { + if ("int".equals(name) || "bool".equals(name)) { + return 0; + } + else if ("real".equals(name)) { + return 1; + } + else if ("unreal".equals(name)) { + return 2; + } + return 3; // string + } + + public boolean loadtable(final LittleEndianDataInputStream stream, final ObjectMap map, final boolean isOriginal, + final WTS wts, final boolean inlineWTS) throws IOException { + final War3ID noid = new War3ID(0); + final ByteBuffer stringByteBuffer = ByteBuffer.allocate(1024); // TODO check max len? + final CharsetDecoder decoder = Charset.forName("utf-8").newDecoder().onMalformedInput(CodingErrorAction.REPLACE) + .onUnmappableCharacter(CodingErrorAction.REPLACE); + int ptr; + final int count = stream.readInt(); + for (int i = 0; i < count; i++) { + final long nanoTime = System.nanoTime(); + War3ID origid; + War3ID newid = null; + origid = readWar3ID(stream); + ObjectDataChangeEntry existingObject; + if (isOriginal) { + if (noid.equals(origid)) { + throw new IOException("the input stream might be screwed"); + } + existingObject = map.get(origid); + if (existingObject == null) { + existingObject = new ObjectDataChangeEntry(origid, noid); + } + existingObject.setNewId(readWar3ID(stream)); + } + else { + newid = readWar3ID(stream); + if (noid.equals(origid) || noid.equals(newid)) { + throw new IOException("the input stream might be screwed"); + } + existingObject = map.get(newid); + if (existingObject == null) { + existingObject = new ObjectDataChangeEntry(origid, newid); + } + } + final int ccount = stream.readInt();// Retera: I assume this is change count? + if ((ccount == 0) && isOriginal) { + // throw new IOException("we seem to have reached the end of the stream and get + // zeroes"); + System.err.println("we seem to have reached the end of the stream and get zeroes"); + } + if (isOriginal) { + debugprint("StandardUnit \"" + origid + "\" " + ccount + " {"); + } + else { + debugprint("CustomUnit \"" + origid + ":" + newid + "\" " + ccount + " {"); + } + for (int j = 0; j < ccount; j++) { + final War3ID chid = readWar3ID(stream); + if (noid.equals(chid)) { + throw new IOException("the input stream might be screwed"); + } + if (!this.detected) { + this.detected = detectKind(chid); + } + + final Change newlyReadChange = new Change(); + newlyReadChange.setId(chid); + newlyReadChange.setVartype(stream.readInt()); + debugprint("\t\"" + chid + "\" {"); + debugprint("\t\tType " + newlyReadChange.getVartype() + ","); + if (extended()) { + newlyReadChange.setLevel(stream.readInt()); + newlyReadChange.setDataptr(stream.readInt()); + debugprint("\t\tLevel " + newlyReadChange.getLevel() + ","); + debugprint("\t\tData " + newlyReadChange.getDataptr() + ","); + } + + switch (newlyReadChange.getVartype()) { + case 0: + newlyReadChange.setLongval(stream.readInt()); + debugprint("\t\tValue " + newlyReadChange.getLongval() + ","); + break; + case 3: + ptr = 0; + stringByteBuffer.clear(); + byte charRead; + while ((charRead = (byte) stream.read()) != 0) { + stringByteBuffer.put(charRead); + } + stringByteBuffer.flip(); + newlyReadChange.setStrval(decoder.decode(stringByteBuffer).toString()); + if (inlineWTS && (newlyReadChange.getStrval().length() > 8) + && "TRIGSTR_".equals(newlyReadChange.getStrval().substring(0, 8))) { + final int key = getWTSValue(newlyReadChange); + newlyReadChange.setStrval(wts.get(key)); + if ((newlyReadChange.getStrval() != null) + && (newlyReadChange.getStrval().length() > MAX_STR_LEN)) { + newlyReadChange.setStrval(newlyReadChange.getStrval().substring(0, MAX_STR_LEN - 1)); + } + } + debugprint("\t\tValue \"" + newlyReadChange.getStrval() + "\","); + break; + case 4: + newlyReadChange.setBoolval(stream.readInt() == 1); + debugprint("\t\tValue " + newlyReadChange.isBoolval() + ","); + break; + default: + newlyReadChange.setRealval(stream.readFloat()); + debugprint("\t\tValue " + newlyReadChange.getRealval() + ","); + break; + } + final War3ID crap = readWar3ID(stream); + debugprint("\t\tExtra \"" + crap + "\","); + newlyReadChange.setJunkDNA(crap); + List existingChanges = existingObject.getChanges().get(chid); + if (existingChanges == null) { + existingChanges = new ArrayList<>(); + } + Change bestTargetChange = null; + for (final Change targetChange : existingChanges) { + if (targetChange.getLevel() == newlyReadChange.getLevel()) { + bestTargetChange = targetChange; + break; + } + } + if (bestTargetChange != null) { + bestTargetChange.copyFrom(newlyReadChange); + } + else { + existingChanges.add(newlyReadChange.clone()); + if (existingChanges.size() == 1) { + existingObject.getChanges().add(chid, existingChanges); + } + } + if (!crap.equals(existingObject.getOldId()) && !crap.equals(existingObject.getNewId()) + && !crap.equals(noid)) { + for (int charIndex = 0; charIndex < 4; charIndex++) { + if ((crap.charAt(charIndex) < 32) || (crap.charAt(charIndex) > 126)) { + return false; + } + } + } + debugprint("\t}"); + } + debugprint("}"); + if ((newid == null) && !isOriginal) { + throw new IllegalStateException("custom unit has no ID!"); + } + map.put(isOriginal ? origid : newid, existingObject); + final long endNanoTime = System.nanoTime(); + final long deltaNanoTime = endNanoTime - nanoTime; + } + return true; + } + + private War3ID readWar3ID(final LittleEndianDataInputStream stream) throws IOException { + return new War3ID(Integer.reverseBytes(stream.readInt())); + } + + private static int getWTSValue(final Change change) { + String numberAsText = change.getStrval().substring(8); + while ((numberAsText.length() > 0) && (numberAsText.charAt(0) == '0')) { + numberAsText = numberAsText.substring(1); + } + if (numberAsText.length() == 0) { + return 0; + } + while (!Character.isDigit(numberAsText.charAt(numberAsText.length() - 1))) { + numberAsText = numberAsText.substring(0, numberAsText.length() - 1); + } + return Integer.parseInt(numberAsText); + } + + public boolean load(final LittleEndianDataInputStream stream, final WTS wts, final boolean inlineWTS) + throws IOException { + this.detected = false; + this.version = stream.readInt(); + if ((this.version != 1) && (this.version != 2)) { + return false; + } + ObjectMap backup = this.original.clone(); + if (!loadtable(stream, this.original, true, wts, inlineWTS)) { + this.original = backup; + return false; + } + backup = this.custom.clone(); + if (!loadtable(stream, this.custom, false, wts, inlineWTS)) { + this.original = backup; + return false; + } + return true; + } + + public boolean load(final File file, final WTS wts, final boolean inlineWTS) throws IOException { + try (LittleEndianDataInputStream inputStream = new LittleEndianDataInputStream(new FileInputStream(file))) { + final boolean result = load(inputStream, wts, inlineWTS); + return result; + } + } + + public static void inlineWTSTable(final ObjectMap map, final WTS wts) { + for (final Map.Entry entry : map.entrySet()) { + for (final Map.Entry> changes : entry.getValue().getChanges()) { + for (final Change change : changes.getValue()) { + if ((change.getStrval().length() > 8) && "TRIGSTR_".equals(change.getStrval().substring(0, 8))) { + final int key = getWTSValue(change); + change.setStrval(wts.get(key)); + if (change.getStrval().length() > MAX_STR_LEN) { + change.setStrval(change.getStrval().substring(0, MAX_STR_LEN - 1)); + } + } + } + } + } + } + + public void inlineWTS(final WTS wts) { + inlineWTSTable(this.original, wts); + inlineWTSTable(this.custom, wts); + } + + public void reset() { + reset('u'); + } + + public void reset(final char expectedkind) { + this.detected = false; + this.kind = 'u'; + this.lastused = War3ID.fromString("u~~~"); + this.expected = expectedkind; + this.original.clear(); + this.custom.clear(); + } + + public boolean saveTable(final LittleEndianDataOutputStream outputStream, final ObjectMap map, + final boolean isOriginal) throws IOException { + final CharsetEncoder encoder = Charset.forName("utf-8").newEncoder().onMalformedInput(CodingErrorAction.REPLACE) + .onUnmappableCharacter(CodingErrorAction.REPLACE); + final CharBuffer charBuffer = CharBuffer.allocate(1024); + final ByteBuffer byteBuffer = ByteBuffer.allocate(1024); + final War3ID noid = new War3ID(0); + int count; + count = map.size(); + outputStream.writeInt(count); + for (final Map.Entry entry : map) { + final ObjectDataChangeEntry cl = entry.getValue(); + int totalSize = 0; + for (final Map.Entry> changeEntry : cl.getChanges()) { + totalSize += changeEntry.getValue().size(); + } + if ((totalSize > 0) || !isOriginal) { + ParseUtils.writeWar3ID(outputStream, cl.getOldId()); + ParseUtils.writeWar3ID(outputStream, cl.getNewId()); + count = totalSize;// cl.getChanges().size(); + outputStream.writeInt(count); + for (final Map.Entry> changes : entry.getValue().getChanges()) { + for (final Change change : changes.getValue()) { + ParseUtils.writeWar3ID(outputStream, change.getId()); + outputStream.writeInt(change.getVartype()); + if (extended()) { + outputStream.writeInt(change.getLevel()); + outputStream.writeInt(change.getDataptr()); + } + switch (change.getVartype()) { + case 0: + outputStream.writeInt(change.getLongval()); + break; + case 3: + charBuffer.clear(); + byteBuffer.clear(); + charBuffer.put(change.getStrval()); + charBuffer.flip(); + encoder.encode(charBuffer, byteBuffer, false); + byteBuffer.flip(); + final byte[] stringBytes = new byte[byteBuffer.remaining() + 1]; + int i = 0; + while (byteBuffer.hasRemaining()) { + stringBytes[i++] = byteBuffer.get(); + } + stringBytes[i] = 0; + outputStream.write(stringBytes); + break; + case 4: + outputStream.writeInt(change.isBoolval() ? 1 : 0); + break; + default: + outputStream.writeFloat(change.getRealval()); + break; + } + // if (change.getJunkDNA() == null) { + // saveWriteChars(outputStream, cl.getNewId().asStringValue().toCharArray()); + // } else { + // saveWriteChars(outputStream, + // change.getJunkDNA().asStringValue().toCharArray()); + // } + // saveWriteChars(outputStream, cl.getNewId().asStringValue().toCharArray()); + ParseUtils.writeWar3ID(outputStream, noid); + } + } + } + } + return true; + } + + public boolean save(final LittleEndianDataOutputStream outputStream, final boolean generateWTS) throws IOException { + if (generateWTS) { + throw new UnsupportedOperationException("FAIL cannot generate WTS, needs more code"); + } + this.version = 2; + outputStream.writeInt(this.version); + if (!saveTable(outputStream, this.original, true)) { + throw new RuntimeException("Failed to save standard unit custom data"); + } + if (!saveTable(outputStream, this.custom, false)) { + throw new RuntimeException("Failed to save custom unit custom data"); + } + return true; + } + + public ObjectMap getOriginal() { + return this.original; + } + + public ObjectMap getCustom() { + return this.custom; + } + + private static void debugprint(final String s) { + + } +} diff --git a/core/src/com/etheller/warsmash/units/manager/MutableObjectData.java b/core/src/com/etheller/warsmash/units/manager/MutableObjectData.java new file mode 100644 index 0000000..95a7a60 --- /dev/null +++ b/core/src/com/etheller/warsmash/units/manager/MutableObjectData.java @@ -0,0 +1,922 @@ +package com.etheller.warsmash.units.manager; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.etheller.warsmash.units.GameObject; +import com.etheller.warsmash.units.ObjectData; +import com.etheller.warsmash.units.custom.Change; +import com.etheller.warsmash.units.custom.ChangeMap; +import com.etheller.warsmash.units.custom.ObjectDataChangeEntry; +import com.etheller.warsmash.units.custom.War3ObjectDataChangeset; +import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.util.WorldEditStrings; + +public final class MutableObjectData { + private static final War3ID ROC_SUPPORT_URAC = War3ID.fromString("urac"); + private static final War3ID ROC_SUPPORT_UCAM = War3ID.fromString("ucam"); + private static final War3ID ROC_SUPPORT_USPE = War3ID.fromString("uspe"); + private static final War3ID ROC_SUPPORT_UBDG = War3ID.fromString("ubdg"); + + private final WorldEditorDataType worldEditorDataType; + private final ObjectData sourceSLKData; + private final ObjectData sourceSLKMetaData; + private final War3ObjectDataChangeset editorData; + private Set cachedKeySet; + private final Map metaNameToMetaId; + private final Map cachedKeyToGameObject; + private final MutableObjectDataChangeNotifier changeNotifier; + private final WorldEditStrings worldEditStrings; + + public MutableObjectData(final WorldEditStrings worldEditStrings, final WorldEditorDataType worldEditorDataType, + final ObjectData sourceSLKData, final ObjectData sourceSLKMetaData, + final War3ObjectDataChangeset editorData) { + this.worldEditStrings = worldEditStrings; + this.worldEditorDataType = worldEditorDataType; + resolveStringReferencesInNames(sourceSLKData); + this.sourceSLKData = sourceSLKData; + this.sourceSLKMetaData = sourceSLKMetaData; + this.editorData = editorData; + this.metaNameToMetaId = new HashMap<>(); + for (final String metaKeyString : sourceSLKMetaData.keySet()) { + final War3ID metaKey = War3ID.fromString(metaKeyString); + this.metaNameToMetaId.put(sourceSLKMetaData.get(metaKeyString).getField("field"), metaKey); + } + this.cachedKeyToGameObject = new HashMap<>(); + this.changeNotifier = new MutableObjectDataChangeNotifier(); + } + + // TODO remove this hack + public War3ObjectDataChangeset getEditorData() { + return this.editorData; + } + + private void resolveStringReferencesInNames(final ObjectData sourceSLKData) { + for (final String key : sourceSLKData.keySet()) { + final GameObject gameObject = sourceSLKData.get(key); + String name = gameObject.getField("Name"); + final String suffix = gameObject.getField("EditorSuffix"); + if (name.startsWith("WESTRING")) { + if (!name.contains(" ")) { + name = this.worldEditStrings.getString(name); + } + else { + final String[] names = name.split(" "); + name = ""; + for (final String subName : names) { + if (name.length() > 0) { + name += " "; + } + if (subName.startsWith("WESTRING")) { + name += this.worldEditStrings.getString(subName); + } + else { + name += subName; + } + } + } + if (name.startsWith("\"") && name.endsWith("\"")) { + name = name.substring(1, name.length() - 1); + } + gameObject.setField("Name", name); + } + if (suffix.startsWith("WESTRING")) { + gameObject.setField("EditorSuffix", this.worldEditStrings.getString(suffix)); + } + } + } + + public void mergeChangset(final War3ObjectDataChangeset changeset) { + final List newObjects = new ArrayList<>(); + final Map previousAliasToNewAlias = new HashMap<>(); + for (final Map.Entry entry : changeset.getCustom()) { + +// final String newId = JOptionPane.showInputDialog("Choose UNIT ID"); + final War3ID nextDefaultEditorId = /* War3ID.fromString(newId); */getNextDefaultEditorId( + War3ID.fromString(entry.getKey().charAt(0) + "000")); + ; + System.out.println("Merging " + nextDefaultEditorId + " for " + entry.getKey()); + // createNew API will notifier the changeNotifier + final MutableGameObject newObject = createNew(nextDefaultEditorId, entry.getValue().getOldId(), false); + for (final Map.Entry> changeList : entry.getValue().getChanges()) { + newObject.customUnitData.getChanges().add(changeList.getKey(), changeList.getValue()); + } + newObjects.add(nextDefaultEditorId); + previousAliasToNewAlias.put(entry.getKey(), nextDefaultEditorId); + } + final War3ID[] fieldsToCheck = this.worldEditorDataType == WorldEditorDataType.UNITS + ? new War3ID[] { War3ID.fromString("utra"), War3ID.fromString("uupt"), War3ID.fromString("ubui") } + : new War3ID[] {}; + for (final War3ID unitId : newObjects) { + final MutableGameObject unit = get(unitId); + for (final War3ID field : fieldsToCheck) { + final String techtreeString = unit.getFieldAsString(field, 0); + final java.util.List techList = Arrays.asList(techtreeString.split(",")); + final ArrayList resultingTechList = new ArrayList<>(); + for (final String tech : techList) { + if (tech.length() == 4) { + final War3ID newTechId = previousAliasToNewAlias.get(War3ID.fromString(tech)); + if (newTechId != null) { + resultingTechList.add(newTechId.toString()); + } + else { + resultingTechList.add(tech); + } + } + else { + resultingTechList.add(tech); + } + } + final StringBuilder sb = new StringBuilder(); + for (final String tech : resultingTechList) { + if (sb.length() > 0) { + sb.append(","); + } + sb.append(tech); + } + unit.setField(field, 0, sb.toString()); + } + } + this.changeNotifier.objectsCreated(newObjects.toArray(new War3ID[newObjects.size()])); + } + + public War3ObjectDataChangeset copySelectedObjects(final List objectsToCopy) { + final War3ObjectDataChangeset changeset = new War3ObjectDataChangeset(this.editorData.getExpectedKind()); + final War3ID[] fieldsToCheck = this.worldEditorDataType == WorldEditorDataType.UNITS + ? new War3ID[] { War3ID.fromString("utra"), War3ID.fromString("uupt"), War3ID.fromString("ubui") } + : new War3ID[] {}; + final Map previousAliasToNewAlias = new HashMap<>(); + for (final MutableGameObject gameObject : objectsToCopy) { + final ObjectDataChangeEntry gameObjectUserDataToCopy; + final ObjectDataChangeEntry gameObjectUserData; + final War3ID alias = gameObject.getAlias(); + if (this.editorData.getOriginal().containsKey(alias)) { + gameObjectUserDataToCopy = this.editorData.getOriginal().get(alias); + final War3ID newAlias = getNextDefaultEditorId( + War3ID.fromString(gameObject.getCode().charAt(0) + "000"), changeset, this.sourceSLKData); + gameObjectUserData = new ObjectDataChangeEntry(gameObjectUserDataToCopy.getOldId(), newAlias); + } + else if (this.editorData.getCustom().containsKey(alias)) { + gameObjectUserDataToCopy = this.editorData.getCustom().get(alias); + gameObjectUserData = new ObjectDataChangeEntry(gameObjectUserDataToCopy.getOldId(), + gameObjectUserDataToCopy.getNewId()); + } + else { + gameObjectUserDataToCopy = null; + final War3ID newAlias = getNextDefaultEditorId( + War3ID.fromString(gameObject.getCode().charAt(0) + "000"), changeset, this.sourceSLKData); + gameObjectUserData = new ObjectDataChangeEntry( + gameObject.isCustom() ? gameObject.getCode() : gameObject.getAlias(), newAlias); + } + if (gameObjectUserDataToCopy != null) { + for (final Map.Entry> changeEntry : gameObjectUserDataToCopy.getChanges()) { + for (final Change change : changeEntry.getValue()) { + final Change newChange = new Change(); + newChange.copyFrom(change); + gameObjectUserData.getChanges().add(change.getId(), newChange); + } + } + } + previousAliasToNewAlias.put(gameObject.getAlias(), gameObjectUserData.getNewId()); + changeset.getCustom().put(gameObjectUserData.getNewId(), gameObjectUserData); + } + final MutableObjectData changeEditManager = new MutableObjectData(this.worldEditStrings, + this.worldEditorDataType, this.sourceSLKData, this.sourceSLKMetaData, changeset); + for (final War3ID unitId : changeEditManager.keySet()) { + final MutableGameObject unit = changeEditManager.get(unitId); + for (final War3ID field : fieldsToCheck) { + final String techtreeString = unit.getFieldAsString(field, 0); + final java.util.List techList = Arrays.asList(techtreeString.split(",")); + final ArrayList resultingTechList = new ArrayList<>(); + for (final String tech : techList) { + if (tech.length() == 4) { + final War3ID newTechId = previousAliasToNewAlias.get(War3ID.fromString(tech)); + if (newTechId != null) { + resultingTechList.add(newTechId.toString()); + } + else { + resultingTechList.add(tech); + } + } + else { + resultingTechList.add(tech); + } + } + final StringBuilder sb = new StringBuilder(); + for (final String tech : resultingTechList) { + if (sb.length() > 0) { + sb.append(","); + } + sb.append(tech); + } + unit.setField(field, 0, sb.toString()); + } + } + return changeset; + + } + + public WorldEditorDataType getWorldEditorDataType() { + return this.worldEditorDataType; + } + + public ObjectData getSourceSLKMetaData() { + return this.sourceSLKMetaData; + } + + public void addChangeListener(final MutableObjectDataChangeListener listener) { + this.changeNotifier.subscribe(listener); + } + + public void removeChangeListener(final MutableObjectDataChangeListener listener) { + this.changeNotifier.unsubscribe(listener); + } + + /** + * Returns the set of all Unit IDs in the map, at the cost of a lot of time to + * go find them all. + * + * @return + */ + + public Set keySet() { + if (this.cachedKeySet == null) { + final Set customUnitKeys = this.editorData.getCustom().keySet(); + final Set customKeys = new HashSet<>(customUnitKeys); + for (final String standardUnitKey : this.sourceSLKData.keySet()) { + customKeys.add(War3ID.fromString(standardUnitKey)); + } + this.cachedKeySet = customKeys; + } + return this.cachedKeySet; + } + + public void dropCachesHack() { + this.cachedKeySet = null; + this.cachedKeyToGameObject.clear(); + } + + public MutableGameObject get(final War3ID id) { + MutableGameObject mutableGameObject = this.cachedKeyToGameObject.get(id); + if (mutableGameObject == null) { + if (this.editorData.getCustom().containsKey(id)) { + final ObjectDataChangeEntry customUnitData = this.editorData.getCustom().get(id); + mutableGameObject = new MutableGameObject( + this.sourceSLKData.get(customUnitData.getOldId().asStringValue()), customUnitData); + this.cachedKeyToGameObject.put(id, mutableGameObject); + } + else if (this.editorData.getOriginal().containsKey(id)) { + final ObjectDataChangeEntry customUnitData = this.editorData.getOriginal().get(id); + mutableGameObject = new MutableGameObject( + this.sourceSLKData.get(customUnitData.getOldId().asStringValue()), + this.editorData.getOriginal().get(id)); + this.cachedKeyToGameObject.put(id, mutableGameObject); + } + else if (this.sourceSLKData.get(id.asStringValue()) != null) { + mutableGameObject = new MutableGameObject(this.sourceSLKData.get(id.asStringValue()), null); + this.cachedKeyToGameObject.put(id, mutableGameObject); + } + } + return mutableGameObject; + } + + public MutableGameObject createNew(final War3ID id, final War3ID parent) { + return createNew(id, parent, true); + } + + private MutableGameObject createNew(final War3ID id, final War3ID parent, final boolean fireListeners) { + this.editorData.getCustom().put(id, new ObjectDataChangeEntry(parent, id)); + if (this.cachedKeySet != null) { + this.cachedKeySet.add(id); + } + if (fireListeners) { + this.changeNotifier.objectCreated(id); + } + return get(id); + } + + public void remove(final War3ID id) { + remove(id, true); + } + + public void remove(final List objects) { + final List removedIds = new ArrayList<>(); + for (final MutableGameObject object : objects) { + if (object.isCustom()) { + remove(object.getAlias(), false); + removedIds.add(object.getAlias()); + } + } + this.changeNotifier.objectsRemoved(removedIds.toArray(new War3ID[removedIds.size()])); + } + + private MutableGameObject remove(final War3ID id, final boolean fireListeners) { + final ObjectDataChangeEntry removedObject = this.editorData.getCustom().remove(id); + final MutableGameObject removedMutableObj = this.cachedKeyToGameObject.remove(id); + if (this.cachedKeySet != null) { + this.cachedKeySet.remove(id); + } + if (fireListeners) { + this.changeNotifier.objectRemoved(id); + } + return removedMutableObj /* might be null based on cache, don't use */; + } + + private static boolean goodForId(final char c) { + return Character.isDigit(c) || ((c >= 'A') && (c <= 'Z')); + } + + public War3ID getNextDefaultEditorId(final War3ID startingId) { + War3ID newId = startingId; + while (this.editorData.getCustom().containsKeyCaseInsensitive(newId) + || (this.sourceSLKData.get(newId.toString()) != null) || !goodForId(newId.charAt(1)) + || !goodForId(newId.charAt(2)) || !goodForId(newId.charAt(3))) { + // TODO good code general solution + if (newId.charAt(3) == 'Z') { + if (newId.charAt(2) == 'Z') { + if (newId.charAt(1) == 'Z') { + newId = new War3ID(((newId.getValue() / (256 * 256 * 256)) * 256 * 256 * 256) + + (256 * 256 * 256) + '0' + ('0' * 256) + ('0' * 256 * 256)); + } + else { + newId = new War3ID( + ((newId.getValue() / (256 * 256)) * 256 * 256) + (256 * 256) + '0' + ('0' * 256)); + } + } + else { + newId = new War3ID(((newId.getValue() / 256) * 256) + 256 + '0'); + } + } + else { + newId = new War3ID(newId.getValue() + 1); + } + } + return newId; + } + + public static War3ID getNextDefaultEditorId(final War3ID startingId, final War3ObjectDataChangeset editorData, + final ObjectData sourceSLKData) { + War3ID newId = startingId; + while (editorData.getCustom().containsKeyCaseInsensitive(newId) || (sourceSLKData.get(newId.toString()) != null) + || !goodForId(newId.charAt(1)) || !goodForId(newId.charAt(2)) || !goodForId(newId.charAt(3))) { + newId = new War3ID(newId.getValue() + 1); + } + return newId; + } + + private static final War3ID BUFF_EDITOR_NAME = War3ID.fromString("fnam"); + private static final War3ID BUFF_BUFFTIP = War3ID.fromString("ftip"); + private static final War3ID UNIT_CAMPAIGN = War3ID.fromString("ucam"); + private static final War3ID UNIT_EDITOR_SUFFIX = War3ID.fromString("unsf"); + private static final War3ID ABIL_EDITOR_SUFFIX = War3ID.fromString("ansf"); + private static final War3ID DESTRUCTABLE_EDITOR_SUFFIX = War3ID.fromString("bsuf"); + private static final War3ID BUFF_EDITOR_SUFFIX = War3ID.fromString("fnsf"); + private static final War3ID UPGRADE_EDITOR_SUFFIX = War3ID.fromString("gnsf"); + private static final War3ID HERO_PROPER_NAMES = War3ID.fromString("upro"); + + private static final Set CATEGORY_FIELDS = new HashSet<>(); + private static final Set TEXT_FIELDS = new HashSet<>(); + private static final Set ICON_FIELDS = new HashSet<>(); + private static final Set FIELD_SETTINGS_FIELDS = new HashSet<>(); + + static { + // categorizing - I thought these would be changeFlags value "c", but no luck + CATEGORY_FIELDS.add(War3ID.fromString("ubdg")); // is a building + CATEGORY_FIELDS.add(War3ID.fromString("uspe")); // categorize special + CATEGORY_FIELDS.add(War3ID.fromString("ucam")); // categorize campaign + CATEGORY_FIELDS.add(War3ID.fromString("urac")); // race + CATEGORY_FIELDS.add(War3ID.fromString("uine")); // in editor + CATEGORY_FIELDS.add(War3ID.fromString("ucls")); // sort string (not a real field, fanmade) + + CATEGORY_FIELDS.add(War3ID.fromString("icla")); // item class + + CATEGORY_FIELDS.add(War3ID.fromString("bcat")); // destructible category + + CATEGORY_FIELDS.add(War3ID.fromString("dcat")); // doodad category + + CATEGORY_FIELDS.add(War3ID.fromString("aher")); // hero ability + CATEGORY_FIELDS.add(War3ID.fromString("aite")); // item ability + CATEGORY_FIELDS.add(War3ID.fromString("arac")); // ability race + + CATEGORY_FIELDS.add(War3ID.fromString("frac")); // buff race + CATEGORY_FIELDS.add(War3ID.fromString("feff")); // is effect + + CATEGORY_FIELDS.add(War3ID.fromString("grac")); // upgrade race + // field structure fields - doesn't seem to be changeFlags 's' like you might + // hope + FIELD_SETTINGS_FIELDS.add(War3ID.fromString("ubdg")); // unit is a builder + FIELD_SETTINGS_FIELDS.add(War3ID.fromString("dvar")); // doodad variations + FIELD_SETTINGS_FIELDS.add(War3ID.fromString("alev")); // ability level + FIELD_SETTINGS_FIELDS.add(War3ID.fromString("glvl")); // upgrade max level + } + + public final class MutableGameObject { + private final GameObject parentWC3Object; + private ObjectDataChangeEntry customUnitData; + + private void fireChangedEvent(final War3ID field, final int level) { + final String changeFlags = MutableObjectData.this.sourceSLKMetaData.get(field.toString()) + .getField("changeFlags"); + if (CATEGORY_FIELDS.contains(field)) { + MutableObjectData.this.changeNotifier.categoriesChanged(getAlias()); + } + else if (changeFlags.contains("t")) { + MutableObjectData.this.changeNotifier.textChanged(getAlias()); + } + else if (changeFlags.contains("m")) { + MutableObjectData.this.changeNotifier.modelChanged(getAlias()); + } + else if (changeFlags.contains("i")) { + MutableObjectData.this.changeNotifier.iconsChanged(getAlias()); + } + else if (FIELD_SETTINGS_FIELDS.contains(field)) { + MutableObjectData.this.changeNotifier.fieldsChanged(getAlias()); + } + } + + public MutableGameObject(final GameObject parentWC3Object, final ObjectDataChangeEntry customUnitData) { + this.parentWC3Object = parentWC3Object; + if (parentWC3Object == null) { + System.err.println( + "Parent object is null for " + customUnitData.getNewId() + ":" + customUnitData.getOldId()); + throw new AssertionError("parentWC3Object cannot be null"); +// this.parentWC3Object = new Element("", new DataTable()); + } + this.customUnitData = customUnitData; + } + + public boolean hasCustomField(final War3ID field, final int level) { + return getMatchingChange(field, level) != null; + } + + public boolean hasEditorData() { + return (this.customUnitData != null) && (this.customUnitData.getChanges().size() > 0); + } + + public boolean isCustom() { + return MutableObjectData.this.editorData.getCustom().containsKey(getAlias()); + } + + public void setField(final War3ID field, final int level, final String value) { + if (value.equals(getFieldStringFromSLKs(field, level))) { + if (!value.equals(getFieldAsString(field, level))) { + fireChangedEvent(field, level); + System.out.println("field was reset"); + } + else { + System.out.println("field was unmodified"); + } + resetFieldToDefaults(field, level); + return; + } + final Change matchingChange = getOrCreateMatchingChange(field, level); + matchingChange.setStrval(value); + matchingChange.setVartype(War3ObjectDataChangeset.VAR_TYPE_STRING); + System.out.println("field created change"); + fireChangedEvent(field, level); + } + + public void setField(final War3ID field, final int level, final boolean value) { + if (value == (asInt(getFieldStringFromSLKs(field, level).trim()) == 1)) { + if (value != getFieldAsBoolean(field, level)) { + fireChangedEvent(field, level); + } + resetFieldToDefaults(field, level); + return; + } + final Change matchingChange = getOrCreateMatchingChange(field, level); + matchingChange.setBoolval(value); + matchingChange.setVartype(War3ObjectDataChangeset.VAR_TYPE_BOOLEAN); + fireChangedEvent(field, level); + } + + public void setField(final War3ID field, final int level, final int value) { + if (value == asInt(getFieldStringFromSLKs(field, level).trim())) { + if (value != getFieldAsInteger(field, level)) { + fireChangedEvent(field, level); + } + resetFieldToDefaults(field, level); + return; + } + final Change matchingChange = getOrCreateMatchingChange(field, level); + matchingChange.setLongval(value); + matchingChange.setVartype(War3ObjectDataChangeset.VAR_TYPE_INT); + fireChangedEvent(field, level); + } + + public void resetFieldToDefaults(final War3ID field, final int level) { + final Change existingChange = getMatchingChange(field, level); + if ((existingChange != null) && (this.customUnitData != null)) { + this.customUnitData.getChanges().delete(field, existingChange); + fireChangedEvent(field, level); + } + return; + } + + public void setField(final War3ID field, final int level, final float value) { + if (Math.abs(value - asFloat(getFieldStringFromSLKs(field, level).trim())) < 0.00001f) { + if (Math.abs(value - getFieldAsFloat(field, level)) > 0.00001f) { + fireChangedEvent(field, level); + } + resetFieldToDefaults(field, level); + return; + } + final Change matchingChange = getOrCreateMatchingChange(field, level); + matchingChange.setRealval(value); + final boolean unsigned = MutableObjectData.this.sourceSLKMetaData.get(field.asStringValue()) + .getField("type").equals("unreal"); + matchingChange.setVartype( + unsigned ? War3ObjectDataChangeset.VAR_TYPE_UNREAL : War3ObjectDataChangeset.VAR_TYPE_REAL); + fireChangedEvent(field, level); + } + + private Change getOrCreateMatchingChange(final War3ID field, final int level) { + if (this.customUnitData == null) { + final War3ID war3Id = War3ID.fromString(this.parentWC3Object.getId()); + final ObjectDataChangeEntry newCustomUnitData = new ObjectDataChangeEntry(war3Id, War3ID.NONE); + MutableObjectData.this.editorData.getOriginal().put(war3Id, newCustomUnitData); + this.customUnitData = newCustomUnitData; + } + Change matchingChange = getMatchingChange(field, level); + if (matchingChange == null) { + final ChangeMap changeMap = this.customUnitData.getChanges(); + final List changeList = changeMap.get(field); + matchingChange = new Change(); + matchingChange.setId(field); + matchingChange.setLevel(level); + if (MutableObjectData.this.editorData.extended()) { + // dunno why, but Blizzard sure likes those dataptrs in the ability data + // my code should grab 0 when the metadata lacks this field + matchingChange.setDataptr( + MutableObjectData.this.sourceSLKMetaData.get(field.asStringValue()).getFieldValue("data")); + } + if (changeList == null) { + changeMap.add(field, matchingChange); + } + else { + boolean insertedChange = false; + for (int i = 0; i < changeList.size(); i++) { + if (changeList.get(i).getLevel() > level) { + insertedChange = true; + changeList.add(i, matchingChange); + break; + } + } + if (!insertedChange) { + changeList.add(changeList.size(), matchingChange); + } + } + } + return matchingChange; + } + + public String getFieldAsString(final War3ID field, final int level) { + final Change matchingChange = getMatchingChange(field, level); + if (matchingChange != null) { + if (matchingChange.getVartype() != War3ObjectDataChangeset.VAR_TYPE_STRING) { + throw new IllegalStateException( + "Requested string value of '" + field + "' from '" + this.parentWC3Object.getId() + + "', but this field was not a string! vartype=" + matchingChange.getVartype()); + } + return matchingChange.getStrval(); + } + // no luck with custom data, look at the standard data + int slkLevel = level; + if (MutableObjectData.this.worldEditorDataType == WorldEditorDataType.UPGRADES) { + slkLevel -= 1; + } + return getFieldStringFromSLKs(field, slkLevel); + } + + private Change getMatchingChange(final War3ID field, final int level) { + Change matchingChange = null; + if (this.customUnitData == null) { + return null; + } + final List changeList = this.customUnitData.getChanges().get(field); + if (changeList != null) { + for (final Change change : changeList) { + if (change.getLevel() == level) { + matchingChange = change; + break; + } + } + } + return matchingChange; + } + + public String readSLKTag(final String key) { + if (MutableObjectData.this.metaNameToMetaId.containsKey(key)) { + return getFieldAsString(MutableObjectData.this.metaNameToMetaId.get(key), 0); + } + return this.parentWC3Object.getField(key); + } + + public boolean readSLKTagBoolean(final String key) { + if (MutableObjectData.this.metaNameToMetaId.containsKey(key)) { + return getFieldAsBoolean(MutableObjectData.this.metaNameToMetaId.get(key), 0); + } + return this.parentWC3Object.getFieldValue(key) == 1; + } + + public String getName() { + String name = getFieldAsString(MutableObjectData.this.editorData.getNameField(), + MutableObjectData.this.worldEditorDataType == WorldEditorDataType.UPGRADES ? 1 : 0); + boolean nameKnown = name.length() >= 1; + if (!nameKnown && !readSLKTag("code").equals(getAlias().toString()) && (readSLKTag("code").length() >= 4) + && !isCustom()) { + final MutableGameObject codeObject = get(War3ID.fromString(readSLKTag("code").substring(0, 4))); + if (codeObject != null) { + name = codeObject.getName(); + nameKnown = true; + } + } + String suf = ""; + switch (MutableObjectData.this.worldEditorDataType) { + case ABILITIES: + suf = getFieldAsString(ABIL_EDITOR_SUFFIX, 0); + break; + case BUFFS_EFFECTS: + final String editorName = getFieldAsString(BUFF_EDITOR_NAME, 0); + if (!nameKnown && (editorName.length() > 1)) { + name = editorName; + nameKnown = true; + } + final String buffTip = getFieldAsString(BUFF_BUFFTIP, 0); + if (!nameKnown && (buffTip.length() > 1)) { + name = buffTip; + nameKnown = true; + } + suf = getFieldAsString(BUFF_EDITOR_SUFFIX, 0); + break; + case DESTRUCTIBLES: + suf = getFieldAsString(DESTRUCTABLE_EDITOR_SUFFIX, 0); + break; + case DOODADS: + break; + case ITEM: + break; + case UNITS: + if (getFieldAsBoolean(UNIT_CAMPAIGN, 0) && Character.isUpperCase(getAlias().charAt(0))) { + name = getFieldAsString(HERO_PROPER_NAMES, 0); + if (name.contains(",")) { + name = name.split(",")[0]; + } + } + suf = getFieldAsString(UNIT_EDITOR_SUFFIX, 0); + break; + case UPGRADES: + suf = getFieldAsString(UPGRADE_EDITOR_SUFFIX, 1); + break; + } + if (nameKnown/* && name.startsWith("WESTRING") */) { + if (!name.contains(" ")) { + // name = WEString.getString(name); + } + else { + final String[] names = name.split(" "); + name = ""; + for (final String subName : names) { + if (name.length() > 0) { + name += " "; + } + // if (subName.startsWith("WESTRING")) { + // name += WEString.getString(subName); + // } else { + name += subName; + // } + } + } + if (name.startsWith("\"") && name.endsWith("\"")) { + name = name.substring(1, name.length() - 1); + } + } + if (!nameKnown) { + name = MutableObjectData.this.worldEditStrings.getString("WESTRING_UNKNOWN") + " '" + + getAlias().toString() + "'"; + } + if ((suf.length() > 0) && !suf.equals("_")) { + // if (suf.startsWith("WESTRING")) { + // suf = WEString.getString(suf); + // } + if (!suf.startsWith(" ")) { + name += " "; + } + name += suf; + } + return name; + } + + private String getFieldStringFromSLKs(final War3ID field, final int level) { + final GameObject metaData = MutableObjectData.this.sourceSLKMetaData.get(field.asStringValue()); + if (metaData == null) { + if (MutableObjectData.this.worldEditorDataType == WorldEditorDataType.UNITS) { + if (ROC_SUPPORT_URAC.equals(field)) { + return this.parentWC3Object.getField("race"); + } + else if (ROC_SUPPORT_UCAM.equals(field)) { + return "0"; + } + else if (ROC_SUPPORT_USPE.equals(field)) { + return this.parentWC3Object.getField("special"); + } + else if (ROC_SUPPORT_UBDG.equals(field)) { + return this.parentWC3Object.getField("isbldg"); + } + } + throw new IllegalStateException("Program requested " + field.toString() + " from " + + MutableObjectData.this.worldEditorDataType); + } + if (this.parentWC3Object == null) { + throw new IllegalStateException("corrupted unit, no parent unit id"); + } + int index = metaData.getFieldValue("index"); + final String upgradeHack = metaData.getField("appendIndex"); + if ("0".equals(upgradeHack)) { + // Engage magic upgrade hack to replace index with level + if (!field.toString().equals("gbpx") && !field.toString().equals("gbpy")) { + index = level; + } + } + else if ((index != -1) && (level > 0)) { + index = level - 1; + } + if (index != -1) { + final String fieldStringValue = this.parentWC3Object + .getField(getEditorMetaDataDisplayKey(level, metaData), index); + return fieldStringValue; + } + final String fieldStringValue = this.parentWC3Object.getField(getEditorMetaDataDisplayKey(level, metaData)); + return fieldStringValue; + } + + public int getFieldAsInteger(final War3ID field, final int level) { + final Change matchingChange = getMatchingChange(field, level); + if (matchingChange != null) { + if (matchingChange.getVartype() != War3ObjectDataChangeset.VAR_TYPE_INT) { + throw new IllegalStateException( + "Requested integer value of '" + field + "' from '" + this.parentWC3Object.getId() + + "', but this field was not an int! vartype=" + matchingChange.getVartype()); + } + return matchingChange.getLongval(); + } + // no luck with custom data, look at the standard data + try { + return Integer.parseInt(getFieldStringFromSLKs(field, level)); + } + catch (final NumberFormatException e) { + return 0; + } + } + + public boolean getFieldAsBoolean(final War3ID field, final int level) { + final Change matchingChange = getMatchingChange(field, level); + if (matchingChange != null) { + if (matchingChange.getVartype() != War3ObjectDataChangeset.VAR_TYPE_BOOLEAN) { + if (matchingChange.getVartype() == War3ObjectDataChangeset.VAR_TYPE_INT) { + return matchingChange.getLongval() == 1; + } + else { + throw new IllegalStateException( + "Requested boolean value of '" + field + "' from '" + this.parentWC3Object.getId() + + "', but this field was not a bool! vartype=" + matchingChange.getVartype()); + } + } + return matchingChange.isBoolval(); + } + // no luck with custom data, look at the standard data + try { + return Integer.parseInt(getFieldStringFromSLKs(field, level)) == 1; + } + catch (final NumberFormatException e) { + return false; + } + } + + public float getFieldAsFloat(final War3ID field, final int level) { + final Change matchingChange = getMatchingChange(field, level); + if (matchingChange != null) { + if ((matchingChange.getVartype() != War3ObjectDataChangeset.VAR_TYPE_REAL) + && (matchingChange.getVartype() != War3ObjectDataChangeset.VAR_TYPE_UNREAL)) { + throw new IllegalStateException( + "Requested float value of '" + field + "' from '" + this.parentWC3Object.getId() + + "', but this field was not a float! vartype=" + matchingChange.getVartype()); + } + return matchingChange.getRealval(); + } + // no luck with custom data, look at the standard data + try { + return Float.parseFloat(getFieldStringFromSLKs(field, level)); + } + catch (final NumberFormatException e) { + return 0; + } + } + + public War3ID getAlias() { + if (this.customUnitData == null) { + return War3ID.fromString(this.parentWC3Object.getId()); + } + if (War3ID.NONE.equals(this.customUnitData.getNewId())) { + return this.customUnitData.getOldId(); + } + return this.customUnitData.getNewId(); + } + + public War3ID getCode() { + if (this.customUnitData == null) { + if ((MutableObjectData.this.worldEditorDataType == WorldEditorDataType.ABILITIES) + || (MutableObjectData.this.worldEditorDataType == WorldEditorDataType.BUFFS_EFFECTS)) { + return War3ID.fromString(this.parentWC3Object.getField("code")); + } + else { + return War3ID.fromString(this.parentWC3Object.getId()); + } + } + if (War3ID.NONE.equals(this.customUnitData.getNewId())) { + if ((MutableObjectData.this.worldEditorDataType == WorldEditorDataType.ABILITIES) + || (MutableObjectData.this.worldEditorDataType == WorldEditorDataType.BUFFS_EFFECTS)) { + return War3ID.fromString(this.parentWC3Object.getField("code")); + } + else { + return this.customUnitData.getOldId(); + } + } + return this.customUnitData.getOldId(); + } + + } + + private static int asInt(final String text) { + return text == null ? 0 + : "".equals(text) ? 0 : "-".equals(text) ? 0 : "_".equals(text) ? 0 : Integer.parseInt(text); + } + + private static float asFloat(final String text) { + return text == null ? 0 + : "".equals(text) ? 0 : "-".equals(text) ? 0 : "_".equals(text) ? 0 : Float.parseFloat(text); + } + + public enum WorldEditorDataType { + UNITS("w3u"), + ITEM("w3t"), + DESTRUCTIBLES("w3b"), + DOODADS("w3d"), + ABILITIES("w3a"), + BUFFS_EFFECTS("w3h"), + UPGRADES("w3q"); + + private String extension; + + private WorldEditorDataType(final String extension) { + this.extension = extension; + } + + public String getExtension() { + return this.extension; + } + } + + public static String getEditorMetaDataDisplayKey(int level, final GameObject metaData) { + final int index = metaData.getFieldValue("index"); + String metaDataName = metaData.getField("field"); + final int repeatCount = metaData.getFieldValue("repeat"); + final String upgradeHack = metaData.getField("appendIndex"); + final boolean repeats = (repeatCount > 0) && !"0".equals(upgradeHack); + final int data = metaData.getFieldValue("data"); + if (data > 0) { + metaDataName += (char) ('A' + (data - 1)); + } + if ("1".equals(upgradeHack)) { + final int upgradeExtensionLevel = level - 1; + if (upgradeExtensionLevel > 0) { + metaDataName += Integer.toString(upgradeExtensionLevel); + } + } + else if (repeats && (index == -1)) { + if (level == 0) { + level = 1; + } + if (repeatCount >= 10) { + metaDataName += String.format("%2d", level).replace(' ', '0'); + } + else { + metaDataName += Integer.toString(level); + } + } + return metaDataName; + } + + public static String getDisplayAsRawDataName(final MutableGameObject gameObject) { + String aliasString = gameObject.getAlias().toString(); + if (!gameObject.getAlias().equals(gameObject.getCode())) { + aliasString += ":" + gameObject.getCode().toString(); + } + return aliasString + " (" + gameObject.getName() + ")"; + } +} diff --git a/core/src/com/etheller/warsmash/units/manager/MutableObjectDataChangeListener.java b/core/src/com/etheller/warsmash/units/manager/MutableObjectDataChangeListener.java new file mode 100644 index 0000000..359863a --- /dev/null +++ b/core/src/com/etheller/warsmash/units/manager/MutableObjectDataChangeListener.java @@ -0,0 +1,23 @@ +package com.etheller.warsmash.units.manager; + +import com.etheller.warsmash.util.War3ID; + +public interface MutableObjectDataChangeListener { + void textChanged(War3ID changedObject); + + void iconsChanged(War3ID changedObject); + + void categoriesChanged(War3ID changedObject); + + void fieldsChanged(War3ID changedObject); + + void modelChanged(War3ID changedObject); + + void objectCreated(War3ID newObject); + + void objectsCreated(War3ID[] newObject); + + void objectRemoved(War3ID removedObject); + + void objectsRemoved(War3ID[] removedObject); +} diff --git a/core/src/com/etheller/warsmash/units/manager/MutableObjectDataChangeNotifier.java b/core/src/com/etheller/warsmash/units/manager/MutableObjectDataChangeNotifier.java new file mode 100644 index 0000000..1ea3dde --- /dev/null +++ b/core/src/com/etheller/warsmash/units/manager/MutableObjectDataChangeNotifier.java @@ -0,0 +1,72 @@ +package com.etheller.warsmash.units.manager; + +import com.etheller.warsmash.util.SubscriberSetNotifier; +import com.etheller.warsmash.util.War3ID; + +public final class MutableObjectDataChangeNotifier extends SubscriberSetNotifier + implements MutableObjectDataChangeListener { + + @Override + public void textChanged(final War3ID changedObject) { + for (final MutableObjectDataChangeListener listener : this.set) { + listener.textChanged(changedObject); + } + } + + @Override + public void categoriesChanged(final War3ID changedObject) { + for (final MutableObjectDataChangeListener listener : this.set) { + listener.categoriesChanged(changedObject); + } + } + + @Override + public void iconsChanged(final War3ID changedObject) { + for (final MutableObjectDataChangeListener listener : this.set) { + listener.iconsChanged(changedObject); + } + } + + @Override + public void fieldsChanged(final War3ID changedObject) { + for (final MutableObjectDataChangeListener listener : this.set) { + listener.fieldsChanged(changedObject); + } + } + + @Override + public void modelChanged(final War3ID changedObject) { + for (final MutableObjectDataChangeListener listener : this.set) { + listener.modelChanged(changedObject); + } + } + + @Override + public void objectCreated(final War3ID newObject) { + for (final MutableObjectDataChangeListener listener : this.set) { + listener.objectCreated(newObject); + } + } + + @Override + public void objectsCreated(final War3ID[] newObject) { + for (final MutableObjectDataChangeListener listener : this.set) { + listener.objectsCreated(newObject); + } + } + + @Override + public void objectRemoved(final War3ID newObject) { + for (final MutableObjectDataChangeListener listener : this.set) { + listener.objectRemoved(newObject); + } + } + + @Override + public void objectsRemoved(final War3ID[] newObject) { + for (final MutableObjectDataChangeListener listener : this.set) { + listener.objectsRemoved(newObject); + } + } + +} diff --git a/core/src/com/etheller/warsmash/util/ImageUtils.java b/core/src/com/etheller/warsmash/util/ImageUtils.java index 71dbb0b..9772bee 100644 --- a/core/src/com/etheller/warsmash/util/ImageUtils.java +++ b/core/src/com/etheller/warsmash/util/ImageUtils.java @@ -1,6 +1,5 @@ package com.etheller.warsmash.util; -import java.awt.Graphics2D; import java.awt.Transparency; import java.awt.color.ColorSpace; import java.awt.image.BufferedImage; @@ -8,6 +7,7 @@ import java.awt.image.ColorModel; import java.awt.image.ComponentColorModel; import java.awt.image.DataBuffer; +import com.badlogic.gdx.graphics.GL30; import com.badlogic.gdx.graphics.Pixmap; import com.badlogic.gdx.graphics.Pixmap.Format; import com.badlogic.gdx.graphics.Texture; @@ -30,14 +30,20 @@ public final class ImageUtils { // for // RGB - final Pixmap pixmap = new Pixmap(image.getWidth(), image.getHeight(), Format.RGBA8888); + final Pixmap pixmap = new Pixmap(image.getWidth(), image.getHeight(), Format.RGBA8888) { + @Override + public int getGLInternalFormat() { + return GL30.GL_SRGB8_ALPHA8; + } + }; for (int y = 0; y < image.getHeight(); y++) { for (int x = 0; x < image.getWidth(); x++) { final int pixel = pixels[(y * image.getWidth()) + x]; pixmap.drawPixel(x, y, (pixel << 8) | (pixel >>> 24)); } } - return new Texture(pixmap); + final Texture texture = new Texture(pixmap); + return texture; } /** @@ -66,12 +72,10 @@ public final class ImageUtils { DataBuffer.TYPE_BYTE); final BufferedImage lRGB = new BufferedImage(lRGBModel, lRGBModel.createCompatibleWritableRaster(in.getWidth(), in.getHeight()), false, null); - final Graphics2D graphic = lRGB.createGraphics(); - try { - graphic.drawImage(in, 0, 0, null); - } - finally { - graphic.dispose(); + for (int i = 0; i < in.getWidth(); i++) { + for (int j = 0; j < in.getHeight(); j++) { + lRGB.setRGB(i, j, in.getRGB(i, j)); + } } // Convert to sRGB. diff --git a/core/src/com/etheller/warsmash/util/MappedData.java b/core/src/com/etheller/warsmash/util/MappedData.java index 97b4915..dddb748 100644 --- a/core/src/com/etheller/warsmash/util/MappedData.java +++ b/core/src/com/etheller/warsmash/util/MappedData.java @@ -13,6 +13,10 @@ import java.util.Map; public class MappedData { private final Map map = new HashMap<>(); + public MappedData() { + this(null); + } + public MappedData(final String buffer) { if (buffer != null) { this.load(buffer); diff --git a/core/src/com/etheller/warsmash/util/ParseUtils.java b/core/src/com/etheller/warsmash/util/ParseUtils.java index 951df08..41e43cb 100644 --- a/core/src/com/etheller/warsmash/util/ParseUtils.java +++ b/core/src/com/etheller/warsmash/util/ParseUtils.java @@ -1,5 +1,6 @@ package com.etheller.warsmash.util; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.charset.Charset; @@ -49,6 +50,18 @@ public class ParseUtils { return array; } + public static void readInt32Array(final LittleEndianDataInputStream stream, final int[] array) throws IOException { + for (int i = 0; i < array.length; i++) { + array[i] = stream.readInt(); + } + } + + public static int[] readInt32Array(final LittleEndianDataInputStream stream, final int length) throws IOException { + final int[] array = new int[length]; + readInt32Array(stream, array); + return array; + } + public static void readUInt16Array(final LittleEndianDataInputStream stream, final int[] array) throws IOException { for (int i = 0; i < array.length; i++) { array[i] = readUInt16(stream); @@ -115,6 +128,13 @@ public class ParseUtils { } } + public static void writeInt32Array(final LittleEndianDataOutputStream stream, final int[] array) + throws IOException { + for (int i = 0; i < array.length; i++) { + stream.writeInt(array[i]); + } + } + public static void writeUInt16Array(final LittleEndianDataOutputStream stream, final int[] array) throws IOException { for (int i = 0; i < array.length; i++) { @@ -138,4 +158,21 @@ public class ParseUtils { final String name = new String(recycleByteArray, 0, i, ParseUtils.UTF8); return name; } + + public static String readUntilNull(final LittleEndianDataInputStream stream) throws IOException { + + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + int c; + while (((c = stream.read()) != 0) && (c != -1)) { + baos.write(c); + } + return new String(baos.toByteArray(), ParseUtils.UTF8); + } + + public static void writeWithNullTerminator(final LittleEndianDataOutputStream stream, final String name) + throws IOException { + final byte[] nameBytes = name.getBytes(ParseUtils.UTF8); + stream.write(nameBytes); + stream.write(0); + } } diff --git a/core/src/com/etheller/warsmash/util/RenderMathUtils.java b/core/src/com/etheller/warsmash/util/RenderMathUtils.java index 5ce1055..dea1abf 100644 --- a/core/src/com/etheller/warsmash/util/RenderMathUtils.java +++ b/core/src/com/etheller/warsmash/util/RenderMathUtils.java @@ -1,5 +1,10 @@ package com.etheller.warsmash.util; +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; +import java.nio.ShortBuffer; import java.util.List; import com.badlogic.gdx.math.Matrix4; @@ -9,6 +14,8 @@ import com.badlogic.gdx.math.Vector3; public enum RenderMathUtils { ; + public static final float[] EMPTY_FLOAT_ARRAY = new float[0]; + public static final long[] EMPTY_LONG_ARRAY = new long[0]; public static final Quaternion QUAT_DEFAULT = new Quaternion(0, 0, 0, 1); public static final Vector3 VEC3_ONE = new Vector3(1, 1, 1); public static final Vector3 VEC3_UNIT_X = new Vector3(1, 0, 0); @@ -42,22 +49,22 @@ public enum RenderMathUtils { final float sy = s.y; final float sz = s.z; out.val[Matrix4.M00] = (1 - (yy + zz)) * sx; - out.val[Matrix4.M01] = (xy + wz) * sx; - out.val[Matrix4.M02] = (xz - wy) * sx; - out.val[Matrix4.M03] = 0; - out.val[Matrix4.M10] = (xy - wz) * sy; + out.val[Matrix4.M10] = (xy + wz) * sx; + out.val[Matrix4.M20] = (xz - wy) * sx; + out.val[Matrix4.M30] = 0; + out.val[Matrix4.M01] = (xy - wz) * sy; out.val[Matrix4.M11] = (1 - (xx + zz)) * sy; - out.val[Matrix4.M12] = (yz + wx) * sy; - out.val[Matrix4.M13] = 0; - out.val[Matrix4.M20] = (xz + wy) * sz; - out.val[Matrix4.M21] = (yz - wx) * sz; + out.val[Matrix4.M21] = (yz + wx) * sy; + out.val[Matrix4.M31] = 0; + out.val[Matrix4.M02] = (xz + wy) * sz; + out.val[Matrix4.M12] = (yz - wx) * sz; out.val[Matrix4.M22] = (1 - (xx + yy)) * sz; - out.val[Matrix4.M23] = 0; - out.val[Matrix4.M30] = (v.x + pivot.x) - ((out.val[Matrix4.M00] * pivot.x) + (out.val[Matrix4.M10] * pivot.y) - + (out.val[Matrix4.M20] * pivot.z)); - out.val[Matrix4.M31] = (v.y + pivot.y) - ((out.val[Matrix4.M01] * pivot.x) + (out.val[Matrix4.M11] * pivot.y) - + (out.val[Matrix4.M21] * pivot.z)); - out.val[Matrix4.M32] = (v.z + pivot.z) - ((out.val[Matrix4.M02] * pivot.x) + (out.val[Matrix4.M12] * pivot.y) + out.val[Matrix4.M32] = 0; + out.val[Matrix4.M03] = (v.x + pivot.x) - ((out.val[Matrix4.M00] * pivot.x) + (out.val[Matrix4.M01] * pivot.y) + + (out.val[Matrix4.M02] * pivot.z)); + out.val[Matrix4.M13] = (v.y + pivot.y) - ((out.val[Matrix4.M10] * pivot.x) + (out.val[Matrix4.M11] * pivot.y) + + (out.val[Matrix4.M12] * pivot.z)); + out.val[Matrix4.M23] = (v.z + pivot.z) - ((out.val[Matrix4.M20] * pivot.x) + (out.val[Matrix4.M21] * pivot.y) + (out.val[Matrix4.M22] * pivot.z)); out.val[Matrix4.M33] = 1; } @@ -86,20 +93,20 @@ public enum RenderMathUtils { final float sy = s.y; final float sz = s.z; out.val[Matrix4.M00] = (1 - (yy + zz)) * sx; - out.val[Matrix4.M01] = (xy + wz) * sx; - out.val[Matrix4.M02] = (xz - wy) * sx; - out.val[Matrix4.M03] = 0; - out.val[Matrix4.M10] = (xy - wz) * sy; + out.val[Matrix4.M10] = (xy + wz) * sx; + out.val[Matrix4.M20] = (xz - wy) * sx; + out.val[Matrix4.M30] = 0; + out.val[Matrix4.M01] = (xy - wz) * sy; out.val[Matrix4.M11] = (1 - (xx + zz)) * sy; - out.val[Matrix4.M12] = (yz + wx) * sy; - out.val[Matrix4.M13] = 0; - out.val[Matrix4.M20] = (xz + wy) * sz; - out.val[Matrix4.M21] = (yz - wx) * sz; + out.val[Matrix4.M21] = (yz + wx) * sy; + out.val[Matrix4.M31] = 0; + out.val[Matrix4.M02] = (xz + wy) * sz; + out.val[Matrix4.M12] = (yz - wx) * sz; out.val[Matrix4.M22] = (1 - (xx + yy)) * sz; - out.val[Matrix4.M23] = 0; - out.val[Matrix4.M30] = v.x; - out.val[Matrix4.M31] = v.y; - out.val[Matrix4.M32] = v.z; + out.val[Matrix4.M32] = 0; + out.val[Matrix4.M03] = v.x; + out.val[Matrix4.M13] = v.y; + out.val[Matrix4.M23] = v.z; out.val[Matrix4.M33] = 1; } @@ -158,27 +165,27 @@ public enum RenderMathUtils { final float far) { final float f = 1.0f / (float) Math.tan(fovy / 2), nf; out.val[Matrix4.M00] = f / aspect; - out.val[Matrix4.M01] = 0; - out.val[Matrix4.M02] = 0; - out.val[Matrix4.M03] = 0; out.val[Matrix4.M10] = 0; - out.val[Matrix4.M11] = f; - out.val[Matrix4.M12] = 0; - out.val[Matrix4.M13] = 0; out.val[Matrix4.M20] = 0; - out.val[Matrix4.M21] = 0; - out.val[Matrix4.M23] = -1; out.val[Matrix4.M30] = 0; + out.val[Matrix4.M01] = 0; + out.val[Matrix4.M11] = f; + out.val[Matrix4.M21] = 0; out.val[Matrix4.M31] = 0; + out.val[Matrix4.M02] = 0; + out.val[Matrix4.M12] = 0; + out.val[Matrix4.M32] = -1; + out.val[Matrix4.M03] = 0; + out.val[Matrix4.M13] = 0; out.val[Matrix4.M33] = 0; if (!Double.isNaN(far) && !Double.isInfinite(far)) { nf = 1 / (near - far); out.val[Matrix4.M22] = (far + near) * nf; - out.val[Matrix4.M32] = (2 * far * near) * nf; + out.val[Matrix4.M23] = (2 * far * near) * nf; } else { out.val[Matrix4.M22] = -1; - out.val[Matrix4.M32] = -2 * near; + out.val[Matrix4.M23] = -2 * near; } return out; } @@ -189,32 +196,32 @@ public enum RenderMathUtils { final float bt = 1 / (bottom - top); final float nf = 1 / (near - far); out.val[Matrix4.M00] = -2 * lr; - out.val[Matrix4.M01] = 0; - out.val[Matrix4.M02] = 0; - out.val[Matrix4.M03] = 0; - out.val[Matrix4.M10] = 0; - out.val[Matrix4.M11] = -2 * bt; - out.val[Matrix4.M12] = 0; - out.val[Matrix4.M13] = 0; - out.val[Matrix4.M20] = 0; - out.val[Matrix4.M21] = 0; - out.val[Matrix4.M22] = 2 * nf; - out.val[Matrix4.M23] = 0; + out.val[Matrix4.M30] = 0; - out.val[Matrix4.M30] = (left + right) * lr; - out.val[Matrix4.M31] = (top + bottom) * bt; - out.val[Matrix4.M32] = (far + near) * nf; + out.val[Matrix4.M01] = 0; + out.val[Matrix4.M11] = -2 * bt; + out.val[Matrix4.M21] = 0; + out.val[Matrix4.M31] = 0; + + out.val[Matrix4.M02] = 0; + out.val[Matrix4.M12] = 0; + out.val[Matrix4.M22] = 2 * nf; + out.val[Matrix4.M32] = 0; + + out.val[Matrix4.M03] = (left + right) * lr; + out.val[Matrix4.M13] = (top + bottom) * bt; + out.val[Matrix4.M23] = (far + near) * nf; out.val[Matrix4.M33] = 1; return out; } public static void unpackPlanes(final Vector4[] planes, final Matrix4 m) { - final float a00 = m.val[Matrix4.M00], a01 = m.val[Matrix4.M01], a02 = m.val[Matrix4.M02], - a03 = m.val[Matrix4.M03], a10 = m.val[Matrix4.M10], a11 = m.val[Matrix4.M11], a12 = m.val[Matrix4.M12], - a13 = m.val[Matrix4.M13], a20 = m.val[Matrix4.M20], a21 = m.val[Matrix4.M21], a22 = m.val[Matrix4.M22], - a23 = m.val[Matrix4.M23], a30 = m.val[Matrix4.M30], a31 = m.val[Matrix4.M31], a32 = m.val[Matrix4.M32], + final float a00 = m.val[Matrix4.M00], a01 = m.val[Matrix4.M10], a02 = m.val[Matrix4.M20], + a03 = m.val[Matrix4.M30], a10 = m.val[Matrix4.M01], a11 = m.val[Matrix4.M11], a12 = m.val[Matrix4.M21], + a13 = m.val[Matrix4.M31], a20 = m.val[Matrix4.M02], a21 = m.val[Matrix4.M12], a22 = m.val[Matrix4.M22], + a23 = m.val[Matrix4.M32], a30 = m.val[Matrix4.M03], a31 = m.val[Matrix4.M13], a32 = m.val[Matrix4.M23], a33 = m.val[Matrix4.M33]; // Left clipping plane @@ -443,4 +450,37 @@ public enum RenderMathUtils { return out; } + public static ShortBuffer wrapFaces(final int[] faces) { + final ShortBuffer wrapper = ByteBuffer.allocateDirect(faces.length * 2).order(ByteOrder.nativeOrder()) + .asShortBuffer(); + for (final int face : faces) { + wrapper.put((short) face); + } + wrapper.clear(); + return wrapper; + } + + public static ByteBuffer wrap(final byte[] skin) { + final ByteBuffer wrapper = ByteBuffer.allocateDirect(skin.length).order(ByteOrder.nativeOrder()); + wrapper.put(skin); + wrapper.clear(); + return wrapper; + } + + public static FloatBuffer wrap(final float[] positions) { + final FloatBuffer wrapper = ByteBuffer.allocateDirect(positions.length * 4).order(ByteOrder.nativeOrder()) + .asFloatBuffer(); + wrapper.put(positions); + wrapper.clear(); + return wrapper; + } + + public static Buffer wrap(final short[] cornerTextures) { + final ByteBuffer wrapper = ByteBuffer.allocateDirect(cornerTextures.length).order(ByteOrder.nativeOrder()); + for (final short face : cornerTextures) { + wrapper.put((byte) face); + } + wrapper.clear(); + return wrapper; + } } diff --git a/core/src/com/etheller/warsmash/util/SubscriberSetNotifier.java b/core/src/com/etheller/warsmash/util/SubscriberSetNotifier.java new file mode 100644 index 0000000..8151abe --- /dev/null +++ b/core/src/com/etheller/warsmash/util/SubscriberSetNotifier.java @@ -0,0 +1,22 @@ +package com.etheller.warsmash.util; + +import java.util.HashSet; +import java.util.Set; + +public abstract class SubscriberSetNotifier { + protected final Set set; // bad for iteration but there + // should never be a dude subscribed + // 2x + + public SubscriberSetNotifier() { + this.set = new HashSet<>(); + } + + public final void subscribe(final LISTENER_TYPE listener) { + this.set.add(listener); + } + + public final void unsubscribe(final LISTENER_TYPE listener) { + this.set.remove(listener); + } +} diff --git a/core/src/com/etheller/warsmash/util/Vector4.java b/core/src/com/etheller/warsmash/util/Vector4.java index f80af83..e6f42c1 100644 --- a/core/src/com/etheller/warsmash/util/Vector4.java +++ b/core/src/com/etheller/warsmash/util/Vector4.java @@ -564,10 +564,10 @@ public class Vector4 implements Serializable, Vector { public static Vector4 transformMat4(final Vector4 out, final Vector4 a, final Matrix4 matrix) { final float x = a.x, y = a.y, z = a.z, w = a.w; final float[] m = matrix.val; - out.x = (m[Matrix4.M00] * x) + (m[Matrix4.M10] * y) + (m[Matrix4.M20] * z) + (m[Matrix4.M30] * w); - out.y = (m[Matrix4.M01] * x) + (m[Matrix4.M11] * y) + (m[Matrix4.M21] * z) + (m[Matrix4.M31] * w); - out.z = (m[Matrix4.M02] * x) + (m[Matrix4.M12] * y) + (m[Matrix4.M22] * z) + (m[Matrix4.M32] * w); - out.w = (m[Matrix4.M03] * x) + (m[Matrix4.M13] * y) + (m[Matrix4.M23] * z) + (m[Matrix4.M33] * w); + out.x = (m[Matrix4.M00] * x) + (m[Matrix4.M01] * y) + (m[Matrix4.M02] * z) + (m[Matrix4.M03] * w); + out.y = (m[Matrix4.M10] * x) + (m[Matrix4.M11] * y) + (m[Matrix4.M12] * z) + (m[Matrix4.M13] * w); + out.z = (m[Matrix4.M20] * x) + (m[Matrix4.M21] * y) + (m[Matrix4.M22] * z) + (m[Matrix4.M23] * w); + out.w = (m[Matrix4.M30] * x) + (m[Matrix4.M31] * y) + (m[Matrix4.M32] * z) + (m[Matrix4.M33] * w); return out; } } \ No newline at end of file diff --git a/core/src/com/etheller/warsmash/util/WarsmashConstants.java b/core/src/com/etheller/warsmash/util/WarsmashConstants.java new file mode 100644 index 0000000..1100226 --- /dev/null +++ b/core/src/com/etheller/warsmash/util/WarsmashConstants.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.util; + +public class WarsmashConstants { + public static final int MAX_PLAYERS = 16; +} diff --git a/core/src/com/etheller/warsmash/viewer/BoundingShape.java b/core/src/com/etheller/warsmash/viewer/BoundingShape.java deleted file mode 100644 index d0d379b..0000000 --- a/core/src/com/etheller/warsmash/viewer/BoundingShape.java +++ /dev/null @@ -1,116 +0,0 @@ -package com.etheller.warsmash.viewer; - -import com.badlogic.gdx.math.Quaternion; -import com.badlogic.gdx.math.Vector3; - -public class BoundingShape extends SceneNode { - private final float[] min = new float[] { -1, -1, -1 }; - private final float[] max = new float[] { 1, 1, 1 }; - private float radius = (float) Math.sqrt(2); - - public void fromBounds(final float[] min, final float[] max) { - System.arraycopy(min, 0, this.min, 0, this.min.length); - System.arraycopy(max, 0, this.max, 0, this.max.length); - - final float dX = max[0] - min[0]; - final float dY = max[1] - min[1]; - final float dZ = max[2] - min[2]; - - this.radius = (float) Math.sqrt((dX * dX) + (dY * dY) + (dZ * dZ)) / 2; - } - - public void fromRadius(final float radius) { - final float s = (float) (radius * Math.cos(radius)); - this.min[0] = this.min[1] = this.min[2] = s; - this.max[0] = this.max[1] = this.max[2] = s; - this.radius = radius; - } - - public void fromVertices(final float[] vertices) { - final float[] min = new float[] { 1E9f, 1E9f, 1E9f }; - final float[] max = new float[] { -1E9f, -1E9f, -1E9f }; - - for (int i = 0, l = vertices.length; i < l; i += 3) { - final float x = vertices[i]; - final float y = vertices[i + 1]; - final float z = vertices[i + 2]; - - if (x > max[0]) { - max[0] = x; - } - if (x < min[0]) { - min[0] = x; - } - if (y > max[1]) { - max[1] = y; - } - if (y < min[1]) { - min[1] = y; - } - if (z > max[2]) { - max[2] = z; - } - if (z < min[2]) { - min[2] = z; - } - } - - fromBounds(min, max); - } - - public Vector3 getPositiveVertex(final Vector3 out, final Vector3 normal) { - if (normal.x >= 0) { - out.x = this.max[0]; - } - else { - out.x = this.min[0]; - } - if (normal.y >= 0) { - out.y = this.max[1]; - } - else { - out.y = this.min[1]; - } - if (normal.z >= 0) { - out.z = this.max[2]; - } - else { - out.z = this.min[2]; - } - - return out; - } - - public Vector3 getNegativeVertex(final Vector3 out, final Vector3 normal) { - if (normal.x >= 0) { - out.x = this.min[0]; - } - else { - out.x = this.max[0]; - } - if (normal.y >= 0) { - out.y = this.min[1]; - } - else { - out.y = this.max[1]; - } - if (normal.z >= 0) { - out.z = this.min[2]; - } - else { - out.z = this.max[2]; - } - - return out; - } - - @Override - protected void updateObject(final Scene scene) { - } - - @Override - protected void convertBasis(final Quaternion computedRotation) { - // TODO ??? - } - -} diff --git a/core/src/com/etheller/warsmash/viewer/Bucket.java b/core/src/com/etheller/warsmash/viewer/Bucket.java deleted file mode 100644 index 98955a1..0000000 --- a/core/src/com/etheller/warsmash/viewer/Bucket.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.etheller.warsmash.viewer; - -import com.badlogic.gdx.graphics.GL20; -import com.etheller.warsmash.viewer.ModelView.SceneData; - -public class Bucket { - private final ModelView modelView; - private final Model model; - private final int count; - - public Bucket(final ModelView modelView) { - final Model model = modelView.model; - final GL20 gl = model.getViewer().gl; - - this.modelView = modelView; - this.model = model; - this.count = 0; - -// this.instanceIdBuffer = - } - - public int fill(final SceneData data, final int baseInstance, final Scene scene) { - // Make believe the bucket is now filled with data for all instances. - // This is because if a non-specific bucket implementation is supplied, - // instancing isn't used, so batching is irrelevant. - return data.instances.size(); - } -} diff --git a/core/src/com/etheller/warsmash/viewer/Camera.java b/core/src/com/etheller/warsmash/viewer/Camera.java deleted file mode 100644 index db37d39..0000000 --- a/core/src/com/etheller/warsmash/viewer/Camera.java +++ /dev/null @@ -1,314 +0,0 @@ -package com.etheller.warsmash.viewer; - -import com.badlogic.gdx.math.Matrix4; -import com.badlogic.gdx.math.Quaternion; -import com.badlogic.gdx.math.Rectangle; -import com.badlogic.gdx.math.Vector2; -import com.badlogic.gdx.math.Vector3; -import com.etheller.warsmash.util.RenderMathUtils; -import com.etheller.warsmash.util.Vector4; - -public class Camera { - private static final Vector3 vectorHeap = new Vector3(); - private static final Vector3 vectorHeap2 = new Vector3(); - private static final Vector3 vectorHeap3 = new Vector3(); - private static final Quaternion quatHeap = new Quaternion(); - private static final Matrix4 matHeap = new Matrix4(); - - private final Rectangle rect; - - private boolean isPerspective; - private float fov; - private float aspect; - - private boolean isOrtho; - private float leftClipPlane; - private float rightClipPlane; - private float bottomClipPlane; - private float topClipPlane; - - private float nearClipPlane; - private float farClipPlane; - - private final Vector3 location; - private final Quaternion rotation; - - public Quaternion inverseRotation; - private final Matrix4 worldMatrix; - private final Matrix4 projectionMatrix; - private final Matrix4 worldProjectionMatrix; - private final Matrix4 inverseWorldMatrix; - private final Matrix4 inverseRotationMatrix; - private final Matrix4 inverseWorldProjectionMatrix; - private final Vector3 directionX; - private final Vector3 directionY; - private final Vector3 directionZ; - private final Vector3[] vectors; - private final Vector3[] billboardedVectors; - - private final Vector4[] planes; - private boolean dirty; - - public Camera() { - // rencered viewport - this.rect = new Rectangle(); - - // perspective values - this.isPerspective = true; - this.fov = 0; - this.aspect = 0; - - // Orthogonal values - this.isOrtho = false; - this.leftClipPlane = 0f; - this.rightClipPlane = 0f; - this.bottomClipPlane = 0f; - this.topClipPlane = 0f; - - // Shared values - this.nearClipPlane = 0f; - this.farClipPlane = 0f; - - // World values - this.location = new Vector3(); - this.rotation = new Quaternion(); - - // Derived values. - this.inverseRotation = new Quaternion(); - this.worldMatrix = new Matrix4(); - this.projectionMatrix = new Matrix4(); - this.worldProjectionMatrix = new Matrix4(); - this.inverseWorldMatrix = new Matrix4(); - this.inverseRotationMatrix = new Matrix4(); - this.inverseWorldProjectionMatrix = new Matrix4(); - this.directionX = new Vector3(); - this.directionY = new Vector3(); - this.directionZ = new Vector3(); - - // First four vectors are the corners of a 2x2 rectangle, the last three vectors - // are the unit axes - this.vectors = new Vector3[] { new Vector3(-1, -1, 0), new Vector3(-1, 1, 0), new Vector3(1, 1, 0), - new Vector3(1, -1, 0), new Vector3(1, 0, 0), new Vector3(0, 1, 0), new Vector3(0, 0, 1) }; - - // First four vectors are the corners of a 2x2 rectangle billboarded to the - // camera, the last three vectors are the unit axes billboarded - this.billboardedVectors = new Vector3[] { new Vector3(), new Vector3(), new Vector3(), new Vector3(), - new Vector3(), new Vector3(), new Vector3() }; - - // Left, right, top, bottom, near, far - this.planes = new Vector4[] { new Vector4(), new Vector4(), new Vector4(), new Vector4(), new Vector4(), - new Vector4() }; - - this.dirty = true; - } - - public void perspective(final float fov, final float aspect, final float near, final float far) { - this.isPerspective = true; - this.isOrtho = false; - this.fov = fov; - this.aspect = aspect; - this.nearClipPlane = near; - this.farClipPlane = far; - - this.dirty = true; - } - - public void ortho(final float left, final float right, final float bottom, final float top, final float near, - final float far) { - this.isPerspective = false; - this.isOrtho = true; - this.leftClipPlane = left; - this.rightClipPlane = right; - this.bottomClipPlane = bottom; - this.topClipPlane = top; - this.nearClipPlane = near; - this.farClipPlane = far; - } - - public void viewport(final Rectangle viewport) { - this.rect.set(viewport); - - this.aspect = viewport.width / viewport.height; - - this.dirty = true; - } - - public void setLocation(final Vector3 location) { - this.location.set(location); - - this.dirty = true; - } - - public void move(final Vector3 offset) { - this.location.add(offset); - - this.dirty = true; - } - - public void setRotation(final Quaternion rotation) { - this.rotation.set(rotation); - - this.dirty = true; - } - - public void rotate(final Quaternion rotation) { - this.rotation.mul(rotation); - - this.dirty = true; - } - - public void setRotationAngles(final float horizontalAngle, final float verticalAngle) { - this.rotation.idt(); -// this.rotateAngles(horizontalAngle, verticalAngle); - throw new UnsupportedOperationException( - "Ghostwolf called a function that does not exist, so I did not know what to do here"); - } - - public void rotateAround(final Quaternion rotation, final Vector3 point) { - this.rotate(rotation); - - quatHeap.conjugate(); // TODO ????????? - vectorHeap.set(this.location); - vectorHeap.sub(point); - rotation.transform(vectorHeap); - vectorHeap.add(point); - this.location.set(vectorHeap); - } - - public void setRotationAround(final Quaternion rotation, final Vector3 point) { - this.setRotation(rotation); - ; - - final float length = vectorHeap.set(this.location).sub(point).len(); - - quatHeap.conjugate(); // TODO ????????? - vectorHeap.set(RenderMathUtils.VEC3_UNIT_Z); - quatHeap.transform(vectorHeap); - vectorHeap.scl(length); - this.location.set(vectorHeap.add(point)); - } - - public void setRotationAroundAngles(final float horizontalAngle, final float verticalAngle, final Vector3 point) { - quatHeap.idt(); - RenderMathUtils.rotateX(quatHeap, quatHeap, verticalAngle); - RenderMathUtils.rotateY(quatHeap, quatHeap, horizontalAngle); - - this.setRotationAround(quatHeap, point); - } - - public void face(final Vector3 point, final Vector3 worldUp) { - matHeap.setToLookAt(this.location, point, worldUp); - matHeap.getRotation(this.rotation); - - this.dirty = true; - } - - public void moveToAndFace(final Vector3 location, final Vector3 target, final Vector3 worldUp) { - this.location.set(location); - this.face(target, worldUp); - } - - public void reset() { - this.location.set(0, 0, 0); - this.rotation.idt(); - - this.dirty = true; - } - - public void update() { - if (this.dirty) { - this.dirty = true; - - final Vector3 location = this.location; - final Quaternion rotation = this.rotation; - final Quaternion inverseRotation = this.inverseRotation; - final Matrix4 worldMatrix = this.worldMatrix; - final Matrix4 projectionMatrix = this.projectionMatrix; - final Matrix4 worldProjectionMatrix = this.worldProjectionMatrix; - final Vector3[] vectors = this.vectors; - final Vector3[] billboardedVectors = this.billboardedVectors; - - if (this.isPerspective) { - RenderMathUtils.perspective(projectionMatrix, this.fov, this.aspect, this.nearClipPlane, - this.farClipPlane); - } - else { - RenderMathUtils.ortho(projectionMatrix, this.leftClipPlane, this.rightClipPlane, this.bottomClipPlane, - this.topClipPlane, this.nearClipPlane, this.farClipPlane); - } - - rotation.toMatrix(projectionMatrix.val); - worldMatrix.translate(vectorHeap.set(location).scl(-1)); - inverseRotation.set(rotation).conjugate(); - - // World projection matrix - // World space -> NDC space - worldProjectionMatrix.set(projectionMatrix).mul(worldMatrix); - - // Recalculate the camera's frustum planes - RenderMathUtils.unpackPlanes(this.planes, worldProjectionMatrix); - - // Inverse world matrix - // Camera space -> world space - this.inverseWorldMatrix.set(worldMatrix).inv(); - - this.directionX.set(RenderMathUtils.VEC3_UNIT_X); - inverseRotation.transform(this.directionX); - this.directionY.set(RenderMathUtils.VEC3_UNIT_Y); - inverseRotation.transform(this.directionY); - this.directionZ.set(RenderMathUtils.VEC3_UNIT_Z); - inverseRotation.transform(this.directionZ); - - // Inverse world projection matrix - // NDC space -> World space - this.inverseWorldProjectionMatrix.set(worldProjectionMatrix); - this.inverseWorldProjectionMatrix.inv(); - - for (int i = 0; i < 7; i++) { - billboardedVectors[i].set(vectors[i]); - inverseRotation.transform(billboardedVectors[i]); - } - } - } - - public boolean testSphere(final Vector3 center, final float radius) { - for (final Vector4 plane : this.planes) { - if (RenderMathUtils.distanceToPlane(plane, center) <= -radius) { - return false; - } - } - return true; - } - - public Vector3 cameraToWorld(final Vector3 out, final Vector3 v) { - return out.set(v).prj(this.inverseWorldMatrix); - } - - public Vector3 worldToCamera(final Vector3 out, final Vector3 v) { - return out.set(v).prj(this.worldMatrix); - } - - public float[] screenToWorldRay(final float[] out, final Vector2 v) { - final Vector3 a = vectorHeap; - final Vector3 b = vectorHeap2; - final Vector3 c = vectorHeap3; - final float x = v.x; - final float y = v.y; - final Rectangle viewport = this.rect; - - // Intersection on the near-plane - RenderMathUtils.unproject(a, c.set(x, y, 0), this.inverseWorldProjectionMatrix, viewport); - - // Intersection on the far-plane - RenderMathUtils.unproject(b, c.set(x, y, 1), this.inverseWorldProjectionMatrix, viewport); - - out[0] = a.x; - out[1] = a.y; - out[2] = a.z; - out[3] = b.x; - out[4] = b.y; - out[5] = b.z; - - return out; - } -} diff --git a/core/src/com/etheller/warsmash/viewer/Model.java b/core/src/com/etheller/warsmash/viewer/Model.java deleted file mode 100644 index 5c5d35a..0000000 --- a/core/src/com/etheller/warsmash/viewer/Model.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.etheller.warsmash.viewer; - -public abstract class Model { - private ModelView modelView; - - public boolean ok; - - public abstract Viewer getViewer(); -} diff --git a/core/src/com/etheller/warsmash/viewer/ModelInstance.java b/core/src/com/etheller/warsmash/viewer/ModelInstance.java deleted file mode 100644 index 9404c32..0000000 --- a/core/src/com/etheller/warsmash/viewer/ModelInstance.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.etheller.warsmash.viewer; - -public class ModelInstance { - -} diff --git a/core/src/com/etheller/warsmash/viewer/ModelView.java b/core/src/com/etheller/warsmash/viewer/ModelView.java deleted file mode 100644 index c2c24f6..0000000 --- a/core/src/com/etheller/warsmash/viewer/ModelView.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.etheller.warsmash.viewer; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; - -public abstract class ModelView { - protected final Model model; - protected final HashSet instanceSet; - protected final HashMap sceneData; - protected final int renderedInstances; - protected final int renderedParticles; - protected final int renderedBuckets; - protected final int renderedCalls; - - public ModelView(final Model model) { - this.model = model; - - this.instanceSet = new HashSet<>(); - this.sceneData = new HashMap<>(); - - this.renderedInstances = 0; - this.renderedParticles = 0; - this.renderedBuckets = 0; - this.renderedCalls = 0; - } - - public abstract Object getShallowCopy(); - - public abstract void applyShallowCopy(final Object view); - - @Override - public abstract boolean equals(Object view); - - @Override - public abstract int hashCode(); - - // public boo - public void addSceneData(final ModelInstance instance, final Scene scene) { - if (this.model.ok && (scene != null)) { - SceneData data = this.sceneData.get(scene); - - if (data == null) { - data = this.createSceneData(scene); - - this.sceneData.put(scene, data); - } - - } - } - - private SceneData createSceneData(final Scene scene) { - return new SceneData(scene, this); - } - - public static final class SceneData { - public final Scene scene; - public final ModelView modelView; - public final int baseIndex = 0; - public final List instances = new ArrayList<>(); - public final List buckets = new ArrayList<>(); - public final int usedBuckets = 0; - - public SceneData(final Scene scene, final ModelView modelView) { - this.scene = scene; - this.modelView = modelView; - } - - } -} diff --git a/core/src/com/etheller/warsmash/viewer/Scene.java b/core/src/com/etheller/warsmash/viewer/Scene.java deleted file mode 100644 index f1dcf53..0000000 --- a/core/src/com/etheller/warsmash/viewer/Scene.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.etheller.warsmash.viewer; - -public abstract class Scene { - public Camera camera; -} diff --git a/core/src/com/etheller/warsmash/viewer/SceneNode.java b/core/src/com/etheller/warsmash/viewer/SceneNode.java deleted file mode 100644 index cc89faf..0000000 --- a/core/src/com/etheller/warsmash/viewer/SceneNode.java +++ /dev/null @@ -1,245 +0,0 @@ -package com.etheller.warsmash.viewer; - -import com.badlogic.gdx.math.Matrix4; -import com.badlogic.gdx.math.Quaternion; -import com.badlogic.gdx.math.Vector3; -import com.etheller.warsmash.util.RenderMathUtils; - -public abstract class SceneNode extends ViewerNode { - - public SceneNode() { - } - - public SceneNode setPivot(final float[] pivot) { - this.pivot.set(pivot); - this.dirty = true; - return this; - } - - public SceneNode setLocation(final float[] location) { - this.localLocation.set(location); - this.dirty = true; - return this; - } - - public SceneNode setRotation(final float[] rotation) { - this.localRotation.set(rotation[0], rotation[1], rotation[2], rotation[3]); - this.dirty = true; - return this; - } - - public SceneNode setScale(final float[] varying) { - this.localScale.set(varying); - this.dirty = true; - return this; - } - - public SceneNode setUniformScale(final float uniform) { - this.localScale.set(uniform, uniform, uniform); - this.dirty = true; - return this; - } - - public SceneNode setTransformation(final Vector3 location, final Quaternion rotation, final Vector3 scale) { - // TODO for performance, Ghostwolf did a direct field write on everything here. - // I'm hoping we can get Java's JIT to just figure it out and do it on its own - this.localLocation.set(location); - this.localRotation.set(rotation); - this.localScale.set(scale); - this.dirty = true; - return this; - } - - public SceneNode resetTransformation() { - this.pivot.set(Vector3.Zero); - this.localLocation.set(Vector3.Zero); - this.localRotation.set(RenderMathUtils.QUAT_DEFAULT); - this.localScale.set(RenderMathUtils.VEC3_ONE); - - this.dirty = true; - return this; - } - - public SceneNode movePivot(final float[] offset) { - this.pivot.add(offset[0], offset[1], offset[2]); - - this.dirty = true; - - return this; - } - - public SceneNode move(final float[] offset) { - this.localLocation.add(offset[0], offset[1], offset[2]); - - this.dirty = true; - - return this; - } - - public SceneNode rotate(final Quaternion rotation) { - RenderMathUtils.mul(this.localRotation, this.localRotation, rotation); - - this.dirty = true; - - return this; - } - - public SceneNode rotateLocal(final Quaternion rotation) { - RenderMathUtils.mul(this.localRotation, rotation, this.localRotation); - - this.dirty = true; - - return this; - } - - public SceneNode scale(final float[] scale) { - this.localScale.x *= scale[0]; - this.localScale.y *= scale[1]; - this.localScale.z *= scale[2]; - - this.dirty = true; - - return this; - } - - public SceneNode uniformScale(final float scale) { - this.localScale.x *= scale; - this.localScale.y *= scale; - this.localScale.z *= scale; - - this.dirty = true; - - return this; - } - - public SceneNode setParent(final ViewerNode parent) { - if (this.parent != null) { - this.parent.children.remove(this); - } - - this.parent = parent; - - if (parent != null) { - parent.children.add(this); - } - - this.dirty = true; - - return this; - } - - public void recalculateTransformation() { - boolean dirty = this.dirty; - final ViewerNode parent = this.parent; - - this.wasDirty = this.dirty; - - if (parent != null) { - dirty = dirty || parent.wasDirty; - } - - this.wasDirty = dirty; - - if (dirty) { - this.dirty = false; - - if (parent != null) { - Vector3 computedLocation; - Vector3 computedScaling; - - final Vector3 parentPivot = parent.pivot; - - computedLocation = locationHeap; - computedLocation.x = this.localLocation.x + parentPivot.x; - computedLocation.y = this.localLocation.y + parentPivot.y; - computedLocation.z = this.localLocation.z + parentPivot.z; - - if (this.dontInheritScaling) { - computedScaling = scalingHeap; - - final Vector3 parentInverseScale = parent.inverseWorldScale; - computedScaling.x = parentInverseScale.x * this.localScale.x; - computedScaling.y = parentInverseScale.y * this.localScale.y; - computedScaling.z = parentInverseScale.z * this.localScale.z; - - this.worldScale.x = this.localScale.x; - this.worldScale.y = this.localScale.y; - this.worldScale.z = this.localScale.z; - } - else { - computedScaling = this.localScale; - - final Vector3 parentScale = parent.worldScale; - this.worldScale.x = parentScale.x * this.localScale.x; - this.worldScale.y = parentScale.y * this.localScale.y; - this.worldScale.z = parentScale.z * this.localScale.z; - } - - RenderMathUtils.fromRotationTranslationScale(this.localRotation, computedLocation, computedScaling, - this.localMatrix); - - RenderMathUtils.mul(this.worldMatrix, parent.worldMatrix, this.localMatrix); - - RenderMathUtils.mul(this.worldRotation, parent.worldRotation, this.localRotation); - } - else { - RenderMathUtils.fromRotationTranslationScale(this.localRotation, this.localLocation, this.localScale, - this.localMatrix); - - this.worldMatrix.set(this.localMatrix); - - this.worldRotation.set(this.localRotation); - - this.worldScale.set(this.localScale); - } - } - - // Inverse world rotation - this.inverseWorldRotation.x = -this.worldRotation.x; - this.inverseWorldRotation.y = -this.worldRotation.y; - this.inverseWorldRotation.z = -this.worldRotation.z; - this.inverseWorldRotation.w = this.worldRotation.w; - - // Inverse world scale - this.inverseWorldScale.x = 1 / this.worldScale.x; - this.inverseWorldScale.y = 1 / this.worldScale.y; - this.inverseWorldScale.z = 1 / this.worldScale.z; - - // World location - this.worldLocation.x = this.worldMatrix.val[Matrix4.M30]; - this.worldLocation.y = this.worldMatrix.val[Matrix4.M31]; - this.worldLocation.z = this.worldMatrix.val[Matrix4.M32]; - - // Inverse world location - this.inverseWorldLocation.x = -this.worldLocation.x; - this.inverseWorldLocation.y = -this.worldLocation.y; - this.inverseWorldLocation.z = -this.worldLocation.z; - - } - - @Override - public void update(final Scene scene) { - if (this.dirty || ((this.parent != null) && this.parent.wasDirty)) { - this.dirty = true; - this.wasDirty = true; - this.recalculateTransformation(); - } - else { - this.wasDirty = false; - } - - this.updateObject(scene); - this.updateChildren(scene); - } - - protected abstract void updateObject(Scene scene); - - protected void updateChildren(final Scene scene) { - for (int i = 0, l = this.children.size(); i < l; i++) { - this.children.get(i).update(scene); - } - } - - protected abstract void convertBasis(Quaternion computedRotation); - -} diff --git a/core/src/com/etheller/warsmash/viewer/SkeletalNode.java b/core/src/com/etheller/warsmash/viewer/SkeletalNode.java deleted file mode 100644 index bd2a1fd..0000000 --- a/core/src/com/etheller/warsmash/viewer/SkeletalNode.java +++ /dev/null @@ -1,118 +0,0 @@ -package com.etheller.warsmash.viewer; - -import java.util.ArrayList; -import java.util.List; - -import com.badlogic.gdx.math.Matrix4; -import com.badlogic.gdx.math.Quaternion; -import com.badlogic.gdx.math.Vector3; -import com.etheller.warsmash.util.Descriptor; -import com.etheller.warsmash.util.RenderMathUtils; - -public abstract class SkeletalNode extends ViewerNode { - - private final Object object; - - private final boolean billboarded = false; - private final boolean billboardedX = false; - private final boolean billboardedY = false; - private final boolean billboardedZ = false; - - public SkeletalNode() { - this.object = null; - } - - public void recalculateTransformation(final Scene scene) { - final Quaternion computedRotation; - Vector3 computedScaling; - - if (this.dontInheritScaling) { - computedScaling = scalingHeap; - - final Vector3 parentInverseScale = this.parent.inverseWorldScale; - computedScaling.x = parentInverseScale.x * this.localScale.x; - computedScaling.y = parentInverseScale.y * this.localScale.y; - computedScaling.z = parentInverseScale.z * this.localScale.z; - - this.worldScale.x = this.localScale.x; - this.worldScale.y = this.localScale.y; - this.worldScale.z = this.localScale.z; - } - else { - computedScaling = this.localScale; - - final Vector3 parentScale = this.parent.worldScale; - this.worldScale.x = parentScale.x * this.worldScale.x; - this.worldScale.y = parentScale.y * this.worldScale.y; - this.worldScale.z = parentScale.z * this.worldScale.z; - } - - if (this.billboarded) { - computedRotation = rotationHeap; - - computedRotation.set(this.parent.inverseWorldRotation); - computedRotation.mul(scene.camera.inverseRotation); - - this.convertBasis(computedRotation); - } - else { - computedRotation = this.localRotation; - } - - RenderMathUtils.fromRotationTranslationScaleOrigin(computedRotation, this.localLocation, computedScaling, - this.localMatrix, this.pivot); - - RenderMathUtils.mul(this.worldMatrix, this.parent.worldMatrix, this.localMatrix); - - RenderMathUtils.mul(this.worldRotation, this.parent.worldRotation, computedRotation); - - // Inverse world rotation - this.inverseWorldRotation.x = -this.worldRotation.x; - this.inverseWorldRotation.y = -this.worldRotation.y; - this.inverseWorldRotation.z = -this.worldRotation.z; - this.inverseWorldRotation.w = this.worldRotation.w; - - // Inverse world scale - this.inverseWorldScale.x = 1 / this.worldScale.x; - this.inverseWorldScale.y = 1 / this.worldScale.y; - this.inverseWorldScale.z = 1 / this.worldScale.z; - - // World location - final float x = this.pivot.x; - final float y = this.pivot.y; - final float z = this.pivot.z; - this.worldLocation.x = (this.worldMatrix.val[Matrix4.M00] * x) + (this.worldMatrix.val[Matrix4.M10] * y) - + (this.worldMatrix.val[Matrix4.M20] * z) + this.worldMatrix.val[Matrix4.M30]; - this.worldLocation.y = (this.worldMatrix.val[Matrix4.M01] * x) + (this.worldMatrix.val[Matrix4.M11] * y) - + (this.worldMatrix.val[Matrix4.M21] * z) + this.worldMatrix.val[Matrix4.M31]; - this.worldLocation.z = (this.worldMatrix.val[Matrix4.M02] * x) + (this.worldMatrix.val[Matrix4.M12] * y) - + (this.worldMatrix.val[Matrix4.M22] * z) + this.worldMatrix.val[Matrix4.M32]; - - // Inverse world location - this.inverseWorldLocation.x = -this.worldLocation.x; - this.inverseWorldLocation.y = -this.worldLocation.y; - this.inverseWorldLocation.z = -this.worldLocation.z; - } - - protected void updateChildren(final Scene scene) { - for (int i = 0, l = this.children.size(); i < l; i++) { - this.children.get(i).update(scene); - } - } - - protected abstract void convertBasis(Quaternion computedRotation); - - public static Object[] createSkeletalNodes(final int count, - final Descriptor nodeDescriptor) { - final List nodes = new ArrayList<>(); - final List worldMatrices = new ArrayList<>(); - for (int i = 0; i < count; i++) { - final NODE node = nodeDescriptor.create(); - nodes.add(node); - worldMatrices.add(node.worldMatrix); - } - final Object[] data = { nodes, worldMatrices }; - return data; - } - -} diff --git a/core/src/com/etheller/warsmash/viewer/Viewer.java b/core/src/com/etheller/warsmash/viewer/Viewer.java deleted file mode 100644 index f1d0232..0000000 --- a/core/src/com/etheller/warsmash/viewer/Viewer.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.etheller.warsmash.viewer; - -import com.badlogic.gdx.graphics.GL20; - -public abstract class Viewer { - public GL20 gl; -} diff --git a/core/src/com/etheller/warsmash/viewer/ViewerNode.java b/core/src/com/etheller/warsmash/viewer/ViewerNode.java deleted file mode 100644 index 5bdd33d..0000000 --- a/core/src/com/etheller/warsmash/viewer/ViewerNode.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.etheller.warsmash.viewer; - -import java.util.ArrayList; -import java.util.List; - -import com.badlogic.gdx.math.Matrix4; -import com.badlogic.gdx.math.Quaternion; -import com.badlogic.gdx.math.Vector3; - -public abstract class ViewerNode { - protected static final Vector3 locationHeap = new Vector3(); - protected static final Quaternion rotationHeap = new Quaternion(); - protected static final Vector3 scalingHeap = new Vector3(); - - protected final Vector3 pivot; - protected final Vector3 localLocation; - protected final Quaternion localRotation; - protected final Vector3 localScale; - protected final Vector3 worldLocation; - protected final Quaternion worldRotation; - protected final Vector3 worldScale; - protected final Vector3 inverseWorldLocation; - protected final Quaternion inverseWorldRotation; - protected final Vector3 inverseWorldScale; - protected final Matrix4 localMatrix; - protected final Matrix4 worldMatrix; - protected final boolean dontInheritTranslation; - protected final boolean dontInheritRotation; - protected final boolean dontInheritScaling; - protected boolean visible; - protected boolean wasDirty; - protected boolean dirty; - - protected ViewerNode parent; - - protected final List children; - - public ViewerNode() { - this.pivot = new Vector3(); - this.localLocation = new Vector3(); - this.localRotation = new Quaternion(0, 0, 0, 1); - this.localScale = new Vector3(1, 1, 1); - this.worldLocation = new Vector3(); - this.worldRotation = new Quaternion(); - this.worldScale = new Vector3(); - this.inverseWorldLocation = new Vector3(); - this.inverseWorldRotation = new Quaternion(); - this.inverseWorldScale = new Vector3(); - this.localMatrix = new Matrix4(); - this.localMatrix.val[0] = 1; - this.localMatrix.val[5] = 1; - this.localMatrix.val[10] = 1; - this.localMatrix.val[15] = 1; - this.worldMatrix = new Matrix4(); - this.dontInheritTranslation = false; - this.dontInheritRotation = false; - this.dontInheritScaling = false; - this.visible = true; - this.wasDirty = false; - this.dirty = true; - this.children = new ArrayList<>(); - } - - public abstract void update(Scene scene); -} diff --git a/core/src/com/etheller/warsmash/viewer5/Camera.java b/core/src/com/etheller/warsmash/viewer5/Camera.java index 41925f4..8b6da40 100644 --- a/core/src/com/etheller/warsmash/viewer5/Camera.java +++ b/core/src/com/etheller/warsmash/viewer5/Camera.java @@ -230,8 +230,6 @@ public class Camera { public void update() { if (this.dirty) { - this.dirty = true; - final Vector3 location = this.location; final Quaternion rotation = this.rotation; final Quaternion inverseRotation = this.inverseRotation; @@ -281,6 +279,7 @@ public class Camera { billboardedVectors[i].set(vectors[i]); inverseRotation.transform(billboardedVectors[i]); } + this.dirty = false; } } diff --git a/core/src/com/etheller/warsmash/viewer5/EmittedObjectUpdater.java b/core/src/com/etheller/warsmash/viewer5/EmittedObjectUpdater.java index b9d4086..e99816d 100644 --- a/core/src/com/etheller/warsmash/viewer5/EmittedObjectUpdater.java +++ b/core/src/com/etheller/warsmash/viewer5/EmittedObjectUpdater.java @@ -34,6 +34,9 @@ public class EmittedObjectUpdater { this.objects.set(i, this.objects.remove(this.alive)); i -= 1; } + else { + this.objects.remove(this.alive); + } } } } diff --git a/core/src/com/etheller/warsmash/viewer5/Emitter.java b/core/src/com/etheller/warsmash/viewer5/Emitter.java index 4d966f8..690dc63 100644 --- a/core/src/com/etheller/warsmash/viewer5/Emitter.java +++ b/core/src/com/etheller/warsmash/viewer5/Emitter.java @@ -38,7 +38,10 @@ public abstract class Emitter resources; public Map fetchCache; @@ -45,7 +45,7 @@ public class ModelViewer { this.resources = new ArrayList<>(); this.fetchCache = new HashMap<>(); this.handlers = new HashSet(); - this.frameTime = 1000 / 60; + this.frameTime = 1000 / 6; this.gl = Gdx.gl; this.webGL = new WebGL(this.gl); this.scenes = new ArrayList<>(); @@ -71,6 +71,10 @@ public class ModelViewer { this.textureMappers = new HashMap>(); } + public void setDataSource(final DataSource dataSource) { + this.dataSource = dataSource; + } + public boolean enableAudio() { this.audioEnabled = true; return this.audioEnabled; @@ -200,6 +204,11 @@ public class ModelViewer { return this.fetchCache.get(key); } + public GenericResource loadGeneric(final String path, final FetchDataTypeName dataType, + final LoadGenericCallback callback) { + return loadGeneric(path, dataType, callback, this.dataSource); + } + /** * Load something generic. * @@ -217,7 +226,7 @@ public class ModelViewer { * promise resolved to. */ public GenericResource loadGeneric(final String path, final FetchDataTypeName dataType, - final LoadGenericCallback callback) { + final LoadGenericCallback callback, final DataSource dataSource) { final Resource cachedResource = this.fetchCache.get(path); if (cachedResource != null) { @@ -235,7 +244,7 @@ public class ModelViewer { // TODO this is a synchronous hack, skipped some Ghostwolf code try { - resource.loadData(this.dataSource.getResourceAsStream(path), null); + resource.loadData(dataSource.getResourceAsStream(path), null); } catch (final IOException e) { throw new IllegalStateException("Unable to load data: " + path); diff --git a/core/src/com/etheller/warsmash/viewer5/Node.java b/core/src/com/etheller/warsmash/viewer5/Node.java index 789aa84..76eb5a1 100644 --- a/core/src/com/etheller/warsmash/viewer5/Node.java +++ b/core/src/com/etheller/warsmash/viewer5/Node.java @@ -21,7 +21,7 @@ public abstract class Node extends GenericNode { this.localScale = new Vector3(1, 1, 1); this.worldLocation = new Vector3(); this.worldRotation = new Quaternion(); - this.worldScale = new Vector3(); + this.worldScale = new Vector3(1, 1, 1); this.inverseWorldLocation = new Vector3(); this.inverseWorldRotation = new Quaternion(); this.inverseWorldScale = new Vector3(); @@ -249,9 +249,9 @@ public abstract class Node extends GenericNode { this.inverseWorldScale.z = 1 / this.worldScale.z; // World location - this.worldLocation.x = this.worldMatrix.val[Matrix4.M30]; - this.worldLocation.y = this.worldMatrix.val[Matrix4.M31]; - this.worldLocation.z = this.worldMatrix.val[Matrix4.M32]; + this.worldLocation.x = this.worldMatrix.val[Matrix4.M03]; + this.worldLocation.y = this.worldMatrix.val[Matrix4.M13]; + this.worldLocation.z = this.worldMatrix.val[Matrix4.M23]; // Inverse world location this.inverseWorldLocation.x = -this.worldLocation.x; diff --git a/core/src/com/etheller/warsmash/viewer5/PathSolver.java b/core/src/com/etheller/warsmash/viewer5/PathSolver.java index 6aace1f..b1b8160 100644 --- a/core/src/com/etheller/warsmash/viewer5/PathSolver.java +++ b/core/src/com/etheller/warsmash/viewer5/PathSolver.java @@ -2,4 +2,16 @@ package com.etheller.warsmash.viewer5; public interface PathSolver { SolvedPath solve(String src, Object solverParams); + + // We generally just use the default path solver. + // These things were apparently meant to work as the Ghostwolf's JavaScript's + // equivalent of the DataSource interface you will find in this Java repo. + // But I did not know that and wasn't sure what it was for, so I kept it in the + // port of his code. Eventually it should be removed. + public static final PathSolver DEFAULT = new PathSolver() { + @Override + public SolvedPath solve(final String src, final Object solverParams) { + return new SolvedPath(src, src.substring(src.lastIndexOf('.')), true); + } + }; } diff --git a/core/src/com/etheller/warsmash/viewer5/RawOpenGLTextureResource.java b/core/src/com/etheller/warsmash/viewer5/RawOpenGLTextureResource.java new file mode 100644 index 0000000..8f7a57e --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/RawOpenGLTextureResource.java @@ -0,0 +1,138 @@ +package com.etheller.warsmash.viewer5; + +import java.awt.image.BufferedImage; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import com.badlogic.gdx.graphics.GL20; +import com.badlogic.gdx.graphics.GL30; +import com.etheller.warsmash.viewer5.handlers.ResourceHandler; + +/** + * Similar to GdxTextureResource, but now I'm probably replacing use of that one + * with this one. I'm trying to fight the system here and avoid porting + * Ghostwolf's BLP parser to java, and just use the Java BLP parser that I + * already had, but the libraries are not playing nicely with each other, so + * this class is written to be a lower level solution (OpenGL calls instead of + * LibGDX api) that will work. + * + * My theory is that because doing it THIS way works on Retera Model Studio, + * therefore it should work here as well. + */ +public abstract class RawOpenGLTextureResource extends Texture { + private static final int BYTES_PER_PIXEL = 4; + private final int target; + protected int handle; + private int width; + private int height; + private int wrapS = GL20.GL_CLAMP_TO_EDGE; + private int wrapT = GL20.GL_CLAMP_TO_EDGE; + private final int magFilter = GL20.GL_LINEAR; + private final int minFilter = GL20.GL_LINEAR; + + public RawOpenGLTextureResource(final ModelViewer viewer, final String extension, final PathSolver pathSolver, + final String fetchUrl, final ResourceHandler handler) { + super(viewer, extension, pathSolver, fetchUrl, handler); + final GL20 gl = this.viewer.gl; + this.handle = gl.glGenTexture(); + this.target = GL20.GL_TEXTURE_2D; + gl.glBindTexture(this.target, this.handle); + gl.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_MIN_FILTER, this.minFilter); + gl.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_MAG_FILTER, this.magFilter); + } + + @Override + protected void error(final Exception e) { + e.printStackTrace(); + } + + @Override + public void bind(final int unit) { + this.viewer.webGL.bindTexture(this, unit); + } + + @Override + public void internalBind() { + this.viewer.gl.glBindTexture(this.target, this.handle); + } + + @Override + public int getWidth() { + return this.width; + } + + @Override + public int getHeight() { + return this.height; + } + + @Override + public int getGlTarget() { + return this.target; + } + + @Override + public void setWrapS(final boolean wrapS) { + this.wrapS = wrapS ? GL20.GL_REPEAT : GL20.GL_CLAMP_TO_EDGE; + final GL20 gl = this.viewer.gl; + + gl.glBindTexture(this.target, this.handle); + gl.glTexParameteri(this.target, GL20.GL_TEXTURE_WRAP_S, this.wrapS); + } + + @Override + public void setWrapT(final boolean wrapT) { + this.wrapT = wrapT ? GL20.GL_REPEAT : GL20.GL_CLAMP_TO_EDGE; + final GL20 gl = this.viewer.gl; + + gl.glBindTexture(this.target, this.handle); + gl.glTexParameteri(this.target, GL20.GL_TEXTURE_WRAP_T, this.wrapT); + + } + + public void update(final BufferedImage image) { + final GL20 gl = this.viewer.gl; + + final int imageWidth = image.getWidth(); + final int imageHeight = image.getHeight(); + final int[] pixels = new int[imageWidth * imageHeight]; + image.getRGB(0, 0, imageWidth, imageHeight, pixels, 0, imageWidth); + + final ByteBuffer buffer = ByteBuffer.allocateDirect(imageWidth * imageHeight * BYTES_PER_PIXEL) + .order(ByteOrder.nativeOrder()); + // 4 + // for + // RGBA, + // 3 + // for + // RGB + + for (int y = 0; y < imageHeight; y++) { + for (int x = 0; x < imageWidth; x++) { + final int pixel = pixels[(y * imageWidth) + x]; + buffer.put((byte) ((pixel >> 16) & 0xFF)); // Red component + buffer.put((byte) ((pixel >> 8) & 0xFF)); // Green component + buffer.put((byte) (pixel & 0xFF)); // Blue component + buffer.put((byte) ((pixel >> 24) & 0xFF)); // Alpha component. + // Only for RGBA + } + } + + buffer.flip(); + + gl.glBindTexture(GL20.GL_TEXTURE_2D, this.handle); + +// if ((this.width == imageWidth) && (this.height == imageHeight)) { +// gl.glTexSubImage2D(GL20.GL_TEXTURE_2D, 0, 0, 0, imageWidth, imageHeight, GL20.GL_RGBA, +// GL20.GL_UNSIGNED_BYTE, buffer); +// } +// else { + gl.glTexImage2D(GL20.GL_TEXTURE_2D, 0, GL30.GL_SRGB8_ALPHA8, imageWidth, imageHeight, 0, GL20.GL_RGBA, + GL20.GL_UNSIGNED_BYTE, buffer); + + this.width = imageWidth; + this.height = imageHeight; +// } + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/Scene.java b/core/src/com/etheller/warsmash/viewer5/Scene.java index 423662d..d447f62 100644 --- a/core/src/com/etheller/warsmash/viewer5/Scene.java +++ b/core/src/com/etheller/warsmash/viewer5/Scene.java @@ -29,7 +29,7 @@ public class Scene { public final ModelViewer viewer; public final Camera camera; - public final Grid grid; + public Grid grid; public int visibleCells; public int visibleInstances; public int updatedParticles; @@ -185,7 +185,8 @@ public class Scene { if (cell.isVisible(this.camera)) { this.visibleCells += 1; - for (final ModelInstance instance : cell.instances) { + for (int i = 0, l = cell.instances.size(); i < l; i++) { + final ModelInstance instance = cell.instances.get(i); if (instance.rendered && (instance.cullFrame < frame) && instance.isVisible(this.camera)) { instance.cullFrame = frame; diff --git a/core/src/com/etheller/warsmash/viewer5/SkeletalNode.java b/core/src/com/etheller/warsmash/viewer5/SkeletalNode.java index ee340d8..bac3e60 100644 --- a/core/src/com/etheller/warsmash/viewer5/SkeletalNode.java +++ b/core/src/com/etheller/warsmash/viewer5/SkeletalNode.java @@ -10,6 +10,7 @@ import com.etheller.warsmash.util.RenderMathUtils; public abstract class SkeletalNode extends GenericNode { protected static final Vector3 locationHeap = new Vector3(); protected static final Quaternion rotationHeap = new Quaternion(); + protected static final Quaternion rotationHeap2 = new Quaternion(); protected static final Vector3 scalingHeap = new Vector3(); public UpdatableObject object; @@ -26,7 +27,7 @@ public abstract class SkeletalNode extends GenericNode { this.localScale = new Vector3(1, 1, 1); this.worldLocation = new Vector3(); this.worldRotation = new Quaternion(); - this.worldScale = new Vector3(); + this.worldScale = new Vector3(1, 1, 1); this.inverseWorldLocation = new Vector3(); this.inverseWorldRotation = new Quaternion(); this.inverseWorldScale = new Vector3(); @@ -123,12 +124,12 @@ public abstract class SkeletalNode extends GenericNode { final float x = this.pivot.x; final float y = this.pivot.y; final float z = this.pivot.z; - this.worldLocation.x = (this.worldMatrix.val[Matrix4.M00] * x) + (this.worldMatrix.val[Matrix4.M10] * y) - + (this.worldMatrix.val[Matrix4.M20] * z) + this.worldMatrix.val[Matrix4.M30]; - this.worldLocation.y = (this.worldMatrix.val[Matrix4.M01] * x) + (this.worldMatrix.val[Matrix4.M11] * y) - + (this.worldMatrix.val[Matrix4.M21] * z) + this.worldMatrix.val[Matrix4.M31]; - this.worldLocation.z = (this.worldMatrix.val[Matrix4.M02] * x) + (this.worldMatrix.val[Matrix4.M12] * y) - + (this.worldMatrix.val[Matrix4.M22] * z) + this.worldMatrix.val[Matrix4.M32]; + this.worldLocation.x = (this.worldMatrix.val[Matrix4.M00] * x) + (this.worldMatrix.val[Matrix4.M01] * y) + + (this.worldMatrix.val[Matrix4.M02] * z) + this.worldMatrix.val[Matrix4.M03]; + this.worldLocation.y = (this.worldMatrix.val[Matrix4.M10] * x) + (this.worldMatrix.val[Matrix4.M11] * y) + + (this.worldMatrix.val[Matrix4.M12] * z) + this.worldMatrix.val[Matrix4.M13]; + this.worldLocation.z = (this.worldMatrix.val[Matrix4.M20] * x) + (this.worldMatrix.val[Matrix4.M21] * y) + + (this.worldMatrix.val[Matrix4.M22] * z) + this.worldMatrix.val[Matrix4.M23]; // Inverse world location this.inverseWorldLocation.x = -this.worldLocation.x; diff --git a/core/src/com/etheller/warsmash/viewer5/Texture.java b/core/src/com/etheller/warsmash/viewer5/Texture.java index 0043ae2..4bf8677 100644 --- a/core/src/com/etheller/warsmash/viewer5/Texture.java +++ b/core/src/com/etheller/warsmash/viewer5/Texture.java @@ -1,51 +1,26 @@ package com.etheller.warsmash.viewer5; -import com.badlogic.gdx.graphics.Texture.TextureWrap; import com.etheller.warsmash.viewer5.handlers.ResourceHandler; public abstract class Texture extends HandlerResource { - private com.badlogic.gdx.graphics.Texture gdxTexture; - public Texture(final ModelViewer viewer, final ResourceHandler handler, final String extension, - final PathSolver pathSolver, final String fetchUrl) { + public Texture(final ModelViewer viewer, final String extension, final PathSolver pathSolver, final String fetchUrl, + final ResourceHandler handler) { super(viewer, extension, pathSolver, fetchUrl, handler); } - public void setGdxTexture(final com.badlogic.gdx.graphics.Texture gdxTexture) { - this.gdxTexture = gdxTexture; - } + public abstract void bind(final int unit); - @Override - protected void error(final Exception e) { - e.printStackTrace(); - } + public abstract void internalBind(); - public void bind(final int unit) { - this.viewer.webGL.bindTexture(this, unit); - } + public abstract int getWidth(); - public void internalBind() { - this.gdxTexture.bind(); - } + public abstract int getHeight(); - public int getWidth() { - return this.gdxTexture.getWidth(); - } + public abstract int getGlTarget(); - public int getHeight() { - return this.gdxTexture.getHeight(); - } + public abstract void setWrapS(final boolean wrapS); - public int getGlTarget() { - return this.gdxTexture.glTarget; - } - - public void setWrapS(final boolean wrapS) { - this.gdxTexture.setWrap(wrapS ? TextureWrap.Repeat : TextureWrap.ClampToEdge, this.gdxTexture.getVWrap()); - } - - public void setWrapT(final boolean wrapT) { - this.gdxTexture.setWrap(this.gdxTexture.getUWrap(), wrapT ? TextureWrap.Repeat : TextureWrap.ClampToEdge); - } + public abstract void setWrapT(final boolean wrapT); } diff --git a/core/src/com/etheller/warsmash/viewer5/UpdatableObject.java b/core/src/com/etheller/warsmash/viewer5/UpdatableObject.java index 52f57fe..7534f54 100644 --- a/core/src/com/etheller/warsmash/viewer5/UpdatableObject.java +++ b/core/src/com/etheller/warsmash/viewer5/UpdatableObject.java @@ -1,5 +1,5 @@ package com.etheller.warsmash.viewer5; public interface UpdatableObject { - void update(float dt); + void update(float dt, boolean visible); } diff --git a/core/src/com/etheller/warsmash/viewer5/gl/DataTexture.java b/core/src/com/etheller/warsmash/viewer5/gl/DataTexture.java index 4ba4a49..744bb41 100644 --- a/core/src/com/etheller/warsmash/viewer5/gl/DataTexture.java +++ b/core/src/com/etheller/warsmash/viewer5/gl/DataTexture.java @@ -3,11 +3,13 @@ package com.etheller.warsmash.viewer5.gl; import java.nio.Buffer; import com.badlogic.gdx.graphics.GL20; +import com.badlogic.gdx.graphics.GL30; public class DataTexture { public GL20 gl; public int texture; public int format; + public int internalFormat; public int width = 0; public int height = 0; @@ -15,11 +17,12 @@ public class DataTexture { this.gl = gl; this.texture = gl.glGenTexture(); this.format = (channels == 3 ? GL20.GL_RGB : GL20.GL_RGBA); + this.internalFormat = (channels == 3 ? GL20.GL_RGB : GL30.GL_RGBA32F); gl.glBindTexture(GL20.GL_TEXTURE_2D, this.texture); gl.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_WRAP_S, GL20.GL_CLAMP_TO_EDGE); gl.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_WRAP_T, GL20.GL_CLAMP_TO_EDGE); - gl.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_MAG_FILTER, GL20.GL_NEAREST); + gl.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_MIN_FILTER, GL20.GL_NEAREST); gl.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_MAG_FILTER, GL20.GL_NEAREST); this.reserve(width, height); @@ -33,8 +36,8 @@ public class DataTexture { this.height = Math.max(this.height, height); gl.glBindTexture(GL20.GL_TEXTURE_2D, this.texture); - gl.glTexImage2D(GL20.GL_TEXTURE_2D, 0, this.format, this.width, this.height, 0, this.format, GL20.GL_FLOAT, - null); + gl.glTexImage2D(GL20.GL_TEXTURE_2D, 0, this.internalFormat, this.width, this.height, 0, this.format, + GL20.GL_FLOAT, null); } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/blp/BlpTexture.java b/core/src/com/etheller/warsmash/viewer5/handlers/blp/BlpTexture.java index 8bbe141..6660cce 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/blp/BlpTexture.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/blp/BlpTexture.java @@ -6,18 +6,16 @@ import java.io.InputStream; import javax.imageio.ImageIO; -import com.badlogic.gdx.graphics.Texture.TextureFilter; -import com.etheller.warsmash.util.ImageUtils; import com.etheller.warsmash.viewer5.ModelViewer; import com.etheller.warsmash.viewer5.PathSolver; -import com.etheller.warsmash.viewer5.Texture; +import com.etheller.warsmash.viewer5.RawOpenGLTextureResource; import com.etheller.warsmash.viewer5.handlers.ResourceHandler; -public class BlpTexture extends Texture { +public class BlpTexture extends RawOpenGLTextureResource { public BlpTexture(final ModelViewer viewer, final ResourceHandler handler, final String extension, final PathSolver pathSolver, final String fetchUrl) { - super(viewer, handler, extension, pathSolver, fetchUrl); + super(viewer, extension, pathSolver, fetchUrl, handler); } @Override @@ -30,10 +28,7 @@ public class BlpTexture extends Texture { BufferedImage img; try { img = ImageIO.read(src); - final com.badlogic.gdx.graphics.Texture texture = ImageUtils - .getTexture(ImageUtils.forceBufferedImagesRGB(img)); - texture.setFilter(TextureFilter.Linear, TextureFilter.Linear); - setGdxTexture(texture); + update(img); } catch (final IOException e) { throw new RuntimeException(e); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/AttachmentInstance.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/AttachmentInstance.java index 5549044..cbabcc7 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/AttachmentInstance.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/AttachmentInstance.java @@ -24,29 +24,33 @@ public class AttachmentInstance implements UpdatableObject { } @Override - public void update(final float dt) { + public void update(final float dt, final boolean objectVisible) { final MdxComplexInstance internalInstance = this.internalInstance; - if (internalInstance.model.ok) { - this.attachment.getVisibility(visbilityHeap, this.instance.sequence, this.instance.frame, - this.instance.counter); - - if (visbilityHeap[0] > 0.1) { - // The parent instance might not actually be in a scene. - // This happens if loading a local model, where loading is instant and adding to - // a scene always comes afterwards. - // Therefore, do it here dynamically. - this.instance.scene.addInstance(internalInstance); - - if (internalInstance.hidden()) { - internalInstance.show(); - - // Every time the attachment becomes visible again, restart its first sequence. - internalInstance.setSequence(0); - } + if (!objectVisible) { + internalInstance.hide(); } else { - internalInstance.hide(); + this.attachment.getVisibility(visbilityHeap, this.instance.sequence, this.instance.frame, + this.instance.counter); + + if (visbilityHeap[0] > 0.1) { + // The parent instance might not actually be in a scene. + // This happens if loading a local model, where loading is instant and adding to + // a scene always comes afterwards. + // Therefore, do it here dynamically. + this.instance.scene.addInstance(internalInstance); + + if (internalInstance.hidden()) { + internalInstance.show(); + + // Every time the attachment becomes visible again, restart its first sequence. + internalInstance.setSequence(0); + } + } + else { + internalInstance.hide(); + } } } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/BatchGroup.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/BatchGroup.java index 78e4335..b254811 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/BatchGroup.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/BatchGroup.java @@ -8,6 +8,7 @@ import com.etheller.warsmash.viewer5.ModelViewer; import com.etheller.warsmash.viewer5.Scene; import com.etheller.warsmash.viewer5.Texture; import com.etheller.warsmash.viewer5.gl.DataTexture; +import com.etheller.warsmash.viewer5.gl.WebGL; public class BatchGroup extends GenericGroup { @@ -31,6 +32,7 @@ public class BatchGroup extends GenericGroup { final List replaceables = model.replaceables; final ModelViewer viewer = model.viewer; final GL20 gl = viewer.gl; + final WebGL webGL = viewer.webGL; final boolean isExtended = this.isExtended; final ShaderProgram shader; @@ -41,7 +43,7 @@ public class BatchGroup extends GenericGroup { shader = MdxHandler.Shaders.complex; } - shader.begin(); + webGL.useShaderProgram(shader); shader.setUniformMatrix("u_mvp", scene.camera.viewProjectionMatrix); @@ -52,7 +54,7 @@ public class BatchGroup extends GenericGroup { boneTexture.bind(15); shader.setUniformf("u_hasBones", 1); - shader.setUniformf("u_boneMap", 15); + shader.setUniformi("u_boneMap", 15); shader.setUniformf("u_vectorSize", 1f / boneTexture.getWidth()); shader.setUniformf("u_rowSize", 1); } @@ -86,7 +88,7 @@ public class BatchGroup extends GenericGroup { shader.setUniform2fv("u_uvTrans", uvAnim, 0, 2); shader.setUniform2fv("u_uvRot", uvAnim, 2, 2); - shader.setUniform1fv("u_uvRot", uvAnim, 4, 1); + shader.setUniform1fv("u_uvScale", uvAnim, 4, 1); layer.bind(shader); @@ -101,13 +103,15 @@ public class BatchGroup extends GenericGroup { } else { texture = textures.get(layerTexture); + + Texture textureLookup = instance.textureMapper.get(texture); + if (textureLookup == null) { + textureLookup = texture; + } + texture = textureLookup; } - Texture textureLookup = instance.textureMapper.get(texture); - if (textureLookup == null) { - textureLookup = texture; - } - viewer.webGL.bindTexture(textureLookup, 0); + viewer.webGL.bindTexture(texture, 0); if (isExtended) { geoset.bindExtended(shader, layer.coordId); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Bone.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Bone.java index 63de3dd..37bac35 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Bone.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Bone.java @@ -7,13 +7,21 @@ public class Bone extends GenericObject { public Bone(final MdxModel model, final com.etheller.warsmash.parsers.mdlx.Bone bone, final int index) { super(model, bone, index); - final int geosetAnimationId = bone.getGeosetAnimationId(); - if (geosetAnimationId != -1) { - this.geosetAnimation = model.getGeosetAnimations().get(geosetAnimationId); - } - else { - this.geosetAnimation = null; + GeosetAnimation geosetAnimation = null; + final int geosetId = bone.getGeosetId(); + if (geosetId != -1) { + final Geoset geoset = model.getGeosets().get(geosetId); + if (geoset.geosetAnimation != null) { + geosetAnimation = geoset.geosetAnimation; + } + else { + final int geosetAnimationId = bone.getGeosetAnimationId(); + if (geosetAnimationId != -1) { + geosetAnimation = model.getGeosetAnimations().get(geosetAnimationId); + } + } } + this.geosetAnimation = geosetAnimation; } @Override diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EmitterGroup.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EmitterGroup.java index 046c9b1..0ad58ff 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EmitterGroup.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EmitterGroup.java @@ -30,7 +30,7 @@ public class EmitterGroup extends GenericGroup { gl.glDisable(GL20.GL_CULL_FACE); gl.glEnable(GL20.GL_DEPTH_TEST); - shader.begin(); + viewer.webGL.useShaderProgram(shader); shader.setUniformMatrix("u_mvp", scene.camera.viewProjectionMatrix); shader.setUniformf("u_texture", 0); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectEmitterObject.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectEmitterObject.java index be6c8e5..0c546be 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectEmitterObject.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectEmitterObject.java @@ -79,7 +79,7 @@ public class EventObjectEmitterObject extends GenericObject implements EmitterOb private float pitch; private float pitchVariance; private float volume; - public List decodedBuffers; + public List decodedBuffers = new ArrayList<>(); /** * If this is an SPL/UBR emitter object, ok will be set to true if the tables * are loaded. @@ -111,6 +111,9 @@ public class EventObjectEmitterObject extends GenericObject implements EmitterOb else if ("UBR".equals(type)) { this.geometryEmitterType = GeometryEmitterFuncs.EMITTER_UBERSPLAT; } + else if ("SPN".equals(type)) { + this.geometryEmitterType = GeometryEmitterFuncs.EMITTER_SPN; + } this.type = type; this.id = id; @@ -219,6 +222,7 @@ public class EventObjectEmitterObject extends GenericObject implements EmitterOb this.columns = getInt(row, "Columns"); this.rows = getInt(row, "Rows"); this.lifeSpan = getFloat(row, "Lifespan") + getFloat(row, "Decay"); + this.intervalTimes = new float[] { getFloat(row, "Lifespan"), getFloat(row, "Decay") }; this.intervals = new float[][] { { getFloat(row, "UVLifespanStart"), getFloat(row, "UVLifespanEnd"), getFloat(row, "LifespanRepeat") }, @@ -262,10 +266,14 @@ public class EventObjectEmitterObject extends GenericObject implements EmitterOb final String[] fileNames = ((String) animSoundsRow.get("FileNames")).split(","); final GenericResource[] resources = new GenericResource[fileNames.length]; for (int i = 0; i < fileNames.length; i++) { - resources[i] = viewer.loadGeneric( + final GenericResource genericResource = viewer.loadGeneric( pathSolver.solve(((String) animSoundsRow.get("DirectoryBase")) + fileNames[i], model.solverParams).finalSrc, FetchDataTypeName.ARRAY_BUFFER, decodedDataCallback); + if (genericResource == null) { + throw new IllegalStateException("Null sound: " + fileNames[i]); + } + resources[i] = genericResource; } // TODO JS async removed diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/FilterMode.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/FilterMode.java index 2709f75..b29c055 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/FilterMode.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/FilterMode.java @@ -6,8 +6,8 @@ public class FilterMode { private static final int[] ERROR_DEFAULT = new int[] { 0, 0 }; private static final int[] MODULATE_2X = new int[] { GL20.GL_DST_COLOR, GL20.GL_SRC_COLOR }; private static final int[] MODULATE = new int[] { GL20.GL_ZERO, GL20.GL_SRC_COLOR }; - private static final int[] ADDITIVE_ALPHA = new int[] { GL20.GL_ALPHA, GL20.GL_ONE }; - private static final int[] BLEND = new int[] { GL20.GL_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA }; + private static final int[] ADDITIVE_ALPHA = new int[] { GL20.GL_SRC_ALPHA, GL20.GL_ONE }; + private static final int[] BLEND = new int[] { GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA }; public static int[] layerFilterMode(final com.etheller.warsmash.parsers.mdlx.Layer.FilterMode filterMode) { switch (filterMode) { @@ -38,7 +38,7 @@ public class FilterMode { case MODULATE2X: return MODULATE_2X; // Modulate 2x case ALPHAKEY: - return ADDITIVE_ALPHA; // Add alpha + return BLEND; // Add alpha default: return ERROR_DEFAULT; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GeometryEmitterFuncs.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GeometryEmitterFuncs.java index 23bc7f6..63d577d 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GeometryEmitterFuncs.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GeometryEmitterFuncs.java @@ -62,6 +62,7 @@ public class GeometryEmitterFuncs { public static final int EMITTER_RIBBON = 1; public static final int EMITTER_SPLAT = 2; public static final int EMITTER_UBERSPLAT = 3; + public static final int EMITTER_SPN = 4; // added by Retera because reasons private static final Vector3 locationHeap = new Vector3(); private static final Vector3 startHeap = new Vector3(); @@ -79,7 +80,8 @@ public class GeometryEmitterFuncs { final int teamColor = instance.teamColor; int offset = 0; - for (final Particle2 object : objects) { + for (int objectIndex = 0; objectIndex < emitter.alive; objectIndex++) { + final Particle2 object = objects.get(objectIndex); final int byteOffset = offset * BYTES_PER_OBJECT; final int floatOffset = offset * FLOATS_PER_OBJECT; final int p0Offset = floatOffset + FLOAT_OFFSET_P0; @@ -127,6 +129,7 @@ public class GeometryEmitterFuncs { floatView.put(p0Offset + 8, scale.z); floatView.put(floatOffset + FLOAT_OFFSET_HEALTH, object.health); + byteView.put(byteOffset + BYTE_OFFSET_TAIL, (byte) tail); byteView.put(byteOffset + BYTE_OFFSET_TEAM_COLOR, (byte) teamColor); @@ -188,9 +191,9 @@ public class GeometryEmitterFuncs { shader.setUniform3fv("u_intervals[2]", intervals[2], 0, 3); shader.setUniform3fv("u_intervals[3]", intervals[3], 0, 3); - shader.setUniform4fv("u_colors[0]", colors[0], 0, 3); - shader.setUniform4fv("u_colors[1]", colors[1], 0, 3); - shader.setUniform4fv("u_colors[2]", colors[2], 0, 3); + shader.setUniform4fv("u_colors[0]", colors[0], 0, 4); + shader.setUniform4fv("u_colors[1]", colors[1], 0, 4); + shader.setUniform4fv("u_colors[2]", colors[2], 0, 4); shader.setUniform3fv("u_scaling", emitterObject.scaling, 0, 3); @@ -377,9 +380,6 @@ public class GeometryEmitterFuncs { } public static void renderEmitter(final MdxEmitter emitter, final ShaderProgram shader) { - if (emitter == null) { - System.err.println("NULL EMITTER"); - } int alive = emitter.alive; final EmitterObject emitterObject = emitter.emitterObject; final int emitterType = emitterObject.getGeometryEmitterType(); @@ -387,6 +387,9 @@ public class GeometryEmitterFuncs { if (emitterType == EMITTER_RIBBON) { alive -= 1; } + else if (emitterType == EMITTER_SPN) { + return; + } if (alive > 0) { final ModelViewer viewer = emitter.instance.model.viewer; @@ -395,6 +398,8 @@ public class GeometryEmitterFuncs { final GL20 gl = viewer.gl; final int size = alive * BYTES_PER_OBJECT; + buffer.reserve(size); + switch (emitterType) { case EMITTER_PARTICLE2: bindParticleEmitter2Buffer((ParticleEmitter2) emitter, buffer); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Geoset.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Geoset.java index 5588e38..1ec84c0 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Geoset.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Geoset.java @@ -94,7 +94,7 @@ public class Geoset { public void bind(final ShaderProgram shader, final int coordId) { // TODO use indices instead of strings for attributes shader.setVertexAttribute("a_position", 3, GL20.GL_FLOAT, false, 0, this.positionOffset); - shader.setVertexAttribute("a_normal", 3, GL20.GL_FLOAT, false, 0, this.normalOffset); +// shader.setVertexAttribute("a_normal", 3, GL20.GL_FLOAT, false, 0, this.normalOffset); shader.setVertexAttribute("a_uv", 2, GL20.GL_FLOAT, false, 0, this.uvOffset + (coordId * this.vertices * 8)); shader.setVertexAttribute("a_bones", 4, GL20.GL_UNSIGNED_BYTE, false, 5, this.skinOffset); shader.setVertexAttribute("a_boneNumber", 1, GL20.GL_UNSIGNED_BYTE, false, 5, this.skinOffset + 4); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Layer.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Layer.java index 359df40..9ae9826 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Layer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Layer.java @@ -90,10 +90,10 @@ public class Layer extends AnimatedObject { } if (this.twoSided != 0) { - gl.glEnable(GL20.GL_CULL_FACE); + gl.glDisable(GL20.GL_CULL_FACE); } else { - gl.glDisable(GL20.GL_CULL_FACE); + gl.glEnable(GL20.GL_CULL_FACE); } if (this.noDepthTest != 0) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java index 1f22002..ff51f8f 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java @@ -345,8 +345,8 @@ public class MdxComplexInstance extends ModelInstance { // This includes attachments and emitters. final UpdatableObject object = node.object; - if ((object != null) && objectVisible) { - object.update(dt); + if (object != null) { + object.update(dt, objectVisible); } // Update all of the node's non-skeletal children, which will update their @@ -462,20 +462,20 @@ public class MdxComplexInstance extends ModelInstance { for (int i = 0, l = this.worldMatrices.length; i < l; i++) { final Matrix4 worldMatrix = this.worldMatrices[i]; this.worldMatricesCopyHeap.put((i * 16) + 0, worldMatrix.val[Matrix4.M00]); - this.worldMatricesCopyHeap.put((i * 16) + 1, worldMatrix.val[Matrix4.M01]); - this.worldMatricesCopyHeap.put((i * 16) + 2, worldMatrix.val[Matrix4.M02]); - this.worldMatricesCopyHeap.put((i * 16) + 3, worldMatrix.val[Matrix4.M03]); - this.worldMatricesCopyHeap.put((i * 16) + 4, worldMatrix.val[Matrix4.M10]); + this.worldMatricesCopyHeap.put((i * 16) + 1, worldMatrix.val[Matrix4.M10]); + this.worldMatricesCopyHeap.put((i * 16) + 2, worldMatrix.val[Matrix4.M20]); + this.worldMatricesCopyHeap.put((i * 16) + 3, worldMatrix.val[Matrix4.M30]); + this.worldMatricesCopyHeap.put((i * 16) + 4, worldMatrix.val[Matrix4.M01]); this.worldMatricesCopyHeap.put((i * 16) + 5, worldMatrix.val[Matrix4.M11]); - this.worldMatricesCopyHeap.put((i * 16) + 6, worldMatrix.val[Matrix4.M12]); - this.worldMatricesCopyHeap.put((i * 16) + 7, worldMatrix.val[Matrix4.M13]); - this.worldMatricesCopyHeap.put((i * 16) + 8, worldMatrix.val[Matrix4.M20]); - this.worldMatricesCopyHeap.put((i * 16) + 9, worldMatrix.val[Matrix4.M21]); + this.worldMatricesCopyHeap.put((i * 16) + 6, worldMatrix.val[Matrix4.M21]); + this.worldMatricesCopyHeap.put((i * 16) + 7, worldMatrix.val[Matrix4.M31]); + this.worldMatricesCopyHeap.put((i * 16) + 8, worldMatrix.val[Matrix4.M02]); + this.worldMatricesCopyHeap.put((i * 16) + 9, worldMatrix.val[Matrix4.M12]); this.worldMatricesCopyHeap.put((i * 16) + 10, worldMatrix.val[Matrix4.M22]); - this.worldMatricesCopyHeap.put((i * 16) + 11, worldMatrix.val[Matrix4.M23]); - this.worldMatricesCopyHeap.put((i * 16) + 12, worldMatrix.val[Matrix4.M30]); - this.worldMatricesCopyHeap.put((i * 16) + 13, worldMatrix.val[Matrix4.M31]); - this.worldMatricesCopyHeap.put((i * 16) + 14, worldMatrix.val[Matrix4.M32]); + this.worldMatricesCopyHeap.put((i * 16) + 11, worldMatrix.val[Matrix4.M32]); + this.worldMatricesCopyHeap.put((i * 16) + 12, worldMatrix.val[Matrix4.M03]); + this.worldMatricesCopyHeap.put((i * 16) + 13, worldMatrix.val[Matrix4.M13]); + this.worldMatricesCopyHeap.put((i * 16) + 14, worldMatrix.val[Matrix4.M23]); this.worldMatricesCopyHeap.put((i * 16) + 15, worldMatrix.val[Matrix4.M33]); } this.boneTexture.bindAndUpdate(this.worldMatricesCopyHeap); @@ -515,7 +515,7 @@ public class MdxComplexInstance extends ModelInstance { this.allowParticleSpawn = true; if (this.frame >= interval[1]) { - if ((this.sequenceLoopMode == 2) || ((this.sequenceLoopMode == 0) && (sequence.getFlags() == 0))) { + if ((this.sequenceLoopMode == 2) || ((this.sequenceLoopMode == 1) && (sequence.getFlags() == 0))) { this.frame = (int) interval[0]; // TODO not cast this.resetEventEmitters(); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxEmitter.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxEmitter.java index 0b1b9eb..f6a0fee 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxEmitter.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxEmitter.java @@ -17,9 +17,12 @@ public abstract class MdxEmitter { super(handler, viewer, extension, pathSolver, fetchUrl); } + @Override public ModelInstance createInstance(final int type) { if (type == 1) { return new MdxSimpleInstance(this); @@ -173,7 +175,7 @@ public class MdxModel extends com.etheller.warsmash.viewer5.Model { final List teamGlows = reforged ? MdxHandler.reforgedTeamGlows : MdxHandler.teamGlows; if (teamColors.isEmpty()) { - for (int i = 0; i < 28; i++) { + for (int i = 0; i < WarsmashConstants.MAX_PLAYERS; i++) { final String id = ReplaceableIds.getIdString(i); teamColors.add((Texture) viewer.load("ReplaceableTextures\\TeamColor\\TeamColor" + id + texturesExt, diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxNode.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxNode.java index c958764..cafc3a2 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxNode.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxNode.java @@ -10,8 +10,8 @@ public class MdxNode extends SkeletalNode { @Override protected void convertBasis(final Quaternion computedRotation) { - computedRotation.mulLeft(HALF_PI_Y); - computedRotation.mulLeft(HALF_PI_X); + computedRotation.mul(HALF_PI_Y); + computedRotation.mul(HALF_PI_X); } @Override diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxRenderBatch.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxRenderBatch.java index f5cf018..2a306c3 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxRenderBatch.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxRenderBatch.java @@ -40,16 +40,16 @@ public class MdxRenderBatch extends RenderBatch { final int offset = i * 12; floatView.put(offset + 0, worldMatrix.val[Matrix4.M00]); - floatView.put(offset + 1, worldMatrix.val[Matrix4.M01]); - floatView.put(offset + 2, worldMatrix.val[Matrix4.M02]); - floatView.put(offset + 3, worldMatrix.val[Matrix4.M03]); - floatView.put(offset + 4, worldMatrix.val[Matrix4.M10]); - floatView.put(offset + 5, worldMatrix.val[Matrix4.M11]); - floatView.put(offset + 6, worldMatrix.val[Matrix4.M12]); - floatView.put(offset + 7, worldMatrix.val[Matrix4.M13]); - floatView.put(offset + 8, worldMatrix.val[Matrix4.M20]); - floatView.put(offset + 9, worldMatrix.val[Matrix4.M21]); - floatView.put(offset + 10, worldMatrix.val[Matrix4.M22]); + floatView.put(offset + 1, worldMatrix.val[Matrix4.M10]); + floatView.put(offset + 2, worldMatrix.val[Matrix4.M20]); + floatView.put(offset + 3, worldMatrix.val[Matrix4.M01]); + floatView.put(offset + 4, worldMatrix.val[Matrix4.M11]); + floatView.put(offset + 5, worldMatrix.val[Matrix4.M21]); + floatView.put(offset + 6, worldMatrix.val[Matrix4.M02]); + floatView.put(offset + 7, worldMatrix.val[Matrix4.M12]); + floatView.put(offset + 8, worldMatrix.val[Matrix4.M22]); + floatView.put(offset + 9, worldMatrix.val[Matrix4.M03]); + floatView.put(offset + 10, worldMatrix.val[Matrix4.M13]); floatView.put(offset + 11, worldMatrix.val[Matrix4.M23]); } @@ -88,7 +88,8 @@ public class MdxRenderBatch extends RenderBatch { transposeHeap.set(this.scene.camera.viewProjectionMatrix); transposeHeap.tra(); - shader.setUniformMatrix4fv("u_VP", transposeHeap.val, 0, transposeHeap.val.length); + shader.setUniformMatrix4fv("u_VP", this.scene.camera.viewProjectionMatrix.val, 0, + this.scene.camera.viewProjectionMatrix.val.length); gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, model.arrayBuffer); gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, model.elementBuffer); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxShaders.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxShaders.java index 8322d7d..8009bb3 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxShaders.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxShaders.java @@ -66,8 +66,7 @@ public class MdxShaders { " varying vec2 v_uv;\r\n" + // " void main() {\r\n" + // " v_uv = a_uv;\r\n" + // -// " gl_Position = u_VP * mat4(a_m0, 0.0, a_m1, 0.0, a_m2, 0.0, a_m3, 1.0) * vec4(a_position, 1.0);\r\n" + // - " gl_Position = u_VP * vec4(a_position, 1.0);\r\n" + // + " gl_Position = u_VP * mat4(a_m0, 0.0, a_m1, 0.0, a_m2, 0.0, a_m3, 1.0) * vec4(a_position, 1.0);\r\n" + // " }\r\n"; public static final String fsSimple = "\r\n" + // @@ -84,8 +83,7 @@ public class MdxShaders { " gl_FragColor = color;\r\n" + // " }\r\n"; - public static final String vsComplex = Shaders.boneTexture + "\r\n" + // - " uniform mat4 u_mvp;\r\n" + // + public static final String vsComplex = " uniform mat4 u_mvp;\r\n" + // " uniform vec4 u_vertexColor;\r\n" + // " uniform vec4 u_geosetColor;\r\n" + // " uniform float u_layerAlpha;\r\n" + // @@ -105,6 +103,7 @@ public class MdxShaders { " varying vec4 v_color;\r\n" + // " varying vec4 v_uvTransRot;\r\n" + // " varying float v_uvScale;\r\n" + // + Shaders.boneTexture + "\r\n" + // " void transform(inout vec3 position, inout vec3 normal) {\r\n" + // " // For the broken models out there, since the game supports this.\r\n" + // " if (a_boneNumber > 0.0) {\r\n" + // @@ -132,6 +131,7 @@ public class MdxShaders { " position = p.xyz / a_boneNumber;\r\n" + // " normal = normalize(n.xyz);\r\n" + // " }\r\n" + // + "\r\n" + // " }\r\n" + // " void main() {\r\n" + // " vec3 position = a_position;\r\n" + // @@ -146,6 +146,65 @@ public class MdxShaders { " gl_Position = u_mvp * vec4(position, 1.0);\r\n" + // " }"; + public static final String vsComplexUnshaded = " uniform mat4 u_mvp;\r\n" + // + " uniform vec4 u_vertexColor;\r\n" + // + " uniform vec4 u_geosetColor;\r\n" + // + " uniform float u_layerAlpha;\r\n" + // + " uniform vec2 u_uvTrans;\r\n" + // + " uniform vec2 u_uvRot;\r\n" + // + " uniform float u_uvScale;\r\n" + // + " uniform bool u_hasBones;\r\n" + // + " attribute vec3 a_position;\r\n" + // + " attribute vec2 a_uv;\r\n" + // + " attribute vec4 a_bones;\r\n" + // + " #ifdef EXTENDED_BONES\r\n" + // + " attribute vec4 a_extendedBones;\r\n" + // + " #endif\r\n" + // + " attribute float a_boneNumber;\r\n" + // + " varying vec2 v_uv;\r\n" + // + " varying vec4 v_color;\r\n" + // + " varying vec4 v_uvTransRot;\r\n" + // + " varying float v_uvScale;\r\n" + // + Shaders.boneTexture + "\r\n" + // + " void transform(inout vec3 position) {\r\n" + // + " // For the broken models out there, since the game supports this.\r\n" + // + " if (a_boneNumber > 0.0) {\r\n" + // + " vec4 position4 = vec4(position, 1.0);\r\n" + // + " mat4 bone;\r\n" + // + " vec4 p = vec4(0.0,0.0,0.0,0.0);\r\n" + // + " for (int i = 0; i < 4; i++) {\r\n" + // + " if (a_bones[i] > 0.0) {\r\n" + // + " bone = fetchMatrix(a_bones[i] - 1.0, 0.0);\r\n" + // + " p += bone * position4;\r\n" + // + " }\r\n" + // + " }\r\n" + // + " #ifdef EXTENDED_BONES\r\n" + // + " for (int i = 0; i < 4; i++) {\r\n" + // + " if (a_extendedBones[i] > 0.0) {\r\n" + // + " bone = fetchMatrix(a_extendedBones[i] - 1.0, 0.0);\r\n" + // + " p += bone * position4;\r\n" + // + " }\r\n" + // + " }\r\n" + // + " #endif\r\n" + // + " position = p.xyz / a_boneNumber;\r\n" + // +// " position.x *= fetchMatrix(0.0, 0.0)[0][0];\r\n" + // + " } else {\r\n" + // + " position.x += 100.0;\r\n" + // + " }\r\n" + // + "\r\n" + // + " }\r\n" + // + " void main() {\r\n" + // + " vec3 position = a_position;\r\n" + // + " if (u_hasBones) {\r\n" + // + " transform(position);\r\n" + // + " }\r\n" + // + " v_uv = a_uv;\r\n" + // + " v_color = u_vertexColor * u_geosetColor.bgra * vec4(1.0, 1.0, 1.0, u_layerAlpha);\r\n" + // + " v_uvTransRot = vec4(u_uvTrans, u_uvRot);\r\n" + // + " v_uvScale = u_uvScale;\r\n" + // + " gl_Position = u_mvp * vec4(position, 1.0);\r\n" + // + " }"; + public static final String fsComplex = Shaders.quatTransform + "\r\n\r\n" + // " uniform sampler2D u_texture;\r\n" + // " uniform float u_filterMode;\r\n" + // diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Particle2.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Particle2.java index c83bb22..b125d8a 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Particle2.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Particle2.java @@ -11,7 +11,7 @@ public class Particle2 extends EmittedObject { @@ -22,7 +23,9 @@ public class QuaternionSd extends Sd { @Override protected void interpolate(final float[] out, final float[][] values, final float[][] inTans, final float[][] outTans, final int start, final int end, final float t) { - Interpolator.interpolateQuaternion(out, values[start], outTans[start], inTans[end], values[end], t, + Interpolator.interpolateQuaternion(out, values[start], + (start < outTans.length) ? outTans[start] : RenderMathUtils.EMPTY_FLOAT_ARRAY, + (start < inTans.length) ? inTans[end] : RenderMathUtils.EMPTY_FLOAT_ARRAY, values[end], t, this.interpolationType); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ReplaceableIds.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ReplaceableIds.java index 2f2371e..c4722a1 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ReplaceableIds.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ReplaceableIds.java @@ -3,12 +3,14 @@ package com.etheller.warsmash.viewer5.handlers.mdx; import java.util.HashMap; import java.util.Map; +import com.etheller.warsmash.util.WarsmashConstants; + public class ReplaceableIds { private static final Map ID_TO_STR = new HashMap<>(); private static final Map REPLACEABLE_ID_TO_STR = new HashMap<>(); static { - for (int i = 0; i < 28; i++) { + for (int i = 0; i < WarsmashConstants.MAX_PLAYERS; i++) { ID_TO_STR.put(Long.valueOf(i), String.format("%2d", i).replace(' ', '0')); } REPLACEABLE_ID_TO_STR.put(Long.valueOf(1), "TeamColor\\TeamColor00"); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ScalarSd.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ScalarSd.java index 7690972..d4f7e82 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ScalarSd.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ScalarSd.java @@ -32,10 +32,12 @@ public class ScalarSd extends Sd { out[0] = RenderMathUtils.lerp(startValue, values[end][0], t); break; case 2: - out[0] = RenderMathUtils.hermite(startValue, outTans[start][0], inTans[end][0], values[end][0], t); + out[0] = RenderMathUtils.hermite(startValue, (start < outTans.length) ? outTans[start][0] : 0f, + (start < inTans.length) ? inTans[end][0] : 0f, values[end][0], t); break; case 3: - out[0] = RenderMathUtils.bezier(startValue, outTans[start][0], inTans[end][0], values[end][0], t); + out[0] = RenderMathUtils.bezier(startValue, (start < outTans.length) ? outTans[start][0] : 0f, + (start < inTans.length) ? inTans[end][0] : 0f, values[end][0], t); break; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Sd.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Sd.java index 9c72446..a8894b8 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Sd.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Sd.java @@ -119,7 +119,8 @@ public abstract class Sd { public int getValue(final TYPE out, final int sequence, final int frame, final int counter) { if (this.globalSequence != null) { - return this.globalSequence.getValue(out, counter % this.globalSequence.end); + return this.globalSequence.getValue(out, + this.globalSequence.end == 0 ? 0 : counter % this.globalSequence.end); } else if (sequence != -1) { return this.sequences.get(sequence).getValue(out, frame); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SdSequence.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SdSequence.java index a877bce..1a6801d 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SdSequence.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SdSequence.java @@ -47,8 +47,8 @@ public final class SdSequence { // This fixes problems spread over many models, e.g. HeroMountainKing // (compare in WE and in Magos). if (isGlobalSequence && (frames.length > 0) && (frames[0] > end)) { - this.frames[0] = frames[0]; - this.values[0] = values[0]; + framesBuilder.add(frames[0]); + valuesBuilder.add(values[0]); } // Go over the keyframes, and add all of the ones that are in this @@ -147,10 +147,10 @@ public final class SdSequence { else { for (int i = 1; i < l; i++) { if (this.frames[i] > frame) { - final long start = this.frames[i = 1]; + final long start = this.frames[i - 1]; final long end = this.frames[i]; - final float t = RenderMathUtils.clamp(((end - start) == 0 ? 0 : ((frame - start) / (end - start))), - 0, 1); + final float t = RenderMathUtils + .clamp(((end - start) == 0 ? 0 : ((frame - start) / (float) (end - start))), 0, 1); this.sd.interpolate(out, this.values, this.inTans, this.outTans, i - 1, i, t); @@ -172,6 +172,9 @@ public final class SdSequence { else if ((a instanceof float[]) && (b instanceof float[])) { return Arrays.equals(((float[]) a), (float[]) b); } + else if ((a instanceof long[]) && (b instanceof long[])) { + return Arrays.equals(((long[]) a), (long[]) b); + } return false; } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SetupGeosets.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SetupGeosets.java index 498fc6c..31e473d 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SetupGeosets.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SetupGeosets.java @@ -1,14 +1,11 @@ package com.etheller.warsmash.viewer5.handlers.mdx; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.FloatBuffer; -import java.nio.ShortBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import com.badlogic.gdx.graphics.GL20; +import com.etheller.warsmash.util.RenderMathUtils; public class SetupGeosets { private static final int NORMAL_BATCH = 0; @@ -56,11 +53,13 @@ public class SetupGeosets { batchTypes[i] = EXTENDED_BATCH; } else { + skinBytes += vertices * 5; + batchTypes[i] = NORMAL_BATCH; } } - faceBytes += geoset.getFaces().length * 4; + faceBytes += geoset.getFaces().length * 2; } } @@ -158,54 +157,33 @@ public class SetupGeosets { } // Positions. - gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, positionOffset, positions.length, wrap(positions)); + gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, positionOffset, positions.length, + RenderMathUtils.wrap(positions)); positionOffset += positions.length * 4; // Normals. - gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, normalOffset, normals.length, wrap(normals)); + gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, normalOffset, normals.length, + RenderMathUtils.wrap(normals)); normalOffset += normals.length * 4; // Texture coordinates. for (final float[] uvSet : uvSets) { - gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, uvOffset, uvSet.length, wrap(uvSet)); + gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, uvOffset, uvSet.length, RenderMathUtils.wrap(uvSet)); uvOffset += uvSet.length * 4; } // Skin. - gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, skinOffset, skin.length, wrap(skin)); + gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, skinOffset, skin.length, RenderMathUtils.wrap(skin)); skinOffset += skin.length * 1; // Faces. - gl.glBufferSubData(GL20.GL_ELEMENT_ARRAY_BUFFER, faceOffset, faces.length, wrapFaces(faces)); - faceOffset += faces.length * 4; + gl.glBufferSubData(GL20.GL_ELEMENT_ARRAY_BUFFER, faceOffset, faces.length, + RenderMathUtils.wrapFaces(faces)); + faceOffset += faces.length * 2; } } } } - private static ShortBuffer wrapFaces(final int[] faces) { - final ShortBuffer wrapper = ByteBuffer.allocateDirect(faces.length * 2).order(ByteOrder.nativeOrder()) - .asShortBuffer(); - for (final int face : faces) { - wrapper.put((short) face); - } - wrapper.clear(); - return wrapper; - } - - private static ByteBuffer wrap(final byte[] skin) { - final ByteBuffer wrapper = ByteBuffer.allocateDirect(skin.length).order(ByteOrder.nativeOrder()); - wrapper.put(skin); - wrapper.clear(); - return wrapper; - } - - private static FloatBuffer wrap(final float[] positions) { - final FloatBuffer wrapper = ByteBuffer.allocateDirect(positions.length * 4).order(ByteOrder.nativeOrder()) - .asFloatBuffer(); - wrapper.put(positions); - wrapper.clear(); - return wrapper; - } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/UInt32Sd.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/UInt32Sd.java index 464b8ee..a8027eb 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/UInt32Sd.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/UInt32Sd.java @@ -36,10 +36,12 @@ public class UInt32Sd extends Sd { out[0] = (long) RenderMathUtils.lerp(startValue, values[end][0], t); break; case 2: - out[0] = (long) RenderMathUtils.hermite(startValue, outTans[start][0], inTans[end][0], values[end][0], t); + out[0] = (long) RenderMathUtils.hermite(startValue, (start < outTans.length) ? outTans[start][0] : 0, + (start < inTans.length) ? inTans[end][0] : 0, values[end][0], t); break; case 3: - out[0] = (long) RenderMathUtils.bezier(startValue, outTans[start][0], inTans[end][0], values[end][0], t); + out[0] = (long) RenderMathUtils.bezier(startValue, (start < outTans.length) ? outTans[start][0] : 0, + (start < inTans.length) ? inTans[end][0] : 0, values[end][0], t); break; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/VectorSd.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/VectorSd.java index 90fbab8..d34547b 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/VectorSd.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/VectorSd.java @@ -2,6 +2,7 @@ package com.etheller.warsmash.viewer5.handlers.mdx; import com.etheller.warsmash.parsers.mdlx.timeline.Timeline; import com.etheller.warsmash.util.Interpolator; +import com.etheller.warsmash.util.RenderMathUtils; public class VectorSd extends Sd { @@ -22,7 +23,9 @@ public class VectorSd extends Sd { @Override protected void interpolate(final float[] out, final float[][] values, final float[][] inTans, final float[][] outTans, final int start, final int end, final float t) { - Interpolator.interpolateVector(out, values[start], outTans[start], inTans[end], values[end], t, + Interpolator.interpolateVector(out, values[start], + (start < outTans.length) ? outTans[start] : RenderMathUtils.EMPTY_FLOAT_ARRAY, + (start < outTans.length) ? inTans[end] : RenderMathUtils.EMPTY_FLOAT_ARRAY, values[end], t, this.interpolationType); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Doodad.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Doodad.java new file mode 100644 index 0000000..69f8d73 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Doodad.java @@ -0,0 +1,33 @@ +package com.etheller.warsmash.viewer5.handlers.w3x; + +import com.badlogic.gdx.math.Quaternion; +import com.etheller.warsmash.util.MappedDataRow; +import com.etheller.warsmash.util.RenderMathUtils; +import com.etheller.warsmash.viewer5.BatchedInstance; +import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; + +public class Doodad { + private final BatchedInstance instance; + private final MappedDataRow row; + + public Doodad(final War3MapViewer map, final MdxModel model, final MappedDataRow row, + final com.etheller.warsmash.parsers.w3x.doo.Doodad doodad) { + final boolean isSimple = ((Number) row.get("lightweight")).intValue() == 1; + BatchedInstance instance; + + if (isSimple) { + instance = (BatchedInstance) model.addInstance(1); + } + else { + instance = (BatchedInstance) model.addInstance(); + } + + instance.move(doodad.getLocation()); + instance.rotateLocal(new Quaternion().setFromAxis(RenderMathUtils.VEC3_UNIT_Z, doodad.getAngle())); + instance.scale(doodad.getScale()); + instance.setScene(map.worldScene); + + this.instance = instance; + this.row = row; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/IndexedSequence.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/IndexedSequence.java new file mode 100644 index 0000000..273a824 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/IndexedSequence.java @@ -0,0 +1,13 @@ +package com.etheller.warsmash.viewer5.handlers.w3x; + +import com.etheller.warsmash.parsers.mdlx.Sequence; + +public class IndexedSequence { + public final Sequence sequence; + public final int index; + + public IndexedSequence(final Sequence sequence, final int index) { + this.sequence = sequence; + this.index = index; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/StandSequence.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/StandSequence.java new file mode 100644 index 0000000..d0b077b --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/StandSequence.java @@ -0,0 +1,64 @@ +package com.etheller.warsmash.viewer5.handlers.w3x; + +import java.util.ArrayList; +import java.util.List; + +import com.etheller.warsmash.parsers.mdlx.Sequence; +import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; +import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; + +public class StandSequence { + + private static final StandSequenceComparator STAND_SEQUENCE_COMPARATOR = new StandSequenceComparator(); + + public static List filterSequences(final String type, final List sequences) { + final List filtered = new ArrayList<>(); + + for (int i = 0, l = sequences.size(); i < l; i++) { + final Sequence sequence = sequences.get(i); + final String name = sequence.getName().split("-")[0].trim().toLowerCase(); + + if (name.equals(type)) { + filtered.add(new IndexedSequence(sequence, i)); + } + } + + return filtered; + } + + public static IndexedSequence selectSequence(final String type, final List sequences) { + final List filtered = filterSequences(type, sequences); + + filtered.sort(STAND_SEQUENCE_COMPARATOR); + + int i = 0; + for (final int l = filtered.size(); i < l; i++) { + final Sequence sequence = filtered.get(i).sequence; + final float rarity = sequence.getRarity(); + + if (rarity == 0) { + break; + } + + if ((Math.random() * 10) > rarity) { + return filtered.get(i); + } + } + + final int sequencesLeft = filtered.size() - i; + final int random = (int) (i + Math.floor(Math.random() * sequencesLeft)); + final IndexedSequence sequence = filtered.get(random); + + return sequence; + } + + public static void randomStandSequence(final MdxComplexInstance target) { + final MdxModel model = (MdxModel) target.model; + final List sequences = model.getSequences(); + final IndexedSequence sequence = selectSequence("stand", sequences); + + if (sequence != null) { + target.setSequence(sequence.index); + } + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/StandSequenceComparator.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/StandSequenceComparator.java new file mode 100644 index 0000000..1aa65af --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/StandSequenceComparator.java @@ -0,0 +1,11 @@ +package com.etheller.warsmash.viewer5.handlers.w3x; + +import java.util.Comparator; + +public class StandSequenceComparator implements Comparator { + @Override + public int compare(final IndexedSequence a, final IndexedSequence b) { + return (int) Math.signum(b.sequence.getRarity() - a.sequence.getRarity()); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/TerrainDoodad.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/TerrainDoodad.java new file mode 100644 index 0000000..0c99622 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/TerrainDoodad.java @@ -0,0 +1,30 @@ +package com.etheller.warsmash.viewer5.handlers.w3x; + +import com.badlogic.gdx.math.Quaternion; +import com.etheller.warsmash.util.MappedDataRow; +import com.etheller.warsmash.util.RenderMathUtils; +import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; +import com.etheller.warsmash.viewer5.handlers.mdx.MdxSimpleInstance; + +public class TerrainDoodad { + private static final float[] locationHeap = new float[3]; + private final MdxSimpleInstance instance; + private final MappedDataRow row; + + public TerrainDoodad(final War3MapViewer map, final MdxModel model, final MappedDataRow row, + final com.etheller.warsmash.parsers.w3x.doo.TerrainDoodad doodad) { + final float[] centerOffset = map.centerOffset; + final MdxSimpleInstance instance = (MdxSimpleInstance) model.addInstance(1); + + locationHeap[0] = (doodad.getLocation()[0] * 128) + centerOffset[0] + 128; + locationHeap[0] = (doodad.getLocation()[1] * 128) + centerOffset[1] + 128; + + instance.move(locationHeap); + instance.rotateLocal( + new Quaternion().setFromAxis(RenderMathUtils.VEC3_UNIT_Z, ((Number) row.get("fixedRot")).floatValue())); + instance.setScene(map.worldScene); + + this.instance = instance; + this.row = row; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/TerrainModel.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/TerrainModel.java new file mode 100644 index 0000000..564c7df --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/TerrainModel.java @@ -0,0 +1,147 @@ +package com.etheller.warsmash.viewer5.handlers.w3x; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.util.List; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.graphics.GL20; +import com.badlogic.gdx.graphics.glutils.ShaderProgram; +import com.etheller.warsmash.WarsmashGdxGame; +import com.etheller.warsmash.parsers.mdlx.Geoset; +import com.etheller.warsmash.parsers.mdlx.MdlxModel; +import com.etheller.warsmash.util.RenderMathUtils; +import com.etheller.warsmash.viewer5.gl.ANGLEInstancedArrays; +import com.etheller.warsmash.viewer5.gl.WebGL; + +public class TerrainModel { + private static final IntBuffer GL_TEMP_BUFFER = ByteBuffer.allocateDirect(4).order(ByteOrder.nativeOrder()) + .asIntBuffer(); + private final War3MapViewer viewer; + private final int vertexBuffer; + private final int faceBuffer; + private final int normalsOffset; + private final int uvsOffset; + private final int elements; + private final int locationAndTextureBuffer; + private final int texturesOffset; + private final int instances; + private final int vao; + + public TerrainModel(final War3MapViewer viewer, final InputStream modelInput, final List locations, + final List textures, final ShaderProgram shader) { + final GL20 gl = viewer.gl; + final WebGL webgl = viewer.webGL; + final ANGLEInstancedArrays instancedArrays = webgl.instancedArrays; + final MdlxModel parser; + try { + parser = new MdlxModel(modelInput); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + final Geoset geoset = parser.getGeosets().get(0); + final float[] vertices = geoset.getVertices(); + final float[] normals = geoset.getNormals(); + final float[] uvs = geoset.getUvSets()[0]; + final int[] faces = geoset.getFaces(); + final int normalsOffset = vertices.length * 4; + final int uvsOffset = normalsOffset + (normals.length * 4); + int vao; + + GL_TEMP_BUFFER.clear(); + Gdx.gl30.glGenVertexArrays(1, GL_TEMP_BUFFER); + vao = GL_TEMP_BUFFER.get(0); + Gdx.gl30.glBindVertexArray(vao); + + final int vertexBuffer = gl.glGenBuffer(); + gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, vertexBuffer); + gl.glBufferData(GL20.GL_ARRAY_BUFFER, uvsOffset + (uvs.length * 4), null, GL20.GL_STATIC_DRAW); + gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, 0, vertices.length * 4, RenderMathUtils.wrap(vertices)); + gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, normalsOffset, normals.length * 4, RenderMathUtils.wrap(normals)); + gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, uvsOffset, uvs.length * 4, RenderMathUtils.wrap(uvs)); + + shader.setVertexAttribute("a_position", 3, GL20.GL_FLOAT, false, 0, 0); + shader.enableVertexAttribute("a_position"); + + shader.setVertexAttribute("a_normal", 3, GL20.GL_FLOAT, false, 0, normalsOffset); + shader.enableVertexAttribute("a_normal"); + + shader.setVertexAttribute("a_uv", 3, GL20.GL_FLOAT, false, 0, uvsOffset); + shader.enableVertexAttribute("a_uv"); + + final int texturesOffset = locations.size() * 3 * 4; + final int locationAndTextureBuffer = gl.glGenBuffer(); + gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, locationAndTextureBuffer); + gl.glBufferData(GL20.GL_ARRAY_BUFFER, texturesOffset + textures.size(), null, GL20.GL_STATIC_DRAW); + gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, 0, locations.size() * 3 * 4, wrapVectors(locations)); + gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, texturesOffset, textures.size(), wrapTexIndices(textures)); + + shader.setVertexAttribute("a_instancePosition", 3, GL20.GL_FLOAT, false, 0, 0); + shader.enableVertexAttribute("a_instancePosition"); + instancedArrays.glVertexAttribDivisorANGLE(shader.getAttributeLocation("a_instancePosition"), 1); + + shader.setVertexAttribute("a_instanceTexture", 1, GL20.GL_UNSIGNED_BYTE, false, 0, texturesOffset); + shader.enableVertexAttribute("a_instanceTexture"); + instancedArrays.glVertexAttribDivisorANGLE(shader.getAttributeLocation("a_instanceTexture"), 1); + + final int faceBuffer = gl.glGenBuffer(); + gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, faceBuffer); + gl.glBufferData(GL20.GL_ELEMENT_ARRAY_BUFFER, faces.length * 2, RenderMathUtils.wrapFaces(faces), + GL20.GL_STATIC_DRAW); + + WarsmashGdxGame.bindDefaultVertexArray(); + + this.viewer = viewer; + this.vertexBuffer = vertexBuffer; + this.faceBuffer = faceBuffer; + this.normalsOffset = normalsOffset; + this.uvsOffset = uvsOffset; + this.elements = faces.length; + this.locationAndTextureBuffer = locationAndTextureBuffer; + this.texturesOffset = texturesOffset; + this.instances = locations.size() / 3; + this.vao = vao; + } + + private Buffer wrapTexIndices(final List textures) { + final ByteBuffer wrapper = ByteBuffer.allocateDirect(textures.size()).order(ByteOrder.nativeOrder()); + for (final Integer texture : textures) { + wrapper.put(texture.byteValue()); + } + wrapper.clear(); + return wrapper; + } + + private Buffer wrapVectors(final List locations) { + final FloatBuffer wrapper = ByteBuffer.allocateDirect(locations.size() * 12).order(ByteOrder.nativeOrder()) + .asFloatBuffer(); + for (final float[] vector : locations) { + wrapper.put(vector[0]); + wrapper.put(vector[1]); + wrapper.put(vector[2]); + } + wrapper.clear(); + return wrapper; + } + + public void render(final ShaderProgram shader) { + final War3MapViewer viewer = this.viewer; + final GL20 gl = viewer.gl; + final WebGL webGL = viewer.webGL; + final ANGLEInstancedArrays instancedArrays = webGL.instancedArrays; + + Gdx.gl30.glBindVertexArray(this.vao); + + instancedArrays.glDrawElementsInstancedANGLE(GL20.GL_TRIANGLES, this.elements, GL20.GL_UNSIGNED_SHORT, 0, + this.instances); + + WarsmashGdxGame.bindDefaultVertexArray(); + + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Unit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Unit.java new file mode 100644 index 0000000..525560c --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Unit.java @@ -0,0 +1,36 @@ +package com.etheller.warsmash.viewer5.handlers.w3x; + +import com.badlogic.gdx.math.Quaternion; +import com.etheller.warsmash.util.MappedDataRow; +import com.etheller.warsmash.util.RenderMathUtils; +import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; +import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; + +public class Unit { + private static final float[] heapZ = new float[3]; + public final MdxComplexInstance instance; + public final MappedDataRow row; + + public Unit(final War3MapViewer map, final MdxModel model, final MappedDataRow row, + final com.etheller.warsmash.parsers.w3x.unitsdoo.Unit unit) { + final MdxComplexInstance instance = (MdxComplexInstance) model.addInstance(); + + instance.move(unit.getLocation()); + instance.rotateLocal(new Quaternion().setFromAxis(RenderMathUtils.VEC3_UNIT_Z, unit.getAngle())); + instance.scale(unit.getScale()); + instance.setTeamColor(unit.getPlayer()); + instance.setScene(map.worldScene); + + if (row != null) { + heapZ[0] = (((Number) row.get("moveHeight")).floatValue()); + + instance.move(heapZ); + instance.setVertexColor(new float[] { ((Number) row.get("red")).intValue() / 255f, + ((Number) row.get("green")).intValue() / 255f, ((Number) row.get("blue")).intValue() / 255f }); + instance.uniformScale(((Number) row.get("modelScale")).floatValue()); + } + + this.instance = instance; + this.row = row; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xShaders.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xShaders.java new file mode 100644 index 0000000..dd94da7 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xShaders.java @@ -0,0 +1,230 @@ +package com.etheller.warsmash.viewer5.handlers.w3x; + +public class W3xShaders { + public static final class Cliffs { + private Cliffs() { + } + + public static final String vert = "\r\n" + // + "uniform mat4 u_VP;\r\n" + // + "uniform sampler2D u_heightMap;\r\n" + // + "uniform vec2 u_pixel;\r\n" + // + "uniform vec2 u_centerOffset;\r\n" + // + "attribute vec3 a_position;\r\n" + // + "attribute vec3 a_normal;\r\n" + // + "attribute vec2 a_uv;\r\n" + // + "attribute vec3 a_instancePosition;\r\n" + // + "attribute float a_instanceTexture;\r\n" + // + "varying vec3 v_normal;\r\n" + // + "varying vec2 v_uv;\r\n" + // + "varying float v_texture;\r\n" + // + "varying vec3 v_position;\r\n" + // + "void main() {\r\n" + // + " // Half of a pixel in the cliff height map.\r\n" + // + " vec2 halfPixel = u_pixel * 0.5;\r\n" + // + " // The bottom left corner of the map tile this vertex is on.\r\n" + // + " vec2 corner = floor((a_instancePosition.xy - vec2(1.0, 0.0) - u_centerOffset.xy) / 128.0);\r\n" + // + " // Get the 4 closest heights in the height map.\r\n" + // + " float bottomLeft = texture2D(u_heightMap, corner * u_pixel + halfPixel).a;\r\n" + // + " float bottomRight = texture2D(u_heightMap, (corner + vec2(1.0, 0.0)) * u_pixel + halfPixel).a;\r\n" + // + " float topLeft = texture2D(u_heightMap, (corner + vec2(0.0, 1.0)) * u_pixel + halfPixel).a;\r\n" + // + " float topRight = texture2D(u_heightMap, (corner + vec2(1.0, 1.0)) * u_pixel + halfPixel).a;\r\n" + // + " \r\n" + // + " // Do a bilinear interpolation between the heights to get the final value.\r\n" + // + " float bottom = mix(bottomRight, bottomLeft, -a_position.x / 128.0);\r\n" + // + " float top = mix(topRight, topLeft, -a_position.x / 128.0);\r\n" + // + " float height = mix(bottom, top, a_position.y / 128.0);\r\n" + // + " v_normal = a_normal;\r\n" + // + " v_uv = a_uv;\r\n" + // + " v_texture = a_instanceTexture;\r\n" + // + " v_position = a_position + vec3(a_instancePosition.xy, a_instancePosition.z + height * 128.0);\r\n" + // + " gl_Position = u_VP * vec4(v_position, 1.0);\r\n" + // + "}\r\n" + // + ""; + + public static final String frag = "\r\n" + // + "// #extension GL_OES_standard_derivatives : enable\r\n" + // + "precision mediump float;\r\n" + // + "uniform sampler2D u_texture1;\r\n" + // + "uniform sampler2D u_texture2;\r\n" + // + "varying vec3 v_normal;\r\n" + // + "varying vec2 v_uv;\r\n" + // + "varying float v_texture;\r\n" + // + "varying vec3 v_position;\r\n" + // + "// const vec3 lightDirection = normalize(vec3(-0.3, -0.3, 0.25));\r\n" + // + "vec4 sample(int texture, vec2 uv) {\r\n" + // + " if (texture == 0) {\r\n" + // + " return texture2D(u_texture1, uv);\r\n" + // + " } else {\r\n" + // + " return texture2D(u_texture2, uv);\r\n" + // + " }\r\n" + // + "}\r\n" + // + "void main() {\r\n" + // + " vec4 color = sample(int(v_texture), v_uv);\r\n" + // + " // vec3 faceNormal = cross(dFdx(v_position), dFdy(v_position));\r\n" + // + " // vec3 normal = normalize((faceNormal + v_normal) * 0.5);\r\n" + // + " // color *= clamp(dot(normal, lightDirection) + 0.45, 0.1, 1.0);\r\n" + // + " gl_FragColor = color;\r\n" + // + "}\r\n" + // + ""; + } + + public static final class Ground { + private Ground() { + } + + public static final String frag = "\r\n" + // + "precision mediump float;\r\n" + // + "uniform sampler2D u_tilesets[15];\r\n" + // + "varying vec4 v_tilesets;\r\n" + // + "varying vec2 v_uv[4];\r\n" + // + "varying vec3 v_normal;\r\n" + // + "const vec3 lightDirection = normalize(vec3(-0.3, -0.3, 0.25));\r\n" + // + "vec4 sample(float tileset, vec2 uv) {\r\n" + // + " if (tileset == 0.0) {\r\n" + // + " return texture2D(u_tilesets[0], uv);\r\n" + // + " } else if (tileset == 1.0) {\r\n" + // + " return texture2D(u_tilesets[1], uv);\r\n" + // + " } else if (tileset == 2.0) {\r\n" + // + " return texture2D(u_tilesets[2], uv);\r\n" + // + " } else if (tileset == 3.0) {\r\n" + // + " return texture2D(u_tilesets[3], uv);\r\n" + // + " } else if (tileset == 4.0) {\r\n" + // + " return texture2D(u_tilesets[4], uv);\r\n" + // + " } else if (tileset == 5.0) {\r\n" + // + " return texture2D(u_tilesets[5], uv);\r\n" + // + " } else if (tileset == 6.0) {\r\n" + // + " return texture2D(u_tilesets[6], uv);\r\n" + // + " } else if (tileset == 7.0) {\r\n" + // + " return texture2D(u_tilesets[7], uv);\r\n" + // + " } else if (tileset == 8.0) {\r\n" + // + " return texture2D(u_tilesets[8], uv);\r\n" + // + " } else if (tileset == 9.0) {\r\n" + // + " return texture2D(u_tilesets[9], uv);\r\n" + // + " } else if (tileset == 10.0) {\r\n" + // + " return texture2D(u_tilesets[10], uv);\r\n" + // + " } else if (tileset == 11.0) {\r\n" + // + " return texture2D(u_tilesets[11], uv);\r\n" + // + " } else if (tileset == 12.0) {\r\n" + // + " return texture2D(u_tilesets[12], uv);\r\n" + // + " } else if (tileset == 13.0) {\r\n" + // + " return texture2D(u_tilesets[13], uv);\r\n" + // + " } else if (tileset == 14.0) {\r\n" + // + " return texture2D(u_tilesets[14], uv);\r\n" + // + " }\r\n" + // + "}\r\n" + // + "vec4 blend(vec4 color, float tileset, vec2 uv) {\r\n" + // + " vec4 texel = sample(tileset, uv);\r\n" + // + " return mix(color, texel, texel.a);\r\n" + // + "}\r\n" + // + "void main() {\r\n" + // + " vec4 color = sample(v_tilesets[0] - 1.0, v_uv[0]);\r\n" + // + " if (v_tilesets[1] > 0.5) {\r\n" + // + " color = blend(color, v_tilesets[1] - 1.0, v_uv[1]);\r\n" + // + " }\r\n" + // + " if (v_tilesets[2] > 0.5) {\r\n" + // + " color = blend(color, v_tilesets[2] - 1.0, v_uv[2]);\r\n" + // + " }\r\n" + // + " if (v_tilesets[3] > 0.5) {\r\n" + // + " color = blend(color, v_tilesets[3] - 1.0, v_uv[3]);\r\n" + // + " }\r\n" + // + " // color *= clamp(dot(v_normal, lightDirection) + 0.45, 0.0, 1.0);\r\n" + // + " gl_FragColor = color;\r\n" + // + "}\r\n" + // + ""; + + public static final String vert = "\r\n" + // + "uniform mat4 u_VP;\r\n" + // + "uniform sampler2D u_heightMap;\r\n" + // + "uniform vec2 u_pixel;\r\n" + // + "uniform vec2 u_centerOffset;\r\n" + // + "attribute vec3 a_position;\r\n" + // + "attribute vec3 a_normal;\r\n" + // + "attribute vec2 a_uv;\r\n" + // + "attribute vec3 a_instancePosition;\r\n" + // + "attribute float a_instanceTexture;\r\n" + // + "varying vec3 v_normal;\r\n" + // + "varying vec2 v_uv;\r\n" + // + "varying float v_texture;\r\n" + // + "varying vec3 v_position;\r\n" + // + "void main() {\r\n" + // + " // Half of a pixel in the cliff height map.\r\n" + // + " vec2 halfPixel = u_pixel * 0.5;\r\n" + // + " // The bottom left corner of the map tile this vertex is on.\r\n" + // + " vec2 corner = floor((a_instancePosition.xy - vec2(1.0, 0.0) - u_centerOffset.xy) / 128.0);\r\n" + // + " // Get the 4 closest heights in the height map.\r\n" + // + " float bottomLeft = texture2D(u_heightMap, corner * u_pixel + halfPixel).a;\r\n" + // + " float bottomRight = texture2D(u_heightMap, (corner + vec2(1.0, 0.0)) * u_pixel + halfPixel).a;\r\n" + // + " float topLeft = texture2D(u_heightMap, (corner + vec2(0.0, 1.0)) * u_pixel + halfPixel).a;\r\n" + // + " float topRight = texture2D(u_heightMap, (corner + vec2(1.0, 1.0)) * u_pixel + halfPixel).a;\r\n" + // + " \r\n" + // + " // Do a bilinear interpolation between the heights to get the final value.\r\n" + // + " float bottom = mix(bottomRight, bottomLeft, -a_position.x / 128.0);\r\n" + // + " float top = mix(topRight, topLeft, -a_position.x / 128.0);\r\n" + // + " float height = mix(bottom, top, a_position.y / 128.0);\r\n" + // + " v_normal = a_normal;\r\n" + // + " v_uv = a_uv;\r\n" + // + " v_texture = a_instanceTexture;\r\n" + // + " v_position = a_position + vec3(a_instancePosition.xy, a_instancePosition.z + height * 128.0);\r\n" + // + " gl_Position = u_VP * vec4(v_position, 1.0);\r\n" + // + "}\r\n" + // + ""; + } + + public static final class Water { + private Water() { + } + + public static final String frag = "\r\n" + // + "precision mediump float;\r\n" + // + "uniform sampler2D u_waterTexture;\r\n" + // + "varying vec2 v_uv;\r\n" + // + "varying vec4 v_color;\r\n" + // + "void main() {\r\n" + // + " gl_FragColor = texture2D(u_waterTexture, v_uv) * v_color;\r\n" + // + "}\r\n" + // + ""; + public static final String vert = "\r\n" + // + "uniform mat4 u_VP;\r\n" + // + "uniform sampler2D u_heightMap;\r\n" + // + "uniform sampler2D u_waterHeightMap;\r\n" + // + "uniform vec2 u_size;\r\n" + // + "uniform vec2 u_offset;\r\n" + // + "uniform float u_offsetHeight;\r\n" + // + "uniform vec4 u_minDeepColor;\r\n" + // + "uniform vec4 u_maxDeepColor;\r\n" + // + "uniform vec4 u_minShallowColor;\r\n" + // + "uniform vec4 u_maxShallowColor;\r\n" + // + "attribute vec2 a_position;\r\n" + // + "attribute float a_InstanceID;\r\n" + // + "attribute float a_isWater;\r\n" + // + "varying vec2 v_uv;\r\n" + // + "varying vec4 v_color;\r\n" + // + "const float minDepth = 10.0 / 128.0;\r\n" + // + "const float deepLevel = 64.0 / 128.0;\r\n" + // + "const float maxDepth = 72.0 / 128.0;\r\n" + // + "void main() {\r\n" + // + " if (a_isWater > 0.5) {\r\n" + // + " v_uv = a_position;\r\n" + // + " vec2 corner = vec2(mod(a_InstanceID, u_size.x), floor(a_InstanceID / u_size.x));\r\n" + // + " vec2 base = corner + a_position;\r\n" + // + " float height = texture2D(u_heightMap, base / u_size).a;\r\n" + // + " float waterHeight = texture2D(u_waterHeightMap, base / u_size).a + u_offsetHeight;\r\n" + // + " float value = clamp(waterHeight - height, 0.0, 1.0);\r\n" + // + " if (value <= deepLevel) {\r\n" + // + " value = max(0.0, value - minDepth) / (deepLevel - minDepth);\r\n" + // + " v_color = mix(u_minShallowColor, u_maxShallowColor, value) / 255.0;\r\n" + // + " } else {\r\n" + // + " value = clamp(value - deepLevel, 0.0, maxDepth - deepLevel) / (maxDepth - deepLevel);\r\n" + // + " v_color = mix(u_minDeepColor, u_maxDeepColor, value) / 255.0;\r\n" + // + " }\r\n" + // + " gl_Position = u_VP * vec4(base * 128.0 + u_offset, waterHeight * 128.0, 1.0);\r\n" + // + " } else {\r\n" + // + " v_uv = vec2(0.0);\r\n" + // + " v_color = vec4(0.0);\r\n" + // + " gl_Position = vec4(0.0);\r\n" + // + " }\r\n" + // + "}\r\n" + // + ""; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java new file mode 100644 index 0000000..3ee27ba --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -0,0 +1,552 @@ +package com.etheller.warsmash.viewer5.handlers.w3x; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; + +import com.badlogic.gdx.graphics.GL20; +import com.badlogic.gdx.graphics.glutils.ShaderProgram; +import com.badlogic.gdx.math.Vector3; +import com.etheller.warsmash.common.FetchDataTypeName; +import com.etheller.warsmash.common.LoadGenericCallback; +import com.etheller.warsmash.datasources.DataSource; +import com.etheller.warsmash.parsers.w3x.War3Map; +import com.etheller.warsmash.parsers.w3x.doo.War3MapDoo; +import com.etheller.warsmash.parsers.w3x.objectdata.Warcraft3MapObjectData; +import com.etheller.warsmash.parsers.w3x.w3e.Corner; +import com.etheller.warsmash.parsers.w3x.w3e.War3MapW3e; +import com.etheller.warsmash.parsers.w3x.w3i.War3MapW3i; +import com.etheller.warsmash.units.DataTable; +import com.etheller.warsmash.units.Element; +import com.etheller.warsmash.units.StandardObjectData; +import com.etheller.warsmash.util.MappedData; +import com.etheller.warsmash.util.MappedDataRow; +import com.etheller.warsmash.util.RenderMathUtils; +import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.CanvasProvider; +import com.etheller.warsmash.viewer5.GenericResource; +import com.etheller.warsmash.viewer5.Grid; +import com.etheller.warsmash.viewer5.ModelViewer; +import com.etheller.warsmash.viewer5.PathSolver; +import com.etheller.warsmash.viewer5.Scene; +import com.etheller.warsmash.viewer5.Texture; +import com.etheller.warsmash.viewer5.gl.WebGL; +import com.etheller.warsmash.viewer5.handlers.mdx.MdxHandler; + +public class War3MapViewer extends ModelViewer { + private static final LoadGenericCallback stringDataCallback = new StringDataCallbackImplementation(); + private static final StreamDataCallbackImplementation streamDataCallback = new StreamDataCallbackImplementation(); + + private static final Vector3 normalHeap1 = new Vector3(); + private static final Vector3 normalHeap2 = new Vector3(); + + public PathSolver wc3PathSolver; + public SolverParams solverParams = new SolverParams(); + public ShaderProgram groundShader; + public ShaderProgram waterShader; + public ShaderProgram cliffShader; + public Scene worldScene; + public int waterIndex; + public int waterIncreasePerFrame; + public float waterHeightOffset; + public List waterTextures = new ArrayList<>(); + public float[] maxDeepColor = new float[4]; + public float[] minDeepColor = new float[4]; + public float[] maxShallowColor = new float[4]; + public float[] minShallowColor = new float[4]; + public boolean anyReady; + public boolean terrainCliffsAndWaterLoaded; + public MappedData terrainData = new MappedData(); + public MappedData cliffTypesData = new MappedData(); + public MappedData waterData = new MappedData(); + public boolean terrainReady; + public boolean cliffsReady; + public boolean doodadsAndDestructiblesLoaded; + public MappedData doodadsData = new MappedData(); + public MappedData doodadMetaData = new MappedData(); + public MappedData destructableMetaData = new MappedData(); + public List doodads = new ArrayList<>(); + public List terrainDoodads = new ArrayList<>(); + public boolean doodadsReady; + public boolean unitsAndItemsLoaded; + public MappedData unitsData = new MappedData(); + public MappedData unitMetaData = new MappedData(); + public List units = new ArrayList<>(); + public boolean unitsReady; + public List tilesetTextures = new ArrayList<>(); + public List cliffTextures = new ArrayList<>(); + public List cliffModels = new ArrayList<>(); + public War3Map mapMpq; + public PathSolver mapPathSolver; + public Corner[][] corners; + public float[] centerOffset = new float[2]; + public int[] mapSize = new int[2]; + public List tilesets = new ArrayList<>(); // TODO + public int blightTextureIndex = -1; + public List cliffTilesets = new ArrayList<>(); + public int columns; + public int rows; + public int vertexBuffer; + public int faceBuffer; + public int instanceBuffer; + public int textureBuffer; + public int variationBuffer; + public int waterBuffer; + public int heightMap; + public int waterHeightMap; + public int cliffHeightMap; + + private final DataSource gameDataSource; + + public War3MapViewer(final DataSource dataSource, final CanvasProvider canvas) { + super(dataSource, canvas); + this.gameDataSource = dataSource; + + final WebGL webGL = this.webGL; + + this.addHandler(new MdxHandler()); + + this.wc3PathSolver = PathSolver.DEFAULT; + + this.groundShader = this.webGL.createShaderProgram(W3xShaders.Ground.vert, W3xShaders.Ground.frag); + this.waterShader = this.webGL.createShaderProgram(W3xShaders.Water.vert, W3xShaders.Water.frag); + this.cliffShader = this.webGL.createShaderProgram(W3xShaders.Cliffs.vert, W3xShaders.Cliffs.frag); + + this.worldScene = this.addScene(); + + loadSLKs(); + } + + public void loadSLKs() { + final GenericResource terrain = this.loadMapGeneric("Terrain\\Terrain.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource cliffTypes = this.loadMapGeneric("Terrain\\CliffTypes.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource water = this.loadMapGeneric("Terrain\\Water.slk", FetchDataTypeName.SLK, + stringDataCallback); + + // == when loaded, which is always in our system == + this.terrainCliffsAndWaterLoaded = true; + this.terrainData.load(terrain.data.toString()); + this.cliffTypesData.load(cliffTypes.data.toString()); + this.waterData.load(water.data.toString()); + // emit terrain loaded?? + + final GenericResource doodads = this.loadMapGeneric("Doodads\\Doodads.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource doodadMetaData = this.loadMapGeneric("Doodads\\DoodadMetaData.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource destructableData = this.loadMapGeneric("Units\\DestructableData.slk", + FetchDataTypeName.SLK, stringDataCallback); + final GenericResource destructableMetaData = this.loadMapGeneric("Units\\DestructableMetaData.slk", + FetchDataTypeName.SLK, stringDataCallback); + + // == when loaded, which is always in our system == + this.doodadsAndDestructiblesLoaded = true; + this.doodadsData.load(doodads.data.toString()); + this.doodadMetaData.load(doodadMetaData.data.toString()); + this.doodadsData.load(destructableData.data.toString()); + this.destructableMetaData.load(destructableData.data.toString()); + // emit doodads loaded + + final GenericResource unitData = this.loadMapGeneric("Units\\UnitData.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource unitUi = this.loadMapGeneric("Units\\unitUI.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource itemData = this.loadMapGeneric("Units\\ItemData.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource unitMetaData = this.loadMapGeneric("Units\\UnitMetaData.slk", FetchDataTypeName.SLK, + stringDataCallback); + + // == when loaded, which is always in our system == + this.unitsAndItemsLoaded = true; + this.unitsData.load(unitData.data.toString()); + this.unitsData.load(unitUi.data.toString()); + this.unitsData.load(itemData.data.toString()); + this.unitMetaData.load(unitMetaData.data.toString()); + // emit loaded + + } + + public GenericResource loadMapGeneric(final String path, final FetchDataTypeName dataType, + final LoadGenericCallback callback) { + if (this.mapMpq == null) { + return loadGeneric(path, dataType, callback); + } + else { + return loadGeneric(path, dataType, callback, this.mapMpq.getCompoundDataSource()); + } + } + + public void loadMap(final String mapFilePath) throws IOException { + final War3Map war3Map = new War3Map(this.gameDataSource, mapFilePath); + + this.mapMpq = war3Map; + + final PathSolver wc3PathSolver = this.wc3PathSolver; + + char tileset = 'A'; + + final War3MapW3i w3iFile = this.mapMpq.readMapInformation(); + + tileset = w3iFile.getTileset(); + + this.solverParams.tileset = Character.toLowerCase(tileset); + + final War3MapW3e terrainData = this.mapMpq.readEnvironment(); + final float[] centerOffset = terrainData.getCenterOffset(); + final int[] mapSize = terrainData.getMapSize(); + + this.corners = terrainData.getCorners(); + System.arraycopy(centerOffset, 0, this.centerOffset, 0, centerOffset.length); + System.arraycopy(mapSize, 0, this.mapSize, 0, mapSize.length); + + // Override the grid based on the map. + this.worldScene.grid = new Grid(centerOffset[0], centerOffset[1], (mapSize[0] * 128) - 128, + (mapSize[1] * 128) - 128, 16 * 128, 16 * 128); + + if (this.terrainCliffsAndWaterLoaded) { + this.loadTerrainCliffsAndWater(terrainData); + } + else { + throw new IllegalStateException("transcription of JS has not loaded a map and has no JS async promises"); + } + + final Warcraft3MapObjectData modifications = this.mapMpq.readModifications(); + + if (this.doodadsAndDestructiblesLoaded) { + this.loadDoodadsAndDestructibles(modifications); + } + else { + throw new IllegalStateException("transcription of JS has not loaded a map and has no JS async promises"); + } + + if (this.unitsAndItemsLoaded) { + this.loadUnitsAndItems(modifications); + } + else { + throw new IllegalStateException("transcription of JS has not loaded a map and has no JS async promises"); + } + } + + private void loadTerrainCliffsAndWater(final War3MapW3e w3e) { + final String texturesExt = this.solverParams.reforged ? ".dds" : ".blp"; + final char tileset = w3e.getTileset(); + + for (final War3ID groundTile : w3e.getGroundTiles()) { + final MappedDataRow row = this.terrainData.getRow(groundTile.asStringValue()); + + this.tilesets.add(row); + this.tilesetTextures.add((Texture) this + .load(row.get("dir").toString() + "\\" + row.get("file") + texturesExt, null, this.solverParams)); + } + + final StandardObjectData standardObjectData = new StandardObjectData(this.mapMpq.getCompoundDataSource()); + final DataTable worldEditData = standardObjectData.getWorldEditData(); + final Element tilesets = worldEditData.get("TileSets"); + + this.blightTextureIndex = this.tilesetTextures.size(); + this.tilesetTextures.add((Texture) this.load(tilesets.getField(Character.toString(tileset)) + texturesExt, null, + this.solverParams)); + + for (final War3ID cliffTile : w3e.getCliffTiles()) { + final MappedDataRow row = this.cliffTypesData.getRow(cliffTile.asStringValue()); + + this.cliffTilesets.add(row); + this.cliffTextures + .add((Texture) this.load(row.get("texDir").toString() + "\\" + row.get("texFile") + texturesExt, + null, this.solverParams)); + } + + final MappedDataRow waterRow = this.waterData.getRow(tileset + "Sha"); + + this.waterHeightOffset = ((Number) waterRow.get("height")).floatValue(); + this.waterIncreasePerFrame = ((Number) waterRow.get("texRate")).intValue() / 60; + this.waterTextures.clear(); + this.maxDeepColor[0] = ((Number) waterRow.get("Dmax_R")).floatValue(); + this.maxDeepColor[1] = ((Number) waterRow.get("Dmax_G")).floatValue(); + this.maxDeepColor[2] = ((Number) waterRow.get("Dmax_B")).floatValue(); + this.maxDeepColor[3] = ((Number) waterRow.get("Dmax_A")).floatValue(); + this.minDeepColor[0] = ((Number) waterRow.get("Dmin_R")).floatValue(); + this.minDeepColor[1] = ((Number) waterRow.get("Dmin_G")).floatValue(); + this.minDeepColor[2] = ((Number) waterRow.get("Dmin_B")).floatValue(); + this.minDeepColor[3] = ((Number) waterRow.get("Dmin_A")).floatValue(); + this.maxShallowColor[0] = ((Number) waterRow.get("Smax_R")).floatValue(); + this.maxShallowColor[1] = ((Number) waterRow.get("Smax_G")).floatValue(); + this.maxShallowColor[2] = ((Number) waterRow.get("Smax_B")).floatValue(); + this.maxShallowColor[3] = ((Number) waterRow.get("Smax_A")).floatValue(); + this.minShallowColor[0] = ((Number) waterRow.get("Smin_R")).floatValue(); + this.minShallowColor[1] = ((Number) waterRow.get("Smin_G")).floatValue(); + this.minShallowColor[2] = ((Number) waterRow.get("Smin_B")).floatValue(); + this.minShallowColor[3] = ((Number) waterRow.get("Smin_A")).floatValue(); + + for (int i = 0, l = ((Number) waterRow.get("numTex")).intValue(); i < l; i++) { + this.waterTextures.add( + (Texture) this.load(waterRow.get("texFile").toString() + ((i < 10) ? "0" : "") + i + texturesExt, + null, this.solverParams)); + } + + final GL20 gl = this.gl; + + final Corner[][] corners = w3e.getCorners(); + final int columns = this.mapSize[0]; + final int rows = this.mapSize[1]; + final float[] centerOffset = this.centerOffset; + final int instanceCount = (columns - 1) * (rows - 1); + final float[] cliffHeights = new float[columns * rows]; + final float[] cornerHeights = new float[columns * rows]; + final float[] waterHeights = new float[columns * rows]; + final short[] cornerTextures = new short[instanceCount * 4]; + final short[] cornerVariations = new short[instanceCount * 4]; + final short[] waterFlags = new short[instanceCount]; + final int instance = 0; + final Map cliffs = new HashMap<>(); + + this.columns = columns - 1; + this.rows = rows - 1; + + for (int y = 0; y < rows; y++) { + for (int x = 0; x < columns; x++) { + final Corner bottomLeft = corners[y][x]; + final int index = (y * columns) + x; + + cliffHeights[index] = bottomLeft.getGroundHeight(); + cornerHeights[index] = (bottomLeft.getGroundHeight() + bottomLeft.getLayerHeight()) - 2; + waterHeights[index] = bottomLeft.getWaterHeight(); + + if ((y < (rows - 1)) && (x < (columns - 1))) { + // Water can be used with cliffs and normal corners, so store water state + // regardless. + waterFlags[instance] = this.isWater(x, y); + + // Is this a cliff, or a normal corner? + if (this.isCliff(x, y)) { + final int bottomLeftLayer = bottomLeft.getLayerHeight(); + final int bottomRightLayer = corners[y][x + 1].getLayerHeight(); + final int topLeftLayer = corners[y + 1][x].getLayerHeight(); + final int topRightLayer = corners[y + 1][x + 1].getLayerHeight(); + final int base = Math.min(Math.min(bottomLeftLayer, bottomRightLayer), + Math.min(topLeftLayer, topRightLayer)); + final String fileName = this.cliffFileName(bottomLeftLayer, bottomRightLayer, topLeftLayer, + topRightLayer, base); + + if (!"AAAA".equals(fileName)) { + final int cliffTexture = bottomLeft.getCliffTexture(); + + // ? + if (cliffTexture == 15) { + cliffTexture = 1; + } + + final MappedDataRow cliffRow = this.cliffTilesets.get(cliffTexture); + final String dir = cliffRow.get("cliffModelDir").toString(); + final String path = "Doodads\\Terrain\\" + dir + "\\" + dir + fileName + + bottomLeft.getCliffVariation() + ".mdx"; + + if (!cliffs.containsKey(path)) { + cliffs.put(path, new CliffInfo()); + } + + cliffs.get(path).locations.add(new float[] { ((x + 1) * 128) + centerOffset[0], + (y * 128) + centerOffset[1], (base - 2) * 128 }); + cliffs.get(path).textures.add(cliffTexture); + } + } + else { + final int bottomLeftTexture = this.cornerTexture(x, y); + final int bottomRightTexture = this.cornerTexture(x + 1, y); + final int topLeftTexture = this.cornerTexture(x, y + 1); + final int topRightTexture = this.cornerTexture(x + 1, y + 1); + final LinkedHashSet texturesUnique = new LinkedHashSet<>(); + texturesUnique.add(bottomLeftTexture); + texturesUnique.add(bottomRightTexture); + texturesUnique.add(topLeftTexture); + texturesUnique.add(topRightTexture); + final List textures = new ArrayList<>(texturesUnique); + Collections.sort(textures); + + int texture = textures.remove(0); + + cornerTextures[instance * 4] = (short) (texture + 1); + cornerVariations[instance * 4] = this.getVariation(texture, bottomLeft.getGroundVariation()); + + for (int i = 0, l = textures.size(); i < l; i++) { + int bitset = 0; + + texture = textures.get(i); + + if (bottomRightTexture == texture) { + bitset |= 0b0001; + } + + if (bottomLeftTexture == texture) { + bitset |= 0b0010; + } + + if (topRightTexture == texture) { + bitset |= 0b0100; + } + + if (topLeftTexture == texture) { + bitset |= 0b1000; + } + + cornerTextures[(instance * 4) + 1 + i] = (short) (texture + 1); + cornerVariations[(instance * 4) + 1 + i] = (short) (bitset); + } + } + + instance += 1; + + } + } + } + + this.vertexBuffer = gl.glGenBuffer(); + gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.vertexBuffer); + gl.glBufferData(GL20.GL_ARRAY_BUFFER, 8 * 4, RenderMathUtils.wrap(new float[] { 0, 0, 1, 0, 0, 1, 1, 1 }), + GL20.GL_STATIC_DRAW); + + this.faceBuffer = gl.glGenBuffer(); + gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, this.faceBuffer); + gl.glBufferData(GL20.GL_ELEMENT_ARRAY_BUFFER, 6, RenderMathUtils.wrap(new byte[] { 0, 1, 2, 1, 3, 2 }), + GL20.GL_STATIC_DRAW); + + this.cliffHeightMap = gl.glGenTexture(); + gl.glBindTexture(GL20.GL_TEXTURE_2D, this.cliffHeightMap); + this.webGL.setTextureMode(GL20.GL_CLAMP_TO_EDGE, GL20.GL_CLAMP_TO_EDGE, GL20.GL_NEAREST, GL20.GL_NEAREST); + gl.glTexImage2D(GL20.GL_TEXTURE_2D, 0, GL20.GL_ALPHA, columns, rows, 0, GL20.GL_ALPHA, GL20.GL_FLOAT, + RenderMathUtils.wrap(cliffHeights)); + + this.heightMap = gl.glGenTexture(); + gl.glBindTexture(GL20.GL_TEXTURE_2D, this.heightMap); + this.webGL.setTextureMode(GL20.GL_CLAMP_TO_EDGE, GL20.GL_CLAMP_TO_EDGE, GL20.GL_NEAREST, GL20.GL_NEAREST); + gl.glTexImage2D(GL20.GL_TEXTURE_2D, 0, GL20.GL_ALPHA, columns, rows, 0, GL20.GL_ALPHA, GL20.GL_FLOAT, + RenderMathUtils.wrap(cornerHeights)); + + this.waterHeightMap = gl.glGenTexture(); + gl.glBindTexture(GL20.GL_TEXTURE_2D, this.waterHeightMap); + this.webGL.setTextureMode(GL20.GL_CLAMP_TO_EDGE, GL20.GL_CLAMP_TO_EDGE, GL20.GL_NEAREST, GL20.GL_NEAREST); + gl.glTexImage2D(GL20.GL_TEXTURE_2D, 0, GL20.GL_ALPHA, columns, rows, 0, GL20.GL_ALPHA, GL20.GL_FLOAT, + RenderMathUtils.wrap(waterHeights)); + + this.instanceBuffer = gl.glGenBuffer(); + gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.instanceBuffer); + final float[] instanceBufferData = new float[instanceCount]; + for (int i = 0; i < instanceBufferData.length; i++) { + instanceBufferData[i] = i; + } + gl.glBufferData(GL20.GL_ARRAY_BUFFER, instanceBufferData.length * 4, RenderMathUtils.wrap(instanceBufferData), + GL20.GL_STATIC_DRAW); + + this.textureBuffer = gl.glGenBuffer(); + gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.textureBuffer); + gl.glBufferData(GL20.GL_ARRAY_BUFFER, cornerTextures.length, RenderMathUtils.wrap(cornerTextures), + GL20.GL_STATIC_DRAW); + + this.variationBuffer = gl.glGenBuffer(); + gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.variationBuffer); + gl.glBufferData(GL20.GL_ARRAY_BUFFER, cornerVariations.length, RenderMathUtils.wrap(cornerVariations), + GL20.GL_STATIC_DRAW); + + this.waterBuffer = gl.glGenBuffer(); + gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.waterBuffer); + gl.glBufferData(GL20.GL_ARRAY_BUFFER, waterFlags.length, RenderMathUtils.wrap(waterFlags), GL20.GL_STATIC_DRAW); + + this.terrainReady = true; + this.anyReady = true; + + final ShaderProgram cliffShader = this.cliffShader; + this.cliffModels.clear(); + for (final Map.Entry entry : cliffs.entrySet()) { + final String path = entry.getKey(); + final CliffInfo cliffInfo = entry.getValue(); + + final GenericResource resource = this.loadMapGeneric(path, FetchDataTypeName.ARRAY_BUFFER, + streamDataCallback); + + this.cliffModels.add(new TerrainModel(this, (InputStream) resource.data, cliffInfo.locations, + cliffInfo.textures, cliffShader)); + } + this.cliffsReady = true; + + } + + private void loadDoodadsAndDestructibles(final Warcraft3MapObjectData modifications) throws IOException { + final War3MapDoo dooFile = this.mapMpq.readDoodads(); + + } + + private void loadUnitsAndItems(final Warcraft3MapObjectData modifications) { + // TODO Auto-generated method stub + + } + + private static final class MappedDataCallbackImplementation implements LoadGenericCallback { + @Override + public Object call(final InputStream data) { + final StringBuilder stringBuilder = new StringBuilder(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(data, "utf-8"))) { + String line; + while ((line = reader.readLine()) != null) { + stringBuilder.append(line); + stringBuilder.append("\n"); + } + } + catch (final UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + return new MappedData(stringBuilder.toString()); + } + } + + private static final class StringDataCallbackImplementation implements LoadGenericCallback { + @Override + public Object call(final InputStream data) { + final StringBuilder stringBuilder = new StringBuilder(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(data, "utf-8"))) { + String line; + while ((line = reader.readLine()) != null) { + stringBuilder.append(line); + stringBuilder.append("\n"); + } + } + catch (final UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + return stringBuilder.toString(); + } + } + + private static final class StreamDataCallbackImplementation implements LoadGenericCallback { + @Override + public Object call(final InputStream data) { + return data; + } + } + + public static final class SolverParams { + public char tileset; + public boolean reforged; + public boolean hd; + } + + public static final class CliffInfo { + public List locations = new ArrayList<>(); + public List textures = new ArrayList<>(); + } +} diff --git a/core/src/mpq/ArchivedFile.java b/core/src/mpq/ArchivedFile.java new file mode 100644 index 0000000..39fa3fd --- /dev/null +++ b/core/src/mpq/ArchivedFile.java @@ -0,0 +1,132 @@ +package mpq; + +import java.io.IOException; +import java.io.Serializable; +import java.nio.ByteBuffer; +import java.nio.channels.SeekableByteChannel; + +import mpq.data.RawArrays; +import mpq.util.Cryption; + +public class ArchivedFile implements Serializable { + private static final long serialVersionUID = 5033693351138253083L; + + // CRC is Adler? + // CRC requires version safety check. + // Specification is unclear when [CRC block size > archive block size]. Assuming this never happens. May need to handle as special case. + // Specification is unclear when [(file is single unit) equals TRUE AND (file uses CRC) equals TRUE]. Assuming flag is ignored. + // Single Unit requires version safety check. + + public boolean ready; + public final int blockShift; + public final int compressedSize; + public final int fileSize; + public final int flags; + public final long fileOffset; + public final int[] blockOffsets; + public int[] blockChecksums = null; + public final int key; + public final byte compression; + + public ArchivedFile( MPQArchive archive, HashLookup search, BlockTable.Entry file ) throws MPQException{ + // *** load simple values + compressedSize = file.compressedSize; + fileSize = file.fileSize; + flags = file.flags; + fileOffset = file.filePosition + archive.getArchiveOffset(); + + // *** load complex values + if( hasFlag(BlockTable.FLAG_SINGLE_UNIT) ) blockShift = -fileSize; + else blockShift = archive.getBlockShift(); + + if( hasFlag(BlockTable.FLAG_ENCRYPTED) ){ + int key = Cryption.HashString(search.lookup, Cryption.MPQ_HASH_FILE_KEY); + if( hasFlag(BlockTable.FLAG_FIX_KEY) ){ + key = Cryption.adjustFileDecryptKey(key, file.getFilePosition(), file.getFileSize()); + } + this.key = key; + }else + key = 0; + + if( hasFlag(BlockTable.FLAG_COMPRESS | BlockTable.FLAG_IMPLODE) ){ + // blocks cannot be both compressed and imploded + if( hasFlag(BlockTable.FLAG_COMPRESS) && hasFlag(BlockTable.FLAG_IMPLODE) ) throw new MPQException("invalid block: a block is both compressed and imploded"); + + // determine the type of sector compression to use + if( hasFlag(BlockTable.FLAG_COMPRESS) ){ + if( archive.getVersion() > 1 ) compression = 3; + else compression = 2; + }else{ + compression = 1; + } + + // all compressed files use sector tables for standardization + // single unit files have a known table + if( hasFlag(BlockTable.FLAG_SINGLE_UNIT) ){ + + blockOffsets = new int[2]; + blockOffsets[1] = compressedSize; + blockChecksums = null; + ready = true; + // table will need to be looked up + }else{ + int blockn = (fileSize + (512 << blockShift) - 1) / (512 << blockShift); + + // if CRC is used, there is an additional checksum sector with checksums for all other sectors. + if( hasFlag(BlockTable.FLAG_SECTOR_CRC) ) blockn+= 1; + + blockOffsets = new int[blockn+1]; + ready = false; + } + }else{ + compression = 0; + blockOffsets = null; + ready = true; + } + } + + public void loadOffsets( SeekableByteChannel in ) throws IOException, MPQException{ + // read sector table from file + ByteBuffer temp = ByteBuffer.allocate(blockOffsets.length * 4); + in.position( fileOffset ); + while( temp.hasRemaining() ) + if( in.read(temp) == -1 ) + break; + temp.rewind(); + + // decrypt if required + if( hasFlag(BlockTable.FLAG_ENCRYPTED) ){ + Cryption.decryptData(temp, temp, key - 1); + } + + // interpret sector table + RawArrays.getArray(temp, blockOffsets); + + // validate offsets in case of corruption + if( blockOffsets[0] >= 0 && blockOffsets[0] < blockOffsets.length * 4 || + blockOffsets[0] < 0 && blockOffsets[blockOffsets.length - 1] > 0 ) throw new MPQException("block sector intersects sector offset table"); + else if( blockOffsets[0] < 0 || blockOffsets[0] > blockOffsets.length * 4 ) + System.err.printf("block at %X has detached sectors starting at %X (%d bytes from end of sector table)%n", + fileOffset, fileOffset + blockOffsets[0], blockOffsets[0] - blockOffsets.length * 4); + if( fileOffset + blockOffsets[0] < 0 || fileOffset + blockOffsets[blockOffsets.length - 1] > in.size() ) + throw new MPQException("block sector located outside channel"); + for( int i = 1, prevoff = blockOffsets[0] ; i < blockOffsets.length ; i+= 1){ + int curroff = blockOffsets[i]; + if( curroff < prevoff ) throw new MPQException("block sector with negative size"); + prevoff = curroff; + } + + // load CRC sector if present + if( hasFlag(BlockTable.FLAG_SECTOR_CRC) && blockOffsets[blockOffsets.length - 1] != blockOffsets[blockOffsets.length - 2] ){ + System.err.println("block sector CRC reading currently not supported"); + } + + ready = true; + } + + public boolean hasFlag(int flag){ + return (flags & flag) != 0; + } +} + + diff --git a/core/src/mpq/ArchivedFileExtractor.java b/core/src/mpq/ArchivedFileExtractor.java new file mode 100644 index 0000000..7703f61 --- /dev/null +++ b/core/src/mpq/ArchivedFileExtractor.java @@ -0,0 +1,66 @@ +package mpq; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.SeekableByteChannel; + +import mpq.compression.Compression; +import mpq.util.Cryption; + +public class ArchivedFileExtractor { + private Compression decompress = new Compression(); + + public ByteBuffer readBlock(ByteBuffer bufferold, SeekableByteChannel in, ArchivedFile file, int block) throws IOException, MPQException{ + // *** calculate the current block size + int currentSize; + if( file.fileSize < (block + 1) * bufferold.capacity() ) + currentSize = file.fileSize % bufferold.capacity(); + else + currentSize = bufferold.capacity(); + + // *** read block + if( file.blockOffsets != null ){ + // use block offset table + if( !file.ready ){ + file.loadOffsets(in); + } + bufferold.limit(file.blockOffsets[block+1] - file.blockOffsets[block]); + in.position(file.fileOffset + file.blockOffsets[block]); + }else{ + // compute offset + bufferold.limit(currentSize); + in.position(file.fileOffset + bufferold.capacity() * block); + } + while( bufferold.hasRemaining() ) + if( in.read(bufferold) == -1 ) + break; + bufferold.rewind(); + + // *** decrypt if required + if( file.key != 0 ){ + Cryption.decryptData(bufferold, bufferold, file.key + block); + } + + // *** CRC check goes here + if( file.blockChecksums != null ){ + // TODO add support for CRC + System.err.println("block sector CRC validation currently not supported"); + } + + // *** decompress if required + if( file.compression > 0 ){ + // only decompress if block is compressed + if( bufferold.limit() < currentSize ){ + // decompress block + if( file.compression >= 3 ){ + bufferold = decompress.blockDecompress3(bufferold, file.blockShift); + }else if( file.compression == 2 ){ + bufferold = decompress.blockDecompress2(bufferold, file.blockShift); + }else{ + bufferold = decompress.blockDecompress1(bufferold, file.blockShift); + } + } + } + return bufferold; + } +} diff --git a/core/src/mpq/ArchivedFileStream.java b/core/src/mpq/ArchivedFileStream.java new file mode 100644 index 0000000..3776105 --- /dev/null +++ b/core/src/mpq/ArchivedFileStream.java @@ -0,0 +1,131 @@ +package mpq; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.NonWritableChannelException; +import java.nio.channels.SeekableByteChannel; + +public class ArchivedFileStream implements SeekableByteChannel{ + private boolean open; + private SeekableByteChannel from; + private ByteBuffer buffer; + private ArchivedFile file; + private ArchivedFileExtractor extractor; + private long position; + private int currentBlock; + + public ArchivedFileStream(SeekableByteChannel in, ArchivedFileExtractor extractor, ArchivedFile file){ + from = in; + this.extractor = extractor; + this.file = file; + if( file.hasFlag(BlockTable.FLAG_SINGLE_UNIT) ){ + buffer = ByteBuffer.allocate(file.fileSize); + }else{ + buffer = ByteBuffer.allocate(512 << file.blockShift); + } + position = 0; + currentBlock = -1; + open = true; + } + + @Override + public void close() throws IOException { + from = null; + buffer = null; + open = false; + } + + @Override + public boolean isOpen() { + return open; + } + + @Override + public long position() throws IOException { + return position; + } + + @Override + public SeekableByteChannel position(long newPosition) + throws IOException { + // *** argument validation as described by SeekableByteChannel interface + if( newPosition < 0 ) throw new IllegalArgumentException("files cannot have a negative positon"); + + // update stream position + position = newPosition; + // try and update the buffer position of loaded sectors + if( currentBlock != -1 ){ + if( currentBlock != newPosition / buffer.capacity() ) + currentBlock = -1; + else + buffer.position((int) (newPosition % buffer.capacity())); + } + + // *** return value as described by SeekableByteChannel interface + return this; + } + + @Override + public int read(ByteBuffer dst) throws IOException { + // closed + if( !open ) throw new ClosedChannelException(); + // end of stream + if( position >= file.fileSize ) return -1; + + // load current block if no block is currently loaded + if( currentBlock == -1 ){ + currentBlock = (int) (position / buffer.capacity()); + buffer.clear(); + try { + buffer = extractor.readBlock(buffer, from, file, currentBlock); + } catch (MPQException e) { + throw new IOException(e); + } + buffer.position((int) (position % buffer.capacity())); + + } + + long positionstart = position; + while( dst.hasRemaining() ){ + if( buffer.remaining() > dst.remaining() ){ + int limit = buffer.limit(); + buffer.limit(buffer.position() + dst.remaining()); + position+= buffer.remaining(); + dst.put(buffer); + buffer.limit(limit); + }else{ + position+= buffer.remaining(); + dst.put(buffer); + if(position < file.fileSize){ + currentBlock = (int) (position / buffer.capacity()); + buffer.clear(); + try { + buffer = extractor.readBlock(buffer, from, file, currentBlock); + } catch (MPQException e) { + throw new IOException(e); + } + }else{ + break; + } + } + } + + return (int) (position - positionstart); + } + + @Override + public long size() throws IOException { + return file.fileSize; + } + + @Override + public SeekableByteChannel truncate(long size) throws IOException { + throw new NonWritableChannelException(); + } + + @Override + public int write(ByteBuffer src) throws IOException { + throw new NonWritableChannelException(); + } +} diff --git a/core/src/mpq/BlockTable.java b/core/src/mpq/BlockTable.java new file mode 100644 index 0000000..6d8f64f --- /dev/null +++ b/core/src/mpq/BlockTable.java @@ -0,0 +1,78 @@ +package mpq; + +import mpq.data.BlockTableEntry; + +public class BlockTable { + public static final int FLAG_IMPLODE = 0x00000100; + public static final int FLAG_COMPRESS = 0x00000200; + public static final int FLAG_ENCRYPTED = 0x00010000; + public static final int FLAG_FIX_KEY = 0x00020000; + public static final int FLAG_PATCH_FILE = 0x00100000; + public static final int FLAG_SINGLE_UNIT = 0x01000000; + public static final int FLAG_DELETE_MARKER = 0x02000000; + public static final int FLAG_SECTOR_CRC = 0x04000000; + public static final int FLAG_EXISTS = 0x80000000; + + private Entry[] tableArray; + + // raw constructor, expects all entries to be non-null. + public BlockTable(Entry[] entries){ + tableArray = entries; + } + + public Entry lookupEntry(int entry){ + return tableArray[entry]; + } + + public static String flagsToString(int source){ + return ( (source&FLAG_IMPLODE) != 0 ? "IMPLODE " : "" )+ + ( (source&FLAG_COMPRESS) != 0 ? "COMPRESS " : "" )+ + ( (source&FLAG_ENCRYPTED) != 0 ? "ENCRYPTED " : "" )+ + ( (source&FLAG_FIX_KEY) != 0 ? "FIX_KEY " : "" )+ + ( (source&FLAG_PATCH_FILE) != 0 ? "PATCH_FILE " : "" )+ + ( (source&FLAG_SINGLE_UNIT) != 0 ? "SINGLE_UNIT " : "" )+ + ( (source&FLAG_DELETE_MARKER) != 0 ? "DELETE_MARKER " : "" )+ + ( (source&FLAG_SECTOR_CRC) != 0 ? "SECTOR_CRC " : "" )+ + ( (source&FLAG_EXISTS) != 0 ? "EXISTS " : "" ); + } + + + public static class Entry{ + public long filePosition; + public int compressedSize; + public int fileSize; + public int flags; + + public Entry(BlockTableEntry source){ + filePosition = source.getFilePosition(); + compressedSize = source.getCompressedSize(); + fileSize = source.getFileSize(); + flags = source.getFlags(); + } + + // raw constructor for data field + public Entry(){ + } + + public int getFilePosition() { + return (int) filePosition; + } + + public int getCompressedSize() { + return compressedSize; + } + + public int getFileSize() { + return fileSize; + } + + public int getFlags() { + return flags; + } + + public boolean hasFlag(int flag){ + return (flags & flag) != 0; + } + + } +} diff --git a/core/src/mpq/HashLookup.java b/core/src/mpq/HashLookup.java new file mode 100644 index 0000000..33a384c --- /dev/null +++ b/core/src/mpq/HashLookup.java @@ -0,0 +1,35 @@ +package mpq; + +import java.io.Serializable; + +import mpq.util.Cryption; + +public class HashLookup implements Serializable { + private static final long serialVersionUID = -731458056988218435L; + + public final byte[] lookup; + public final long hash; + public final int index; + + public HashLookup(String path){ + // *** convert string to 8 bit ascii + byte[] raw = Cryption.stringToHashable(path); + + // *** generate hashtable lookup arguments + hash = Cryption.HashString(raw, Cryption.MPQ_HASH_NAME_A) & 0xFFFFFFFFL | (long)Cryption.HashString(raw, Cryption.MPQ_HASH_NAME_B)<<32; + index = Cryption.HashString(raw, Cryption.MPQ_HASH_TABLE_OFFSET); + + // *** find file name + int index = 0; + for( int i = raw.length ; --i >= 0 ; ){ + if( raw[i] == (byte) '\\' || raw[i] == (byte) '/' ){ + index = i + 1; + break; + } + } + + // *** save raw ascii file name in-case file is encrypted + lookup = new byte[raw.length - index]; + System.arraycopy(raw, index, lookup, 0, lookup.length); + } +} diff --git a/core/src/mpq/HashTable.java b/core/src/mpq/HashTable.java new file mode 100644 index 0000000..aa0174f --- /dev/null +++ b/core/src/mpq/HashTable.java @@ -0,0 +1,82 @@ +package mpq; + +import mpq.data.HashTableEntry; + +public class HashTable { + public static final int BLOCK_EMPTY_ALWAYS = 0xFFFFFFFF; + public static final int BLOCK_EMPTY_NOW = 0xFFFFFFFE; + private Entry[] bucketArray; + + // raw constructor, assumes every entry is not null and the array is a power of 2 + public HashTable(Entry[] entries){ + bucketArray = entries; + } + + public int lookupBlock(HashLookup what) throws MPQException{ + int mask = bucketArray.length-1; + int index = what.index & mask; + for(int pos = index ; ; ){ + Entry temp = bucketArray[pos]; + if(temp.blockIndex == BLOCK_EMPTY_ALWAYS) break; + if(temp.getHash() == what.hash) return temp.blockIndex; + pos = ( pos + 1 ) & mask; + if(pos == index) break; + } + throw new MPQException("lookup not found"); + } + + /*public static int lookupBlock(Entry[] hashtable, byte[] file) throws FileNotFoundException{ + int mask = hashtable.length-1; + int index = Cryption.HashString(file, Cryption.MPQ_HASH_TABLE_OFFSET) & mask; + long hash = Cryption.HashString(file, Cryption.MPQ_HASH_NAME_A) & 0xFFFFFFFFL | (long)Cryption.HashString(file, Cryption.MPQ_HASH_NAME_B)<<32; + for(int pos = index ; ; ){ + Entry temp = hashtable[pos]; + if(temp.getBlockIndex() == BLOCK_EMPTY_ALWAYS) break; + if(temp.getHash() == hash) return temp.getBlockIndex(); + pos = ( pos + 1 ) & mask; + if(pos == index) break; + } + throw new FileNotFoundException("hash not in hashtable"); + }*/ + + /*public static int lookupBlock(Entry[] hashtable, String file) throws FileNotFoundException{ + return lookupBlock(hashtable, Cryption.stringToHashable(file)); + }*/ + + // entry is an internal data type and as such performs no safety checks + public static class Entry{ + public long hash; + public short locale; + public short platform; + public int blockIndex; + + // raw constructor + public Entry(){ + } + + public Entry(HashTableEntry source){ + hash = source.getHash(); + locale = source.getLocale(); + platform = source.getPlatform(); + blockIndex = source.getBlockIndex(); + } + + public long getHash() { + return hash; + } + + public short getLocale() { + return locale; + } + + public short getPlatform() { + return platform; + } + + public int getBlockIndex() { + return blockIndex; + } + + + } +} diff --git a/core/src/mpq/MPQArchive.java b/core/src/mpq/MPQArchive.java new file mode 100644 index 0000000..db05042 --- /dev/null +++ b/core/src/mpq/MPQArchive.java @@ -0,0 +1,291 @@ +package mpq; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.SeekableByteChannel; + +import mpq.compression.Compression; +import mpq.data.BlockTableEntry; +import mpq.data.FileHeader; +import mpq.data.HashTableEntry; +import mpq.data.ArchiveHeader; +import mpq.data.RawArrays; +import mpq.data.UserDataHeader; +import mpq.util.Cryption; + +public class MPQArchive { + private long archiveOffset; + private short blockShift; + private HashTable hashTable; + private BlockTable blockTable; + private short version; + private long archiveSize; + + // locates the archive header from within a SeekableByteChannel + private ArchiveHeader locateArchive(SeekableByteChannel in) throws IOException, MPQException{ + // *** find MPQ archive header + // allocate a buffer and header interpreter + ByteBuffer buffer = ByteBuffer.allocate(FileHeader.STRUCT_SIZE); + FileHeader header = new FileHeader(buffer); + + // look for the header positioned at 512 bytes + archiveOffset = in.position() & ~(512 - 1); + for(;;){ + // read in possible header data + in.position(archiveOffset); + while( buffer.hasRemaining() ) + if( in.read(buffer) == -1 ) + throw new MPQException("channel does not contain a MPQ archive"); + buffer.clear(); + + // check header validity + if( header.getIdentifierInt() == FileHeader.ARCHIVE_IDENTIFIER_INT ) break; + // if user data header is found, extract archive header offset and continue + else if( header.getIdentifierInt() == FileHeader.USERDATA_IDENTIFIER_INT ){ + ByteBuffer temp = ByteBuffer.allocate(header.getHeaderSize()); + UserDataHeader udheader = new UserDataHeader(temp); + + while( temp.hasRemaining() ) + if( in.read(temp) == -1 ) + break; + + archiveOffset+= udheader.getArchiveOffset(); + continue; + } + + // prepare buffer for next operation and skip to next 512 bytes + archiveOffset+= 512; + } + + // *** load MPQ archive header + // allocate a buffer and archive header interpreter + int size = header.getHeaderSize() - FileHeader.STRUCT_SIZE; + // place a reasonable 8KB limit to archive header size + if( size > 1 << 13 || size < 0 ) size = 1 << 13; + buffer = ByteBuffer.allocate(size); + ArchiveHeader archiveheader = new ArchiveHeader(buffer); + + // read in archive header bytes + while( buffer.hasRemaining() ) + if( in.read(buffer) == -1 ) + break; + + return archiveheader; + } + + // + private void deserializeHashTable(SeekableByteChannel in, ArchiveHeader archiveheader) throws IOException, MPQException{ + // *** deserialize header + // version 1 + long htoffset = (long) archiveheader.getHashTablePosition() & 0xFFFFFFFFL; + int htsize = archiveheader.getHashTableSize(); + int rsize = htsize * HashTableEntry.STRUCT_SIZE; + // version 2 + if( version >= 1 ){ + htoffset|= ((long) archiveheader.getHashTablePositionHigh() & 0xFFFFL) << 32; + } + // version 3 + int csize; + if( version >= 3 ){ + csize = (int) archiveheader.getHashTableSizeCompressed(); + }else{ + csize = rsize; + } + + // *** validate hashtable + // no hashtable to load + if( htoffset == 0 ){ + hashTable = null; + return; + // hashtable size not power of 2 + }else if( (htsize & htsize - 1) != 0 ) + throw new MPQException("hashtable was not power of two ( was " + htsize + " )"); + + // *** read MPQ archive hashtable + ByteBuffer buffer = ByteBuffer.allocate(rsize); + buffer.limit(csize); + in.position(htoffset + archiveOffset); + while( buffer.hasRemaining() ) + if( in.read(buffer) == -1 ) + break; + buffer.rewind(); + + // *** decrypt hashtable + Cryption.decryptData(buffer, buffer, Cryption.KEY_HASH_TABLE); + + // *** decompress hashtable + if( csize < rsize ){ + buffer = new Compression().blockDecompressAny(buffer, ByteBuffer.allocate(rsize)); + if( buffer.limit() != buffer.capacity() ) System.err.println("hashtable decompressed size did not match expected size"); + } + + // *** deserialize hashtable + HashTable.Entry[] entries = new HashTable.Entry[htsize]; + HashTableEntry htentry = new HashTableEntry(); + for( int i = 0 ; i < htsize ; i+= 1 ){ + htentry.move(buffer); + HashTable.Entry tempentry = new HashTable.Entry(); + tempentry.hash = htentry.getHash(); + tempentry.locale = htentry.getLocale(); + tempentry.platform = htentry.getPlatform(); + tempentry.blockIndex = htentry.getBlockIndex(); + entries[i] = tempentry; + buffer.position(buffer.position() + HashTableEntry.STRUCT_SIZE); + } + hashTable = new HashTable(entries); + } + + private void deserializeBlockTable(SeekableByteChannel in, ArchiveHeader archiveheader) throws IOException, MPQException{ + // *** deserialize header + // version 1 + long btoffset = (long) archiveheader.getBlockTablePosition() & 0xFFFFFFFFL; + int btsize = archiveheader.getBlockTableSize(); + int rsize = btsize * BlockTableEntry.STRUCT_SIZE; + // version 2 + long hbtoffset; + int rhsize; + if( version >= 1 ){ + hbtoffset = archiveheader.getHighBlockTablePosition(); + rhsize = btsize * 2; + btoffset|= ((long) archiveheader.getBlockTablePositionHigh() & 0xFFFFL) << 32; + }else{ + hbtoffset = 0; + rhsize = 0; + } + // version 4 + int csize; + int chsize; + if( version >= 3 ){ + csize = (int) archiveheader.getBlockTableSizeCompressed(); + chsize = (int) archiveheader.getHighBlockTableSizeCompressed(); + }else{ + csize = rsize; + chsize = rhsize; + } + + // *** validate blocktable + // no blocktable to load + if( btoffset == 0 ){ + blockTable = null; + return; + // blocktable size clamp + }else if( btsize > 1 << 20 || btsize < 0 ){ + System.err.println("blocktable is stupidly large ( " + btsize + " ) so was clamped to " + (1 << 20)); + btsize = 1 << 20; + } + + // *** read MPQ archive blocktable + ByteBuffer buffer = ByteBuffer.allocate(rsize); + buffer.limit(csize); + in.position(btoffset + archiveOffset); + while( buffer.hasRemaining() ) + if( in.read(buffer) == -1 ) + break; + buffer.rewind(); + + // *** decrypt blocktable + Cryption.decryptData(buffer, buffer, Cryption.KEY_BLOCK_TABLE); + + // *** decompress blocktable + if( csize < rsize ){ + buffer = new Compression().blockDecompressAny(buffer, ByteBuffer.allocate(rsize)); + if( buffer.limit() != buffer.capacity() ) System.err.println("blocktable decompressed size did not match expected size"); + } + + // *** deserialize blocktable + BlockTable.Entry[] entries = new BlockTable.Entry[btsize]; + BlockTableEntry btentry = new BlockTableEntry(); + for( int i = 0 ; i < btsize ; i+= 1 ){ + btentry.move(buffer); + BlockTable.Entry tempentry = new BlockTable.Entry(); + tempentry.filePosition = (long) btentry.getFilePosition() & 0xFFFFFFFFL; + tempentry.compressedSize = btentry.getCompressedSize(); + tempentry.fileSize = btentry.getFileSize(); + tempentry.flags = btentry.getFlags(); + entries[i] = tempentry; + buffer.position(buffer.position() + BlockTableEntry.STRUCT_SIZE); + } + + // *** add high blocktable + if( hbtoffset > 0 ){ + // read MPQ archive high blocktable + buffer = ByteBuffer.allocate(rhsize); + buffer.limit(chsize); + in.position(hbtoffset + archiveOffset); + while( buffer.hasRemaining() ) + if( in.read(buffer) == -1 ) + break; + buffer.rewind(); + + // decompress high blocktable + if( chsize < rhsize ){ + buffer = new Compression().blockDecompressAny(buffer, ByteBuffer.allocate(rhsize)); + if( buffer.limit() != buffer.capacity() ) System.err.println("high blocktable decompressed size did not match expected size"); + } + + // deserialize high blocktable + short[] highposarray = RawArrays.getShortArray(buffer); + for( int i = 0 ; i < btsize ; i+= 1 ){ + entries[i].filePosition|= ((long) highposarray[i] & 0xFFFFL) << 32; + } + } + blockTable = new BlockTable(entries); + } + + public void loadArchive(SeekableByteChannel in, boolean fold) throws IOException, MPQException{ + // *** find archive header + ArchiveHeader archiveheader = locateArchive(in); + + // *** deserialize archive globals + archiveSize = (long) archiveheader.getArchiveSize() & 0xFFFFFFFFL; + // force old allows support for Warcraft III archives with corrupted version field (not 0) + if( fold ) version = 0; + else version = archiveheader.getFormatVersion(); + blockShift = archiveheader.getBlockSize(); + if( version >= 2 ) archiveSize = archiveheader.getArchiveSizeLong(); + + // *** deserialize archive components + deserializeHashTable(in, archiveheader); + deserializeBlockTable(in, archiveheader); + if( version >= 2 ) System.err.println("het and bet tables not supported"); + } + + public MPQArchive(SeekableByteChannel in) throws MPQException, IOException{ + loadArchive(in, false); + } + + public MPQArchive(){ + } + + public long getArchiveOffset() { + return archiveOffset; + } + + public short getBlockShift() { + return blockShift; + } + + public short getVersion() { + return version; + } + + public boolean isOffsetInArchive(long offset){ + return offset >= 0 && offset <= archiveSize; + } + + public boolean isPositionInArchive(long position){ + return isOffsetInArchive(position - archiveOffset); + } + + public int lookupPath(String path) throws MPQException{ + return hashTable.lookupBlock(new HashLookup(path)); + } + + public BlockTable.Entry lookupHash(HashLookup hash) throws MPQException{ + return blockTable.lookupEntry(hashTable.lookupBlock(hash)); + } + + public ArchivedFile lookupHash2(HashLookup hash) throws MPQException{ + return new ArchivedFile(this, hash, blockTable.lookupEntry(hashTable.lookupBlock(hash))); + } +} diff --git a/core/src/mpq/MPQException.java b/core/src/mpq/MPQException.java new file mode 100644 index 0000000..c90140a --- /dev/null +++ b/core/src/mpq/MPQException.java @@ -0,0 +1,29 @@ +package mpq; + +public class MPQException extends Exception { + + /** + * + */ + private static final long serialVersionUID = 1106829951432295418L; + + public MPQException() { + } + + public MPQException(String arg0) { + super(arg0); + } + + public MPQException(Throwable arg0) { + super(arg0); + } + + public MPQException(String arg0, Throwable arg1) { + super(arg0, arg1); + } + + public MPQException(String arg0, Throwable arg1, boolean arg2, boolean arg3) { + super(arg0, arg1, arg2, arg3); + } + +} diff --git a/core/src/mpq/compression/Compression.java b/core/src/mpq/compression/Compression.java new file mode 100644 index 0000000..2caa5e3 --- /dev/null +++ b/core/src/mpq/compression/Compression.java @@ -0,0 +1,397 @@ +package mpq.compression; + +import java.nio.ByteBuffer; +import java.util.zip.DataFormatException; +import java.util.zip.Inflater; + +import mpq.compression.adpcm.ADPCM; +import mpq.compression.huffman.Huffman; +import mpq.compression.pkware.PKException; +import mpq.compression.pkware.PKExploder; + +public class Compression { + /*static TDecompressTable dcmp_table[] = + { + {MPQ_COMPRESSION_BZIP2, Decompress_BZIP2}, // Decompression with Bzip2 library + {MPQ_COMPRESSION_PKWARE, Decompress_PKLIB}, // Decompression with Pkware Data Compression Library + {MPQ_COMPRESSION_ZLIB, Decompress_ZLIB}, // Decompression with the "zlib" library + {MPQ_COMPRESSION_HUFFMANN, Decompress_huff}, // Huffmann decompression + {MPQ_COMPRESSION_ADPCM_STEREO, Decompress_ADPCM_stereo}, // IMA ADPCM stereo decompression + {MPQ_COMPRESSION_ADPCM_MONO, Decompress_ADPCM_mono}, // IMA ADPCM mono decompression + {MPQ_COMPRESSION_SPARSE, Decompress_SPARSE} // Sparse decompression + };*/ + + //private WritableByteBuffer adapter = new WritableByteBuffer(); + /*private OutputStream adapterFront = Channels.newOutputStream(adapter); + + private InflaterOutputStream zlibInflater = new InflaterOutputStream(adapterFront); + private WritableByteChannel zlibDecompressWriter = Channels.newChannel(zlibInflater);*/ + private PKExploder pkexploderDecompress = new PKExploder(); + private Huffman huffmanDecompress = new Huffman(); + private ADPCM adpcmDecompress = new ADPCM(2); + + // an array used to cache buffers of various regular sizes to reduce allocation overhead + private final ByteBuffer[] bufferCache = new ByteBuffer[22]; + + /* + * Compression Masks + */ + /* Masks for Compression Type 2 */ + private static final byte FLAG_HUFFMAN = 0x01; + private static final byte FLAG_DEFLATE = 0x02; + // 0x04 is unknown + private static final byte FLAG_IMPLODE = 0x08; + private static final byte FLAG_BZIP2 = 0x10; // introduced in version 1 + private static final byte FLAG_SPARSE = 0x20; // introduced in version 2 + private static final byte FLAG_ADPCM1C = 0x40; + private static final byte FLAG_ADPCM2C =-0x80; + /* Masks for Compresion Type 3 */ + private static final byte FLAG_LZMA = 0x12; + private static final byte FLAG_SPARSE_DEFLATE = FLAG_SPARSE | FLAG_DEFLATE; + private static final byte FLAG_SPARSE_BZIP2 = FLAG_SPARSE | FLAG_BZIP2 ; + + private ByteBuffer fetchBuffer(int size){ + ByteBuffer out; + if( size >= 0 ){ + out = bufferCache[size]; + if( out == null ){ + out = ByteBuffer.allocate(512 << size); + bufferCache[size] = out; + } + }else out = ByteBuffer.allocate(-size); + return out; + } + + /** + * Decompresses a sector following compression specification 3 used in version 2 and later MPQs. + * + * A lookup table is used to resolve compression. + * + * @param in buffer with compressed sector + * @param size sector size as bit shift or negative for sectors of irregular size + * @return buffer with decompressed sector + * @throws DecompressionException when decompression fails + */ + public ByteBuffer blockDecompress3(ByteBuffer in, int size) throws DecompressionException{ + byte mask = in.get(); + + ByteBuffer out = fetchBuffer(size); + boolean flip = true; + + // lookup table for valid compression types + switch( mask ) { + case FLAG_DEFLATE: + sectorInflate(in, out); + break; + case FLAG_IMPLODE: + sectorExplode(in, out); + break; + case FLAG_BZIP2: + // TODO add support + throw new DecompressionException(in, "unsupported compression type: BZIP2"); + case FLAG_SPARSE: + // TODO add support + throw new DecompressionException(in, "unsupported compression type: SPARSE"); + case FLAG_LZMA: + // TODO add support + throw new DecompressionException(in, "unsupported compression type: LZMA"); + case FLAG_SPARSE_DEFLATE: + // TODO add support + throw new DecompressionException(in, "unsupported compression type: SPARSE"); + case FLAG_SPARSE_BZIP2: + // TODO add support + throw new DecompressionException(in, "unsupported compression type: SPARSE"); + default: + throw new DecompressionException(in, "sector has unknown compression"); + } + + if( size >= 0 ) + if( flip ){ + in.clear(); + bufferCache[size] = in; + }else{ + out.clear(); + out = in; + } + else + if( !flip ) out = in; + + return out; + } + + /** + * Decompresses a sector following compression specification 2 used in version 0 and 1 MPQs. + * + * Masks are evaluated in order to undo compression. + * + * @param in buffer with compressed sector + * @param size sector size as bit shift or negative for sectors of irregular size + * @return buffer with decompressed sector + * @throws DecompressionException when decompression fails + */ + public ByteBuffer blockDecompress2(ByteBuffer in, int size) throws DecompressionException{ + byte mask = in.get(); + + ByteBuffer out = fetchBuffer(size); + boolean flip = false; + + // apply decompression flag at a time + if( (mask & FLAG_BZIP2) != 0 ){ + // TODO add support + throw new DecompressionException(in, "unsupported compression type: BZIP2"); + } + if( (mask & FLAG_IMPLODE) != 0 ){ + sectorExplode(flip ? out : in, flip ? in : out); + (flip ? out : in).clear(); + flip = !flip; + } + if( (mask & FLAG_DEFLATE) != 0 ){ + sectorInflate(flip ? out : in, flip ? in : out); + (flip ? out : in).clear(); + flip = !flip; + } + if( (mask & FLAG_HUFFMAN) != 0 ){ + sectorHuffmanExpand(flip ? out : in, flip ? in : out); + (flip ? out : in).clear(); + flip = !flip; + } + if( (mask & FLAG_ADPCM2C) != 0 ){ + sectorADPCMReconstruct(flip ? out : in, flip ? in : out, 2); + (flip ? out : in).clear(); + flip = !flip; + } + if( (mask & FLAG_ADPCM1C) != 0 ){ + sectorADPCMReconstruct(flip ? out : in, flip ? in : out, 1); + (flip ? out : in).clear(); + flip = !flip; + } + if( (mask & FLAG_SPARSE) != 0 ) System.err.println("sparse compression flag present in mpq version that lacked support"); + + if( flip ){ + bufferCache[size] = in; + return out; + }else return in; + } + + /** + * Decompresses a sector following compression specification 1 used by imploded blocks. + * + * pkware explode is always used on the input. A buffer flip always occurs. + * + * @param in buffer with compressed sector + * @param size sector size as bit shift or negative for sectors of irregular size + * @return buffer with decompressed sector + * @throws DecompressionException when decompression fails + */ + public ByteBuffer blockDecompress1(ByteBuffer in, int size) throws DecompressionException{ + ByteBuffer out = fetchBuffer(size); + sectorExplode(in, out); + in.clear(); + bufferCache[size] = in; + return out; + } + + private void sectorExplode(ByteBuffer in, ByteBuffer out) throws DecompressionException{ + try { + pkexploderDecompress.explode(in, out); + } catch (PKException e) { + throw new DecompressionException(in, "sector explode exception", e); + } + + out.flip(); + } + + private void sectorInflate(ByteBuffer in, ByteBuffer out) throws DecompressionException{ + try { + // a new inflater is needed for each sector as they cannot be recycled + Inflater zlibInflater = new Inflater(); + zlibInflater.setInput(in.array(), in.position(), in.remaining()); + out.position(zlibInflater.inflate(out.array())); + } catch ( DataFormatException e ) { + throw new DecompressionException(in, "sector deflae exception", e); + } + + out.flip(); + } + + private void sectorHuffmanExpand(ByteBuffer in, ByteBuffer out) throws DecompressionException{ + try { + huffmanDecompress.Decompress(in, out); + } catch ( Exception e ) { + throw new DecompressionException(in, "sector huffman expand exception", e); + } + + out.flip(); + } + + private void sectorADPCMReconstruct(ByteBuffer in, ByteBuffer out, int channeln) throws DecompressionException{ + try { + adpcmDecompress.decompress(in, out, channeln); + } catch ( Exception e ) { + throw new DecompressionException(in, "sector adpcm reconstruction exception", e); + } + + out.flip(); + } + + /*private static class WritableByteBuffer implements WritableByteChannel{ + public ByteBuffer dst; + + @Override + public void close() throws IOException { + // nothing to close + } + + @Override + public boolean isOpen() { + // always open + return true; + } + + @Override + public int write(ByteBuffer src) throws IOException { + int size = src.remaining(); + dst.put(src); + return size; + } + + }*/ + + public ByteBuffer blockDecompressAny(ByteBuffer block, ByteBuffer extra) throws DecompressionException{ + if( blockDecompress(block, extra) ) return extra; + else return block; + } + + public void blockExplode(ByteBuffer block, ByteBuffer extra){ + try { + pkexploderDecompress.explode(block, extra); + } catch (PKException e) { + System.err.println("pkware decompression exception: "+e.getLocalizedMessage()); + } + + if( extra.position() != extra.limit() ){ + System.err.println("a block failed exploding"); + } + + block.clear(); + extra.rewind();; + } + + public boolean blockDecompress(ByteBuffer block, ByteBuffer extra) throws DecompressionException{ + return blockDecompress(block, extra, false); + } + + public boolean blockDecompress(ByteBuffer block, ByteBuffer extra, boolean strict) throws DecompressionException{ + byte mask = block.get(); + + if( strict ){ + System.err.println("strict compression flag mode not supported"); + } + + boolean swap = false; + + // BZIP2 + if( (mask & 0x10) > 0 ){ + // TODO add support + throw new DecompressionException(block, "unsupported compression type: BZIP2"); + } + + // PKWARE + if( (mask & 0x08) > 0 ){ + try { + pkexploderDecompress.explode(block, extra); + } catch (PKException e) { + throw new DecompressionException(block, "failed PKWARE decompression", e); + } + block.rewind(); + block.limit(extra.limit()); + extra.flip(); + + ByteBuffer temp = extra; + extra = block; + block = temp; + + swap = !swap; + } + + // ZLIB + if( (mask & 0x02) > 0 ){ + try { + Inflater zlibInflater = new Inflater(); + zlibInflater.setInput(block.array(), block.position(), block.remaining()); + extra.position(zlibInflater.inflate(extra.array())); + } catch ( DataFormatException e ) { + throw new DecompressionException(block, "failed ZLIB decompression", e); + } + block.rewind(); + block.limit(extra.limit()); + extra.flip(); + + ByteBuffer temp = extra; + extra = block; + block = temp; + + swap = !swap; + } + + // HUFFMANN + if( (mask & 0x01) > 0 ){ + huffmanDecompress.Decompress(block, extra); + + block.rewind(); + block.limit(extra.limit()); + extra.flip(); + + ByteBuffer temp = extra; + extra = block; + block = temp; + + swap = !swap; + } + + // ADPCM_STEREO + if( (mask & 0x80) > 0 ){ + adpcmDecompress.decompress(block, extra, 2); + + block.rewind(); + block.limit(extra.limit()); + extra.flip(); + + ByteBuffer temp = extra; + extra = block; + block = temp; + + swap = !swap; + } + + // ADPCM_MONO + if( (mask & 0x40) > 0 ){ + adpcmDecompress.decompress(block, extra, 1); + + block.rewind(); + block.limit(extra.limit()); + extra.flip(); + + ByteBuffer temp = extra; + extra = block; + block = temp; + + swap = !swap; + } + + // SPARSE + if( (mask & 0x20) > 0 ){ + // TODO add support + throw new DecompressionException(block, "unsupported compression type: SPARSE"); + } + + if( block.limit() != extra.limit() ){ + throw new DecompressionException(block, "decompression result was smaller than expected"); + //System.err.println("a sector passed decompression but failed to meet the expected size"); + //block.limit(extra.limit()); + } + + extra.clear(); + return swap; + } +} \ No newline at end of file diff --git a/core/src/mpq/compression/DecompressionException.java b/core/src/mpq/compression/DecompressionException.java new file mode 100644 index 0000000..7a46595 --- /dev/null +++ b/core/src/mpq/compression/DecompressionException.java @@ -0,0 +1,25 @@ +package mpq.compression; + +import java.nio.ByteBuffer; + +import mpq.MPQException; + +public class DecompressionException extends MPQException { + + private static final long serialVersionUID = 5481075695238468958L; + private final ByteBuffer decompressedBuffer; + + public DecompressionException(ByteBuffer buff, String arg0) { + super(arg0); + decompressedBuffer = buff.asReadOnlyBuffer(); + } + + public DecompressionException(ByteBuffer buff, String arg0, Throwable arg1) { + super(arg0, arg1); + decompressedBuffer = buff.asReadOnlyBuffer(); + } + + public ByteBuffer getDecompressedBuffer() { + return decompressedBuffer; + } +} diff --git a/core/src/mpq/compression/adpcm/ADPCM.java b/core/src/mpq/compression/adpcm/ADPCM.java new file mode 100644 index 0000000..c39ee3e --- /dev/null +++ b/core/src/mpq/compression/adpcm/ADPCM.java @@ -0,0 +1,118 @@ +package mpq.compression.adpcm; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +public class ADPCM { + + private static class Channel{ + public short value; + public byte rate; + } + + private final Channel[] state; + + public ADPCM(int channelmax){ + state = new Channel[channelmax]; + for( int i = 0 ; i < state.length ; i+= 1 ) state[i] = new Channel(); + } + + public void decompress(ByteBuffer in, ByteBuffer out, int channeln){ + // prepare buffers + in.order(ByteOrder.LITTLE_ENDIAN); + out.order(ByteOrder.LITTLE_ENDIAN); + + byte stepshift = (byte) (in.getShort() >>> 8); + + // initialize channels + for( int i = 0 ; i < channeln ; i+= 1 ){ + Channel chan = state[i]; + chan.rate = 0x2C; + chan.value = in.getShort(); + out.putShort(chan.value); + } + + int current = 0; + Channel chan = state[current]; + boolean multichannel = channeln > 1; + + // decompress + while( in.hasRemaining() ){ + byte op = in.get(); + + if( (op & 0x80) > 0 ){ + switch( op & 0x7F ){ + // write current value + case 0 : + if( chan.rate != 0 ) chan.rate-= 1; + out.putShort(chan.value); + if( multichannel ) chan = state[++current % channeln]; + break; + // increment period + case 1 : + chan.rate+= 8; + if( chan.rate > 0x58 ) chan.rate = 0x58; + break; + // skip channel + case 2 : + if( multichannel ) chan = state[++current % channeln]; + break; + // all other values + default : + chan.rate-= 8; + if( chan.rate < 0 ) chan.rate = 0; + } + }else{ + // adjust value + short stepunit = STEP_TABLE[chan.rate]; + short stepsize = (short) (stepunit >>> stepshift); + int value = chan.value; + + for( int i = 0 ; i < 6 ; i+= 1 ){ + if( (op & 1 << i) > 0 ) stepsize+= stepunit >> i; + } + + if( (op & 0x40) > 0 ){ + value-= stepsize; + if( value < Short.MIN_VALUE ) value = Short.MIN_VALUE; + }else{ + value+= stepsize; + if( value > Short.MAX_VALUE ) value = Short.MAX_VALUE; + } + chan.value = (short) value; + + out.putShort(chan.value); + + chan.rate+= CHANGE_TABLE[op & 0x1F]; + if( chan.rate < 0 ) chan.rate = 0; + else if( chan.rate > 0x58 ) chan.rate = 0x58; + + if( multichannel ) chan = state[++current % channeln]; + } + } + } + + private static final byte CHANGE_TABLE[] = + { + 0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000004, 0xFFFFFFFF, 0x00000002, 0xFFFFFFFF, 0x00000006, + 0xFFFFFFFF, 0x00000001, 0xFFFFFFFF, 0x00000005, 0xFFFFFFFF, 0x00000003, 0xFFFFFFFF, 0x00000007, + 0xFFFFFFFF, 0x00000001, 0xFFFFFFFF, 0x00000005, 0xFFFFFFFF, 0x00000003, 0xFFFFFFFF, 0x00000007, + 0xFFFFFFFF, 0x00000002, 0xFFFFFFFF, 0x00000004, 0xFFFFFFFF, 0x00000006, 0xFFFFFFFF, 0x00000008 + }; + + private static final short STEP_TABLE[] = + { + 0x00000007, 0x00000008, 0x00000009, 0x0000000A, 0x0000000B, 0x0000000C, 0x0000000D, 0x0000000E, + 0x00000010, 0x00000011, 0x00000013, 0x00000015, 0x00000017, 0x00000019, 0x0000001C, 0x0000001F, + 0x00000022, 0x00000025, 0x00000029, 0x0000002D, 0x00000032, 0x00000037, 0x0000003C, 0x00000042, + 0x00000049, 0x00000050, 0x00000058, 0x00000061, 0x0000006B, 0x00000076, 0x00000082, 0x0000008F, + 0x0000009D, 0x000000AD, 0x000000BE, 0x000000D1, 0x000000E6, 0x000000FD, 0x00000117, 0x00000133, + 0x00000151, 0x00000173, 0x00000198, 0x000001C1, 0x000001EE, 0x00000220, 0x00000256, 0x00000292, + 0x000002D4, 0x0000031C, 0x0000036C, 0x000003C3, 0x00000424, 0x0000048E, 0x00000502, 0x00000583, + 0x00000610, 0x000006AB, 0x00000756, 0x00000812, 0x000008E0, 0x000009C3, 0x00000ABD, 0x00000BD0, + 0x00000CFF, 0x00000E4C, 0x00000FBA, 0x0000114C, 0x00001307, 0x000014EE, 0x00001706, 0x00001954, + 0x00001BDC, 0x00001EA5, 0x000021B6, 0x00002515, 0x000028CA, 0x00002CDF, 0x0000315B, 0x0000364B, + 0x00003BB9, 0x000041B2, 0x00004844, 0x00004F7E, 0x00005771, 0x0000602F, 0x000069CE, 0x00007462, + 0x00007FFF + }; +} diff --git a/core/src/mpq/compression/huffman/Huffman.java b/core/src/mpq/compression/huffman/Huffman.java new file mode 100644 index 0000000..1d11739 --- /dev/null +++ b/core/src/mpq/compression/huffman/Huffman.java @@ -0,0 +1,481 @@ +package mpq.compression.huffman; + +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Map.Entry; +import java.util.TreeMap; + +public class Huffman { + private static class Node { + public Node parent; + public final Node[] child = new Node[2]; + public Node next; + public Node prev; + public int value; + public int probability; + + public void treeSwap(Node with){ + Node temp; + + if( parent == with.parent ){ + temp = parent.child[0]; + parent.child[0] = parent.child[1]; + parent.child[1] = temp; + }else{ + if( with.parent.child[0] == with ) with.parent.child[0] = this; + else with.parent.child[1] = this; + if( this.parent.child[0] == this ) this.parent.child[0] = with; + else this.parent.child[1] = with; + } + + temp = parent; + parent = with.parent; + with.parent = temp; + } + + public void insertAfter(Node where){ + prev = where; + next = where.next; + where.next = this; + next.prev = this; + } + + public void listSwap(Node with){ + if( next == with ){ + next = with.next; + with.next = this; + with.prev = prev; + prev = with; + + with.prev.next = with; + next.prev = this; + }else if( prev == with ){ + prev = with.prev; + with.prev = this; + with.next = next; + next = with; + + with.next.prev = with; + prev.next = this; + }else{ + Node temp = prev; + prev = with.prev; + with.prev = temp; + + temp = next; + next = with.next; + with.next = temp; + + prev.next = this; + next.prev = this; + + with.prev.next = with; + with.next.prev = with; + } + } + + public void newList(){ + prev = next = this; + } + + public Node removeFromList(){ + if( this == next ) return null; + + prev.next = next; + next.prev = prev; + + return next; + } + + public void joinList(Node list){ + Node tail = prev; + + prev = list.prev; + prev.next = this; + + list.prev = tail; + tail.next = list; + } + } + + private boolean adjustProbability; + + private Node nodes = null; + private TreeMap sorted2 = new TreeMap(); + + private Node root = null; + private Node[] valueToNode = new Node[0x102]; + + private int bitBuffer; + private byte bitNumber; + private ByteBuffer source; + + private void setSource(ByteBuffer source){ + this.source = source; + bitBuffer = 0; + bitNumber = 0; + } + + private int getBits(int bits){ + while( bitNumber < bits ){ + bitBuffer|= ((int) source.get() & 0xFF) << bitNumber; + bitNumber+= 8; + } + + int result = bitBuffer & ((1 << bits) - 1); + bitBuffer>>>= bits; + bitNumber-= bits; + + return result; + } + + private Node getNode(){ + Node node; + if( nodes == null ) node = new Node(); + else{ + node = nodes; + nodes = nodes.removeFromList(); + } + return node; + } + + private void destroyTree(Node root){ + if( nodes == null ) nodes = root; + else nodes.joinList(root); + this.root = null; + sorted2.clear(); + Arrays.fill(valueToNode, null); + } + + private void insertNode( Node node ){ + Entry test2 = sorted2.ceilingEntry(node.probability); + Node current; + + + if( test2 != null ){ + current = test2.getValue(); + node.insertAfter(current); + }else{ + current = root; + if( root != null ){ + node.insertAfter(root.prev); + }else{ + node.newList(); + } + root = node; + } + + sorted2.put(node.probability, node); + } + + private Node addValueToTree( int value ){ + // create leaf node + Node node = getNode(); + node.value = value; + node.probability = 0; + node.child[0] = null; + node.child[1] = null; + + valueToNode[value] = node; + insertNode( node ); + + // create branch node + Node node2 = getNode(); + Node child1 = root.prev; + Node child2 = child1.prev; + + node2.value = -1; + node2.probability = child1.probability + child2.probability; + node2.child[0] = child1; + node2.child[1] = child2; + node2.parent = child2.parent; + + node2.insertAfter(child2.prev); + + // insert into tree + if( node2.parent.child[0] == child2 ) node2.parent.child[0] = node2; + else node2.parent.child[1] = node2; + + child1.parent = node2; + child2.parent = node2; + + return node; + } + + private void incrementProbability( Node node ){ + while( node != null ){ + // possible optimization here. Is all this really nescescary to enforce order? + if( sorted2.get(node.probability) == node ){ + if( node.probability == node.prev.probability ) + sorted2.put(node.probability, node.prev); + else + sorted2.remove(node.probability); + } + node.probability+= 1; + + Entry test2 = sorted2.ceilingEntry(node.probability); + Node where; + if( test2 != null ) where = test2.getValue().next; + else where = root; + + if( where != node ){ + node.listSwap(where); + node.treeSwap(where); + + if( where.probability != where.next.probability ){ + sorted2.put(where.probability, where); + } + } + sorted2.put(node.probability, node); + + node = node.parent; + } + } + + private void buildTree( byte tree ){ + byte[] probabilities = PROBABILITY_TABLES[tree]; + + // destroy any existing tree + if( root != null ) destroyTree(root); + + // generate leaves + for( int i = 0 ; i < 0x102 ; i++ ){ + int prob = (int) probabilities[i] & 0xFF; + + if( prob == 0 ) continue; + + Node node = getNode(); + node.value = i; + node.probability = prob; + node.child[0] = null; + node.child[1] = null; + + insertNode( node ); + valueToNode[i] = node; + } + + // generate tree + Node current = root.prev; + while( current != root ){ + Node node = getNode(); + Node child1 = current; + Node child2 = current = current.prev; + + child1.parent = node; + child2.parent = node; + + node.value = -1; + node.probability = child1.probability + child2.probability; + node.child[0] = child1; + node.child[1] = child2; + insertNode( node ); + + current = current.prev; + } + + root.parent = null; + } + + public void Decompress( ByteBuffer in, ByteBuffer out ){ + setSource(in); + byte type = (byte) getBits(8); + buildTree(type); + + adjustProbability = type == 0; + + for(;;){ + Node current = root; + while( current.value == -1 ) + current = current.child[getBits(1)]; + + if( current.value == 0x101 ){ + int value = getBits(8); + current = addValueToTree(value); + incrementProbability(current); + if( !adjustProbability ) incrementProbability(current); + }else if( current.value == 0x100 ){ + break; + } + + out.put((byte) current.value); + + if( adjustProbability ){ + incrementProbability(current); + } + } + + } + + private static final byte[][] PROBABILITY_TABLES = { + // Data for compression type 0x00 + {0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, + 0x01, 0x01}, + + // Data for compression type 0x01 + {0x54, 0x16, 0x16, 0x0D, 0x0C, 0x08, 0x06, 0x05, 0x06, 0x05, 0x06, 0x03, 0x04, 0x04, 0x03, 0x05, + 0x0E, 0x0B, 0x14, 0x13, 0x13, 0x09, 0x0B, 0x06, 0x05, 0x04, 0x03, 0x02, 0x03, 0x02, 0x02, 0x02, + 0x0D, 0x07, 0x09, 0x06, 0x06, 0x04, 0x03, 0x02, 0x04, 0x03, 0x03, 0x03, 0x03, 0x03, 0x02, 0x02, + 0x09, 0x06, 0x04, 0x04, 0x04, 0x04, 0x03, 0x02, 0x03, 0x02, 0x02, 0x02, 0x02, 0x03, 0x02, 0x04, + 0x08, 0x03, 0x04, 0x07, 0x09, 0x05, 0x03, 0x03, 0x03, 0x03, 0x02, 0x02, 0x02, 0x03, 0x02, 0x02, + 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x01, 0x01, 0x01, 0x02, 0x01, 0x02, 0x02, + 0x06, 0x0A, 0x08, 0x08, 0x06, 0x07, 0x04, 0x03, 0x04, 0x04, 0x02, 0x02, 0x04, 0x02, 0x03, 0x03, + 0x04, 0x03, 0x07, 0x07, 0x09, 0x06, 0x04, 0x03, 0x03, 0x02, 0x01, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x0A, 0x02, 0x02, 0x03, 0x02, 0x02, 0x01, 0x01, 0x02, 0x02, 0x02, 0x06, 0x03, 0x05, 0x02, 0x03, + 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x03, 0x01, 0x01, 0x01, + 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x04, 0x04, 0x04, 0x07, 0x09, 0x08, 0x0C, 0x02, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x03, + 0x04, 0x01, 0x02, 0x04, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01, + 0x04, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x01, 0x01, 0x02, 0x02, 0x02, 0x06, 0x4B, + 0x01, 0x01}, + + // Data for compression type 0x02 + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x27, 0x00, 0x00, 0x23, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + (byte) 0xFF, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x01, 0x01, 0x06, 0x0E, 0x10, 0x04, + 0x06, 0x08, 0x05, 0x04, 0x04, 0x03, 0x03, 0x02, 0x02, 0x03, 0x03, 0x01, 0x01, 0x02, 0x01, 0x01, + 0x01, 0x04, 0x02, 0x04, 0x02, 0x02, 0x02, 0x01, 0x01, 0x04, 0x01, 0x01, 0x02, 0x03, 0x03, 0x02, + 0x03, 0x01, 0x03, 0x06, 0x04, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x01, 0x02, 0x01, 0x01, + 0x01, 0x29, 0x07, 0x16, 0x12, 0x40, 0x0A, 0x0A, 0x11, 0x25, 0x01, 0x03, 0x17, 0x10, 0x26, 0x2A, + 0x10, 0x01, 0x23, 0x23, 0x2F, 0x10, 0x06, 0x07, 0x02, 0x09, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x01}, + + // Data for compression type 0x03 + {(byte) 0xFF, 0x0B, 0x07, 0x05, 0x0B, 0x02, 0x02, 0x02, 0x06, 0x02, 0x02, 0x01, 0x04, 0x02, 0x01, 0x03, + 0x09, 0x01, 0x01, 0x01, 0x03, 0x04, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01, + 0x05, 0x01, 0x01, 0x01, 0x0D, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x02, 0x01, 0x01, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01, 0x01, + 0x0A, 0x04, 0x02, 0x01, 0x06, 0x03, 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x03, 0x01, 0x01, 0x01, + 0x05, 0x02, 0x03, 0x04, 0x03, 0x03, 0x03, 0x02, 0x01, 0x01, 0x01, 0x02, 0x01, 0x02, 0x03, 0x03, + 0x01, 0x03, 0x01, 0x01, 0x02, 0x05, 0x01, 0x01, 0x04, 0x03, 0x05, 0x01, 0x03, 0x01, 0x03, 0x03, + 0x02, 0x01, 0x04, 0x03, 0x0A, 0x06, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x02, 0x02, 0x01, 0x0A, 0x02, 0x05, 0x01, 0x01, 0x02, 0x07, 0x02, 0x17, 0x01, 0x05, 0x01, 0x01, + 0x0E, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x06, 0x02, 0x01, 0x04, 0x05, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x07, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01, 0x01, + 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x11, + 0x01, 0x01}, + + // Data for compression type 0x04 + {(byte) 0xFF, (byte) 0xFB, (byte) 0x98, (byte) 0x9A, (byte) 0x84, (byte) 0x85, 0x63, 0x64, 0x3E, 0x3E, 0x22, 0x22, 0x13, 0x13, 0x18, 0x17, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x01}, + + // Data for compression type 0x05 + {(byte) 0xFF, (byte) 0xF1, (byte) 0x9D, (byte) 0x9E, (byte) 0x9A, (byte) 0x9B, (byte) 0x9A, (byte) 0x97, (byte) 0x93, (byte) 0x93, (byte) 0x8C, (byte) 0x8E, (byte) 0x86, (byte) 0x88, (byte) 0x80, (byte) 0x82, + 0x7C, 0x7C, 0x72, 0x73, 0x69, 0x6B, 0x5F, 0x60, 0x55, 0x56, 0x4A, 0x4B, 0x40, 0x41, 0x37, 0x37, + 0x2F, 0x2F, 0x27, 0x27, 0x21, 0x21, 0x1B, 0x1C, 0x17, 0x17, 0x13, 0x13, 0x10, 0x10, 0x0D, 0x0D, + 0x0B, 0x0B, 0x09, 0x09, 0x08, 0x08, 0x07, 0x07, 0x06, 0x05, 0x05, 0x04, 0x04, 0x04, 0x19, 0x18, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x01}, + + // Data for compression type 0x06 + {(byte) 0xC3, (byte) 0xCB, (byte) 0xF5, 0x41, (byte) 0xFF, 0x7B, (byte) 0xF7, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + (byte) 0xBF, (byte) 0xCC, (byte) 0xF2, 0x40, (byte) 0xFD, 0x7C, (byte) 0xF7, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x7A, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x01}, + + // Data for compression type 0x07 + {(byte) 0xC3, (byte) 0xD9, (byte) 0xEF, 0x3D, (byte) 0xF9, 0x7C, (byte) 0xE9, 0x1E, (byte) 0xFD, (byte) 0xAB, (byte) 0xF1, 0x2C, (byte) 0xFC, 0x5B, (byte) 0xFE, 0x17, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + (byte) 0xBD, (byte) 0xD9, (byte) 0xEC, 0x3D, (byte) 0xF5, 0x7D, (byte) 0xE8, 0x1D, (byte) 0xFB, (byte) 0xAE, (byte) 0xF0, 0x2C, (byte) 0xFB, 0x5C, (byte) 0xFF, 0x18, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x70, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x01}, + + // Data for compression type 0x08 + {(byte) 0xBA, (byte) 0xC5, (byte) 0xDA, 0x33, (byte) 0xE3, 0x6D, (byte) 0xD8, 0x18, (byte) 0xE5, (byte) 0x94, (byte) 0xDA, 0x23, (byte) 0xDF, 0x4A, (byte) 0xD1, 0x10, + (byte) 0xEE, (byte) 0xAF, (byte) 0xE4, 0x2C, (byte) 0xEA, 0x5A, (byte) 0xDE, 0x15, (byte) 0xF4, (byte) 0x87, (byte) 0xE9, 0x21, (byte) 0xF6, 0x43, (byte) 0xFC, 0x12, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + (byte) 0xB0, (byte) 0xC7, (byte) 0xD8, 0x33, (byte) 0xE3, 0x6B, (byte) 0xD6, 0x18, (byte) 0xE7, (byte) 0x95, (byte) 0xD8, 0x23, (byte) 0xDB, 0x49, (byte) 0xD0, 0x11, + (byte) 0xE9, (byte) 0xB2, (byte) 0xE2, 0x2B, (byte) 0xE8, 0x5C, (byte) 0xDD, 0x15, (byte) 0xF1, (byte) 0x87, (byte) 0xE7, 0x20, (byte) 0xF7, 0x44, (byte) 0xFF, 0x13, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x5F, (byte) 0x9E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x01} + }; +} diff --git a/core/src/mpq/compression/pkware/PKException.java b/core/src/mpq/compression/pkware/PKException.java new file mode 100644 index 0000000..858ca19 --- /dev/null +++ b/core/src/mpq/compression/pkware/PKException.java @@ -0,0 +1,14 @@ +package mpq.compression.pkware; + +public class PKException extends Exception { + + /** + * + */ + private static final long serialVersionUID = 6514086311357764773L; + + public PKException(String arg0) { + super(arg0); + } + +} diff --git a/core/src/mpq/compression/pkware/PKExploder.java b/core/src/mpq/compression/pkware/PKExploder.java new file mode 100644 index 0000000..8a6db4c --- /dev/null +++ b/core/src/mpq/compression/pkware/PKExploder.java @@ -0,0 +1,200 @@ +package mpq.compression.pkware; + +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; + +public class PKExploder { + private static final byte CMP_BINARY = 0; + private static final byte CMP_ASCII = 1; + + private static final byte LEN_SIZE = 16; + private static final byte[] LEN_BITS = { + 0x03, 0x02, 0x03, 0x03, 0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x05, 0x06, 0x06, 0x06, 0x07, 0x07 + }; + private static final short[] LEN_CODES = { + 0x05, 0x03, 0x01, 0x06, 0x0A, 0x02, 0x0C, 0x14, 0x04, 0x18, 0x08, 0x30, 0x10, 0x20, 0x40, 0x00 + }; + private static final byte[] LENGTH_CODES = new byte[0x100]; + static { + denDecodeTabs(LENGTH_CODES, LEN_CODES, LEN_BITS, LEN_SIZE); + } + + private static final byte[] EX_LEN_BITS = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 + }; + + private static final short[] LEN_BASE ={ + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x000A, 0x000E, 0x0016, 0x0026, 0x0046, 0x0086, 0x0106 + }; + + private static final byte DIST_SIZE = 64; + private static final byte[] DIST_BITS = { + 0x02, 0x04, 0x04, 0x05, 0x05, 0x05, 0x05, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08 + }; + private static final short[] DIST_CODES = + { + 0x03, 0x0D, 0x05, 0x19, 0x09, 0x11, 0x01, 0x3E, 0x1E, 0x2E, 0x0E, 0x36, 0x16, 0x26, 0x06, 0x3A, + 0x1A, 0x2A, 0x0A, 0x32, 0x12, 0x22, 0x42, 0x02, 0x7C, 0x3C, 0x5C, 0x1C, 0x6C, 0x2C, 0x4C, 0x0C, + 0x74, 0x34, 0x54, 0x14, 0x64, 0x24, 0x44, 0x04, 0x78, 0x38, 0x58, 0x18, 0x68, 0x28, 0x48, 0x08, + 0xF0, 0x70, 0xB0, 0x30, 0xD0, 0x50, 0x90, 0x10, 0xE0, 0x60, 0xA0, 0x20, 0xC0, 0x40, 0x80, 0x00 + }; + private static final byte[] DIST_POS_CODES = new byte[0x100]; + static { + denDecodeTabs(DIST_POS_CODES, DIST_CODES, DIST_BITS, DIST_SIZE); + } + + private static void denDecodeTabs(byte[] pos, short[] sindex, byte[] len, int size){ + for( byte i = 0 ; i < size ; i++ ){ + short length = (short) (1 << len[i]); + + for( short index = sindex[i] ; index < pos.length ; index+= length ) + { + pos[index] = i; + } + } + } + + private ByteBuffer in; + private ByteBuffer out; + private byte ctype; + private byte dsize_bits; + private short bit_buff; + private byte extra_bits; + private byte dsize_mask; + + private void WasteBits(byte nbits){ + bit_buff = (short) ((bit_buff & 0xFFFF) >>> nbits); + if( nbits <= extra_bits ){ + extra_bits-= nbits; + }else{ + bit_buff = (short) ((bit_buff | ((in.get() & 0xFF ) << (8 - nbits + extra_bits))) & 0xFFFF); + extra_bits-= nbits - 8; + } + } + + private void expand() throws PKException{ + for( ; ; ){ + if( (bit_buff & 0x01) != 0 ){ + WasteBits((byte) 1); + + // --- repeat bytes + + // get length + short length_code = (short) LENGTH_CODES[bit_buff & 0xFF]; + WasteBits((byte) LEN_BITS[length_code]); + + byte extra_length_bits; + if((extra_length_bits = EX_LEN_BITS[length_code]) != 0) + { + byte extra_length = (byte) (bit_buff & ((1 << extra_length_bits) - 1)); + + try{ + WasteBits(extra_length_bits); + }catch( BufferUnderflowException e ){ + if( (length_code + (extra_length & 0xFF)) == 0x10E ) return; + else throw e; + } + length_code = (short) (LEN_BASE[length_code] + (extra_length & 0xFF)); + + } + + length_code+= 2; + + // get distance + byte dist_pos_code = DIST_POS_CODES[bit_buff & 0xFF]; + byte dist_pos_bits = DIST_BITS[dist_pos_code]; + WasteBits(dist_pos_bits); + + short distance; + + if(length_code == 2){ + // If the repetition is only 2 bytes length, + // then take 2 bits from the stream in order to get the distance + distance = (short) ((dist_pos_code << 2) | (bit_buff & 0x03)); + WasteBits((byte) 2); + }else{ + // If the repetition is more than 2 bytes length, + // then take "dsize_bits" bits in order to get the distance + distance = (short) ((dist_pos_code << dsize_bits) | (bit_buff & dsize_mask)); + WasteBits(dsize_bits); + } + distance+= 1; + + // do the copying + int target = out.position(); + int source = target - distance; + + while( length_code > 0 ){ + if(source >= 0) + out.put(target++, out.get(source++)); + else{ + throw new PKException("distance pointing before output"); + } + length_code-= 1; + } + + out.position(target); + + }else{ + WasteBits((byte) 1); + + // --- raw byte + + switch( ctype ){ + case CMP_BINARY: + // read raw byte + byte uncompressed_byte = (byte) (bit_buff & 0xFF); + WasteBits((byte) 8); + + // write raw byte + out.put(uncompressed_byte); + break; + case CMP_ASCII: + // TODO add ASCII decompression stuff here + System.err.println("pkware ascii compression not supported"); + return; + default: throw new PKException("invalid compression mode"); + } + } + } + } + + public void explode(ByteBuffer in, ByteBuffer out) throws PKException{ + // set in and out buffers + this.in = in; + this.out = out; + if( in.remaining() <= 4 ) throw new PKException("received bad data"); + + // initialize state with compression header + ctype = in.get(); + dsize_bits = in.get(); + bit_buff = (short) (in.get() & 0xFF); + extra_bits = 0; + + // dictionary size mask + if( dsize_bits < 4 || 6 < dsize_bits ) throw new PKException("invalid dictionary size"); + dsize_mask = (byte) (0xFFFF >> (0x10 - dsize_bits)); + + // setup compression type dependent data + switch( ctype ){ + case CMP_BINARY: break; + case CMP_ASCII: + // TODO add ASCII decompression stuff here + System.err.println("pkware ascii compression not supported"); + return; + default: throw new PKException("invalid compression mode"); + } + + // perform explode + try{ + expand(); + }catch( BufferUnderflowException e ){ + throw new PKException("unexpected end of data"); + } + + } +} diff --git a/core/src/mpq/data/ArchiveHeader.java b/core/src/mpq/data/ArchiveHeader.java new file mode 100644 index 0000000..2da8caf --- /dev/null +++ b/core/src/mpq/data/ArchiveHeader.java @@ -0,0 +1,98 @@ +package mpq.data; + +import java.nio.ByteBuffer; + +public class ArchiveHeader extends Raw{ + public static final int STRUCT_SIZE_V1 = 32 - 8; + public static final int STRUCT_SIZE_V2 = STRUCT_SIZE_V1 + 12; + public static final int STRUCT_SIZE_V3 = STRUCT_SIZE_V2 + 24; + public static final int STRUCT_SIZE_V4 = STRUCT_SIZE_V3 + 44; + + public ArchiveHeader(ByteBuffer source) { + super(source); + } + + // VERSION 1 + + public int getArchiveSize() { + return data.getInt(0); + } + + public short getFormatVersion() { + return data.getShort(4); + } + + public short getBlockSize() { + return data.getShort(6); + } + + public int getHashTablePosition() { + return data.getInt(8); + } + + public int getBlockTablePosition() { + return data.getInt(12); + } + + public int getHashTableSize() { + return data.getInt(16); + } + + public int getBlockTableSize() { + return data.getInt(20); + } + + // VERSION 2 + + public long getHighBlockTablePosition() { + return data.getLong(24); + } + + public short getHashTablePositionHigh() { + return data.getShort(32); + } + + public short getBlockTablePositionHigh() { + return data.getShort(34); + } + + // VERSION 3 + + public long getArchiveSizeLong() { + return data.getLong(36); + } + + public long getBetTablePosition() { + return data.getLong(44); + } + + public long getHetTablePosition() { + return data.getLong(52); + } + + // VERSION 4 + + public long getHashTableSizeCompressed() { + return data.getLong(60); + } + + public long getBlockTableSizeCompressed() { + return data.getLong(68); + } + + public long getHighBlockTableSizeCompressed() { + return data.getLong(76); + } + + public long getHetTableSizeCompressed() { + return data.getLong(84); + } + + public long getBetTableSizeCompressed() { + return data.getLong(92); + } + + public int getRawChunkSize() { + return data.getInt(100); + } +} diff --git a/core/src/mpq/data/BlockTableEntry.java b/core/src/mpq/data/BlockTableEntry.java new file mode 100644 index 0000000..bd262d5 --- /dev/null +++ b/core/src/mpq/data/BlockTableEntry.java @@ -0,0 +1,31 @@ +package mpq.data; + +import java.nio.ByteBuffer; + +public class BlockTableEntry extends Raw { + public static final int STRUCT_SIZE = 16; + + public BlockTableEntry(ByteBuffer source) { + super(source); + } + + public BlockTableEntry() { + super(); + } + + public int getFilePosition() { + return data.getInt(0); + } + + public int getCompressedSize() { + return data.getInt(4); + } + + public int getFileSize() { + return data.getInt(8); + } + + public int getFlags() { + return data.getInt(12); + } +} diff --git a/core/src/mpq/data/FileHeader.java b/core/src/mpq/data/FileHeader.java new file mode 100644 index 0000000..ca1b4c1 --- /dev/null +++ b/core/src/mpq/data/FileHeader.java @@ -0,0 +1,31 @@ +package mpq.data; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +public class FileHeader extends Raw{ + public static final byte[] ARCHIVE_IDENTIFIER_BYTES = {'M','P','Q',0x1a}; + public static final int ARCHIVE_IDENTIFIER_INT = ByteBuffer.wrap(ARCHIVE_IDENTIFIER_BYTES).order(ByteOrder.LITTLE_ENDIAN).getInt(0); + public static final byte[] USERDATA_IDENTIFIER_BYTES = {'M','P','Q',0x1b}; + public static final int USERDATA_IDENTIFIER_INT = ByteBuffer.wrap(USERDATA_IDENTIFIER_BYTES).order(ByteOrder.LITTLE_ENDIAN).getInt(0); + public static final int STRUCT_SIZE = 8; + + public FileHeader(ByteBuffer source) { + super(source); + } + + public byte[] getIdentifierBytes() { + byte[] bytes = new byte[4]; + data.position(0); + data.get(bytes); + return bytes; + } + + public int getIdentifierInt() { + return data.getInt(0); + } + + public int getHeaderSize() { + return data.getInt(4); + } +} diff --git a/core/src/mpq/data/HashTableEntry.java b/core/src/mpq/data/HashTableEntry.java new file mode 100644 index 0000000..1e970f8 --- /dev/null +++ b/core/src/mpq/data/HashTableEntry.java @@ -0,0 +1,31 @@ +package mpq.data; + +import java.nio.ByteBuffer; + +public class HashTableEntry extends Raw { + public static final int STRUCT_SIZE = 16; + + public HashTableEntry(ByteBuffer source) { + super(source); + } + + public HashTableEntry() { + super(); + } + + public long getHash() { + return data.getLong(0); + } + + public short getLocale() { + return data.getShort(8); + } + + public short getPlatform() { + return data.getShort(10); + } + + public int getBlockIndex() { + return data.getInt(12); + } +} diff --git a/core/src/mpq/data/Raw.java b/core/src/mpq/data/Raw.java new file mode 100644 index 0000000..80fd27f --- /dev/null +++ b/core/src/mpq/data/Raw.java @@ -0,0 +1,20 @@ +package mpq.data; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +public abstract class Raw { + protected ByteBuffer data; + + public Raw(ByteBuffer source){ + move(source); + } + + public Raw(){ + data = null; + } + + public void move(ByteBuffer source){ + data = source.slice().order(ByteOrder.LITTLE_ENDIAN); + } +} diff --git a/core/src/mpq/data/RawArrays.java b/core/src/mpq/data/RawArrays.java new file mode 100644 index 0000000..4efe1e1 --- /dev/null +++ b/core/src/mpq/data/RawArrays.java @@ -0,0 +1,36 @@ +package mpq.data; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; + +public class RawArrays { + public static int[] getArray(ByteBuffer source){ + source.order(ByteOrder.LITTLE_ENDIAN); + IntBuffer in = source.asIntBuffer(); + + IntBuffer out = IntBuffer.allocate(in.capacity()); + out.put(in); + + return out.array(); + } + + public static void getArray(ByteBuffer source, int[] destination){ + source.order(ByteOrder.LITTLE_ENDIAN); + IntBuffer in = source.asIntBuffer(); + + IntBuffer out = IntBuffer.wrap(destination); + out.put(in); + } + + public static short[] getShortArray(ByteBuffer source){ + source.order(ByteOrder.LITTLE_ENDIAN); + ShortBuffer in = source.asShortBuffer(); + + ShortBuffer out = ShortBuffer.allocate(in.capacity()); + out.put(in); + + return out.array(); + } +} diff --git a/core/src/mpq/data/UserDataHeader.java b/core/src/mpq/data/UserDataHeader.java new file mode 100644 index 0000000..978186a --- /dev/null +++ b/core/src/mpq/data/UserDataHeader.java @@ -0,0 +1,21 @@ +package mpq.data; + +import java.nio.ByteBuffer; + +public class UserDataHeader extends Raw{ + public static final int STRUCT_SIZE = 16 - 8; + + public UserDataHeader(ByteBuffer source) { + super(source); + } + + + public int getArchiveOffset() { + return data.getInt(0); + } + + public int getUserDataSize() { + return data.getInt(4); + } + +} diff --git a/core/src/mpq/util/Cryption.java b/core/src/mpq/util/Cryption.java new file mode 100644 index 0000000..073c226 --- /dev/null +++ b/core/src/mpq/util/Cryption.java @@ -0,0 +1,116 @@ +package mpq.util; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; +import java.util.Locale; + +public class Cryption { + private static final int[] CRYPT_TABLE = new int[0x500]; + static { + int seed = 0x00100001; + + for (int index1 = 0; index1 < 0x100; index1++) { + for (int index2 = index1, i = 0; i < 5; i++, index2 += 0x100) { + seed = ((seed * 125) + 3) % 0x2AAAAB; + final int temp1 = (seed & 0xFFFF) << 0x10; + + seed = ((seed * 125) + 3) % 0x2AAAAB; + final int temp2 = (seed & 0xFFFF); + + CRYPT_TABLE[index2] = (temp1 | temp2); + } + } + } + + // different types of hashes to make with HashString + public static final int MPQ_HASH_TABLE_OFFSET = 0; + public static final int MPQ_HASH_NAME_A = 1; + public static final int MPQ_HASH_NAME_B = 2; + public static final int MPQ_HASH_FILE_KEY = 3; + + // cached hashes + public static final int KEY_HASH_TABLE = HashString("(hash table)", MPQ_HASH_FILE_KEY); + public static final int KEY_BLOCK_TABLE = HashString("(block table)", MPQ_HASH_FILE_KEY); + + public static void cryptData(ByteBuffer in, ByteBuffer out, int length, int key, final boolean de) { + // prepare platform independent views + in = in.asReadOnlyBuffer().order(ByteOrder.LITTLE_ENDIAN); + out = out.duplicate().order(ByteOrder.LITTLE_ENDIAN); + // cryption + int seed = 0xEEEEEEEE; + length /= 4; + while (length-- > 0) { + seed += CRYPT_TABLE[0x400 + (key & 0xFF)]; + // basic algorithm + final int read = in.getInt(); + final int ch = read ^ (key + seed); + out.putInt(ch); + // generation for next iteration + seed += (de ? ch : read) + (seed << 5) + 3; + key = ((~key << 21) + 0x11111111) | (key >>> 11); + } + out.rewind(); + } + + public static void encryptData(final ByteBuffer in, final ByteBuffer out, final int length, final int key) { + cryptData(in, out, length, key, false); + } + + public static void decryptData(final ByteBuffer in, final ByteBuffer out, final int length, final int key) { + cryptData(in, out, length, key, true); + } + + public static void cryptData(ByteBuffer in, ByteBuffer out, int key, final boolean de) { + // prepare platform independent views + in = in.asReadOnlyBuffer().order(ByteOrder.LITTLE_ENDIAN); + out = out.duplicate().order(ByteOrder.LITTLE_ENDIAN); + + // round to last dword to prevent buffer underflow + in.limit(in.limit() & ~0x03); + + // cryption + int seed = 0xEEEEEEEE; + while (in.hasRemaining()) { + seed += CRYPT_TABLE[0x400 + (key & 0xFF)]; + // basic algorithm + final int read = in.getInt(); + final int ch = read ^ (key + seed); + out.putInt(ch); + // generation for next iteration + seed += (de ? ch : read) + (seed << 5) + 3; + key = ((~key << 21) + 0x11111111) | (key >>> 11); + } + } + + public static void encryptData(final ByteBuffer in, final ByteBuffer out, final int key) { + cryptData(in, out, key, false); + } + + public static void decryptData(final ByteBuffer in, final ByteBuffer out, final int key) { + cryptData(in, out, key, true); + } + + public static byte[] stringToHashable(final String in) { + return in.toUpperCase(Locale.US).getBytes(StandardCharsets.UTF_8); // UTF_8 defined for platform independence + } + + public static int HashString(final String in, final int HashType) { + return HashString(stringToHashable(in), HashType); + } + + // Based on code from StormLib. + public static int HashString(final byte[] in, final int HashType) { + int seed1 = 0x7FED7FED; + int seed2 = 0xEEEEEEEE; + for (final byte ch : in) { + seed1 = CRYPT_TABLE[(HashType * 0x100) + ch] ^ (seed1 + seed2); + seed2 = ch + seed1 + seed2 + (seed2 << 5) + 3; + } + return seed1; + } + + public static int adjustFileDecryptKey(final int in, final int pos, final int size) { + return (in + pos) ^ size; + } +} diff --git a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java index b4b6f7d..b07c2f9 100644 --- a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java +++ b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java @@ -33,6 +33,10 @@ public class DesktopLauncher { config.useGL30 = true; config.gles30ContextMajorVersion = 3; config.gles30ContextMinorVersion = 3; +// config.fullscreen = true; +// final DisplayMode desktopDisplayMode = LwjglApplicationConfiguration.getDesktopDisplayMode(); +// config.width = desktopDisplayMode.width; +// config.height = desktopDisplayMode.height; new LwjglApplication(new WarsmashGdxGame(), config); } } From e998367f774a77f667ee689e1572f7598c74f269 Mon Sep 17 00:00:00 2001 From: Retera Date: Tue, 21 Jan 2020 20:20:37 -0600 Subject: [PATCH 013/116] Visually able to see units and glitchy terrain now --- .../etheller/warsmash/WarsmashGdxGame.java | 2 +- .../etheller/warsmash/WarsmashGdxMapGame.java | 253 ++++++++ .../units/manager/MutableObjectData.java | 19 + .../etheller/warsmash/util/MappedData.java | 2 +- .../warsmash/util/RenderMathUtils.java | 6 +- .../warsmash/util/WarsmashConstants.java | 2 +- .../warsmash/viewer5/GdxTextureResource.java | 7 + .../com/etheller/warsmash/viewer5/Grid.java | 11 +- .../etheller/warsmash/viewer5/GridCell.java | 10 +- .../warsmash/viewer5/ModelInstance.java | 3 + .../warsmash/viewer5/ModelViewer.java | 2 +- .../com/etheller/warsmash/viewer5/Node.java | 2 +- .../etheller/warsmash/viewer5/PathSolver.java | 6 +- .../viewer5/RawOpenGLTextureResource.java | 5 + .../com/etheller/warsmash/viewer5/Scene.java | 31 +- .../warsmash/viewer5/SkeletalNode.java | 6 +- .../etheller/warsmash/viewer5/Texture.java | 2 + .../handlers/mdx/ParticleEmitter2Object.java | 2 +- .../warsmash/viewer5/handlers/w3x/Doodad.java | 20 +- .../viewer5/handlers/w3x/HiveWEShaders.java | 45 ++ .../viewer5/handlers/w3x/StandSequence.java | 3 + .../viewer5/handlers/w3x/TerrainDoodad.java | 9 +- .../viewer5/handlers/w3x/TerrainModel.java | 31 + .../warsmash/viewer5/handlers/w3x/Unit.java | 25 +- .../viewer5/handlers/w3x/Variations.java | 154 +++++ .../viewer5/handlers/w3x/W3xShaders.java | 135 +++-- .../w3x/W3xShadersWebGLDeprecated.java | 230 ++++++++ .../viewer5/handlers/w3x/War3MapViewer.java | 548 +++++++++++++++++- .../warsmash/desktop/DesktopLauncher.java | 4 +- 29 files changed, 1446 insertions(+), 129 deletions(-) create mode 100644 core/src/com/etheller/warsmash/WarsmashGdxMapGame.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/HiveWEShaders.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/Variations.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xShadersWebGLDeprecated.java diff --git a/core/src/com/etheller/warsmash/WarsmashGdxGame.java b/core/src/com/etheller/warsmash/WarsmashGdxGame.java index 688611c..6f96dcf 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxGame.java @@ -35,7 +35,7 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide private ModelViewer viewer; private MdxModel model; private CameraManager cameraManager; - private static int VAO; + public static int VAO; private final Rectangle tempRect = new Rectangle(); private BitmapFont font; diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java new file mode 100644 index 0000000..a1afee6 --- /dev/null +++ b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java @@ -0,0 +1,253 @@ +package com.etheller.warsmash; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.IntBuffer; +import java.util.Arrays; + +import com.badlogic.gdx.ApplicationAdapter; +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.Input; +import com.badlogic.gdx.InputProcessor; +import com.badlogic.gdx.graphics.GL20; +import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.badlogic.gdx.math.Quaternion; +import com.badlogic.gdx.math.Rectangle; +import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.math.Vector3; +import com.etheller.warsmash.datasources.CompoundDataSourceDescriptor; +import com.etheller.warsmash.datasources.DataSource; +import com.etheller.warsmash.datasources.DataSourceDescriptor; +import com.etheller.warsmash.datasources.FolderDataSourceDescriptor; +import com.etheller.warsmash.viewer5.Camera; +import com.etheller.warsmash.viewer5.CanvasProvider; +import com.etheller.warsmash.viewer5.Scene; +import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; + +public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProvider, InputProcessor { + private DataSource codebase; + private War3MapViewer viewer; + private CameraManager cameraManager; + private final Rectangle tempRect = new Rectangle(); + + private BitmapFont font; + private SpriteBatch batch; + + @Override + public void create() { + + final ByteBuffer tempByteBuffer = ByteBuffer.allocateDirect(4); + tempByteBuffer.order(ByteOrder.LITTLE_ENDIAN); + final IntBuffer temp = tempByteBuffer.asIntBuffer(); + + Gdx.gl30.glGenVertexArrays(1, temp); + WarsmashGdxGame.VAO = temp.get(0); + + Gdx.gl30.glBindVertexArray(WarsmashGdxGame.VAO); + + final String renderer = Gdx.gl.glGetString(GL20.GL_RENDERER); + System.err.println("Renderer: " + renderer); + +// final FolderDataSourceDescriptor war3mpq = new FolderDataSourceDescriptor("E:\\Backups\\Warcraft\\Data\\127"); + final FolderDataSourceDescriptor war3mpq = new FolderDataSourceDescriptor( + "D:\\NEEDS_ORGANIZING\\MPQBuild\\War3.mpq\\war3.mpq"); + final FolderDataSourceDescriptor war3xLocalmpq = new FolderDataSourceDescriptor( + "D:\\NEEDS_ORGANIZING\\MPQBuild\\War3xLocal.mpq\\enus-war3local.mpq"); + final FolderDataSourceDescriptor testingFolder = new FolderDataSourceDescriptor( + "D:\\NEEDS_ORGANIZING\\MPQBuild\\Test"); + final FolderDataSourceDescriptor currentFolder = new FolderDataSourceDescriptor("."); + this.codebase = new CompoundDataSourceDescriptor( + Arrays.asList(war3mpq, war3xLocalmpq, testingFolder, currentFolder)) + .createDataSource(); + this.viewer = new War3MapViewer(this.codebase, this); + + try { + this.viewer.loadMap("ReforgedGeorgeVacation.w3x"); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + + this.cameraManager = new CameraManager(); + this.cameraManager.setupCamera(this.viewer.worldScene); + + System.out.println("Loaded"); + Gdx.gl30.glClearColor(0.0f, 0.0f, 0.0f, 1); // TODO remove white background + + this.font = new BitmapFont(); + this.batch = new SpriteBatch(); + + Gdx.input.setInputProcessor(this); + } + + @Override + public void render() { + Gdx.gl30.glBindVertexArray(WarsmashGdxGame.VAO); + this.cameraManager.target.add(this.cameraVelocity.x, this.cameraVelocity.y, 0); + this.cameraManager.updateCamera(); + this.viewer.updateAndRender(); + +// gl.glDrawElements(GL20.GL_TRIANGLES, this.elements, GL20.GL_UNSIGNED_SHORT, this.faceOffset); + +// this.batch.begin(); +// this.font.draw(this.batch, Integer.toString(Gdx.graphics.getFramesPerSecond()), 0, 0); +// this.batch.end(); + } + + @Override + public void dispose() { + } + + @Override + public float getWidth() { + return Gdx.graphics.getWidth(); + } + + @Override + public float getHeight() { + return Gdx.graphics.getHeight(); + } + + @Override + public void resize(final int width, final int height) { + this.tempRect.width = width; + this.tempRect.height = height; + this.cameraManager.camera.viewport(this.tempRect); + } + + class CameraManager { + private CanvasProvider canvas; + private Camera camera; + private float moveSpeed; + private float rotationSpeed; + private float zoomFactor; + private float horizontalAngle; + private float verticalAngle; + private float distance; + private Vector3 position; + private Vector3 target; + private Vector3 worldUp; + private Vector3 vecHeap; + private Quaternion quatHeap; + private Quaternion quatHeap2; + + // An orbit camera setup example. + // Left mouse button controls the orbit itself. + // The right mouse button allows to move the camera and the point it's looking + // at on the XY plane. + // Scrolling zooms in and out. + private void setupCamera(final Scene scene) { + this.canvas = scene.viewer.canvas; + this.camera = scene.camera; + this.moveSpeed = 2; + this.rotationSpeed = (float) (Math.PI / 180); + this.zoomFactor = 0.1f; + this.horizontalAngle = 0;// (float) (Math.PI / 2); + this.verticalAngle = (float) (Math.PI / 4); + this.distance = 1600; + this.position = new Vector3(); + this.target = new Vector3(0, 0, 50); + this.worldUp = new Vector3(0, 0, 1); + this.vecHeap = new Vector3(); + this.quatHeap = new Quaternion(); + this.quatHeap2 = new Quaternion(); + + updateCamera(); + +// cameraUpdate(); + } + + private void updateCamera() { + // Limit the vertical angle so it doesn't flip. + // Since the camera uses a quaternion, flips don't matter to it, but this feels + // better. + this.verticalAngle = (float) Math.min(Math.max(0.01, this.verticalAngle), Math.PI - 0.01); + + this.quatHeap.idt(); + this.quatHeap.setFromAxisRad(0, 0, 1, this.horizontalAngle); + this.quatHeap2.idt(); + this.quatHeap2.setFromAxisRad(1, 0, 0, this.verticalAngle); + this.quatHeap.mul(this.quatHeap2); + + this.position.set(0, 0, 1); + this.quatHeap.transform(this.position); + this.position.scl(this.distance); + this.position = this.position.add(this.target); + + this.camera.moveToAndFace(this.position, this.target, this.worldUp); + } + +// private void cameraUpdate() { +// +// } + } + + private final float cameraSpeed = 100.0f; + private final Vector2 cameraVelocity = new Vector2(); + + @Override + public boolean keyDown(final int keycode) { + if (keycode == Input.Keys.LEFT) { + this.cameraVelocity.x = -this.cameraSpeed; + } + else if (keycode == Input.Keys.RIGHT) { + this.cameraVelocity.x = this.cameraSpeed; + } + else if (keycode == Input.Keys.DOWN) { + this.cameraVelocity.y = -this.cameraSpeed; + } + else if (keycode == Input.Keys.UP) { + this.cameraVelocity.y = this.cameraSpeed; + } + return true; + } + + @Override + public boolean keyUp(final int keycode) { + if (keycode == Input.Keys.LEFT) { + this.cameraVelocity.x = 0; + } + else if (keycode == Input.Keys.RIGHT) { + this.cameraVelocity.x = 0; + } + else if (keycode == Input.Keys.DOWN) { + this.cameraVelocity.y = 0; + } + else if (keycode == Input.Keys.UP) { + this.cameraVelocity.y = 0; + } + return true; + } + + @Override + public boolean keyTyped(final char character) { + return false; + } + + @Override + public boolean touchDown(final int screenX, final int screenY, final int pointer, final int button) { + return false; + } + + @Override + public boolean touchUp(final int screenX, final int screenY, final int pointer, final int button) { + return false; + } + + @Override + public boolean touchDragged(final int screenX, final int screenY, final int pointer) { + return false; + } + + @Override + public boolean mouseMoved(final int screenX, final int screenY) { + return false; + } + + @Override + public boolean scrolled(final int amount) { + return false; + } +} diff --git a/core/src/com/etheller/warsmash/units/manager/MutableObjectData.java b/core/src/com/etheller/warsmash/units/manager/MutableObjectData.java index 95a7a60..e6fd636 100644 --- a/core/src/com/etheller/warsmash/units/manager/MutableObjectData.java +++ b/core/src/com/etheller/warsmash/units/manager/MutableObjectData.java @@ -624,6 +624,25 @@ public final class MutableObjectData { return this.parentWC3Object.getFieldValue(key) == 1; } + public int readSLKTagInt(final String key) { + if (MutableObjectData.this.metaNameToMetaId.containsKey(key)) { + return getFieldAsInteger(MutableObjectData.this.metaNameToMetaId.get(key), 0); + } + return this.parentWC3Object.getFieldValue(key); + } + + public float readSLKTagFloat(final String key) { + if (MutableObjectData.this.metaNameToMetaId.containsKey(key)) { + return getFieldAsFloat(MutableObjectData.this.metaNameToMetaId.get(key), 0); + } + try { + return Float.parseFloat(this.parentWC3Object.getField(key)); + } + catch (final NumberFormatException exc) { + return Float.NaN; + } + } + public String getName() { String name = getFieldAsString(MutableObjectData.this.editorData.getNameField(), MutableObjectData.this.worldEditorDataType == WorldEditorDataType.UPGRADES ? 1 : 0); diff --git a/core/src/com/etheller/warsmash/util/MappedData.java b/core/src/com/etheller/warsmash/util/MappedData.java index dddb748..6c17021 100644 --- a/core/src/com/etheller/warsmash/util/MappedData.java +++ b/core/src/com/etheller/warsmash/util/MappedData.java @@ -56,7 +56,7 @@ public class MappedData { key = "column" + j; } - mapped.put(key, row.get(j)); + mapped.put(key, j < row.size() ? row.get(j) : null); } } } diff --git a/core/src/com/etheller/warsmash/util/RenderMathUtils.java b/core/src/com/etheller/warsmash/util/RenderMathUtils.java index dea1abf..79c96f7 100644 --- a/core/src/com/etheller/warsmash/util/RenderMathUtils.java +++ b/core/src/com/etheller/warsmash/util/RenderMathUtils.java @@ -321,8 +321,8 @@ public enum RenderMathUtils { return -1; } - public static int testCell(final Vector4[] planes, final int left, final int right, final int bottom, final int top, - int first) { + public static int testCell(final Vector4[] planes, final float left, final float right, final float bottom, + final float top, int first) { if (first == -1) { first = 0; } @@ -340,7 +340,7 @@ public enum RenderMathUtils { return -1; } - public static float distance2Plane2(final Vector4 plane, final int px, final int py) { + public static float distance2Plane2(final Vector4 plane, final float px, final float py) { return (plane.x * px) + (plane.y * py) + plane.w; } diff --git a/core/src/com/etheller/warsmash/util/WarsmashConstants.java b/core/src/com/etheller/warsmash/util/WarsmashConstants.java index 1100226..0e05786 100644 --- a/core/src/com/etheller/warsmash/util/WarsmashConstants.java +++ b/core/src/com/etheller/warsmash/util/WarsmashConstants.java @@ -1,5 +1,5 @@ package com.etheller.warsmash.util; public class WarsmashConstants { - public static final int MAX_PLAYERS = 16; + public static final int MAX_PLAYERS = 28; } diff --git a/core/src/com/etheller/warsmash/viewer5/GdxTextureResource.java b/core/src/com/etheller/warsmash/viewer5/GdxTextureResource.java index a4bcca4..aff666b 100644 --- a/core/src/com/etheller/warsmash/viewer5/GdxTextureResource.java +++ b/core/src/com/etheller/warsmash/viewer5/GdxTextureResource.java @@ -45,10 +45,17 @@ public abstract class GdxTextureResource extends Texture { return this.gdxTexture.glTarget; } + @Override + public int getGlHandle() { + return this.gdxTexture.getTextureObjectHandle(); + } + + @Override public void setWrapS(final boolean wrapS) { this.gdxTexture.setWrap(wrapS ? TextureWrap.Repeat : TextureWrap.ClampToEdge, this.gdxTexture.getVWrap()); } + @Override public void setWrapT(final boolean wrapT) { this.gdxTexture.setWrap(this.gdxTexture.getUWrap(), wrapT ? TextureWrap.Repeat : TextureWrap.ClampToEdge); } diff --git a/core/src/com/etheller/warsmash/viewer5/Grid.java b/core/src/com/etheller/warsmash/viewer5/Grid.java index 65d7591..3ac16c1 100644 --- a/core/src/com/etheller/warsmash/viewer5/Grid.java +++ b/core/src/com/etheller/warsmash/viewer5/Grid.java @@ -13,7 +13,8 @@ public class Grid { private final int rows; final GridCell[] cells; - public Grid(final float x, float y, final int width, final int depth, final int cellWidth, final int cellDepth) { + public Grid(final float x, final float y, final int width, final int depth, final int cellWidth, + final int cellDepth) { final int columns = width / cellWidth; final int rows = depth / cellDepth; @@ -29,10 +30,10 @@ public class Grid { for (int row = 0; row < rows; row++) { for (int column = 0; column < columns; column++) { - final int left = x + (columns * cellWidth); - final int right = left + cellWidth; - final int bottom = y = row * cellDepth; - final int top = bottom + cellDepth; + final float left = x + (columns * cellWidth); + final float right = left + cellWidth; + final float bottom = y + (row * cellDepth); + final float top = bottom + cellDepth; this.cells[(row * columns) + column] = new GridCell(left, right, bottom, top); } diff --git a/core/src/com/etheller/warsmash/viewer5/GridCell.java b/core/src/com/etheller/warsmash/viewer5/GridCell.java index 194c2a9..1b82ff7 100644 --- a/core/src/com/etheller/warsmash/viewer5/GridCell.java +++ b/core/src/com/etheller/warsmash/viewer5/GridCell.java @@ -6,15 +6,15 @@ import java.util.List; import com.etheller.warsmash.util.RenderMathUtils; public class GridCell { - public final int left; - public final int right; - public final int bottom; - public final int top; + public final float left; + public final float right; + public final float bottom; + public final float top; public int plane; final List instances; public final boolean visible; - public GridCell(final int left, final int right, final int bottom, final int top) { + public GridCell(final float left, final float right, final float bottom, final float top) { this.left = left; this.right = right; this.bottom = bottom; diff --git a/core/src/com/etheller/warsmash/viewer5/ModelInstance.java b/core/src/com/etheller/warsmash/viewer5/ModelInstance.java index 671f739..96a7afe 100644 --- a/core/src/com/etheller/warsmash/viewer5/ModelInstance.java +++ b/core/src/com/etheller/warsmash/viewer5/ModelInstance.java @@ -93,6 +93,9 @@ public abstract class ModelInstance extends Node { } public boolean isVisible(final Camera camera) { + if (true) { + return true; + } final float x = this.worldLocation.x; final float y = this.worldLocation.y; final float z = this.worldLocation.z; diff --git a/core/src/com/etheller/warsmash/viewer5/ModelViewer.java b/core/src/com/etheller/warsmash/viewer5/ModelViewer.java index 738d2e1..fbe86a5 100644 --- a/core/src/com/etheller/warsmash/viewer5/ModelViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/ModelViewer.java @@ -45,7 +45,7 @@ public class ModelViewer { this.resources = new ArrayList<>(); this.fetchCache = new HashMap<>(); this.handlers = new HashSet(); - this.frameTime = 1000 / 6; + this.frameTime = 1000 / 60; this.gl = Gdx.gl; this.webGL = new WebGL(this.gl); this.scenes = new ArrayList<>(); diff --git a/core/src/com/etheller/warsmash/viewer5/Node.java b/core/src/com/etheller/warsmash/viewer5/Node.java index 76eb5a1..dd42089 100644 --- a/core/src/com/etheller/warsmash/viewer5/Node.java +++ b/core/src/com/etheller/warsmash/viewer5/Node.java @@ -17,7 +17,7 @@ public abstract class Node extends GenericNode { public Node() { this.pivot = new Vector3(); this.localLocation = new Vector3(); - this.localRotation = new Quaternion(0, 0, 0, 1); + this.localRotation = new Quaternion(); this.localScale = new Vector3(1, 1, 1); this.worldLocation = new Vector3(); this.worldRotation = new Quaternion(); diff --git a/core/src/com/etheller/warsmash/viewer5/PathSolver.java b/core/src/com/etheller/warsmash/viewer5/PathSolver.java index b1b8160..a4432e0 100644 --- a/core/src/com/etheller/warsmash/viewer5/PathSolver.java +++ b/core/src/com/etheller/warsmash/viewer5/PathSolver.java @@ -11,7 +11,11 @@ public interface PathSolver { public static final PathSolver DEFAULT = new PathSolver() { @Override public SolvedPath solve(final String src, final Object solverParams) { - return new SolvedPath(src, src.substring(src.lastIndexOf('.')), true); + final int dotIndex = src.lastIndexOf('.'); + if (dotIndex == -1) { + throw new IllegalStateException("unable to resolve: " + src); + } + return new SolvedPath(src, src.substring(dotIndex), true); } }; } diff --git a/core/src/com/etheller/warsmash/viewer5/RawOpenGLTextureResource.java b/core/src/com/etheller/warsmash/viewer5/RawOpenGLTextureResource.java index 8f7a57e..8cd0d63 100644 --- a/core/src/com/etheller/warsmash/viewer5/RawOpenGLTextureResource.java +++ b/core/src/com/etheller/warsmash/viewer5/RawOpenGLTextureResource.java @@ -71,6 +71,11 @@ public abstract class RawOpenGLTextureResource extends Texture { return this.target; } + @Override + public int getGlHandle() { + return this.handle; + } + @Override public void setWrapS(final boolean wrapS) { this.wrapS = wrapS ? GL20.GL_REPEAT : GL20.GL_CLAMP_TO_EDGE; diff --git a/core/src/com/etheller/warsmash/viewer5/Scene.java b/core/src/com/etheller/warsmash/viewer5/Scene.java index d447f62..5d0c038 100644 --- a/core/src/com/etheller/warsmash/viewer5/Scene.java +++ b/core/src/com/etheller/warsmash/viewer5/Scene.java @@ -7,6 +7,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.math.Rectangle; /** @@ -43,6 +44,15 @@ public class Scene { public final EmittedObjectUpdater emitterObjectUpdater; public final Map batches; public final Comparator instanceDepthComparator; + /** + * Similar to WebGL's own `alpha` parameter. + * + * If false, the scene will be cleared before rendering, meaning that scenes + * behind it won't be visible through it. + * + * If true, alpha works as usual. + */ + public boolean alpha = false; public Scene(final ModelViewer viewer) { final CanvasProvider canvas = viewer.canvas; @@ -185,8 +195,8 @@ public class Scene { if (cell.isVisible(this.camera)) { this.visibleCells += 1; - for (int i = 0, l = cell.instances.size(); i < l; i++) { - final ModelInstance instance = cell.instances.get(i); + for (final ModelInstance instance : new ArrayList<>(cell.instances)) { +// final ModelInstance instance = cell.instances.get(i); if (instance.rendered && (instance.cullFrame < frame) && instance.isVisible(this.camera)) { instance.cullFrame = frame; @@ -232,6 +242,23 @@ public class Scene { this.updatedParticles = this.emitterObjectUpdater.objects.size(); } + public void startFrame() { + final GL20 gl = this.viewer.gl; + final Rectangle viewport = this.camera.rect; + + // Set the viewport + gl.glViewport((int) viewport.x, (int) viewport.y, (int) viewport.width, (int) viewport.height); + + // Allow to render only in the viewport + gl.glScissor((int) viewport.x, (int) viewport.y, (int) viewport.width, (int) viewport.height); + + // If this scene doesn't want alpha, clear it. + if (!this.alpha) { + gl.glDepthMask(true); + gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT); + } + } + public void renderOpaque() { final Rectangle viewport = this.camera.rect; this.viewer.gl.glViewport((int) viewport.x, (int) viewport.y, (int) viewport.width, (int) viewport.height); diff --git a/core/src/com/etheller/warsmash/viewer5/SkeletalNode.java b/core/src/com/etheller/warsmash/viewer5/SkeletalNode.java index bac3e60..6d971de 100644 --- a/core/src/com/etheller/warsmash/viewer5/SkeletalNode.java +++ b/core/src/com/etheller/warsmash/viewer5/SkeletalNode.java @@ -85,9 +85,9 @@ public abstract class SkeletalNode extends GenericNode { computedScaling = this.localScale; final Vector3 parentScale = this.parent.worldScale; - this.worldScale.x = parentScale.x * this.worldScale.x; - this.worldScale.y = parentScale.y * this.worldScale.y; - this.worldScale.z = parentScale.z * this.worldScale.z; + this.worldScale.x = parentScale.x * this.localScale.x; + this.worldScale.y = parentScale.y * this.localScale.y; + this.worldScale.z = parentScale.z * this.localScale.z; } if (this.billboarded) { diff --git a/core/src/com/etheller/warsmash/viewer5/Texture.java b/core/src/com/etheller/warsmash/viewer5/Texture.java index 4bf8677..fc75b47 100644 --- a/core/src/com/etheller/warsmash/viewer5/Texture.java +++ b/core/src/com/etheller/warsmash/viewer5/Texture.java @@ -19,6 +19,8 @@ public abstract class Texture extends HandlerResource { public abstract int getGlTarget(); + public abstract int getGlHandle(); + public abstract void setWrapS(final boolean wrapS); public abstract void setWrapT(final boolean wrapT); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ParticleEmitter2Object.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ParticleEmitter2Object.java index af7936b..b03de6b 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ParticleEmitter2Object.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ParticleEmitter2Object.java @@ -53,7 +53,7 @@ public class ParticleEmitter2Object extends GenericObject implements EmitterObje this.columns = emitter.getColumns(); this.rows = emitter.getRows(); - if (this.replaceableId == 0) { + if (replaceableId == 0) { this.internalTexture = model.getTextures().get(emitter.getTextureId()); } else if ((replaceableId == 1) || (replaceableId == 2)) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Doodad.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Doodad.java index 69f8d73..e6a0bd4 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Doodad.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Doodad.java @@ -1,29 +1,29 @@ package com.etheller.warsmash.viewer5.handlers.w3x; import com.badlogic.gdx.math.Quaternion; -import com.etheller.warsmash.util.MappedDataRow; +import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; import com.etheller.warsmash.util.RenderMathUtils; -import com.etheller.warsmash.viewer5.BatchedInstance; +import com.etheller.warsmash.viewer5.ModelInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; public class Doodad { - private final BatchedInstance instance; - private final MappedDataRow row; + private final ModelInstance instance; + private final MutableGameObject row; - public Doodad(final War3MapViewer map, final MdxModel model, final MappedDataRow row, + public Doodad(final War3MapViewer map, final MdxModel model, final MutableGameObject row, final com.etheller.warsmash.parsers.w3x.doo.Doodad doodad) { - final boolean isSimple = ((Number) row.get("lightweight")).intValue() == 1; - BatchedInstance instance; + final boolean isSimple = row.readSLKTagBoolean("lightweight"); + ModelInstance instance; if (isSimple) { - instance = (BatchedInstance) model.addInstance(1); + instance = model.addInstance(1); } else { - instance = (BatchedInstance) model.addInstance(); + instance = model.addInstance(); } instance.move(doodad.getLocation()); - instance.rotateLocal(new Quaternion().setFromAxis(RenderMathUtils.VEC3_UNIT_Z, doodad.getAngle())); + instance.rotate(new Quaternion().setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, doodad.getAngle())); instance.scale(doodad.getScale()); instance.setScene(map.worldScene); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/HiveWEShaders.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/HiveWEShaders.java new file mode 100644 index 0000000..9ab109f --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/HiveWEShaders.java @@ -0,0 +1,45 @@ +package com.etheller.warsmash.viewer5.handlers.w3x; + +public class HiveWEShaders { + public static final class Cliffs { + private Cliffs() { + } + + public static final String vert = "#version 450 core\r\n" + // + "\r\n" + // + "layout (location = 0) in vec3 vPosition;\r\n" + // + "layout (location = 1) in vec2 vUV;\r\n" + // + "layout (location = 2) in vec3 vNormal;\r\n" + // + "layout (location = 3) in vec4 vOffset;\r\n" + // + "\r\n" + // + "layout (location = 0) uniform mat4 MVP;\r\n" + // + "\r\n" + // + "layout (binding = 1) uniform sampler2D height_texture;\r\n" + // + "\r\n" + // + "layout (location = 0) out vec3 UV;\r\n" + // + "layout (location = 1) out vec3 Normal;\r\n" + // + "layout (location = 2) out vec2 pathing_map_uv;\r\n" + // + "\r\n" + // + "void main() {\r\n" + // + " pathing_map_uv = (vec2(vPosition.x + 128, vPosition.y) / 128 + vOffset.xy) * 4;\r\n" + // + " \r\n" + // + " ivec2 size = textureSize(height_texture, 0);\r\n" + // + " float value = texture(height_texture, (vOffset.xy + vec2(vPosition.x + 192, vPosition.y + 64) / 128) / vec2(size)).r;\r\n" + + // + "\r\n" + // + " gl_Position = MVP * vec4(vPosition + vec3(vOffset.xy + vec2(1, 0), vOffset.z + value) * 128, 1);\r\n" + + // + " UV = vec3(vUV, vOffset.a);\r\n" + // + "\r\n" + // + " ivec2 height_pos = ivec2(vOffset.xy + vec2(vPosition.x + 128, vPosition.y) / 128);\r\n" + // + " ivec3 off = ivec3(1, 1, 0);\r\n" + // + " float hL = texelFetch(height_texture, height_pos - off.xz, 0).r;\r\n" + // + " float hR = texelFetch(height_texture, height_pos + off.xz, 0).r;\r\n" + // + " float hD = texelFetch(height_texture, height_pos - off.zy, 0).r;\r\n" + // + " float hU = texelFetch(height_texture, height_pos + off.zy, 0).r;\r\n" + // + " vec3 terrain_normal = normalize(vec3(hL - hR, hD - hU, 2.0));\r\n" + // + "\r\n" + // + " Normal = terrain_normal;\r\n" + // + "}"; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/StandSequence.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/StandSequence.java index d0b077b..e6033d4 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/StandSequence.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/StandSequence.java @@ -47,6 +47,9 @@ public class StandSequence { final int sequencesLeft = filtered.size() - i; final int random = (int) (i + Math.floor(Math.random() * sequencesLeft)); + if (sequencesLeft <= 0) { + return new IndexedSequence(null, 0); + } final IndexedSequence sequence = filtered.get(random); return sequence; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/TerrainDoodad.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/TerrainDoodad.java index 0c99622..6c753b7 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/TerrainDoodad.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/TerrainDoodad.java @@ -1,7 +1,7 @@ package com.etheller.warsmash.viewer5.handlers.w3x; import com.badlogic.gdx.math.Quaternion; -import com.etheller.warsmash.util.MappedDataRow; +import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; import com.etheller.warsmash.util.RenderMathUtils; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; import com.etheller.warsmash.viewer5.handlers.mdx.MdxSimpleInstance; @@ -9,9 +9,9 @@ import com.etheller.warsmash.viewer5.handlers.mdx.MdxSimpleInstance; public class TerrainDoodad { private static final float[] locationHeap = new float[3]; private final MdxSimpleInstance instance; - private final MappedDataRow row; + private final MutableGameObject row; - public TerrainDoodad(final War3MapViewer map, final MdxModel model, final MappedDataRow row, + public TerrainDoodad(final War3MapViewer map, final MdxModel model, final MutableGameObject row, final com.etheller.warsmash.parsers.w3x.doo.TerrainDoodad doodad) { final float[] centerOffset = map.centerOffset; final MdxSimpleInstance instance = (MdxSimpleInstance) model.addInstance(1); @@ -20,8 +20,7 @@ public class TerrainDoodad { locationHeap[0] = (doodad.getLocation()[1] * 128) + centerOffset[1] + 128; instance.move(locationHeap); - instance.rotateLocal( - new Quaternion().setFromAxis(RenderMathUtils.VEC3_UNIT_Z, ((Number) row.get("fixedRot")).floatValue())); + instance.rotate(new Quaternion().setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, row.readSLKTagFloat("fixedRot"))); instance.setScene(map.worldScene); this.instance = instance; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/TerrainModel.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/TerrainModel.java index 564c7df..5259602 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/TerrainModel.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/TerrainModel.java @@ -19,6 +19,37 @@ import com.etheller.warsmash.util.RenderMathUtils; import com.etheller.warsmash.viewer5.gl.ANGLEInstancedArrays; import com.etheller.warsmash.viewer5.gl.WebGL; +/* + * + * +PuffTheMagicDragonIsNoMoreToday at 9:06 PM +that being said I think we call the tiles corners or whatever, since we store the points rather than the quads +but at the same time there's also per-quad data xDS + +ReteraToday at 9:06 PM +yeah, I've seen the corner class go by several times while transcribing this latest bit to java +hmmm + +PuffTheMagicDragonIsNoMoreToday at 9:07 PM +some things are per-corner, some per-tile +note that the existing code only somewhat takes care of cliff/terrain doodads, and it's not tested much + +ReteraToday at 9:10 PM +well, I'll probably rip some stuff off of HiveWE too +that was what I was thinking I'd probably do if it was necessary + +PuffTheMagicDragonIsNoMoreToday at 9:11 PM +last time I checked it wasn't implemented there, but that was a long time ago +basically you want to not have ground tiles where the doodads exist, and to know where that is you need the pathing texture used by the doodads + +ReteraToday at 9:12 PM +oh yea +makes sense + +PuffTheMagicDragonIsNoMoreToday at 9:13 PM +they also can't be supported by my hacky TerrainModel or whatever it was called, since at least some of them require blending + * + */ public class TerrainModel { private static final IntBuffer GL_TEMP_BUFFER = ByteBuffer.allocateDirect(4).order(ByteOrder.nativeOrder()) .asIntBuffer(); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Unit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Unit.java index 525560c..f7a9145 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Unit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Unit.java @@ -1,7 +1,7 @@ package com.etheller.warsmash.viewer5.handlers.w3x; import com.badlogic.gdx.math.Quaternion; -import com.etheller.warsmash.util.MappedDataRow; +import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; import com.etheller.warsmash.util.RenderMathUtils; import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; @@ -9,25 +9,34 @@ import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; public class Unit { private static final float[] heapZ = new float[3]; public final MdxComplexInstance instance; - public final MappedDataRow row; + public final MutableGameObject row; - public Unit(final War3MapViewer map, final MdxModel model, final MappedDataRow row, + public Unit(final War3MapViewer map, final MdxModel model, final MutableGameObject row, final com.etheller.warsmash.parsers.w3x.unitsdoo.Unit unit) { final MdxComplexInstance instance = (MdxComplexInstance) model.addInstance(); instance.move(unit.getLocation()); - instance.rotateLocal(new Quaternion().setFromAxis(RenderMathUtils.VEC3_UNIT_Z, unit.getAngle())); + float angle; + if ((row != null) && row.readSLKTagBoolean("isBldg")) { + angle = (float) Math.toRadians(270.0f); + } + else { + angle = unit.getAngle(); + } +// instance.localRotation.setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, angle); + instance.rotate(new Quaternion().setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, angle)); instance.scale(unit.getScale()); instance.setTeamColor(unit.getPlayer()); instance.setScene(map.worldScene); if (row != null) { - heapZ[0] = (((Number) row.get("moveHeight")).floatValue()); + heapZ[2] = row.readSLKTagFloat("moveHeight"); instance.move(heapZ); - instance.setVertexColor(new float[] { ((Number) row.get("red")).intValue() / 255f, - ((Number) row.get("green")).intValue() / 255f, ((Number) row.get("blue")).intValue() / 255f }); - instance.uniformScale(((Number) row.get("modelScale")).floatValue()); + instance.setVertexColor(new float[] { (row.readSLKTagInt("red")) / 255f, + (row.readSLKTagInt("green")) / 255f, (row.readSLKTagInt("blue")) / 255f }); + instance.uniformScale(row.readSLKTagFloat("modelScale")); + } this.instance = instance; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Variations.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Variations.java new file mode 100644 index 0000000..d22f348 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Variations.java @@ -0,0 +1,154 @@ +package com.etheller.warsmash.viewer5.handlers.w3x; + +import java.util.HashMap; +import java.util.Map; + +public class Variations { + private static final Map CLIFF_VARS; + private static final Map CITY_CLIFF_VARS; + + static { + final Map cliffVariations = new HashMap<>(); + cliffVariations.put("AAAB", 1); + cliffVariations.put("AAAC", 1); + cliffVariations.put("AABA", 1); + cliffVariations.put("AABB", 2); + cliffVariations.put("AABC", 0); + cliffVariations.put("AACA", 1); + cliffVariations.put("AACB", 0); + cliffVariations.put("AACC", 1); + cliffVariations.put("ABAA", 1); + cliffVariations.put("ABAB", 1); + cliffVariations.put("ABAC", 0); + cliffVariations.put("ABBA", 2); + cliffVariations.put("ABBB", 1); + cliffVariations.put("ABBC", 0); + cliffVariations.put("ABCA", 0); + cliffVariations.put("ABCB", 0); + cliffVariations.put("ABCC", 0); + cliffVariations.put("ACAA", 1); + cliffVariations.put("ACAB", 0); + cliffVariations.put("ACAC", 1); + cliffVariations.put("ACBA", 0); + cliffVariations.put("ACBB", 0); + cliffVariations.put("ACBC", 0); + cliffVariations.put("ACCA", 1); + cliffVariations.put("ACCB", 0); + cliffVariations.put("ACCC", 1); + cliffVariations.put("BAAA", 1); + cliffVariations.put("BAAB", 1); + cliffVariations.put("BAAC", 0); + cliffVariations.put("BABA", 1); + cliffVariations.put("BABB", 1); + cliffVariations.put("BABC", 0); + cliffVariations.put("BACA", 0); + cliffVariations.put("BACB", 0); + cliffVariations.put("BACC", 0); + cliffVariations.put("BBAA", 1); + cliffVariations.put("BBAB", 1); + cliffVariations.put("BBAC", 0); + cliffVariations.put("BBBA", 1); + cliffVariations.put("BBCA", 0); + cliffVariations.put("BCAA", 0); + cliffVariations.put("BCAB", 0); + cliffVariations.put("BCAC", 0); + cliffVariations.put("BCBA", 0); + cliffVariations.put("BCCA", 0); + cliffVariations.put("CAAA", 1); + cliffVariations.put("CAAB", 0); + cliffVariations.put("CAAC", 1); + cliffVariations.put("CABA", 0); + cliffVariations.put("CABB", 0); + cliffVariations.put("CABC", 0); + cliffVariations.put("CACA", 1); + cliffVariations.put("CACB", 0); + cliffVariations.put("CACC", 1); + cliffVariations.put("CBAA", 0); + cliffVariations.put("CBAB", 0); + cliffVariations.put("CBAC", 0); + cliffVariations.put("CBBA", 0); + cliffVariations.put("CBCA", 0); + cliffVariations.put("CCAA", 1); + cliffVariations.put("CCAB", 0); + cliffVariations.put("CCAC", 1); + cliffVariations.put("CCBA", 0); + cliffVariations.put("CCCA", 1); + CLIFF_VARS = cliffVariations; + + final Map cityCliffVariations = new HashMap<>(); + cityCliffVariations.put("AAAB", 2); + cityCliffVariations.put("AAAC", 1); + cityCliffVariations.put("AABA", 1); + cityCliffVariations.put("AABB", 3); + cityCliffVariations.put("AABC", 0); + cityCliffVariations.put("AACA", 1); + cityCliffVariations.put("AACB", 0); + cityCliffVariations.put("AACC", 3); + cityCliffVariations.put("ABAA", 1); + cityCliffVariations.put("ABAB", 2); + cityCliffVariations.put("ABAC", 0); + cityCliffVariations.put("ABBA", 3); + cityCliffVariations.put("ABBB", 0); + cityCliffVariations.put("ABBC", 0); + cityCliffVariations.put("ABCA", 0); + cityCliffVariations.put("ABCB", 0); + cityCliffVariations.put("ABCC", 0); + cityCliffVariations.put("ACAA", 1); + cityCliffVariations.put("ACAB", 0); + cityCliffVariations.put("ACAC", 2); + cityCliffVariations.put("ACBA", 0); + cityCliffVariations.put("ACBB", 0); + cityCliffVariations.put("ACBC", 0); + cityCliffVariations.put("ACCA", 3); + cityCliffVariations.put("ACCB", 0); + cityCliffVariations.put("ACCC", 1); + cityCliffVariations.put("BAAA", 1); + cityCliffVariations.put("BAAB", 3); + cityCliffVariations.put("BAAC", 0); + cityCliffVariations.put("BABA", 2); + cityCliffVariations.put("BABB", 0); + cityCliffVariations.put("BABC", 0); + cityCliffVariations.put("BACA", 0); + cityCliffVariations.put("BACB", 0); + cityCliffVariations.put("BACC", 0); + cityCliffVariations.put("BBAA", 3); + cityCliffVariations.put("BBAB", 1); + cityCliffVariations.put("BBAC", 0); + cityCliffVariations.put("BBBA", 1); + cityCliffVariations.put("BBCA", 0); + cityCliffVariations.put("BCAA", 0); + cityCliffVariations.put("BCAB", 0); + cityCliffVariations.put("BCAC", 0); + cityCliffVariations.put("BCBA", 0); + cityCliffVariations.put("BCCA", 0); + cityCliffVariations.put("CAAA", 1); + cityCliffVariations.put("CAAB", 0); + cityCliffVariations.put("CAAC", 3); + cityCliffVariations.put("CABA", 0); + cityCliffVariations.put("CABB", 0); + cityCliffVariations.put("CABC", 0); + cityCliffVariations.put("CACA", 2); + cityCliffVariations.put("CACB", 0); + cityCliffVariations.put("CACC", 1); + cityCliffVariations.put("CBAA", 0); + cityCliffVariations.put("CBAB", 0); + cityCliffVariations.put("CBAC", 0); + cityCliffVariations.put("CBBA", 0); + cityCliffVariations.put("CBCA", 0); + cityCliffVariations.put("CCAA", 3); + cityCliffVariations.put("CCAB", 0); + cityCliffVariations.put("CCAC", 1); + cityCliffVariations.put("CCBA", 0); + cityCliffVariations.put("CCCA", 1); + CITY_CLIFF_VARS = cityCliffVariations; + } + + public static int getCliffVariation(final String dir, final String tag, final int variation) { + if ("Cliffs".equals(dir)) { + return Math.min(variation, CLIFF_VARS.get(tag)); + } + else { + return Math.min(variation, CITY_CLIFF_VARS.get(tag)); + } + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xShaders.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xShaders.java index dd94da7..b02e267 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xShaders.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xShaders.java @@ -11,7 +11,7 @@ public class W3xShaders { "uniform vec2 u_pixel;\r\n" + // "uniform vec2 u_centerOffset;\r\n" + // "attribute vec3 a_position;\r\n" + // - "attribute vec3 a_normal;\r\n" + // +// "attribute vec3 a_normal;\r\n" + // "attribute vec2 a_uv;\r\n" + // "attribute vec3 a_instancePosition;\r\n" + // "attribute float a_instanceTexture;\r\n" + // @@ -25,16 +25,16 @@ public class W3xShaders { " // The bottom left corner of the map tile this vertex is on.\r\n" + // " vec2 corner = floor((a_instancePosition.xy - vec2(1.0, 0.0) - u_centerOffset.xy) / 128.0);\r\n" + // " // Get the 4 closest heights in the height map.\r\n" + // - " float bottomLeft = texture2D(u_heightMap, corner * u_pixel + halfPixel).a;\r\n" + // - " float bottomRight = texture2D(u_heightMap, (corner + vec2(1.0, 0.0)) * u_pixel + halfPixel).a;\r\n" + // - " float topLeft = texture2D(u_heightMap, (corner + vec2(0.0, 1.0)) * u_pixel + halfPixel).a;\r\n" + // - " float topRight = texture2D(u_heightMap, (corner + vec2(1.0, 1.0)) * u_pixel + halfPixel).a;\r\n" + // + " float bottomLeft = texture2D(u_heightMap, corner * u_pixel + halfPixel).r;\r\n" + // + " float bottomRight = texture2D(u_heightMap, (corner + vec2(1.0, 0.0)) * u_pixel + halfPixel).r;\r\n" + // + " float topLeft = texture2D(u_heightMap, (corner + vec2(0.0, 1.0)) * u_pixel + halfPixel).r;\r\n" + // + " float topRight = texture2D(u_heightMap, (corner + vec2(1.0, 1.0)) * u_pixel + halfPixel).r;\r\n" + // " \r\n" + // " // Do a bilinear interpolation between the heights to get the final value.\r\n" + // " float bottom = mix(bottomRight, bottomLeft, -a_position.x / 128.0);\r\n" + // " float top = mix(topRight, topLeft, -a_position.x / 128.0);\r\n" + // " float height = mix(bottom, top, a_position.y / 128.0);\r\n" + // - " v_normal = a_normal;\r\n" + // +// " v_normal = a_normal;\r\n" + // " v_uv = a_uv;\r\n" + // " v_texture = a_instanceTexture;\r\n" + // " v_position = a_position + vec3(a_instancePosition.xy, a_instancePosition.z + height * 128.0);\r\n" + // @@ -47,7 +47,7 @@ public class W3xShaders { "precision mediump float;\r\n" + // "uniform sampler2D u_texture1;\r\n" + // "uniform sampler2D u_texture2;\r\n" + // - "varying vec3 v_normal;\r\n" + // +// "varying vec3 v_normal;\r\n" + // "varying vec2 v_uv;\r\n" + // "varying float v_texture;\r\n" + // "varying vec3 v_position;\r\n" + // @@ -81,35 +81,35 @@ public class W3xShaders { "varying vec3 v_normal;\r\n" + // "const vec3 lightDirection = normalize(vec3(-0.3, -0.3, 0.25));\r\n" + // "vec4 sample(float tileset, vec2 uv) {\r\n" + // - " if (tileset == 0.0) {\r\n" + // + " if (tileset <= 0.5) {\r\n" + // " return texture2D(u_tilesets[0], uv);\r\n" + // - " } else if (tileset == 1.0) {\r\n" + // + " } else if (tileset <= 1.5) {\r\n" + // " return texture2D(u_tilesets[1], uv);\r\n" + // - " } else if (tileset == 2.0) {\r\n" + // + " } else if (tileset <= 2.5) {\r\n" + // " return texture2D(u_tilesets[2], uv);\r\n" + // - " } else if (tileset == 3.0) {\r\n" + // + " } else if (tileset <= 3.5) {\r\n" + // " return texture2D(u_tilesets[3], uv);\r\n" + // - " } else if (tileset == 4.0) {\r\n" + // + " } else if (tileset <= 4.5) {\r\n" + // " return texture2D(u_tilesets[4], uv);\r\n" + // - " } else if (tileset == 5.0) {\r\n" + // + " } else if (tileset <= 5.5) {\r\n" + // " return texture2D(u_tilesets[5], uv);\r\n" + // - " } else if (tileset == 6.0) {\r\n" + // + " } else if (tileset <= 6.5) {\r\n" + // " return texture2D(u_tilesets[6], uv);\r\n" + // - " } else if (tileset == 7.0) {\r\n" + // + " } else if (tileset <= 7.5) {\r\n" + // " return texture2D(u_tilesets[7], uv);\r\n" + // - " } else if (tileset == 8.0) {\r\n" + // + " } else if (tileset <= 8.5) {\r\n" + // " return texture2D(u_tilesets[8], uv);\r\n" + // - " } else if (tileset == 9.0) {\r\n" + // + " } else if (tileset <= 9.5) {\r\n" + // " return texture2D(u_tilesets[9], uv);\r\n" + // - " } else if (tileset == 10.0) {\r\n" + // + " } else if (tileset <= 10.5) {\r\n" + // " return texture2D(u_tilesets[10], uv);\r\n" + // - " } else if (tileset == 11.0) {\r\n" + // + " } else if (tileset <= 11.5) {\r\n" + // " return texture2D(u_tilesets[11], uv);\r\n" + // - " } else if (tileset == 12.0) {\r\n" + // + " } else if (tileset <= 12.5) {\r\n" + // " return texture2D(u_tilesets[12], uv);\r\n" + // - " } else if (tileset == 13.0) {\r\n" + // + " } else if (tileset <= 13.5) {\r\n" + // " return texture2D(u_tilesets[13], uv);\r\n" + // - " } else if (tileset == 14.0) {\r\n" + // + " } else if (tileset <= 14.5) {\r\n" + // " return texture2D(u_tilesets[14], uv);\r\n" + // " }\r\n" + // "}\r\n" + // @@ -130,45 +130,66 @@ public class W3xShaders { " }\r\n" + // " // color *= clamp(dot(v_normal, lightDirection) + 0.45, 0.0, 1.0);\r\n" + // " gl_FragColor = color;\r\n" + // - "}\r\n" + // - ""; + "}"; public static final String vert = "\r\n" + // "uniform mat4 u_VP;\r\n" + // "uniform sampler2D u_heightMap;\r\n" + // - "uniform vec2 u_pixel;\r\n" + // - "uniform vec2 u_centerOffset;\r\n" + // - "attribute vec3 a_position;\r\n" + // - "attribute vec3 a_normal;\r\n" + // - "attribute vec2 a_uv;\r\n" + // - "attribute vec3 a_instancePosition;\r\n" + // - "attribute float a_instanceTexture;\r\n" + // + "uniform vec2 u_size;\r\n" + // + "uniform vec2 u_offset;\r\n" + // + "uniform bool u_extended[14];\r\n" + // + "uniform float u_baseTileset;\r\n" + // + "attribute vec2 a_position;\r\n" + // + "attribute float a_InstanceID;\r\n" + // + "attribute vec4 a_textures;\r\n" + // + "attribute vec4 a_variations;\r\n" + // + "varying vec4 v_tilesets;\r\n" + // + "varying vec2 v_uv[4];\r\n" + // "varying vec3 v_normal;\r\n" + // - "varying vec2 v_uv;\r\n" + // - "varying float v_texture;\r\n" + // - "varying vec3 v_position;\r\n" + // - "void main() {\r\n" + // - " // Half of a pixel in the cliff height map.\r\n" + // - " vec2 halfPixel = u_pixel * 0.5;\r\n" + // - " // The bottom left corner of the map tile this vertex is on.\r\n" + // - " vec2 corner = floor((a_instancePosition.xy - vec2(1.0, 0.0) - u_centerOffset.xy) / 128.0);\r\n" + // - " // Get the 4 closest heights in the height map.\r\n" + // - " float bottomLeft = texture2D(u_heightMap, corner * u_pixel + halfPixel).a;\r\n" + // - " float bottomRight = texture2D(u_heightMap, (corner + vec2(1.0, 0.0)) * u_pixel + halfPixel).a;\r\n" + // - " float topLeft = texture2D(u_heightMap, (corner + vec2(0.0, 1.0)) * u_pixel + halfPixel).a;\r\n" + // - " float topRight = texture2D(u_heightMap, (corner + vec2(1.0, 1.0)) * u_pixel + halfPixel).a;\r\n" + // - " \r\n" + // - " // Do a bilinear interpolation between the heights to get the final value.\r\n" + // - " float bottom = mix(bottomRight, bottomLeft, -a_position.x / 128.0);\r\n" + // - " float top = mix(topRight, topLeft, -a_position.x / 128.0);\r\n" + // - " float height = mix(bottom, top, a_position.y / 128.0);\r\n" + // - " v_normal = a_normal;\r\n" + // - " v_uv = a_uv;\r\n" + // - " v_texture = a_instanceTexture;\r\n" + // - " v_position = a_position + vec3(a_instancePosition.xy, a_instancePosition.z + height * 128.0);\r\n" + // - " gl_Position = u_VP * vec4(v_position, 1.0);\r\n" + // + "vec2 getCell(float variation) {\r\n" + // + " if (variation < 16.0) {\r\n" + // + " return vec2(mod(variation, 4.0), floor(variation / 4.0));\r\n" + // + " } else {\r\n" + // + " variation -= 16.0;\r\n" + // + " return vec2(4.0 + mod(variation, 4.0), floor(variation / 4.0));\r\n" + // + " }\r\n" + // "}\r\n" + // - ""; + "vec2 getUV(vec2 position, bool extended, float variation) {\r\n" + // + " vec2 cell = getCell(variation);\r\n" + // + " vec2 cellSize = vec2(extended ? 0.125 : 0.25, 0.25);\r\n" + // + " vec2 uv = vec2(position.x, 1.0 - position.y);\r\n" + // + " vec2 pixelSize = vec2(1.0 / 512.0, 1.0 / 256.0); /// Note: hardcoded to 512x256 for now.\r\n" + // + " return clamp((cell + uv) * cellSize, cell * cellSize + pixelSize, (cell + 1.0) * cellSize - pixelSize); \r\n" + + // + "}\r\n" + // + "void main() {\r\n" + // + " vec4 textures = a_textures - u_baseTileset;\r\n" + // + " \r\n" + // + " if (textures[0] > 0.0 || textures[1] > 0.0 || textures[2] > 0.0 || textures[3] > 0.0) {\r\n" + // + " v_tilesets = textures;\r\n" + // + " v_uv[0] = getUV(a_position, u_extended[int(textures[0]) - 1], a_variations[0]);\r\n" + // + " v_uv[1] = getUV(a_position, u_extended[int(textures[1]) - 1], a_variations[1]);\r\n" + // + " v_uv[2] = getUV(a_position, u_extended[int(textures[2]) - 1], a_variations[2]);\r\n" + // + " v_uv[3] = getUV(a_position, u_extended[int(textures[3]) - 1], a_variations[3]);\r\n" + // + " vec2 corner = vec2(mod(a_InstanceID, u_size.x), floor(a_InstanceID / u_size.x));\r\n" + // + " vec2 base = corner + a_position;\r\n" + // + " float height = texture2D(u_heightMap, base / u_size).r;\r\n" + // + " float hL = texture2D(u_heightMap, vec2(base - vec2(1.0, 0.0)) / (u_size)).r;\r\n" + // + " float hR = texture2D(u_heightMap, vec2(base + vec2(1.0, 0.0)) / (u_size)).r;\r\n" + // + " float hD = texture2D(u_heightMap, vec2(base - vec2(0.0, 1.0)) / (u_size)).r;\r\n" + // + " float hU = texture2D(u_heightMap, vec2(base + vec2(0.0, 1.0)) / (u_size)).r;\r\n" + // + " v_normal = normalize(vec3(hL - hR, hD - hU, 2.0));\r\n" + // + " gl_Position = u_VP * vec4(base * 128.0 + u_offset, height * 128.0, 1.0);\r\n" + // + " } else {\r\n" + // + " v_tilesets = vec4(0.0);\r\n" + // + " v_uv[0] = vec2(0.0);\r\n" + // + " v_uv[1] = vec2(0.0);\r\n" + // + " v_uv[2] = vec2(0.0);\r\n" + // + " v_uv[3] = vec2(0.0);\r\n" + // + " v_normal = vec3(0.0);\r\n" + // + " gl_Position = vec4(0.0);\r\n" + // + " }\r\n" + // + "}"; } public static final class Water { @@ -208,8 +229,8 @@ public class W3xShaders { " v_uv = a_position;\r\n" + // " vec2 corner = vec2(mod(a_InstanceID, u_size.x), floor(a_InstanceID / u_size.x));\r\n" + // " vec2 base = corner + a_position;\r\n" + // - " float height = texture2D(u_heightMap, base / u_size).a;\r\n" + // - " float waterHeight = texture2D(u_waterHeightMap, base / u_size).a + u_offsetHeight;\r\n" + // + " float height = texture2D(u_heightMap, base / u_size).r;\r\n" + // + " float waterHeight = texture2D(u_waterHeightMap, base / u_size).r + u_offsetHeight;\r\n" + // " float value = clamp(waterHeight - height, 0.0, 1.0);\r\n" + // " if (value <= deepLevel) {\r\n" + // " value = max(0.0, value - minDepth) / (deepLevel - minDepth);\r\n" + // diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xShadersWebGLDeprecated.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xShadersWebGLDeprecated.java new file mode 100644 index 0000000..1562e36 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xShadersWebGLDeprecated.java @@ -0,0 +1,230 @@ +package com.etheller.warsmash.viewer5.handlers.w3x; + +public class W3xShadersWebGLDeprecated { + public static final class Cliffs { + private Cliffs() { + } + + public static final String vert = "\r\n" + // + "uniform mat4 u_VP;\r\n" + // + "uniform sampler2D u_heightMap;\r\n" + // + "uniform vec2 u_pixel;\r\n" + // + "uniform vec2 u_centerOffset;\r\n" + // + "attribute vec3 a_position;\r\n" + // + "attribute vec3 a_normal;\r\n" + // + "attribute vec2 a_uv;\r\n" + // + "attribute vec3 a_instancePosition;\r\n" + // + "attribute float a_instanceTexture;\r\n" + // + "varying vec3 v_normal;\r\n" + // + "varying vec2 v_uv;\r\n" + // + "varying float v_texture;\r\n" + // + "varying vec3 v_position;\r\n" + // + "void main() {\r\n" + // + " // Half of a pixel in the cliff height map.\r\n" + // + " vec2 halfPixel = u_pixel * 0.5;\r\n" + // + " // The bottom left corner of the map tile this vertex is on.\r\n" + // + " vec2 corner = floor((a_instancePosition.xy - vec2(1.0, 0.0) - u_centerOffset.xy) / 128.0);\r\n" + // + " // Get the 4 closest heights in the height map.\r\n" + // + " float bottomLeft = texture2D(u_heightMap, corner * u_pixel + halfPixel).a;\r\n" + // + " float bottomRight = texture2D(u_heightMap, (corner + vec2(1.0, 0.0)) * u_pixel + halfPixel).a;\r\n" + // + " float topLeft = texture2D(u_heightMap, (corner + vec2(0.0, 1.0)) * u_pixel + halfPixel).a;\r\n" + // + " float topRight = texture2D(u_heightMap, (corner + vec2(1.0, 1.0)) * u_pixel + halfPixel).a;\r\n" + // + " \r\n" + // + " // Do a bilinear interpolation between the heights to get the final value.\r\n" + // + " float bottom = mix(bottomRight, bottomLeft, -a_position.x / 128.0);\r\n" + // + " float top = mix(topRight, topLeft, -a_position.x / 128.0);\r\n" + // + " float height = mix(bottom, top, a_position.y / 128.0);\r\n" + // + " v_normal = a_normal;\r\n" + // + " v_uv = a_uv;\r\n" + // + " v_texture = a_instanceTexture;\r\n" + // + " v_position = a_position + vec3(a_instancePosition.xy, a_instancePosition.z + height * 128.0);\r\n" + // + " gl_Position = u_VP * vec4(v_position, 1.0);\r\n" + // + "}\r\n" + // + ""; + + public static final String frag = "\r\n" + // + "// #extension GL_OES_standard_derivatives : enable\r\n" + // + "precision mediump float;\r\n" + // + "uniform sampler2D u_texture1;\r\n" + // + "uniform sampler2D u_texture2;\r\n" + // + "varying vec3 v_normal;\r\n" + // + "varying vec2 v_uv;\r\n" + // + "varying float v_texture;\r\n" + // + "varying vec3 v_position;\r\n" + // + "// const vec3 lightDirection = normalize(vec3(-0.3, -0.3, 0.25));\r\n" + // + "vec4 sample(int texture, vec2 uv) {\r\n" + // + " if (texture == 0) {\r\n" + // + " return texture2D(u_texture1, uv);\r\n" + // + " } else {\r\n" + // + " return texture2D(u_texture2, uv);\r\n" + // + " }\r\n" + // + "}\r\n" + // + "void main() {\r\n" + // + " vec4 color = sample(int(v_texture), v_uv);\r\n" + // + " // vec3 faceNormal = cross(dFdx(v_position), dFdy(v_position));\r\n" + // + " // vec3 normal = normalize((faceNormal + v_normal) * 0.5);\r\n" + // + " // color *= clamp(dot(normal, lightDirection) + 0.45, 0.1, 1.0);\r\n" + // + " gl_FragColor = color;\r\n" + // + "}\r\n" + // + ""; + } + + public static final class Ground { + private Ground() { + } + + public static final String frag = "\r\n" + // + "precision mediump float;\r\n" + // + "uniform sampler2D u_tilesets[15];\r\n" + // + "varying vec4 v_tilesets;\r\n" + // + "varying vec2 v_uv[4];\r\n" + // + "varying vec3 v_normal;\r\n" + // + "const vec3 lightDirection = normalize(vec3(-0.3, -0.3, 0.25));\r\n" + // + "vec4 sample(float tileset, vec2 uv) {\r\n" + // + " if (tileset == 0.0) {\r\n" + // + " return texture2D(u_tilesets[0], uv);\r\n" + // + " } else if (tileset == 1.0) {\r\n" + // + " return texture2D(u_tilesets[1], uv);\r\n" + // + " } else if (tileset == 2.0) {\r\n" + // + " return texture2D(u_tilesets[2], uv);\r\n" + // + " } else if (tileset == 3.0) {\r\n" + // + " return texture2D(u_tilesets[3], uv);\r\n" + // + " } else if (tileset == 4.0) {\r\n" + // + " return texture2D(u_tilesets[4], uv);\r\n" + // + " } else if (tileset == 5.0) {\r\n" + // + " return texture2D(u_tilesets[5], uv);\r\n" + // + " } else if (tileset == 6.0) {\r\n" + // + " return texture2D(u_tilesets[6], uv);\r\n" + // + " } else if (tileset == 7.0) {\r\n" + // + " return texture2D(u_tilesets[7], uv);\r\n" + // + " } else if (tileset == 8.0) {\r\n" + // + " return texture2D(u_tilesets[8], uv);\r\n" + // + " } else if (tileset == 9.0) {\r\n" + // + " return texture2D(u_tilesets[9], uv);\r\n" + // + " } else if (tileset == 10.0) {\r\n" + // + " return texture2D(u_tilesets[10], uv);\r\n" + // + " } else if (tileset == 11.0) {\r\n" + // + " return texture2D(u_tilesets[11], uv);\r\n" + // + " } else if (tileset == 12.0) {\r\n" + // + " return texture2D(u_tilesets[12], uv);\r\n" + // + " } else if (tileset == 13.0) {\r\n" + // + " return texture2D(u_tilesets[13], uv);\r\n" + // + " } else if (tileset == 14.0) {\r\n" + // + " return texture2D(u_tilesets[14], uv);\r\n" + // + " }\r\n" + // + "}\r\n" + // + "vec4 blend(vec4 color, float tileset, vec2 uv) {\r\n" + // + " vec4 texel = sample(tileset, uv);\r\n" + // + " return mix(color, texel, texel.a);\r\n" + // + "}\r\n" + // + "void main() {\r\n" + // + " vec4 color = sample(v_tilesets[0] - 1.0, v_uv[0]);\r\n" + // + " if (v_tilesets[1] > 0.5) {\r\n" + // + " color = blend(color, v_tilesets[1] - 1.0, v_uv[1]);\r\n" + // + " }\r\n" + // + " if (v_tilesets[2] > 0.5) {\r\n" + // + " color = blend(color, v_tilesets[2] - 1.0, v_uv[2]);\r\n" + // + " }\r\n" + // + " if (v_tilesets[3] > 0.5) {\r\n" + // + " color = blend(color, v_tilesets[3] - 1.0, v_uv[3]);\r\n" + // + " }\r\n" + // + " // color *= clamp(dot(v_normal, lightDirection) + 0.45, 0.0, 1.0);\r\n" + // + " gl_FragColor = color;\r\n" + // + "}\r\n" + // + ""; + + public static final String vert = "\r\n" + // + "uniform mat4 u_VP;\r\n" + // + "uniform sampler2D u_heightMap;\r\n" + // + "uniform vec2 u_pixel;\r\n" + // + "uniform vec2 u_centerOffset;\r\n" + // + "attribute vec3 a_position;\r\n" + // + "attribute vec3 a_normal;\r\n" + // + "attribute vec2 a_uv;\r\n" + // + "attribute vec3 a_instancePosition;\r\n" + // + "attribute float a_instanceTexture;\r\n" + // + "varying vec3 v_normal;\r\n" + // + "varying vec2 v_uv;\r\n" + // + "varying float v_texture;\r\n" + // + "varying vec3 v_position;\r\n" + // + "void main() {\r\n" + // + " // Half of a pixel in the cliff height map.\r\n" + // + " vec2 halfPixel = u_pixel * 0.5;\r\n" + // + " // The bottom left corner of the map tile this vertex is on.\r\n" + // + " vec2 corner = floor((a_instancePosition.xy - vec2(1.0, 0.0) - u_centerOffset.xy) / 128.0);\r\n" + // + " // Get the 4 closest heights in the height map.\r\n" + // + " float bottomLeft = texture2D(u_heightMap, corner * u_pixel + halfPixel).a;\r\n" + // + " float bottomRight = texture2D(u_heightMap, (corner + vec2(1.0, 0.0)) * u_pixel + halfPixel).a;\r\n" + // + " float topLeft = texture2D(u_heightMap, (corner + vec2(0.0, 1.0)) * u_pixel + halfPixel).a;\r\n" + // + " float topRight = texture2D(u_heightMap, (corner + vec2(1.0, 1.0)) * u_pixel + halfPixel).a;\r\n" + // + " \r\n" + // + " // Do a bilinear interpolation between the heights to get the final value.\r\n" + // + " float bottom = mix(bottomRight, bottomLeft, -a_position.x / 128.0);\r\n" + // + " float top = mix(topRight, topLeft, -a_position.x / 128.0);\r\n" + // + " float height = mix(bottom, top, a_position.y / 128.0);\r\n" + // + " v_normal = a_normal;\r\n" + // + " v_uv = a_uv;\r\n" + // + " v_texture = a_instanceTexture;\r\n" + // + " v_position = a_position + vec3(a_instancePosition.xy, a_instancePosition.z + height * 128.0);\r\n" + // + " gl_Position = u_VP * vec4(v_position, 1.0);\r\n" + // + "}\r\n" + // + ""; + } + + public static final class Water { + private Water() { + } + + public static final String frag = "\r\n" + // + "precision mediump float;\r\n" + // + "uniform sampler2D u_waterTexture;\r\n" + // + "varying vec2 v_uv;\r\n" + // + "varying vec4 v_color;\r\n" + // + "void main() {\r\n" + // + " gl_FragColor = texture2D(u_waterTexture, v_uv) * v_color;\r\n" + // + "}\r\n" + // + ""; + public static final String vert = "\r\n" + // + "uniform mat4 u_VP;\r\n" + // + "uniform sampler2D u_heightMap;\r\n" + // + "uniform sampler2D u_waterHeightMap;\r\n" + // + "uniform vec2 u_size;\r\n" + // + "uniform vec2 u_offset;\r\n" + // + "uniform float u_offsetHeight;\r\n" + // + "uniform vec4 u_minDeepColor;\r\n" + // + "uniform vec4 u_maxDeepColor;\r\n" + // + "uniform vec4 u_minShallowColor;\r\n" + // + "uniform vec4 u_maxShallowColor;\r\n" + // + "attribute vec2 a_position;\r\n" + // + "attribute float a_InstanceID;\r\n" + // + "attribute float a_isWater;\r\n" + // + "varying vec2 v_uv;\r\n" + // + "varying vec4 v_color;\r\n" + // + "const float minDepth = 10.0 / 128.0;\r\n" + // + "const float deepLevel = 64.0 / 128.0;\r\n" + // + "const float maxDepth = 72.0 / 128.0;\r\n" + // + "void main() {\r\n" + // + " if (a_isWater > 0.5) {\r\n" + // + " v_uv = a_position;\r\n" + // + " vec2 corner = vec2(mod(a_InstanceID, u_size.x), floor(a_InstanceID / u_size.x));\r\n" + // + " vec2 base = corner + a_position;\r\n" + // + " float height = texture2D(u_heightMap, base / u_size).a;\r\n" + // + " float waterHeight = texture2D(u_waterHeightMap, base / u_size).a + u_offsetHeight;\r\n" + // + " float value = clamp(waterHeight - height, 0.0, 1.0);\r\n" + // + " if (value <= deepLevel) {\r\n" + // + " value = max(0.0, value - minDepth) / (deepLevel - minDepth);\r\n" + // + " v_color = mix(u_minShallowColor, u_maxShallowColor, value) / 255.0;\r\n" + // + " } else {\r\n" + // + " value = clamp(value - deepLevel, 0.0, maxDepth - deepLevel) / (maxDepth - deepLevel);\r\n" + // + " v_color = mix(u_minDeepColor, u_maxDeepColor, value) / 255.0;\r\n" + // + " }\r\n" + // + " gl_Position = u_VP * vec4(base * 128.0 + u_offset, waterHeight * 128.0, 1.0);\r\n" + // + " } else {\r\n" + // + " v_uv = vec2(0.0);\r\n" + // + " v_color = vec4(0.0);\r\n" + // + " gl_Position = vec4(0.0);\r\n" + // + " }\r\n" + // + "}\r\n" + // + ""; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index 3ee27ba..03e914f 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -13,6 +13,7 @@ import java.util.List; import java.util.Map; import com.badlogic.gdx.graphics.GL20; +import com.badlogic.gdx.graphics.GL30; import com.badlogic.gdx.graphics.glutils.ShaderProgram; import com.badlogic.gdx.math.Vector3; import com.etheller.warsmash.common.FetchDataTypeName; @@ -21,12 +22,16 @@ import com.etheller.warsmash.datasources.DataSource; import com.etheller.warsmash.parsers.w3x.War3Map; import com.etheller.warsmash.parsers.w3x.doo.War3MapDoo; import com.etheller.warsmash.parsers.w3x.objectdata.Warcraft3MapObjectData; +import com.etheller.warsmash.parsers.w3x.unitsdoo.War3MapUnitsDoo; import com.etheller.warsmash.parsers.w3x.w3e.Corner; import com.etheller.warsmash.parsers.w3x.w3e.War3MapW3e; import com.etheller.warsmash.parsers.w3x.w3i.War3MapW3i; import com.etheller.warsmash.units.DataTable; import com.etheller.warsmash.units.Element; import com.etheller.warsmash.units.StandardObjectData; +import com.etheller.warsmash.units.manager.MutableObjectData; +import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; +import com.etheller.warsmash.units.manager.MutableObjectData.WorldEditorDataType; import com.etheller.warsmash.util.MappedData; import com.etheller.warsmash.util.MappedDataRow; import com.etheller.warsmash.util.RenderMathUtils; @@ -34,28 +39,34 @@ import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.CanvasProvider; import com.etheller.warsmash.viewer5.GenericResource; import com.etheller.warsmash.viewer5.Grid; +import com.etheller.warsmash.viewer5.ModelInstance; import com.etheller.warsmash.viewer5.ModelViewer; import com.etheller.warsmash.viewer5.PathSolver; import com.etheller.warsmash.viewer5.Scene; import com.etheller.warsmash.viewer5.Texture; +import com.etheller.warsmash.viewer5.gl.ANGLEInstancedArrays; import com.etheller.warsmash.viewer5.gl.WebGL; +import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxHandler; +import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; public class War3MapViewer extends ModelViewer { + private static final float[] sizeHeap = new float[2]; + private static final War3ID sloc = War3ID.fromString("sloc"); private static final LoadGenericCallback stringDataCallback = new StringDataCallbackImplementation(); private static final StreamDataCallbackImplementation streamDataCallback = new StreamDataCallbackImplementation(); private static final Vector3 normalHeap1 = new Vector3(); private static final Vector3 normalHeap2 = new Vector3(); - public PathSolver wc3PathSolver; + public PathSolver wc3PathSolver = PathSolver.DEFAULT; public SolverParams solverParams = new SolverParams(); public ShaderProgram groundShader; public ShaderProgram waterShader; public ShaderProgram cliffShader; public Scene worldScene; - public int waterIndex; - public int waterIncreasePerFrame; + public float waterIndex; + public float waterIncreasePerFrame; public float waterHeightOffset; public List waterTextures = new ArrayList<>(); public float[] maxDeepColor = new float[4]; @@ -85,7 +96,7 @@ public class War3MapViewer extends ModelViewer { public List cliffTextures = new ArrayList<>(); public List cliffModels = new ArrayList<>(); public War3Map mapMpq; - public PathSolver mapPathSolver; + public PathSolver mapPathSolver = PathSolver.DEFAULT; public Corner[][] corners; public float[] centerOffset = new float[2]; public int[] mapSize = new int[2]; @@ -126,11 +137,11 @@ public class War3MapViewer extends ModelViewer { } public void loadSLKs() { - final GenericResource terrain = this.loadMapGeneric("Terrain\\Terrain.slk", FetchDataTypeName.SLK, + final GenericResource terrain = this.loadMapGeneric("TerrainArt\\Terrain.slk", FetchDataTypeName.SLK, stringDataCallback); - final GenericResource cliffTypes = this.loadMapGeneric("Terrain\\CliffTypes.slk", FetchDataTypeName.SLK, + final GenericResource cliffTypes = this.loadMapGeneric("TerrainArt\\CliffTypes.slk", FetchDataTypeName.SLK, stringDataCallback); - final GenericResource water = this.loadMapGeneric("Terrain\\Water.slk", FetchDataTypeName.SLK, + final GenericResource water = this.loadMapGeneric("TerrainArt\\Water.slk", FetchDataTypeName.SLK, stringDataCallback); // == when loaded, which is always in our system == @@ -190,6 +201,9 @@ public class War3MapViewer extends ModelViewer { final War3Map war3Map = new War3Map(this.gameDataSource, mapFilePath); this.mapMpq = war3Map; + setDataSource(war3Map.getCompoundDataSource()); + +// loadSLKs(); final PathSolver wc3PathSolver = this.wc3PathSolver; @@ -245,8 +259,9 @@ public class War3MapViewer extends ModelViewer { final MappedDataRow row = this.terrainData.getRow(groundTile.asStringValue()); this.tilesets.add(row); - this.tilesetTextures.add((Texture) this - .load(row.get("dir").toString() + "\\" + row.get("file") + texturesExt, null, this.solverParams)); + this.tilesetTextures + .add((Texture) this.load(row.get("dir").toString() + "\\" + row.get("file") + texturesExt, + this.mapPathSolver, this.solverParams)); } final StandardObjectData standardObjectData = new StandardObjectData(this.mapMpq.getCompoundDataSource()); @@ -254,8 +269,9 @@ public class War3MapViewer extends ModelViewer { final Element tilesets = worldEditData.get("TileSets"); this.blightTextureIndex = this.tilesetTextures.size(); - this.tilesetTextures.add((Texture) this.load(tilesets.getField(Character.toString(tileset)) + texturesExt, null, - this.solverParams)); + this.tilesetTextures + .add((Texture) this.load(tilesets.getField(Character.toString(tileset)).split(",")[1] + texturesExt, + this.mapPathSolver, this.solverParams)); for (final War3ID cliffTile : w3e.getCliffTiles()) { final MappedDataRow row = this.cliffTypesData.getRow(cliffTile.asStringValue()); @@ -263,13 +279,13 @@ public class War3MapViewer extends ModelViewer { this.cliffTilesets.add(row); this.cliffTextures .add((Texture) this.load(row.get("texDir").toString() + "\\" + row.get("texFile") + texturesExt, - null, this.solverParams)); + this.mapPathSolver, this.solverParams)); } final MappedDataRow waterRow = this.waterData.getRow(tileset + "Sha"); this.waterHeightOffset = ((Number) waterRow.get("height")).floatValue(); - this.waterIncreasePerFrame = ((Number) waterRow.get("texRate")).intValue() / 60; + this.waterIncreasePerFrame = ((Number) waterRow.get("texRate")).intValue() / (float) 60; this.waterTextures.clear(); this.maxDeepColor[0] = ((Number) waterRow.get("Dmax_R")).floatValue(); this.maxDeepColor[1] = ((Number) waterRow.get("Dmax_G")).floatValue(); @@ -291,7 +307,7 @@ public class War3MapViewer extends ModelViewer { for (int i = 0, l = ((Number) waterRow.get("numTex")).intValue(); i < l; i++) { this.waterTextures.add( (Texture) this.load(waterRow.get("texFile").toString() + ((i < 10) ? "0" : "") + i + texturesExt, - null, this.solverParams)); + this.mapPathSolver, this.solverParams)); } final GL20 gl = this.gl; @@ -307,7 +323,7 @@ public class War3MapViewer extends ModelViewer { final short[] cornerTextures = new short[instanceCount * 4]; final short[] cornerVariations = new short[instanceCount * 4]; final short[] waterFlags = new short[instanceCount]; - final int instance = 0; + int instance = 0; final Map cliffs = new HashMap<>(); this.columns = columns - 1; @@ -339,7 +355,7 @@ public class War3MapViewer extends ModelViewer { topRightLayer, base); if (!"AAAA".equals(fileName)) { - final int cliffTexture = bottomLeft.getCliffTexture(); + int cliffTexture = bottomLeft.getCliffTexture(); // ? if (cliffTexture == 15) { @@ -349,7 +365,8 @@ public class War3MapViewer extends ModelViewer { final MappedDataRow cliffRow = this.cliffTilesets.get(cliffTexture); final String dir = cliffRow.get("cliffModelDir").toString(); final String path = "Doodads\\Terrain\\" + dir + "\\" + dir + fileName - + bottomLeft.getCliffVariation() + ".mdx"; + + Variations.getCliffVariation(dir, fileName, bottomLeft.getCliffVariation()) + + ".mdx"; if (!cliffs.containsKey(path)) { cliffs.put(path, new CliffInfo()); @@ -423,19 +440,19 @@ public class War3MapViewer extends ModelViewer { this.cliffHeightMap = gl.glGenTexture(); gl.glBindTexture(GL20.GL_TEXTURE_2D, this.cliffHeightMap); this.webGL.setTextureMode(GL20.GL_CLAMP_TO_EDGE, GL20.GL_CLAMP_TO_EDGE, GL20.GL_NEAREST, GL20.GL_NEAREST); - gl.glTexImage2D(GL20.GL_TEXTURE_2D, 0, GL20.GL_ALPHA, columns, rows, 0, GL20.GL_ALPHA, GL20.GL_FLOAT, + gl.glTexImage2D(GL20.GL_TEXTURE_2D, 0, GL30.GL_R32F, columns, rows, 0, GL30.GL_RED, GL20.GL_FLOAT, RenderMathUtils.wrap(cliffHeights)); this.heightMap = gl.glGenTexture(); gl.glBindTexture(GL20.GL_TEXTURE_2D, this.heightMap); this.webGL.setTextureMode(GL20.GL_CLAMP_TO_EDGE, GL20.GL_CLAMP_TO_EDGE, GL20.GL_NEAREST, GL20.GL_NEAREST); - gl.glTexImage2D(GL20.GL_TEXTURE_2D, 0, GL20.GL_ALPHA, columns, rows, 0, GL20.GL_ALPHA, GL20.GL_FLOAT, + gl.glTexImage2D(GL20.GL_TEXTURE_2D, 0, GL30.GL_R32F, columns, rows, 0, GL30.GL_RED, GL20.GL_FLOAT, RenderMathUtils.wrap(cornerHeights)); this.waterHeightMap = gl.glGenTexture(); gl.glBindTexture(GL20.GL_TEXTURE_2D, this.waterHeightMap); this.webGL.setTextureMode(GL20.GL_CLAMP_TO_EDGE, GL20.GL_CLAMP_TO_EDGE, GL20.GL_NEAREST, GL20.GL_NEAREST); - gl.glTexImage2D(GL20.GL_TEXTURE_2D, 0, GL20.GL_ALPHA, columns, rows, 0, GL20.GL_ALPHA, GL20.GL_FLOAT, + gl.glTexImage2D(GL20.GL_TEXTURE_2D, 0, GL30.GL_R32F, columns, rows, 0, GL30.GL_RED, GL20.GL_FLOAT, RenderMathUtils.wrap(waterHeights)); this.instanceBuffer = gl.glGenBuffer(); @@ -483,13 +500,481 @@ public class War3MapViewer extends ModelViewer { private void loadDoodadsAndDestructibles(final Warcraft3MapObjectData modifications) throws IOException { final War3MapDoo dooFile = this.mapMpq.readDoodads(); + this.applyModificationFile(this.doodadsData, this.doodadMetaData, modifications.getDoodads(), + WorldEditorDataType.DOODADS); + this.applyModificationFile(this.doodadsData, this.destructableMetaData, modifications.getDestructibles(), + WorldEditorDataType.DESTRUCTIBLES); + + final War3MapDoo doo = this.mapMpq.readDoodads(); + + for (final com.etheller.warsmash.parsers.w3x.doo.Doodad doodad : doo.getDoodads()) { + + MutableGameObject row = modifications.getDoodads().get(doodad.getId()); + if (row == null) { + row = modifications.getDestructibles().get(doodad.getId()); + } + String file = row.readSLKTag("file"); + final int numVar = row.readSLKTagInt("numVar"); + + if (file.endsWith(".mdx")) { + file = file.substring(0, file.length() - 4); + } + + String fileVar = file; + + file += ".mdx"; + + if (numVar > 1) { + fileVar += Math.min(doodad.getVariation(), numVar - 1); + } + + fileVar += ".mdx"; + + // First see if the model is local. + // Doodads referring to local models may have invalid variations, so if the + // variation doesn't exist, try without a variation. + + String path; + if (this.mapMpq.has(fileVar)) { + path = fileVar; + } + else { + path = file; + } + MdxModel model; + if (this.mapMpq.has(path)) { + model = (MdxModel) this.load(path, this.mapPathSolver, this.solverParams); + } + else { + model = (MdxModel) this.load(fileVar, this.mapPathSolver, this.solverParams); + } + + this.doodads.add(new Doodad(this, model, row, doodad)); + } + + // Cliff/Terrain doodads. + for (final com.etheller.warsmash.parsers.w3x.doo.TerrainDoodad doodad : doo.getTerrainDoodads()) { + final MutableGameObject row = modifications.getDoodads().get(doodad.getId()); + String file = row.readSLKTag("file"); + if (file.toLowerCase().endsWith(".mdl")) { + file = file.substring(0, file.length() - 4); + } + if (!file.toLowerCase().endsWith(".mdx")) { + file += ".mdx"; + } + final MdxModel model = (MdxModel) this.load(file, this.mapPathSolver, this.solverParams); + + this.terrainDoodads.add(new TerrainDoodad(this, model, row, doodad)); + } + + this.doodadsReady = true; + this.anyReady = true; } - private void loadUnitsAndItems(final Warcraft3MapObjectData modifications) { - // TODO Auto-generated method stub + private void applyModificationFile(final MappedData doodadsData2, final MappedData doodadMetaData2, + final MutableObjectData destructibles, final WorldEditorDataType dataType) { + // TODO condense ported MappedData from Ghostwolf and MutableObjectData from + // Retera } + private void loadUnitsAndItems(final Warcraft3MapObjectData modifications) throws IOException { + final War3Map mpq = this.mapMpq; + + final War3MapUnitsDoo dooFile = mpq.readUnits(); + + // Collect the units and items data. + for (final com.etheller.warsmash.parsers.w3x.unitsdoo.Unit unit : dooFile.getUnits()) { + MutableGameObject row = null; + String path = null; + + // Hardcoded? + if (sloc.equals(unit.getId())) { + path = "Objects\\StartLocation\\StartLocation.mdx"; + } + else { + row = modifications.getUnits().get(unit.getId()); + if (row == null) { + row = modifications.getItems().get(unit.getId()); + } + + if (row != null) { + path = row.readSLKTag("file"); + + if (path.toLowerCase().endsWith(".mdl") || path.toLowerCase().endsWith(".mdx")) { + path = path.substring(0, path.length() - 4); + } + if (row.readSLKTagInt("fileVerFlags") == 2) { + path += "_V1"; + } + + path += ".mdx"; + } + } + + if (path != null) { + final MdxModel model = (MdxModel) this.load(path, this.mapPathSolver, this.solverParams); + + this.units.add(new Unit(this, model, row, unit)); + } + else { + System.err.println("Unknown unit ID: " + unit.getId()); + } + } + + this.unitsReady = true; + this.anyReady = true; + } + + @Override + public void update() { + if (this.anyReady) { + this.waterIndex += this.waterIncreasePerFrame; + + if (this.waterIndex >= this.waterTextures.size()) { + this.waterIndex = 0; + } + + super.update(); + + final List instances = this.worldScene.instances; + + for (final ModelInstance instance : instances) { + if (instance instanceof MdxComplexInstance) { + final MdxComplexInstance mdxComplexInstance = (MdxComplexInstance) instance; + if (mdxComplexInstance.sequenceEnded || (mdxComplexInstance.sequence == -1)) { + StandSequence.randomStandSequence(mdxComplexInstance); + } + } + } + } + } + + @Override + public void render() { + if (this.anyReady) { + final Scene worldScene = this.worldScene; + + worldScene.startFrame(); + this.renderGround(); + this.renderCliffs(); + worldScene.renderOpaque(); + this.renderWater(); + worldScene.renderTranslucent(); + } + } + + public void renderGround() { + if (this.terrainReady) { + final GL20 gl = this.gl; + final WebGL webgl = this.webGL; + final ANGLEInstancedArrays instancedArrays = webgl.instancedArrays; + final ShaderProgram shader = this.groundShader; + final List tilesetTextures = this.tilesetTextures; + final int instanceAttrib = shader.getAttributeLocation("a_InstanceID"); + final int positionAttrib = shader.getAttributeLocation("a_position"); + final int texturesAttrib = shader.getAttributeLocation("a_textures"); + final int variationsAttrib = shader.getAttributeLocation("a_variations"); + final int tilesetCount = tilesetTextures.size(); + + gl.glEnable(GL20.GL_BLEND); + gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA); + + webgl.useShaderProgram(shader); + + shader.setUniformMatrix("u_VP", this.worldScene.camera.viewProjectionMatrix); + shader.setUniform2fv("u_offset", this.centerOffset, 0, 2); + sizeHeap[0] = this.columns; + sizeHeap[1] = this.rows; + shader.setUniform2fv("u_size", sizeHeap, 0, 2); + shader.setUniformi("u_heightMap", 15); + + gl.glActiveTexture(GL20.GL_TEXTURE15); + gl.glBindTexture(GL20.GL_TEXTURE_2D, this.heightMap); + + gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.vertexBuffer); + shader.setVertexAttribute(positionAttrib, 2, GL20.GL_FLOAT, false, 8, 0); + + gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.instanceBuffer); + shader.setVertexAttribute(instanceAttrib, 1, GL20.GL_FLOAT, false, 4, 0); + instancedArrays.glVertexAttribDivisorANGLE(instanceAttrib, 1); + + gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.textureBuffer); + shader.setVertexAttribute(texturesAttrib, 4, GL20.GL_UNSIGNED_BYTE, false, 4, 0); + instancedArrays.glVertexAttribDivisorANGLE(texturesAttrib, 1); + + gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.variationBuffer); + shader.setVertexAttribute(variationsAttrib, 4, GL20.GL_UNSIGNED_BYTE, false, 4, 0); + instancedArrays.glVertexAttribDivisorANGLE(variationsAttrib, 1); + + gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, this.faceBuffer); + + shader.setUniformi("u_baseTileset", 0); + + for (int i = 0, l = Math.min(tilesetCount, 15); i < l; i++) { + final int isExtended = (tilesetTextures.get(i).getWidth() > tilesetTextures.get(i).getHeight()) ? 1 : 0; + + shader.setUniformf("u_extended[" + i + "]", isExtended); + shader.setUniformi("u_tilesets[" + i + "]", i); + + tilesetTextures.get(i).bind(i); + } + + instancedArrays.glDrawElementsInstancedANGLE(GL20.GL_TRIANGLES, 6, GL20.GL_UNSIGNED_BYTE, 0, + this.rows * this.columns); + + if (tilesetCount > 15) { + shader.setUniformi("u_baseTileset", 15); + + for (int i = 0, l = tilesetCount - 15; i < l; i++) { + final int isExtended = (tilesetTextures.get(i + 15).getWidth() > tilesetTextures.get(i + 15) + .getHeight()) ? 1 : 0; + + shader.setUniformf("u_extended[" + i + "]", isExtended); + + tilesetTextures.get(i + 15).bind(i); + } + + instancedArrays.glDrawElementsInstancedANGLE(GL20.GL_TRIANGLES, 6, GL20.GL_UNSIGNED_BYTE, 0, + this.rows * this.columns); + } + + instancedArrays.glVertexAttribDivisorANGLE(texturesAttrib, 0); + instancedArrays.glVertexAttribDivisorANGLE(variationsAttrib, 0); + instancedArrays.glVertexAttribDivisorANGLE(instanceAttrib, 0); + } + } + + public void renderWater() { + if (this.terrainReady) { + final GL20 gl = this.gl; + final WebGL webgl = this.webGL; + final ANGLEInstancedArrays instancedArrays = webgl.instancedArrays; + final ShaderProgram shader = this.waterShader; + final int instanceAttrib = shader.getAttributeLocation("a_InstanceID"); + final int positionAttrib = shader.getAttributeLocation("a_position"); + final int isWaterAttrib = shader.getAttributeLocation("a_isWater"); + + gl.glDepthMask(false); + + gl.glEnable(GL20.GL_BLEND); + gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA); + + webgl.useShaderProgram(shader); + + shader.setUniformMatrix("u_VP", this.worldScene.camera.viewProjectionMatrix); + shader.setUniform2fv("u_offset", this.centerOffset, 0, 2); + sizeHeap[0] = this.columns; + sizeHeap[1] = this.rows; + shader.setUniform2fv("u_size", sizeHeap, 0, 2); + shader.setUniformi("u_heightMap", 0); + shader.setUniformi("u_waterHeightMap", 1); + shader.setUniformi("u_waterTexture", 2); + shader.setUniformf("u_offsetHeight", this.waterHeightOffset); + shader.setUniform4fv("u_maxDeepColor", this.maxDeepColor, 0, 4); + shader.setUniform4fv("u_minDeepColor", this.minDeepColor, 0, 4); + shader.setUniform4fv("u_maxShallowColor", this.maxShallowColor, 0, 4); + shader.setUniform4fv("u_minShallowColor", this.minShallowColor, 0, 4); + + gl.glActiveTexture(GL20.GL_TEXTURE0); + gl.glBindTexture(GL20.GL_TEXTURE_2D, this.heightMap); + + gl.glActiveTexture(GL20.GL_TEXTURE1); + gl.glBindTexture(GL20.GL_TEXTURE_2D, this.waterHeightMap); + + this.waterTextures.get((int) this.waterIndex).bind(2); + + gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.vertexBuffer); + shader.setVertexAttribute(positionAttrib, 2, GL20.GL_FLOAT, false, 8, 0); + + gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.instanceBuffer); + shader.setVertexAttribute(instanceAttrib, 1, GL20.GL_FLOAT, false, 4, 0); + instancedArrays.glVertexAttribDivisorANGLE(instanceAttrib, 1); + + gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.waterBuffer); + shader.setVertexAttribute(isWaterAttrib, 1, GL20.GL_UNSIGNED_BYTE, false, 1, 0); + instancedArrays.glVertexAttribDivisorANGLE(isWaterAttrib, 1); + + gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, this.faceBuffer); + instancedArrays.glDrawElementsInstancedANGLE(GL20.GL_TRIANGLES, 6, GL20.GL_UNSIGNED_BYTE, 0, + this.rows * this.columns); + + instancedArrays.glVertexAttribDivisorANGLE(isWaterAttrib, 0); + instancedArrays.glVertexAttribDivisorANGLE(instanceAttrib, 0); + } + } + + public void renderCliffs() { + if (this.cliffsReady) { + final GL20 gl = this.gl; + final WebGL webGL = this.webGL; + final ANGLEInstancedArrays instancedArrays = webGL.instancedArrays; + final ShaderProgram shader = this.cliffShader; + + gl.glDisable(GL20.GL_BLEND); + + webGL.useShaderProgram(shader); + + shader.setUniformMatrix("u_VP", this.worldScene.camera.viewProjectionMatrix); + shader.setUniformi("u_heightMap", 0); + shader.setUniformf("u_pixel[0]", 1 / (float) (this.columns + 1)); + shader.setUniformf("u_pixel[1]", 1 / (float) (this.rows + 1)); + shader.setUniform2fv("u_centerOffset", this.centerOffset, 0, 2); + shader.setUniformi("u_texture1", 1); + shader.setUniformi("u_texture2", 2); + + gl.glActiveTexture(GL20.GL_TEXTURE0); + gl.glBindTexture(GL20.GL_TEXTURE_2D, this.cliffHeightMap); + + gl.glActiveTexture(GL20.GL_TEXTURE1); + gl.glBindTexture(GL20.GL_TEXTURE_2D, this.cliffTextures.get(0).getGlTarget()); + + if (this.cliffTextures.size() > 1) { + gl.glActiveTexture(GL20.GL_TEXTURE2); + gl.glBindTexture(GL20.GL_TEXTURE_2D, this.cliffTextures.get(1).getGlTarget()); + } + + // Set instanced attributes. + for (final TerrainModel cliff : this.cliffModels) { + cliff.render(shader); + } + } + } + + public String cliffFileName(final int bottomLeftLayer, final int bottomRightLayer, final int topLeftLayer, + final int topRightLayer, final int base) { + return Character.toString((char) ((65 + bottomLeftLayer) - base)) + + Character.toString((char) ((65 + topLeftLayer) - base)) + + Character.toString((char) ((65 + topRightLayer) - base)) + + Character.toString((char) ((65 + bottomRightLayer) - base)); + } + + public short getVariation(final int groundTexture, final int variation) { + final Texture texture = this.tilesetTextures.get(groundTexture); + + // Extended ? + if (texture.getWidth() > texture.getHeight()) { + if (variation < 16) { + return (short) (16 + variation); + } + else if (variation == 16) { + return 15; + } + else { + return 0; + } + } + else { + if (variation == 0) { + return 0; + } + else { + return 15; + } + } + } + + public boolean isCliff(final int column, final int row) { + if ((column < 1) || (column > (this.columns - 1)) || (row < 1) || (row > (this.rows - 1))) { + return false; + } + + final Corner[][] corners = this.corners; + final int bottomLeft = corners[row][column].getLayerHeight(); + final int bottomRight = corners[row][column + 1].getLayerHeight(); + final int topLeft = corners[row + 1][column].getLayerHeight(); + final int topRight = corners[row + 1][column + 1].getLayerHeight(); + + return (bottomLeft != bottomRight) || (bottomLeft != topLeft) || (bottomLeft != topRight); + } + + public short isWater(final int column, final int row) { + return ((this.corners[row][column].getWater() != 0) || (this.corners[row][column + 1].getWater() != 0) + || (this.corners[row + 1][column].getWater() != 0) + || (this.corners[row + 1][column + 1].getWater() != 0)) ? (short) 1 : (short) 0; + } + + public int cliffGroundIndex(final int whichCliff) { + final String whichTile = this.cliffTilesets.get(whichCliff).get("groundTile").toString(); + final List tilesets = this.tilesets; + + for (int i = 0, l = tilesets.size(); i < l; i++) { + if (tilesets.get(i).get("tileID").toString().equals(whichTile)) { + return i; + } + } + throw new IllegalArgumentException(Integer.toString(whichCliff)); + } + + public int cornerTexture(final int column, final int row) { + final Corner[][] corners = this.corners; + final int columns = this.columns; + final int rows = this.rows; + + for (int y = -1; y < 1; y++) { + for (int x = -1; x < 1; x++) { + if (((column + x) > 0) && ((column + x) < (columns - 1)) && ((row + y) > 0) + && ((row + y) < (rows - 1))) { + if (this.isCliff(column + x, row + y)) { + int texture = corners[row + y][column + x].getCliffTexture(); + + if (texture == 15) { + texture = 1; + } + + return this.cliffGroundIndex(texture); + } + } + } + } + + final Corner corner = corners[row][column]; + if (corner.getBlight() != 0) { + return this.blightTextureIndex; + } + return corner.getGroundTexture(); + } + + public Vector3 groundNormal(final Vector3 out, int x, int y) { + final float[] centerOffset = this.centerOffset; + final int[] mapSize = this.mapSize; + + x = (int) ((x - centerOffset[0]) / 128); + y = (int) ((y - centerOffset[1]) / 128); + + final int cellX = x; + final int cellY = y; + + // See if this coordinate is in the map + + if ((cellX >= 0) && (cellX < (mapSize[0] - 1)) && (cellY >= 0) && (cellY < (mapSize[1] - 1))) { + // See http://gamedev.stackexchange.com/a/24574 + final Corner[][] corners = this.corners; + final int bottomLeft = corners[cellY][cellX].getGroundHeight(); + final int bottomRight = corners[cellY][cellX + 1].getGroundHeight(); + final int topLeft = corners[cellY + 1][cellX].getGroundHeight(); + final int topRight = corners[cellY + 1][cellX + 1].getGroundHeight(); + final int sqX = x - cellX; + final int sqY = y - cellY; + + if ((sqX + sqY) < 1) { + normalHeap1.set(1, 0, bottomRight - bottomLeft); + normalHeap2.set(0, 1, topLeft - bottomLeft); + } + else { + normalHeap1.set(-1, 0, topRight - topLeft); + normalHeap2.set(0, 1, topRight - bottomRight); + } + + out.set(normalHeap1.crs(normalHeap2)).nor(); + } + else { + out.set(0, 0, 1); + } + + return out; + } + private static final class MappedDataCallbackImplementation implements LoadGenericCallback { @Override public Object call(final InputStream data) { @@ -514,6 +999,9 @@ public class War3MapViewer extends ModelViewer { private static final class StringDataCallbackImplementation implements LoadGenericCallback { @Override public Object call(final InputStream data) { + if (data == null) { + System.err.println("data null"); + } final StringBuilder stringBuilder = new StringBuilder(); try (BufferedReader reader = new BufferedReader(new InputStreamReader(data, "utf-8"))) { String line; @@ -549,4 +1037,20 @@ public class War3MapViewer extends ModelViewer { public List locations = new ArrayList<>(); public List textures = new ArrayList<>(); } + + private static final int MAXIMUM_ACCEPTED = 1 << 30; + + /** + * Returns a power of two size for the given target capacity. + */ + private static final int pow2GreaterThan(final int capacity) { + int numElements = capacity - 1; + numElements |= numElements >>> 1; + numElements |= numElements >>> 2; + numElements |= numElements >>> 4; + numElements |= numElements >>> 8; + numElements |= numElements >>> 16; + return (numElements < 0) ? 1 : (numElements >= MAXIMUM_ACCEPTED) ? MAXIMUM_ACCEPTED : numElements + 1; + } + } diff --git a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java index b07c2f9..84e654e 100644 --- a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java +++ b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java @@ -5,7 +5,7 @@ import org.lwjgl.opengl.GL33; import com.badlogic.gdx.backends.lwjgl.LwjglApplication; import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration; -import com.etheller.warsmash.WarsmashGdxGame; +import com.etheller.warsmash.WarsmashGdxMapGame; import com.etheller.warsmash.viewer5.gl.ANGLEInstancedArrays; import com.etheller.warsmash.viewer5.gl.Extensions; @@ -37,6 +37,6 @@ public class DesktopLauncher { // final DisplayMode desktopDisplayMode = LwjglApplicationConfiguration.getDesktopDisplayMode(); // config.width = desktopDisplayMode.width; // config.height = desktopDisplayMode.height; - new LwjglApplication(new WarsmashGdxGame(), config); + new LwjglApplication(new WarsmashGdxMapGame(), config); } } From bfab516832a0abce98f08f0ab0edbab446295909 Mon Sep 17 00:00:00 2001 From: Retera Date: Tue, 21 Jan 2020 20:55:38 -0600 Subject: [PATCH 014/116] Terrain as separate file --- .../etheller/warsmash/WarsmashGdxMapGame.java | 2 +- .../warsmash/parsers/w3x/w3e/Corner.java | 24 +- .../viewer5/handlers/w3x/HiveWEShaders.java | 253 +++++++ .../viewer5/handlers/w3x/TerrainDoodad.java | 2 +- .../viewer5/handlers/w3x/War3MapViewer.java | 631 +---------------- .../handlers/w3x/environment/Terrain.java | 650 ++++++++++++++++++ 6 files changed, 933 insertions(+), 629 deletions(-) create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java index a1afee6..93b47d2 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java @@ -184,7 +184,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv // } } - private final float cameraSpeed = 100.0f; + private final float cameraSpeed = 10.0f; private final Vector2 cameraVelocity = new Vector2(); @Override diff --git a/core/src/com/etheller/warsmash/parsers/w3x/w3e/Corner.java b/core/src/com/etheller/warsmash/parsers/w3x/w3e/Corner.java index ea65ac4..7bac45b 100644 --- a/core/src/com/etheller/warsmash/parsers/w3x/w3e/Corner.java +++ b/core/src/com/etheller/warsmash/parsers/w3x/w3e/Corner.java @@ -10,8 +10,8 @@ import com.google.common.io.LittleEndianDataOutputStream; * A tile corner. */ public class Corner { - private int groundHeight; - private int waterHeight; + private float groundHeight; + private float waterHeight; private int mapEdge; private int ramp; private int blight; @@ -24,10 +24,10 @@ public class Corner { private int layerHeight; public void load(final LittleEndianDataInputStream stream) throws IOException { - this.groundHeight = (stream.readShort() - 8192) / 512; + this.groundHeight = (stream.readShort() - 8192) / (float) 512; final short waterAndEdge = stream.readShort(); - this.waterHeight = ((waterAndEdge & 0x3FFF) - 8192) / 512; + this.waterHeight = ((waterAndEdge & 0x3FFF) - 8192) / (float) 512; this.mapEdge = waterAndEdge & 0x4000; final short textureAndFlags = ParseUtils.readUInt8(stream); @@ -52,19 +52,19 @@ public class Corner { } public void save(final LittleEndianDataOutputStream stream) throws IOException { - stream.writeShort((this.groundHeight * 512) + 8192); - stream.writeShort((this.waterHeight + 8192 + this.mapEdge) << 14); + stream.writeShort((short) ((this.groundHeight * 512f) + 8192f)); + stream.writeShort((short) ((this.waterHeight * 512f) + 8192f + (this.mapEdge << 14))); ParseUtils.writeUInt8(stream, (short) ((this.ramp << 4) | (this.blight << 5) | (this.water << 6) | (this.boundary << 7) | this.groundTexture)); ParseUtils.writeUInt8(stream, (short) ((this.cliffVariation << 5) | this.groundVariation)); ParseUtils.writeUInt8(stream, (short) ((this.cliffTexture << 4) + this.layerHeight)); } - public int getGroundHeight() { + public float getGroundHeight() { return this.groundHeight; } - public int getWaterHeight() { + public float getWaterHeight() { return this.waterHeight; } @@ -107,4 +107,12 @@ public class Corner { public int getLayerHeight() { return this.layerHeight; } + + public float computeFinalGroundHeight() { + return (this.groundHeight + this.layerHeight) - 2.0f; + } + + public float computeFinalWaterHeight(final float waterOffset) { + return this.waterHeight + waterOffset; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/HiveWEShaders.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/HiveWEShaders.java index 9ab109f..46310b3 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/HiveWEShaders.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/HiveWEShaders.java @@ -41,5 +41,258 @@ public class HiveWEShaders { "\r\n" + // " Normal = terrain_normal;\r\n" + // "}"; + + public static final String frag = "#version 450 core\r\n" + // + "\r\n" + // + "layout (binding = 0) uniform sampler2DArray cliff_textures;\r\n" + // + "layout (binding = 2) uniform usampler2D pathing_map_static;\r\n" + // + "\r\n" + // + "layout (location = 1) uniform bool show_pathing_map_static;\r\n" + // + "layout (location = 2) uniform bool show_lighting;\r\n" + // + "\r\n" + // + "layout (location = 0) in vec3 UV;\r\n" + // + "layout (location = 1) in vec3 Normal;\r\n" + // + "layout (location = 2) in vec2 pathing_map_uv;\r\n" + // + "\r\n" + // + "out vec4 color;\r\n" + // + "\r\n" + // + "void main() {\r\n" + // + " color = texture(cliff_textures, UV);\r\n" + // + "\r\n" + // + " if (show_lighting) {\r\n" + // + " vec3 light_direction = vec3(-0.3, -0.3, 0.25);\r\n" + // + " light_direction = normalize(light_direction);\r\n" + // + "\r\n" + // + " color.rgb *= clamp(dot(Normal, light_direction) + 0.45, 0, 1);\r\n" + // + " }\r\n" + // + "\r\n" + // + " uvec4 byte = texelFetch(pathing_map_static, ivec2(pathing_map_uv), 0);\r\n" + // + " if (show_pathing_map_static) {\r\n" + // + " vec4 pathing_color = vec4(min(byte.r & 2, 1), min(byte.r & 4, 1), min(byte.r & 8, 1), 0.25);\r\n" + + // + " color = length(pathing_color.rgb) > 0 ? color * 0.75 + pathing_color * 0.5 : color;\r\n" + // + " }\r\n" + // + "}"; + } + + public static final class Terrain { + private Terrain() { + } + + public static final String vert = "#version 450 core\r\n" + // + "\r\n" + // + "layout (location = 0) in vec2 vPosition;\r\n" + // + "layout (location = 1) uniform mat4 MVP;\r\n" + // + "\r\n" + // + "layout (binding = 0) uniform sampler2D height_texture;\r\n" + // + "layout (binding = 1) uniform sampler2D height_cliff_texture;\r\n" + // + "layout (binding = 2) uniform usampler2D terrain_texture_list;\r\n" + // + "\r\n" + // + "layout (location = 0) out vec2 UV;\r\n" + // + "layout (location = 1) out flat uvec4 texture_indices;\r\n" + // + "layout (location = 2) out vec2 pathing_map_uv;\r\n" + // + "layout (location = 3) out vec3 normal;\r\n" + // + "\r\n" + // + "void main() { \r\n" + // + " ivec2 size = textureSize(terrain_texture_list, 0);\r\n" + // + " ivec2 pos = ivec2(gl_InstanceID % size.x, gl_InstanceID / size.x);\r\n" + // + "\r\n" + // + " ivec2 height_pos = ivec2(vPosition + pos);\r\n" + // + " vec4 height = texelFetch(height_cliff_texture, height_pos, 0);\r\n" + // + "\r\n" + // + " ivec3 off = ivec3(1, 1, 0);\r\n" + // + " float hL = texelFetch(height_texture, height_pos - off.xz, 0).r;\r\n" + // + " float hR = texelFetch(height_texture, height_pos + off.xz, 0).r;\r\n" + // + " float hD = texelFetch(height_texture, height_pos - off.zy, 0).r;\r\n" + // + " float hU = texelFetch(height_texture, height_pos + off.zy, 0).r;\r\n" + // + " normal = normalize(vec3(hL - hR, hD - hU, 2.0));\r\n" + // + "\r\n" + // + " UV = vec2(vPosition.x, 1 - vPosition.y);\r\n" + // + " texture_indices = texelFetch(terrain_texture_list, pos, 0);\r\n" + // + " pathing_map_uv = (vPosition + pos) * 4; \r\n" + // + "\r\n" + // + " // Cliff culling\r\n" + // + " gl_Position = ((texture_indices.a & 32768) == 0) ? MVP * vec4(vPosition + pos, height.r, 1) : vec4(2.0, 0.0, 0.0, 1.0);\r\n" + + // + "}"; + + public static final String frag = "#version 450 core\r\n" + // + "\r\n" + // + "layout (location = 2) uniform bool show_pathing_map;\r\n" + // + "layout (location = 3) uniform bool show_lighting;\r\n" + // + "\r\n" + // + "layout (binding = 3) uniform sampler2DArray sample0;\r\n" + // + "layout (binding = 4) uniform sampler2DArray sample1;\r\n" + // + "layout (binding = 5) uniform sampler2DArray sample2;\r\n" + // + "layout (binding = 6) uniform sampler2DArray sample3;\r\n" + // + "layout (binding = 7) uniform sampler2DArray sample4;\r\n" + // + "layout (binding = 8) uniform sampler2DArray sample5;\r\n" + // + "layout (binding = 9) uniform sampler2DArray sample6;\r\n" + // + "layout (binding = 10) uniform sampler2DArray sample7;\r\n" + // + "layout (binding = 11) uniform sampler2DArray sample8;\r\n" + // + "layout (binding = 12) uniform sampler2DArray sample9;\r\n" + // + "layout (binding = 13) uniform sampler2DArray sample10;\r\n" + // + "layout (binding = 14) uniform sampler2DArray sample11;\r\n" + // + "layout (binding = 15) uniform sampler2DArray sample12;\r\n" + // + "layout (binding = 16) uniform sampler2DArray sample13;\r\n" + // + "layout (binding = 17) uniform sampler2DArray sample14;\r\n" + // + "layout (binding = 18) uniform sampler2DArray sample15;\r\n" + // + "layout (binding = 19) uniform sampler2DArray sample16;\r\n" + // + "\r\n" + // + "layout (binding = 20) uniform usampler2D pathing_map_static;\r\n" + // + "layout (binding = 21) uniform usampler2D pathing_map_dynamic;\r\n" + // + "\r\n" + // + "layout (location = 0) in vec2 UV;\r\n" + // + "layout (location = 1) in flat uvec4 texture_indices;\r\n" + // + "layout (location = 2) in vec2 pathing_map_uv;\r\n" + // + "layout (location = 3) in vec3 normal;\r\n" + // + "\r\n" + // + "layout (location = 0) out vec4 color;\r\n" + // + "layout (location = 1) out vec4 position;\r\n" + // + "\r\n" + // + "vec4 get_fragment(uint id, vec3 uv) {\r\n" + // + " vec2 dx = dFdx(uv.xy);\r\n" + // + " vec2 dy = dFdy(uv.xy);\r\n" + // + "\r\n" + // + " switch(id) {\r\n" + // + " case 0:\r\n" + // + " return textureGrad(sample0, uv, dx, dy);\r\n" + // + " case 1:\r\n" + // + " return textureGrad(sample1, uv, dx, dy);\r\n" + // + " case 2:\r\n" + // + " return textureGrad(sample2, uv, dx, dy);\r\n" + // + " case 3:\r\n" + // + " return textureGrad(sample3, uv, dx, dy);\r\n" + // + " case 4:\r\n" + // + " return textureGrad(sample4, uv, dx, dy);\r\n" + // + " case 5:\r\n" + // + " return textureGrad(sample5, uv, dx, dy);\r\n" + // + " case 6:\r\n" + // + " return textureGrad(sample6, uv, dx, dy);\r\n" + // + " case 7:\r\n" + // + " return textureGrad(sample7, uv, dx, dy);\r\n" + // + " case 8:\r\n" + // + " return textureGrad(sample8, uv, dx, dy);\r\n" + // + " case 9:\r\n" + // + " return textureGrad(sample9, uv, dx, dy);\r\n" + // + " case 10:\r\n" + // + " return textureGrad(sample10, uv, dx, dy);\r\n" + // + " case 11:\r\n" + // + " return textureGrad(sample11, uv, dx, dy);\r\n" + // + " case 12:\r\n" + // + " return textureGrad(sample12, uv, dx, dy);\r\n" + // + " case 13:\r\n" + // + " return textureGrad(sample13, uv, dx, dy);\r\n" + // + " case 14:\r\n" + // + " return textureGrad(sample14, uv, dx, dy);\r\n" + // + " case 15:\r\n" + // + " return textureGrad(sample15, uv, dx, dy);\r\n" + // + " case 16:\r\n" + // + " return textureGrad(sample16, uv, dx, dy);\r\n" + // + " case 17:\r\n" + // + " return vec4(0, 0, 0, 0);\r\n" + // + " }\r\n" + // + "}\r\n" + // + "\r\n" + // + "\r\n" + // + "void main() {\r\n" + // + " color = get_fragment(texture_indices.a & 31, vec3(UV, texture_indices.a >> 5));\r\n" + // + " color = color * color.a + get_fragment(texture_indices.b & 31, vec3(UV, texture_indices.b >> 5)) * (1 - color.a);\r\n" + + // + " color = color * color.a + get_fragment(texture_indices.g & 31, vec3(UV, texture_indices.g >> 5)) * (1 - color.a);\r\n" + + // + " color = color * color.a + get_fragment(texture_indices.r & 31, vec3(UV, texture_indices.r >> 5)) * (1 - color.a);\r\n" + + // + "\r\n" + // + " if (show_lighting) {\r\n" + // + " vec3 light_direction = vec3(-0.3, -0.3, 0.25);\r\n" + // + " light_direction = normalize(light_direction);\r\n" + // + "\r\n" + // + " color.rgb *= clamp(dot(normal, light_direction) + 0.45, 0, 1);\r\n" + // + " }\r\n" + // + "\r\n" + // + " uint byte_static = texelFetch(pathing_map_static, ivec2(pathing_map_uv), 0).r;\r\n" + // + " uint byte_dynamic = texelFetch(pathing_map_dynamic, ivec2(pathing_map_uv), 0).r;\r\n" + // + " if (show_pathing_map) {\r\n" + // + " uint final = byte_static.r | byte_dynamic.r;\r\n" + // + "\r\n" + // + " vec4 pathing_static_color = vec4((final & 2) >> 1, (final & 4) >> 2, (final & 8) >> 3, 0.25);\r\n" + + // + "\r\n" + // + " color = length(pathing_static_color.rgb) > 0 ? color * 0.75 + pathing_static_color * 0.5 : color;\r\n" + + // + " }\r\n" + // + "}"; + } + + public static final class Water { + private Water() { + } + + public static final String vert = "#version 450 core\r\n" + // + "\r\n" + // + "layout (location = 0) in vec2 vPosition;\r\n" + // + "\r\n" + // + "layout (binding = 0) uniform sampler2D water_height_texture;\r\n" + // + "layout (binding = 1) uniform sampler2D ground_height_texture;\r\n" + // + "layout (binding = 2) uniform sampler2D water_exists_texture;\r\n" + // + "\r\n" + // + "layout (location = 0) uniform mat4 MVP;\r\n" + // + "layout (location = 1) uniform vec4 shallow_color_min;\r\n" + // + "layout (location = 2) uniform vec4 shallow_color_max;\r\n" + // + "layout (location = 3) uniform vec4 deep_color_min;\r\n" + // + "layout (location = 4) uniform vec4 deep_color_max;\r\n" + // + "layout (location = 5) uniform float water_offset;\r\n" + // + "\r\n" + // + "out vec2 UV;\r\n" + // + "out vec4 Color;\r\n" + // + "\r\n" + // + "const float min_depth = 10.f / 128;\r\n" + // + "const float deeplevel = 64.f / 128;\r\n" + // + "const float maxdepth = 72.f / 128;\r\n" + // + "\r\n" + // + "void main() { \r\n" + // + " ivec2 size = textureSize(water_height_texture, 0) - 1;\r\n" + // + " ivec2 pos = ivec2(gl_InstanceID % size.x, gl_InstanceID / size.x);\r\n" + // + " ivec2 height_pos = ivec2(vPosition + pos);\r\n" + // + " float water_height = texelFetch(water_height_texture, height_pos, 0).r + water_offset;\r\n" + // + "\r\n" + // + " bool is_water = texelFetch(water_exists_texture, pos, 0).r > 0\r\n" + // + " || texelFetch(water_exists_texture, pos + ivec2(1, 0), 0).r > 0\r\n" + // + " || texelFetch(water_exists_texture, pos + ivec2(1, 1), 0).r > 0\r\n" + // + " || texelFetch(water_exists_texture, pos + ivec2(0, 1), 0).r > 0;\r\n" + // + "\r\n" + // + " gl_Position = is_water ? MVP * vec4(vPosition + pos, water_height, 1) : vec4(2.0, 0.0, 0.0, 1.0);\r\n" + + // + "\r\n" + // + " UV = vec2(vPosition.x, 1 - vPosition.y);\r\n" + // + "\r\n" + // + " float ground_height = texelFetch(ground_height_texture, height_pos, 0).r;\r\n" + // + " float value = clamp(water_height - ground_height, 0.f, 1.f);\r\n" + // + " if (value <= deeplevel) {\r\n" + // + " value = max(0.f, value - min_depth) / (deeplevel - min_depth);\r\n" + // + " Color = shallow_color_min * (1.f - value) + shallow_color_max * value;\r\n" + // + " } else {\r\n" + // + " value = clamp(value - deeplevel, 0.f, maxdepth - deeplevel) / (maxdepth - deeplevel);\r\n" + // + " Color = deep_color_min * (1.f - value) + deep_color_max * value;\r\n" + // + " }\r\n" + // + " }"; + + public static final String frag = "#version 450 core\r\n" + // + "\r\n" + // + "layout (binding = 3) uniform sampler2DArray water_textures;\r\n" + // + "layout (binding = 2) uniform sampler2D water_exists_texture;\r\n" + // + "\r\n" + // + "\r\n" + // + "layout (location = 6) uniform int current_texture;\r\n" + // + "\r\n" + // + "in vec2 UV;\r\n" + // + "in vec4 Color;\r\n" + // + "\r\n" + // + "out vec4 outColor;\r\n" + // + "\r\n" + // + "void main() {\r\n" + // + " outColor = texture(water_textures, vec3(UV, current_texture)) * Color;\r\n" + // + "}"; } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/TerrainDoodad.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/TerrainDoodad.java index 6c753b7..7bc7e6a 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/TerrainDoodad.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/TerrainDoodad.java @@ -13,7 +13,7 @@ public class TerrainDoodad { public TerrainDoodad(final War3MapViewer map, final MdxModel model, final MutableGameObject row, final com.etheller.warsmash.parsers.w3x.doo.TerrainDoodad doodad) { - final float[] centerOffset = map.centerOffset; + final float[] centerOffset = map.terrain.centerOffset; final MdxSimpleInstance instance = (MdxSimpleInstance) model.addInstance(1); locationHeap[0] = (doodad.getLocation()[0] * 128) + centerOffset[0] + 128; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index 03e914f..a3b2e36 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -6,16 +6,8 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedHashSet; import java.util.List; -import java.util.Map; -import com.badlogic.gdx.graphics.GL20; -import com.badlogic.gdx.graphics.GL30; -import com.badlogic.gdx.graphics.glutils.ShaderProgram; -import com.badlogic.gdx.math.Vector3; import com.etheller.warsmash.common.FetchDataTypeName; import com.etheller.warsmash.common.LoadGenericCallback; import com.etheller.warsmash.datasources.DataSource; @@ -23,18 +15,12 @@ import com.etheller.warsmash.parsers.w3x.War3Map; import com.etheller.warsmash.parsers.w3x.doo.War3MapDoo; import com.etheller.warsmash.parsers.w3x.objectdata.Warcraft3MapObjectData; import com.etheller.warsmash.parsers.w3x.unitsdoo.War3MapUnitsDoo; -import com.etheller.warsmash.parsers.w3x.w3e.Corner; import com.etheller.warsmash.parsers.w3x.w3e.War3MapW3e; import com.etheller.warsmash.parsers.w3x.w3i.War3MapW3i; -import com.etheller.warsmash.units.DataTable; -import com.etheller.warsmash.units.Element; -import com.etheller.warsmash.units.StandardObjectData; import com.etheller.warsmash.units.manager.MutableObjectData; import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; import com.etheller.warsmash.units.manager.MutableObjectData.WorldEditorDataType; import com.etheller.warsmash.util.MappedData; -import com.etheller.warsmash.util.MappedDataRow; -import com.etheller.warsmash.util.RenderMathUtils; import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.CanvasProvider; import com.etheller.warsmash.viewer5.GenericResource; @@ -43,36 +29,20 @@ import com.etheller.warsmash.viewer5.ModelInstance; import com.etheller.warsmash.viewer5.ModelViewer; import com.etheller.warsmash.viewer5.PathSolver; import com.etheller.warsmash.viewer5.Scene; -import com.etheller.warsmash.viewer5.Texture; -import com.etheller.warsmash.viewer5.gl.ANGLEInstancedArrays; import com.etheller.warsmash.viewer5.gl.WebGL; import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxHandler; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; +import com.etheller.warsmash.viewer5.handlers.w3x.environment.Terrain; public class War3MapViewer extends ModelViewer { - private static final float[] sizeHeap = new float[2]; private static final War3ID sloc = War3ID.fromString("sloc"); private static final LoadGenericCallback stringDataCallback = new StringDataCallbackImplementation(); - private static final StreamDataCallbackImplementation streamDataCallback = new StreamDataCallbackImplementation(); - - private static final Vector3 normalHeap1 = new Vector3(); - private static final Vector3 normalHeap2 = new Vector3(); + public static final StreamDataCallbackImplementation streamDataCallback = new StreamDataCallbackImplementation(); public PathSolver wc3PathSolver = PathSolver.DEFAULT; public SolverParams solverParams = new SolverParams(); - public ShaderProgram groundShader; - public ShaderProgram waterShader; - public ShaderProgram cliffShader; public Scene worldScene; - public float waterIndex; - public float waterIncreasePerFrame; - public float waterHeightOffset; - public List waterTextures = new ArrayList<>(); - public float[] maxDeepColor = new float[4]; - public float[] minDeepColor = new float[4]; - public float[] maxShallowColor = new float[4]; - public float[] minShallowColor = new float[4]; public boolean anyReady; public boolean terrainCliffsAndWaterLoaded; public MappedData terrainData = new MappedData(); @@ -92,28 +62,8 @@ public class War3MapViewer extends ModelViewer { public MappedData unitMetaData = new MappedData(); public List units = new ArrayList<>(); public boolean unitsReady; - public List tilesetTextures = new ArrayList<>(); - public List cliffTextures = new ArrayList<>(); - public List cliffModels = new ArrayList<>(); public War3Map mapMpq; public PathSolver mapPathSolver = PathSolver.DEFAULT; - public Corner[][] corners; - public float[] centerOffset = new float[2]; - public int[] mapSize = new int[2]; - public List tilesets = new ArrayList<>(); // TODO - public int blightTextureIndex = -1; - public List cliffTilesets = new ArrayList<>(); - public int columns; - public int rows; - public int vertexBuffer; - public int faceBuffer; - public int instanceBuffer; - public int textureBuffer; - public int variationBuffer; - public int waterBuffer; - public int heightMap; - public int waterHeightMap; - public int cliffHeightMap; private final DataSource gameDataSource; @@ -127,9 +77,7 @@ public class War3MapViewer extends ModelViewer { this.wc3PathSolver = PathSolver.DEFAULT; - this.groundShader = this.webGL.createShaderProgram(W3xShaders.Ground.vert, W3xShaders.Ground.frag); - this.waterShader = this.webGL.createShaderProgram(W3xShaders.Water.vert, W3xShaders.Water.frag); - this.cliffShader = this.webGL.createShaderProgram(W3xShaders.Cliffs.vert, W3xShaders.Cliffs.frag); + this.terrain = new Terrain(webGL); this.worldScene = this.addScene(); @@ -219,9 +167,10 @@ public class War3MapViewer extends ModelViewer { final float[] centerOffset = terrainData.getCenterOffset(); final int[] mapSize = terrainData.getMapSize(); - this.corners = terrainData.getCorners(); - System.arraycopy(centerOffset, 0, this.centerOffset, 0, centerOffset.length); - System.arraycopy(mapSize, 0, this.mapSize, 0, mapSize.length); + this.terrain.load(terrainData, centerOffset, mapSize, this); + this.terrainReady = true; + this.anyReady = true; + this.cliffsReady = true; // Override the grid based on the map. this.worldScene.grid = new Grid(centerOffset[0], centerOffset[1], (mapSize[0] * 128) - 128, @@ -252,248 +201,6 @@ public class War3MapViewer extends ModelViewer { } private void loadTerrainCliffsAndWater(final War3MapW3e w3e) { - final String texturesExt = this.solverParams.reforged ? ".dds" : ".blp"; - final char tileset = w3e.getTileset(); - - for (final War3ID groundTile : w3e.getGroundTiles()) { - final MappedDataRow row = this.terrainData.getRow(groundTile.asStringValue()); - - this.tilesets.add(row); - this.tilesetTextures - .add((Texture) this.load(row.get("dir").toString() + "\\" + row.get("file") + texturesExt, - this.mapPathSolver, this.solverParams)); - } - - final StandardObjectData standardObjectData = new StandardObjectData(this.mapMpq.getCompoundDataSource()); - final DataTable worldEditData = standardObjectData.getWorldEditData(); - final Element tilesets = worldEditData.get("TileSets"); - - this.blightTextureIndex = this.tilesetTextures.size(); - this.tilesetTextures - .add((Texture) this.load(tilesets.getField(Character.toString(tileset)).split(",")[1] + texturesExt, - this.mapPathSolver, this.solverParams)); - - for (final War3ID cliffTile : w3e.getCliffTiles()) { - final MappedDataRow row = this.cliffTypesData.getRow(cliffTile.asStringValue()); - - this.cliffTilesets.add(row); - this.cliffTextures - .add((Texture) this.load(row.get("texDir").toString() + "\\" + row.get("texFile") + texturesExt, - this.mapPathSolver, this.solverParams)); - } - - final MappedDataRow waterRow = this.waterData.getRow(tileset + "Sha"); - - this.waterHeightOffset = ((Number) waterRow.get("height")).floatValue(); - this.waterIncreasePerFrame = ((Number) waterRow.get("texRate")).intValue() / (float) 60; - this.waterTextures.clear(); - this.maxDeepColor[0] = ((Number) waterRow.get("Dmax_R")).floatValue(); - this.maxDeepColor[1] = ((Number) waterRow.get("Dmax_G")).floatValue(); - this.maxDeepColor[2] = ((Number) waterRow.get("Dmax_B")).floatValue(); - this.maxDeepColor[3] = ((Number) waterRow.get("Dmax_A")).floatValue(); - this.minDeepColor[0] = ((Number) waterRow.get("Dmin_R")).floatValue(); - this.minDeepColor[1] = ((Number) waterRow.get("Dmin_G")).floatValue(); - this.minDeepColor[2] = ((Number) waterRow.get("Dmin_B")).floatValue(); - this.minDeepColor[3] = ((Number) waterRow.get("Dmin_A")).floatValue(); - this.maxShallowColor[0] = ((Number) waterRow.get("Smax_R")).floatValue(); - this.maxShallowColor[1] = ((Number) waterRow.get("Smax_G")).floatValue(); - this.maxShallowColor[2] = ((Number) waterRow.get("Smax_B")).floatValue(); - this.maxShallowColor[3] = ((Number) waterRow.get("Smax_A")).floatValue(); - this.minShallowColor[0] = ((Number) waterRow.get("Smin_R")).floatValue(); - this.minShallowColor[1] = ((Number) waterRow.get("Smin_G")).floatValue(); - this.minShallowColor[2] = ((Number) waterRow.get("Smin_B")).floatValue(); - this.minShallowColor[3] = ((Number) waterRow.get("Smin_A")).floatValue(); - - for (int i = 0, l = ((Number) waterRow.get("numTex")).intValue(); i < l; i++) { - this.waterTextures.add( - (Texture) this.load(waterRow.get("texFile").toString() + ((i < 10) ? "0" : "") + i + texturesExt, - this.mapPathSolver, this.solverParams)); - } - - final GL20 gl = this.gl; - - final Corner[][] corners = w3e.getCorners(); - final int columns = this.mapSize[0]; - final int rows = this.mapSize[1]; - final float[] centerOffset = this.centerOffset; - final int instanceCount = (columns - 1) * (rows - 1); - final float[] cliffHeights = new float[columns * rows]; - final float[] cornerHeights = new float[columns * rows]; - final float[] waterHeights = new float[columns * rows]; - final short[] cornerTextures = new short[instanceCount * 4]; - final short[] cornerVariations = new short[instanceCount * 4]; - final short[] waterFlags = new short[instanceCount]; - int instance = 0; - final Map cliffs = new HashMap<>(); - - this.columns = columns - 1; - this.rows = rows - 1; - - for (int y = 0; y < rows; y++) { - for (int x = 0; x < columns; x++) { - final Corner bottomLeft = corners[y][x]; - final int index = (y * columns) + x; - - cliffHeights[index] = bottomLeft.getGroundHeight(); - cornerHeights[index] = (bottomLeft.getGroundHeight() + bottomLeft.getLayerHeight()) - 2; - waterHeights[index] = bottomLeft.getWaterHeight(); - - if ((y < (rows - 1)) && (x < (columns - 1))) { - // Water can be used with cliffs and normal corners, so store water state - // regardless. - waterFlags[instance] = this.isWater(x, y); - - // Is this a cliff, or a normal corner? - if (this.isCliff(x, y)) { - final int bottomLeftLayer = bottomLeft.getLayerHeight(); - final int bottomRightLayer = corners[y][x + 1].getLayerHeight(); - final int topLeftLayer = corners[y + 1][x].getLayerHeight(); - final int topRightLayer = corners[y + 1][x + 1].getLayerHeight(); - final int base = Math.min(Math.min(bottomLeftLayer, bottomRightLayer), - Math.min(topLeftLayer, topRightLayer)); - final String fileName = this.cliffFileName(bottomLeftLayer, bottomRightLayer, topLeftLayer, - topRightLayer, base); - - if (!"AAAA".equals(fileName)) { - int cliffTexture = bottomLeft.getCliffTexture(); - - // ? - if (cliffTexture == 15) { - cliffTexture = 1; - } - - final MappedDataRow cliffRow = this.cliffTilesets.get(cliffTexture); - final String dir = cliffRow.get("cliffModelDir").toString(); - final String path = "Doodads\\Terrain\\" + dir + "\\" + dir + fileName - + Variations.getCliffVariation(dir, fileName, bottomLeft.getCliffVariation()) - + ".mdx"; - - if (!cliffs.containsKey(path)) { - cliffs.put(path, new CliffInfo()); - } - - cliffs.get(path).locations.add(new float[] { ((x + 1) * 128) + centerOffset[0], - (y * 128) + centerOffset[1], (base - 2) * 128 }); - cliffs.get(path).textures.add(cliffTexture); - } - } - else { - final int bottomLeftTexture = this.cornerTexture(x, y); - final int bottomRightTexture = this.cornerTexture(x + 1, y); - final int topLeftTexture = this.cornerTexture(x, y + 1); - final int topRightTexture = this.cornerTexture(x + 1, y + 1); - final LinkedHashSet texturesUnique = new LinkedHashSet<>(); - texturesUnique.add(bottomLeftTexture); - texturesUnique.add(bottomRightTexture); - texturesUnique.add(topLeftTexture); - texturesUnique.add(topRightTexture); - final List textures = new ArrayList<>(texturesUnique); - Collections.sort(textures); - - int texture = textures.remove(0); - - cornerTextures[instance * 4] = (short) (texture + 1); - cornerVariations[instance * 4] = this.getVariation(texture, bottomLeft.getGroundVariation()); - - for (int i = 0, l = textures.size(); i < l; i++) { - int bitset = 0; - - texture = textures.get(i); - - if (bottomRightTexture == texture) { - bitset |= 0b0001; - } - - if (bottomLeftTexture == texture) { - bitset |= 0b0010; - } - - if (topRightTexture == texture) { - bitset |= 0b0100; - } - - if (topLeftTexture == texture) { - bitset |= 0b1000; - } - - cornerTextures[(instance * 4) + 1 + i] = (short) (texture + 1); - cornerVariations[(instance * 4) + 1 + i] = (short) (bitset); - } - } - - instance += 1; - - } - } - } - - this.vertexBuffer = gl.glGenBuffer(); - gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.vertexBuffer); - gl.glBufferData(GL20.GL_ARRAY_BUFFER, 8 * 4, RenderMathUtils.wrap(new float[] { 0, 0, 1, 0, 0, 1, 1, 1 }), - GL20.GL_STATIC_DRAW); - - this.faceBuffer = gl.glGenBuffer(); - gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, this.faceBuffer); - gl.glBufferData(GL20.GL_ELEMENT_ARRAY_BUFFER, 6, RenderMathUtils.wrap(new byte[] { 0, 1, 2, 1, 3, 2 }), - GL20.GL_STATIC_DRAW); - - this.cliffHeightMap = gl.glGenTexture(); - gl.glBindTexture(GL20.GL_TEXTURE_2D, this.cliffHeightMap); - this.webGL.setTextureMode(GL20.GL_CLAMP_TO_EDGE, GL20.GL_CLAMP_TO_EDGE, GL20.GL_NEAREST, GL20.GL_NEAREST); - gl.glTexImage2D(GL20.GL_TEXTURE_2D, 0, GL30.GL_R32F, columns, rows, 0, GL30.GL_RED, GL20.GL_FLOAT, - RenderMathUtils.wrap(cliffHeights)); - - this.heightMap = gl.glGenTexture(); - gl.glBindTexture(GL20.GL_TEXTURE_2D, this.heightMap); - this.webGL.setTextureMode(GL20.GL_CLAMP_TO_EDGE, GL20.GL_CLAMP_TO_EDGE, GL20.GL_NEAREST, GL20.GL_NEAREST); - gl.glTexImage2D(GL20.GL_TEXTURE_2D, 0, GL30.GL_R32F, columns, rows, 0, GL30.GL_RED, GL20.GL_FLOAT, - RenderMathUtils.wrap(cornerHeights)); - - this.waterHeightMap = gl.glGenTexture(); - gl.glBindTexture(GL20.GL_TEXTURE_2D, this.waterHeightMap); - this.webGL.setTextureMode(GL20.GL_CLAMP_TO_EDGE, GL20.GL_CLAMP_TO_EDGE, GL20.GL_NEAREST, GL20.GL_NEAREST); - gl.glTexImage2D(GL20.GL_TEXTURE_2D, 0, GL30.GL_R32F, columns, rows, 0, GL30.GL_RED, GL20.GL_FLOAT, - RenderMathUtils.wrap(waterHeights)); - - this.instanceBuffer = gl.glGenBuffer(); - gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.instanceBuffer); - final float[] instanceBufferData = new float[instanceCount]; - for (int i = 0; i < instanceBufferData.length; i++) { - instanceBufferData[i] = i; - } - gl.glBufferData(GL20.GL_ARRAY_BUFFER, instanceBufferData.length * 4, RenderMathUtils.wrap(instanceBufferData), - GL20.GL_STATIC_DRAW); - - this.textureBuffer = gl.glGenBuffer(); - gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.textureBuffer); - gl.glBufferData(GL20.GL_ARRAY_BUFFER, cornerTextures.length, RenderMathUtils.wrap(cornerTextures), - GL20.GL_STATIC_DRAW); - - this.variationBuffer = gl.glGenBuffer(); - gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.variationBuffer); - gl.glBufferData(GL20.GL_ARRAY_BUFFER, cornerVariations.length, RenderMathUtils.wrap(cornerVariations), - GL20.GL_STATIC_DRAW); - - this.waterBuffer = gl.glGenBuffer(); - gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.waterBuffer); - gl.glBufferData(GL20.GL_ARRAY_BUFFER, waterFlags.length, RenderMathUtils.wrap(waterFlags), GL20.GL_STATIC_DRAW); - - this.terrainReady = true; - this.anyReady = true; - - final ShaderProgram cliffShader = this.cliffShader; - this.cliffModels.clear(); - for (final Map.Entry entry : cliffs.entrySet()) { - final String path = entry.getKey(); - final CliffInfo cliffInfo = entry.getValue(); - - final GenericResource resource = this.loadMapGeneric(path, FetchDataTypeName.ARRAY_BUFFER, - streamDataCallback); - - this.cliffModels.add(new TerrainModel(this, (InputStream) resource.data, cliffInfo.locations, - cliffInfo.textures, cliffShader)); - } - this.cliffsReady = true; } @@ -629,11 +336,7 @@ public class War3MapViewer extends ModelViewer { @Override public void update() { if (this.anyReady) { - this.waterIndex += this.waterIncreasePerFrame; - - if (this.waterIndex >= this.waterTextures.size()) { - this.waterIndex = 0; - } + this.terrain.update(); super.update(); @@ -656,325 +359,14 @@ public class War3MapViewer extends ModelViewer { final Scene worldScene = this.worldScene; worldScene.startFrame(); - this.renderGround(); - this.renderCliffs(); + this.terrain.renderGround(this.gl, this.webGL, worldScene); + this.terrain.renderCliffs(this.gl, this.webGL, worldScene); worldScene.renderOpaque(); - this.renderWater(); + this.terrain.renderWater(this.gl, this.webGL, worldScene); worldScene.renderTranslucent(); } } - public void renderGround() { - if (this.terrainReady) { - final GL20 gl = this.gl; - final WebGL webgl = this.webGL; - final ANGLEInstancedArrays instancedArrays = webgl.instancedArrays; - final ShaderProgram shader = this.groundShader; - final List tilesetTextures = this.tilesetTextures; - final int instanceAttrib = shader.getAttributeLocation("a_InstanceID"); - final int positionAttrib = shader.getAttributeLocation("a_position"); - final int texturesAttrib = shader.getAttributeLocation("a_textures"); - final int variationsAttrib = shader.getAttributeLocation("a_variations"); - final int tilesetCount = tilesetTextures.size(); - - gl.glEnable(GL20.GL_BLEND); - gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA); - - webgl.useShaderProgram(shader); - - shader.setUniformMatrix("u_VP", this.worldScene.camera.viewProjectionMatrix); - shader.setUniform2fv("u_offset", this.centerOffset, 0, 2); - sizeHeap[0] = this.columns; - sizeHeap[1] = this.rows; - shader.setUniform2fv("u_size", sizeHeap, 0, 2); - shader.setUniformi("u_heightMap", 15); - - gl.glActiveTexture(GL20.GL_TEXTURE15); - gl.glBindTexture(GL20.GL_TEXTURE_2D, this.heightMap); - - gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.vertexBuffer); - shader.setVertexAttribute(positionAttrib, 2, GL20.GL_FLOAT, false, 8, 0); - - gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.instanceBuffer); - shader.setVertexAttribute(instanceAttrib, 1, GL20.GL_FLOAT, false, 4, 0); - instancedArrays.glVertexAttribDivisorANGLE(instanceAttrib, 1); - - gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.textureBuffer); - shader.setVertexAttribute(texturesAttrib, 4, GL20.GL_UNSIGNED_BYTE, false, 4, 0); - instancedArrays.glVertexAttribDivisorANGLE(texturesAttrib, 1); - - gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.variationBuffer); - shader.setVertexAttribute(variationsAttrib, 4, GL20.GL_UNSIGNED_BYTE, false, 4, 0); - instancedArrays.glVertexAttribDivisorANGLE(variationsAttrib, 1); - - gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, this.faceBuffer); - - shader.setUniformi("u_baseTileset", 0); - - for (int i = 0, l = Math.min(tilesetCount, 15); i < l; i++) { - final int isExtended = (tilesetTextures.get(i).getWidth() > tilesetTextures.get(i).getHeight()) ? 1 : 0; - - shader.setUniformf("u_extended[" + i + "]", isExtended); - shader.setUniformi("u_tilesets[" + i + "]", i); - - tilesetTextures.get(i).bind(i); - } - - instancedArrays.glDrawElementsInstancedANGLE(GL20.GL_TRIANGLES, 6, GL20.GL_UNSIGNED_BYTE, 0, - this.rows * this.columns); - - if (tilesetCount > 15) { - shader.setUniformi("u_baseTileset", 15); - - for (int i = 0, l = tilesetCount - 15; i < l; i++) { - final int isExtended = (tilesetTextures.get(i + 15).getWidth() > tilesetTextures.get(i + 15) - .getHeight()) ? 1 : 0; - - shader.setUniformf("u_extended[" + i + "]", isExtended); - - tilesetTextures.get(i + 15).bind(i); - } - - instancedArrays.glDrawElementsInstancedANGLE(GL20.GL_TRIANGLES, 6, GL20.GL_UNSIGNED_BYTE, 0, - this.rows * this.columns); - } - - instancedArrays.glVertexAttribDivisorANGLE(texturesAttrib, 0); - instancedArrays.glVertexAttribDivisorANGLE(variationsAttrib, 0); - instancedArrays.glVertexAttribDivisorANGLE(instanceAttrib, 0); - } - } - - public void renderWater() { - if (this.terrainReady) { - final GL20 gl = this.gl; - final WebGL webgl = this.webGL; - final ANGLEInstancedArrays instancedArrays = webgl.instancedArrays; - final ShaderProgram shader = this.waterShader; - final int instanceAttrib = shader.getAttributeLocation("a_InstanceID"); - final int positionAttrib = shader.getAttributeLocation("a_position"); - final int isWaterAttrib = shader.getAttributeLocation("a_isWater"); - - gl.glDepthMask(false); - - gl.glEnable(GL20.GL_BLEND); - gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA); - - webgl.useShaderProgram(shader); - - shader.setUniformMatrix("u_VP", this.worldScene.camera.viewProjectionMatrix); - shader.setUniform2fv("u_offset", this.centerOffset, 0, 2); - sizeHeap[0] = this.columns; - sizeHeap[1] = this.rows; - shader.setUniform2fv("u_size", sizeHeap, 0, 2); - shader.setUniformi("u_heightMap", 0); - shader.setUniformi("u_waterHeightMap", 1); - shader.setUniformi("u_waterTexture", 2); - shader.setUniformf("u_offsetHeight", this.waterHeightOffset); - shader.setUniform4fv("u_maxDeepColor", this.maxDeepColor, 0, 4); - shader.setUniform4fv("u_minDeepColor", this.minDeepColor, 0, 4); - shader.setUniform4fv("u_maxShallowColor", this.maxShallowColor, 0, 4); - shader.setUniform4fv("u_minShallowColor", this.minShallowColor, 0, 4); - - gl.glActiveTexture(GL20.GL_TEXTURE0); - gl.glBindTexture(GL20.GL_TEXTURE_2D, this.heightMap); - - gl.glActiveTexture(GL20.GL_TEXTURE1); - gl.glBindTexture(GL20.GL_TEXTURE_2D, this.waterHeightMap); - - this.waterTextures.get((int) this.waterIndex).bind(2); - - gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.vertexBuffer); - shader.setVertexAttribute(positionAttrib, 2, GL20.GL_FLOAT, false, 8, 0); - - gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.instanceBuffer); - shader.setVertexAttribute(instanceAttrib, 1, GL20.GL_FLOAT, false, 4, 0); - instancedArrays.glVertexAttribDivisorANGLE(instanceAttrib, 1); - - gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.waterBuffer); - shader.setVertexAttribute(isWaterAttrib, 1, GL20.GL_UNSIGNED_BYTE, false, 1, 0); - instancedArrays.glVertexAttribDivisorANGLE(isWaterAttrib, 1); - - gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, this.faceBuffer); - instancedArrays.glDrawElementsInstancedANGLE(GL20.GL_TRIANGLES, 6, GL20.GL_UNSIGNED_BYTE, 0, - this.rows * this.columns); - - instancedArrays.glVertexAttribDivisorANGLE(isWaterAttrib, 0); - instancedArrays.glVertexAttribDivisorANGLE(instanceAttrib, 0); - } - } - - public void renderCliffs() { - if (this.cliffsReady) { - final GL20 gl = this.gl; - final WebGL webGL = this.webGL; - final ANGLEInstancedArrays instancedArrays = webGL.instancedArrays; - final ShaderProgram shader = this.cliffShader; - - gl.glDisable(GL20.GL_BLEND); - - webGL.useShaderProgram(shader); - - shader.setUniformMatrix("u_VP", this.worldScene.camera.viewProjectionMatrix); - shader.setUniformi("u_heightMap", 0); - shader.setUniformf("u_pixel[0]", 1 / (float) (this.columns + 1)); - shader.setUniformf("u_pixel[1]", 1 / (float) (this.rows + 1)); - shader.setUniform2fv("u_centerOffset", this.centerOffset, 0, 2); - shader.setUniformi("u_texture1", 1); - shader.setUniformi("u_texture2", 2); - - gl.glActiveTexture(GL20.GL_TEXTURE0); - gl.glBindTexture(GL20.GL_TEXTURE_2D, this.cliffHeightMap); - - gl.glActiveTexture(GL20.GL_TEXTURE1); - gl.glBindTexture(GL20.GL_TEXTURE_2D, this.cliffTextures.get(0).getGlTarget()); - - if (this.cliffTextures.size() > 1) { - gl.glActiveTexture(GL20.GL_TEXTURE2); - gl.glBindTexture(GL20.GL_TEXTURE_2D, this.cliffTextures.get(1).getGlTarget()); - } - - // Set instanced attributes. - for (final TerrainModel cliff : this.cliffModels) { - cliff.render(shader); - } - } - } - - public String cliffFileName(final int bottomLeftLayer, final int bottomRightLayer, final int topLeftLayer, - final int topRightLayer, final int base) { - return Character.toString((char) ((65 + bottomLeftLayer) - base)) - + Character.toString((char) ((65 + topLeftLayer) - base)) - + Character.toString((char) ((65 + topRightLayer) - base)) - + Character.toString((char) ((65 + bottomRightLayer) - base)); - } - - public short getVariation(final int groundTexture, final int variation) { - final Texture texture = this.tilesetTextures.get(groundTexture); - - // Extended ? - if (texture.getWidth() > texture.getHeight()) { - if (variation < 16) { - return (short) (16 + variation); - } - else if (variation == 16) { - return 15; - } - else { - return 0; - } - } - else { - if (variation == 0) { - return 0; - } - else { - return 15; - } - } - } - - public boolean isCliff(final int column, final int row) { - if ((column < 1) || (column > (this.columns - 1)) || (row < 1) || (row > (this.rows - 1))) { - return false; - } - - final Corner[][] corners = this.corners; - final int bottomLeft = corners[row][column].getLayerHeight(); - final int bottomRight = corners[row][column + 1].getLayerHeight(); - final int topLeft = corners[row + 1][column].getLayerHeight(); - final int topRight = corners[row + 1][column + 1].getLayerHeight(); - - return (bottomLeft != bottomRight) || (bottomLeft != topLeft) || (bottomLeft != topRight); - } - - public short isWater(final int column, final int row) { - return ((this.corners[row][column].getWater() != 0) || (this.corners[row][column + 1].getWater() != 0) - || (this.corners[row + 1][column].getWater() != 0) - || (this.corners[row + 1][column + 1].getWater() != 0)) ? (short) 1 : (short) 0; - } - - public int cliffGroundIndex(final int whichCliff) { - final String whichTile = this.cliffTilesets.get(whichCliff).get("groundTile").toString(); - final List tilesets = this.tilesets; - - for (int i = 0, l = tilesets.size(); i < l; i++) { - if (tilesets.get(i).get("tileID").toString().equals(whichTile)) { - return i; - } - } - throw new IllegalArgumentException(Integer.toString(whichCliff)); - } - - public int cornerTexture(final int column, final int row) { - final Corner[][] corners = this.corners; - final int columns = this.columns; - final int rows = this.rows; - - for (int y = -1; y < 1; y++) { - for (int x = -1; x < 1; x++) { - if (((column + x) > 0) && ((column + x) < (columns - 1)) && ((row + y) > 0) - && ((row + y) < (rows - 1))) { - if (this.isCliff(column + x, row + y)) { - int texture = corners[row + y][column + x].getCliffTexture(); - - if (texture == 15) { - texture = 1; - } - - return this.cliffGroundIndex(texture); - } - } - } - } - - final Corner corner = corners[row][column]; - if (corner.getBlight() != 0) { - return this.blightTextureIndex; - } - return corner.getGroundTexture(); - } - - public Vector3 groundNormal(final Vector3 out, int x, int y) { - final float[] centerOffset = this.centerOffset; - final int[] mapSize = this.mapSize; - - x = (int) ((x - centerOffset[0]) / 128); - y = (int) ((y - centerOffset[1]) / 128); - - final int cellX = x; - final int cellY = y; - - // See if this coordinate is in the map - - if ((cellX >= 0) && (cellX < (mapSize[0] - 1)) && (cellY >= 0) && (cellY < (mapSize[1] - 1))) { - // See http://gamedev.stackexchange.com/a/24574 - final Corner[][] corners = this.corners; - final int bottomLeft = corners[cellY][cellX].getGroundHeight(); - final int bottomRight = corners[cellY][cellX + 1].getGroundHeight(); - final int topLeft = corners[cellY + 1][cellX].getGroundHeight(); - final int topRight = corners[cellY + 1][cellX + 1].getGroundHeight(); - final int sqX = x - cellX; - final int sqY = y - cellY; - - if ((sqX + sqY) < 1) { - normalHeap1.set(1, 0, bottomRight - bottomLeft); - normalHeap2.set(0, 1, topLeft - bottomLeft); - } - else { - normalHeap1.set(-1, 0, topRight - topLeft); - normalHeap2.set(0, 1, topRight - bottomRight); - } - - out.set(normalHeap1.crs(normalHeap2)).nor(); - } - else { - out.set(0, 0, 1); - } - - return out; - } - private static final class MappedDataCallbackImplementation implements LoadGenericCallback { @Override public Object call(final InputStream data) { @@ -1039,6 +431,7 @@ public class War3MapViewer extends ModelViewer { } private static final int MAXIMUM_ACCEPTED = 1 << 30; + public final Terrain terrain; /** * Returns a power of two size for the given target capacity. diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java new file mode 100644 index 0000000..5d4340e --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java @@ -0,0 +1,650 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.environment; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; + +import com.badlogic.gdx.graphics.GL20; +import com.badlogic.gdx.graphics.GL30; +import com.badlogic.gdx.graphics.glutils.ShaderProgram; +import com.badlogic.gdx.math.Vector3; +import com.etheller.warsmash.common.FetchDataTypeName; +import com.etheller.warsmash.datasources.DataSource; +import com.etheller.warsmash.parsers.w3x.w3e.Corner; +import com.etheller.warsmash.parsers.w3x.w3e.War3MapW3e; +import com.etheller.warsmash.units.DataTable; +import com.etheller.warsmash.units.Element; +import com.etheller.warsmash.units.StandardObjectData; +import com.etheller.warsmash.util.MappedDataRow; +import com.etheller.warsmash.util.RenderMathUtils; +import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.util.WorldEditStrings; +import com.etheller.warsmash.viewer5.GenericResource; +import com.etheller.warsmash.viewer5.Scene; +import com.etheller.warsmash.viewer5.Texture; +import com.etheller.warsmash.viewer5.gl.ANGLEInstancedArrays; +import com.etheller.warsmash.viewer5.gl.WebGL; +import com.etheller.warsmash.viewer5.handlers.w3x.TerrainModel; +import com.etheller.warsmash.viewer5.handlers.w3x.Variations; +import com.etheller.warsmash.viewer5.handlers.w3x.W3xShaders; +import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; +import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer.CliffInfo; + +public class Terrain { + private static final float[] sizeHeap = new float[2]; + private static final Vector3 normalHeap1 = new Vector3(); + private static final Vector3 normalHeap2 = new Vector3(); + + public ShaderProgram groundShader; + public ShaderProgram waterShader; + public ShaderProgram cliffShader; + public float waterIndex; + public float waterIncreasePerFrame; + public float waterHeightOffset; + public List waterTextures = new ArrayList<>(); + public float[] maxDeepColor = new float[4]; + public float[] minDeepColor = new float[4]; + public float[] maxShallowColor = new float[4]; + public float[] minShallowColor = new float[4]; + + public List tilesetTextures = new ArrayList<>(); + public List cliffTextures = new ArrayList<>(); + public List cliffModels = new ArrayList<>(); + + public Corner[][] corners; + public float[] centerOffset = new float[2]; + public int[] mapSize = new int[2]; + + public List tilesets = new ArrayList<>(); // TODO + public int blightTextureIndex = -1; + public List cliffTilesets = new ArrayList<>(); + public int columns; + public int rows; + + public int vertexBuffer; + public int faceBuffer; + public int instanceBuffer; + public int textureBuffer; + public int variationBuffer; + public int waterBuffer; + public int heightMap; + public int waterHeightMap; + public int cliffHeightMap; + + public Terrain(final WebGL webGL, final DataSource dataSource, final WorldEditStrings worldEditStrings) + throws IOException { + + final DataTable terrainTable = new DataTable(worldEditStrings); + try (InputStream terrainSlkStream = dataSource.getResourceAsStream("TerrainArt\\Terrain.slk")) { + terrainTable.readSLK(terrainSlkStream); + } + final DataTable cliffTable = new DataTable(worldEditStrings); + try (InputStream cliffSlkStream = dataSource.getResourceAsStream("TerrainArt\\CliffTypes.slk")) { + cliffTable.readSLK(cliffSlkStream); + } + + this.groundShader = webGL.createShaderProgram(W3xShaders.Ground.vert, W3xShaders.Ground.frag); + this.waterShader = webGL.createShaderProgram(W3xShaders.Water.vert, W3xShaders.Water.frag); + this.cliffShader = webGL.createShaderProgram(W3xShaders.Cliffs.vert, W3xShaders.Cliffs.frag); + + } + + public void load(final War3MapW3e w3e, final float[] centerOffset, final int[] mapSize, + final War3MapViewer viewer) { + + this.corners = w3e.getCorners(); + System.arraycopy(centerOffset, 0, this.centerOffset, 0, centerOffset.length); + System.arraycopy(mapSize, 0, this.mapSize, 0, mapSize.length); + + final String texturesExt = viewer.solverParams.reforged ? ".dds" : ".blp"; + final char tileset = w3e.getTileset(); + + for (final War3ID groundTile : w3e.getGroundTiles()) { + final MappedDataRow row = viewer.terrainData.getRow(groundTile.asStringValue()); + + this.tilesets.add(row); + this.tilesetTextures + .add((Texture) viewer.load(row.get("dir").toString() + "\\" + row.get("file") + texturesExt, + viewer.mapPathSolver, viewer.solverParams)); + } + + final StandardObjectData standardObjectData = new StandardObjectData(viewer.mapMpq.getCompoundDataSource()); + final DataTable worldEditData = standardObjectData.getWorldEditData(); + final Element tilesets = worldEditData.get("TileSets"); + + this.blightTextureIndex = this.tilesetTextures.size(); + this.tilesetTextures + .add((Texture) viewer.load(tilesets.getField(Character.toString(tileset)).split(",")[1] + texturesExt, + viewer.mapPathSolver, viewer.solverParams)); + + for (final War3ID cliffTile : w3e.getCliffTiles()) { + final MappedDataRow row = viewer.cliffTypesData.getRow(cliffTile.asStringValue()); + + this.cliffTilesets.add(row); + this.cliffTextures + .add((Texture) viewer.load(row.get("texDir").toString() + "\\" + row.get("texFile") + texturesExt, + viewer.mapPathSolver, viewer.solverParams)); + } + + final MappedDataRow waterRow = viewer.waterData.getRow(tileset + "Sha"); + + this.waterHeightOffset = ((Number) waterRow.get("height")).floatValue(); + this.waterIncreasePerFrame = ((Number) waterRow.get("texRate")).intValue() / (float) 60; + this.waterTextures.clear(); + this.maxDeepColor[0] = ((Number) waterRow.get("Dmax_R")).floatValue(); + this.maxDeepColor[1] = ((Number) waterRow.get("Dmax_G")).floatValue(); + this.maxDeepColor[2] = ((Number) waterRow.get("Dmax_B")).floatValue(); + this.maxDeepColor[3] = ((Number) waterRow.get("Dmax_A")).floatValue(); + this.minDeepColor[0] = ((Number) waterRow.get("Dmin_R")).floatValue(); + this.minDeepColor[1] = ((Number) waterRow.get("Dmin_G")).floatValue(); + this.minDeepColor[2] = ((Number) waterRow.get("Dmin_B")).floatValue(); + this.minDeepColor[3] = ((Number) waterRow.get("Dmin_A")).floatValue(); + this.maxShallowColor[0] = ((Number) waterRow.get("Smax_R")).floatValue(); + this.maxShallowColor[1] = ((Number) waterRow.get("Smax_G")).floatValue(); + this.maxShallowColor[2] = ((Number) waterRow.get("Smax_B")).floatValue(); + this.maxShallowColor[3] = ((Number) waterRow.get("Smax_A")).floatValue(); + this.minShallowColor[0] = ((Number) waterRow.get("Smin_R")).floatValue(); + this.minShallowColor[1] = ((Number) waterRow.get("Smin_G")).floatValue(); + this.minShallowColor[2] = ((Number) waterRow.get("Smin_B")).floatValue(); + this.minShallowColor[3] = ((Number) waterRow.get("Smin_A")).floatValue(); + + for (int i = 0, l = ((Number) waterRow.get("numTex")).intValue(); i < l; i++) { + this.waterTextures.add( + (Texture) viewer.load(waterRow.get("texFile").toString() + ((i < 10) ? "0" : "") + i + texturesExt, + viewer.mapPathSolver, viewer.solverParams)); + } + + final GL20 gl = viewer.gl; + + final Corner[][] corners = w3e.getCorners(); + final int columns = this.mapSize[0]; + final int rows = this.mapSize[1]; + final int instanceCount = (columns - 1) * (rows - 1); + final float[] cliffHeights = new float[columns * rows]; + final float[] cornerHeights = new float[columns * rows]; + final float[] waterHeights = new float[columns * rows]; + final short[] cornerTextures = new short[instanceCount * 4]; + final short[] cornerVariations = new short[instanceCount * 4]; + final short[] waterFlags = new short[instanceCount]; + int instance = 0; + final Map cliffs = new HashMap<>(); + + this.columns = columns - 1; + this.rows = rows - 1; + + for (int y = 0; y < rows; y++) { + for (int x = 0; x < columns; x++) { + final Corner bottomLeft = corners[y][x]; + final int index = (y * columns) + x; + + cliffHeights[index] = bottomLeft.getGroundHeight(); + cornerHeights[index] = (bottomLeft.getGroundHeight() + bottomLeft.getLayerHeight()) - 2; + waterHeights[index] = bottomLeft.getWaterHeight(); + + if ((y < (rows - 1)) && (x < (columns - 1))) { + // Water can be used with cliffs and normal corners, so store water state + // regardless. + waterFlags[instance] = this.isWater(x, y); + + // Is this a cliff, or a normal corner? + if (this.isCliff(x, y)) { + final int bottomLeftLayer = bottomLeft.getLayerHeight(); + final int bottomRightLayer = corners[y][x + 1].getLayerHeight(); + final int topLeftLayer = corners[y + 1][x].getLayerHeight(); + final int topRightLayer = corners[y + 1][x + 1].getLayerHeight(); + final int base = Math.min(Math.min(bottomLeftLayer, bottomRightLayer), + Math.min(topLeftLayer, topRightLayer)); + final String fileName = this.cliffFileName(bottomLeftLayer, bottomRightLayer, topLeftLayer, + topRightLayer, base); + + if (!"AAAA".equals(fileName)) { + int cliffTexture = bottomLeft.getCliffTexture(); + + // ? + if (cliffTexture == 15) { + cliffTexture = 1; + } + + final MappedDataRow cliffRow = this.cliffTilesets.get(cliffTexture); + final String dir = cliffRow.get("cliffModelDir").toString(); + final String path = "Doodads\\Terrain\\" + dir + "\\" + dir + fileName + + Variations.getCliffVariation(dir, fileName, bottomLeft.getCliffVariation()) + + ".mdx"; + + if (!cliffs.containsKey(path)) { + cliffs.put(path, new CliffInfo()); + } + + cliffs.get(path).locations.add(new float[] { ((x + 1) * 128) + centerOffset[0], + (y * 128) + centerOffset[1], (base - 2) * 128 }); + cliffs.get(path).textures.add(cliffTexture); + } + } + else { + final int bottomLeftTexture = this.cornerTexture(x, y); + final int bottomRightTexture = this.cornerTexture(x + 1, y); + final int topLeftTexture = this.cornerTexture(x, y + 1); + final int topRightTexture = this.cornerTexture(x + 1, y + 1); + final LinkedHashSet texturesUnique = new LinkedHashSet<>(); + texturesUnique.add(bottomLeftTexture); + texturesUnique.add(bottomRightTexture); + texturesUnique.add(topLeftTexture); + texturesUnique.add(topRightTexture); + final List textures = new ArrayList<>(texturesUnique); + Collections.sort(textures); + + int texture = textures.remove(0); + + cornerTextures[instance * 4] = (short) (texture + 1); + cornerVariations[instance * 4] = this.getVariation(texture, bottomLeft.getGroundVariation()); + + for (int i = 0, l = textures.size(); i < l; i++) { + int bitset = 0; + + texture = textures.get(i); + + if (bottomRightTexture == texture) { + bitset |= 0b0001; + } + + if (bottomLeftTexture == texture) { + bitset |= 0b0010; + } + + if (topRightTexture == texture) { + bitset |= 0b0100; + } + + if (topLeftTexture == texture) { + bitset |= 0b1000; + } + + cornerTextures[(instance * 4) + 1 + i] = (short) (texture + 1); + cornerVariations[(instance * 4) + 1 + i] = (short) (bitset); + } + } + + instance += 1; + + } + } + } + + this.vertexBuffer = gl.glGenBuffer(); + gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.vertexBuffer); + gl.glBufferData(GL20.GL_ARRAY_BUFFER, 8 * 4, RenderMathUtils.wrap(new float[] { 0, 0, 1, 0, 0, 1, 1, 1 }), + GL20.GL_STATIC_DRAW); + + this.faceBuffer = gl.glGenBuffer(); + gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, this.faceBuffer); + gl.glBufferData(GL20.GL_ELEMENT_ARRAY_BUFFER, 6, RenderMathUtils.wrap(new byte[] { 0, 1, 2, 1, 3, 2 }), + GL20.GL_STATIC_DRAW); + + this.cliffHeightMap = gl.glGenTexture(); + gl.glBindTexture(GL20.GL_TEXTURE_2D, this.cliffHeightMap); + viewer.webGL.setTextureMode(GL20.GL_CLAMP_TO_EDGE, GL20.GL_CLAMP_TO_EDGE, GL20.GL_NEAREST, GL20.GL_NEAREST); + gl.glTexImage2D(GL20.GL_TEXTURE_2D, 0, GL30.GL_R32F, columns, rows, 0, GL30.GL_RED, GL20.GL_FLOAT, + RenderMathUtils.wrap(cliffHeights)); + + this.heightMap = gl.glGenTexture(); + gl.glBindTexture(GL20.GL_TEXTURE_2D, this.heightMap); + viewer.webGL.setTextureMode(GL20.GL_CLAMP_TO_EDGE, GL20.GL_CLAMP_TO_EDGE, GL20.GL_NEAREST, GL20.GL_NEAREST); + gl.glTexImage2D(GL20.GL_TEXTURE_2D, 0, GL30.GL_R32F, columns, rows, 0, GL30.GL_RED, GL20.GL_FLOAT, + RenderMathUtils.wrap(cornerHeights)); + + this.waterHeightMap = gl.glGenTexture(); + gl.glBindTexture(GL20.GL_TEXTURE_2D, this.waterHeightMap); + viewer.webGL.setTextureMode(GL20.GL_CLAMP_TO_EDGE, GL20.GL_CLAMP_TO_EDGE, GL20.GL_NEAREST, GL20.GL_NEAREST); + gl.glTexImage2D(GL20.GL_TEXTURE_2D, 0, GL30.GL_R32F, columns, rows, 0, GL30.GL_RED, GL20.GL_FLOAT, + RenderMathUtils.wrap(waterHeights)); + + this.instanceBuffer = gl.glGenBuffer(); + gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.instanceBuffer); + final float[] instanceBufferData = new float[instanceCount]; + for (int i = 0; i < instanceBufferData.length; i++) { + instanceBufferData[i] = i; + } + gl.glBufferData(GL20.GL_ARRAY_BUFFER, instanceBufferData.length * 4, RenderMathUtils.wrap(instanceBufferData), + GL20.GL_STATIC_DRAW); + + this.textureBuffer = gl.glGenBuffer(); + gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.textureBuffer); + gl.glBufferData(GL20.GL_ARRAY_BUFFER, cornerTextures.length, RenderMathUtils.wrap(cornerTextures), + GL20.GL_STATIC_DRAW); + + this.variationBuffer = gl.glGenBuffer(); + gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.variationBuffer); + gl.glBufferData(GL20.GL_ARRAY_BUFFER, cornerVariations.length, RenderMathUtils.wrap(cornerVariations), + GL20.GL_STATIC_DRAW); + + this.waterBuffer = gl.glGenBuffer(); + gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.waterBuffer); + gl.glBufferData(GL20.GL_ARRAY_BUFFER, waterFlags.length, RenderMathUtils.wrap(waterFlags), GL20.GL_STATIC_DRAW); + + final ShaderProgram cliffShader = this.cliffShader; + this.cliffModels.clear(); + for (final Map.Entry entry : cliffs.entrySet()) { + final String path = entry.getKey(); + final CliffInfo cliffInfo = entry.getValue(); + + final GenericResource resource = viewer.loadMapGeneric(path, FetchDataTypeName.ARRAY_BUFFER, + viewer.streamDataCallback); + + this.cliffModels.add(new TerrainModel(viewer, (InputStream) resource.data, cliffInfo.locations, + cliffInfo.textures, cliffShader)); + } + } + + public boolean isCliff(final int column, final int row) { + if ((column < 1) || (column > (this.columns - 1)) || (row < 1) || (row > (this.rows - 1))) { + return false; + } + + final Corner[][] corners = this.corners; + final int bottomLeft = corners[row][column].getLayerHeight(); + final int bottomRight = corners[row][column + 1].getLayerHeight(); + final int topLeft = corners[row + 1][column].getLayerHeight(); + final int topRight = corners[row + 1][column + 1].getLayerHeight(); + + return (bottomLeft != bottomRight) || (bottomLeft != topLeft) || (bottomLeft != topRight); + } + + public short isWater(final int column, final int row) { + return ((this.corners[row][column].getWater() != 0) || (this.corners[row][column + 1].getWater() != 0) + || (this.corners[row + 1][column].getWater() != 0) + || (this.corners[row + 1][column + 1].getWater() != 0)) ? (short) 1 : (short) 0; + } + + public String cliffFileName(final int bottomLeftLayer, final int bottomRightLayer, final int topLeftLayer, + final int topRightLayer, final int base) { + return Character.toString((char) ((65 + bottomLeftLayer) - base)) + + Character.toString((char) ((65 + topLeftLayer) - base)) + + Character.toString((char) ((65 + topRightLayer) - base)) + + Character.toString((char) ((65 + bottomRightLayer) - base)); + } + + public short getVariation(final int groundTexture, final int variation) { + final Texture texture = this.tilesetTextures.get(groundTexture); + + // Extended ? + if (texture.getWidth() > texture.getHeight()) { + if (variation < 16) { + return (short) (16 + variation); + } + else if (variation == 16) { + return 15; + } + else { + return 0; + } + } + else { + if (variation == 0) { + return 0; + } + else { + return 15; + } + } + } + + public int cliffGroundIndex(final int whichCliff) { + final String whichTile = this.cliffTilesets.get(whichCliff).get("groundTile").toString(); + final List tilesets = this.tilesets; + + for (int i = 0, l = tilesets.size(); i < l; i++) { + if (tilesets.get(i).get("tileID").toString().equals(whichTile)) { + return i; + } + } + throw new IllegalArgumentException(Integer.toString(whichCliff)); + } + + public int cornerTexture(final int column, final int row) { + final Corner[][] corners = this.corners; + final int columns = this.columns; + final int rows = this.rows; + + for (int y = -1; y < 1; y++) { + for (int x = -1; x < 1; x++) { + if (((column + x) > 0) && ((column + x) < (columns - 1)) && ((row + y) > 0) + && ((row + y) < (rows - 1))) { + if (this.isCliff(column + x, row + y)) { + int texture = corners[row + y][column + x].getCliffTexture(); + + if (texture == 15) { + texture = 1; + } + + return this.cliffGroundIndex(texture); + } + } + } + } + + final Corner corner = corners[row][column]; + if (corner.getBlight() != 0) { + return this.blightTextureIndex; + } + return corner.getGroundTexture(); + } + + public void update() { + this.waterIndex += this.waterIncreasePerFrame; + + if (this.waterIndex >= this.waterTextures.size()) { + this.waterIndex = 0; + } + } + + public void renderGround(final GL20 gl, final WebGL webgl, final Scene worldScene) { + final ANGLEInstancedArrays instancedArrays = webgl.instancedArrays; + final ShaderProgram shader = this.groundShader; + final List tilesetTextures = this.tilesetTextures; + final int instanceAttrib = shader.getAttributeLocation("a_InstanceID"); + final int positionAttrib = shader.getAttributeLocation("a_position"); + final int texturesAttrib = shader.getAttributeLocation("a_textures"); + final int variationsAttrib = shader.getAttributeLocation("a_variations"); + final int tilesetCount = tilesetTextures.size(); + + gl.glEnable(GL20.GL_BLEND); + gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA); + + webgl.useShaderProgram(shader); + + shader.setUniformMatrix("u_VP", worldScene.camera.viewProjectionMatrix); + shader.setUniform2fv("u_offset", this.centerOffset, 0, 2); + sizeHeap[0] = this.columns; + sizeHeap[1] = this.rows; + shader.setUniform2fv("u_size", sizeHeap, 0, 2); + shader.setUniformi("u_heightMap", 15); + + gl.glActiveTexture(GL20.GL_TEXTURE15); + gl.glBindTexture(GL20.GL_TEXTURE_2D, this.heightMap); + + gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.vertexBuffer); + shader.setVertexAttribute(positionAttrib, 2, GL20.GL_FLOAT, false, 8, 0); + + gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.instanceBuffer); + shader.setVertexAttribute(instanceAttrib, 1, GL20.GL_FLOAT, false, 4, 0); + instancedArrays.glVertexAttribDivisorANGLE(instanceAttrib, 1); + + gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.textureBuffer); + shader.setVertexAttribute(texturesAttrib, 4, GL20.GL_UNSIGNED_BYTE, false, 4, 0); + instancedArrays.glVertexAttribDivisorANGLE(texturesAttrib, 1); + + gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.variationBuffer); + shader.setVertexAttribute(variationsAttrib, 4, GL20.GL_UNSIGNED_BYTE, false, 4, 0); + instancedArrays.glVertexAttribDivisorANGLE(variationsAttrib, 1); + + gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, this.faceBuffer); + + shader.setUniformi("u_baseTileset", 0); + + for (int i = 0, l = Math.min(tilesetCount, 15); i < l; i++) { + final int isExtended = (tilesetTextures.get(i).getWidth() > tilesetTextures.get(i).getHeight()) ? 1 : 0; + + shader.setUniformf("u_extended[" + i + "]", isExtended); + shader.setUniformi("u_tilesets[" + i + "]", i); + + tilesetTextures.get(i).bind(i); + } + + instancedArrays.glDrawElementsInstancedANGLE(GL20.GL_TRIANGLES, 6, GL20.GL_UNSIGNED_BYTE, 0, + this.rows * this.columns); + + if (tilesetCount > 15) { + shader.setUniformi("u_baseTileset", 15); + + for (int i = 0, l = tilesetCount - 15; i < l; i++) { + final int isExtended = (tilesetTextures.get(i + 15).getWidth() > tilesetTextures.get(i + 15) + .getHeight()) ? 1 : 0; + + shader.setUniformf("u_extended[" + i + "]", isExtended); + + tilesetTextures.get(i + 15).bind(i); + } + + instancedArrays.glDrawElementsInstancedANGLE(GL20.GL_TRIANGLES, 6, GL20.GL_UNSIGNED_BYTE, 0, + this.rows * this.columns); + } + + instancedArrays.glVertexAttribDivisorANGLE(texturesAttrib, 0); + instancedArrays.glVertexAttribDivisorANGLE(variationsAttrib, 0); + instancedArrays.glVertexAttribDivisorANGLE(instanceAttrib, 0); + } + + public void renderWater(final GL20 gl, final WebGL webgl, final Scene worldScene) { + final ANGLEInstancedArrays instancedArrays = webgl.instancedArrays; + final ShaderProgram shader = this.waterShader; + final int instanceAttrib = shader.getAttributeLocation("a_InstanceID"); + final int positionAttrib = shader.getAttributeLocation("a_position"); + final int isWaterAttrib = shader.getAttributeLocation("a_isWater"); + + gl.glDepthMask(false); + + gl.glEnable(GL20.GL_BLEND); + gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA); + + webgl.useShaderProgram(shader); + + shader.setUniformMatrix("u_VP", worldScene.camera.viewProjectionMatrix); + shader.setUniform2fv("u_offset", this.centerOffset, 0, 2); + sizeHeap[0] = this.columns; + sizeHeap[1] = this.rows; + shader.setUniform2fv("u_size", sizeHeap, 0, 2); + shader.setUniformi("u_heightMap", 0); + shader.setUniformi("u_waterHeightMap", 1); + shader.setUniformi("u_waterTexture", 2); + shader.setUniformf("u_offsetHeight", this.waterHeightOffset); + shader.setUniform4fv("u_maxDeepColor", this.maxDeepColor, 0, 4); + shader.setUniform4fv("u_minDeepColor", this.minDeepColor, 0, 4); + shader.setUniform4fv("u_maxShallowColor", this.maxShallowColor, 0, 4); + shader.setUniform4fv("u_minShallowColor", this.minShallowColor, 0, 4); + + gl.glActiveTexture(GL20.GL_TEXTURE0); + gl.glBindTexture(GL20.GL_TEXTURE_2D, this.heightMap); + + gl.glActiveTexture(GL20.GL_TEXTURE1); + gl.glBindTexture(GL20.GL_TEXTURE_2D, this.waterHeightMap); + + this.waterTextures.get((int) this.waterIndex).bind(2); + + gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.vertexBuffer); + shader.setVertexAttribute(positionAttrib, 2, GL20.GL_FLOAT, false, 8, 0); + + gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.instanceBuffer); + shader.setVertexAttribute(instanceAttrib, 1, GL20.GL_FLOAT, false, 4, 0); + instancedArrays.glVertexAttribDivisorANGLE(instanceAttrib, 1); + + gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.waterBuffer); + shader.setVertexAttribute(isWaterAttrib, 1, GL20.GL_UNSIGNED_BYTE, false, 1, 0); + instancedArrays.glVertexAttribDivisorANGLE(isWaterAttrib, 1); + + gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, this.faceBuffer); + instancedArrays.glDrawElementsInstancedANGLE(GL20.GL_TRIANGLES, 6, GL20.GL_UNSIGNED_BYTE, 0, + this.rows * this.columns); + + instancedArrays.glVertexAttribDivisorANGLE(isWaterAttrib, 0); + instancedArrays.glVertexAttribDivisorANGLE(instanceAttrib, 0); + } + + public void renderCliffs(final GL20 gl, final WebGL webGL, final Scene worldScene) { + final ANGLEInstancedArrays instancedArrays = webGL.instancedArrays; + final ShaderProgram shader = this.cliffShader; + + gl.glDisable(GL20.GL_BLEND); + + webGL.useShaderProgram(shader); + + shader.setUniformMatrix("u_VP", worldScene.camera.viewProjectionMatrix); + shader.setUniformi("u_heightMap", 0); + shader.setUniformf("u_pixel[0]", 1 / (float) (this.columns + 1)); + shader.setUniformf("u_pixel[1]", 1 / (float) (this.rows + 1)); + shader.setUniform2fv("u_centerOffset", this.centerOffset, 0, 2); + shader.setUniformi("u_texture1", 1); + shader.setUniformi("u_texture2", 2); + + gl.glActiveTexture(GL20.GL_TEXTURE0); + gl.glBindTexture(GL20.GL_TEXTURE_2D, this.cliffHeightMap); + + gl.glActiveTexture(GL20.GL_TEXTURE1); + gl.glBindTexture(GL20.GL_TEXTURE_2D, this.cliffTextures.get(0).getGlTarget()); + + if (this.cliffTextures.size() > 1) { + gl.glActiveTexture(GL20.GL_TEXTURE2); + gl.glBindTexture(GL20.GL_TEXTURE_2D, this.cliffTextures.get(1).getGlTarget()); + } + + // Set instanced attributes. + for (final TerrainModel cliff : this.cliffModels) { + cliff.render(shader); + } + } + + public Vector3 groundNormal(final Vector3 out, int x, int y) { + final float[] centerOffset = this.centerOffset; + final int[] mapSize = this.mapSize; + + x = (int) ((x - centerOffset[0]) / 128); + y = (int) ((y - centerOffset[1]) / 128); + + final int cellX = x; + final int cellY = y; + + // See if this coordinate is in the map + + if ((cellX >= 0) && (cellX < (mapSize[0] - 1)) && (cellY >= 0) && (cellY < (mapSize[1] - 1))) { + // See http://gamedev.stackexchange.com/a/24574 + final Corner[][] corners = this.corners; + final float bottomLeft = corners[cellY][cellX].getGroundHeight(); + final float bottomRight = corners[cellY][cellX + 1].getGroundHeight(); + final float topLeft = corners[cellY + 1][cellX].getGroundHeight(); + final float topRight = corners[cellY + 1][cellX + 1].getGroundHeight(); + final int sqX = x - cellX; + final int sqY = y - cellY; + + if ((sqX + sqY) < 1) { + normalHeap1.set(1, 0, bottomRight - bottomLeft); + normalHeap2.set(0, 1, topLeft - bottomLeft); + } + else { + normalHeap1.set(-1, 0, topRight - topLeft); + normalHeap2.set(0, 1, topRight - bottomRight); + } + + out.set(normalHeap1.crs(normalHeap2)).nor(); + } + else { + out.set(0, 0, 1); + } + + return out; + } + +} From 46942c2d8dff87eb86c2e9d986db7b2c60e19460 Mon Sep 17 00:00:00 2001 From: Retera Date: Thu, 23 Jan 2020 01:42:49 -0600 Subject: [PATCH 015/116] Sample test with ReforgedGeorgeVacation is beginning to work as a viewer --- .../etheller/warsmash/WarsmashGdxMapGame.java | 5 +- .../warsmash/parsers/w3x/w3e/Corner.java | 19 + .../etheller/warsmash/units/GameObject.java | 2 + .../warsmash/units/HashedGameObject.java | 12 + .../warsmash/units/StandardObjectData.java | 11 + .../etheller/warsmash/util/ImageUtils.java | 34 + .../warsmash/util/RenderMathUtils.java | 34 + .../warsmash/viewer5/ModelViewer.java | 2 +- .../warsmash/viewer5/gl/Extensions.java | 2 + .../viewer5/handlers/w3x/Variations.java | 4 +- .../viewer5/handlers/w3x/War3MapViewer.java | 55 +- .../handlers/w3x/environment/CliffMesh.java | 111 ++ .../w3x/environment/GroundTexture.java | 59 + .../w3x/{ => environment}/HiveWEShaders.java | 18 +- .../handlers/w3x/environment/IVec3.java | 38 + .../w3x/environment/RenderCorner.java | 13 + .../handlers/w3x/environment/Shapes.java | 32 + .../handlers/w3x/environment/Terrain.java | 1123 ++++++++++------- .../warsmash/desktop/DesktopLauncher.java | 2 + 19 files changed, 1082 insertions(+), 494 deletions(-) create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/CliffMesh.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/GroundTexture.java rename core/src/com/etheller/warsmash/viewer5/handlers/w3x/{ => environment}/HiveWEShaders.java (91%) create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/IVec3.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/RenderCorner.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Shapes.java diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java index 93b47d2..9fe4927 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java @@ -74,7 +74,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.cameraManager.setupCamera(this.viewer.worldScene); System.out.println("Loaded"); - Gdx.gl30.glClearColor(0.0f, 0.0f, 0.0f, 1); // TODO remove white background + Gdx.gl30.glClearColor(0.5f, 0.5f, 0.5f, 1); // TODO remove white background this.font = new BitmapFont(); this.batch = new SpriteBatch(); @@ -248,6 +248,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv @Override public boolean scrolled(final int amount) { - return false; + this.cameraManager.distance += amount * 10.0; + return true; } } diff --git a/core/src/com/etheller/warsmash/parsers/w3x/w3e/Corner.java b/core/src/com/etheller/warsmash/parsers/w3x/w3e/Corner.java index 7bac45b..4e3b298 100644 --- a/core/src/com/etheller/warsmash/parsers/w3x/w3e/Corner.java +++ b/core/src/com/etheller/warsmash/parsers/w3x/w3e/Corner.java @@ -23,6 +23,25 @@ public class Corner { private int cliffTexture; private int layerHeight; + public Corner() { + // TODO Auto-generated constructor stub + } + + public Corner(final Corner other) { + this.groundHeight = other.groundHeight; + this.waterHeight = other.waterHeight; + this.mapEdge = other.mapEdge; + this.ramp = other.ramp; + this.blight = other.blight; + this.water = other.water; + this.boundary = other.boundary; + this.groundTexture = other.groundTexture; + this.cliffVariation = other.cliffVariation; + this.groundVariation = other.groundVariation; + this.cliffTexture = other.cliffTexture; + this.layerHeight = other.layerHeight; + } + public void load(final LittleEndianDataInputStream stream) throws IOException { this.groundHeight = (stream.readShort() - 8192) / (float) 512; diff --git a/core/src/com/etheller/warsmash/units/GameObject.java b/core/src/com/etheller/warsmash/units/GameObject.java index d3b9de9..191cec2 100644 --- a/core/src/com/etheller/warsmash/units/GameObject.java +++ b/core/src/com/etheller/warsmash/units/GameObject.java @@ -17,6 +17,8 @@ public interface GameObject { public int getFieldValue(String field, int index); + public float getFieldFloatValue(String field); + public List getFieldAsList(String field, ObjectData objectData); public String getId(); diff --git a/core/src/com/etheller/warsmash/units/HashedGameObject.java b/core/src/com/etheller/warsmash/units/HashedGameObject.java index 396449a..a138344 100644 --- a/core/src/com/etheller/warsmash/units/HashedGameObject.java +++ b/core/src/com/etheller/warsmash/units/HashedGameObject.java @@ -67,6 +67,18 @@ public abstract class HashedGameObject implements GameObject { return i; } + @Override + public float getFieldFloatValue(final String field) { + float i = 0; + try { + i = Float.parseFloat(getField(field)); + } + catch (final NumberFormatException e) { + + } + return i; + } + @Override public void setField(final String field, final String value, final int index) { final StringKey key = new StringKey(field); diff --git a/core/src/com/etheller/warsmash/units/StandardObjectData.java b/core/src/com/etheller/warsmash/units/StandardObjectData.java index 7c9953d..34c71f7 100644 --- a/core/src/com/etheller/warsmash/units/StandardObjectData.java +++ b/core/src/com/etheller/warsmash/units/StandardObjectData.java @@ -493,6 +493,17 @@ public class StandardObjectData { return 0; } + @Override + public float getFieldFloatValue(final String field) { + for (final DataTable table : this.dataSource.getTables()) { + final Element element = table.get(this.id); + if ((element != null) && element.hasField(field)) { + return element.getFieldFloatValue(field); + } + } + return 0f; + } + /* * (non-Javadoc) I'm not entirely sure this is still safe to use * diff --git a/core/src/com/etheller/warsmash/util/ImageUtils.java b/core/src/com/etheller/warsmash/util/ImageUtils.java index 9772bee..c402cf0 100644 --- a/core/src/com/etheller/warsmash/util/ImageUtils.java +++ b/core/src/com/etheller/warsmash/util/ImageUtils.java @@ -6,6 +6,9 @@ import java.awt.image.BufferedImage; import java.awt.image.ColorModel; import java.awt.image.ComponentColorModel; import java.awt.image.DataBuffer; +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; import com.badlogic.gdx.graphics.GL30; import com.badlogic.gdx.graphics.Pixmap; @@ -46,6 +49,37 @@ public final class ImageUtils { return texture; } + public static Buffer getTextureBuffer(final BufferedImage image) { + + final int imageWidth = image.getWidth(); + final int imageHeight = image.getHeight(); + final int[] pixels = new int[imageWidth * imageHeight]; + image.getRGB(0, 0, imageWidth, imageHeight, pixels, 0, imageWidth); + + final ByteBuffer buffer = ByteBuffer.allocateDirect(imageWidth * imageHeight * BYTES_PER_PIXEL) + .order(ByteOrder.nativeOrder()); + // 4 + // for + // RGBA, + // 3 + // for + // RGB + + for (int y = 0; y < imageHeight; y++) { + for (int x = 0; x < imageWidth; x++) { + final int pixel = pixels[(y * imageWidth) + x]; + buffer.put((byte) ((pixel >> 16) & 0xFF)); // Red component + buffer.put((byte) ((pixel >> 8) & 0xFF)); // Green component + buffer.put((byte) (pixel & 0xFF)); // Blue component + buffer.put((byte) ((pixel >> 24) & 0xFF)); // Alpha component. + // Only for RGBA + } + } + + buffer.flip(); + return buffer; + } + /** * Convert an input buffered image into sRGB color space using component values * directly instead of performing a color space conversion. diff --git a/core/src/com/etheller/warsmash/util/RenderMathUtils.java b/core/src/com/etheller/warsmash/util/RenderMathUtils.java index 79c96f7..c27c314 100644 --- a/core/src/com/etheller/warsmash/util/RenderMathUtils.java +++ b/core/src/com/etheller/warsmash/util/RenderMathUtils.java @@ -4,6 +4,7 @@ import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; +import java.nio.IntBuffer; import java.nio.ShortBuffer; import java.util.List; @@ -483,4 +484,37 @@ public enum RenderMathUtils { wrapper.clear(); return wrapper; } + + public static Buffer wrapShort(final short[] cornerTextures) { + final ByteBuffer wrapper = ByteBuffer.allocateDirect(cornerTextures.length * 2).order(ByteOrder.nativeOrder()); + for (final short face : cornerTextures) { + wrapper.putShort(face); + } + wrapper.clear(); + return wrapper; + } + + public static Buffer wrapPairs(final float[][] quadVertices) { + final FloatBuffer wrapper = ByteBuffer.allocateDirect(quadVertices.length * 8).order(ByteOrder.nativeOrder()) + .asFloatBuffer(); + for (int i = 0; i < quadVertices.length; i++) { + for (int j = 0; j < 2; j++) { + wrapper.put(quadVertices[i][j]); + } + } + wrapper.clear(); + return wrapper; + } + + public static Buffer wrap(final int[][] quadIndices) { + final IntBuffer wrapper = ByteBuffer.allocateDirect(quadIndices.length * 3 * 4).order(ByteOrder.nativeOrder()) + .asIntBuffer(); + for (int i = 0; i < quadIndices.length; i++) { + for (int j = 0; j < 3; j++) { + wrapper.put(quadIndices[i][j]); + } + } + wrapper.clear(); + return wrapper; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/ModelViewer.java b/core/src/com/etheller/warsmash/viewer5/ModelViewer.java index fbe86a5..806fc5e 100644 --- a/core/src/com/etheller/warsmash/viewer5/ModelViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/ModelViewer.java @@ -21,7 +21,7 @@ import com.etheller.warsmash.viewer5.handlers.ResourceHandler; import com.etheller.warsmash.viewer5.handlers.ResourceHandlerConstructionParams; public class ModelViewer { - private DataSource dataSource; + public DataSource dataSource; public final CanvasProvider canvas; public List resources; public Map fetchCache; diff --git a/core/src/com/etheller/warsmash/viewer5/gl/Extensions.java b/core/src/com/etheller/warsmash/viewer5/gl/Extensions.java index 9bec9da..56459e5 100644 --- a/core/src/com/etheller/warsmash/viewer5/gl/Extensions.java +++ b/core/src/com/etheller/warsmash/viewer5/gl/Extensions.java @@ -2,4 +2,6 @@ package com.etheller.warsmash.viewer5.gl; public class Extensions { public static ANGLEInstancedArrays angleInstancedArrays; + + public static int GL_BGRA; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Variations.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Variations.java index d22f348..f2133c0 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Variations.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Variations.java @@ -4,8 +4,8 @@ import java.util.HashMap; import java.util.Map; public class Variations { - private static final Map CLIFF_VARS; - private static final Map CITY_CLIFF_VARS; + public static final Map CLIFF_VARS; + public static final Map CITY_CLIFF_VARS; static { final Map cliffVariations = new HashMap<>(); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index a3b2e36..be773b1 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -5,12 +5,19 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; +import java.nio.channels.SeekableByteChannel; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import org.apache.commons.compress.utils.IOUtils; +import org.apache.commons.compress.utils.SeekableInMemoryByteChannel; + import com.etheller.warsmash.common.FetchDataTypeName; import com.etheller.warsmash.common.LoadGenericCallback; +import com.etheller.warsmash.datasources.CompoundDataSource; import com.etheller.warsmash.datasources.DataSource; +import com.etheller.warsmash.datasources.MpqDataSource; import com.etheller.warsmash.parsers.w3x.War3Map; import com.etheller.warsmash.parsers.w3x.doo.War3MapDoo; import com.etheller.warsmash.parsers.w3x.objectdata.Warcraft3MapObjectData; @@ -22,6 +29,7 @@ import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; import com.etheller.warsmash.units.manager.MutableObjectData.WorldEditorDataType; import com.etheller.warsmash.util.MappedData; import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.util.WorldEditStrings; import com.etheller.warsmash.viewer5.CanvasProvider; import com.etheller.warsmash.viewer5.GenericResource; import com.etheller.warsmash.viewer5.Grid; @@ -35,6 +43,9 @@ import com.etheller.warsmash.viewer5.handlers.mdx.MdxHandler; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; import com.etheller.warsmash.viewer5.handlers.w3x.environment.Terrain; +import mpq.MPQArchive; +import mpq.MPQException; + public class War3MapViewer extends ModelViewer { private static final War3ID sloc = War3ID.fromString("sloc"); private static final LoadGenericCallback stringDataCallback = new StringDataCallbackImplementation(); @@ -67,6 +78,10 @@ public class War3MapViewer extends ModelViewer { private final DataSource gameDataSource; + public Terrain terrain; + public int renderPathing = 0; + public int renderLighting = 0; + public War3MapViewer(final DataSource dataSource, final CanvasProvider canvas) { super(dataSource, canvas); this.gameDataSource = dataSource; @@ -77,8 +92,6 @@ public class War3MapViewer extends ModelViewer { this.wc3PathSolver = PathSolver.DEFAULT; - this.terrain = new Terrain(webGL); - this.worldScene = this.addScene(); loadSLKs(); @@ -141,7 +154,7 @@ public class War3MapViewer extends ModelViewer { return loadGeneric(path, dataType, callback); } else { - return loadGeneric(path, dataType, callback, this.mapMpq.getCompoundDataSource()); + return loadGeneric(path, dataType, callback, this.dataSource); } } @@ -149,7 +162,6 @@ public class War3MapViewer extends ModelViewer { final War3Map war3Map = new War3Map(this.gameDataSource, mapFilePath); this.mapMpq = war3Map; - setDataSource(war3Map.getCompoundDataSource()); // loadSLKs(); @@ -161,13 +173,39 @@ public class War3MapViewer extends ModelViewer { tileset = w3iFile.getTileset(); + final DataSource tilesetSource; + try { + // Slightly complex. Here's the theory: + // 1.) Copy map into RAM + // 2.) Setup a Data Source that will read assets + // from either the map or the game, giving the map priority. + SeekableByteChannel sbc; + try (InputStream mapStream = war3Map.getCompoundDataSource().getResourceAsStream(tileset + ".mpq")) { + final byte[] mapData = IOUtils.toByteArray(mapStream); + sbc = new SeekableInMemoryByteChannel(mapData); + final DataSource internalMpqContentsDataSource = new MpqDataSource(new MPQArchive(sbc), sbc); + tilesetSource = new CompoundDataSource( + Arrays.asList(war3Map.getCompoundDataSource(), internalMpqContentsDataSource)); + } + } + catch (final IOException e) { + throw new RuntimeException(e); + } + catch (final MPQException e) { + throw new RuntimeException(e); + } + setDataSource(tilesetSource); + this.solverParams.tileset = Character.toLowerCase(tileset); final War3MapW3e terrainData = this.mapMpq.readEnvironment(); + + this.terrain = new Terrain(terrainData, this.webGL, this.dataSource, new WorldEditStrings(this.dataSource), + this); + final float[] centerOffset = terrainData.getCenterOffset(); final int[] mapSize = terrainData.getMapSize(); - this.terrain.load(terrainData, centerOffset, mapSize, this); this.terrainReady = true; this.anyReady = true; this.cliffsReady = true; @@ -359,10 +397,10 @@ public class War3MapViewer extends ModelViewer { final Scene worldScene = this.worldScene; worldScene.startFrame(); - this.terrain.renderGround(this.gl, this.webGL, worldScene); - this.terrain.renderCliffs(this.gl, this.webGL, worldScene); + this.terrain.renderGround(); +// this.terrain.renderCliffs(); worldScene.renderOpaque(); - this.terrain.renderWater(this.gl, this.webGL, worldScene); + this.terrain.renderWater(); worldScene.renderTranslucent(); } } @@ -431,7 +469,6 @@ public class War3MapViewer extends ModelViewer { } private static final int MAXIMUM_ACCEPTED = 1 << 30; - public final Terrain terrain; /** * Returns a power of two size for the given target capacity. diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/CliffMesh.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/CliffMesh.java new file mode 100644 index 0000000..cffa014 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/CliffMesh.java @@ -0,0 +1,111 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.environment; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; + +import com.badlogic.gdx.graphics.GL20; +import com.badlogic.gdx.graphics.GL30; +import com.etheller.warsmash.datasources.DataSource; +import com.etheller.warsmash.parsers.mdlx.Geoset; +import com.etheller.warsmash.parsers.mdlx.MdlxModel; +import com.etheller.warsmash.util.RenderMathUtils; + +public class CliffMesh { + public int vertexBuffer; + public int uvBuffer; + public int normalBuffer; + public int indexBuffer; + public int instanceBuffer; + public int indices; + + private FloatBuffer renderJobs = ByteBuffer.allocateDirect(16 * 16).order(ByteOrder.nativeOrder()).asFloatBuffer(); + private final GL30 gl; + + public CliffMesh(final String path, final DataSource dataSource, final GL30 gl) throws IOException { + this.gl = gl; + if (path.endsWith(".mdx") || path.endsWith(".MDX")) { + MdlxModel model; + try (InputStream stream = dataSource.getResourceAsStream(path)) { + model = new MdlxModel(stream); + } + final Geoset geoset = model.getGeosets().get(0); + + this.vertexBuffer = gl.glGenBuffer(); + gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.vertexBuffer); + gl.glBufferData(GL20.GL_ARRAY_BUFFER, geoset.getVertices().length * 4, + RenderMathUtils.wrap(geoset.getVertices()), GL20.GL_STATIC_DRAW); + + this.uvBuffer = gl.glGenBuffer(); + gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.uvBuffer); + gl.glBufferData(GL20.GL_ARRAY_BUFFER, geoset.getUvSets()[0].length * 4, + RenderMathUtils.wrap(geoset.getUvSets()[0]), GL20.GL_STATIC_DRAW); + + this.normalBuffer = gl.glGenBuffer(); + gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.normalBuffer); + gl.glBufferData(GL20.GL_ARRAY_BUFFER, geoset.getNormals().length * 4, + RenderMathUtils.wrap(geoset.getNormals()), GL20.GL_STATIC_DRAW); + + this.instanceBuffer = gl.glGenBuffer(); + + this.indices = geoset.getFaces().length; + this.indexBuffer = gl.glGenBuffer(); + gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.indexBuffer); + gl.glBufferData(GL20.GL_ARRAY_BUFFER, geoset.getFaces().length * 2, + RenderMathUtils.wrapFaces(geoset.getFaces()), GL20.GL_STATIC_DRAW); + } + } + + public void renderQueue(final float[] position) { + if (this.renderJobs.remaining() < 4) { + final FloatBuffer newRenderJobs = ByteBuffer.allocateDirect(this.renderJobs.capacity() * 2) + .order(ByteOrder.nativeOrder()).asFloatBuffer(); + this.renderJobs.flip(); + newRenderJobs.put(this.renderJobs); + this.renderJobs = newRenderJobs; + } + this.renderJobs.put(position); + } + + public void render() { + if (this.renderJobs.position() == 0) { + return; + } + this.renderJobs.flip(); + + this.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.instanceBuffer); + this.gl.glBufferData(GL20.GL_ARRAY_BUFFER, this.renderJobs.remaining() * 4, this.renderJobs, + GL20.GL_DYNAMIC_DRAW); + + this.gl.glEnableVertexAttribArray(0); + this.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.vertexBuffer); + this.gl.glVertexAttribPointer(0, 3, GL20.GL_FLOAT, false, 0, 0); + + this.gl.glEnableVertexAttribArray(1); + this.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.uvBuffer); + this.gl.glVertexAttribPointer(1, 2, GL20.GL_FLOAT, false, 0, 0); + + this.gl.glEnableVertexAttribArray(2); + this.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.normalBuffer); + this.gl.glVertexAttribPointer(2, 3, GL20.GL_FLOAT, false, 0, 0); + + this.gl.glEnableVertexAttribArray(3); + this.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.normalBuffer); + this.gl.glVertexAttribPointer(3, 4, GL20.GL_FLOAT, false, 0, 0); + this.gl.glVertexAttribDivisor(3, 1); + + this.gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, this.indexBuffer); + this.gl.glDrawElementsInstanced(GL20.GL_TRIANGLES, this.indices, GL30.GL_UNSIGNED_SHORT, 0, + this.renderJobs.remaining() / 4); + + this.gl.glVertexAttribDivisor(3, 0); // ToDo use vao + this.gl.glDisableVertexAttribArray(0); + this.gl.glDisableVertexAttribArray(1); + this.gl.glDisableVertexAttribArray(2); + this.gl.glDisableVertexAttribArray(3); + + this.renderJobs.clear(); + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/GroundTexture.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/GroundTexture.java new file mode 100644 index 0000000..71f92d1 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/GroundTexture.java @@ -0,0 +1,59 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.environment; + +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.InputStream; +import java.nio.Buffer; + +import javax.imageio.ImageIO; + +import com.badlogic.gdx.graphics.GL30; +import com.etheller.warsmash.datasources.DataSource; +import com.etheller.warsmash.util.ImageUtils; +import com.etheller.warsmash.viewer5.gl.Extensions; + +public class GroundTexture { + public int id; + private int tileSize; + public boolean extended; + + public GroundTexture(final String path, final DataSource dataSource, final GL30 gl) throws IOException { + if (path.toLowerCase().endsWith(".blp")) { + try (InputStream stream = dataSource.getResourceAsStream(path)) { + final BufferedImage image = ImageIO.read(stream); + final Buffer buffer = ImageUtils.getTextureBuffer(ImageUtils.forceBufferedImagesRGB(image)); + final int width = image.getWidth(); + final int height = image.getHeight(); + + this.tileSize = (int) (height * 0.25); + this.extended = (width > height); + + this.id = gl.glGenTexture(); + gl.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.id); + gl.glTexImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, GL30.GL_RGBA8, this.tileSize, this.tileSize, + this.extended ? 32 : 16, 0, Extensions.GL_BGRA, GL30.GL_UNSIGNED_BYTE, null); + gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_LINEAR_MIPMAP_LINEAR); + gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_WRAP_S, GL30.GL_CLAMP_TO_EDGE); + gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_WRAP_T, GL30.GL_CLAMP_TO_EDGE); + + gl.glPixelStorei(GL30.GL_UNPACK_ROW_LENGTH, width); + for (int y = 0; y < 4; y++) { + for (int x = 0; x < 4; x++) { + buffer.position(((y * this.tileSize * width) + (x * this.tileSize)) * 4); + gl.glTexSubImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, 0, 0, (y * 4) + x, this.tileSize, this.tileSize, + 1, GL30.GL_RGBA, GL30.GL_UNSIGNED_BYTE, buffer); + + if (this.extended) { + buffer.position(((y * this.tileSize * width) + ((x + 4) * this.tileSize)) * 4); + gl.glTexSubImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, 0, 0, (y * 4) + x + 16, this.tileSize, + this.tileSize, 1, GL30.GL_RGBA, GL30.GL_UNSIGNED_BYTE, buffer); + } + } + } + gl.glPixelStorei(GL30.GL_UNPACK_ROW_LENGTH, 0); + gl.glGenerateMipmap(GL30.GL_TEXTURE_2D_ARRAY); + } + } + + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/HiveWEShaders.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/HiveWEShaders.java similarity index 91% rename from core/src/com/etheller/warsmash/viewer5/handlers/w3x/HiveWEShaders.java rename to core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/HiveWEShaders.java index 46310b3..16d7d21 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/HiveWEShaders.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/HiveWEShaders.java @@ -1,4 +1,4 @@ -package com.etheller.warsmash.viewer5.handlers.w3x; +package com.etheller.warsmash.viewer5.handlers.w3x.environment; public class HiveWEShaders { public static final class Cliffs { @@ -87,11 +87,14 @@ public class HiveWEShaders { "layout (binding = 0) uniform sampler2D height_texture;\r\n" + // "layout (binding = 1) uniform sampler2D height_cliff_texture;\r\n" + // "layout (binding = 2) uniform usampler2D terrain_texture_list;\r\n" + // + "layout (location = 4) uniform float centerOffsetX;\r\n" + // + "layout (location = 5) uniform float centerOffsetY;\r\n" + // "\r\n" + // "layout (location = 0) out vec2 UV;\r\n" + // "layout (location = 1) out flat uvec4 texture_indices;\r\n" + // "layout (location = 2) out vec2 pathing_map_uv;\r\n" + // "layout (location = 3) out vec3 normal;\r\n" + // + "layout (location = 6) out float v2Position;\r\n" + // "\r\n" + // "void main() { \r\n" + // " ivec2 size = textureSize(terrain_texture_list, 0);\r\n" + // @@ -112,8 +115,9 @@ public class HiveWEShaders { " pathing_map_uv = (vPosition + pos) * 4; \r\n" + // "\r\n" + // " // Cliff culling\r\n" + // - " gl_Position = ((texture_indices.a & 32768) == 0) ? MVP * vec4(vPosition + pos, height.r, 1) : vec4(2.0, 0.0, 0.0, 1.0);\r\n" + " gl_Position = ((texture_indices.a & 32768) == 0) ? MVP * vec4((vPosition.x + pos.x)*128.0 + centerOffsetX, (vPosition.y + pos.y)*128.0 + centerOffsetY, height.r*128.0, 1) : vec4(2.0, 0.0, 0.0, 1.0);\r\n" + // +// " v2Position = float(texture_indices.r+texture_indices.b+texture_indices.g+texture_indices.a);\r\n" + // "}"; public static final String frag = "#version 450 core\r\n" + // @@ -146,6 +150,7 @@ public class HiveWEShaders { "layout (location = 1) in flat uvec4 texture_indices;\r\n" + // "layout (location = 2) in vec2 pathing_map_uv;\r\n" + // "layout (location = 3) in vec3 normal;\r\n" + // + "layout (location = 6) in float v2Position;\r\n" + // "\r\n" + // "layout (location = 0) out vec4 color;\r\n" + // "layout (location = 1) out vec4 position;\r\n" + // @@ -211,9 +216,9 @@ public class HiveWEShaders { " color.rgb *= clamp(dot(normal, light_direction) + 0.45, 0, 1);\r\n" + // " }\r\n" + // "\r\n" + // - " uint byte_static = texelFetch(pathing_map_static, ivec2(pathing_map_uv), 0).r;\r\n" + // - " uint byte_dynamic = texelFetch(pathing_map_dynamic, ivec2(pathing_map_uv), 0).r;\r\n" + // " if (show_pathing_map) {\r\n" + // + " uint byte_static = texelFetch(pathing_map_static, ivec2(pathing_map_uv), 0).r;\r\n" + // + " uint byte_dynamic = texelFetch(pathing_map_dynamic, ivec2(pathing_map_uv), 0).r;\r\n" + // " uint final = byte_static.r | byte_dynamic.r;\r\n" + // "\r\n" + // " vec4 pathing_static_color = vec4((final & 2) >> 1, (final & 4) >> 2, (final & 8) >> 3, 0.25);\r\n" @@ -222,6 +227,7 @@ public class HiveWEShaders { " color = length(pathing_static_color.rgb) > 0 ? color * 0.75 + pathing_static_color * 0.5 : color;\r\n" + // " }\r\n" + // +// " color = vec4(texture_indices.a,texture_indices.b,texture_indices.g,1.0);\r\n" + // "}"; } @@ -236,6 +242,8 @@ public class HiveWEShaders { "layout (binding = 0) uniform sampler2D water_height_texture;\r\n" + // "layout (binding = 1) uniform sampler2D ground_height_texture;\r\n" + // "layout (binding = 2) uniform sampler2D water_exists_texture;\r\n" + // + "layout (location = 7) uniform float centerOffsetX;\r\n" + // + "layout (location = 8) uniform float centerOffsetY;\r\n" + // "\r\n" + // "layout (location = 0) uniform mat4 MVP;\r\n" + // "layout (location = 1) uniform vec4 shallow_color_min;\r\n" + // @@ -262,7 +270,7 @@ public class HiveWEShaders { " || texelFetch(water_exists_texture, pos + ivec2(1, 1), 0).r > 0\r\n" + // " || texelFetch(water_exists_texture, pos + ivec2(0, 1), 0).r > 0;\r\n" + // "\r\n" + // - " gl_Position = is_water ? MVP * vec4(vPosition + pos, water_height, 1) : vec4(2.0, 0.0, 0.0, 1.0);\r\n" + " gl_Position = is_water ? MVP * vec4((vPosition.x + pos.x)*128.0 + centerOffsetX, (vPosition.y + pos.y)*128.0 + centerOffsetY, water_height*128.0, 1) : vec4(2.0, 0.0, 0.0, 1.0);\r\n" + // "\r\n" + // " UV = vec2(vPosition.x, 1 - vPosition.y);\r\n" + // diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/IVec3.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/IVec3.java new file mode 100644 index 0000000..08ef443 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/IVec3.java @@ -0,0 +1,38 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.environment; + +public class IVec3 { + public int x; + public int y; + public int z; + + public IVec3(final int x, final int y, final int z) { + this.x = x; + this.y = y; + this.z = z; + } + + public int getX() { + return this.x; + } + + public int getY() { + return this.y; + } + + public int getZ() { + return this.z; + } + + public void setX(final int x) { + this.x = x; + } + + public void setY(final int y) { + this.y = y; + } + + public void setZ(final int z) { + this.z = z; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/RenderCorner.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/RenderCorner.java new file mode 100644 index 0000000..be3239c --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/RenderCorner.java @@ -0,0 +1,13 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.environment; + +import com.etheller.warsmash.parsers.w3x.w3e.Corner; + +public class RenderCorner extends Corner { + public boolean cliff; + public boolean romp; + + public RenderCorner(final Corner corner) { + super(corner); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Shapes.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Shapes.java new file mode 100644 index 0000000..a227be9 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Shapes.java @@ -0,0 +1,32 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.environment; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.graphics.GL30; +import com.etheller.warsmash.util.RenderMathUtils; + +public class Shapes { + public static Shapes INSTANCE = new Shapes(); + static { + INSTANCE.init(); + } + + public int vertexBuffer; + public int indexBuffer; + + float[][] quadVertices = { { 1, 1 }, { 0, 1 }, { 0, 0 }, { 1, 0 } }; + + int[][] quadIndices = { { 0, 3, 1 }, { 1, 3, 2 } }; + + public void init() { + this.vertexBuffer = Gdx.gl30.glGenBuffer(); + Gdx.gl30.glBindBuffer(GL30.GL_ARRAY_BUFFER, this.vertexBuffer); + Gdx.gl30.glBufferData(GL30.GL_ARRAY_BUFFER, this.quadVertices.length * 8, + RenderMathUtils.wrapPairs(this.quadVertices), GL30.GL_STATIC_DRAW); + + this.indexBuffer = Gdx.gl30.glGenBuffer(); + Gdx.gl30.glBindBuffer(GL30.GL_ELEMENT_ARRAY_BUFFER, this.indexBuffer); + Gdx.gl30.glBufferData(GL30.GL_ELEMENT_ARRAY_BUFFER, this.quadIndices.length * 3 * 4, + RenderMathUtils.wrap(this.quadIndices), GL30.GL_STATIC_DRAW); + + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java index 5d4340e..a71fb70 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java @@ -1,44 +1,49 @@ package com.etheller.warsmash.viewer5.handlers.w3x.environment; +import java.awt.image.BufferedImage; import java.io.IOException; import java.io.InputStream; +import java.nio.Buffer; import java.util.ArrayList; -import java.util.Collections; +import java.util.Arrays; import java.util.HashMap; -import java.util.LinkedHashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.TreeSet; -import com.badlogic.gdx.graphics.GL20; +import javax.imageio.ImageIO; + +import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.GL30; import com.badlogic.gdx.graphics.glutils.ShaderProgram; +import com.badlogic.gdx.math.Intersector; +import com.badlogic.gdx.math.Matrix4; +import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Vector3; -import com.etheller.warsmash.common.FetchDataTypeName; import com.etheller.warsmash.datasources.DataSource; import com.etheller.warsmash.parsers.w3x.w3e.Corner; import com.etheller.warsmash.parsers.w3x.w3e.War3MapW3e; import com.etheller.warsmash.units.DataTable; import com.etheller.warsmash.units.Element; import com.etheller.warsmash.units.StandardObjectData; -import com.etheller.warsmash.util.MappedDataRow; +import com.etheller.warsmash.util.ImageUtils; import com.etheller.warsmash.util.RenderMathUtils; import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.util.WorldEditStrings; -import com.etheller.warsmash.viewer5.GenericResource; -import com.etheller.warsmash.viewer5.Scene; -import com.etheller.warsmash.viewer5.Texture; -import com.etheller.warsmash.viewer5.gl.ANGLEInstancedArrays; +import com.etheller.warsmash.viewer5.Camera; +import com.etheller.warsmash.viewer5.gl.Extensions; import com.etheller.warsmash.viewer5.gl.WebGL; -import com.etheller.warsmash.viewer5.handlers.w3x.TerrainModel; import com.etheller.warsmash.viewer5.handlers.w3x.Variations; -import com.etheller.warsmash.viewer5.handlers.w3x.W3xShaders; import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; -import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer.CliffInfo; public class Terrain { + private static final String[] colorTags = { "R", "G", "B", "A" }; private static final float[] sizeHeap = new float[2]; private static final Vector3 normalHeap1 = new Vector3(); private static final Vector3 normalHeap2 = new Vector3(); + private static final float[] fourComponentHeap = new float[4]; + private static final Matrix4 tempMatrix = new Matrix4(); public ShaderProgram groundShader; public ShaderProgram waterShader; @@ -46,334 +51,571 @@ public class Terrain { public float waterIndex; public float waterIncreasePerFrame; public float waterHeightOffset; - public List waterTextures = new ArrayList<>(); + +// + public List groundTextures = new ArrayList<>(); + public List cliffTextures = new ArrayList<>(); + public RenderCorner[][] corners; + public int columns; + public int rows; + public int blightTextureIndex = -1; public float[] maxDeepColor = new float[4]; public float[] minDeepColor = new float[4]; public float[] maxShallowColor = new float[4]; public float[] minShallowColor = new float[4]; - public List tilesetTextures = new ArrayList<>(); - public List cliffTextures = new ArrayList<>(); - public List cliffModels = new ArrayList<>(); + private final DataTable terrainTable; + private final DataTable cliffTable; + private final DataTable waterTable; + private final int waterTextureCount; + private int cliffTexturesSize; + private final List cliffMeshes = new ArrayList<>(); + private final Map pathToCliff = new HashMap<>(); + private final Map groundTextureToId = new HashMap<>(); + private final List cliffToGroundTexture = new ArrayList<>(); + private final List cliffs = new ArrayList<>(); + private final DataSource dataSource; + private final float[] groundHeights; + private final float[] groundCornerHeights; + private final short[] groundTextureList; + private final float[] waterHeights; + private final byte[] waterExistsData; - public Corner[][] corners; - public float[] centerOffset = new float[2]; - public int[] mapSize = new int[2]; + private int groundTextureData = -1; + private final int groundHeight; + private final int groundCornerHeight; + private final int cliffTextureArray; + private final int waterHeight; + private final int waterExists; + private final int waterTextureArray; + private final Camera camera; + private final War3MapViewer viewer; + public float[] centerOffset; + private final WebGL webGL; - public List tilesets = new ArrayList<>(); // TODO - public int blightTextureIndex = -1; - public List cliffTilesets = new ArrayList<>(); - public int columns; - public int rows; + public Terrain(final War3MapW3e w3eFile, final WebGL webGL, final DataSource dataSource, + final WorldEditStrings worldEditStrings, final War3MapViewer viewer) throws IOException { + this.webGL = webGL; + this.viewer = viewer; + this.camera = viewer.worldScene.camera; + this.dataSource = dataSource; + final String texturesExt = ".blp"; + final Corner[][] corners = w3eFile.getCorners(); + this.corners = new RenderCorner[corners[0].length][corners.length]; + for (int i = 0; i < corners.length; i++) { + for (int j = 0; j < corners[i].length; j++) { + this.corners[j][i] = new RenderCorner(corners[i][j]); + } + } + final int width = w3eFile.getMapSize()[0]; + final int height = w3eFile.getMapSize()[1]; + this.columns = width; + this.rows = height; + for (int i = 0; i < (width - 1); i++) { + for (int j = 0; j < (height - 1); j++) { + final RenderCorner bottomLeft = this.corners[i][j]; + final RenderCorner bottomRight = this.corners[i + 1][j]; + final RenderCorner topLeft = this.corners[i][j + 1]; + final RenderCorner topRight = this.corners[i + 1][j + 1]; - public int vertexBuffer; - public int faceBuffer; - public int instanceBuffer; - public int textureBuffer; - public int variationBuffer; - public int waterBuffer; - public int heightMap; - public int waterHeightMap; - public int cliffHeightMap; + bottomLeft.cliff = (bottomLeft.getLayerHeight() != bottomRight.getLayerHeight()) + || (bottomLeft.getLayerHeight() != topLeft.getLayerHeight()) + || (bottomLeft.getLayerHeight() != topRight.getLayerHeight()); + } + } - public Terrain(final WebGL webGL, final DataSource dataSource, final WorldEditStrings worldEditStrings) - throws IOException { - - final DataTable terrainTable = new DataTable(worldEditStrings); + this.terrainTable = new DataTable(worldEditStrings); try (InputStream terrainSlkStream = dataSource.getResourceAsStream("TerrainArt\\Terrain.slk")) { - terrainTable.readSLK(terrainSlkStream); + this.terrainTable.readSLK(terrainSlkStream); } - final DataTable cliffTable = new DataTable(worldEditStrings); + this.cliffTable = new DataTable(worldEditStrings); try (InputStream cliffSlkStream = dataSource.getResourceAsStream("TerrainArt\\CliffTypes.slk")) { - cliffTable.readSLK(cliffSlkStream); + this.cliffTable.readSLK(cliffSlkStream); + } + this.waterTable = new DataTable(worldEditStrings); + try (InputStream waterSlkStream = dataSource.getResourceAsStream("TerrainArt\\Water.slk")) { + this.waterTable.readSLK(waterSlkStream); } - this.groundShader = webGL.createShaderProgram(W3xShaders.Ground.vert, W3xShaders.Ground.frag); - this.waterShader = webGL.createShaderProgram(W3xShaders.Water.vert, W3xShaders.Water.frag); - this.cliffShader = webGL.createShaderProgram(W3xShaders.Cliffs.vert, W3xShaders.Cliffs.frag); + final char tileset = w3eFile.getTileset(); + final Element waterInfo = this.waterTable.get(tileset + "Sha"); + this.waterHeightOffset = waterInfo.getFieldFloatValue("height"); + this.waterTextureCount = waterInfo.getFieldValue("numTex"); + this.waterIncreasePerFrame = waterInfo.getFieldValue("texRate") / 60f; - } + loadWaterColor(this.minShallowColor, "Smin", waterInfo); + loadWaterColor(this.maxShallowColor, "Smax", waterInfo); + loadWaterColor(this.minDeepColor, "Dmin", waterInfo); + loadWaterColor(this.maxDeepColor, "Dmax", waterInfo); - public void load(final War3MapW3e w3e, final float[] centerOffset, final int[] mapSize, - final War3MapViewer viewer) { + // Cliff Meshes - this.corners = w3e.getCorners(); - System.arraycopy(centerOffset, 0, this.centerOffset, 0, centerOffset.length); - System.arraycopy(mapSize, 0, this.mapSize, 0, mapSize.length); - - final String texturesExt = viewer.solverParams.reforged ? ".dds" : ".blp"; - final char tileset = w3e.getTileset(); - - for (final War3ID groundTile : w3e.getGroundTiles()) { - final MappedDataRow row = viewer.terrainData.getRow(groundTile.asStringValue()); - - this.tilesets.add(row); - this.tilesetTextures - .add((Texture) viewer.load(row.get("dir").toString() + "\\" + row.get("file") + texturesExt, - viewer.mapPathSolver, viewer.solverParams)); + final Map cliffVars = Variations.CLIFF_VARS; + for (final Map.Entry cliffVar : cliffVars.entrySet()) { + final Integer maxVariations = cliffVar.getValue(); + for (int variation = 0; variation <= maxVariations; variation++) { + final String fileName = "Doodads\\Terrain\\Cliffs\\Cliffs" + cliffVar.getKey() + variation + ".mdx"; + this.cliffMeshes.add(new CliffMesh(fileName, dataSource, Gdx.gl30)); + this.pathToCliff.put(cliffVar.getKey() + variation, this.cliffMeshes.size() - 1); + } } - final StandardObjectData standardObjectData = new StandardObjectData(viewer.mapMpq.getCompoundDataSource()); + // Ground textures + for (final War3ID groundTile : w3eFile.getGroundTiles()) { + final Element terrainTileInfo = this.terrainTable.get(groundTile.asStringValue()); + final String dir = terrainTileInfo.getField("dir"); + final String file = terrainTileInfo.getField("file"); + this.groundTextures.add(new GroundTexture(dir + "/" + file + texturesExt, dataSource, Gdx.gl30)); + this.groundTextureToId.put(groundTile.asStringValue(), this.groundTextures.size() - 1); + } + + final StandardObjectData standardObjectData = new StandardObjectData(dataSource); final DataTable worldEditData = standardObjectData.getWorldEditData(); final Element tilesets = worldEditData.get("TileSets"); - this.blightTextureIndex = this.tilesetTextures.size(); - this.tilesetTextures - .add((Texture) viewer.load(tilesets.getField(Character.toString(tileset)).split(",")[1] + texturesExt, - viewer.mapPathSolver, viewer.solverParams)); + this.blightTextureIndex = this.groundTextures.size(); + this.groundTextures.add(new GroundTexture( + tilesets.getField(Character.toString(tileset)).split(",")[1] + texturesExt, dataSource, Gdx.gl30)); - for (final War3ID cliffTile : w3e.getCliffTiles()) { - final MappedDataRow row = viewer.cliffTypesData.getRow(cliffTile.asStringValue()); - - this.cliffTilesets.add(row); - this.cliffTextures - .add((Texture) viewer.load(row.get("texDir").toString() + "\\" + row.get("texFile") + texturesExt, - viewer.mapPathSolver, viewer.solverParams)); + // Cliff Textures + for (final War3ID cliffTile : w3eFile.getCliffTiles()) { + final Element cliffInfo = this.cliffTable.get(cliffTile.asStringValue()); + final String texDir = cliffInfo.getField("texDir"); + final String texFile = cliffInfo.getField("texFile"); + try (InputStream imageStream = dataSource.getResourceAsStream(texDir + "/" + texFile + texturesExt)) { + final BufferedImage image = ImageIO.read(imageStream); + this.cliffTextures.add(new UnloadedTexture(image.getWidth(), image.getHeight(), + ImageUtils.getTextureBuffer(ImageUtils.forceBufferedImagesRGB(image)))); + } + this.cliffTexturesSize = Math.max(this.cliffTexturesSize, + this.cliffTextures.get(this.cliffTextures.size() - 1).width); + this.cliffToGroundTexture.add(this.groundTextureToId.get(cliffInfo.getField("groundTile"))); } - final MappedDataRow waterRow = viewer.waterData.getRow(tileset + "Sha"); + updateCliffMeshes(new Rectangle(0, 0, width - 1, height - 1)); - this.waterHeightOffset = ((Number) waterRow.get("height")).floatValue(); - this.waterIncreasePerFrame = ((Number) waterRow.get("texRate")).intValue() / (float) 60; - this.waterTextures.clear(); - this.maxDeepColor[0] = ((Number) waterRow.get("Dmax_R")).floatValue(); - this.maxDeepColor[1] = ((Number) waterRow.get("Dmax_G")).floatValue(); - this.maxDeepColor[2] = ((Number) waterRow.get("Dmax_B")).floatValue(); - this.maxDeepColor[3] = ((Number) waterRow.get("Dmax_A")).floatValue(); - this.minDeepColor[0] = ((Number) waterRow.get("Dmin_R")).floatValue(); - this.minDeepColor[1] = ((Number) waterRow.get("Dmin_G")).floatValue(); - this.minDeepColor[2] = ((Number) waterRow.get("Dmin_B")).floatValue(); - this.minDeepColor[3] = ((Number) waterRow.get("Dmin_A")).floatValue(); - this.maxShallowColor[0] = ((Number) waterRow.get("Smax_R")).floatValue(); - this.maxShallowColor[1] = ((Number) waterRow.get("Smax_G")).floatValue(); - this.maxShallowColor[2] = ((Number) waterRow.get("Smax_B")).floatValue(); - this.maxShallowColor[3] = ((Number) waterRow.get("Smax_A")).floatValue(); - this.minShallowColor[0] = ((Number) waterRow.get("Smin_R")).floatValue(); - this.minShallowColor[1] = ((Number) waterRow.get("Smin_G")).floatValue(); - this.minShallowColor[2] = ((Number) waterRow.get("Smin_B")).floatValue(); - this.minShallowColor[3] = ((Number) waterRow.get("Smin_A")).floatValue(); + // prepare GPU data + this.groundHeights = new float[width * height]; + this.groundCornerHeights = new float[width * height]; + this.groundTextureList = new short[(width - 1) * (height - 1) * 4]; + this.waterHeights = new float[width * height]; + this.waterExistsData = new byte[width * height]; - for (int i = 0, l = ((Number) waterRow.get("numTex")).intValue(); i < l; i++) { - this.waterTextures.add( - (Texture) viewer.load(waterRow.get("texFile").toString() + ((i < 10) ? "0" : "") + i + texturesExt, - viewer.mapPathSolver, viewer.solverParams)); + updateGroundTextures(new Rectangle(0, 0, width - 1, height - 1)); + for (int i = 0; i < width; i++) { + for (int j = 0; j < height; j++) { + this.groundCornerHeights[(j * width) + i] = this.corners[i][j].computeFinalGroundHeight(); + this.waterExistsData[(j * width) + i] = (byte) this.corners[i][j].getWater(); + this.groundHeights[(j * width) + i] = this.corners[i][j].getGroundHeight(); + this.waterHeights[(j * width) + i] = this.corners[i][j].getWaterHeight(); + } } - final GL20 gl = viewer.gl; + final GL30 gl = Gdx.gl30; + // Ground + this.groundTextureData = gl.glGenTexture(); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundTextureData); + gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_RGBA16UI, width - 1, height - 1, 0, GL30.GL_RGBA_INTEGER, + GL30.GL_UNSIGNED_SHORT, RenderMathUtils.wrapShort(this.groundTextureList)); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_NEAREST); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_NEAREST); - final Corner[][] corners = w3e.getCorners(); - final int columns = this.mapSize[0]; - final int rows = this.mapSize[1]; - final int instanceCount = (columns - 1) * (rows - 1); - final float[] cliffHeights = new float[columns * rows]; - final float[] cornerHeights = new float[columns * rows]; - final float[] waterHeights = new float[columns * rows]; - final short[] cornerTextures = new short[instanceCount * 4]; - final short[] cornerVariations = new short[instanceCount * 4]; - final short[] waterFlags = new short[instanceCount]; - int instance = 0; - final Map cliffs = new HashMap<>(); + this.groundHeight = gl.glGenTexture(); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundHeight); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_NEAREST); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_NEAREST); - this.columns = columns - 1; - this.rows = rows - 1; + gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_R16F, width, height, 0, GL30.GL_RED, GL30.GL_FLOAT, + RenderMathUtils.wrap(this.groundHeights)); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_S, GL30.GL_CLAMP_TO_EDGE); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_T, GL30.GL_CLAMP_TO_EDGE); - for (int y = 0; y < rows; y++) { - for (int x = 0; x < columns; x++) { - final Corner bottomLeft = corners[y][x]; - final int index = (y * columns) + x; + this.groundCornerHeight = gl.glGenTexture(); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeight); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_NEAREST); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_NEAREST); - cliffHeights[index] = bottomLeft.getGroundHeight(); - cornerHeights[index] = (bottomLeft.getGroundHeight() + bottomLeft.getLayerHeight()) - 2; - waterHeights[index] = bottomLeft.getWaterHeight(); + gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_R16F, width, height, 0, GL30.GL_RED, GL30.GL_FLOAT, + RenderMathUtils.wrap(this.groundCornerHeights)); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_S, GL30.GL_CLAMP_TO_EDGE); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_T, GL30.GL_CLAMP_TO_EDGE); - if ((y < (rows - 1)) && (x < (columns - 1))) { - // Water can be used with cliffs and normal corners, so store water state - // regardless. - waterFlags[instance] = this.isWater(x, y); + // Cliff + this.cliffTextureArray = gl.glGenTexture(); + gl.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.cliffTextureArray); + gl.glTexImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, GL30.GL_RGBA8, this.cliffTexturesSize, this.cliffTexturesSize, + this.cliffTextures.size(), 0, Extensions.GL_BGRA, GL30.GL_UNSIGNED_BYTE, null); + gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_LINEAR_MIPMAP_LINEAR); + gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_BASE_LEVEL, 0); - // Is this a cliff, or a normal corner? - if (this.isCliff(x, y)) { - final int bottomLeftLayer = bottomLeft.getLayerHeight(); - final int bottomRightLayer = corners[y][x + 1].getLayerHeight(); - final int topLeftLayer = corners[y + 1][x].getLayerHeight(); - final int topRightLayer = corners[y + 1][x + 1].getLayerHeight(); - final int base = Math.min(Math.min(bottomLeftLayer, bottomRightLayer), - Math.min(topLeftLayer, topRightLayer)); - final String fileName = this.cliffFileName(bottomLeftLayer, bottomRightLayer, topLeftLayer, - topRightLayer, base); + int sub = 0; + for (final UnloadedTexture i : this.cliffTextures) { + gl.glTexSubImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, 0, 0, sub, i.width, i.height, 1, Extensions.GL_BGRA, + GL30.GL_UNSIGNED_BYTE, i.data); + sub += 1; + } + gl.glGenerateMipmap(GL30.GL_TEXTURE_2D_ARRAY); - if (!"AAAA".equals(fileName)) { - int cliffTexture = bottomLeft.getCliffTexture(); + // Water + this.waterHeight = gl.glGenTexture(); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.waterHeight); + gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_R16F, width, height, 0, GL30.GL_RED, GL30.GL_FLOAT, + RenderMathUtils.wrap(this.waterHeights)); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_NEAREST); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_NEAREST); - // ? - if (cliffTexture == 15) { - cliffTexture = 1; + this.waterExists = gl.glGenTexture(); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.waterExists); + gl.glPixelStorei(GL30.GL_UNPACK_ALIGNMENT, 1); + gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_R8, width, height, 0, GL30.GL_RED, GL30.GL_UNSIGNED_BYTE, + RenderMathUtils.wrap(this.waterExistsData)); + gl.glPixelStorei(GL30.GL_UNPACK_ALIGNMENT, 4); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_NEAREST); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_NEAREST); + + // Water textures + this.waterTextureArray = gl.glGenTexture(); + gl.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.waterTextureArray); + gl.glTexImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, GL30.GL_RGBA8, 128, 128, this.waterTextureCount, 0, + Extensions.GL_BGRA, GL30.GL_UNSIGNED_BYTE, null); + gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_WRAP_S, GL30.GL_CLAMP_TO_EDGE); + gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_WRAP_T, GL30.GL_CLAMP_TO_EDGE); + gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_BASE_LEVEL, 0); + + final String fileName = waterInfo.getField("texFile"); + for (int i = 0; i < this.waterTextureCount; i++) { + + try (InputStream imageStream = dataSource + .getResourceAsStream(fileName + (i < 10 ? "0" : "") + Integer.toString(i) + texturesExt)) { + final BufferedImage image = ImageIO.read(imageStream); + if ((image.getWidth() != 128) || (image.getHeight() != 128)) { + System.err.println( + "Odd water texture size detected of " + image.getWidth() + " x " + image.getHeight()); + } + + gl.glTexSubImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, 0, 0, i, image.getWidth(), image.getHeight(), 1, + GL30.GL_RGBA, GL30.GL_UNSIGNED_BYTE, + ImageUtils.getTextureBuffer(ImageUtils.forceBufferedImagesRGB(image))); + } + } + gl.glGenerateMipmap(GL30.GL_TEXTURE_2D_ARRAY); + + updateGroundHeights(new Rectangle(0, 0, width - 1, height - 1)); + + this.groundShader = webGL.createShaderProgram(HiveWEShaders.Terrain.vert, HiveWEShaders.Terrain.frag); + this.cliffShader = webGL.createShaderProgram(HiveWEShaders.Cliffs.vert, HiveWEShaders.Cliffs.frag); + this.waterShader = webGL.createShaderProgram(HiveWEShaders.Water.vert, HiveWEShaders.Water.frag); + + // TODO collision bodies (?) + + this.centerOffset = w3eFile.getCenterOffset(); + } + + private void updateGroundHeights(final Rectangle area) { + for (int j = (int) area.y; j < (area.y + area.height); j++) { + for (int i = (int) area.x; i < (area.x + area.width); i++) { + this.groundHeights[(j * this.columns) + i] = this.corners[i][j].getGroundHeight(); + + float rampHeight = 0f; + // Check if in one of the configurations the bottom_left is a ramp + XLoop: for (int xOffset = -1; xOffset <= 0; xOffset++) { + for (int yOffset = -1; yOffset <= 0; yOffset++) { + if (((i + xOffset) >= 0) && ((i + xOffset) < (this.columns - 1)) && ((j + yOffset) >= 0) + && ((j + yOffset) < (this.rows - 1))) { + final RenderCorner bottomLeft = this.corners[i + xOffset][j + yOffset]; + final RenderCorner bottomRight = this.corners[i + 1 + xOffset][j + yOffset]; + final RenderCorner topLeft = this.corners[i + xOffset][j + 1 + yOffset]; + final RenderCorner topRight = this.corners[i + 1 + xOffset][j + 1 + yOffset]; + + final int base = Math.min( + Math.min(bottomLeft.getLayerHeight(), bottomRight.getLayerHeight()), + Math.min(topLeft.getLayerHeight(), topRight.getLayerHeight())); + if (this.corners[i][j].getLayerHeight() != base) { + continue; } - final MappedDataRow cliffRow = this.cliffTilesets.get(cliffTexture); - final String dir = cliffRow.get("cliffModelDir").toString(); - final String path = "Doodads\\Terrain\\" + dir + "\\" + dir + fileName - + Variations.getCliffVariation(dir, fileName, bottomLeft.getCliffVariation()) - + ".mdx"; - - if (!cliffs.containsKey(path)) { - cliffs.put(path, new CliffInfo()); + if (isCornerRampEntrance(i + xOffset, j + yOffset)) { + rampHeight = 0.5f; + break XLoop; } - - cliffs.get(path).locations.add(new float[] { ((x + 1) * 128) + centerOffset[0], - (y * 128) + centerOffset[1], (base - 2) * 128 }); - cliffs.get(path).textures.add(cliffTexture); } } - else { - final int bottomLeftTexture = this.cornerTexture(x, y); - final int bottomRightTexture = this.cornerTexture(x + 1, y); - final int topLeftTexture = this.cornerTexture(x, y + 1); - final int topRightTexture = this.cornerTexture(x + 1, y + 1); - final LinkedHashSet texturesUnique = new LinkedHashSet<>(); - texturesUnique.add(bottomLeftTexture); - texturesUnique.add(bottomRightTexture); - texturesUnique.add(topLeftTexture); - texturesUnique.add(topRightTexture); - final List textures = new ArrayList<>(texturesUnique); - Collections.sort(textures); + } - int texture = textures.remove(0); + this.groundCornerHeights[(j * this.columns) + i] = this.corners[i][j].computeFinalGroundHeight() + + rampHeight; + } + } + updateGroundHeights(); + updateCornerHeights(); + } - cornerTextures[instance * 4] = (short) (texture + 1); - cornerVariations[instance * 4] = this.getVariation(texture, bottomLeft.getGroundVariation()); + private void updateGroundHeights() { + Gdx.gl30.glBindTexture(GL30.GL_TEXTURE_2D, this.groundHeight); + Gdx.gl30.glTexSubImage2D(GL30.GL_TEXTURE_2D, 0, 0, 0, this.columns, this.rows, GL30.GL_RED, GL30.GL_FLOAT, + RenderMathUtils.wrap(this.groundHeights)); + } - for (int i = 0, l = textures.size(); i < l; i++) { - int bitset = 0; + private void updateCornerHeights() { + Gdx.gl30.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeight); + Gdx.gl30.glTexSubImage2D(GL30.GL_TEXTURE_2D, 0, 0, 0, this.columns, this.rows, GL30.GL_RED, GL30.GL_FLOAT, + RenderMathUtils.wrap(this.groundCornerHeights)); + } - texture = textures.get(i); + /// TODO clean + /// Function is a bit of a mess + /// Updates the cliff and ramp meshes for an area + private void updateCliffMeshes(final Rectangle area) throws IOException { + // Remove all existing cliff meshes in area + for (int i = this.cliffs.size(); i-- > 0;) { + final IVec3 pos = this.cliffs.get(i); + if (area.contains(pos.x, pos.y)) { + this.cliffs.remove(i); + } + } - if (bottomRightTexture == texture) { - bitset |= 0b0001; + for (int i = (int) area.getX(); i < (int) (area.getX() + area.getWidth()); i++) { + for (int j = (int) area.getY(); j < (int) (area.getY() + area.getHeight()); j++) { + this.corners[i][j].romp = false; + } + } + + final Rectangle adjusted = new Rectangle(area.x - 2, area.y - 2, area.width + 4, area.height + 4); + final Rectangle rampArea = new Rectangle(); + Intersector.intersectRectangles(new Rectangle(0, 0, this.columns, this.rows), adjusted, rampArea); + + // Add new cliff meshes + final int xLimit = (int) ((rampArea.getX() + rampArea.getWidth()) - 1); + for (int i = (int) rampArea.getX(); i < xLimit; i++) { + final int yLimit = (int) ((rampArea.getY() + rampArea.getHeight()) - 1); + for (int j = (int) rampArea.getY(); j < yLimit; j++) { + final RenderCorner bottomLeft = this.corners[i][j]; + final RenderCorner bottomRight = this.corners[i + 1][j]; + final RenderCorner topLeft = this.corners[i][j + 1]; + final RenderCorner topRight = this.corners[i + 1][j + 1]; + + if (bottomLeft.cliff) { + final int base = Math.min(Math.min(bottomLeft.getLayerHeight(), bottomRight.getLayerHeight()), + Math.min(topLeft.getLayerHeight(), topRight.getLayerHeight())); + + final boolean facingDown = (topLeft.getLayerHeight() >= bottomLeft.getLayerHeight()) + && (topRight.getLayerHeight() >= bottomRight.getLayerHeight()); + final boolean facingLeft = (bottomRight.getLayerHeight() >= bottomLeft.getLayerHeight()) + && (topRight.getLayerHeight() >= topLeft.getLayerHeight()); + + if (!(facingDown && (j == 0)) && !(!facingDown && (j >= (this.rows - 2))) + && !(facingLeft && (i == 0)) && !(!facingLeft && (i >= (this.columns - 2)))) { + final boolean br = ((bottomLeft.getRamp() != 0) != (bottomRight.getRamp() != 0)) + && ((topLeft.getRamp() != 0) != (topRight.getRamp() != 0)) + && !this.corners[i + bottomRight.getRamp()][j + (facingDown ? -1 : 1)].cliff; + + final boolean bo = ((bottomLeft.getRamp() != 0) != (topLeft.getRamp() != 0)) + && ((bottomRight.getRamp() != 0) != (topRight.getRamp() != 0)) + && !this.corners[i + (facingLeft ? -1 : 1)][j + topLeft.getRamp()].cliff; + + if (br || bo) { + String fileName = "" + (char) ((bottomLeft.getRamp() != 0 ? 'L' : 'A') + + ((bottomLeft.getLayerHeight() - base) * (bottomLeft.getRamp() != 0 ? -4 : 1))) + + (char) ((topLeft.getRamp() != 0 ? 'L' : 'A') + + ((topLeft.getLayerHeight() - base) * (topLeft.getRamp() != 0 ? -4 : 1))) + + (char) ((topRight.getRamp() != 0 ? 'L' : 'A') + + ((topRight.getLayerHeight() - base) * (topRight.getRamp() != 0 ? -4 : 1))) + + (char) ((bottomRight.getRamp() != 0 ? 'L' : 'A') + + ((bottomRight.getLayerHeight() - base) + * (bottomRight.getRamp() != 0 ? -4 : 1))); + + fileName = "Doodads\\Terrain\\CliffTrans\\CliffTrans" + fileName + "0.mdx"; + + if (this.dataSource.has(fileName)) { + if (!this.pathToCliff.containsKey(fileName)) { + this.cliffMeshes.add(new CliffMesh(fileName, this.dataSource, Gdx.gl30)); + this.pathToCliff.put(fileName, this.cliffMeshes.size() - 1); + } + + for (int ji = this.cliffs.size(); ji-- > 0;) { + final IVec3 pos = this.cliffs.get(ji); + if ((pos.x == (i + ((bo ? 1 : 0) * (facingLeft ? 0 : 1)))) + && (pos.y == (j - ((br ? 1 : 0) * (facingDown ? 1 : 0))))) { + this.cliffs.remove(ji); + break; + } + } + + this.cliffs.add(new IVec3((i + ((bo ? 1 : 0) * (facingDown ? 0 : 1))), + (j - ((br ? 1 : 0) * (facingDown ? 1 : 0))), this.pathToCliff.get(fileName))); + bottomLeft.romp = true; + + this.corners[i + ((facingLeft ? -1 : 1) * (bo ? 1 : 0))][j + + ((facingDown ? -1 : 1) * (br ? 1 : 0))].romp = true; + + continue; } - - if (bottomLeftTexture == texture) { - bitset |= 0b0010; - } - - if (topRightTexture == texture) { - bitset |= 0b0100; - } - - if (topLeftTexture == texture) { - bitset |= 0b1000; - } - - cornerTextures[(instance * 4) + 1 + i] = (short) (texture + 1); - cornerVariations[(instance * 4) + 1 + i] = (short) (bitset); } } - instance += 1; + if (isCornerRampEntrance(i, j)) { + continue; + } + // Ramps move 1 right/down in some cases and thus their area is one bigger to + // the top and left. + if (!area.contains(i, j)) { + continue; + } + + // Cliff model path + + String fileName = "" + (char) (('A' + bottomLeft.getLayerHeight()) - base) + + (char) (('A' + topLeft.getLayerHeight()) - base) + + (char) (('A' + topRight.getLayerHeight()) - base) + + (char) (('A' + bottomRight.getLayerHeight()) - base); + + if ("AAAA".equals(fileName)) { + continue; + } + + // Clamp to within max variations + fileName += Variations.getCliffVariation("Cliffs", fileName, bottomLeft.getCliffVariation()); + if (!this.pathToCliff.containsKey(fileName)) { + throw new IllegalArgumentException("No such pathToCliff entry: " + fileName); + } + this.cliffs.add(new IVec3(i, j, this.pathToCliff.get(fileName))); } } } - this.vertexBuffer = gl.glGenBuffer(); - gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.vertexBuffer); - gl.glBufferData(GL20.GL_ARRAY_BUFFER, 8 * 4, RenderMathUtils.wrap(new float[] { 0, 0, 1, 0, 0, 1, 1, 1 }), - GL20.GL_STATIC_DRAW); + } - this.faceBuffer = gl.glGenBuffer(); - gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, this.faceBuffer); - gl.glBufferData(GL20.GL_ELEMENT_ARRAY_BUFFER, 6, RenderMathUtils.wrap(new byte[] { 0, 1, 2, 1, 3, 2 }), - GL20.GL_STATIC_DRAW); + private void updateGroundTextures(final Rectangle area) { + final Rectangle adjusted = new Rectangle(area.x - 1, area.y - 1, area.width + 2, area.height + 2); + final Rectangle updateArea = new Rectangle(); + Intersector.intersectRectangles(new Rectangle(0, 0, this.columns - 1, this.rows - 1), adjusted, updateArea); - this.cliffHeightMap = gl.glGenTexture(); - gl.glBindTexture(GL20.GL_TEXTURE_2D, this.cliffHeightMap); - viewer.webGL.setTextureMode(GL20.GL_CLAMP_TO_EDGE, GL20.GL_CLAMP_TO_EDGE, GL20.GL_NEAREST, GL20.GL_NEAREST); - gl.glTexImage2D(GL20.GL_TEXTURE_2D, 0, GL30.GL_R32F, columns, rows, 0, GL30.GL_RED, GL20.GL_FLOAT, - RenderMathUtils.wrap(cliffHeights)); + for (int j = (int) (updateArea.getY()); j <= (int) ((updateArea.getY() + updateArea.getHeight()) - 1); j++) { + for (int i = (int) (updateArea.getX()); i <= (int) ((updateArea.getX() + updateArea.getWidth()) - 1); i++) { + getTextureVariations(i, j, this.groundTextureList, ((j * (this.columns - 1)) + i) * 4); - this.heightMap = gl.glGenTexture(); - gl.glBindTexture(GL20.GL_TEXTURE_2D, this.heightMap); - viewer.webGL.setTextureMode(GL20.GL_CLAMP_TO_EDGE, GL20.GL_CLAMP_TO_EDGE, GL20.GL_NEAREST, GL20.GL_NEAREST); - gl.glTexImage2D(GL20.GL_TEXTURE_2D, 0, GL30.GL_R32F, columns, rows, 0, GL30.GL_RED, GL20.GL_FLOAT, - RenderMathUtils.wrap(cornerHeights)); - - this.waterHeightMap = gl.glGenTexture(); - gl.glBindTexture(GL20.GL_TEXTURE_2D, this.waterHeightMap); - viewer.webGL.setTextureMode(GL20.GL_CLAMP_TO_EDGE, GL20.GL_CLAMP_TO_EDGE, GL20.GL_NEAREST, GL20.GL_NEAREST); - gl.glTexImage2D(GL20.GL_TEXTURE_2D, 0, GL30.GL_R32F, columns, rows, 0, GL30.GL_RED, GL20.GL_FLOAT, - RenderMathUtils.wrap(waterHeights)); - - this.instanceBuffer = gl.glGenBuffer(); - gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.instanceBuffer); - final float[] instanceBufferData = new float[instanceCount]; - for (int i = 0; i < instanceBufferData.length; i++) { - instanceBufferData[i] = i; + if (this.corners[i][j].cliff || this.corners[i][j].romp) { + if (isCornerRampEntrance(i, j)) { + continue; + } + this.groundTextureList[(((j * (this.columns - 1)) + i) * 4) + 3] |= 0b1000000000000000; + } + } } - gl.glBufferData(GL20.GL_ARRAY_BUFFER, instanceBufferData.length * 4, RenderMathUtils.wrap(instanceBufferData), - GL20.GL_STATIC_DRAW); - this.textureBuffer = gl.glGenBuffer(); - gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.textureBuffer); - gl.glBufferData(GL20.GL_ARRAY_BUFFER, cornerTextures.length, RenderMathUtils.wrap(cornerTextures), - GL20.GL_STATIC_DRAW); + uploadGroundTexture(); + } - this.variationBuffer = gl.glGenBuffer(); - gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.variationBuffer); - gl.glBufferData(GL20.GL_ARRAY_BUFFER, cornerVariations.length, RenderMathUtils.wrap(cornerVariations), - GL20.GL_STATIC_DRAW); - - this.waterBuffer = gl.glGenBuffer(); - gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.waterBuffer); - gl.glBufferData(GL20.GL_ARRAY_BUFFER, waterFlags.length, RenderMathUtils.wrap(waterFlags), GL20.GL_STATIC_DRAW); - - final ShaderProgram cliffShader = this.cliffShader; - this.cliffModels.clear(); - for (final Map.Entry entry : cliffs.entrySet()) { - final String path = entry.getKey(); - final CliffInfo cliffInfo = entry.getValue(); - - final GenericResource resource = viewer.loadMapGeneric(path, FetchDataTypeName.ARRAY_BUFFER, - viewer.streamDataCallback); - - this.cliffModels.add(new TerrainModel(viewer, (InputStream) resource.data, cliffInfo.locations, - cliffInfo.textures, cliffShader)); + private void uploadGroundTexture() { + if (this.groundTextureData != -1) { + Gdx.gl30.glBindTexture(GL30.GL_TEXTURE_2D, this.groundTextureData); + Gdx.gl30.glTexSubImage2D(GL30.GL_TEXTURE_2D, 0, 0, 0, this.columns - 1, this.rows - 1, GL30.GL_RGBA_INTEGER, + GL30.GL_UNSIGNED_SHORT, RenderMathUtils.wrapShort(this.groundTextureList)); } } - public boolean isCliff(final int column, final int row) { - if ((column < 1) || (column > (this.columns - 1)) || (row < 1) || (row > (this.rows - 1))) { + /// The 4 ground textures of the tilepoint. First 5 bits are which texture array + /// to use and the next 5 bits are which subtexture to use + private void getTextureVariations(final int x, final int y, final short[] out, final int outStartOffset) { + final int bottomLeft = realTileTexture(x, y); + final int bottomRight = realTileTexture(x + 1, y); + final int topLeft = realTileTexture(x, y + 1); + final int topRight = realTileTexture(x + 1, y + 1); + + final TreeSet set = new TreeSet<>(); + set.add(bottomLeft); + set.add(bottomRight); + set.add(topLeft); + set.add(topRight); + Arrays.fill(out, outStartOffset, outStartOffset + 4, (short) 17); + int component = outStartOffset + 1; + + final Iterator iterator = set.iterator(); + iterator.hasNext(); + final short firstValue = iterator.next().shortValue(); + out[outStartOffset] = (short) (firstValue + + (getVariation(firstValue, this.corners[x][y].getGroundVariation()) << 5)); + + int index; + while (iterator.hasNext()) { + index = 0; + final int texture = iterator.next().intValue(); + index |= (bottomRight == texture ? 1 : 0) << 0; + index |= (bottomLeft == texture ? 1 : 0) << 1; + index |= (topRight == texture ? 1 : 0) << 2; + index |= (topLeft == texture ? 1 : 0) << 3; + + out[component++] = (short) (texture + (index << 5)); + } + } + + private int realTileTexture(final int x, final int y) { + ILoop: for (int i = -1; i < 1; i++) { + for (int j = -1; j < 1; j++) { + if (((x + i) >= 0) && ((x + i) < this.columns) && ((y + j) >= 0) && ((y + j) < this.rows)) { + if (this.corners[x + i][y + j].cliff) { + if (((x + i) < (this.columns - 1)) && ((y + j) < (this.rows - 1))) { + final RenderCorner bottomLeft = this.corners[x + i][y + j]; + final RenderCorner bottomRight = this.corners[x + i + 1][y + j]; + final RenderCorner topLeft = this.corners[x + i][y + j + 1]; + final RenderCorner topRight = this.corners[x + i + 1][y + j + 1]; + + if ((bottomLeft.getRamp() != 0) && (topLeft.getRamp() != 0) && (bottomRight.getRamp() != 0) + && (topRight.getRamp() != 0) && (!bottomLeft.romp) && (!bottomRight.romp) + && (!topLeft.romp) && (!topRight.romp)) { + break ILoop; + } + } + } + if (this.corners[x + i][y + j].romp || this.corners[x + i][y + j].cliff) { + int texture = this.corners[x + i][y + j].getCliffTexture(); + // Number 15 seems to be something + if (texture == 15) { + texture -= 14; + } + + return this.cliffToGroundTexture.get(texture); + } + } + } + } + + if (this.corners[x][y].getBlight() != 0) { + return this.blightTextureIndex; + } + + return this.corners[x][y].getGroundTexture(); + } + + private boolean isCornerRampEntrance(final int x, final int y) { + if ((x == this.columns) || (y == this.rows)) { return false; } - final Corner[][] corners = this.corners; - final int bottomLeft = corners[row][column].getLayerHeight(); - final int bottomRight = corners[row][column + 1].getLayerHeight(); - final int topLeft = corners[row + 1][column].getLayerHeight(); - final int topRight = corners[row + 1][column + 1].getLayerHeight(); + final RenderCorner bottomLeft = this.corners[x][y]; + final RenderCorner bottomRight = this.corners[x + 1][y]; + final RenderCorner topLeft = this.corners[x][y + 1]; + final RenderCorner topRight = this.corners[x + 1][y + 1]; - return (bottomLeft != bottomRight) || (bottomLeft != topLeft) || (bottomLeft != topRight); + return (bottomLeft.getRamp() != 0) && (topLeft.getRamp() != 0) && (bottomRight.getRamp() != 0) + && (topRight.getRamp() != 0) && !((bottomLeft.getLayerHeight() == topRight.getLayerHeight()) + && (topLeft.getLayerHeight() == bottomRight.getLayerHeight())); } - public short isWater(final int column, final int row) { - return ((this.corners[row][column].getWater() != 0) || (this.corners[row][column + 1].getWater() != 0) - || (this.corners[row + 1][column].getWater() != 0) - || (this.corners[row + 1][column + 1].getWater() != 0)) ? (short) 1 : (short) 0; - } - - public String cliffFileName(final int bottomLeftLayer, final int bottomRightLayer, final int topLeftLayer, - final int topRightLayer, final int base) { - return Character.toString((char) ((65 + bottomLeftLayer) - base)) - + Character.toString((char) ((65 + topLeftLayer) - base)) - + Character.toString((char) ((65 + topRightLayer) - base)) - + Character.toString((char) ((65 + bottomRightLayer) - base)); + private static void loadWaterColor(final float[] out, final String prefix, final Element waterInfo) { + for (int i = 0; i < colorTags.length; i++) { + final String colorTag = colorTags[i]; + out[i] = waterInfo.getFieldFloatValue(prefix + "_" + colorTag) / 255f; + } } public short getVariation(final int groundTexture, final int variation) { - final Texture texture = this.tilesetTextures.get(groundTexture); + final GroundTexture texture = this.groundTextures.get(groundTexture); // Extended ? - if (texture.getWidth() > texture.getHeight()) { - if (variation < 16) { + if (texture.extended) { + if (variation <= 15) { return (short) (16 + variation); } else if (variation == 16) { @@ -393,258 +635,189 @@ public class Terrain { } } - public int cliffGroundIndex(final int whichCliff) { - final String whichTile = this.cliffTilesets.get(whichCliff).get("groundTile").toString(); - final List tilesets = this.tilesets; - - for (int i = 0, l = tilesets.size(); i < l; i++) { - if (tilesets.get(i).get("tileID").toString().equals(whichTile)) { - return i; - } - } - throw new IllegalArgumentException(Integer.toString(whichCliff)); - } - - public int cornerTexture(final int column, final int row) { - final Corner[][] corners = this.corners; - final int columns = this.columns; - final int rows = this.rows; - - for (int y = -1; y < 1; y++) { - for (int x = -1; x < 1; x++) { - if (((column + x) > 0) && ((column + x) < (columns - 1)) && ((row + y) > 0) - && ((row + y) < (rows - 1))) { - if (this.isCliff(column + x, row + y)) { - int texture = corners[row + y][column + x].getCliffTexture(); - - if (texture == 15) { - texture = 1; - } - - return this.cliffGroundIndex(texture); - } - } - } - } - - final Corner corner = corners[row][column]; - if (corner.getBlight() != 0) { - return this.blightTextureIndex; - } - return corner.getGroundTexture(); - } - public void update() { this.waterIndex += this.waterIncreasePerFrame; - if (this.waterIndex >= this.waterTextures.size()) { + if (this.waterIndex >= this.waterTextureCount) { this.waterIndex = 0; } } - public void renderGround(final GL20 gl, final WebGL webgl, final Scene worldScene) { - final ANGLEInstancedArrays instancedArrays = webgl.instancedArrays; - final ShaderProgram shader = this.groundShader; - final List tilesetTextures = this.tilesetTextures; - final int instanceAttrib = shader.getAttributeLocation("a_InstanceID"); - final int positionAttrib = shader.getAttributeLocation("a_position"); - final int texturesAttrib = shader.getAttributeLocation("a_textures"); - final int variationsAttrib = shader.getAttributeLocation("a_variations"); - final int tilesetCount = tilesetTextures.size(); + public void renderGround() { + // Render tiles +// this.groundShader.begin(); + this.webGL.useShaderProgram(this.groundShader); - gl.glEnable(GL20.GL_BLEND); - gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA); + final GL30 gl = Gdx.gl30; + gl.glDisable(GL30.GL_BLEND); - webgl.useShaderProgram(shader); + gl.glUniformMatrix4fv(this.groundShader.getUniformLocation("MVP"), 1, false, + this.camera.viewProjectionMatrix.val, 0); + gl.glUniform1i(this.groundShader.getUniformLocation("show_pathing_map"), this.viewer.renderPathing); + gl.glUniform1i(this.groundShader.getUniformLocation("show_lighting"), this.viewer.renderLighting); + gl.glUniform1i(this.groundShader.getUniformLocation("height_texture"), 0); + gl.glUniform1i(this.groundShader.getUniformLocation("height_cliff_texture"), 1); + gl.glUniform1i(this.groundShader.getUniformLocation("terrain_texture_list"), 2); + gl.glUniform1f(this.groundShader.getUniformLocation("centerOffsetX"), this.centerOffset[0]); + gl.glUniform1f(this.groundShader.getUniformLocation("centerOffsetY"), this.centerOffset[1]); - shader.setUniformMatrix("u_VP", worldScene.camera.viewProjectionMatrix); - shader.setUniform2fv("u_offset", this.centerOffset, 0, 2); - sizeHeap[0] = this.columns; - sizeHeap[1] = this.rows; - shader.setUniform2fv("u_size", sizeHeap, 0, 2); - shader.setUniformi("u_heightMap", 15); + gl.glActiveTexture(GL30.GL_TEXTURE0); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundHeight); - gl.glActiveTexture(GL20.GL_TEXTURE15); - gl.glBindTexture(GL20.GL_TEXTURE_2D, this.heightMap); + gl.glActiveTexture(GL30.GL_TEXTURE1); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeight); - gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.vertexBuffer); - shader.setVertexAttribute(positionAttrib, 2, GL20.GL_FLOAT, false, 8, 0); + gl.glActiveTexture(GL30.GL_TEXTURE2); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundTextureData); - gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.instanceBuffer); - shader.setVertexAttribute(instanceAttrib, 1, GL20.GL_FLOAT, false, 4, 0); - instancedArrays.glVertexAttribDivisorANGLE(instanceAttrib, 1); - - gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.textureBuffer); - shader.setVertexAttribute(texturesAttrib, 4, GL20.GL_UNSIGNED_BYTE, false, 4, 0); - instancedArrays.glVertexAttribDivisorANGLE(texturesAttrib, 1); - - gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.variationBuffer); - shader.setVertexAttribute(variationsAttrib, 4, GL20.GL_UNSIGNED_BYTE, false, 4, 0); - instancedArrays.glVertexAttribDivisorANGLE(variationsAttrib, 1); - - gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, this.faceBuffer); - - shader.setUniformi("u_baseTileset", 0); - - for (int i = 0, l = Math.min(tilesetCount, 15); i < l; i++) { - final int isExtended = (tilesetTextures.get(i).getWidth() > tilesetTextures.get(i).getHeight()) ? 1 : 0; - - shader.setUniformf("u_extended[" + i + "]", isExtended); - shader.setUniformi("u_tilesets[" + i + "]", i); - - tilesetTextures.get(i).bind(i); + for (int i = 0; i < this.groundTextures.size(); i++) { + gl.glActiveTexture(GL30.GL_TEXTURE3 + i); + gl.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.groundTextures.get(i).id); } - instancedArrays.glDrawElementsInstancedANGLE(GL20.GL_TRIANGLES, 6, GL20.GL_UNSIGNED_BYTE, 0, - this.rows * this.columns); +// gl.glActiveTexture(GL30.GL_TEXTURE20, /*pathingMap.getTextureStatic()*/); +// gl.glActiveTexture(GL30.GL_TEXTURE21, /*pathingMap.getTextureDynamic()*/); - if (tilesetCount > 15) { - shader.setUniformi("u_baseTileset", 15); +// gl.glEnableVertexAttribArray(0); + gl.glBindBuffer(GL30.GL_ARRAY_BUFFER, Shapes.INSTANCE.vertexBuffer); + gl.glVertexAttribPointer(0, 2, GL30.GL_FLOAT, false, 0, 0); - for (int i = 0, l = tilesetCount - 15; i < l; i++) { - final int isExtended = (tilesetTextures.get(i + 15).getWidth() > tilesetTextures.get(i + 15) - .getHeight()) ? 1 : 0; + gl.glBindBuffer(GL30.GL_ELEMENT_ARRAY_BUFFER, Shapes.INSTANCE.indexBuffer); + gl.glDrawElementsInstanced(GL30.GL_TRIANGLES, Shapes.INSTANCE.quadIndices.length * 3, GL30.GL_UNSIGNED_INT, 0, + (this.columns - 1) * (this.rows - 1)); - shader.setUniformf("u_extended[" + i + "]", isExtended); +// gl.glDisableVertexAttribArray(0); - tilesetTextures.get(i + 15).bind(i); - } + gl.glEnable(GL30.GL_BLEND); - instancedArrays.glDrawElementsInstancedANGLE(GL20.GL_TRIANGLES, 6, GL20.GL_UNSIGNED_BYTE, 0, - this.rows * this.columns); - } - - instancedArrays.glVertexAttribDivisorANGLE(texturesAttrib, 0); - instancedArrays.glVertexAttribDivisorANGLE(variationsAttrib, 0); - instancedArrays.glVertexAttribDivisorANGLE(instanceAttrib, 0); } - public void renderWater(final GL20 gl, final WebGL webgl, final Scene worldScene) { - final ANGLEInstancedArrays instancedArrays = webgl.instancedArrays; - final ShaderProgram shader = this.waterShader; - final int instanceAttrib = shader.getAttributeLocation("a_InstanceID"); - final int positionAttrib = shader.getAttributeLocation("a_position"); - final int isWaterAttrib = shader.getAttributeLocation("a_isWater"); + public void renderWater() { + // Render water + this.webGL.useShaderProgram(this.waterShader); - gl.glDepthMask(false); + final GL30 gl = Gdx.gl30; + gl.glEnable(GL30.GL_BLEND); - gl.glEnable(GL20.GL_BLEND); - gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA); + gl.glUniformMatrix4fv(0, 1, false, this.camera.viewProjectionMatrix.val, 0); + gl.glUniform4fv(1, 1, this.minShallowColor, 0); + gl.glUniform4fv(2, 1, this.maxShallowColor, 0); + gl.glUniform4fv(3, 1, this.minDeepColor, 0); + gl.glUniform4fv(4, 1, this.maxDeepColor, 0); + gl.glUniform1f(5, this.waterHeightOffset); + gl.glUniform1i(6, (int) this.waterIndex); + gl.glUniform1f(this.waterShader.getUniformLocation("centerOffsetX"), this.centerOffset[0]); + gl.glUniform1f(this.waterShader.getUniformLocation("centerOffsetY"), this.centerOffset[1]); - webgl.useShaderProgram(shader); + gl.glActiveTexture(GL30.GL_TEXTURE0); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.waterHeight); + gl.glActiveTexture(GL30.GL_TEXTURE1); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeight); + gl.glActiveTexture(GL30.GL_TEXTURE2); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.waterExists); + gl.glActiveTexture(GL30.GL_TEXTURE3); + gl.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.waterTextureArray); - shader.setUniformMatrix("u_VP", worldScene.camera.viewProjectionMatrix); - shader.setUniform2fv("u_offset", this.centerOffset, 0, 2); - sizeHeap[0] = this.columns; - sizeHeap[1] = this.rows; - shader.setUniform2fv("u_size", sizeHeap, 0, 2); - shader.setUniformi("u_heightMap", 0); - shader.setUniformi("u_waterHeightMap", 1); - shader.setUniformi("u_waterTexture", 2); - shader.setUniformf("u_offsetHeight", this.waterHeightOffset); - shader.setUniform4fv("u_maxDeepColor", this.maxDeepColor, 0, 4); - shader.setUniform4fv("u_minDeepColor", this.minDeepColor, 0, 4); - shader.setUniform4fv("u_maxShallowColor", this.maxShallowColor, 0, 4); - shader.setUniform4fv("u_minShallowColor", this.minShallowColor, 0, 4); + gl.glBindBuffer(GL30.GL_ARRAY_BUFFER, Shapes.INSTANCE.vertexBuffer); + gl.glVertexAttribPointer(0, 2, GL30.GL_FLOAT, false, 0, 0); - gl.glActiveTexture(GL20.GL_TEXTURE0); - gl.glBindTexture(GL20.GL_TEXTURE_2D, this.heightMap); + gl.glBindBuffer(GL30.GL_ELEMENT_ARRAY_BUFFER, Shapes.INSTANCE.indexBuffer); + gl.glDrawElementsInstanced(GL30.GL_TRIANGLES, Shapes.INSTANCE.quadIndices.length * 3, GL30.GL_UNSIGNED_INT, 0, + (this.columns - 1) * (this.rows - 1)); - gl.glActiveTexture(GL20.GL_TEXTURE1); - gl.glBindTexture(GL20.GL_TEXTURE_2D, this.waterHeightMap); - - this.waterTextures.get((int) this.waterIndex).bind(2); - - gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.vertexBuffer); - shader.setVertexAttribute(positionAttrib, 2, GL20.GL_FLOAT, false, 8, 0); - - gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.instanceBuffer); - shader.setVertexAttribute(instanceAttrib, 1, GL20.GL_FLOAT, false, 4, 0); - instancedArrays.glVertexAttribDivisorANGLE(instanceAttrib, 1); - - gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.waterBuffer); - shader.setVertexAttribute(isWaterAttrib, 1, GL20.GL_UNSIGNED_BYTE, false, 1, 0); - instancedArrays.glVertexAttribDivisorANGLE(isWaterAttrib, 1); - - gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, this.faceBuffer); - instancedArrays.glDrawElementsInstancedANGLE(GL20.GL_TRIANGLES, 6, GL20.GL_UNSIGNED_BYTE, 0, - this.rows * this.columns); - - instancedArrays.glVertexAttribDivisorANGLE(isWaterAttrib, 0); - instancedArrays.glVertexAttribDivisorANGLE(instanceAttrib, 0); + gl.glEnable(GL30.GL_BLEND); } - public void renderCliffs(final GL20 gl, final WebGL webGL, final Scene worldScene) { - final ANGLEInstancedArrays instancedArrays = webGL.instancedArrays; - final ShaderProgram shader = this.cliffShader; + public void renderCliffs() { - gl.glDisable(GL20.GL_BLEND); + // Render cliffs + for (final IVec3 i : this.cliffs) { + final RenderCorner bottomLeft = this.corners[i.x][i.y]; + final RenderCorner bottomRight = this.corners[i.x + 1][i.y]; + final RenderCorner topLeft = this.corners[i.x][i.y + 1]; + final RenderCorner topRight = this.corners[i.x + 1][i.y + 1]; - webGL.useShaderProgram(shader); + final float min = Math.min(Math.min(bottomLeft.getLayerHeight() - 2, bottomRight.getLayerHeight() - 2), + Math.min(topLeft.getLayerHeight() - 2, topRight.getLayerHeight() - 2)); - shader.setUniformMatrix("u_VP", worldScene.camera.viewProjectionMatrix); - shader.setUniformi("u_heightMap", 0); - shader.setUniformf("u_pixel[0]", 1 / (float) (this.columns + 1)); - shader.setUniformf("u_pixel[1]", 1 / (float) (this.rows + 1)); - shader.setUniform2fv("u_centerOffset", this.centerOffset, 0, 2); - shader.setUniformi("u_texture1", 1); - shader.setUniformi("u_texture2", 2); - - gl.glActiveTexture(GL20.GL_TEXTURE0); - gl.glBindTexture(GL20.GL_TEXTURE_2D, this.cliffHeightMap); - - gl.glActiveTexture(GL20.GL_TEXTURE1); - gl.glBindTexture(GL20.GL_TEXTURE_2D, this.cliffTextures.get(0).getGlTarget()); - - if (this.cliffTextures.size() > 1) { - gl.glActiveTexture(GL20.GL_TEXTURE2); - gl.glBindTexture(GL20.GL_TEXTURE_2D, this.cliffTextures.get(1).getGlTarget()); + fourComponentHeap[0] = i.x; + fourComponentHeap[1] = i.y; + fourComponentHeap[2] = min; + fourComponentHeap[3] = bottomLeft.getCliffTexture(); + this.cliffMeshes.get(i.z).renderQueue(fourComponentHeap); } - // Set instanced attributes. - for (final TerrainModel cliff : this.cliffModels) { - cliff.render(shader); + this.cliffShader.begin(); + + final GL30 gl = Gdx.gl30; + + // WC3 models are 128x too large + tempMatrix.set(this.camera.viewProjectionMatrix); + tempMatrix.scale(1 / 128f, 1 / 128f, 1 / 128f); + gl.glUniformMatrix4fv(0, 1, false, tempMatrix.val, 0); + gl.glUniform1i(1, this.viewer.renderPathing); + gl.glUniform1i(2, this.viewer.renderLighting); + + gl.glActiveTexture(GL30.GL_TEXTURE0); + gl.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.cliffTextureArray); + gl.glActiveTexture(GL30.GL_TEXTURE1); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundHeight); +// gl.glActiveTexture(GL30.GL_TEXTURE2); + for (final CliffMesh i : this.cliffMeshes) { + i.render(); } } - public Vector3 groundNormal(final Vector3 out, int x, int y) { - final float[] centerOffset = this.centerOffset; - final int[] mapSize = this.mapSize; +// public Vector3 groundNormal(final Vector3 out, int x, int y) { +// final float[] centerOffset = this.centerOffset; +// final int[] mapSize = this.mapSize; +// +// x = (int) ((x - centerOffset[0]) / 128); +// y = (int) ((y - centerOffset[1]) / 128); +// +// final int cellX = x; +// final int cellY = y; +// +// // See if this coordinate is in the map +// +// if ((cellX >= 0) && (cellX < (mapSize[0] - 1)) && (cellY >= 0) && (cellY < (mapSize[1] - 1))) { +// // See http://gamedev.stackexchange.com/a/24574 +// final Corner[][] corners = this.corners; +// final float bottomLeft = corners[cellY][cellX].getGroundHeight(); +// final float bottomRight = corners[cellY][cellX + 1].getGroundHeight(); +// final float topLeft = corners[cellY + 1][cellX].getGroundHeight(); +// final float topRight = corners[cellY + 1][cellX + 1].getGroundHeight(); +// final int sqX = x - cellX; +// final int sqY = y - cellY; +// +// if ((sqX + sqY) < 1) { +// normalHeap1.set(1, 0, bottomRight - bottomLeft); +// normalHeap2.set(0, 1, topLeft - bottomLeft); +// } +// else { +// normalHeap1.set(-1, 0, topRight - topLeft); +// normalHeap2.set(0, 1, topRight - bottomRight); +// } +// +// out.set(normalHeap1.crs(normalHeap2)).nor(); +// } +// else { +// out.set(0, 0, 1); +// } +// +// return out; +// } - x = (int) ((x - centerOffset[0]) / 128); - y = (int) ((y - centerOffset[1]) / 128); + private static final class UnloadedTexture { + private final int width; + private final int height; + private final Buffer data; - final int cellX = x; - final int cellY = y; - - // See if this coordinate is in the map - - if ((cellX >= 0) && (cellX < (mapSize[0] - 1)) && (cellY >= 0) && (cellY < (mapSize[1] - 1))) { - // See http://gamedev.stackexchange.com/a/24574 - final Corner[][] corners = this.corners; - final float bottomLeft = corners[cellY][cellX].getGroundHeight(); - final float bottomRight = corners[cellY][cellX + 1].getGroundHeight(); - final float topLeft = corners[cellY + 1][cellX].getGroundHeight(); - final float topRight = corners[cellY + 1][cellX + 1].getGroundHeight(); - final int sqX = x - cellX; - final int sqY = y - cellY; - - if ((sqX + sqY) < 1) { - normalHeap1.set(1, 0, bottomRight - bottomLeft); - normalHeap2.set(0, 1, topLeft - bottomLeft); - } - else { - normalHeap1.set(-1, 0, topRight - topLeft); - normalHeap2.set(0, 1, topRight - bottomRight); - } - - out.set(normalHeap1.crs(normalHeap2)).nor(); - } - else { - out.set(0, 0, 1); + public UnloadedTexture(final int width, final int height, final Buffer data) { + this.width = width; + this.height = height; + this.data = data; } - return out; } - } diff --git a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java index 84e654e..07fe4e9 100644 --- a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java +++ b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java @@ -1,5 +1,6 @@ package com.etheller.warsmash.desktop; +import org.lwjgl.opengl.GL12; import org.lwjgl.opengl.GL31; import org.lwjgl.opengl.GL33; @@ -29,6 +30,7 @@ public class DesktopLauncher { GL31.glDrawArraysInstanced(mode, first, count, instanceCount); } }; + Extensions.GL_BGRA = GL12.GL_BGRA; final LwjglApplicationConfiguration config = new LwjglApplicationConfiguration(); config.useGL30 = true; config.gles30ContextMajorVersion = 3; From 67e37244bd1a014a496f5bce620484020036acae Mon Sep 17 00:00:00 2001 From: Retera Date: Fri, 24 Jan 2020 00:59:18 -0600 Subject: [PATCH 016/116] Basic map viewer and basic portrait --- .../etheller/warsmash/WarsmashGdxGame.java | 49 ++++++++++-- .../etheller/warsmash/WarsmashGdxMapGame.java | 78 ++++++++++++++++++- .../com/etheller/warsmash/viewer5/Scene.java | 2 +- .../warsmash/viewer5/handlers/mdx/Camera.java | 19 +++-- .../handlers/mdx/MdxComplexInstance.java | 2 + .../viewer5/handlers/mdx/MdxModel.java | 4 + .../warsmash/viewer5/handlers/w3x/Doodad.java | 7 +- .../viewer5/handlers/w3x/War3MapViewer.java | 31 ++++++-- .../handlers/w3x/environment/Terrain.java | 7 +- ...HiveWEShaders.java => TerrainShaders.java} | 4 +- 10 files changed, 176 insertions(+), 27 deletions(-) rename core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/{HiveWEShaders.java => TerrainShaders.java} (99%) diff --git a/core/src/com/etheller/warsmash/WarsmashGdxGame.java b/core/src/com/etheller/warsmash/WarsmashGdxGame.java index 6f96dcf..02a2916 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxGame.java @@ -73,12 +73,16 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide this.cameraManager = new CameraManager(); this.cameraManager.setupCamera(scene); - this.mainModel = (MdxModel) this.viewer.load("Buildings\\Undead\\Necropolis\\Necropolis.mdx", new PathSolver() { - @Override - public SolvedPath solve(final String src, final Object solverParams) { - return new SolvedPath(src, src.substring(src.lastIndexOf('.')), true); - } - }, null); +// this.mainModel = (MdxModel) this.viewer.load("UI\\Glues\\MainMenu\\MainMenu3D_exp\\MainMenu3D_exp.mdx", + this.mainModel = (MdxModel) this.viewer.load("Units\\NightElf\\DruidOfTheClaw\\DruidOfTheClaw_Portrait.mdx", + new PathSolver() { + @Override + public SolvedPath solve(final String src, final Object solverParams) { + return new SolvedPath(src, src.substring(src.lastIndexOf('.')), true); + } + }, null); + + this.modelCamera = this.mainModel.cameras.get(0); this.mainInstance = (MdxComplexInstance) this.mainModel.addInstance(0); @@ -343,6 +347,9 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide private int frame = 0; private MdxComplexInstance mainInstance; private MdxModel mainModel; + private com.etheller.warsmash.viewer5.handlers.mdx.Camera modelCamera; + private final float[] cameraPositionTemp = new float[3]; + private final float[] cameraTargetTemp = new float[3]; @Override public void render() { @@ -406,6 +413,7 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide private Vector3 target; private Vector3 worldUp; private Vector3 vecHeap; + private Vector3 vecHeap2; private Quaternion quatHeap; private Quaternion quatHeap2; @@ -427,6 +435,7 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide this.target = new Vector3(0, 0, 50); this.worldUp = new Vector3(0, 0, 1); this.vecHeap = new Vector3(); + this.vecHeap2 = new Vector3(); this.quatHeap = new Quaternion(); this.quatHeap2 = new Quaternion(); @@ -451,6 +460,34 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide this.quatHeap.transform(this.position); this.position.scl(this.distance); this.position = this.position.add(this.target); + if (WarsmashGdxGame.this.modelCamera != null) { + WarsmashGdxGame.this.modelCamera.getPositionTranslation(WarsmashGdxGame.this.cameraPositionTemp, + WarsmashGdxGame.this.mainInstance.sequence, WarsmashGdxGame.this.mainInstance.frame, + WarsmashGdxGame.this.mainInstance.counter); + WarsmashGdxGame.this.modelCamera.getTargetTranslation(WarsmashGdxGame.this.cameraTargetTemp, + WarsmashGdxGame.this.mainInstance.sequence, WarsmashGdxGame.this.mainInstance.frame, + WarsmashGdxGame.this.mainInstance.counter); + + this.position.set(WarsmashGdxGame.this.modelCamera.position); + this.target.set(WarsmashGdxGame.this.modelCamera.targetPosition); +// this.vecHeap2.set(this.target); +// this.vecHeap2.sub(this.position); +// this.vecHeap.set(this.vecHeap2); +// this.vecHeap.crs(this.worldUp); +// this.vecHeap.crs(this.vecHeap2); +// this.vecHeap.nor(); +// this.vecHeap.scl(this.camera.rect.height / 2f); +// this.position.add(this.vecHeap); + + this.position.add(WarsmashGdxGame.this.cameraPositionTemp[0], + WarsmashGdxGame.this.cameraPositionTemp[1], WarsmashGdxGame.this.cameraPositionTemp[2]); + this.target.add(WarsmashGdxGame.this.cameraTargetTemp[0], WarsmashGdxGame.this.cameraTargetTemp[1], + WarsmashGdxGame.this.cameraTargetTemp[2]); + this.camera.perspective(WarsmashGdxGame.this.modelCamera.fieldOfView, + Gdx.graphics.getWidth() / (float) Gdx.graphics.getHeight(), + WarsmashGdxGame.this.modelCamera.nearClippingPlane, + WarsmashGdxGame.this.modelCamera.farClippingPlane); + } this.camera.moveToAndFace(this.position, this.target, this.worldUp); } diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java index 9fe4927..5e58942 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java @@ -11,6 +11,7 @@ import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Input; import com.badlogic.gdx.InputProcessor; import com.badlogic.gdx.graphics.GL20; +import com.badlogic.gdx.graphics.GL30; import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.math.Quaternion; @@ -23,7 +24,11 @@ import com.etheller.warsmash.datasources.DataSourceDescriptor; import com.etheller.warsmash.datasources.FolderDataSourceDescriptor; import com.etheller.warsmash.viewer5.Camera; import com.etheller.warsmash.viewer5.CanvasProvider; +import com.etheller.warsmash.viewer5.PathSolver; import com.etheller.warsmash.viewer5.Scene; +import com.etheller.warsmash.viewer5.SolvedPath; +import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; +import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProvider, InputProcessor { @@ -34,6 +39,11 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv private BitmapFont font; private SpriteBatch batch; + private CameraManager portraitCameraManager; + private MdxModel portraitModel; + private MdxComplexInstance portraitInstance; + private final float[] cameraPositionTemp = new float[3]; + private final float[] cameraTargetTemp = new float[3]; @Override public void create() { @@ -74,7 +84,35 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.cameraManager.setupCamera(this.viewer.worldScene); System.out.println("Loaded"); - Gdx.gl30.glClearColor(0.5f, 0.5f, 0.5f, 1); // TODO remove white background + Gdx.gl30.glClearColor(0.0f, 0.0f, 0.0f, 1); // TODO remove white background + Gdx.gl30.glEnable(GL30.GL_SCISSOR_TEST); + + this.portraitScene = this.viewer.addScene(); + this.portraitCameraManager = new CameraManager(); + this.portraitCameraManager.setupCamera(this.portraitScene); + +// this.mainModel = (MdxModel) this.viewer.load("UI\\Glues\\MainMenu\\MainMenu3D_exp\\MainMenu3D_exp.mdx", + this.portraitModel = (MdxModel) this.viewer.load("Units\\NightElf\\Runner\\Runner_Portrait.mdx", + new PathSolver() { + @Override + public SolvedPath solve(final String src, final Object solverParams) { + return new SolvedPath(src, src.substring(src.lastIndexOf('.')), true); + } + }, null); + + this.portraitCameraManager.modelCamera = this.portraitModel.cameras.get(0); + this.portraitScene.camera.viewport(new Rectangle(100, 0, 100, 100)); + + this.portraitInstance = (MdxComplexInstance) this.portraitModel.addInstance(0); + + this.portraitInstance.setTeamColor(1); + + this.portraitInstance.setScene(this.portraitScene); + + final int animIndex = 0; + this.portraitInstance.setSequence(animIndex); + + this.portraitInstance.setSequenceLoopMode(0); this.font = new BitmapFont(); this.batch = new SpriteBatch(); @@ -87,6 +125,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv Gdx.gl30.glBindVertexArray(WarsmashGdxGame.VAO); this.cameraManager.target.add(this.cameraVelocity.x, this.cameraVelocity.y, 0); this.cameraManager.updateCamera(); + this.portraitCameraManager.updateCamera(); this.viewer.updateAndRender(); // gl.glDrawElements(GL20.GL_TRIANGLES, this.elements, GL20.GL_UNSIGNED_SHORT, this.faceOffset); @@ -94,6 +133,11 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv // this.batch.begin(); // this.font.draw(this.batch, Integer.toString(Gdx.graphics.getFramesPerSecond()), 0, 0); // this.batch.end(); + + if (this.portraitInstance.sequenceEnded) { + this.portraitInstance + .setSequence((this.portraitInstance.sequence + 1) % this.portraitModel.getSequences().size()); + } } @Override @@ -112,12 +156,22 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv @Override public void resize(final int width, final int height) { + this.tempRect.x = 0; + this.tempRect.y = 0; this.tempRect.width = width; this.tempRect.height = height; this.cameraManager.camera.viewport(this.tempRect); + final float portraitTestWidth = (100 / 640f) * width; + final float portraitTestHeight = (100 / 480f) * height; + this.tempRect.x = portraitTestWidth; + this.tempRect.y = 0; + this.tempRect.width = portraitTestWidth; + this.tempRect.height = portraitTestHeight; + this.portraitScene.camera.viewport(this.tempRect); } class CameraManager { + public com.etheller.warsmash.viewer5.handlers.mdx.Camera modelCamera; private CanvasProvider canvas; private Camera camera; private float moveSpeed; @@ -175,6 +229,27 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.quatHeap.transform(this.position); this.position.scl(this.distance); this.position = this.position.add(this.target); + if (this.modelCamera != null) { + this.modelCamera.getPositionTranslation(WarsmashGdxMapGame.this.cameraPositionTemp, + WarsmashGdxMapGame.this.portraitInstance.sequence, + WarsmashGdxMapGame.this.portraitInstance.frame, + WarsmashGdxMapGame.this.portraitInstance.counter); + this.modelCamera.getTargetTranslation(WarsmashGdxMapGame.this.cameraTargetTemp, + WarsmashGdxMapGame.this.portraitInstance.sequence, + WarsmashGdxMapGame.this.portraitInstance.frame, + WarsmashGdxMapGame.this.portraitInstance.counter); + + this.position.set(this.modelCamera.position); + this.target.set(this.modelCamera.targetPosition); + + this.position.add(WarsmashGdxMapGame.this.cameraPositionTemp[0], + WarsmashGdxMapGame.this.cameraPositionTemp[1], WarsmashGdxMapGame.this.cameraPositionTemp[2]); + this.target.add(WarsmashGdxMapGame.this.cameraTargetTemp[0], + WarsmashGdxMapGame.this.cameraTargetTemp[1], WarsmashGdxMapGame.this.cameraTargetTemp[2]); + this.camera.perspective(this.modelCamera.fieldOfView, + Gdx.graphics.getWidth() / (float) Gdx.graphics.getHeight(), this.modelCamera.nearClippingPlane, + this.modelCamera.farClippingPlane); + } this.camera.moveToAndFace(this.position, this.target, this.worldUp); } @@ -186,6 +261,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv private final float cameraSpeed = 10.0f; private final Vector2 cameraVelocity = new Vector2(); + private Scene portraitScene; @Override public boolean keyDown(final int keycode) { diff --git a/core/src/com/etheller/warsmash/viewer5/Scene.java b/core/src/com/etheller/warsmash/viewer5/Scene.java index 5d0c038..02315d4 100644 --- a/core/src/com/etheller/warsmash/viewer5/Scene.java +++ b/core/src/com/etheller/warsmash/viewer5/Scene.java @@ -255,7 +255,7 @@ public class Scene { // If this scene doesn't want alpha, clear it. if (!this.alpha) { gl.glDepthMask(true); - gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT); + gl.glClear(GL20.GL_DEPTH_BUFFER_BIT | GL20.GL_COLOR_BUFFER_BIT); } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Camera.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Camera.java index d074e4b..697c138 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Camera.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Camera.java @@ -1,15 +1,16 @@ package com.etheller.warsmash.viewer5.handlers.mdx; import com.etheller.warsmash.parsers.mdlx.AnimationMap; +import com.etheller.warsmash.util.RenderMathUtils; public class Camera extends AnimatedObject { - private final String name; - private final float[] position; - private final float fieldOfView; - private final float farClippingPlane; - private final float nearClippingPlane; - private final float[] targetPosition; + public final String name; + public final float[] position; + public final float fieldOfView; + public final float farClippingPlane; + public final float nearClippingPlane; + public final float[] targetPosition; public Camera(final MdxModel model, final com.etheller.warsmash.parsers.mdlx.Camera camera) { super(model, camera); @@ -23,11 +24,13 @@ public class Camera extends AnimatedObject { } public int getPositionTranslation(final float[] out, final int sequence, final int frame, final int counter) { - return this.getVectorValue(out, AnimationMap.KCTR.getWar3id(), sequence, frame, counter, this.position); + return this.getVectorValue(out, AnimationMap.KCTR.getWar3id(), sequence, frame, counter, + RenderMathUtils.FLOAT_VEC3_ZERO); } public int getTargetTranslation(final float[] out, final int sequence, final int frame, final int counter) { - return this.getVectorValue(out, AnimationMap.KTTR.getWar3id(), sequence, frame, counter, this.targetPosition); + return this.getVectorValue(out, AnimationMap.KTTR.getWar3id(), sequence, frame, counter, + RenderMathUtils.FLOAT_VEC3_ZERO); } public int getRotation(final float[] out, final int sequence, final int frame, final int counter) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java index ff51f8f..4b77b29 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java @@ -16,6 +16,7 @@ import com.etheller.warsmash.viewer5.Node; import com.etheller.warsmash.viewer5.RenderBatch; import com.etheller.warsmash.viewer5.Scene; import com.etheller.warsmash.viewer5.SkeletalNode; +import com.etheller.warsmash.viewer5.Texture; import com.etheller.warsmash.viewer5.TextureMapper; import com.etheller.warsmash.viewer5.UpdatableObject; import com.etheller.warsmash.viewer5.gl.DataTexture; @@ -58,6 +59,7 @@ public class MdxComplexInstance extends ModelInstance { public Matrix4[] worldMatrices; public FloatBuffer worldMatricesCopyHeap; public DataTexture boneTexture; + public Texture[] replaceableTextures = new Texture[64]; public MdxComplexInstance(final MdxModel model) { super(model); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java index 656afeb..4a19552 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java @@ -347,4 +347,8 @@ public class MdxModel extends com.etheller.warsmash.viewer5.Model { } + public List getCameras() { + return this.cameras; + } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Doodad.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Doodad.java index e6a0bd4..eb1c28b 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Doodad.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Doodad.java @@ -2,6 +2,7 @@ package com.etheller.warsmash.viewer5.handlers.w3x; import com.badlogic.gdx.math.Quaternion; import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; +import com.etheller.warsmash.units.manager.MutableObjectData.WorldEditorDataType; import com.etheller.warsmash.util.RenderMathUtils; import com.etheller.warsmash.viewer5.ModelInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; @@ -11,7 +12,7 @@ public class Doodad { private final MutableGameObject row; public Doodad(final War3MapViewer map, final MdxModel model, final MutableGameObject row, - final com.etheller.warsmash.parsers.w3x.doo.Doodad doodad) { + final com.etheller.warsmash.parsers.w3x.doo.Doodad doodad, final WorldEditorDataType type) { final boolean isSimple = row.readSLKTagBoolean("lightweight"); ModelInstance instance; @@ -25,6 +26,10 @@ public class Doodad { instance.move(doodad.getLocation()); instance.rotate(new Quaternion().setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, doodad.getAngle())); instance.scale(doodad.getScale()); + if (type == WorldEditorDataType.DOODADS) { + final float defScale = row.readSLKTagFloat("defScale"); + instance.uniformScale(defScale); + } instance.setScene(map.worldScene); this.instance = instance; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index be773b1..02524db 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -47,6 +47,8 @@ import mpq.MPQArchive; import mpq.MPQException; public class War3MapViewer extends ModelViewer { + private static final War3ID UNIT_FILE = War3ID.fromString("umdl"); + private static final War3ID ITEM_FILE = War3ID.fromString("ifil"); private static final War3ID sloc = War3ID.fromString("sloc"); private static final LoadGenericCallback stringDataCallback = new StringDataCallbackImplementation(); public static final StreamDataCallbackImplementation streamDataCallback = new StreamDataCallbackImplementation(); @@ -253,10 +255,11 @@ public class War3MapViewer extends ModelViewer { final War3MapDoo doo = this.mapMpq.readDoodads(); for (final com.etheller.warsmash.parsers.w3x.doo.Doodad doodad : doo.getDoodads()) { - + WorldEditorDataType type = WorldEditorDataType.DOODADS; MutableGameObject row = modifications.getDoodads().get(doodad.getId()); if (row == null) { row = modifications.getDestructibles().get(doodad.getId()); + type = WorldEditorDataType.DESTRUCTIBLES; } String file = row.readSLKTag("file"); final int numVar = row.readSLKTagInt("numVar"); @@ -294,7 +297,7 @@ public class War3MapViewer extends ModelViewer { model = (MdxModel) this.load(fileVar, this.mapPathSolver, this.solverParams); } - this.doodads.add(new Doodad(this, model, row, doodad)); + this.doodads.add(new Doodad(this, model, row, doodad, type)); } // Cliff/Terrain doodads. @@ -341,10 +344,19 @@ public class War3MapViewer extends ModelViewer { row = modifications.getUnits().get(unit.getId()); if (row == null) { row = modifications.getItems().get(unit.getId()); - } + path = row.getFieldAsString(ITEM_FILE, 0); - if (row != null) { - path = row.readSLKTag("file"); + if (path.toLowerCase().endsWith(".mdl") || path.toLowerCase().endsWith(".mdx")) { + path = path.substring(0, path.length() - 4); + } + if (row.readSLKTagInt("fileVerFlags") == 2) { + path += "_V1"; + } + + path += ".mdx"; + } + else { + path = row.getFieldAsString(UNIT_FILE, 0); if (path.toLowerCase().endsWith(".mdl") || path.toLowerCase().endsWith(".mdx")) { path = path.substring(0, path.length() - 4); @@ -402,6 +414,15 @@ public class War3MapViewer extends ModelViewer { worldScene.renderOpaque(); this.terrain.renderWater(); worldScene.renderTranslucent(); + + final List scenes = this.scenes; + for (final Scene scene : scenes) { + if (scene != worldScene) { + scene.startFrame(); + scene.renderOpaque(); + scene.renderTranslucent(); + } + } } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java index a71fb70..67ba5ea 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java @@ -302,9 +302,9 @@ public class Terrain { updateGroundHeights(new Rectangle(0, 0, width - 1, height - 1)); - this.groundShader = webGL.createShaderProgram(HiveWEShaders.Terrain.vert, HiveWEShaders.Terrain.frag); - this.cliffShader = webGL.createShaderProgram(HiveWEShaders.Cliffs.vert, HiveWEShaders.Cliffs.frag); - this.waterShader = webGL.createShaderProgram(HiveWEShaders.Water.vert, HiveWEShaders.Water.frag); + this.groundShader = webGL.createShaderProgram(TerrainShaders.Terrain.vert, TerrainShaders.Terrain.frag); + this.cliffShader = webGL.createShaderProgram(TerrainShaders.Cliffs.vert, TerrainShaders.Cliffs.frag); + this.waterShader = webGL.createShaderProgram(TerrainShaders.Water.vert, TerrainShaders.Water.frag); // TODO collision bodies (?) @@ -649,6 +649,7 @@ public class Terrain { this.webGL.useShaderProgram(this.groundShader); final GL30 gl = Gdx.gl30; + gl.glDisable(GL30.GL_CULL_FACE); gl.glDisable(GL30.GL_BLEND); gl.glUniformMatrix4fv(this.groundShader.getUniformLocation("MVP"), 1, false, diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/HiveWEShaders.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/TerrainShaders.java similarity index 99% rename from core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/HiveWEShaders.java rename to core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/TerrainShaders.java index 16d7d21..311a955 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/HiveWEShaders.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/TerrainShaders.java @@ -1,6 +1,6 @@ package com.etheller.warsmash.viewer5.handlers.w3x.environment; -public class HiveWEShaders { +public class TerrainShaders { public static final class Cliffs { private Cliffs() { } @@ -273,7 +273,7 @@ public class HiveWEShaders { " gl_Position = is_water ? MVP * vec4((vPosition.x + pos.x)*128.0 + centerOffsetX, (vPosition.y + pos.y)*128.0 + centerOffsetY, water_height*128.0, 1) : vec4(2.0, 0.0, 0.0, 1.0);\r\n" + // "\r\n" + // - " UV = vec2(vPosition.x, 1 - vPosition.y);\r\n" + // + " UV = vec2(vPosition.x, vPosition.y);\r\n" + // "\r\n" + // " float ground_height = texelFetch(ground_height_texture, height_pos, 0).r;\r\n" + // " float value = clamp(water_height - ground_height, 0.f, 1.f);\r\n" + // From 960f6b973808e9cd32cd50a77980b36f2625bd68 Mon Sep 17 00:00:00 2001 From: Retera Date: Sat, 25 Jan 2020 12:00:26 -0600 Subject: [PATCH 017/116] Add splats and camera target height based on terrain and other stuff --- core/src/com/etheller/warsmash/TestMain.java | 29 ++ .../etheller/warsmash/WarsmashGdxGame.java | 4 +- .../etheller/warsmash/WarsmashGdxMapGame.java | 36 ++- .../datasources/FolderDataSource.java | 3 +- .../datasources/SubdirDataSource.java | 49 +++ .../warsmash/util/RenderMathUtils.java | 37 +++ .../warsmash/util/WarsmashConstants.java | 1 + .../com/etheller/warsmash/viewer5/Camera.java | 4 + .../warsmash/viewer5/ModelInstance.java | 2 + .../warsmash/viewer5/ModelViewer.java | 3 + .../etheller/warsmash/viewer5/PathSolver.java | 12 +- .../viewer5/RawOpenGLTextureResource.java | 19 ++ .../warsmash/viewer5/gl/Extensions.java | 2 - .../viewer5/handlers/mdx/BatchGroup.java | 11 +- .../handlers/mdx/GeometryEmitterFuncs.java | 16 +- .../handlers/mdx/MdxComplexInstance.java | 19 +- .../viewer5/handlers/mdx/MdxHandler.java | 10 - .../viewer5/handlers/mdx/MdxModel.java | 34 +-- .../handlers/mdx/MdxSimpleInstance.java | 9 + .../handlers/mdx/ParticleEmitter2Object.java | 4 +- .../viewer5/handlers/mdx/ReplaceableIds.java | 4 +- .../warsmash/viewer5/handlers/w3x/Doodad.java | 14 + .../viewer5/handlers/w3x/SplatModel.java | 138 +++++++++ .../viewer5/handlers/w3x/TerrainModel.java | 178 ----------- .../warsmash/viewer5/handlers/w3x/Unit.java | 40 ++- .../viewer5/handlers/w3x/W3xShaders.java | 287 +++--------------- .../viewer5/handlers/w3x/War3MapViewer.java | 112 +++++-- .../handlers/w3x/environment/CliffMesh.java | 26 +- .../w3x/environment/GroundTexture.java | 3 +- .../handlers/w3x/environment/Terrain.java | 271 +++++++++++++++-- .../w3x/environment/TerrainShaders.java | 18 +- .../warsmash/desktop/DesktopLauncher.java | 2 - 32 files changed, 824 insertions(+), 573 deletions(-) create mode 100644 core/src/com/etheller/warsmash/TestMain.java create mode 100644 core/src/com/etheller/warsmash/datasources/SubdirDataSource.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/SplatModel.java delete mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/TerrainModel.java diff --git a/core/src/com/etheller/warsmash/TestMain.java b/core/src/com/etheller/warsmash/TestMain.java new file mode 100644 index 0000000..ae0767c --- /dev/null +++ b/core/src/com/etheller/warsmash/TestMain.java @@ -0,0 +1,29 @@ +package com.etheller.warsmash; + +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.InputStream; + +import javax.imageio.ImageIO; +import javax.swing.ImageIcon; +import javax.swing.JLabel; +import javax.swing.JOptionPane; + +import com.etheller.warsmash.datasources.DataSource; +import com.etheller.warsmash.datasources.MpqDataSourceDescriptor; + +public class TestMain { + public static void main(final String[] args) { + final MpqDataSourceDescriptor desc = new MpqDataSourceDescriptor("E:\\Backups\\Warcraft\\Data\\127\\Z.mpq"); + final DataSource createDataSource = desc.createDataSource(); + try { + final InputStream cliffZ = createDataSource.getResourceAsStream("ReplaceableTextures\\Cliff\\Cliff0.blp"); + final BufferedImage img = ImageIO.read(cliffZ); + JOptionPane.showMessageDialog(null, new JLabel(new ImageIcon(img))); + } + catch (final IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } +} diff --git a/core/src/com/etheller/warsmash/WarsmashGdxGame.java b/core/src/com/etheller/warsmash/WarsmashGdxGame.java index 02a2916..daf71d6 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxGame.java @@ -74,7 +74,7 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide this.cameraManager.setupCamera(scene); // this.mainModel = (MdxModel) this.viewer.load("UI\\Glues\\MainMenu\\MainMenu3D_exp\\MainMenu3D_exp.mdx", - this.mainModel = (MdxModel) this.viewer.load("Units\\NightElf\\DruidOfTheClaw\\DruidOfTheClaw_Portrait.mdx", + this.mainModel = (MdxModel) this.viewer.load("Doodads\\Cinematic\\RisingWaterDoodad\\RisingWaterDoodad.mdx", new PathSolver() { @Override public SolvedPath solve(final String src, final Object solverParams) { @@ -82,7 +82,7 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide } }, null); - this.modelCamera = this.mainModel.cameras.get(0); +// this.modelCamera = this.mainModel.cameras.get(0); this.mainInstance = (MdxComplexInstance) this.mainModel.addInstance(0); diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java index 5e58942..a5b14cb 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java @@ -60,21 +60,21 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv final String renderer = Gdx.gl.glGetString(GL20.GL_RENDERER); System.err.println("Renderer: " + renderer); -// final FolderDataSourceDescriptor war3mpq = new FolderDataSourceDescriptor("E:\\Backups\\Warcraft\\Data\\127"); - final FolderDataSourceDescriptor war3mpq = new FolderDataSourceDescriptor( - "D:\\NEEDS_ORGANIZING\\MPQBuild\\War3.mpq\\war3.mpq"); - final FolderDataSourceDescriptor war3xLocalmpq = new FolderDataSourceDescriptor( - "D:\\NEEDS_ORGANIZING\\MPQBuild\\War3xLocal.mpq\\enus-war3local.mpq"); + final FolderDataSourceDescriptor war3mpq = new FolderDataSourceDescriptor("E:\\Backups\\Warcraft\\Data\\127"); +// final FolderDataSourceDescriptor war3mpq = new FolderDataSourceDescriptor( +// "D:\\NEEDS_ORGANIZING\\MPQBuild\\War3.mpq\\war3.mpq"); +// final FolderDataSourceDescriptor war3xLocalmpq = new FolderDataSourceDescriptor( +// "D:\\NEEDS_ORGANIZING\\MPQBuild\\War3xLocal.mpq\\enus-war3local.mpq"); final FolderDataSourceDescriptor testingFolder = new FolderDataSourceDescriptor( "D:\\NEEDS_ORGANIZING\\MPQBuild\\Test"); final FolderDataSourceDescriptor currentFolder = new FolderDataSourceDescriptor("."); this.codebase = new CompoundDataSourceDescriptor( - Arrays.asList(war3mpq, war3xLocalmpq, testingFolder, currentFolder)) + Arrays.asList(war3mpq, /* war3xLocalmpq, */ testingFolder, currentFolder)) .createDataSource(); this.viewer = new War3MapViewer(this.codebase, this); try { - this.viewer.loadMap("ReforgedGeorgeVacation.w3x"); + this.viewer.loadMap("(2)BootyBay.w3m"); } catch (final IOException e) { throw new RuntimeException(e); @@ -101,7 +101,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv }, null); this.portraitCameraManager.modelCamera = this.portraitModel.cameras.get(0); - this.portraitScene.camera.viewport(new Rectangle(100, 0, 100, 100)); + this.portraitScene.camera.viewport(new Rectangle(100, 0, 6400, 48)); this.portraitInstance = (MdxComplexInstance) this.portraitModel.addInstance(0); @@ -120,10 +120,15 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv Gdx.input.setInputProcessor(this); } + private int frame = 0; + @Override public void render() { + final float deltaTime = Gdx.graphics.getDeltaTime(); Gdx.gl30.glBindVertexArray(WarsmashGdxGame.VAO); - this.cameraManager.target.add(this.cameraVelocity.x, this.cameraVelocity.y, 0); + this.cameraManager.target.add(this.cameraVelocity.x * deltaTime, this.cameraVelocity.y * deltaTime, 0); + this.cameraManager.target.z = this.viewer.terrain.getGroundHeight(this.cameraManager.target.x, + this.cameraManager.target.y); this.cameraManager.updateCamera(); this.portraitCameraManager.updateCamera(); this.viewer.updateAndRender(); @@ -138,6 +143,10 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.portraitInstance .setSequence((this.portraitInstance.sequence + 1) % this.portraitModel.getSequences().size()); } + + if ((this.frame++ % 1000) == 0) { + System.out.println(Gdx.graphics.getFramesPerSecond()); + } } @Override @@ -156,6 +165,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv @Override public void resize(final int width, final int height) { + super.resize(width, height); this.tempRect.x = 0; this.tempRect.y = 0; this.tempRect.width = width; @@ -246,9 +256,8 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv WarsmashGdxMapGame.this.cameraPositionTemp[1], WarsmashGdxMapGame.this.cameraPositionTemp[2]); this.target.add(WarsmashGdxMapGame.this.cameraTargetTemp[0], WarsmashGdxMapGame.this.cameraTargetTemp[1], WarsmashGdxMapGame.this.cameraTargetTemp[2]); - this.camera.perspective(this.modelCamera.fieldOfView, - Gdx.graphics.getWidth() / (float) Gdx.graphics.getHeight(), this.modelCamera.nearClippingPlane, - this.modelCamera.farClippingPlane); + this.camera.perspective(this.modelCamera.fieldOfView, this.camera.getAspect(), + this.modelCamera.nearClippingPlane, this.modelCamera.farClippingPlane); } this.camera.moveToAndFace(this.position, this.target, this.worldUp); @@ -259,7 +268,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv // } } - private final float cameraSpeed = 10.0f; + private final float cameraSpeed = 4096.0f; // per second private final Vector2 cameraVelocity = new Vector2(); private Scene portraitScene; @@ -304,6 +313,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv @Override public boolean touchDown(final int screenX, final int screenY, final int pointer, final int button) { + System.out.println(screenX + "," + screenY); return false; } diff --git a/core/src/com/etheller/warsmash/datasources/FolderDataSource.java b/core/src/com/etheller/warsmash/datasources/FolderDataSource.java index 7fac978..ba903a7 100644 --- a/core/src/com/etheller/warsmash/datasources/FolderDataSource.java +++ b/core/src/com/etheller/warsmash/datasources/FolderDataSource.java @@ -53,7 +53,8 @@ public class FolderDataSource implements DataSource { if ("".equals(filepath)) { return false; // special case for folder data source, dont do this } - return Files.exists(this.folderPath.resolve(filepath)); + final Path resolvedPath = this.folderPath.resolve(filepath); + return Files.exists(resolvedPath) && !Files.isDirectory(resolvedPath); } @Override diff --git a/core/src/com/etheller/warsmash/datasources/SubdirDataSource.java b/core/src/com/etheller/warsmash/datasources/SubdirDataSource.java new file mode 100644 index 0000000..d591390 --- /dev/null +++ b/core/src/com/etheller/warsmash/datasources/SubdirDataSource.java @@ -0,0 +1,49 @@ +package com.etheller.warsmash.datasources; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public class SubdirDataSource implements DataSource { + private final DataSource dataSource; + private final String subdir; + + public SubdirDataSource(final DataSource dataSource, final String subdir) { + this.dataSource = dataSource; + this.subdir = subdir; + } + + @Override + public File getFile(final String filepath) throws IOException { + return this.dataSource.getFile(this.subdir + filepath); + } + + @Override + public InputStream getResourceAsStream(final String filepath) throws IOException { + return this.dataSource.getResourceAsStream(this.subdir + filepath); + } + + @Override + public boolean has(final String filepath) { + return this.dataSource.has(this.subdir + filepath); + } + + @Override + public Collection getListfile() { + final List results = new ArrayList<>(); + for (final String x : this.dataSource.getListfile()) { + if (x.startsWith(this.subdir)) { + results.add(x.substring(this.subdir.length())); + } + } + return results; + } + + @Override + public void close() throws IOException { + this.dataSource.close(); + } +} diff --git a/core/src/com/etheller/warsmash/util/RenderMathUtils.java b/core/src/com/etheller/warsmash/util/RenderMathUtils.java index c27c314..0232076 100644 --- a/core/src/com/etheller/warsmash/util/RenderMathUtils.java +++ b/core/src/com/etheller/warsmash/util/RenderMathUtils.java @@ -451,6 +451,11 @@ public enum RenderMathUtils { return out; } + // ==== All of the following "wrap" calls are horribly inefficient. Eventually + // they should be removed entirely with better design. + // Until that happens, be sure to only call them during setup and not while the + // simulation is live. Otherwise you'll probably get some + // bad lag (and memory leaks?). public static ShortBuffer wrapFaces(final int[] faces) { final ShortBuffer wrapper = ByteBuffer.allocateDirect(faces.length * 2).order(ByteOrder.nativeOrder()) .asShortBuffer(); @@ -517,4 +522,36 @@ public enum RenderMathUtils { wrapper.clear(); return wrapper; } + + public static Buffer wrap(final List vertices) { + if (vertices.isEmpty()) { + return null; + } + final int expectedNumberOfFloats = vertices.get(0).length; + final FloatBuffer wrapper = ByteBuffer.allocateDirect(vertices.size() * expectedNumberOfFloats * 4) + .order(ByteOrder.nativeOrder()).asFloatBuffer(); + for (final float[] subArray : vertices) { + for (final float f : subArray) { + wrapper.put(f); + } + } + wrapper.clear(); + return wrapper; + } + + public static Buffer wrapFaces(final List indices) { + if (indices.isEmpty()) { + return null; + } + final int expectedNumberOfValues = indices.get(0).length; + final ShortBuffer wrapper = ByteBuffer.allocateDirect(indices.size() * expectedNumberOfValues * 2) + .order(ByteOrder.nativeOrder()).asShortBuffer(); + for (final int[] subArray : indices) { + for (final int value : subArray) { + wrapper.put((short) value); + } + } + wrapper.clear(); + return wrapper; + } } diff --git a/core/src/com/etheller/warsmash/util/WarsmashConstants.java b/core/src/com/etheller/warsmash/util/WarsmashConstants.java index 0e05786..0e4a23d 100644 --- a/core/src/com/etheller/warsmash/util/WarsmashConstants.java +++ b/core/src/com/etheller/warsmash/util/WarsmashConstants.java @@ -2,4 +2,5 @@ package com.etheller.warsmash.util; public class WarsmashConstants { public static final int MAX_PLAYERS = 28; + public static final int REPLACEABLE_TEXTURE_LIMIT = 64; } diff --git a/core/src/com/etheller/warsmash/viewer5/Camera.java b/core/src/com/etheller/warsmash/viewer5/Camera.java index 8b6da40..6ad07b4 100644 --- a/core/src/com/etheller/warsmash/viewer5/Camera.java +++ b/core/src/com/etheller/warsmash/viewer5/Camera.java @@ -146,6 +146,10 @@ public class Camera { this.dirty = true; } + public float getAspect() { + return this.aspect; + } + public void setLocation(final Vector3 location) { this.location.set(location); diff --git a/core/src/com/etheller/warsmash/viewer5/ModelInstance.java b/core/src/com/etheller/warsmash/viewer5/ModelInstance.java index 96a7afe..68c987f 100644 --- a/core/src/com/etheller/warsmash/viewer5/ModelInstance.java +++ b/core/src/com/etheller/warsmash/viewer5/ModelInstance.java @@ -124,4 +124,6 @@ public abstract class ModelInstance extends Node { public abstract void load(); protected abstract RenderBatch getBatch(TextureMapper textureMapper2); + + public abstract void setReplaceableTexture(int replaceableTextureId, String replaceableTextureFile); } diff --git a/core/src/com/etheller/warsmash/viewer5/ModelViewer.java b/core/src/com/etheller/warsmash/viewer5/ModelViewer.java index 806fc5e..7986dc6 100644 --- a/core/src/com/etheller/warsmash/viewer5/ModelViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/ModelViewer.java @@ -177,6 +177,9 @@ public class ModelViewer { // TODO this is a synchronous hack, skipped some Ghostwolf code try { + if (!this.dataSource.has(finalSrc)) { + System.err.println("Attempting to load non-existant file: " + finalSrc); + } resource.loadData(this.dataSource.getResourceAsStream(finalSrc), null); } catch (final IOException e) { diff --git a/core/src/com/etheller/warsmash/viewer5/PathSolver.java b/core/src/com/etheller/warsmash/viewer5/PathSolver.java index a4432e0..159e097 100644 --- a/core/src/com/etheller/warsmash/viewer5/PathSolver.java +++ b/core/src/com/etheller/warsmash/viewer5/PathSolver.java @@ -12,10 +12,20 @@ public interface PathSolver { @Override public SolvedPath solve(final String src, final Object solverParams) { final int dotIndex = src.lastIndexOf('.'); - if (dotIndex == -1) { + if ((dotIndex == -1)) { throw new IllegalStateException("unable to resolve: " + src); } return new SolvedPath(src, src.substring(dotIndex), true); } }; + public static final PathSolver NOFETCH = new PathSolver() { + @Override + public SolvedPath solve(final String src, final Object solverParams) { + final int dotIndex = src.lastIndexOf('.'); + if ((dotIndex == -1)) { + throw new IllegalStateException("unable to resolve: " + src); + } + return new SolvedPath(src, src.substring(dotIndex), false); + } + }; } diff --git a/core/src/com/etheller/warsmash/viewer5/RawOpenGLTextureResource.java b/core/src/com/etheller/warsmash/viewer5/RawOpenGLTextureResource.java index 8cd0d63..78869e8 100644 --- a/core/src/com/etheller/warsmash/viewer5/RawOpenGLTextureResource.java +++ b/core/src/com/etheller/warsmash/viewer5/RawOpenGLTextureResource.java @@ -29,6 +29,7 @@ public abstract class RawOpenGLTextureResource extends Texture { private int wrapT = GL20.GL_CLAMP_TO_EDGE; private final int magFilter = GL20.GL_LINEAR; private final int minFilter = GL20.GL_LINEAR; + private ByteBuffer data; public RawOpenGLTextureResource(final ModelViewer viewer, final String extension, final PathSolver pathSolver, final String fetchUrl, final ResourceHandler handler) { @@ -124,6 +125,7 @@ public abstract class RawOpenGLTextureResource extends Texture { } buffer.flip(); + this.data = buffer; gl.glBindTexture(GL20.GL_TEXTURE_2D, this.handle); @@ -140,4 +142,21 @@ public abstract class RawOpenGLTextureResource extends Texture { // } } + /** + * I really don't like holding the reference to the original buffer like this. + * Seems wasteful. It's already on the GPU. However, while porting some code for + * shadow maps I hit a point where I really finally felt obligated to add this + * (there is some code in the Terrain stuff that should've had this, but + * doesn't, and does its own texture management as a result). + * + * So, as a note to future authors, please reinvent the system such that this + * cached buffer data is only stored for shadow maps and terrain textures or + * whatever. Right now, this holds a reference to these guys on every texture, + * on every unit, on every doodad, etc. Java will not be able to garbage collect + * them because we hold on to the buffer in "update()". + */ + public ByteBuffer getData() { + return this.data; + } + } diff --git a/core/src/com/etheller/warsmash/viewer5/gl/Extensions.java b/core/src/com/etheller/warsmash/viewer5/gl/Extensions.java index 56459e5..9bec9da 100644 --- a/core/src/com/etheller/warsmash/viewer5/gl/Extensions.java +++ b/core/src/com/etheller/warsmash/viewer5/gl/Extensions.java @@ -2,6 +2,4 @@ package com.etheller.warsmash.viewer5.gl; public class Extensions { public static ANGLEInstancedArrays angleInstancedArrays; - - public static int GL_BGRA; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/BatchGroup.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/BatchGroup.java index b254811..e1506ac 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/BatchGroup.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/BatchGroup.java @@ -4,6 +4,7 @@ import java.util.List; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.glutils.ShaderProgram; +import com.etheller.warsmash.util.WarsmashConstants; import com.etheller.warsmash.viewer5.ModelViewer; import com.etheller.warsmash.viewer5.Scene; import com.etheller.warsmash.viewer5.Texture; @@ -26,8 +27,6 @@ public class BatchGroup extends GenericGroup { final MdxModel model = this.model; final List textures = model.getTextures(); final MdxHandler handler = model.handler; - final List teamColors = MdxHandler.teamColors; - final List teamGlows = MdxHandler.teamGlows; final List batches = model.batches; final List replaceables = model.replaceables; final ModelViewer viewer = model.viewer; @@ -95,11 +94,9 @@ public class BatchGroup extends GenericGroup { final Integer replaceable = replaceables.get(layerTexture); // TODO is this OK? Texture texture; - if (replaceable == 1) { - texture = teamColors.get(instance.teamColor); - } - else if (replaceable == 2) { - texture = teamGlows.get(instance.teamColor); + if ((replaceable > 0) && (replaceable < WarsmashConstants.REPLACEABLE_TEXTURE_LIMIT) + && (instance.replaceableTextures[replaceable] != null)) { + texture = instance.replaceableTextures[replaceable]; } else { texture = textures.get(layerTexture); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GeometryEmitterFuncs.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GeometryEmitterFuncs.java index 63d577d..dd0e1de 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GeometryEmitterFuncs.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GeometryEmitterFuncs.java @@ -7,6 +7,7 @@ import java.util.List; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.glutils.ShaderProgram; import com.badlogic.gdx.math.Vector3; +import com.etheller.warsmash.util.WarsmashConstants; import com.etheller.warsmash.viewer5.Camera; import com.etheller.warsmash.viewer5.ModelViewer; import com.etheller.warsmash.viewer5.Scene; @@ -77,7 +78,6 @@ public class GeometryEmitterFuncs { final ParticleEmitter2Object emitterObject = emitter.emitterObject; final int modelSpace = emitterObject.modelSpace; final float tailLength = emitterObject.tailLength; - final int teamColor = instance.teamColor; int offset = 0; for (int objectIndex = 0; objectIndex < emitter.alive; objectIndex++) { @@ -131,7 +131,7 @@ public class GeometryEmitterFuncs { floatView.put(floatOffset + FLOAT_OFFSET_HEALTH, object.health); byteView.put(byteOffset + BYTE_OFFSET_TAIL, (byte) tail); - byteView.put(byteOffset + BYTE_OFFSET_TEAM_COLOR, (byte) teamColor); + byteView.put(byteOffset + BYTE_OFFSET_TEAM_COLOR, (byte) 0); offset += 1; } @@ -154,15 +154,9 @@ public class GeometryEmitterFuncs { gl.glBlendFunc(emitterObject.blendSrc, emitterObject.blendDst); - if (replaceableId == 1) { - final List teamColors = model.reforged ? MdxHandler.reforgedTeamColors : MdxHandler.teamColors; - - texture = teamColors.get(instance.teamColor); - } - else if (replaceableId == 2) { - final List teamGlows = model.reforged ? MdxHandler.reforgedTeamGlows : MdxHandler.teamGlows; - - texture = teamGlows.get(instance.teamColor); + if ((replaceableId > 0) && (replaceableId < WarsmashConstants.REPLACEABLE_TEXTURE_LIMIT) + && (instance.replaceableTextures[(int) replaceableId] != null)) { + texture = instance.replaceableTextures[(int) replaceableId]; } else { texture = emitterObject.internalTexture; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java index 4b77b29..809a2e3 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java @@ -10,9 +10,11 @@ import com.badlogic.gdx.math.Matrix4; import com.badlogic.gdx.math.Quaternion; import com.badlogic.gdx.math.Vector3; import com.etheller.warsmash.parsers.mdlx.Sequence; +import com.etheller.warsmash.util.WarsmashConstants; import com.etheller.warsmash.viewer5.GenericNode; import com.etheller.warsmash.viewer5.ModelInstance; import com.etheller.warsmash.viewer5.Node; +import com.etheller.warsmash.viewer5.PathSolver; import com.etheller.warsmash.viewer5.RenderBatch; import com.etheller.warsmash.viewer5.Scene; import com.etheller.warsmash.viewer5.SkeletalNode; @@ -43,7 +45,6 @@ public class MdxComplexInstance extends ModelInstance { public int sequence = -1; public int sequenceLoopMode = 0; public boolean sequenceEnded = false; - public int teamColor = 0; public float[] vertexColor = { 1, 1, 1, 1 }; // Particles do not spawn when the sequence is -1, or when the sequence finished // and it's not repeating @@ -59,7 +60,7 @@ public class MdxComplexInstance extends ModelInstance { public Matrix4[] worldMatrices; public FloatBuffer worldMatricesCopyHeap; public DataTexture boneTexture; - public Texture[] replaceableTextures = new Texture[64]; + public Texture[] replaceableTextures = new Texture[WarsmashConstants.REPLACEABLE_TEXTURE_LIMIT]; public MdxComplexInstance(final MdxModel model) { super(model); @@ -572,11 +573,21 @@ public class MdxComplexInstance extends ModelInstance { * Set the team color of this instance. */ public MdxComplexInstance setTeamColor(final int id) { - this.teamColor = id; - + this.replaceableTextures[1] = (Texture) this.model.viewer.load( + "ReplaceableTextures\\" + ReplaceableIds.getPathString(1) + ReplaceableIds.getIdString(id) + ".blp", + PathSolver.DEFAULT, null); + this.replaceableTextures[2] = (Texture) this.model.viewer.load( + "ReplaceableTextures\\" + ReplaceableIds.getPathString(2) + ReplaceableIds.getIdString(id) + ".blp", + PathSolver.DEFAULT, null); return this; } + @Override + public void setReplaceableTexture(final int replaceableTextureId, final String replaceableTextureFile) { + this.replaceableTextures[replaceableTextureId] = (Texture) this.model.viewer.load(replaceableTextureFile, + PathSolver.DEFAULT, null); + } + /** * Set the vertex color of this instance. */ diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxHandler.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxHandler.java index 9f856fd..77d8da5 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxHandler.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxHandler.java @@ -1,26 +1,16 @@ package com.etheller.warsmash.viewer5.handlers.mdx; import java.util.ArrayList; -import java.util.List; import com.badlogic.gdx.graphics.glutils.ShaderProgram; import com.etheller.warsmash.viewer5.HandlerResource; import com.etheller.warsmash.viewer5.ModelViewer; -import com.etheller.warsmash.viewer5.Texture; import com.etheller.warsmash.viewer5.handlers.ModelHandler; import com.etheller.warsmash.viewer5.handlers.ResourceHandlerConstructionParams; import com.etheller.warsmash.viewer5.handlers.blp.BlpHandler; public class MdxHandler extends ModelHandler { - // Team color/glow textures, shared between all models, but loaded with the - // first model that uses them. - public static final List teamColors = new ArrayList<>(); - public static final List teamGlows = new ArrayList<>(); - - public static final List reforgedTeamColors = new ArrayList<>(); - public static final List reforgedTeamGlows = new ArrayList<>(); - public MdxHandler() { this.extensions = new ArrayList<>(); this.extensions.add(new String[] { ".mdx", "arrayBuffer" }); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java index 4a19552..01ce84b 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java @@ -9,7 +9,6 @@ import com.badlogic.gdx.graphics.GL20; import com.etheller.warsmash.parsers.mdlx.Extent; import com.etheller.warsmash.parsers.mdlx.MdlxModel; import com.etheller.warsmash.parsers.mdlx.Sequence; -import com.etheller.warsmash.util.WarsmashConstants; import com.etheller.warsmash.viewer5.ModelInstance; import com.etheller.warsmash.viewer5.ModelViewer; import com.etheller.warsmash.viewer5.PathSolver; @@ -131,7 +130,6 @@ public class MdxModel extends com.etheller.warsmash.viewer5.Model { } final GL20 gl = viewer.gl; - boolean usingTeamTextures = false; // Textures. for (final com.etheller.warsmash.parsers.mdlx.Texture texture : parser.getTextures()) { @@ -140,16 +138,20 @@ public class MdxModel extends com.etheller.warsmash.viewer5.Model { final int flags = texture.getFlags(); if (replaceableId != 0) { - path = "ReplaceableTextures\\" + ReplaceableIds.getPathString(replaceableId) + ".blp"; - - if ((replaceableId == 1) || (replaceableId == 2)) { - usingTeamTextures = true; - } + // TODO This uses dumb, stupid, terrible, no-good hardcoded replaceable IDs + // instead of the real system, because currently MdxSimpleInstance is not + // supporting it correctly. + final String idString = ((replaceableId == 1) || (replaceableId == 2)) ? ReplaceableIds.getIdString(0) + : ""; + path = "ReplaceableTextures\\" + ReplaceableIds.getPathString(replaceableId) + idString + ".blp"; } if (reforged && !path.endsWith(".dds")) { path = path.substring(0, path.length() - 4) + ".dds"; } + else if ("".equals(path)) { + path = "Textures\\white.blp"; + } final Texture viewerTexture = (Texture) viewer.load(path, pathSolver, solverParams); @@ -168,24 +170,6 @@ public class MdxModel extends com.etheller.warsmash.viewer5.Model { this.textures.add(viewerTexture); } - // Start loading the team color and glow textures if this model uses them and - // they weren't loaded previously. - if (usingTeamTextures) { - final List teamColors = reforged ? MdxHandler.reforgedTeamColors : MdxHandler.teamColors; - final List teamGlows = reforged ? MdxHandler.reforgedTeamGlows : MdxHandler.teamGlows; - - if (teamColors.isEmpty()) { - for (int i = 0; i < WarsmashConstants.MAX_PLAYERS; i++) { - final String id = ReplaceableIds.getIdString(i); - - teamColors.add((Texture) viewer.load("ReplaceableTextures\\TeamColor\\TeamColor" + id + texturesExt, - pathSolver, solverParams)); - teamGlows.add((Texture) viewer.load("ReplaceableTextures\\TeamGlow\\TeamGlow" + id + texturesExt, - pathSolver, solverParams)); - } - } - } - // Geoset animations for (final com.etheller.warsmash.parsers.mdlx.GeosetAnimation geosetAnimation : parser.getGeosetAnimations()) { this.geosetAnimations.add(new GeosetAnimation(this, geosetAnimation)); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxSimpleInstance.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxSimpleInstance.java index 8a075f2..6377dda 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxSimpleInstance.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxSimpleInstance.java @@ -1,11 +1,15 @@ package com.etheller.warsmash.viewer5.handlers.mdx; +import com.etheller.warsmash.util.WarsmashConstants; import com.etheller.warsmash.viewer5.BatchedInstance; import com.etheller.warsmash.viewer5.Model; +import com.etheller.warsmash.viewer5.PathSolver; import com.etheller.warsmash.viewer5.RenderBatch; +import com.etheller.warsmash.viewer5.Texture; import com.etheller.warsmash.viewer5.TextureMapper; public class MdxSimpleInstance extends BatchedInstance { + public Texture[] replaceableTextures = new Texture[WarsmashConstants.REPLACEABLE_TEXTURE_LIMIT]; public MdxSimpleInstance(final Model model) { super(model); @@ -36,4 +40,9 @@ public class MdxSimpleInstance extends BatchedInstance { return new MdxRenderBatch(this.scene, this.model, textureMapper); } + @Override + public void setReplaceableTexture(final int replaceableTextureId, final String replaceableTextureFile) { + this.replaceableTextures[replaceableTextureId] = (Texture) this.model.viewer.load(replaceableTextureFile, + PathSolver.DEFAULT, null); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ParticleEmitter2Object.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ParticleEmitter2Object.java index b03de6b..48f7298 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ParticleEmitter2Object.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ParticleEmitter2Object.java @@ -106,11 +106,11 @@ public class ParticleEmitter2Object extends GenericObject implements EmitterObje } public int getWidth(final float[] out, final int sequence, final int frame, final int counter) { - return this.getScalarValue(out, AnimationMap.KP2N.getWar3id(), sequence, frame, counter, this.width); + return this.getScalarValue(out, AnimationMap.KP2N.getWar3id(), sequence, frame, counter, this.length); } public int getLength(final float[] out, final int sequence, final int frame, final int counter) { - return this.getScalarValue(out, AnimationMap.KP2W.getWar3id(), sequence, frame, counter, this.length); + return this.getScalarValue(out, AnimationMap.KP2W.getWar3id(), sequence, frame, counter, this.width); } public int getSpeed(final float[] out, final int sequence, final int frame, final int counter) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ReplaceableIds.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ReplaceableIds.java index c4722a1..f62fc99 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ReplaceableIds.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ReplaceableIds.java @@ -13,8 +13,8 @@ public class ReplaceableIds { for (int i = 0; i < WarsmashConstants.MAX_PLAYERS; i++) { ID_TO_STR.put(Long.valueOf(i), String.format("%2d", i).replace(' ', '0')); } - REPLACEABLE_ID_TO_STR.put(Long.valueOf(1), "TeamColor\\TeamColor00"); - REPLACEABLE_ID_TO_STR.put(Long.valueOf(2), "TeamGlow\\TeamGlow00"); + REPLACEABLE_ID_TO_STR.put(Long.valueOf(1), "TeamColor\\TeamColor"); + REPLACEABLE_ID_TO_STR.put(Long.valueOf(2), "TeamGlow\\TeamGlow"); REPLACEABLE_ID_TO_STR.put(Long.valueOf(11), "Cliff\\Cliff0"); REPLACEABLE_ID_TO_STR.put(Long.valueOf(21), ""); // Used by all cursor models (HumanCursor, OrcCursor, // UndeadCursor, NightElfCursor) diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Doodad.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Doodad.java index eb1c28b..b3ce2b2 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Doodad.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Doodad.java @@ -4,10 +4,13 @@ import com.badlogic.gdx.math.Quaternion; import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; import com.etheller.warsmash.units.manager.MutableObjectData.WorldEditorDataType; import com.etheller.warsmash.util.RenderMathUtils; +import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.ModelInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; public class Doodad { + private static final War3ID TEX_FILE = War3ID.fromString("btxf"); + private static final War3ID TEX_ID = War3ID.fromString("btxi"); private final ModelInstance instance; private final MutableGameObject row; @@ -30,6 +33,17 @@ public class Doodad { final float defScale = row.readSLKTagFloat("defScale"); instance.uniformScale(defScale); } + if (type == WorldEditorDataType.DESTRUCTIBLES) { + // TODO destructables need to be their own type, game simulation, etc + String replaceableTextureFile = row.getFieldAsString(TEX_FILE, 0); + final int replaceableTextureId = row.getFieldAsInteger(TEX_ID, 0); + if ((replaceableTextureFile != null) && (replaceableTextureFile.length() > 1)) { + if (!replaceableTextureFile.toLowerCase().endsWith(".blp")) { + replaceableTextureFile += ".blp"; + } + instance.setReplaceableTexture(replaceableTextureId, replaceableTextureFile); + } + } instance.setScene(map.worldScene); this.instance = instance; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SplatModel.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SplatModel.java new file mode 100644 index 0000000..3837a2e --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SplatModel.java @@ -0,0 +1,138 @@ +package com.etheller.warsmash.viewer5.handlers.w3x; + +import java.util.ArrayList; +import java.util.List; + +import com.badlogic.gdx.graphics.GL30; +import com.badlogic.gdx.graphics.glutils.ShaderProgram; +import com.etheller.warsmash.util.RenderMathUtils; +import com.etheller.warsmash.viewer5.Texture; + +/** + * TODO this is copied from RivSoft stuff. + * https://github.com/d07RiV/wc3data/blob/3435e9728663825d892693318d0a0bb823dfad8c/src/mdx/viewer/handlers/w3x/splatmodel.js + * + * Shouldn't this just be a geomtry shader that takes X/Y/Texture as input and + * renders a splat, so that we can simply change the X/Y attribute values and + * move around the unit selection circles without memory allocations? For now I + * plan to simply port the RivSoft stuff, and come back later. + */ +public class SplatModel { + private static final int MAX_VERTICES = 65000; + private final Texture texture; + private final List batches; + public final float[] color; + + public SplatModel(final GL30 gl, final Texture texture, final List locations, final float[] centerOffset) { + this.texture = texture; + this.batches = new ArrayList<>(); + this.color = new float[] { 1, 1, 1, 1 }; + + final List vertices = new ArrayList<>(); + final List uvs = new ArrayList<>(); + final List indices = new ArrayList<>(); + final int instances = locations.size(); + for (int idx = 0; idx < instances; ++idx) { + final float[] locs = locations.get(idx); + final float x0 = locs[0]; + final float y0 = locs[1]; + final float x1 = locs[2]; + final float y1 = locs[3]; + final float zoffs = locs[4]; + + final int ix0 = (int) Math.floor((x0 - centerOffset[0]) / 128.0); + final int ix1 = (int) Math.ceil((x1 - centerOffset[0]) / 128.0); + final int iy0 = (int) Math.floor((y0 - centerOffset[1]) / 128.0); + final int iy1 = (int) Math.ceil((y1 - centerOffset[1]) / 128.0); + + final float newVerts = ((iy1 - iy0) + 1) * ((ix1 - ix0) + 1); + if (newVerts > MAX_VERTICES) { + continue; + } + + int start = vertices.size(); + final int step = (ix1 - ix0) + 1; + if ((start + newVerts) > MAX_VERTICES) { + this.addBatch(gl, vertices, uvs, indices); + vertices.clear(); + uvs.clear(); + indices.clear(); + start = 0; + } + + for (int iy = iy0; iy <= iy1; ++iy) { + final float y = (iy * 128.0f) + centerOffset[1]; + for (int ix = ix0; ix <= ix1; ++ix) { + final float x = (ix * 128.0f) + centerOffset[0]; + vertices.add(new float[] { x, y, zoffs }); + uvs.add(new float[] { (x - x0) / (x1 - x0), 1.0f - ((y - y0) / (y1 - y0)) }); + } + } + for (int i = 0; i < (iy1 - iy0); ++i) { + for (int j = 0; j < (ix1 - ix0); ++j) { + final int i0 = start + (i * step) + j; + indices.add(new int[] { i0, i0 + 1, i0 + step, i0 + 1, i0 + step + 1, i0 + step }); + } + } + + } + if (indices.size() > 0) { + this.addBatch(gl, vertices, uvs, indices); + } + + } + + private void addBatch(final GL30 gl, final List vertices, final List uvs, + final List indices) { + final int uvsOffset = vertices.size() * 3 * 4; + + final int vertexBuffer = gl.glGenBuffer(); + gl.glBindBuffer(GL30.GL_ARRAY_BUFFER, vertexBuffer); + gl.glBufferData(GL30.GL_ARRAY_BUFFER, uvsOffset + (uvs.size() * 4 * 2), null, GL30.GL_STATIC_DRAW); + gl.glBufferSubData(GL30.GL_ARRAY_BUFFER, 0, vertices.size() * 4 * 5, RenderMathUtils.wrap(vertices)); + gl.glBufferSubData(GL30.GL_ARRAY_BUFFER, uvsOffset, uvs.size() * 4 * 2, RenderMathUtils.wrap(uvs)); + + final int faceBuffer = gl.glGenBuffer(); + gl.glBindBuffer(GL30.GL_ELEMENT_ARRAY_BUFFER, faceBuffer); + gl.glBufferData(GL30.GL_ELEMENT_ARRAY_BUFFER, indices.size() * 6 * 2, RenderMathUtils.wrapFaces(indices), + GL30.GL_STATIC_DRAW); + + this.batches.add(new Batch(uvsOffset, vertexBuffer, faceBuffer, indices.size() * 6)); + } + + public void render(final GL30 gl, final ShaderProgram shader) { + // Texture + + gl.glActiveTexture(GL30.GL_TEXTURE1); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.texture.getGlHandle()); + shader.setUniform4fv("u_color", this.color, 0, 4); + + for (final Batch b : this.batches) { + // Vertices + gl.glBindBuffer(GL30.GL_ARRAY_BUFFER, b.vertexBuffer); + shader.setVertexAttribute("a_position", 3, GL30.GL_FLOAT, false, 12, 0); + shader.setVertexAttribute("a_uv", 2, GL30.GL_FLOAT, false, 8, b.uvsOffset); + + // Faces. + gl.glBindBuffer(GL30.GL_ELEMENT_ARRAY_BUFFER, b.faceBuffer); + + // Draw + gl.glDrawElements(GL30.GL_TRIANGLES, b.elements, GL30.GL_UNSIGNED_SHORT, 0); + } + + } + + private static final class Batch { + private final int uvsOffset; + private final int vertexBuffer; + private final int faceBuffer; + private final int elements; + + public Batch(final int uvsOffset, final int vertexBuffer, final int faceBuffer, final int elements) { + this.uvsOffset = uvsOffset; + this.vertexBuffer = vertexBuffer; + this.faceBuffer = faceBuffer; + this.elements = elements; + } + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/TerrainModel.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/TerrainModel.java deleted file mode 100644 index 5259602..0000000 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/TerrainModel.java +++ /dev/null @@ -1,178 +0,0 @@ -package com.etheller.warsmash.viewer5.handlers.w3x; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.Buffer; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.FloatBuffer; -import java.nio.IntBuffer; -import java.util.List; - -import com.badlogic.gdx.Gdx; -import com.badlogic.gdx.graphics.GL20; -import com.badlogic.gdx.graphics.glutils.ShaderProgram; -import com.etheller.warsmash.WarsmashGdxGame; -import com.etheller.warsmash.parsers.mdlx.Geoset; -import com.etheller.warsmash.parsers.mdlx.MdlxModel; -import com.etheller.warsmash.util.RenderMathUtils; -import com.etheller.warsmash.viewer5.gl.ANGLEInstancedArrays; -import com.etheller.warsmash.viewer5.gl.WebGL; - -/* - * - * -PuffTheMagicDragonIsNoMoreToday at 9:06 PM -that being said I think we call the tiles corners or whatever, since we store the points rather than the quads -but at the same time there's also per-quad data xDS - -ReteraToday at 9:06 PM -yeah, I've seen the corner class go by several times while transcribing this latest bit to java -hmmm - -PuffTheMagicDragonIsNoMoreToday at 9:07 PM -some things are per-corner, some per-tile -note that the existing code only somewhat takes care of cliff/terrain doodads, and it's not tested much - -ReteraToday at 9:10 PM -well, I'll probably rip some stuff off of HiveWE too -that was what I was thinking I'd probably do if it was necessary - -PuffTheMagicDragonIsNoMoreToday at 9:11 PM -last time I checked it wasn't implemented there, but that was a long time ago -basically you want to not have ground tiles where the doodads exist, and to know where that is you need the pathing texture used by the doodads - -ReteraToday at 9:12 PM -oh yea -makes sense - -PuffTheMagicDragonIsNoMoreToday at 9:13 PM -they also can't be supported by my hacky TerrainModel or whatever it was called, since at least some of them require blending - * - */ -public class TerrainModel { - private static final IntBuffer GL_TEMP_BUFFER = ByteBuffer.allocateDirect(4).order(ByteOrder.nativeOrder()) - .asIntBuffer(); - private final War3MapViewer viewer; - private final int vertexBuffer; - private final int faceBuffer; - private final int normalsOffset; - private final int uvsOffset; - private final int elements; - private final int locationAndTextureBuffer; - private final int texturesOffset; - private final int instances; - private final int vao; - - public TerrainModel(final War3MapViewer viewer, final InputStream modelInput, final List locations, - final List textures, final ShaderProgram shader) { - final GL20 gl = viewer.gl; - final WebGL webgl = viewer.webGL; - final ANGLEInstancedArrays instancedArrays = webgl.instancedArrays; - final MdlxModel parser; - try { - parser = new MdlxModel(modelInput); - } - catch (final IOException e) { - throw new RuntimeException(e); - } - final Geoset geoset = parser.getGeosets().get(0); - final float[] vertices = geoset.getVertices(); - final float[] normals = geoset.getNormals(); - final float[] uvs = geoset.getUvSets()[0]; - final int[] faces = geoset.getFaces(); - final int normalsOffset = vertices.length * 4; - final int uvsOffset = normalsOffset + (normals.length * 4); - int vao; - - GL_TEMP_BUFFER.clear(); - Gdx.gl30.glGenVertexArrays(1, GL_TEMP_BUFFER); - vao = GL_TEMP_BUFFER.get(0); - Gdx.gl30.glBindVertexArray(vao); - - final int vertexBuffer = gl.glGenBuffer(); - gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, vertexBuffer); - gl.glBufferData(GL20.GL_ARRAY_BUFFER, uvsOffset + (uvs.length * 4), null, GL20.GL_STATIC_DRAW); - gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, 0, vertices.length * 4, RenderMathUtils.wrap(vertices)); - gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, normalsOffset, normals.length * 4, RenderMathUtils.wrap(normals)); - gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, uvsOffset, uvs.length * 4, RenderMathUtils.wrap(uvs)); - - shader.setVertexAttribute("a_position", 3, GL20.GL_FLOAT, false, 0, 0); - shader.enableVertexAttribute("a_position"); - - shader.setVertexAttribute("a_normal", 3, GL20.GL_FLOAT, false, 0, normalsOffset); - shader.enableVertexAttribute("a_normal"); - - shader.setVertexAttribute("a_uv", 3, GL20.GL_FLOAT, false, 0, uvsOffset); - shader.enableVertexAttribute("a_uv"); - - final int texturesOffset = locations.size() * 3 * 4; - final int locationAndTextureBuffer = gl.glGenBuffer(); - gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, locationAndTextureBuffer); - gl.glBufferData(GL20.GL_ARRAY_BUFFER, texturesOffset + textures.size(), null, GL20.GL_STATIC_DRAW); - gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, 0, locations.size() * 3 * 4, wrapVectors(locations)); - gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, texturesOffset, textures.size(), wrapTexIndices(textures)); - - shader.setVertexAttribute("a_instancePosition", 3, GL20.GL_FLOAT, false, 0, 0); - shader.enableVertexAttribute("a_instancePosition"); - instancedArrays.glVertexAttribDivisorANGLE(shader.getAttributeLocation("a_instancePosition"), 1); - - shader.setVertexAttribute("a_instanceTexture", 1, GL20.GL_UNSIGNED_BYTE, false, 0, texturesOffset); - shader.enableVertexAttribute("a_instanceTexture"); - instancedArrays.glVertexAttribDivisorANGLE(shader.getAttributeLocation("a_instanceTexture"), 1); - - final int faceBuffer = gl.glGenBuffer(); - gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, faceBuffer); - gl.glBufferData(GL20.GL_ELEMENT_ARRAY_BUFFER, faces.length * 2, RenderMathUtils.wrapFaces(faces), - GL20.GL_STATIC_DRAW); - - WarsmashGdxGame.bindDefaultVertexArray(); - - this.viewer = viewer; - this.vertexBuffer = vertexBuffer; - this.faceBuffer = faceBuffer; - this.normalsOffset = normalsOffset; - this.uvsOffset = uvsOffset; - this.elements = faces.length; - this.locationAndTextureBuffer = locationAndTextureBuffer; - this.texturesOffset = texturesOffset; - this.instances = locations.size() / 3; - this.vao = vao; - } - - private Buffer wrapTexIndices(final List textures) { - final ByteBuffer wrapper = ByteBuffer.allocateDirect(textures.size()).order(ByteOrder.nativeOrder()); - for (final Integer texture : textures) { - wrapper.put(texture.byteValue()); - } - wrapper.clear(); - return wrapper; - } - - private Buffer wrapVectors(final List locations) { - final FloatBuffer wrapper = ByteBuffer.allocateDirect(locations.size() * 12).order(ByteOrder.nativeOrder()) - .asFloatBuffer(); - for (final float[] vector : locations) { - wrapper.put(vector[0]); - wrapper.put(vector[1]); - wrapper.put(vector[2]); - } - wrapper.clear(); - return wrapper; - } - - public void render(final ShaderProgram shader) { - final War3MapViewer viewer = this.viewer; - final GL20 gl = viewer.gl; - final WebGL webGL = viewer.webGL; - final ANGLEInstancedArrays instancedArrays = webGL.instancedArrays; - - Gdx.gl30.glBindVertexArray(this.vao); - - instancedArrays.glDrawElementsInstancedANGLE(GL20.GL_TRIANGLES, this.elements, GL20.GL_UNSIGNED_SHORT, 0, - this.instances); - - WarsmashGdxGame.bindDefaultVertexArray(); - - } -} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Unit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Unit.java index f7a9145..dbb845e 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Unit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Unit.java @@ -2,22 +2,34 @@ package com.etheller.warsmash.viewer5.handlers.w3x; import com.badlogic.gdx.math.Quaternion; import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; +import com.etheller.warsmash.units.manager.MutableObjectData.WorldEditorDataType; import com.etheller.warsmash.util.RenderMathUtils; +import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; public class Unit { + private static final War3ID IS_BLDG = War3ID.fromString("ubdg"); + private static final War3ID RED = War3ID.fromString("uclr"); + private static final War3ID GREEN = War3ID.fromString("uclg"); + private static final War3ID BLUE = War3ID.fromString("uclb"); + private static final War3ID MODEL_SCALE = War3ID.fromString("usca"); + private static final War3ID MOVE_HEIGHT = War3ID.fromString("umvh"); + private static final War3ID ITEM_MODEL_SCALE = War3ID.fromString("isca"); + private static final War3ID ITEM_RED = War3ID.fromString("iclr"); + private static final War3ID ITEM_GREEN = War3ID.fromString("iclg"); + private static final War3ID ITEM_BLUE = War3ID.fromString("iclb"); private static final float[] heapZ = new float[3]; public final MdxComplexInstance instance; public final MutableGameObject row; public Unit(final War3MapViewer map, final MdxModel model, final MutableGameObject row, - final com.etheller.warsmash.parsers.w3x.unitsdoo.Unit unit) { + final com.etheller.warsmash.parsers.w3x.unitsdoo.Unit unit, final WorldEditorDataType type) { final MdxComplexInstance instance = (MdxComplexInstance) model.addInstance(); instance.move(unit.getLocation()); float angle; - if ((row != null) && row.readSLKTagBoolean("isBldg")) { + if ((row != null) && row.getFieldAsBoolean(IS_BLDG, 0)) { angle = (float) Math.toRadians(270.0f); } else { @@ -30,12 +42,28 @@ public class Unit { instance.setScene(map.worldScene); if (row != null) { - heapZ[2] = row.readSLKTagFloat("moveHeight"); + heapZ[2] = row.getFieldAsFloat(MOVE_HEIGHT, 0); instance.move(heapZ); - instance.setVertexColor(new float[] { (row.readSLKTagInt("red")) / 255f, - (row.readSLKTagInt("green")) / 255f, (row.readSLKTagInt("blue")) / 255f }); - instance.uniformScale(row.readSLKTagFloat("modelScale")); + War3ID red; + War3ID green; + War3ID blue; + War3ID scale; + if (type == WorldEditorDataType.UNITS) { + scale = MODEL_SCALE; + red = RED; + green = GREEN; + blue = BLUE; + } + else { + scale = ITEM_MODEL_SCALE; + red = ITEM_RED; + green = ITEM_GREEN; + blue = ITEM_BLUE; + } + instance.setVertexColor(new float[] { (row.getFieldAsInteger(red, 0)) / 255f, + (row.getFieldAsInteger(green, 0)) / 255f, (row.getFieldAsInteger(blue, 0)) / 255f }); + instance.uniformScale(row.getFieldAsFloat(scale, 0)); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xShaders.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xShaders.java index b02e267..26c351c 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xShaders.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xShaders.java @@ -1,251 +1,60 @@ package com.etheller.warsmash.viewer5.handlers.w3x; public class W3xShaders { - public static final class Cliffs { - private Cliffs() { + public static final class UberSplat { + private UberSplat() { } public static final String vert = "\r\n" + // - "uniform mat4 u_VP;\r\n" + // - "uniform sampler2D u_heightMap;\r\n" + // - "uniform vec2 u_pixel;\r\n" + // - "uniform vec2 u_centerOffset;\r\n" + // - "attribute vec3 a_position;\r\n" + // -// "attribute vec3 a_normal;\r\n" + // - "attribute vec2 a_uv;\r\n" + // - "attribute vec3 a_instancePosition;\r\n" + // - "attribute float a_instanceTexture;\r\n" + // - "varying vec3 v_normal;\r\n" + // - "varying vec2 v_uv;\r\n" + // - "varying float v_texture;\r\n" + // - "varying vec3 v_position;\r\n" + // - "void main() {\r\n" + // - " // Half of a pixel in the cliff height map.\r\n" + // - " vec2 halfPixel = u_pixel * 0.5;\r\n" + // - " // The bottom left corner of the map tile this vertex is on.\r\n" + // - " vec2 corner = floor((a_instancePosition.xy - vec2(1.0, 0.0) - u_centerOffset.xy) / 128.0);\r\n" + // - " // Get the 4 closest heights in the height map.\r\n" + // - " float bottomLeft = texture2D(u_heightMap, corner * u_pixel + halfPixel).r;\r\n" + // - " float bottomRight = texture2D(u_heightMap, (corner + vec2(1.0, 0.0)) * u_pixel + halfPixel).r;\r\n" + // - " float topLeft = texture2D(u_heightMap, (corner + vec2(0.0, 1.0)) * u_pixel + halfPixel).r;\r\n" + // - " float topRight = texture2D(u_heightMap, (corner + vec2(1.0, 1.0)) * u_pixel + halfPixel).r;\r\n" + // - " \r\n" + // - " // Do a bilinear interpolation between the heights to get the final value.\r\n" + // - " float bottom = mix(bottomRight, bottomLeft, -a_position.x / 128.0);\r\n" + // - " float top = mix(topRight, topLeft, -a_position.x / 128.0);\r\n" + // - " float height = mix(bottom, top, a_position.y / 128.0);\r\n" + // -// " v_normal = a_normal;\r\n" + // - " v_uv = a_uv;\r\n" + // - " v_texture = a_instanceTexture;\r\n" + // - " v_position = a_position + vec3(a_instancePosition.xy, a_instancePosition.z + height * 128.0);\r\n" + // - " gl_Position = u_VP * vec4(v_position, 1.0);\r\n" + // - "}\r\n" + // - ""; - - public static final String frag = "\r\n" + // - "// #extension GL_OES_standard_derivatives : enable\r\n" + // - "precision mediump float;\r\n" + // - "uniform sampler2D u_texture1;\r\n" + // - "uniform sampler2D u_texture2;\r\n" + // -// "varying vec3 v_normal;\r\n" + // - "varying vec2 v_uv;\r\n" + // - "varying float v_texture;\r\n" + // - "varying vec3 v_position;\r\n" + // - "// const vec3 lightDirection = normalize(vec3(-0.3, -0.3, 0.25));\r\n" + // - "vec4 sample(int texture, vec2 uv) {\r\n" + // - " if (texture == 0) {\r\n" + // - " return texture2D(u_texture1, uv);\r\n" + // - " } else {\r\n" + // - " return texture2D(u_texture2, uv);\r\n" + // - " }\r\n" + // - "}\r\n" + // - "void main() {\r\n" + // - " vec4 color = sample(int(v_texture), v_uv);\r\n" + // - " // vec3 faceNormal = cross(dFdx(v_position), dFdy(v_position));\r\n" + // - " // vec3 normal = normalize((faceNormal + v_normal) * 0.5);\r\n" + // - " // color *= clamp(dot(normal, lightDirection) + 0.45, 0.1, 1.0);\r\n" + // - " gl_FragColor = color;\r\n" + // - "}\r\n" + // - ""; - } - - public static final class Ground { - private Ground() { - } - - public static final String frag = "\r\n" + // - "precision mediump float;\r\n" + // - "uniform sampler2D u_tilesets[15];\r\n" + // - "varying vec4 v_tilesets;\r\n" + // - "varying vec2 v_uv[4];\r\n" + // - "varying vec3 v_normal;\r\n" + // - "const vec3 lightDirection = normalize(vec3(-0.3, -0.3, 0.25));\r\n" + // - "vec4 sample(float tileset, vec2 uv) {\r\n" + // - " if (tileset <= 0.5) {\r\n" + // - " return texture2D(u_tilesets[0], uv);\r\n" + // - " } else if (tileset <= 1.5) {\r\n" + // - " return texture2D(u_tilesets[1], uv);\r\n" + // - " } else if (tileset <= 2.5) {\r\n" + // - " return texture2D(u_tilesets[2], uv);\r\n" + // - " } else if (tileset <= 3.5) {\r\n" + // - " return texture2D(u_tilesets[3], uv);\r\n" + // - " } else if (tileset <= 4.5) {\r\n" + // - " return texture2D(u_tilesets[4], uv);\r\n" + // - " } else if (tileset <= 5.5) {\r\n" + // - " return texture2D(u_tilesets[5], uv);\r\n" + // - " } else if (tileset <= 6.5) {\r\n" + // - " return texture2D(u_tilesets[6], uv);\r\n" + // - " } else if (tileset <= 7.5) {\r\n" + // - " return texture2D(u_tilesets[7], uv);\r\n" + // - " } else if (tileset <= 8.5) {\r\n" + // - " return texture2D(u_tilesets[8], uv);\r\n" + // - " } else if (tileset <= 9.5) {\r\n" + // - " return texture2D(u_tilesets[9], uv);\r\n" + // - " } else if (tileset <= 10.5) {\r\n" + // - " return texture2D(u_tilesets[10], uv);\r\n" + // - " } else if (tileset <= 11.5) {\r\n" + // - " return texture2D(u_tilesets[11], uv);\r\n" + // - " } else if (tileset <= 12.5) {\r\n" + // - " return texture2D(u_tilesets[12], uv);\r\n" + // - " } else if (tileset <= 13.5) {\r\n" + // - " return texture2D(u_tilesets[13], uv);\r\n" + // - " } else if (tileset <= 14.5) {\r\n" + // - " return texture2D(u_tilesets[14], uv);\r\n" + // - " }\r\n" + // - "}\r\n" + // - "vec4 blend(vec4 color, float tileset, vec2 uv) {\r\n" + // - " vec4 texel = sample(tileset, uv);\r\n" + // - " return mix(color, texel, texel.a);\r\n" + // - "}\r\n" + // - "void main() {\r\n" + // - " vec4 color = sample(v_tilesets[0] - 1.0, v_uv[0]);\r\n" + // - " if (v_tilesets[1] > 0.5) {\r\n" + // - " color = blend(color, v_tilesets[1] - 1.0, v_uv[1]);\r\n" + // - " }\r\n" + // - " if (v_tilesets[2] > 0.5) {\r\n" + // - " color = blend(color, v_tilesets[2] - 1.0, v_uv[2]);\r\n" + // - " }\r\n" + // - " if (v_tilesets[3] > 0.5) {\r\n" + // - " color = blend(color, v_tilesets[3] - 1.0, v_uv[3]);\r\n" + // - " }\r\n" + // - " // color *= clamp(dot(v_normal, lightDirection) + 0.45, 0.0, 1.0);\r\n" + // - " gl_FragColor = color;\r\n" + // - "}"; - - public static final String vert = "\r\n" + // - "uniform mat4 u_VP;\r\n" + // - "uniform sampler2D u_heightMap;\r\n" + // - "uniform vec2 u_size;\r\n" + // - "uniform vec2 u_offset;\r\n" + // - "uniform bool u_extended[14];\r\n" + // - "uniform float u_baseTileset;\r\n" + // - "attribute vec2 a_position;\r\n" + // - "attribute float a_InstanceID;\r\n" + // - "attribute vec4 a_textures;\r\n" + // - "attribute vec4 a_variations;\r\n" + // - "varying vec4 v_tilesets;\r\n" + // - "varying vec2 v_uv[4];\r\n" + // - "varying vec3 v_normal;\r\n" + // - "vec2 getCell(float variation) {\r\n" + // - " if (variation < 16.0) {\r\n" + // - " return vec2(mod(variation, 4.0), floor(variation / 4.0));\r\n" + // - " } else {\r\n" + // - " variation -= 16.0;\r\n" + // - " return vec2(4.0 + mod(variation, 4.0), floor(variation / 4.0));\r\n" + // - " }\r\n" + // - "}\r\n" + // - "vec2 getUV(vec2 position, bool extended, float variation) {\r\n" + // - " vec2 cell = getCell(variation);\r\n" + // - " vec2 cellSize = vec2(extended ? 0.125 : 0.25, 0.25);\r\n" + // - " vec2 uv = vec2(position.x, 1.0 - position.y);\r\n" + // - " vec2 pixelSize = vec2(1.0 / 512.0, 1.0 / 256.0); /// Note: hardcoded to 512x256 for now.\r\n" + // - " return clamp((cell + uv) * cellSize, cell * cellSize + pixelSize, (cell + 1.0) * cellSize - pixelSize); \r\n" + " uniform mat4 u_mvp;\r\n" + // + " uniform sampler2D u_heightMap;\r\n" + // + " uniform vec2 u_pixel;\r\n" + // + " uniform vec2 u_size;\r\n" + // + " uniform vec2 u_shadowPixel;\r\n" + // + " uniform vec2 u_centerOffset;\r\n" + // + " attribute vec3 a_position;\r\n" + // + " attribute vec2 a_uv;\r\n" + // + " varying vec2 v_uv;\r\n" + // + " varying vec2 v_suv;\r\n" + // + " varying vec3 v_normal;\r\n" + // + " const float normalDist = 0.25;\r\n" + // + " void main() {\r\n" + // + " vec2 halfPixel = u_pixel * 0.5;\r\n" + // + " vec2 base = (a_position.xy - u_centerOffset) / 128.0;\r\n" + // + " float height = texture2D(u_heightMap, base * u_pixel + halfPixel).r;\r\n" + // + " float hL = texture2D(u_heightMap, vec2(base - vec2(normalDist, 0.0)) * u_pixel + halfPixel).r;\r\n" + // - "}\r\n" + // - "void main() {\r\n" + // - " vec4 textures = a_textures - u_baseTileset;\r\n" + // - " \r\n" + // - " if (textures[0] > 0.0 || textures[1] > 0.0 || textures[2] > 0.0 || textures[3] > 0.0) {\r\n" + // - " v_tilesets = textures;\r\n" + // - " v_uv[0] = getUV(a_position, u_extended[int(textures[0]) - 1], a_variations[0]);\r\n" + // - " v_uv[1] = getUV(a_position, u_extended[int(textures[1]) - 1], a_variations[1]);\r\n" + // - " v_uv[2] = getUV(a_position, u_extended[int(textures[2]) - 1], a_variations[2]);\r\n" + // - " v_uv[3] = getUV(a_position, u_extended[int(textures[3]) - 1], a_variations[3]);\r\n" + // - " vec2 corner = vec2(mod(a_InstanceID, u_size.x), floor(a_InstanceID / u_size.x));\r\n" + // - " vec2 base = corner + a_position;\r\n" + // - " float height = texture2D(u_heightMap, base / u_size).r;\r\n" + // - " float hL = texture2D(u_heightMap, vec2(base - vec2(1.0, 0.0)) / (u_size)).r;\r\n" + // - " float hR = texture2D(u_heightMap, vec2(base + vec2(1.0, 0.0)) / (u_size)).r;\r\n" + // - " float hD = texture2D(u_heightMap, vec2(base - vec2(0.0, 1.0)) / (u_size)).r;\r\n" + // - " float hU = texture2D(u_heightMap, vec2(base + vec2(0.0, 1.0)) / (u_size)).r;\r\n" + // - " v_normal = normalize(vec3(hL - hR, hD - hU, 2.0));\r\n" + // - " gl_Position = u_VP * vec4(base * 128.0 + u_offset, height * 128.0, 1.0);\r\n" + // - " } else {\r\n" + // - " v_tilesets = vec4(0.0);\r\n" + // - " v_uv[0] = vec2(0.0);\r\n" + // - " v_uv[1] = vec2(0.0);\r\n" + // - " v_uv[2] = vec2(0.0);\r\n" + // - " v_uv[3] = vec2(0.0);\r\n" + // - " v_normal = vec3(0.0);\r\n" + // - " gl_Position = vec4(0.0);\r\n" + // - " }\r\n" + // - "}"; - } - - public static final class Water { - private Water() { - } + " float hR = texture2D(u_heightMap, vec2(base + vec2(normalDist, 0.0)) * u_pixel + halfPixel).r;\r\n" + + // + " float hD = texture2D(u_heightMap, vec2(base - vec2(0.0, normalDist)) * u_pixel + halfPixel).r;\r\n" + + // + " float hU = texture2D(u_heightMap, vec2(base + vec2(0.0, normalDist)) * u_pixel + halfPixel).r;\r\n" + + // + " v_normal = normalize(vec3(hL - hR, hD - hU, normalDist * 2.0));\r\n" + // + " v_uv = a_uv;\r\n" + // + " v_suv = base / u_size;\r\n" + // + " gl_Position = u_mvp * vec4(a_position.xy, height * 128.0 + a_position.z, 1.0);\r\n" + // + " }\r\n" + // + " "; public static final String frag = "\r\n" + // - "precision mediump float;\r\n" + // - "uniform sampler2D u_waterTexture;\r\n" + // - "varying vec2 v_uv;\r\n" + // - "varying vec4 v_color;\r\n" + // - "void main() {\r\n" + // - " gl_FragColor = texture2D(u_waterTexture, v_uv) * v_color;\r\n" + // - "}\r\n" + // - ""; - public static final String vert = "\r\n" + // - "uniform mat4 u_VP;\r\n" + // - "uniform sampler2D u_heightMap;\r\n" + // - "uniform sampler2D u_waterHeightMap;\r\n" + // - "uniform vec2 u_size;\r\n" + // - "uniform vec2 u_offset;\r\n" + // - "uniform float u_offsetHeight;\r\n" + // - "uniform vec4 u_minDeepColor;\r\n" + // - "uniform vec4 u_maxDeepColor;\r\n" + // - "uniform vec4 u_minShallowColor;\r\n" + // - "uniform vec4 u_maxShallowColor;\r\n" + // - "attribute vec2 a_position;\r\n" + // - "attribute float a_InstanceID;\r\n" + // - "attribute float a_isWater;\r\n" + // - "varying vec2 v_uv;\r\n" + // - "varying vec4 v_color;\r\n" + // - "const float minDepth = 10.0 / 128.0;\r\n" + // - "const float deepLevel = 64.0 / 128.0;\r\n" + // - "const float maxDepth = 72.0 / 128.0;\r\n" + // - "void main() {\r\n" + // - " if (a_isWater > 0.5) {\r\n" + // - " v_uv = a_position;\r\n" + // - " vec2 corner = vec2(mod(a_InstanceID, u_size.x), floor(a_InstanceID / u_size.x));\r\n" + // - " vec2 base = corner + a_position;\r\n" + // - " float height = texture2D(u_heightMap, base / u_size).r;\r\n" + // - " float waterHeight = texture2D(u_waterHeightMap, base / u_size).r + u_offsetHeight;\r\n" + // - " float value = clamp(waterHeight - height, 0.0, 1.0);\r\n" + // - " if (value <= deepLevel) {\r\n" + // - " value = max(0.0, value - minDepth) / (deepLevel - minDepth);\r\n" + // - " v_color = mix(u_minShallowColor, u_maxShallowColor, value) / 255.0;\r\n" + // - " } else {\r\n" + // - " value = clamp(value - deepLevel, 0.0, maxDepth - deepLevel) / (maxDepth - deepLevel);\r\n" + // - " v_color = mix(u_minDeepColor, u_maxDeepColor, value) / 255.0;\r\n" + // + " uniform sampler2D u_texture;\r\n" + // + " uniform sampler2D u_shadowMap;\r\n" + // + " uniform vec4 u_color;\r\n" + // + " varying vec2 v_uv;\r\n" + // + " varying vec2 v_suv;\r\n" + // + " varying vec3 v_normal;\r\n" + // + " const vec3 lightDirection = normalize(vec3(-0.3, -0.3, 0.25));\r\n" + // + " void main() {\r\n" + // + " if (any(bvec4(lessThan(v_uv, vec2(0.0)), greaterThan(v_uv, vec2(1.0))))) {\r\n" + // + " discard;\r\n" + // + " }\r\n" + // + " vec4 color = texture2D(u_texture, clamp(v_uv, 0.0, 1.0)).rgba * u_color;\r\n" + // + " float shadow = texture2D(u_shadowMap, v_suv).r;\r\n" + // + " color.xyz *= clamp(dot(v_normal, lightDirection) + 0.45, 0.0, 1.0);\r\n" + // + " color.xyz *= 1.0 - shadow;\r\n" + // + " gl_FragColor = color;\r\n" + // " }\r\n" + // - " gl_Position = u_VP * vec4(base * 128.0 + u_offset, waterHeight * 128.0, 1.0);\r\n" + // - " } else {\r\n" + // - " v_uv = vec2(0.0);\r\n" + // - " v_color = vec4(0.0);\r\n" + // - " gl_Position = vec4(0.0);\r\n" + // - " }\r\n" + // - "}\r\n" + // - ""; + " "; } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index 02524db..587e70b 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -18,12 +18,14 @@ import com.etheller.warsmash.common.LoadGenericCallback; import com.etheller.warsmash.datasources.CompoundDataSource; import com.etheller.warsmash.datasources.DataSource; import com.etheller.warsmash.datasources.MpqDataSource; +import com.etheller.warsmash.datasources.SubdirDataSource; import com.etheller.warsmash.parsers.w3x.War3Map; import com.etheller.warsmash.parsers.w3x.doo.War3MapDoo; import com.etheller.warsmash.parsers.w3x.objectdata.Warcraft3MapObjectData; import com.etheller.warsmash.parsers.w3x.unitsdoo.War3MapUnitsDoo; import com.etheller.warsmash.parsers.w3x.w3e.War3MapW3e; import com.etheller.warsmash.parsers.w3x.w3i.War3MapW3i; +import com.etheller.warsmash.units.Element; import com.etheller.warsmash.units.manager.MutableObjectData; import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; import com.etheller.warsmash.units.manager.MutableObjectData.WorldEditorDataType; @@ -42,12 +44,20 @@ import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxHandler; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; import com.etheller.warsmash.viewer5.handlers.w3x.environment.Terrain; +import com.etheller.warsmash.viewer5.handlers.w3x.environment.Terrain.Splat; import mpq.MPQArchive; import mpq.MPQException; public class War3MapViewer extends ModelViewer { private static final War3ID UNIT_FILE = War3ID.fromString("umdl"); + private static final War3ID UBER_SPLAT = War3ID.fromString("uubs"); + private static final War3ID UNIT_SHADOW = War3ID.fromString("ushu"); + private static final War3ID UNIT_SHADOW_X = War3ID.fromString("ushx"); + private static final War3ID UNIT_SHADOW_Y = War3ID.fromString("ushy"); + private static final War3ID UNIT_SHADOW_W = War3ID.fromString("ushw"); + private static final War3ID UNIT_SHADOW_H = War3ID.fromString("ushh"); + private static final War3ID BUILDING_SHADOW = War3ID.fromString("ushb"); private static final War3ID ITEM_FILE = War3ID.fromString("ifil"); private static final War3ID sloc = War3ID.fromString("sloc"); private static final LoadGenericCallback stringDataCallback = new StringDataCallbackImplementation(); @@ -96,7 +106,6 @@ public class War3MapViewer extends ModelViewer { this.worldScene = this.addScene(); - loadSLKs(); } public void loadSLKs() { @@ -161,6 +170,7 @@ public class War3MapViewer extends ModelViewer { } public void loadMap(final String mapFilePath) throws IOException { + loadSLKs(); final War3Map war3Map = new War3Map(this.gameDataSource, mapFilePath); this.mapMpq = war3Map; @@ -175,23 +185,31 @@ public class War3MapViewer extends ModelViewer { tileset = w3iFile.getTileset(); - final DataSource tilesetSource; + DataSource tilesetSource; try { // Slightly complex. Here's the theory: // 1.) Copy map into RAM // 2.) Setup a Data Source that will read assets // from either the map or the game, giving the map priority. SeekableByteChannel sbc; - try (InputStream mapStream = war3Map.getCompoundDataSource().getResourceAsStream(tileset + ".mpq")) { - final byte[] mapData = IOUtils.toByteArray(mapStream); - sbc = new SeekableInMemoryByteChannel(mapData); - final DataSource internalMpqContentsDataSource = new MpqDataSource(new MPQArchive(sbc), sbc); - tilesetSource = new CompoundDataSource( - Arrays.asList(war3Map.getCompoundDataSource(), internalMpqContentsDataSource)); + final CompoundDataSource compoundDataSource = war3Map.getCompoundDataSource(); + try (InputStream mapStream = compoundDataSource.getResourceAsStream(tileset + ".mpq")) { + if (mapStream == null) { + tilesetSource = new CompoundDataSource(Arrays.asList(compoundDataSource, + new SubdirDataSource(compoundDataSource, tileset + ".mpq/"))); + } + else { + final byte[] mapData = IOUtils.toByteArray(mapStream); + sbc = new SeekableInMemoryByteChannel(mapData); + final DataSource internalMpqContentsDataSource = new MpqDataSource(new MPQArchive(sbc), sbc); + tilesetSource = new CompoundDataSource( + Arrays.asList(compoundDataSource, internalMpqContentsDataSource)); + } + } + catch (final IOException exc) { + tilesetSource = new CompoundDataSource( + Arrays.asList(compoundDataSource, new SubdirDataSource(compoundDataSource, tileset + ".mpq/"))); } - } - catch (final IOException e) { - throw new RuntimeException(e); } catch (final MPQException e) { throw new RuntimeException(e); @@ -202,8 +220,8 @@ public class War3MapViewer extends ModelViewer { final War3MapW3e terrainData = this.mapMpq.readEnvironment(); - this.terrain = new Terrain(terrainData, this.webGL, this.dataSource, new WorldEditStrings(this.dataSource), - this); + this.terrain = new Terrain(terrainData, w3iFile, this.webGL, this.dataSource, + new WorldEditStrings(this.dataSource), this); final float[] centerOffset = terrainData.getCenterOffset(); final int[] mapSize = terrainData.getMapSize(); @@ -264,7 +282,7 @@ public class War3MapViewer extends ModelViewer { String file = row.readSLKTag("file"); final int numVar = row.readSLKTagInt("numVar"); - if (file.endsWith(".mdx")) { + if (file.endsWith(".mdx") || file.endsWith(".mdl")) { file = file.substring(0, file.length() - 4); } @@ -337,25 +355,28 @@ public class War3MapViewer extends ModelViewer { String path = null; // Hardcoded? + WorldEditorDataType type = null; if (sloc.equals(unit.getId())) { path = "Objects\\StartLocation\\StartLocation.mdx"; + type = null; /// ?????? } else { row = modifications.getUnits().get(unit.getId()); if (row == null) { row = modifications.getItems().get(unit.getId()); - path = row.getFieldAsString(ITEM_FILE, 0); + if (row != null) { + type = WorldEditorDataType.ITEM; + path = row.getFieldAsString(ITEM_FILE, 0); - if (path.toLowerCase().endsWith(".mdl") || path.toLowerCase().endsWith(".mdx")) { - path = path.substring(0, path.length() - 4); - } - if (row.readSLKTagInt("fileVerFlags") == 2) { - path += "_V1"; - } + if (path.toLowerCase().endsWith(".mdl") || path.toLowerCase().endsWith(".mdx")) { + path = path.substring(0, path.length() - 4); + } - path += ".mdx"; + path += ".mdx"; + } } else { + type = WorldEditorDataType.UNITS; path = row.getFieldAsString(UNIT_FILE, 0); if (path.toLowerCase().endsWith(".mdl") || path.toLowerCase().endsWith(".mdx")) { @@ -366,19 +387,61 @@ public class War3MapViewer extends ModelViewer { } path += ".mdx"; + + final String uberSplat = row.getFieldAsString(UBER_SPLAT, 0); + if (uberSplat != null) { + final Element uberSplatInfo = this.terrain.uberSplatTable.get(uberSplat); + if (uberSplatInfo != null) { + final String texturePath = uberSplatInfo.getField("Dir") + "\\" + + uberSplatInfo.getField("file") + ".blp"; + if (!this.terrain.splats.containsKey(texturePath)) { + this.terrain.splats.put(texturePath, new Splat()); + } + final float x = unit.getLocation()[0]; + final float y = unit.getLocation()[1]; + final float s = uberSplatInfo.getFieldFloatValue("Scale"); + this.terrain.splats.get(texturePath).locations + .add(new float[] { x - s, y - s, x + s, y + s, 1 }); + } + } + + final String unitShadow = row.getFieldAsString(UNIT_SHADOW, 0); + if ((unitShadow != null) && !"_".equals(unitShadow)) { + final String texture = "ReplaceableTextures\\Shadows\\" + unitShadow + ".blp"; + final float shadowX = row.getFieldAsFloat(UNIT_SHADOW_X, 0); + final float shadowY = row.getFieldAsFloat(UNIT_SHADOW_Y, 0); + final float shadowWidth = row.getFieldAsFloat(UNIT_SHADOW_W, 0); + final float shadowHeight = row.getFieldAsFloat(UNIT_SHADOW_H, 0); + if (!this.terrain.splats.containsKey(texture)) { + final Splat splat = new Splat(); + splat.opacity = 0.5f; + this.terrain.splats.put(texture, splat); + } + final float x = unit.getLocation()[0] - shadowX; + final float y = unit.getLocation()[1] - shadowY; + this.terrain.splats.get(texture).locations + .add(new float[] { x, y, x + shadowWidth, y + shadowHeight, 3 }); + } + + final String buildingShadow = row.getFieldAsString(BUILDING_SHADOW, 0); + if ((buildingShadow != null) && !"_".equals(buildingShadow)) { + this.terrain.addShadow(buildingShadow, unit.getLocation()[0], unit.getLocation()[1]); + } } } if (path != null) { final MdxModel model = (MdxModel) this.load(path, this.mapPathSolver, this.solverParams); - this.units.add(new Unit(this, model, row, unit)); + this.units.add(new Unit(this, model, row, unit, type)); } else { System.err.println("Unknown unit ID: " + unit.getId()); } } + this.terrain.loadSplats(); + this.unitsReady = true; this.anyReady = true; } @@ -410,8 +473,9 @@ public class War3MapViewer extends ModelViewer { worldScene.startFrame(); this.terrain.renderGround(); -// this.terrain.renderCliffs(); + this.terrain.renderCliffs(); worldScene.renderOpaque(); + this.terrain.renderUberSplats(); this.terrain.renderWater(); worldScene.renderTranslucent(); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/CliffMesh.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/CliffMesh.java index cffa014..00ed08b 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/CliffMesh.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/CliffMesh.java @@ -60,8 +60,10 @@ public class CliffMesh { public void renderQueue(final float[] position) { if (this.renderJobs.remaining() < 4) { - final FloatBuffer newRenderJobs = ByteBuffer.allocateDirect(this.renderJobs.capacity() * 2) - .order(ByteOrder.nativeOrder()).asFloatBuffer(); + final int newCapacity = this.renderJobs.capacity() * 2; + final FloatBuffer newRenderJobs = ByteBuffer.allocateDirect(newCapacity * 4).order(ByteOrder.nativeOrder()) + .asFloatBuffer(); + newRenderJobs.clear(); this.renderJobs.flip(); newRenderJobs.put(this.renderJobs); this.renderJobs = newRenderJobs; @@ -79,32 +81,24 @@ public class CliffMesh { this.gl.glBufferData(GL20.GL_ARRAY_BUFFER, this.renderJobs.remaining() * 4, this.renderJobs, GL20.GL_DYNAMIC_DRAW); - this.gl.glEnableVertexAttribArray(0); this.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.vertexBuffer); this.gl.glVertexAttribPointer(0, 3, GL20.GL_FLOAT, false, 0, 0); - this.gl.glEnableVertexAttribArray(1); this.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.uvBuffer); this.gl.glVertexAttribPointer(1, 2, GL20.GL_FLOAT, false, 0, 0); - this.gl.glEnableVertexAttribArray(2); - this.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.normalBuffer); - this.gl.glVertexAttribPointer(2, 3, GL20.GL_FLOAT, false, 0, 0); +// this.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.normalBuffer); +// this.gl.glVertexAttribPointer(2, 3, GL20.GL_FLOAT, false, 0, 0); - this.gl.glEnableVertexAttribArray(3); - this.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.normalBuffer); - this.gl.glVertexAttribPointer(3, 4, GL20.GL_FLOAT, false, 0, 0); - this.gl.glVertexAttribDivisor(3, 1); + this.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.instanceBuffer); + this.gl.glVertexAttribPointer(2, 4, GL20.GL_FLOAT, false, 0, 0); + this.gl.glVertexAttribDivisor(2, 1); this.gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, this.indexBuffer); this.gl.glDrawElementsInstanced(GL20.GL_TRIANGLES, this.indices, GL30.GL_UNSIGNED_SHORT, 0, this.renderJobs.remaining() / 4); - this.gl.glVertexAttribDivisor(3, 0); // ToDo use vao - this.gl.glDisableVertexAttribArray(0); - this.gl.glDisableVertexAttribArray(1); - this.gl.glDisableVertexAttribArray(2); - this.gl.glDisableVertexAttribArray(3); + this.gl.glVertexAttribDivisor(2, 0); // ToDo use vao this.renderJobs.clear(); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/GroundTexture.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/GroundTexture.java index 71f92d1..7df3a89 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/GroundTexture.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/GroundTexture.java @@ -10,7 +10,6 @@ import javax.imageio.ImageIO; import com.badlogic.gdx.graphics.GL30; import com.etheller.warsmash.datasources.DataSource; import com.etheller.warsmash.util.ImageUtils; -import com.etheller.warsmash.viewer5.gl.Extensions; public class GroundTexture { public int id; @@ -31,7 +30,7 @@ public class GroundTexture { this.id = gl.glGenTexture(); gl.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.id); gl.glTexImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, GL30.GL_RGBA8, this.tileSize, this.tileSize, - this.extended ? 32 : 16, 0, Extensions.GL_BGRA, GL30.GL_UNSIGNED_BYTE, null); + this.extended ? 32 : 16, 0, GL30.GL_RGBA, GL30.GL_UNSIGNED_BYTE, null); gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_LINEAR_MIPMAP_LINEAR); gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_WRAP_S, GL30.GL_CLAMP_TO_EDGE); gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_WRAP_T, GL30.GL_CLAMP_TO_EDGE); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java index 67ba5ea..0321f96 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java @@ -14,6 +14,8 @@ import java.util.TreeSet; import javax.imageio.ImageIO; +import org.apache.commons.compress.utils.IOUtils; + import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.GL30; import com.badlogic.gdx.graphics.glutils.ShaderProgram; @@ -24,6 +26,7 @@ import com.badlogic.gdx.math.Vector3; import com.etheller.warsmash.datasources.DataSource; import com.etheller.warsmash.parsers.w3x.w3e.Corner; import com.etheller.warsmash.parsers.w3x.w3e.War3MapW3e; +import com.etheller.warsmash.parsers.w3x.w3i.War3MapW3i; import com.etheller.warsmash.units.DataTable; import com.etheller.warsmash.units.Element; import com.etheller.warsmash.units.StandardObjectData; @@ -32,9 +35,13 @@ import com.etheller.warsmash.util.RenderMathUtils; import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.util.WorldEditStrings; import com.etheller.warsmash.viewer5.Camera; -import com.etheller.warsmash.viewer5.gl.Extensions; +import com.etheller.warsmash.viewer5.PathSolver; +import com.etheller.warsmash.viewer5.RawOpenGLTextureResource; +import com.etheller.warsmash.viewer5.Texture; import com.etheller.warsmash.viewer5.gl.WebGL; +import com.etheller.warsmash.viewer5.handlers.w3x.SplatModel; import com.etheller.warsmash.viewer5.handlers.w3x.Variations; +import com.etheller.warsmash.viewer5.handlers.w3x.W3xShaders; import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; public class Terrain { @@ -92,8 +99,18 @@ public class Terrain { private final War3MapViewer viewer; public float[] centerOffset; private final WebGL webGL; + private final ShaderProgram uberSplatShader; + public final DataTable uberSplatTable; - public Terrain(final War3MapW3e w3eFile, final WebGL webGL, final DataSource dataSource, + private final List uberSplatModels; + private int shadowMap; + public final Map splats = new HashMap<>(); + public final Map> shadows = new HashMap<>(); + public final Map shadowTextures = new HashMap<>(); + private final int[] mapBounds; + private final int[] mapSize; + + public Terrain(final War3MapW3e w3eFile, final War3MapW3i w3iFile, final WebGL webGL, final DataSource dataSource, final WorldEditStrings worldEditStrings, final War3MapViewer viewer) throws IOException { this.webGL = webGL; this.viewer = viewer; @@ -136,6 +153,10 @@ public class Terrain { try (InputStream waterSlkStream = dataSource.getResourceAsStream("TerrainArt\\Water.slk")) { this.waterTable.readSLK(waterSlkStream); } + this.uberSplatTable = new DataTable(worldEditStrings); + try (InputStream uberSlkStream = dataSource.getResourceAsStream("Splats\\UberSplatData.slk")) { + this.uberSplatTable.readSLK(uberSlkStream); + } final char tileset = w3eFile.getTileset(); final Element waterInfo = this.waterTable.get(tileset + "Sha"); @@ -147,16 +168,31 @@ public class Terrain { loadWaterColor(this.maxShallowColor, "Smax", waterInfo); loadWaterColor(this.minDeepColor, "Dmin", waterInfo); loadWaterColor(this.maxDeepColor, "Dmax", waterInfo); + for (int i = 0; i < 3; i++) { + if (this.minDeepColor[i] > this.maxDeepColor[i]) { + this.maxDeepColor[i] = this.minDeepColor[i]; + } + } // Cliff Meshes - final Map cliffVars = Variations.CLIFF_VARS; + Map cliffVars = Variations.CLIFF_VARS; for (final Map.Entry cliffVar : cliffVars.entrySet()) { final Integer maxVariations = cliffVar.getValue(); for (int variation = 0; variation <= maxVariations; variation++) { final String fileName = "Doodads\\Terrain\\Cliffs\\Cliffs" + cliffVar.getKey() + variation + ".mdx"; this.cliffMeshes.add(new CliffMesh(fileName, dataSource, Gdx.gl30)); - this.pathToCliff.put(cliffVar.getKey() + variation, this.cliffMeshes.size() - 1); + this.pathToCliff.put("Cliffs" + cliffVar.getKey() + variation, this.cliffMeshes.size() - 1); + } + } + cliffVars = Variations.CITY_CLIFF_VARS; + for (final Map.Entry cliffVar : cliffVars.entrySet()) { + final Integer maxVariations = cliffVar.getValue(); + for (int variation = 0; variation <= maxVariations; variation++) { + final String fileName = "Doodads\\Terrain\\CityCliffs\\CityCliffs" + cliffVar.getKey() + variation + + ".mdx"; + this.cliffMeshes.add(new CliffMesh(fileName, dataSource, Gdx.gl30)); + this.pathToCliff.put("CityCliffs" + cliffVar.getKey() + variation, this.cliffMeshes.size() - 1); } } @@ -165,7 +201,7 @@ public class Terrain { final Element terrainTileInfo = this.terrainTable.get(groundTile.asStringValue()); final String dir = terrainTileInfo.getField("dir"); final String file = terrainTileInfo.getField("file"); - this.groundTextures.add(new GroundTexture(dir + "/" + file + texturesExt, dataSource, Gdx.gl30)); + this.groundTextures.add(new GroundTexture(dir + "\\" + file + texturesExt, dataSource, Gdx.gl30)); this.groundTextureToId.put(groundTile.asStringValue(), this.groundTextures.size() - 1); } @@ -182,10 +218,11 @@ public class Terrain { final Element cliffInfo = this.cliffTable.get(cliffTile.asStringValue()); final String texDir = cliffInfo.getField("texDir"); final String texFile = cliffInfo.getField("texFile"); - try (InputStream imageStream = dataSource.getResourceAsStream(texDir + "/" + texFile + texturesExt)) { + try (InputStream imageStream = dataSource.getResourceAsStream(texDir + "\\" + texFile + texturesExt)) { final BufferedImage image = ImageIO.read(imageStream); this.cliffTextures.add(new UnloadedTexture(image.getWidth(), image.getHeight(), - ImageUtils.getTextureBuffer(ImageUtils.forceBufferedImagesRGB(image)))); + ImageUtils.getTextureBuffer(ImageUtils.forceBufferedImagesRGB(image)), + cliffInfo.getField("cliffModelDir"), cliffInfo.getField("rampModelDir"))); } this.cliffTexturesSize = Math.max(this.cliffTexturesSize, this.cliffTextures.get(this.cliffTextures.size() - 1).width); @@ -222,8 +259,8 @@ public class Terrain { this.groundHeight = gl.glGenTexture(); gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundHeight); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_NEAREST); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_NEAREST); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_LINEAR); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_LINEAR); gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_R16F, width, height, 0, GL30.GL_RED, GL30.GL_FLOAT, RenderMathUtils.wrap(this.groundHeights)); @@ -244,13 +281,13 @@ public class Terrain { this.cliffTextureArray = gl.glGenTexture(); gl.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.cliffTextureArray); gl.glTexImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, GL30.GL_RGBA8, this.cliffTexturesSize, this.cliffTexturesSize, - this.cliffTextures.size(), 0, Extensions.GL_BGRA, GL30.GL_UNSIGNED_BYTE, null); + this.cliffTextures.size(), 0, GL30.GL_RGBA, GL30.GL_UNSIGNED_BYTE, null); gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_LINEAR_MIPMAP_LINEAR); gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_BASE_LEVEL, 0); int sub = 0; for (final UnloadedTexture i : this.cliffTextures) { - gl.glTexSubImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, 0, 0, sub, i.width, i.height, 1, Extensions.GL_BGRA, + gl.glTexSubImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, 0, 0, sub, i.width, i.height, 1, GL30.GL_RGBA, GL30.GL_UNSIGNED_BYTE, i.data); sub += 1; } @@ -276,8 +313,8 @@ public class Terrain { // Water textures this.waterTextureArray = gl.glGenTexture(); gl.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.waterTextureArray); - gl.glTexImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, GL30.GL_RGBA8, 128, 128, this.waterTextureCount, 0, - Extensions.GL_BGRA, GL30.GL_UNSIGNED_BYTE, null); + gl.glTexImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, GL30.GL_SRGB8_ALPHA8, 128, 128, this.waterTextureCount, 0, + GL30.GL_RGBA, GL30.GL_UNSIGNED_BYTE, null); gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_WRAP_S, GL30.GL_CLAMP_TO_EDGE); gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_WRAP_T, GL30.GL_CLAMP_TO_EDGE); gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_BASE_LEVEL, 0); @@ -294,8 +331,7 @@ public class Terrain { } gl.glTexSubImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, 0, 0, i, image.getWidth(), image.getHeight(), 1, - GL30.GL_RGBA, GL30.GL_UNSIGNED_BYTE, - ImageUtils.getTextureBuffer(ImageUtils.forceBufferedImagesRGB(image))); + GL30.GL_RGBA, GL30.GL_UNSIGNED_BYTE, ImageUtils.getTextureBuffer(image)); } } gl.glGenerateMipmap(GL30.GL_TEXTURE_2D_ARRAY); @@ -306,9 +342,14 @@ public class Terrain { this.cliffShader = webGL.createShaderProgram(TerrainShaders.Cliffs.vert, TerrainShaders.Cliffs.frag); this.waterShader = webGL.createShaderProgram(TerrainShaders.Water.vert, TerrainShaders.Water.frag); + this.uberSplatShader = webGL.createShaderProgram(W3xShaders.UberSplat.vert, W3xShaders.UberSplat.frag); + // TODO collision bodies (?) this.centerOffset = w3eFile.getCenterOffset(); + this.uberSplatModels = new ArrayList<>(); + this.mapBounds = w3iFile.getCameraBoundsComplements(); + this.mapSize = w3eFile.getMapSize(); } private void updateGroundHeights(final Rectangle area) { @@ -403,6 +444,10 @@ public class Terrain { final boolean facingLeft = (bottomRight.getLayerHeight() >= bottomLeft.getLayerHeight()) && (topRight.getLayerHeight() >= topLeft.getLayerHeight()); + int bottomLeftCliffTex = bottomLeft.getCliffTexture(); + if (bottomLeftCliffTex == 15) { + bottomLeftCliffTex -= 14; + } if (!(facingDown && (j == 0)) && !(!facingDown && (j >= (this.rows - 2))) && !(facingLeft && (i == 0)) && !(!facingLeft && (i >= (this.columns - 2)))) { final boolean br = ((bottomLeft.getRamp() != 0) != (bottomRight.getRamp() != 0)) @@ -424,7 +469,8 @@ public class Terrain { + ((bottomRight.getLayerHeight() - base) * (bottomRight.getRamp() != 0 ? -4 : 1))); - fileName = "Doodads\\Terrain\\CliffTrans\\CliffTrans" + fileName + "0.mdx"; + final String rampModelDir = this.cliffTextures.get(bottomLeftCliffTex).rampModelDir; + fileName = "Doodads\\Terrain\\" + rampModelDir + "\\" + rampModelDir + fileName + "0.mdx"; if (this.dataSource.has(fileName)) { if (!this.pathToCliff.containsKey(fileName)) { @@ -441,7 +487,7 @@ public class Terrain { } } - this.cliffs.add(new IVec3((i + ((bo ? 1 : 0) * (facingDown ? 0 : 1))), + this.cliffs.add(new IVec3((i + ((bo ? 1 : 0) * (facingLeft ? 0 : 1))), (j - ((br ? 1 : 0) * (facingDown ? 1 : 0))), this.pathToCliff.get(fileName))); bottomLeft.romp = true; @@ -475,7 +521,10 @@ public class Terrain { } // Clamp to within max variations - fileName += Variations.getCliffVariation("Cliffs", fileName, bottomLeft.getCliffVariation()); + + fileName = this.cliffTextures.get(bottomLeftCliffTex).cliffModelDir + fileName + + Variations.getCliffVariation(this.cliffTextures.get(bottomLeftCliffTex).cliffModelDir, + fileName, bottomLeft.getCliffVariation()); if (!this.pathToCliff.containsKey(fileName)) { throw new IllegalArgumentException("No such pathToCliff entry: " + fileName); } @@ -693,12 +742,51 @@ public class Terrain { } + public void renderUberSplats() { + final GL30 gl = Gdx.gl30; + final WebGL webGL = this.webGL; + final ShaderProgram shader = this.uberSplatShader; + + gl.glDepthMask(false); + gl.glEnable(GL30.GL_BLEND); + gl.glBlendFunc(GL30.GL_SRC_ALPHA, GL30.GL_ONE_MINUS_SRC_ALPHA); + gl.glBlendEquation(GL30.GL_FUNC_ADD); + + webGL.useShaderProgram(this.uberSplatShader); + + shader.setUniformMatrix("u_mvp", this.camera.viewProjectionMatrix); + shader.setUniformi("u_heightMap", 0); + sizeHeap[0] = this.columns - 1; + sizeHeap[1] = this.rows - 1; + shader.setUniform2fv("u_size", sizeHeap, 0, 2); + sizeHeap[0] = 1 / (float) this.columns; + sizeHeap[1] = 1 / (float) this.rows; + shader.setUniform2fv("u_pixel", sizeHeap, 0, 2); + shader.setUniform2fv("u_centerOffset", this.centerOffset, 0, 2); + shader.setUniformi("u_texture", 1); + shader.setUniformi("u_shadowMap", 2); + + gl.glActiveTexture(GL30.GL_TEXTURE0); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeight); + + gl.glActiveTexture(GL30.GL_TEXTURE2); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.shadowMap); + + // Render the cliffs + for (final SplatModel splat : this.uberSplatModels) { + splat.render(gl, shader); + } + } + public void renderWater() { // Render water this.webGL.useShaderProgram(this.waterShader); final GL30 gl = Gdx.gl30; + gl.glDepthMask(false); + gl.glDisable(GL30.GL_CULL_FACE); gl.glEnable(GL30.GL_BLEND); + gl.glBlendFunc(GL30.GL_SRC_ALPHA, GL30.GL_ONE_MINUS_SRC_ALPHA); gl.glUniformMatrix4fv(0, 1, false, this.camera.viewProjectionMatrix.val, 0); gl.glUniform4fv(1, 1, this.minShallowColor, 0); @@ -748,13 +836,12 @@ public class Terrain { this.cliffMeshes.get(i.z).renderQueue(fourComponentHeap); } - this.cliffShader.begin(); + this.webGL.useShaderProgram(this.cliffShader); final GL30 gl = Gdx.gl30; // WC3 models are 128x too large tempMatrix.set(this.camera.viewProjectionMatrix); - tempMatrix.scale(1 / 128f, 1 / 128f, 1 / 128f); gl.glUniformMatrix4fv(0, 1, false, tempMatrix.val, 0); gl.glUniform1i(1, this.viewer.renderPathing); gl.glUniform1i(2, this.viewer.renderLighting); @@ -765,10 +852,102 @@ public class Terrain { gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundHeight); // gl.glActiveTexture(GL30.GL_TEXTURE2); for (final CliffMesh i : this.cliffMeshes) { + gl.glUniform1f(this.cliffShader.getUniformLocation("centerOffsetX"), this.centerOffset[0]); + gl.glUniform1f(this.cliffShader.getUniformLocation("centerOffsetY"), this.centerOffset[1]); i.render(); } } + public void addShadow(final String file, final float x, final float y) { + if (!this.shadows.containsKey(file)) { + final String path = "ReplaceableTextures\\Shadows\\" + file + ".blp"; + this.shadows.put(file, new ArrayList<>()); + this.shadowTextures.put(file, (Texture) this.viewer.load(path, PathSolver.DEFAULT, null)); + } + this.shadows.get(file).add(new float[] { x, y }); + } + + public void initShadows() throws IOException { + final GL30 gl = Gdx.gl30; + final float[] centerOffset = this.centerOffset; + final int columns = (this.columns - 1) * 4; + final int rows = (this.rows - 1) * 4; + + final int shadowSize = columns * rows; + final byte[] shadowData = new byte[columns * rows]; + if (this.viewer.mapMpq.has("war3map.shd")) { + final InputStream shadowSource = this.viewer.mapMpq.getResourceAsStream("war3map.shd"); + final byte[] buffer = IOUtils.toByteArray(shadowSource); + for (int i = 0; i < shadowSize; i++) { + shadowData[i] = (byte) (buffer[i] / 2); + } + } + + for (final Map.Entry fileAndTexture : this.shadowTextures.entrySet()) { + final String file = fileAndTexture.getKey(); + final Texture texture = fileAndTexture.getValue(); + + final int width = texture.getWidth(); + final int height = texture.getHeight(); + final int ox = (int) Math.round(width * 0.3); + final int oy = (int) Math.round(height * 0.7); + for (final float[] location : this.shadows.get(file)) { + final int x0 = (int) Math.floor((location[0] - centerOffset[0]) / 32.0) - ox; + final int y0 = (int) Math.floor((location[1] - centerOffset[1]) / 32.0) + oy; + for (int y = 0; y < height; ++y) { + if (((y0 - y) < 0) || ((y0 - y) >= rows)) { + continue; + } + for (int x = 0; x < width; ++x) { + if (((x0 + x) < 0) || ((x0 + x) >= columns)) { + continue; + } + if (((RawOpenGLTextureResource) texture).getData().get((((y * width) + x) * 4) + 3) != 0) { + shadowData[((y0 - y) * columns) + x0 + x] = (byte) 128; + } + } + } + } + } + + final byte outsideArea = (byte) 204; + final int x0 = this.mapBounds[0] * 4, x1 = (this.mapSize[0] - this.mapBounds[1] - 1) * 4, + y0 = this.mapBounds[2] * 4, y1 = (this.mapSize[1] - this.mapBounds[3] - 1) * 4; + for (int y = y0; y < y1; ++y) { + for (int x = x0; x < x1; ++x) { + final RenderCorner c = this.corners[x >> 2][y >> 2]; + if (c.getBoundary() != 0) { + shadowData[(y * columns) + x] = outsideArea; + } + } + } + for (int y = 0; y < rows; ++y) { + for (int x = 0; x < x0; ++x) { + shadowData[(y * columns) + x] = outsideArea; + } + for (int x = x1; x < columns; ++x) { + shadowData[(y * columns) + x] = outsideArea; + } + } + for (int x = x0; x < x1; ++x) { + for (int y = 0; y < y0; ++y) { + shadowData[(y * columns) + x] = outsideArea; + } + for (int y = y1; y < rows; ++y) { + shadowData[(y * columns) + x] = outsideArea; + } + } + + this.shadowMap = gl.glGenBuffer(); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.shadowMap); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_LINEAR); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_LINEAR); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_S, GL30.GL_CLAMP_TO_EDGE); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_T, GL30.GL_CLAMP_TO_EDGE); + gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_R8, columns, rows, 0, GL30.GL_RED, GL30.GL_UNSIGNED_BYTE, + RenderMathUtils.wrap(shadowData)); + } + // public Vector3 groundNormal(final Vector3 out, int x, int y) { // final float[] centerOffset = this.centerOffset; // final int[] mapSize = this.mapSize; @@ -813,12 +992,62 @@ public class Terrain { private final int width; private final int height; private final Buffer data; + private final String cliffModelDir; + private final String rampModelDir; - public UnloadedTexture(final int width, final int height, final Buffer data) { + public UnloadedTexture(final int width, final int height, final Buffer data, final String cliffModelDir, + final String rampModelDir) { this.width = width; this.height = height; this.data = data; + this.cliffModelDir = cliffModelDir; + this.rampModelDir = rampModelDir; } } + + public float getGroundHeight(final float x, final float y) { + final float userCellSpaceX = (x - this.centerOffset[0]) / 128.0f; + final float userCellSpaceY = (y - this.centerOffset[1]) / 128.0f; + final int cellX = (int) userCellSpaceX; + final int cellY = (int) userCellSpaceY; + + if ((cellX >= 0) && (cellX < (this.mapSize[0] - 1)) && (cellY >= 0) && (cellY < (this.mapSize[1] - 1))) { + final float bottomLeft = this.corners[cellX][cellY].computeFinalGroundHeight(); + final float bottomRight = this.corners[cellX][cellY].computeFinalGroundHeight(); + final float topLeft = this.corners[cellX][cellY].computeFinalGroundHeight(); + final float topRight = this.corners[cellX][cellY].computeFinalGroundHeight(); + final float sqX = userCellSpaceX - cellX; + final float sqY = userCellSpaceY - cellY; + float height; + + if ((sqX + sqY) < 1) { + height = bottomLeft + ((bottomRight - bottomLeft) * sqX) + ((topLeft - bottomLeft) * sqY); + } + else { + height = topRight + ((bottomRight - topRight) * (1 - sqY)) + ((topLeft - topRight) * (1 - sqX)); + } + + return height * 128.0f; + } + + return 0; + } + + public static final class Splat { + public List locations = new ArrayList<>(); + public float opacity = 1; + } + + public void loadSplats() throws IOException { + for (final Map.Entry entry : this.splats.entrySet()) { + final String path = entry.getKey(); + final Splat splat = entry.getValue(); + + final SplatModel splatModel = new SplatModel(Gdx.gl30, + (Texture) this.viewer.load(path, PathSolver.DEFAULT, null), splat.locations, this.centerOffset); + splatModel.color[3] = splat.opacity; + this.uberSplatModels.add(splatModel); + } + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/TerrainShaders.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/TerrainShaders.java index 311a955..0134adb 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/TerrainShaders.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/TerrainShaders.java @@ -1,5 +1,8 @@ package com.etheller.warsmash.viewer5.handlers.w3x.environment; +/** + * Mostly copied from HiveWE! + */ public class TerrainShaders { public static final class Cliffs { private Cliffs() { @@ -9,12 +12,14 @@ public class TerrainShaders { "\r\n" + // "layout (location = 0) in vec3 vPosition;\r\n" + // "layout (location = 1) in vec2 vUV;\r\n" + // - "layout (location = 2) in vec3 vNormal;\r\n" + // - "layout (location = 3) in vec4 vOffset;\r\n" + // +// "layout (location = 2) in vec3 vNormal;\r\n" + // + "layout (location = 2) in vec4 vOffset;\r\n" + // "\r\n" + // "layout (location = 0) uniform mat4 MVP;\r\n" + // "\r\n" + // "layout (binding = 1) uniform sampler2D height_texture;\r\n" + // + "layout (location = 3) uniform float centerOffsetX;\r\n" + // + "layout (location = 4) uniform float centerOffsetY;\r\n" + // "\r\n" + // "layout (location = 0) out vec3 UV;\r\n" + // "layout (location = 1) out vec3 Normal;\r\n" + // @@ -24,11 +29,14 @@ public class TerrainShaders { " pathing_map_uv = (vec2(vPosition.x + 128, vPosition.y) / 128 + vOffset.xy) * 4;\r\n" + // " \r\n" + // " ivec2 size = textureSize(height_texture, 0);\r\n" + // - " float value = texture(height_texture, (vOffset.xy + vec2(vPosition.x + 192, vPosition.y + 64) / 128) / vec2(size)).r;\r\n" + " float value = texture(height_texture, (vOffset.xy + vec2(vPosition.x + 192, vPosition.y + 64) / 128.0) / vec2(size)).r;\r\n" + // "\r\n" + // - " gl_Position = MVP * vec4(vPosition + vec3(vOffset.xy + vec2(1, 0), vOffset.z + value) * 128, 1);\r\n" + " vec4 myposition = vec4((vPosition + vec3(vOffset.xy + vec2(1, 0), vOffset.z + value) * 128 ), 1);\r\n" + // + " myposition.x += centerOffsetX;\r\n" + // + " myposition.y += centerOffsetY;\r\n" + // + " gl_Position = MVP * myposition;\r\n" + // " UV = vec3(vUV, vOffset.a);\r\n" + // "\r\n" + // " ivec2 height_pos = ivec2(vOffset.xy + vec2(vPosition.x + 128, vPosition.y) / 128);\r\n" + // @@ -273,7 +281,7 @@ public class TerrainShaders { " gl_Position = is_water ? MVP * vec4((vPosition.x + pos.x)*128.0 + centerOffsetX, (vPosition.y + pos.y)*128.0 + centerOffsetY, water_height*128.0, 1) : vec4(2.0, 0.0, 0.0, 1.0);\r\n" + // "\r\n" + // - " UV = vec2(vPosition.x, vPosition.y);\r\n" + // + " UV = vec2((vPosition.x + pos.x%2)/2.0, (vPosition.y + pos.y%2)/2.0);\r\n" + // "\r\n" + // " float ground_height = texelFetch(ground_height_texture, height_pos, 0).r;\r\n" + // " float value = clamp(water_height - ground_height, 0.f, 1.f);\r\n" + // diff --git a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java index 07fe4e9..84e654e 100644 --- a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java +++ b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java @@ -1,6 +1,5 @@ package com.etheller.warsmash.desktop; -import org.lwjgl.opengl.GL12; import org.lwjgl.opengl.GL31; import org.lwjgl.opengl.GL33; @@ -30,7 +29,6 @@ public class DesktopLauncher { GL31.glDrawArraysInstanced(mode, first, count, instanceCount); } }; - Extensions.GL_BGRA = GL12.GL_BGRA; final LwjglApplicationConfiguration config = new LwjglApplicationConfiguration(); config.useGL30 = true; config.gles30ContextMajorVersion = 3; From ebf7bb93dbcd5cbc9e574af17f064af84e28af7d Mon Sep 17 00:00:00 2001 From: Retera Date: Sat, 25 Jan 2020 12:49:03 -0600 Subject: [PATCH 018/116] FPS meter --- .../etheller/warsmash/WarsmashGdxMapGame.java | 81 +++++++++++++++++-- .../warsmash/viewer5/ModelViewer.java | 1 + .../etheller/warsmash/viewer5/gl/WebGL.java | 18 +++++ .../viewer5/handlers/w3x/War3MapViewer.java | 1 + .../warsmash/desktop/DesktopLauncher.java | 1 + 5 files changed, 95 insertions(+), 7 deletions(-) diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java index a5b14cb..6c8043c 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java @@ -1,6 +1,7 @@ package com.etheller.warsmash; import java.io.IOException; +import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.IntBuffer; @@ -10,14 +11,22 @@ import com.badlogic.gdx.ApplicationAdapter; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Input; import com.badlogic.gdx.InputProcessor; +import com.badlogic.gdx.files.FileHandle; +import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.GL30; +import com.badlogic.gdx.graphics.OrthographicCamera; import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.g2d.GlyphLayout; import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator; +import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator.FreeTypeFontParameter; import com.badlogic.gdx.math.Quaternion; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.math.Vector3; +import com.badlogic.gdx.utils.viewport.FitViewport; +import com.badlogic.gdx.utils.viewport.Viewport; import com.etheller.warsmash.datasources.CompoundDataSourceDescriptor; import com.etheller.warsmash.datasources.DataSource; import com.etheller.warsmash.datasources.DataSourceDescriptor; @@ -37,14 +46,19 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv private CameraManager cameraManager; private final Rectangle tempRect = new Rectangle(); - private BitmapFont font; - private SpriteBatch batch; private CameraManager portraitCameraManager; private MdxModel portraitModel; private MdxComplexInstance portraitInstance; private final float[] cameraPositionTemp = new float[3]; private final float[] cameraTargetTemp = new float[3]; + // libGDX stuff + private OrthographicCamera uiCamera; + private BitmapFont font; + private SpriteBatch batch; + private Viewport uiViewport; + private GlyphLayout glyphLayout; + @Override public void create() { @@ -92,7 +106,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.portraitCameraManager.setupCamera(this.portraitScene); // this.mainModel = (MdxModel) this.viewer.load("UI\\Glues\\MainMenu\\MainMenu3D_exp\\MainMenu3D_exp.mdx", - this.portraitModel = (MdxModel) this.viewer.load("Units\\NightElf\\Runner\\Runner_Portrait.mdx", + this.portraitModel = (MdxModel) this.viewer.load("Units\\NightElf\\DruidOfTheClaw\\DruidOfTheClaw_Portrait.mdx", new PathSolver() { @Override public SolvedPath solve(final String src, final Object solverParams) { @@ -105,7 +119,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.portraitInstance = (MdxComplexInstance) this.portraitModel.addInstance(0); - this.portraitInstance.setTeamColor(1); + this.portraitInstance.setTeamColor(6); this.portraitInstance.setScene(this.portraitScene); @@ -114,7 +128,38 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.portraitInstance.setSequenceLoopMode(0); - this.font = new BitmapFont(); + // libGDX stuff + final float w = Gdx.graphics.getWidth(); + final float h = Gdx.graphics.getHeight(); + + final FreeTypeFontGenerator fontGenerator = new FreeTypeFontGenerator(new FileHandle("fonts\\FRIZQT__.TTF") { + @Override + public InputStream read() { + // TODO load font name from WC3 configs, since actually in WC3 mods were able to + // change it + try { + return WarsmashGdxMapGame.this.viewer.dataSource.getResourceAsStream("fonts\\FRIZQT__.TTF"); + } + catch (final IOException e) { + throw new IllegalStateException("No such file: " + path()); + } + } + }); + final FreeTypeFontParameter fontParam = new FreeTypeFontParameter(); + fontParam.size = 32; + this.font = fontGenerator.generateFont(fontParam); + fontGenerator.dispose(); + this.glyphLayout = new GlyphLayout(); + + // Constructs a new OrthographicCamera, using the given viewport width and + // height + // Height is multiplied by aspect ratio. + this.uiCamera = new OrthographicCamera(); + this.uiViewport = new FitViewport(1600, 1200, this.uiCamera); + + this.uiCamera.position.set(this.uiCamera.viewportWidth / 2f, this.uiCamera.viewportHeight / 2f, 0); + this.uiCamera.update(); + this.batch = new SpriteBatch(); Gdx.input.setInputProcessor(this); @@ -124,6 +169,8 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv @Override public void render() { + this.uiCamera.update(); + Gdx.gl30.glEnable(GL30.GL_SCISSOR_TEST); final float deltaTime = Gdx.graphics.getDeltaTime(); Gdx.gl30.glBindVertexArray(WarsmashGdxGame.VAO); this.cameraManager.target.add(this.cameraVelocity.x * deltaTime, this.cameraVelocity.y * deltaTime, 0); @@ -147,6 +194,21 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv if ((this.frame++ % 1000) == 0) { System.out.println(Gdx.graphics.getFramesPerSecond()); } + Gdx.gl30.glDisable(GL30.GL_SCISSOR_TEST); + + Gdx.gl30.glDisable(GL30.GL_CULL_FACE); + + this.viewer.webGL.useShaderProgram(null); + + Gdx.gl30.glActiveTexture(GL30.GL_TEXTURE0); + this.uiViewport.apply(); + this.batch.setProjectionMatrix(this.uiCamera.combined); + this.batch.begin(); + this.font.setColor(Color.YELLOW); + final String fpsString = "FPS: " + Gdx.graphics.getFramesPerSecond(); + this.glyphLayout.setText(this.font, fpsString); + this.font.draw(this.batch, fpsString, (this.uiViewport.getWorldWidth() - this.glyphLayout.width) / 2, 1100); + this.batch.end(); } @Override @@ -165,11 +227,13 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv @Override public void resize(final int width, final int height) { + final float worldFrameTestHeight = (360 / 480f) * height; + final float worldFrameTestY = (100 / 480f) * height; super.resize(width, height); this.tempRect.x = 0; - this.tempRect.y = 0; + this.tempRect.y = worldFrameTestY; this.tempRect.width = width; - this.tempRect.height = height; + this.tempRect.height = worldFrameTestHeight; this.cameraManager.camera.viewport(this.tempRect); final float portraitTestWidth = (100 / 640f) * width; final float portraitTestHeight = (100 / 480f) * height; @@ -178,6 +242,9 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.tempRect.width = portraitTestWidth; this.tempRect.height = portraitTestHeight; this.portraitScene.camera.viewport(this.tempRect); + + this.uiViewport.update(width, height); + this.uiCamera.position.set(this.uiCamera.viewportWidth / 2, this.uiCamera.viewportHeight / 2, 0); } class CameraManager { diff --git a/core/src/com/etheller/warsmash/viewer5/ModelViewer.java b/core/src/com/etheller/warsmash/viewer5/ModelViewer.java index 7986dc6..8627d64 100644 --- a/core/src/com/etheller/warsmash/viewer5/ModelViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/ModelViewer.java @@ -293,6 +293,7 @@ public class ModelViewer { } public void startFrame() { + Gdx.gl.glScissor(0, 0, (int) this.canvas.getWidth(), (int) this.canvas.getHeight()); this.gl.glDepthMask(true); this.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT); // WarsmashGdxGame.bindDefaultVertexArray(); diff --git a/core/src/com/etheller/warsmash/viewer5/gl/WebGL.java b/core/src/com/etheller/warsmash/viewer5/gl/WebGL.java index b9e3eb0..6235a95 100644 --- a/core/src/com/etheller/warsmash/viewer5/gl/WebGL.java +++ b/core/src/com/etheller/warsmash/viewer5/gl/WebGL.java @@ -116,6 +116,24 @@ public class WebGL { this.disableVertexAttribs(newAttribs, oldAttribs); } + this.currentShaderProgram = shaderProgram; + } + else if (shaderProgram == null) { + int oldAttribs = 0; + final int newAttribs = 0; + + if (currentShaderProgram != null) { + oldAttribs = currentShaderProgram.getAttributes().length; + currentShaderProgram.end(); + } + + if (newAttribs > oldAttribs) { + this.enableVertexAttribs(oldAttribs, newAttribs); + } + else if (newAttribs < oldAttribs) { + this.disableVertexAttribs(newAttribs, oldAttribs); + } + this.currentShaderProgram = shaderProgram; } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index 587e70b..c23e32a 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -471,6 +471,7 @@ public class War3MapViewer extends ModelViewer { if (this.anyReady) { final Scene worldScene = this.worldScene; + startFrame(); worldScene.startFrame(); this.terrain.renderGround(); this.terrain.renderCliffs(); diff --git a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java index 84e654e..25d30e5 100644 --- a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java +++ b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java @@ -33,6 +33,7 @@ public class DesktopLauncher { config.useGL30 = true; config.gles30ContextMajorVersion = 3; config.gles30ContextMinorVersion = 3; + config.samples = 16; // config.fullscreen = true; // final DisplayMode desktopDisplayMode = LwjglApplicationConfiguration.getDesktopDisplayMode(); // config.width = desktopDisplayMode.width; From 8c1d64b1e4868c4f66581e4f221fe06c5016388e Mon Sep 17 00:00:00 2001 From: Retera Date: Sun, 26 Jan 2020 15:42:05 -0600 Subject: [PATCH 019/116] Added some fun soundsets and stuff --- .../etheller/warsmash/WarsmashGdxMapGame.java | 173 +++++++++----- .../warsmash/util/DataSourceFileHandle.java | 26 +++ .../etheller/warsmash/util/ImageUtils.java | 22 ++ .../com/etheller/warsmash/viewer5/Node.java | 6 + .../viewer5/handlers/blp/BlpGdxTexture.java | 39 ++++ .../viewer5/handlers/blp/BlpTexture.java | 2 +- .../viewer5/handlers/tga/ImageUtils.java | 179 +++++++++++++++ .../viewer5/handlers/tga/TgaFile.java | 217 ++++++++++++++++++ .../viewer5/handlers/w3x/StandSequence.java | 44 +++- .../warsmash/viewer5/handlers/w3x/Unit.java | 19 +- .../viewer5/handlers/w3x/UnitSoundset.java | 107 +++++++++ .../viewer5/handlers/w3x/War3MapViewer.java | 214 ++++++++++++++++- .../handlers/w3x/environment/Terrain.java | 154 ++++++++++++- .../w3x/environment/TerrainShaders.java | 149 +++++++++++- 14 files changed, 1267 insertions(+), 84 deletions(-) create mode 100644 core/src/com/etheller/warsmash/util/DataSourceFileHandle.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/blp/BlpGdxTexture.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/tga/ImageUtils.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/tga/TgaFile.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitSoundset.java diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java index 6c8043c..c2b7d76 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java @@ -1,21 +1,23 @@ package com.etheller.warsmash; import java.io.IOException; -import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.IntBuffer; import java.util.Arrays; +import java.util.List; + +import javax.imageio.ImageIO; import com.badlogic.gdx.ApplicationAdapter; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Input; import com.badlogic.gdx.InputProcessor; -import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.GL30; import com.badlogic.gdx.graphics.OrthographicCamera; +import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.GlyphLayout; import com.badlogic.gdx.graphics.g2d.SpriteBatch; @@ -31,23 +33,26 @@ import com.etheller.warsmash.datasources.CompoundDataSourceDescriptor; import com.etheller.warsmash.datasources.DataSource; import com.etheller.warsmash.datasources.DataSourceDescriptor; import com.etheller.warsmash.datasources.FolderDataSourceDescriptor; +import com.etheller.warsmash.util.DataSourceFileHandle; +import com.etheller.warsmash.util.ImageUtils; import com.etheller.warsmash.viewer5.Camera; import com.etheller.warsmash.viewer5.CanvasProvider; -import com.etheller.warsmash.viewer5.PathSolver; import com.etheller.warsmash.viewer5.Scene; -import com.etheller.warsmash.viewer5.SolvedPath; import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; +import com.etheller.warsmash.viewer5.handlers.tga.TgaFile; +import com.etheller.warsmash.viewer5.handlers.w3x.StandSequence; +import com.etheller.warsmash.viewer5.handlers.w3x.Unit; import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProvider, InputProcessor { + private static final Vector3 clickLocationTemp = new Vector3(); private DataSource codebase; private War3MapViewer viewer; private CameraManager cameraManager; private final Rectangle tempRect = new Rectangle(); private CameraManager portraitCameraManager; - private MdxModel portraitModel; private MdxComplexInstance portraitInstance; private final float[] cameraPositionTemp = new float[3]; private final float[] cameraTargetTemp = new float[3]; @@ -59,6 +64,10 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv private Viewport uiViewport; private GlyphLayout glyphLayout; + private Texture consoleUITexture; + private final Vector2 projectionTemp1 = new Vector2(); + private final Vector2 projectionTemp2 = new Vector2(); + @Override public void create() { @@ -88,11 +97,13 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.viewer = new War3MapViewer(this.codebase, this); try { - this.viewer.loadMap("(2)BootyBay.w3m"); + this.viewer.loadMap("Farm.w3x"); } catch (final IOException e) { throw new RuntimeException(e); } + this.viewer.worldScene.enableAudio(); + this.viewer.enableAudio(); this.cameraManager = new CameraManager(); this.cameraManager.setupCamera(this.viewer.worldScene); @@ -106,45 +117,15 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.portraitCameraManager.setupCamera(this.portraitScene); // this.mainModel = (MdxModel) this.viewer.load("UI\\Glues\\MainMenu\\MainMenu3D_exp\\MainMenu3D_exp.mdx", - this.portraitModel = (MdxModel) this.viewer.load("Units\\NightElf\\DruidOfTheClaw\\DruidOfTheClaw_Portrait.mdx", - new PathSolver() { - @Override - public SolvedPath solve(final String src, final Object solverParams) { - return new SolvedPath(src, src.substring(src.lastIndexOf('.')), true); - } - }, null); - this.portraitCameraManager.modelCamera = this.portraitModel.cameras.get(0); this.portraitScene.camera.viewport(new Rectangle(100, 0, 6400, 48)); - this.portraitInstance = (MdxComplexInstance) this.portraitModel.addInstance(0); - - this.portraitInstance.setTeamColor(6); - - this.portraitInstance.setScene(this.portraitScene); - - final int animIndex = 0; - this.portraitInstance.setSequence(animIndex); - - this.portraitInstance.setSequenceLoopMode(0); - // libGDX stuff final float w = Gdx.graphics.getWidth(); final float h = Gdx.graphics.getHeight(); - final FreeTypeFontGenerator fontGenerator = new FreeTypeFontGenerator(new FileHandle("fonts\\FRIZQT__.TTF") { - @Override - public InputStream read() { - // TODO load font name from WC3 configs, since actually in WC3 mods were able to - // change it - try { - return WarsmashGdxMapGame.this.viewer.dataSource.getResourceAsStream("fonts\\FRIZQT__.TTF"); - } - catch (final IOException e) { - throw new IllegalStateException("No such file: " + path()); - } - } - }); + final FreeTypeFontGenerator fontGenerator = new FreeTypeFontGenerator( + new DataSourceFileHandle(this.viewer.dataSource, "fonts\\FRIZQT__.TTF")); final FreeTypeFontParameter fontParam = new FreeTypeFontParameter(); fontParam.size = 32; this.font = fontGenerator.generateFont(fontParam); @@ -156,16 +137,44 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv // Height is multiplied by aspect ratio. this.uiCamera = new OrthographicCamera(); this.uiViewport = new FitViewport(1600, 1200, this.uiCamera); + this.uiViewport.update(Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); this.uiCamera.position.set(this.uiCamera.viewportWidth / 2f, this.uiCamera.viewportHeight / 2f, 0); this.uiCamera.update(); + positionPortrait(); + this.batch = new SpriteBatch(); - Gdx.input.setInputProcessor(this); - } + this.consoleUITexture = new Texture(new DataSourceFileHandle(this.viewer.dataSource, "AlphaUi.png")); + if (this.viewer.dataSource.has("war3mapMap.tga")) { + try { + this.minimapTexture = ImageUtils.getTextureNoColorCorrection(TgaFile.readTGA("war3mapMap.tga", + this.viewer.dataSource.getResourceAsStream("war3mapMap.tga"))); + } + catch (final IOException e) { + System.err.println("Could not load minimap TGA file"); + e.printStackTrace(); + } + } + else if (this.viewer.dataSource.has("war3mapMap.blp")) { + try { + this.minimapTexture = ImageUtils + .getTexture(ImageIO.read(this.viewer.dataSource.getResourceAsStream("war3mapMap.blp"))); + } + catch (final IOException e) { + System.err.println("Could not load minimap BLP file"); + e.printStackTrace(); + } + } - private int frame = 0; + Gdx.input.setInputProcessor(this); + +// final Music music = Gdx.audio.newMusic(new DataSourceFileHandle(this.viewer.dataSource, "undead_dance.mp3")); +// music.setVolume(0.7f); +// music.setLooping(true); +// music.play(); + } @Override public void render() { @@ -186,14 +195,11 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv // this.font.draw(this.batch, Integer.toString(Gdx.graphics.getFramesPerSecond()), 0, 0); // this.batch.end(); - if (this.portraitInstance.sequenceEnded) { - this.portraitInstance - .setSequence((this.portraitInstance.sequence + 1) % this.portraitModel.getSequences().size()); + if ((this.portraitInstance != null) + && (this.portraitInstance.sequenceEnded || (this.portraitInstance.sequence == -1))) { + StandSequence.randomPortraitSequence(this.portraitInstance); } - if ((this.frame++ % 1000) == 0) { - System.out.println(Gdx.graphics.getFramesPerSecond()); - } Gdx.gl30.glDisable(GL30.GL_SCISSOR_TEST); Gdx.gl30.glDisable(GL30.GL_CULL_FACE); @@ -208,6 +214,8 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv final String fpsString = "FPS: " + Gdx.graphics.getFramesPerSecond(); this.glyphLayout.setText(this.font, fpsString); this.font.draw(this.batch, fpsString, (this.uiViewport.getWorldWidth() - this.glyphLayout.width) / 2, 1100); + this.batch.draw(this.consoleUITexture, 0, 0, this.uiViewport.getWorldWidth(), 320); + this.batch.draw(this.minimapTexture, 35, 7, 305, 272); this.batch.end(); } @@ -227,24 +235,35 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv @Override public void resize(final int width, final int height) { - final float worldFrameTestHeight = (360 / 480f) * height; - final float worldFrameTestY = (100 / 480f) * height; super.resize(width, height); this.tempRect.x = 0; - this.tempRect.y = worldFrameTestY; + this.tempRect.y = 0; this.tempRect.width = width; - this.tempRect.height = worldFrameTestHeight; + this.tempRect.height = height; this.cameraManager.camera.viewport(this.tempRect); final float portraitTestWidth = (100 / 640f) * width; final float portraitTestHeight = (100 / 480f) * height; - this.tempRect.x = portraitTestWidth; - this.tempRect.y = 0; - this.tempRect.width = portraitTestWidth; - this.tempRect.height = portraitTestHeight; - this.portraitScene.camera.viewport(this.tempRect); this.uiViewport.update(width, height); this.uiCamera.position.set(this.uiCamera.viewportWidth / 2, this.uiCamera.viewportHeight / 2, 0); + + positionPortrait(); + + } + + private void positionPortrait() { + this.projectionTemp1.x = 385; + this.projectionTemp1.y = 0; + this.projectionTemp2.x = 385 + 180; + this.projectionTemp2.y = 177; + this.uiViewport.project(this.projectionTemp1); + this.uiViewport.project(this.projectionTemp2); + + this.tempRect.x = this.projectionTemp1.x; + this.tempRect.y = this.projectionTemp1.y; + this.tempRect.width = this.projectionTemp2.x - this.projectionTemp1.x; + this.tempRect.height = this.projectionTemp2.y - this.projectionTemp1.y; + this.portraitScene.camera.viewport(this.tempRect); } class CameraManager { @@ -276,7 +295,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.rotationSpeed = (float) (Math.PI / 180); this.zoomFactor = 0.1f; this.horizontalAngle = 0;// (float) (Math.PI / 2); - this.verticalAngle = (float) (Math.PI / 4); + this.verticalAngle = (float) (Math.PI / 5); this.distance = 1600; this.position = new Vector3(); this.target = new Vector3(0, 0, 50); @@ -338,6 +357,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv private final float cameraSpeed = 4096.0f; // per second private final Vector2 cameraVelocity = new Vector2(); private Scene portraitScene; + private Texture minimapTexture; @Override public boolean keyDown(final int keycode) { @@ -381,6 +401,45 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv @Override public boolean touchDown(final int screenX, final int screenY, final int pointer, final int button) { System.out.println(screenX + "," + screenY); + if (button == Input.Buttons.RIGHT) { + this.viewer.getClickLocation(clickLocationTemp, screenX, screenY); + System.out.println(clickLocationTemp); + this.viewer.showConfirmation(clickLocationTemp, 0, 1, 0); + final int x = (int) ((clickLocationTemp.x - this.viewer.terrain.centerOffset[0]) / 128); + final int y = (int) ((clickLocationTemp.y - this.viewer.terrain.centerOffset[1]) / 128); + System.out.println(x + "," + y); + } + else { + final List selectedUnits = this.viewer.selectUnit(screenX, screenY, true); + if (!selectedUnits.isEmpty()) { + final Unit unit = selectedUnits.get(0); + if (unit.soundset != null) { + unit.soundset.what.play(this.viewer.worldScene.audioContext, unit.location[0], unit.location[1]); + } + final MdxModel portraitModel = unit.portraitModel; + if (portraitModel != null) { + if (this.portraitInstance != null) { + this.portraitScene.removeInstance(this.portraitInstance); + } + this.portraitInstance = (MdxComplexInstance) portraitModel.addInstance(); + this.portraitInstance.setSequenceLoopMode(1); + this.portraitInstance.setScene(this.portraitScene); + if (portraitModel.getCameras().size() > 0) { + this.portraitCameraManager.modelCamera = portraitModel.getCameras().get(0); + } + this.portraitInstance.setTeamColor(unit.playerIndex); + StandSequence.randomPortraitTalkSequence(this.portraitInstance); + + } + } + else { + if (this.portraitInstance != null) { + this.portraitScene.removeInstance(this.portraitInstance); + } + this.portraitInstance = null; + this.portraitCameraManager.modelCamera = null; + } + } return false; } diff --git a/core/src/com/etheller/warsmash/util/DataSourceFileHandle.java b/core/src/com/etheller/warsmash/util/DataSourceFileHandle.java new file mode 100644 index 0000000..0742560 --- /dev/null +++ b/core/src/com/etheller/warsmash/util/DataSourceFileHandle.java @@ -0,0 +1,26 @@ +package com.etheller.warsmash.util; + +import java.io.IOException; +import java.io.InputStream; + +import com.badlogic.gdx.files.FileHandle; +import com.etheller.warsmash.datasources.DataSource; + +public class DataSourceFileHandle extends FileHandle { + private final DataSource dataSource; + + public DataSourceFileHandle(final DataSource dataSource, final String path) { + super(path); + this.dataSource = dataSource; + } + + @Override + public InputStream read() { + try { + return this.dataSource.getResourceAsStream(path()); + } + catch (final IOException e) { + throw new RuntimeException("Failed to load FileHandle from DataSource: " + path()); + } + } +} diff --git a/core/src/com/etheller/warsmash/util/ImageUtils.java b/core/src/com/etheller/warsmash/util/ImageUtils.java index c402cf0..c1e8686 100644 --- a/core/src/com/etheller/warsmash/util/ImageUtils.java +++ b/core/src/com/etheller/warsmash/util/ImageUtils.java @@ -49,6 +49,28 @@ public final class ImageUtils { return texture; } + public static Texture getTextureNoColorCorrection(final BufferedImage image) { + final int[] pixels = new int[image.getWidth() * image.getHeight()]; + image.getRGB(0, 0, image.getWidth(), image.getHeight(), pixels, 0, image.getWidth()); + + // 4 + // for + // RGBA, + // 3 + // for + // RGB + + final Pixmap pixmap = new Pixmap(image.getWidth(), image.getHeight(), Format.RGBA8888); + for (int y = 0; y < image.getHeight(); y++) { + for (int x = 0; x < image.getWidth(); x++) { + final int pixel = pixels[(y * image.getWidth()) + x]; + pixmap.drawPixel(x, y, (pixel << 8) | (pixel >>> 24)); + } + } + final Texture texture = new Texture(pixmap); + return texture; + } + public static Buffer getTextureBuffer(final BufferedImage image) { final int imageWidth = image.getWidth(); diff --git a/core/src/com/etheller/warsmash/viewer5/Node.java b/core/src/com/etheller/warsmash/viewer5/Node.java index dd42089..c0014a1 100644 --- a/core/src/com/etheller/warsmash/viewer5/Node.java +++ b/core/src/com/etheller/warsmash/viewer5/Node.java @@ -54,6 +54,12 @@ public abstract class Node extends GenericNode { return this; } + public Node setLocation(final Vector3 location) { + this.localLocation.set(location); + this.dirty = true; + return this; + } + public Node setRotation(final float[] rotation) { this.localRotation.set(rotation[0], rotation[1], rotation[2], rotation[3]); this.dirty = true; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/blp/BlpGdxTexture.java b/core/src/com/etheller/warsmash/viewer5/handlers/blp/BlpGdxTexture.java new file mode 100644 index 0000000..8641e03 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/blp/BlpGdxTexture.java @@ -0,0 +1,39 @@ +package com.etheller.warsmash.viewer5.handlers.blp; + +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.InputStream; + +import javax.imageio.ImageIO; + +import com.etheller.warsmash.util.ImageUtils; +import com.etheller.warsmash.viewer5.GdxTextureResource; +import com.etheller.warsmash.viewer5.ModelViewer; +import com.etheller.warsmash.viewer5.PathSolver; +import com.etheller.warsmash.viewer5.handlers.ResourceHandler; + +public class BlpGdxTexture extends GdxTextureResource { + + public BlpGdxTexture(final ModelViewer viewer, final ResourceHandler handler, final String extension, + final PathSolver pathSolver, final String fetchUrl) { + super(viewer, handler, extension, pathSolver, fetchUrl); + } + + @Override + protected void lateLoad() { + + } + + @Override + protected void load(final InputStream src, final Object options) { + BufferedImage img; + try { + img = ImageIO.read(src); + setGdxTexture(ImageUtils.getTexture(img)); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/blp/BlpTexture.java b/core/src/com/etheller/warsmash/viewer5/handlers/blp/BlpTexture.java index 6660cce..dfd09c5 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/blp/BlpTexture.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/blp/BlpTexture.java @@ -35,4 +35,4 @@ public class BlpTexture extends RawOpenGLTextureResource { } } -} +} \ No newline at end of file diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/tga/ImageUtils.java b/core/src/com/etheller/warsmash/viewer5/handlers/tga/ImageUtils.java new file mode 100644 index 0000000..feebac6 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/tga/ImageUtils.java @@ -0,0 +1,179 @@ +package com.etheller.warsmash.viewer5.handlers.tga; + +import java.awt.AlphaComposite; +import java.awt.Composite; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.image.BufferedImage; + +public class ImageUtils { + + /** + * Takes an images as input and generates an array containing this image and all + * possible mipmaps + * + * @param input + * @return + */ + public static BufferedImage[] generateMipMaps(final BufferedImage input) { + int num = 0; + int curWidth = input.getWidth(); + int curHeight = input.getHeight(); + int pow; + do { + num++; + pow = (int) Math.pow(2.0D, num - 1); + } + while ((pow < curWidth) || (pow < curHeight)); + final BufferedImage[] result = new BufferedImage[num]; + result[0] = input; + for (int i = 1; i < num; i++) { + curWidth /= 2; + curHeight /= 2; + if (curHeight == 0) { + curHeight = 1; + } + if (curWidth == 0) { + curWidth = 1; + } + result[i] = ImageUtils.getScaledInstance(result[(i - 1)], curWidth, curHeight, + RenderingHints.VALUE_INTERPOLATION_BICUBIC, true); + } + return result; + } + + /** + * Scales an Image + * + * @param img + * @param targetWidth + * @param targetHeight + * @param hint Rendering Hint + * @param higherQuality + * @return + */ + public static BufferedImage getScaledInstance(final BufferedImage img, final int targetWidth, + final int targetHeight, final Object hint, final boolean higherQuality) { + final int type = img.getTransparency() == 1 ? 1 : 2; + BufferedImage ret = img; + int h; + int w; + if (higherQuality) { + w = img.getWidth(); + h = img.getHeight(); + } + else { + w = targetWidth; + h = targetHeight; + } + do { + if ((higherQuality) && (w > targetWidth)) { + w /= 2; + if (w < targetWidth) { + w = targetWidth; + } + } + if ((higherQuality) && (h > targetHeight)) { + h /= 2; + if (h < targetHeight) { + h = targetHeight; + } + } + + BufferedImage tmp; + if (img.getColorModel().hasAlpha() == false) { + tmp = new BufferedImage(w, h, type); + final Graphics2D g2 = tmp.createGraphics(); + g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint); + g2.drawImage(ret, 0, 0, w, h, null); + g2.dispose(); + } + else { + // Necessary because otherwise Bilinear resize would couse transparent pixel to + // change color + tmp = resizeWorkAround(ret, w, h, hint); + } + + ret = tmp; + } + while ((w != targetWidth) || (h != targetHeight)); + return ret; + } + + private static BufferedImage resizeWorkAround(final BufferedImage ret, final int w, final int h, + final Object hint) { + + final BufferedImage noAlpha = new BufferedImage(ret.getWidth(), ret.getHeight(), BufferedImage.TYPE_INT_ARGB); + + for (int x = 0; x < ret.getWidth(); x++) { + for (int y = 0; y < ret.getHeight(); y++) { + int color = ret.getRGB(x, y); + color = color | 0xff000000; + noAlpha.setRGB(x, y, color); + } + } + + final BufferedImage noAlphaSmall = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); + Graphics2D g2 = noAlphaSmall.createGraphics(); + g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint); + g2.drawImage(noAlpha, 0, 0, w, h, null); + g2.dispose(); + + final BufferedImage tmp = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); + g2 = tmp.createGraphics(); + g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint); + g2.drawImage(ret, 0, 0, w, h, null); + g2.dispose(); + + noAlphaSmall.getAlphaRaster().setRect(0, 0, tmp.getAlphaRaster()); + + return noAlphaSmall; + } + + /** + * An alternative way to convert an image to type_byte_indexed (paletted) that + * avoids dithering. + * + * @param src + * @return + */ + public static BufferedImage antiDitherConvert(final BufferedImage src) { + final BufferedImage convertedImage = new BufferedImage(src.getWidth(), src.getHeight(), + BufferedImage.TYPE_BYTE_INDEXED); + for (int x = 0; x < src.getWidth(); x++) { + for (int y = 0; y < src.getHeight(); y++) { + convertedImage.setRGB(x, y, src.getRGB(x, y)); + } + } + return convertedImage; + } + + public static BufferedImage changeImageType(final BufferedImage src, final int type) { + final BufferedImage img = new BufferedImage(src.getWidth(), src.getHeight(), type); + final Graphics2D g = (Graphics2D) img.getGraphics(); + + if (img.getColorModel().hasAlpha()) { + final Composite comp = AlphaComposite.getInstance(AlphaComposite.SRC); + g.setComposite(comp); + } + + g.drawImage(src, 0, 0, null); + g.dispose(); + + return img; + } + + public static BufferedImage convertStandardImageType(final BufferedImage src, final boolean useAlpha) { + + if (useAlpha && (src.getType() == BufferedImage.TYPE_INT_ARGB)) { + return src; + } + + if ((useAlpha == false) && (src.getType() == BufferedImage.TYPE_INT_RGB)) { + return src; + } + + return ImageUtils.changeImageType(src, useAlpha ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB); + + } +} \ No newline at end of file diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/tga/TgaFile.java b/core/src/com/etheller/warsmash/viewer5/handlers/tga/TgaFile.java new file mode 100644 index 0000000..f7a766b --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/tga/TgaFile.java @@ -0,0 +1,217 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package com.etheller.warsmash.viewer5.handlers.tga; + +import java.awt.image.BufferedImage; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferByte; +import java.awt.image.DataBufferInt; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.RandomAccessFile; + +/** + * + * @author Riven, modified by Oger-Lord + */ +public class TgaFile { + + /** + * Read a TGA image from a file + * + * @param file + * @return + * @throws FileNotFoundException + * @throws IOException + */ + public static BufferedImage readTGA(final File file) throws FileNotFoundException, IOException { + return readTGA(file.getName(), new FileInputStream(file)); + + } + + /** + * Read a TGA image from an input stream. + * + * @param name + * @param stream + * @return + * @throws IOException + */ + public static BufferedImage readTGA(final String name, final InputStream stream) throws IOException { + + // Read Header + final byte[] header = new byte[18]; + stream.read(header); + + // Read pixel data + final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + int nRead; + byte[] data = new byte[16384]; + + while ((nRead = stream.read(data, 0, data.length)) != -1) { + buffer.write(data, 0, nRead); + } + buffer.flush(); + data = buffer.toByteArray(); + + // Verify Header + if ((header[0] | header[1]) != 0) { + throw new IllegalStateException("Error " + name); + } + if (header[2] != 2) { + throw new IllegalStateException("Error " + name); + } + int w = 0, h = 0; + w |= (header[12] & 0xFF) << 0; + w |= (header[13] & 0xFF) << 8; + h |= (header[14] & 0xFF) << 0; + h |= (header[15] & 0xFF) << 8; + + boolean alpha; + + if ((header[16] == 24)) { + alpha = false; + } + else if (header[16] == 32) { + alpha = true; + } + else { + throw new IllegalStateException("Error " + name + " invalid pixel depth: " + header[16]); + } + + if ((header[17] & 15) != (alpha ? 8 : 0)) { + throw new IllegalStateException("Error " + name); + } + + final BufferedImage dst = new BufferedImage(w, h, + alpha ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB); + final int[] pixels = ((DataBufferInt) dst.getRaster().getDataBuffer()).getData(); + if (pixels.length != (w * h)) { + throw new IllegalStateException("Error " + name); + } + if (data.length < (pixels.length * (alpha ? 4 : 3))) { + throw new IllegalStateException("Error " + name + " not enaugh pixel data"); + } + + if (alpha) { + for (int i = 0, p = (pixels.length - 1) * 4; i < pixels.length; i++, p -= 4) { + pixels[i] |= ((data[p + 0]) & 0xFF) << 0; + pixels[i] |= ((data[p + 1]) & 0xFF) << 8; + pixels[i] |= ((data[p + 2]) & 0xFF) << 16; + pixels[i] |= ((data[p + 3]) & 0xFF) << 24; + } + } + else { + for (int i = 0, p = (pixels.length - 1) * 3; i < pixels.length; i++, p -= 3) { + pixels[i] |= ((data[p + 0]) & 0xFF) << 0; + pixels[i] |= ((data[p + 1]) & 0xFF) << 8; + pixels[i] |= ((data[p + 2]) & 0xFF) << 16; + } + } + + if ((header[17] >> 4) == 1) { + // ok + } + else if ((header[17] >> 4) == 0) { + // flip horizontally + + for (int y = 0; y < h; y++) { + final int w2 = w / 2; + for (int x = 0; x < w2; x++) { + final int a = (y * w) + x; + final int b = (y * w) + (w - 1 - x); + final int t = pixels[a]; + pixels[a] = pixels[b]; + pixels[b] = t; + } + } + } + else { + throw new UnsupportedOperationException("Error " + name); + } + + return dst; + } + + /** + * Write a BufferedImage to a TGA file BufferedImages should be TYPE_INT_ARGB or + * TYPE_INT_RGB + * + * @param src + * @param file + * @throws IOException + */ + public static void writeTGA(BufferedImage src, final File file) throws IOException { + + final boolean alpha = src.getColorModel().hasAlpha(); + src = ImageUtils.convertStandardImageType(src, alpha); + + final DataBuffer buffer = src.getRaster().getDataBuffer(); + byte[] data; + + if (buffer instanceof DataBufferByte) { + + // Not used anymore because convert to standard image type => Buffer is int + final byte[] pixels = ((DataBufferByte) src.getRaster().getDataBuffer()).getData(); + if (pixels.length != (src.getWidth() * src.getHeight() * (alpha ? 4 : 3))) { + throw new IllegalStateException(); + } + + data = pixels; + + } + else if (buffer instanceof DataBufferInt) { + + final int[] pixels = ((DataBufferInt) src.getRaster().getDataBuffer()).getData(); + if (pixels.length != (src.getWidth() * src.getHeight())) { + throw new IllegalStateException(); + } + + data = new byte[pixels.length * (alpha ? 4 : 3)]; + + if (alpha) { + + for (int p = 0; p < pixels.length; p++) { + final int i = p * 4; + data[i + 0] = (byte) ((pixels[p] >> 0) & 0xFF); + data[i + 1] = (byte) ((pixels[p] >> 8) & 0xFF); + data[i + 2] = (byte) ((pixels[p] >> 16) & 0xFF); + data[i + 3] = (byte) ((pixels[p] >> 24) & 0xFF); + } + } + else { + + for (int p = 0; p < pixels.length; p++) { + final int i = p * 3; + data[i + 0] = (byte) ((pixels[p] >> 0) & 0xFF); + data[i + 1] = (byte) ((pixels[p] >> 8) & 0xFF); + data[i + 2] = (byte) ((pixels[p] >> 16) & 0xFF); + } + } + } + else { + throw new UnsupportedOperationException(); + } + + final byte[] header = new byte[18]; + header[2] = 2; // uncompressed, true-color image + header[12] = (byte) ((src.getWidth() >> 0) & 0xFF); + header[13] = (byte) ((src.getWidth() >> 8) & 0xFF); + header[14] = (byte) ((src.getHeight() >> 0) & 0xFF); + header[15] = (byte) ((src.getHeight() >> 8) & 0xFF); + header[16] = (byte) (alpha ? 32 : 24); // bits per pixel + header[17] = (byte) ((alpha ? 8 : 0) | (2 << 4)); + + final RandomAccessFile raf = new RandomAccessFile(file, "rw"); + raf.write(header); + raf.write(data); + raf.setLength(raf.getFilePointer()); // trim + raf.close(); + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/StandSequence.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/StandSequence.java index e6033d4..9ad31fc 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/StandSequence.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/StandSequence.java @@ -48,7 +48,7 @@ public class StandSequence { final int sequencesLeft = filtered.size() - i; final int random = (int) (i + Math.floor(Math.random() * sequencesLeft)); if (sequencesLeft <= 0) { - return new IndexedSequence(null, 0); + return null; // new IndexedSequence(null, 0); } final IndexedSequence sequence = filtered.get(random); @@ -63,5 +63,47 @@ public class StandSequence { if (sequence != null) { target.setSequence(sequence.index); } + else { + target.setSequence(0); + } + } + + public static void randomWalkSequence(final MdxComplexInstance target) { + final MdxModel model = (MdxModel) target.model; + final List sequences = model.getSequences(); + final IndexedSequence sequence = selectSequence("walk", sequences); + + if (sequence != null) { + target.setSequence(sequence.index); + } + else { + randomStandSequence(target); + } + } + + public static void randomPortraitSequence(final MdxComplexInstance target) { + final MdxModel model = (MdxModel) target.model; + final List sequences = model.getSequences(); + final IndexedSequence sequence = selectSequence("portrait", sequences); + + if (sequence != null) { + target.setSequence(sequence.index); + } + else { + randomStandSequence(target); + } + } + + public static void randomPortraitTalkSequence(final MdxComplexInstance target) { + final MdxModel model = (MdxModel) target.model; + final List sequences = model.getSequences(); + final IndexedSequence sequence = selectSequence("portrait talk", sequences); + + if (sequence != null) { + target.setSequence(sequence.index); + } + else { + randomPortraitSequence(target); + } } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Unit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Unit.java index dbb845e..f6350f9 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Unit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Unit.java @@ -22,12 +22,21 @@ public class Unit { private static final float[] heapZ = new float[3]; public final MdxComplexInstance instance; public final MutableGameObject row; + public final float[] location = new float[3]; + public float radius; + public UnitSoundset soundset; + public final MdxModel portraitModel; + public int playerIndex; public Unit(final War3MapViewer map, final MdxModel model, final MutableGameObject row, - final com.etheller.warsmash.parsers.w3x.unitsdoo.Unit unit, final WorldEditorDataType type) { + final com.etheller.warsmash.parsers.w3x.unitsdoo.Unit unit, final WorldEditorDataType type, + final UnitSoundset soundset, final MdxModel portraitModel) { + this.portraitModel = portraitModel; final MdxComplexInstance instance = (MdxComplexInstance) model.addInstance(); - instance.move(unit.getLocation()); + final float[] location = unit.getLocation(); + System.arraycopy(location, 0, this.location, 0, 3); + instance.move(location); float angle; if ((row != null) && row.getFieldAsBoolean(IS_BLDG, 0)) { angle = (float) Math.toRadians(270.0f); @@ -38,11 +47,13 @@ public class Unit { // instance.localRotation.setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, angle); instance.rotate(new Quaternion().setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, angle)); instance.scale(unit.getScale()); - instance.setTeamColor(unit.getPlayer()); + this.playerIndex = unit.getPlayer(); + instance.setTeamColor(this.playerIndex); instance.setScene(map.worldScene); if (row != null) { heapZ[2] = row.getFieldAsFloat(MOVE_HEIGHT, 0); + this.location[2] += heapZ[2]; instance.move(heapZ); War3ID red; @@ -65,9 +76,11 @@ public class Unit { (row.getFieldAsInteger(green, 0)) / 255f, (row.getFieldAsInteger(blue, 0)) / 255f }); instance.uniformScale(row.getFieldAsFloat(scale, 0)); + this.radius = row.getFieldAsFloat(War3MapViewer.UNIT_SELECT_SCALE, 0) * 36; } this.instance = instance; this.row = row; + this.soundset = soundset; } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitSoundset.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitSoundset.java new file mode 100644 index 0000000..f2cacce --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitSoundset.java @@ -0,0 +1,107 @@ +package com.etheller.warsmash.viewer5.handlers.w3x; + +import java.util.ArrayList; +import java.util.List; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.audio.Sound; +import com.etheller.warsmash.datasources.DataSource; +import com.etheller.warsmash.units.DataTable; +import com.etheller.warsmash.units.Element; +import com.etheller.warsmash.util.DataSourceFileHandle; +import com.etheller.warsmash.viewer5.AudioBufferSource; +import com.etheller.warsmash.viewer5.AudioContext; +import com.etheller.warsmash.viewer5.AudioPanner; + +public class UnitSoundset { + public final UnitAckSound what; + public final UnitAckSound pissed; + public final UnitAckSound yesAttack; + public final UnitAckSound yes; + public final UnitAckSound ready; + public final UnitAckSound warcry; + + public UnitSoundset(final DataSource dataSource, final DataTable unitAckSounds, final String soundName) { + this.what = UnitAckSound.create(dataSource, unitAckSounds, soundName, "What"); + this.pissed = UnitAckSound.create(dataSource, unitAckSounds, soundName, "Pissed"); + this.yesAttack = UnitAckSound.create(dataSource, unitAckSounds, soundName, "YesAttack"); + this.yes = UnitAckSound.create(dataSource, unitAckSounds, soundName, "Yes"); + this.ready = UnitAckSound.create(dataSource, unitAckSounds, soundName, "Ready"); + this.warcry = UnitAckSound.create(dataSource, unitAckSounds, soundName, "Warcry"); + } + + public static final class UnitAckSound { + private static final UnitAckSound SILENT = new UnitAckSound(0, 0, 0, 0, 0, 0); + + private final List sounds = new ArrayList<>(); + private final float volume; + private final float pitch; + private final float pitchVariation; + private final float minDistance; + private final float maxDistance; + private final float distanceCutoff; + + public static UnitAckSound create(final DataSource dataSource, final DataTable unitAckSounds, + final String soundName, final String soundType) { + final Element row = unitAckSounds.get(soundName + soundType); + if (row == null) { + return SILENT; + } + final String fileNames = row.getField("FileNames"); + String directoryBase = row.getField("DirectoryBase"); + if ((directoryBase.length() > 1) && !directoryBase.endsWith("\\")) { + directoryBase += "\\"; + } + final float volume = row.getFieldFloatValue("Volume"); + final float pitch = row.getFieldFloatValue("Pitch"); + final float pitchVariation = row.getFieldFloatValue("PitchVariance"); + final float minDistance = row.getFieldFloatValue("MinDistance"); + final float maxDistance = row.getFieldFloatValue("MaxDistance"); + final float distanceCutoff = row.getFieldFloatValue("DistanceCutoff"); + final UnitAckSound sound = new UnitAckSound(volume, pitch, pitchVariation, minDistance, maxDistance, + distanceCutoff); + for (final String fileName : fileNames.split(",")) { + String filePath = directoryBase + fileName; + if (!filePath.toLowerCase().endsWith(".wav")) { + filePath += ".wav"; + } + if (dataSource.has(filePath)) { + sound.sounds.add(Gdx.audio.newSound(new DataSourceFileHandle(dataSource, filePath))); + } + } + return sound; + } + + public UnitAckSound(final float volume, final float pitch, final float pitchVariation, final float minDistance, + final float maxDistance, final float distanceCutoff) { + this.volume = volume; + this.pitch = pitch; + this.pitchVariation = pitchVariation; + this.minDistance = minDistance; + this.maxDistance = maxDistance; + this.distanceCutoff = distanceCutoff; + } + + public void play(final AudioContext audioContext, final float x, final float y) { + if (this.sounds.isEmpty()) { + return; + } + + final AudioPanner panner = audioContext.createPanner(); + final AudioBufferSource source = audioContext.createBufferSource(); + + // Panner settings + panner.setPosition(x, y, 0); + panner.maxDistance = this.distanceCutoff; + panner.refDistance = this.minDistance; + panner.connect(audioContext.destination); + + // Source. + source.buffer = this.sounds.get((int) (Math.random() * this.sounds.size())); + source.connect(panner); + + // Make a sound. + source.start(0); + } + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index c23e32a..5baa819 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -8,11 +8,18 @@ import java.io.UnsupportedEncodingException; import java.nio.channels.SeekableByteChannel; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.apache.commons.compress.utils.IOUtils; import org.apache.commons.compress.utils.SeekableInMemoryByteChannel; +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.math.Vector3; +import com.badlogic.gdx.math.collision.Ray; import com.etheller.warsmash.common.FetchDataTypeName; import com.etheller.warsmash.common.LoadGenericCallback; import com.etheller.warsmash.datasources.CompoundDataSource; @@ -25,6 +32,7 @@ import com.etheller.warsmash.parsers.w3x.objectdata.Warcraft3MapObjectData; import com.etheller.warsmash.parsers.w3x.unitsdoo.War3MapUnitsDoo; import com.etheller.warsmash.parsers.w3x.w3e.War3MapW3e; import com.etheller.warsmash.parsers.w3x.w3i.War3MapW3i; +import com.etheller.warsmash.units.DataTable; import com.etheller.warsmash.units.Element; import com.etheller.warsmash.units.manager.MutableObjectData; import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; @@ -39,6 +47,7 @@ import com.etheller.warsmash.viewer5.ModelInstance; import com.etheller.warsmash.viewer5.ModelViewer; import com.etheller.warsmash.viewer5.PathSolver; import com.etheller.warsmash.viewer5.Scene; +import com.etheller.warsmash.viewer5.Texture; import com.etheller.warsmash.viewer5.gl.WebGL; import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxHandler; @@ -58,9 +67,16 @@ public class War3MapViewer extends ModelViewer { private static final War3ID UNIT_SHADOW_W = War3ID.fromString("ushw"); private static final War3ID UNIT_SHADOW_H = War3ID.fromString("ushh"); private static final War3ID BUILDING_SHADOW = War3ID.fromString("ushb"); + public static final War3ID UNIT_SELECT_SCALE = War3ID.fromString("ussc"); + private static final War3ID UNIT_SELECT_HEIGHT = War3ID.fromString("uslz"); + private static final War3ID UNIT_SOUNDSET = War3ID.fromString("usnd"); private static final War3ID ITEM_FILE = War3ID.fromString("ifil"); private static final War3ID sloc = War3ID.fromString("sloc"); private static final LoadGenericCallback stringDataCallback = new StringDataCallbackImplementation(); + private static final float[] rayHeap = new float[6]; + private static final Vector2 mousePosHeap = new Vector2(); + private static final Vector3 normalHeap = new Vector3(); + private static final Vector3 intersectionHeap = new Vector3(); public static final StreamDataCallbackImplementation streamDataCallback = new StreamDataCallbackImplementation(); public PathSolver wc3PathSolver = PathSolver.DEFAULT; @@ -94,6 +110,11 @@ public class War3MapViewer extends ModelViewer { public int renderPathing = 0; public int renderLighting = 0; + public List selModels = new ArrayList<>(); + public List selected = new ArrayList<>(); + private DataTable unitAckSoundsTable; + private MdxComplexInstance confirmationInstance; + public War3MapViewer(final DataSource dataSource, final CanvasProvider canvas) { super(dataSource, canvas); this.gameDataSource = dataSource; @@ -105,10 +126,9 @@ public class War3MapViewer extends ModelViewer { this.wc3PathSolver = PathSolver.DEFAULT; this.worldScene = this.addScene(); - } - public void loadSLKs() { + public void loadSLKs(final WorldEditStrings worldEditStrings) throws IOException { final GenericResource terrain = this.loadMapGeneric("TerrainArt\\Terrain.slk", FetchDataTypeName.SLK, stringDataCallback); final GenericResource cliffTypes = this.loadMapGeneric("TerrainArt\\CliffTypes.slk", FetchDataTypeName.SLK, @@ -157,6 +177,11 @@ public class War3MapViewer extends ModelViewer { this.unitMetaData.load(unitMetaData.data.toString()); // emit loaded + this.unitAckSoundsTable = new DataTable(worldEditStrings); + try (InputStream terrainSlkStream = this.dataSource.getResourceAsStream("UI\\SoundInfo\\UnitAckSounds.slk")) { + this.unitAckSoundsTable.readSLK(terrainSlkStream); + } + } public GenericResource loadMapGeneric(final String path, final FetchDataTypeName dataType, @@ -170,13 +195,10 @@ public class War3MapViewer extends ModelViewer { } public void loadMap(final String mapFilePath) throws IOException { - loadSLKs(); final War3Map war3Map = new War3Map(this.gameDataSource, mapFilePath); this.mapMpq = war3Map; -// loadSLKs(); - final PathSolver wc3PathSolver = this.wc3PathSolver; char tileset = 'A'; @@ -215,13 +237,14 @@ public class War3MapViewer extends ModelViewer { throw new RuntimeException(e); } setDataSource(tilesetSource); + final WorldEditStrings worldEditStrings = new WorldEditStrings(this.dataSource); + loadSLKs(worldEditStrings); this.solverParams.tileset = Character.toLowerCase(tileset); final War3MapW3e terrainData = this.mapMpq.readEnvironment(); - this.terrain = new Terrain(terrainData, w3iFile, this.webGL, this.dataSource, - new WorldEditStrings(this.dataSource), this); + this.terrain = new Terrain(terrainData, w3iFile, this.webGL, this.dataSource, worldEditStrings, this); final float[] centerOffset = terrainData.getCenterOffset(); final int[] mapSize = terrainData.getMapSize(); @@ -234,6 +257,13 @@ public class War3MapViewer extends ModelViewer { this.worldScene.grid = new Grid(centerOffset[0], centerOffset[1], (mapSize[0] * 128) - 128, (mapSize[1] * 128) - 128, 16 * 128, 16 * 128); + final MdxModel confirmation = (MdxModel) load("UI\\Feedback\\Confirmation\\Confirmation.mdx", + PathSolver.DEFAULT, null); + this.confirmationInstance = (MdxComplexInstance) confirmation.addInstance(); + this.confirmationInstance.setSequenceLoopMode(0); + this.confirmationInstance.setSequence(0); + this.confirmationInstance.setScene(this.worldScene); + if (this.terrainCliffsAndWaterLoaded) { this.loadTerrainCliffsAndWater(terrainData); } @@ -349,7 +379,10 @@ public class War3MapViewer extends ModelViewer { final War3MapUnitsDoo dooFile = mpq.readUnits(); + final Map soundsetNameToSoundset = new HashMap<>(); + // Collect the units and items data. + UnitSoundset soundset = null; for (final com.etheller.warsmash.parsers.w3x.unitsdoo.Unit unit : dooFile.getUnits()) { MutableGameObject row = null; String path = null; @@ -357,7 +390,7 @@ public class War3MapViewer extends ModelViewer { // Hardcoded? WorldEditorDataType type = null; if (sloc.equals(unit.getId())) { - path = "Objects\\StartLocation\\StartLocation.mdx"; +// path = "Objects\\StartLocation\\StartLocation.mdx"; type = null; /// ?????? } else { @@ -427,13 +460,28 @@ public class War3MapViewer extends ModelViewer { if ((buildingShadow != null) && !"_".equals(buildingShadow)) { this.terrain.addShadow(buildingShadow, unit.getLocation()[0], unit.getLocation()[1]); } + + final String soundName = row.getFieldAsString(UNIT_SOUNDSET, 0); + UnitSoundset unitSoundset = soundsetNameToSoundset.get(soundName); + if (unitSoundset == null) { + unitSoundset = new UnitSoundset(this.dataSource, this.unitAckSoundsTable, soundName); + soundsetNameToSoundset.put(soundName, unitSoundset); + } + soundset = unitSoundset; } } if (path != null) { final MdxModel model = (MdxModel) this.load(path, this.mapPathSolver, this.solverParams); - - this.units.add(new Unit(this, model, row, unit, type)); + MdxModel portraitModel; + final String portraitPath = path.substring(0, path.length() - 4) + "_portrait.mdx"; + if (this.dataSource.has(portraitPath)) { + portraitModel = (MdxModel) this.load(portraitPath, this.mapPathSolver, this.solverParams); + } + else { + portraitModel = model; + } + this.units.add(new Unit(this, model, row, unit, type, soundset, portraitModel)); } else { System.err.println("Unknown unit ID: " + unit.getId()); @@ -456,13 +504,16 @@ public class War3MapViewer extends ModelViewer { final List instances = this.worldScene.instances; for (final ModelInstance instance : instances) { - if (instance instanceof MdxComplexInstance) { + if ((instance instanceof MdxComplexInstance) && (instance != this.confirmationInstance)) { final MdxComplexInstance mdxComplexInstance = (MdxComplexInstance) instance; if (mdxComplexInstance.sequenceEnded || (mdxComplexInstance.sequence == -1)) { StandSequence.randomStandSequence(mdxComplexInstance); } } } + if (this.confirmationInstance.sequenceEnded) { + this.confirmationInstance.hide(); + } } } @@ -491,6 +542,147 @@ public class War3MapViewer extends ModelViewer { } } + public void deselect() { + if (!this.selModels.isEmpty()) { + for (final SplatModel model : this.selModels) { + this.terrain.removeSplatBatchModel(model); + } + this.selModels.clear(); + } + this.selected.clear(); + } + + public void doSelectUnit(final List units) { + deselect(); + if (units.isEmpty()) { + return; + } + + final Map splats = new HashMap(); + for (final Unit unit : units) { + if (unit.row != null) { + if (unit.radius > 0) { + final float radius = unit.radius; + String path; + // TODO these radius values must be read from UI\MiscData.txt instead + if (radius < 100) { + path = "ReplaceableTextures\\Selection\\SelectionCircleSmall.blp"; + } + else if (radius < 300) { + path = "ReplaceableTextures\\Selection\\SelectionCircleMed.blp"; + } + else { + path = "ReplaceableTextures\\Selection\\SelectionCircleLarge.blp"; + } + if (!splats.containsKey(path)) { + splats.put(path, new Splat()); + } + final float x = unit.location[0]; + final float y = unit.location[1]; + final float z = unit.row.getFieldAsFloat(UNIT_SELECT_HEIGHT, 0); + splats.get(path).locations + .add(new float[] { x - radius, y - radius, x + radius, y + radius, z + 5 }); + } + } + } + this.selModels.clear(); + for (final Map.Entry entry : splats.entrySet()) { + final String path = entry.getKey(); + final Splat locations = entry.getValue(); + final SplatModel model = new SplatModel(Gdx.gl30, (Texture) load(path, PathSolver.DEFAULT, null), + locations.locations, this.terrain.centerOffset); + model.color[0] = 0; + model.color[1] = 1; + model.color[2] = 0; + model.color[3] = 1; + this.selModels.add(model); + this.terrain.addSplatBatchModel(model); + } + } + + public void getClickLocation(final Vector3 out, final int screenX, final int screenY) { + final float[] ray = rayHeap; + mousePosHeap.set(screenX, screenY); + this.worldScene.camera.screenToWorldRay(ray, mousePosHeap); + final Ray gdxRay = new Ray(new Vector3(ray[0], ray[1], ray[2]), + new Vector3(ray[3] - ray[0], ray[4] - ray[1], ray[5] - ray[2])); + Terrain.intersectRayTriangles(gdxRay, this.terrain.softwareGroundMesh.vertices, + this.terrain.softwareGroundMesh.indices, 3, out); + } + + public void showConfirmation(final Vector3 position, final float red, final float green, final float blue) { + this.confirmationInstance.show(); + this.confirmationInstance.setSequence(0); + this.confirmationInstance.setLocation(position); + this.confirmationInstance.vertexColor[0] = red; + this.confirmationInstance.vertexColor[1] = green; + this.confirmationInstance.vertexColor[2] = blue; + } + + public List selectUnit(final float x, final float y, final boolean toggle) { + final float[] ray = rayHeap; + mousePosHeap.set(x, y); + this.worldScene.camera.screenToWorldRay(ray, mousePosHeap); + final Vector3 dir = normalHeap; + dir.x = ray[3] - ray[0]; + dir.y = ray[4] - ray[1]; + dir.z = ray[5] - ray[2]; + dir.nor(); + // TODO good performance, do not create vectors on every check + final Vector3 eMid = new Vector3(); + final Vector3 eSize = new Vector3(); + final Vector3 rDir = new Vector3(); + + Unit entity = null; + float entDist = 1e6f; + + for (final Unit unit : this.units) { + final float radius = unit.radius; + final float[] location = unit.location; + final MdxComplexInstance instance = unit.instance; + eMid.set(0, 0, radius / 2); + eSize.set(radius, radius, radius); + + eMid.add(location[0], location[1], location[2]); + eMid.sub(ray[0], ray[1], ray[2]); + eMid.scl(1 / eSize.x, 1 / eSize.y, 1 / eSize.z); + rDir.x = dir.x / eSize.x; + rDir.y = dir.y / eSize.y; + rDir.z = dir.z / eSize.z; + final float dlen = rDir.len2(); + final float dp = Math.max(0, rDir.dot(eMid)) / dlen; + if (dp > entDist) { + continue; + } + rDir.scl(dp); + if (rDir.dst2(eMid) < 1.0) { + entity = unit; + entDist = dp; + } + } + List sel; + if (entity != null) { + if (toggle) { + sel = new ArrayList<>(this.selected); + final int idx = sel.indexOf(entity); + if (idx >= 0) { + sel.remove(idx); + } + else { + sel.add(entity); + } + } + else { + sel = Arrays.asList(entity); + } + } + else { + sel = Collections.emptyList(); + } + this.doSelectUnit(sel); + return sel; + } + private static final class MappedDataCallbackImplementation implements LoadGenericCallback { @Override public Object call(final InputStream data) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java index 0321f96..a579617 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java @@ -23,6 +23,7 @@ import com.badlogic.gdx.math.Intersector; import com.badlogic.gdx.math.Matrix4; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Vector3; +import com.badlogic.gdx.math.collision.Ray; import com.etheller.warsmash.datasources.DataSource; import com.etheller.warsmash.parsers.w3x.w3e.Corner; import com.etheller.warsmash.parsers.w3x.w3e.War3MapW3e; @@ -55,6 +56,7 @@ public class Terrain { public ShaderProgram groundShader; public ShaderProgram waterShader; public ShaderProgram cliffShader; + public ShaderProgram testShader; public float waterIndex; public float waterIncreasePerFrame; public float waterHeightOffset; @@ -109,6 +111,9 @@ public class Terrain { public final Map shadowTextures = new HashMap<>(); private final int[] mapBounds; private final int[] mapSize; + public final SoftwareGroundMesh softwareGroundMesh; + private final int testArrayBuffer; + private final int testElementBuffer; public Terrain(final War3MapW3e w3eFile, final War3MapW3i w3iFile, final WebGL webGL, final DataSource dataSource, final WorldEditStrings worldEditStrings, final War3MapViewer viewer) throws IOException { @@ -341,6 +346,7 @@ public class Terrain { this.groundShader = webGL.createShaderProgram(TerrainShaders.Terrain.vert, TerrainShaders.Terrain.frag); this.cliffShader = webGL.createShaderProgram(TerrainShaders.Cliffs.vert, TerrainShaders.Cliffs.frag); this.waterShader = webGL.createShaderProgram(TerrainShaders.Water.vert, TerrainShaders.Water.frag); + this.testShader = webGL.createShaderProgram(TerrainShaders.Test.vert, TerrainShaders.Test.frag); this.uberSplatShader = webGL.createShaderProgram(W3xShaders.UberSplat.vert, W3xShaders.UberSplat.frag); @@ -350,6 +356,18 @@ public class Terrain { this.uberSplatModels = new ArrayList<>(); this.mapBounds = w3iFile.getCameraBoundsComplements(); this.mapSize = w3eFile.getMapSize(); + this.softwareGroundMesh = new SoftwareGroundMesh(this.groundHeights, this.groundCornerHeights, + this.centerOffset, width, height); + + this.testArrayBuffer = gl.glGenBuffer(); + gl.glBindBuffer(GL30.GL_ARRAY_BUFFER, this.testArrayBuffer); + gl.glBufferData(GL30.GL_ARRAY_BUFFER, this.softwareGroundMesh.vertices.length, + RenderMathUtils.wrap(this.softwareGroundMesh.vertices), GL30.GL_STATIC_DRAW); + + this.testElementBuffer = gl.glGenBuffer(); +// gl.glBindBuffer(GL30.GL_ELEMENT_ARRAY_BUFFER, this.testElementBuffer); +// gl.glBufferData(GL30.GL_ELEMENT_ARRAY_BUFFER, this.softwareGroundMesh.indices.length, +// RenderMathUtils.wrap(this.softwareGroundMesh.indices), GL30.GL_STATIC_DRAW); } private void updateGroundHeights(final Rectangle area) { @@ -430,6 +448,12 @@ public class Terrain { for (int i = (int) rampArea.getX(); i < xLimit; i++) { final int yLimit = (int) ((rampArea.getY() + rampArea.getHeight()) - 1); for (int j = (int) rampArea.getY(); j < yLimit; j++) { + if ((i == (84)) && (j == (82))) { + System.out.println("test"); + } + if ((i == (84)) && (j == (81))) { + System.out.println("test"); + } final RenderCorner bottomLeft = this.corners[i][j]; final RenderCorner bottomRight = this.corners[i + 1][j]; final RenderCorner topLeft = this.corners[i][j + 1]; @@ -452,11 +476,12 @@ public class Terrain { && !(facingLeft && (i == 0)) && !(!facingLeft && (i >= (this.columns - 2)))) { final boolean br = ((bottomLeft.getRamp() != 0) != (bottomRight.getRamp() != 0)) && ((topLeft.getRamp() != 0) != (topRight.getRamp() != 0)) - && !this.corners[i + bottomRight.getRamp()][j + (facingDown ? -1 : 1)].cliff; + && !this.corners[i + (bottomRight.getRamp() != 0 ? 1 : 0)][j + + (facingDown ? -1 : 1)].cliff; final boolean bo = ((bottomLeft.getRamp() != 0) != (topLeft.getRamp() != 0)) && ((bottomRight.getRamp() != 0) != (topRight.getRamp() != 0)) - && !this.corners[i + (facingLeft ? -1 : 1)][j + topLeft.getRamp()].cliff; + && !this.corners[i + (facingLeft ? -1 : 1)][j + (topLeft.getRamp() != 0 ? 1 : 0)].cliff; if (br || bo) { String fileName = "" + (char) ((bottomLeft.getRamp() != 0 ? 'L' : 'A') @@ -694,7 +719,7 @@ public class Terrain { public void renderGround() { // Render tiles -// this.groundShader.begin(); + this.webGL.useShaderProgram(this.groundShader); final GL30 gl = Gdx.gl30; @@ -742,6 +767,26 @@ public class Terrain { } + private GL30 renderGroundIntersectionMesh() { + if (true) { + throw new UnsupportedOperationException("No longer supported"); + } + this.webGL.useShaderProgram(this.testShader); + + final GL30 gl = Gdx.gl30; + gl.glDisable(GL30.GL_CULL_FACE); + gl.glDisable(GL30.GL_BLEND); + this.testShader.setUniformMatrix("MVP", this.camera.viewProjectionMatrix); + gl.glBindBuffer(GL30.GL_ARRAY_BUFFER, this.testArrayBuffer); + this.testShader.setVertexAttribute("vPosition", 3, GL30.GL_FLOAT, false, 12, 0); + + gl.glBindBuffer(GL30.GL_ELEMENT_ARRAY_BUFFER, this.testElementBuffer); + gl.glDrawElements(GL30.GL_LINES, this.softwareGroundMesh.indices.length, GL30.GL_UNSIGNED_SHORT, 0);// ); + + gl.glEnable(GL30.GL_BLEND); + return gl; + } + public void renderUberSplats() { final GL30 gl = Gdx.gl30; final WebGL webGL = this.webGL; @@ -988,6 +1033,64 @@ public class Terrain { // return out; // } + static Vector3 best = new Vector3(); + static Vector3 tmp = new Vector3(); + static Vector3 tmp1 = new Vector3(); + static Vector3 tmp2 = new Vector3(); + static Vector3 tmp3 = new Vector3(); + + /** + * Intersects the given ray with list of triangles. Returns the nearest + * intersection point in intersection + * + * @param ray The ray + * @param vertices the vertices + * @param indices the indices, each successive 3 shorts index the 3 + * vertices of a triangle + * @param vertexSize the size of a vertex in floats + * @param intersection The nearest intersection point (optional) + * @return Whether the ray and the triangles intersect. + */ + public static boolean intersectRayTriangles(final Ray ray, final float[] vertices, final int[] indices, + final int vertexSize, final Vector3 intersection) { + float min_dist = Float.MAX_VALUE; + boolean hit = false; + + if ((indices.length % 3) != 0) { + throw new RuntimeException("triangle list size is not a multiple of 3"); + } + + for (int i = 0; i < indices.length; i += 3) { + final int i1 = indices[i] * vertexSize; + final int i2 = indices[i + 1] * vertexSize; + final int i3 = indices[i + 2] * vertexSize; + + final boolean result = Intersector.intersectRayTriangle(ray, + tmp1.set(vertices[i1], vertices[i1 + 1], vertices[i1 + 2]), + tmp2.set(vertices[i2], vertices[i2 + 1], vertices[i2 + 2]), + tmp3.set(vertices[i3], vertices[i3 + 1], vertices[i3 + 2]), tmp); + + if (result == true) { + final float dist = ray.origin.dst2(tmp); + if (dist < min_dist) { + min_dist = dist; + best.set(tmp); + hit = true; + } + } + } + + if (hit == false) { + return false; + } + else { + if (intersection != null) { + intersection.set(best); + } + return true; + } + } + private static final class UnloadedTexture { private final int width; private final int height; @@ -1050,4 +1153,49 @@ public class Terrain { this.uberSplatModels.add(splatModel); } } + + public void removeSplatBatchModel(final SplatModel model) { + this.uberSplatModels.remove(model); + } + + public void addSplatBatchModel(final SplatModel model) { + this.uberSplatModels.add(model); + } + + public static final class SoftwareGroundMesh { + public final float[] vertices; + public final int[] indices; + + private SoftwareGroundMesh(final float[] groundHeights, final float[] groundCornerHeights, + final float[] centerOffset, final int columns, final int rows) { + this.vertices = new float[(columns - 1) * (rows - 1) * Shapes.INSTANCE.quadVertices.length * 3]; + this.indices = new int[(columns - 1) * (rows - 1) * Shapes.INSTANCE.quadIndices.length * 3]; + for (int y = 0; y < (rows - 1); y++) { + for (int x = 0; x < (columns - 1); x++) { + final int instanceId = (y * (columns - 1)) + x; + for (int vertexId = 0; vertexId < Shapes.INSTANCE.quadVertices.length; vertexId++) { + final float vPositionX = Shapes.INSTANCE.quadVertices[vertexId][0]; + final float vPositionY = Shapes.INSTANCE.quadVertices[vertexId][1]; + final int groundCornerHeightIndex = (int) (((vPositionY + y) * (columns)) + (vPositionX + x)); + final float height = groundCornerHeights[groundCornerHeightIndex]; + this.vertices[(instanceId * 4 * 3) + (vertexId * 3)] = ((vPositionX + x) * 128f) + + centerOffset[0]; + this.vertices[(instanceId * 4 * 3) + (vertexId * 3) + 1] = ((vPositionY + y) * 128f) + + centerOffset[1]; + this.vertices[(instanceId * 4 * 3) + (vertexId * 3) + 2] = height * 128f; + } + for (int triangle = 0; triangle < Shapes.INSTANCE.quadIndices.length; triangle++) { + for (int vertexId = 0; vertexId < Shapes.INSTANCE.quadIndices[triangle].length; vertexId++) { + final int vertexIndex = Shapes.INSTANCE.quadIndices[triangle][vertexId]; + final int indexValue = (vertexIndex + (instanceId * 4)); + if ((indexValue * 3) >= this.vertices.length) { + throw new IllegalStateException(); + } + this.indices[(instanceId * 2 * 3) + (triangle * 3) + vertexId] = indexValue; + } + } + } + } + } + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/TerrainShaders.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/TerrainShaders.java index 0134adb..73cffc5 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/TerrainShaders.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/TerrainShaders.java @@ -24,6 +24,7 @@ public class TerrainShaders { "layout (location = 0) out vec3 UV;\r\n" + // "layout (location = 1) out vec3 Normal;\r\n" + // "layout (location = 2) out vec2 pathing_map_uv;\r\n" + // + "layout (location = 3) out vec3 position;\r\n" + // "\r\n" + // "void main() {\r\n" + // " pathing_map_uv = (vec2(vPosition.x + 128, vPosition.y) / 128 + vOffset.xy) * 4;\r\n" + // @@ -32,10 +33,12 @@ public class TerrainShaders { " float value = texture(height_texture, (vOffset.xy + vec2(vPosition.x + 192, vPosition.y + 64) / 128.0) / vec2(size)).r;\r\n" + // "\r\n" + // - " vec4 myposition = vec4((vPosition + vec3(vOffset.xy + vec2(1, 0), vOffset.z + value) * 128 ), 1);\r\n" - + // + " position = (vPosition + vec3(vOffset.xy + vec2(1, 0), vOffset.z + value) * 128 );\r\n" + // + " vec4 myposition = vec4(position, 1);\r\n" + // " myposition.x += centerOffsetX;\r\n" + // " myposition.y += centerOffsetY;\r\n" + // + " position.x /= (size.x * 128.0);\r\n" + // + " position.y /= (size.x * 128.0);\r\n" + // " gl_Position = MVP * myposition;\r\n" + // " UV = vec3(vUV, vOffset.a);\r\n" + // "\r\n" + // @@ -81,6 +84,25 @@ public class TerrainShaders { " color = length(pathing_color.rgb) > 0 ? color * 0.75 + pathing_color * 0.5 : color;\r\n" + // " }\r\n" + // "}"; + + public static final String posFrag = "#version 450 core\r\n" + // + "\r\n" + // + "layout (binding = 0) uniform sampler2DArray cliff_textures;\r\n" + // + "layout (binding = 2) uniform usampler2D pathing_map_static;\r\n" + // + "\r\n" + // + "layout (location = 1) uniform bool show_pathing_map_static;\r\n" + // + "layout (location = 2) uniform bool show_lighting;\r\n" + // + "\r\n" + // + "layout (location = 0) in vec3 UV;\r\n" + // + "layout (location = 1) in vec3 Normal;\r\n" + // + "layout (location = 2) in vec2 pathing_map_uv;\r\n" + // + "layout (location = 3) in vec3 position;\r\n" + // + "\r\n" + // + "out vec4 color;\r\n" + // + "\r\n" + // + "void main() {\r\n" + /// + " color = vec4(position.xyz, 1.0);\r\n" + // + "}"; } public static final class Terrain { @@ -102,7 +124,7 @@ public class TerrainShaders { "layout (location = 1) out flat uvec4 texture_indices;\r\n" + // "layout (location = 2) out vec2 pathing_map_uv;\r\n" + // "layout (location = 3) out vec3 normal;\r\n" + // - "layout (location = 6) out float v2Position;\r\n" + // + "layout (location = 4) out vec3 position;\r\n" + // "\r\n" + // "void main() { \r\n" + // " ivec2 size = textureSize(terrain_texture_list, 0);\r\n" + // @@ -123,9 +145,12 @@ public class TerrainShaders { " pathing_map_uv = (vPosition + pos) * 4; \r\n" + // "\r\n" + // " // Cliff culling\r\n" + // - " gl_Position = ((texture_indices.a & 32768) == 0) ? MVP * vec4((vPosition.x + pos.x)*128.0 + centerOffsetX, (vPosition.y + pos.y)*128.0 + centerOffsetY, height.r*128.0, 1) : vec4(2.0, 0.0, 0.0, 1.0);\r\n" + " position = vec3((vPosition.x + pos.x)*128.0 + centerOffsetX, (vPosition.y + pos.y)*128.0 + centerOffsetY, height.r*128.0);\r\n" + // -// " v2Position = float(texture_indices.r+texture_indices.b+texture_indices.g+texture_indices.a);\r\n" + // + " gl_Position = ((texture_indices.a & 32768) == 0) ? MVP * vec4(position.xyz, 1) : vec4(2.0, 0.0, 0.0, 1.0);\r\n" + + // + " position.x = (position.x - centerOffsetX) / (size.x * 128.0);\r\n" + // + " position.y = (position.y - centerOffsetY) / (size.y * 128.0);\r\n" + // "}"; public static final String frag = "#version 450 core\r\n" + // @@ -158,10 +183,10 @@ public class TerrainShaders { "layout (location = 1) in flat uvec4 texture_indices;\r\n" + // "layout (location = 2) in vec2 pathing_map_uv;\r\n" + // "layout (location = 3) in vec3 normal;\r\n" + // - "layout (location = 6) in float v2Position;\r\n" + // + "layout (location = 4) in vec3 position;\r\n" + // "\r\n" + // "layout (location = 0) out vec4 color;\r\n" + // - "layout (location = 1) out vec4 position;\r\n" + // +// "layout (location = 1) out vec4 position;\r\n" + // "\r\n" + // "vec4 get_fragment(uint id, vec3 uv) {\r\n" + // " vec2 dx = dFdx(uv.xy);\r\n" + // @@ -235,7 +260,115 @@ public class TerrainShaders { " color = length(pathing_static_color.rgb) > 0 ? color * 0.75 + pathing_static_color * 0.5 : color;\r\n" + // " }\r\n" + // -// " color = vec4(texture_indices.a,texture_indices.b,texture_indices.g,1.0);\r\n" + // + "}"; + + public static final String posFrag = "#version 450 core\r\n" + // + "\r\n" + // + "layout (location = 2) uniform bool show_pathing_map;\r\n" + // + "layout (location = 3) uniform bool show_lighting;\r\n" + // + "\r\n" + // + "layout (binding = 3) uniform sampler2DArray sample0;\r\n" + // + "layout (binding = 4) uniform sampler2DArray sample1;\r\n" + // + "layout (binding = 5) uniform sampler2DArray sample2;\r\n" + // + "layout (binding = 6) uniform sampler2DArray sample3;\r\n" + // + "layout (binding = 7) uniform sampler2DArray sample4;\r\n" + // + "layout (binding = 8) uniform sampler2DArray sample5;\r\n" + // + "layout (binding = 9) uniform sampler2DArray sample6;\r\n" + // + "layout (binding = 10) uniform sampler2DArray sample7;\r\n" + // + "layout (binding = 11) uniform sampler2DArray sample8;\r\n" + // + "layout (binding = 12) uniform sampler2DArray sample9;\r\n" + // + "layout (binding = 13) uniform sampler2DArray sample10;\r\n" + // + "layout (binding = 14) uniform sampler2DArray sample11;\r\n" + // + "layout (binding = 15) uniform sampler2DArray sample12;\r\n" + // + "layout (binding = 16) uniform sampler2DArray sample13;\r\n" + // + "layout (binding = 17) uniform sampler2DArray sample14;\r\n" + // + "layout (binding = 18) uniform sampler2DArray sample15;\r\n" + // + "layout (binding = 19) uniform sampler2DArray sample16;\r\n" + // + "\r\n" + // + "layout (binding = 20) uniform usampler2D pathing_map_static;\r\n" + // + "layout (binding = 21) uniform usampler2D pathing_map_dynamic;\r\n" + // + "\r\n" + // + "layout (location = 0) in vec2 UV;\r\n" + // + "layout (location = 1) in flat uvec4 texture_indices;\r\n" + // + "layout (location = 2) in vec2 pathing_map_uv;\r\n" + // + "layout (location = 3) in vec3 normal;\r\n" + // + "layout (location = 4) in vec3 position;\r\n" + // + "\r\n" + // + "layout (location = 0) out vec4 color;\r\n" + // + "\r\n" + // + "vec4 get_fragment(uint id, vec3 uv) {\r\n" + // + " vec2 dx = dFdx(uv.xy);\r\n" + // + " vec2 dy = dFdy(uv.xy);\r\n" + // + "\r\n" + // + " switch(id) {\r\n" + // + " case 0:\r\n" + // + " return textureGrad(sample0, uv, dx, dy);\r\n" + // + " case 1:\r\n" + // + " return textureGrad(sample1, uv, dx, dy);\r\n" + // + " case 2:\r\n" + // + " return textureGrad(sample2, uv, dx, dy);\r\n" + // + " case 3:\r\n" + // + " return textureGrad(sample3, uv, dx, dy);\r\n" + // + " case 4:\r\n" + // + " return textureGrad(sample4, uv, dx, dy);\r\n" + // + " case 5:\r\n" + // + " return textureGrad(sample5, uv, dx, dy);\r\n" + // + " case 6:\r\n" + // + " return textureGrad(sample6, uv, dx, dy);\r\n" + // + " case 7:\r\n" + // + " return textureGrad(sample7, uv, dx, dy);\r\n" + // + " case 8:\r\n" + // + " return textureGrad(sample8, uv, dx, dy);\r\n" + // + " case 9:\r\n" + // + " return textureGrad(sample9, uv, dx, dy);\r\n" + // + " case 10:\r\n" + // + " return textureGrad(sample10, uv, dx, dy);\r\n" + // + " case 11:\r\n" + // + " return textureGrad(sample11, uv, dx, dy);\r\n" + // + " case 12:\r\n" + // + " return textureGrad(sample12, uv, dx, dy);\r\n" + // + " case 13:\r\n" + // + " return textureGrad(sample13, uv, dx, dy);\r\n" + // + " case 14:\r\n" + // + " return textureGrad(sample14, uv, dx, dy);\r\n" + // + " case 15:\r\n" + // + " return textureGrad(sample15, uv, dx, dy);\r\n" + // + " case 16:\r\n" + // + " return textureGrad(sample16, uv, dx, dy);\r\n" + // + " case 17:\r\n" + // + " return vec4(0, 0, 0, 0);\r\n" + // + " }\r\n" + // + "}\r\n" + // + "\r\n" + // + "\r\n" + // + "void main() {\r\n" + // + " color = vec4(position.xyz, 1.0);\r\n" + // + "}"; + } + + public static final class Test { + private Test() { + } + + public static final String vert = "#version 450 core\r\n" + // + "\r\n" + // + "layout (location = 0) in vec3 vPosition;\r\n" + // + "layout (location = 1) uniform mat4 MVP;\r\n" + // + "\r\n" + // + "\r\n" + // + "void main() { \r\n" + // + " gl_Position = MVP * vec4(vPosition, 1.0);\r\n" + // + "}"; + + public static final String frag = "#version 450 core\r\n" + // + "\r\n" + // + "\r\n" + // + "layout (location = 0) out vec4 color;\r\n" + // + "\r\n" + // + "\r\n" + // + "\r\n" + // + "void main() {\r\n" + // + " color = vec4(1.0,1.0,1.0,1.0);\r\n" + // "}"; } From b50f9114442901f19efc88b0b95bd837a322c33f Mon Sep 17 00:00:00 2001 From: Retera Date: Mon, 27 Jan 2020 23:23:53 -0600 Subject: [PATCH 020/116] Unit right click move demo --- .../etheller/warsmash/WarsmashGdxMapGame.java | 138 +++++++- .../warsmash/parsers/w3x/w3e/Corner.java | 4 + .../etheller/warsmash/units/GameObject.java | 62 ++++ .../units/manager/MutableObjectData.java | 26 +- .../etheller/warsmash/util/ImageUtils.java | 20 ++ .../warsmash/util/WarsmashConstants.java | 3 +- .../com/etheller/warsmash/viewer5/Node.java | 16 + .../com/etheller/warsmash/viewer5/Scene.java | 4 + .../handlers/mdx/GeometryEmitterFuncs.java | 6 + .../handlers/mdx/MdxComplexInstance.java | 7 +- .../warsmash/viewer5/handlers/w3x/Doodad.java | 2 +- .../viewer5/handlers/w3x/SplatModel.java | 52 ++- .../viewer5/handlers/w3x/StandSequence.java | 13 + .../viewer5/handlers/w3x/TerrainDoodad.java | 2 +- .../viewer5/handlers/w3x/UnitSoundset.java | 3 + .../viewer5/handlers/w3x/War3MapViewer.java | 295 ++++++++++++++---- .../w3x/environment/RenderCorner.java | 1 + .../handlers/w3x/environment/Terrain.java | 85 ++++- .../w3x/rendersim/CommandCardIcon.java | 33 ++ .../{Unit.java => rendersim/RenderItem.java} | 50 +-- .../handlers/w3x/rendersim/RenderUnit.java | 164 ++++++++++ .../w3x/simulation/CDestructable.java | 9 + .../handlers/w3x/simulation/CItem.java | 14 + .../handlers/w3x/simulation/COrder.java | 29 ++ .../handlers/w3x/simulation/CPlayer.java | 35 +++ .../w3x/simulation/CPlayerController.java | 9 + .../handlers/w3x/simulation/CSimulation.java | 47 +++ .../handlers/w3x/simulation/CUnit.java | 124 ++++++++ .../handlers/w3x/simulation/CWidget.java | 44 +++ .../w3x/simulation/HandleIdAllocator.java | 14 + .../simulation/StringsToExternalizeLater.java | 6 + .../w3x/simulation/abilities/CAbility.java | 22 ++ .../simulation/abilities/CAbilityAttack.java | 68 ++++ .../abilities/CAbilityHoldPosition.java | 69 ++++ .../simulation/abilities/CAbilityMove.java | 70 +++++ .../simulation/abilities/CAbilityPatrol.java | 70 +++++ .../simulation/abilities/CAbilityStop.java | 69 ++++ .../simulation/abilities/CAbilityView.java | 19 ++ .../w3x/simulation/data/CAbilityData.java | 11 + .../w3x/simulation/data/CUnitData.java | 99 ++++++ .../w3x/simulation/orders/CAttackOrder.java | 92 ++++++ .../simulation/orders/CDoNothingOrder.java | 28 ++ .../w3x/simulation/orders/CMoveOrder.java | 93 ++++++ .../util/AbilityActivationReceiver.java | 15 + .../util/AbilityTargetCheckReceiver.java | 32 ++ .../BooleanAbilityActivationReceiver.java | 41 +++ .../CWidgetAbilityTargetCheckReceiver.java | 55 ++++ .../util/PointAbilityTargetCheckReceiver.java | 55 ++++ .../w3x/simulation/util/ResourceType.java | 7 + .../warsmash/desktop/DesktopLauncher.java | 9 +- 50 files changed, 2106 insertions(+), 135 deletions(-) create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/CommandCardIcon.java rename core/src/com/etheller/warsmash/viewer5/handlers/w3x/{Unit.java => rendersim/RenderItem.java} (58%) create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CDestructable.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CItem.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/COrder.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CPlayer.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CPlayerController.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWidget.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/HandleIdAllocator.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/StringsToExternalizeLater.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbility.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityHoldPosition.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityMove.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityPatrol.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityStop.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityView.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CAbilityData.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CAttackOrder.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CDoNothingOrder.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CMoveOrder.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/AbilityActivationReceiver.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/AbilityTargetCheckReceiver.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/BooleanAbilityActivationReceiver.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/CWidgetAbilityTargetCheckReceiver.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/PointAbilityTargetCheckReceiver.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/ResourceType.java diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java index c2b7d76..79b5754 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java @@ -13,6 +13,7 @@ import com.badlogic.gdx.ApplicationAdapter; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Input; import com.badlogic.gdx.InputProcessor; +import com.badlogic.gdx.audio.Music; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.GL30; @@ -35,18 +36,24 @@ import com.etheller.warsmash.datasources.DataSourceDescriptor; import com.etheller.warsmash.datasources.FolderDataSourceDescriptor; import com.etheller.warsmash.util.DataSourceFileHandle; import com.etheller.warsmash.util.ImageUtils; +import com.etheller.warsmash.util.WarsmashConstants; import com.etheller.warsmash.viewer5.Camera; import com.etheller.warsmash.viewer5.CanvasProvider; import com.etheller.warsmash.viewer5.Scene; import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; +import com.etheller.warsmash.viewer5.handlers.mdx.ReplaceableIds; import com.etheller.warsmash.viewer5.handlers.tga.TgaFile; import com.etheller.warsmash.viewer5.handlers.w3x.StandSequence; -import com.etheller.warsmash.viewer5.handlers.w3x.Unit; import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; +import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.CommandCardIcon; +import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.COrder; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityStop; public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProvider, InputProcessor { private static final Vector3 clickLocationTemp = new Vector3(); + private static final Vector2 clickLocationTemp2 = new Vector2(); private DataSource codebase; private War3MapViewer viewer; private CameraManager cameraManager; @@ -60,6 +67,8 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv // libGDX stuff private OrthographicCamera uiCamera; private BitmapFont font; + private BitmapFont font24; + private BitmapFont font20; private SpriteBatch batch; private Viewport uiViewport; private GlyphLayout glyphLayout; @@ -67,6 +76,13 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv private Texture consoleUITexture; private final Vector2 projectionTemp1 = new Vector2(); private final Vector2 projectionTemp2 = new Vector2(); + private RenderUnit selectedUnit; + + private Texture activeButtonTexture; + + private Rectangle minimap; + + private final Texture[] teamColors = new Texture[WarsmashConstants.MAX_PLAYERS]; @Override public void create() { @@ -97,7 +113,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.viewer = new War3MapViewer(this.codebase, this); try { - this.viewer.loadMap("Farm.w3x"); + this.viewer.loadMap("Maps\\FrozenThrone\\Campaign\\NightElfX03.w3x"); } catch (final IOException e) { throw new RuntimeException(e); @@ -129,6 +145,10 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv final FreeTypeFontParameter fontParam = new FreeTypeFontParameter(); fontParam.size = 32; this.font = fontGenerator.generateFont(fontParam); + fontParam.size = 24; + this.font24 = fontGenerator.generateFont(fontParam); + fontParam.size = 20; + this.font20 = fontGenerator.generateFont(fontParam); fontGenerator.dispose(); this.glyphLayout = new GlyphLayout(); @@ -168,12 +188,26 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv } } + this.activeButtonTexture = ImageUtils.getBLPTexture(this.viewer.dataSource, + "UI\\Widgets\\Console\\Human\\CommandButton\\human-activebutton.blp"); + + for (int i = 0; i < this.teamColors.length; i++) { + this.teamColors[i] = ImageUtils.getBLPTexture(this.viewer.dataSource, + "ReplaceableTextures\\" + ReplaceableIds.getPathString(1) + ReplaceableIds.getIdString(i) + ".blp"); + } + Gdx.input.setInputProcessor(this); -// final Music music = Gdx.audio.newMusic(new DataSourceFileHandle(this.viewer.dataSource, "undead_dance.mp3")); -// music.setVolume(0.7f); -// music.setLooping(true); -// music.play(); + final Music music = Gdx.audio + .newMusic(new DataSourceFileHandle(this.viewer.dataSource, "Sound\\Music\\mp3Music\\NightElfX1.mp3")); + music.setVolume(0.2f); + music.setLooping(true); + music.play(); + + this.minimap = new Rectangle(35, 7, 305, 272); + + this.cameraManager.target.x = this.viewer.startLocations[0].x; + this.cameraManager.target.y = this.viewer.startLocations[0].y; } @Override @@ -214,8 +248,58 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv final String fpsString = "FPS: " + Gdx.graphics.getFramesPerSecond(); this.glyphLayout.setText(this.font, fpsString); this.font.draw(this.batch, fpsString, (this.uiViewport.getWorldWidth() - this.glyphLayout.width) / 2, 1100); + this.batch.draw(this.consoleUITexture, 0, 0, this.uiViewport.getWorldWidth(), 320); this.batch.draw(this.minimapTexture, 35, 7, 305, 272); + + if (this.selectedUnit != null) { + this.font24.setColor(Color.WHITE); + final String name = this.viewer.simulation.getUnitData() + .getName(this.selectedUnit.getSimulationUnit().getTypeId()); + this.glyphLayout.setText(this.font24, name); + this.font24.draw(this.batch, name, ((this.uiViewport.getWorldWidth() - this.glyphLayout.width) / 2) + 100, + 200); + + this.font20.setColor(Color.YELLOW); + this.font20.draw(this.batch, "Attack:", 600, 120); + this.font20.draw(this.batch, "Defense:", 600, 98); + this.font20.draw(this.batch, "Speed:", 600, 76); + this.font20.setColor(Color.WHITE); + final int dmgMin = this.viewer.simulation.getUnitData() + .getA1MinDamage(this.selectedUnit.getSimulationUnit().getTypeId()); + final int dmgMax = this.viewer.simulation.getUnitData() + .getA1MaxDamage(this.selectedUnit.getSimulationUnit().getTypeId()); + final int def = this.viewer.simulation.getUnitData() + .getDefense(this.selectedUnit.getSimulationUnit().getTypeId()); + this.font20.draw(this.batch, Integer.toString(dmgMin) + " - " + Integer.toString(dmgMax), 700, 120); + this.font20.draw(this.batch, Integer.toString(def), 700, 98); + this.font20.draw(this.batch, Integer.toString(this.selectedUnit.getSimulationUnit().getSpeed()), 700, 76); + + final COrder currentOrder = this.selectedUnit.getSimulationUnit().getCurrentOrder(); + for (final CommandCardIcon commandCardIcon : this.selectedUnit.getCommandCardIcons()) { + this.batch.draw(commandCardIcon.getTexture(), 1225 + (70 * commandCardIcon.getX()), + 160 - (70 * commandCardIcon.getY()), 64, 64); + if (((currentOrder != null) && (currentOrder.getOrderId() == commandCardIcon.getOrderId())) + || ((currentOrder == null) && (commandCardIcon.getOrderId() == CAbilityStop.ORDER_ID))) { + final int blendDstFunc = this.batch.getBlendDstFunc(); + final int blendSrcFunc = this.batch.getBlendSrcFunc(); + this.batch.setBlendFunction(GL20.GL_SRC_ALPHA, GL20.GL_ONE); + this.batch.draw(this.activeButtonTexture, 1225 + (70 * commandCardIcon.getX()), + 160 - (70 * commandCardIcon.getY()), 64, 64); + this.batch.setBlendFunction(blendSrcFunc, blendDstFunc); + } + } + + } + for (final RenderUnit unit : this.viewer.units) { + final Texture minimapIcon = this.teamColors[unit.playerIndex]; + this.batch.draw(minimapIcon, + this.minimap.x + (((unit.location[0] - this.viewer.terrain.centerOffset[0]) + / ((this.viewer.terrain.columns - 1) * 128f)) * this.minimap.width), + this.minimap.y + (((unit.location[1] - this.viewer.terrain.centerOffset[1]) + / ((this.viewer.terrain.rows - 1) * 128f)) * this.minimap.height), + 4, 4); + } this.batch.end(); } @@ -401,18 +485,43 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv @Override public boolean touchDown(final int screenX, final int screenY, final int pointer, final int button) { System.out.println(screenX + "," + screenY); + clickLocationTemp2.x = screenX; + clickLocationTemp2.y = screenY; + this.uiViewport.unproject(clickLocationTemp2); + if (this.minimap.contains(clickLocationTemp2.x, clickLocationTemp2.y)) { + final float clickX = (clickLocationTemp2.x - this.minimap.x) / this.minimap.width; + final float clickY = (clickLocationTemp2.y - this.minimap.y) / this.minimap.height; + this.cameraManager.target.x = (clickX * this.viewer.terrain.columns * 128) + + this.viewer.terrain.centerOffset[0]; + this.cameraManager.target.y = (clickY * this.viewer.terrain.rows * 128) + + this.viewer.terrain.centerOffset[1]; + return false; + } if (button == Input.Buttons.RIGHT) { - this.viewer.getClickLocation(clickLocationTemp, screenX, screenY); - System.out.println(clickLocationTemp); - this.viewer.showConfirmation(clickLocationTemp, 0, 1, 0); - final int x = (int) ((clickLocationTemp.x - this.viewer.terrain.centerOffset[0]) / 128); - final int y = (int) ((clickLocationTemp.y - this.viewer.terrain.centerOffset[1]) / 128); - System.out.println(x + "," + y); + final RenderUnit rayPickUnit = this.viewer.rayPickUnit(screenX, screenY); + if ((rayPickUnit != null) && (rayPickUnit.playerIndex != this.selectedUnit.playerIndex)) { + if (this.viewer.orderSmart(rayPickUnit)) { + StandSequence.randomPortraitTalkSequence(this.portraitInstance); + } + } + else { + this.viewer.getClickLocation(clickLocationTemp, screenX, screenY); + System.out.println(clickLocationTemp); + this.viewer.showConfirmation(clickLocationTemp, 0, 1, 0); + final int x = (int) ((clickLocationTemp.x - this.viewer.terrain.centerOffset[0]) / 128); + final int y = (int) ((clickLocationTemp.y - this.viewer.terrain.centerOffset[1]) / 128); + System.out.println(x + "," + y); + this.viewer.terrain.logRomp(x, y); + if (this.viewer.orderSmart(clickLocationTemp.x, clickLocationTemp.y)) { + StandSequence.randomPortraitTalkSequence(this.portraitInstance); + } + } } else { - final List selectedUnits = this.viewer.selectUnit(screenX, screenY, true); + final List selectedUnits = this.viewer.selectUnit(screenX, screenY, false); if (!selectedUnits.isEmpty()) { - final Unit unit = selectedUnits.get(0); + final RenderUnit unit = selectedUnits.get(0); + this.selectedUnit = unit; if (unit.soundset != null) { unit.soundset.what.play(this.viewer.worldScene.audioContext, unit.location[0], unit.location[1]); } @@ -433,6 +542,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv } } else { + this.selectedUnit = null; if (this.portraitInstance != null) { this.portraitScene.removeInstance(this.portraitInstance); } diff --git a/core/src/com/etheller/warsmash/parsers/w3x/w3e/Corner.java b/core/src/com/etheller/warsmash/parsers/w3x/w3e/Corner.java index 4e3b298..398a82c 100644 --- a/core/src/com/etheller/warsmash/parsers/w3x/w3e/Corner.java +++ b/core/src/com/etheller/warsmash/parsers/w3x/w3e/Corner.java @@ -95,6 +95,10 @@ public class Corner { return this.ramp; } + public boolean isRamp() { + return this.ramp != 0; + } + public int getBlight() { return this.blight; } diff --git a/core/src/com/etheller/warsmash/units/GameObject.java b/core/src/com/etheller/warsmash/units/GameObject.java index 191cec2..e630a4a 100644 --- a/core/src/com/etheller/warsmash/units/GameObject.java +++ b/core/src/com/etheller/warsmash/units/GameObject.java @@ -1,5 +1,6 @@ package com.etheller.warsmash.units; +import java.util.Collections; import java.util.List; import java.util.Set; @@ -29,4 +30,65 @@ public interface GameObject { public Set keySet(); + GameObject EMPTY = new GameObject() { + + @Override + public void setField(final String field, final String value, final int index) { + } + + @Override + public void setField(final String field, final String value) { + } + + @Override + public Set keySet() { + return Collections.emptySet(); + } + + @Override + public ObjectData getTable() { + return null; + } + + @Override + public String getName() { + return ""; + } + + @Override + public String getId() { + return "0000"; + } + + @Override + public int getFieldValue(final String field, final int index) { + return 0; + } + + @Override + public int getFieldValue(final String field) { + return 0; + } + + @Override + public float getFieldFloatValue(final String field) { + return 0; + } + + @Override + public List getFieldAsList(final String field, final ObjectData objectData) { + return Collections.emptyList(); + } + + @Override + public String getField(final String field, final int index) { + return ""; + } + + @Override + public String getField(final String field) { + return ""; + } + }; + } \ No newline at end of file diff --git a/core/src/com/etheller/warsmash/units/manager/MutableObjectData.java b/core/src/com/etheller/warsmash/units/manager/MutableObjectData.java index e6fd636..89f8200 100644 --- a/core/src/com/etheller/warsmash/units/manager/MutableObjectData.java +++ b/core/src/com/etheller/warsmash/units/manager/MutableObjectData.java @@ -266,19 +266,33 @@ public final class MutableObjectData { if (mutableGameObject == null) { if (this.editorData.getCustom().containsKey(id)) { final ObjectDataChangeEntry customUnitData = this.editorData.getCustom().get(id); - mutableGameObject = new MutableGameObject( - this.sourceSLKData.get(customUnitData.getOldId().asStringValue()), customUnitData); + GameObject parentWC3Object = this.sourceSLKData.get(customUnitData.getOldId().asStringValue()); + if (parentWC3Object == null) { + System.err.println("Error parsing unit data: custom unit inherits from unknown id '" + + customUnitData.getOldId().asStringValue() + "'"); + parentWC3Object = GameObject.EMPTY; + } + mutableGameObject = new MutableGameObject(parentWC3Object, customUnitData); this.cachedKeyToGameObject.put(id, mutableGameObject); } else if (this.editorData.getOriginal().containsKey(id)) { final ObjectDataChangeEntry customUnitData = this.editorData.getOriginal().get(id); - mutableGameObject = new MutableGameObject( - this.sourceSLKData.get(customUnitData.getOldId().asStringValue()), - this.editorData.getOriginal().get(id)); + GameObject parentWC3Object = this.sourceSLKData.get(customUnitData.getOldId().asStringValue()); + if (parentWC3Object == null) { + System.err.println("Error parsing unit data: standard unit modifies unknown id '" + + customUnitData.getOldId().asStringValue() + "'"); + parentWC3Object = GameObject.EMPTY; + } + mutableGameObject = new MutableGameObject(parentWC3Object, this.editorData.getOriginal().get(id)); this.cachedKeyToGameObject.put(id, mutableGameObject); } else if (this.sourceSLKData.get(id.asStringValue()) != null) { - mutableGameObject = new MutableGameObject(this.sourceSLKData.get(id.asStringValue()), null); + GameObject parentWC3Object = this.sourceSLKData.get(id.asStringValue()); + if (parentWC3Object == null) { + System.err.println("Error parsing unit data: id does not exist: '" + id.asStringValue() + "'"); + parentWC3Object = GameObject.EMPTY; + } + mutableGameObject = new MutableGameObject(parentWC3Object, null); this.cachedKeyToGameObject.put(id, mutableGameObject); } } diff --git a/core/src/com/etheller/warsmash/util/ImageUtils.java b/core/src/com/etheller/warsmash/util/ImageUtils.java index c1e8686..392b092 100644 --- a/core/src/com/etheller/warsmash/util/ImageUtils.java +++ b/core/src/com/etheller/warsmash/util/ImageUtils.java @@ -6,14 +6,19 @@ import java.awt.image.BufferedImage; import java.awt.image.ColorModel; import java.awt.image.ComponentColorModel; import java.awt.image.DataBuffer; +import java.io.IOException; +import java.io.InputStream; import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import javax.imageio.ImageIO; + import com.badlogic.gdx.graphics.GL30; import com.badlogic.gdx.graphics.Pixmap; import com.badlogic.gdx.graphics.Pixmap.Format; import com.badlogic.gdx.graphics.Texture; +import com.etheller.warsmash.datasources.DataSource; /** * Uses AWT stuff @@ -22,6 +27,21 @@ import com.badlogic.gdx.graphics.Texture; public final class ImageUtils { private static final int BYTES_PER_PIXEL = 4; + public static Texture getBLPTexture(final DataSource dataSource, final String path) { + try { + try (final InputStream resourceAsStream = dataSource.getResourceAsStream(path)) { + if (resourceAsStream == null) { + throw new IllegalStateException("missing resource: " + path); + } + return ImageUtils.getTexture(ImageIO.read(resourceAsStream)); + } + + } + catch (final IOException e) { + throw new RuntimeException(e); + } + } + public static Texture getTexture(final BufferedImage image) { final int[] pixels = new int[image.getWidth() * image.getHeight()]; image.getRGB(0, 0, image.getWidth(), image.getHeight(), pixels, 0, image.getWidth()); diff --git a/core/src/com/etheller/warsmash/util/WarsmashConstants.java b/core/src/com/etheller/warsmash/util/WarsmashConstants.java index 0e4a23d..1205058 100644 --- a/core/src/com/etheller/warsmash/util/WarsmashConstants.java +++ b/core/src/com/etheller/warsmash/util/WarsmashConstants.java @@ -1,6 +1,7 @@ package com.etheller.warsmash.util; public class WarsmashConstants { - public static final int MAX_PLAYERS = 28; + public static final int MAX_PLAYERS = 16; public static final int REPLACEABLE_TEXTURE_LIMIT = 64; + public static final float SIMULATION_STEP_TIME = 1 / 60f; } diff --git a/core/src/com/etheller/warsmash/viewer5/Node.java b/core/src/com/etheller/warsmash/viewer5/Node.java index c0014a1..3e7ee1a 100644 --- a/core/src/com/etheller/warsmash/viewer5/Node.java +++ b/core/src/com/etheller/warsmash/viewer5/Node.java @@ -114,6 +114,14 @@ public abstract class Node extends GenericNode { return this; } + public Node moveTo(final float[] offset) { + this.localLocation.set(offset[0], offset[1], offset[2]); + + this.dirty = true; + + return this; + } + public Node rotate(final Quaternion rotation) { RenderMathUtils.mul(this.localRotation, this.localRotation, rotation); @@ -122,6 +130,14 @@ public abstract class Node extends GenericNode { return this; } + public Node setLocalRotation(final Quaternion rotation) { + this.localRotation.set(rotation); + + this.dirty = true; + + return this; + } + public Node rotateLocal(final Quaternion rotation) { RenderMathUtils.mul(this.localRotation, rotation, this.localRotation); diff --git a/core/src/com/etheller/warsmash/viewer5/Scene.java b/core/src/com/etheller/warsmash/viewer5/Scene.java index 02315d4..a948129 100644 --- a/core/src/com/etheller/warsmash/viewer5/Scene.java +++ b/core/src/com/etheller/warsmash/viewer5/Scene.java @@ -202,6 +202,10 @@ public class Scene { if (instance.updateFrame < frame) { instance.update(dt, this); + if (!instance.rendered) { + // it became hidden while it updated + continue; + } } if (instance.isBatched()) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GeometryEmitterFuncs.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GeometryEmitterFuncs.java index dd0e1de..e9f6721 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GeometryEmitterFuncs.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GeometryEmitterFuncs.java @@ -404,10 +404,16 @@ public class GeometryEmitterFuncs { bindRibbonEmitterShader((RibbonEmitter) emitter, shader); break; case EMITTER_SPLAT: + if (true) { + return; + } bindEventObjectEmitterBuffer((EventObjectSplEmitter) emitter, buffer); bindEventObjectSplEmitterShader((EventObjectSplEmitter) emitter, shader); break; default: + if (true) { + return; + } bindEventObjectEmitterBuffer((EventObjectUbrEmitter) emitter, buffer); bindEventObjectUbrEmitterShader((EventObjectUbrEmitter) emitter, shader); break; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java index 809a2e3..3d61165 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java @@ -528,6 +528,9 @@ public class MdxComplexInstance extends ModelInstance { this.counter -= frameTime; this.allowParticleSpawn = false; } + if (this.sequenceLoopMode == 3) { + hide(); + } this.sequenceEnded = true; } @@ -615,6 +618,7 @@ public class MdxComplexInstance extends ModelInstance { } else { this.frame = (int) sequences.get(id).getInterval()[0]; // TODO not cast + this.sequenceEnded = false; } this.resetEventEmitters(); @@ -627,7 +631,8 @@ public class MdxComplexInstance extends ModelInstance { /** * Set the seuqnece loop mode. 0 to never loop, 1 to loop based on the model, - * and 2 to always loop. + * and 2 to always loop. 3 was added by Retera as "hide after done" for gameplay + * spawned effects */ public MdxComplexInstance setSequenceLoopMode(final int mode) { this.sequenceLoopMode = mode; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Doodad.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Doodad.java index b3ce2b2..ae34f40 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Doodad.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Doodad.java @@ -11,7 +11,7 @@ import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; public class Doodad { private static final War3ID TEX_FILE = War3ID.fromString("btxf"); private static final War3ID TEX_ID = War3ID.fromString("btxi"); - private final ModelInstance instance; + public final ModelInstance instance; private final MutableGameObject row; public Doodad(final War3MapViewer map, final MdxModel model, final MutableGameObject row, diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SplatModel.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SplatModel.java index 3837a2e..83d9e90 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SplatModel.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SplatModel.java @@ -2,7 +2,9 @@ package com.etheller.warsmash.viewer5.handlers.w3x; import java.util.ArrayList; import java.util.List; +import java.util.function.Consumer; +import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.GL30; import com.badlogic.gdx.graphics.glutils.ShaderProgram; import com.etheller.warsmash.util.RenderMathUtils; @@ -23,7 +25,8 @@ public class SplatModel { private final List batches; public final float[] color; - public SplatModel(final GL30 gl, final Texture texture, final List locations, final float[] centerOffset) { + public SplatModel(final GL30 gl, final Texture texture, final List locations, final float[] centerOffset, + final List> unitMapping) { this.texture = texture; this.batches = new ArrayList<>(); this.color = new float[] { 1, 1, 1, 1 }; @@ -31,8 +34,12 @@ public class SplatModel { final List vertices = new ArrayList<>(); final List uvs = new ArrayList<>(); final List indices = new ArrayList<>(); + final List batchRenderUnits = new ArrayList<>(); final int instances = locations.size(); for (int idx = 0; idx < instances; ++idx) { + final Consumer unit = ((unitMapping != null) && (idx < unitMapping.size())) + ? unitMapping.get(idx) + : null; final float[] locs = locations.get(idx); final float x0 = locs[0]; final float y0 = locs[1]; @@ -51,12 +58,14 @@ public class SplatModel { } int start = vertices.size(); + final SplatMover splatMover = unit == null ? null : new SplatMover(start * 3 * 4); final int step = (ix1 - ix0) + 1; if ((start + newVerts) > MAX_VERTICES) { - this.addBatch(gl, vertices, uvs, indices); + this.addBatch(gl, vertices, uvs, indices, batchRenderUnits); vertices.clear(); uvs.clear(); indices.clear(); + batchRenderUnits.clear(); start = 0; } @@ -64,8 +73,12 @@ public class SplatModel { final float y = (iy * 128.0f) + centerOffset[1]; for (int ix = ix0; ix <= ix1; ++ix) { final float x = (ix * 128.0f) + centerOffset[0]; - vertices.add(new float[] { x, y, zoffs }); + final float[] vertex = new float[] { x, y, zoffs }; + vertices.add(vertex); uvs.add(new float[] { (x - x0) / (x1 - x0), 1.0f - ((y - y0) / (y1 - y0)) }); + if (splatMover != null) { + splatMover.vertices.add(vertex); + } } } for (int i = 0; i < (iy1 - iy0); ++i) { @@ -74,16 +87,20 @@ public class SplatModel { indices.add(new int[] { i0, i0 + 1, i0 + step, i0 + 1, i0 + step + 1, i0 + step }); } } + if (unit != null) { + unit.accept(splatMover); + batchRenderUnits.add(splatMover); + } } if (indices.size() > 0) { - this.addBatch(gl, vertices, uvs, indices); + this.addBatch(gl, vertices, uvs, indices, batchRenderUnits); } } private void addBatch(final GL30 gl, final List vertices, final List uvs, - final List indices) { + final List indices, final List batchRenderUnits) { final int uvsOffset = vertices.size() * 3 * 4; final int vertexBuffer = gl.glGenBuffer(); @@ -98,6 +115,9 @@ public class SplatModel { GL30.GL_STATIC_DRAW); this.batches.add(new Batch(uvsOffset, vertexBuffer, faceBuffer, indices.size() * 6)); + for (final SplatMover mover : batchRenderUnits) { + mover.vertexBuffer = vertexBuffer; + } } public void render(final GL30 gl, final ShaderProgram shader) { @@ -135,4 +155,26 @@ public class SplatModel { this.elements = elements; } } + + public static final class SplatMover { + private int vertexBuffer; + private final int startOffset; + private final List vertices = new ArrayList<>(); + + private SplatMover(final int i) { + this.startOffset = i; + } + + public void move(final float deltaX, final float deltaY) { + for (final float[] vertex : this.vertices) { + vertex[0] += deltaX; + vertex[1] += deltaY; + } + + final GL30 gl = Gdx.gl30; + gl.glBindBuffer(GL30.GL_ARRAY_BUFFER, this.vertexBuffer); + gl.glBufferSubData(GL30.GL_ARRAY_BUFFER, this.startOffset, 4 * 3 * this.vertices.size(), + RenderMathUtils.wrap(this.vertices)); + } + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/StandSequence.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/StandSequence.java index 9ad31fc..3aa2048 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/StandSequence.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/StandSequence.java @@ -106,4 +106,17 @@ public class StandSequence { randomPortraitSequence(target); } } + + public static void randomSequence(final MdxComplexInstance target, final String sequenceName) { + final MdxModel model = (MdxModel) target.model; + final List sequences = model.getSequences(); + final IndexedSequence sequence = selectSequence(sequenceName, sequences); + + if (sequence != null) { + target.setSequence(sequence.index); + } + else { + randomStandSequence(target); + } + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/TerrainDoodad.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/TerrainDoodad.java index 7bc7e6a..6fa9fe7 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/TerrainDoodad.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/TerrainDoodad.java @@ -8,7 +8,7 @@ import com.etheller.warsmash.viewer5.handlers.mdx.MdxSimpleInstance; public class TerrainDoodad { private static final float[] locationHeap = new float[3]; - private final MdxSimpleInstance instance; + public final MdxSimpleInstance instance; private final MutableGameObject row; public TerrainDoodad(final War3MapViewer map, final MdxModel model, final MutableGameObject row, diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitSoundset.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitSoundset.java index f2cacce..b06238c 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitSoundset.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitSoundset.java @@ -41,6 +41,8 @@ public class UnitSoundset { private final float maxDistance; private final float distanceCutoff; + private Sound lastPlayedSound; + public static UnitAckSound create(final DataSource dataSource, final DataTable unitAckSounds, final String soundName, final String soundType) { final Element row = unitAckSounds.get(soundName + soundType); @@ -102,6 +104,7 @@ public class UnitSoundset { // Make a sound. source.start(0); + this.lastPlayedSound = source.buffer; } } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index 5baa819..c828eae 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -12,6 +12,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Consumer; import org.apache.commons.compress.utils.IOUtils; import org.apache.commons.compress.utils.SeekableInMemoryByteChannel; @@ -39,6 +40,7 @@ import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; import com.etheller.warsmash.units.manager.MutableObjectData.WorldEditorDataType; import com.etheller.warsmash.util.MappedData; import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.util.WarsmashConstants; import com.etheller.warsmash.util.WorldEditStrings; import com.etheller.warsmash.viewer5.CanvasProvider; import com.etheller.warsmash.viewer5.GenericResource; @@ -52,8 +54,20 @@ import com.etheller.warsmash.viewer5.gl.WebGL; import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxHandler; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; +import com.etheller.warsmash.viewer5.handlers.w3x.SplatModel.SplatMover; import com.etheller.warsmash.viewer5.handlers.w3x.environment.Terrain; import com.etheller.warsmash.viewer5.handlers.w3x.environment.Terrain.Splat; +import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderItem; +import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityAttack; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityMove; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.BooleanAbilityActivationReceiver; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.CWidgetAbilityTargetCheckReceiver; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.PointAbilityTargetCheckReceiver; import mpq.MPQArchive; import mpq.MPQException; @@ -94,12 +108,13 @@ public class War3MapViewer extends ModelViewer { public MappedData doodadMetaData = new MappedData(); public MappedData destructableMetaData = new MappedData(); public List doodads = new ArrayList<>(); - public List terrainDoodads = new ArrayList<>(); + public List terrainDoodads = new ArrayList<>(); public boolean doodadsReady; public boolean unitsAndItemsLoaded; public MappedData unitsData = new MappedData(); public MappedData unitMetaData = new MappedData(); - public List units = new ArrayList<>(); + public List units = new ArrayList<>(); + public List items = new ArrayList<>(); public boolean unitsReady; public War3Map mapMpq; public PathSolver mapPathSolver = PathSolver.DEFAULT; @@ -111,9 +126,13 @@ public class War3MapViewer extends ModelViewer { public int renderLighting = 0; public List selModels = new ArrayList<>(); - public List selected = new ArrayList<>(); + public List selected = new ArrayList<>(); private DataTable unitAckSoundsTable; private MdxComplexInstance confirmationInstance; + public CSimulation simulation; + private float updateTime; + + public Vector2[] startLocations = new Vector2[WarsmashConstants.MAX_PLAYERS]; public War3MapViewer(final DataSource dataSource, final CanvasProvider canvas) { super(dataSource, canvas); @@ -260,7 +279,7 @@ public class War3MapViewer extends ModelViewer { final MdxModel confirmation = (MdxModel) load("UI\\Feedback\\Confirmation\\Confirmation.mdx", PathSolver.DEFAULT, null); this.confirmationInstance = (MdxComplexInstance) confirmation.addInstance(); - this.confirmationInstance.setSequenceLoopMode(0); + this.confirmationInstance.setSequenceLoopMode(3); this.confirmationInstance.setSequence(0); this.confirmationInstance.setScene(this.worldScene); @@ -272,6 +291,7 @@ public class War3MapViewer extends ModelViewer { } final Warcraft3MapObjectData modifications = this.mapMpq.readModifications(); + this.simulation = new CSimulation(modifications.getUnits(), modifications.getAbilities()); if (this.doodadsAndDestructiblesLoaded) { this.loadDoodadsAndDestructibles(modifications); @@ -309,43 +329,45 @@ public class War3MapViewer extends ModelViewer { row = modifications.getDestructibles().get(doodad.getId()); type = WorldEditorDataType.DESTRUCTIBLES; } - String file = row.readSLKTag("file"); - final int numVar = row.readSLKTagInt("numVar"); + if (row != null) { + String file = row.readSLKTag("file"); + final int numVar = row.readSLKTagInt("numVar"); - if (file.endsWith(".mdx") || file.endsWith(".mdl")) { - file = file.substring(0, file.length() - 4); + if (file.endsWith(".mdx") || file.endsWith(".mdl")) { + file = file.substring(0, file.length() - 4); + } + + String fileVar = file; + + file += ".mdx"; + + if (numVar > 1) { + fileVar += Math.min(doodad.getVariation(), numVar - 1); + } + + fileVar += ".mdx"; + + // First see if the model is local. + // Doodads referring to local models may have invalid variations, so if the + // variation doesn't exist, try without a variation. + + String path; + if (this.mapMpq.has(fileVar)) { + path = fileVar; + } + else { + path = file; + } + MdxModel model; + if (this.mapMpq.has(path)) { + model = (MdxModel) this.load(path, this.mapPathSolver, this.solverParams); + } + else { + model = (MdxModel) this.load(fileVar, this.mapPathSolver, this.solverParams); + } + + this.doodads.add(new Doodad(this, model, row, doodad, type)); } - - String fileVar = file; - - file += ".mdx"; - - if (numVar > 1) { - fileVar += Math.min(doodad.getVariation(), numVar - 1); - } - - fileVar += ".mdx"; - - // First see if the model is local. - // Doodads referring to local models may have invalid variations, so if the - // variation doesn't exist, try without a variation. - - String path; - if (this.mapMpq.has(fileVar)) { - path = fileVar; - } - else { - path = file; - } - MdxModel model; - if (this.mapMpq.has(path)) { - model = (MdxModel) this.load(path, this.mapPathSolver, this.solverParams); - } - else { - model = (MdxModel) this.load(fileVar, this.mapPathSolver, this.solverParams); - } - - this.doodads.add(new Doodad(this, model, row, doodad, type)); } // Cliff/Terrain doodads. @@ -386,12 +408,14 @@ public class War3MapViewer extends ModelViewer { for (final com.etheller.warsmash.parsers.w3x.unitsdoo.Unit unit : dooFile.getUnits()) { MutableGameObject row = null; String path = null; + Splat unitShadowSplat = null; // Hardcoded? WorldEditorDataType type = null; if (sloc.equals(unit.getId())) { // path = "Objects\\StartLocation\\StartLocation.mdx"; type = null; /// ?????? + this.startLocations[unit.getPlayer()] = new Vector2(unit.getLocation()[0], unit.getLocation()[1]); } else { row = modifications.getUnits().get(unit.getId()); @@ -453,7 +477,8 @@ public class War3MapViewer extends ModelViewer { final float x = unit.getLocation()[0] - shadowX; final float y = unit.getLocation()[1] - shadowY; this.terrain.splats.get(texture).locations - .add(new float[] { x, y, x + shadowWidth, y + shadowHeight, 3 }); + .add(new float[] { x, y, x + shadowWidth, y + shadowHeight, 30 }); + unitShadowSplat = this.terrain.splats.get(texture); } final String buildingShadow = row.getFieldAsString(BUILDING_SHADOW, 0); @@ -481,7 +506,34 @@ public class War3MapViewer extends ModelViewer { else { portraitModel = model; } - this.units.add(new Unit(this, model, row, unit, type, soundset, portraitModel)); + if (type == WorldEditorDataType.UNITS) { + float angle; + if (this.simulation.getUnitData().isBuilding(row.getAlias())) { + // TODO pretty sure 270 is a Gameplay Constants value that should be dynamically + // loaded + angle = 270.0f; + } + else { + angle = (float) Math.toDegrees(unit.getAngle()); + } + final CUnit simulationUnit = this.simulation.createUnit(row.getAlias(), unit.getLocation()[0], + unit.getLocation()[1], angle); + final RenderUnit renderUnit = new RenderUnit(this, model, row, unit, soundset, portraitModel, + simulationUnit); + this.units.add(renderUnit); + if (unitShadowSplat != null) { + unitShadowSplat.unitMapping.add(new Consumer() { + @Override + public void accept(final SplatMover t) { + renderUnit.shadow = t; + } + }); + } + } + else { + this.items.add(new RenderItem(this, model, row, unit, soundset, portraitModel)); // TODO store + // somewhere + } } else { System.err.println("Unknown unit ID: " + unit.getId()); @@ -501,9 +553,18 @@ public class War3MapViewer extends ModelViewer { super.update(); - final List instances = this.worldScene.instances; - - for (final ModelInstance instance : instances) { + for (final RenderUnit unit : this.units) { + unit.updateAnimations(this); + } + for (final RenderItem item : this.items) { + final MdxComplexInstance instance = item.instance; + final MdxComplexInstance mdxComplexInstance = instance; + if (mdxComplexInstance.sequenceEnded || (mdxComplexInstance.sequence == -1)) { + StandSequence.randomStandSequence(mdxComplexInstance); + } + } + for (final Doodad item : this.doodads) { + final ModelInstance instance = item.instance; if ((instance instanceof MdxComplexInstance) && (instance != this.confirmationInstance)) { final MdxComplexInstance mdxComplexInstance = (MdxComplexInstance) instance; if (mdxComplexInstance.sequenceEnded || (mdxComplexInstance.sequence == -1)) { @@ -511,8 +572,11 @@ public class War3MapViewer extends ModelViewer { } } } - if (this.confirmationInstance.sequenceEnded) { - this.confirmationInstance.hide(); + + this.updateTime += Gdx.graphics.getRawDeltaTime(); + while (this.updateTime >= WarsmashConstants.SIMULATION_STEP_TIME) { + this.updateTime -= WarsmashConstants.SIMULATION_STEP_TIME; + this.simulation.update(); } } } @@ -548,18 +612,21 @@ public class War3MapViewer extends ModelViewer { this.terrain.removeSplatBatchModel(model); } this.selModels.clear(); + for (final RenderUnit unit : this.selected) { + unit.selectionCircle = null; + } } this.selected.clear(); } - public void doSelectUnit(final List units) { + public void doSelectUnit(final List units) { deselect(); if (units.isEmpty()) { return; } final Map splats = new HashMap(); - for (final Unit unit : units) { + for (final RenderUnit unit : units) { if (unit.row != null) { if (unit.radius > 0) { final float radius = unit.radius; @@ -581,8 +648,15 @@ public class War3MapViewer extends ModelViewer { final float y = unit.location[1]; final float z = unit.row.getFieldAsFloat(UNIT_SELECT_HEIGHT, 0); splats.get(path).locations - .add(new float[] { x - radius, y - radius, x + radius, y + radius, z + 5 }); + .add(new float[] { x - radius, y - radius, x + radius, y + radius, z + 35 }); + splats.get(path).unitMapping.add(new Consumer() { + @Override + public void accept(final SplatMover t) { + unit.selectionCircle = t; + } + }); } + this.selected.add(unit); } } this.selModels.clear(); @@ -590,7 +664,7 @@ public class War3MapViewer extends ModelViewer { final String path = entry.getKey(); final Splat locations = entry.getValue(); final SplatModel model = new SplatModel(Gdx.gl30, (Texture) load(path, PathSolver.DEFAULT, null), - locations.locations, this.terrain.centerOffset); + locations.locations, this.terrain.centerOffset, locations.unitMapping); model.color[0] = 0; model.color[1] = 1; model.color[2] = 0; @@ -619,7 +693,7 @@ public class War3MapViewer extends ModelViewer { this.confirmationInstance.vertexColor[2] = blue; } - public List selectUnit(final float x, final float y, final boolean toggle) { + public List selectUnit(final float x, final float y, final boolean toggle) { final float[] ray = rayHeap; mousePosHeap.set(x, y); this.worldScene.camera.screenToWorldRay(ray, mousePosHeap); @@ -633,10 +707,10 @@ public class War3MapViewer extends ModelViewer { final Vector3 eSize = new Vector3(); final Vector3 rDir = new Vector3(); - Unit entity = null; + RenderUnit entity = null; float entDist = 1e6f; - for (final Unit unit : this.units) { + for (final RenderUnit unit : this.units) { final float radius = unit.radius; final float[] location = unit.location; final MdxComplexInstance instance = unit.instance; @@ -660,7 +734,7 @@ public class War3MapViewer extends ModelViewer { entDist = dp; } } - List sel; + List sel; if (entity != null) { if (toggle) { sel = new ArrayList<>(this.selected); @@ -683,6 +757,50 @@ public class War3MapViewer extends ModelViewer { return sel; } + public RenderUnit rayPickUnit(final float x, final float y) { + final float[] ray = rayHeap; + mousePosHeap.set(x, y); + this.worldScene.camera.screenToWorldRay(ray, mousePosHeap); + final Vector3 dir = normalHeap; + dir.x = ray[3] - ray[0]; + dir.y = ray[4] - ray[1]; + dir.z = ray[5] - ray[2]; + dir.nor(); + // TODO good performance, do not create vectors on every check + final Vector3 eMid = new Vector3(); + final Vector3 eSize = new Vector3(); + final Vector3 rDir = new Vector3(); + + RenderUnit entity = null; + float entDist = 1e6f; + + for (final RenderUnit unit : this.units) { + final float radius = unit.radius; + final float[] location = unit.location; + final MdxComplexInstance instance = unit.instance; + eMid.set(0, 0, radius / 2); + eSize.set(radius, radius, radius); + + eMid.add(location[0], location[1], location[2]); + eMid.sub(ray[0], ray[1], ray[2]); + eMid.scl(1 / eSize.x, 1 / eSize.y, 1 / eSize.z); + rDir.x = dir.x / eSize.x; + rDir.y = dir.y / eSize.y; + rDir.z = dir.z / eSize.z; + final float dlen = rDir.len2(); + final float dp = Math.max(0, rDir.dot(eMid)) / dlen; + if (dp > entDist) { + continue; + } + rDir.scl(dp); + if (rDir.dst2(eMid) < 1.0) { + entity = unit; + entDist = dp; + } + } + return entity; + } + private static final class MappedDataCallbackImplementation implements LoadGenericCallback { @Override public Object call(final InputStream data) { @@ -761,4 +879,73 @@ public class War3MapViewer extends ModelViewer { return (numElements < 0) ? 1 : (numElements >= MAXIMUM_ACCEPTED) ? MAXIMUM_ACCEPTED : numElements + 1; } + public boolean orderSmart(final float x, final float y) { + mousePosHeap.x = x; + mousePosHeap.y = y; + boolean ordered = false; + for (final RenderUnit unit : this.selected) { + for (final CAbility ability : unit.getSimulationUnit().getAbilities()) { + if (ability instanceof CAbilityMove) { + ability.checkCanUse(this.simulation, unit.getSimulationUnit(), + BooleanAbilityActivationReceiver.INSTANCE); + if (BooleanAbilityActivationReceiver.INSTANCE.isOk()) { + ability.checkCanTarget(this.simulation, unit.getSimulationUnit(), mousePosHeap, + PointAbilityTargetCheckReceiver.INSTANCE); + final Vector2 target = PointAbilityTargetCheckReceiver.INSTANCE.getTarget(); + if (target != null) { + ability.onOrder(this.simulation, unit.getSimulationUnit(), mousePosHeap, false); + unit.soundset.yes.play(this.worldScene.audioContext, unit.location[0], unit.location[1]); + ordered = true; + } + else { + System.err.println("Target not valid."); + } + } + else { + System.err.println("Ability not ok to use."); + } + } + else { + System.err.println("Ability not move."); + } + } + + } + return ordered; + } + + public boolean orderSmart(final RenderUnit target) { + boolean ordered = false; + for (final RenderUnit unit : this.selected) { + for (final CAbility ability : unit.getSimulationUnit().getAbilities()) { + if (ability instanceof CAbilityAttack) { + ability.checkCanUse(this.simulation, unit.getSimulationUnit(), + BooleanAbilityActivationReceiver.INSTANCE); + if (BooleanAbilityActivationReceiver.INSTANCE.isOk()) { + ability.checkCanTarget(this.simulation, unit.getSimulationUnit(), target.getSimulationUnit(), + CWidgetAbilityTargetCheckReceiver.INSTANCE); + final CWidget targetWidget = CWidgetAbilityTargetCheckReceiver.INSTANCE.getTarget(); + if (targetWidget != null) { + ability.onOrder(this.simulation, unit.getSimulationUnit(), targetWidget, false); + unit.soundset.yesAttack.play(this.worldScene.audioContext, unit.location[0], + unit.location[1]); + ordered = true; + } + else { + System.err.println("Target not valid."); + } + } + else { + System.err.println("Ability not ok to use."); + } + } + else { + System.err.println("Ability not move."); + } + } + + } + return ordered; + } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/RenderCorner.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/RenderCorner.java index be3239c..2dc35fa 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/RenderCorner.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/RenderCorner.java @@ -5,6 +5,7 @@ import com.etheller.warsmash.parsers.w3x.w3e.Corner; public class RenderCorner extends Corner { public boolean cliff; public boolean romp; + public float rampAdjust; public RenderCorner(final Corner corner) { super(corner); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java index a579617..e1b9cc6 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java @@ -11,6 +11,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.TreeSet; +import java.util.function.Consumer; import javax.imageio.ImageIO; @@ -41,6 +42,7 @@ import com.etheller.warsmash.viewer5.RawOpenGLTextureResource; import com.etheller.warsmash.viewer5.Texture; import com.etheller.warsmash.viewer5.gl.WebGL; import com.etheller.warsmash.viewer5.handlers.w3x.SplatModel; +import com.etheller.warsmash.viewer5.handlers.w3x.SplatModel.SplatMover; import com.etheller.warsmash.viewer5.handlers.w3x.Variations; import com.etheller.warsmash.viewer5.handlers.w3x.W3xShaders; import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; @@ -93,6 +95,7 @@ public class Terrain { private int groundTextureData = -1; private final int groundHeight; private final int groundCornerHeight; + private final int groundCornerHeightLinear; private final int cliffTextureArray; private final int waterHeight; private final int waterExists; @@ -282,6 +285,16 @@ public class Terrain { gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_S, GL30.GL_CLAMP_TO_EDGE); gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_T, GL30.GL_CLAMP_TO_EDGE); + this.groundCornerHeightLinear = gl.glGenTexture(); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeightLinear); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_LINEAR); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_LINEAR); + + gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_R16F, width, height, 0, GL30.GL_RED, GL30.GL_FLOAT, + RenderMathUtils.wrap(this.groundCornerHeights)); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_S, GL30.GL_CLAMP_TO_EDGE); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_T, GL30.GL_CLAMP_TO_EDGE); + // Cliff this.cliffTextureArray = gl.glGenTexture(); gl.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.cliffTextureArray); @@ -421,6 +434,53 @@ public class Terrain { RenderMathUtils.wrap(this.groundCornerHeights)); } + /** + * calculateRamps() is copied from Riv whereas a lot of the rest of the terrain + * was copied from HiveWE + */ + private void calculateRamps() { + final int columns = this.mapSize[0]; + final int rows = this.mapSize[1]; + + final String[] ramps = { "AAHL", "AALH", "ABHL", "AHLA", "ALHA", "ALHB", "BALH", "BHLA", "HAAL", "HBAL", "HLAA", + "HLAB", "LAAH", "LABH", "LHAA", "LHBA" }; + + // Adjust terrain height inside ramps (set rampAdjust) + for (int y = 1; y < (rows - 1); ++y) { + for (int x = 1; x < (columns - 1); ++x) { + final RenderCorner o = this.corners[x][y]; + if (!o.isRamp()) { + continue; + } + final RenderCorner a = this.corners[x - 1][y - 1]; + final RenderCorner b = this.corners[x - 1][y]; + final RenderCorner c = this.corners[x - 1][y + 1]; + final RenderCorner d = this.corners[x][y + 1]; + final RenderCorner e = this.corners[x + 1][y + 1]; + final RenderCorner f = this.corners[x + 1][y]; + final RenderCorner g = this.corners[x + 1][y - 1]; + final RenderCorner h = this.corners[x][y - 1]; + final int base = o.getLayerHeight(); + if ((b.isRamp() && f.isRamp()) || (d.isRamp() && h.isRamp())) { + float adjust = 0; + if (b.isRamp() && f.isRamp()) { + adjust = Math.max(adjust, ((b.getLayerHeight() + f.getLayerHeight()) / 2) - base); + } + if (d.isRamp() && h.isRamp()) { + adjust = Math.max(adjust, ((d.getLayerHeight() + h.getLayerHeight()) / 2) - base); + } + if (a.isRamp() && e.isRamp()) { + adjust = Math.max(adjust, (((a.getLayerHeight() + e.getLayerHeight()) / 2) - base) / 2); + } + if (c.isRamp() && g.isRamp()) { + adjust = Math.max(adjust, (((c.getLayerHeight() + g.getLayerHeight()) / 2) - base) / 2); + } + o.rampAdjust = adjust; + } + } + } + } + /// TODO clean /// Function is a bit of a mess /// Updates the cliff and ramp meshes for an area @@ -448,12 +508,6 @@ public class Terrain { for (int i = (int) rampArea.getX(); i < xLimit; i++) { final int yLimit = (int) ((rampArea.getY() + rampArea.getHeight()) - 1); for (int j = (int) rampArea.getY(); j < yLimit; j++) { - if ((i == (84)) && (j == (82))) { - System.out.println("test"); - } - if ((i == (84)) && (j == (81))) { - System.out.println("test"); - } final RenderCorner bottomLeft = this.corners[i][j]; final RenderCorner bottomRight = this.corners[i + 1][j]; final RenderCorner topLeft = this.corners[i][j + 1]; @@ -560,6 +614,12 @@ public class Terrain { } + public void logRomp(final int x, final int y) { + System.out.println("romp: " + this.corners[x][y].romp); + System.out.println("ramp: " + this.corners[x][y].isRamp()); + System.out.println("cliff: " + this.corners[x][y].cliff); + } + private void updateGroundTextures(final Rectangle area) { final Rectangle adjusted = new Rectangle(area.x - 1, area.y - 1, area.width + 2, area.height + 2); final Rectangle updateArea = new Rectangle(); @@ -812,7 +872,7 @@ public class Terrain { shader.setUniformi("u_shadowMap", 2); gl.glActiveTexture(GL30.GL_TEXTURE0); - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeight); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeightLinear); gl.glActiveTexture(GL30.GL_TEXTURE2); gl.glBindTexture(GL30.GL_TEXTURE_2D, this.shadowMap); @@ -884,6 +944,7 @@ public class Terrain { this.webGL.useShaderProgram(this.cliffShader); final GL30 gl = Gdx.gl30; + gl.glDisable(GL30.GL_BLEND); // WC3 models are 128x too large tempMatrix.set(this.camera.viewProjectionMatrix); @@ -1117,9 +1178,9 @@ public class Terrain { if ((cellX >= 0) && (cellX < (this.mapSize[0] - 1)) && (cellY >= 0) && (cellY < (this.mapSize[1] - 1))) { final float bottomLeft = this.corners[cellX][cellY].computeFinalGroundHeight(); - final float bottomRight = this.corners[cellX][cellY].computeFinalGroundHeight(); - final float topLeft = this.corners[cellX][cellY].computeFinalGroundHeight(); - final float topRight = this.corners[cellX][cellY].computeFinalGroundHeight(); + final float bottomRight = this.corners[cellX + 1][cellY].computeFinalGroundHeight(); + final float topLeft = this.corners[cellX][cellY + 1].computeFinalGroundHeight(); + final float topRight = this.corners[cellX + 1][cellY + 1].computeFinalGroundHeight(); final float sqX = userCellSpaceX - cellX; final float sqY = userCellSpaceY - cellY; float height; @@ -1139,6 +1200,7 @@ public class Terrain { public static final class Splat { public List locations = new ArrayList<>(); + public List> unitMapping = new ArrayList<>(); public float opacity = 1; } @@ -1148,7 +1210,8 @@ public class Terrain { final Splat splat = entry.getValue(); final SplatModel splatModel = new SplatModel(Gdx.gl30, - (Texture) this.viewer.load(path, PathSolver.DEFAULT, null), splat.locations, this.centerOffset); + (Texture) this.viewer.load(path, PathSolver.DEFAULT, null), splat.locations, this.centerOffset, + splat.unitMapping); splatModel.color[3] = splat.opacity; this.uberSplatModels.add(splatModel); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/CommandCardIcon.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/CommandCardIcon.java new file mode 100644 index 0000000..4d702a6 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/CommandCardIcon.java @@ -0,0 +1,33 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.rendersim; + +import com.badlogic.gdx.graphics.Texture; + +public class CommandCardIcon { + private final int x; + private final int y; + private final Texture texture; + private final int orderId; + + public CommandCardIcon(final int x, final int y, final Texture texture, final int orderId) { + this.x = x; + this.y = y; + this.texture = texture; + this.orderId = orderId; + } + + public int getX() { + return this.x; + } + + public int getY() { + return this.y; + } + + public Texture getTexture() { + return this.texture; + } + + public int getOrderId() { + return this.orderId; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Unit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderItem.java similarity index 58% rename from core/src/com/etheller/warsmash/viewer5/handlers/w3x/Unit.java rename to core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderItem.java index f6350f9..f90d0e1 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Unit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderItem.java @@ -1,25 +1,19 @@ -package com.etheller.warsmash.viewer5.handlers.w3x; +package com.etheller.warsmash.viewer5.handlers.w3x.rendersim; import com.badlogic.gdx.math.Quaternion; import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; -import com.etheller.warsmash.units.manager.MutableObjectData.WorldEditorDataType; import com.etheller.warsmash.util.RenderMathUtils; import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; +import com.etheller.warsmash.viewer5.handlers.w3x.UnitSoundset; +import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; -public class Unit { - private static final War3ID IS_BLDG = War3ID.fromString("ubdg"); - private static final War3ID RED = War3ID.fromString("uclr"); - private static final War3ID GREEN = War3ID.fromString("uclg"); - private static final War3ID BLUE = War3ID.fromString("uclb"); - private static final War3ID MODEL_SCALE = War3ID.fromString("usca"); - private static final War3ID MOVE_HEIGHT = War3ID.fromString("umvh"); +public class RenderItem { private static final War3ID ITEM_MODEL_SCALE = War3ID.fromString("isca"); private static final War3ID ITEM_RED = War3ID.fromString("iclr"); private static final War3ID ITEM_GREEN = War3ID.fromString("iclg"); private static final War3ID ITEM_BLUE = War3ID.fromString("iclb"); - private static final float[] heapZ = new float[3]; public final MdxComplexInstance instance; public final MutableGameObject row; public final float[] location = new float[3]; @@ -28,22 +22,16 @@ public class Unit { public final MdxModel portraitModel; public int playerIndex; - public Unit(final War3MapViewer map, final MdxModel model, final MutableGameObject row, - final com.etheller.warsmash.parsers.w3x.unitsdoo.Unit unit, final WorldEditorDataType type, - final UnitSoundset soundset, final MdxModel portraitModel) { + public RenderItem(final War3MapViewer map, final MdxModel model, final MutableGameObject row, + final com.etheller.warsmash.parsers.w3x.unitsdoo.Unit unit, final UnitSoundset soundset, + final MdxModel portraitModel) { this.portraitModel = portraitModel; final MdxComplexInstance instance = (MdxComplexInstance) model.addInstance(); final float[] location = unit.getLocation(); System.arraycopy(location, 0, this.location, 0, 3); instance.move(location); - float angle; - if ((row != null) && row.getFieldAsBoolean(IS_BLDG, 0)) { - angle = (float) Math.toRadians(270.0f); - } - else { - angle = unit.getAngle(); - } + final float angle = unit.getAngle(); // instance.localRotation.setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, angle); instance.rotate(new Quaternion().setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, angle)); instance.scale(unit.getScale()); @@ -52,31 +40,19 @@ public class Unit { instance.setScene(map.worldScene); if (row != null) { - heapZ[2] = row.getFieldAsFloat(MOVE_HEIGHT, 0); - this.location[2] += heapZ[2]; - - instance.move(heapZ); War3ID red; War3ID green; War3ID blue; War3ID scale; - if (type == WorldEditorDataType.UNITS) { - scale = MODEL_SCALE; - red = RED; - green = GREEN; - blue = BLUE; - } - else { - scale = ITEM_MODEL_SCALE; - red = ITEM_RED; - green = ITEM_GREEN; - blue = ITEM_BLUE; - } + scale = ITEM_MODEL_SCALE; + red = ITEM_RED; + green = ITEM_GREEN; + blue = ITEM_BLUE; instance.setVertexColor(new float[] { (row.getFieldAsInteger(red, 0)) / 255f, (row.getFieldAsInteger(green, 0)) / 255f, (row.getFieldAsInteger(blue, 0)) / 255f }); instance.uniformScale(row.getFieldAsFloat(scale, 0)); - this.radius = row.getFieldAsFloat(War3MapViewer.UNIT_SELECT_SCALE, 0) * 36; + this.radius = 1 * 36; } this.instance = instance; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java new file mode 100644 index 0000000..018be6c --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java @@ -0,0 +1,164 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.rendersim; + +import java.util.ArrayList; +import java.util.List; + +import com.badlogic.gdx.math.Quaternion; +import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; +import com.etheller.warsmash.util.ImageUtils; +import com.etheller.warsmash.util.RenderMathUtils; +import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; +import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; +import com.etheller.warsmash.viewer5.handlers.w3x.SplatModel.SplatMover; +import com.etheller.warsmash.viewer5.handlers.w3x.StandSequence; +import com.etheller.warsmash.viewer5.handlers.w3x.UnitSoundset; +import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.COrder; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityAttack; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityHoldPosition; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityMove; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityPatrol; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityStop; + +public class RenderUnit { + private static final Quaternion tempQuat = new Quaternion(); + private static final War3ID RED = War3ID.fromString("uclr"); + private static final War3ID GREEN = War3ID.fromString("uclg"); + private static final War3ID BLUE = War3ID.fromString("uclb"); + private static final War3ID MODEL_SCALE = War3ID.fromString("usca"); + private static final War3ID MOVE_HEIGHT = War3ID.fromString("umvh"); + private static final float[] heapZ = new float[3]; + public final MdxComplexInstance instance; + public final MutableGameObject row; + public final float[] location = new float[3]; + public float radius; + public UnitSoundset soundset; + public final MdxModel portraitModel; + public int playerIndex; + private final CUnit simulationUnit; + private COrder lastOrder; + private String lastOrderAnimation; + private float flyingHeight = 0; + public SplatMover shadow; + public SplatMover selectionCircle; + private final List commandCardIcons = new ArrayList<>(); + + public RenderUnit(final War3MapViewer map, final MdxModel model, final MutableGameObject row, + final com.etheller.warsmash.parsers.w3x.unitsdoo.Unit unit, final UnitSoundset soundset, + final MdxModel portraitModel, final CUnit simulationUnit) { + this.portraitModel = portraitModel; + this.simulationUnit = simulationUnit; + final MdxComplexInstance instance = (MdxComplexInstance) model.addInstance(); + + final float[] location = unit.getLocation(); + System.arraycopy(location, 0, this.location, 0, 3); + instance.move(location); + final float angle = (float) Math.toRadians(simulationUnit.getFacing()); +// instance.localRotation.setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, angle); + instance.rotate(tempQuat.setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, angle)); + instance.scale(unit.getScale()); + this.playerIndex = unit.getPlayer(); + instance.setTeamColor(this.playerIndex); + instance.setScene(map.worldScene); + + if (row != null) { + heapZ[2] = this.flyingHeight = row.getFieldAsFloat(MOVE_HEIGHT, 0); + this.location[2] += heapZ[2]; + + instance.move(heapZ); + War3ID red; + War3ID green; + War3ID blue; + War3ID scale; + scale = MODEL_SCALE; + red = RED; + green = GREEN; + blue = BLUE; + instance.setVertexColor(new float[] { (row.getFieldAsInteger(red, 0)) / 255f, + (row.getFieldAsInteger(green, 0)) / 255f, (row.getFieldAsInteger(blue, 0)) / 255f }); + instance.uniformScale(row.getFieldAsFloat(scale, 0)); + + this.radius = row.getFieldAsFloat(War3MapViewer.UNIT_SELECT_SCALE, 0) * 36; + } + + this.instance = instance; + this.row = row; + this.soundset = soundset; + + for (final CAbility ability : simulationUnit.getAbilities()) { + if (ability instanceof CAbilityMove) { + this.commandCardIcons.add(new CommandCardIcon(0, 0, + ImageUtils.getBLPTexture(map.dataSource, "ReplaceableTextures\\CommandButtons\\BTNMove.blp"), + ability.getOrderId())); + } + else if (ability instanceof CAbilityAttack) { + this.commandCardIcons.add(new CommandCardIcon(4, 0, + ImageUtils.getBLPTexture(map.dataSource, "ReplaceableTextures\\CommandButtons\\BTNAttack.blp"), + ability.getOrderId())); + } + else if (ability instanceof CAbilityHoldPosition) { + this.commandCardIcons + .add(new CommandCardIcon(2, 0, + ImageUtils.getBLPTexture(map.dataSource, + "ReplaceableTextures\\CommandButtons\\BTNHoldPosition.blp"), + ability.getOrderId())); + } + else if (ability instanceof CAbilityPatrol) { + this.commandCardIcons.add(new CommandCardIcon(3, 0, + ImageUtils.getBLPTexture(map.dataSource, "ReplaceableTextures\\CommandButtons\\BTNPatrol.blp"), + ability.getOrderId())); + } + else if (ability instanceof CAbilityStop) { + this.commandCardIcons.add(new CommandCardIcon(1, 0, + ImageUtils.getBLPTexture(map.dataSource, "ReplaceableTextures\\CommandButtons\\BTNStop.blp"), + ability.getOrderId())); + } + } + } + + public void updateAnimations(final War3MapViewer map) { + final float x = this.simulationUnit.getX(); + final float dx = x - this.location[0]; + this.location[0] = x; + final float y = this.simulationUnit.getY(); + final float dy = y - this.location[1]; + this.location[1] = y; + this.location[2] = this.flyingHeight + map.terrain.getGroundHeight(x, y); + this.instance.moveTo(this.location); + this.instance + .setLocalRotation(tempQuat.setFromAxis(RenderMathUtils.VEC3_UNIT_Z, this.simulationUnit.getFacing())); + map.worldScene.grid.moved(this.instance); + final MdxComplexInstance mdxComplexInstance = this.instance; + final COrder currentOrder = this.simulationUnit.getCurrentOrder(); + if (mdxComplexInstance.sequenceEnded || (mdxComplexInstance.sequence == -1) || (currentOrder != this.lastOrder) + || ((currentOrder != null) && (currentOrder.getAnimationName() != null) + && !currentOrder.getAnimationName().equals(this.lastOrderAnimation))) { + if (this.simulationUnit.getCurrentOrder() != null) { + final String animationName = this.simulationUnit.getCurrentOrder().getAnimationName(); + StandSequence.randomSequence(mdxComplexInstance, animationName); + this.lastOrderAnimation = animationName; + } + else { + StandSequence.randomStandSequence(mdxComplexInstance); + } + } + this.lastOrder = currentOrder; + if (this.shadow != null) { + this.shadow.move(dx, dy); + } + if (this.selectionCircle != null) { + this.selectionCircle.move(dx, dy); + } + } + + public CUnit getSimulationUnit() { + return this.simulationUnit; + } + + public List getCommandCardIcons() { + return this.commandCardIcons; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CDestructable.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CDestructable.java new file mode 100644 index 0000000..a40ec54 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CDestructable.java @@ -0,0 +1,9 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation; + +public class CDestructable extends CWidget { + + public CDestructable(final int handleId, final float x, final float y, final float life) { + super(handleId, x, y, life); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CItem.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CItem.java new file mode 100644 index 0000000..e6b6bff --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CItem.java @@ -0,0 +1,14 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation; + +import com.etheller.warsmash.util.War3ID; + +public class CItem extends CWidget { + + private final War3ID itemType; + + public CItem(final int handleId, final float x, final float y, final float life, final War3ID itemType) { + super(handleId, x, y, life); + this.itemType = itemType; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/COrder.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/COrder.java new file mode 100644 index 0000000..d4e60f2 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/COrder.java @@ -0,0 +1,29 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation; + +public interface COrder { + /** + * Executes one step of game simulation of the current order, returning true if + * the order has completed. Many orders may wrap the move order and spend a + * number of simulation steps moving to get within range of the target point + * before completing. + * + * @return + */ + boolean update(CSimulation game); + + /** + * Gets the Order ID of the order, useful for determining which icon to + * highlight on the unit's command card. + * + * @return + */ + int getOrderId(); + + /** + * Gets the animation name used for visuals. Calling this function should not + * impact the game state of the CSimulation in any way. + * + * @return + */ + String getAnimationName(); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CPlayer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CPlayer.java new file mode 100644 index 0000000..1afe88a --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CPlayer.java @@ -0,0 +1,35 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation; + +public class CPlayer { + private int id; + private int gold; + private int lumber; + + public CPlayer(final int id) { + this.id = id; + } + + public int getId() { + return this.id; + } + + public int getGold() { + return this.gold; + } + + public int getLumber() { + return this.lumber; + } + + public void setId(final int id) { + this.id = id; + } + + public void setGold(final int gold) { + this.gold = gold; + } + + public void setLumber(final int lumber) { + this.lumber = lumber; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CPlayerController.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CPlayerController.java new file mode 100644 index 0000000..5378300 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CPlayerController.java @@ -0,0 +1,9 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation; + +public interface CPlayerController { + boolean issueTargetOrder(int unitHandleId, int orderId, int targetHandleId); + + boolean issuePointOrder(int unitHandleId, int orderId, float x, float y); + + boolean issueImmediateOrder(int unitHandleId, int orderId); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java new file mode 100644 index 0000000..5b1e53a --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java @@ -0,0 +1,47 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation; + +import java.util.ArrayList; +import java.util.List; + +import com.etheller.warsmash.units.manager.MutableObjectData; +import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.data.CAbilityData; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.data.CUnitData; + +public class CSimulation { + private final CUnitData unitData; + private final CAbilityData abilityData; + private final List units; + private final HandleIdAllocator handleIdAllocator; + + public CSimulation(final MutableObjectData parsedUnitData, final MutableObjectData parsedAbilityData) { + this.unitData = new CUnitData(parsedUnitData); + this.abilityData = new CAbilityData(parsedAbilityData); + this.units = new ArrayList<>(); + this.handleIdAllocator = new HandleIdAllocator(); + } + + public CUnitData getUnitData() { + return this.unitData; + } + + public CAbilityData getAbilityData() { + return this.abilityData; + } + + public List getUnits() { + return this.units; + } + + public CUnit createUnit(final War3ID typeId, final float x, final float y, final float facing) { + final CUnit unit = this.unitData.create(this, this.handleIdAllocator.createId(), typeId, x, y, facing); + this.units.add(unit); + return unit; + } + + public void update() { + for (final CUnit unit : this.units) { + unit.update(this); + } + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java new file mode 100644 index 0000000..a867a22 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java @@ -0,0 +1,124 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; + +public class CUnit extends CWidget { + + private War3ID typeId; + private float facing; // degrees + private float mana; + private int maximumLife; + private int maximumMana; + private int speed; + + private final List abilities = new ArrayList<>(); + + private COrder currentOrder; + private final Queue orderQueue = new LinkedList<>(); + + public CUnit(final int handleId, final float x, final float y, final float life, final War3ID typeId, + final float facing, final float mana, final int maximumLife, final int maximumMana, final int speed) { + super(handleId, x, y, life); + this.typeId = typeId; + this.facing = facing; + this.mana = mana; + this.maximumLife = maximumLife; + this.maximumMana = maximumMana; + this.speed = speed; + } + + public void add(final CSimulation simulation, final CAbility ability) { + this.abilities.add(ability); + ability.onAdd(simulation, this); + } + + public War3ID getTypeId() { + return this.typeId; + } + + /** + * @return facing in DEGREES + */ + public float getFacing() { + return this.facing; + } + + public float getMana() { + return this.mana; + } + + public int getMaximumLife() { + return this.maximumLife; + } + + public int getMaximumMana() { + return this.maximumMana; + } + + public void setTypeId(final War3ID typeId) { + this.typeId = typeId; + } + + public void setFacing(final float facing) { + // java modulo output can be negative, but not if we + // force positive and modulo again + this.facing = ((facing % 360) + 360) % 360; + } + + public void setMana(final float mana) { + this.mana = mana; + } + + public void setMaximumLife(final int maximumLife) { + this.maximumLife = maximumLife; + } + + public void setMaximumMana(final int maximumMana) { + this.maximumMana = maximumMana; + } + + public void setSpeed(final int speed) { + this.speed = speed; + } + + public int getSpeed() { + return this.speed; + } + + /** + * Updates one tick of simulation logic. + */ + public void update(final CSimulation game) { + if (this.currentOrder != null) { + if (this.currentOrder.update(game)) { + // remove current order, because it's completed, polling next + // item from order queue + this.currentOrder = this.orderQueue.poll(); + } + } + } + + public void order(final COrder order, final boolean queue) { + if (queue && (this.currentOrder != null)) { + this.orderQueue.add(order); + } + else { + this.currentOrder = order; + } + } + + public COrder getCurrentOrder() { + return this.currentOrder; + } + + public List getAbilities() { + return this.abilities; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWidget.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWidget.java new file mode 100644 index 0000000..2c31a15 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWidget.java @@ -0,0 +1,44 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation; + +public class CWidget { + private final int handleId; + private float x; + private float y; + private float life; + + public CWidget(final int handleId, final float x, final float y, final float life) { + this.handleId = handleId; + this.x = x; + this.y = y; + this.life = life; + } + + public int getHandleId() { + return this.handleId; + } + + public float getX() { + return this.x; + } + + public float getY() { + return this.y; + } + + public float getLife() { + return this.life; + } + + public void setX(final float x) { + this.x = x; + } + + public void setY(final float y) { + this.y = y; + } + + public void setLife(final float life) { + this.life = life; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/HandleIdAllocator.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/HandleIdAllocator.java new file mode 100644 index 0000000..c1be65c --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/HandleIdAllocator.java @@ -0,0 +1,14 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation; + +/** + * This class is not similar to how WC3 allocates handle IDs in any way. + * Changing this would probably be necessary to support TimerUtils madness, + * because I forget how it works but I know it uses subtraction on handle IDs. + */ +public class HandleIdAllocator { + private int next = 3412532; // bogus number + + public int createId() { + return this.next++; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/StringsToExternalizeLater.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/StringsToExternalizeLater.java new file mode 100644 index 0000000..8398643 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/StringsToExternalizeLater.java @@ -0,0 +1,6 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation; + +public class StringsToExternalizeLater { + public static final String MUST_TARGET_POINT = "Must target a point."; + public static final String MUST_TARGET_WIDGET = "Must target a unit with this action."; +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbility.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbility.java new file mode 100644 index 0000000..ac615e7 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbility.java @@ -0,0 +1,22 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities; + +import com.badlogic.gdx.math.Vector2; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; + +public interface CAbility extends CAbilityView { + /* should fire when ability added to unit */ + void onAdd(CSimulation game, CUnit unit); + + /* should fire when ability removed from unit */ + void onRemove(CSimulation game, CUnit unit); + + void onOrder(CSimulation game, CUnit caster, CWidget target, boolean queue); + + void onOrder(CSimulation game, CUnit caster, Vector2 point, boolean queue); + + void onOrderNoTarget(CSimulation game, CUnit caster, boolean queue); + + int getOrderId(); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java new file mode 100644 index 0000000..3968632 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java @@ -0,0 +1,68 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities; + +import com.badlogic.gdx.math.Vector2; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.StringsToExternalizeLater; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.CAttackOrder; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver.TargetType; + +public class CAbilityAttack implements CAbility { + public static final int ORDER_ID = 860000; // fake, later will use WC3 one probably + public static final CAbilityAttack INSTANCE = new CAbilityAttack(); + + @Override + public void checkCanUse(final CSimulation game, final CUnit unit, final AbilityActivationReceiver receiver) { + receiver.useOk(); + } + + @Override + public void checkCanTarget(final CSimulation game, final CUnit unit, final CWidget target, + final AbilityTargetCheckReceiver receiver) { + receiver.targetOk(target); + } + + @Override + public void checkCanTarget(final CSimulation game, final CUnit unit, final Vector2 target, + final AbilityTargetCheckReceiver receiver) { + receiver.mustTargetType(TargetType.UNIT); + } + + @Override + public void checkCanTargetNoTarget(final CSimulation game, final CUnit unit, + final AbilityTargetCheckReceiver receiver) { + receiver.mustTargetType(TargetType.UNIT); + } + + @Override + public void onAdd(final CSimulation game, final CUnit unit) { + } + + @Override + public void onRemove(final CSimulation game, final CUnit unit) { + } + + @Override + public void onOrder(final CSimulation game, final CUnit caster, final CWidget target, final boolean queue) { + caster.order(new CAttackOrder(caster, target), queue); + } + + @Override + public void onOrder(final CSimulation game, final CUnit caster, final Vector2 target, final boolean queue) { + throw new IllegalArgumentException(StringsToExternalizeLater.MUST_TARGET_WIDGET); + } + + @Override + public void onOrderNoTarget(final CSimulation game, final CUnit caster, final boolean queue) { + throw new IllegalArgumentException(StringsToExternalizeLater.MUST_TARGET_WIDGET); + } + + @Override + public int getOrderId() { + return ORDER_ID; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityHoldPosition.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityHoldPosition.java new file mode 100644 index 0000000..0ede105 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityHoldPosition.java @@ -0,0 +1,69 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities; + +import com.badlogic.gdx.math.Vector2; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.CDoNothingOrder; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver.TargetType; + +public class CAbilityHoldPosition implements CAbility { + public static final int ORDER_ID = 860002; // fake, later will use WC3 one probably + public static CAbilityHoldPosition INSTANCE = new CAbilityHoldPosition(); + + @Override + public void checkCanUse(final CSimulation game, final CUnit unit, final AbilityActivationReceiver receiver) { + receiver.useOk(); + } + + @Override + public void checkCanTarget(final CSimulation game, final CUnit unit, final CWidget target, + final AbilityTargetCheckReceiver receiver) { + receiver.mustTargetType(TargetType.NO_TARGET); + } + + @Override + public void checkCanTarget(final CSimulation game, final CUnit unit, final Vector2 target, + final AbilityTargetCheckReceiver receiver) { + receiver.mustTargetType(TargetType.NO_TARGET); + } + + @Override + public void checkCanTargetNoTarget(final CSimulation game, final CUnit unit, + final AbilityTargetCheckReceiver receiver) { + receiver.targetOk(null); + } + + @Override + public void onAdd(final CSimulation game, final CUnit unit) { + + } + + @Override + public void onRemove(final CSimulation game, final CUnit unit) { + + } + + @Override + public void onOrder(final CSimulation game, final CUnit caster, final CWidget target, final boolean queue) { + caster.order(new CDoNothingOrder(getOrderId()), queue); + } + + @Override + public void onOrder(final CSimulation game, final CUnit caster, final Vector2 target, final boolean queue) { + caster.order(new CDoNothingOrder(getOrderId()), queue); + } + + @Override + public void onOrderNoTarget(final CSimulation game, final CUnit caster, final boolean queue) { + caster.order(new CDoNothingOrder(getOrderId()), queue); + } + + @Override + public int getOrderId() { + return ORDER_ID; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityMove.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityMove.java new file mode 100644 index 0000000..2fb4d21 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityMove.java @@ -0,0 +1,70 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities; + +import com.badlogic.gdx.math.Vector2; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.StringsToExternalizeLater; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.CMoveOrder; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver.TargetType; + +public class CAbilityMove implements CAbility { + public static final int ORDER_ID = 859999; // fake, later will use WC3 one probably + public static CAbilityMove INSTANCE = new CAbilityMove(); + + @Override + public void checkCanUse(final CSimulation game, final CUnit unit, final AbilityActivationReceiver receiver) { + receiver.useOk(); + } + + @Override + public void checkCanTarget(final CSimulation game, final CUnit unit, final CWidget target, + final AbilityTargetCheckReceiver receiver) { + receiver.mustTargetType(TargetType.POINT); + } + + @Override + public void checkCanTarget(final CSimulation game, final CUnit unit, final Vector2 target, + final AbilityTargetCheckReceiver receiver) { + receiver.targetOk(target); + } + + @Override + public void checkCanTargetNoTarget(final CSimulation game, final CUnit unit, + final AbilityTargetCheckReceiver receiver) { + receiver.mustTargetType(TargetType.POINT); + } + + @Override + public void onAdd(final CSimulation game, final CUnit unit) { + + } + + @Override + public void onRemove(final CSimulation game, final CUnit unit) { + + } + + @Override + public void onOrder(final CSimulation game, final CUnit caster, final CWidget target, final boolean queue) { + throw new IllegalArgumentException(StringsToExternalizeLater.MUST_TARGET_POINT); + } + + @Override + public void onOrder(final CSimulation game, final CUnit caster, final Vector2 target, final boolean queue) { + caster.order(new CMoveOrder(caster, target.x, target.y), queue); + } + + @Override + public void onOrderNoTarget(final CSimulation game, final CUnit caster, final boolean queue) { + throw new IllegalArgumentException(StringsToExternalizeLater.MUST_TARGET_POINT); + } + + @Override + public int getOrderId() { + return ORDER_ID; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityPatrol.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityPatrol.java new file mode 100644 index 0000000..5d08daa --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityPatrol.java @@ -0,0 +1,70 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities; + +import com.badlogic.gdx.math.Vector2; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.StringsToExternalizeLater; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.CMoveOrder; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver.TargetType; + +public class CAbilityPatrol implements CAbility { + public static final int ORDER_ID = 860001; // fake, later will use WC3 one probably + public static CAbilityPatrol INSTANCE = new CAbilityPatrol(); + + @Override + public void checkCanUse(final CSimulation game, final CUnit unit, final AbilityActivationReceiver receiver) { + receiver.useOk(); + } + + @Override + public void checkCanTarget(final CSimulation game, final CUnit unit, final CWidget target, + final AbilityTargetCheckReceiver receiver) { + receiver.mustTargetType(TargetType.POINT); + } + + @Override + public void checkCanTarget(final CSimulation game, final CUnit unit, final Vector2 target, + final AbilityTargetCheckReceiver receiver) { + receiver.targetOk(target); + } + + @Override + public void checkCanTargetNoTarget(final CSimulation game, final CUnit unit, + final AbilityTargetCheckReceiver receiver) { + receiver.mustTargetType(TargetType.POINT); + } + + @Override + public void onAdd(final CSimulation game, final CUnit unit) { + + } + + @Override + public void onRemove(final CSimulation game, final CUnit unit) { + + } + + @Override + public void onOrder(final CSimulation game, final CUnit caster, final CWidget target, final boolean queue) { + throw new IllegalArgumentException(StringsToExternalizeLater.MUST_TARGET_POINT); + } + + @Override + public void onOrder(final CSimulation game, final CUnit caster, final Vector2 target, final boolean queue) { + caster.order(new CMoveOrder(caster, target.x, target.y), queue); + } + + @Override + public void onOrderNoTarget(final CSimulation game, final CUnit caster, final boolean queue) { + throw new IllegalArgumentException(StringsToExternalizeLater.MUST_TARGET_POINT); + } + + @Override + public int getOrderId() { + return ORDER_ID; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityStop.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityStop.java new file mode 100644 index 0000000..352d47b --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityStop.java @@ -0,0 +1,69 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities; + +import com.badlogic.gdx.math.Vector2; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.CDoNothingOrder; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver.TargetType; + +public class CAbilityStop implements CAbility { + public static final int ORDER_ID = 860003; // fake, later will use WC3 one probably + public static CAbilityStop INSTANCE = new CAbilityStop(); + + @Override + public void checkCanUse(final CSimulation game, final CUnit unit, final AbilityActivationReceiver receiver) { + receiver.useOk(); + } + + @Override + public void checkCanTarget(final CSimulation game, final CUnit unit, final CWidget target, + final AbilityTargetCheckReceiver receiver) { + receiver.mustTargetType(TargetType.NO_TARGET); + } + + @Override + public void checkCanTarget(final CSimulation game, final CUnit unit, final Vector2 target, + final AbilityTargetCheckReceiver receiver) { + receiver.mustTargetType(TargetType.NO_TARGET); + } + + @Override + public void checkCanTargetNoTarget(final CSimulation game, final CUnit unit, + final AbilityTargetCheckReceiver receiver) { + receiver.targetOk(null); + } + + @Override + public void onAdd(final CSimulation game, final CUnit unit) { + + } + + @Override + public void onRemove(final CSimulation game, final CUnit unit) { + + } + + @Override + public void onOrder(final CSimulation game, final CUnit caster, final CWidget target, final boolean queue) { + caster.order(new CDoNothingOrder(getOrderId()), queue); + } + + @Override + public void onOrder(final CSimulation game, final CUnit caster, final Vector2 target, final boolean queue) { + caster.order(new CDoNothingOrder(getOrderId()), queue); + } + + @Override + public void onOrderNoTarget(final CSimulation game, final CUnit caster, final boolean queue) { + caster.order(new CDoNothingOrder(getOrderId()), queue); + } + + @Override + public int getOrderId() { + return ORDER_ID; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityView.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityView.java new file mode 100644 index 0000000..ad90fd0 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityView.java @@ -0,0 +1,19 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities; + +import com.badlogic.gdx.math.Vector2; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; + +public interface CAbilityView { + void checkCanUse(CSimulation game, CUnit unit, AbilityActivationReceiver receiver); + + void checkCanTarget(CSimulation game, CUnit unit, CWidget target, AbilityTargetCheckReceiver receiver); + + void checkCanTarget(CSimulation game, CUnit unit, Vector2 target, AbilityTargetCheckReceiver receiver); + + void checkCanTargetNoTarget(CSimulation game, CUnit unit, AbilityTargetCheckReceiver receiver); + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CAbilityData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CAbilityData.java new file mode 100644 index 0000000..1a4e939 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CAbilityData.java @@ -0,0 +1,11 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.data; + +import com.etheller.warsmash.units.manager.MutableObjectData; + +public class CAbilityData { + private final MutableObjectData abilityData; + + public CAbilityData(final MutableObjectData abilityData) { + this.abilityData = abilityData; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java new file mode 100644 index 0000000..66e393d --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java @@ -0,0 +1,99 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.data; + +import com.etheller.warsmash.units.manager.MutableObjectData; +import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; +import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityAttack; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityHoldPosition; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityMove; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityPatrol; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityStop; + +public class CUnitData { + private static final War3ID MANA_INITIAL_AMOUNT = War3ID.fromString("umpi"); + private static final War3ID MANA_MAXIMUM = War3ID.fromString("umpm"); + private static final War3ID HIT_POINT_MAXIMUM = War3ID.fromString("uhpm"); + private static final War3ID MOVEMENT_SPEED_BASE = War3ID.fromString("umvs"); + private static final War3ID PROPULSION_WINDOW = War3ID.fromString("uprw"); + private static final War3ID TURN_RATE = War3ID.fromString("umvr"); + private static final War3ID IS_BLDG = War3ID.fromString("ubdg"); + private static final War3ID NAME = War3ID.fromString("unam"); + private static final War3ID ATTACK1_DMG_BASE = War3ID.fromString("ua1b"); + private static final War3ID ATTACK1_DMG_DICE = War3ID.fromString("ua1d"); + private static final War3ID ATTACK1_DMG_SIDES_PER_DIE = War3ID.fromString("ua1s"); + private static final War3ID ATTACK2_DMG_BASE = War3ID.fromString("ua2b"); + private static final War3ID ATTACK2_DMG_DICE = War3ID.fromString("ua2d"); + private static final War3ID ATTACK2_DMG_SIDES_PER_DIE = War3ID.fromString("ua2s"); + private static final War3ID DEFENSE = War3ID.fromString("udef"); + private final MutableObjectData unitData; + + public CUnitData(final MutableObjectData unitData) { + this.unitData = unitData; + } + + public CUnit create(final CSimulation simulation, final int handleId, final War3ID typeId, final float x, + final float y, final float facing) { + final MutableGameObject unitType = this.unitData.get(typeId); + final int life = unitType.getFieldAsInteger(HIT_POINT_MAXIMUM, 0); + final int manaInitial = unitType.getFieldAsInteger(MANA_INITIAL_AMOUNT, 0); + final int manaMaximum = unitType.getFieldAsInteger(MANA_MAXIMUM, 0); + final int speed = unitType.getFieldAsInteger(MOVEMENT_SPEED_BASE, 0); + final CUnit unit = new CUnit(handleId, x, y, life, typeId, facing, manaInitial, life, manaMaximum, speed); + if (speed > 0) { + unit.add(simulation, CAbilityMove.INSTANCE); + unit.add(simulation, CAbilityPatrol.INSTANCE); + unit.add(simulation, CAbilityHoldPosition.INSTANCE); + unit.add(simulation, CAbilityStop.INSTANCE); + } + final int dmgDice1 = unitType.getFieldAsInteger(ATTACK1_DMG_DICE, 0); + final int dmgDice2 = unitType.getFieldAsInteger(ATTACK2_DMG_DICE, 0); + if ((dmgDice1 != 0) || (dmgDice2 != 0)) { + unit.add(simulation, CAbilityAttack.INSTANCE); + } + return unit; + } + + public float getPropulsionWindow(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(PROPULSION_WINDOW, 0); + } + + public float getTurnRate(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(TURN_RATE, 0); + } + + public boolean isBuilding(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsBoolean(IS_BLDG, 0); + } + + public String getName(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getName(); + } + + public int getA1MinDamage(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_BASE, 0) + + this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_DICE, 0); + } + + public int getA1MaxDamage(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_BASE, 0) + + (this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_DICE, 0) + * this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_SIDES_PER_DIE, 0)); + } + + public int getA2MinDamage(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_BASE, 0) + + this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_DICE, 0); + } + + public int getA2MaxDamage(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_BASE, 0) + + (this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_DICE, 0) + * this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_SIDES_PER_DIE, 0)); + } + + public int getDefense(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(DEFENSE, 0); + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CAttackOrder.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CAttackOrder.java new file mode 100644 index 0000000..22d54fd --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CAttackOrder.java @@ -0,0 +1,92 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders; + +import com.etheller.warsmash.util.WarsmashConstants; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.COrder; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityAttack; + +public class CAttackOrder implements COrder { + private final CUnit unit; + private boolean wasWithinPropWindow = false; + private final CWidget target; + + public CAttackOrder(final CUnit unit, final CWidget target) { + this.unit = unit; + this.target = target; + } + + @Override + public boolean update(final CSimulation simulation) { + final float prevX = this.unit.getX(); + final float prevY = this.unit.getY(); + final float deltaY = this.target.getY() - prevY; + final float deltaX = this.target.getX() - prevX; + final double goalAngleRad = Math.atan2(deltaY, deltaX); + float goalAngle = (float) Math.toDegrees(goalAngleRad); + if (goalAngle < 0) { + goalAngle += 360; + } + final float facing = this.unit.getFacing(); + float delta = goalAngle - facing; + final float absDelta = Math.abs(delta); + final float propulsionWindow = simulation.getUnitData().getPropulsionWindow(this.unit.getTypeId()); + final float turnRate = simulation.getUnitData().getTurnRate(this.unit.getTypeId()); + final int speed = this.unit.getSpeed(); + + if (delta < -180) { + delta = 360 + delta; + } + if (delta > 180) { + delta = -360 + delta; + } + + if ((absDelta <= 1.0) && (absDelta != 0)) { + this.unit.setFacing(goalAngle); + } + else { + float angleToAdd = ((Math.signum(delta) * turnRate) * WarsmashConstants.SIMULATION_STEP_TIME) * 360; + if (absDelta < Math.abs(angleToAdd)) { + angleToAdd = delta; + } + this.unit.setFacing(facing + angleToAdd); + } + if (absDelta < propulsionWindow) { + final float speedTick = speed * WarsmashConstants.SIMULATION_STEP_TIME; + final float speedTickSq = speedTick * speedTick; + + if (((deltaX * deltaX) + (deltaY * deltaY)) <= speedTickSq) { + this.unit.setX(this.target.getX()); + this.unit.setY(this.target.getY()); + return true; + } + else { + this.unit.setX(prevX + (float) (Math.cos(goalAngleRad) * speedTick)); + this.unit.setY(prevY + (float) (Math.sin(goalAngleRad) * speedTick)); + } + this.wasWithinPropWindow = true; + } + else { + // If this happens, the unit is facing the wrong way, and has to turn before + // moving. + this.wasWithinPropWindow = false; + } + + return false; + } + + @Override + public int getOrderId() { + return CAbilityAttack.ORDER_ID; + } + + @Override + public String getAnimationName() { + if (!this.wasWithinPropWindow) { + return "stand"; + } + return "walk"; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CDoNothingOrder.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CDoNothingOrder.java new file mode 100644 index 0000000..230a6c1 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CDoNothingOrder.java @@ -0,0 +1,28 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.COrder; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; + +public class CDoNothingOrder implements COrder { + private final int orderId; + + public CDoNothingOrder(final int orderId) { + this.orderId = orderId; + } + + @Override + public boolean update(final CSimulation game) { + return true; + } + + @Override + public int getOrderId() { + return this.orderId; + } + + @Override + public String getAnimationName() { + return "stand"; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CMoveOrder.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CMoveOrder.java new file mode 100644 index 0000000..465e825 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CMoveOrder.java @@ -0,0 +1,93 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders; + +import com.etheller.warsmash.util.WarsmashConstants; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.COrder; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityMove; + +public class CMoveOrder implements COrder { + private final CUnit unit; + private final float targetX; + private final float targetY; + private boolean wasWithinPropWindow = false; + + public CMoveOrder(final CUnit unit, final float targetX, final float targetY) { + this.unit = unit; + this.targetX = targetX; + this.targetY = targetY; + } + + @Override + public boolean update(final CSimulation simulation) { + final float prevX = this.unit.getX(); + final float prevY = this.unit.getY(); + final float deltaY = this.targetY - prevY; + final float deltaX = this.targetX - prevX; + final double goalAngleRad = Math.atan2(deltaY, deltaX); + float goalAngle = (float) Math.toDegrees(goalAngleRad); + if (goalAngle < 0) { + goalAngle += 360; + } + final float facing = this.unit.getFacing(); + float delta = goalAngle - facing; + final float absDelta = Math.abs(delta); + final float propulsionWindow = simulation.getUnitData().getPropulsionWindow(this.unit.getTypeId()); + final float turnRate = simulation.getUnitData().getTurnRate(this.unit.getTypeId()); + final int speed = this.unit.getSpeed(); + + if (delta < -180) { + delta = 360 + delta; + } + if (delta > 180) { + delta = -360 + delta; + } + + if ((absDelta <= 1.0) && (absDelta != 0)) { + this.unit.setFacing(goalAngle); + } + else { + float angleToAdd = ((Math.signum(delta) * turnRate) * WarsmashConstants.SIMULATION_STEP_TIME) * 360; + if (absDelta < Math.abs(angleToAdd)) { + angleToAdd = delta; + } + this.unit.setFacing(facing + angleToAdd); + } + if (absDelta < propulsionWindow) { + final float speedTick = speed * WarsmashConstants.SIMULATION_STEP_TIME; + final float speedTickSq = speedTick * speedTick; + + if (((deltaX * deltaX) + (deltaY * deltaY)) <= speedTickSq) { + this.unit.setX(this.targetX); + this.unit.setY(this.targetY); + return true; + } + else { + this.unit.setX(prevX + (float) (Math.cos(goalAngleRad) * speedTick)); + this.unit.setY(prevY + (float) (Math.sin(goalAngleRad) * speedTick)); + } + this.wasWithinPropWindow = true; + } + else { + // If this happens, the unit is facing the wrong way, and has to turn before + // moving. + this.wasWithinPropWindow = false; + } + + return false; + } + + @Override + public int getOrderId() { + return CAbilityMove.ORDER_ID; + } + + @Override + public String getAnimationName() { + if (!this.wasWithinPropWindow) { + return "stand"; + } + return "walk"; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/AbilityActivationReceiver.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/AbilityActivationReceiver.java new file mode 100644 index 0000000..242d59a --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/AbilityActivationReceiver.java @@ -0,0 +1,15 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.util; + +public interface AbilityActivationReceiver { + void useOk(); + + void notEnoughResources(ResourceType resource, int amount); + + void notAnActiveAbility(); + + void missingRequirement(String name); + + void casterMovementDisabled(); + + void cargoCapacityUnavailable(); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/AbilityTargetCheckReceiver.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/AbilityTargetCheckReceiver.java new file mode 100644 index 0000000..0faccd5 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/AbilityTargetCheckReceiver.java @@ -0,0 +1,32 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.util; + +public interface AbilityTargetCheckReceiver { + void targetOk(TARGET_TYPE target); + + void mustTargetTeamType(TeamType correctType); + + void mustTargetType(TargetType correctType); + + void targetOutsideRange(double howMuch); + + void notAnActiveAbility(); + + void targetNotVisible(); + + void targetTooComplicated(); + + void targetNotInPlayableMap(); + + public static enum TeamType { + ALLIED, + ENEMY, + PLAYER_UNITS; + } + + public static enum TargetType { + UNIT, + POINT, + UNIT_OR_POINT, + NO_TARGET, + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/BooleanAbilityActivationReceiver.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/BooleanAbilityActivationReceiver.java new file mode 100644 index 0000000..8bca4f9 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/BooleanAbilityActivationReceiver.java @@ -0,0 +1,41 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.util; + +public class BooleanAbilityActivationReceiver implements AbilityActivationReceiver { + public static final BooleanAbilityActivationReceiver INSTANCE = new BooleanAbilityActivationReceiver(); + private boolean ok; + + @Override + public void useOk() { + this.ok = true; + } + + @Override + public void notEnoughResources(final ResourceType resource, final int amount) { + this.ok = false; + } + + @Override + public void notAnActiveAbility() { + this.ok = false; + } + + @Override + public void missingRequirement(final String name) { + this.ok = false; + } + + @Override + public void casterMovementDisabled() { + this.ok = false; + } + + @Override + public void cargoCapacityUnavailable() { + this.ok = false; + } + + public boolean isOk() { + return this.ok; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/CWidgetAbilityTargetCheckReceiver.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/CWidgetAbilityTargetCheckReceiver.java new file mode 100644 index 0000000..94bce87 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/CWidgetAbilityTargetCheckReceiver.java @@ -0,0 +1,55 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.util; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; + +public class CWidgetAbilityTargetCheckReceiver implements AbilityTargetCheckReceiver { + public static final CWidgetAbilityTargetCheckReceiver INSTANCE = new CWidgetAbilityTargetCheckReceiver(); + + private CWidget target; + + @Override + public void targetOk(final CWidget target) { + this.target = target; + } + + @Override + public void mustTargetTeamType(final TeamType correctType) { + this.target = null; + } + + @Override + public void mustTargetType(final TargetType correctType) { + this.target = null; + + } + + @Override + public void targetOutsideRange(final double howMuch) { + this.target = null; + } + + @Override + public void notAnActiveAbility() { + this.target = null; + } + + @Override + public void targetNotVisible() { + this.target = null; + } + + @Override + public void targetTooComplicated() { + this.target = null; + } + + @Override + public void targetNotInPlayableMap() { + this.target = null; + } + + public CWidget getTarget() { + return this.target; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/PointAbilityTargetCheckReceiver.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/PointAbilityTargetCheckReceiver.java new file mode 100644 index 0000000..6c4721a --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/PointAbilityTargetCheckReceiver.java @@ -0,0 +1,55 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.util; + +import com.badlogic.gdx.math.Vector2; + +public class PointAbilityTargetCheckReceiver implements AbilityTargetCheckReceiver { + public static final PointAbilityTargetCheckReceiver INSTANCE = new PointAbilityTargetCheckReceiver(); + + private Vector2 target; + + @Override + public void targetOk(final Vector2 target) { + this.target = target; + } + + @Override + public void mustTargetTeamType(final TeamType correctType) { + this.target = null; + } + + @Override + public void mustTargetType(final TargetType correctType) { + this.target = null; + + } + + @Override + public void targetOutsideRange(final double howMuch) { + this.target = null; + } + + @Override + public void notAnActiveAbility() { + this.target = null; + } + + @Override + public void targetNotVisible() { + this.target = null; + } + + @Override + public void targetTooComplicated() { + this.target = null; + } + + @Override + public void targetNotInPlayableMap() { + this.target = null; + } + + public Vector2 getTarget() { + return this.target; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/ResourceType.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/ResourceType.java new file mode 100644 index 0000000..dd123b7 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/ResourceType.java @@ -0,0 +1,7 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.util; + +public enum ResourceType { + GOLD, + LUMBER; + +} diff --git a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java index 25d30e5..0b4c958 100644 --- a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java +++ b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java @@ -3,6 +3,7 @@ package com.etheller.warsmash.desktop; import org.lwjgl.opengl.GL31; import org.lwjgl.opengl.GL33; +import com.badlogic.gdx.Graphics.DisplayMode; import com.badlogic.gdx.backends.lwjgl.LwjglApplication; import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration; import com.etheller.warsmash.WarsmashGdxMapGame; @@ -34,10 +35,10 @@ public class DesktopLauncher { config.gles30ContextMajorVersion = 3; config.gles30ContextMinorVersion = 3; config.samples = 16; -// config.fullscreen = true; -// final DisplayMode desktopDisplayMode = LwjglApplicationConfiguration.getDesktopDisplayMode(); -// config.width = desktopDisplayMode.width; -// config.height = desktopDisplayMode.height; + config.fullscreen = false; + final DisplayMode desktopDisplayMode = LwjglApplicationConfiguration.getDesktopDisplayMode(); + config.width = desktopDisplayMode.width; + config.height = desktopDisplayMode.height; new LwjglApplication(new WarsmashGdxMapGame(), config); } } From c4a24934dd9e1e459ab37ee40c6e62e7db13edeb Mon Sep 17 00:00:00 2001 From: Retera Date: Tue, 18 Feb 2020 23:36:24 -0600 Subject: [PATCH 021/116] Milestone after attempted dynamic shadow code. For now I am keeping it in the repo, but disabled, while I move forward towards something else --- .../etheller/warsmash/WarsmashGdxMapGame.java | 45 ++++-- .../w3x/objectdata/MakeMeTFTBeROC.java | 98 ++++++++++++ .../objectdata/Warcraft3MapObjectData.java | 9 +- .../etheller/warsmash/units/DataTable.java | 6 + .../units/manager/MutableObjectData.java | 6 +- .../com/etheller/warsmash/util/SlkFile.java | 3 + .../warsmash/viewer5/ModelInstance.java | 3 +- .../com/etheller/warsmash/viewer5/Scene.java | 26 +++- .../viewer5/gl/DynamicShadowExtension.java | 7 + .../warsmash/viewer5/gl/Extensions.java | 2 + .../viewer5/handlers/mdx/BatchGroup.java | 27 +++- .../viewer5/handlers/mdx/EmitterGroup.java | 10 +- .../viewer5/handlers/mdx/GenericGroup.java | 4 +- .../viewer5/handlers/mdx/GenericObject.java | 8 +- .../warsmash/viewer5/handlers/mdx/Layer.java | 36 +++++ .../handlers/mdx/MdxComplexInstance.java | 16 +- .../viewer5/handlers/mdx/MdxHandler.java | 8 + .../viewer5/handlers/mdx/MdxModel.java | 7 +- .../viewer5/handlers/mdx/MdxRenderBatch.java | 4 + .../viewer5/handlers/mdx/MdxShaders.java | 39 ++++- .../handlers/mdx/MdxSimpleInstance.java | 3 +- .../warsmash/viewer5/handlers/mdx/Sd.java | 2 +- .../viewer5/handlers/tga/TgaHandler.java | 28 ++++ .../viewer5/handlers/tga/TgaTexture.java | 36 +++++ .../handlers/w3x/DynamicShadowManager.java | 139 ++++++++++++++++++ .../viewer5/handlers/w3x/War3MapViewer.java | 9 +- .../handlers/w3x/environment/Terrain.java | 10 +- .../w3x/environment/TerrainShaders.java | 51 ++++--- .../warsmash/desktop/DesktopLauncher.java | 24 ++- 29 files changed, 603 insertions(+), 63 deletions(-) create mode 100644 core/src/com/etheller/warsmash/parsers/w3x/objectdata/MakeMeTFTBeROC.java create mode 100644 core/src/com/etheller/warsmash/viewer5/gl/DynamicShadowExtension.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/tga/TgaHandler.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/tga/TgaTexture.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/DynamicShadowManager.java diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java index 79b5754..70e02ed 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java @@ -81,6 +81,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv private Texture activeButtonTexture; private Rectangle minimap; + private Rectangle minimapFilledArea; private final Texture[] teamColors = new Texture[WarsmashConstants.MAX_PLAYERS]; @@ -104,6 +105,8 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv // "D:\\NEEDS_ORGANIZING\\MPQBuild\\War3.mpq\\war3.mpq"); // final FolderDataSourceDescriptor war3xLocalmpq = new FolderDataSourceDescriptor( // "D:\\NEEDS_ORGANIZING\\MPQBuild\\War3xLocal.mpq\\enus-war3local.mpq"); +// final FolderDataSourceDescriptor rebirth = new FolderDataSourceDescriptor( +// "E:\\Games\\Warcraft III Patch 1.31 Rebirth"); final FolderDataSourceDescriptor testingFolder = new FolderDataSourceDescriptor( "D:\\NEEDS_ORGANIZING\\MPQBuild\\Test"); final FolderDataSourceDescriptor currentFolder = new FolderDataSourceDescriptor("."); @@ -113,7 +116,8 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.viewer = new War3MapViewer(this.codebase, this); try { - this.viewer.loadMap("Maps\\FrozenThrone\\Campaign\\NightElfX03.w3x"); + // "Maps\\Campaign\\NightElf03.w3m" + this.viewer.loadMap("Maps\\Campaign\\NightElf03.w3m"); } catch (final IOException e) { throw new RuntimeException(e); @@ -199,12 +203,21 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv Gdx.input.setInputProcessor(this); final Music music = Gdx.audio - .newMusic(new DataSourceFileHandle(this.viewer.dataSource, "Sound\\Music\\mp3Music\\NightElfX1.mp3")); + .newMusic(new DataSourceFileHandle(this.viewer.dataSource, "Sound\\Music\\mp3Music\\NightElf3.mp3")); music.setVolume(0.2f); music.setLooping(true); music.play(); this.minimap = new Rectangle(35, 7, 305, 272); + final float worldWidth = (this.viewer.terrain.columns - 1); + final float worldHeight = this.viewer.terrain.rows - 1; + final float worldSize = Math.max(worldWidth, worldHeight); + final float minimapFilledWidth = (worldWidth / worldSize) * this.minimap.width; + final float minimapFilledHeight = (worldHeight / worldSize) * this.minimap.height; + + this.minimapFilledArea = new Rectangle(this.minimap.x + ((this.minimap.width - minimapFilledWidth) / 2), + this.minimap.y + ((this.minimap.height - minimapFilledHeight) / 2), minimapFilledWidth, + minimapFilledHeight); this.cameraManager.target.x = this.viewer.startLocations[0].x; this.cameraManager.target.y = this.viewer.startLocations[0].y; @@ -292,12 +305,17 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv } for (final RenderUnit unit : this.viewer.units) { + if (unit.playerIndex >= WarsmashConstants.MAX_PLAYERS) { + System.err.println(unit.row.getName() + " at ( " + unit.location[0] + ", " + unit.location[1] + " )" + + " with " + unit.playerIndex); + unit.playerIndex -= 12; + } final Texture minimapIcon = this.teamColors[unit.playerIndex]; this.batch.draw(minimapIcon, - this.minimap.x + (((unit.location[0] - this.viewer.terrain.centerOffset[0]) - / ((this.viewer.terrain.columns - 1) * 128f)) * this.minimap.width), - this.minimap.y + (((unit.location[1] - this.viewer.terrain.centerOffset[1]) - / ((this.viewer.terrain.rows - 1) * 128f)) * this.minimap.height), + this.minimapFilledArea.x + (((unit.location[0] - this.viewer.terrain.centerOffset[0]) + / ((this.viewer.terrain.columns - 1) * 128f)) * this.minimapFilledArea.width), + this.minimapFilledArea.y + (((unit.location[1] - this.viewer.terrain.centerOffset[1]) + / ((this.viewer.terrain.rows - 1) * 128f)) * this.minimapFilledArea.height), 4, 4); } this.batch.end(); @@ -488,9 +506,9 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv clickLocationTemp2.x = screenX; clickLocationTemp2.y = screenY; this.uiViewport.unproject(clickLocationTemp2); - if (this.minimap.contains(clickLocationTemp2.x, clickLocationTemp2.y)) { - final float clickX = (clickLocationTemp2.x - this.minimap.x) / this.minimap.width; - final float clickY = (clickLocationTemp2.y - this.minimap.y) / this.minimap.height; + if (this.minimapFilledArea.contains(clickLocationTemp2.x, clickLocationTemp2.y)) { + final float clickX = (clickLocationTemp2.x - this.minimapFilledArea.x) / this.minimapFilledArea.width; + final float clickY = (clickLocationTemp2.y - this.minimapFilledArea.y) / this.minimapFilledArea.height; this.cameraManager.target.x = (clickX * this.viewer.terrain.columns * 128) + this.viewer.terrain.centerOffset[0]; this.cameraManager.target.y = (clickY * this.viewer.terrain.rows * 128) @@ -533,6 +551,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.portraitInstance = (MdxComplexInstance) portraitModel.addInstance(); this.portraitInstance.setSequenceLoopMode(1); this.portraitInstance.setScene(this.portraitScene); +// this.portraitInstance.setVertexColor(new float[] { 1, 1, 1, 0.5f }); if (portraitModel.getCameras().size() > 0) { this.portraitCameraManager.modelCamera = portraitModel.getCameras().get(0); } @@ -570,7 +589,13 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv @Override public boolean scrolled(final int amount) { - this.cameraManager.distance += amount * 10.0; + this.cameraManager.verticalAngle -= amount / 10.f; + if (this.cameraManager.verticalAngle > (Math.PI / 2)) { + this.cameraManager.verticalAngle = (float) Math.PI / 2; + } + if (this.cameraManager.verticalAngle < (Math.PI / 5)) { + this.cameraManager.verticalAngle = (float) (Math.PI / 5); + } return true; } } diff --git a/core/src/com/etheller/warsmash/parsers/w3x/objectdata/MakeMeTFTBeROC.java b/core/src/com/etheller/warsmash/parsers/w3x/objectdata/MakeMeTFTBeROC.java new file mode 100644 index 0000000..92a14de --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/w3x/objectdata/MakeMeTFTBeROC.java @@ -0,0 +1,98 @@ +package com.etheller.warsmash.parsers.w3x.objectdata; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Arrays; + +import com.etheller.warsmash.datasources.CompoundDataSourceDescriptor; +import com.etheller.warsmash.datasources.FolderDataSourceDescriptor; +import com.etheller.warsmash.datasources.MpqDataSourceDescriptor; +import com.etheller.warsmash.units.GameObject; +import com.etheller.warsmash.units.ObjectData; +import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; +import com.etheller.warsmash.util.War3ID; +import com.google.common.io.LittleEndianDataOutputStream; + +public class MakeMeTFTBeROC { + + public static void main(final String[] args) { + + try { + final MpqDataSourceDescriptor reignOfChaosData = new MpqDataSourceDescriptor( + "E:\\Games\\Warcraft III Patch 1.14\\war3.mpq"); + final Warcraft3MapObjectData reignOfChaosUnitData = Warcraft3MapObjectData + .load(reignOfChaosData.createDataSource(), true); + + final FolderDataSourceDescriptor tftDesc1 = new FolderDataSourceDescriptor( + "D:\\NEEDS_ORGANIZING\\Reforged Beta 13991\\war3.w3mod"); + final FolderDataSourceDescriptor tftDesc2 = new FolderDataSourceDescriptor( + "D:\\NEEDS_ORGANIZING\\Reforged Beta 13991\\war3.w3mod\\_balance\\custom_v1.w3mod"); + final FolderDataSourceDescriptor tftDesc3 = new FolderDataSourceDescriptor( + "D:\\NEEDS_ORGANIZING\\Reforged Beta 13991\\war3.w3mod\\_locales\\enus.w3mod"); + final CompoundDataSourceDescriptor frozenThroneData = new CompoundDataSourceDescriptor( + Arrays.asList(tftDesc1, tftDesc2, tftDesc3)); + + final Warcraft3MapObjectData frozenThroneUnitData = Warcraft3MapObjectData + .load(frozenThroneData.createDataSource(), true); + for (final War3ID unitId : reignOfChaosUnitData.getUnits().keySet()) { + final MutableGameObject reignOfChaosUnit = reignOfChaosUnitData.getUnits().get(unitId); + final MutableGameObject frozenThroneEquivalentUnit = frozenThroneUnitData.getUnits().get(unitId); + if (frozenThroneEquivalentUnit == null) { + System.err.println("No TFT equivalent for: " + reignOfChaosUnit.getName()); + continue; + } + final ObjectData metaDataSlk = reignOfChaosUnitData.getUnits().getSourceSLKMetaData(); + for (final String fieldTypeId : metaDataSlk.keySet()) { + final War3ID fieldTypeIdCode = War3ID.fromString(fieldTypeId); + final GameObject unitFieldInformation = metaDataSlk.get(fieldTypeId); + if (unitFieldInformation.getFieldValue("useItem") == 1) { + continue; + } + final String fieldType = unitFieldInformation.getField("type"); + switch (fieldType) { + case "int": + frozenThroneEquivalentUnit.setField(fieldTypeIdCode, 0, + reignOfChaosUnit.getFieldAsInteger(fieldTypeIdCode, 0)); + break; + case "real": + case "unreal": + frozenThroneEquivalentUnit.setField(fieldTypeIdCode, 0, + reignOfChaosUnit.getFieldAsFloat(fieldTypeIdCode, 0)); + break; + case "bool": + frozenThroneEquivalentUnit.setField(fieldTypeIdCode, 0, + reignOfChaosUnit.getFieldAsBoolean(fieldTypeIdCode, 0)); + break; + case "string": + case "abilityList": + case "stringList": + case "soundLabel": + case "unitList": + case "itemList": + case "techList": + case "intList": + case "model": + case "char": + case "icon": + frozenThroneEquivalentUnit.setField(fieldTypeIdCode, 0, + reignOfChaosUnit.getFieldAsString(fieldTypeIdCode, 0)); + break; + default: // treat as string + break; + + } + } + } + + try (LittleEndianDataOutputStream outputStream = new LittleEndianDataOutputStream( + new FileOutputStream("C:\\Users\\micro\\OneDrive\\Documents\\Warcraft III\\Data\\roc.w3u"))) { + frozenThroneUnitData.getUnits().getEditorData().save(outputStream, false); + } + + } + catch (final IOException e) { + e.printStackTrace(); + } + } + +} diff --git a/core/src/com/etheller/warsmash/parsers/w3x/objectdata/Warcraft3MapObjectData.java b/core/src/com/etheller/warsmash/parsers/w3x/objectdata/Warcraft3MapObjectData.java index fa288ba..a02449a 100644 --- a/core/src/com/etheller/warsmash/parsers/w3x/objectdata/Warcraft3MapObjectData.java +++ b/core/src/com/etheller/warsmash/parsers/w3x/objectdata/Warcraft3MapObjectData.java @@ -10,6 +10,7 @@ import com.etheller.warsmash.datasources.DataSource; import com.etheller.warsmash.units.DataTable; import com.etheller.warsmash.units.StandardObjectData; import com.etheller.warsmash.units.StandardObjectData.WarcraftData; +import com.etheller.warsmash.units.custom.WTS; import com.etheller.warsmash.units.custom.WTSFile; import com.etheller.warsmash.units.custom.War3ObjectDataChangeset; import com.etheller.warsmash.units.manager.MutableObjectData; @@ -113,7 +114,13 @@ public final class Warcraft3MapObjectData { final War3ObjectDataChangeset buffChangeset = new War3ObjectDataChangeset('h'); final War3ObjectDataChangeset upgradeChangeset = new War3ObjectDataChangeset('q'); - final WTSFile wts = new WTSFile(dataSource.getResourceAsStream("war3map.wts")); + final WTS wts = dataSource.has("war3map.wts") ? new WTSFile(dataSource.getResourceAsStream("war3map.wts")) + : new WTS() { + @Override + public String get(final int key) { + return "TRIGSTR_" + key; + } + }; if (dataSource.has("war3map.w3u")) { unitChangeset.load(new LittleEndianDataInputStream(dataSource.getResourceAsStream("war3map.w3u")), wts, inlineWTS); diff --git a/core/src/com/etheller/warsmash/units/DataTable.java b/core/src/com/etheller/warsmash/units/DataTable.java index 7ec7c66..ae85a0c 100644 --- a/core/src/com/etheller/warsmash/units/DataTable.java +++ b/core/src/com/etheller/warsmash/units/DataTable.java @@ -71,6 +71,9 @@ public class DataTable implements ObjectData { } public void readTXT(final InputStream txt, final boolean canProduce) throws IOException { + if (txt == null) { + return; + } final BufferedReader reader = new BufferedReader(new InputStreamReader(txt, "utf-8")); // BOM marker will only appear on the very beginning reader.mark(4); @@ -135,6 +138,9 @@ public class DataTable implements ObjectData { } public void readSLK(final InputStream txt) throws IOException { + if (txt == null) { + return; + } final BufferedReader reader = new BufferedReader(new InputStreamReader(txt, "utf-8")); String input = ""; diff --git a/core/src/com/etheller/warsmash/units/manager/MutableObjectData.java b/core/src/com/etheller/warsmash/units/manager/MutableObjectData.java index 89f8200..a343452 100644 --- a/core/src/com/etheller/warsmash/units/manager/MutableObjectData.java +++ b/core/src/com/etheller/warsmash/units/manager/MutableObjectData.java @@ -480,10 +480,8 @@ public final class MutableObjectData { if (value.equals(getFieldStringFromSLKs(field, level))) { if (!value.equals(getFieldAsString(field, level))) { fireChangedEvent(field, level); - System.out.println("field was reset"); } else { - System.out.println("field was unmodified"); } resetFieldToDefaults(field, level); return; @@ -491,7 +489,6 @@ public final class MutableObjectData { final Change matchingChange = getOrCreateMatchingChange(field, level); matchingChange.setStrval(value); matchingChange.setVartype(War3ObjectDataChangeset.VAR_TYPE_STRING); - System.out.println("field created change"); fireChangedEvent(field, level); } @@ -886,6 +883,9 @@ public final class MutableObjectData { } private static int asInt(final String text) { + if ("#VALUE!".equals(text)) { + return 0; + } return text == null ? 0 : "".equals(text) ? 0 : "-".equals(text) ? 0 : "_".equals(text) ? 0 : Integer.parseInt(text); } diff --git a/core/src/com/etheller/warsmash/util/SlkFile.java b/core/src/com/etheller/warsmash/util/SlkFile.java index da5e6d6..bebac99 100644 --- a/core/src/com/etheller/warsmash/util/SlkFile.java +++ b/core/src/com/etheller/warsmash/util/SlkFile.java @@ -26,6 +26,9 @@ public class SlkFile { // way if (line.charAt(0) != 'B') { for (final String token : line.split(";")) { + if (token.isEmpty()) { + continue; + } final char op = token.charAt(0); final String valueString = token.substring(1).trim(); final Object value; diff --git a/core/src/com/etheller/warsmash/viewer5/ModelInstance.java b/core/src/com/etheller/warsmash/viewer5/ModelInstance.java index 68c987f..8d07713 100644 --- a/core/src/com/etheller/warsmash/viewer5/ModelInstance.java +++ b/core/src/com/etheller/warsmash/viewer5/ModelInstance.java @@ -1,5 +1,6 @@ package com.etheller.warsmash.viewer5; +import com.badlogic.gdx.math.Matrix4; import com.etheller.warsmash.util.RenderMathUtils; import com.etheller.warsmash.util.Vector4; @@ -117,7 +118,7 @@ public abstract class ModelInstance extends Node { return false; } - public abstract void renderOpaque(); + public abstract void renderOpaque(Matrix4 mvp); public abstract void renderTranslucent(); diff --git a/core/src/com/etheller/warsmash/viewer5/Scene.java b/core/src/com/etheller/warsmash/viewer5/Scene.java index a948129..582d313 100644 --- a/core/src/com/etheller/warsmash/viewer5/Scene.java +++ b/core/src/com/etheller/warsmash/viewer5/Scene.java @@ -7,8 +7,13 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.GL20; +import com.badlogic.gdx.graphics.GL30; +import com.badlogic.gdx.math.Matrix4; import com.badlogic.gdx.math.Rectangle; +import com.etheller.warsmash.viewer5.gl.WebGL; +import com.etheller.warsmash.viewer5.handlers.w3x.DynamicShadowManager; /** * A scene. @@ -44,6 +49,7 @@ public class Scene { public final EmittedObjectUpdater emitterObjectUpdater; public final Map batches; public final Comparator instanceDepthComparator; + public DynamicShadowManager shadowManager; /** * Similar to WebGL's own `alpha` parameter. * @@ -284,10 +290,28 @@ public class Scene { // Render all of the opaque things of non-batched instances. for (final ModelInstance instance : this.instances) { - instance.renderOpaque(); + instance.renderOpaque(this.camera.viewProjectionMatrix); } } + public void renderOpaque(final DynamicShadowManager dynamicShadowManager, final WebGL webGL) { + final Matrix4 depthMatrix = dynamicShadowManager.prepareShadowMatrix(); + dynamicShadowManager.beginShadowMap(webGL); + Gdx.gl30.glDepthMask(true); + Gdx.gl30.glClear(GL20.GL_DEPTH_BUFFER_BIT | GL20.GL_COLOR_BUFFER_BIT); + Gdx.gl30.glDisable(GL30.GL_SCISSOR_TEST); + + // Render all of the opaque things of non-batched instances. +// for (final ModelInstance instance : this.instances) { +// instance.renderOpaque(depthMatrix); +// } + + dynamicShadowManager.endShadowMap(); + final Rectangle viewport = this.camera.rect; + this.viewer.gl.glViewport((int) viewport.x, (int) viewport.y, (int) viewport.width, (int) viewport.height); + Gdx.gl30.glEnable(GL30.GL_SCISSOR_TEST); + } + /** * Renders all translucent things in this scene. Automatically applies the * camera's viewport. diff --git a/core/src/com/etheller/warsmash/viewer5/gl/DynamicShadowExtension.java b/core/src/com/etheller/warsmash/viewer5/gl/DynamicShadowExtension.java new file mode 100644 index 0000000..aeae3c1 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/gl/DynamicShadowExtension.java @@ -0,0 +1,7 @@ +package com.etheller.warsmash.viewer5.gl; + +public interface DynamicShadowExtension { + void glFramebufferTexture(int target, int attachment, int texture, int level); + + void glDrawBuffer(int mode); +} diff --git a/core/src/com/etheller/warsmash/viewer5/gl/Extensions.java b/core/src/com/etheller/warsmash/viewer5/gl/Extensions.java index 9bec9da..37663f3 100644 --- a/core/src/com/etheller/warsmash/viewer5/gl/Extensions.java +++ b/core/src/com/etheller/warsmash/viewer5/gl/Extensions.java @@ -2,4 +2,6 @@ package com.etheller.warsmash.viewer5.gl; public class Extensions { public static ANGLEInstancedArrays angleInstancedArrays; + + public static DynamicShadowExtension dynamicShadowExtension; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/BatchGroup.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/BatchGroup.java index e1506ac..72f23a1 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/BatchGroup.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/BatchGroup.java @@ -4,12 +4,14 @@ import java.util.List; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.glutils.ShaderProgram; +import com.badlogic.gdx.math.Matrix4; import com.etheller.warsmash.util.WarsmashConstants; import com.etheller.warsmash.viewer5.ModelViewer; import com.etheller.warsmash.viewer5.Scene; import com.etheller.warsmash.viewer5.Texture; import com.etheller.warsmash.viewer5.gl.DataTexture; import com.etheller.warsmash.viewer5.gl.WebGL; +import com.etheller.warsmash.viewer5.handlers.w3x.DynamicShadowManager; public class BatchGroup extends GenericGroup { @@ -22,7 +24,7 @@ public class BatchGroup extends GenericGroup { } @Override - public void render(final MdxComplexInstance instance) { + public void render(final MdxComplexInstance instance, final Matrix4 mvp) { final Scene scene = instance.scene; final MdxModel model = this.model; final List textures = model.getTextures(); @@ -36,15 +38,25 @@ public class BatchGroup extends GenericGroup { final ShaderProgram shader; if (isExtended) { - shader = MdxHandler.Shaders.extended; + if (DynamicShadowManager.IS_SHADOW_MAPPING) { + shader = MdxHandler.Shaders.extendedShadowMap; + } + else { + shader = MdxHandler.Shaders.extended; + } } else { - shader = MdxHandler.Shaders.complex; + if (DynamicShadowManager.IS_SHADOW_MAPPING) { + shader = MdxHandler.Shaders.complexShadowMap; + } + else { + shader = MdxHandler.Shaders.complex; + } } webGL.useShaderProgram(shader); - shader.setUniformMatrix("u_mvp", scene.camera.viewProjectionMatrix); + shader.setUniformMatrix("u_mvp", mvp); final DataTexture boneTexture = instance.boneTexture; @@ -89,7 +101,12 @@ public class BatchGroup extends GenericGroup { shader.setUniform2fv("u_uvRot", uvAnim, 2, 2); shader.setUniform1fv("u_uvScale", uvAnim, 4, 1); - layer.bind(shader); + if (instance.vertexColor[3] < 1.0f) { + layer.bindBlended(shader); + } + else { + layer.bind(shader); + } final Integer replaceable = replaceables.get(layerTexture); // TODO is this OK? Texture texture; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EmitterGroup.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EmitterGroup.java index 0ad58ff..b4ed813 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EmitterGroup.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EmitterGroup.java @@ -2,11 +2,13 @@ package com.etheller.warsmash.viewer5.handlers.mdx; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.glutils.ShaderProgram; +import com.badlogic.gdx.math.Matrix4; import com.etheller.warsmash.viewer5.Model; import com.etheller.warsmash.viewer5.ModelViewer; import com.etheller.warsmash.viewer5.Scene; import com.etheller.warsmash.viewer5.SkeletalNode; import com.etheller.warsmash.viewer5.gl.ANGLEInstancedArrays; +import com.etheller.warsmash.viewer5.handlers.w3x.DynamicShadowManager; public class EmitterGroup extends GenericGroup { private final MdxModel model; @@ -16,7 +18,11 @@ public class EmitterGroup extends GenericGroup { } @Override - public void render(final MdxComplexInstance instance) { + public void render(final MdxComplexInstance instance, final Matrix4 mvp) { + if (DynamicShadowManager.IS_SHADOW_MAPPING) { + return; + } + final Scene scene = instance.scene; final SkeletalNode[] nodes = instance.nodes; final Model model = instance.model; @@ -32,7 +38,7 @@ public class EmitterGroup extends GenericGroup { viewer.webGL.useShaderProgram(shader); - shader.setUniformMatrix("u_mvp", scene.camera.viewProjectionMatrix); + shader.setUniformMatrix("u_mvp", mvp); shader.setUniformf("u_texture", 0); final int a_position = shader.getAttributeLocation("a_position"); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GenericGroup.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GenericGroup.java index 69621f5..f4b27e5 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GenericGroup.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GenericGroup.java @@ -3,10 +3,12 @@ package com.etheller.warsmash.viewer5.handlers.mdx; import java.util.ArrayList; import java.util.List; +import com.badlogic.gdx.math.Matrix4; + public abstract class GenericGroup { public final List objects; - public abstract void render(MdxComplexInstance instance); + public abstract void render(MdxComplexInstance instance, Matrix4 mvp); public GenericGroup() { this.objects = new ArrayList<>(); // TODO IntArrayList diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GenericObject.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GenericObject.java index ff04b5e..952a124 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GenericObject.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GenericObject.java @@ -44,9 +44,13 @@ public class GenericObject extends AnimatedObject implements GenericIndexed { this.index = index; this.name = object.getName(); - this.objectId = object.getObjectId(); + int objectId = object.getObjectId(); + if (objectId == -1) { + objectId = index; + } + this.objectId = objectId; int parentId = object.getParentId(); - this.pivot = (this.objectId < model.getPivotPoints().size()) ? model.getPivotPoints().get(this.objectId) + this.pivot = ((this.objectId < model.getPivotPoints().size())) ? model.getPivotPoints().get(this.objectId) : new float[] { 0, 0, 0 }; final int flags = object.getFlags(); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Layer.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Layer.java index 9ae9826..b270535 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Layer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Layer.java @@ -111,6 +111,42 @@ public class Layer extends AnimatedObject { } } + public void bindBlended(final ShaderProgram shader) { + final GL20 gl = this.model.viewer.gl; + + // gl.uniform1f(shader.uniforms.u_unshaded, this.unshaded); + shader.setUniformf("u_filterMode", this.filterMode); + + gl.glEnable(GL20.GL_BLEND); + if ((this.blendSrc == 0) && (this.blendDst == 0)) { + gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA); + } + else { + gl.glBlendFunc(this.blendSrc, this.blendDst); + } + + if (this.twoSided != 0) { + gl.glDisable(GL20.GL_CULL_FACE); + } + else { + gl.glEnable(GL20.GL_CULL_FACE); + } + + if (this.noDepthTest != 0) { + gl.glDisable(GL20.GL_DEPTH_TEST); + } + else { + gl.glEnable(GL20.GL_DEPTH_TEST); + } + + if (this.noDepthSet != 0) { + gl.glDepthMask(false); + } + else { + gl.glDepthMask(this.depthMaskValue); + } + } + public int getAlpha(final float[] out, final int sequence, final int frame, final int counter) { return this.getScalarValue(out, AnimationMap.KMTA.getWar3id(), sequence, frame, counter, this.alpha); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java index 3d61165..2616166 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java @@ -22,6 +22,7 @@ import com.etheller.warsmash.viewer5.Texture; import com.etheller.warsmash.viewer5.TextureMapper; import com.etheller.warsmash.viewer5.UpdatableObject; import com.etheller.warsmash.viewer5.gl.DataTexture; +import com.etheller.warsmash.viewer5.handlers.w3x.DynamicShadowManager; public class MdxComplexInstance extends ModelInstance { private static final float[] visibilityHeap = new float[1]; @@ -267,6 +268,9 @@ public class MdxComplexInstance extends ModelInstance { * children down the hierarchy. */ public void updateNodes(final float dt, final boolean forced) { + if (!this.model.ok) { + return; + } final int sequence = this.sequence; final int frame = this.frame; final int counter = this.counter; @@ -367,6 +371,9 @@ public class MdxComplexInstance extends ModelInstance { final int frame = this.frame; final int counter = this.counter; final MdxModel model = (MdxModel) this.model; + if (!model.ok) { + return; + } final List geosets = model.geosets; final List layers = model.layers; final float[][] geosetColors = this.geosetColors; @@ -486,20 +493,23 @@ public class MdxComplexInstance extends ModelInstance { } @Override - public void renderOpaque() { + public void renderOpaque(final Matrix4 mvp) { final MdxModel model = (MdxModel) this.model; for (final GenericGroup group : model.opaqueGroups) { - group.render(this); + group.render(this, mvp); } } @Override public void renderTranslucent() { + if (DynamicShadowManager.IS_SHADOW_MAPPING) { + return; + } final MdxModel model = (MdxModel) this.model; for (final GenericGroup group : model.translucentGroups) { - group.render(this); + group.render(this, this.scene.camera.viewProjectionMatrix); } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxHandler.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxHandler.java index 77d8da5..ba50313 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxHandler.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxHandler.java @@ -8,6 +8,7 @@ import com.etheller.warsmash.viewer5.ModelViewer; import com.etheller.warsmash.viewer5.handlers.ModelHandler; import com.etheller.warsmash.viewer5.handlers.ResourceHandlerConstructionParams; import com.etheller.warsmash.viewer5.handlers.blp.BlpHandler; +import com.etheller.warsmash.viewer5.handlers.tga.TgaHandler; public class MdxHandler extends ModelHandler { @@ -21,10 +22,15 @@ public class MdxHandler extends ModelHandler { @Override public boolean load(final ModelViewer viewer) { viewer.addHandler(new BlpHandler()); + viewer.addHandler(new TgaHandler()); Shaders.complex = viewer.webGL.createShaderProgram(MdxShaders.vsComplexUnshaded, MdxShaders.fsComplex); Shaders.extended = viewer.webGL.createShaderProgram("#define EXTENDED_BONES\r\n" + MdxShaders.vsComplexUnshaded, MdxShaders.fsComplex); + Shaders.complexShadowMap = viewer.webGL.createShaderProgram(MdxShaders.vsComplexUnshaded, + MdxShaders.fsComplexShadowMap); + Shaders.extendedShadowMap = viewer.webGL.createShaderProgram( + "#define EXTENDED_BONES\r\n" + MdxShaders.vsComplexUnshaded, MdxShaders.fsComplexShadowMap); Shaders.particles = viewer.webGL.createShaderProgram(MdxShaders.vsParticles, MdxShaders.fsParticles); Shaders.simple = viewer.webGL.createShaderProgram(MdxShaders.vsSimple, MdxShaders.fsSimple); // Shaders.hd = viewer.webGL.createShaderProgram(MdxShaders.vsHd, MdxShaders.fsHd); @@ -48,7 +54,9 @@ public class MdxHandler extends ModelHandler { } public static ShaderProgram complex; + public static ShaderProgram complexShadowMap; public static ShaderProgram extended; + public static ShaderProgram extendedShadowMap; public static ShaderProgram simple; public static ShaderProgram particles; public static ShaderProgram hd; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java index 01ce84b..890ff65 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java @@ -252,7 +252,12 @@ public class MdxModel extends com.etheller.warsmash.viewer5.Model { SetupSimpleGroups.setupSimpleGroups(this); // Creates the sorted indices array of the generic objects - this.setupHierarchy(-1); + try { + this.setupHierarchy(-1); + } + catch (final StackOverflowError e) { + System.out.println("bah"); + } // Keep a sorted array. for (int i = 0, l = this.genericObjects.size(); i < l; i++) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxRenderBatch.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxRenderBatch.java index 2a306c3..91f6586 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxRenderBatch.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxRenderBatch.java @@ -16,6 +16,7 @@ import com.etheller.warsmash.viewer5.TextureMapper; import com.etheller.warsmash.viewer5.gl.ANGLEInstancedArrays; import com.etheller.warsmash.viewer5.gl.ClientBuffer; import com.etheller.warsmash.viewer5.gl.WebGL; +import com.etheller.warsmash.viewer5.handlers.w3x.DynamicShadowManager; public class MdxRenderBatch extends RenderBatch { private static final Matrix4 transposeHeap = new Matrix4(); @@ -59,6 +60,9 @@ public class MdxRenderBatch extends RenderBatch { @Override public void render() { + if (DynamicShadowManager.IS_SHADOW_MAPPING) { + return; + } final int count = this.count; if (count != 0) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxShaders.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxShaders.java index 8009bb3..6063d14 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxShaders.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxShaders.java @@ -207,11 +207,45 @@ public class MdxShaders { public static final String fsComplex = Shaders.quatTransform + "\r\n\r\n" + // " uniform sampler2D u_texture;\r\n" + // + " uniform vec4 u_vertexColor;\r\n" + // " uniform float u_filterMode;\r\n" + // " varying vec2 v_uv;\r\n" + // " varying vec4 v_color;\r\n" + // " varying vec4 v_uvTransRot;\r\n" + // " varying float v_uvScale;\r\n" + // + " void main() {\r\n" + // + " vec2 uv = v_uv;\r\n" + // + " // Translation animation\r\n" + // + " uv += v_uvTransRot.xy;\r\n" + // + " // Rotation animation\r\n" + // + " uv = quat_transform(v_uvTransRot.zw, uv - 0.5) + 0.5;\r\n" + // + " // Scale animation\r\n" + // + " uv = v_uvScale * (uv - 0.5) + 0.5;\r\n" + // + " vec4 texel = texture2D(u_texture, uv);\r\n" + // + " vec4 color = texel * v_color;\r\n" + // + " // 1bit Alpha\r\n" + // + " if (u_vertexColor.a == 1.0 && u_filterMode == 1.0 && color.a < 0.75) {\r\n" + // + " discard;\r\n" + // + " }\r\n" + // + " // \"Close to 0 alpha\"\r\n" + // + " if (u_filterMode >= 5.0 && color.a < 0.02) {\r\n" + // + " discard;\r\n" + // + " }\r\n" + // + " // if (!u_unshaded) {\r\n" + // + " // color *= clamp(dot(v_normal, lightDirection) + 0.45, 0.0, 1.0);\r\n" + // + " // }\r\n" + // + " gl_FragColor = color;\r\n" + // + " }"; + + public static final String fsComplexShadowMap = "\r\n\r\n" + // + Shaders.quatTransform + "\r\n\r\n" + // + " uniform sampler2D u_texture;\r\n" + // + " uniform float u_filterMode;\r\n" + // + " varying vec2 v_uv;\r\n" + // + " varying vec4 v_color;\r\n" + // + " varying vec4 v_uvTransRot;\r\n" + // + " varying float v_uvScale;\r\n" + // +// " layout(location = 0) out float fragmentdepth;\r\n" + // " void main() {\r\n" + // " vec2 uv = v_uv;\r\n" + // " // Translation animation\r\n" + // @@ -230,10 +264,7 @@ public class MdxShaders { " if (u_filterMode >= 5.0 && color.a < 0.02) {\r\n" + // " discard;\r\n" + // " }\r\n" + // - " // if (!u_unshaded) {\r\n" + // - " // color *= clamp(dot(v_normal, lightDirection) + 0.45, 0.0, 1.0);\r\n" + // - " // }\r\n" + // - " gl_FragColor = color;\r\n" + // + " gl_FragColor = vec4(0.0, 0, 0, 1.0);//gl_FragCoord.z;\r\n" + // " }"; public static final String vsParticles = "\r\n" + // diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxSimpleInstance.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxSimpleInstance.java index 6377dda..50190e1 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxSimpleInstance.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxSimpleInstance.java @@ -1,5 +1,6 @@ package com.etheller.warsmash.viewer5.handlers.mdx; +import com.badlogic.gdx.math.Matrix4; import com.etheller.warsmash.util.WarsmashConstants; import com.etheller.warsmash.viewer5.BatchedInstance; import com.etheller.warsmash.viewer5.Model; @@ -24,7 +25,7 @@ public class MdxSimpleInstance extends BatchedInstance { } @Override - public void renderOpaque() { + public void renderOpaque(final Matrix4 mvp) { } @Override diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Sd.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Sd.java index a8894b8..523e1cb 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Sd.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Sd.java @@ -122,7 +122,7 @@ public abstract class Sd { return this.globalSequence.getValue(out, this.globalSequence.end == 0 ? 0 : counter % this.globalSequence.end); } - else if (sequence != -1) { + else if ((sequence != -1) && (this.sequences.size() > 0)) { return this.sequences.get(sequence).getValue(out, frame); } else { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/tga/TgaHandler.java b/core/src/com/etheller/warsmash/viewer5/handlers/tga/TgaHandler.java new file mode 100644 index 0000000..1505f0d --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/tga/TgaHandler.java @@ -0,0 +1,28 @@ +package com.etheller.warsmash.viewer5.handlers.tga; + +import java.util.ArrayList; + +import com.etheller.warsmash.viewer5.HandlerResource; +import com.etheller.warsmash.viewer5.ModelViewer; +import com.etheller.warsmash.viewer5.handlers.ResourceHandler; +import com.etheller.warsmash.viewer5.handlers.ResourceHandlerConstructionParams; + +public class TgaHandler extends ResourceHandler { + + public TgaHandler() { + this.extensions = new ArrayList<>(); + this.extensions.add(new String[] { ".tga", "arrayBuffer" }); + } + + @Override + public boolean load(final ModelViewer modelViewer) { + return true; + } + + @Override + public HandlerResource construct(final ResourceHandlerConstructionParams params) { + return new TgaTexture(params.getViewer(), params.getHandler(), params.getExtension(), params.getPathSolver(), + params.getFetchUrl()); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/tga/TgaTexture.java b/core/src/com/etheller/warsmash/viewer5/handlers/tga/TgaTexture.java new file mode 100644 index 0000000..293480b --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/tga/TgaTexture.java @@ -0,0 +1,36 @@ +package com.etheller.warsmash.viewer5.handlers.tga; + +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.InputStream; + +import com.etheller.warsmash.viewer5.ModelViewer; +import com.etheller.warsmash.viewer5.PathSolver; +import com.etheller.warsmash.viewer5.RawOpenGLTextureResource; +import com.etheller.warsmash.viewer5.handlers.ResourceHandler; + +public class TgaTexture extends RawOpenGLTextureResource { + + public TgaTexture(final ModelViewer viewer, final ResourceHandler handler, final String extension, + final PathSolver pathSolver, final String fetchUrl) { + super(viewer, extension, pathSolver, fetchUrl, handler); + } + + @Override + protected void lateLoad() { + + } + + @Override + protected void load(final InputStream src, final Object options) { + BufferedImage img; + try { + img = TgaFile.readTGA(this.fetchUrl, src); + update(img); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + } + +} \ No newline at end of file diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/DynamicShadowManager.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/DynamicShadowManager.java new file mode 100644 index 0000000..5847c68 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/DynamicShadowManager.java @@ -0,0 +1,139 @@ +package com.etheller.warsmash.viewer5.handlers.w3x; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.graphics.GL30; +import com.badlogic.gdx.math.Matrix4; +import com.badlogic.gdx.math.Vector3; +import com.etheller.warsmash.util.RenderMathUtils; +import com.etheller.warsmash.viewer5.gl.Extensions; +import com.etheller.warsmash.viewer5.gl.WebGL; + +public class DynamicShadowManager { + public static boolean IS_SHADOW_MAPPING = false; + + private final Vector3 shadowVector = new Vector3(); + private final Matrix4 depthProjectionMatrix = new Matrix4(); + private final Matrix4 depthViewMatrix = new Matrix4(); + private final Matrix4 depthModelMatrix = new Matrix4(); + private final Matrix4 depthMVP = new Matrix4(); + private final Matrix4 biasMatrix = new Matrix4(); + private final Matrix4 depthBiasMVP = new Matrix4(); + + public boolean setup(final WebGL webGL) { + // The framebuffer, which regroups 0, 1, or more textures, and 0 or 1 depth + // buffer. + final GL30 gl = Gdx.gl30; + this.framebufferName = 0; + this.framebufferName = gl.glGenFramebuffer(); + gl.glBindFramebuffer(GL30.GL_FRAMEBUFFER, this.framebufferName); + + this.depthTexture = gl.glGenTexture(); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.depthTexture); + gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_DEPTH_COMPONENT16, 1024, 1024, 0, GL30.GL_DEPTH_COMPONENT, + GL30.GL_FLOAT, null); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_NEAREST); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_NEAREST); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_S, GL30.GL_CLAMP_TO_EDGE); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_T, GL30.GL_CLAMP_TO_EDGE); + Extensions.dynamicShadowExtension.glFramebufferTexture(GL30.GL_FRAMEBUFFER, GL30.GL_DEPTH_ATTACHMENT, + this.depthTexture, 0); + + Extensions.dynamicShadowExtension.glDrawBuffer(GL30.GL_NONE); // No color buffer is drawn to. + + // Always check that our framebuffer is ok + if (gl.glCheckFramebufferStatus(GL30.GL_FRAMEBUFFER) != GL30.GL_FRAMEBUFFER_COMPLETE) { + return false; + } + Gdx.gl30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0); + + return true; + } + + /** + * @return + */ + public Matrix4 prepareShadowMatrix() { + final Vector3 lightInvDir = this.shadowVector; + lightInvDir.set(500f, 2000, 2000); + + // Compute the MVP matrix from the light's point of view + this.depthProjectionMatrix.setToOrtho(-10, 10, -10, 10, -10, 20); + this.depthViewMatrix.set(this.depthProjectionMatrix); + this.depthViewMatrix.setToLookAt(lightInvDir, Vector3.Zero, RenderMathUtils.VEC3_UNIT_Y); + this.depthModelMatrix.idt(); + this.depthMVP.set(this.depthProjectionMatrix).mul(this.depthViewMatrix).mul(this.depthModelMatrix); + +// this.shader.setUniformMatrix("depthMVP", this.depthMVP); + + this.biasMatrix.val[Matrix4.M00] = 0.5f; + this.biasMatrix.val[Matrix4.M10] = 0.0f; + this.biasMatrix.val[Matrix4.M20] = 0.0f; + this.biasMatrix.val[Matrix4.M30] = 0.5f; + this.biasMatrix.val[Matrix4.M01] = 0.0f; + this.biasMatrix.val[Matrix4.M11] = 0.5f; + this.biasMatrix.val[Matrix4.M21] = 0.0f; + this.biasMatrix.val[Matrix4.M31] = 0.5f; + this.biasMatrix.val[Matrix4.M02] = 0.0f; + this.biasMatrix.val[Matrix4.M12] = 0.0f; + this.biasMatrix.val[Matrix4.M22] = 0.5f; + this.biasMatrix.val[Matrix4.M32] = 0.5f; + this.biasMatrix.val[Matrix4.M03] = 0.0f; + this.biasMatrix.val[Matrix4.M13] = 0.0f; + this.biasMatrix.val[Matrix4.M23] = 0.0f; + this.biasMatrix.val[Matrix4.M33] = 1.0f; + this.depthBiasMVP.set(this.biasMatrix).mul(this.depthMVP); + + return this.depthMVP; + } + + public void beginShadowMap(final WebGL webGL) { + IS_SHADOW_MAPPING = true; + + Gdx.gl30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, this.framebufferName); + Extensions.dynamicShadowExtension.glFramebufferTexture(GL30.GL_FRAMEBUFFER, GL30.GL_DEPTH_ATTACHMENT, + this.depthTexture, 0); + + Extensions.dynamicShadowExtension.glDrawBuffer(GL30.GL_NONE); // No color buffer is drawn to. + Gdx.gl30.glViewport(0, 0, 1024, 1024); + + } + + public Matrix4 getDepthBiasMVP() { + return this.depthBiasMVP; + } + + // Don't forget to change viewport back + public void endShadowMap() { + IS_SHADOW_MAPPING = false; + Gdx.gl30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0); + } + + public int getDepthTexture() { + return this.depthTexture; + } + + public static final String vertexShader = "#version 330 core\r\n" + // + "\r\n" + // + "// Input vertex data, different for all executions of this shader.\r\n" + // + "layout(location = 0) in vec3 vertexPosition_modelspace;\r\n" + // + "\r\n" + // + "// Values that stay constant for the whole mesh.\r\n" + // + "uniform mat4 depthMVP;\r\n" + // + "\r\n" + // + "void main(){\r\n" + // + " gl_Position = depthMVP * vec4(vertexPosition_modelspace,1);\r\n" + // + "}"; + + public static final String fragmentShader = "#version 330 core\r\n" + // + "\r\n" + // + "// Ouput data\r\n" + // + "layout(location = 0) out float fragmentdepth;\r\n" + // + "\r\n" + // + "void main(){\r\n" + // + " // Not really needed, OpenGL does it anyway\r\n" + // + " fragmentdepth = gl_FragCoord.z;\r\n" + // + "}"; + private int depthTexture; + private int framebufferName; + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index c828eae..b3b0c99 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -134,6 +134,8 @@ public class War3MapViewer extends ModelViewer { public Vector2[] startLocations = new Vector2[WarsmashConstants.MAX_PLAYERS]; + private final DynamicShadowManager dynamicShadowManager = new DynamicShadowManager(); + public War3MapViewer(final DataSource dataSource, final CanvasProvider canvas) { super(dataSource, canvas); this.gameDataSource = dataSource; @@ -145,6 +147,10 @@ public class War3MapViewer extends ModelViewer { this.wc3PathSolver = PathSolver.DEFAULT; this.worldScene = this.addScene(); + + if (!this.dynamicShadowManager.setup(webGL)) { + throw new IllegalStateException("FrameBuffer setup failed"); + } } public void loadSLKs(final WorldEditStrings worldEditStrings) throws IOException { @@ -588,7 +594,8 @@ public class War3MapViewer extends ModelViewer { startFrame(); worldScene.startFrame(); - this.terrain.renderGround(); + worldScene.renderOpaque(this.dynamicShadowManager, this.webGL); + this.terrain.renderGround(this.dynamicShadowManager); this.terrain.renderCliffs(); worldScene.renderOpaque(); this.terrain.renderUberSplats(); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java index e1b9cc6..9d9ca1b 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java @@ -41,6 +41,7 @@ import com.etheller.warsmash.viewer5.PathSolver; import com.etheller.warsmash.viewer5.RawOpenGLTextureResource; import com.etheller.warsmash.viewer5.Texture; import com.etheller.warsmash.viewer5.gl.WebGL; +import com.etheller.warsmash.viewer5.handlers.w3x.DynamicShadowManager; import com.etheller.warsmash.viewer5.handlers.w3x.SplatModel; import com.etheller.warsmash.viewer5.handlers.w3x.SplatModel.SplatMover; import com.etheller.warsmash.viewer5.handlers.w3x.Variations; @@ -777,7 +778,7 @@ public class Terrain { } } - public void renderGround() { + public void renderGround(final DynamicShadowManager dynamicShadowManager) { // Render tiles this.webGL.useShaderProgram(this.groundShader); @@ -793,9 +794,13 @@ public class Terrain { gl.glUniform1i(this.groundShader.getUniformLocation("height_texture"), 0); gl.glUniform1i(this.groundShader.getUniformLocation("height_cliff_texture"), 1); gl.glUniform1i(this.groundShader.getUniformLocation("terrain_texture_list"), 2); + gl.glUniform1i(this.groundShader.getUniformLocation("shadowMap"), 20); gl.glUniform1f(this.groundShader.getUniformLocation("centerOffsetX"), this.centerOffset[0]); gl.glUniform1f(this.groundShader.getUniformLocation("centerOffsetY"), this.centerOffset[1]); + gl.glUniformMatrix4fv(this.groundShader.getUniformLocation("DepthBiasMVP"), 1, false, + dynamicShadowManager.getDepthBiasMVP().val, 0); + gl.glActiveTexture(GL30.GL_TEXTURE0); gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundHeight); @@ -813,6 +818,9 @@ public class Terrain { // gl.glActiveTexture(GL30.GL_TEXTURE20, /*pathingMap.getTextureStatic()*/); // gl.glActiveTexture(GL30.GL_TEXTURE21, /*pathingMap.getTextureDynamic()*/); + gl.glActiveTexture(GL30.GL_TEXTURE20); + gl.glBindTexture(GL30.GL_TEXTURE_2D, dynamicShadowManager.getDepthTexture()); + // gl.glEnableVertexAttribArray(0); gl.glBindBuffer(GL30.GL_ARRAY_BUFFER, Shapes.INSTANCE.vertexBuffer); gl.glVertexAttribPointer(0, 2, GL30.GL_FLOAT, false, 0, 0); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/TerrainShaders.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/TerrainShaders.java index 73cffc5..7c6d7bf 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/TerrainShaders.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/TerrainShaders.java @@ -113,6 +113,7 @@ public class TerrainShaders { "\r\n" + // "layout (location = 0) in vec2 vPosition;\r\n" + // "layout (location = 1) uniform mat4 MVP;\r\n" + // + "layout (location = 6) uniform mat4 DepthBiasMVP;\r\n" + // "\r\n" + // "layout (binding = 0) uniform sampler2D height_texture;\r\n" + // "layout (binding = 1) uniform sampler2D height_cliff_texture;\r\n" + // @@ -125,6 +126,7 @@ public class TerrainShaders { "layout (location = 2) out vec2 pathing_map_uv;\r\n" + // "layout (location = 3) out vec3 normal;\r\n" + // "layout (location = 4) out vec3 position;\r\n" + // + "layout (location = 5) out vec3 ShadowCoord;\r\n" + // "\r\n" + // "void main() { \r\n" + // " ivec2 size = textureSize(terrain_texture_list, 0);\r\n" + // @@ -149,6 +151,8 @@ public class TerrainShaders { + // " gl_Position = ((texture_indices.a & 32768) == 0) ? MVP * vec4(position.xyz, 1) : vec4(2.0, 0.0, 0.0, 1.0);\r\n" + // + " ShadowCoord = (((texture_indices.a & 32768) == 0) ? DepthBiasMVP * vec4(position.xyz, 1) : vec4(2.0, 0.0, 0.0, 1.0)).xyz;\r\n" + + // " position.x = (position.x - centerOffsetX) / (size.x * 128.0);\r\n" + // " position.y = (position.y - centerOffsetY) / (size.y * 128.0);\r\n" + // "}"; @@ -176,14 +180,16 @@ public class TerrainShaders { "layout (binding = 18) uniform sampler2DArray sample15;\r\n" + // "layout (binding = 19) uniform sampler2DArray sample16;\r\n" + // "\r\n" + // - "layout (binding = 20) uniform usampler2D pathing_map_static;\r\n" + // - "layout (binding = 21) uniform usampler2D pathing_map_dynamic;\r\n" + // +// "layout (binding = 20) uniform usampler2D pathing_map_static;\r\n" + // +// "layout (binding = 21) uniform usampler2D pathing_map_dynamic;\r\n" + // + "layout (binding = 20) uniform sampler2D shadowMap;\r\n" + // "\r\n" + // "layout (location = 0) in vec2 UV;\r\n" + // "layout (location = 1) in flat uvec4 texture_indices;\r\n" + // "layout (location = 2) in vec2 pathing_map_uv;\r\n" + // "layout (location = 3) in vec3 normal;\r\n" + // "layout (location = 4) in vec3 position;\r\n" + // + "layout (location = 5) in vec3 ShadowCoord;\r\n" + // "\r\n" + // "layout (location = 0) out vec4 color;\r\n" + // // "layout (location = 1) out vec4 position;\r\n" + // @@ -241,25 +247,30 @@ public class TerrainShaders { + // " color = color * color.a + get_fragment(texture_indices.r & 31, vec3(UV, texture_indices.r >> 5)) * (1 - color.a);\r\n" + // + " float visibility = 1.0;\r\n" + // +// " if ( texture2D(shadowMap, ShadowCoord.xy).z > ShadowCoord.z ) {\r\n" + // +// " visibility = 0.5;\r\n" + // +// " }\r\n" + // + " color = vec4(color.xyz * visibility, 1.0);\r\n" + // "\r\n" + // - " if (show_lighting) {\r\n" + // - " vec3 light_direction = vec3(-0.3, -0.3, 0.25);\r\n" + // - " light_direction = normalize(light_direction);\r\n" + // - "\r\n" + // - " color.rgb *= clamp(dot(normal, light_direction) + 0.45, 0, 1);\r\n" + // - " }\r\n" + // - "\r\n" + // - " if (show_pathing_map) {\r\n" + // - " uint byte_static = texelFetch(pathing_map_static, ivec2(pathing_map_uv), 0).r;\r\n" + // - " uint byte_dynamic = texelFetch(pathing_map_dynamic, ivec2(pathing_map_uv), 0).r;\r\n" + // - " uint final = byte_static.r | byte_dynamic.r;\r\n" + // - "\r\n" + // - " vec4 pathing_static_color = vec4((final & 2) >> 1, (final & 4) >> 2, (final & 8) >> 3, 0.25);\r\n" - + // - "\r\n" + // - " color = length(pathing_static_color.rgb) > 0 ? color * 0.75 + pathing_static_color * 0.5 : color;\r\n" - + // - " }\r\n" + // +// " if (show_lighting) {\r\n" + // +// " vec3 light_direction = vec3(-0.3, -0.3, 0.25);\r\n" + // +// " light_direction = normalize(light_direction);\r\n" + // +// "\r\n" + // +// " color.rgb *= clamp(dot(normal, light_direction) + 0.45, 0, 1);\r\n" + // +// " }\r\n" + // +// "\r\n" + // +// " if (show_pathing_map) {\r\n" + // +// " uint byte_static = texelFetch(pathing_map_static, ivec2(pathing_map_uv), 0).r;\r\n" + // +// " uint byte_dynamic = texelFetch(pathing_map_dynamic, ivec2(pathing_map_uv), 0).r;\r\n" + // +// " uint final = byte_static.r | byte_dynamic.r;\r\n" + // +// "\r\n" + // +// " vec4 pathing_static_color = vec4((final & 2) >> 1, (final & 4) >> 2, (final & 8) >> 3, 0.25);\r\n" +// + // +// "\r\n" + // +// " color = length(pathing_static_color.rgb) > 0 ? color * 0.75 + pathing_static_color * 0.5 : color;\r\n" +// + // +// " }\r\n" + // "}"; public static final String posFrag = "#version 450 core\r\n" + // diff --git a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java index 0b4c958..004d6f8 100644 --- a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java +++ b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java @@ -1,13 +1,15 @@ package com.etheller.warsmash.desktop; +import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL31; +import org.lwjgl.opengl.GL32; import org.lwjgl.opengl.GL33; -import com.badlogic.gdx.Graphics.DisplayMode; import com.badlogic.gdx.backends.lwjgl.LwjglApplication; import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration; import com.etheller.warsmash.WarsmashGdxMapGame; import com.etheller.warsmash.viewer5.gl.ANGLEInstancedArrays; +import com.etheller.warsmash.viewer5.gl.DynamicShadowExtension; import com.etheller.warsmash.viewer5.gl.Extensions; public class DesktopLauncher { @@ -30,15 +32,27 @@ public class DesktopLauncher { GL31.glDrawArraysInstanced(mode, first, count, instanceCount); } }; + Extensions.dynamicShadowExtension = new DynamicShadowExtension() { + @Override + public void glFramebufferTexture(final int target, final int attachment, final int texture, + final int level) { + GL32.glFramebufferTexture(target, attachment, texture, level); + } + + @Override + public void glDrawBuffer(final int mode) { + GL11.glDrawBuffer(mode); + } + }; final LwjglApplicationConfiguration config = new LwjglApplicationConfiguration(); config.useGL30 = true; config.gles30ContextMajorVersion = 3; config.gles30ContextMinorVersion = 3; config.samples = 16; - config.fullscreen = false; - final DisplayMode desktopDisplayMode = LwjglApplicationConfiguration.getDesktopDisplayMode(); - config.width = desktopDisplayMode.width; - config.height = desktopDisplayMode.height; +// config.fullscreen = false; +// final DisplayMode desktopDisplayMode = LwjglApplicationConfiguration.getDesktopDisplayMode(); +// config.width = desktopDisplayMode.width; +// config.height = desktopDisplayMode.height; new LwjglApplication(new WarsmashGdxMapGame(), config); } } From f776a602f950ae3cc4a219a776c7979e8113fb15 Mon Sep 17 00:00:00 2001 From: Retera Date: Sun, 1 Mar 2020 11:04:44 -0600 Subject: [PATCH 022/116] Update some movement code and fun with projectiles --- .../com/etheller/warsmash/CodeCounter.java | 35 ++++++ .../etheller/warsmash/MathSpeedBenchmark.java | 61 +++++++++++ .../etheller/warsmash/WarsmashGdxGame.java | 10 +- .../etheller/warsmash/WarsmashGdxMapGame.java | 26 +++-- .../warsmash/util/WarsmashConstants.java | 2 +- .../com/etheller/warsmash/viewer5/Node.java | 6 ++ .../com/etheller/warsmash/viewer5/Scene.java | 1 + .../mdx/EventObjectEmitterObject.java | 9 +- .../viewer5/handlers/mdx/MdxModel.java | 4 + .../viewer5/handlers/w3x/StandSequence.java | 26 +++++ .../viewer5/handlers/w3x/War3MapViewer.java | 58 +++++++++- .../w3x/rendersim/RenderAttackProjectile.java | 68 ++++++++++++ .../handlers/w3x/rendersim/RenderUnit.java | 73 +++++++++++-- .../w3x/simulation/CDestructable.java | 5 + .../handlers/w3x/simulation/CItem.java | 5 + .../handlers/w3x/simulation/CSimulation.java | 29 ++++- .../handlers/w3x/simulation/CUnit.java | 23 +++- .../handlers/w3x/simulation/CWidget.java | 8 +- .../w3x/simulation/data/CUnitData.java | 45 +++++++- .../w3x/simulation/orders/CAttackOrder.java | 27 ++--- .../w3x/simulation/orders/CMoveOrder.java | 14 +-- .../projectile/CAttackProjectile.java | 102 ++++++++++++++++++ .../simulation/util/ProjectileCreator.java | 10 ++ 23 files changed, 592 insertions(+), 55 deletions(-) create mode 100644 core/src/com/etheller/warsmash/CodeCounter.java create mode 100644 core/src/com/etheller/warsmash/MathSpeedBenchmark.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackProjectile.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/projectile/CAttackProjectile.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/ProjectileCreator.java diff --git a/core/src/com/etheller/warsmash/CodeCounter.java b/core/src/com/etheller/warsmash/CodeCounter.java new file mode 100644 index 0000000..0dda796 --- /dev/null +++ b/core/src/com/etheller/warsmash/CodeCounter.java @@ -0,0 +1,35 @@ +package com.etheller.warsmash; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; + +public class CodeCounter { + + public static void main(final String[] args) { + final int sourceLines = countFile(new File("src/com")); + System.out.println(sourceLines); + } + + public static int countFile(final File file) { + if (file.isDirectory()) { + int sum = 0; + for (final File subFile : file.listFiles()) { + sum += countFile(subFile); + } + return sum; + } + else { + try { + if (file.getName().toLowerCase().endsWith(".java")) { + return Files.readAllLines(file.toPath()).size(); + } + } + catch (final IOException e) { + e.printStackTrace(); + } + return 0; + } + } + +} diff --git a/core/src/com/etheller/warsmash/MathSpeedBenchmark.java b/core/src/com/etheller/warsmash/MathSpeedBenchmark.java new file mode 100644 index 0000000..be1f00d --- /dev/null +++ b/core/src/com/etheller/warsmash/MathSpeedBenchmark.java @@ -0,0 +1,61 @@ +package com.etheller.warsmash; + +public class MathSpeedBenchmark { + private static final int NUMBER_OF_ITERATIONS = 100000000; + + public static void main(final String[] args) { + // Let us solve for Ground Distance two ways. + + long sumCosineTime = 0; + long sumSquareRootTime = 0; + final float[] thrallXs = new float[NUMBER_OF_ITERATIONS]; + final float[] thrallYs = new float[NUMBER_OF_ITERATIONS]; + final float[] murlocXs = new float[NUMBER_OF_ITERATIONS]; + final float[] murlocYs = new float[NUMBER_OF_ITERATIONS]; + for (int i = 0; i < NUMBER_OF_ITERATIONS; i++) { + + thrallXs[i] = getRandomFloat(-25000.0f, 25000.0f); + thrallYs[i] = getRandomFloat(-25000.0f, 25000.0f); + murlocXs[i] = getRandomFloat(-25000.0f, 25000.0f); + murlocYs[i] = getRandomFloat(-25000.0f, 25000.0f); + } + final long clockTime1 = System.currentTimeMillis(); + for (int i = 0; i < NUMBER_OF_ITERATIONS; i++) { + final float distance2 = groundDistanceSqrt(thrallXs[i], thrallYs[i], murlocXs[i], murlocYs[i]); + } + final long clockTime2 = System.currentTimeMillis(); + for (int i = 0; i < NUMBER_OF_ITERATIONS; i++) { + final float distance1 = groundDistanceCos(thrallXs[i], thrallYs[i], murlocXs[i], murlocYs[i]); + } + final long clockTime3 = System.currentTimeMillis(); +// if (Math.abs(distance2 - distance1) > 0.1) { +// System.out.println(thrallX + "," + thrallY); +// System.out.println(murlocX + "," + murlocY); +// System.err.println(distance1 + " != " + distance2); +// throw new RuntimeException("You have failed to do mathematics."); +// } + sumCosineTime = clockTime2 - clockTime1; + sumSquareRootTime = clockTime3 - clockTime2; + System.out.println("Square Root: " + sumCosineTime); + System.out.println("Cosine: " + sumSquareRootTime); + } + + static float getRandomFloat(final float min, final float max) { + final float range = max - min; + return (float) ((Math.random() * range) + min); + } + + static float groundDistanceSqrt(final float thrallX, final float thrallY, final float murlocX, + final float murlocY) { + final float dx = murlocX - thrallX; + final float dy = murlocY - thrallY; + return (float) Math.sqrt((dx * dx) + (dy * dy)); + } + + static float groundDistanceCos(final float thrallX, final float thrallY, final float murlocX, final float murlocY) { + final float dx = murlocX - thrallX; + final float dy = murlocY - thrallY; + final double angle = Math.atan2(dy, dx); + return (float) (dx / Math.cos(angle)); + } +} diff --git a/core/src/com/etheller/warsmash/WarsmashGdxGame.java b/core/src/com/etheller/warsmash/WarsmashGdxGame.java index daf71d6..934f894 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxGame.java @@ -24,6 +24,7 @@ import com.etheller.warsmash.viewer5.ModelViewer; import com.etheller.warsmash.viewer5.PathSolver; import com.etheller.warsmash.viewer5.Scene; import com.etheller.warsmash.viewer5.SolvedPath; +import com.etheller.warsmash.viewer5.handlers.mdx.EventObjectEmitterObject; import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxHandler; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; @@ -74,7 +75,7 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide this.cameraManager.setupCamera(scene); // this.mainModel = (MdxModel) this.viewer.load("UI\\Glues\\MainMenu\\MainMenu3D_exp\\MainMenu3D_exp.mdx", - this.mainModel = (MdxModel) this.viewer.load("Doodads\\Cinematic\\RisingWaterDoodad\\RisingWaterDoodad.mdx", + this.mainModel = (MdxModel) this.viewer.load("Units\\Human\\HeroPaladinBoss\\HeroPaladinBoss.mdx", new PathSolver() { @Override public SolvedPath solve(final String src, final Object solverParams) { @@ -82,6 +83,13 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide } }, null); + final EventObjectEmitterObject evt = this.mainModel.getEventObjects().get(1); + for (final Sequence seq : this.mainModel.getSequences()) { + System.out.println(seq.getName() + ": " + Arrays.toString(seq.getInterval())); + } + System.out.println(Arrays.toString(evt.keyFrames)); + System.out.println(evt.name); + // this.modelCamera = this.mainModel.cameras.get(0); this.mainInstance = (MdxComplexInstance) this.mainModel.addInstance(0); diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java index 70e02ed..caa2dfb 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java @@ -13,7 +13,6 @@ import com.badlogic.gdx.ApplicationAdapter; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Input; import com.badlogic.gdx.InputProcessor; -import com.badlogic.gdx.audio.Music; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.GL30; @@ -84,6 +83,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv private Rectangle minimapFilledArea; private final Texture[] teamColors = new Texture[WarsmashConstants.MAX_PLAYERS]; + private Texture solidGreenTexture; @Override public void create() { @@ -115,15 +115,15 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv .createDataSource(); this.viewer = new War3MapViewer(this.codebase, this); + this.viewer.worldScene.enableAudio(); + this.viewer.enableAudio(); try { // "Maps\\Campaign\\NightElf03.w3m" - this.viewer.loadMap("Maps\\Campaign\\NightElf03.w3m"); + this.viewer.loadMap("ProjectileTest.w3x"); } catch (final IOException e) { throw new RuntimeException(e); } - this.viewer.worldScene.enableAudio(); - this.viewer.enableAudio(); this.cameraManager = new CameraManager(); this.cameraManager.setupCamera(this.viewer.worldScene); @@ -200,13 +200,16 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv "ReplaceableTextures\\" + ReplaceableIds.getPathString(1) + ReplaceableIds.getIdString(i) + ".blp"); } - Gdx.input.setInputProcessor(this); + this.solidGreenTexture = ImageUtils.getBLPTexture(this.viewer.dataSource, + "ReplaceableTextures\\TeamColor\\TeamColor06.blp"); - final Music music = Gdx.audio - .newMusic(new DataSourceFileHandle(this.viewer.dataSource, "Sound\\Music\\mp3Music\\NightElf3.mp3")); - music.setVolume(0.2f); - music.setLooping(true); - music.play(); + Gdx.input.setInputProcessor(this); +// +// final Music music = Gdx.audio +// .newMusic(new DataSourceFileHandle(this.viewer.dataSource, "Sound\\Music\\mp3Music\\OrcTheme.mp3")); +// music.setVolume(0.2f); +// music.setLooping(true); +// music.play(); this.minimap = new Rectangle(35, 7, 305, 272); final float worldWidth = (this.viewer.terrain.columns - 1); @@ -303,6 +306,9 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv } } + this.batch.draw(this.solidGreenTexture, 413, 34, 122 * (this.selectedUnit.getSimulationUnit().getLife() + / this.selectedUnit.getSimulationUnit().getMaximumLife()), 7); + } for (final RenderUnit unit : this.viewer.units) { if (unit.playerIndex >= WarsmashConstants.MAX_PLAYERS) { diff --git a/core/src/com/etheller/warsmash/util/WarsmashConstants.java b/core/src/com/etheller/warsmash/util/WarsmashConstants.java index 1205058..ea441be 100644 --- a/core/src/com/etheller/warsmash/util/WarsmashConstants.java +++ b/core/src/com/etheller/warsmash/util/WarsmashConstants.java @@ -3,5 +3,5 @@ package com.etheller.warsmash.util; public class WarsmashConstants { public static final int MAX_PLAYERS = 16; public static final int REPLACEABLE_TEXTURE_LIMIT = 64; - public static final float SIMULATION_STEP_TIME = 1 / 60f; + public static final float SIMULATION_STEP_TIME = 1 / 20f; } diff --git a/core/src/com/etheller/warsmash/viewer5/Node.java b/core/src/com/etheller/warsmash/viewer5/Node.java index 3e7ee1a..be2d95a 100644 --- a/core/src/com/etheller/warsmash/viewer5/Node.java +++ b/core/src/com/etheller/warsmash/viewer5/Node.java @@ -48,6 +48,12 @@ public abstract class Node extends GenericNode { return this; } + public Node setLocation(final float x, final float y, final float z) { + this.localLocation.set(x, y, z); + this.dirty = true; + return this; + } + public Node setLocation(final float[] location) { this.localLocation.set(location); this.dirty = true; diff --git a/core/src/com/etheller/warsmash/viewer5/Scene.java b/core/src/com/etheller/warsmash/viewer5/Scene.java index 582d313..76f1365 100644 --- a/core/src/com/etheller/warsmash/viewer5/Scene.java +++ b/core/src/com/etheller/warsmash/viewer5/Scene.java @@ -131,6 +131,7 @@ public class Scene { this.grid.remove(instance); instance.scene = null; + this.instances.remove(instance); return true; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectEmitterObject.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectEmitterObject.java index 0c546be..cebd32c 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectEmitterObject.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectEmitterObject.java @@ -59,7 +59,7 @@ public class EventObjectEmitterObject extends GenericObject implements EmitterOb private int geometryEmitterType = -1; public final String type; private final String id; - private final long[] keyFrames; + public final long[] keyFrames; private long globalSequence = -1; private final long[] defval = { 1 }; public MdxModel internalModel; @@ -185,7 +185,7 @@ public class EventObjectEmitterObject extends GenericObject implements EmitterOb private void load(final List tables) { final MappedData firstTable = (MappedData) tables.get(0).data; - final MappedDataRow row = firstTable.getRow(this.id); + final MappedDataRow row = firstTable.getRow(this.id.trim()); if (row != null) { final MdxModel model = this.model; @@ -285,9 +285,12 @@ public class EventObjectEmitterObject extends GenericObject implements EmitterOb } } else { - System.err.println("Unknown event object ID: " + this.type + this.id); + System.err.println("Unknown event object type: " + this.type + this.id); } } + else { + System.err.println("Unknown event object ID: " + this.type + this.id); + } } public int getValue(final long[] out, final MdxComplexInstance instance) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java index 890ff65..f3b22f8 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java @@ -340,4 +340,8 @@ public class MdxModel extends com.etheller.warsmash.viewer5.Model { return this.cameras; } + public List getEventObjects() { + return this.eventObjects; + } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/StandSequence.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/StandSequence.java index 3aa2048..ddb44a1 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/StandSequence.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/StandSequence.java @@ -68,6 +68,19 @@ public class StandSequence { } } + public static void randomDeathSequence(final MdxComplexInstance target) { + final MdxModel model = (MdxModel) target.model; + final List sequences = model.getSequences(); + final IndexedSequence sequence = selectSequence("death", sequences); + + if (sequence != null) { + target.setSequence(sequence.index); + } + else { + target.setSequence(0); + } + } + public static void randomWalkSequence(final MdxComplexInstance target) { final MdxModel model = (MdxModel) target.model; final List sequences = model.getSequences(); @@ -81,6 +94,19 @@ public class StandSequence { } } + public static void randomBirthSequence(final MdxComplexInstance target) { + final MdxModel model = (MdxModel) target.model; + final List sequences = model.getSequences(); + final IndexedSequence sequence = selectSequence("birth", sequences); + + if (sequence != null) { + target.setSequence(sequence.index); + } + else { + randomStandSequence(target); + } + } + public static void randomPortraitSequence(final MdxComplexInstance target) { final MdxModel model = (MdxModel) target.model; final List sequences = model.getSequences(); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index b3b0c99..94b4e9c 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -10,8 +10,10 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Random; import java.util.function.Consumer; import org.apache.commons.compress.utils.IOUtils; @@ -57,6 +59,7 @@ import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; import com.etheller.warsmash.viewer5.handlers.w3x.SplatModel.SplatMover; import com.etheller.warsmash.viewer5.handlers.w3x.environment.Terrain; import com.etheller.warsmash.viewer5.handlers.w3x.environment.Terrain.Splat; +import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderAttackProjectile; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderItem; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; @@ -65,9 +68,11 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityAttack; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityMove; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.projectile.CAttackProjectile; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.BooleanAbilityActivationReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.CWidgetAbilityTargetCheckReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.PointAbilityTargetCheckReceiver; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.ProjectileCreator; import mpq.MPQArchive; import mpq.MPQException; @@ -115,6 +120,7 @@ public class War3MapViewer extends ModelViewer { public MappedData unitMetaData = new MappedData(); public List units = new ArrayList<>(); public List items = new ArrayList<>(); + public List projectiles = new ArrayList<>(); public boolean unitsReady; public War3Map mapMpq; public PathSolver mapPathSolver = PathSolver.DEFAULT; @@ -136,6 +142,8 @@ public class War3MapViewer extends ModelViewer { private final DynamicShadowManager dynamicShadowManager = new DynamicShadowManager(); + private final Random seededRandom = new Random(1337L); + public War3MapViewer(final DataSource dataSource, final CanvasProvider canvas) { super(dataSource, canvas); this.gameDataSource = dataSource; @@ -297,7 +305,44 @@ public class War3MapViewer extends ModelViewer { } final Warcraft3MapObjectData modifications = this.mapMpq.readModifications(); - this.simulation = new CSimulation(modifications.getUnits(), modifications.getAbilities()); + this.simulation = new CSimulation(modifications.getUnits(), modifications.getAbilities(), + new ProjectileCreator() { + @Override + public CAttackProjectile create(final CSimulation simulation, final CUnit source, + final int attackIndex, final CWidget target) { + final int a1ProjectileSpeed = simulation.getUnitData().getA1ProjectileSpeed(source.getTypeId()); + final float a1ProjectileArc = simulation.getUnitData().getA1ProjectileArc(source.getTypeId()); + String a1MissileArt = simulation.getUnitData().getA1MissileArt(source.getTypeId()); + final int a1MinDamage = simulation.getUnitData().getA1MinDamage(source.getTypeId()); + final int a1MaxDamage = simulation.getUnitData().getA1MaxDamage(source.getTypeId()); + final int damage = War3MapViewer.this.seededRandom.nextInt(a1MaxDamage - a1MinDamage) + + a1MinDamage; + if (a1MissileArt.toLowerCase().endsWith(".mdl")) { + a1MissileArt = a1MissileArt.substring(0, a1MissileArt.length() - 4); + } + if (!a1MissileArt.toLowerCase().endsWith(".mdx")) { + a1MissileArt += ".mdx"; + } + final float x = source.getX(); + final float y = source.getY(); + final float height = War3MapViewer.this.terrain.getGroundHeight(x, y) + source.getFlyHeight(); + final CAttackProjectile simulationAttackProjectile = new CAttackProjectile(x, y, height, + a1ProjectileSpeed, a1ProjectileArc, target, source, damage); + + final MdxModel model = (MdxModel) load(a1MissileArt, War3MapViewer.this.mapPathSolver, + War3MapViewer.this.solverParams); + final MdxComplexInstance modelInstance = (MdxComplexInstance) model.addInstance(); + modelInstance.setScene(War3MapViewer.this.worldScene); + StandSequence.randomBirthSequence(modelInstance); + modelInstance.setLocation(x, y, height); + final RenderAttackProjectile renderAttackProjectile = new RenderAttackProjectile( + simulationAttackProjectile, modelInstance); + + War3MapViewer.this.projectiles.add(renderAttackProjectile); + + return simulationAttackProjectile; + } + }); if (this.doodadsAndDestructiblesLoaded) { this.loadDoodadsAndDestructibles(modifications); @@ -483,7 +528,7 @@ public class War3MapViewer extends ModelViewer { final float x = unit.getLocation()[0] - shadowX; final float y = unit.getLocation()[1] - shadowY; this.terrain.splats.get(texture).locations - .add(new float[] { x, y, x + shadowWidth, y + shadowHeight, 30 }); + .add(new float[] { x, y, x + shadowWidth, y + shadowHeight, 3 }); unitShadowSplat = this.terrain.splats.get(texture); } @@ -562,6 +607,13 @@ public class War3MapViewer extends ModelViewer { for (final RenderUnit unit : this.units) { unit.updateAnimations(this); } + final Iterator projectileIterator = this.projectiles.iterator(); + while (projectileIterator.hasNext()) { + final RenderAttackProjectile projectile = projectileIterator.next(); + if (projectile.updateAnimations(this)) { + projectileIterator.remove(); + } + } for (final RenderItem item : this.items) { final MdxComplexInstance instance = item.instance; final MdxComplexInstance mdxComplexInstance = instance; @@ -655,7 +707,7 @@ public class War3MapViewer extends ModelViewer { final float y = unit.location[1]; final float z = unit.row.getFieldAsFloat(UNIT_SELECT_HEIGHT, 0); splats.get(path).locations - .add(new float[] { x - radius, y - radius, x + radius, y + radius, z + 35 }); + .add(new float[] { x - radius, y - radius, x + radius, y + radius, z + 5 }); splats.get(path).unitMapping.add(new Consumer() { @Override public void accept(final SplatMover t) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackProjectile.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackProjectile.java new file mode 100644 index 0000000..20306cb --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackProjectile.java @@ -0,0 +1,68 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.rendersim; + +import java.util.List; + +import com.badlogic.gdx.Gdx; +import com.etheller.warsmash.parsers.mdlx.Sequence; +import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; +import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; +import com.etheller.warsmash.viewer5.handlers.w3x.IndexedSequence; +import com.etheller.warsmash.viewer5.handlers.w3x.StandSequence; +import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.projectile.CAttackProjectile; + +public class RenderAttackProjectile { + private final CAttackProjectile simulationProjectile; + private final MdxComplexInstance modelInstance; + private float x; + private float y; + private float z; + + public RenderAttackProjectile(final CAttackProjectile simulationProjectile, + final MdxComplexInstance modelInstance) { + this.simulationProjectile = simulationProjectile; + this.modelInstance = modelInstance; + this.x = simulationProjectile.getX(); + this.y = simulationProjectile.getY(); + this.z = simulationProjectile.getZ(); + } + + public boolean updateAnimations(final War3MapViewer war3MapViewer) { + if (this.simulationProjectile.isDone()) { + final MdxModel model = (MdxModel) this.modelInstance.model; + final List sequences = model.getSequences(); + final IndexedSequence sequence = StandSequence.selectSequence("death", sequences); + if (this.modelInstance.sequence != sequence.index) { + this.modelInstance.setSequence(sequence.index); + } + } + else { + if (this.modelInstance.sequenceEnded || (this.modelInstance.sequence == -1)) { + StandSequence.randomStandSequence(this.modelInstance); + } + } + final float simX = this.simulationProjectile.getX(); + final float simY = this.simulationProjectile.getY(); + final float simZ = this.simulationProjectile.getZ(); + final float simDx = simX - this.x; + final float simDy = simY - this.y; + final float simDz = simZ - this.z; + final float simD = (float) Math.sqrt((simDx * simDx) + (simDy * simDy)); + final float deltaTime = Gdx.graphics.getDeltaTime(); + final float speed = Math.min(simD, this.simulationProjectile.getSpeed() * deltaTime); + if (simD > 0) { + this.x = this.x + ((speed * simDx) / simD); + this.y = this.y + ((speed * simDy) / simD); + this.z = this.z + ((speed * simDz) / simD); + } + + this.modelInstance.setLocation(this.x, this.y, this.z); + war3MapViewer.worldScene.grid.moved(this.modelInstance); + + final boolean everythingDone = this.simulationProjectile.isDone() && this.modelInstance.sequenceEnded; + if (everythingDone) { + war3MapViewer.worldScene.removeInstance(this.modelInstance); + } + return everythingDone; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java index 018be6c..537e33b 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java @@ -3,13 +3,16 @@ package com.etheller.warsmash.viewer5.handlers.w3x.rendersim; import java.util.ArrayList; import java.util.List; +import com.badlogic.gdx.Gdx; import com.badlogic.gdx.math.Quaternion; +import com.etheller.warsmash.parsers.mdlx.Sequence; import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; import com.etheller.warsmash.util.ImageUtils; import com.etheller.warsmash.util.RenderMathUtils; import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; +import com.etheller.warsmash.viewer5.handlers.w3x.IndexedSequence; import com.etheller.warsmash.viewer5.handlers.w3x.SplatModel.SplatMover; import com.etheller.warsmash.viewer5.handlers.w3x.StandSequence; import com.etheller.warsmash.viewer5.handlers.w3x.UnitSoundset; @@ -24,6 +27,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityP import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityStop; public class RenderUnit { + private static final double GLOBAL_TURN_RATE = Math.toDegrees(7f); private static final Quaternion tempQuat = new Quaternion(); private static final War3ID RED = War3ID.fromString("uclr"); private static final War3ID GREEN = War3ID.fromString("uclg"); @@ -41,11 +45,14 @@ public class RenderUnit { private final CUnit simulationUnit; private COrder lastOrder; private String lastOrderAnimation; - private float flyingHeight = 0; public SplatMover shadow; public SplatMover selectionCircle; private final List commandCardIcons = new ArrayList<>(); + private float x; + private float y; + private float facing; + public RenderUnit(final War3MapViewer map, final MdxModel model, final MutableGameObject row, final com.etheller.warsmash.parsers.w3x.unitsdoo.Unit unit, final UnitSoundset soundset, final MdxModel portraitModel, final CUnit simulationUnit) { @@ -56,8 +63,11 @@ public class RenderUnit { final float[] location = unit.getLocation(); System.arraycopy(location, 0, this.location, 0, 3); instance.move(location); - final float angle = (float) Math.toRadians(simulationUnit.getFacing()); + this.facing = simulationUnit.getFacing(); + final float angle = (float) Math.toRadians(this.facing); // instance.localRotation.setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, angle); + this.x = simulationUnit.getX(); + this.y = simulationUnit.getY(); instance.rotate(tempQuat.setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, angle)); instance.scale(unit.getScale()); this.playerIndex = unit.getPlayer(); @@ -65,7 +75,7 @@ public class RenderUnit { instance.setScene(map.worldScene); if (row != null) { - heapZ[2] = this.flyingHeight = row.getFieldAsFloat(MOVE_HEIGHT, 0); + heapZ[2] = simulationUnit.getFlyHeight(); this.location[2] += heapZ[2]; instance.move(heapZ); @@ -120,20 +130,65 @@ public class RenderUnit { } public void updateAnimations(final War3MapViewer map) { - final float x = this.simulationUnit.getX(); + final float deltaTime = Gdx.graphics.getDeltaTime(); + final float simulationX = this.simulationUnit.getX(); + final float simulationY = this.simulationUnit.getY(); + final float simDx = simulationX - this.x; + final float simDy = simulationY - this.y; + final float distanceToSimulation = (float) Math.sqrt((simDx * simDx) + (simDy * simDy)); + final int speed = this.simulationUnit.getSpeed(); + final float speedDelta = speed * deltaTime; + if (distanceToSimulation > speedDelta) { + this.x += (speedDelta * simDx) / distanceToSimulation; + this.y += (speedDelta * simDy) / distanceToSimulation; + } + else { + this.x = simulationX; + this.y = simulationY; + } + final float x = this.x; final float dx = x - this.location[0]; this.location[0] = x; - final float y = this.simulationUnit.getY(); + final float y = this.y; final float dy = y - this.location[1]; this.location[1] = y; - this.location[2] = this.flyingHeight + map.terrain.getGroundHeight(x, y); + this.location[2] = this.simulationUnit.getFlyHeight() + map.terrain.getGroundHeight(x, y); this.instance.moveTo(this.location); - this.instance - .setLocalRotation(tempQuat.setFromAxis(RenderMathUtils.VEC3_UNIT_Z, this.simulationUnit.getFacing())); + float simulationFacing = this.simulationUnit.getFacing(); + if (simulationFacing < 0) { + simulationFacing += 360; + } + float renderFacing = this.facing; + if (renderFacing < 0) { + renderFacing += 360; + } + float facingDelta = simulationFacing - renderFacing; + if (facingDelta < -180) { + facingDelta = 360 + facingDelta; + } + if (facingDelta > 180) { + facingDelta = -360 + facingDelta; + } + final float absoluteFacingDelta = Math.abs(facingDelta); + float angleToAdd = (float) (Math.signum(facingDelta) * GLOBAL_TURN_RATE * deltaTime); + if (absoluteFacingDelta < Math.abs(angleToAdd)) { + angleToAdd = facingDelta; + } + this.facing = (((this.facing + angleToAdd) % 360) + 360) % 360; + this.instance.setLocalRotation(tempQuat.setFromAxis(RenderMathUtils.VEC3_UNIT_Z, this.facing)); map.worldScene.grid.moved(this.instance); final MdxComplexInstance mdxComplexInstance = this.instance; final COrder currentOrder = this.simulationUnit.getCurrentOrder(); - if (mdxComplexInstance.sequenceEnded || (mdxComplexInstance.sequence == -1) || (currentOrder != this.lastOrder) + if (this.simulationUnit.getLife() <= 0) { + final MdxModel model = (MdxModel) mdxComplexInstance.model; + final List sequences = model.getSequences(); + final IndexedSequence sequence = StandSequence.selectSequence("death", sequences); + if ((sequence != null) && (mdxComplexInstance.sequence != sequence.index)) { + mdxComplexInstance.setSequence(sequence.index); + } + } + else if (mdxComplexInstance.sequenceEnded || (mdxComplexInstance.sequence == -1) + || (currentOrder != this.lastOrder) || ((currentOrder != null) && (currentOrder.getAnimationName() != null) && !currentOrder.getAnimationName().equals(this.lastOrderAnimation))) { if (this.simulationUnit.getCurrentOrder() != null) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CDestructable.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CDestructable.java index a40ec54..754f81d 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CDestructable.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CDestructable.java @@ -6,4 +6,9 @@ public class CDestructable extends CWidget { super(handleId, x, y, life); } + @Override + public float getFlyHeight() { + return 0; + } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CItem.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CItem.java index e6b6bff..73de42b 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CItem.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CItem.java @@ -11,4 +11,9 @@ public class CItem extends CWidget { this.itemType = itemType; } + @Override + public float getFlyHeight() { + return 0; + } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java index 5b1e53a..8272670 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java @@ -1,23 +1,32 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import com.etheller.warsmash.units.manager.MutableObjectData; import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.data.CAbilityData; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.data.CUnitData; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.projectile.CAttackProjectile; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.ProjectileCreator; public class CSimulation { private final CUnitData unitData; private final CAbilityData abilityData; private final List units; + private final List projectiles; private final HandleIdAllocator handleIdAllocator; + private final ProjectileCreator projectileCreator; + private int gameTurnTick = 0; - public CSimulation(final MutableObjectData parsedUnitData, final MutableObjectData parsedAbilityData) { + public CSimulation(final MutableObjectData parsedUnitData, final MutableObjectData parsedAbilityData, + final ProjectileCreator projectileCreator) { + this.projectileCreator = projectileCreator; this.unitData = new CUnitData(parsedUnitData); this.abilityData = new CAbilityData(parsedAbilityData); this.units = new ArrayList<>(); + this.projectiles = new ArrayList<>(); this.handleIdAllocator = new HandleIdAllocator(); } @@ -39,9 +48,27 @@ public class CSimulation { return unit; } + public CAttackProjectile createProjectile(final CUnit source, final int attackIndex, final CWidget target) { + final CAttackProjectile projectile = this.projectileCreator.create(this, source, attackIndex, target); + this.projectiles.add(projectile); + return projectile; + } + public void update() { for (final CUnit unit : this.units) { unit.update(this); } + final Iterator projectileIterator = this.projectiles.iterator(); + while (projectileIterator.hasNext()) { + final CAttackProjectile projectile = projectileIterator.next(); + if (projectile.update(this)) { + projectileIterator.remove(); + } + } + this.gameTurnTick++; + } + + public int getGameTurnTick() { + return this.gameTurnTick; } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java index a867a22..336c5a8 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java @@ -9,13 +9,14 @@ import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; public class CUnit extends CWidget { - private War3ID typeId; private float facing; // degrees private float mana; private int maximumLife; private int maximumMana; private int speed; + private int cooldownEndTime = 0; + private float flyHeight; private final List abilities = new ArrayList<>(); @@ -23,7 +24,8 @@ public class CUnit extends CWidget { private final Queue orderQueue = new LinkedList<>(); public CUnit(final int handleId, final float x, final float y, final float life, final War3ID typeId, - final float facing, final float mana, final int maximumLife, final int maximumMana, final int speed) { + final float facing, final float mana, final int maximumLife, final int maximumMana, final int speed, + final float defaultFlyingHeight) { super(handleId, x, y, life); this.typeId = typeId; this.facing = facing; @@ -31,6 +33,7 @@ public class CUnit extends CWidget { this.maximumLife = maximumLife; this.maximumMana = maximumMana; this.speed = speed; + this.flyHeight = defaultFlyingHeight; } public void add(final CSimulation simulation, final CAbility ability) { @@ -121,4 +124,20 @@ public class CUnit extends CWidget { return this.abilities; } + public void setCooldownEndTime(final int cooldownEndTime) { + this.cooldownEndTime = cooldownEndTime; + } + + public int getCooldownEndTime() { + return this.cooldownEndTime; + } + + public float getFlyHeight() { + return this.flyHeight; + } + + public void setFlyHeight(final float flyHeight) { + this.flyHeight = flyHeight; + } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWidget.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWidget.java index 2c31a15..b59a667 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWidget.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWidget.java @@ -1,6 +1,6 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation; -public class CWidget { +public abstract class CWidget { private final int handleId; private float x; private float y; @@ -41,4 +41,10 @@ public class CWidget { this.life = life; } + public void damage(final CUnit source, final int damage) { + this.life -= damage; + } + + public abstract float getFlyHeight(); + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java index 66e393d..79bb8de 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java @@ -23,10 +23,19 @@ public class CUnitData { private static final War3ID ATTACK1_DMG_BASE = War3ID.fromString("ua1b"); private static final War3ID ATTACK1_DMG_DICE = War3ID.fromString("ua1d"); private static final War3ID ATTACK1_DMG_SIDES_PER_DIE = War3ID.fromString("ua1s"); + private static final War3ID ATTACK1_PROJECTILE_SPEED = War3ID.fromString("ua1z"); + private static final War3ID ATTACK1_MISSILE_ART = War3ID.fromString("ua1m"); + private static final War3ID ATTACK1_PROJECTILE_ARC = War3ID.fromString("uma1"); + private static final War3ID ATTACK1_COOLDOWN = War3ID.fromString("ua1c"); private static final War3ID ATTACK2_DMG_BASE = War3ID.fromString("ua2b"); private static final War3ID ATTACK2_DMG_DICE = War3ID.fromString("ua2d"); private static final War3ID ATTACK2_DMG_SIDES_PER_DIE = War3ID.fromString("ua2s"); + private static final War3ID ATTACK2_PROJECTILE_SPEED = War3ID.fromString("ua2z"); + private static final War3ID ATTACK2_MISSILE_ART = War3ID.fromString("ua2m"); + private static final War3ID ATTACK2_PROJECTILE_ARC = War3ID.fromString("uma2"); + private static final War3ID ATTACK2_COOLDOWN = War3ID.fromString("ua2c"); private static final War3ID DEFENSE = War3ID.fromString("udef"); + private static final War3ID MOVE_HEIGHT = War3ID.fromString("umvh"); private final MutableObjectData unitData; public CUnitData(final MutableObjectData unitData) { @@ -40,7 +49,9 @@ public class CUnitData { final int manaInitial = unitType.getFieldAsInteger(MANA_INITIAL_AMOUNT, 0); final int manaMaximum = unitType.getFieldAsInteger(MANA_MAXIMUM, 0); final int speed = unitType.getFieldAsInteger(MOVEMENT_SPEED_BASE, 0); - final CUnit unit = new CUnit(handleId, x, y, life, typeId, facing, manaInitial, life, manaMaximum, speed); + final float moveHeight = unitType.getFieldAsFloat(MOVE_HEIGHT, 0); + final CUnit unit = new CUnit(handleId, x, y, life, typeId, facing, manaInitial, life, manaMaximum, speed, + moveHeight); if (speed > 0) { unit.add(simulation, CAbilityMove.INSTANCE); unit.add(simulation, CAbilityPatrol.INSTANCE); @@ -96,4 +107,36 @@ public class CUnitData { public int getDefense(final War3ID unitTypeId) { return this.unitData.get(unitTypeId).getFieldAsInteger(DEFENSE, 0); } + + public int getA1ProjectileSpeed(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_PROJECTILE_SPEED, 0); + } + + public float getA1ProjectileArc(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK1_PROJECTILE_ARC, 0); + } + + public int getA2ProjectileSpeed(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_PROJECTILE_SPEED, 0); + } + + public float getA2ProjectileArc(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK2_PROJECTILE_ARC, 0); + } + + public String getA1MissileArt(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsString(ATTACK1_MISSILE_ART, 0); + } + + public String getA2MissileArt(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsString(ATTACK2_MISSILE_ART, 0); + } + + public float getA1Cooldown(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK1_COOLDOWN, 0); + } + + public float getA2Cooldown(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK2_COOLDOWN, 0); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CAttackOrder.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CAttackOrder.java index 22d54fd..cd92835 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CAttackOrder.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CAttackOrder.java @@ -33,7 +33,6 @@ public class CAttackOrder implements COrder { final float absDelta = Math.abs(delta); final float propulsionWindow = simulation.getUnitData().getPropulsionWindow(this.unit.getTypeId()); final float turnRate = simulation.getUnitData().getTurnRate(this.unit.getTypeId()); - final int speed = this.unit.getSpeed(); if (delta < -180) { delta = 360 + delta; @@ -53,18 +52,6 @@ public class CAttackOrder implements COrder { this.unit.setFacing(facing + angleToAdd); } if (absDelta < propulsionWindow) { - final float speedTick = speed * WarsmashConstants.SIMULATION_STEP_TIME; - final float speedTickSq = speedTick * speedTick; - - if (((deltaX * deltaX) + (deltaY * deltaY)) <= speedTickSq) { - this.unit.setX(this.target.getX()); - this.unit.setY(this.target.getY()); - return true; - } - else { - this.unit.setX(prevX + (float) (Math.cos(goalAngleRad) * speedTick)); - this.unit.setY(prevY + (float) (Math.sin(goalAngleRad) * speedTick)); - } this.wasWithinPropWindow = true; } else { @@ -73,6 +60,15 @@ public class CAttackOrder implements COrder { this.wasWithinPropWindow = false; } + final int cooldownEndTime = this.unit.getCooldownEndTime(); + final int currentTurnTick = simulation.getGameTurnTick(); + if (currentTurnTick >= cooldownEndTime) { + final float a1Cooldown = simulation.getUnitData().getA1Cooldown(this.unit.getTypeId()); + final int a1CooldownSteps = (int) (a1Cooldown / WarsmashConstants.SIMULATION_STEP_TIME); + this.unit.setCooldownEndTime(currentTurnTick + a1CooldownSteps); + simulation.createProjectile(this.unit, 0, this.target); + } + return false; } @@ -83,10 +79,7 @@ public class CAttackOrder implements COrder { @Override public String getAnimationName() { - if (!this.wasWithinPropWindow) { - return "stand"; - } - return "walk"; + return "attack"; } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CMoveOrder.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CMoveOrder.java index 465e825..fad6cb4 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CMoveOrder.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CMoveOrder.java @@ -29,9 +29,8 @@ public class CMoveOrder implements COrder { if (goalAngle < 0) { goalAngle += 360; } - final float facing = this.unit.getFacing(); + float facing = this.unit.getFacing(); float delta = goalAngle - facing; - final float absDelta = Math.abs(delta); final float propulsionWindow = simulation.getUnitData().getPropulsionWindow(this.unit.getTypeId()); final float turnRate = simulation.getUnitData().getTurnRate(this.unit.getTypeId()); final int speed = this.unit.getSpeed(); @@ -42,16 +41,18 @@ public class CMoveOrder implements COrder { if (delta > 180) { delta = -360 + delta; } + final float absDelta = Math.abs(delta); if ((absDelta <= 1.0) && (absDelta != 0)) { this.unit.setFacing(goalAngle); } else { - float angleToAdd = ((Math.signum(delta) * turnRate) * WarsmashConstants.SIMULATION_STEP_TIME) * 360; + float angleToAdd = Math.signum(delta) * (float) Math.toDegrees(turnRate); if (absDelta < Math.abs(angleToAdd)) { angleToAdd = delta; } - this.unit.setFacing(facing + angleToAdd); + facing += angleToAdd; + this.unit.setFacing(facing); } if (absDelta < propulsionWindow) { final float speedTick = speed * WarsmashConstants.SIMULATION_STEP_TIME; @@ -63,8 +64,9 @@ public class CMoveOrder implements COrder { return true; } else { - this.unit.setX(prevX + (float) (Math.cos(goalAngleRad) * speedTick)); - this.unit.setY(prevY + (float) (Math.sin(goalAngleRad) * speedTick)); + final double radianFacing = Math.toRadians(facing); + this.unit.setX(prevX + (float) (Math.cos(radianFacing) * speedTick)); + this.unit.setY(prevY + (float) (Math.sin(radianFacing) * speedTick)); } this.wasWithinPropWindow = true; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/projectile/CAttackProjectile.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/projectile/CAttackProjectile.java new file mode 100644 index 0000000..6b2750d --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/projectile/CAttackProjectile.java @@ -0,0 +1,102 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.projectile; + +import com.etheller.warsmash.util.WarsmashConstants; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; + +public class CAttackProjectile { + private float x; + private float y; + private float z; + private final float startingHeight; + private final float speed; + private final float arc; + private final CWidget target; + private final float halfStartingDistance; + private final float arcPeakHeight; + private float totalTravelDistance; + private boolean done; + private final CUnit source; + private final int damage; + + public CAttackProjectile(final float x, final float y, final float z, final float speed, final float arc, + final CWidget target, final CUnit source, final int damage) { + this.x = x; + this.y = y; + this.z = z; + this.startingHeight = z; + this.speed = speed; + this.arc = arc; + this.target = target; + final float dx = target.getX() - x; + final float dy = target.getY() - y; + final float startingDistance = (float) Math.sqrt((dx * dx) + (dy * dy)); + this.halfStartingDistance = startingDistance / 2f; + this.arcPeakHeight = arc * startingDistance; + this.source = source; + this.damage = damage; + } + + public boolean update(final CSimulation cSimulation) { + final float tx = this.target.getX(); + final float ty = this.target.getY(); + final float sx = this.x; + final float sy = this.y; + final float dtsx = tx - sx; + final float dtsy = ty - sy; + final float dtsz = this.target.getFlyHeight() - this.startingHeight; + final float c = (float) Math.sqrt((dtsx * dtsx) + (dtsy * dtsy)); + + final float d1x = dtsx / c; + final float d1y = dtsy / c; + final float d1z = dtsz / (this.halfStartingDistance * 2); + + float travelDistance = Math.min(c, this.speed * WarsmashConstants.SIMULATION_STEP_TIME); + if (c <= travelDistance) { + if (!this.done) { + this.target.damage(this.source, this.damage); + } + this.done = true; + travelDistance = c; + } + + final float dx = d1x * travelDistance; + final float dy = d1y * travelDistance; + this.totalTravelDistance += travelDistance; + final float dz = d1z * this.totalTravelDistance; + + this.x = this.x + dx; + this.y = this.y + dy; + + float firstTerm = ((1 / this.halfStartingDistance) * (this.totalTravelDistance - this.halfStartingDistance)); + firstTerm = firstTerm * firstTerm; + this.z = this.startingHeight + ((-firstTerm + 1) * this.arcPeakHeight) + dz; + + return this.done; + } + + public float getX() { + return this.x; + } + + public float getY() { + return this.y; + } + + public float getZ() { + return this.z; + } + + public float getSpeed() { + return this.speed; + } + + public CWidget getTarget() { + return this.target; + } + + public boolean isDone() { + return this.done; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/ProjectileCreator.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/ProjectileCreator.java new file mode 100644 index 0000000..1f182c9 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/ProjectileCreator.java @@ -0,0 +1,10 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.util; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.projectile.CAttackProjectile; + +public interface ProjectileCreator { + CAttackProjectile create(CSimulation simulation, CUnit source, int attackIndex, CWidget target); +} From ec3fa06e016a83c24108a0024226c2a5702ab433 Mon Sep 17 00:00:00 2001 From: Retera Date: Sun, 1 Mar 2020 14:41:53 -0600 Subject: [PATCH 023/116] Test with skew --- .../warsmash/util/WarsmashConstants.java | 2 +- .../w3x/rendersim/RenderAttackProjectile.java | 27 ++++++++++++++++++- .../projectile/CAttackProjectile.java | 8 +++++- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/core/src/com/etheller/warsmash/util/WarsmashConstants.java b/core/src/com/etheller/warsmash/util/WarsmashConstants.java index ea441be..1205058 100644 --- a/core/src/com/etheller/warsmash/util/WarsmashConstants.java +++ b/core/src/com/etheller/warsmash/util/WarsmashConstants.java @@ -3,5 +3,5 @@ package com.etheller.warsmash.util; public class WarsmashConstants { public static final int MAX_PLAYERS = 16; public static final int REPLACEABLE_TEXTURE_LIMIT = 64; - public static final float SIMULATION_STEP_TIME = 1 / 20f; + public static final float SIMULATION_STEP_TIME = 1 / 60f; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackProjectile.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackProjectile.java index 20306cb..bb5f6dd 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackProjectile.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackProjectile.java @@ -3,6 +3,8 @@ package com.etheller.warsmash.viewer5.handlers.w3x.rendersim; import java.util.List; import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.math.Quaternion; +import com.badlogic.gdx.math.Vector3; import com.etheller.warsmash.parsers.mdlx.Sequence; import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; @@ -12,6 +14,9 @@ import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.projectile.CAttackProjectile; public class RenderAttackProjectile { + private static final Quaternion pitchHeap = new Quaternion(); + private static final Vector3 skewVector = new Vector3(); + private final CAttackProjectile simulationProjectile; private final MdxComplexInstance modelInstance; private float x; @@ -56,7 +61,27 @@ public class RenderAttackProjectile { this.z = this.z + ((speed * simDz) / simD); } - this.modelInstance.setLocation(this.x, this.y, this.z); + final float dxToTarget = this.simulationProjectile.getTarget().getX() - this.x; + final float dyToTarget = this.simulationProjectile.getTarget().getY() - this.y; + final float dzToTarget = this.simulationProjectile.getTarget().getFlyHeight() - this.z; + final float d2DToTarget = (float) Math.sqrt((dxToTarget * dxToTarget) + (dyToTarget * dyToTarget)); + final float yaw = (float) Math.atan2(dxToTarget, dyToTarget); + + final float pitch = (float) Math.atan2(dzToTarget, d2DToTarget); + + final float arcCurrentHeight = this.simulationProjectile.getArcCurrentHeight(); + final float oppositeYaw = yaw + (float) Math.PI; + skewVector.set((float) (Math.cos(oppositeYaw) * Math.cos(Math.PI / 4)), + (float) (Math.sin(oppositeYaw) * Math.cos(Math.PI / 4)), (float) (Math.sin(Math.PI / 4))); + + final float skewX = arcCurrentHeight * skewVector.x; + final float skewY = arcCurrentHeight * skewVector.y; + final float skewZ = arcCurrentHeight * skewVector.z; + + this.modelInstance.setLocation(this.x + skewX, this.y + skewY, this.z + skewZ); +// this.modelInstance.localRotation.setFromAxisRad(0, 0, 1, yaw); + this.modelInstance.localRotation.setFromAxisRad(0, -1, 0, pitch); +// this.modelInstance.rotateLocal(pitchHeap.setFromAxisRad(0, -1, 0, pitch)); war3MapViewer.worldScene.grid.moved(this.modelInstance); final boolean everythingDone = this.simulationProjectile.isDone() && this.modelInstance.sequenceEnded; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/projectile/CAttackProjectile.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/projectile/CAttackProjectile.java index 6b2750d..7727ee9 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/projectile/CAttackProjectile.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/projectile/CAttackProjectile.java @@ -19,6 +19,7 @@ public class CAttackProjectile { private boolean done; private final CUnit source; private final int damage; + private float arcCurrentHeight; public CAttackProjectile(final float x, final float y, final float z, final float speed, final float arc, final CWidget target, final CUnit source, final int damage) { @@ -71,7 +72,8 @@ public class CAttackProjectile { float firstTerm = ((1 / this.halfStartingDistance) * (this.totalTravelDistance - this.halfStartingDistance)); firstTerm = firstTerm * firstTerm; - this.z = this.startingHeight + ((-firstTerm + 1) * this.arcPeakHeight) + dz; + this.arcCurrentHeight = (-firstTerm + 1) * this.arcPeakHeight; + this.z = this.startingHeight + dz; return this.done; } @@ -99,4 +101,8 @@ public class CAttackProjectile { public boolean isDone() { return this.done; } + + public float getArcCurrentHeight() { + return this.arcCurrentHeight; + } } From 0e34a7d539a3b64282055ec870c26583f1837355 Mon Sep 17 00:00:00 2001 From: Retera Date: Sat, 7 Mar 2020 16:18:56 -0600 Subject: [PATCH 024/116] Some small updates --- .../warsmash/util/WarsmashConstants.java | 2 +- .../viewer5/handlers/w3x/War3MapViewer.java | 33 +++++++++++++------ .../w3x/rendersim/RenderAttackProjectile.java | 33 +++++++------------ .../handlers/w3x/simulation/CSimulation.java | 6 ++-- .../handlers/w3x/simulation/CUnit.java | 16 +++++++-- .../w3x/simulation/data/CUnitData.java | 23 ++++++++++--- .../w3x/simulation/orders/CAttackOrder.java | 10 +++--- .../projectile/CAttackProjectile.java | 11 +++---- 8 files changed, 83 insertions(+), 51 deletions(-) diff --git a/core/src/com/etheller/warsmash/util/WarsmashConstants.java b/core/src/com/etheller/warsmash/util/WarsmashConstants.java index 1205058..ea441be 100644 --- a/core/src/com/etheller/warsmash/util/WarsmashConstants.java +++ b/core/src/com/etheller/warsmash/util/WarsmashConstants.java @@ -3,5 +3,5 @@ package com.etheller.warsmash.util; public class WarsmashConstants { public static final int MAX_PLAYERS = 16; public static final int REPLACEABLE_TEXTURE_LIMIT = 64; - public static final float SIMULATION_STEP_TIME = 1 / 60f; + public static final float SIMULATION_STEP_TIME = 1 / 20f; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index 94b4e9c..21a2e12 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -310,11 +310,16 @@ public class War3MapViewer extends ModelViewer { @Override public CAttackProjectile create(final CSimulation simulation, final CUnit source, final int attackIndex, final CWidget target) { - final int a1ProjectileSpeed = simulation.getUnitData().getA1ProjectileSpeed(source.getTypeId()); - final float a1ProjectileArc = simulation.getUnitData().getA1ProjectileArc(source.getTypeId()); - String a1MissileArt = simulation.getUnitData().getA1MissileArt(source.getTypeId()); - final int a1MinDamage = simulation.getUnitData().getA1MinDamage(source.getTypeId()); - final int a1MaxDamage = simulation.getUnitData().getA1MaxDamage(source.getTypeId()); + final War3ID typeId = source.getTypeId(); + final int a1ProjectileSpeed = simulation.getUnitData().getA1ProjectileSpeed(typeId); + final float a1ProjectileArc = simulation.getUnitData().getA1ProjectileArc(typeId); + String a1MissileArt = simulation.getUnitData().getA1MissileArt(typeId); + final int a1MinDamage = simulation.getUnitData().getA1MinDamage(typeId); + final int a1MaxDamage = simulation.getUnitData().getA1MaxDamage(typeId); + final float projectileLaunchX = simulation.getUnitData().getProjectileLaunchX(typeId); + final float projectileLaunchY = simulation.getUnitData().getProjectileLaunchY(typeId); + final float projectileLaunchZ = simulation.getUnitData().getProjectileLaunchZ(typeId); + final int damage = War3MapViewer.this.seededRandom.nextInt(a1MaxDamage - a1MinDamage) + a1MinDamage; if (a1MissileArt.toLowerCase().endsWith(".mdl")) { @@ -323,15 +328,23 @@ public class War3MapViewer extends ModelViewer { if (!a1MissileArt.toLowerCase().endsWith(".mdx")) { a1MissileArt += ".mdx"; } - final float x = source.getX(); - final float y = source.getY(); - final float height = War3MapViewer.this.terrain.getGroundHeight(x, y) + source.getFlyHeight(); + final float facing = (float) Math.toRadians(source.getFacing()); + final float sinFacing = (float) Math.sin(facing); + final float cosFacing = (float) Math.cos(facing); + final float x = (source.getX() + (projectileLaunchX * cosFacing)) + - (projectileLaunchY * sinFacing); + final float y = source.getY() + (projectileLaunchX * sinFacing) + + (projectileLaunchY * cosFacing); + + final float height = War3MapViewer.this.terrain.getGroundHeight(x, y) + source.getFlyHeight() + + projectileLaunchZ; final CAttackProjectile simulationAttackProjectile = new CAttackProjectile(x, y, height, a1ProjectileSpeed, a1ProjectileArc, target, source, damage); final MdxModel model = (MdxModel) load(a1MissileArt, War3MapViewer.this.mapPathSolver, War3MapViewer.this.solverParams); final MdxComplexInstance modelInstance = (MdxComplexInstance) model.addInstance(); + modelInstance.setTeamColor(source.getPlayerIndex()); modelInstance.setScene(War3MapViewer.this.worldScene); StandSequence.randomBirthSequence(modelInstance); modelInstance.setLocation(x, y, height); @@ -567,8 +580,8 @@ public class War3MapViewer extends ModelViewer { else { angle = (float) Math.toDegrees(unit.getAngle()); } - final CUnit simulationUnit = this.simulation.createUnit(row.getAlias(), unit.getLocation()[0], - unit.getLocation()[1], angle); + final CUnit simulationUnit = this.simulation.createUnit(row.getAlias(), unit.getPlayer(), + unit.getLocation()[0], unit.getLocation()[1], angle); final RenderUnit renderUnit = new RenderUnit(this, model, row, unit, soundset, portraitModel, simulationUnit); this.units.add(renderUnit); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackProjectile.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackProjectile.java index bb5f6dd..f5da9f9 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackProjectile.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackProjectile.java @@ -4,7 +4,6 @@ import java.util.List; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.math.Quaternion; -import com.badlogic.gdx.math.Vector3; import com.etheller.warsmash.parsers.mdlx.Sequence; import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; @@ -15,7 +14,6 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.projectile.CAttackP public class RenderAttackProjectile { private static final Quaternion pitchHeap = new Quaternion(); - private static final Vector3 skewVector = new Vector3(); private final CAttackProjectile simulationProjectile; private final MdxComplexInstance modelInstance; @@ -37,7 +35,7 @@ public class RenderAttackProjectile { final MdxModel model = (MdxModel) this.modelInstance.model; final List sequences = model.getSequences(); final IndexedSequence sequence = StandSequence.selectSequence("death", sequences); - if (this.modelInstance.sequence != sequence.index) { + if ((sequence != null) && (this.modelInstance.sequence != sequence.index)) { this.modelInstance.setSequence(sequence.index); } } @@ -61,27 +59,20 @@ public class RenderAttackProjectile { this.z = this.z + ((speed * simDz) / simD); } - final float dxToTarget = this.simulationProjectile.getTarget().getX() - this.x; - final float dyToTarget = this.simulationProjectile.getTarget().getY() - this.y; - final float dzToTarget = this.simulationProjectile.getTarget().getFlyHeight() - this.z; + final float targetX = this.simulationProjectile.getTarget().getX(); + final float dxToTarget = targetX - this.x; + final float targetY = this.simulationProjectile.getTarget().getY(); + final float dyToTarget = targetY - this.y; + final float dzToTarget = (war3MapViewer.terrain.getGroundHeight(targetX, targetY) + + this.simulationProjectile.getTarget().getFlyHeight()) - this.z; final float d2DToTarget = (float) Math.sqrt((dxToTarget * dxToTarget) + (dyToTarget * dyToTarget)); - final float yaw = (float) Math.atan2(dxToTarget, dyToTarget); + final float yaw = (float) Math.atan2(dyToTarget, dxToTarget); - final float pitch = (float) Math.atan2(dzToTarget, d2DToTarget); + final float pitch = (float) Math.atan2(simDz, simD); - final float arcCurrentHeight = this.simulationProjectile.getArcCurrentHeight(); - final float oppositeYaw = yaw + (float) Math.PI; - skewVector.set((float) (Math.cos(oppositeYaw) * Math.cos(Math.PI / 4)), - (float) (Math.sin(oppositeYaw) * Math.cos(Math.PI / 4)), (float) (Math.sin(Math.PI / 4))); - - final float skewX = arcCurrentHeight * skewVector.x; - final float skewY = arcCurrentHeight * skewVector.y; - final float skewZ = arcCurrentHeight * skewVector.z; - - this.modelInstance.setLocation(this.x + skewX, this.y + skewY, this.z + skewZ); -// this.modelInstance.localRotation.setFromAxisRad(0, 0, 1, yaw); - this.modelInstance.localRotation.setFromAxisRad(0, -1, 0, pitch); -// this.modelInstance.rotateLocal(pitchHeap.setFromAxisRad(0, -1, 0, pitch)); + this.modelInstance.setLocation(this.x, this.y, this.z); + this.modelInstance.localRotation.setFromAxisRad(0, 0, 1, yaw); + this.modelInstance.rotate(pitchHeap.setFromAxisRad(0, -1, 0, pitch)); war3MapViewer.worldScene.grid.moved(this.modelInstance); final boolean everythingDone = this.simulationProjectile.isDone() && this.modelInstance.sequenceEnded; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java index 8272670..708335c 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java @@ -42,8 +42,10 @@ public class CSimulation { return this.units; } - public CUnit createUnit(final War3ID typeId, final float x, final float y, final float facing) { - final CUnit unit = this.unitData.create(this, this.handleIdAllocator.createId(), typeId, x, y, facing); + public CUnit createUnit(final War3ID typeId, final int playerIndex, final float x, final float y, + final float facing) { + final CUnit unit = this.unitData.create(this, this.handleIdAllocator.createId(), playerIndex, typeId, x, y, + facing); this.units.add(unit); return unit; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java index 336c5a8..356de5b 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java @@ -17,15 +17,16 @@ public class CUnit extends CWidget { private int speed; private int cooldownEndTime = 0; private float flyHeight; + private int playerIndex; private final List abilities = new ArrayList<>(); private COrder currentOrder; private final Queue orderQueue = new LinkedList<>(); - public CUnit(final int handleId, final float x, final float y, final float life, final War3ID typeId, - final float facing, final float mana, final int maximumLife, final int maximumMana, final int speed, - final float defaultFlyingHeight) { + public CUnit(final int handleId, final int playerIndex, final float x, final float y, final float life, + final War3ID typeId, final float facing, final float mana, final int maximumLife, final int maximumMana, + final int speed, final float defaultFlyingHeight) { super(handleId, x, y, life); this.typeId = typeId; this.facing = facing; @@ -132,6 +133,7 @@ public class CUnit extends CWidget { return this.cooldownEndTime; } + @Override public float getFlyHeight() { return this.flyHeight; } @@ -140,4 +142,12 @@ public class CUnit extends CWidget { this.flyHeight = flyHeight; } + public int getPlayerIndex() { + return this.playerIndex; + } + + public void setPlayerIndex(final int playerIndex) { + this.playerIndex = playerIndex; + } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java index 79bb8de..9f96282 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java @@ -20,6 +20,9 @@ public class CUnitData { private static final War3ID TURN_RATE = War3ID.fromString("umvr"); private static final War3ID IS_BLDG = War3ID.fromString("ubdg"); private static final War3ID NAME = War3ID.fromString("unam"); + private static final War3ID PROJECTILE_LAUNCH_X = War3ID.fromString("ulpx"); + private static final War3ID PROJECTILE_LAUNCH_Y = War3ID.fromString("ulpy"); + private static final War3ID PROJECTILE_LAUNCH_Z = War3ID.fromString("ulpz"); private static final War3ID ATTACK1_DMG_BASE = War3ID.fromString("ua1b"); private static final War3ID ATTACK1_DMG_DICE = War3ID.fromString("ua1d"); private static final War3ID ATTACK1_DMG_SIDES_PER_DIE = War3ID.fromString("ua1s"); @@ -42,16 +45,16 @@ public class CUnitData { this.unitData = unitData; } - public CUnit create(final CSimulation simulation, final int handleId, final War3ID typeId, final float x, - final float y, final float facing) { + public CUnit create(final CSimulation simulation, final int playerIndex, final int handleId, final War3ID typeId, + final float x, final float y, final float facing) { final MutableGameObject unitType = this.unitData.get(typeId); final int life = unitType.getFieldAsInteger(HIT_POINT_MAXIMUM, 0); final int manaInitial = unitType.getFieldAsInteger(MANA_INITIAL_AMOUNT, 0); final int manaMaximum = unitType.getFieldAsInteger(MANA_MAXIMUM, 0); final int speed = unitType.getFieldAsInteger(MOVEMENT_SPEED_BASE, 0); final float moveHeight = unitType.getFieldAsFloat(MOVE_HEIGHT, 0); - final CUnit unit = new CUnit(handleId, x, y, life, typeId, facing, manaInitial, life, manaMaximum, speed, - moveHeight); + final CUnit unit = new CUnit(handleId, playerIndex, x, y, life, typeId, facing, manaInitial, life, manaMaximum, + speed, moveHeight); if (speed > 0) { unit.add(simulation, CAbilityMove.INSTANCE); unit.add(simulation, CAbilityPatrol.INSTANCE); @@ -139,4 +142,16 @@ public class CUnitData { public float getA2Cooldown(final War3ID unitTypeId) { return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK2_COOLDOWN, 0); } + + public float getProjectileLaunchX(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(PROJECTILE_LAUNCH_X, 0); + } + + public float getProjectileLaunchY(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(PROJECTILE_LAUNCH_Y, 0); + } + + public float getProjectileLaunchZ(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(PROJECTILE_LAUNCH_Z, 0); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CAttackOrder.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CAttackOrder.java index cd92835..3513b06 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CAttackOrder.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CAttackOrder.java @@ -28,11 +28,11 @@ public class CAttackOrder implements COrder { if (goalAngle < 0) { goalAngle += 360; } - final float facing = this.unit.getFacing(); + float facing = this.unit.getFacing(); float delta = goalAngle - facing; - final float absDelta = Math.abs(delta); final float propulsionWindow = simulation.getUnitData().getPropulsionWindow(this.unit.getTypeId()); final float turnRate = simulation.getUnitData().getTurnRate(this.unit.getTypeId()); + final int speed = this.unit.getSpeed(); if (delta < -180) { delta = 360 + delta; @@ -40,16 +40,18 @@ public class CAttackOrder implements COrder { if (delta > 180) { delta = -360 + delta; } + final float absDelta = Math.abs(delta); if ((absDelta <= 1.0) && (absDelta != 0)) { this.unit.setFacing(goalAngle); } else { - float angleToAdd = ((Math.signum(delta) * turnRate) * WarsmashConstants.SIMULATION_STEP_TIME) * 360; + float angleToAdd = Math.signum(delta) * (float) Math.toDegrees(turnRate); if (absDelta < Math.abs(angleToAdd)) { angleToAdd = delta; } - this.unit.setFacing(facing + angleToAdd); + facing += angleToAdd; + this.unit.setFacing(facing); } if (absDelta < propulsionWindow) { this.wasWithinPropWindow = true; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/projectile/CAttackProjectile.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/projectile/CAttackProjectile.java index 7727ee9..708f998 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/projectile/CAttackProjectile.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/projectile/CAttackProjectile.java @@ -11,7 +11,6 @@ public class CAttackProjectile { private float z; private final float startingHeight; private final float speed; - private final float arc; private final CWidget target; private final float halfStartingDistance; private final float arcPeakHeight; @@ -28,7 +27,6 @@ public class CAttackProjectile { this.z = z; this.startingHeight = z; this.speed = speed; - this.arc = arc; this.target = target; final float dx = target.getX() - x; final float dy = target.getY() - y; @@ -70,10 +68,11 @@ public class CAttackProjectile { this.x = this.x + dx; this.y = this.y + dy; - float firstTerm = ((1 / this.halfStartingDistance) * (this.totalTravelDistance - this.halfStartingDistance)); - firstTerm = firstTerm * firstTerm; - this.arcCurrentHeight = (-firstTerm + 1) * this.arcPeakHeight; - this.z = this.startingHeight + dz; + final float distanceToPeak = this.totalTravelDistance - this.halfStartingDistance; + final float normPeakDist = distanceToPeak / this.halfStartingDistance; + final float currentHeightPercentage = 1 - (normPeakDist * normPeakDist); + this.arcCurrentHeight = currentHeightPercentage * this.arcPeakHeight; + this.z = this.startingHeight + dz + this.arcCurrentHeight; return this.done; } From fabc3f4e615879ba92f0e23e2fb2b808fc3c02c6 Mon Sep 17 00:00:00 2001 From: Retera Date: Sun, 8 Mar 2020 15:23:51 -0500 Subject: [PATCH 025/116] Begin networking --- .../etheller/warsmash/MathSpeedBenchmark.java | 6 +- .../warsmash/networking/TestClient.java | 32 ++++++++ .../networking/WarsmashGameServer.java | 82 +++++++++++++++++++ .../warsmash/util/WarsmashConstants.java | 1 + .../viewer5/handlers/w3x/War3MapViewer.java | 14 ++-- .../w3x/rendersim/RenderAttackProjectile.java | 71 +++++++++++----- .../handlers/w3x/rendersim/RenderUnit.java | 2 +- .../projectile/CAttackProjectile.java | 35 +------- 8 files changed, 178 insertions(+), 65 deletions(-) create mode 100644 core/src/com/etheller/warsmash/networking/TestClient.java create mode 100644 core/src/com/etheller/warsmash/networking/WarsmashGameServer.java diff --git a/core/src/com/etheller/warsmash/MathSpeedBenchmark.java b/core/src/com/etheller/warsmash/MathSpeedBenchmark.java index be1f00d..9968868 100644 --- a/core/src/com/etheller/warsmash/MathSpeedBenchmark.java +++ b/core/src/com/etheller/warsmash/MathSpeedBenchmark.java @@ -49,13 +49,13 @@ public class MathSpeedBenchmark { final float murlocY) { final float dx = murlocX - thrallX; final float dy = murlocY - thrallY; - return (float) Math.sqrt((dx * dx) + (dy * dy)); + return (float) StrictMath.sqrt((dx * dx) + (dy * dy)); } static float groundDistanceCos(final float thrallX, final float thrallY, final float murlocX, final float murlocY) { final float dx = murlocX - thrallX; final float dy = murlocY - thrallY; - final double angle = Math.atan2(dy, dx); - return (float) (dx / Math.cos(angle)); + final double angle = StrictMath.atan2(dy, dx); + return (float) (dx / StrictMath.cos(angle)); } } diff --git a/core/src/com/etheller/warsmash/networking/TestClient.java b/core/src/com/etheller/warsmash/networking/TestClient.java new file mode 100644 index 0000000..6d534f2 --- /dev/null +++ b/core/src/com/etheller/warsmash/networking/TestClient.java @@ -0,0 +1,32 @@ +package com.etheller.warsmash.networking; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.DatagramChannel; + +import com.etheller.warsmash.util.WarsmashConstants; + +public class TestClient { + + public static void main(final String[] args) { + try { + try (final DatagramChannel channel = DatagramChannel.open() + .connect(new InetSocketAddress(InetAddress.getLocalHost(), WarsmashConstants.PORT_NUMBER))) { + final ByteBuffer buffer = ByteBuffer.allocate(256); + buffer.clear(); + buffer.put((byte) 3); + buffer.put((byte) 2); + buffer.put((byte) 4); + buffer.put((byte) 1); + buffer.flip(); + channel.write(buffer); + } + } + catch (final IOException e) { + e.printStackTrace(); + } + } + +} diff --git a/core/src/com/etheller/warsmash/networking/WarsmashGameServer.java b/core/src/com/etheller/warsmash/networking/WarsmashGameServer.java new file mode 100644 index 0000000..c10be23 --- /dev/null +++ b/core/src/com/etheller/warsmash/networking/WarsmashGameServer.java @@ -0,0 +1,82 @@ +package com.etheller.warsmash.networking; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.DatagramChannel; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.util.Iterator; +import java.util.Set; + +import com.etheller.warsmash.util.WarsmashConstants; + +public class WarsmashGameServer implements Runnable { + + private final Selector selector; + private boolean running; + private final SelectionKey key; + private final ByteBuffer readBuffer; + + public WarsmashGameServer() throws IOException { + this.selector = Selector.open(); + this.running = true; + final DatagramChannel channel = DatagramChannel.open() + .bind(new InetSocketAddress(WarsmashConstants.PORT_NUMBER)); + channel.configureBlocking(false); + this.key = channel.register(this.selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE); + this.readBuffer = ByteBuffer.allocate(256); + } + + @Override + public void run() { + while (this.running) { + try { + final int selectedKeyCount = this.selector.select(); + if (selectedKeyCount > 0) { + final Set selectedKeys = this.selector.selectedKeys(); + + final Iterator keyIterator = selectedKeys.iterator(); + + while (keyIterator.hasNext()) { + final SelectionKey key = keyIterator.next(); + + if (key.isReadable()) { + final DatagramChannel channel = (DatagramChannel) key.channel(); + this.readBuffer.clear(); + final SocketAddress receiveAddr = channel.receive(this.readBuffer); + this.readBuffer.flip(); + System.out.println("NOTE - Received from: " + receiveAddr); + while (this.readBuffer.hasRemaining()) { + System.out.println("NOTE - Received: " + this.readBuffer.get()); + } + } + + keyIterator.remove(); + } + } + } + catch (final IOException e) { + System.err.println("Error reading from channel:"); + e.printStackTrace(); + } + } + } + + public void setRunning(final boolean running) { + this.running = running; + } + + public static void main(final String[] args) { + WarsmashGameServer warsmashGameServer; + try { + warsmashGameServer = new WarsmashGameServer(); + new Thread(warsmashGameServer).start(); + } + catch (final IOException e) { + e.printStackTrace(); + } + } + +} diff --git a/core/src/com/etheller/warsmash/util/WarsmashConstants.java b/core/src/com/etheller/warsmash/util/WarsmashConstants.java index ea441be..24a43ec 100644 --- a/core/src/com/etheller/warsmash/util/WarsmashConstants.java +++ b/core/src/com/etheller/warsmash/util/WarsmashConstants.java @@ -4,4 +4,5 @@ public class WarsmashConstants { public static final int MAX_PLAYERS = 16; public static final int REPLACEABLE_TEXTURE_LIMIT = 64; public static final float SIMULATION_STEP_TIME = 1 / 20f; + public static final int PORT_NUMBER = 6115; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index 21a2e12..76296e2 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -331,15 +331,15 @@ public class War3MapViewer extends ModelViewer { final float facing = (float) Math.toRadians(source.getFacing()); final float sinFacing = (float) Math.sin(facing); final float cosFacing = (float) Math.cos(facing); - final float x = (source.getX() + (projectileLaunchX * cosFacing)) - - (projectileLaunchY * sinFacing); - final float y = source.getY() + (projectileLaunchX * sinFacing) - + (projectileLaunchY * cosFacing); + final float x = (source.getX() + (projectileLaunchY * cosFacing)) + - (projectileLaunchX * sinFacing); + final float y = source.getY() + (projectileLaunchY * sinFacing) + + (projectileLaunchX * cosFacing); final float height = War3MapViewer.this.terrain.getGroundHeight(x, y) + source.getFlyHeight() + projectileLaunchZ; - final CAttackProjectile simulationAttackProjectile = new CAttackProjectile(x, y, height, - a1ProjectileSpeed, a1ProjectileArc, target, source, damage); + final CAttackProjectile simulationAttackProjectile = new CAttackProjectile(x, y, + a1ProjectileSpeed, target, source, damage); final MdxModel model = (MdxModel) load(a1MissileArt, War3MapViewer.this.mapPathSolver, War3MapViewer.this.solverParams); @@ -349,7 +349,7 @@ public class War3MapViewer extends ModelViewer { StandSequence.randomBirthSequence(modelInstance); modelInstance.setLocation(x, y, height); final RenderAttackProjectile renderAttackProjectile = new RenderAttackProjectile( - simulationAttackProjectile, modelInstance); + simulationAttackProjectile, modelInstance, height, a1ProjectileArc, War3MapViewer.this); War3MapViewer.this.projectiles.add(renderAttackProjectile); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackProjectile.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackProjectile.java index f5da9f9..3f92efd 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackProjectile.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackProjectile.java @@ -20,14 +20,34 @@ public class RenderAttackProjectile { private float x; private float y; private float z; + private final float startingHeight; + private final float arcPeakHeight; + private float totalTravelDistance; - public RenderAttackProjectile(final CAttackProjectile simulationProjectile, - final MdxComplexInstance modelInstance) { + private final float targetHeight; + + private float yaw; + + private float pitch; + + public RenderAttackProjectile(final CAttackProjectile simulationProjectile, final MdxComplexInstance modelInstance, + final float z, final float arc, final War3MapViewer war3MapViewer) { this.simulationProjectile = simulationProjectile; this.modelInstance = modelInstance; this.x = simulationProjectile.getX(); this.y = simulationProjectile.getY(); - this.z = simulationProjectile.getZ(); + this.z = z; + this.startingHeight = z; + final float targetX = this.simulationProjectile.getTarget().getX(); + final float targetY = this.simulationProjectile.getTarget().getY(); + final float dxToTarget = targetX - this.x; + final float dyToTarget = targetY - this.y; + final float d2DToTarget = (float) StrictMath.sqrt((dxToTarget * dxToTarget) + (dyToTarget * dyToTarget)); + final float startingDistance = d2DToTarget + this.totalTravelDistance; + this.targetHeight = (war3MapViewer.terrain.getGroundHeight(targetX, targetY) + + this.simulationProjectile.getTarget().getFlyHeight()); + this.arcPeakHeight = arc * startingDistance; + this.yaw = (float) StrictMath.atan2(dyToTarget, dxToTarget); } public boolean updateAnimations(final War3MapViewer war3MapViewer) { @@ -46,33 +66,42 @@ public class RenderAttackProjectile { } final float simX = this.simulationProjectile.getX(); final float simY = this.simulationProjectile.getY(); - final float simZ = this.simulationProjectile.getZ(); final float simDx = simX - this.x; final float simDy = simY - this.y; - final float simDz = simZ - this.z; - final float simD = (float) Math.sqrt((simDx * simDx) + (simDy * simDy)); + final float simD = (float) StrictMath.sqrt((simDx * simDx) + (simDy * simDy)); final float deltaTime = Gdx.graphics.getDeltaTime(); - final float speed = Math.min(simD, this.simulationProjectile.getSpeed() * deltaTime); + final float speed = StrictMath.min(simD, this.simulationProjectile.getSpeed() * deltaTime); if (simD > 0) { this.x = this.x + ((speed * simDx) / simD); this.y = this.y + ((speed * simDy) / simD); - this.z = this.z + ((speed * simDz) / simD); + final float targetX = this.simulationProjectile.getTarget().getX(); + final float targetY = this.simulationProjectile.getTarget().getY(); + final float dxToTarget = targetX - this.x; + final float dyToTarget = targetY - this.y; + final float d2DToTarget = (float) StrictMath.sqrt((dxToTarget * dxToTarget) + (dyToTarget * dyToTarget)); + final float startingDistance = d2DToTarget + this.totalTravelDistance; + final float halfStartingDistance = startingDistance / 2f; + + final float dtsz = this.targetHeight - this.startingHeight; + final float d1z = dtsz / (halfStartingDistance * 2); + this.totalTravelDistance += speed; + final float dz = d1z * this.totalTravelDistance; + + final float distanceToPeak = this.totalTravelDistance - halfStartingDistance; + final float normPeakDist = distanceToPeak / halfStartingDistance; + final float currentHeightPercentage = 1 - (normPeakDist * normPeakDist); + final float arcCurrentHeight = currentHeightPercentage * this.arcPeakHeight; + this.z = this.startingHeight + dz + arcCurrentHeight; + + this.yaw = (float) StrictMath.atan2(dyToTarget, dxToTarget); + + final float slope = (-2 * (normPeakDist) * this.arcPeakHeight) / halfStartingDistance; + this.pitch = (float) StrictMath.atan2(slope + d1z, 1); } - final float targetX = this.simulationProjectile.getTarget().getX(); - final float dxToTarget = targetX - this.x; - final float targetY = this.simulationProjectile.getTarget().getY(); - final float dyToTarget = targetY - this.y; - final float dzToTarget = (war3MapViewer.terrain.getGroundHeight(targetX, targetY) - + this.simulationProjectile.getTarget().getFlyHeight()) - this.z; - final float d2DToTarget = (float) Math.sqrt((dxToTarget * dxToTarget) + (dyToTarget * dyToTarget)); - final float yaw = (float) Math.atan2(dyToTarget, dxToTarget); - - final float pitch = (float) Math.atan2(simDz, simD); - this.modelInstance.setLocation(this.x, this.y, this.z); - this.modelInstance.localRotation.setFromAxisRad(0, 0, 1, yaw); - this.modelInstance.rotate(pitchHeap.setFromAxisRad(0, -1, 0, pitch)); + this.modelInstance.localRotation.setFromAxisRad(0, 0, 1, this.yaw); + this.modelInstance.rotate(pitchHeap.setFromAxisRad(0, -1, 0, this.pitch)); war3MapViewer.worldScene.grid.moved(this.modelInstance); final boolean everythingDone = this.simulationProjectile.isDone() && this.modelInstance.sequenceEnded; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java index 537e33b..32f75f5 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java @@ -105,7 +105,7 @@ public class RenderUnit { ability.getOrderId())); } else if (ability instanceof CAbilityAttack) { - this.commandCardIcons.add(new CommandCardIcon(4, 0, + this.commandCardIcons.add(new CommandCardIcon(-2, -2, ImageUtils.getBLPTexture(map.dataSource, "ReplaceableTextures\\CommandButtons\\BTNAttack.blp"), ability.getOrderId())); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/projectile/CAttackProjectile.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/projectile/CAttackProjectile.java index 708f998..4246f3c 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/projectile/CAttackProjectile.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/projectile/CAttackProjectile.java @@ -8,31 +8,18 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; public class CAttackProjectile { private float x; private float y; - private float z; - private final float startingHeight; private final float speed; private final CWidget target; - private final float halfStartingDistance; - private final float arcPeakHeight; - private float totalTravelDistance; private boolean done; private final CUnit source; private final int damage; - private float arcCurrentHeight; - public CAttackProjectile(final float x, final float y, final float z, final float speed, final float arc, - final CWidget target, final CUnit source, final int damage) { + public CAttackProjectile(final float x, final float y, final float speed, final CWidget target, final CUnit source, + final int damage) { this.x = x; this.y = y; - this.z = z; - this.startingHeight = z; this.speed = speed; this.target = target; - final float dx = target.getX() - x; - final float dy = target.getY() - y; - final float startingDistance = (float) Math.sqrt((dx * dx) + (dy * dy)); - this.halfStartingDistance = startingDistance / 2f; - this.arcPeakHeight = arc * startingDistance; this.source = source; this.damage = damage; } @@ -44,12 +31,10 @@ public class CAttackProjectile { final float sy = this.y; final float dtsx = tx - sx; final float dtsy = ty - sy; - final float dtsz = this.target.getFlyHeight() - this.startingHeight; final float c = (float) Math.sqrt((dtsx * dtsx) + (dtsy * dtsy)); final float d1x = dtsx / c; final float d1y = dtsy / c; - final float d1z = dtsz / (this.halfStartingDistance * 2); float travelDistance = Math.min(c, this.speed * WarsmashConstants.SIMULATION_STEP_TIME); if (c <= travelDistance) { @@ -62,18 +47,10 @@ public class CAttackProjectile { final float dx = d1x * travelDistance; final float dy = d1y * travelDistance; - this.totalTravelDistance += travelDistance; - final float dz = d1z * this.totalTravelDistance; this.x = this.x + dx; this.y = this.y + dy; - final float distanceToPeak = this.totalTravelDistance - this.halfStartingDistance; - final float normPeakDist = distanceToPeak / this.halfStartingDistance; - final float currentHeightPercentage = 1 - (normPeakDist * normPeakDist); - this.arcCurrentHeight = currentHeightPercentage * this.arcPeakHeight; - this.z = this.startingHeight + dz + this.arcCurrentHeight; - return this.done; } @@ -85,10 +62,6 @@ public class CAttackProjectile { return this.y; } - public float getZ() { - return this.z; - } - public float getSpeed() { return this.speed; } @@ -100,8 +73,4 @@ public class CAttackProjectile { public boolean isDone() { return this.done; } - - public float getArcCurrentHeight() { - return this.arcCurrentHeight; - } } From 2ac6a5fa1d4d90ab48457c9ccce5d1b329fcf485 Mon Sep 17 00:00:00 2001 From: Retera Date: Sat, 16 May 2020 14:36:41 -0400 Subject: [PATCH 026/116] Update with waves --- .../etheller/warsmash/WarsmashGdxGame.java | 20 +- .../etheller/warsmash/WarsmashGdxMapGame.java | 93 +++++++-- .../warsmash/parsers/mdlx/CollisionShape.java | 26 ++- .../com/etheller/warsmash/viewer5/Bounds.java | 15 ++ .../warsmash/viewer5/gl/Extensions.java | 5 + .../viewer5/gl/WireframeExtension.java | 5 + .../viewer5/handlers/mdx/CollisionShape.java | 80 ++++++++ .../mdx/EventObjectEmitterObject.java | 46 +++-- .../handlers/mdx/GeometryEmitterFuncs.java | 12 ++ .../handlers/mdx/MdxComplexInstance.java | 22 ++ .../viewer5/handlers/mdx/MdxShaders.java | 23 ++- .../viewer5/handlers/w3x/AnimationTokens.java | 71 +++++++ .../viewer5/handlers/w3x/SplatModel.java | 191 ++++++++++++++++-- .../viewer5/handlers/w3x/War3MapViewer.java | 59 +++++- .../w3x/environment/RenderCorner.java | 1 + .../handlers/w3x/environment/Terrain.java | 72 ++++++- .../w3x/environment/TerrainShaders.java | 30 ++- .../handlers/w3x/environment/WaveBuilder.java | 152 ++++++++++++++ .../handlers/w3x/rendersim/RenderUnit.java | 8 +- .../warsmash/desktop/DesktopLauncher.java | 9 + 20 files changed, 851 insertions(+), 89 deletions(-) create mode 100644 core/src/com/etheller/warsmash/viewer5/gl/WireframeExtension.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/AnimationTokens.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/WaveBuilder.java diff --git a/core/src/com/etheller/warsmash/WarsmashGdxGame.java b/core/src/com/etheller/warsmash/WarsmashGdxGame.java index 934f894..0d6bd42 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxGame.java @@ -24,7 +24,6 @@ import com.etheller.warsmash.viewer5.ModelViewer; import com.etheller.warsmash.viewer5.PathSolver; import com.etheller.warsmash.viewer5.Scene; import com.etheller.warsmash.viewer5.SolvedPath; -import com.etheller.warsmash.viewer5.handlers.mdx.EventObjectEmitterObject; import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxHandler; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; @@ -58,8 +57,7 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide System.err.println("Renderer: " + renderer); final FolderDataSourceDescriptor war3mpq = new FolderDataSourceDescriptor("E:\\Backups\\Warcraft\\Data\\127"); - final FolderDataSourceDescriptor testingFolder = new FolderDataSourceDescriptor( - "D:\\NEEDS_ORGANIZING\\MPQBuild\\Test"); + final FolderDataSourceDescriptor testingFolder = new FolderDataSourceDescriptor("E:\\Backups\\Warsmash\\Data"); final FolderDataSourceDescriptor currentFolder = new FolderDataSourceDescriptor("."); this.codebase = new CompoundDataSourceDescriptor( Arrays.asList(war3mpq, testingFolder, currentFolder)).createDataSource(); @@ -74,8 +72,8 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide this.cameraManager = new CameraManager(); this.cameraManager.setupCamera(scene); -// this.mainModel = (MdxModel) this.viewer.load("UI\\Glues\\MainMenu\\MainMenu3D_exp\\MainMenu3D_exp.mdx", - this.mainModel = (MdxModel) this.viewer.load("Units\\Human\\HeroPaladinBoss\\HeroPaladinBoss.mdx", + this.mainModel = (MdxModel) this.viewer.load("Buildings\\Other\\TempArtB\\TempArtB.mdx", +// this.mainModel = (MdxModel) this.viewer.load("Abilities\\Spells\\Orc\\FeralSpirit\\feralspirittarget.mdx", new PathSolver() { @Override public SolvedPath solve(final String src, final Object solverParams) { @@ -83,12 +81,12 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide } }, null); - final EventObjectEmitterObject evt = this.mainModel.getEventObjects().get(1); - for (final Sequence seq : this.mainModel.getSequences()) { - System.out.println(seq.getName() + ": " + Arrays.toString(seq.getInterval())); - } - System.out.println(Arrays.toString(evt.keyFrames)); - System.out.println(evt.name); +// final EventObjectEmitterObject evt = this.mainModel.getEventObjects().get(1); +// for (final Sequence seq : this.mainModel.getSequences()) { +// System.out.println(seq.getName() + ": " + Arrays.toString(seq.getInterval())); +// } +// System.out.println(Arrays.toString(evt.keyFrames)); +// System.out.println(evt.name); // this.modelCamera = this.mainModel.cameras.get(0); diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java index caa2dfb..fb8b6f9 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java @@ -5,6 +5,7 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.IntBuffer; import java.util.Arrays; +import java.util.LinkedList; import java.util.List; import javax.imageio.ImageIO; @@ -13,6 +14,7 @@ import com.badlogic.gdx.ApplicationAdapter; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Input; import com.badlogic.gdx.InputProcessor; +import com.badlogic.gdx.audio.Music; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.GL30; @@ -23,6 +25,8 @@ import com.badlogic.gdx.graphics.g2d.GlyphLayout; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator; import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator.FreeTypeFontParameter; +import com.badlogic.gdx.graphics.glutils.ShapeRenderer; +import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType; import com.badlogic.gdx.math.Quaternion; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Vector2; @@ -85,6 +89,11 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv private final Texture[] teamColors = new Texture[WarsmashConstants.MAX_PLAYERS]; private Texture solidGreenTexture; + private ShapeRenderer shapeRenderer; + private boolean showTalentTree; + + private final List messages = new LinkedList<>(); + @Override public void create() { @@ -107,8 +116,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv // "D:\\NEEDS_ORGANIZING\\MPQBuild\\War3xLocal.mpq\\enus-war3local.mpq"); // final FolderDataSourceDescriptor rebirth = new FolderDataSourceDescriptor( // "E:\\Games\\Warcraft III Patch 1.31 Rebirth"); - final FolderDataSourceDescriptor testingFolder = new FolderDataSourceDescriptor( - "D:\\NEEDS_ORGANIZING\\MPQBuild\\Test"); + final FolderDataSourceDescriptor testingFolder = new FolderDataSourceDescriptor("E:\\Backups\\Warsmash\\Data"); final FolderDataSourceDescriptor currentFolder = new FolderDataSourceDescriptor("."); this.codebase = new CompoundDataSourceDescriptor( Arrays.asList(war3mpq, /* war3xLocalmpq, */ testingFolder, currentFolder)) @@ -118,8 +126,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.viewer.worldScene.enableAudio(); this.viewer.enableAudio(); try { - // "Maps\\Campaign\\NightElf03.w3m" - this.viewer.loadMap("ProjectileTest.w3x"); + this.viewer.loadMap("ReforgedGeorgeVacation.w3x"); } catch (final IOException e) { throw new RuntimeException(e); @@ -170,7 +177,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.batch = new SpriteBatch(); - this.consoleUITexture = new Texture(new DataSourceFileHandle(this.viewer.dataSource, "AlphaUi.png")); +// this.consoleUITexture = new Texture(new DataSourceFileHandle(this.viewer.dataSource, "AlphaUi.png")); if (this.viewer.dataSource.has("war3mapMap.tga")) { try { this.minimapTexture = ImageUtils.getTextureNoColorCorrection(TgaFile.readTGA("war3mapMap.tga", @@ -204,12 +211,12 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv "ReplaceableTextures\\TeamColor\\TeamColor06.blp"); Gdx.input.setInputProcessor(this); -// -// final Music music = Gdx.audio -// .newMusic(new DataSourceFileHandle(this.viewer.dataSource, "Sound\\Music\\mp3Music\\OrcTheme.mp3")); -// music.setVolume(0.2f); -// music.setLooping(true); -// music.play(); + + final Music music = Gdx.audio + .newMusic(new DataSourceFileHandle(this.viewer.dataSource, "Sound\\Music\\mp3Music\\DarkAgents.mp3")); + music.setVolume(0.2f); + music.setLooping(true); + music.play(); this.minimap = new Rectangle(35, 7, 305, 272); final float worldWidth = (this.viewer.terrain.columns - 1); @@ -224,6 +231,9 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.cameraManager.target.x = this.viewer.startLocations[0].x; this.cameraManager.target.y = this.viewer.startLocations[0].y; + + this.shapeRenderer = new ShapeRenderer(); + this.talentTreeWindow = new Rectangle(100, 300, 1400, 800); } @Override @@ -265,7 +275,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.glyphLayout.setText(this.font, fpsString); this.font.draw(this.batch, fpsString, (this.uiViewport.getWorldWidth() - this.glyphLayout.width) / 2, 1100); - this.batch.draw(this.consoleUITexture, 0, 0, this.uiViewport.getWorldWidth(), 320); +// this.batch.draw(this.consoleUITexture, 0, 0, this.uiViewport.getWorldWidth(), 320); this.batch.draw(this.minimapTexture, 35, 7, 305, 272); if (this.selectedUnit != null) { @@ -281,6 +291,11 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.font20.draw(this.batch, "Defense:", 600, 98); this.font20.draw(this.batch, "Speed:", 600, 76); this.font20.setColor(Color.WHITE); + int messageIndex = 0; + for (final Message message : this.messages) { + this.font20.draw(this.batch, message.text, 100, 400 + (25 * (messageIndex++))); + } + this.font20.setColor(Color.WHITE); final int dmgMin = this.viewer.simulation.getUnitData() .getA1MinDamage(this.selectedUnit.getSimulationUnit().getTypeId()); final int dmgMax = this.viewer.simulation.getUnitData() @@ -325,6 +340,33 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv 4, 4); } this.batch.end(); + + if (this.showTalentTree) { + this.shapeRenderer.setProjectionMatrix(this.uiCamera.combined); + + this.shapeRenderer.setColor(Color.BLACK); + this.shapeRenderer.begin(ShapeType.Filled); + this.shapeRenderer.rect(this.talentTreeWindow.x, this.talentTreeWindow.y, this.talentTreeWindow.width, + this.talentTreeWindow.height); + this.shapeRenderer.end(); + + this.shapeRenderer.setColor(Color.YELLOW); + this.shapeRenderer.begin(ShapeType.Line); + this.shapeRenderer.rect(100, 300, 1400, 800); + this.shapeRenderer.end(); + + this.batch.begin(); + + this.font.setColor(Color.YELLOW); + final String title = "Mage Talent Tree"; + this.glyphLayout.setText(this.font, title); + this.font.draw(this.batch, title, + this.talentTreeWindow.x + ((this.talentTreeWindow.width - this.glyphLayout.width) / 2), + (this.talentTreeWindow.y + this.talentTreeWindow.height) - 45); + + this.batch.end(); + } + } @Override @@ -466,6 +508,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv private final Vector2 cameraVelocity = new Vector2(); private Scene portraitScene; private Texture minimapTexture; + private Rectangle talentTreeWindow; @Override public boolean keyDown(final int keycode) { @@ -509,9 +552,25 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv @Override public boolean touchDown(final int screenX, final int screenY, final int pointer, final int button) { System.out.println(screenX + "," + screenY); + clickLocationTemp2.x = screenX; clickLocationTemp2.y = screenY; this.uiViewport.unproject(clickLocationTemp2); + + if (this.selectedUnit != null) { + for (final CommandCardIcon commandCardIcon : this.selectedUnit.getCommandCardIcons()) { + if (new Rectangle(1225 + (70 * commandCardIcon.getX()), 160 - (70 * commandCardIcon.getY()), 64, 64) + .contains(clickLocationTemp2)) { + if (button == Input.Buttons.RIGHT) { + this.messages.add(new Message(Gdx.input.getCurrentEventTime(), "Right mouse click")); + } + else { + this.messages.add(new Message(Gdx.input.getCurrentEventTime(), "Left mouse click")); + } + return true; + } + } + } if (this.minimapFilledArea.contains(clickLocationTemp2.x, clickLocationTemp2.y)) { final float clickX = (clickLocationTemp2.x - this.minimapFilledArea.x) / this.minimapFilledArea.width; final float clickY = (clickLocationTemp2.y - this.minimapFilledArea.y) / this.minimapFilledArea.height; @@ -604,4 +663,14 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv } return true; } + + private static class Message { + private final float time; + private final String text; + + public Message(final float time, final String text) { + this.time = time; + this.text = text; + } + } } diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/CollisionShape.java b/core/src/com/etheller/warsmash/parsers/mdlx/CollisionShape.java index 7e82a21..f5fc810 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/CollisionShape.java +++ b/core/src/com/etheller/warsmash/parsers/mdlx/CollisionShape.java @@ -9,8 +9,8 @@ import com.google.common.io.LittleEndianDataOutputStream; public class CollisionShape extends GenericObject { public static enum Type { - PLANE, BOX, + PLANE, SPHERE, CYLINDER; @@ -77,12 +77,12 @@ public class CollisionShape extends GenericObject { public void readMdl(final MdlTokenInputStream stream) { for (final String token : super.readMdlGeneric(stream)) { switch (token) { - case MdlUtils.TOKEN_PLANE: - this.type = Type.PLANE; - break; case MdlUtils.TOKEN_BOX: this.type = Type.BOX; break; + case MdlUtils.TOKEN_PLANE: + this.type = Type.PLANE; + break; case MdlUtils.TOKEN_SPHERE: this.type = Type.SPHERE; break; @@ -116,12 +116,12 @@ public class CollisionShape extends GenericObject { String type; int vertices = 2; switch (this.type) { - case PLANE: - type = MdlUtils.TOKEN_PLANE; - break; case BOX: type = MdlUtils.TOKEN_BOX; break; + case PLANE: + type = MdlUtils.TOKEN_PLANE; + break; case SPHERE: type = MdlUtils.TOKEN_SPHERE; vertices = 1; @@ -164,4 +164,16 @@ public class CollisionShape extends GenericObject { return size; } + public float[][] getVertices() { + return this.vertices; + } + + public CollisionShape.Type getType() { + return this.type; + } + + public float getBoundsRadius() { + return this.boundsRadius; + } + } diff --git a/core/src/com/etheller/warsmash/viewer5/Bounds.java b/core/src/com/etheller/warsmash/viewer5/Bounds.java index 0de9b71..82413c7 100644 --- a/core/src/com/etheller/warsmash/viewer5/Bounds.java +++ b/core/src/com/etheller/warsmash/viewer5/Bounds.java @@ -1,7 +1,13 @@ package com.etheller.warsmash.viewer5; +import com.badlogic.gdx.math.Intersector; +import com.badlogic.gdx.math.Vector3; +import com.badlogic.gdx.math.collision.BoundingBox; +import com.badlogic.gdx.math.collision.Ray; + public class Bounds { public float x, y, z, r; + private BoundingBox boundingBox; public void fromExtents(final float[] min, final float[] max) { final float x = min[0]; @@ -15,5 +21,14 @@ public class Bounds { this.y = y + (d / 2f); this.z = z + (h / 2f); this.r = (float) (Math.max(Math.max(w, d), h) / 2.); + this.boundingBox = new BoundingBox(new Vector3(min), new Vector3(max)); + } + + public void intersectRay(final Ray ray, final Vector3 intersection) { + Intersector.intersectRayBounds(ray, this.boundingBox, intersection); + } + + public boolean intersectRayFast(final Ray ray) { + return Intersector.intersectRayBoundsFast(ray, this.boundingBox); } } diff --git a/core/src/com/etheller/warsmash/viewer5/gl/Extensions.java b/core/src/com/etheller/warsmash/viewer5/gl/Extensions.java index 37663f3..5096471 100644 --- a/core/src/com/etheller/warsmash/viewer5/gl/Extensions.java +++ b/core/src/com/etheller/warsmash/viewer5/gl/Extensions.java @@ -4,4 +4,9 @@ public class Extensions { public static ANGLEInstancedArrays angleInstancedArrays; public static DynamicShadowExtension dynamicShadowExtension; + + public static WireframeExtension wireframeExtension; + + public static int GL_LINE = 0; + public static int GL_FILL = 0; } diff --git a/core/src/com/etheller/warsmash/viewer5/gl/WireframeExtension.java b/core/src/com/etheller/warsmash/viewer5/gl/WireframeExtension.java new file mode 100644 index 0000000..1041bc6 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/gl/WireframeExtension.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.viewer5.gl; + +public interface WireframeExtension { + void glPolygonMode(int face, int mode); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/CollisionShape.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/CollisionShape.java index 7ae765c..d5db9d1 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/CollisionShape.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/CollisionShape.java @@ -1,10 +1,90 @@ package com.etheller.warsmash.viewer5.handlers.mdx; +import com.badlogic.gdx.math.Intersector; +import com.badlogic.gdx.math.Matrix4; +import com.badlogic.gdx.math.Vector3; +import com.badlogic.gdx.math.collision.BoundingBox; +import com.badlogic.gdx.math.collision.Ray; + public class CollisionShape extends GenericObject { + private static Vector3 intersectHeap = new Vector3(); + private static Vector3 intersectHeap2 = new Vector3(); + private static Matrix4 intersectMatrixHeap = new Matrix4(); + private static Ray intersectRayHeap = new Ray(); + private Intersectable intersectable; public CollisionShape(final MdxModel model, final com.etheller.warsmash.parsers.mdlx.CollisionShape object, final int index) { super(model, object, index); + final float[][] vertices = object.getVertices(); + + switch (object.getType()) { + case BOX: + this.intersectable = new IntersectableBox(vertices[0], vertices[1]); + break; + case CYLINDER: + this.intersectable = null; // TODO + break; + case PLANE: + this.intersectable = null; // TODO + break; + case SPHERE: + this.intersectable = new IntersectableSphere(vertices[0], object.getBoundsRadius()); + break; + } } + public boolean checkIntersect(final Ray ray, final MdxNode mdxNode, final Vector3 intersection) { + if (this.intersectable != null) { + return this.intersectable.checkIntersect(ray, mdxNode, intersection); + } + return false; + } + + private static interface Intersectable { + boolean checkIntersect(final Ray ray, final MdxNode mdxNode, final Vector3 intersection); + } + + private static final class IntersectableBox implements Intersectable { + private final BoundingBox boundingBox; + + public IntersectableBox(final float[] vertex1, final float[] vertex2) { + this.boundingBox = new BoundingBox(new Vector3(vertex1), new Vector3(vertex2)); + } + + @Override + public boolean checkIntersect(final Ray ray, final MdxNode mdxNode, final Vector3 intersection) { + intersectMatrixHeap.set(mdxNode.worldMatrix); + Matrix4.inv(intersectMatrixHeap.val); + intersectHeap.set(ray.origin); + intersectHeap2.set(ray.direction); + intersectHeap2.add(ray.origin); + intersectHeap.prj(intersectMatrixHeap); + intersectHeap2.prj(intersectMatrixHeap); + intersectHeap2.sub(intersectHeap); + intersectRayHeap.set(intersectHeap, intersectHeap2); + if (Intersector.intersectRayBounds(intersectRayHeap, this.boundingBox, intersection)) { + intersection.prj(mdxNode.worldMatrix); + return true; + } + return false; + } + } + + private static final class IntersectableSphere implements Intersectable { + private final Vector3 center; + private final float radius; + + public IntersectableSphere(final float[] center, final float radius) { + this.center = new Vector3(center); + this.radius = radius; + } + + @Override + public boolean checkIntersect(final Ray ray, final MdxNode mdxNode, final Vector3 intersection) { + intersectHeap.set(this.center); + intersectHeap.prj(mdxNode.worldMatrix); + return Intersector.intersectRaySphere(ray, intersectHeap, this.radius, intersection); + } + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectEmitterObject.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectEmitterObject.java index cebd32c..bf1663e 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectEmitterObject.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectEmitterObject.java @@ -22,6 +22,31 @@ import com.etheller.warsmash.viewer5.Texture; import com.etheller.warsmash.viewer5.handlers.EmitterObject; public class EventObjectEmitterObject extends GenericObject implements EmitterObject { + private static final class LoadGenericSoundCallback implements LoadGenericCallback { + private final String filename; + + public LoadGenericSoundCallback(final String filename) { + this.filename = filename; + } + + @Override + public Object call(final InputStream data) { + final FileHandle temp = new FileHandle(this.filename) { + @Override + public InputStream read() { + return data; + }; + }; + if (data != null) { + return Gdx.audio.newSound(temp); + } + else { + System.err.println("Warning: missing sound file: " + this.filename); + return null; + } + } + } + private static final LoadGenericCallback mappedDataCallback = new LoadGenericCallback() { @Override @@ -43,18 +68,6 @@ public class EventObjectEmitterObject extends GenericObject implements EmitterOb return new MappedData(stringBuilder.toString()); } }; - private static final LoadGenericCallback decodedDataCallback = new LoadGenericCallback() { - @Override - public Object call(final InputStream data) { - final FileHandle temp = new FileHandle("sound.wav") { - @Override - public InputStream read() { - return data; - }; - }; - return Gdx.audio.newSound(temp); - } - }; private int geometryEmitterType = -1; public final String type; @@ -266,10 +279,11 @@ public class EventObjectEmitterObject extends GenericObject implements EmitterOb final String[] fileNames = ((String) animSoundsRow.get("FileNames")).split(","); final GenericResource[] resources = new GenericResource[fileNames.length]; for (int i = 0; i < fileNames.length; i++) { - final GenericResource genericResource = viewer.loadGeneric( - pathSolver.solve(((String) animSoundsRow.get("DirectoryBase")) + fileNames[i], - model.solverParams).finalSrc, - FetchDataTypeName.ARRAY_BUFFER, decodedDataCallback); + final String pathString = pathSolver.solve( + ((String) animSoundsRow.get("DirectoryBase")) + fileNames[i], + model.solverParams).finalSrc; + final GenericResource genericResource = viewer.loadGeneric(pathString, + FetchDataTypeName.ARRAY_BUFFER, new LoadGenericSoundCallback(pathString)); if (genericResource == null) { throw new IllegalStateException("Null sound: " + fileNames[i]); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GeometryEmitterFuncs.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GeometryEmitterFuncs.java index e9f6721..1cf61cd 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GeometryEmitterFuncs.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GeometryEmitterFuncs.java @@ -69,6 +69,8 @@ public class GeometryEmitterFuncs { private static final Vector3 startHeap = new Vector3(); private static final Vector3 endHeap = new Vector3(); private static final float[] vectorTemp = new float[3]; + private static final Vector3[] vector3Heap = { new Vector3(), new Vector3(), new Vector3(), new Vector3(), + new Vector3(), new Vector3() }; public static void bindParticleEmitter2Buffer(final ParticleEmitter2 emitter, final ClientBuffer buffer) { final MdxComplexInstance instance = emitter.instance; @@ -99,6 +101,16 @@ public class GeometryEmitterFuncs { floatView.put(p0Offset + 0, location.x); floatView.put(p0Offset + 1, location.y); floatView.put(p0Offset + 2, location.z); + if (emitterObject.xYQuad != 0) { + final Vector3 velocity = object.velocity; + floatView.put(p0Offset + 3, velocity.x); + floatView.put(p0Offset + 4, velocity.y); + floatView.put(p0Offset + 5, velocity.z); + } + else { + floatView.put(p0Offset + 3, 0); + floatView.put(p0Offset + 4, 0); + } } else { final Vector3 velocity = object.velocity; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java index 2616166..65712c3 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java @@ -9,6 +9,7 @@ import java.util.List; import com.badlogic.gdx.math.Matrix4; import com.badlogic.gdx.math.Quaternion; import com.badlogic.gdx.math.Vector3; +import com.badlogic.gdx.math.collision.Ray; import com.etheller.warsmash.parsers.mdlx.Sequence; import com.etheller.warsmash.util.WarsmashConstants; import com.etheller.warsmash.viewer5.GenericNode; @@ -681,4 +682,25 @@ public class MdxComplexInstance extends ModelInstance { protected RenderBatch getBatch(final TextureMapper textureMapper2) { throw new UnsupportedOperationException("NOT API"); } + + public void intersectRayBounds(final Ray ray, final Vector3 intersection) { + this.model.bounds.intersectRay(ray, intersection); + } + + /** + * Intersects a world ray with the model's CollisionShapes. Only ever call this + * function on the Gdx thread because it uses static variables to hold state + * while processing. + * + * @param ray + */ + public boolean intersectRayWithCollision(final Ray ray, final Vector3 intersection) { + for (final CollisionShape collisionShape : ((MdxModel) this.model).collisionShapes) { + final MdxNode mdxNode = this.nodes[collisionShape.index]; + if (collisionShape.checkIntersect(ray, mdxNode, intersection)) { + return true; + } + } + return false; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxShaders.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxShaders.java index 6063d14..1124992 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxShaders.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxShaders.java @@ -363,7 +363,28 @@ public class MdxShaders { " v_color = color;\r\n" + // " \r\n" + // " if (a_tail == HEAD) {\r\n" + // - " gl_Position = u_mvp * vec4(a_p0 + (u_vertices[int(a_position)] * scale), 1.0);\r\n" + // + " vec3 vertices[4];\r\n" + // + " if(a_p1[0] != 0.0 || a_p1[1] != 0.0) {\r\n" + // + " vec3 vx;\r\n" + // + " vx[0] = a_p1[0];\r\n" + // + " vx[1] = a_p1[1];\r\n" + // + " vx[2] = 0.0;\r\n" + // + " vx = normalize(vx);\r\n" + // + " vec3 vy;\r\n" + // + " vy[0] = -vx[1];\r\n" + // + " vy[1] = vx[0];\r\n" + // + " vy[2] = 0.0;\r\n" + // + " vertices[2] = - vx - vy;\r\n" + // + " vertices[1] = vx - vy;\r\n" + // + " vertices[0] = -vertices[2];\r\n" + // + " vertices[3] = -vertices[1];\r\n" + // + " } else {\r\n" + // + " vertices[0] = u_vertices[0];\r\n" + // + " vertices[1] = u_vertices[1];\r\n" + // + " vertices[2] = u_vertices[2];\r\n" + // + " vertices[3] = u_vertices[3];\r\n" + // + " }\r\n" + // + " gl_Position = u_mvp * vec4(a_p0 + (vertices[int(a_position)] * scale), 1.0);\r\n" + // " } else {\r\n" + // " // Get the normal to the tail in camera space.\r\n" + // " // This allows to build a 2D rectangle around the 3D tail.\r\n" + // diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/AnimationTokens.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/AnimationTokens.java new file mode 100644 index 0000000..bbed7f5 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/AnimationTokens.java @@ -0,0 +1,71 @@ +package com.etheller.warsmash.viewer5.handlers.w3x; + +public class AnimationTokens { + public static enum PrimaryTag { + ATTACK, + BIRTH, + CINEMATIC, + DEATH, + DECAY, + DISSIPATE, + MORPH, + PORTRAIT, + SLEEP, + SPELL, + STAND, + WALK; + } + + public static enum SecondaryTag { + ALTERNATE, + ALTERNATEEX, + CHAIN, + CHANNEL, + COMPLETE, + CRITICAL, + DEFEND, + DRAIN, + EATTREE, + FAST, + FILL, + FLAIL, + FLESH, + FIFTH, + FIRE, + FIRST, + FIVE, + FOUR, + FOURTH, + GOLD, + HIT, + LARGE, + LEFT, + LIGHT, + LOOPING, + LUMBER, + MEDIUM, + MODERATE, + OFF, + ONE, + PUKE, + READY, + RIGHT, + SECOND, + SEVERE, + SLAM, + SMALL, + SPIKED, + SPIN, + SWIM, + TALK, + THIRD, + THREE, + THROW, + TWO, + TURN, + VICTORY, + WORK, + WOUNDED, + UPGRADE; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SplatModel.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SplatModel.java index 83d9e90..bec4e5c 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SplatModel.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SplatModel.java @@ -47,20 +47,29 @@ public class SplatModel { final float y1 = locs[3]; final float zoffs = locs[4]; - final int ix0 = (int) Math.floor((x0 - centerOffset[0]) / 128.0); - final int ix1 = (int) Math.ceil((x1 - centerOffset[0]) / 128.0); - final int iy0 = (int) Math.floor((y0 - centerOffset[1]) / 128.0); - final int iy1 = (int) Math.ceil((y1 - centerOffset[1]) / 128.0); + final float centerOffsetX = centerOffset[0]; + final float centerOffsetY = centerOffset[1]; + final int ix0 = (int) Math.floor((x0 - centerOffsetX) / 128.0); + final int ix1 = (int) Math.ceil((x1 - centerOffsetX) / 128.0); + final int iy0 = (int) Math.floor((y0 - centerOffsetY) / 128.0); + final int iy1 = (int) Math.ceil((y1 - centerOffsetY) / 128.0); - final float newVerts = ((iy1 - iy0) + 1) * ((ix1 - ix0) + 1); - if (newVerts > MAX_VERTICES) { + final int newVerts = ((iy1 - iy0) + 1) * ((ix1 - ix0) + 1); + final int maxPossibleVerts = ((int) Math.ceil((y1 - y0) / 128.0) + 2) + * ((int) Math.ceil((x1 - x0) / 128.0) + 2); + final int maxPossibleFaces = ((int) Math.ceil((y1 - y0) / 128.0) + 1) + * ((int) Math.ceil((x1 - x0) / 128.0) + 1); + + int start = vertices.size(); + final SplatMover splatMover = unit == null ? null : new SplatMover(start * 3 * 4, indices.size() * 6 * 2); + + final int numVertsToCrate = splatMover == null ? newVerts : maxPossibleVerts; + if (numVertsToCrate > MAX_VERTICES) { continue; } - int start = vertices.size(); - final SplatMover splatMover = unit == null ? null : new SplatMover(start * 3 * 4); final int step = (ix1 - ix0) + 1; - if ((start + newVerts) > MAX_VERTICES) { + if ((start + numVertsToCrate) > MAX_VERTICES) { this.addBatch(gl, vertices, uvs, indices, batchRenderUnits); vertices.clear(); uvs.clear(); @@ -69,27 +78,62 @@ public class SplatModel { start = 0; } + final float uvXScale = x1 - x0; + final float uvYScale = y1 - y0; for (int iy = iy0; iy <= iy1; ++iy) { - final float y = (iy * 128.0f) + centerOffset[1]; + final float y = (iy * 128.0f) + centerOffsetY; for (int ix = ix0; ix <= ix1; ++ix) { - final float x = (ix * 128.0f) + centerOffset[0]; + final float x = (ix * 128.0f) + centerOffsetX; final float[] vertex = new float[] { x, y, zoffs }; vertices.add(vertex); - uvs.add(new float[] { (x - x0) / (x1 - x0), 1.0f - ((y - y0) / (y1 - y0)) }); + final float[] uv = new float[] { (x - x0) / uvXScale, 1.0f - ((y - y0) / uvYScale) }; + uvs.add(uv); if (splatMover != null) { splatMover.vertices.add(vertex); + splatMover.uvs.add(uv); } } } + if (splatMover != null) { + splatMover.uvXScale = uvXScale; + splatMover.uvYScale = uvYScale; + splatMover.locs = locs; + splatMover.ix0 = ix0; + splatMover.iy0 = iy0; + splatMover.ix1 = ix1; + splatMover.iy1 = iy1; + + final float y = (iy1 * 128.0f) + centerOffsetY; + final float x = (ix1 * 128.0f) + centerOffsetX; + while (splatMover.vertices.size() < maxPossibleVerts) { + final float[] vertex = new float[] { x, y, zoffs }; + vertices.add(vertex); + final float[] uv = new float[] { (x - x0) / uvXScale, 1.0f - ((y - y0) / uvYScale) }; + uvs.add(uv); + splatMover.vertices.add(vertex); + splatMover.uvs.add(uv); + } + } for (int i = 0; i < (iy1 - iy0); ++i) { for (int j = 0; j < (ix1 - ix0); ++j) { final int i0 = start + (i * step) + j; - indices.add(new int[] { i0, i0 + 1, i0 + step, i0 + 1, i0 + step + 1, i0 + step }); + final int[] indexArray = new int[] { i0, i0 + 1, i0 + step, i0 + 1, i0 + step + 1, i0 + step }; + indices.add(indexArray); + if (splatMover != null) { + splatMover.indices.add(indexArray); + } } } if (unit != null) { unit.accept(splatMover); batchRenderUnits.add(splatMover); + + while (splatMover.indices.size() < maxPossibleFaces) { + final int i0 = start; + final int[] indexArray = new int[] { i0, i0, i0, i0, i0, i0 }; + indices.add(indexArray); + splatMover.indices.add(indexArray); + } } } @@ -117,6 +161,8 @@ public class SplatModel { this.batches.add(new Batch(uvsOffset, vertexBuffer, faceBuffer, indices.size() * 6)); for (final SplatMover mover : batchRenderUnits) { mover.vertexBuffer = vertexBuffer; + mover.uvsOffset = uvsOffset; + mover.faceBuffer = faceBuffer; } } @@ -157,24 +203,127 @@ public class SplatModel { } public static final class SplatMover { + public int faceBuffer; + public int uvsOffset; + public int iy1; + public int ix1; + public int iy0; + public int ix0; + public float[] locs; + public float uvYScale; + public float uvXScale; private int vertexBuffer; private final int startOffset; + private final int start; private final List vertices = new ArrayList<>(); + private final List uvs = new ArrayList<>(); + private final List indices = new ArrayList<>(); + private final int indicesStartOffset; - private SplatMover(final int i) { + private SplatMover(final int i, final int indicesStartOffset) { this.startOffset = i; + this.indicesStartOffset = indicesStartOffset; + this.start = i / 12; } - public void move(final float deltaX, final float deltaY) { - for (final float[] vertex : this.vertices) { - vertex[0] += deltaX; - vertex[1] += deltaY; - } + public void move(final float deltaX, final float deltaY, final float[] centerOffset) { + this.locs[0] += deltaX; + this.locs[2] += deltaX; + this.locs[1] += deltaY; + this.locs[3] += deltaY; + final float x0 = this.locs[0]; + final float y0 = this.locs[1]; + final float x1 = this.locs[2]; + final float y1 = this.locs[3]; + + final float centerOffsetX = centerOffset[0]; + final float centerOffsetY = centerOffset[1]; + final int ix0 = (int) Math.floor((x0 - centerOffsetX) / 128.0); + final int ix1 = (int) Math.ceil((x1 - centerOffsetX) / 128.0); + final int iy0 = (int) Math.floor((y0 - centerOffsetY) / 128.0); + final int iy1 = (int) Math.ceil((y1 - centerOffsetY) / 128.0); final GL30 gl = Gdx.gl30; gl.glBindBuffer(GL30.GL_ARRAY_BUFFER, this.vertexBuffer); - gl.glBufferSubData(GL30.GL_ARRAY_BUFFER, this.startOffset, 4 * 3 * this.vertices.size(), - RenderMathUtils.wrap(this.vertices)); + if ((ix0 != this.ix0) || (iy0 != this.iy0) || (ix1 != this.ix1) || (iy1 != this.iy1)) { + // splat geometry has moved, difficult case + final float newVerts = ((iy1 - iy0) + 1) * ((ix1 - ix0) + 1); + if (newVerts <= this.uvs.size()) { + } + int vertexIndex = 0; + float y = 0; + float x = 0; + for (int iy = iy0; iy <= iy1; ++iy) { + y = (iy * 128.0f) + centerOffsetY; + for (int ix = ix0; ix <= ix1; ++ix) { + x = (ix * 128.0f) + centerOffsetX; + final float[] vertexToUpdate = this.vertices.get(vertexIndex); + vertexToUpdate[0] = x; + vertexToUpdate[1] = y; + final float[] uvItem = this.uvs.get(vertexIndex); + uvItem[0] = (x - x0) / this.uvXScale; + uvItem[1] = 1.0f - ((y - y0) / this.uvYScale); + vertexIndex++; + } + } + + for (; vertexIndex < this.vertices.size(); vertexIndex++) { + final float[] vertexToUpdate = this.vertices.get(vertexIndex); + vertexToUpdate[0] = x; + vertexToUpdate[1] = y; + final float[] uvItem = this.uvs.get(vertexIndex); + uvItem[0] = (x - x0) / this.uvXScale; + uvItem[1] = 1.0f - ((y - y0) / this.uvYScale); + } + gl.glBufferSubData(GL30.GL_ARRAY_BUFFER, this.startOffset, 4 * 3 * this.vertices.size(), + RenderMathUtils.wrap(this.vertices)); + + final int step = (ix1 - ix0) + 1; + int faceIndicesIndex = 0; + for (int i = 0; i < (iy1 - iy0); ++i) { + for (int j = 0; j < (ix1 - ix0); ++j) { + final int i0 = this.start + (i * step) + j; + final int[] indexArr = this.indices.get(faceIndicesIndex++); + indexArr[0] = i0; + indexArr[1] = i0 + 1; + indexArr[2] = i0 + step; + indexArr[3] = i0 + 1; + indexArr[4] = i0 + step + 1; + indexArr[5] = i0 + step; + } + } + + for (; faceIndicesIndex < this.indices.size(); faceIndicesIndex++) { + final int i0 = this.start; + final int[] indexArr = this.indices.get(faceIndicesIndex); + for (int i = 0; i < indexArr.length; i++) { + indexArr[i] = i0; + } + } + gl.glBindBuffer(GL30.GL_ELEMENT_ARRAY_BUFFER, this.faceBuffer); + gl.glBufferSubData(GL30.GL_ELEMENT_ARRAY_BUFFER, this.indicesStartOffset, 6 * 2 * this.indices.size(), + RenderMathUtils.wrapFaces(this.indices)); + this.ix0 = ix0; + this.iy0 = iy0; + this.ix1 = ix1; + this.iy1 = iy1; + } + else { + // splat will use same geometry, easy case, just update the UVs + + int index = 0; + for (int iy = iy0; iy <= iy1; ++iy) { + final float y = (iy * 128.0f) + centerOffsetY; + for (int ix = ix0; ix <= ix1; ++ix) { + final float x = (ix * 128.0f) + centerOffsetX; + final float[] uvItem = this.uvs.get(index++); + uvItem[0] = (x - x0) / this.uvXScale; + uvItem[1] = 1.0f - ((y - y0) / this.uvYScale); + } + } + } + gl.glBufferSubData(GL30.GL_ARRAY_BUFFER, this.uvsOffset + ((this.startOffset / 3) * 2), + 4 * 2 * this.uvs.size(), RenderMathUtils.wrap(this.uvs)); } } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index 76296e2..be1cadc 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -93,6 +93,7 @@ public class War3MapViewer extends ModelViewer { private static final War3ID sloc = War3ID.fromString("sloc"); private static final LoadGenericCallback stringDataCallback = new StringDataCallbackImplementation(); private static final float[] rayHeap = new float[6]; + private static final Ray gdxRayHeap = new Ray(); private static final Vector2 mousePosHeap = new Vector2(); private static final Vector3 normalHeap = new Vector3(); private static final Vector3 intersectionHeap = new Vector3(); @@ -370,6 +371,9 @@ public class War3MapViewer extends ModelViewer { else { throw new IllegalStateException("transcription of JS has not loaded a map and has no JS async promises"); } + + this.terrain.initShadows(); + this.terrain.createWaves(); } private void loadTerrainCliffsAndWater(final War3MapW3e w3e) { @@ -411,6 +415,13 @@ public class War3MapViewer extends ModelViewer { fileVar += ".mdx"; + if (type == WorldEditorDataType.DESTRUCTIBLES) { + final String shadowString = row.readSLKTag("shadow"); + if ((shadowString != null) && (shadowString.length() > 0) && !"_".equals(shadowString)) { + this.terrain.addShadow(shadowString, doodad.getLocation()[0], doodad.getLocation()[1]); + } + + } // First see if the model is local. // Doodads referring to local models may have invalid variations, so if the // variation doesn't exist, try without a variation. @@ -750,9 +761,9 @@ public class War3MapViewer extends ModelViewer { final float[] ray = rayHeap; mousePosHeap.set(screenX, screenY); this.worldScene.camera.screenToWorldRay(ray, mousePosHeap); - final Ray gdxRay = new Ray(new Vector3(ray[0], ray[1], ray[2]), - new Vector3(ray[3] - ray[0], ray[4] - ray[1], ray[5] - ray[2])); - Terrain.intersectRayTriangles(gdxRay, this.terrain.softwareGroundMesh.vertices, + gdxRayHeap.set(ray[0], ray[1], ray[2], ray[3] - ray[0], ray[4] - ray[1], ray[5] - ray[2]); + gdxRayHeap.direction.nor();// needed for libgdx + Terrain.intersectRayTriangles(gdxRayHeap, this.terrain.softwareGroundMesh.vertices, this.terrain.softwareGroundMesh.indices, 3, out); } @@ -766,6 +777,43 @@ public class War3MapViewer extends ModelViewer { } public List selectUnit(final float x, final float y, final boolean toggle) { + final float[] ray = rayHeap; + mousePosHeap.set(x, y); + this.worldScene.camera.screenToWorldRay(ray, mousePosHeap); + gdxRayHeap.set(ray[0], ray[1], ray[2], ray[3] - ray[0], ray[4] - ray[1], ray[5] - ray[2]); + gdxRayHeap.direction.nor();// needed for libgdx + + RenderUnit entity = null; + for (final RenderUnit unit : this.units) { + final MdxComplexInstance instance = unit.instance; + if (instance.intersectRayWithCollision(gdxRayHeap, intersectionHeap)) { + entity = unit; + } + } + List sel; + if (entity != null) { + if (toggle) { + sel = new ArrayList<>(this.selected); + final int idx = sel.indexOf(entity); + if (idx >= 0) { + sel.remove(idx); + } + else { + sel.add(entity); + } + } + else { + sel = Arrays.asList(entity); + } + } + else { + sel = Collections.emptyList(); + } + this.doSelectUnit(sel); + return sel; + } + + public List selectUnitOld(final float x, final float y, final boolean toggle) { final float[] ray = rayHeap; mousePosHeap.set(x, y); this.worldScene.camera.screenToWorldRay(ray, mousePosHeap); @@ -1020,4 +1068,9 @@ public class War3MapViewer extends ModelViewer { return ordered; } + public void standOnRepeat(final MdxComplexInstance instance) { + instance.setSequenceLoopMode(2); + StandSequence.randomStandSequence(instance); + } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/RenderCorner.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/RenderCorner.java index 2dc35fa..f974f84 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/RenderCorner.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/RenderCorner.java @@ -6,6 +6,7 @@ public class RenderCorner extends Corner { public boolean cliff; public boolean romp; public float rampAdjust; + public float depth; public RenderCorner(final Corner corner) { super(corner); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java index 9d9ca1b..46ade03 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java @@ -4,6 +4,7 @@ import java.awt.image.BufferedImage; import java.io.IOException; import java.io.InputStream; import java.nio.Buffer; +import java.nio.FloatBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -18,6 +19,7 @@ import javax.imageio.ImageIO; import org.apache.commons.compress.utils.IOUtils; import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.GL30; import com.badlogic.gdx.graphics.glutils.ShaderProgram; import com.badlogic.gdx.math.Intersector; @@ -40,6 +42,7 @@ import com.etheller.warsmash.viewer5.Camera; import com.etheller.warsmash.viewer5.PathSolver; import com.etheller.warsmash.viewer5.RawOpenGLTextureResource; import com.etheller.warsmash.viewer5.Texture; +import com.etheller.warsmash.viewer5.gl.Extensions; import com.etheller.warsmash.viewer5.gl.WebGL; import com.etheller.warsmash.viewer5.handlers.w3x.DynamicShadowManager; import com.etheller.warsmash.viewer5.handlers.w3x.SplatModel; @@ -55,6 +58,7 @@ public class Terrain { private static final Vector3 normalHeap2 = new Vector3(); private static final float[] fourComponentHeap = new float[4]; private static final Matrix4 tempMatrix = new Matrix4(); + private static final boolean WIREFRAME_TERRAIN = false; public ShaderProgram groundShader; public ShaderProgram waterShader; @@ -114,6 +118,7 @@ public class Terrain { public final Map> shadows = new HashMap<>(); public final Map shadowTextures = new HashMap<>(); private final int[] mapBounds; + private final float[] shaderMapBounds; private final int[] mapSize; public final SoftwareGroundMesh softwareGroundMesh; private final int testArrayBuffer; @@ -369,6 +374,10 @@ public class Terrain { this.centerOffset = w3eFile.getCenterOffset(); this.uberSplatModels = new ArrayList<>(); this.mapBounds = w3iFile.getCameraBoundsComplements(); + this.shaderMapBounds = new float[] { (this.mapBounds[0] * 128.0f) + this.centerOffset[0], + (this.mapBounds[2] * 128.0f) + this.centerOffset[1], + ((this.columns - this.mapBounds[1] - 1) * 128.0f) + this.centerOffset[0], + ((this.rows - this.mapBounds[3] - 1) * 128.0f) + this.centerOffset[1] }; this.mapSize = w3eFile.getMapSize(); this.softwareGroundMesh = new SoftwareGroundMesh(this.groundHeights, this.groundCornerHeights, this.centerOffset, width, height); @@ -382,6 +391,13 @@ public class Terrain { // gl.glBindBuffer(GL30.GL_ELEMENT_ARRAY_BUFFER, this.testElementBuffer); // gl.glBufferData(GL30.GL_ELEMENT_ARRAY_BUFFER, this.softwareGroundMesh.indices.length, // RenderMathUtils.wrap(this.softwareGroundMesh.indices), GL30.GL_STATIC_DRAW); + + this.waveBuilder = new WaveBuilder(this.mapSize, this.waterTable, viewer, this.corners, this.centerOffset, + this.waterHeightOffset, w3eFile, w3iFile); + } + + public void createWaves() { + this.waveBuilder.createWaves(this); } private void updateGroundHeights(final Rectangle area) { @@ -415,8 +431,12 @@ public class Terrain { } } - this.groundCornerHeights[(j * this.columns) + i] = this.corners[i][j].computeFinalGroundHeight() - + rampHeight; + final RenderCorner corner = this.corners[i][j]; + final float newGroundCornerHeight = corner.computeFinalGroundHeight() + rampHeight; + this.groundCornerHeights[(j * this.columns) + i] = newGroundCornerHeight; + corner.depth = (corner.getWater() != 0) + ? (this.waterHeightOffset + corner.getWaterHeight()) - newGroundCornerHeight + : 0; } } updateGroundHeights(); @@ -430,9 +450,13 @@ public class Terrain { } private void updateCornerHeights() { + final FloatBuffer groundCornerHeightsWrapped = RenderMathUtils.wrap(this.groundCornerHeights); Gdx.gl30.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeight); Gdx.gl30.glTexSubImage2D(GL30.GL_TEXTURE_2D, 0, 0, 0, this.columns, this.rows, GL30.GL_RED, GL30.GL_FLOAT, - RenderMathUtils.wrap(this.groundCornerHeights)); + groundCornerHeightsWrapped); + Gdx.gl30.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeightLinear); + Gdx.gl30.glTexSubImage2D(GL30.GL_TEXTURE_2D, 0, 0, 0, this.columns, this.rows, GL30.GL_RED, GL30.GL_FLOAT, + groundCornerHeightsWrapped); } /** @@ -819,15 +843,21 @@ public class Terrain { // gl.glActiveTexture(GL30.GL_TEXTURE21, /*pathingMap.getTextureDynamic()*/); gl.glActiveTexture(GL30.GL_TEXTURE20); - gl.glBindTexture(GL30.GL_TEXTURE_2D, dynamicShadowManager.getDepthTexture()); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.shadowMap); // gl.glEnableVertexAttribArray(0); gl.glBindBuffer(GL30.GL_ARRAY_BUFFER, Shapes.INSTANCE.vertexBuffer); gl.glVertexAttribPointer(0, 2, GL30.GL_FLOAT, false, 0, 0); gl.glBindBuffer(GL30.GL_ELEMENT_ARRAY_BUFFER, Shapes.INSTANCE.indexBuffer); + if (WIREFRAME_TERRAIN) { + Extensions.wireframeExtension.glPolygonMode(GL20.GL_FRONT_AND_BACK, Extensions.GL_LINE); + } gl.glDrawElementsInstanced(GL30.GL_TRIANGLES, Shapes.INSTANCE.quadIndices.length * 3, GL30.GL_UNSIGNED_INT, 0, (this.columns - 1) * (this.rows - 1)); + if (WIREFRAME_TERRAIN) { + Extensions.wireframeExtension.glPolygonMode(GL20.GL_FRONT_AND_BACK, Extensions.GL_FILL); + } // gl.glDisableVertexAttribArray(0); @@ -910,6 +940,7 @@ public class Terrain { gl.glUniform1i(6, (int) this.waterIndex); gl.glUniform1f(this.waterShader.getUniformLocation("centerOffsetX"), this.centerOffset[0]); gl.glUniform1f(this.waterShader.getUniformLocation("centerOffsetY"), this.centerOffset[1]); + gl.glUniform4fv(9, 1, this.shaderMapBounds, 0); gl.glActiveTexture(GL30.GL_TEXTURE0); gl.glBindTexture(GL30.GL_TEXTURE_2D, this.waterHeight); @@ -960,6 +991,10 @@ public class Terrain { gl.glUniform1i(1, this.viewer.renderPathing); gl.glUniform1i(2, this.viewer.renderLighting); + this.cliffShader.setUniformi("shadowMap", 2); + gl.glActiveTexture(GL30.GL_TEXTURE2); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.shadowMap); + gl.glActiveTexture(GL30.GL_TEXTURE0); gl.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.cliffTextureArray); gl.glActiveTexture(GL30.GL_TEXTURE1); @@ -1052,7 +1087,7 @@ public class Terrain { } } - this.shadowMap = gl.glGenBuffer(); + this.shadowMap = gl.glGenTexture(); gl.glBindTexture(GL30.GL_TEXTURE_2D, this.shadowMap); gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_LINEAR); gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_LINEAR); @@ -1107,6 +1142,7 @@ public class Terrain { static Vector3 tmp1 = new Vector3(); static Vector3 tmp2 = new Vector3(); static Vector3 tmp3 = new Vector3(); + private final WaveBuilder waveBuilder; /** * Intersects the given ray with list of triangles. Returns the nearest @@ -1185,10 +1221,10 @@ public class Terrain { final int cellY = (int) userCellSpaceY; if ((cellX >= 0) && (cellX < (this.mapSize[0] - 1)) && (cellY >= 0) && (cellY < (this.mapSize[1] - 1))) { - final float bottomLeft = this.corners[cellX][cellY].computeFinalGroundHeight(); - final float bottomRight = this.corners[cellX + 1][cellY].computeFinalGroundHeight(); - final float topLeft = this.corners[cellX][cellY + 1].computeFinalGroundHeight(); - final float topRight = this.corners[cellX + 1][cellY + 1].computeFinalGroundHeight(); + final float bottomLeft = this.groundCornerHeights[(cellY * this.columns) + cellX]; + final float bottomRight = this.groundCornerHeights[(cellY * this.columns) + cellX + 1]; + final float topLeft = this.groundCornerHeights[((cellY + 1) * this.columns) + cellX]; + final float topRight = this.groundCornerHeights[((cellY + 1) * this.columns) + cellX + 1]; final float sqX = userCellSpaceX - cellX; final float sqY = userCellSpaceY - cellY; float height; @@ -1269,4 +1305,22 @@ public class Terrain { } } } + + public boolean inPlayableArea(float x, float y) { + x = (x - this.centerOffset[0]) / 128.0f; + y = (y - this.centerOffset[1]) / 128.0f; + if (x < this.mapBounds[0]) { + return false; + } + if (x >= (this.mapSize[0] - this.mapBounds[1] - 1)) { + return false; + } + if (y < this.mapBounds[2]) { + return false; + } + if (y >= (this.mapSize[1] - this.mapBounds[3] - 1)) { + return false; + } // TODO why do we use floor if we can use int cast? + return this.corners[(int) Math.floor(x)][(int) Math.floor(y)].getBoundary() == 0; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/TerrainShaders.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/TerrainShaders.java index 7c6d7bf..ec3b62e 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/TerrainShaders.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/TerrainShaders.java @@ -18,6 +18,7 @@ public class TerrainShaders { "layout (location = 0) uniform mat4 MVP;\r\n" + // "\r\n" + // "layout (binding = 1) uniform sampler2D height_texture;\r\n" + // + "layout (binding = 3) uniform sampler2D shadowMap;\r\n" + // "layout (location = 3) uniform float centerOffsetX;\r\n" + // "layout (location = 4) uniform float centerOffsetY;\r\n" + // "\r\n" + // @@ -25,11 +26,14 @@ public class TerrainShaders { "layout (location = 1) out vec3 Normal;\r\n" + // "layout (location = 2) out vec2 pathing_map_uv;\r\n" + // "layout (location = 3) out vec3 position;\r\n" + // + "layout (location = 4) out vec2 v_suv;\r\n" + // "\r\n" + // "void main() {\r\n" + // " pathing_map_uv = (vec2(vPosition.x + 128, vPosition.y) / 128 + vOffset.xy) * 4;\r\n" + // " \r\n" + // " ivec2 size = textureSize(height_texture, 0);\r\n" + // + " ivec2 shadowSize = textureSize(shadowMap, 0);\r\n" + // + " v_suv = pathing_map_uv / shadowSize;\r\n" + // " float value = texture(height_texture, (vOffset.xy + vec2(vPosition.x + 192, vPosition.y + 64) / 128.0) / vec2(size)).r;\r\n" + // "\r\n" + // @@ -38,7 +42,7 @@ public class TerrainShaders { " myposition.x += centerOffsetX;\r\n" + // " myposition.y += centerOffsetY;\r\n" + // " position.x /= (size.x * 128.0);\r\n" + // - " position.y /= (size.x * 128.0);\r\n" + // + " position.y /= (size.y * 128.0);\r\n" + // " gl_Position = MVP * myposition;\r\n" + // " UV = vec3(vUV, vOffset.a);\r\n" + // "\r\n" + // @@ -57,6 +61,7 @@ public class TerrainShaders { "\r\n" + // "layout (binding = 0) uniform sampler2DArray cliff_textures;\r\n" + // "layout (binding = 2) uniform usampler2D pathing_map_static;\r\n" + // + "layout (binding = 3) uniform sampler2D shadowMap;\r\n" + // "\r\n" + // "layout (location = 1) uniform bool show_pathing_map_static;\r\n" + // "layout (location = 2) uniform bool show_lighting;\r\n" + // @@ -64,12 +69,15 @@ public class TerrainShaders { "layout (location = 0) in vec3 UV;\r\n" + // "layout (location = 1) in vec3 Normal;\r\n" + // "layout (location = 2) in vec2 pathing_map_uv;\r\n" + // + "layout (location = 4) in vec2 v_suv;\r\n" + // "\r\n" + // "out vec4 color;\r\n" + // "\r\n" + // "void main() {\r\n" + // " color = texture(cliff_textures, UV);\r\n" + // "\r\n" + // + " float shadow = texture2D(shadowMap, v_suv).r;\r\n" + // + " color.rgb *= (1.0 - shadow);\r\n" + // " if (show_lighting) {\r\n" + // " vec3 light_direction = vec3(-0.3, -0.3, 0.25);\r\n" + // " light_direction = normalize(light_direction);\r\n" + // @@ -127,6 +135,7 @@ public class TerrainShaders { "layout (location = 3) out vec3 normal;\r\n" + // "layout (location = 4) out vec3 position;\r\n" + // "layout (location = 5) out vec3 ShadowCoord;\r\n" + // + "layout (location = 6) out vec2 v_suv;\r\n" + // "\r\n" + // "void main() { \r\n" + // " ivec2 size = textureSize(terrain_texture_list, 0);\r\n" + // @@ -153,6 +162,7 @@ public class TerrainShaders { + // " ShadowCoord = (((texture_indices.a & 32768) == 0) ? DepthBiasMVP * vec4(position.xyz, 1) : vec4(2.0, 0.0, 0.0, 1.0)).xyz;\r\n" + // + " v_suv = (vPosition + pos) / size;\r\n" + // " position.x = (position.x - centerOffsetX) / (size.x * 128.0);\r\n" + // " position.y = (position.y - centerOffsetY) / (size.y * 128.0);\r\n" + // "}"; @@ -190,6 +200,7 @@ public class TerrainShaders { "layout (location = 3) in vec3 normal;\r\n" + // "layout (location = 4) in vec3 position;\r\n" + // "layout (location = 5) in vec3 ShadowCoord;\r\n" + // + "layout (location = 6) in vec2 v_suv;\r\n" + // "\r\n" + // "layout (location = 0) out vec4 color;\r\n" + // // "layout (location = 1) out vec4 position;\r\n" + // @@ -247,11 +258,12 @@ public class TerrainShaders { + // " color = color * color.a + get_fragment(texture_indices.r & 31, vec3(UV, texture_indices.r >> 5)) * (1 - color.a);\r\n" + // - " float visibility = 1.0;\r\n" + // + " float shadow = texture2D(shadowMap, v_suv).r;\r\n" + // +// " float visibility = 1.0;\r\n" + // // " if ( texture2D(shadowMap, ShadowCoord.xy).z > ShadowCoord.z ) {\r\n" + // // " visibility = 0.5;\r\n" + // // " }\r\n" + // - " color = vec4(color.xyz * visibility, 1.0);\r\n" + // + " color = vec4(color.xyz * (1.0 - shadow), 1.0);\r\n" + // "\r\n" + // // " if (show_lighting) {\r\n" + // // " vec3 light_direction = vec3(-0.3, -0.3, 0.25);\r\n" + // @@ -406,6 +418,7 @@ public class TerrainShaders { "\r\n" + // "out vec2 UV;\r\n" + // "out vec4 Color;\r\n" + // + "out vec2 position;\r\n" + // "\r\n" + // "const float min_depth = 10.f / 128;\r\n" + // "const float deeplevel = 64.f / 128;\r\n" + // @@ -422,7 +435,9 @@ public class TerrainShaders { " || texelFetch(water_exists_texture, pos + ivec2(1, 1), 0).r > 0\r\n" + // " || texelFetch(water_exists_texture, pos + ivec2(0, 1), 0).r > 0;\r\n" + // "\r\n" + // - " gl_Position = is_water ? MVP * vec4((vPosition.x + pos.x)*128.0 + centerOffsetX, (vPosition.y + pos.y)*128.0 + centerOffsetY, water_height*128.0, 1) : vec4(2.0, 0.0, 0.0, 1.0);\r\n" + " position = vec2((vPosition.x + pos.x)*128.0 + centerOffsetX, (vPosition.y + pos.y)*128.0 + centerOffsetY);\r\n" + + // + " gl_Position = is_water ? MVP * vec4(position.xy, water_height*128.0, 1) : vec4(2.0, 0.0, 0.0, 1.0);\r\n" + // "\r\n" + // " UV = vec2((vPosition.x + pos.x%2)/2.0, (vPosition.y + pos.y%2)/2.0);\r\n" + // @@ -445,14 +460,19 @@ public class TerrainShaders { "\r\n" + // "\r\n" + // "layout (location = 6) uniform int current_texture;\r\n" + // + "layout (location = 9) uniform vec4 mapBounds;\r\n" + // "\r\n" + // "in vec2 UV;\r\n" + // "in vec4 Color;\r\n" + // + "in vec2 position;\r\n" + // "\r\n" + // "out vec4 outColor;\r\n" + // "\r\n" + // "void main() {\r\n" + // - " outColor = texture(water_textures, vec3(UV, current_texture)) * Color;\r\n" + // + " vec2 d2 = min(position - mapBounds.xy, mapBounds.zw - position);\r\n" + // + " float d1 = clamp(min(d2.x, d2.y) / 64.0 + 1.0, 0.0, 1.0) * 0.8 + 0.2;;\r\n" + // + " outColor = texture(water_textures, vec3(UV, current_texture)) * vec4(Color.rgb * d1, Color.a);\r\n" + + // "}"; } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/WaveBuilder.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/WaveBuilder.java new file mode 100644 index 0000000..dfb639c --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/WaveBuilder.java @@ -0,0 +1,152 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.environment; + +import java.util.HashMap; +import java.util.Map; + +import com.badlogic.gdx.math.Quaternion; +import com.etheller.warsmash.parsers.w3x.w3e.War3MapW3e; +import com.etheller.warsmash.parsers.w3x.w3i.War3MapW3i; +import com.etheller.warsmash.units.DataTable; +import com.etheller.warsmash.units.Element; +import com.etheller.warsmash.util.RenderMathUtils; +import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; +import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; +import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; + +public class WaveBuilder { + + private final int[] mapSize; + private final DataTable waterTable; + private final War3MapViewer viewer; + private final RenderCorner[][] corners; + private final float[] centerOffset; + private final float waterHeightOffset; + private float[] locations; + + private final Map models; + private final War3MapW3e w3eFile; + private final War3MapW3i w3iFile; + + public WaveBuilder(final int[] mapSize, final DataTable waterTable, final War3MapViewer viewer, + final RenderCorner[][] corners, final float[] centerOffset, final float waterHeightOffset, + final War3MapW3e w3eFile, final War3MapW3i w3iFile) { + this.mapSize = mapSize; + this.waterTable = waterTable; + this.viewer = viewer; + this.corners = corners; + this.centerOffset = centerOffset; + this.waterHeightOffset = waterHeightOffset; + this.w3eFile = w3eFile; + this.w3iFile = w3iFile; + this.models = new HashMap<>(); + } + + public void createWaves(final Terrain terrain) { + final int columns = this.mapSize[0]; + final int rows = this.mapSize[1]; + final float wavesDepth = 25f / 128f; + final char tileset = this.w3eFile.getTileset(); + final Element waterRow = this.waterTable.get(tileset + "Sha"); + + final long wavesCliff = (this.w3iFile.getFlags() & 0x0800); + final long wavesRolling = (this.w3iFile.getFlags() & 0x1000); + + final String shoreline = waterRow.getField("shoreDir") + "\\" + waterRow.getField("shoreSFile") + "\\" + + waterRow.getField("shoreSFile") + "0.mdx"; + final String outsideCorner = waterRow.getField("shoreDir") + "\\" + waterRow.getField("shoreOCFile") + "\\" + + waterRow.getField("shoreOCFile") + "0.mdx"; + final String insideCorner = waterRow.getField("shoreDir") + "\\" + waterRow.getField("shoreICFile") + "\\" + + waterRow.getField("shoreICFile") + "0.mdx"; +// final String shoreline = "Buildings\\Other\\TempArtB\\TempArtB.mdx"; +// final String outsideCorner = "Buildings\\Other\\TempArtB\\TempArtB.mdx"; +// final String insideCorner = "Buildings\\Other\\TempArtB\\TempArtB.mdx"; + + this.locations = new float[3]; + + for (int y = 0; y < (rows - 1); ++y) { + for (int x = 0; x < (columns - 1); ++x) { + final RenderCorner a = this.corners[x][y]; + final RenderCorner b = this.corners[x + 1][y]; + final RenderCorner c = this.corners[x + 1][y + 1]; + final RenderCorner d = this.corners[x][y + 1]; + if ((a.getWater() != 0) || (b.getWater() != 0) || (c.getWater() != 0) || (d.getWater() != 0)) { + final boolean isCliff = (a.getLayerHeight() != b.getLayerHeight()) + || (a.getLayerHeight() != c.getLayerHeight()) || (a.getLayerHeight() != d.getLayerHeight()); + if (isCliff && (wavesCliff == 0)) { + continue; + } + if (!isCliff && (wavesRolling == 0)) { + continue; + } + final int ad = (a.depth > wavesDepth) ? 1 : 0; + final int bd = (b.depth > wavesDepth) ? 1 : 0; + final int cd = (c.depth > wavesDepth) ? 1 : 0; + final int dd = (d.depth > wavesDepth) ? 1 : 0; + final int count = ad + bd + cd + dd; + this.locations[0] = (x * 128.0f) + this.centerOffset[0] + 64.0f; + this.locations[1] = (y * 128.0f) + this.centerOffset[1] + 64.0f; + this.locations[2] = ((((a.getWaterHeight() + b.getWaterHeight() + c.getWaterHeight() + + d.getWaterHeight()) / 4f) + this.waterHeightOffset) * 128.0f) + 1.0f; + if (count == 1) { + addModelInstance(terrain, insideCorner, rotation(ad, bd, cd/* , dd */) - ((3 * Math.PI) / 4)); + } + else if (count == 2) { + final double rot = rotation2(ad, bd, cd, dd); + if (!Double.isNaN(rot)) { + addModelInstance(terrain, shoreline, rot); + } + } + else if (count == 3) { + addModelInstance(terrain, outsideCorner, + rotation(1 ^ ad, 1 ^ bd, 1 ^ cd/* , 1 ^ dd */) + ((5 * Math.PI) / 4)); + } + } + } + } + } + + private void addModelInstance(final Terrain terrain, final String path, final double rotation) { + if (!this.models.containsKey(path)) { + this.models.put(path, + (MdxModel) this.viewer.load(path, this.viewer.wc3PathSolver, this.viewer.solverParams)); + } + final MdxModel model = this.models.get(path); + final MdxComplexInstance instance = (MdxComplexInstance) model.addInstance(); + instance.setLocation(this.locations); + instance.setLocalRotation(new Quaternion().setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, (float) rotation)); + instance.setScene(this.viewer.worldScene); + if (!terrain.inPlayableArea(this.locations[0], this.locations[1])) { + instance.setVertexColor(new float[] { 51 / 255f, 51 / 255f, 51 / 255f, 1.0f }); + } + this.viewer.standOnRepeat(instance); + } + + private static double rotation(final int a, final int b, final int c) { + if (a != 0) { + return (-3 * Math.PI) / 4; + } + if (b != 0) { + return -Math.PI / 4; + } + if (c != 0) { + return Math.PI / 4; + } + return (3 * Math.PI) / 4; + } + + private static double rotation2(final int a, final int b, final int c, final int d) { + if ((a != 0) && (b != 0)) { + return -Math.PI / 2; + } + if ((b != 0) && (c != 0)) { + return 0; + } + if ((c != 0) && (d != 0)) { + return Math.PI / 2; + } + if ((a != 0) && (d != 0)) { + return Math.PI; + } + return Double.NaN; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java index 32f75f5..09b6ea3 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java @@ -105,7 +105,7 @@ public class RenderUnit { ability.getOrderId())); } else if (ability instanceof CAbilityAttack) { - this.commandCardIcons.add(new CommandCardIcon(-2, -2, + this.commandCardIcons.add(new CommandCardIcon(3, 0, ImageUtils.getBLPTexture(map.dataSource, "ReplaceableTextures\\CommandButtons\\BTNAttack.blp"), ability.getOrderId())); } @@ -117,7 +117,7 @@ public class RenderUnit { ability.getOrderId())); } else if (ability instanceof CAbilityPatrol) { - this.commandCardIcons.add(new CommandCardIcon(3, 0, + this.commandCardIcons.add(new CommandCardIcon(0, 1, ImageUtils.getBLPTexture(map.dataSource, "ReplaceableTextures\\CommandButtons\\BTNPatrol.blp"), ability.getOrderId())); } @@ -202,10 +202,10 @@ public class RenderUnit { } this.lastOrder = currentOrder; if (this.shadow != null) { - this.shadow.move(dx, dy); + this.shadow.move(dx, dy, map.terrain.centerOffset); } if (this.selectionCircle != null) { - this.selectionCircle.move(dx, dy); + this.selectionCircle.move(dx, dy, map.terrain.centerOffset); } } diff --git a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java index 004d6f8..16e56f2 100644 --- a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java +++ b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java @@ -11,6 +11,7 @@ import com.etheller.warsmash.WarsmashGdxMapGame; import com.etheller.warsmash.viewer5.gl.ANGLEInstancedArrays; import com.etheller.warsmash.viewer5.gl.DynamicShadowExtension; import com.etheller.warsmash.viewer5.gl.Extensions; +import com.etheller.warsmash.viewer5.gl.WireframeExtension; public class DesktopLauncher { public static void main(final String[] arg) { @@ -44,6 +45,14 @@ public class DesktopLauncher { GL11.glDrawBuffer(mode); } }; + Extensions.wireframeExtension = new WireframeExtension() { + @Override + public void glPolygonMode(final int face, final int mode) { + GL11.glPolygonMode(face, mode); + } + }; + Extensions.GL_LINE = GL11.GL_LINE; + Extensions.GL_FILL = GL11.GL_FILL; final LwjglApplicationConfiguration config = new LwjglApplicationConfiguration(); config.useGL30 = true; config.gles30ContextMajorVersion = 3; From 9bf38e02684b105f6bb44ee348b4d75f49e25410 Mon Sep 17 00:00:00 2001 From: Retera Date: Sun, 17 May 2020 14:57:42 -0400 Subject: [PATCH 027/116] Begin work on sequence flags --- .../warsmash/parsers/mdlx/Sequence.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/Sequence.java b/core/src/com/etheller/warsmash/parsers/mdlx/Sequence.java index cfa34d6..945a95c 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/Sequence.java +++ b/core/src/com/etheller/warsmash/parsers/mdlx/Sequence.java @@ -1,9 +1,13 @@ package com.etheller.warsmash.parsers.mdlx; import java.io.IOException; +import java.util.EnumSet; import com.etheller.warsmash.util.MdlUtils; import com.etheller.warsmash.util.ParseUtils; +import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens; +import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; +import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.SecondaryTag; import com.google.common.io.LittleEndianDataInputStream; import com.google.common.io.LittleEndianDataOutputStream; @@ -15,6 +19,9 @@ public class Sequence implements MdlxBlock { private float rarity = 0; private long syncPoint = 0; private final Extent extent = new Extent(); + private final EnumSet primaryTags = EnumSet.noneOf(AnimationTokens.PrimaryTag.class); + private final EnumSet secondaryTags = EnumSet + .noneOf(AnimationTokens.SecondaryTag.class); /** * Restricts us to only be able to parse models on one thread at a time, in @@ -31,6 +38,7 @@ public class Sequence implements MdlxBlock { this.rarity = stream.readFloat(); this.syncPoint = ParseUtils.readUInt32(stream); this.extent.readMdx(stream); + populateTags(); } @Override @@ -79,6 +87,7 @@ public class Sequence implements MdlxBlock { throw new IllegalStateException("Unknown token in Sequence \"" + this.name + "\": " + token); } } + populateTags(); } @Override @@ -102,6 +111,27 @@ public class Sequence implements MdlxBlock { stream.endBlock(); } + private void populateTags() { + this.primaryTags.clear(); + this.secondaryTags.clear(); + for (final String token : this.name.split("\\s+")) { + final String upperCaseToken = token.toUpperCase(); + for (final PrimaryTag primaryTag : PrimaryTag.values()) { + if (upperCaseToken.equals(primaryTag.name())) { + this.primaryTags.add(primaryTag); + continue; + } + } + for (final SecondaryTag secondaryTag : SecondaryTag.values()) { + if (upperCaseToken.equals(secondaryTag.name())) { + this.secondaryTags.add(secondaryTag); + continue; + } + } + break; + } + } + public long[] getInterval() { return this.interval; } From 29d72eac4883f4b5eee1f55d05c3a96e4b110bc5 Mon Sep 17 00:00:00 2001 From: Retera Date: Sun, 17 May 2020 23:57:07 -0400 Subject: [PATCH 028/116] Beginning work on FDF parser --- LICENSE | 21 ++ build.gradle | 11 + fdfparser/antlr-src/FDF.g4 | 317 ++++++++++++++++++ fdfparser/build.gradle | 49 +++ .../warsmash/fdfparser/FDFParserBuilder.java | 5 + .../fdfparser/FDFStatementVisitor.java | 36 ++ .../fdfparser/FrameDefinitionVisitor.java | 41 +++ .../com/etheller/warsmash/fdfparser/Main.java | 46 +++ .../fdfparser/TestFDFParserBuilder.java | 27 ++ .../warsmash/parsers/fdf/FDFNamedString.java | 19 ++ .../warsmash/parsers/fdf/FDFStatement.java | 7 + .../parsers/fdf/FDFStringListStatement.java | 20 ++ .../parsers/fdf/templates/FrameEvent.java | 20 ++ .../parsers/fdf/templates/FramePoint.java | 13 + .../templates/FrameTemplateEnvironment.java | 20 ++ .../parsers/fdf/templates/TextJustify.java | 10 + settings.gradle | 2 +- 17 files changed, 663 insertions(+), 1 deletion(-) create mode 100644 LICENSE create mode 100644 fdfparser/antlr-src/FDF.g4 create mode 100644 fdfparser/build.gradle create mode 100644 fdfparser/src/com/etheller/warsmash/fdfparser/FDFParserBuilder.java create mode 100644 fdfparser/src/com/etheller/warsmash/fdfparser/FDFStatementVisitor.java create mode 100644 fdfparser/src/com/etheller/warsmash/fdfparser/FrameDefinitionVisitor.java create mode 100644 fdfparser/src/com/etheller/warsmash/fdfparser/Main.java create mode 100644 fdfparser/src/com/etheller/warsmash/fdfparser/TestFDFParserBuilder.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/FDFNamedString.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/FDFStatement.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/FDFStringListStatement.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/templates/FrameEvent.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/templates/FramePoint.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/templates/FrameTemplateEnvironment.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/templates/TextJustify.java diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..825ef4b --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 DrSuperGood + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/build.gradle b/build.gradle index 5a5d981..c0bc5fb 100644 --- a/build.gradle +++ b/build.gradle @@ -30,6 +30,7 @@ allprojects { box2DLightsVersion = '1.4' ashleyVersion = '1.7.0' aiVersion = '1.8.0' + antlrVersion = '4.7' } repositories { @@ -64,6 +65,7 @@ project(":core") { dependencies { + compile project(":fdfparser") compile "com.badlogicgames.gdx:gdx:$gdxVersion" compile "com.badlogicgames.gdx:gdx-box2d:$gdxVersion" compile "com.badlogicgames.gdx:gdx-freetype:$gdxVersion" @@ -74,6 +76,15 @@ project(":core") { } } +project(":fdfparser") { + apply plugin: "antlr" + + + dependencies { + antlr "org.antlr:antlr4:$antlrVersion" // use antlr version 4 + } +} + tasks.eclipse.doLast { delete ".project" } \ No newline at end of file diff --git a/fdfparser/antlr-src/FDF.g4 b/fdfparser/antlr-src/FDF.g4 new file mode 100644 index 0000000..8aa9c02 --- /dev/null +++ b/fdfparser/antlr-src/FDF.g4 @@ -0,0 +1,317 @@ +/** + * Define a grammar called FDF + */ +grammar FDF; + +@header { + package com.etheller.warsmash.fdfparser; +} + +program : + (statement)* + ; + +statement: + STRING_LIST OPEN_CURLY (ID STRING_LITERAL COMMA)*? CLOSE_CURLY # StringListStatement + | + INCLUDE_FILE STRING_LITERAL COMMA # IncludeStatement + | + frame # FrameStatement + ; + +frame: + FRAME STRING_LITERAL STRING_LITERAL OPEN_CURLY frame_element* CLOSE_CURLY # SimpleFrameDefinition + | + FRAME STRING_LITERAL STRING_LITERAL INHERITS STRING_LITERAL OPEN_CURLY frame_element* CLOSE_CURLY # FrameDefinition + | + FRAME STRING_LITERAL STRING_LITERAL INHERITS WITHCHILDREN STRING_LITERAL OPEN_CURLY frame_element* CLOSE_CURLY # FrameDefinitionWithChildren + ; + +frame_element: + frame # FrameFrameElement + | + HEIGHT FLOAT COMMA # HeightElement + | + WIDTH FLOAT COMMA # WidthElement + | + CONTROL_STYLE STRING_LITERAL COMMA # ControlStyleElement + | + CONTROL_BACKDROP STRING_LITERAL COMMA # ControlBackdropElement + | + CONTROL_PUSHED_BACKDROP STRING_LITERAL COMMA # ControlPushedBackdropElement + | + CONTROL_DISABLED_BACKDROP STRING_LITERAL COMMA # ControlDisabledBackdropElement + | + CONTROL_FOCUS_HIGHLIGHT STRING_LITERAL COMMA # ControlFocusHighlightElement + | + CONTROL_MOUSE_OVER_HIGHLIGHT STRING_LITERAL COMMA # ControlMouseOverHighlightElement + | + HIGHLIGHT_TYPE STRING_LITERAL COMMA # HighlightTypeElement + | + HIGHLIGHT_ALPHA_FILE STRING_LITERAL COMMA # HighlightAlphaFileElement + | + HIGHLIGHT_ALPHA_MODE STRING_LITERAL COMMA # HighlightAlphaModeElement + | + BUTTON_PUSHED_TEXT_OFFSET FLOAT FLOAT COMMA # ButtonPushedTextOffsetElement + | + DIALOG_BACKDROP STRING_LITERAL COMMA # DialogBackdropElement + | + BACKDROP_TILE_BACKGROUND COMMA # BackdropTileBackgroundElement + | + DECORATE_FILE_NAMES COMMA # DecorateFileNamesElement + | + BACKDROP_HALF_SIDES COMMA # BackdropHalfSidesElement + | + BACKDROP_BACKGROUND STRING_LITERAL COMMA # BackdropBackgroundElement + | + BACKDROP_CORNER_FLAGS STRING_LITERAL COMMA # BackdropCornerFlagsElement + | + BACKDROP_CORNER_SIZE FLOAT COMMA # BackdropCornerSizeElement + | + BACKDROP_BACKGROUND_SIZE FLOAT COMMA # BackdropBackgroundSizeElement + | + BACKDROP_BACKGROUND_INSETS FLOAT FLOAT FLOAT FLOAT COMMA # BackdropBackgroundInsetsElement + | + BACKDROP_EDGE_FILE STRING_LITERAL COMMA # BackdropEdgeFileElement + | + BACKDROP_BACKGROUND STRING_LITERAL COMMA # BackdropBackgroundElement + | + BACKDROP_BLEND_ALL COMMA # BackdropBlendAllElement + | + BUTTON_TEXT STRING_LITERAL COMMA #ButtonTextElement + | + TEXT STRING_LITERAL COMMA # TextElement + | + SETPOINT frame_point COMMA STRING_LITERAL COMMA frame_point COMMA FLOAT COMMA FLOAT COMMA # SetPointElement + | + BACKDROP_CORNER_FILE STRING_LITERAL COMMA # BackdropCornerFileElement + | + BACKDROP_LEFT_FILE STRING_LITERAL COMMA # BackdropLeftFileElement + | + BACKDROP_RIGHT_FILE STRING_LITERAL COMMA # BackdropRightFileElement + | + BACKDROP_TOP_FILE STRING_LITERAL COMMA # BackdropTopFileElement + | + BACKDROP_BOTTOM_FILE STRING_LITERAL COMMA # BackdropBottomFileElement + | + FRAME_FONT STRING_LITERAL COMMA FLOAT COMMA STRING_LITERAL COMMA # DecorateFileNamesElement + | + FONT_FLAGS STRING_LITERAL COMMA # FontFlagsElement + | + FONT_COLOR color COMMA # FontColorElement + | + FONT_HIGHLIGHT_COLOR color COMMA # FontHighlightColorElement + | + FONT_DISABLED_COLOR color COMMA # FontDisabledColorElement + | + FONT_SHADOW_COLOR color COMMA # FontShadowColorElement + | + FONT_SHADOW_OFFSET FLOAT FLOAT COMMA # FontShadowOffsetElement + | + FONT_JUSTIFICATION_H text_justify COMMA # FontJustificationHElement + | + FONT_JUSTIFICATION_V text_justify COMMA # FontJustificationVElement + | + FONT_JUSTIFICATION_OFFSET FLOAT FLOAT COMMA # FontJustificationOffsetElement + | + SLIDER_LAYOUT_HORIZONTAL COMMA # SliderLayoutHorizontalElement + | + SLIDER_LAYOUT_VERTICAL COMMA # SliderLayoutVerticalElement + | + SCROLL_BAR_INC_BUTTON_FRAME STRING_LITERAL COMMA # ScrollBarIncButtonFrameElement + | + SCROLL_BAR_DEC_BUTTON_FRAME STRING_LITERAL COMMA # ScrollBarDecButtonFrameElement + | + SLIDER_THUMB_BUTTON_FRAME STRING_LITERAL COMMA # SliderThumbButtonFrameElement + | + LIST_BOX_BORDER FLOAT COMMA # ListBoxBorderElement + | + LIST_BOX_SCROLL_BAR STRING_LITERAL COMMA # ListBoxScrollBarElement + | + EDIT_BORDER_SIZE FLOAT COMMA # EditBorderSizeElement + | + EDIT_CURSOR_COLOR color COMMA # EditCursorColorElement + | + MENU_TEXT_HIGHLIGHT_COLOR color COMMA # MenuTextHighlightColorElement + | + MENU_ITEM_HEIGHT FLOAT COMMA # MenuItemHighlightElement + | + MENU_BORDER FLOAT COMMA # MenuBorderElement + | + POPUP_BUTTON_INSET FLOAT COMMA # PopupButtonInsetElement + | + POPUP_TITLE_FRAME STRING_LITERAL COMMA # PopupTitleFrameElement + | + POPUP_ARROW_FRAME STRING_LITERAL COMMA # PopupArrowFrameElement + | + POPUP_MENU_FRAME STRING_LITERAL COMMA # PopupMenuFrameElement + | + CHECK_BOX_CHECK_HIGHLIGHT STRING_LITERAL COMMA # CheckBoxCheckHighlightElement + | + CHECK_BOX_DISABLED_CHECK_HIGHLIGHT STRING_LITERAL COMMA # CheckBoxDisabledCheckHighlightElement + | + TEXT_AREA_LINE_HEIGHT FLOAT COMMA # TextAreaLineHeightElement + | + TEXT_AREA_LINE_GAP FLOAT COMMA # TextAreaLineGapElement + | + TEXT_AREA_INSET FLOAT COMMA # TextAreaInsetElement + | + TEXT_AREA_SCROLL_BAR STRING_LITERAL COMMA # TextAreaScrollBarElement + | + CHAT_DISPLAY_LINE_HEIGHT FLOAT COMMA # ChatDisplayLineHeightElement + | + CHAT_DISPLAY_BORDER_SIZE FLOAT COMMA # ChatDisplayBorderSize + | + CHAT_DISPLAY_SCROLL_BAR STRING_LITERAL COMMA # ChatDisplayScrollBarElement + | + CHAT_DISPLAY_EDIT_BOX STRING_LITERAL COMMA # ChatDisplayEditBoxElement + ; + +text_justify: + JUSTIFYTOP | JUSTIFYMIDDLE | JUSTIFYBOTTOM | JUSTIFYLEFT | JUSTIFYCENTER | JUSTIFYRIGHT; + +frame_point: + FRAMEPOINT_TOPLEFT + | FRAMEPOINT_TOP + | FRAMEPOINT_TOPRIGHT + | FRAMEPOINT_LEFT + | FRAMEPOINT_CENTER + | FRAMEPOINT_RIGHT + | FRAMEPOINT_BOTTOMLEFT + | FRAMEPOINT_BOTTOM + | FRAMEPOINT_BOTTOMRIGHT; + +color: + FLOAT FLOAT FLOAT + | + FLOAT FLOAT FLOAT FLOAT + ; + +OPEN_CURLY : '{'; + +CLOSE_CURLY : '}'; + +STRING_LIST : 'StringList' ; + +INCLUDE_FILE : 'IncludeFile' ; + +FRAME : 'Frame' ; + +INHERITS : 'INHERITS' ; + +WITHCHILDREN : 'WITHCHILDREN' ; + +DECORATE_FILE_NAMES : 'DecorateFileNames'; + +HEIGHT : 'Height'; + +WIDTH : 'Width'; + +HIGHLIGHT_TYPE : 'HighlightType'; +HIGHLIGHT_ALPHA_FILE : 'HighlightAlphaFile'; +HIGHLIGHT_ALPHA_MODE : 'HighlightAlphaMode'; + +CONTROL_STYLE : 'ControlStyle'; +CONTROL_BACKDROP : 'ControlBackdrop'; +CONTROL_PUSHED_BACKDROP : 'ControlPushedBackdrop'; +CONTROL_DISABLED_BACKDROP : 'ControlDisabledBackdrop'; +CONTROL_FOCUS_HIGHLIGHT : 'ControlFocusHighlight'; +CONTROL_MOUSE_OVER_HIGHLIGHT : 'ControlMouseOverHighlight'; + +DIALOG_BACKDROP : 'DialogBackdrop'; + +BACKDROP_TILE_BACKGROUND : 'BackdropTileBackground'; +BACKDROP_HALF_SIDES : 'BackdropHalfSides'; +BACKDROP_BACKGROUND : 'BackdropBackground'; +BACKDROP_BLEND_ALL : 'BackdropBlendAll'; +BACKDROP_CORNER_FLAGS : 'BackdropCornerFlags'; +BACKDROP_CORNER_SIZE : 'BackdropCornerSize'; +BACKDROP_BACKGROUND_SIZE : 'BackdropBackgroundSize'; +BACKDROP_BACKGROUND_INSETS : 'BackdropBackgroundInsets'; +BACKDROP_EDGE_FILE : 'BackdropEdgeFile'; +BACKDROP_CORNER_FILE : 'BackdropCornerFile'; +BACKDROP_LEFT_FILE : 'BackdropLeftFile'; +BACKDROP_RIGHT_FILE : 'BackdropRightFile'; +BACKDROP_TOP_FILE : 'BackdropTopFile'; +BACKDROP_BOTTOM_FILE : 'BackdropBottomFile'; + +FRAME_FONT : 'FrameFont'; +FONT_FLAGS : 'FontFlags'; +FONT_COLOR : 'FontColor'; +FONT_HIGHLIGHT_COLOR : 'FontHighlightColor'; +FONT_DISABLED_COLOR : 'FontDisabledColor'; +FONT_SHADOW_COLOR : 'FontShadowColor'; +FONT_SHADOW_OFFSET : 'FontShadowOffset'; +FONT_JUSTIFICATION_H : 'FontJustificationH'; +FONT_JUSTIFICATION_V : 'FontJustificationV'; +FONT_JUSTIFICATION_OFFSET : 'FontJustificationOffset'; + +SLIDER_LAYOUT_HORIZONTAL : 'SliderLayoutHorizontal'; +SLIDER_LAYOUT_VERTICAL : 'SliderLayoutVertical'; +SLIDER_THUMB_BUTTON_FRAME : 'SliderThumbButtonFrame'; +SCROLL_BAR_DEC_BUTTON_FRAME : 'ScrollBarDecButtonFrame'; +SCROLL_BAR_INC_BUTTON_FRAME : 'ScrollBarIncButtonFrame'; + +LIST_BOX_BORDER : 'ListBoxBorder'; +LIST_BOX_SCROLL_BAR : 'ListBoxScrollBar'; + +EDIT_BORDER_SIZE : 'EditBorderSize'; +EDIT_CURSOR_COLOR : 'EditCursorColor'; + +MENU_TEXT_HIGHLIGHT_COLOR : 'MenuTextHighlightColor'; +MENU_ITEM_HEIGHT : 'MenuItemHeight'; +MENU_BORDER : 'MenuBorder'; + +POPUP_BUTTON_INSET : 'PopupButtonInset'; +POPUP_TITLE_FRAME : 'PopupTitleFrame'; +POPUP_ARROW_FRAME : 'PopupArrowFrame'; +POPUP_MENU_FRAME : 'PopupMenuFrame'; +CHECK_BOX_CHECK_HIGHLIGHT : 'CheckBoxCheckHighlight'; +CHECK_BOX_DISABLED_CHECK_HIGHLIGHT : 'CheckBoxDisabledCheckHighlight'; + +TEXT_AREA_LINE_HEIGHT : 'TextAreaLineHeight'; +TEXT_AREA_LINE_GAP : 'TextAreaLineGap'; +TEXT_AREA_INSET : 'TextAreaInset'; +TEXT_AREA_SCROLL_BAR : 'TextAreaScrollBar'; + +CHAT_DISPLAY_LINE_HEIGHT : 'ChatDisplayLineHeight'; +CHAT_DISPLAY_BORDER_SIZE : 'ChatDisplayBorderSize'; +CHAT_DISPLAY_SCROLL_BAR : 'ChatDisplayScrollBar'; +CHAT_DISPLAY_EDIT_BOX : 'ChatDisplayEditBox'; + +BUTTON_TEXT : 'ButtonText'; +BUTTON_PUSHED_TEXT_OFFSET : 'ButtonPushedTextOffset'; + +TEXT : 'Text'; + +SETPOINT : 'SetPoint'; + +JUSTIFYTOP : 'JUSTIFYTOP'; +JUSTIFYMIDDLE : 'JUSTIFYMIDDLE'; +JUSTIFYBOTTOM : 'JUSTIFYBOTTOM'; +JUSTIFYLEFT : 'JUSTIFYLEFT'; +JUSTIFYCENTER : 'JUSTIFYCENTER'; +JUSTIFYRIGHT : 'JUSTIFYRIGHT'; + +FRAMEPOINT_TOPLEFT : 'TOPLEFT'; +FRAMEPOINT_TOP : 'TOP'; +FRAMEPOINT_TOPRIGHT : 'TOPRIGHT'; +FRAMEPOINT_LEFT : 'LEFT'; +FRAMEPOINT_CENTER : 'CENTER'; +FRAMEPOINT_RIGHT : 'RIGHT'; +FRAMEPOINT_BOTTOMLEFT : 'BOTTOMLEFT'; +FRAMEPOINT_BOTTOM : 'BOTTOM'; +FRAMEPOINT_BOTTOMRIGHT : 'BOTTOMRIGHT'; + +ID : ([a-zA-Z_][a-zA-Z_0-9]*) ; + +COMMA : ','; + +STRING_LITERAL : ('"'.*?'"'); + +WS : [ \t\r\n]+ -> skip ; + +FLOAT : '-'?([0]|([1-9][0-9]*))('.'([0-9]*)?)?'f'? ; + +MULTI_LINE_COMMENT : '/*'.*?'*/' -> skip ; +COMMENT : '//'.*?'\n' -> skip ; diff --git a/fdfparser/build.gradle b/fdfparser/build.gradle new file mode 100644 index 0000000..679dff9 --- /dev/null +++ b/fdfparser/build.gradle @@ -0,0 +1,49 @@ +apply plugin: "antlr" + +sourceCompatibility = 1.8 +[compileJava, compileTestJava]*.options*.encoding = 'UTF-8' + +sourceSets.main.java.srcDirs = [ "src/", "build/generated-src" ] +sourceSets.main.antlr.srcDirs = [ "antlr-src/" ] + +project.ext.mainClassName = "com.etheller.warsmash.fdfparser.Main" + +task run(dependsOn: classes, type: JavaExec) { + main = project.mainClassName + classpath = sourceSets.main.runtimeClasspath + standardInput = System.in + ignoreExitValue = true +} + +task dist(type: Jar) { + from files(sourceSets.main.output.classesDir) + from files(sourceSets.main.output.resourcesDir) + from {configurations.compile.collect {zipTree(it)}} + + manifest { + attributes 'Main-Class': project.mainClassName + } +} + +dist.dependsOn classes + +eclipse.project { + name = appName + "-fdfparser" +} + +task afterEclipseImport(description: "Post processing after project generation", group: "IDE") { + doLast { + def classpath = new XmlParser().parse(file(".classpath")) + def writer = new FileWriter(file(".classpath")) + def printer = new XmlNodePrinter(new PrintWriter(writer)) + printer.setPreserveWhitespace(true) + printer.print(classpath) + } +} + + +generateGrammarSource { + maxHeapSize = "64m" + arguments += ["-visitor", "-no-listener"] + outputDirectory = file("build/generated-src/com/etheller/warsmash/fdfparser") +} \ No newline at end of file diff --git a/fdfparser/src/com/etheller/warsmash/fdfparser/FDFParserBuilder.java b/fdfparser/src/com/etheller/warsmash/fdfparser/FDFParserBuilder.java new file mode 100644 index 0000000..66c2df4 --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/fdfparser/FDFParserBuilder.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.fdfparser; + +public interface FDFParserBuilder { + FDFParser build(String path); +} diff --git a/fdfparser/src/com/etheller/warsmash/fdfparser/FDFStatementVisitor.java b/fdfparser/src/com/etheller/warsmash/fdfparser/FDFStatementVisitor.java new file mode 100644 index 0000000..3b33223 --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/fdfparser/FDFStatementVisitor.java @@ -0,0 +1,36 @@ +package com.etheller.warsmash.fdfparser; + +import java.util.ArrayList; +import java.util.List; + +import org.antlr.v4.runtime.tree.TerminalNode; + +import com.etheller.warsmash.fdfparser.FDFParser.IncludeStatementContext; +import com.etheller.warsmash.fdfparser.FDFParser.StringListStatementContext; +import com.etheller.warsmash.parsers.fdf.FDFNamedString; +import com.etheller.warsmash.parsers.fdf.FDFStatement; +import com.etheller.warsmash.parsers.fdf.FDFStringListStatement; + +public class FDFStatementVisitor extends FDFBaseVisitor { + + @Override + public FDFStatement visitStringListStatement(final StringListStatementContext ctx) { + final List namedStrings = new ArrayList<>(); + final List ids = ctx.ID(); + final List strings = ctx.STRING_LITERAL(); + for (int i = 0; i < ids.size(); i++) { + final String id = ids.get(i).getText(); + String value = strings.get(i).getText(); + value = value.substring(1, value.length() - 1); + namedStrings.add(new FDFNamedString(id, value)); + } + return new FDFStringListStatement(namedStrings); + } + + @Override + public FDFStatement visitIncludeStatement(final IncludeStatementContext ctx) { + String includeFilePath = ctx.STRING_LITERAL().getText(); + includeFilePath = includeFilePath.substring(1, includeFilePath.length() - 1); + return super.visitIncludeStatement(ctx); + } +} diff --git a/fdfparser/src/com/etheller/warsmash/fdfparser/FrameDefinitionVisitor.java b/fdfparser/src/com/etheller/warsmash/fdfparser/FrameDefinitionVisitor.java new file mode 100644 index 0000000..c56cb63 --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/fdfparser/FrameDefinitionVisitor.java @@ -0,0 +1,41 @@ +package com.etheller.warsmash.fdfparser; + +import java.util.List; + +import org.antlr.v4.runtime.tree.TerminalNode; + +import com.etheller.warsmash.fdfparser.FDFParser.IncludeStatementContext; +import com.etheller.warsmash.fdfparser.FDFParser.StringListStatementContext; +import com.etheller.warsmash.parsers.fdf.templates.FrameTemplateEnvironment; + +public class FrameDefinitionVisitor extends FDFBaseVisitor { + private final FrameTemplateEnvironment templates; + private final FDFParserBuilder fdfParserBuilder; + + public FrameDefinitionVisitor(final FrameTemplateEnvironment templates, final FDFParserBuilder fdfParserBuilder) { + this.templates = templates; + this.fdfParserBuilder = fdfParserBuilder; + } + + @Override + public Void visitStringListStatement(final StringListStatementContext ctx) { + final List ids = ctx.ID(); + final List strings = ctx.STRING_LITERAL(); + for (int i = 0; i < ids.size(); i++) { + final String id = ids.get(i).getText(); + String value = strings.get(i).getText(); + value = value.substring(1, value.length() - 1); + this.templates.addDecoratedString(id, value); + } + return null; + } + + @Override + public Void visitIncludeStatement(final IncludeStatementContext ctx) { + String includeFilePath = ctx.STRING_LITERAL().getText(); + includeFilePath = includeFilePath.substring(1, includeFilePath.length() - 1); + final FDFParser parser = this.fdfParserBuilder.build(includeFilePath); + visit(parser.program()); + return null; + } +} diff --git a/fdfparser/src/com/etheller/warsmash/fdfparser/Main.java b/fdfparser/src/com/etheller/warsmash/fdfparser/Main.java new file mode 100644 index 0000000..be42bff --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/fdfparser/Main.java @@ -0,0 +1,46 @@ +package com.etheller.warsmash.fdfparser; + +import org.antlr.v4.runtime.BaseErrorListener; +import org.antlr.v4.runtime.RecognitionException; +import org.antlr.v4.runtime.Recognizer; + +import com.etheller.warsmash.parsers.fdf.templates.FrameTemplateEnvironment; + +public class Main { + public static final boolean REPORT_SYNTAX_ERRORS = true; + + public static void main(final String[] args) { + if (args.length < 1) { + System.err.println("Usage: "); + return; + } + try { + final BaseErrorListener errorListener = new BaseErrorListener() { + @Override + public void syntaxError(final Recognizer recognizer, final Object offendingSymbol, final int line, + final int charPositionInLine, final String msg, final RecognitionException e) { + if (!REPORT_SYNTAX_ERRORS) { + return; + } + + String sourceName = recognizer.getInputStream().getSourceName(); + if (!sourceName.isEmpty()) { + sourceName = String.format("%s:%d:%d: ", sourceName, line, charPositionInLine); + } + + System.err.println(sourceName + "line " + line + ":" + charPositionInLine + " " + msg); + } + }; + final FrameTemplateEnvironment templates = new FrameTemplateEnvironment(); + final TestFDFParserBuilder testFDFParserBuilder = new TestFDFParserBuilder(errorListener); + final FrameDefinitionVisitor fdfVisitor = new FrameDefinitionVisitor(templates, testFDFParserBuilder); + final FDFParser firstFileParser = testFDFParserBuilder.build(args[0]); + fdfVisitor.visit(firstFileParser.program()); + System.out.println("Value of MONTH_12: " + templates.getDecoratedString("MONTH_12")); + } + catch (final Exception exc) { + System.err.println(exc.getMessage()); + } + } + +} diff --git a/fdfparser/src/com/etheller/warsmash/fdfparser/TestFDFParserBuilder.java b/fdfparser/src/com/etheller/warsmash/fdfparser/TestFDFParserBuilder.java new file mode 100644 index 0000000..df363f1 --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/fdfparser/TestFDFParserBuilder.java @@ -0,0 +1,27 @@ +package com.etheller.warsmash.fdfparser; + +import java.io.IOException; + +import org.antlr.v4.runtime.BaseErrorListener; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CommonTokenStream; + +public class TestFDFParserBuilder implements FDFParserBuilder { + private final BaseErrorListener errorListener; + + public TestFDFParserBuilder(final BaseErrorListener errorListener) { + this.errorListener = errorListener; + } + + @Override + public FDFParser build(final String path) { + FDFLexer lexer; + try { + lexer = new FDFLexer(CharStreams.fromFileName(path)); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + return new FDFParser(new CommonTokenStream(lexer)); + } +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/FDFNamedString.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/FDFNamedString.java new file mode 100644 index 0000000..46a8e0c --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/FDFNamedString.java @@ -0,0 +1,19 @@ +package com.etheller.warsmash.parsers.fdf; + +public class FDFNamedString { + private final String name; + private final String value; + + public FDFNamedString(final String name, final String value) { + this.name = name; + this.value = value; + } + + public String getName() { + return this.name; + } + + public String getValue() { + return this.value; + } +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/FDFStatement.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/FDFStatement.java new file mode 100644 index 0000000..720e500 --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/FDFStatement.java @@ -0,0 +1,7 @@ +package com.etheller.warsmash.parsers.fdf; + +import com.etheller.warsmash.parsers.fdf.templates.FrameTemplateEnvironment; + +public interface FDFStatement { + void loadTemplate(FrameTemplateEnvironment frameDef); +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/FDFStringListStatement.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/FDFStringListStatement.java new file mode 100644 index 0000000..7e65385 --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/FDFStringListStatement.java @@ -0,0 +1,20 @@ +package com.etheller.warsmash.parsers.fdf; + +import java.util.List; + +import com.etheller.warsmash.parsers.fdf.templates.FrameTemplateEnvironment; + +public class FDFStringListStatement implements FDFStatement { + private final List namedStrings; + + public FDFStringListStatement(final List namedStrings) { + this.namedStrings = namedStrings; + } + + @Override + public void loadTemplate(final FrameTemplateEnvironment frameDef) { + for (final FDFNamedString string : this.namedStrings) { + frameDef.addDecoratedString(string.getName(), string.getValue()); + } + } +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/templates/FrameEvent.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/templates/FrameEvent.java new file mode 100644 index 0000000..1c8d85d --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/templates/FrameEvent.java @@ -0,0 +1,20 @@ +package com.etheller.warsmash.parsers.fdf.templates; + +public enum FrameEvent { + CONTROL_CLICK, + MOUSE_ENTER, + MOUSE_LEAVE, + MOUSE_UP, + MOUSE_DOWN, + MOUSE_WHEEL, + CHECKBOX_CHECKED, + CHECKBOX_UNCHECKED, + EDITBOX_TEXT_CHANGED, + POPUPMENU_ITEM_CHANGED, + MOUSE_DOUBLECLICK, + SPRITE_ANIM_UPDATE, + SLIDER_VALUE_CHANGED, + DIALOG_CANCEL, + DIALOG_ACCEPT, + EDITBOX_ENTER +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/templates/FramePoint.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/templates/FramePoint.java new file mode 100644 index 0000000..ec66f90 --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/templates/FramePoint.java @@ -0,0 +1,13 @@ +package com.etheller.warsmash.parsers.fdf.templates; + +public enum FramePoint { + TOPLEFT, + TOP, + TOPRIGHT, + LEFT, + CENTER, + RIGHT, + BOTTOMLEFT, + BOTTOM, + BOTTOMRIGHT; +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/templates/FrameTemplateEnvironment.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/templates/FrameTemplateEnvironment.java new file mode 100644 index 0000000..f632619 --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/templates/FrameTemplateEnvironment.java @@ -0,0 +1,20 @@ +package com.etheller.warsmash.parsers.fdf.templates; + +import java.util.HashMap; +import java.util.Map; + +public class FrameTemplateEnvironment { + private final Map idToDecoratedString = new HashMap<>(); + + public void addDecoratedString(final String id, final String value) { + this.idToDecoratedString.put(id, value); + } + + public String getDecoratedString(final String id) { + final String decoratedString = this.idToDecoratedString.get(id); + if (decoratedString != null) { + return decoratedString; + } + return id; + } +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/templates/TextJustify.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/templates/TextJustify.java new file mode 100644 index 0000000..c793401 --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/templates/TextJustify.java @@ -0,0 +1,10 @@ +package com.etheller.warsmash.parsers.fdf.templates; + +public enum TextJustify { + TOP, + MIDDLE, + BOTTOM, + LEFT, + CENTER, + RIGHT; +} diff --git a/settings.gradle b/settings.gradle index 74fc652..c398e3a 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -include 'desktop', 'core' \ No newline at end of file +include 'desktop', 'core', 'fdfparser' \ No newline at end of file From 155c0cffa849a439bbda44703e0b525b28abbcf6 Mon Sep 17 00:00:00 2001 From: Retera Date: Mon, 18 May 2020 00:00:26 -0400 Subject: [PATCH 029/116] Fix typo --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 825ef4b..4723c86 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019 DrSuperGood +Copyright (c) 2020 Retera Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 57e7538ec8c0ef721db6116e5d7c56e7dc9d6569 Mon Sep 17 00:00:00 2001 From: Retera Date: Wed, 20 May 2020 01:32:52 -0400 Subject: [PATCH 030/116] Work on FDF --- .../viewer5/handlers/w3x/War3MapViewer.java | 27 +++++ .../fdfparser/FrameDefinitionVisitor.java | 2 +- .../com/etheller/warsmash/fdfparser/Main.java | 2 +- .../warsmash/parsers/fdf/FDFStatement.java | 2 +- .../parsers/fdf/FDFStringListStatement.java | 2 +- .../fdf/datamodel/AnchorDefinition.java | 25 ++++ .../fdf/datamodel/BackdropCornerFlags.java | 12 ++ .../fdf/datamodel/ColorDefinition.java | 28 +++++ .../parsers/fdf/datamodel/ControlStyle.java | 7 ++ .../parsers/fdf/datamodel/FontDefinition.java | 25 ++++ .../parsers/fdf/datamodel/FontFlags.java | 5 + .../parsers/fdf/datamodel/FrameDefintion.java | 45 +++++++ .../fdf/datamodel/FrameDefintionOld.java | 68 +++++++++++ .../{templates => datamodel}/FrameEvent.java | 2 +- .../{templates => datamodel}/FramePoint.java | 2 +- .../FrameTemplateEnvironment.java | 2 +- .../fdf/datamodel/HighlightAlphaMode.java | 5 + .../parsers/fdf/datamodel/HighlightType.java | 5 + .../parsers/fdf/datamodel/Insets.java | 41 +++++++ .../parsers/fdf/datamodel/Offset.java | 23 ++++ .../fdf/datamodel/SetPointDefinition.java | 38 ++++++ .../{templates => datamodel}/TextJustify.java | 2 +- .../fields/ColorFrameDefinitionField.java | 21 ++++ .../fields/FloatFrameDefinitionField.java | 18 +++ .../fields/FontFrameDefinitionField.java | 21 ++++ .../fields/FrameDefinitionField.java | 5 + .../fields/FrameDefinitionFieldVisitor.java | 15 +++ .../fields/InsetsFrameDefinitionField.java | 20 ++++ .../fields/OffsetFrameDefinitionField.java | 20 ++++ .../fields/StringFrameDefinitionField.java | 18 +++ .../parsers/fdf/frames/FrameBackdrop.java | 113 ++++++++++++++++++ .../parsers/fdf/frames/FrameButton.java | 5 + .../parsers/fdf/frames/FrameChatDisplay.java | 5 + .../parsers/fdf/frames/FrameCheckBox.java | 5 + .../parsers/fdf/frames/FrameDialog.java | 5 + .../parsers/fdf/frames/FrameEditBox.java | 5 + .../parsers/fdf/frames/FrameFrame.java | 5 + .../parsers/fdf/frames/FrameHighlight.java | 35 ++++++ .../parsers/fdf/frames/FrameListBox.java | 5 + .../parsers/fdf/frames/FrameMenu.java | 5 + .../parsers/fdf/frames/FramePopupMenu.java | 5 + .../parsers/fdf/frames/FrameScrollbar.java | 5 + .../parsers/fdf/frames/FrameSlider.java | 5 + .../parsers/fdf/frames/FrameText.java | 5 + .../parsers/fdf/frames/FrameTextArea.java | 5 + .../parsers/fdf/frames/FrameTextButton.java | 19 +++ .../parsers/fdf/frames/base/Frame.java | 39 ++++++ .../fdf/frames/simple/FrameSimpleButton.java | 5 + .../frames/simple/FrameSimpleCheckBox.java | 5 + .../fdf/frames/simple/FrameSimpleFrame.java | 5 + .../frames/simple/FrameSimpleStatusBar.java | 5 + 51 files changed, 791 insertions(+), 8 deletions(-) create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/AnchorDefinition.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/BackdropCornerFlags.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/ColorDefinition.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/ControlStyle.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FontDefinition.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FontFlags.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FrameDefintion.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FrameDefintionOld.java rename fdfparser/src/com/etheller/warsmash/parsers/fdf/{templates => datamodel}/FrameEvent.java (85%) rename fdfparser/src/com/etheller/warsmash/parsers/fdf/{templates => datamodel}/FramePoint.java (68%) rename fdfparser/src/com/etheller/warsmash/parsers/fdf/{templates => datamodel}/FrameTemplateEnvironment.java (90%) create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/HighlightAlphaMode.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/HighlightType.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/Insets.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/Offset.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/SetPointDefinition.java rename fdfparser/src/com/etheller/warsmash/parsers/fdf/{templates => datamodel}/TextJustify.java (59%) create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/ColorFrameDefinitionField.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/FloatFrameDefinitionField.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/FontFrameDefinitionField.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/FrameDefinitionField.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/FrameDefinitionFieldVisitor.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/InsetsFrameDefinitionField.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/OffsetFrameDefinitionField.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/StringFrameDefinitionField.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameBackdrop.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameButton.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameChatDisplay.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameCheckBox.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameDialog.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameEditBox.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameFrame.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameHighlight.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameListBox.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameMenu.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FramePopupMenu.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameScrollbar.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameSlider.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameText.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameTextArea.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameTextButton.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/base/Frame.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/simple/FrameSimpleButton.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/simple/FrameSimpleCheckBox.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/simple/FrameSimpleFrame.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/simple/FrameSimpleStatusBar.java diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index be1cadc..d993d68 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -504,6 +504,25 @@ public class War3MapViewer extends ModelViewer { path = path.substring(0, path.length() - 4); } + final String unitShadow = "Shadow"; + if ((unitShadow != null) && !"_".equals(unitShadow)) { + final String texture = "ReplaceableTextures\\Shadows\\" + unitShadow + ".blp"; + final float shadowX = 50; + final float shadowY = 50; + final float shadowWidth = 128; + final float shadowHeight = 128; + if (!this.terrain.splats.containsKey(texture)) { + final Splat splat = new Splat(); + splat.opacity = 0.5f; + this.terrain.splats.put(texture, splat); + } + final float x = unit.getLocation()[0] - shadowX; + final float y = unit.getLocation()[1] - shadowY; + this.terrain.splats.get(texture).locations + .add(new float[] { x, y, x + shadowWidth, y + shadowHeight, 3 }); + unitShadowSplat = this.terrain.splats.get(texture); + } + path += ".mdx"; } } @@ -608,6 +627,14 @@ public class War3MapViewer extends ModelViewer { else { this.items.add(new RenderItem(this, model, row, unit, soundset, portraitModel)); // TODO store // somewhere + if (unitShadowSplat != null) { + unitShadowSplat.unitMapping.add(new Consumer() { + @Override + public void accept(final SplatMover t) { + + } + }); + } } } else { diff --git a/fdfparser/src/com/etheller/warsmash/fdfparser/FrameDefinitionVisitor.java b/fdfparser/src/com/etheller/warsmash/fdfparser/FrameDefinitionVisitor.java index c56cb63..5c5aa9e 100644 --- a/fdfparser/src/com/etheller/warsmash/fdfparser/FrameDefinitionVisitor.java +++ b/fdfparser/src/com/etheller/warsmash/fdfparser/FrameDefinitionVisitor.java @@ -6,7 +6,7 @@ import org.antlr.v4.runtime.tree.TerminalNode; import com.etheller.warsmash.fdfparser.FDFParser.IncludeStatementContext; import com.etheller.warsmash.fdfparser.FDFParser.StringListStatementContext; -import com.etheller.warsmash.parsers.fdf.templates.FrameTemplateEnvironment; +import com.etheller.warsmash.parsers.fdf.datamodel.FrameTemplateEnvironment; public class FrameDefinitionVisitor extends FDFBaseVisitor { private final FrameTemplateEnvironment templates; diff --git a/fdfparser/src/com/etheller/warsmash/fdfparser/Main.java b/fdfparser/src/com/etheller/warsmash/fdfparser/Main.java index be42bff..3769298 100644 --- a/fdfparser/src/com/etheller/warsmash/fdfparser/Main.java +++ b/fdfparser/src/com/etheller/warsmash/fdfparser/Main.java @@ -4,7 +4,7 @@ import org.antlr.v4.runtime.BaseErrorListener; import org.antlr.v4.runtime.RecognitionException; import org.antlr.v4.runtime.Recognizer; -import com.etheller.warsmash.parsers.fdf.templates.FrameTemplateEnvironment; +import com.etheller.warsmash.parsers.fdf.datamodel.FrameTemplateEnvironment; public class Main { public static final boolean REPORT_SYNTAX_ERRORS = true; diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/FDFStatement.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/FDFStatement.java index 720e500..c039767 100644 --- a/fdfparser/src/com/etheller/warsmash/parsers/fdf/FDFStatement.java +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/FDFStatement.java @@ -1,6 +1,6 @@ package com.etheller.warsmash.parsers.fdf; -import com.etheller.warsmash.parsers.fdf.templates.FrameTemplateEnvironment; +import com.etheller.warsmash.parsers.fdf.datamodel.FrameTemplateEnvironment; public interface FDFStatement { void loadTemplate(FrameTemplateEnvironment frameDef); diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/FDFStringListStatement.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/FDFStringListStatement.java index 7e65385..121ce7e 100644 --- a/fdfparser/src/com/etheller/warsmash/parsers/fdf/FDFStringListStatement.java +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/FDFStringListStatement.java @@ -2,7 +2,7 @@ package com.etheller.warsmash.parsers.fdf; import java.util.List; -import com.etheller.warsmash.parsers.fdf.templates.FrameTemplateEnvironment; +import com.etheller.warsmash.parsers.fdf.datamodel.FrameTemplateEnvironment; public class FDFStringListStatement implements FDFStatement { private final List namedStrings; diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/AnchorDefinition.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/AnchorDefinition.java new file mode 100644 index 0000000..7a27ec6 --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/AnchorDefinition.java @@ -0,0 +1,25 @@ +package com.etheller.warsmash.parsers.fdf.datamodel; + +public class AnchorDefinition { + private final FramePoint myPoint; + private final float x; + private final float y; + + public AnchorDefinition(final FramePoint myPoint, final float x, final float y) { + this.myPoint = myPoint; + this.x = x; + this.y = y; + } + + public FramePoint getMyPoint() { + return this.myPoint; + } + + public float getX() { + return this.x; + } + + public float getY() { + return this.y; + } +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/BackdropCornerFlags.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/BackdropCornerFlags.java new file mode 100644 index 0000000..eae2f0d --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/BackdropCornerFlags.java @@ -0,0 +1,12 @@ +package com.etheller.warsmash.parsers.fdf.datamodel; + +public enum BackdropCornerFlags { + UL, + UR, + BL, + BR, + T, + L, + B, + R; +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/ColorDefinition.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/ColorDefinition.java new file mode 100644 index 0000000..eea2153 --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/ColorDefinition.java @@ -0,0 +1,28 @@ +package com.etheller.warsmash.parsers.fdf.datamodel; + +public class ColorDefinition { + private final float red, green, blue, alpha; + + public ColorDefinition(final float red, final float green, final float blue, final float alpha) { + this.red = red; + this.green = green; + this.blue = blue; + this.alpha = alpha; + } + + public float getRed() { + return this.red; + } + + public float getGreen() { + return this.green; + } + + public float getBlue() { + return this.blue; + } + + public float getAlpha() { + return this.alpha; + } +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/ControlStyle.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/ControlStyle.java new file mode 100644 index 0000000..4243b8d --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/ControlStyle.java @@ -0,0 +1,7 @@ +package com.etheller.warsmash.parsers.fdf.datamodel; + +public enum ControlStyle { + AUTOTRACK, + HIGHLIGHTONFOCUS, + HIGHLIGHTONMOUSEOVER; +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FontDefinition.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FontDefinition.java new file mode 100644 index 0000000..4f0b0f8 --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FontDefinition.java @@ -0,0 +1,25 @@ +package com.etheller.warsmash.parsers.fdf.datamodel; + +public class FontDefinition { + private final String fontName; + private final float fontSize; + private final String extra; + + public FontDefinition(final String fontName, final float fontSize, final String extra) { + this.fontName = fontName; + this.fontSize = fontSize; + this.extra = extra; + } + + public String getFontName() { + return this.fontName; + } + + public float getFontSize() { + return this.fontSize; + } + + public String getExtra() { + return this.extra; + } +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FontFlags.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FontFlags.java new file mode 100644 index 0000000..99fef40 --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FontFlags.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.parsers.fdf.datamodel; + +public enum FontFlags { + FIXEDSIZE; +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FrameDefintion.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FrameDefintion.java new file mode 100644 index 0000000..6c9374a --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FrameDefintion.java @@ -0,0 +1,45 @@ +package com.etheller.warsmash.parsers.fdf.datamodel; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.etheller.warsmash.parsers.fdf.datamodel.fields.FrameDefinitionField; + +/** + * Pretty sure this is probably not how it works in-game but this silly + * everything class might help get a prototype running fast until I have a + * better understanding of how I want these classes designed. + */ +public class FrameDefintion { + private String frameType; + private String name; + private final List innerFrames = new ArrayList<>(); + private final Set flags = new HashSet<>(); + private final Map nameToField = new HashMap<>(); + private final List setPoints = new ArrayList<>(); + private final List anchors = new ArrayList<>(); + + public void inheritFrom(final FrameDefintion other, final boolean withChildren) { + this.flags.addAll(other.flags); + this.nameToField.putAll(other.nameToField); + if (withChildren) { + this.innerFrames.addAll(other.innerFrames); + } + } + + public void set(final String fieldName, final FrameDefinitionField value) { + this.nameToField.put(fieldName, value); + } + + public void add(final SetPointDefinition setPointDefinition) { + this.setPoints.add(setPointDefinition); + } + + public void add(final AnchorDefinition anchorDefinition) { + this.anchors.add(anchorDefinition); + } +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FrameDefintionOld.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FrameDefintionOld.java new file mode 100644 index 0000000..86c6cb0 --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FrameDefintionOld.java @@ -0,0 +1,68 @@ +package com.etheller.warsmash.parsers.fdf.datamodel; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; + +/** + * Pretty sure this is probably not how it works in-game but this silly + * everything class might help get a prototype running fast until I have a + * better understanding of how I want these classes designed. + */ +public class FrameDefintionOld { + private String frameType; + private String name; + // everything + private float width; + private float height; + private boolean decorateFileNames; + private boolean setAllPoints; + private final List innerFrames = new ArrayList<>(); + // "BACKDROP" + private boolean backdropTileBackground; + private boolean backdropHalfSides; + private boolean backdropBlendAll; + private String backdropBackground; + private EnumSet backdropCornerFlags; + private float backdropCornerSize; + private float backdropBackgroundSize; + private Insets backdropBackgroundInsets; + private String backdropCornerFile; + private String backdropLeftFile; + private String backdropRightFile; + private String backdropTopFile; + private String backdropBottomFile; + private String backdropEdgeFile; + // "HIGHLIGHT" + private HighlightType highlightType; + private String highlightAlphaFile; + private HighlightAlphaMode highlightAlphaMode; + // "TEXTBUTTON" or "GLUEBUTTON" + private EnumSet controlStyle; + private Offset buttonPushedTextOffset; + private String controlBackdrop; + private String controlPushedBackdrop; + private String controlDisabledBackdrop; + private String controlFocusHighlight; + private String controlMouseOverHighlight; + // "TEXT" + private FontDefinition frameFont; + private TextJustify fontJustificationH; + private TextJustify fontJustificationV; + private EnumSet fontFlags; + private ColorDefinition fontColor; + private ColorDefinition fontHighlightColor; + private ColorDefinition fontDisabledColor; + private ColorDefinition fontShadowColor; + private Offset fontShadowOffset; + // "SLIDER" + private boolean sliderLayoutHorizontal; + // "SCROLLBAR" + private boolean sliderLayoutVertical; + private String scrollBarIncButtonFrame; + private String scrollBarDecButtonFrame; + private String scrollBarThumbButtonFrame; + // "LISTBOX" + private float listBoxBorder; + // "EDITBOX" +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/templates/FrameEvent.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FrameEvent.java similarity index 85% rename from fdfparser/src/com/etheller/warsmash/parsers/fdf/templates/FrameEvent.java rename to fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FrameEvent.java index 1c8d85d..3e4472b 100644 --- a/fdfparser/src/com/etheller/warsmash/parsers/fdf/templates/FrameEvent.java +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FrameEvent.java @@ -1,4 +1,4 @@ -package com.etheller.warsmash.parsers.fdf.templates; +package com.etheller.warsmash.parsers.fdf.datamodel; public enum FrameEvent { CONTROL_CLICK, diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/templates/FramePoint.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FramePoint.java similarity index 68% rename from fdfparser/src/com/etheller/warsmash/parsers/fdf/templates/FramePoint.java rename to fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FramePoint.java index ec66f90..2a9229a 100644 --- a/fdfparser/src/com/etheller/warsmash/parsers/fdf/templates/FramePoint.java +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FramePoint.java @@ -1,4 +1,4 @@ -package com.etheller.warsmash.parsers.fdf.templates; +package com.etheller.warsmash.parsers.fdf.datamodel; public enum FramePoint { TOPLEFT, diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/templates/FrameTemplateEnvironment.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FrameTemplateEnvironment.java similarity index 90% rename from fdfparser/src/com/etheller/warsmash/parsers/fdf/templates/FrameTemplateEnvironment.java rename to fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FrameTemplateEnvironment.java index f632619..fc13cd0 100644 --- a/fdfparser/src/com/etheller/warsmash/parsers/fdf/templates/FrameTemplateEnvironment.java +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FrameTemplateEnvironment.java @@ -1,4 +1,4 @@ -package com.etheller.warsmash.parsers.fdf.templates; +package com.etheller.warsmash.parsers.fdf.datamodel; import java.util.HashMap; import java.util.Map; diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/HighlightAlphaMode.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/HighlightAlphaMode.java new file mode 100644 index 0000000..4766c42 --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/HighlightAlphaMode.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.parsers.fdf.datamodel; + +public enum HighlightAlphaMode { + ADD; +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/HighlightType.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/HighlightType.java new file mode 100644 index 0000000..f03ce4a --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/HighlightType.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.parsers.fdf.datamodel; + +public enum HighlightType { + FILETEXTURE; +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/Insets.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/Insets.java new file mode 100644 index 0000000..7ad6db6 --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/Insets.java @@ -0,0 +1,41 @@ +package com.etheller.warsmash.parsers.fdf.datamodel; + +public class Insets { + private float top; + private float left; + private float bottom; + private float right; + + public float getTop() { + return this.top; + } + + public float getLeft() { + return this.left; + } + + public float getBottom() { + return this.bottom; + } + + public float getRight() { + return this.right; + } + + public void setTop(final float top) { + this.top = top; + } + + public void setLeft(final float left) { + this.left = left; + } + + public void setBottom(final float bottom) { + this.bottom = bottom; + } + + public void setRight(final float right) { + this.right = right; + } + +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/Offset.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/Offset.java new file mode 100644 index 0000000..eb2732b --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/Offset.java @@ -0,0 +1,23 @@ +package com.etheller.warsmash.parsers.fdf.datamodel; + +public class Offset { + private float x; + private float y; + + public float getX() { + return this.x; + } + + public float getY() { + return this.y; + } + + public void setX(final float x) { + this.x = x; + } + + public void setY(final float y) { + this.y = y; + } + +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/SetPointDefinition.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/SetPointDefinition.java new file mode 100644 index 0000000..bbfbe0b --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/SetPointDefinition.java @@ -0,0 +1,38 @@ +package com.etheller.warsmash.parsers.fdf.datamodel; + +public class SetPointDefinition { + private final FramePoint myPoint; + private final String other; + private final FramePoint otherPoint; + private final float x; + private final float y; + + public SetPointDefinition(final FramePoint myPoint, final String other, final FramePoint otherPoint, final float x, + final float y) { + this.myPoint = myPoint; + this.other = other; + this.otherPoint = otherPoint; + this.x = x; + this.y = y; + } + + public FramePoint getMyPoint() { + return this.myPoint; + } + + public String getOther() { + return this.other; + } + + public FramePoint getOtherPoint() { + return this.otherPoint; + } + + public float getX() { + return this.x; + } + + public float getY() { + return this.y; + } +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/templates/TextJustify.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/TextJustify.java similarity index 59% rename from fdfparser/src/com/etheller/warsmash/parsers/fdf/templates/TextJustify.java rename to fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/TextJustify.java index c793401..ae30591 100644 --- a/fdfparser/src/com/etheller/warsmash/parsers/fdf/templates/TextJustify.java +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/TextJustify.java @@ -1,4 +1,4 @@ -package com.etheller.warsmash.parsers.fdf.templates; +package com.etheller.warsmash.parsers.fdf.datamodel; public enum TextJustify { TOP, diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/ColorFrameDefinitionField.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/ColorFrameDefinitionField.java new file mode 100644 index 0000000..9fdebc5 --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/ColorFrameDefinitionField.java @@ -0,0 +1,21 @@ +package com.etheller.warsmash.parsers.fdf.datamodel.fields; + +import com.etheller.warsmash.parsers.fdf.datamodel.ColorDefinition; + +public class ColorFrameDefinitionField implements FrameDefinitionField { + private final ColorDefinition value; + + public ColorFrameDefinitionField(final ColorDefinition value) { + this.value = value; + } + + public ColorDefinition getValue() { + return this.value; + } + + @Override + public TYPE visit(final FrameDefinitionFieldVisitor visitor) { + return visitor.accept(this); + } + +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/FloatFrameDefinitionField.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/FloatFrameDefinitionField.java new file mode 100644 index 0000000..8c21db2 --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/FloatFrameDefinitionField.java @@ -0,0 +1,18 @@ +package com.etheller.warsmash.parsers.fdf.datamodel.fields; + +public class FloatFrameDefinitionField implements FrameDefinitionField { + private final float value; + + public FloatFrameDefinitionField(final float value) { + this.value = value; + } + + public float getValue() { + return this.value; + } + + @Override + public TYPE visit(final FrameDefinitionFieldVisitor visitor) { + return visitor.accept(this); + } +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/FontFrameDefinitionField.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/FontFrameDefinitionField.java new file mode 100644 index 0000000..d6ca65c --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/FontFrameDefinitionField.java @@ -0,0 +1,21 @@ +package com.etheller.warsmash.parsers.fdf.datamodel.fields; + +import com.etheller.warsmash.parsers.fdf.datamodel.FontDefinition; + +public class FontFrameDefinitionField implements FrameDefinitionField { + private final FontDefinition value; + + public FontFrameDefinitionField(final FontDefinition value) { + this.value = value; + } + + public FontDefinition getValue() { + return this.value; + } + + @Override + public TYPE visit(final FrameDefinitionFieldVisitor visitor) { + return visitor.accept(this); + } + +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/FrameDefinitionField.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/FrameDefinitionField.java new file mode 100644 index 0000000..f0b84ee --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/FrameDefinitionField.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.parsers.fdf.datamodel.fields; + +public interface FrameDefinitionField { + TYPE visit(FrameDefinitionFieldVisitor visitor); +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/FrameDefinitionFieldVisitor.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/FrameDefinitionFieldVisitor.java new file mode 100644 index 0000000..aa082ae --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/FrameDefinitionFieldVisitor.java @@ -0,0 +1,15 @@ +package com.etheller.warsmash.parsers.fdf.datamodel.fields; + +public interface FrameDefinitionFieldVisitor { + TYPE accept(StringFrameDefinitionField field); + + TYPE accept(FloatFrameDefinitionField field); + + TYPE accept(ColorFrameDefinitionField field); + + TYPE accept(InsetsFrameDefinitionField field); + + TYPE accept(OffsetFrameDefinitionField field); + + TYPE accept(FontFrameDefinitionField field); +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/InsetsFrameDefinitionField.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/InsetsFrameDefinitionField.java new file mode 100644 index 0000000..67c3be2 --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/InsetsFrameDefinitionField.java @@ -0,0 +1,20 @@ +package com.etheller.warsmash.parsers.fdf.datamodel.fields; + +import com.etheller.warsmash.parsers.fdf.datamodel.Insets; + +public class InsetsFrameDefinitionField implements FrameDefinitionField { + private final Insets value; + + public InsetsFrameDefinitionField(final Insets value) { + this.value = value; + } + + public Insets getValue() { + return this.value; + } + + @Override + public TYPE visit(final FrameDefinitionFieldVisitor visitor) { + return visitor.accept(this); + } +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/OffsetFrameDefinitionField.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/OffsetFrameDefinitionField.java new file mode 100644 index 0000000..770c135 --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/OffsetFrameDefinitionField.java @@ -0,0 +1,20 @@ +package com.etheller.warsmash.parsers.fdf.datamodel.fields; + +import com.etheller.warsmash.parsers.fdf.datamodel.Offset; + +public class OffsetFrameDefinitionField implements FrameDefinitionField { + private final Offset value; + + public OffsetFrameDefinitionField(final Offset value) { + this.value = value; + } + + public Offset getValue() { + return this.value; + } + + @Override + public TYPE visit(final FrameDefinitionFieldVisitor visitor) { + return visitor.accept(this); + } +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/StringFrameDefinitionField.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/StringFrameDefinitionField.java new file mode 100644 index 0000000..c2c903e --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/StringFrameDefinitionField.java @@ -0,0 +1,18 @@ +package com.etheller.warsmash.parsers.fdf.datamodel.fields; + +public class StringFrameDefinitionField implements FrameDefinitionField { + private final String value; + + public StringFrameDefinitionField(final String value) { + this.value = value; + } + + public String getValue() { + return this.value; + } + + @Override + public TYPE visit(final FrameDefinitionFieldVisitor visitor) { + return visitor.accept(this); + } +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameBackdrop.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameBackdrop.java new file mode 100644 index 0000000..c4f9872 --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameBackdrop.java @@ -0,0 +1,113 @@ +package com.etheller.warsmash.parsers.fdf.frames; + +import java.util.EnumSet; + +import com.etheller.warsmash.parsers.fdf.datamodel.BackdropCornerFlags; +import com.etheller.warsmash.parsers.fdf.datamodel.Insets; +import com.etheller.warsmash.parsers.fdf.frames.base.Frame; + +public class FrameBackdrop extends Frame { + private boolean tileBackground; + private boolean halfSides; + private boolean blendAll; + private String background; + private EnumSet cornerFlags; + private float cornerSize; + private float backgroundSize; + private Insets backgroundInsets; + private String cornerFile; + private String leftFile; + private String rightFile; + private String topFile; + private String bottomFile; + private String edgeFile; + + public boolean isTileBackground() { + return this.tileBackground; + } + + public boolean isHalfSides() { + return this.halfSides; + } + + public boolean isBlendAll() { + return this.blendAll; + } + + public String getBackground() { + return this.background; + } + + public EnumSet getCornerFlags() { + return this.cornerFlags; + } + + public float getCornerSize() { + return this.cornerSize; + } + + public String getCornerFile() { + return this.cornerFile; + } + + public String getLeftFile() { + return this.leftFile; + } + + public String getRightFile() { + return this.rightFile; + } + + public String getTopFile() { + return this.topFile; + } + + public String getBottomFile() { + return this.bottomFile; + } + + public void setTileBackground(final boolean tileBackground) { + this.tileBackground = tileBackground; + } + + public void setHalfSides(final boolean halfSides) { + this.halfSides = halfSides; + } + + public void setBlendAll(final boolean blendAll) { + this.blendAll = blendAll; + } + + public void setBackground(final String background) { + this.background = background; + } + + public void setCornerFlags(final EnumSet cornerFlags) { + this.cornerFlags = cornerFlags; + } + + public void setCornerSize(final float cornerSize) { + this.cornerSize = cornerSize; + } + + public void setCornerFile(final String cornerFile) { + this.cornerFile = cornerFile; + } + + public void setLeftFile(final String leftFile) { + this.leftFile = leftFile; + } + + public void setRightFile(final String rightFile) { + this.rightFile = rightFile; + } + + public void setTopFile(final String topFile) { + this.topFile = topFile; + } + + public void setBottomFile(final String bottomFile) { + this.bottomFile = bottomFile; + } + +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameButton.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameButton.java new file mode 100644 index 0000000..27ed71e --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameButton.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.parsers.fdf.frames; + +public class FrameButton { + +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameChatDisplay.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameChatDisplay.java new file mode 100644 index 0000000..87fab0c --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameChatDisplay.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.parsers.fdf.frames; + +public class FrameChatDisplay { + +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameCheckBox.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameCheckBox.java new file mode 100644 index 0000000..b0cb3a9 --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameCheckBox.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.parsers.fdf.frames; + +public class FrameCheckBox { + +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameDialog.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameDialog.java new file mode 100644 index 0000000..c464d54 --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameDialog.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.parsers.fdf.frames; + +public class FrameDialog { + +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameEditBox.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameEditBox.java new file mode 100644 index 0000000..cd67fd7 --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameEditBox.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.parsers.fdf.frames; + +public class FrameEditBox { + +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameFrame.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameFrame.java new file mode 100644 index 0000000..ba09ed7 --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameFrame.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.parsers.fdf.frames; + +public class FrameFrame { + +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameHighlight.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameHighlight.java new file mode 100644 index 0000000..37a4e47 --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameHighlight.java @@ -0,0 +1,35 @@ +package com.etheller.warsmash.parsers.fdf.frames; + +import com.etheller.warsmash.parsers.fdf.datamodel.HighlightAlphaMode; +import com.etheller.warsmash.parsers.fdf.datamodel.HighlightType; +import com.etheller.warsmash.parsers.fdf.frames.base.Frame; + +public class FrameHighlight extends Frame { + private HighlightType type; + private String alphaFile; + private HighlightAlphaMode alphaMode; + + public HighlightType getType() { + return this.type; + } + + public String getAlphaFile() { + return this.alphaFile; + } + + public HighlightAlphaMode getAlphaMode() { + return this.alphaMode; + } + + public void setType(final HighlightType type) { + this.type = type; + } + + public void setAlphaFile(final String alphaFile) { + this.alphaFile = alphaFile; + } + + public void setAlphaMode(final HighlightAlphaMode alphaMode) { + this.alphaMode = alphaMode; + } +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameListBox.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameListBox.java new file mode 100644 index 0000000..e298393 --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameListBox.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.parsers.fdf.frames; + +public class FrameListBox { + +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameMenu.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameMenu.java new file mode 100644 index 0000000..3540d5e --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameMenu.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.parsers.fdf.frames; + +public class FrameMenu { + +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FramePopupMenu.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FramePopupMenu.java new file mode 100644 index 0000000..db29d10 --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FramePopupMenu.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.parsers.fdf.frames; + +public class FramePopupMenu { + +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameScrollbar.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameScrollbar.java new file mode 100644 index 0000000..8652a93 --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameScrollbar.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.parsers.fdf.frames; + +public class FrameScrollbar { + +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameSlider.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameSlider.java new file mode 100644 index 0000000..140276d --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameSlider.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.parsers.fdf.frames; + +public class FrameSlider { + +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameText.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameText.java new file mode 100644 index 0000000..a55e0d1 --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameText.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.parsers.fdf.frames; + +public class FrameText { + +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameTextArea.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameTextArea.java new file mode 100644 index 0000000..9e561ca --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameTextArea.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.parsers.fdf.frames; + +public class FrameTextArea { + +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameTextButton.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameTextButton.java new file mode 100644 index 0000000..7ce0acc --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameTextButton.java @@ -0,0 +1,19 @@ +package com.etheller.warsmash.parsers.fdf.frames; + +import java.util.EnumSet; + +import com.etheller.warsmash.parsers.fdf.datamodel.ControlStyle; +import com.etheller.warsmash.parsers.fdf.datamodel.Offset; +import com.etheller.warsmash.parsers.fdf.frames.base.Frame; + +public class FrameTextButton extends Frame { + private EnumSet controlStyle; + private Offset buttonPushedTextOffset; + private String controlBackdrop; + + private String controlPushedBackdrop; + private String controlDisabledBackdrop; + private String controlFocusHighlight; + private String controlMouseOverHighlight; + +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/base/Frame.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/base/Frame.java new file mode 100644 index 0000000..46dd834 --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/base/Frame.java @@ -0,0 +1,39 @@ +package com.etheller.warsmash.parsers.fdf.frames.base; + +import java.util.ArrayList; +import java.util.List; + +public class Frame { + private String name; + private float width; + private float height; + private final List innerFrames = new ArrayList<>(); + + public String getName() { + return this.name; + } + + public void setName(final String name) { + this.name = name; + } + + public float getWidth() { + return this.width; + } + + public float getHeight() { + return this.height; + } + + public void setWidth(final float width) { + this.width = width; + } + + public void setHeight(final float height) { + this.height = height; + } + + public List getInnerFrames() { + return this.innerFrames; + } +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/simple/FrameSimpleButton.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/simple/FrameSimpleButton.java new file mode 100644 index 0000000..440322b --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/simple/FrameSimpleButton.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.parsers.fdf.frames.simple; + +public class FrameSimpleButton { + +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/simple/FrameSimpleCheckBox.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/simple/FrameSimpleCheckBox.java new file mode 100644 index 0000000..185da3b --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/simple/FrameSimpleCheckBox.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.parsers.fdf.frames.simple; + +public class FrameSimpleCheckBox { + +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/simple/FrameSimpleFrame.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/simple/FrameSimpleFrame.java new file mode 100644 index 0000000..be38e67 --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/simple/FrameSimpleFrame.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.parsers.fdf.frames.simple; + +public class FrameSimpleFrame { + +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/simple/FrameSimpleStatusBar.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/simple/FrameSimpleStatusBar.java new file mode 100644 index 0000000..0aaffd8 --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/simple/FrameSimpleStatusBar.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.parsers.fdf.frames.simple; + +public class FrameSimpleStatusBar { + +} From 6034509ebe1f0197587dc333f94b3a2ee51e6d5c Mon Sep 17 00:00:00 2001 From: Retera Date: Wed, 20 May 2020 01:39:41 -0400 Subject: [PATCH 031/116] More FDF updates --- fdfparser/antlr-src/FDF.g4 | 46 ++++++- .../fdfparser/FDFStatementVisitor.java | 36 ------ .../FrameDefinitionFieldVisitor.java | 19 +++ .../fdfparser/FrameDefinitionVisitor.java | 46 ++++++- .../warsmash/parsers/fdf/FDFNamedString.java | 19 --- .../warsmash/parsers/fdf/FDFStatement.java | 7 -- .../parsers/fdf/FDFStringListStatement.java | 20 ---- .../fdf/datamodel/FrameDefintionOld.java | 68 ----------- .../parsers/fdf/frames/FrameBackdrop.java | 113 ------------------ .../parsers/fdf/frames/FrameButton.java | 5 - .../parsers/fdf/frames/FrameChatDisplay.java | 5 - .../parsers/fdf/frames/FrameCheckBox.java | 5 - .../parsers/fdf/frames/FrameDialog.java | 5 - .../parsers/fdf/frames/FrameEditBox.java | 5 - .../parsers/fdf/frames/FrameFrame.java | 5 - .../parsers/fdf/frames/FrameHighlight.java | 35 ------ .../parsers/fdf/frames/FrameListBox.java | 5 - .../parsers/fdf/frames/FrameMenu.java | 5 - .../parsers/fdf/frames/FramePopupMenu.java | 5 - .../parsers/fdf/frames/FrameScrollbar.java | 5 - .../parsers/fdf/frames/FrameSlider.java | 5 - .../parsers/fdf/frames/FrameText.java | 5 - .../parsers/fdf/frames/FrameTextArea.java | 5 - .../parsers/fdf/frames/FrameTextButton.java | 19 --- .../parsers/fdf/frames/base/Frame.java | 39 ------ .../fdf/frames/simple/FrameSimpleButton.java | 5 - .../frames/simple/FrameSimpleCheckBox.java | 5 - .../fdf/frames/simple/FrameSimpleFrame.java | 5 - .../frames/simple/FrameSimpleStatusBar.java | 5 - 29 files changed, 104 insertions(+), 448 deletions(-) delete mode 100644 fdfparser/src/com/etheller/warsmash/fdfparser/FDFStatementVisitor.java create mode 100644 fdfparser/src/com/etheller/warsmash/fdfparser/FrameDefinitionFieldVisitor.java delete mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/FDFNamedString.java delete mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/FDFStatement.java delete mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/FDFStringListStatement.java delete mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FrameDefintionOld.java delete mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameBackdrop.java delete mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameButton.java delete mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameChatDisplay.java delete mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameCheckBox.java delete mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameDialog.java delete mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameEditBox.java delete mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameFrame.java delete mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameHighlight.java delete mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameListBox.java delete mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameMenu.java delete mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FramePopupMenu.java delete mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameScrollbar.java delete mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameSlider.java delete mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameText.java delete mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameTextArea.java delete mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameTextButton.java delete mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/base/Frame.java delete mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/simple/FrameSimpleButton.java delete mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/simple/FrameSimpleCheckBox.java delete mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/simple/FrameSimpleFrame.java delete mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/simple/FrameSimpleStatusBar.java diff --git a/fdfparser/antlr-src/FDF.g4 b/fdfparser/antlr-src/FDF.g4 index 8aa9c02..4203744 100644 --- a/fdfparser/antlr-src/FDF.g4 +++ b/fdfparser/antlr-src/FDF.g4 @@ -17,14 +17,20 @@ statement: INCLUDE_FILE STRING_LITERAL COMMA # IncludeStatement | frame # FrameStatement - ; + ; frame: - FRAME STRING_LITERAL STRING_LITERAL OPEN_CURLY frame_element* CLOSE_CURLY # SimpleFrameDefinition + frame_type_qualifier STRING_LITERAL OPEN_CURLY frame_element* CLOSE_CURLY # CompDefinition | - FRAME STRING_LITERAL STRING_LITERAL INHERITS STRING_LITERAL OPEN_CURLY frame_element* CLOSE_CURLY # FrameDefinition + frame_type_qualifier STRING_LITERAL INHERITS STRING_LITERAL OPEN_CURLY frame_element* CLOSE_CURLY # CompSubTypeDefinition | - FRAME STRING_LITERAL STRING_LITERAL INHERITS WITHCHILDREN STRING_LITERAL OPEN_CURLY frame_element* CLOSE_CURLY # FrameDefinitionWithChildren + frame_type_qualifier STRING_LITERAL INHERITS WITHCHILDREN STRING_LITERAL OPEN_CURLY frame_element* CLOSE_CURLY # CompSubTypeDefinitionWithChildren + | + FRAME STRING_LITERAL STRING_LITERAL OPEN_CURLY frame_element* CLOSE_CURLY # FrameDefinition + | + FRAME STRING_LITERAL STRING_LITERAL INHERITS STRING_LITERAL OPEN_CURLY frame_element* CLOSE_CURLY # FrameSubTypeDefinition + | + FRAME STRING_LITERAL STRING_LITERAL INHERITS WITHCHILDREN STRING_LITERAL OPEN_CURLY frame_element* CLOSE_CURLY # FrameSubTypeDefinitionWithChildren ; frame_element: @@ -60,6 +66,10 @@ frame_element: | DECORATE_FILE_NAMES COMMA # DecorateFileNamesElement | + SET_ALL_POINTS COMMA # SetAllPointsElement + | + USE_ACTIVE_CONTEXT COMMA # UseActiveContextElement + | BACKDROP_HALF_SIDES COMMA # BackdropHalfSidesElement | BACKDROP_BACKGROUND STRING_LITERAL COMMA # BackdropBackgroundElement @@ -165,6 +175,12 @@ frame_element: CHAT_DISPLAY_SCROLL_BAR STRING_LITERAL COMMA # ChatDisplayScrollBarElement | CHAT_DISPLAY_EDIT_BOX STRING_LITERAL COMMA # ChatDisplayEditBoxElement + | + TEXT STRING_LITERAL COMMA # TextElement + | + FILE STRING_LITERAL COMMA # FileElement + | + FONT STRING_LITERAL COMMA FLOAT COMMA # FontElement ; text_justify: @@ -186,6 +202,14 @@ color: | FLOAT FLOAT FLOAT FLOAT ; + +frame_type_qualifier: + STRING + | + TEXTURE + | + LAYER + ; OPEN_CURLY : '{'; @@ -197,12 +221,22 @@ INCLUDE_FILE : 'IncludeFile' ; FRAME : 'Frame' ; +STRING : 'String' ; + +TEXTURE : 'Texture' ; + +LAYER : 'Layer' ; + INHERITS : 'INHERITS' ; WITHCHILDREN : 'WITHCHILDREN' ; DECORATE_FILE_NAMES : 'DecorateFileNames'; +SET_ALL_POINTS : 'SetAllPoints'; + +USE_ACTIVE_CONTEXT : 'UseActiveContext'; + HEIGHT : 'Height'; WIDTH : 'Width'; @@ -284,6 +318,10 @@ BUTTON_PUSHED_TEXT_OFFSET : 'ButtonPushedTextOffset'; TEXT : 'Text'; +FILE : 'File'; + +FONT : 'Font'; + SETPOINT : 'SetPoint'; JUSTIFYTOP : 'JUSTIFYTOP'; diff --git a/fdfparser/src/com/etheller/warsmash/fdfparser/FDFStatementVisitor.java b/fdfparser/src/com/etheller/warsmash/fdfparser/FDFStatementVisitor.java deleted file mode 100644 index 3b33223..0000000 --- a/fdfparser/src/com/etheller/warsmash/fdfparser/FDFStatementVisitor.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.etheller.warsmash.fdfparser; - -import java.util.ArrayList; -import java.util.List; - -import org.antlr.v4.runtime.tree.TerminalNode; - -import com.etheller.warsmash.fdfparser.FDFParser.IncludeStatementContext; -import com.etheller.warsmash.fdfparser.FDFParser.StringListStatementContext; -import com.etheller.warsmash.parsers.fdf.FDFNamedString; -import com.etheller.warsmash.parsers.fdf.FDFStatement; -import com.etheller.warsmash.parsers.fdf.FDFStringListStatement; - -public class FDFStatementVisitor extends FDFBaseVisitor { - - @Override - public FDFStatement visitStringListStatement(final StringListStatementContext ctx) { - final List namedStrings = new ArrayList<>(); - final List ids = ctx.ID(); - final List strings = ctx.STRING_LITERAL(); - for (int i = 0; i < ids.size(); i++) { - final String id = ids.get(i).getText(); - String value = strings.get(i).getText(); - value = value.substring(1, value.length() - 1); - namedStrings.add(new FDFNamedString(id, value)); - } - return new FDFStringListStatement(namedStrings); - } - - @Override - public FDFStatement visitIncludeStatement(final IncludeStatementContext ctx) { - String includeFilePath = ctx.STRING_LITERAL().getText(); - includeFilePath = includeFilePath.substring(1, includeFilePath.length() - 1); - return super.visitIncludeStatement(ctx); - } -} diff --git a/fdfparser/src/com/etheller/warsmash/fdfparser/FrameDefinitionFieldVisitor.java b/fdfparser/src/com/etheller/warsmash/fdfparser/FrameDefinitionFieldVisitor.java new file mode 100644 index 0000000..633819a --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/fdfparser/FrameDefinitionFieldVisitor.java @@ -0,0 +1,19 @@ +package com.etheller.warsmash.fdfparser; + +import com.etheller.warsmash.fdfparser.FDFParser.BackdropBackgroundElementContext; +import com.etheller.warsmash.parsers.fdf.datamodel.FrameDefintion; + +public class FrameDefinitionFieldVisitor extends FDFBaseVisitor { + private final FrameDefintion frameDefintion; + + public FrameDefinitionFieldVisitor(final FrameDefintion frameDefintion, + final FrameDefinitionVisitor frameDefinitionVisitor) { + this.frameDefintion = frameDefintion; + } + + @Override + public Void visitBackdropBackgroundElement(final BackdropBackgroundElementContext ctx) { + this.frameDefintion.set("BackdropBackground", value); + return null; + } +} diff --git a/fdfparser/src/com/etheller/warsmash/fdfparser/FrameDefinitionVisitor.java b/fdfparser/src/com/etheller/warsmash/fdfparser/FrameDefinitionVisitor.java index 5c5aa9e..097a407 100644 --- a/fdfparser/src/com/etheller/warsmash/fdfparser/FrameDefinitionVisitor.java +++ b/fdfparser/src/com/etheller/warsmash/fdfparser/FrameDefinitionVisitor.java @@ -4,11 +4,18 @@ import java.util.List; import org.antlr.v4.runtime.tree.TerminalNode; +import com.etheller.warsmash.fdfparser.FDFParser.CompDefinitionContext; +import com.etheller.warsmash.fdfparser.FDFParser.CompSubTypeDefinitionContext; +import com.etheller.warsmash.fdfparser.FDFParser.CompSubTypeDefinitionWithChildrenContext; +import com.etheller.warsmash.fdfparser.FDFParser.FrameDefinitionContext; +import com.etheller.warsmash.fdfparser.FDFParser.FrameSubTypeDefinitionContext; +import com.etheller.warsmash.fdfparser.FDFParser.FrameSubTypeDefinitionWithChildrenContext; import com.etheller.warsmash.fdfparser.FDFParser.IncludeStatementContext; import com.etheller.warsmash.fdfparser.FDFParser.StringListStatementContext; +import com.etheller.warsmash.parsers.fdf.datamodel.FrameDefintion; import com.etheller.warsmash.parsers.fdf.datamodel.FrameTemplateEnvironment; -public class FrameDefinitionVisitor extends FDFBaseVisitor { +public class FrameDefinitionVisitor extends FDFBaseVisitor { private final FrameTemplateEnvironment templates; private final FDFParserBuilder fdfParserBuilder; @@ -18,7 +25,7 @@ public class FrameDefinitionVisitor extends FDFBaseVisitor { } @Override - public Void visitStringListStatement(final StringListStatementContext ctx) { + public FrameDefintion visitStringListStatement(final StringListStatementContext ctx) { final List ids = ctx.ID(); final List strings = ctx.STRING_LITERAL(); for (int i = 0; i < ids.size(); i++) { @@ -31,11 +38,44 @@ public class FrameDefinitionVisitor extends FDFBaseVisitor { } @Override - public Void visitIncludeStatement(final IncludeStatementContext ctx) { + public FrameDefintion visitIncludeStatement(final IncludeStatementContext ctx) { String includeFilePath = ctx.STRING_LITERAL().getText(); includeFilePath = includeFilePath.substring(1, includeFilePath.length() - 1); final FDFParser parser = this.fdfParserBuilder.build(includeFilePath); visit(parser.program()); return null; } + + @Override + public FrameDefintion visitFrameDefinition(final FrameDefinitionContext ctx) { + return super.visitFrameDefinition(ctx); + } + + @Override + public FrameDefintion visitFrameSubTypeDefinition(final FrameSubTypeDefinitionContext ctx) { + return super.visitFrameSubTypeDefinition(ctx); + } + + @Override + public FrameDefintion visitFrameSubTypeDefinitionWithChildren(final FrameSubTypeDefinitionWithChildrenContext ctx) { + return super.visitFrameSubTypeDefinitionWithChildren(ctx); + } + + @Override + public FrameDefintion visitCompDefinition(final CompDefinitionContext ctx) { + // TODO Auto-generated method stub + return super.visitCompDefinition(ctx); + } + + @Override + public FrameDefintion visitCompSubTypeDefinition(final CompSubTypeDefinitionContext ctx) { + // TODO Auto-generated method stub + return super.visitCompSubTypeDefinition(ctx); + } + + @Override + public FrameDefintion visitCompSubTypeDefinitionWithChildren(final CompSubTypeDefinitionWithChildrenContext ctx) { + // TODO Auto-generated method stub + return super.visitCompSubTypeDefinitionWithChildren(ctx); + } } diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/FDFNamedString.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/FDFNamedString.java deleted file mode 100644 index 46a8e0c..0000000 --- a/fdfparser/src/com/etheller/warsmash/parsers/fdf/FDFNamedString.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.etheller.warsmash.parsers.fdf; - -public class FDFNamedString { - private final String name; - private final String value; - - public FDFNamedString(final String name, final String value) { - this.name = name; - this.value = value; - } - - public String getName() { - return this.name; - } - - public String getValue() { - return this.value; - } -} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/FDFStatement.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/FDFStatement.java deleted file mode 100644 index c039767..0000000 --- a/fdfparser/src/com/etheller/warsmash/parsers/fdf/FDFStatement.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.etheller.warsmash.parsers.fdf; - -import com.etheller.warsmash.parsers.fdf.datamodel.FrameTemplateEnvironment; - -public interface FDFStatement { - void loadTemplate(FrameTemplateEnvironment frameDef); -} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/FDFStringListStatement.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/FDFStringListStatement.java deleted file mode 100644 index 121ce7e..0000000 --- a/fdfparser/src/com/etheller/warsmash/parsers/fdf/FDFStringListStatement.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.etheller.warsmash.parsers.fdf; - -import java.util.List; - -import com.etheller.warsmash.parsers.fdf.datamodel.FrameTemplateEnvironment; - -public class FDFStringListStatement implements FDFStatement { - private final List namedStrings; - - public FDFStringListStatement(final List namedStrings) { - this.namedStrings = namedStrings; - } - - @Override - public void loadTemplate(final FrameTemplateEnvironment frameDef) { - for (final FDFNamedString string : this.namedStrings) { - frameDef.addDecoratedString(string.getName(), string.getValue()); - } - } -} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FrameDefintionOld.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FrameDefintionOld.java deleted file mode 100644 index 86c6cb0..0000000 --- a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FrameDefintionOld.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.etheller.warsmash.parsers.fdf.datamodel; - -import java.util.ArrayList; -import java.util.EnumSet; -import java.util.List; - -/** - * Pretty sure this is probably not how it works in-game but this silly - * everything class might help get a prototype running fast until I have a - * better understanding of how I want these classes designed. - */ -public class FrameDefintionOld { - private String frameType; - private String name; - // everything - private float width; - private float height; - private boolean decorateFileNames; - private boolean setAllPoints; - private final List innerFrames = new ArrayList<>(); - // "BACKDROP" - private boolean backdropTileBackground; - private boolean backdropHalfSides; - private boolean backdropBlendAll; - private String backdropBackground; - private EnumSet backdropCornerFlags; - private float backdropCornerSize; - private float backdropBackgroundSize; - private Insets backdropBackgroundInsets; - private String backdropCornerFile; - private String backdropLeftFile; - private String backdropRightFile; - private String backdropTopFile; - private String backdropBottomFile; - private String backdropEdgeFile; - // "HIGHLIGHT" - private HighlightType highlightType; - private String highlightAlphaFile; - private HighlightAlphaMode highlightAlphaMode; - // "TEXTBUTTON" or "GLUEBUTTON" - private EnumSet controlStyle; - private Offset buttonPushedTextOffset; - private String controlBackdrop; - private String controlPushedBackdrop; - private String controlDisabledBackdrop; - private String controlFocusHighlight; - private String controlMouseOverHighlight; - // "TEXT" - private FontDefinition frameFont; - private TextJustify fontJustificationH; - private TextJustify fontJustificationV; - private EnumSet fontFlags; - private ColorDefinition fontColor; - private ColorDefinition fontHighlightColor; - private ColorDefinition fontDisabledColor; - private ColorDefinition fontShadowColor; - private Offset fontShadowOffset; - // "SLIDER" - private boolean sliderLayoutHorizontal; - // "SCROLLBAR" - private boolean sliderLayoutVertical; - private String scrollBarIncButtonFrame; - private String scrollBarDecButtonFrame; - private String scrollBarThumbButtonFrame; - // "LISTBOX" - private float listBoxBorder; - // "EDITBOX" -} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameBackdrop.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameBackdrop.java deleted file mode 100644 index c4f9872..0000000 --- a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameBackdrop.java +++ /dev/null @@ -1,113 +0,0 @@ -package com.etheller.warsmash.parsers.fdf.frames; - -import java.util.EnumSet; - -import com.etheller.warsmash.parsers.fdf.datamodel.BackdropCornerFlags; -import com.etheller.warsmash.parsers.fdf.datamodel.Insets; -import com.etheller.warsmash.parsers.fdf.frames.base.Frame; - -public class FrameBackdrop extends Frame { - private boolean tileBackground; - private boolean halfSides; - private boolean blendAll; - private String background; - private EnumSet cornerFlags; - private float cornerSize; - private float backgroundSize; - private Insets backgroundInsets; - private String cornerFile; - private String leftFile; - private String rightFile; - private String topFile; - private String bottomFile; - private String edgeFile; - - public boolean isTileBackground() { - return this.tileBackground; - } - - public boolean isHalfSides() { - return this.halfSides; - } - - public boolean isBlendAll() { - return this.blendAll; - } - - public String getBackground() { - return this.background; - } - - public EnumSet getCornerFlags() { - return this.cornerFlags; - } - - public float getCornerSize() { - return this.cornerSize; - } - - public String getCornerFile() { - return this.cornerFile; - } - - public String getLeftFile() { - return this.leftFile; - } - - public String getRightFile() { - return this.rightFile; - } - - public String getTopFile() { - return this.topFile; - } - - public String getBottomFile() { - return this.bottomFile; - } - - public void setTileBackground(final boolean tileBackground) { - this.tileBackground = tileBackground; - } - - public void setHalfSides(final boolean halfSides) { - this.halfSides = halfSides; - } - - public void setBlendAll(final boolean blendAll) { - this.blendAll = blendAll; - } - - public void setBackground(final String background) { - this.background = background; - } - - public void setCornerFlags(final EnumSet cornerFlags) { - this.cornerFlags = cornerFlags; - } - - public void setCornerSize(final float cornerSize) { - this.cornerSize = cornerSize; - } - - public void setCornerFile(final String cornerFile) { - this.cornerFile = cornerFile; - } - - public void setLeftFile(final String leftFile) { - this.leftFile = leftFile; - } - - public void setRightFile(final String rightFile) { - this.rightFile = rightFile; - } - - public void setTopFile(final String topFile) { - this.topFile = topFile; - } - - public void setBottomFile(final String bottomFile) { - this.bottomFile = bottomFile; - } - -} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameButton.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameButton.java deleted file mode 100644 index 27ed71e..0000000 --- a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameButton.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.etheller.warsmash.parsers.fdf.frames; - -public class FrameButton { - -} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameChatDisplay.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameChatDisplay.java deleted file mode 100644 index 87fab0c..0000000 --- a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameChatDisplay.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.etheller.warsmash.parsers.fdf.frames; - -public class FrameChatDisplay { - -} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameCheckBox.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameCheckBox.java deleted file mode 100644 index b0cb3a9..0000000 --- a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameCheckBox.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.etheller.warsmash.parsers.fdf.frames; - -public class FrameCheckBox { - -} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameDialog.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameDialog.java deleted file mode 100644 index c464d54..0000000 --- a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameDialog.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.etheller.warsmash.parsers.fdf.frames; - -public class FrameDialog { - -} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameEditBox.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameEditBox.java deleted file mode 100644 index cd67fd7..0000000 --- a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameEditBox.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.etheller.warsmash.parsers.fdf.frames; - -public class FrameEditBox { - -} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameFrame.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameFrame.java deleted file mode 100644 index ba09ed7..0000000 --- a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameFrame.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.etheller.warsmash.parsers.fdf.frames; - -public class FrameFrame { - -} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameHighlight.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameHighlight.java deleted file mode 100644 index 37a4e47..0000000 --- a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameHighlight.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.etheller.warsmash.parsers.fdf.frames; - -import com.etheller.warsmash.parsers.fdf.datamodel.HighlightAlphaMode; -import com.etheller.warsmash.parsers.fdf.datamodel.HighlightType; -import com.etheller.warsmash.parsers.fdf.frames.base.Frame; - -public class FrameHighlight extends Frame { - private HighlightType type; - private String alphaFile; - private HighlightAlphaMode alphaMode; - - public HighlightType getType() { - return this.type; - } - - public String getAlphaFile() { - return this.alphaFile; - } - - public HighlightAlphaMode getAlphaMode() { - return this.alphaMode; - } - - public void setType(final HighlightType type) { - this.type = type; - } - - public void setAlphaFile(final String alphaFile) { - this.alphaFile = alphaFile; - } - - public void setAlphaMode(final HighlightAlphaMode alphaMode) { - this.alphaMode = alphaMode; - } -} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameListBox.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameListBox.java deleted file mode 100644 index e298393..0000000 --- a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameListBox.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.etheller.warsmash.parsers.fdf.frames; - -public class FrameListBox { - -} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameMenu.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameMenu.java deleted file mode 100644 index 3540d5e..0000000 --- a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameMenu.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.etheller.warsmash.parsers.fdf.frames; - -public class FrameMenu { - -} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FramePopupMenu.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FramePopupMenu.java deleted file mode 100644 index db29d10..0000000 --- a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FramePopupMenu.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.etheller.warsmash.parsers.fdf.frames; - -public class FramePopupMenu { - -} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameScrollbar.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameScrollbar.java deleted file mode 100644 index 8652a93..0000000 --- a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameScrollbar.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.etheller.warsmash.parsers.fdf.frames; - -public class FrameScrollbar { - -} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameSlider.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameSlider.java deleted file mode 100644 index 140276d..0000000 --- a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameSlider.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.etheller.warsmash.parsers.fdf.frames; - -public class FrameSlider { - -} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameText.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameText.java deleted file mode 100644 index a55e0d1..0000000 --- a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameText.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.etheller.warsmash.parsers.fdf.frames; - -public class FrameText { - -} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameTextArea.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameTextArea.java deleted file mode 100644 index 9e561ca..0000000 --- a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameTextArea.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.etheller.warsmash.parsers.fdf.frames; - -public class FrameTextArea { - -} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameTextButton.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameTextButton.java deleted file mode 100644 index 7ce0acc..0000000 --- a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/FrameTextButton.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.etheller.warsmash.parsers.fdf.frames; - -import java.util.EnumSet; - -import com.etheller.warsmash.parsers.fdf.datamodel.ControlStyle; -import com.etheller.warsmash.parsers.fdf.datamodel.Offset; -import com.etheller.warsmash.parsers.fdf.frames.base.Frame; - -public class FrameTextButton extends Frame { - private EnumSet controlStyle; - private Offset buttonPushedTextOffset; - private String controlBackdrop; - - private String controlPushedBackdrop; - private String controlDisabledBackdrop; - private String controlFocusHighlight; - private String controlMouseOverHighlight; - -} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/base/Frame.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/base/Frame.java deleted file mode 100644 index 46dd834..0000000 --- a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/base/Frame.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.etheller.warsmash.parsers.fdf.frames.base; - -import java.util.ArrayList; -import java.util.List; - -public class Frame { - private String name; - private float width; - private float height; - private final List innerFrames = new ArrayList<>(); - - public String getName() { - return this.name; - } - - public void setName(final String name) { - this.name = name; - } - - public float getWidth() { - return this.width; - } - - public float getHeight() { - return this.height; - } - - public void setWidth(final float width) { - this.width = width; - } - - public void setHeight(final float height) { - this.height = height; - } - - public List getInnerFrames() { - return this.innerFrames; - } -} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/simple/FrameSimpleButton.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/simple/FrameSimpleButton.java deleted file mode 100644 index 440322b..0000000 --- a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/simple/FrameSimpleButton.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.etheller.warsmash.parsers.fdf.frames.simple; - -public class FrameSimpleButton { - -} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/simple/FrameSimpleCheckBox.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/simple/FrameSimpleCheckBox.java deleted file mode 100644 index 185da3b..0000000 --- a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/simple/FrameSimpleCheckBox.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.etheller.warsmash.parsers.fdf.frames.simple; - -public class FrameSimpleCheckBox { - -} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/simple/FrameSimpleFrame.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/simple/FrameSimpleFrame.java deleted file mode 100644 index be38e67..0000000 --- a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/simple/FrameSimpleFrame.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.etheller.warsmash.parsers.fdf.frames.simple; - -public class FrameSimpleFrame { - -} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/simple/FrameSimpleStatusBar.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/simple/FrameSimpleStatusBar.java deleted file mode 100644 index 0aaffd8..0000000 --- a/fdfparser/src/com/etheller/warsmash/parsers/fdf/frames/simple/FrameSimpleStatusBar.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.etheller.warsmash.parsers.fdf.frames.simple; - -public class FrameSimpleStatusBar { - -} From 2ca02df71b1e6c856c551c0ca31a2638e15479d8 Mon Sep 17 00:00:00 2001 From: Retera Date: Wed, 20 May 2020 08:21:29 -0400 Subject: [PATCH 032/116] Key value pair parser for FDF, new design --- .../fdf/DataSourceFDFParserBuilder.java | 48 +++++ .../etheller/warsmash/parsers/fdf/Main.java | 55 +++++ .../FrameDefinitionFieldVisitor.java | 151 +++++++++++++- .../fdfparser/FrameDefinitionVisitor.java | 189 +++++++++++++++--- .../com/etheller/warsmash/fdfparser/Main.java | 4 +- .../fdf/datamodel/ColorDefinition.java | 28 --- .../parsers/fdf/datamodel/FrameClass.java | 8 + ...ameDefintion.java => FrameDefinition.java} | 33 ++- .../datamodel/FrameTemplateEnvironment.java | 9 + .../parsers/fdf/datamodel/Insets.java | 41 ---- .../{Offset.java => Vector2Definition.java} | 7 +- .../fdf/datamodel/Vector3Definition.java | 23 +++ .../fdf/datamodel/Vector4Definition.java | 28 +++ .../fields/ColorFrameDefinitionField.java | 21 -- .../fields/FloatFrameDefinitionField.java | 1 + .../fields/FrameDefinitionFieldVisitor.java | 10 +- .../fields/InsetsFrameDefinitionField.java | 20 -- .../fields/OffsetFrameDefinitionField.java | 20 -- .../StringPairFrameDefinitionField.java | 24 +++ .../TextJustifyFrameDefinitionField.java | 21 ++ .../fields/Vector2FrameDefinitionField.java | 20 ++ .../fields/Vector3FrameDefinitionField.java | 21 ++ .../fields/Vector4FrameDefinitionField.java | 20 ++ 23 files changed, 632 insertions(+), 170 deletions(-) create mode 100644 core/src/com/etheller/warsmash/parsers/fdf/DataSourceFDFParserBuilder.java create mode 100644 core/src/com/etheller/warsmash/parsers/fdf/Main.java delete mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/ColorDefinition.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FrameClass.java rename fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/{FrameDefintion.java => FrameDefinition.java} (56%) delete mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/Insets.java rename fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/{Offset.java => Vector2Definition.java} (70%) create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/Vector3Definition.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/Vector4Definition.java delete mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/ColorFrameDefinitionField.java delete mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/InsetsFrameDefinitionField.java delete mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/OffsetFrameDefinitionField.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/StringPairFrameDefinitionField.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/TextJustifyFrameDefinitionField.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/Vector2FrameDefinitionField.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/Vector3FrameDefinitionField.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/Vector4FrameDefinitionField.java diff --git a/core/src/com/etheller/warsmash/parsers/fdf/DataSourceFDFParserBuilder.java b/core/src/com/etheller/warsmash/parsers/fdf/DataSourceFDFParserBuilder.java new file mode 100644 index 0000000..a2bec5b --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/fdf/DataSourceFDFParserBuilder.java @@ -0,0 +1,48 @@ +package com.etheller.warsmash.parsers.fdf; + +import java.io.IOException; + +import org.antlr.v4.runtime.BaseErrorListener; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.RecognitionException; +import org.antlr.v4.runtime.Recognizer; + +import com.etheller.warsmash.datasources.DataSource; +import com.etheller.warsmash.fdfparser.FDFLexer; +import com.etheller.warsmash.fdfparser.FDFParser; +import com.etheller.warsmash.fdfparser.FDFParserBuilder; + +public class DataSourceFDFParserBuilder implements FDFParserBuilder { + private final DataSource dataSource; + + public DataSourceFDFParserBuilder(final DataSource dataSource) { + this.dataSource = dataSource; + } + + @Override + public FDFParser build(final String path) { + FDFLexer lexer; + try { + lexer = new FDFLexer(CharStreams.fromStream(this.dataSource.getResourceAsStream(path))); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + final FDFParser fdfParser = new FDFParser(new CommonTokenStream(lexer)); + final BaseErrorListener errorListener = new BaseErrorListener() { + @Override + public void syntaxError(final Recognizer recognizer, final Object offendingSymbol, final int line, + final int charPositionInLine, final String msg, final RecognitionException e) { + String sourceName = path; + if (!sourceName.isEmpty()) { + sourceName = String.format("%s:%d:%d: ", sourceName, line, charPositionInLine); + } + + System.err.println(sourceName + "line " + line + ":" + charPositionInLine + " " + msg); + } + }; + fdfParser.addErrorListener(errorListener); + return fdfParser; + } +} \ No newline at end of file diff --git a/core/src/com/etheller/warsmash/parsers/fdf/Main.java b/core/src/com/etheller/warsmash/parsers/fdf/Main.java new file mode 100644 index 0000000..02a6fc1 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/fdf/Main.java @@ -0,0 +1,55 @@ +package com.etheller.warsmash.parsers.fdf; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.util.Arrays; + +import com.etheller.warsmash.datasources.CompoundDataSourceDescriptor; +import com.etheller.warsmash.datasources.DataSource; +import com.etheller.warsmash.datasources.DataSourceDescriptor; +import com.etheller.warsmash.datasources.FolderDataSourceDescriptor; +import com.etheller.warsmash.fdfparser.FDFParser; +import com.etheller.warsmash.fdfparser.FrameDefinitionVisitor; +import com.etheller.warsmash.parsers.fdf.datamodel.FrameDefinition; +import com.etheller.warsmash.parsers.fdf.datamodel.FrameTemplateEnvironment; + +public class Main { + public static final boolean REPORT_SYNTAX_ERRORS = true; + + public static void main(final String[] args) { + if (args.length < 1) { + System.err.println("Usage: "); + return; + } + try { + + final FolderDataSourceDescriptor war3mpq = new FolderDataSourceDescriptor( + "E:\\Backups\\Warcraft\\Data\\127"); + final FolderDataSourceDescriptor testingFolder = new FolderDataSourceDescriptor( + "E:\\Backups\\Warsmash\\Data"); + final FolderDataSourceDescriptor currentFolder = new FolderDataSourceDescriptor("."); + final DataSource dataSource = new CompoundDataSourceDescriptor( + Arrays.asList(war3mpq, testingFolder, currentFolder)).createDataSource(); + + final FrameTemplateEnvironment templates = new FrameTemplateEnvironment(); + final DataSourceFDFParserBuilder dataSourceFDFParserBuilder = new DataSourceFDFParserBuilder(dataSource); + final FrameDefinitionVisitor fdfVisitor = new FrameDefinitionVisitor(templates, dataSourceFDFParserBuilder); + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(dataSource.getResourceAsStream("UI\\FrameDef\\FrameDef.toc")))) { + String line; + while ((line = reader.readLine()) != null) { + final FDFParser firstFileParser = dataSourceFDFParserBuilder.build(line); + fdfVisitor.visit(firstFileParser.program()); + } + } + + final FrameDefinition bnetChat = templates.getFrame("ConsoleUI"); + System.out.println("Value of ConsoleUI: " + bnetChat); + } + catch (final Exception exc) { + exc.printStackTrace(); + System.err.println(exc.getMessage()); + } + } + +} diff --git a/fdfparser/src/com/etheller/warsmash/fdfparser/FrameDefinitionFieldVisitor.java b/fdfparser/src/com/etheller/warsmash/fdfparser/FrameDefinitionFieldVisitor.java index 633819a..e366a1d 100644 --- a/fdfparser/src/com/etheller/warsmash/fdfparser/FrameDefinitionFieldVisitor.java +++ b/fdfparser/src/com/etheller/warsmash/fdfparser/FrameDefinitionFieldVisitor.java @@ -1,19 +1,156 @@ package com.etheller.warsmash.fdfparser; -import com.etheller.warsmash.fdfparser.FDFParser.BackdropBackgroundElementContext; -import com.etheller.warsmash.parsers.fdf.datamodel.FrameDefintion; +import com.etheller.warsmash.fdfparser.FDFParser.AnchorElementContext; +import com.etheller.warsmash.fdfparser.FDFParser.FlagElementContext; +import com.etheller.warsmash.fdfparser.FDFParser.FloatElementContext; +import com.etheller.warsmash.fdfparser.FDFParser.FontElementContext; +import com.etheller.warsmash.fdfparser.FDFParser.FrameFrameElementContext; +import com.etheller.warsmash.fdfparser.FDFParser.SetPointElementContext; +import com.etheller.warsmash.fdfparser.FDFParser.SimpleFontElementContext; +import com.etheller.warsmash.fdfparser.FDFParser.StringElementContext; +import com.etheller.warsmash.fdfparser.FDFParser.StringPairElementContext; +import com.etheller.warsmash.fdfparser.FDFParser.TextJustifyElementContext; +import com.etheller.warsmash.fdfparser.FDFParser.Vector2ElementContext; +import com.etheller.warsmash.fdfparser.FDFParser.Vector3ElementContext; +import com.etheller.warsmash.fdfparser.FDFParser.Vector4CommaElementContext; +import com.etheller.warsmash.fdfparser.FDFParser.Vector4ElementContext; +import com.etheller.warsmash.parsers.fdf.datamodel.AnchorDefinition; +import com.etheller.warsmash.parsers.fdf.datamodel.FontDefinition; +import com.etheller.warsmash.parsers.fdf.datamodel.FrameDefinition; +import com.etheller.warsmash.parsers.fdf.datamodel.FramePoint; +import com.etheller.warsmash.parsers.fdf.datamodel.SetPointDefinition; +import com.etheller.warsmash.parsers.fdf.datamodel.TextJustify; +import com.etheller.warsmash.parsers.fdf.datamodel.Vector2Definition; +import com.etheller.warsmash.parsers.fdf.datamodel.Vector4Definition; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.FloatFrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.FontFrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.StringFrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.StringPairFrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.TextJustifyFrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.Vector2FrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.Vector4FrameDefinitionField; public class FrameDefinitionFieldVisitor extends FDFBaseVisitor { - private final FrameDefintion frameDefintion; + private static final int JUSTIFY_OFFSET = "JUSTIFY".length(); + private final FrameDefinition frameDefinition; + private final FrameDefinitionVisitor frameDefinitionVisitor; - public FrameDefinitionFieldVisitor(final FrameDefintion frameDefintion, + public FrameDefinitionFieldVisitor(final FrameDefinition frameDefinition, final FrameDefinitionVisitor frameDefinitionVisitor) { - this.frameDefintion = frameDefintion; + this.frameDefinition = frameDefinition; + this.frameDefinitionVisitor = frameDefinitionVisitor; } @Override - public Void visitBackdropBackgroundElement(final BackdropBackgroundElementContext ctx) { - this.frameDefintion.set("BackdropBackground", value); + public Void visitStringElement(final StringElementContext ctx) { + String text = ctx.STRING_LITERAL().getText(); + text = text.substring(1, text.length() - 1); + this.frameDefinition.set(ctx.ID().getText(), new StringFrameDefinitionField(text)); + return null; + } + + @Override + public Void visitStringPairElement(final StringPairElementContext ctx) { + String first = ctx.STRING_LITERAL(0).getText(); + first = first.substring(1, first.length() - 1); + String second = ctx.STRING_LITERAL(1).getText(); + second = second.substring(1, second.length() - 1); + this.frameDefinition.set(ctx.ID().getText(), new StringPairFrameDefinitionField(first, second)); + return super.visitStringPairElement(ctx); + } + + @Override + public Void visitFloatElement(final FloatElementContext ctx) { + this.frameDefinition.set(ctx.ID().getText(), + new FloatFrameDefinitionField(Float.parseFloat(ctx.FLOAT().getText()))); + return null; + } + + @Override + public Void visitFlagElement(final FlagElementContext ctx) { + this.frameDefinition.add(ctx.ID().getText()); + return null; + } + + @Override + public Void visitVector2Element(final Vector2ElementContext ctx) { + this.frameDefinition.set(ctx.ID().getText(), + new Vector2FrameDefinitionField(new Vector2Definition(Float.parseFloat(ctx.FLOAT(0).getText()), + Float.parseFloat(ctx.FLOAT(1).getText())))); + return null; + } + + @Override + public Void visitVector3Element(final Vector3ElementContext ctx) { + this.frameDefinition.set(ctx.ID().getText(), + new Vector4FrameDefinitionField(new Vector4Definition(Float.parseFloat(ctx.FLOAT(0).getText()), + Float.parseFloat(ctx.FLOAT(1).getText()), Float.parseFloat(ctx.FLOAT(2).getText()), 1.0f))); + return null; + } + + @Override + public Void visitVector4Element(final Vector4ElementContext ctx) { + this.frameDefinition.set(ctx.ID().getText(), + new Vector4FrameDefinitionField(new Vector4Definition(Float.parseFloat(ctx.FLOAT(0).getText()), + Float.parseFloat(ctx.FLOAT(1).getText()), Float.parseFloat(ctx.FLOAT(2).getText()), + Float.parseFloat(ctx.FLOAT(3).getText())))); + return null; + } + + @Override + public Void visitVector4CommaElement(final Vector4CommaElementContext ctx) { + this.frameDefinition.set(ctx.ID().getText(), + new Vector4FrameDefinitionField(new Vector4Definition(Float.parseFloat(ctx.FLOAT(0).getText()), + Float.parseFloat(ctx.FLOAT(1).getText()), Float.parseFloat(ctx.FLOAT(2).getText()), + Float.parseFloat(ctx.FLOAT(3).getText())))); + return null; + } + + @Override + public Void visitSetPointElement(final SetPointElementContext ctx) { + String other = ctx.STRING_LITERAL().getText(); + other = other.substring(1, other.length() - 1); + final SetPointDefinition setPointDefinition = new SetPointDefinition( + FramePoint.valueOf(ctx.frame_point(0).getText()), other, + FramePoint.valueOf(ctx.frame_point(1).getText()), Float.parseFloat(ctx.FLOAT(0).getText()), + Float.parseFloat(ctx.FLOAT(1).getText())); + this.frameDefinition.add(setPointDefinition); + return null; + } + + @Override + public Void visitAnchorElement(final AnchorElementContext ctx) { + final AnchorDefinition anchorDefinition = new AnchorDefinition(FramePoint.valueOf(ctx.frame_point().getText()), + Float.parseFloat(ctx.FLOAT(0).getText()), Float.parseFloat(ctx.FLOAT(1).getText())); + this.frameDefinition.add(anchorDefinition); + return null; + } + + @Override + public Void visitTextJustifyElement(final TextJustifyElementContext ctx) { + final TextJustify justify = TextJustify.valueOf(ctx.text_justify().getText().substring(JUSTIFY_OFFSET)); + this.frameDefinition.set(ctx.ID().getText(), new TextJustifyFrameDefinitionField(justify)); + return null; + } + + @Override + public Void visitFontElement(final FontElementContext ctx) { + this.frameDefinition.set(ctx.ID().getText(), + new FontFrameDefinitionField(new FontDefinition(ctx.STRING_LITERAL(0).getText(), + Float.parseFloat(ctx.FLOAT().getText()), ctx.STRING_LITERAL(1).getText()))); + return null; + } + + @Override + public Void visitSimpleFontElement(final SimpleFontElementContext ctx) { + this.frameDefinition.set(ctx.ID().getText(), new FontFrameDefinitionField( + new FontDefinition(ctx.STRING_LITERAL().getText(), Float.parseFloat(ctx.FLOAT().getText()), null))); + return null; + } + + @Override + public Void visitFrameFrameElement(final FrameFrameElementContext ctx) { + this.frameDefinition.add(this.frameDefinitionVisitor.visit(ctx)); return null; } } diff --git a/fdfparser/src/com/etheller/warsmash/fdfparser/FrameDefinitionVisitor.java b/fdfparser/src/com/etheller/warsmash/fdfparser/FrameDefinitionVisitor.java index 097a407..f9f0f45 100644 --- a/fdfparser/src/com/etheller/warsmash/fdfparser/FrameDefinitionVisitor.java +++ b/fdfparser/src/com/etheller/warsmash/fdfparser/FrameDefinitionVisitor.java @@ -4,18 +4,23 @@ import java.util.List; import org.antlr.v4.runtime.tree.TerminalNode; +import com.etheller.warsmash.fdfparser.FDFParser.AnonymousCompDefinitionContext; +import com.etheller.warsmash.fdfparser.FDFParser.AnonymousCompSubTypeDefinitionContext; +import com.etheller.warsmash.fdfparser.FDFParser.AnonymousCompSubTypeDefinitionWithChildrenContext; import com.etheller.warsmash.fdfparser.FDFParser.CompDefinitionContext; import com.etheller.warsmash.fdfparser.FDFParser.CompSubTypeDefinitionContext; import com.etheller.warsmash.fdfparser.FDFParser.CompSubTypeDefinitionWithChildrenContext; import com.etheller.warsmash.fdfparser.FDFParser.FrameDefinitionContext; import com.etheller.warsmash.fdfparser.FDFParser.FrameSubTypeDefinitionContext; import com.etheller.warsmash.fdfparser.FDFParser.FrameSubTypeDefinitionWithChildrenContext; +import com.etheller.warsmash.fdfparser.FDFParser.Frame_elementContext; import com.etheller.warsmash.fdfparser.FDFParser.IncludeStatementContext; import com.etheller.warsmash.fdfparser.FDFParser.StringListStatementContext; -import com.etheller.warsmash.parsers.fdf.datamodel.FrameDefintion; +import com.etheller.warsmash.parsers.fdf.datamodel.FrameClass; +import com.etheller.warsmash.parsers.fdf.datamodel.FrameDefinition; import com.etheller.warsmash.parsers.fdf.datamodel.FrameTemplateEnvironment; -public class FrameDefinitionVisitor extends FDFBaseVisitor { +public class FrameDefinitionVisitor extends FDFBaseVisitor { private final FrameTemplateEnvironment templates; private final FDFParserBuilder fdfParserBuilder; @@ -25,57 +30,195 @@ public class FrameDefinitionVisitor extends FDFBaseVisitor { } @Override - public FrameDefintion visitStringListStatement(final StringListStatementContext ctx) { + public FrameDefinition visitStringListStatement(final StringListStatementContext ctx) { final List ids = ctx.ID(); final List strings = ctx.STRING_LITERAL(); for (int i = 0; i < ids.size(); i++) { final String id = ids.get(i).getText(); String value = strings.get(i).getText(); - value = value.substring(1, value.length() - 1); + value = unquote(value); this.templates.addDecoratedString(id, value); } return null; } @Override - public FrameDefintion visitIncludeStatement(final IncludeStatementContext ctx) { - String includeFilePath = ctx.STRING_LITERAL().getText(); - includeFilePath = includeFilePath.substring(1, includeFilePath.length() - 1); + public FrameDefinition visitIncludeStatement(final IncludeStatementContext ctx) { + final String includeFilePath = unquote(ctx.STRING_LITERAL().getText()); final FDFParser parser = this.fdfParserBuilder.build(includeFilePath); visit(parser.program()); return null; } - @Override - public FrameDefintion visitFrameDefinition(final FrameDefinitionContext ctx) { - return super.visitFrameDefinition(ctx); + private String unquote(String includeFilePath) { + includeFilePath = includeFilePath.substring(1, includeFilePath.length() - 1); + return includeFilePath; } @Override - public FrameDefintion visitFrameSubTypeDefinition(final FrameSubTypeDefinitionContext ctx) { - return super.visitFrameSubTypeDefinition(ctx); + public FrameDefinition visitFrameDefinition(final FrameDefinitionContext ctx) { + final String type = unquote(ctx.STRING_LITERAL(0).getText()); + final String name = unquote(ctx.STRING_LITERAL(1).getText()); + final FrameDefinition frameDefinition = new FrameDefinition(FrameClass.Frame, type, name); + final FrameDefinitionFieldVisitor fieldVisitor = new FrameDefinitionFieldVisitor(frameDefinition, this); + for (final Frame_elementContext element : ctx.frame_element()) { + fieldVisitor.visit(element); + } + this.templates.put(name, frameDefinition); + return frameDefinition; } @Override - public FrameDefintion visitFrameSubTypeDefinitionWithChildren(final FrameSubTypeDefinitionWithChildrenContext ctx) { - return super.visitFrameSubTypeDefinitionWithChildren(ctx); + public FrameDefinition visitFrameSubTypeDefinition(final FrameSubTypeDefinitionContext ctx) { + final String type = unquote(ctx.STRING_LITERAL(0).getText()); + final String name = unquote(ctx.STRING_LITERAL(1).getText()); + final String parent = unquote(ctx.STRING_LITERAL(2).getText()); + final FrameDefinition frameDefinition = new FrameDefinition(FrameClass.Frame, type, name); + // INHERITS + final FrameDefinition inheritParent = this.templates.getFrame(parent); + if (inheritParent == null) { + throw new IllegalStateException( + "\"" + name + "\" cannot inherit from \"" + parent + "\" because it does not exist!"); + } + frameDefinition.inheritFrom(inheritParent, false); + + final FrameDefinitionFieldVisitor fieldVisitor = new FrameDefinitionFieldVisitor(frameDefinition, this); + for (final Frame_elementContext element : ctx.frame_element()) { + fieldVisitor.visit(element); + } + this.templates.put(name, frameDefinition); + return frameDefinition; } @Override - public FrameDefintion visitCompDefinition(final CompDefinitionContext ctx) { - // TODO Auto-generated method stub - return super.visitCompDefinition(ctx); + public FrameDefinition visitFrameSubTypeDefinitionWithChildren( + final FrameSubTypeDefinitionWithChildrenContext ctx) { + final String type = unquote(ctx.STRING_LITERAL(0).getText()); + final String name = unquote(ctx.STRING_LITERAL(1).getText()); + final String parent = unquote(ctx.STRING_LITERAL(2).getText()); + final FrameDefinition frameDefinition = new FrameDefinition(FrameClass.Frame, type, name); + // INHERITS + final FrameDefinition inheritParent = this.templates.getFrame(parent); + if (inheritParent == null) { + throw new IllegalStateException( + "\"" + name + "\" cannot inherit from \"" + parent + "\" because it does not exist!"); + } + frameDefinition.inheritFrom(inheritParent, true); + + final FrameDefinitionFieldVisitor fieldVisitor = new FrameDefinitionFieldVisitor(frameDefinition, this); + for (final Frame_elementContext element : ctx.frame_element()) { + fieldVisitor.visit(element); + } + this.templates.put(name, frameDefinition); + return frameDefinition; } @Override - public FrameDefintion visitCompSubTypeDefinition(final CompSubTypeDefinitionContext ctx) { - // TODO Auto-generated method stub - return super.visitCompSubTypeDefinition(ctx); + public FrameDefinition visitAnonymousCompDefinition(final AnonymousCompDefinitionContext ctx) { + final FrameDefinition frameDefinition = new FrameDefinition( + FrameClass.valueOf(ctx.frame_type_qualifier().getText()), null, null); + final FrameDefinitionFieldVisitor fieldVisitor = new FrameDefinitionFieldVisitor(frameDefinition, this); + for (final Frame_elementContext element : ctx.frame_element()) { + fieldVisitor.visit(element); + } + return frameDefinition; } @Override - public FrameDefintion visitCompSubTypeDefinitionWithChildren(final CompSubTypeDefinitionWithChildrenContext ctx) { - // TODO Auto-generated method stub - return super.visitCompSubTypeDefinitionWithChildren(ctx); + public FrameDefinition visitAnonymousCompSubTypeDefinition(final AnonymousCompSubTypeDefinitionContext ctx) { + final String parent = unquote(ctx.STRING_LITERAL().getText()); + final FrameClass frameClass = FrameClass.valueOf(ctx.frame_type_qualifier().getText()); + final FrameDefinition frameDefinition = new FrameDefinition(frameClass, null, null); + // INHERITS + final FrameDefinition inheritParent = this.templates.getFrame(parent); + if (inheritParent == null) { + throw new IllegalStateException( + "" + frameClass + " cannot inherit from \"" + parent + "\" because it does not exist!"); + } + frameDefinition.inheritFrom(inheritParent, false); + + final FrameDefinitionFieldVisitor fieldVisitor = new FrameDefinitionFieldVisitor(frameDefinition, this); + for (final Frame_elementContext element : ctx.frame_element()) { + fieldVisitor.visit(element); + } + return frameDefinition; + } + + @Override + public FrameDefinition visitAnonymousCompSubTypeDefinitionWithChildren( + final AnonymousCompSubTypeDefinitionWithChildrenContext ctx) { + final String parent = unquote(ctx.STRING_LITERAL().getText()); + final FrameClass frameClass = FrameClass.valueOf(ctx.frame_type_qualifier().getText()); + final FrameDefinition frameDefinition = new FrameDefinition(frameClass, null, null); + // INHERITS + final FrameDefinition inheritParent = this.templates.getFrame(parent); + if (inheritParent == null) { + throw new IllegalStateException( + "" + frameClass + " cannot inherit from \"" + parent + "\" because it does not exist!"); + } + frameDefinition.inheritFrom(inheritParent, true); + + final FrameDefinitionFieldVisitor fieldVisitor = new FrameDefinitionFieldVisitor(frameDefinition, this); + for (final Frame_elementContext element : ctx.frame_element()) { + fieldVisitor.visit(element); + } + return frameDefinition; + } + + @Override + public FrameDefinition visitCompDefinition(final CompDefinitionContext ctx) { + final String name = unquote(ctx.STRING_LITERAL().getText()); + final FrameDefinition frameDefinition = new FrameDefinition( + FrameClass.valueOf(ctx.frame_type_qualifier().getText()), null, name); + final FrameDefinitionFieldVisitor fieldVisitor = new FrameDefinitionFieldVisitor(frameDefinition, this); + for (final Frame_elementContext element : ctx.frame_element()) { + fieldVisitor.visit(element); + } + this.templates.put(name, frameDefinition); + return frameDefinition; + } + + @Override + public FrameDefinition visitCompSubTypeDefinition(final CompSubTypeDefinitionContext ctx) { + final String name = unquote(ctx.STRING_LITERAL(0).getText()); + final String parent = unquote(ctx.STRING_LITERAL(1).getText()); + final FrameDefinition frameDefinition = new FrameDefinition( + FrameClass.valueOf(ctx.frame_type_qualifier().getText()), null, name); + // INHERITS + final FrameDefinition inheritParent = this.templates.getFrame(parent); + if (inheritParent == null) { + throw new IllegalStateException( + "\"" + name + "\" cannot inherit from \"" + parent + "\" because it does not exist!"); + } + frameDefinition.inheritFrom(inheritParent, false); + + final FrameDefinitionFieldVisitor fieldVisitor = new FrameDefinitionFieldVisitor(frameDefinition, this); + for (final Frame_elementContext element : ctx.frame_element()) { + fieldVisitor.visit(element); + } + this.templates.put(name, frameDefinition); + return frameDefinition; + } + + @Override + public FrameDefinition visitCompSubTypeDefinitionWithChildren(final CompSubTypeDefinitionWithChildrenContext ctx) { + final String name = unquote(ctx.STRING_LITERAL(0).getText()); + final String parent = unquote(ctx.STRING_LITERAL(1).getText()); + final FrameDefinition frameDefinition = new FrameDefinition( + FrameClass.valueOf(ctx.frame_type_qualifier().getText()), null, name); + // INHERITS + final FrameDefinition inheritParent = this.templates.getFrame(parent); + if (inheritParent == null) { + throw new IllegalStateException( + "\"" + name + "\" cannot inherit from \"" + parent + "\" because it does not exist!"); + } + frameDefinition.inheritFrom(inheritParent, true); + + final FrameDefinitionFieldVisitor fieldVisitor = new FrameDefinitionFieldVisitor(frameDefinition, this); + for (final Frame_elementContext element : ctx.frame_element()) { + fieldVisitor.visit(element); + } + this.templates.put(name, frameDefinition); + return frameDefinition; } } diff --git a/fdfparser/src/com/etheller/warsmash/fdfparser/Main.java b/fdfparser/src/com/etheller/warsmash/fdfparser/Main.java index 3769298..c2c4f81 100644 --- a/fdfparser/src/com/etheller/warsmash/fdfparser/Main.java +++ b/fdfparser/src/com/etheller/warsmash/fdfparser/Main.java @@ -4,6 +4,7 @@ import org.antlr.v4.runtime.BaseErrorListener; import org.antlr.v4.runtime.RecognitionException; import org.antlr.v4.runtime.Recognizer; +import com.etheller.warsmash.parsers.fdf.datamodel.FrameDefinition; import com.etheller.warsmash.parsers.fdf.datamodel.FrameTemplateEnvironment; public class Main { @@ -36,7 +37,8 @@ public class Main { final FrameDefinitionVisitor fdfVisitor = new FrameDefinitionVisitor(templates, testFDFParserBuilder); final FDFParser firstFileParser = testFDFParserBuilder.build(args[0]); fdfVisitor.visit(firstFileParser.program()); - System.out.println("Value of MONTH_12: " + templates.getDecoratedString("MONTH_12")); + final FrameDefinition bnetChat = templates.getFrame("BattleNetTextAreaTemplate"); + System.out.println("Value of BattleNetTextAreaTemplate: " + bnetChat); } catch (final Exception exc) { System.err.println(exc.getMessage()); diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/ColorDefinition.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/ColorDefinition.java deleted file mode 100644 index eea2153..0000000 --- a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/ColorDefinition.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.etheller.warsmash.parsers.fdf.datamodel; - -public class ColorDefinition { - private final float red, green, blue, alpha; - - public ColorDefinition(final float red, final float green, final float blue, final float alpha) { - this.red = red; - this.green = green; - this.blue = blue; - this.alpha = alpha; - } - - public float getRed() { - return this.red; - } - - public float getGreen() { - return this.green; - } - - public float getBlue() { - return this.blue; - } - - public float getAlpha() { - return this.alpha; - } -} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FrameClass.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FrameClass.java new file mode 100644 index 0000000..ad62408 --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FrameClass.java @@ -0,0 +1,8 @@ +package com.etheller.warsmash.parsers.fdf.datamodel; + +public enum FrameClass { + Frame, + String, + Texture, + Layer; +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FrameDefintion.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FrameDefinition.java similarity index 56% rename from fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FrameDefintion.java rename to fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FrameDefinition.java index 6c9374a..1225719 100644 --- a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FrameDefintion.java +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FrameDefinition.java @@ -14,16 +14,23 @@ import com.etheller.warsmash.parsers.fdf.datamodel.fields.FrameDefinitionField; * everything class might help get a prototype running fast until I have a * better understanding of how I want these classes designed. */ -public class FrameDefintion { - private String frameType; - private String name; - private final List innerFrames = new ArrayList<>(); +public class FrameDefinition { + private final FrameClass frameClass; + private final String frameType; + private final String name; + private final List innerFrames = new ArrayList<>(); private final Set flags = new HashSet<>(); private final Map nameToField = new HashMap<>(); private final List setPoints = new ArrayList<>(); private final List anchors = new ArrayList<>(); - public void inheritFrom(final FrameDefintion other, final boolean withChildren) { + public FrameDefinition(final FrameClass frameClass, final String frameType, final String name) { + this.frameClass = frameClass; + this.frameType = frameType; + this.name = name; + } + + public void inheritFrom(final FrameDefinition other, final boolean withChildren) { this.flags.addAll(other.flags); this.nameToField.putAll(other.nameToField); if (withChildren) { @@ -35,6 +42,10 @@ public class FrameDefintion { this.nameToField.put(fieldName, value); } + public void add(final FrameDefinition childDefition) { + this.innerFrames.add(childDefition); + } + public void add(final SetPointDefinition setPointDefinition) { this.setPoints.add(setPointDefinition); } @@ -42,4 +53,16 @@ public class FrameDefintion { public void add(final AnchorDefinition anchorDefinition) { this.anchors.add(anchorDefinition); } + + public void add(final String flag) { + this.flags.add(flag); + } + + @Override + public String toString() { + return "FrameDefinition [frameClass=" + this.frameClass + ", frameType=" + this.frameType + ", name=" + + this.name + ", innerFrames=" + this.innerFrames + ", flags=" + this.flags + ", nameToField=" + + this.nameToField + ", setPoints=" + this.setPoints + ", anchors=" + this.anchors + "]"; + } + } diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FrameTemplateEnvironment.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FrameTemplateEnvironment.java index fc13cd0..ba9da1b 100644 --- a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FrameTemplateEnvironment.java +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FrameTemplateEnvironment.java @@ -5,6 +5,7 @@ import java.util.Map; public class FrameTemplateEnvironment { private final Map idToDecoratedString = new HashMap<>(); + private final Map idToFrame = new HashMap<>(); public void addDecoratedString(final String id, final String value) { this.idToDecoratedString.put(id, value); @@ -17,4 +18,12 @@ public class FrameTemplateEnvironment { } return id; } + + public void put(final String id, final FrameDefinition frame) { + this.idToFrame.put(id, frame); + } + + public FrameDefinition getFrame(final String id) { + return this.idToFrame.get(id); + } } diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/Insets.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/Insets.java deleted file mode 100644 index 7ad6db6..0000000 --- a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/Insets.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.etheller.warsmash.parsers.fdf.datamodel; - -public class Insets { - private float top; - private float left; - private float bottom; - private float right; - - public float getTop() { - return this.top; - } - - public float getLeft() { - return this.left; - } - - public float getBottom() { - return this.bottom; - } - - public float getRight() { - return this.right; - } - - public void setTop(final float top) { - this.top = top; - } - - public void setLeft(final float left) { - this.left = left; - } - - public void setBottom(final float bottom) { - this.bottom = bottom; - } - - public void setRight(final float right) { - this.right = right; - } - -} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/Offset.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/Vector2Definition.java similarity index 70% rename from fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/Offset.java rename to fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/Vector2Definition.java index eb2732b..209ce72 100644 --- a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/Offset.java +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/Vector2Definition.java @@ -1,9 +1,14 @@ package com.etheller.warsmash.parsers.fdf.datamodel; -public class Offset { +public class Vector2Definition { private float x; private float y; + public Vector2Definition(final float x, final float y) { + this.x = x; + this.y = y; + } + public float getX() { return this.x; } diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/Vector3Definition.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/Vector3Definition.java new file mode 100644 index 0000000..22aa15d --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/Vector3Definition.java @@ -0,0 +1,23 @@ +package com.etheller.warsmash.parsers.fdf.datamodel; + +public class Vector3Definition { + private final float x, y, z; + + public Vector3Definition(final float x, final float y, final float z) { + this.x = x; + this.y = y; + this.z = z; + } + + public float getX() { + return this.x; + } + + public float getY() { + return this.y; + } + + public float getZ() { + return this.z; + } +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/Vector4Definition.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/Vector4Definition.java new file mode 100644 index 0000000..c2741ed --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/Vector4Definition.java @@ -0,0 +1,28 @@ +package com.etheller.warsmash.parsers.fdf.datamodel; + +public class Vector4Definition { + private final float x, y, z, w; + + public Vector4Definition(final float x, final float y, final float z, final float w) { + this.x = x; + this.y = y; + this.z = z; + this.w = w; + } + + public float getX() { + return this.x; + } + + public float getY() { + return this.y; + } + + public float getZ() { + return this.z; + } + + public float getW() { + return this.w; + } +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/ColorFrameDefinitionField.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/ColorFrameDefinitionField.java deleted file mode 100644 index 9fdebc5..0000000 --- a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/ColorFrameDefinitionField.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.etheller.warsmash.parsers.fdf.datamodel.fields; - -import com.etheller.warsmash.parsers.fdf.datamodel.ColorDefinition; - -public class ColorFrameDefinitionField implements FrameDefinitionField { - private final ColorDefinition value; - - public ColorFrameDefinitionField(final ColorDefinition value) { - this.value = value; - } - - public ColorDefinition getValue() { - return this.value; - } - - @Override - public TYPE visit(final FrameDefinitionFieldVisitor visitor) { - return visitor.accept(this); - } - -} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/FloatFrameDefinitionField.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/FloatFrameDefinitionField.java index 8c21db2..5218833 100644 --- a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/FloatFrameDefinitionField.java +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/FloatFrameDefinitionField.java @@ -15,4 +15,5 @@ public class FloatFrameDefinitionField implements FrameDefinitionField { public TYPE visit(final FrameDefinitionFieldVisitor visitor) { return visitor.accept(this); } + } diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/FrameDefinitionFieldVisitor.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/FrameDefinitionFieldVisitor.java index aa082ae..1049510 100644 --- a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/FrameDefinitionFieldVisitor.java +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/FrameDefinitionFieldVisitor.java @@ -3,13 +3,17 @@ package com.etheller.warsmash.parsers.fdf.datamodel.fields; public interface FrameDefinitionFieldVisitor { TYPE accept(StringFrameDefinitionField field); + TYPE accept(StringPairFrameDefinitionField field); + TYPE accept(FloatFrameDefinitionField field); - TYPE accept(ColorFrameDefinitionField field); + TYPE accept(Vector3FrameDefinitionField field); - TYPE accept(InsetsFrameDefinitionField field); + TYPE accept(Vector4FrameDefinitionField field); - TYPE accept(OffsetFrameDefinitionField field); + TYPE accept(Vector2FrameDefinitionField field); TYPE accept(FontFrameDefinitionField field); + + TYPE accept(TextJustifyFrameDefinitionField field); } diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/InsetsFrameDefinitionField.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/InsetsFrameDefinitionField.java deleted file mode 100644 index 67c3be2..0000000 --- a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/InsetsFrameDefinitionField.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.etheller.warsmash.parsers.fdf.datamodel.fields; - -import com.etheller.warsmash.parsers.fdf.datamodel.Insets; - -public class InsetsFrameDefinitionField implements FrameDefinitionField { - private final Insets value; - - public InsetsFrameDefinitionField(final Insets value) { - this.value = value; - } - - public Insets getValue() { - return this.value; - } - - @Override - public TYPE visit(final FrameDefinitionFieldVisitor visitor) { - return visitor.accept(this); - } -} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/OffsetFrameDefinitionField.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/OffsetFrameDefinitionField.java deleted file mode 100644 index 770c135..0000000 --- a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/OffsetFrameDefinitionField.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.etheller.warsmash.parsers.fdf.datamodel.fields; - -import com.etheller.warsmash.parsers.fdf.datamodel.Offset; - -public class OffsetFrameDefinitionField implements FrameDefinitionField { - private final Offset value; - - public OffsetFrameDefinitionField(final Offset value) { - this.value = value; - } - - public Offset getValue() { - return this.value; - } - - @Override - public TYPE visit(final FrameDefinitionFieldVisitor visitor) { - return visitor.accept(this); - } -} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/StringPairFrameDefinitionField.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/StringPairFrameDefinitionField.java new file mode 100644 index 0000000..9cf192b --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/StringPairFrameDefinitionField.java @@ -0,0 +1,24 @@ +package com.etheller.warsmash.parsers.fdf.datamodel.fields; + +public class StringPairFrameDefinitionField implements FrameDefinitionField { + private final String first; + private final String second; + + public StringPairFrameDefinitionField(final String first, final String second) { + this.first = first; + this.second = second; + } + + public String getFirst() { + return this.first; + } + + public String getSecond() { + return this.second; + } + + @Override + public TYPE visit(final FrameDefinitionFieldVisitor visitor) { + return visitor.accept(this); + } +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/TextJustifyFrameDefinitionField.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/TextJustifyFrameDefinitionField.java new file mode 100644 index 0000000..c0aa295 --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/TextJustifyFrameDefinitionField.java @@ -0,0 +1,21 @@ +package com.etheller.warsmash.parsers.fdf.datamodel.fields; + +import com.etheller.warsmash.parsers.fdf.datamodel.TextJustify; + +public class TextJustifyFrameDefinitionField implements FrameDefinitionField { + private final TextJustify value; + + public TextJustifyFrameDefinitionField(final TextJustify value) { + this.value = value; + } + + public TextJustify getValue() { + return this.value; + } + + @Override + public TYPE visit(final FrameDefinitionFieldVisitor visitor) { + return visitor.accept(this); + } + +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/Vector2FrameDefinitionField.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/Vector2FrameDefinitionField.java new file mode 100644 index 0000000..e07f798 --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/Vector2FrameDefinitionField.java @@ -0,0 +1,20 @@ +package com.etheller.warsmash.parsers.fdf.datamodel.fields; + +import com.etheller.warsmash.parsers.fdf.datamodel.Vector2Definition; + +public class Vector2FrameDefinitionField implements FrameDefinitionField { + private final Vector2Definition value; + + public Vector2FrameDefinitionField(final Vector2Definition value) { + this.value = value; + } + + public Vector2Definition getValue() { + return this.value; + } + + @Override + public TYPE visit(final FrameDefinitionFieldVisitor visitor) { + return visitor.accept(this); + } +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/Vector3FrameDefinitionField.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/Vector3FrameDefinitionField.java new file mode 100644 index 0000000..da09089 --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/Vector3FrameDefinitionField.java @@ -0,0 +1,21 @@ +package com.etheller.warsmash.parsers.fdf.datamodel.fields; + +import com.etheller.warsmash.parsers.fdf.datamodel.Vector3Definition; + +public class Vector3FrameDefinitionField implements FrameDefinitionField { + private final Vector3Definition value; + + public Vector3FrameDefinitionField(final Vector3Definition value) { + this.value = value; + } + + public Vector3Definition getValue() { + return this.value; + } + + @Override + public TYPE visit(final FrameDefinitionFieldVisitor visitor) { + return visitor.accept(this); + } + +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/Vector4FrameDefinitionField.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/Vector4FrameDefinitionField.java new file mode 100644 index 0000000..94bc3b6 --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/Vector4FrameDefinitionField.java @@ -0,0 +1,20 @@ +package com.etheller.warsmash.parsers.fdf.datamodel.fields; + +import com.etheller.warsmash.parsers.fdf.datamodel.Vector4Definition; + +public class Vector4FrameDefinitionField implements FrameDefinitionField { + private final Vector4Definition value; + + public Vector4FrameDefinitionField(final Vector4Definition value) { + this.value = value; + } + + public Vector4Definition getValue() { + return this.value; + } + + @Override + public TYPE visit(final FrameDefinitionFieldVisitor visitor) { + return visitor.accept(this); + } +} From 521aa51d955e852307d6fb665481ef76257d26dc Mon Sep 17 00:00:00 2001 From: Retera Date: Wed, 20 May 2020 08:21:41 -0400 Subject: [PATCH 033/116] Key value pair parser for FDF, new design, g4 file --- fdfparser/antlr-src/FDF.g4 | 242 +++---------------------------------- 1 file changed, 19 insertions(+), 223 deletions(-) diff --git a/fdfparser/antlr-src/FDF.g4 b/fdfparser/antlr-src/FDF.g4 index 4203744..7728f25 100644 --- a/fdfparser/antlr-src/FDF.g4 +++ b/fdfparser/antlr-src/FDF.g4 @@ -20,6 +20,12 @@ statement: ; frame: + frame_type_qualifier OPEN_CURLY frame_element* CLOSE_CURLY # AnonymousCompDefinition + | + frame_type_qualifier INHERITS STRING_LITERAL OPEN_CURLY frame_element* CLOSE_CURLY # AnonymousCompSubTypeDefinition + | + frame_type_qualifier INHERITS WITHCHILDREN STRING_LITERAL OPEN_CURLY frame_element* CLOSE_CURLY # AnonymousCompSubTypeDefinitionWithChildren + | frame_type_qualifier STRING_LITERAL OPEN_CURLY frame_element* CLOSE_CURLY # CompDefinition | frame_type_qualifier STRING_LITERAL INHERITS STRING_LITERAL OPEN_CURLY frame_element* CLOSE_CURLY # CompSubTypeDefinition @@ -36,151 +42,31 @@ frame: frame_element: frame # FrameFrameElement | - HEIGHT FLOAT COMMA # HeightElement + ID FLOAT COMMA # FloatElement | - WIDTH FLOAT COMMA # WidthElement + ID STRING_LITERAL COMMA # StringElement | - CONTROL_STYLE STRING_LITERAL COMMA # ControlStyleElement + ID STRING_LITERAL STRING_LITERAL COMMA # StringPairElement | - CONTROL_BACKDROP STRING_LITERAL COMMA # ControlBackdropElement + ID FLOAT FLOAT COMMA # Vector2Element | - CONTROL_PUSHED_BACKDROP STRING_LITERAL COMMA # ControlPushedBackdropElement + ID COMMA # FlagElement | - CONTROL_DISABLED_BACKDROP STRING_LITERAL COMMA # ControlDisabledBackdropElement + ID FLOAT FLOAT FLOAT FLOAT COMMA # Vector4Element | - CONTROL_FOCUS_HIGHLIGHT STRING_LITERAL COMMA # ControlFocusHighlightElement - | - CONTROL_MOUSE_OVER_HIGHLIGHT STRING_LITERAL COMMA # ControlMouseOverHighlightElement - | - HIGHLIGHT_TYPE STRING_LITERAL COMMA # HighlightTypeElement - | - HIGHLIGHT_ALPHA_FILE STRING_LITERAL COMMA # HighlightAlphaFileElement - | - HIGHLIGHT_ALPHA_MODE STRING_LITERAL COMMA # HighlightAlphaModeElement - | - BUTTON_PUSHED_TEXT_OFFSET FLOAT FLOAT COMMA # ButtonPushedTextOffsetElement - | - DIALOG_BACKDROP STRING_LITERAL COMMA # DialogBackdropElement - | - BACKDROP_TILE_BACKGROUND COMMA # BackdropTileBackgroundElement - | - DECORATE_FILE_NAMES COMMA # DecorateFileNamesElement - | - SET_ALL_POINTS COMMA # SetAllPointsElement - | - USE_ACTIVE_CONTEXT COMMA # UseActiveContextElement - | - BACKDROP_HALF_SIDES COMMA # BackdropHalfSidesElement - | - BACKDROP_BACKGROUND STRING_LITERAL COMMA # BackdropBackgroundElement - | - BACKDROP_CORNER_FLAGS STRING_LITERAL COMMA # BackdropCornerFlagsElement - | - BACKDROP_CORNER_SIZE FLOAT COMMA # BackdropCornerSizeElement - | - BACKDROP_BACKGROUND_SIZE FLOAT COMMA # BackdropBackgroundSizeElement - | - BACKDROP_BACKGROUND_INSETS FLOAT FLOAT FLOAT FLOAT COMMA # BackdropBackgroundInsetsElement - | - BACKDROP_EDGE_FILE STRING_LITERAL COMMA # BackdropEdgeFileElement - | - BACKDROP_BACKGROUND STRING_LITERAL COMMA # BackdropBackgroundElement - | - BACKDROP_BLEND_ALL COMMA # BackdropBlendAllElement - | - BUTTON_TEXT STRING_LITERAL COMMA #ButtonTextElement - | - TEXT STRING_LITERAL COMMA # TextElement + ID FLOAT COMMA FLOAT COMMA FLOAT COMMA FLOAT COMMA # Vector4CommaElement | SETPOINT frame_point COMMA STRING_LITERAL COMMA frame_point COMMA FLOAT COMMA FLOAT COMMA # SetPointElement | - BACKDROP_CORNER_FILE STRING_LITERAL COMMA # BackdropCornerFileElement + ANCHOR frame_point COMMA FLOAT COMMA FLOAT COMMA # AnchorElement | - BACKDROP_LEFT_FILE STRING_LITERAL COMMA # BackdropLeftFileElement + ID STRING_LITERAL COMMA FLOAT COMMA STRING_LITERAL COMMA # FontElement | - BACKDROP_RIGHT_FILE STRING_LITERAL COMMA # BackdropRightFileElement + ID FLOAT FLOAT FLOAT COMMA # Vector3Element | - BACKDROP_TOP_FILE STRING_LITERAL COMMA # BackdropTopFileElement + ID text_justify COMMA # TextJustifyElement | - BACKDROP_BOTTOM_FILE STRING_LITERAL COMMA # BackdropBottomFileElement - | - FRAME_FONT STRING_LITERAL COMMA FLOAT COMMA STRING_LITERAL COMMA # DecorateFileNamesElement - | - FONT_FLAGS STRING_LITERAL COMMA # FontFlagsElement - | - FONT_COLOR color COMMA # FontColorElement - | - FONT_HIGHLIGHT_COLOR color COMMA # FontHighlightColorElement - | - FONT_DISABLED_COLOR color COMMA # FontDisabledColorElement - | - FONT_SHADOW_COLOR color COMMA # FontShadowColorElement - | - FONT_SHADOW_OFFSET FLOAT FLOAT COMMA # FontShadowOffsetElement - | - FONT_JUSTIFICATION_H text_justify COMMA # FontJustificationHElement - | - FONT_JUSTIFICATION_V text_justify COMMA # FontJustificationVElement - | - FONT_JUSTIFICATION_OFFSET FLOAT FLOAT COMMA # FontJustificationOffsetElement - | - SLIDER_LAYOUT_HORIZONTAL COMMA # SliderLayoutHorizontalElement - | - SLIDER_LAYOUT_VERTICAL COMMA # SliderLayoutVerticalElement - | - SCROLL_BAR_INC_BUTTON_FRAME STRING_LITERAL COMMA # ScrollBarIncButtonFrameElement - | - SCROLL_BAR_DEC_BUTTON_FRAME STRING_LITERAL COMMA # ScrollBarDecButtonFrameElement - | - SLIDER_THUMB_BUTTON_FRAME STRING_LITERAL COMMA # SliderThumbButtonFrameElement - | - LIST_BOX_BORDER FLOAT COMMA # ListBoxBorderElement - | - LIST_BOX_SCROLL_BAR STRING_LITERAL COMMA # ListBoxScrollBarElement - | - EDIT_BORDER_SIZE FLOAT COMMA # EditBorderSizeElement - | - EDIT_CURSOR_COLOR color COMMA # EditCursorColorElement - | - MENU_TEXT_HIGHLIGHT_COLOR color COMMA # MenuTextHighlightColorElement - | - MENU_ITEM_HEIGHT FLOAT COMMA # MenuItemHighlightElement - | - MENU_BORDER FLOAT COMMA # MenuBorderElement - | - POPUP_BUTTON_INSET FLOAT COMMA # PopupButtonInsetElement - | - POPUP_TITLE_FRAME STRING_LITERAL COMMA # PopupTitleFrameElement - | - POPUP_ARROW_FRAME STRING_LITERAL COMMA # PopupArrowFrameElement - | - POPUP_MENU_FRAME STRING_LITERAL COMMA # PopupMenuFrameElement - | - CHECK_BOX_CHECK_HIGHLIGHT STRING_LITERAL COMMA # CheckBoxCheckHighlightElement - | - CHECK_BOX_DISABLED_CHECK_HIGHLIGHT STRING_LITERAL COMMA # CheckBoxDisabledCheckHighlightElement - | - TEXT_AREA_LINE_HEIGHT FLOAT COMMA # TextAreaLineHeightElement - | - TEXT_AREA_LINE_GAP FLOAT COMMA # TextAreaLineGapElement - | - TEXT_AREA_INSET FLOAT COMMA # TextAreaInsetElement - | - TEXT_AREA_SCROLL_BAR STRING_LITERAL COMMA # TextAreaScrollBarElement - | - CHAT_DISPLAY_LINE_HEIGHT FLOAT COMMA # ChatDisplayLineHeightElement - | - CHAT_DISPLAY_BORDER_SIZE FLOAT COMMA # ChatDisplayBorderSize - | - CHAT_DISPLAY_SCROLL_BAR STRING_LITERAL COMMA # ChatDisplayScrollBarElement - | - CHAT_DISPLAY_EDIT_BOX STRING_LITERAL COMMA # ChatDisplayEditBoxElement - | - TEXT STRING_LITERAL COMMA # TextElement - | - FILE STRING_LITERAL COMMA # FileElement - | - FONT STRING_LITERAL COMMA FLOAT COMMA # FontElement + ID STRING_LITERAL COMMA FLOAT COMMA # SimpleFontElement ; text_justify: @@ -231,98 +117,8 @@ INHERITS : 'INHERITS' ; WITHCHILDREN : 'WITHCHILDREN' ; -DECORATE_FILE_NAMES : 'DecorateFileNames'; - -SET_ALL_POINTS : 'SetAllPoints'; - -USE_ACTIVE_CONTEXT : 'UseActiveContext'; - -HEIGHT : 'Height'; - -WIDTH : 'Width'; - -HIGHLIGHT_TYPE : 'HighlightType'; -HIGHLIGHT_ALPHA_FILE : 'HighlightAlphaFile'; -HIGHLIGHT_ALPHA_MODE : 'HighlightAlphaMode'; - -CONTROL_STYLE : 'ControlStyle'; -CONTROL_BACKDROP : 'ControlBackdrop'; -CONTROL_PUSHED_BACKDROP : 'ControlPushedBackdrop'; -CONTROL_DISABLED_BACKDROP : 'ControlDisabledBackdrop'; -CONTROL_FOCUS_HIGHLIGHT : 'ControlFocusHighlight'; -CONTROL_MOUSE_OVER_HIGHLIGHT : 'ControlMouseOverHighlight'; - -DIALOG_BACKDROP : 'DialogBackdrop'; - -BACKDROP_TILE_BACKGROUND : 'BackdropTileBackground'; -BACKDROP_HALF_SIDES : 'BackdropHalfSides'; -BACKDROP_BACKGROUND : 'BackdropBackground'; -BACKDROP_BLEND_ALL : 'BackdropBlendAll'; -BACKDROP_CORNER_FLAGS : 'BackdropCornerFlags'; -BACKDROP_CORNER_SIZE : 'BackdropCornerSize'; -BACKDROP_BACKGROUND_SIZE : 'BackdropBackgroundSize'; -BACKDROP_BACKGROUND_INSETS : 'BackdropBackgroundInsets'; -BACKDROP_EDGE_FILE : 'BackdropEdgeFile'; -BACKDROP_CORNER_FILE : 'BackdropCornerFile'; -BACKDROP_LEFT_FILE : 'BackdropLeftFile'; -BACKDROP_RIGHT_FILE : 'BackdropRightFile'; -BACKDROP_TOP_FILE : 'BackdropTopFile'; -BACKDROP_BOTTOM_FILE : 'BackdropBottomFile'; - -FRAME_FONT : 'FrameFont'; -FONT_FLAGS : 'FontFlags'; -FONT_COLOR : 'FontColor'; -FONT_HIGHLIGHT_COLOR : 'FontHighlightColor'; -FONT_DISABLED_COLOR : 'FontDisabledColor'; -FONT_SHADOW_COLOR : 'FontShadowColor'; -FONT_SHADOW_OFFSET : 'FontShadowOffset'; -FONT_JUSTIFICATION_H : 'FontJustificationH'; -FONT_JUSTIFICATION_V : 'FontJustificationV'; -FONT_JUSTIFICATION_OFFSET : 'FontJustificationOffset'; - -SLIDER_LAYOUT_HORIZONTAL : 'SliderLayoutHorizontal'; -SLIDER_LAYOUT_VERTICAL : 'SliderLayoutVertical'; -SLIDER_THUMB_BUTTON_FRAME : 'SliderThumbButtonFrame'; -SCROLL_BAR_DEC_BUTTON_FRAME : 'ScrollBarDecButtonFrame'; -SCROLL_BAR_INC_BUTTON_FRAME : 'ScrollBarIncButtonFrame'; - -LIST_BOX_BORDER : 'ListBoxBorder'; -LIST_BOX_SCROLL_BAR : 'ListBoxScrollBar'; - -EDIT_BORDER_SIZE : 'EditBorderSize'; -EDIT_CURSOR_COLOR : 'EditCursorColor'; - -MENU_TEXT_HIGHLIGHT_COLOR : 'MenuTextHighlightColor'; -MENU_ITEM_HEIGHT : 'MenuItemHeight'; -MENU_BORDER : 'MenuBorder'; - -POPUP_BUTTON_INSET : 'PopupButtonInset'; -POPUP_TITLE_FRAME : 'PopupTitleFrame'; -POPUP_ARROW_FRAME : 'PopupArrowFrame'; -POPUP_MENU_FRAME : 'PopupMenuFrame'; -CHECK_BOX_CHECK_HIGHLIGHT : 'CheckBoxCheckHighlight'; -CHECK_BOX_DISABLED_CHECK_HIGHLIGHT : 'CheckBoxDisabledCheckHighlight'; - -TEXT_AREA_LINE_HEIGHT : 'TextAreaLineHeight'; -TEXT_AREA_LINE_GAP : 'TextAreaLineGap'; -TEXT_AREA_INSET : 'TextAreaInset'; -TEXT_AREA_SCROLL_BAR : 'TextAreaScrollBar'; - -CHAT_DISPLAY_LINE_HEIGHT : 'ChatDisplayLineHeight'; -CHAT_DISPLAY_BORDER_SIZE : 'ChatDisplayBorderSize'; -CHAT_DISPLAY_SCROLL_BAR : 'ChatDisplayScrollBar'; -CHAT_DISPLAY_EDIT_BOX : 'ChatDisplayEditBox'; - -BUTTON_TEXT : 'ButtonText'; -BUTTON_PUSHED_TEXT_OFFSET : 'ButtonPushedTextOffset'; - -TEXT : 'Text'; - -FILE : 'File'; - -FONT : 'Font'; - SETPOINT : 'SetPoint'; +ANCHOR : 'Anchor'; JUSTIFYTOP : 'JUSTIFYTOP'; JUSTIFYMIDDLE : 'JUSTIFYMIDDLE'; From 813e7c9d9b3533f9f8bdbfd62056bb270cf53b07 Mon Sep 17 00:00:00 2001 From: Retera Date: Wed, 27 May 2020 23:55:48 -0400 Subject: [PATCH 034/116] Snapshot after breaking scene, work on FDF, work on JASS --- build.gradle | 10 + .../etheller/warsmash/WarsmashGdxGame.java | 17 +- .../etheller/warsmash/WarsmashGdxMapGame.java | 137 +++++---- .../etheller/warsmash/gameui/FDFGameUI.java | 11 + .../etheller/warsmash/parsers/fdf/GameUI.java | 200 ++++++++++++ .../warsmash/parsers/fdf/ModelExport.java | 37 +++ .../fdf/frames/AbstractRenderableFrame.java | 291 ++++++++++++++++++ .../parsers/fdf/frames/AbstractUIFrame.java | 36 +++ .../parsers/fdf/frames/SimpleFrame.java | 9 + .../parsers/fdf/frames/TextureFrame.java | 25 ++ .../warsmash/parsers/fdf/frames/UIFrame.java | 22 ++ .../warsmash/parsers/jass/JassTest.java | 64 ++++ .../etheller/warsmash/units/DataTable.java | 6 +- .../warsmash/util/RenderMathUtils.java | 27 +- .../etheller/warsmash/util/StringBundle.java | 19 ++ .../warsmash/util/WorldEditStrings.java | 4 +- .../com/etheller/warsmash/viewer5/Grid.java | 6 +- .../etheller/warsmash/viewer5/GridCell.java | 1 + .../warsmash/viewer5/ModelInstance.java | 26 +- .../com/etheller/warsmash/viewer5/Scene.java | 83 +---- .../warsmash/viewer5/SimpleScene.java | 73 +++++ .../etheller/warsmash/viewer5/WorldScene.java | 105 +++++++ .../warsmash/viewer5/handlers/mdx/Geoset.java | 22 +- .../viewer5/handlers/mdx/MdxModel.java | 8 +- .../viewer5/handlers/mdx/SetupGeosets.java | 52 +++- .../viewer5/handlers/w3x/War3MapViewer.java | 3 +- .../handlers/w3x/environment/Terrain.java | 10 +- .../w3x/rendersim/RenderAttackProjectile.java | 2 +- .../handlers/w3x/rendersim/RenderUnit.java | 2 +- fdfparser/.gitignore | 1 + .../fdf/datamodel/AnchorDefinition.java | 5 + .../fdf/datamodel/FrameDefinition.java | 58 ++++ .../fields/visitor/GetFloatFieldVisitor.java | 56 ++++ .../fields/visitor/GetStringFieldVisitor.java | 56 ++++ .../visitor/GetVector4FieldVisitor.java | 57 ++++ jassparser/antlr-src/Jass.g4 | 169 ++++++++++ jassparser/build.gradle | 49 +++ .../etheller/interpreter/ast/Assignable.java | 29 ++ .../etheller/interpreter/ast/JassRunner.java | 50 +++ .../expression/ArrayRefJassExpression.java | 38 +++ .../FunctionCallJassExpression.java | 34 ++ .../FunctionReferenceJassExpression.java | 25 ++ .../ast/expression/JassExpression.java | 9 + .../ast/expression/LiteralJassExpression.java | 19 ++ .../expression/ReferenceJassExpression.java | 24 ++ .../ast/function/AbstractJassFunction.java | 44 +++ .../ast/function/JassFunction.java | 10 + .../ast/function/JassNativeManager.java | 33 ++ .../ast/function/JassParameter.java | 27 ++ .../ast/function/NativeJassFunction.java | 26 ++ .../ast/function/UserJassFunction.java | 44 +++ .../interpreter/ast/scope/GlobalScope.java | 94 ++++++ .../interpreter/ast/scope/LocalScope.java | 42 +++ .../interpreter/ast/scope/TypeDefinition.java | 11 + .../JassArrayedAssignmentStatement.java | 44 +++ .../ast/statement/JassCallStatement.java | 37 +++ .../ast/statement/JassReturnStatement.java | 20 ++ .../ast/statement/JassSetStatement.java | 29 ++ .../ast/statement/JassStatement.java | 11 + .../interpreter/ast/value/ArrayJassType.java | 18 ++ .../interpreter/ast/value/ArrayJassValue.java | 34 ++ .../ast/value/BooleanJassValue.java | 18 ++ .../interpreter/ast/value/CodeJassValue.java | 21 ++ .../ast/value/IntegerJassValue.java | 18 ++ .../interpreter/ast/value/JassType.java | 12 + .../ast/value/JassTypeVisitor.java | 7 + .../interpreter/ast/value/JassValue.java | 5 + .../ast/value/JassValueVisitor.java | 15 + .../ast/value/PrimitiveJassType.java | 19 ++ .../interpreter/ast/value/RealJassValue.java | 18 ++ .../ast/value/StringJassValue.java | 18 ++ .../value/visitor/ArrayJassValueVisitor.java | 48 +++ .../visitor/ArrayPrimitiveTypeVisitor.java | 24 ++ .../visitor/IntegerJassValueVisitor.java | 48 +++ .../visitor/JassFunctionJassValueVisitor.java | 49 +++ .../visitor/JassTypeGettingValueVisitor.java | 49 +++ .../value/visitor/StringJassValueVisitor.java | 48 +++ .../visitors/ArgumentExpressionHandler.java | 15 + .../ast/visitors/JassArgumentsVisitor.java | 38 +++ .../ast/visitors/JassExpressionVisitor.java | 77 +++++ .../ast/visitors/JassGlobalsVisitor.java | 49 +++ .../ast/visitors/JassParametersVisitor.java | 38 +++ .../ast/visitors/JassProgramVisitor.java | 87 ++++++ .../ast/visitors/JassStatementVisitor.java | 44 +++ .../ast/visitors/JassTypeVisitor.java | 31 ++ settings.gradle | 2 +- 86 files changed, 3134 insertions(+), 175 deletions(-) create mode 100644 core/src/com/etheller/warsmash/gameui/FDFGameUI.java create mode 100644 core/src/com/etheller/warsmash/parsers/fdf/GameUI.java create mode 100644 core/src/com/etheller/warsmash/parsers/fdf/ModelExport.java create mode 100644 core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java create mode 100644 core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractUIFrame.java create mode 100644 core/src/com/etheller/warsmash/parsers/fdf/frames/SimpleFrame.java create mode 100644 core/src/com/etheller/warsmash/parsers/fdf/frames/TextureFrame.java create mode 100644 core/src/com/etheller/warsmash/parsers/fdf/frames/UIFrame.java create mode 100644 core/src/com/etheller/warsmash/parsers/jass/JassTest.java create mode 100644 core/src/com/etheller/warsmash/util/StringBundle.java create mode 100644 core/src/com/etheller/warsmash/viewer5/SimpleScene.java create mode 100644 core/src/com/etheller/warsmash/viewer5/WorldScene.java create mode 100644 fdfparser/.gitignore create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/visitor/GetFloatFieldVisitor.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/visitor/GetStringFieldVisitor.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/visitor/GetVector4FieldVisitor.java create mode 100644 jassparser/antlr-src/Jass.g4 create mode 100644 jassparser/build.gradle create mode 100644 jassparser/src/com/etheller/interpreter/ast/Assignable.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/JassRunner.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/expression/ArrayRefJassExpression.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/expression/FunctionCallJassExpression.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/expression/FunctionReferenceJassExpression.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/expression/JassExpression.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/expression/LiteralJassExpression.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/expression/ReferenceJassExpression.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/function/AbstractJassFunction.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/function/JassFunction.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/function/JassNativeManager.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/function/JassParameter.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/function/NativeJassFunction.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/function/UserJassFunction.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/scope/GlobalScope.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/scope/LocalScope.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/scope/TypeDefinition.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/statement/JassArrayedAssignmentStatement.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/statement/JassCallStatement.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/statement/JassReturnStatement.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/statement/JassSetStatement.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/statement/JassStatement.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/value/ArrayJassType.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/value/ArrayJassValue.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/value/BooleanJassValue.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/value/CodeJassValue.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/value/IntegerJassValue.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/value/JassType.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/value/JassTypeVisitor.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/value/JassValue.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/value/JassValueVisitor.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/value/PrimitiveJassType.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/value/RealJassValue.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/value/StringJassValue.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/value/visitor/ArrayJassValueVisitor.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/value/visitor/ArrayPrimitiveTypeVisitor.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/value/visitor/IntegerJassValueVisitor.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/value/visitor/JassFunctionJassValueVisitor.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/value/visitor/JassTypeGettingValueVisitor.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/value/visitor/StringJassValueVisitor.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/visitors/ArgumentExpressionHandler.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/visitors/JassArgumentsVisitor.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/visitors/JassExpressionVisitor.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/visitors/JassGlobalsVisitor.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/visitors/JassParametersVisitor.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/visitors/JassProgramVisitor.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/visitors/JassStatementVisitor.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/visitors/JassTypeVisitor.java diff --git a/build.gradle b/build.gradle index c0bc5fb..30c58f8 100644 --- a/build.gradle +++ b/build.gradle @@ -66,6 +66,7 @@ project(":core") { dependencies { compile project(":fdfparser") + compile project(":jassparser") compile "com.badlogicgames.gdx:gdx:$gdxVersion" compile "com.badlogicgames.gdx:gdx-box2d:$gdxVersion" compile "com.badlogicgames.gdx:gdx-freetype:$gdxVersion" @@ -85,6 +86,15 @@ project(":fdfparser") { } } +project(":jassparser") { + apply plugin: "antlr" + + + dependencies { + antlr "org.antlr:antlr4:$antlrVersion" // use antlr version 4 + } +} + tasks.eclipse.doLast { delete ".project" } \ No newline at end of file diff --git a/core/src/com/etheller/warsmash/WarsmashGdxGame.java b/core/src/com/etheller/warsmash/WarsmashGdxGame.java index 0d6bd42..9840bb8 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxGame.java @@ -7,6 +7,7 @@ import java.util.Arrays; import com.badlogic.gdx.ApplicationAdapter; import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.audio.Music; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.SpriteBatch; @@ -18,6 +19,7 @@ import com.etheller.warsmash.datasources.DataSource; import com.etheller.warsmash.datasources.DataSourceDescriptor; import com.etheller.warsmash.datasources.FolderDataSourceDescriptor; import com.etheller.warsmash.parsers.mdlx.Sequence; +import com.etheller.warsmash.util.DataSourceFileHandle; import com.etheller.warsmash.viewer5.Camera; import com.etheller.warsmash.viewer5.CanvasProvider; import com.etheller.warsmash.viewer5.ModelViewer; @@ -72,7 +74,7 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide this.cameraManager = new CameraManager(); this.cameraManager.setupCamera(scene); - this.mainModel = (MdxModel) this.viewer.load("Buildings\\Other\\TempArtB\\TempArtB.mdx", + this.mainModel = (MdxModel) this.viewer.load("UI\\Glues\\MainMenu\\MainMenu3d\\MainMenu3d.mdx", // this.mainModel = (MdxModel) this.viewer.load("Abilities\\Spells\\Orc\\FeralSpirit\\feralspirittarget.mdx", new PathSolver() { @Override @@ -88,7 +90,7 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide // System.out.println(Arrays.toString(evt.keyFrames)); // System.out.println(evt.name); -// this.modelCamera = this.mainModel.cameras.get(0); + this.modelCamera = this.mainModel.cameras.get(0); this.mainInstance = (MdxComplexInstance) this.mainModel.addInstance(0); @@ -96,14 +98,21 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide int animIndex = 0; for (final Sequence s : this.mainModel.getSequences()) { - if (s.getName().toLowerCase().startsWith("walk")) { + if (s.getName().toLowerCase().startsWith("stand")) { animIndex = this.mainModel.getSequences().indexOf(s); + break; } } this.mainInstance.setSequence(animIndex); this.mainInstance.setSequenceLoopMode(0); + final Music music = Gdx.audio + .newMusic(new DataSourceFileHandle(this.viewer.dataSource, "Sound\\Music\\mp3Music\\MainScreen.mp3")); + music.setVolume(0.2f); + music.setLooping(true); + music.play(); + // acolytesHarvestingSceneJoke2(scene); System.out.println("Loaded"); @@ -489,7 +498,7 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide WarsmashGdxGame.this.cameraPositionTemp[1], WarsmashGdxGame.this.cameraPositionTemp[2]); this.target.add(WarsmashGdxGame.this.cameraTargetTemp[0], WarsmashGdxGame.this.cameraTargetTemp[1], WarsmashGdxGame.this.cameraTargetTemp[2]); - this.camera.perspective(WarsmashGdxGame.this.modelCamera.fieldOfView, + this.camera.perspective(WarsmashGdxGame.this.modelCamera.fieldOfView * 0.75f, Gdx.graphics.getWidth() / (float) Gdx.graphics.getHeight(), WarsmashGdxGame.this.modelCamera.nearClippingPlane, WarsmashGdxGame.this.modelCamera.farClippingPlane); diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java index fb8b6f9..177c486 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java @@ -14,7 +14,6 @@ import com.badlogic.gdx.ApplicationAdapter; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Input; import com.badlogic.gdx.InputProcessor; -import com.badlogic.gdx.audio.Music; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.GL30; @@ -37,6 +36,11 @@ import com.etheller.warsmash.datasources.CompoundDataSourceDescriptor; import com.etheller.warsmash.datasources.DataSource; import com.etheller.warsmash.datasources.DataSourceDescriptor; import com.etheller.warsmash.datasources.FolderDataSourceDescriptor; +import com.etheller.warsmash.parsers.fdf.GameUI; +import com.etheller.warsmash.parsers.fdf.datamodel.AnchorDefinition; +import com.etheller.warsmash.parsers.fdf.datamodel.FramePoint; +import com.etheller.warsmash.parsers.fdf.frames.UIFrame; +import com.etheller.warsmash.units.Element; import com.etheller.warsmash.util.DataSourceFileHandle; import com.etheller.warsmash.util.ImageUtils; import com.etheller.warsmash.util.WarsmashConstants; @@ -93,7 +97,13 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv private boolean showTalentTree; private final List messages = new LinkedList<>(); + private MdxModel timeIndicator; + /* + * (non-Javadoc) + * + * @see com.badlogic.gdx.ApplicationAdapter#create() + */ @Override public void create() { @@ -126,7 +136,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.viewer.worldScene.enableAudio(); this.viewer.enableAudio(); try { - this.viewer.loadMap("ReforgedGeorgeVacation.w3x"); + this.viewer.loadMap("PeasantTest.w3x"); } catch (final IOException e) { throw new RuntimeException(e); @@ -143,6 +153,9 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.portraitCameraManager = new CameraManager(); this.portraitCameraManager.setupCamera(this.portraitScene); + this.uiScene = this.viewer.addScene(); + this.uiScene.alpha = true; + // this.mainModel = (MdxModel) this.viewer.load("UI\\Glues\\MainMenu\\MainMenu3D_exp\\MainMenu3D_exp.mdx", this.portraitScene.camera.viewport(new Rectangle(100, 0, 6400, 48)); @@ -151,6 +164,13 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv final float w = Gdx.graphics.getWidth(); final float h = Gdx.graphics.getHeight(); + this.tempRect.x = 0; + this.tempRect.y = 0; + this.tempRect.width = w; + this.tempRect.height = h; + this.uiScene.camera.viewport(this.tempRect); + this.uiScene.camera.ortho(0, 0.8f, 0, 0.6f, 0, 1); + final FreeTypeFontGenerator fontGenerator = new FreeTypeFontGenerator( new DataSourceFileHandle(this.viewer.dataSource, "fonts\\FRIZQT__.TTF")); final FreeTypeFontParameter fontParam = new FreeTypeFontParameter(); @@ -212,13 +232,13 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv Gdx.input.setInputProcessor(this); - final Music music = Gdx.audio - .newMusic(new DataSourceFileHandle(this.viewer.dataSource, "Sound\\Music\\mp3Music\\DarkAgents.mp3")); - music.setVolume(0.2f); - music.setLooping(true); - music.play(); +// final Music music = Gdx.audio +// .newMusic(new DataSourceFileHandle(this.viewer.dataSource, "Sound\\Music\\mp3Music\\DarkAgents.mp3")); +// music.setVolume(0.2f); +// music.setLooping(true); +// music.play(); - this.minimap = new Rectangle(35, 7, 305, 272); + this.minimap = new Rectangle(18.75f, 13.75f, 278.75f, 276.25f); final float worldWidth = (this.viewer.terrain.columns - 1); final float worldHeight = this.viewer.terrain.rows - 1; final float worldSize = Math.max(worldWidth, worldHeight); @@ -234,6 +254,33 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.shapeRenderer = new ShapeRenderer(); this.talentTreeWindow = new Rectangle(100, 300, 1400, 800); + + final Element skin = GameUI.loadSkin(this.viewer.dataSource, "Human"); + this.gameUI = new GameUI(this.viewer.dataSource, skin, this.uiViewport); + String timeIndicatorPath = skin.getField("TimeOfDayIndicator"); + if (!this.viewer.dataSource.has(timeIndicatorPath)) { + final int lastDotIndex = timeIndicatorPath.lastIndexOf('.'); + if (lastDotIndex >= 0) { + timeIndicatorPath = timeIndicatorPath.substring(0, lastDotIndex); + } + timeIndicatorPath += ".mdx"; + } + this.timeIndicator = (MdxModel) this.viewer.load(timeIndicatorPath, this.viewer.mapPathSolver, + this.viewer.solverParams); + final MdxComplexInstance timeIndicatorInstance = (MdxComplexInstance) this.timeIndicator.addInstance(); + timeIndicatorInstance.setScene(this.uiScene); + timeIndicatorInstance.setSequence(0); + timeIndicatorInstance.setSequenceLoopMode(2); + try { + this.gameUI.loadTOCFile("UI\\FrameDef\\FrameDef.toc"); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + this.gameUI.createSimpleFrame("ConsoleUI", this.gameUI, 0); + final UIFrame resourceBarFrame = this.gameUI.createSimpleFrame("ResourceBarFrame", this.gameUI, 0); + resourceBarFrame.addAnchor(new AnchorDefinition(FramePoint.TOPRIGHT, 0, 0)); + this.gameUI.positionBounds(this.uiViewport); } @Override @@ -270,60 +317,35 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.uiViewport.apply(); this.batch.setProjectionMatrix(this.uiCamera.combined); this.batch.begin(); + this.gameUI.render(this.batch); this.font.setColor(Color.YELLOW); final String fpsString = "FPS: " + Gdx.graphics.getFramesPerSecond(); this.glyphLayout.setText(this.font, fpsString); this.font.draw(this.batch, fpsString, (this.uiViewport.getWorldWidth() - this.glyphLayout.width) / 2, 1100); -// this.batch.draw(this.consoleUITexture, 0, 0, this.uiViewport.getWorldWidth(), 320); - this.batch.draw(this.minimapTexture, 35, 7, 305, 272); + this.batch.draw(this.minimapTexture, this.minimap.x, this.minimap.y, this.minimap.width, this.minimap.height); if (this.selectedUnit != null) { - this.font24.setColor(Color.WHITE); - final String name = this.viewer.simulation.getUnitData() - .getName(this.selectedUnit.getSimulationUnit().getTypeId()); - this.glyphLayout.setText(this.font24, name); - this.font24.draw(this.batch, name, ((this.uiViewport.getWorldWidth() - this.glyphLayout.width) / 2) + 100, - 200); - - this.font20.setColor(Color.YELLOW); - this.font20.draw(this.batch, "Attack:", 600, 120); - this.font20.draw(this.batch, "Defense:", 600, 98); - this.font20.draw(this.batch, "Speed:", 600, 76); - this.font20.setColor(Color.WHITE); int messageIndex = 0; for (final Message message : this.messages) { this.font20.draw(this.batch, message.text, 100, 400 + (25 * (messageIndex++))); } this.font20.setColor(Color.WHITE); - final int dmgMin = this.viewer.simulation.getUnitData() - .getA1MinDamage(this.selectedUnit.getSimulationUnit().getTypeId()); - final int dmgMax = this.viewer.simulation.getUnitData() - .getA1MaxDamage(this.selectedUnit.getSimulationUnit().getTypeId()); - final int def = this.viewer.simulation.getUnitData() - .getDefense(this.selectedUnit.getSimulationUnit().getTypeId()); - this.font20.draw(this.batch, Integer.toString(dmgMin) + " - " + Integer.toString(dmgMax), 700, 120); - this.font20.draw(this.batch, Integer.toString(def), 700, 98); - this.font20.draw(this.batch, Integer.toString(this.selectedUnit.getSimulationUnit().getSpeed()), 700, 76); final COrder currentOrder = this.selectedUnit.getSimulationUnit().getCurrentOrder(); for (final CommandCardIcon commandCardIcon : this.selectedUnit.getCommandCardIcons()) { - this.batch.draw(commandCardIcon.getTexture(), 1225 + (70 * commandCardIcon.getX()), - 160 - (70 * commandCardIcon.getY()), 64, 64); + this.batch.draw(commandCardIcon.getTexture(), 1235 + (86.8f * commandCardIcon.getX()), + 190 - (88 * commandCardIcon.getY()), 78f, 78f); if (((currentOrder != null) && (currentOrder.getOrderId() == commandCardIcon.getOrderId())) || ((currentOrder == null) && (commandCardIcon.getOrderId() == CAbilityStop.ORDER_ID))) { final int blendDstFunc = this.batch.getBlendDstFunc(); final int blendSrcFunc = this.batch.getBlendSrcFunc(); this.batch.setBlendFunction(GL20.GL_SRC_ALPHA, GL20.GL_ONE); - this.batch.draw(this.activeButtonTexture, 1225 + (70 * commandCardIcon.getX()), - 160 - (70 * commandCardIcon.getY()), 64, 64); + this.batch.draw(this.activeButtonTexture, 1235 + (86.8f * commandCardIcon.getX()), + 190 - (88 * commandCardIcon.getY()), 78f, 78f); this.batch.setBlendFunction(blendSrcFunc, blendDstFunc); } } - - this.batch.draw(this.solidGreenTexture, 413, 34, 122 * (this.selectedUnit.getSimulationUnit().getLife() - / this.selectedUnit.getSimulationUnit().getMaximumLife()), 7); - } for (final RenderUnit unit : this.viewer.units) { if (unit.playerIndex >= WarsmashConstants.MAX_PLAYERS) { @@ -387,9 +409,11 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv public void resize(final int width, final int height) { super.resize(width, height); this.tempRect.x = 0; - this.tempRect.y = 0; this.tempRect.width = width; - this.tempRect.height = height; + final float topHeight = 0.02666f * height; + final float bottomHeight = 0.21333f * height; + this.tempRect.y = (int) bottomHeight; + this.tempRect.height = height - (int) (topHeight + bottomHeight); this.cameraManager.camera.viewport(this.tempRect); final float portraitTestWidth = (100 / 640f) * width; final float portraitTestHeight = (100 / 480f) * height; @@ -397,15 +421,21 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.uiViewport.update(width, height); this.uiCamera.position.set(this.uiCamera.viewportWidth / 2, this.uiCamera.viewportHeight / 2, 0); - positionPortrait(); + this.tempRect.x = this.uiViewport.getScreenX(); + this.tempRect.y = this.uiViewport.getScreenY(); + this.tempRect.width = this.uiViewport.getScreenWidth(); + this.tempRect.height = this.uiViewport.getScreenHeight(); + this.uiScene.camera.viewport(this.tempRect); + this.uiScene.camera.ortho(0f, 0.8f, 0f, 0.6f, -1f, 1); + positionPortrait(); } private void positionPortrait() { - this.projectionTemp1.x = 385; - this.projectionTemp1.y = 0; - this.projectionTemp2.x = 385 + 180; - this.projectionTemp2.y = 177; + this.projectionTemp1.x = 422; + this.projectionTemp1.y = 57; + this.projectionTemp2.x = 422 + 167; + this.projectionTemp2.y = 57 + 170; this.uiViewport.project(this.projectionTemp1); this.uiViewport.project(this.projectionTemp2); @@ -492,7 +522,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv WarsmashGdxMapGame.this.cameraPositionTemp[1], WarsmashGdxMapGame.this.cameraPositionTemp[2]); this.target.add(WarsmashGdxMapGame.this.cameraTargetTemp[0], WarsmashGdxMapGame.this.cameraTargetTemp[1], WarsmashGdxMapGame.this.cameraTargetTemp[2]); - this.camera.perspective(this.modelCamera.fieldOfView, this.camera.getAspect(), + this.camera.perspective(this.modelCamera.fieldOfView * 0.75f, this.camera.getAspect(), this.modelCamera.nearClippingPlane, this.modelCamera.farClippingPlane); } @@ -509,6 +539,8 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv private Scene portraitScene; private Texture minimapTexture; private Rectangle talentTreeWindow; + private GameUI gameUI; + private Scene uiScene; @Override public boolean keyDown(final int keycode) { @@ -551,6 +583,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv @Override public boolean touchDown(final int screenX, final int screenY, final int pointer, final int button) { + final float worldScreenY = getHeight() - screenY; System.out.println(screenX + "," + screenY); clickLocationTemp2.x = screenX; @@ -559,8 +592,8 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv if (this.selectedUnit != null) { for (final CommandCardIcon commandCardIcon : this.selectedUnit.getCommandCardIcons()) { - if (new Rectangle(1225 + (70 * commandCardIcon.getX()), 160 - (70 * commandCardIcon.getY()), 64, 64) - .contains(clickLocationTemp2)) { + if (new Rectangle(1235 + (86.8f * commandCardIcon.getX()), 190 - (88 * commandCardIcon.getY()), 78f, + 78f).contains(clickLocationTemp2)) { if (button == Input.Buttons.RIGHT) { this.messages.add(new Message(Gdx.input.getCurrentEventTime(), "Right mouse click")); } @@ -581,14 +614,14 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv return false; } if (button == Input.Buttons.RIGHT) { - final RenderUnit rayPickUnit = this.viewer.rayPickUnit(screenX, screenY); + final RenderUnit rayPickUnit = this.viewer.rayPickUnit(screenX, worldScreenY); if ((rayPickUnit != null) && (rayPickUnit.playerIndex != this.selectedUnit.playerIndex)) { if (this.viewer.orderSmart(rayPickUnit)) { StandSequence.randomPortraitTalkSequence(this.portraitInstance); } } else { - this.viewer.getClickLocation(clickLocationTemp, screenX, screenY); + this.viewer.getClickLocation(clickLocationTemp, screenX, (int) worldScreenY); System.out.println(clickLocationTemp); this.viewer.showConfirmation(clickLocationTemp, 0, 1, 0); final int x = (int) ((clickLocationTemp.x - this.viewer.terrain.centerOffset[0]) / 128); @@ -601,7 +634,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv } } else { - final List selectedUnits = this.viewer.selectUnit(screenX, screenY, false); + final List selectedUnits = this.viewer.selectUnit(screenX, worldScreenY, false); if (!selectedUnits.isEmpty()) { final RenderUnit unit = selectedUnits.get(0); this.selectedUnit = unit; diff --git a/core/src/com/etheller/warsmash/gameui/FDFGameUI.java b/core/src/com/etheller/warsmash/gameui/FDFGameUI.java new file mode 100644 index 0000000..cad3521 --- /dev/null +++ b/core/src/com/etheller/warsmash/gameui/FDFGameUI.java @@ -0,0 +1,11 @@ +package com.etheller.warsmash.gameui; + +import com.etheller.warsmash.parsers.fdf.datamodel.FrameTemplateEnvironment; + +public class FDFGameUI { + private final FrameTemplateEnvironment frameTemplateEnvironment; + + public FDFGameUI(final FrameTemplateEnvironment frameTemplateEnvironment) { + this.frameTemplateEnvironment = frameTemplateEnvironment; + } +} diff --git a/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java new file mode 100644 index 0000000..76baf89 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java @@ -0,0 +1,200 @@ +package com.etheller.warsmash.parsers.fdf; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.HashMap; +import java.util.Map; + +import com.badlogic.gdx.graphics.Texture; +import com.badlogic.gdx.graphics.g2d.TextureRegion; +import com.badlogic.gdx.utils.viewport.Viewport; +import com.etheller.warsmash.datasources.DataSource; +import com.etheller.warsmash.fdfparser.FDFParser; +import com.etheller.warsmash.fdfparser.FrameDefinitionVisitor; +import com.etheller.warsmash.parsers.fdf.datamodel.AnchorDefinition; +import com.etheller.warsmash.parsers.fdf.datamodel.FrameClass; +import com.etheller.warsmash.parsers.fdf.datamodel.FrameDefinition; +import com.etheller.warsmash.parsers.fdf.datamodel.FrameTemplateEnvironment; +import com.etheller.warsmash.parsers.fdf.datamodel.Vector4Definition; +import com.etheller.warsmash.parsers.fdf.frames.AbstractUIFrame; +import com.etheller.warsmash.parsers.fdf.frames.SimpleFrame; +import com.etheller.warsmash.parsers.fdf.frames.TextureFrame; +import com.etheller.warsmash.parsers.fdf.frames.UIFrame; +import com.etheller.warsmash.units.DataTable; +import com.etheller.warsmash.units.Element; +import com.etheller.warsmash.util.ImageUtils; +import com.etheller.warsmash.util.StringBundle; + +public final class GameUI extends AbstractUIFrame implements UIFrame { + + private final DataSource dataSource; + private final Element skin; + private final Viewport viewport; + private final FrameTemplateEnvironment templates; + private final Map pathToTexture = new HashMap<>(); + private final boolean autoPosition = false; + + public GameUI(final DataSource dataSource, final Element skin, final Viewport viewport) { + super("GameUI", null); + this.dataSource = dataSource; + this.skin = skin; + this.viewport = viewport; + this.renderBounds.set(0, 0, viewport.getWorldWidth(), viewport.getWorldHeight()); + this.templates = new FrameTemplateEnvironment(); + + } + + public static Element loadSkin(final DataSource dataSource, final String skin) { + final DataTable skinsTable = new DataTable(StringBundle.EMPTY); + try (InputStream stream = dataSource.getResourceAsStream("UI\\war3skins.txt")) { + skinsTable.readTXT(stream, true); + } + catch (final IOException e) { + throw new RuntimeException(e); + } +// final Element main = skinsTable.get("Main"); +// final String skinsField = main.getField("Skins"); +// final String[] skins = skinsField.split(","); + final Element defaultSkin = skinsTable.get("Default"); + final Element userSkin = skinsTable.get(skin); + for (final String key : defaultSkin.keySet()) { + if (!userSkin.hasField(key)) { + userSkin.setField(key, defaultSkin.getField(key)); + } + } + return userSkin; + } + + public void loadTOCFile(final String tocFilePath) throws IOException { + final DataSourceFDFParserBuilder dataSourceFDFParserBuilder = new DataSourceFDFParserBuilder(this.dataSource); + final FrameDefinitionVisitor fdfVisitor = new FrameDefinitionVisitor(this.templates, + dataSourceFDFParserBuilder); + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(this.dataSource.getResourceAsStream(tocFilePath)))) { + String line; + int tocLines = 0; + while ((line = reader.readLine()) != null) { + final FDFParser firstFileParser = dataSourceFDFParserBuilder.build(line); + fdfVisitor.visit(firstFileParser.program()); + tocLines++; + } + System.out.println("TOC file loaded " + tocLines + " lines"); + } + } + + public UIFrame createFrame(final String name, final UIFrame owner, final int priority, final int createContext) { + throw new UnsupportedOperationException("Not yet implemented"); + } + + public UIFrame createSimpleFrame(final String name, final UIFrame owner, final int createContext) { + final FrameDefinition frameDefinition = this.templates.getFrame(name); + if (frameDefinition.getFrameClass() == FrameClass.Frame) { + if ("SIMPLEFRAME".equals(frameDefinition.getFrameType())) { + final UIFrame inflated = inflate(frameDefinition, owner, null); + if (this.autoPosition) { + inflated.positionBounds(this.viewport); + } + add(inflated); + return inflated; + } + } + return null; + } + + public UIFrame inflate(final FrameDefinition frameDefinition, final UIFrame parent, + final FrameDefinition parentDefinitionIfAvailable) { + UIFrame inflatedFrame = null; + switch (frameDefinition.getFrameClass()) { + case Frame: + if ("SIMPLEFRAME".equals(frameDefinition.getFrameType())) { + final SimpleFrame simpleFrame = new SimpleFrame(frameDefinition.getName(), parent); + for (final FrameDefinition childDefinition : frameDefinition.getInnerFrames()) { + simpleFrame.add(inflate(childDefinition, simpleFrame, frameDefinition)); + } + inflatedFrame = simpleFrame; + } + break; + case Layer: + // NOT HANDLED YET + break; + case String: + break; + case Texture: + String file = frameDefinition.getString("File"); + if (frameDefinition.has("DecorateFileNames") || ((parentDefinitionIfAvailable != null) + && parentDefinitionIfAvailable.has("DecorateFileNames"))) { + if (this.skin.hasField(file)) { + file = this.skin.getField(file); + } + else { + throw new IllegalStateException("Decorated file name lookup not available: " + file); + } + } + final Texture texture = loadTexture(file); + final Vector4Definition texCoord = frameDefinition.getVector4("TexCoord"); + final TextureRegion texRegion; + if (texCoord != null) { + texRegion = new TextureRegion(texture, texCoord.getX(), texCoord.getZ(), texCoord.getY(), + texCoord.getW()); + } + else { + texRegion = new TextureRegion(texture); + } + final TextureFrame textureFrame = new TextureFrame(frameDefinition.getName(), parent, texRegion); + inflatedFrame = textureFrame; + break; + default: + break; + } + if (inflatedFrame != null) { + final Float width = frameDefinition.getFloat("Width"); + if (width != null) { + inflatedFrame.setWidth(convertX(this.viewport, width)); + } + final Float height = frameDefinition.getFloat("Height"); + if (height != null) { + inflatedFrame.setHeight(convertY(this.viewport, height)); + } + for (final AnchorDefinition anchor : frameDefinition.getAnchors()) { + inflatedFrame.addAnchor(new AnchorDefinition(anchor.getMyPoint(), + convertX(this.viewport, anchor.getX()), convertY(this.viewport, anchor.getY()))); + } + } + else { + // TODO in production throw some kind of exception here + } + return inflatedFrame; + } + + public UIFrame createFrameByType(final String typeName, final String name, final UIFrame owner, + final String inherits, final int createContext) { + throw new UnsupportedOperationException("Not yet implemented"); + } + + public static float convertX(final Viewport viewport, final float fdfX) { + return (fdfX / 0.8f) * viewport.getWorldWidth(); + } + + public static float convertY(final Viewport viewport, final float fdfY) { + return (fdfY / 0.6f) * viewport.getWorldHeight(); + } + + private Texture loadTexture(String path) { + if (!path.contains(".")) { + path = path + ".blp"; + } + Texture texture = this.pathToTexture.get(path); + if (texture == null) { + texture = ImageUtils.getBLPTexture(this.dataSource, path); + this.pathToTexture.put(path, texture); + } + return texture; + } + + @Override + public final void positionBounds(final Viewport viewport) { + innerPositionBounds(viewport); + } +} diff --git a/core/src/com/etheller/warsmash/parsers/fdf/ModelExport.java b/core/src/com/etheller/warsmash/parsers/fdf/ModelExport.java new file mode 100644 index 0000000..6967bff --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/fdf/ModelExport.java @@ -0,0 +1,37 @@ +package com.etheller.warsmash.parsers.fdf; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; + +import com.etheller.warsmash.datasources.CompoundDataSourceDescriptor; +import com.etheller.warsmash.datasources.DataSource; +import com.etheller.warsmash.datasources.DataSourceDescriptor; +import com.etheller.warsmash.datasources.FolderDataSourceDescriptor; +import com.etheller.warsmash.parsers.mdlx.MdlxModel; + +public class ModelExport { + + public static void main(final String[] args) { + + final FolderDataSourceDescriptor war3mpq = new FolderDataSourceDescriptor("E:\\Backups\\Warcraft\\Data\\127"); + final FolderDataSourceDescriptor testingFolder = new FolderDataSourceDescriptor("E:\\Backups\\Warsmash\\Data"); + final FolderDataSourceDescriptor currentFolder = new FolderDataSourceDescriptor("."); + final DataSource dataSource = new CompoundDataSourceDescriptor( + Arrays.asList(war3mpq, testingFolder, currentFolder)).createDataSource(); + + try (InputStream modelStream = dataSource + .getResourceAsStream("UI\\Glues\\MainMenu\\MainMenu3D\\MainMenu3D.mdx")) { + final MdlxModel model = new MdlxModel(modelStream); + try (FileOutputStream fos = new FileOutputStream(new File("C:\\Temp\\MainMenu3D.mdl"))) { + model.saveMdl(fos); + } + } + catch (final IOException e) { + e.printStackTrace(); + } + } + +} diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java new file mode 100644 index 0000000..65e1aae --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java @@ -0,0 +1,291 @@ +package com.etheller.warsmash.parsers.fdf.frames; + +import java.util.ArrayList; +import java.util.List; + +import com.badlogic.gdx.math.Rectangle; +import com.badlogic.gdx.utils.viewport.Viewport; +import com.etheller.warsmash.parsers.fdf.datamodel.AnchorDefinition; +import com.etheller.warsmash.parsers.fdf.datamodel.FramePoint; + +public abstract class AbstractRenderableFrame implements UIFrame { + protected String name; + protected UIFrame parent; + protected boolean visible; + protected int level; + protected final Rectangle renderBounds = new Rectangle(0, 0, 0, 0); // in libgdx rendering space + protected List anchors = new ArrayList<>(); + + public AbstractRenderableFrame(final String name, final UIFrame parent) { + this.name = name; + this.parent = parent; + } + + public void setWidth(final float width) { + this.renderBounds.width = width; + } + + public void setHeight(final float height) { + this.renderBounds.height = height; + } + + private boolean hasLeftAnchor() { + for (final AnchorDefinition anchor : this.anchors) { + switch (anchor.getMyPoint()) { + case CENTER: + case BOTTOM: + case TOP: + case BOTTOMRIGHT: + case RIGHT: + case TOPRIGHT: + break; + case BOTTOMLEFT: + case LEFT: + case TOPLEFT: + return true; + default: + break; + } + } + return false; + } + + private boolean hasRightAnchor() { + for (final AnchorDefinition anchor : this.anchors) { + switch (anchor.getMyPoint()) { + case CENTER: + case BOTTOM: + case TOP: + case BOTTOMLEFT: + case LEFT: + case TOPLEFT: + break; + case BOTTOMRIGHT: + case RIGHT: + case TOPRIGHT: + return true; + default: + break; + } + } + return false; + } + + private boolean hasTopAnchor() { + for (final AnchorDefinition anchor : this.anchors) { + switch (anchor.getMyPoint()) { + case CENTER: + case BOTTOM: + case BOTTOMLEFT: + case LEFT: + case BOTTOMRIGHT: + case RIGHT: + break; + case TOP: + case TOPLEFT: + case TOPRIGHT: + return true; + default: + break; + } + } + return false; + } + + private boolean hasBottomAnchor() { + for (final AnchorDefinition anchor : this.anchors) { + switch (anchor.getMyPoint()) { + case CENTER: + case LEFT: + case RIGHT: + case TOP: + case TOPLEFT: + case TOPRIGHT: + break; + case BOTTOM: + case BOTTOMLEFT: + case BOTTOMRIGHT: + return true; + default: + break; + } + } + return false; + } + + @Override + public float getFramePointX(final FramePoint framePoint) { + switch (framePoint) { + case CENTER: + case BOTTOM: + case TOP: + return this.renderBounds.x + (this.renderBounds.width / 2); + case BOTTOMLEFT: + case LEFT: + case TOPLEFT: + return this.renderBounds.x; + case BOTTOMRIGHT: + case RIGHT: + case TOPRIGHT: + return this.renderBounds.x + this.renderBounds.width; + default: + return 0; + } + } + + public void setFramePointX(final FramePoint framePoint, final float x) { + if (this.renderBounds.width == 0) { + this.renderBounds.x = x; + return; + } + switch (framePoint) { + case CENTER: + case BOTTOM: + case TOP: + this.renderBounds.x = x - (this.renderBounds.width / 2); + return; + case BOTTOMLEFT: + case LEFT: + case TOPLEFT: + if (hasRightAnchor()) { + final float oldRightX = this.renderBounds.x + this.renderBounds.width; + this.renderBounds.x = x; + this.renderBounds.width = oldRightX - x; + } + else { + // no right anchor, keep width + this.renderBounds.x = x; + } + return; + case BOTTOMRIGHT: + case RIGHT: + case TOPRIGHT: + if (hasLeftAnchor()) { + this.renderBounds.width = x - this.renderBounds.x; + } + else { + this.renderBounds.x = x - this.renderBounds.width; + } + return; + default: + return; + } + } + + @Override + public float getFramePointY(final FramePoint framePoint) { + switch (framePoint) { + case LEFT: + case CENTER: + case RIGHT: + return this.renderBounds.y + (this.renderBounds.height / 2); + case BOTTOMLEFT: + case BOTTOM: + case BOTTOMRIGHT: + return this.renderBounds.y; + case TOPLEFT: + case TOP: + case TOPRIGHT: + return this.renderBounds.y + this.renderBounds.height; + default: + return 0; + } + } + + public void setFramePointY(final FramePoint framePoint, final float y) { + if (this.renderBounds.height == 0) { + this.renderBounds.y = y; + return; + } + switch (framePoint) { + case LEFT: + case CENTER: + case RIGHT: + this.renderBounds.y = y - (this.renderBounds.height / 2); + return; + case TOPLEFT: + case TOP: + case TOPRIGHT: + if (hasBottomAnchor()) { + this.renderBounds.height = y - this.renderBounds.y; + } + else { + this.renderBounds.y = y - this.renderBounds.height; + } + return; + case BOTTOMLEFT: + case BOTTOM: + case BOTTOMRIGHT: + if (hasTopAnchor()) { + final float oldBottomY = this.renderBounds.y + this.renderBounds.height; + this.renderBounds.y = y; + this.renderBounds.height = oldBottomY - y; + } + else { + this.renderBounds.y = y; + } + return; + default: + return; + } + } + + @Override + public void addAnchor(final AnchorDefinition anchorDefinition) { + this.anchors.add(anchorDefinition); + } + + @Override + public void positionBounds(final Viewport viewport) { + if (this.parent == null) { + // TODO this is a bit of a hack, remove later + return; + } + if ("ResourceBarFrame".equals(this.name)) { + System.out.println("doing resource bar"); + } + if (this.anchors.isEmpty()) { + this.renderBounds.x = this.parent.getFramePointX(FramePoint.LEFT); + this.renderBounds.y = this.parent.getFramePointY(FramePoint.BOTTOM); + } + else { + for (final AnchorDefinition anchor : this.anchors) { + final float parentPointX = this.parent.getFramePointX(anchor.getMyPoint()); + final float parentPointY = this.parent.getFramePointY(anchor.getMyPoint()); + setFramePointX(anchor.getMyPoint(), parentPointX + anchor.getX()); + setFramePointY(anchor.getMyPoint(), parentPointY + anchor.getY()); + System.out.println(getClass().getSimpleName() + ":" + this.name + " anchoring to: " + anchor); + } + } + if (this.renderBounds.width == 0) { + this.renderBounds.width = this.parent.getFramePointX(FramePoint.RIGHT) + - this.parent.getFramePointX(FramePoint.LEFT); + } + if (this.renderBounds.height == 0) { + this.renderBounds.height = this.parent.getFramePointY(FramePoint.TOP) + - this.parent.getFramePointY(FramePoint.BOTTOM); + } + System.out.println( + getClass().getSimpleName() + ":" + this.name + " finishing position bounds: " + this.renderBounds); + innerPositionBounds(viewport); + } + + protected abstract void innerPositionBounds(final Viewport viewport); + + public boolean isVisible() { + return this.visible; + } + + public int getLevel() { + return this.level; + } + + public void setVisible(final boolean visible) { + this.visible = visible; + } + + public void setLevel(final int level) { + this.level = level; + } + +} diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractUIFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractUIFrame.java new file mode 100644 index 0000000..a203fdb --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractUIFrame.java @@ -0,0 +1,36 @@ +package com.etheller.warsmash.parsers.fdf.frames; + +import java.util.ArrayList; +import java.util.List; + +import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.badlogic.gdx.utils.viewport.Viewport; + +public abstract class AbstractUIFrame extends AbstractRenderableFrame implements UIFrame { + private final List childFrames = new ArrayList<>(); + + public void add(final UIFrame childFrame) { + if (childFrame == null) { + return; + } + this.childFrames.add(childFrame); + } + + public AbstractUIFrame(final String name, final UIFrame parent) { + super(name, parent); + } + + @Override + public void render(final SpriteBatch batch) { + for (final UIFrame childFrame : this.childFrames) { + childFrame.render(batch); + } + } + + @Override + protected void innerPositionBounds(final Viewport viewport) { + for (final UIFrame childFrame : this.childFrames) { + childFrame.positionBounds(viewport); + } + } +} diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/SimpleFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/SimpleFrame.java new file mode 100644 index 0000000..b90dd09 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/SimpleFrame.java @@ -0,0 +1,9 @@ +package com.etheller.warsmash.parsers.fdf.frames; + +public class SimpleFrame extends AbstractUIFrame { + + public SimpleFrame(final String name, final UIFrame parent) { + super(name, parent); + } + +} diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/TextureFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/TextureFrame.java new file mode 100644 index 0000000..65b291b --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/TextureFrame.java @@ -0,0 +1,25 @@ +package com.etheller.warsmash.parsers.fdf.frames; + +import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.badlogic.gdx.graphics.g2d.TextureRegion; +import com.badlogic.gdx.utils.viewport.Viewport; + +public class TextureFrame extends AbstractRenderableFrame { + private final TextureRegion texture; + + public TextureFrame(final String name, final UIFrame parent, final TextureRegion texture) { + super(name, parent); + this.texture = texture; + } + + @Override + public void render(final SpriteBatch batch) { + batch.draw(this.texture, this.renderBounds.x, this.renderBounds.y, this.renderBounds.width, + this.renderBounds.height); + } + + @Override + protected void innerPositionBounds(final Viewport viewport) { + } + +} diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/UIFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/UIFrame.java new file mode 100644 index 0000000..8f20224 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/UIFrame.java @@ -0,0 +1,22 @@ +package com.etheller.warsmash.parsers.fdf.frames; + +import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.badlogic.gdx.utils.viewport.Viewport; +import com.etheller.warsmash.parsers.fdf.datamodel.AnchorDefinition; +import com.etheller.warsmash.parsers.fdf.datamodel.FramePoint; + +public interface UIFrame { + public void render(SpriteBatch batch); + + public float getFramePointX(FramePoint framePoint); + + public float getFramePointY(FramePoint framePoint); + + void positionBounds(final Viewport viewport); + + void addAnchor(final AnchorDefinition anchorDefinition); + + void setWidth(final float width); + + void setHeight(final float height); +} diff --git a/core/src/com/etheller/warsmash/parsers/jass/JassTest.java b/core/src/com/etheller/warsmash/parsers/jass/JassTest.java new file mode 100644 index 0000000..629dd51 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/jass/JassTest.java @@ -0,0 +1,64 @@ +package com.etheller.warsmash.parsers.jass; + +import java.io.IOException; +import java.util.Arrays; + +import org.antlr.v4.runtime.BaseErrorListener; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.RecognitionException; +import org.antlr.v4.runtime.Recognizer; + +import com.etheller.interpreter.JassLexer; +import com.etheller.interpreter.JassParser; +import com.etheller.interpreter.ast.visitors.JassProgramVisitor; +import com.etheller.warsmash.datasources.CompoundDataSourceDescriptor; +import com.etheller.warsmash.datasources.DataSource; +import com.etheller.warsmash.datasources.DataSourceDescriptor; +import com.etheller.warsmash.datasources.FolderDataSourceDescriptor; + +public class JassTest { + public static final boolean REPORT_SYNTAX_ERRORS = true; + + public static void main(final String[] args) { + final JassProgramVisitor jassProgramVisitor = new JassProgramVisitor(); + try { + final FolderDataSourceDescriptor war3mpq = new FolderDataSourceDescriptor( + "E:\\Backups\\Warcraft\\Data\\127"); + final FolderDataSourceDescriptor testingFolder = new FolderDataSourceDescriptor( + "E:\\Backups\\Warsmash\\Data"); + final FolderDataSourceDescriptor currentFolder = new FolderDataSourceDescriptor("."); + final DataSource dataSource = new CompoundDataSourceDescriptor( + Arrays.asList(war3mpq, testingFolder, currentFolder)).createDataSource(); + JassLexer lexer; + try { + lexer = new JassLexer(CharStreams.fromStream(dataSource.getResourceAsStream("Scripts\\common.j"))); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + final JassParser parser = new JassParser(new CommonTokenStream(lexer)); + parser.addErrorListener(new BaseErrorListener() { + @Override + public void syntaxError(final Recognizer recognizer, final Object offendingSymbol, final int line, + final int charPositionInLine, final String msg, final RecognitionException e) { + if (!REPORT_SYNTAX_ERRORS) { + return; + } + + String sourceName = recognizer.getInputStream().getSourceName(); + if (!sourceName.isEmpty()) { + sourceName = String.format("%s:%d:%d: ", sourceName, line, charPositionInLine); + } + + System.err.println(sourceName + "line " + line + ":" + charPositionInLine + " " + msg); + } + }); + jassProgramVisitor.visit(parser.program()); + } + catch (final Exception e) { + e.printStackTrace(); + } + } + +} diff --git a/core/src/com/etheller/warsmash/units/DataTable.java b/core/src/com/etheller/warsmash/units/DataTable.java index ae85a0c..16e8919 100644 --- a/core/src/com/etheller/warsmash/units/DataTable.java +++ b/core/src/com/etheller/warsmash/units/DataTable.java @@ -11,16 +11,16 @@ import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; -import com.etheller.warsmash.util.WorldEditStrings; +import com.etheller.warsmash.util.StringBundle; public class DataTable implements ObjectData { private static final boolean DEBUG = false; Map dataTable = new LinkedHashMap<>(); - private final WorldEditStrings worldEditStrings; + private final StringBundle worldEditStrings; - public DataTable(final WorldEditStrings worldEditStrings) { + public DataTable(final StringBundle worldEditStrings) { this.worldEditStrings = worldEditStrings; } diff --git a/core/src/com/etheller/warsmash/util/RenderMathUtils.java b/core/src/com/etheller/warsmash/util/RenderMathUtils.java index 0232076..6c554fd 100644 --- a/core/src/com/etheller/warsmash/util/RenderMathUtils.java +++ b/core/src/com/etheller/warsmash/util/RenderMathUtils.java @@ -219,10 +219,10 @@ public enum RenderMathUtils { } public static void unpackPlanes(final Vector4[] planes, final Matrix4 m) { - final float a00 = m.val[Matrix4.M00], a01 = m.val[Matrix4.M10], a02 = m.val[Matrix4.M20], - a03 = m.val[Matrix4.M30], a10 = m.val[Matrix4.M01], a11 = m.val[Matrix4.M11], a12 = m.val[Matrix4.M21], - a13 = m.val[Matrix4.M31], a20 = m.val[Matrix4.M02], a21 = m.val[Matrix4.M12], a22 = m.val[Matrix4.M22], - a23 = m.val[Matrix4.M32], a30 = m.val[Matrix4.M03], a31 = m.val[Matrix4.M13], a32 = m.val[Matrix4.M23], + final float a00 = m.val[Matrix4.M00], a01 = m.val[Matrix4.M01], a02 = m.val[Matrix4.M02], + a03 = m.val[Matrix4.M03], a10 = m.val[Matrix4.M10], a11 = m.val[Matrix4.M11], a12 = m.val[Matrix4.M12], + a13 = m.val[Matrix4.M13], a20 = m.val[Matrix4.M20], a21 = m.val[Matrix4.M21], a22 = m.val[Matrix4.M22], + a23 = m.val[Matrix4.M23], a30 = m.val[Matrix4.M30], a31 = m.val[Matrix4.M31], a32 = m.val[Matrix4.M32], a33 = m.val[Matrix4.M33]; // Left clipping plane @@ -293,7 +293,7 @@ public enum RenderMathUtils { public static Vector3 unproject(final Vector3 out, final Vector3 v, final Matrix4 inverseMatrix, final Rectangle viewport) { final float x = ((2 * (v.x - viewport.x)) / viewport.width) - 1; - final float y = 1 - ((2 * (v.y - viewport.y)) / viewport.height); + final float y = ((2 * (v.y - viewport.y)) / viewport.height) - 1; final float z = (2 * v.z) - 1; heap.set(x, y, z, 1); @@ -481,6 +481,23 @@ public enum RenderMathUtils { return wrapper; } + public static IntBuffer wrap(final int[] positions) { + final IntBuffer wrapper = ByteBuffer.allocateDirect(positions.length * 4).order(ByteOrder.nativeOrder()) + .asIntBuffer(); + wrapper.put(positions); + wrapper.clear(); + return wrapper; + } + + public static ByteBuffer wrapAsBytes(final int[] positions) { + final ByteBuffer wrapper = ByteBuffer.allocateDirect(positions.length).order(ByteOrder.nativeOrder()); + for (final int face : positions) { + wrapper.put((byte) face); + } + wrapper.clear(); + return wrapper; + } + public static Buffer wrap(final short[] cornerTextures) { final ByteBuffer wrapper = ByteBuffer.allocateDirect(cornerTextures.length).order(ByteOrder.nativeOrder()); for (final short face : cornerTextures) { diff --git a/core/src/com/etheller/warsmash/util/StringBundle.java b/core/src/com/etheller/warsmash/util/StringBundle.java new file mode 100644 index 0000000..d9d1150 --- /dev/null +++ b/core/src/com/etheller/warsmash/util/StringBundle.java @@ -0,0 +1,19 @@ +package com.etheller.warsmash.util; + +public interface StringBundle { + String getString(String string); + + String getStringCaseSensitive(final String key); + + StringBundle EMPTY = new StringBundle() { + @Override + public String getStringCaseSensitive(final String key) { + return key; + } + + @Override + public String getString(final String string) { + return string; + } + }; +} diff --git a/core/src/com/etheller/warsmash/util/WorldEditStrings.java b/core/src/com/etheller/warsmash/util/WorldEditStrings.java index bc8eb41..8347d2c 100644 --- a/core/src/com/etheller/warsmash/util/WorldEditStrings.java +++ b/core/src/com/etheller/warsmash/util/WorldEditStrings.java @@ -9,7 +9,7 @@ import java.util.ResourceBundle; import com.etheller.warsmash.datasources.DataSource; -public class WorldEditStrings { +public class WorldEditStrings implements StringBundle { private ResourceBundle bundle; private ResourceBundle bundlegs; @@ -30,6 +30,7 @@ public class WorldEditStrings { } } + @Override public String getString(String string) { try { while (string.toUpperCase().startsWith("WESTRING")) { @@ -60,6 +61,7 @@ public class WorldEditStrings { } } + @Override public String getStringCaseSensitive(final String key) { try { return this.bundle.getString(key); diff --git a/core/src/com/etheller/warsmash/viewer5/Grid.java b/core/src/com/etheller/warsmash/viewer5/Grid.java index 3ac16c1..63364d1 100644 --- a/core/src/com/etheller/warsmash/viewer5/Grid.java +++ b/core/src/com/etheller/warsmash/viewer5/Grid.java @@ -72,10 +72,10 @@ public class Grid { } } - public void moved(final ModelInstance instance) { + public void moved(final ModelInstance instance, final float upcomingX, final float upcomingY) { final Bounds bounds = instance.model.bounds; - final float x = (instance.worldLocation.x + bounds.x) - this.x; - final float y = (instance.worldLocation.y + bounds.y) - this.y; + final float x = (upcomingX + bounds.x) - this.x; + final float y = (upcomingY + bounds.y) - this.y; final float r = bounds.r; final Vector3 s = instance.worldScale; int left = (int) (Math.floor((x - (r * s.x)) / this.cellWidth)); diff --git a/core/src/com/etheller/warsmash/viewer5/GridCell.java b/core/src/com/etheller/warsmash/viewer5/GridCell.java index 1b82ff7..10b4062 100644 --- a/core/src/com/etheller/warsmash/viewer5/GridCell.java +++ b/core/src/com/etheller/warsmash/viewer5/GridCell.java @@ -40,6 +40,7 @@ public class GridCell { if (true) { return true; } + this.plane = RenderMathUtils.testCell(camera.planes, this.left, this.right, this.bottom, this.top, this.plane); return this.plane == -1; diff --git a/core/src/com/etheller/warsmash/viewer5/ModelInstance.java b/core/src/com/etheller/warsmash/viewer5/ModelInstance.java index 8d07713..839b820 100644 --- a/core/src/com/etheller/warsmash/viewer5/ModelInstance.java +++ b/core/src/com/etheller/warsmash/viewer5/ModelInstance.java @@ -89,17 +89,31 @@ public abstract class ModelInstance extends Node { super.recalculateTransformation(); if (this.scene != null) { - this.scene.grid.moved(this); + this.scene.grid.moved(this, this.worldLocation.x, this.worldLocation.y); } } public boolean isVisible(final Camera camera) { - if (true) { - return true; + // can't just use world location if it moves + float x, y, z; + if (this.dirty) { + // TODO this is an incorrect, predicted location for dirty case + if ((this.parent != null) && !this.dontInheritTranslation) { + x = this.parent.localLocation.x + this.localLocation.x; + y = this.parent.localLocation.y + this.localLocation.y; + z = this.parent.localLocation.z + this.localLocation.z; + } + else { + x = this.localLocation.x; + y = this.localLocation.y; + z = this.localLocation.z; + } + } + else { + x = this.worldLocation.x; + y = this.worldLocation.y; + z = this.worldLocation.z; } - final float x = this.worldLocation.x; - final float y = this.worldLocation.y; - final float z = this.worldLocation.z; final Bounds bounds = this.model.bounds; final Vector4[] planes = camera.planes; diff --git a/core/src/com/etheller/warsmash/viewer5/Scene.java b/core/src/com/etheller/warsmash/viewer5/Scene.java index 76f1365..08f86fb 100644 --- a/core/src/com/etheller/warsmash/viewer5/Scene.java +++ b/core/src/com/etheller/warsmash/viewer5/Scene.java @@ -31,21 +31,18 @@ import com.etheller.warsmash.viewer5.handlers.w3x.DynamicShadowManager; * audio is always on in LibGDX generally. So we will probably simplify or skip * over those behaviors other than a boolean on/off toggle for audio. */ -public class Scene { +public abstract class Scene { public final ModelViewer viewer; public final Camera camera; - public Grid grid; - public int visibleCells; - public int visibleInstances; public int updatedParticles; public boolean audioEnabled; public AudioContext audioContext; public final List instances; - public final int currentInstance; + public int currentInstance; public final List batchedInstances; - public final int currentBatchedInstance; + public int currentBatchedInstance; public final EmittedObjectUpdater emitterObjectUpdater; public final Map batches; public final Comparator instanceDepthComparator; @@ -64,10 +61,7 @@ public class Scene { final CanvasProvider canvas = viewer.canvas; this.viewer = viewer; this.camera = new Camera(); - this.grid = new Grid(-100000, -100000, 200000, 200000, 200000, 200000); - this.visibleCells = 0; - this.visibleInstances = 0; this.updatedParticles = 0; this.audioEnabled = false; @@ -117,8 +111,7 @@ public class Scene { // Only allow instances that are actually ok to be added the scene. if (instance.model.ok) { - this.grid.moved(instance); - + instanceMoved(instance, instance.worldLocation.x, instance.worldLocation.y); return true; } } @@ -126,9 +119,11 @@ public class Scene { return false; } + public abstract void instanceMoved(ModelInstance instance, float x, float y); + public boolean removeInstance(final ModelInstance instance) { if (instance.scene == this) { - this.grid.remove(instance); + innerRemove(instance); instance.scene = null; this.instances.remove(instance); @@ -138,17 +133,9 @@ public class Scene { return false; } - public void clear() { - // First remove references to this scene stored in the instances. - for (final GridCell cell : this.grid.cells) { - for (final ModelInstance instance : cell.instances) { - instance.scene = null; - } - } + protected abstract void innerRemove(ModelInstance instance); - // Then remove references to the instances. - this.grid.clear(); - } + public abstract void clear(); public boolean detach() { if (this.viewer != null) { @@ -191,54 +178,10 @@ public class Scene { final int frame = this.viewer.frame; - int currentInstance = 0; - int currentBatchedInstance = 0; + final int currentInstance = 0; + final int currentBatchedInstance = 0; - this.visibleCells = 0; - this.visibleInstances = 0; - - // Update and collect all of the visible instances. - for (final GridCell cell : this.grid.cells) { - if (cell.isVisible(this.camera)) { - this.visibleCells += 1; - - for (final ModelInstance instance : new ArrayList<>(cell.instances)) { -// final ModelInstance instance = cell.instances.get(i); - if (instance.rendered && (instance.cullFrame < frame) && instance.isVisible(this.camera)) { - instance.cullFrame = frame; - - if (instance.updateFrame < frame) { - instance.update(dt, this); - if (!instance.rendered) { - // it became hidden while it updated - continue; - } - } - - if (instance.isBatched()) { - if (currentBatchedInstance < this.batchedInstances.size()) { - this.batchedInstances.set(currentBatchedInstance++, instance); - } - else { - this.batchedInstances.add(instance); - currentBatchedInstance++; - } - } - else { - if (currentInstance < this.instances.size()) { - this.instances.set(currentInstance++, instance); - } - else { - this.instances.add(instance); - currentInstance++; - } - } - - this.visibleInstances += 1; - } - } - } - } + innerUpdate(dt, frame); for (int i = this.batchedInstances.size() - 1; i >= currentBatchedInstance; i--) { this.batchedInstances.remove(i); @@ -253,6 +196,8 @@ public class Scene { this.updatedParticles = this.emitterObjectUpdater.objects.size(); } + protected abstract void innerUpdate(float dt, int frame); + public void startFrame() { final GL20 gl = this.viewer.gl; final Rectangle viewport = this.camera.rect; diff --git a/core/src/com/etheller/warsmash/viewer5/SimpleScene.java b/core/src/com/etheller/warsmash/viewer5/SimpleScene.java new file mode 100644 index 0000000..735ca65 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/SimpleScene.java @@ -0,0 +1,73 @@ +package com.etheller.warsmash.viewer5; + +import java.util.ArrayList; +import java.util.List; + +public class SimpleScene extends Scene { + private final List allInstances = new ArrayList<>(); + + @Override + public void instanceMoved(final ModelInstance instance, final float x, final float y) { + if (instance.left == -1) { + instance.left = 0; + this.allInstances.add(instance); + } + } + + @Override + protected void innerRemove(final ModelInstance instance) { + this.allInstances.remove(instance); + instance.left = -1; + } + + @Override + public void clear() { + for (final ModelInstance instance : this.allInstances) { + instance.scene = null; + } + this.allInstances.clear(); + } + + @Override + protected void innerUpdate(final float dt, final int frame) { + + // Update and collect all of the visible instances. + for (final ModelInstance instance : new ArrayList<>(this.allInstances)) { + // Below: current SimpleScene is not checking instance visibility. + // It's meant to be simple. Low number of models. Render everything. + // Otherwise unit portraits bust + if (instance.rendered && (instance.cullFrame < frame) && instance.isVisible(this.camera)) { + instance.cullFrame = frame; + + if (instance.updateFrame < frame) { + instance.update(dt, this); + if (!instance.rendered) { + // it became hidden while it updated + continue; + } + } + + if (instance.isBatched()) { + if (this.currentBatchedInstance < this.batchedInstances.size()) { + this.batchedInstances.set(this.currentBatchedInstance++, instance); + } + else { + this.batchedInstances.add(instance); + this.currentBatchedInstance++; + } + } + else { + if (this.currentInstance < this.instances.size()) { + this.instances.set(this.currentInstance++, instance); + } + else { + this.instances.add(instance); + this.currentInstance++; + } + } + + } + } + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/WorldScene.java b/core/src/com/etheller/warsmash/viewer5/WorldScene.java new file mode 100644 index 0000000..b2d6043 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/WorldScene.java @@ -0,0 +1,105 @@ +package com.etheller.warsmash.viewer5; + +import java.util.ArrayList; + +/** + * A scene. + * + * Every scene has its own list of model instances, and its own camera and + * viewport. + * + * In addition, in Ghostwolf's original code every scene may have its own + * AudioContext if enableAudio() is called. If audo is enabled, the + * AudioContext's listener's location will be updated automatically. Note that + * due to browser policies, this may be done only after user interaction with + * the web page. + * + * In "Warsmash", we are starting from an attempt to replicate Ghostwolf, but + * audio is always on in LibGDX generally. So we will probably simplify or skip + * over those behaviors other than a boolean on/off toggle for audio. + */ +public class WorldScene extends Scene { + + public Grid grid; + public int visibleCells; + public int visibleInstances; + + public WorldScene(final ModelViewer viewer) { + super(viewer); + this.grid = new Grid(-100000, -100000, 200000, 200000, 200000, 200000); + this.visibleCells = 0; + this.visibleInstances = 0; + } + + @Override + public void instanceMoved(final ModelInstance instance, final float x, final float y) { + this.grid.moved(instance, x, y); + } + + @Override + protected void innerRemove(final ModelInstance instance) { + this.grid.remove(instance); + } + + @Override + public void clear() { + // First remove references to this scene stored in the instances. + for (final GridCell cell : this.grid.cells) { + for (final ModelInstance instance : cell.instances) { + instance.scene = null; + } + } + + // Then remove references to the instances. + this.grid.clear(); + } + + @Override + protected void innerUpdate(final float dt, final int frame) { + this.visibleCells = 0; + this.visibleInstances = 0; + + // Update and collect all of the visible instances. + for (final GridCell cell : this.grid.cells) { + if (cell.isVisible(this.camera)) { + this.visibleCells += 1; + + for (final ModelInstance instance : new ArrayList<>(cell.instances)) { +// final ModelInstance instance = cell.instances.get(i); + if (instance.rendered && (instance.cullFrame < frame) && instance.isVisible(this.camera)) { + instance.cullFrame = frame; + + if (instance.updateFrame < frame) { + instance.update(dt, this); + if (!instance.rendered) { + // it became hidden while it updated + continue; + } + } + + if (instance.isBatched()) { + if (this.currentBatchedInstance < this.batchedInstances.size()) { + this.batchedInstances.set(this.currentBatchedInstance++, instance); + } + else { + this.batchedInstances.add(instance); + this.currentBatchedInstance++; + } + } + else { + if (this.currentInstance < this.instances.size()) { + this.instances.set(this.currentInstance++, instance); + } + else { + this.instances.add(instance); + this.currentInstance++; + } + } + + this.visibleInstances += 1; + } + } + } + } + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Geoset.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Geoset.java index 1ec84c0..b3975f1 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Geoset.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Geoset.java @@ -21,9 +21,13 @@ public class Geoset { public boolean hasAlphaAnim; public boolean hasColorAnim; public boolean hasObjectAnim; + private final int openGLSkinType; + private final int skinStride; + private final int boneCountOffsetBytes; public Geoset(final MdxModel model, final int index, final int positionOffset, final int normalOffset, - final int uvOffset, final int skinOffset, final int faceOffset, final int vertices, final int elements) { + final int uvOffset, final int skinOffset, final int faceOffset, final int vertices, final int elements, + final int openGLSkinType, final int skinStride, final int boneCountOffsetBytes) { this.model = model; this.index = index; this.positionOffset = positionOffset; @@ -33,6 +37,9 @@ public class Geoset { this.faceOffset = faceOffset; this.vertices = vertices; this.elements = elements; + this.openGLSkinType = openGLSkinType; + this.skinStride = skinStride; + this.boneCountOffsetBytes = boneCountOffsetBytes; for (final GeosetAnimation geosetAnimation : model.getGeosetAnimations()) { if (geosetAnimation.geosetId == index) { @@ -96,8 +103,9 @@ public class Geoset { shader.setVertexAttribute("a_position", 3, GL20.GL_FLOAT, false, 0, this.positionOffset); // shader.setVertexAttribute("a_normal", 3, GL20.GL_FLOAT, false, 0, this.normalOffset); shader.setVertexAttribute("a_uv", 2, GL20.GL_FLOAT, false, 0, this.uvOffset + (coordId * this.vertices * 8)); - shader.setVertexAttribute("a_bones", 4, GL20.GL_UNSIGNED_BYTE, false, 5, this.skinOffset); - shader.setVertexAttribute("a_boneNumber", 1, GL20.GL_UNSIGNED_BYTE, false, 5, this.skinOffset + 4); + shader.setVertexAttribute("a_bones", 4, this.openGLSkinType, false, this.skinStride, this.skinOffset); + shader.setVertexAttribute("a_boneNumber", 1, this.openGLSkinType, false, this.skinStride, + this.skinOffset + this.boneCountOffsetBytes); } public void bindExtended(final ShaderProgram shader, final int coordId) { @@ -105,9 +113,11 @@ public class Geoset { shader.setVertexAttribute("a_position", 3, GL20.GL_FLOAT, false, 0, this.positionOffset); shader.setVertexAttribute("a_normal", 3, GL20.GL_FLOAT, false, 0, this.normalOffset); shader.setVertexAttribute("a_uv", 2, GL20.GL_FLOAT, false, 0, this.uvOffset + (coordId * this.vertices * 8)); - shader.setVertexAttribute("a_bones", 4, GL20.GL_UNSIGNED_BYTE, false, 9, this.skinOffset); - shader.setVertexAttribute("a_extendedBones", 4, GL20.GL_UNSIGNED_BYTE, false, 9, this.skinOffset + 4); - shader.setVertexAttribute("a_boneNumber", 1, GL20.GL_UNSIGNED_BYTE, false, 9, this.skinOffset + 8); + shader.setVertexAttribute("a_bones", 4, this.openGLSkinType, false, this.skinStride, this.skinOffset); + shader.setVertexAttribute("a_extendedBones", 4, this.openGLSkinType, false, this.skinStride, + this.skinOffset + (this.boneCountOffsetBytes / 2)); + shader.setVertexAttribute("a_boneNumber", 1, this.openGLSkinType, false, this.skinStride, + this.skinOffset + this.boneCountOffsetBytes); } public void render() { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java index f3b22f8..8495a08 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java @@ -58,7 +58,7 @@ public class MdxModel extends com.etheller.warsmash.viewer5.Model { @Override public ModelInstance createInstance(final int type) { - if (type == 1) { + if ((type == 1) && false) { return new MdxSimpleInstance(this); } else { @@ -176,7 +176,7 @@ public class MdxModel extends com.etheller.warsmash.viewer5.Model { } // Geosets - SetupGeosets.setupGeosets(this, parser.getGeosets()); + SetupGeosets.setupGeosets(this, parser.getGeosets(), parser.getBones().size() >= 256); this.pivotPoints = parser.getPivotPoints(); @@ -344,4 +344,8 @@ public class MdxModel extends com.etheller.warsmash.viewer5.Model { return this.eventObjects; } + public List getBones() { + return this.bones; + } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SetupGeosets.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SetupGeosets.java index 31e473d..ad214d8 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SetupGeosets.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SetupGeosets.java @@ -12,8 +12,8 @@ public class SetupGeosets { private static final int EXTENDED_BATCH = 1; private static final int REFORGED_BATCH = 2; - public static void setupGeosets(final MdxModel model, - final List geosets) { + public static void setupGeosets(final MdxModel model, final List geosets, + final boolean bigNodeSpace) { if (geosets.size() > 0) { final GL20 gl = model.viewer.gl; int positionBytes = 0; @@ -23,6 +23,12 @@ public class SetupGeosets { int faceBytes = 0; final int[] batchTypes = new int[geosets.size()]; + final int extendedBatchStride = bigNodeSpace ? 36 : 9; + final int normalBatchStride = bigNodeSpace ? 20 : 5; + final int openGLSkinType = bigNodeSpace ? GL20.GL_UNSIGNED_INT : GL20.GL_UNSIGNED_BYTE; + final int normalBatchBoneCountOffsetBytes = bigNodeSpace ? 16 : 4; + final int extendedBatchBoneCountOffsetBytes = bigNodeSpace ? 32 : 8; + for (int i = 0, l = geosets.size(); i < l; i++) { final com.etheller.warsmash.parsers.mdlx.Geoset geoset = geosets.get(i); @@ -48,12 +54,12 @@ public class SetupGeosets { } if (biggestGroup > 4) { - skinBytes += vertices * 9; + skinBytes += vertices * extendedBatchStride; batchTypes[i] = EXTENDED_BATCH; } else { - skinBytes += vertices * 5; + skinBytes += vertices * normalBatchStride; batchTypes[i] = NORMAL_BATCH; } @@ -80,17 +86,31 @@ public class SetupGeosets { for (int i = 0, l = geosets.size(); i < l; i++) { final com.etheller.warsmash.parsers.mdlx.Geoset geoset = geosets.get(i); + final int batchType = batchTypes[i]; if (true /* geoset.lod == 0 */) { final float[] positions = geoset.getVertices(); final float[] normals = geoset.getNormals(); final float[][] uvSets = geoset.getUvSets(); final int[] faces = geoset.getFaces(); - byte[] skin = null; + int[] skin = null; final int vertices = geoset.getVertices().length / 3; - final int batchType = batchTypes[i]; + + int maxBones; + int skinStride; + int boneCountOffsetBytes; + if (batchType == EXTENDED_BATCH) { + maxBones = 8; + skinStride = extendedBatchStride; + boneCountOffsetBytes = extendedBatchBoneCountOffsetBytes; + } + else { + maxBones = 4; + skinStride = normalBatchStride; + boneCountOffsetBytes = normalBatchBoneCountOffsetBytes; + } if (batchType == REFORGED_BATCH) { - // skin = geoset.skin; + // skin = geoset.skin; // THIS IS NOT IMPLEMENTED } else { final long[] matrixIndices = geoset.getMatrixIndices(); @@ -102,12 +122,8 @@ public class SetupGeosets { // That being said, there are a few models with geosets that need more, for // example the Water Elemental. // These geosets use a different shader, which support up to 8 bones per vertex. - int maxBones = 4; - if (batchType == EXTENDED_BATCH) { - maxBones = 8; - } - skin = new byte[vertices * (maxBones + 1)]; + skin = new int[vertices * (maxBones + 1)]; // Slice the matrix groups for (final long size : geoset.getMatrixGroups()) { @@ -130,17 +146,18 @@ public class SetupGeosets { final int bones = Math.min(matrixGroup.length, maxBones); for (int j = 0; j < bones; j++) { - skin[offset + j] = (byte) (matrixGroup[j] + 1); // 1 is added to diffrentiate + skin[offset + j] = (int) (matrixGroup[j] + 1); // 1 is added to diffrentiate // between matrix 0, and no matrix. } - skin[offset + maxBones] = (byte) bones; + skin[offset + maxBones] = bones; } } } final Geoset vGeoset = new Geoset(model, model.getGeosets().size(), positionOffset, normalOffset, - uvOffset, skinOffset, faceOffset, vertices, faces.length); + uvOffset, skinOffset, faceOffset, vertices, faces.length, openGLSkinType, skinStride, + boneCountOffsetBytes); model.getGeosets().add(vGeoset); @@ -173,8 +190,9 @@ public class SetupGeosets { } // Skin. - gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, skinOffset, skin.length, RenderMathUtils.wrap(skin)); - skinOffset += skin.length * 1; + gl.glBufferSubData(GL20.GL_ARRAY_BUFFER, skinOffset, skin.length, + bigNodeSpace ? RenderMathUtils.wrap(skin) : RenderMathUtils.wrapAsBytes(skin)); + skinOffset += skin.length * (bigNodeSpace ? 4 : 1); // Faces. gl.glBufferSubData(GL20.GL_ELEMENT_ARRAY_BUFFER, faceOffset, faces.length, diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index d993d68..e18308c 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -533,7 +533,7 @@ public class War3MapViewer extends ModelViewer { if (path.toLowerCase().endsWith(".mdl") || path.toLowerCase().endsWith(".mdx")) { path = path.substring(0, path.length() - 4); } - if (row.readSLKTagInt("fileVerFlags") == 2) { + if ((row.readSLKTagInt("fileVerFlags") == 2) && this.dataSource.has(path + "_V1.mdx")) { path += "_V1"; } @@ -804,6 +804,7 @@ public class War3MapViewer extends ModelViewer { } public List selectUnit(final float x, final float y, final boolean toggle) { + System.out.println("world: " + x + "," + y); final float[] ray = rayHeap; mousePosHeap.set(x, y); this.worldScene.camera.screenToWorldRay(ray, mousePosHeap); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java index 46ade03..284a61b 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java @@ -1025,10 +1025,14 @@ public class Terrain { final int shadowSize = columns * rows; final byte[] shadowData = new byte[columns * rows]; if (this.viewer.mapMpq.has("war3map.shd")) { - final InputStream shadowSource = this.viewer.mapMpq.getResourceAsStream("war3map.shd"); - final byte[] buffer = IOUtils.toByteArray(shadowSource); + final byte[] buffer; + + try (final InputStream shadowSource = this.viewer.mapMpq.getResourceAsStream("war3map.shd")) { + buffer = IOUtils.toByteArray(shadowSource); + } + for (int i = 0; i < shadowSize; i++) { - shadowData[i] = (byte) (buffer[i] / 2); + shadowData[i] = (byte) ((buffer[i] & 0xFF) / 2f); } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackProjectile.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackProjectile.java index 3f92efd..f1091d0 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackProjectile.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackProjectile.java @@ -102,7 +102,7 @@ public class RenderAttackProjectile { this.modelInstance.setLocation(this.x, this.y, this.z); this.modelInstance.localRotation.setFromAxisRad(0, 0, 1, this.yaw); this.modelInstance.rotate(pitchHeap.setFromAxisRad(0, -1, 0, this.pitch)); - war3MapViewer.worldScene.grid.moved(this.modelInstance); + war3MapViewer.worldScene.grid.moved(this.modelInstance, this.x, this.y); final boolean everythingDone = this.simulationProjectile.isDone() && this.modelInstance.sequenceEnded; if (everythingDone) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java index 09b6ea3..88649fb 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java @@ -176,7 +176,7 @@ public class RenderUnit { } this.facing = (((this.facing + angleToAdd) % 360) + 360) % 360; this.instance.setLocalRotation(tempQuat.setFromAxis(RenderMathUtils.VEC3_UNIT_Z, this.facing)); - map.worldScene.grid.moved(this.instance); + map.worldScene.grid.moved(this.instance, this.location[0], this.location[1]); final MdxComplexInstance mdxComplexInstance = this.instance; final COrder currentOrder = this.simulationUnit.getCurrentOrder(); if (this.simulationUnit.getLife() <= 0) { diff --git a/fdfparser/.gitignore b/fdfparser/.gitignore new file mode 100644 index 0000000..84c048a --- /dev/null +++ b/fdfparser/.gitignore @@ -0,0 +1 @@ +/build/ diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/AnchorDefinition.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/AnchorDefinition.java index 7a27ec6..1385223 100644 --- a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/AnchorDefinition.java +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/AnchorDefinition.java @@ -22,4 +22,9 @@ public class AnchorDefinition { public float getY() { return this.y; } + + @Override + public String toString() { + return "AnchorDefinition [myPoint=" + this.myPoint + ", x=" + this.x + ", y=" + this.y + "]"; + } } diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FrameDefinition.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FrameDefinition.java index 1225719..b4f58c5 100644 --- a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FrameDefinition.java +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FrameDefinition.java @@ -8,6 +8,9 @@ import java.util.Map; import java.util.Set; import com.etheller.warsmash.parsers.fdf.datamodel.fields.FrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.visitor.GetFloatFieldVisitor; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.visitor.GetStringFieldVisitor; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.visitor.GetVector4FieldVisitor; /** * Pretty sure this is probably not how it works in-game but this silly @@ -58,6 +61,14 @@ public class FrameDefinition { this.flags.add(flag); } + public boolean has(final String flag) { + return this.flags.contains(flag); + } + + public FrameDefinitionField get(final String fieldName) { + return this.nameToField.get(fieldName); + } + @Override public String toString() { return "FrameDefinition [frameClass=" + this.frameClass + ", frameType=" + this.frameType + ", name=" @@ -65,4 +76,51 @@ public class FrameDefinition { + this.nameToField + ", setPoints=" + this.setPoints + ", anchors=" + this.anchors + "]"; } + public String getFrameType() { + return this.frameType; + } + + public String getName() { + return this.name; + } + + public FrameClass getFrameClass() { + return this.frameClass; + } + + public List getInnerFrames() { + return this.innerFrames; + } + + public List getAnchors() { + return this.anchors; + } + + public List getSetPoints() { + return this.setPoints; + } + + public String getString(final String id) { + final FrameDefinitionField frameDefinitionField = this.nameToField.get(id); + if (frameDefinitionField != null) { + return frameDefinitionField.visit(GetStringFieldVisitor.INSTANCE); + } + return null; + } + + public Float getFloat(final String id) { + final FrameDefinitionField frameDefinitionField = this.nameToField.get(id); + if (frameDefinitionField != null) { + return frameDefinitionField.visit(GetFloatFieldVisitor.INSTANCE); + } + return null; + } + + public Vector4Definition getVector4(final String id) { + final FrameDefinitionField frameDefinitionField = this.nameToField.get(id); + if (frameDefinitionField != null) { + return frameDefinitionField.visit(GetVector4FieldVisitor.INSTANCE); + } + return null; + } } diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/visitor/GetFloatFieldVisitor.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/visitor/GetFloatFieldVisitor.java new file mode 100644 index 0000000..38a3084 --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/visitor/GetFloatFieldVisitor.java @@ -0,0 +1,56 @@ +package com.etheller.warsmash.parsers.fdf.datamodel.fields.visitor; + +import com.etheller.warsmash.parsers.fdf.datamodel.fields.FloatFrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.FontFrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.FrameDefinitionFieldVisitor; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.StringFrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.StringPairFrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.TextJustifyFrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.Vector2FrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.Vector3FrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.Vector4FrameDefinitionField; + +public class GetFloatFieldVisitor implements FrameDefinitionFieldVisitor { + public static GetFloatFieldVisitor INSTANCE = new GetFloatFieldVisitor(); + + @Override + public Float accept(final StringFrameDefinitionField field) { + return null; + } + + @Override + public Float accept(final StringPairFrameDefinitionField field) { + return null; + } + + @Override + public Float accept(final FloatFrameDefinitionField field) { + return field.getValue(); + } + + @Override + public Float accept(final Vector3FrameDefinitionField field) { + return null; + } + + @Override + public Float accept(final Vector4FrameDefinitionField field) { + return null; + } + + @Override + public Float accept(final Vector2FrameDefinitionField field) { + return null; + } + + @Override + public Float accept(final FontFrameDefinitionField field) { + return null; + } + + @Override + public Float accept(final TextJustifyFrameDefinitionField field) { + return null; + } + +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/visitor/GetStringFieldVisitor.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/visitor/GetStringFieldVisitor.java new file mode 100644 index 0000000..b90b83d --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/visitor/GetStringFieldVisitor.java @@ -0,0 +1,56 @@ +package com.etheller.warsmash.parsers.fdf.datamodel.fields.visitor; + +import com.etheller.warsmash.parsers.fdf.datamodel.fields.FloatFrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.FontFrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.FrameDefinitionFieldVisitor; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.StringFrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.StringPairFrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.TextJustifyFrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.Vector2FrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.Vector3FrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.Vector4FrameDefinitionField; + +public class GetStringFieldVisitor implements FrameDefinitionFieldVisitor { + public static GetStringFieldVisitor INSTANCE = new GetStringFieldVisitor(); + + @Override + public String accept(final StringFrameDefinitionField field) { + return field.getValue(); + } + + @Override + public String accept(final StringPairFrameDefinitionField field) { + return null; + } + + @Override + public String accept(final FloatFrameDefinitionField field) { + return null; + } + + @Override + public String accept(final Vector3FrameDefinitionField field) { + return null; + } + + @Override + public String accept(final Vector4FrameDefinitionField field) { + return null; + } + + @Override + public String accept(final Vector2FrameDefinitionField field) { + return null; + } + + @Override + public String accept(final FontFrameDefinitionField field) { + return null; + } + + @Override + public String accept(final TextJustifyFrameDefinitionField field) { + return null; + } + +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/visitor/GetVector4FieldVisitor.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/visitor/GetVector4FieldVisitor.java new file mode 100644 index 0000000..7bc33c7 --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/visitor/GetVector4FieldVisitor.java @@ -0,0 +1,57 @@ +package com.etheller.warsmash.parsers.fdf.datamodel.fields.visitor; + +import com.etheller.warsmash.parsers.fdf.datamodel.Vector4Definition; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.FloatFrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.FontFrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.FrameDefinitionFieldVisitor; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.StringFrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.StringPairFrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.TextJustifyFrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.Vector2FrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.Vector3FrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.Vector4FrameDefinitionField; + +public class GetVector4FieldVisitor implements FrameDefinitionFieldVisitor { + public static GetVector4FieldVisitor INSTANCE = new GetVector4FieldVisitor(); + + @Override + public Vector4Definition accept(final StringFrameDefinitionField field) { + return null; + } + + @Override + public Vector4Definition accept(final StringPairFrameDefinitionField field) { + return null; + } + + @Override + public Vector4Definition accept(final FloatFrameDefinitionField field) { + return null; + } + + @Override + public Vector4Definition accept(final Vector3FrameDefinitionField field) { + return null; + } + + @Override + public Vector4Definition accept(final Vector4FrameDefinitionField field) { + return field.getValue(); + } + + @Override + public Vector4Definition accept(final Vector2FrameDefinitionField field) { + return null; + } + + @Override + public Vector4Definition accept(final FontFrameDefinitionField field) { + return null; + } + + @Override + public Vector4Definition accept(final TextJustifyFrameDefinitionField field) { + return null; + } + +} diff --git a/jassparser/antlr-src/Jass.g4 b/jassparser/antlr-src/Jass.g4 new file mode 100644 index 0000000..595643c --- /dev/null +++ b/jassparser/antlr-src/Jass.g4 @@ -0,0 +1,169 @@ +/** + * Define a grammar called Hello + */ +grammar Jass; + +@header { + package com.etheller.interpreter; +} + + +program : + newlines + | + newlines_opt + typeDefinitionBlock + (block)* + (functionBlock)* + ; + +typeDefinition : + TYPE ID EXTENDS ID newlines + ; + +type : + ID # BasicType + | + ID ARRAY # ArrayType + | + 'nothing' # NothingType + ; + +global : + type ID newlines # BasicGlobal + | + type ID assignTail newlines # DefinitionGlobal + ; + +assignTail: + EQUALS expression; + +expression: + ID # ReferenceExpression + | + STRING_LITERAL #StringLiteralExpression + | + INTEGER #IntegerLiteralExpression + | + FUNCTION ID #FunctionReferenceExpression + | + NULL # NullExpression + | + TRUE # TrueExpression + | + FALSE # FalseExpression + | + ID '[' expression ']' # ArrayReferenceExpression + | + functionExpression # FunctionCallExpression + | + '(' expression ')' # ParentheticalExpression + ; + +functionExpression: + ID '(' argsList ')' + ; + +argsList: + expression # SingleArgument + | + expression ',' argsList # ListArgument + | + #EmptyArgument + ; + +//#booleanExpression: +// simpleArithmeticExpression # PassBooleanThroughExpression +// | + +statement: + CALL functionExpression newlines #CallStatement + | + SET ID EQUALS expression newlines #SetStatement + | + SET ID '[' expression ']' EQUALS expression newlines # ArrayedAssignmentStatement + | + RETURN expression newlines # ReturnStatement + ; + +param: + type ID; + +paramList: + param # SingleParameter + | + param ',' paramList # ListParameter + | + 'nothing' # NothingParameter + ; + +globalsBlock : + GLOBALS newlines (global)* ENDGLOBALS newlines ; + +typeDefinitionBlock : + (typeDefinition)* + ; + +nativeBlock: + NATIVE ID TAKES paramList RETURNS type newlines + ; + +block: + globalsBlock + | + nativeBlock + ; + +functionBlock: + FUNCTION ID TAKES paramList RETURNS type newlines (statement)* ENDFUNCTION newlines + ; + +newlines: + NEWLINES + | + EOF; + +newlines_opt: + NEWLINES + | + EOF + | + ; + +EQUALS : '='; + + +GLOBALS : 'globals' ; // globals +ENDGLOBALS : 'endglobals' ; // end globals block + +NATIVE : 'native' ; + +FUNCTION : 'function' ; // function +TAKES : 'takes' ; // takes +RETURNS : 'returns' ; +ENDFUNCTION : 'endfunction' ; // endfunction + +CALL : 'call' ; +SET : 'set' ; +RETURN : 'return' ; + +ARRAY : 'array' ; + +TYPE : 'type'; + +EXTENDS : 'extends'; + +STRING_LITERAL : ('"'.*?'"'); + +INTEGER : [0]|([1-9][0-9]*) ; + +NULL : 'null' ; +TRUE : 'true' ; +FALSE : 'false' ; + +ID : ([a-zA-Z_][a-zA-Z_0-9]*) ; // match identifiers + +WS : [ \t]+ -> skip ; // skip spaces, tabs + +NEWLINES : NEWLINE+; +fragment NEWLINE : '\r' '\n' | '\n' | '\r' | ('//'.*?'\n'); \ No newline at end of file diff --git a/jassparser/build.gradle b/jassparser/build.gradle new file mode 100644 index 0000000..e12bfa4 --- /dev/null +++ b/jassparser/build.gradle @@ -0,0 +1,49 @@ +apply plugin: "antlr" + +sourceCompatibility = 1.8 +[compileJava, compileTestJava]*.options*.encoding = 'UTF-8' + +sourceSets.main.java.srcDirs = [ "src/", "build/generated-src" ] +sourceSets.main.antlr.srcDirs = [ "antlr-src/" ] + +project.ext.mainClassName = "com.etheller.warsmash.jassparser.Main" + +task run(dependsOn: classes, type: JavaExec) { + main = project.mainClassName + classpath = sourceSets.main.runtimeClasspath + standardInput = System.in + ignoreExitValue = true +} + +task dist(type: Jar) { + from files(sourceSets.main.output.classesDir) + from files(sourceSets.main.output.resourcesDir) + from {configurations.compile.collect {zipTree(it)}} + + manifest { + attributes 'Main-Class': project.mainClassName + } +} + +dist.dependsOn classes + +eclipse.project { + name = appName + "-jassparser" +} + +task afterEclipseImport(description: "Post processing after project generation", group: "IDE") { + doLast { + def classpath = new XmlParser().parse(file(".classpath")) + def writer = new FileWriter(file(".classpath")) + def printer = new XmlNodePrinter(new PrintWriter(writer)) + printer.setPreserveWhitespace(true) + printer.print(classpath) + } +} + + +generateGrammarSource { + maxHeapSize = "64m" + arguments += ["-visitor", "-no-listener"] + outputDirectory = file("build/generated-src/com/etheller/warsmash/jassparser") +} \ No newline at end of file diff --git a/jassparser/src/com/etheller/interpreter/ast/Assignable.java b/jassparser/src/com/etheller/interpreter/ast/Assignable.java new file mode 100644 index 0000000..bf98b50 --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/Assignable.java @@ -0,0 +1,29 @@ +package com.etheller.interpreter.ast; + +import com.etheller.interpreter.ast.value.JassType; +import com.etheller.interpreter.ast.value.JassValue; +import com.etheller.interpreter.ast.value.visitor.JassTypeGettingValueVisitor; + +public class Assignable { + private JassValue value; + private final JassType type; + + public Assignable(final JassType type) { + this.type = type; + } + + public void setValue(final JassValue value) { + if (value.visit(JassTypeGettingValueVisitor.getInstance()) != type) { + throw new RuntimeException("Incompatible types"); + } + this.value = value; + } + + public JassValue getValue() { + return value; + } + + public JassType getType() { + return type; + } +} diff --git a/jassparser/src/com/etheller/interpreter/ast/JassRunner.java b/jassparser/src/com/etheller/interpreter/ast/JassRunner.java new file mode 100644 index 0000000..5470c69 --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/JassRunner.java @@ -0,0 +1,50 @@ +package com.etheller.interpreter.ast; + +import org.antlr.v4.runtime.BaseErrorListener; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.RecognitionException; +import org.antlr.v4.runtime.Recognizer; + +import com.etheller.interpreter.JassLexer; +import com.etheller.interpreter.JassParser; +import com.etheller.interpreter.ast.visitors.JassProgramVisitor; + +public class JassRunner { + public static final boolean REPORT_SYNTAX_ERRORS = true; + + public static void main(final String[] args) { + if (args.length < 1) { + System.err.println("Usage: [...]"); + return; + } + final JassProgramVisitor jassProgramVisitor = new JassProgramVisitor(); + for (final String arg : args) { + try { + final JassLexer lexer = new JassLexer(CharStreams.fromFileName(arg)); + final JassParser parser = new JassParser(new CommonTokenStream(lexer)); + parser.addErrorListener(new BaseErrorListener() { + @Override + public void syntaxError(final Recognizer recognizer, final Object offendingSymbol, + final int line, final int charPositionInLine, final String msg, + final RecognitionException e) { + if (!REPORT_SYNTAX_ERRORS) { + return; + } + + String sourceName = recognizer.getInputStream().getSourceName(); + if (!sourceName.isEmpty()) { + sourceName = String.format("%s:%d:%d: ", sourceName, line, charPositionInLine); + } + + System.err.println(sourceName + "line " + line + ":" + charPositionInLine + " " + msg); + } + }); + jassProgramVisitor.visit(parser.program()); + } catch (final Exception e) { + e.printStackTrace(); + } + } + } + +} diff --git a/jassparser/src/com/etheller/interpreter/ast/expression/ArrayRefJassExpression.java b/jassparser/src/com/etheller/interpreter/ast/expression/ArrayRefJassExpression.java new file mode 100644 index 0000000..1ae050e --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/expression/ArrayRefJassExpression.java @@ -0,0 +1,38 @@ +package com.etheller.interpreter.ast.expression; + +import com.etheller.interpreter.ast.Assignable; +import com.etheller.interpreter.ast.scope.GlobalScope; +import com.etheller.interpreter.ast.scope.LocalScope; +import com.etheller.interpreter.ast.value.ArrayJassValue; +import com.etheller.interpreter.ast.value.JassValue; +import com.etheller.interpreter.ast.value.visitor.ArrayJassValueVisitor; +import com.etheller.interpreter.ast.value.visitor.IntegerJassValueVisitor; + +public class ArrayRefJassExpression implements JassExpression { + private final String identifier; + private final JassExpression indexExpression; + + public ArrayRefJassExpression(final String identifier, final JassExpression indexExpression) { + this.identifier = identifier; + this.indexExpression = indexExpression; + } + + @Override + public JassValue evaluate(final GlobalScope globalScope, final LocalScope localScope) { + Assignable variable = localScope.getAssignableLocal(identifier); + final JassValue index = indexExpression.evaluate(globalScope, localScope); + if (variable == null) { + variable = globalScope.getAssignableGlobal(identifier); + } + if (variable.getValue() == null) { + throw new RuntimeException("Unable to use subscript on uninitialized variable"); + } + final ArrayJassValue arrayValue = variable.getValue().visit(ArrayJassValueVisitor.getInstance()); + if (arrayValue != null) { + return arrayValue.get(index.visit(IntegerJassValueVisitor.getInstance())); + } else { + throw new RuntimeException("Not an array"); + } + } + +} diff --git a/jassparser/src/com/etheller/interpreter/ast/expression/FunctionCallJassExpression.java b/jassparser/src/com/etheller/interpreter/ast/expression/FunctionCallJassExpression.java new file mode 100644 index 0000000..9cdece9 --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/expression/FunctionCallJassExpression.java @@ -0,0 +1,34 @@ +package com.etheller.interpreter.ast.expression; + +import java.util.ArrayList; +import java.util.List; + +import com.etheller.interpreter.ast.function.JassFunction; +import com.etheller.interpreter.ast.scope.GlobalScope; +import com.etheller.interpreter.ast.scope.LocalScope; +import com.etheller.interpreter.ast.value.JassValue; + +public class FunctionCallJassExpression implements JassExpression { + private final String functionName; + private final List arguments; + + public FunctionCallJassExpression(final String functionName, final List arguments) { + this.functionName = functionName; + this.arguments = arguments; + } + + @Override + public JassValue evaluate(final GlobalScope globalScope, final LocalScope localScope) { + final JassFunction functionByName = globalScope.getFunctionByName(functionName); + if (functionByName == null) { + throw new RuntimeException("Undefined function: " + functionName); + } + final List evaluatedExpressions = new ArrayList<>(); + for (final JassExpression expr : arguments) { + final JassValue evaluatedExpression = expr.evaluate(globalScope, localScope); + evaluatedExpressions.add(evaluatedExpression); + } + return functionByName.call(evaluatedExpressions, globalScope); + } + +} diff --git a/jassparser/src/com/etheller/interpreter/ast/expression/FunctionReferenceJassExpression.java b/jassparser/src/com/etheller/interpreter/ast/expression/FunctionReferenceJassExpression.java new file mode 100644 index 0000000..0e9a2b9 --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/expression/FunctionReferenceJassExpression.java @@ -0,0 +1,25 @@ +package com.etheller.interpreter.ast.expression; + +import com.etheller.interpreter.ast.function.JassFunction; +import com.etheller.interpreter.ast.scope.GlobalScope; +import com.etheller.interpreter.ast.scope.LocalScope; +import com.etheller.interpreter.ast.value.CodeJassValue; +import com.etheller.interpreter.ast.value.JassValue; + +public class FunctionReferenceJassExpression implements JassExpression { + private final String identifier; + + public FunctionReferenceJassExpression(final String identifier) { + this.identifier = identifier; + } + + @Override + public JassValue evaluate(final GlobalScope globalScope, final LocalScope localScope) { + final JassFunction functionByName = globalScope.getFunctionByName(identifier); + if (functionByName == null) { + throw new RuntimeException("Unable to find function: " + identifier); + } + return new CodeJassValue(functionByName); + } + +} diff --git a/jassparser/src/com/etheller/interpreter/ast/expression/JassExpression.java b/jassparser/src/com/etheller/interpreter/ast/expression/JassExpression.java new file mode 100644 index 0000000..edc47af --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/expression/JassExpression.java @@ -0,0 +1,9 @@ +package com.etheller.interpreter.ast.expression; + +import com.etheller.interpreter.ast.scope.GlobalScope; +import com.etheller.interpreter.ast.scope.LocalScope; +import com.etheller.interpreter.ast.value.JassValue; + +public interface JassExpression { + JassValue evaluate(GlobalScope globalScope, LocalScope localScope); +} diff --git a/jassparser/src/com/etheller/interpreter/ast/expression/LiteralJassExpression.java b/jassparser/src/com/etheller/interpreter/ast/expression/LiteralJassExpression.java new file mode 100644 index 0000000..c989de6 --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/expression/LiteralJassExpression.java @@ -0,0 +1,19 @@ +package com.etheller.interpreter.ast.expression; + +import com.etheller.interpreter.ast.scope.GlobalScope; +import com.etheller.interpreter.ast.scope.LocalScope; +import com.etheller.interpreter.ast.value.JassValue; + +public class LiteralJassExpression implements JassExpression { + private final JassValue value; + + public LiteralJassExpression(final JassValue value) { + this.value = value; + } + + @Override + public JassValue evaluate(final GlobalScope globalScope, final LocalScope localScope) { + return value; + } + +} diff --git a/jassparser/src/com/etheller/interpreter/ast/expression/ReferenceJassExpression.java b/jassparser/src/com/etheller/interpreter/ast/expression/ReferenceJassExpression.java new file mode 100644 index 0000000..b952a4e --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/expression/ReferenceJassExpression.java @@ -0,0 +1,24 @@ +package com.etheller.interpreter.ast.expression; + +import com.etheller.interpreter.ast.Assignable; +import com.etheller.interpreter.ast.scope.GlobalScope; +import com.etheller.interpreter.ast.scope.LocalScope; +import com.etheller.interpreter.ast.value.JassValue; + +public class ReferenceJassExpression implements JassExpression { + private final String identifier; + + public ReferenceJassExpression(final String identifier) { + this.identifier = identifier; + } + + @Override + public JassValue evaluate(final GlobalScope globalScope, final LocalScope localScope) { + final Assignable local = localScope.getAssignableLocal(identifier); + if (local == null) { + return globalScope.getGlobal(identifier); + } + return local.getValue(); + } + +} diff --git a/jassparser/src/com/etheller/interpreter/ast/function/AbstractJassFunction.java b/jassparser/src/com/etheller/interpreter/ast/function/AbstractJassFunction.java new file mode 100644 index 0000000..d132b75 --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/function/AbstractJassFunction.java @@ -0,0 +1,44 @@ +package com.etheller.interpreter.ast.function; + +import java.util.List; + +import com.etheller.interpreter.ast.scope.GlobalScope; +import com.etheller.interpreter.ast.scope.LocalScope; +import com.etheller.interpreter.ast.value.JassType; +import com.etheller.interpreter.ast.value.JassValue; + +/** + * Not a native + * + * @author Eric + * + */ +public abstract class AbstractJassFunction implements JassFunction { + protected final List parameters; + protected final JassType returnType; + + public AbstractJassFunction(final List parameters, final JassType returnType) { + this.parameters = parameters; + this.returnType = returnType; + } + + @Override + public final JassValue call(final List arguments, final GlobalScope globalScope) { + if (arguments.size() != parameters.size()) { + throw new RuntimeException("Invalid number of arguments passed to function"); + } + final LocalScope localScope = new LocalScope(); + for (int i = 0; i < parameters.size(); i++) { + final JassParameter parameter = parameters.get(i); + final JassValue argument = arguments.get(i); + if (!parameter.matchesType(argument)) { + throw new RuntimeException("Invalid type for specified argument"); + } + localScope.createLocal(parameter.getIdentifier(), parameter.getType(), argument); + } + return innerCall(arguments, globalScope, localScope); + } + + protected abstract JassValue innerCall(final List arguments, final GlobalScope globalScope, + final LocalScope localScope); +} diff --git a/jassparser/src/com/etheller/interpreter/ast/function/JassFunction.java b/jassparser/src/com/etheller/interpreter/ast/function/JassFunction.java new file mode 100644 index 0000000..b5b2ec0 --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/function/JassFunction.java @@ -0,0 +1,10 @@ +package com.etheller.interpreter.ast.function; + +import java.util.List; + +import com.etheller.interpreter.ast.scope.GlobalScope; +import com.etheller.interpreter.ast.value.JassValue; + +public interface JassFunction { + JassValue call(List arguments, GlobalScope globalScope); +} diff --git a/jassparser/src/com/etheller/interpreter/ast/function/JassNativeManager.java b/jassparser/src/com/etheller/interpreter/ast/function/JassNativeManager.java new file mode 100644 index 0000000..59242b3 --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/function/JassNativeManager.java @@ -0,0 +1,33 @@ +package com.etheller.interpreter.ast.function; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.etheller.interpreter.ast.scope.GlobalScope; +import com.etheller.interpreter.ast.value.JassType; + +public class JassNativeManager { + private final Map nameToNativeCode; + private final Set registeredNativeNames = new HashSet<>(); + + public JassNativeManager() { + this.nameToNativeCode = new HashMap<>(); + } + + public void registerNativeCode(final String name, final List parameters, final JassType returnType, + final GlobalScope globals) { + if (this.registeredNativeNames.contains(name)) { + throw new RuntimeException("Native already registered: " + name); + } + final JassFunction nativeCode = this.nameToNativeCode.remove(name); + globals.defineFunction(name, new NativeJassFunction(parameters, returnType, name, nativeCode)); + this.registeredNativeNames.add(name); + } + + public void checkUnregisteredNatives() { + // TODO maybe do this later + } +} diff --git a/jassparser/src/com/etheller/interpreter/ast/function/JassParameter.java b/jassparser/src/com/etheller/interpreter/ast/function/JassParameter.java new file mode 100644 index 0000000..1c59957 --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/function/JassParameter.java @@ -0,0 +1,27 @@ +package com.etheller.interpreter.ast.function; + +import com.etheller.interpreter.ast.value.JassType; +import com.etheller.interpreter.ast.value.JassValue; +import com.etheller.interpreter.ast.value.visitor.JassTypeGettingValueVisitor; + +public class JassParameter { + private final JassType type; + private final String identifier; + + public JassParameter(final JassType type, final String identifier) { + this.type = type; + this.identifier = identifier; + } + + public String getIdentifier() { + return identifier; + } + + public JassType getType() { + return type; + } + + public boolean matchesType(final JassValue value) { + return type == value.visit(JassTypeGettingValueVisitor.getInstance()); + } +} diff --git a/jassparser/src/com/etheller/interpreter/ast/function/NativeJassFunction.java b/jassparser/src/com/etheller/interpreter/ast/function/NativeJassFunction.java new file mode 100644 index 0000000..14605bf --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/function/NativeJassFunction.java @@ -0,0 +1,26 @@ +package com.etheller.interpreter.ast.function; + +import java.util.List; + +import com.etheller.interpreter.ast.scope.GlobalScope; +import com.etheller.interpreter.ast.scope.LocalScope; +import com.etheller.interpreter.ast.value.JassType; +import com.etheller.interpreter.ast.value.JassValue; + +public class NativeJassFunction extends AbstractJassFunction { + private final String name; + private final JassFunction implementation; + + public NativeJassFunction(final List parameters, final JassType returnType, final String name, + final JassFunction impl) { + super(parameters, returnType); + this.name = name; + implementation = impl; + } + + @Override + protected JassValue innerCall(final List arguments, final GlobalScope globalScope, + final LocalScope localScope) { + return implementation.call(arguments, globalScope); + } +} diff --git a/jassparser/src/com/etheller/interpreter/ast/function/UserJassFunction.java b/jassparser/src/com/etheller/interpreter/ast/function/UserJassFunction.java new file mode 100644 index 0000000..105a6da --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/function/UserJassFunction.java @@ -0,0 +1,44 @@ +package com.etheller.interpreter.ast.function; + +import java.util.List; + +import com.etheller.interpreter.ast.scope.GlobalScope; +import com.etheller.interpreter.ast.scope.LocalScope; +import com.etheller.interpreter.ast.statement.JassStatement; +import com.etheller.interpreter.ast.value.JassType; +import com.etheller.interpreter.ast.value.JassValue; +import com.etheller.interpreter.ast.value.visitor.JassTypeGettingValueVisitor; + +/** + * Not a native + * + * @author Eric + * + */ +public final class UserJassFunction extends AbstractJassFunction { + private final List statements; + + public UserJassFunction(final List statements, final List parameters, + final JassType returnType) { + super(parameters, returnType); + this.statements = statements; + } + + @Override + public JassValue innerCall(final List arguments, final GlobalScope globalScope, + final LocalScope localScope) { + for (final JassStatement statement : statements) { + final JassValue returnValue = statement.execute(globalScope, localScope); + if (returnValue != null) { + if (returnValue.visit(JassTypeGettingValueVisitor.getInstance()) != returnType) { + throw new RuntimeException("Invalid return type"); + } + return returnValue; + } + } + if (JassType.NOTHING != returnType) { + throw new RuntimeException("Invalid return type"); + } + return null; + } +} diff --git a/jassparser/src/com/etheller/interpreter/ast/scope/GlobalScope.java b/jassparser/src/com/etheller/interpreter/ast/scope/GlobalScope.java new file mode 100644 index 0000000..5ad1924 --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/scope/GlobalScope.java @@ -0,0 +1,94 @@ +package com.etheller.interpreter.ast.scope; + +import java.util.HashMap; +import java.util.Map; + +import com.etheller.interpreter.ast.Assignable; +import com.etheller.interpreter.ast.function.JassFunction; +import com.etheller.interpreter.ast.value.ArrayJassType; +import com.etheller.interpreter.ast.value.ArrayJassValue; +import com.etheller.interpreter.ast.value.JassType; +import com.etheller.interpreter.ast.value.JassValue; +import com.etheller.interpreter.ast.value.PrimitiveJassType; +import com.etheller.interpreter.ast.value.visitor.ArrayPrimitiveTypeVisitor; + +public final class GlobalScope { + private final Map globals = new HashMap<>(); + private final Map functions = new HashMap<>(); + private final Map types = new HashMap<>(); + + public void createGlobalArray(final String name, final JassType type) { + final Assignable assignable = new Assignable(type); + assignable.setValue(new ArrayJassValue((ArrayJassType) type)); // TODO less bad code + globals.put(name, assignable); + } + + public void createGlobal(final String name, final JassType type) { + globals.put(name, new Assignable(type)); + } + + public void createGlobal(final String name, final JassType type, final JassValue value) { + final Assignable assignable = new Assignable(type); + assignable.setValue(value); + globals.put(name, assignable); + } + + public void setGlobal(final String name, final JassValue value) { + final Assignable assignable = globals.get(name); + if (assignable == null) { + throw new RuntimeException("Undefined global: " + name); + } + if (assignable.getType().visit(ArrayPrimitiveTypeVisitor.getInstance()) != null) { + throw new RuntimeException("Unable to assign array variable: " + name); + } + assignable.setValue(value); + } + + public JassValue getGlobal(final String name) { + final Assignable global = globals.get(name); + if (global == null) { + throw new RuntimeException("Undefined global: " + name); + } + return global.getValue(); + } + + public Assignable getAssignableGlobal(final String name) { + return globals.get(name); + } + + public void defineFunction(final String name, final JassFunction function) { + functions.put(name, function); + } + + public JassFunction getFunctionByName(final String name) { + return functions.get(name); + } + + public PrimitiveJassType parseType(final String text) { + if (text.equals("string")) { + return JassType.STRING; + } else if (text.equals("integer")) { + return JassType.INTEGER; + } else if (text.equals("boolean")) { + return JassType.BOOLEAN; + } else if (text.equals("real")) { + return JassType.REAL; + } else if (text.equals("code")) { + return JassType.CODE; + } else if (text.equals("nothing")) { + return JassType.NOTHING; + } else { + throw new RuntimeException("Unknown type: " + text); + } + } + + public JassType parseArrayType(final String primitiveTypeName) { + final String arrayTypeName = primitiveTypeName + " array"; + JassType arrayType = types.get(arrayTypeName); + if (arrayType == null) { + arrayType = new ArrayJassType(parseType(primitiveTypeName)); + types.put(arrayTypeName, arrayType); + } + return arrayType; + } +} diff --git a/jassparser/src/com/etheller/interpreter/ast/scope/LocalScope.java b/jassparser/src/com/etheller/interpreter/ast/scope/LocalScope.java new file mode 100644 index 0000000..8ab2f01 --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/scope/LocalScope.java @@ -0,0 +1,42 @@ +package com.etheller.interpreter.ast.scope; + +import java.util.HashMap; +import java.util.Map; + +import com.etheller.interpreter.ast.Assignable; +import com.etheller.interpreter.ast.value.JassType; +import com.etheller.interpreter.ast.value.JassValue; + +public final class LocalScope { + private final Map locals = new HashMap<>(); + + public void createLocal(final String name, final JassType type) { + locals.put(name, new Assignable(type)); + } + + public void createLocal(final String name, final JassType type, final JassValue value) { + final Assignable assignable = new Assignable(type); + assignable.setValue(value); + locals.put(name, assignable); + } + + public void setLocal(final String name, final JassValue value) { + final Assignable assignable = locals.get(name); + if (assignable == null) { + throw new RuntimeException("Undefined local variable: " + name); + } + assignable.setValue(value); + } + + public JassValue getLocal(final String name) { + final Assignable local = locals.get(name); + if (local == null) { + throw new RuntimeException("Undefined local variable: " + name); + } + return local.getValue(); + } + + public Assignable getAssignableLocal(final String name) { + return locals.get(name); + } +} diff --git a/jassparser/src/com/etheller/interpreter/ast/scope/TypeDefinition.java b/jassparser/src/com/etheller/interpreter/ast/scope/TypeDefinition.java new file mode 100644 index 0000000..1331a96 --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/scope/TypeDefinition.java @@ -0,0 +1,11 @@ +package com.etheller.interpreter.ast.scope; + +public class TypeDefinition { + private final String name; + private final String supertype; + + public TypeDefinition(final String name, final String supertype) { + this.name = name; + this.supertype = supertype; + } +} diff --git a/jassparser/src/com/etheller/interpreter/ast/statement/JassArrayedAssignmentStatement.java b/jassparser/src/com/etheller/interpreter/ast/statement/JassArrayedAssignmentStatement.java new file mode 100644 index 0000000..501f5f6 --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/statement/JassArrayedAssignmentStatement.java @@ -0,0 +1,44 @@ +package com.etheller.interpreter.ast.statement; + +import com.etheller.interpreter.ast.Assignable; +import com.etheller.interpreter.ast.expression.JassExpression; +import com.etheller.interpreter.ast.scope.GlobalScope; +import com.etheller.interpreter.ast.scope.LocalScope; +import com.etheller.interpreter.ast.value.ArrayJassValue; +import com.etheller.interpreter.ast.value.JassValue; +import com.etheller.interpreter.ast.value.visitor.ArrayJassValueVisitor; +import com.etheller.interpreter.ast.value.visitor.IntegerJassValueVisitor; + +public class JassArrayedAssignmentStatement implements JassStatement { + private final String identifier; + private final JassExpression indexExpression; + private final JassExpression expression; + + public JassArrayedAssignmentStatement(final String identifier, final JassExpression indexExpression, + final JassExpression expression) { + this.identifier = identifier; + this.indexExpression = indexExpression; + this.expression = expression; + } + + @Override + public JassValue execute(final GlobalScope globalScope, final LocalScope localScope) { + Assignable variable = localScope.getAssignableLocal(identifier); + final JassValue index = indexExpression.evaluate(globalScope, localScope); + if (variable == null) { + variable = globalScope.getAssignableGlobal(identifier); + } + if (variable.getValue() == null) { + throw new RuntimeException("Unable to assign uninitialized array"); + } + final ArrayJassValue arrayValue = variable.getValue().visit(ArrayJassValueVisitor.getInstance()); + if (arrayValue != null) { + arrayValue.set(index.visit(IntegerJassValueVisitor.getInstance()), + expression.evaluate(globalScope, localScope)); + } else { + throw new RuntimeException("Not an array"); + } + return null; + } + +} diff --git a/jassparser/src/com/etheller/interpreter/ast/statement/JassCallStatement.java b/jassparser/src/com/etheller/interpreter/ast/statement/JassCallStatement.java new file mode 100644 index 0000000..5a52634 --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/statement/JassCallStatement.java @@ -0,0 +1,37 @@ +package com.etheller.interpreter.ast.statement; + +import java.util.ArrayList; +import java.util.List; + +import com.etheller.interpreter.ast.expression.JassExpression; +import com.etheller.interpreter.ast.function.JassFunction; +import com.etheller.interpreter.ast.scope.GlobalScope; +import com.etheller.interpreter.ast.scope.LocalScope; +import com.etheller.interpreter.ast.value.JassValue; + +public class JassCallStatement implements JassStatement { + private final String functionName; + private final List arguments; + + public JassCallStatement(final String functionName, final List arguments) { + this.functionName = functionName; + this.arguments = arguments; + } + + @Override + public JassValue execute(final GlobalScope globalScope, final LocalScope localScope) { + final JassFunction functionByName = globalScope.getFunctionByName(functionName); + if (functionByName == null) { + throw new RuntimeException("Undefined function: " + functionName); + } + final List evaluatedExpressions = new ArrayList<>(); + for (final JassExpression expr : arguments) { + final JassValue evaluatedExpression = expr.evaluate(globalScope, localScope); + evaluatedExpressions.add(evaluatedExpression); + } + functionByName.call(evaluatedExpressions, globalScope); + // throw away return value + return null; + } + +} diff --git a/jassparser/src/com/etheller/interpreter/ast/statement/JassReturnStatement.java b/jassparser/src/com/etheller/interpreter/ast/statement/JassReturnStatement.java new file mode 100644 index 0000000..404074f --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/statement/JassReturnStatement.java @@ -0,0 +1,20 @@ +package com.etheller.interpreter.ast.statement; + +import com.etheller.interpreter.ast.expression.JassExpression; +import com.etheller.interpreter.ast.scope.GlobalScope; +import com.etheller.interpreter.ast.scope.LocalScope; +import com.etheller.interpreter.ast.value.JassValue; + +public class JassReturnStatement implements JassStatement { + private final JassExpression expression; + + public JassReturnStatement(final JassExpression expression) { + this.expression = expression; + } + + @Override + public JassValue execute(final GlobalScope globalScope, final LocalScope localScope) { + return expression.evaluate(globalScope, localScope); + } + +} diff --git a/jassparser/src/com/etheller/interpreter/ast/statement/JassSetStatement.java b/jassparser/src/com/etheller/interpreter/ast/statement/JassSetStatement.java new file mode 100644 index 0000000..35d182a --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/statement/JassSetStatement.java @@ -0,0 +1,29 @@ +package com.etheller.interpreter.ast.statement; + +import com.etheller.interpreter.ast.Assignable; +import com.etheller.interpreter.ast.expression.JassExpression; +import com.etheller.interpreter.ast.scope.GlobalScope; +import com.etheller.interpreter.ast.scope.LocalScope; +import com.etheller.interpreter.ast.value.JassValue; + +public class JassSetStatement implements JassStatement { + private final String identifier; + private final JassExpression expression; + + public JassSetStatement(final String identifier, final JassExpression expression) { + this.identifier = identifier; + this.expression = expression; + } + + @Override + public JassValue execute(final GlobalScope globalScope, final LocalScope localScope) { + final Assignable local = localScope.getAssignableLocal(identifier); + if (local != null) { + local.setValue(expression.evaluate(globalScope, localScope)); + } else { + globalScope.setGlobal(identifier, expression.evaluate(globalScope, localScope)); + } + return null; + } + +} diff --git a/jassparser/src/com/etheller/interpreter/ast/statement/JassStatement.java b/jassparser/src/com/etheller/interpreter/ast/statement/JassStatement.java new file mode 100644 index 0000000..196f415 --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/statement/JassStatement.java @@ -0,0 +1,11 @@ +package com.etheller.interpreter.ast.statement; + +import com.etheller.interpreter.ast.scope.GlobalScope; +import com.etheller.interpreter.ast.scope.LocalScope; +import com.etheller.interpreter.ast.value.JassValue; + +public interface JassStatement { + // When a value is returned, this indicates a RETURN statement, + // and will end outer execution + JassValue execute(GlobalScope globalScope, LocalScope localScope); +} diff --git a/jassparser/src/com/etheller/interpreter/ast/value/ArrayJassType.java b/jassparser/src/com/etheller/interpreter/ast/value/ArrayJassType.java new file mode 100644 index 0000000..0027c3a --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/value/ArrayJassType.java @@ -0,0 +1,18 @@ +package com.etheller.interpreter.ast.value; + +public class ArrayJassType implements JassType { + private final PrimitiveJassType primitiveType; + + public ArrayJassType(final PrimitiveJassType primitiveType) { + this.primitiveType = primitiveType; + } + + public PrimitiveJassType getPrimitiveType() { + return primitiveType; + } + + @Override + public TYPE visit(final JassTypeVisitor visitor) { + return visitor.accept(this); + } +} diff --git a/jassparser/src/com/etheller/interpreter/ast/value/ArrayJassValue.java b/jassparser/src/com/etheller/interpreter/ast/value/ArrayJassValue.java new file mode 100644 index 0000000..158d8d3 --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/value/ArrayJassValue.java @@ -0,0 +1,34 @@ +package com.etheller.interpreter.ast.value; + +import com.etheller.interpreter.ast.value.visitor.JassTypeGettingValueVisitor; + +public class ArrayJassValue implements JassValue { + private final JassValue[] data = new JassValue[8192]; // that's the array size in JASS + private final ArrayJassType type; + + public ArrayJassValue(final ArrayJassType type) { + this.type = type; + } + + @Override + public TYPE visit(final JassValueVisitor visitor) { + return visitor.accept(this); + } + + public void set(final int index, final JassValue value) { + if (value.visit(JassTypeGettingValueVisitor.getInstance()) != type.getPrimitiveType()) { + throw new IllegalStateException( + "Illegal type for assignment to " + type.getPrimitiveType().getName() + " array"); + } + data[index] = value; + } + + public JassValue get(final int index) { + return data[index]; + } + + public ArrayJassType getType() { + return type; + } + +} diff --git a/jassparser/src/com/etheller/interpreter/ast/value/BooleanJassValue.java b/jassparser/src/com/etheller/interpreter/ast/value/BooleanJassValue.java new file mode 100644 index 0000000..e044000 --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/value/BooleanJassValue.java @@ -0,0 +1,18 @@ +package com.etheller.interpreter.ast.value; + +public class BooleanJassValue implements JassValue { + private final boolean value; + + public BooleanJassValue(final boolean value) { + this.value = value; + } + + public boolean getValue() { + return value; + } + + @Override + public TYPE visit(final JassValueVisitor visitor) { + return visitor.accept(this); + } +} diff --git a/jassparser/src/com/etheller/interpreter/ast/value/CodeJassValue.java b/jassparser/src/com/etheller/interpreter/ast/value/CodeJassValue.java new file mode 100644 index 0000000..2858184 --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/value/CodeJassValue.java @@ -0,0 +1,21 @@ +package com.etheller.interpreter.ast.value; + +import com.etheller.interpreter.ast.function.JassFunction; + +public class CodeJassValue implements JassValue { + private final JassFunction value; + + public CodeJassValue(final JassFunction value) { + this.value = value; + } + + public JassFunction getValue() { + return value; + } + + @Override + public TYPE visit(final JassValueVisitor visitor) { + return visitor.accept(this); + } + +} diff --git a/jassparser/src/com/etheller/interpreter/ast/value/IntegerJassValue.java b/jassparser/src/com/etheller/interpreter/ast/value/IntegerJassValue.java new file mode 100644 index 0000000..bec6cfe --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/value/IntegerJassValue.java @@ -0,0 +1,18 @@ +package com.etheller.interpreter.ast.value; + +public class IntegerJassValue implements JassValue { + private final int value; + + public IntegerJassValue(final int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + @Override + public TYPE visit(final JassValueVisitor visitor) { + return visitor.accept(this); + } +} diff --git a/jassparser/src/com/etheller/interpreter/ast/value/JassType.java b/jassparser/src/com/etheller/interpreter/ast/value/JassType.java new file mode 100644 index 0000000..7b1beb8 --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/value/JassType.java @@ -0,0 +1,12 @@ +package com.etheller.interpreter.ast.value; + +public interface JassType { + TYPE visit(JassTypeVisitor visitor); + + public static final PrimitiveJassType INTEGER = new PrimitiveJassType("integer"); + public static final PrimitiveJassType STRING = new PrimitiveJassType("string"); + public static final PrimitiveJassType CODE = new PrimitiveJassType("code"); + public static final PrimitiveJassType REAL = new PrimitiveJassType("real"); + public static final PrimitiveJassType BOOLEAN = new PrimitiveJassType("boolean"); + public static final PrimitiveJassType NOTHING = new PrimitiveJassType("nothing"); +} diff --git a/jassparser/src/com/etheller/interpreter/ast/value/JassTypeVisitor.java b/jassparser/src/com/etheller/interpreter/ast/value/JassTypeVisitor.java new file mode 100644 index 0000000..031e921 --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/value/JassTypeVisitor.java @@ -0,0 +1,7 @@ +package com.etheller.interpreter.ast.value; + +public interface JassTypeVisitor { + TYPE accept(PrimitiveJassType primitiveType); + + TYPE accept(ArrayJassType arrayType); +} diff --git a/jassparser/src/com/etheller/interpreter/ast/value/JassValue.java b/jassparser/src/com/etheller/interpreter/ast/value/JassValue.java new file mode 100644 index 0000000..c1c0c43 --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/value/JassValue.java @@ -0,0 +1,5 @@ +package com.etheller.interpreter.ast.value; + +public interface JassValue { + TYPE visit(JassValueVisitor visitor); +} diff --git a/jassparser/src/com/etheller/interpreter/ast/value/JassValueVisitor.java b/jassparser/src/com/etheller/interpreter/ast/value/JassValueVisitor.java new file mode 100644 index 0000000..e02f265 --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/value/JassValueVisitor.java @@ -0,0 +1,15 @@ +package com.etheller.interpreter.ast.value; + +public interface JassValueVisitor { + TYPE accept(IntegerJassValue value); + + TYPE accept(RealJassValue value); + + TYPE accept(BooleanJassValue value); + + TYPE accept(StringJassValue value); + + TYPE accept(CodeJassValue value); + + TYPE accept(ArrayJassValue value); +} diff --git a/jassparser/src/com/etheller/interpreter/ast/value/PrimitiveJassType.java b/jassparser/src/com/etheller/interpreter/ast/value/PrimitiveJassType.java new file mode 100644 index 0000000..21ba0b0 --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/value/PrimitiveJassType.java @@ -0,0 +1,19 @@ +package com.etheller.interpreter.ast.value; + +public class PrimitiveJassType implements JassType { + private final String name; + + public PrimitiveJassType(final String name) { + this.name = name; + } + + public String getName() { + return name; + } + + @Override + public TYPE visit(final JassTypeVisitor visitor) { + return visitor.accept(this); + } + +} diff --git a/jassparser/src/com/etheller/interpreter/ast/value/RealJassValue.java b/jassparser/src/com/etheller/interpreter/ast/value/RealJassValue.java new file mode 100644 index 0000000..49636dc --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/value/RealJassValue.java @@ -0,0 +1,18 @@ +package com.etheller.interpreter.ast.value; + +public class RealJassValue implements JassValue { + private final double value; + + public RealJassValue(final double value) { + this.value = value; + } + + public double getValue() { + return value; + } + + @Override + public TYPE visit(final JassValueVisitor visitor) { + return visitor.accept(this); + } +} diff --git a/jassparser/src/com/etheller/interpreter/ast/value/StringJassValue.java b/jassparser/src/com/etheller/interpreter/ast/value/StringJassValue.java new file mode 100644 index 0000000..d37c98f --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/value/StringJassValue.java @@ -0,0 +1,18 @@ +package com.etheller.interpreter.ast.value; + +public class StringJassValue implements JassValue { + private final String value; + + public StringJassValue(final String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + @Override + public TYPE visit(final JassValueVisitor visitor) { + return visitor.accept(this); + } +} diff --git a/jassparser/src/com/etheller/interpreter/ast/value/visitor/ArrayJassValueVisitor.java b/jassparser/src/com/etheller/interpreter/ast/value/visitor/ArrayJassValueVisitor.java new file mode 100644 index 0000000..f3d43d7 --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/value/visitor/ArrayJassValueVisitor.java @@ -0,0 +1,48 @@ +package com.etheller.interpreter.ast.value.visitor; + +import com.etheller.interpreter.ast.value.ArrayJassValue; +import com.etheller.interpreter.ast.value.BooleanJassValue; +import com.etheller.interpreter.ast.value.CodeJassValue; +import com.etheller.interpreter.ast.value.IntegerJassValue; +import com.etheller.interpreter.ast.value.JassValueVisitor; +import com.etheller.interpreter.ast.value.RealJassValue; +import com.etheller.interpreter.ast.value.StringJassValue; + +public class ArrayJassValueVisitor implements JassValueVisitor { + private static final ArrayJassValueVisitor INSTANCE = new ArrayJassValueVisitor(); + + public static ArrayJassValueVisitor getInstance() { + return INSTANCE; + } + + @Override + public ArrayJassValue accept(final IntegerJassValue value) { + return null; + } + + @Override + public ArrayJassValue accept(final RealJassValue value) { + return null; + } + + @Override + public ArrayJassValue accept(final BooleanJassValue value) { + return null; + } + + @Override + public ArrayJassValue accept(final StringJassValue value) { + return null; + } + + @Override + public ArrayJassValue accept(final CodeJassValue value) { + return null; + } + + @Override + public ArrayJassValue accept(final ArrayJassValue value) { + return value; + } + +} diff --git a/jassparser/src/com/etheller/interpreter/ast/value/visitor/ArrayPrimitiveTypeVisitor.java b/jassparser/src/com/etheller/interpreter/ast/value/visitor/ArrayPrimitiveTypeVisitor.java new file mode 100644 index 0000000..70b543c --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/value/visitor/ArrayPrimitiveTypeVisitor.java @@ -0,0 +1,24 @@ +package com.etheller.interpreter.ast.value.visitor; + +import com.etheller.interpreter.ast.value.ArrayJassType; +import com.etheller.interpreter.ast.value.JassTypeVisitor; +import com.etheller.interpreter.ast.value.PrimitiveJassType; + +public class ArrayPrimitiveTypeVisitor implements JassTypeVisitor { + private static final ArrayPrimitiveTypeVisitor INSTANCE = new ArrayPrimitiveTypeVisitor(); + + public static ArrayPrimitiveTypeVisitor getInstance() { + return INSTANCE; + } + + @Override + public PrimitiveJassType accept(final PrimitiveJassType primitiveType) { + return null; + } + + @Override + public PrimitiveJassType accept(final ArrayJassType arrayType) { + return arrayType.getPrimitiveType(); + } + +} diff --git a/jassparser/src/com/etheller/interpreter/ast/value/visitor/IntegerJassValueVisitor.java b/jassparser/src/com/etheller/interpreter/ast/value/visitor/IntegerJassValueVisitor.java new file mode 100644 index 0000000..6d99a02 --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/value/visitor/IntegerJassValueVisitor.java @@ -0,0 +1,48 @@ +package com.etheller.interpreter.ast.value.visitor; + +import com.etheller.interpreter.ast.value.ArrayJassValue; +import com.etheller.interpreter.ast.value.BooleanJassValue; +import com.etheller.interpreter.ast.value.CodeJassValue; +import com.etheller.interpreter.ast.value.IntegerJassValue; +import com.etheller.interpreter.ast.value.JassValueVisitor; +import com.etheller.interpreter.ast.value.RealJassValue; +import com.etheller.interpreter.ast.value.StringJassValue; + +public class IntegerJassValueVisitor implements JassValueVisitor { + private static final IntegerJassValueVisitor INSTANCE = new IntegerJassValueVisitor(); + + public static IntegerJassValueVisitor getInstance() { + return INSTANCE; + } + + @Override + public Integer accept(final IntegerJassValue value) { + return value.getValue(); + } + + @Override + public Integer accept(final RealJassValue value) { + return (int) value.getValue(); + } + + @Override + public Integer accept(final BooleanJassValue value) { + return 0; + } + + @Override + public Integer accept(final StringJassValue value) { + return 0; + } + + @Override + public Integer accept(final CodeJassValue value) { + return 0; + } + + @Override + public Integer accept(final ArrayJassValue value) { + return 0; + } + +} diff --git a/jassparser/src/com/etheller/interpreter/ast/value/visitor/JassFunctionJassValueVisitor.java b/jassparser/src/com/etheller/interpreter/ast/value/visitor/JassFunctionJassValueVisitor.java new file mode 100644 index 0000000..6d9cf1b --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/value/visitor/JassFunctionJassValueVisitor.java @@ -0,0 +1,49 @@ +package com.etheller.interpreter.ast.value.visitor; + +import com.etheller.interpreter.ast.function.JassFunction; +import com.etheller.interpreter.ast.value.ArrayJassValue; +import com.etheller.interpreter.ast.value.BooleanJassValue; +import com.etheller.interpreter.ast.value.CodeJassValue; +import com.etheller.interpreter.ast.value.IntegerJassValue; +import com.etheller.interpreter.ast.value.JassValueVisitor; +import com.etheller.interpreter.ast.value.RealJassValue; +import com.etheller.interpreter.ast.value.StringJassValue; + +public class JassFunctionJassValueVisitor implements JassValueVisitor { + private static final JassFunctionJassValueVisitor INSTANCE = new JassFunctionJassValueVisitor(); + + public static JassFunctionJassValueVisitor getInstance() { + return INSTANCE; + } + + @Override + public JassFunction accept(final IntegerJassValue value) { + return null; + } + + @Override + public JassFunction accept(final RealJassValue value) { + return null; + } + + @Override + public JassFunction accept(final BooleanJassValue value) { + return null; + } + + @Override + public JassFunction accept(final StringJassValue value) { + return null; + } + + @Override + public JassFunction accept(final CodeJassValue value) { + return value.getValue(); + } + + @Override + public JassFunction accept(final ArrayJassValue value) { + return null; + } + +} diff --git a/jassparser/src/com/etheller/interpreter/ast/value/visitor/JassTypeGettingValueVisitor.java b/jassparser/src/com/etheller/interpreter/ast/value/visitor/JassTypeGettingValueVisitor.java new file mode 100644 index 0000000..2f1f1ab --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/value/visitor/JassTypeGettingValueVisitor.java @@ -0,0 +1,49 @@ +package com.etheller.interpreter.ast.value.visitor; + +import com.etheller.interpreter.ast.value.ArrayJassValue; +import com.etheller.interpreter.ast.value.BooleanJassValue; +import com.etheller.interpreter.ast.value.CodeJassValue; +import com.etheller.interpreter.ast.value.IntegerJassValue; +import com.etheller.interpreter.ast.value.JassType; +import com.etheller.interpreter.ast.value.JassValueVisitor; +import com.etheller.interpreter.ast.value.RealJassValue; +import com.etheller.interpreter.ast.value.StringJassValue; + +public class JassTypeGettingValueVisitor implements JassValueVisitor { + public static JassTypeGettingValueVisitor INSTANCE = new JassTypeGettingValueVisitor(); + + public static JassTypeGettingValueVisitor getInstance() { + return INSTANCE; + } + + @Override + public JassType accept(final IntegerJassValue value) { + return JassType.INTEGER; + } + + @Override + public JassType accept(final RealJassValue value) { + return JassType.REAL; + } + + @Override + public JassType accept(final BooleanJassValue value) { + return JassType.BOOLEAN; + } + + @Override + public JassType accept(final StringJassValue value) { + return JassType.STRING; + } + + @Override + public JassType accept(final CodeJassValue value) { + return JassType.CODE; + } + + @Override + public JassType accept(final ArrayJassValue value) { + return value.getType(); + } + +} diff --git a/jassparser/src/com/etheller/interpreter/ast/value/visitor/StringJassValueVisitor.java b/jassparser/src/com/etheller/interpreter/ast/value/visitor/StringJassValueVisitor.java new file mode 100644 index 0000000..e589d0b --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/value/visitor/StringJassValueVisitor.java @@ -0,0 +1,48 @@ +package com.etheller.interpreter.ast.value.visitor; + +import com.etheller.interpreter.ast.value.ArrayJassValue; +import com.etheller.interpreter.ast.value.BooleanJassValue; +import com.etheller.interpreter.ast.value.CodeJassValue; +import com.etheller.interpreter.ast.value.IntegerJassValue; +import com.etheller.interpreter.ast.value.JassValueVisitor; +import com.etheller.interpreter.ast.value.RealJassValue; +import com.etheller.interpreter.ast.value.StringJassValue; + +public class StringJassValueVisitor implements JassValueVisitor { + private static final StringJassValueVisitor INSTANCE = new StringJassValueVisitor(); + + public static StringJassValueVisitor getInstance() { + return INSTANCE; + } + + @Override + public String accept(final IntegerJassValue value) { + return null; + } + + @Override + public String accept(final RealJassValue value) { + return null; + } + + @Override + public String accept(final BooleanJassValue value) { + return null; + } + + @Override + public String accept(final StringJassValue value) { + return value.getValue(); + } + + @Override + public String accept(final CodeJassValue value) { + return null; + } + + @Override + public String accept(final ArrayJassValue value) { + return null; + } + +} diff --git a/jassparser/src/com/etheller/interpreter/ast/visitors/ArgumentExpressionHandler.java b/jassparser/src/com/etheller/interpreter/ast/visitors/ArgumentExpressionHandler.java new file mode 100644 index 0000000..8ec596e --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/visitors/ArgumentExpressionHandler.java @@ -0,0 +1,15 @@ +package com.etheller.interpreter.ast.visitors; + +public class ArgumentExpressionHandler { + protected JassArgumentsVisitor argumentsVisitor; + protected JassExpressionVisitor expressionVisitor; + + public void setJassArgumentsVisitor(final JassArgumentsVisitor jassArgumentsVisitor) { + this.argumentsVisitor = jassArgumentsVisitor; + } + + public void setJassExpressionVisitor(final JassExpressionVisitor jassExpressionVisitor) { + this.expressionVisitor = jassExpressionVisitor; + } + +} diff --git a/jassparser/src/com/etheller/interpreter/ast/visitors/JassArgumentsVisitor.java b/jassparser/src/com/etheller/interpreter/ast/visitors/JassArgumentsVisitor.java new file mode 100644 index 0000000..ce9cdd5 --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/visitors/JassArgumentsVisitor.java @@ -0,0 +1,38 @@ +package com.etheller.interpreter.ast.visitors; + +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +import com.etheller.interpreter.JassBaseVisitor; +import com.etheller.interpreter.JassParser.EmptyArgumentContext; +import com.etheller.interpreter.JassParser.ListArgumentContext; +import com.etheller.interpreter.JassParser.SingleArgumentContext; +import com.etheller.interpreter.ast.expression.JassExpression; + +public class JassArgumentsVisitor extends JassBaseVisitor> { + private final ArgumentExpressionHandler argumentExpressionHandler; + + public JassArgumentsVisitor(final ArgumentExpressionHandler argumentExpressionHandler) { + this.argumentExpressionHandler = argumentExpressionHandler; + } + + @Override + public List visitSingleArgument(final SingleArgumentContext ctx) { + final List list = new LinkedList<>(); + list.add(argumentExpressionHandler.expressionVisitor.visit(ctx.expression())); + return list; + } + + @Override + public List visitListArgument(final ListArgumentContext ctx) { + final List list = visit(ctx.argsList()); + list.add(0, argumentExpressionHandler.expressionVisitor.visit(ctx.expression())); + return list; + } + + @Override + public List visitEmptyArgument(final EmptyArgumentContext ctx) { + return Collections.EMPTY_LIST; + } +} diff --git a/jassparser/src/com/etheller/interpreter/ast/visitors/JassExpressionVisitor.java b/jassparser/src/com/etheller/interpreter/ast/visitors/JassExpressionVisitor.java new file mode 100644 index 0000000..21dcf35 --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/visitors/JassExpressionVisitor.java @@ -0,0 +1,77 @@ +package com.etheller.interpreter.ast.visitors; + +import com.etheller.interpreter.JassBaseVisitor; +import com.etheller.interpreter.JassParser.ArrayReferenceExpressionContext; +import com.etheller.interpreter.JassParser.FalseExpressionContext; +import com.etheller.interpreter.JassParser.FunctionCallExpressionContext; +import com.etheller.interpreter.JassParser.FunctionReferenceExpressionContext; +import com.etheller.interpreter.JassParser.IntegerLiteralExpressionContext; +import com.etheller.interpreter.JassParser.ParentheticalExpressionContext; +import com.etheller.interpreter.JassParser.ReferenceExpressionContext; +import com.etheller.interpreter.JassParser.StringLiteralExpressionContext; +import com.etheller.interpreter.JassParser.TrueExpressionContext; +import com.etheller.interpreter.ast.expression.ArrayRefJassExpression; +import com.etheller.interpreter.ast.expression.FunctionCallJassExpression; +import com.etheller.interpreter.ast.expression.FunctionReferenceJassExpression; +import com.etheller.interpreter.ast.expression.JassExpression; +import com.etheller.interpreter.ast.expression.LiteralJassExpression; +import com.etheller.interpreter.ast.expression.ReferenceJassExpression; +import com.etheller.interpreter.ast.value.BooleanJassValue; +import com.etheller.interpreter.ast.value.IntegerJassValue; +import com.etheller.interpreter.ast.value.StringJassValue; + +public class JassExpressionVisitor extends JassBaseVisitor { + private final ArgumentExpressionHandler argumentExpressionHandler; + + public JassExpressionVisitor(final ArgumentExpressionHandler argumentExpressionHandler) { + this.argumentExpressionHandler = argumentExpressionHandler; + } + + @Override + public JassExpression visitReferenceExpression(final ReferenceExpressionContext ctx) { + return new ReferenceJassExpression(ctx.ID().getText()); + } + + @Override + public JassExpression visitParentheticalExpression(final ParentheticalExpressionContext ctx) { + return visit(ctx.expression()); + } + + @Override + public JassExpression visitStringLiteralExpression(final StringLiteralExpressionContext ctx) { + final String stringLiteralText = ctx.STRING_LITERAL().getText(); + return new LiteralJassExpression( + new StringJassValue(stringLiteralText.substring(1, stringLiteralText.length() - 1))); + } + + @Override + public JassExpression visitIntegerLiteralExpression(final IntegerLiteralExpressionContext ctx) { + return new LiteralJassExpression(new IntegerJassValue(Integer.parseInt(ctx.INTEGER().getText()))); + } + + @Override + public JassExpression visitFunctionReferenceExpression(final FunctionReferenceExpressionContext ctx) { + return new FunctionReferenceJassExpression(ctx.ID().getText()); + } + + @Override + public JassExpression visitArrayReferenceExpression(final ArrayReferenceExpressionContext ctx) { + return new ArrayRefJassExpression(ctx.ID().getText(), visit(ctx.expression())); + } + + @Override + public JassExpression visitFalseExpression(final FalseExpressionContext ctx) { + return new LiteralJassExpression(new BooleanJassValue(false)); + } + + @Override + public JassExpression visitTrueExpression(final TrueExpressionContext ctx) { + return new LiteralJassExpression(new BooleanJassValue(true)); + } + + @Override + public JassExpression visitFunctionCallExpression(final FunctionCallExpressionContext ctx) { + return new FunctionCallJassExpression(ctx.functionExpression().ID().getText(), + argumentExpressionHandler.argumentsVisitor.visit(ctx.functionExpression().argsList())); + } +} diff --git a/jassparser/src/com/etheller/interpreter/ast/visitors/JassGlobalsVisitor.java b/jassparser/src/com/etheller/interpreter/ast/visitors/JassGlobalsVisitor.java new file mode 100644 index 0000000..347d954 --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/visitors/JassGlobalsVisitor.java @@ -0,0 +1,49 @@ +package com.etheller.interpreter.ast.visitors; + +import com.etheller.interpreter.JassBaseVisitor; +import com.etheller.interpreter.JassParser.BasicGlobalContext; +import com.etheller.interpreter.JassParser.DefinitionGlobalContext; +import com.etheller.interpreter.ast.scope.GlobalScope; +import com.etheller.interpreter.ast.scope.LocalScope; +import com.etheller.interpreter.ast.value.JassType; +import com.etheller.interpreter.ast.value.PrimitiveJassType; +import com.etheller.interpreter.ast.value.visitor.ArrayPrimitiveTypeVisitor; + +public class JassGlobalsVisitor extends JassBaseVisitor { + private static final LocalScope EMPTY_LOCAL_SCOPE = new LocalScope(); + private final GlobalScope globals; + private final JassTypeVisitor jassTypeVisitor; + private final JassExpressionVisitor jassExpressionVisitor; + + public JassGlobalsVisitor(final GlobalScope globals, final JassTypeVisitor jassTypeVisitor, + final JassExpressionVisitor jassExpressionVisitor) { + this.globals = globals; + this.jassTypeVisitor = jassTypeVisitor; + this.jassExpressionVisitor = jassExpressionVisitor; + } + + @Override + public Void visitBasicGlobal(final BasicGlobalContext ctx) { + final JassType type = jassTypeVisitor.visit(ctx.type()); + final PrimitiveJassType arrayPrimType = type.visit(ArrayPrimitiveTypeVisitor.getInstance()); + if (arrayPrimType != null) { + globals.createGlobalArray(ctx.ID().getText(), type); + } else { + globals.createGlobal(ctx.ID().getText(), type); + } + return null; + } + + @Override + public Void visitDefinitionGlobal(final DefinitionGlobalContext ctx) { + final JassType type = jassTypeVisitor.visit(ctx.type()); + final PrimitiveJassType arrayPrimType = type.visit(ArrayPrimitiveTypeVisitor.getInstance()); + if (arrayPrimType != null) { + globals.createGlobalArray(ctx.ID().getText(), type); + } else { + globals.createGlobal(ctx.ID().getText(), type, + jassExpressionVisitor.visit(ctx.assignTail().expression()).evaluate(globals, EMPTY_LOCAL_SCOPE)); + } + return null; + } +} diff --git a/jassparser/src/com/etheller/interpreter/ast/visitors/JassParametersVisitor.java b/jassparser/src/com/etheller/interpreter/ast/visitors/JassParametersVisitor.java new file mode 100644 index 0000000..89cde26 --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/visitors/JassParametersVisitor.java @@ -0,0 +1,38 @@ +package com.etheller.interpreter.ast.visitors; + +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +import com.etheller.interpreter.JassBaseVisitor; +import com.etheller.interpreter.JassParser.ListParameterContext; +import com.etheller.interpreter.JassParser.NothingParameterContext; +import com.etheller.interpreter.JassParser.SingleParameterContext; +import com.etheller.interpreter.ast.function.JassParameter; + +public class JassParametersVisitor extends JassBaseVisitor> { + private final JassTypeVisitor typeVisitor; + + public JassParametersVisitor(final JassTypeVisitor typeVisitor) { + this.typeVisitor = typeVisitor; + } + + @Override + public List visitSingleParameter(final SingleParameterContext ctx) { + final List list = new LinkedList<>(); + list.add(new JassParameter(typeVisitor.visit(ctx.param().type()), ctx.param().ID().getText())); + return list; + } + + @Override + public List visitListParameter(final ListParameterContext ctx) { + final List list = visit(ctx.paramList()); + list.add(0, new JassParameter(typeVisitor.visit(ctx.param().type()), ctx.param().ID().getText())); + return list; + } + + @Override + public List visitNothingParameter(final NothingParameterContext ctx) { + return Collections.EMPTY_LIST; + } +} diff --git a/jassparser/src/com/etheller/interpreter/ast/visitors/JassProgramVisitor.java b/jassparser/src/com/etheller/interpreter/ast/visitors/JassProgramVisitor.java new file mode 100644 index 0000000..6162427 --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/visitors/JassProgramVisitor.java @@ -0,0 +1,87 @@ +package com.etheller.interpreter.ast.visitors; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.etheller.interpreter.JassBaseVisitor; +import com.etheller.interpreter.JassParser.BlockContext; +import com.etheller.interpreter.JassParser.FunctionBlockContext; +import com.etheller.interpreter.JassParser.GlobalContext; +import com.etheller.interpreter.JassParser.ProgramContext; +import com.etheller.interpreter.JassParser.StatementContext; +import com.etheller.interpreter.JassParser.TypeDefinitionContext; +import com.etheller.interpreter.ast.function.JassFunction; +import com.etheller.interpreter.ast.function.JassNativeManager; +import com.etheller.interpreter.ast.function.UserJassFunction; +import com.etheller.interpreter.ast.scope.GlobalScope; +import com.etheller.interpreter.ast.statement.JassStatement; + +public class JassProgramVisitor extends JassBaseVisitor { + private final GlobalScope globals = new GlobalScope(); + private final JassNativeManager jassNativeManager = new JassNativeManager(); + private final Map typeToSuperType = new HashMap<>(); + private final JassTypeVisitor jassTypeVisitor = new JassTypeVisitor(this.globals); + private final ArgumentExpressionHandler argumentExpressionHandler = new ArgumentExpressionHandler(); + private final JassExpressionVisitor jassExpressionVisitor = new JassExpressionVisitor( + this.argumentExpressionHandler); + private final JassArgumentsVisitor jassArgumentsVisitor = new JassArgumentsVisitor(this.argumentExpressionHandler); + { + this.argumentExpressionHandler.setJassArgumentsVisitor(this.jassArgumentsVisitor); + this.argumentExpressionHandler.setJassExpressionVisitor(this.jassExpressionVisitor); + } + private final JassGlobalsVisitor jassGlobalsVisitor = new JassGlobalsVisitor(this.globals, this.jassTypeVisitor, + this.jassExpressionVisitor); + private final JassParametersVisitor jassParametersVisitor = new JassParametersVisitor(this.jassTypeVisitor); + private final JassStatementVisitor jassStatementVisitor = new JassStatementVisitor(this.argumentExpressionHandler); + + @Override + public Void visitBlock(final BlockContext ctx) { + if (ctx.globalsBlock() != null) { + for (final GlobalContext globalContext : ctx.globalsBlock().global()) { + this.jassGlobalsVisitor.visit(globalContext); + } + } + else if (ctx.nativeBlock() != null) { + this.jassNativeManager.registerNativeCode(ctx.nativeBlock().ID().getText(), + this.jassParametersVisitor.visit(ctx.nativeBlock().paramList()), + this.jassTypeVisitor.visit(ctx.nativeBlock().type()), this.globals); + } + return null; + } + + @Override + public Void visitProgram(final ProgramContext ctx) { + for (final TypeDefinitionContext typeDefinitionContext : ctx.typeDefinitionBlock().typeDefinition()) { + this.typeToSuperType.put(typeDefinitionContext.ID(0).getText(), typeDefinitionContext.ID(1).getText()); + } + for (final BlockContext blockContext : ctx.block()) { + visit(blockContext); + } + for (final FunctionBlockContext functionBlockContext : ctx.functionBlock()) { + final List statements = new ArrayList<>(); + for (final StatementContext statementContext : functionBlockContext.statement()) { + statements.add(this.jassStatementVisitor.visit(statementContext)); + } + final UserJassFunction userJassFunction = new UserJassFunction(statements, + this.jassParametersVisitor.visit(functionBlockContext.paramList()), + this.jassTypeVisitor.visit(functionBlockContext.type())); + this.globals.defineFunction(functionBlockContext.ID().getText(), userJassFunction); + } + final JassFunction mainFunction = this.globals.getFunctionByName("main"); + if (mainFunction != null) { + mainFunction.call(Collections.EMPTY_LIST, this.globals); + } + return null; + } + + public GlobalScope getGlobals() { + return this.globals; + } + + public JassNativeManager getJassNativeManager() { + return this.jassNativeManager; + } +} diff --git a/jassparser/src/com/etheller/interpreter/ast/visitors/JassStatementVisitor.java b/jassparser/src/com/etheller/interpreter/ast/visitors/JassStatementVisitor.java new file mode 100644 index 0000000..44cabed --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/visitors/JassStatementVisitor.java @@ -0,0 +1,44 @@ +package com.etheller.interpreter.ast.visitors; + +import com.etheller.interpreter.JassBaseVisitor; +import com.etheller.interpreter.JassParser.ArrayedAssignmentStatementContext; +import com.etheller.interpreter.JassParser.CallStatementContext; +import com.etheller.interpreter.JassParser.ReturnStatementContext; +import com.etheller.interpreter.JassParser.SetStatementContext; +import com.etheller.interpreter.ast.statement.JassArrayedAssignmentStatement; +import com.etheller.interpreter.ast.statement.JassCallStatement; +import com.etheller.interpreter.ast.statement.JassReturnStatement; +import com.etheller.interpreter.ast.statement.JassSetStatement; +import com.etheller.interpreter.ast.statement.JassStatement; + +public class JassStatementVisitor extends JassBaseVisitor { + private final ArgumentExpressionHandler argumentExpressionHandler; + + public JassStatementVisitor(final ArgumentExpressionHandler argumentExpressionHandler) { + this.argumentExpressionHandler = argumentExpressionHandler; + } + + @Override + public JassStatement visitCallStatement(final CallStatementContext ctx) { + return new JassCallStatement(ctx.functionExpression().ID().getText(), + argumentExpressionHandler.argumentsVisitor.visit(ctx.functionExpression().argsList())); + } + + @Override + public JassStatement visitSetStatement(final SetStatementContext ctx) { + return new JassSetStatement(ctx.ID().getText(), + argumentExpressionHandler.expressionVisitor.visit(ctx.expression())); + } + + @Override + public JassStatement visitReturnStatement(final ReturnStatementContext ctx) { + return new JassReturnStatement(argumentExpressionHandler.expressionVisitor.visit(ctx.expression())); + } + + @Override + public JassStatement visitArrayedAssignmentStatement(final ArrayedAssignmentStatementContext ctx) { + return new JassArrayedAssignmentStatement(ctx.ID().getText(), + argumentExpressionHandler.expressionVisitor.visit(ctx.expression(0)), + argumentExpressionHandler.expressionVisitor.visit(ctx.expression(1))); + } +} diff --git a/jassparser/src/com/etheller/interpreter/ast/visitors/JassTypeVisitor.java b/jassparser/src/com/etheller/interpreter/ast/visitors/JassTypeVisitor.java new file mode 100644 index 0000000..d4530b0 --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/visitors/JassTypeVisitor.java @@ -0,0 +1,31 @@ +package com.etheller.interpreter.ast.visitors; + +import com.etheller.interpreter.JassBaseVisitor; +import com.etheller.interpreter.JassParser.ArrayTypeContext; +import com.etheller.interpreter.JassParser.BasicTypeContext; +import com.etheller.interpreter.JassParser.NothingTypeContext; +import com.etheller.interpreter.ast.scope.GlobalScope; +import com.etheller.interpreter.ast.value.JassType; + +public class JassTypeVisitor extends JassBaseVisitor { + private final GlobalScope globals; + + public JassTypeVisitor(final GlobalScope globals) { + this.globals = globals; + } + + @Override + public JassType visitArrayType(final ArrayTypeContext ctx) { + return globals.parseArrayType(ctx.ID().getText()); + } + + @Override + public JassType visitBasicType(final BasicTypeContext ctx) { + return globals.parseType(ctx.ID().getText()); + } + + @Override + public JassType visitNothingType(final NothingTypeContext ctx) { + return JassType.NOTHING; + } +} diff --git a/settings.gradle b/settings.gradle index c398e3a..5d318b0 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -include 'desktop', 'core', 'fdfparser' \ No newline at end of file +include 'desktop', 'core', 'fdfparser', 'jassparser' \ No newline at end of file From f2af7d7f73c550fe12895a74212fc1469307b4a2 Mon Sep 17 00:00:00 2001 From: Retera Date: Wed, 27 May 2020 23:56:52 -0400 Subject: [PATCH 035/116] Scene revert --- .../com/etheller/warsmash/viewer5/Scene.java | 83 +++++++++++--- .../warsmash/viewer5/SimpleScene.java | 73 ------------ .../etheller/warsmash/viewer5/WorldScene.java | 105 ------------------ 3 files changed, 69 insertions(+), 192 deletions(-) delete mode 100644 core/src/com/etheller/warsmash/viewer5/SimpleScene.java delete mode 100644 core/src/com/etheller/warsmash/viewer5/WorldScene.java diff --git a/core/src/com/etheller/warsmash/viewer5/Scene.java b/core/src/com/etheller/warsmash/viewer5/Scene.java index 08f86fb..b5a2433 100644 --- a/core/src/com/etheller/warsmash/viewer5/Scene.java +++ b/core/src/com/etheller/warsmash/viewer5/Scene.java @@ -31,18 +31,21 @@ import com.etheller.warsmash.viewer5.handlers.w3x.DynamicShadowManager; * audio is always on in LibGDX generally. So we will probably simplify or skip * over those behaviors other than a boolean on/off toggle for audio. */ -public abstract class Scene { +public class Scene { public final ModelViewer viewer; public final Camera camera; + public Grid grid; + public int visibleCells; + public int visibleInstances; public int updatedParticles; public boolean audioEnabled; public AudioContext audioContext; public final List instances; - public int currentInstance; + public final int currentInstance; public final List batchedInstances; - public int currentBatchedInstance; + public final int currentBatchedInstance; public final EmittedObjectUpdater emitterObjectUpdater; public final Map batches; public final Comparator instanceDepthComparator; @@ -61,7 +64,10 @@ public abstract class Scene { final CanvasProvider canvas = viewer.canvas; this.viewer = viewer; this.camera = new Camera(); + this.grid = new Grid(-100000, -100000, 200000, 200000, 200000, 200000); + this.visibleCells = 0; + this.visibleInstances = 0; this.updatedParticles = 0; this.audioEnabled = false; @@ -111,7 +117,8 @@ public abstract class Scene { // Only allow instances that are actually ok to be added the scene. if (instance.model.ok) { - instanceMoved(instance, instance.worldLocation.x, instance.worldLocation.y); + this.grid.moved(instance, instance.worldLocation.x, instance.worldLocation.y); + return true; } } @@ -119,11 +126,9 @@ public abstract class Scene { return false; } - public abstract void instanceMoved(ModelInstance instance, float x, float y); - public boolean removeInstance(final ModelInstance instance) { if (instance.scene == this) { - innerRemove(instance); + this.grid.remove(instance); instance.scene = null; this.instances.remove(instance); @@ -133,9 +138,17 @@ public abstract class Scene { return false; } - protected abstract void innerRemove(ModelInstance instance); + public void clear() { + // First remove references to this scene stored in the instances. + for (final GridCell cell : this.grid.cells) { + for (final ModelInstance instance : cell.instances) { + instance.scene = null; + } + } - public abstract void clear(); + // Then remove references to the instances. + this.grid.clear(); + } public boolean detach() { if (this.viewer != null) { @@ -178,10 +191,54 @@ public abstract class Scene { final int frame = this.viewer.frame; - final int currentInstance = 0; - final int currentBatchedInstance = 0; + int currentInstance = 0; + int currentBatchedInstance = 0; - innerUpdate(dt, frame); + this.visibleCells = 0; + this.visibleInstances = 0; + + // Update and collect all of the visible instances. + for (final GridCell cell : this.grid.cells) { + if (cell.isVisible(this.camera)) { + this.visibleCells += 1; + + for (final ModelInstance instance : new ArrayList<>(cell.instances)) { +// final ModelInstance instance = cell.instances.get(i); + if (instance.rendered && (instance.cullFrame < frame) && instance.isVisible(this.camera)) { + instance.cullFrame = frame; + + if (instance.updateFrame < frame) { + instance.update(dt, this); + if (!instance.rendered) { + // it became hidden while it updated + continue; + } + } + + if (instance.isBatched()) { + if (currentBatchedInstance < this.batchedInstances.size()) { + this.batchedInstances.set(currentBatchedInstance++, instance); + } + else { + this.batchedInstances.add(instance); + currentBatchedInstance++; + } + } + else { + if (currentInstance < this.instances.size()) { + this.instances.set(currentInstance++, instance); + } + else { + this.instances.add(instance); + currentInstance++; + } + } + + this.visibleInstances += 1; + } + } + } + } for (int i = this.batchedInstances.size() - 1; i >= currentBatchedInstance; i--) { this.batchedInstances.remove(i); @@ -196,8 +253,6 @@ public abstract class Scene { this.updatedParticles = this.emitterObjectUpdater.objects.size(); } - protected abstract void innerUpdate(float dt, int frame); - public void startFrame() { final GL20 gl = this.viewer.gl; final Rectangle viewport = this.camera.rect; diff --git a/core/src/com/etheller/warsmash/viewer5/SimpleScene.java b/core/src/com/etheller/warsmash/viewer5/SimpleScene.java deleted file mode 100644 index 735ca65..0000000 --- a/core/src/com/etheller/warsmash/viewer5/SimpleScene.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.etheller.warsmash.viewer5; - -import java.util.ArrayList; -import java.util.List; - -public class SimpleScene extends Scene { - private final List allInstances = new ArrayList<>(); - - @Override - public void instanceMoved(final ModelInstance instance, final float x, final float y) { - if (instance.left == -1) { - instance.left = 0; - this.allInstances.add(instance); - } - } - - @Override - protected void innerRemove(final ModelInstance instance) { - this.allInstances.remove(instance); - instance.left = -1; - } - - @Override - public void clear() { - for (final ModelInstance instance : this.allInstances) { - instance.scene = null; - } - this.allInstances.clear(); - } - - @Override - protected void innerUpdate(final float dt, final int frame) { - - // Update and collect all of the visible instances. - for (final ModelInstance instance : new ArrayList<>(this.allInstances)) { - // Below: current SimpleScene is not checking instance visibility. - // It's meant to be simple. Low number of models. Render everything. - // Otherwise unit portraits bust - if (instance.rendered && (instance.cullFrame < frame) && instance.isVisible(this.camera)) { - instance.cullFrame = frame; - - if (instance.updateFrame < frame) { - instance.update(dt, this); - if (!instance.rendered) { - // it became hidden while it updated - continue; - } - } - - if (instance.isBatched()) { - if (this.currentBatchedInstance < this.batchedInstances.size()) { - this.batchedInstances.set(this.currentBatchedInstance++, instance); - } - else { - this.batchedInstances.add(instance); - this.currentBatchedInstance++; - } - } - else { - if (this.currentInstance < this.instances.size()) { - this.instances.set(this.currentInstance++, instance); - } - else { - this.instances.add(instance); - this.currentInstance++; - } - } - - } - } - } - -} diff --git a/core/src/com/etheller/warsmash/viewer5/WorldScene.java b/core/src/com/etheller/warsmash/viewer5/WorldScene.java deleted file mode 100644 index b2d6043..0000000 --- a/core/src/com/etheller/warsmash/viewer5/WorldScene.java +++ /dev/null @@ -1,105 +0,0 @@ -package com.etheller.warsmash.viewer5; - -import java.util.ArrayList; - -/** - * A scene. - * - * Every scene has its own list of model instances, and its own camera and - * viewport. - * - * In addition, in Ghostwolf's original code every scene may have its own - * AudioContext if enableAudio() is called. If audo is enabled, the - * AudioContext's listener's location will be updated automatically. Note that - * due to browser policies, this may be done only after user interaction with - * the web page. - * - * In "Warsmash", we are starting from an attempt to replicate Ghostwolf, but - * audio is always on in LibGDX generally. So we will probably simplify or skip - * over those behaviors other than a boolean on/off toggle for audio. - */ -public class WorldScene extends Scene { - - public Grid grid; - public int visibleCells; - public int visibleInstances; - - public WorldScene(final ModelViewer viewer) { - super(viewer); - this.grid = new Grid(-100000, -100000, 200000, 200000, 200000, 200000); - this.visibleCells = 0; - this.visibleInstances = 0; - } - - @Override - public void instanceMoved(final ModelInstance instance, final float x, final float y) { - this.grid.moved(instance, x, y); - } - - @Override - protected void innerRemove(final ModelInstance instance) { - this.grid.remove(instance); - } - - @Override - public void clear() { - // First remove references to this scene stored in the instances. - for (final GridCell cell : this.grid.cells) { - for (final ModelInstance instance : cell.instances) { - instance.scene = null; - } - } - - // Then remove references to the instances. - this.grid.clear(); - } - - @Override - protected void innerUpdate(final float dt, final int frame) { - this.visibleCells = 0; - this.visibleInstances = 0; - - // Update and collect all of the visible instances. - for (final GridCell cell : this.grid.cells) { - if (cell.isVisible(this.camera)) { - this.visibleCells += 1; - - for (final ModelInstance instance : new ArrayList<>(cell.instances)) { -// final ModelInstance instance = cell.instances.get(i); - if (instance.rendered && (instance.cullFrame < frame) && instance.isVisible(this.camera)) { - instance.cullFrame = frame; - - if (instance.updateFrame < frame) { - instance.update(dt, this); - if (!instance.rendered) { - // it became hidden while it updated - continue; - } - } - - if (instance.isBatched()) { - if (this.currentBatchedInstance < this.batchedInstances.size()) { - this.batchedInstances.set(this.currentBatchedInstance++, instance); - } - else { - this.batchedInstances.add(instance); - this.currentBatchedInstance++; - } - } - else { - if (this.currentInstance < this.instances.size()) { - this.instances.set(this.currentInstance++, instance); - } - else { - this.instances.add(instance); - this.currentInstance++; - } - } - - this.visibleInstances += 1; - } - } - } - } - } -} From 55dfe10034dc74f4880294c1cf8eeff2d9c9de66 Mon Sep 17 00:00:00 2001 From: Retera Date: Thu, 28 May 2020 01:16:41 -0400 Subject: [PATCH 036/116] Mostly working render optimizations --- .../etheller/warsmash/WarsmashGdxGame.java | 2 +- .../etheller/warsmash/WarsmashGdxMapGame.java | 8 +- .../com/etheller/warsmash/viewer5/Grid.java | 2 +- .../etheller/warsmash/viewer5/GridCell.java | 4 - .../warsmash/viewer5/ModelInstance.java | 2 +- .../warsmash/viewer5/ModelViewer.java | 12 +- .../com/etheller/warsmash/viewer5/Scene.java | 106 ++++++------------ .../warsmash/viewer5/SimpleScene.java | 79 +++++++++++++ .../etheller/warsmash/viewer5/WorldScene.java | 101 +++++++++++++++++ .../viewer5/handlers/w3x/War3MapViewer.java | 6 +- .../w3x/rendersim/RenderAttackProjectile.java | 2 +- .../handlers/w3x/rendersim/RenderUnit.java | 2 +- 12 files changed, 239 insertions(+), 87 deletions(-) create mode 100644 core/src/com/etheller/warsmash/viewer5/SimpleScene.java create mode 100644 core/src/com/etheller/warsmash/viewer5/WorldScene.java diff --git a/core/src/com/etheller/warsmash/WarsmashGdxGame.java b/core/src/com/etheller/warsmash/WarsmashGdxGame.java index 9840bb8..2a02287 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxGame.java @@ -68,7 +68,7 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide this.viewer.addHandler(new MdxHandler()); this.viewer.enableAudio(); - final Scene scene = this.viewer.addScene(); + final Scene scene = this.viewer.addWorldScene(); scene.enableAudio(); this.cameraManager = new CameraManager(); diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java index 177c486..5ca6faf 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java @@ -136,7 +136,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.viewer.worldScene.enableAudio(); this.viewer.enableAudio(); try { - this.viewer.loadMap("PeasantTest.w3x"); + this.viewer.loadMap("American Colo EX 1.0 unpro.w3x"); } catch (final IOException e) { throw new RuntimeException(e); @@ -149,11 +149,11 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv Gdx.gl30.glClearColor(0.0f, 0.0f, 0.0f, 1); // TODO remove white background Gdx.gl30.glEnable(GL30.GL_SCISSOR_TEST); - this.portraitScene = this.viewer.addScene(); + this.portraitScene = this.viewer.addSimpleScene(); this.portraitCameraManager = new CameraManager(); this.portraitCameraManager.setupCamera(this.portraitScene); - this.uiScene = this.viewer.addScene(); + this.uiScene = this.viewer.addSimpleScene(); this.uiScene.alpha = true; // this.mainModel = (MdxModel) this.viewer.load("UI\\Glues\\MainMenu\\MainMenu3D_exp\\MainMenu3D_exp.mdx", @@ -649,7 +649,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.portraitInstance = (MdxComplexInstance) portraitModel.addInstance(); this.portraitInstance.setSequenceLoopMode(1); this.portraitInstance.setScene(this.portraitScene); -// this.portraitInstance.setVertexColor(new float[] { 1, 1, 1, 0.5f }); + this.portraitInstance.setVertexColor(unit.instance.vertexColor); if (portraitModel.getCameras().size() > 0) { this.portraitCameraManager.modelCamera = portraitModel.getCameras().get(0); } diff --git a/core/src/com/etheller/warsmash/viewer5/Grid.java b/core/src/com/etheller/warsmash/viewer5/Grid.java index 63364d1..ee2b427 100644 --- a/core/src/com/etheller/warsmash/viewer5/Grid.java +++ b/core/src/com/etheller/warsmash/viewer5/Grid.java @@ -30,7 +30,7 @@ public class Grid { for (int row = 0; row < rows; row++) { for (int column = 0; column < columns; column++) { - final float left = x + (columns * cellWidth); + final float left = x + (column * cellWidth); final float right = left + cellWidth; final float bottom = y + (row * cellDepth); final float top = bottom + cellDepth; diff --git a/core/src/com/etheller/warsmash/viewer5/GridCell.java b/core/src/com/etheller/warsmash/viewer5/GridCell.java index 10b4062..a3c035d 100644 --- a/core/src/com/etheller/warsmash/viewer5/GridCell.java +++ b/core/src/com/etheller/warsmash/viewer5/GridCell.java @@ -37,10 +37,6 @@ public class GridCell { } public boolean isVisible(final Camera camera) { - if (true) { - return true; - } - this.plane = RenderMathUtils.testCell(camera.planes, this.left, this.right, this.bottom, this.top, this.plane); return this.plane == -1; diff --git a/core/src/com/etheller/warsmash/viewer5/ModelInstance.java b/core/src/com/etheller/warsmash/viewer5/ModelInstance.java index 839b820..2c4f334 100644 --- a/core/src/com/etheller/warsmash/viewer5/ModelInstance.java +++ b/core/src/com/etheller/warsmash/viewer5/ModelInstance.java @@ -89,7 +89,7 @@ public abstract class ModelInstance extends Node { super.recalculateTransformation(); if (this.scene != null) { - this.scene.grid.moved(this, this.worldLocation.x, this.worldLocation.y); + this.scene.instanceMoved(this, this.worldLocation.x, this.worldLocation.y); } } diff --git a/core/src/com/etheller/warsmash/viewer5/ModelViewer.java b/core/src/com/etheller/warsmash/viewer5/ModelViewer.java index 8627d64..b27546e 100644 --- a/core/src/com/etheller/warsmash/viewer5/ModelViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/ModelViewer.java @@ -103,8 +103,16 @@ public class ModelViewer { return false; } - public Scene addScene() { - final Scene scene = new Scene(this); + public Scene addSimpleScene() { + final Scene scene = new SimpleScene(this); + + this.scenes.add(scene); + + return scene; + } + + public WorldScene addWorldScene() { + final WorldScene scene = new WorldScene(this); this.scenes.add(scene); diff --git a/core/src/com/etheller/warsmash/viewer5/Scene.java b/core/src/com/etheller/warsmash/viewer5/Scene.java index b5a2433..48a99ec 100644 --- a/core/src/com/etheller/warsmash/viewer5/Scene.java +++ b/core/src/com/etheller/warsmash/viewer5/Scene.java @@ -31,21 +31,20 @@ import com.etheller.warsmash.viewer5.handlers.w3x.DynamicShadowManager; * audio is always on in LibGDX generally. So we will probably simplify or skip * over those behaviors other than a boolean on/off toggle for audio. */ -public class Scene { +public abstract class Scene { public final ModelViewer viewer; - public final Camera camera; - public Grid grid; public int visibleCells; public int visibleInstances; + public final Camera camera; public int updatedParticles; public boolean audioEnabled; public AudioContext audioContext; public final List instances; - public final int currentInstance; + public int currentInstance; public final List batchedInstances; - public final int currentBatchedInstance; + public int currentBatchedInstance; public final EmittedObjectUpdater emitterObjectUpdater; public final Map batches; public final Comparator instanceDepthComparator; @@ -64,10 +63,7 @@ public class Scene { final CanvasProvider canvas = viewer.canvas; this.viewer = viewer; this.camera = new Camera(); - this.grid = new Grid(-100000, -100000, 200000, 200000, 200000, 200000); - this.visibleCells = 0; - this.visibleInstances = 0; this.updatedParticles = 0; this.audioEnabled = false; @@ -87,6 +83,8 @@ public class Scene { this.batches = new HashMap<>(); this.instanceDepthComparator = new InstanceDepthComparator(); + this.visibleCells = 0; + this.visibleInstances = 0; } public boolean enableAudio() { @@ -117,8 +115,24 @@ public class Scene { // Only allow instances that are actually ok to be added the scene. if (instance.model.ok) { - this.grid.moved(instance, instance.worldLocation.x, instance.worldLocation.y); - + // predict x and y of model + float x, y; + if (instance.dirty) { + // TODO this is an incorrect, predicted location for dirty case + if ((instance.parent != null) && !instance.dontInheritTranslation) { + x = instance.parent.localLocation.x + instance.localLocation.x; + y = instance.parent.localLocation.y + instance.localLocation.y; + } + else { + x = instance.localLocation.x; + y = instance.localLocation.y; + } + } + else { + x = instance.worldLocation.x; + y = instance.worldLocation.y; + } + instanceMoved(instance, x, y); return true; } } @@ -126,9 +140,11 @@ public class Scene { return false; } + public abstract void instanceMoved(ModelInstance instance, float x, float y); + public boolean removeInstance(final ModelInstance instance) { if (instance.scene == this) { - this.grid.remove(instance); + innerRemove(instance); instance.scene = null; this.instances.remove(instance); @@ -138,17 +154,9 @@ public class Scene { return false; } - public void clear() { - // First remove references to this scene stored in the instances. - for (final GridCell cell : this.grid.cells) { - for (final ModelInstance instance : cell.instances) { - instance.scene = null; - } - } + protected abstract void innerRemove(ModelInstance instance); - // Then remove references to the instances. - this.grid.clear(); - } + public abstract void clear(); public boolean detach() { if (this.viewer != null) { @@ -191,60 +199,16 @@ public class Scene { final int frame = this.viewer.frame; - int currentInstance = 0; - int currentBatchedInstance = 0; + this.currentInstance = 0; + this.currentBatchedInstance = 0; - this.visibleCells = 0; - this.visibleInstances = 0; + innerUpdate(dt, frame); - // Update and collect all of the visible instances. - for (final GridCell cell : this.grid.cells) { - if (cell.isVisible(this.camera)) { - this.visibleCells += 1; - - for (final ModelInstance instance : new ArrayList<>(cell.instances)) { -// final ModelInstance instance = cell.instances.get(i); - if (instance.rendered && (instance.cullFrame < frame) && instance.isVisible(this.camera)) { - instance.cullFrame = frame; - - if (instance.updateFrame < frame) { - instance.update(dt, this); - if (!instance.rendered) { - // it became hidden while it updated - continue; - } - } - - if (instance.isBatched()) { - if (currentBatchedInstance < this.batchedInstances.size()) { - this.batchedInstances.set(currentBatchedInstance++, instance); - } - else { - this.batchedInstances.add(instance); - currentBatchedInstance++; - } - } - else { - if (currentInstance < this.instances.size()) { - this.instances.set(currentInstance++, instance); - } - else { - this.instances.add(instance); - currentInstance++; - } - } - - this.visibleInstances += 1; - } - } - } - } - - for (int i = this.batchedInstances.size() - 1; i >= currentBatchedInstance; i--) { + for (int i = this.batchedInstances.size() - 1; i >= this.currentBatchedInstance; i--) { this.batchedInstances.remove(i); } - for (int i = this.instances.size() - 1; i >= currentInstance; i--) { + for (int i = this.instances.size() - 1; i >= this.currentInstance; i--) { this.instances.remove(i); } Collections.sort(this.instances, this.instanceDepthComparator); @@ -253,6 +217,8 @@ public class Scene { this.updatedParticles = this.emitterObjectUpdater.objects.size(); } + protected abstract void innerUpdate(float dt, int frame); + public void startFrame() { final GL20 gl = this.viewer.gl; final Rectangle viewport = this.camera.rect; diff --git a/core/src/com/etheller/warsmash/viewer5/SimpleScene.java b/core/src/com/etheller/warsmash/viewer5/SimpleScene.java new file mode 100644 index 0000000..9436e27 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/SimpleScene.java @@ -0,0 +1,79 @@ +package com.etheller.warsmash.viewer5; + +import java.util.ArrayList; +import java.util.List; + +public class SimpleScene extends Scene { + private final List allInstances = new ArrayList<>(); + + public SimpleScene(final ModelViewer viewer) { + super(viewer); + this.visibleCells = 1; + this.visibleInstances = 0; + } + + @Override + public void instanceMoved(final ModelInstance instance, final float x, final float y) { + if (instance.left == -1) { + instance.left = 0; + this.allInstances.add(instance); + } + } + + @Override + protected void innerRemove(final ModelInstance instance) { + this.allInstances.remove(instance); + instance.left = -1; + } + + @Override + public void clear() { + for (final ModelInstance instance : this.allInstances) { + instance.scene = null; + } + this.allInstances.clear(); + } + + @Override + protected void innerUpdate(final float dt, final int frame) { + + // Update and collect all of the visible instances. + for (final ModelInstance instance : new ArrayList<>(this.allInstances)) { + // Below: current SimpleScene is not checking instance visibility. + // It's meant to be simple. Low number of models. Render everything, + // dont check visible + if (instance.rendered && (instance.cullFrame < frame)) { + instance.cullFrame = frame; + + if (instance.updateFrame < frame) { + instance.update(dt, this); + if (!instance.rendered) { + // it became hidden while it updated + continue; + } + } + + if (instance.isBatched()) { + if (this.currentBatchedInstance < this.batchedInstances.size()) { + this.batchedInstances.set(this.currentBatchedInstance++, instance); + } + else { + this.batchedInstances.add(instance); + this.currentBatchedInstance++; + } + } + else { + if (this.currentInstance < this.instances.size()) { + this.instances.set(this.currentInstance++, instance); + } + else { + this.instances.add(instance); + this.currentInstance++; + } + } + + } + } + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/WorldScene.java b/core/src/com/etheller/warsmash/viewer5/WorldScene.java new file mode 100644 index 0000000..1e31686 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/WorldScene.java @@ -0,0 +1,101 @@ +package com.etheller.warsmash.viewer5; + +import java.util.ArrayList; + +/** + * A scene. + * + * Every scene has its own list of model instances, and its own camera and + * viewport. + * + * In addition, in Ghostwolf's original code every scene may have its own + * AudioContext if enableAudio() is called. If audo is enabled, the + * AudioContext's listener's location will be updated automatically. Note that + * due to browser policies, this may be done only after user interaction with + * the web page. + * + * In "Warsmash", we are starting from an attempt to replicate Ghostwolf, but + * audio is always on in LibGDX generally. So we will probably simplify or skip + * over those behaviors other than a boolean on/off toggle for audio. + */ +public class WorldScene extends Scene { + + public Grid grid; + + public WorldScene(final ModelViewer viewer) { + super(viewer); + this.grid = new Grid(-100000, -100000, 200000, 200000, 200000, 200000); + } + + @Override + public void instanceMoved(final ModelInstance instance, final float x, final float y) { + this.grid.moved(instance, x, y); + } + + @Override + protected void innerRemove(final ModelInstance instance) { + this.grid.remove(instance); + } + + @Override + public void clear() { + // First remove references to this scene stored in the instances. + for (final GridCell cell : this.grid.cells) { + for (final ModelInstance instance : cell.instances) { + instance.scene = null; + } + } + + // Then remove references to the instances. + this.grid.clear(); + } + + @Override + protected void innerUpdate(final float dt, final int frame) { + this.visibleCells = 0; + this.visibleInstances = 0; + + // Update and collect all of the visible instances. + for (final GridCell cell : this.grid.cells) { + if (cell.isVisible(this.camera)) { + this.visibleCells += 1; + + for (final ModelInstance instance : new ArrayList<>(cell.instances)) { +// final ModelInstance instance = cell.instances.get(i); + if (instance.rendered && (instance.cullFrame < frame) && instance.isVisible(this.camera)) { + instance.cullFrame = frame; + + if (instance.updateFrame < frame) { + instance.update(dt, this); + if (!instance.rendered) { + // it became hidden while it updated + continue; + } + } + + if (instance.isBatched()) { + if (this.currentBatchedInstance < this.batchedInstances.size()) { + this.batchedInstances.set(this.currentBatchedInstance++, instance); + } + else { + this.batchedInstances.add(instance); + this.currentBatchedInstance++; + } + } + else { + if (this.currentInstance < this.instances.size()) { + this.instances.set(this.currentInstance++, instance); + } + else { + this.instances.add(instance); + this.currentInstance++; + } + } + + this.visibleInstances += 1; + } + } + } + } + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index e18308c..0a54aa5 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -52,6 +52,7 @@ import com.etheller.warsmash.viewer5.ModelViewer; import com.etheller.warsmash.viewer5.PathSolver; import com.etheller.warsmash.viewer5.Scene; import com.etheller.warsmash.viewer5.Texture; +import com.etheller.warsmash.viewer5.WorldScene; import com.etheller.warsmash.viewer5.gl.WebGL; import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxHandler; @@ -101,7 +102,7 @@ public class War3MapViewer extends ModelViewer { public PathSolver wc3PathSolver = PathSolver.DEFAULT; public SolverParams solverParams = new SolverParams(); - public Scene worldScene; + public WorldScene worldScene; public boolean anyReady; public boolean terrainCliffsAndWaterLoaded; public MappedData terrainData = new MappedData(); @@ -155,7 +156,7 @@ public class War3MapViewer extends ModelViewer { this.wc3PathSolver = PathSolver.DEFAULT; - this.worldScene = this.addScene(); + this.worldScene = this.addWorldScene(); if (!this.dynamicShadowManager.setup(webGL)) { throw new IllegalStateException("FrameBuffer setup failed"); @@ -798,6 +799,7 @@ public class War3MapViewer extends ModelViewer { this.confirmationInstance.show(); this.confirmationInstance.setSequence(0); this.confirmationInstance.setLocation(position); + this.worldScene.instanceMoved(this.confirmationInstance, position.x, position.y); this.confirmationInstance.vertexColor[0] = red; this.confirmationInstance.vertexColor[1] = green; this.confirmationInstance.vertexColor[2] = blue; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackProjectile.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackProjectile.java index f1091d0..a2557bd 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackProjectile.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackProjectile.java @@ -102,7 +102,7 @@ public class RenderAttackProjectile { this.modelInstance.setLocation(this.x, this.y, this.z); this.modelInstance.localRotation.setFromAxisRad(0, 0, 1, this.yaw); this.modelInstance.rotate(pitchHeap.setFromAxisRad(0, -1, 0, this.pitch)); - war3MapViewer.worldScene.grid.moved(this.modelInstance, this.x, this.y); + war3MapViewer.worldScene.instanceMoved(this.modelInstance, this.x, this.y); final boolean everythingDone = this.simulationProjectile.isDone() && this.modelInstance.sequenceEnded; if (everythingDone) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java index 88649fb..d1977ac 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java @@ -176,7 +176,7 @@ public class RenderUnit { } this.facing = (((this.facing + angleToAdd) % 360) + 360) % 360; this.instance.setLocalRotation(tempQuat.setFromAxis(RenderMathUtils.VEC3_UNIT_Z, this.facing)); - map.worldScene.grid.moved(this.instance, this.location[0], this.location[1]); + map.worldScene.instanceMoved(this.instance, this.location[0], this.location[1]); final MdxComplexInstance mdxComplexInstance = this.instance; final COrder currentOrder = this.simulationUnit.getCurrentOrder(); if (this.simulationUnit.getLife() <= 0) { From 1c084e9695ceff9ebfb82099ca90451b51fab03d Mon Sep 17 00:00:00 2001 From: Retera Date: Fri, 26 Jun 2020 23:54:42 -0400 Subject: [PATCH 037/116] First draft of a jass parser for some configurable ui tests --- .../etheller/warsmash/WarsmashGdxGame.java | 39 ++-- .../etheller/warsmash/WarsmashGdxMapGame.java | 53 ++---- .../etheller/warsmash/parsers/jass/Jass2.java | 172 ++++++++++++++++++ .../warsmash/viewer5/ModelViewer.java | 2 +- .../viewer5/RawOpenGLTextureResource.java | 9 +- .../handlers/mdx/MdxComplexInstance.java | 12 +- .../viewer5/handlers/mdx/MdxModel.java | 12 +- .../warsmash/desktop/DesktopLauncher.java | 4 +- jassparser/antlr-src/Jass.g4 | 55 ++++-- .../etheller/interpreter/ast/Assignable.java | 9 +- .../ast/expression/NotJassExpression.java | 20 ++ .../ast/function/AbstractJassFunction.java | 13 +- .../ast/function/JassNativeManager.java | 4 + .../ast/function/JassParameter.java | 7 +- .../interpreter/ast/scope/GlobalScope.java | 97 +++++++--- .../interpreter/ast/scope/LocalScope.java | 10 +- .../JassArrayedAssignmentStatement.java | 18 +- .../ast/statement/JassCallStatement.java | 11 +- .../ast/statement/JassIfElseIfStatement.java | 42 +++++ .../ast/statement/JassIfElseStatement.java | 47 +++++ .../ast/statement/JassIfStatement.java | 44 +++++ .../ast/statement/JassReturnStatement.java | 7 +- .../ast/statement/JassSetStatement.java | 14 +- .../interpreter/ast/value/ArrayJassType.java | 20 +- .../ast/value/BooleanJassValue.java | 14 +- .../interpreter/ast/value/HandleJassType.java | 43 +++++ .../ast/value/HandleJassValue.java | 25 +++ .../interpreter/ast/value/JassType.java | 6 +- .../ast/value/JassTypeVisitor.java | 2 + .../ast/value/JassValueVisitor.java | 2 + .../ast/value/PrimitiveJassType.java | 8 +- .../interpreter/ast/value/RealJassType.java | 16 ++ .../value/visitor/ArrayJassValueVisitor.java | 6 + .../visitor/ArrayPrimitiveTypeVisitor.java | 13 +- .../visitor/BooleanJassValueVisitor.java | 54 ++++++ .../value/visitor/HandleJassTypeVisitor.java | 30 +++ .../HandleTypeSuperTypeLoadingVisitor.java | 32 ++++ .../visitor/IntegerJassValueVisitor.java | 6 + .../visitor/JassFunctionJassValueVisitor.java | 6 + .../visitor/JassTypeGettingValueVisitor.java | 6 + .../value/visitor/NotJassValueVisitor.java | 56 ++++++ .../value/visitor/ObjectJassValueVisitor.java | 54 ++++++ .../value/visitor/RealJassValueVisitor.java | 54 ++++++ .../value/visitor/StringJassValueVisitor.java | 6 + .../ast/value/visitor/SuperTypeVisitor.java | 30 +++ .../ast/visitors/JassArgumentsVisitor.java | 11 +- .../ast/visitors/JassExpressionVisitor.java | 21 ++- .../ast/visitors/JassGlobalsVisitor.java | 25 +-- .../ast/visitors/JassProgramVisitor.java | 31 +++- .../ast/visitors/JassStatementVisitor.java | 63 ++++++- resources/Scripts/common.jui | 94 ++++++++++ resources/Scripts/melee.jui | 35 ++++ 52 files changed, 1275 insertions(+), 195 deletions(-) create mode 100644 core/src/com/etheller/warsmash/parsers/jass/Jass2.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/expression/NotJassExpression.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/statement/JassIfElseIfStatement.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/statement/JassIfElseStatement.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/statement/JassIfStatement.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/value/HandleJassType.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/value/HandleJassValue.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/value/RealJassType.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/value/visitor/BooleanJassValueVisitor.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/value/visitor/HandleJassTypeVisitor.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/value/visitor/HandleTypeSuperTypeLoadingVisitor.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/value/visitor/NotJassValueVisitor.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/value/visitor/ObjectJassValueVisitor.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/value/visitor/RealJassValueVisitor.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/value/visitor/SuperTypeVisitor.java create mode 100644 resources/Scripts/common.jui create mode 100644 resources/Scripts/melee.jui diff --git a/core/src/com/etheller/warsmash/WarsmashGdxGame.java b/core/src/com/etheller/warsmash/WarsmashGdxGame.java index 2a02287..3dff1f4 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxGame.java @@ -32,7 +32,7 @@ import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvider { private static final boolean SPIN = false; - private static final boolean ADVANCE_ANIMS = true; + private static final boolean ADVANCE_ANIMS = false; private DataSource codebase; private ModelViewer viewer; private MdxModel model; @@ -69,12 +69,12 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide this.viewer.enableAudio(); final Scene scene = this.viewer.addWorldScene(); - scene.enableAudio(); +// scene.enableAudio(); this.cameraManager = new CameraManager(); this.cameraManager.setupCamera(scene); - this.mainModel = (MdxModel) this.viewer.load("UI\\Glues\\MainMenu\\MainMenu3d\\MainMenu3d.mdx", + this.mainModel = (MdxModel) this.viewer.load("Doodads\\Cinematic\\ArthasIllidanFight\\ArthasIllidanFight.mdx", // this.mainModel = (MdxModel) this.viewer.load("Abilities\\Spells\\Orc\\FeralSpirit\\feralspirittarget.mdx", new PathSolver() { @Override @@ -90,28 +90,15 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide // System.out.println(Arrays.toString(evt.keyFrames)); // System.out.println(evt.name); - this.modelCamera = this.mainModel.cameras.get(0); - this.mainInstance = (MdxComplexInstance) this.mainModel.addInstance(0); this.mainInstance.setScene(scene); - int animIndex = 0; - for (final Sequence s : this.mainModel.getSequences()) { - if (s.getName().toLowerCase().startsWith("stand")) { - animIndex = this.mainModel.getSequences().indexOf(s); - break; - } - } + final int animIndex = 1; + this.modelCamera = this.mainModel.cameras.get(animIndex); this.mainInstance.setSequence(animIndex); - this.mainInstance.setSequenceLoopMode(0); - - final Music music = Gdx.audio - .newMusic(new DataSourceFileHandle(this.viewer.dataSource, "Sound\\Music\\mp3Music\\MainScreen.mp3")); - music.setVolume(0.2f); - music.setLooping(true); - music.play(); + this.mainInstance.setSequenceLoopMode(4); // acolytesHarvestingSceneJoke2(scene); @@ -365,6 +352,7 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide private com.etheller.warsmash.viewer5.handlers.mdx.Camera modelCamera; private final float[] cameraPositionTemp = new float[3]; private final float[] cameraTargetTemp = new float[3]; + private boolean firstFrame = true; @Override public void render() { @@ -375,6 +363,7 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide this.cameraManager.horizontalAngle = 0; } } + this.modelCamera = this.mainModel.cameras.get(this.mainInstance.sequence); this.cameraManager.updateCamera(); this.viewer.updateAndRender(); @@ -390,7 +379,17 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide } if (ADVANCE_ANIMS && this.mainInstance.sequenceEnded) { - this.mainInstance.setSequence((this.mainInstance.sequence + 1) % this.mainModel.getSequences().size()); + final int sequence = (this.mainInstance.sequence + 1) % this.mainModel.getSequences().size(); + this.mainInstance.setSequence(sequence); + this.mainInstance.frame += (int) (Gdx.graphics.getRawDeltaTime() * 1000); + } + if (this.firstFrame) { + final Music music = Gdx.audio.newMusic(new DataSourceFileHandle(this.viewer.dataSource, + "Sound\\Ambient\\DoodadEffects\\FinalCinematic.mp3")); + music.setVolume(0.2f); + music.setLooping(true); + music.play(); + this.firstFrame = false; } } diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java index 5ca6faf..081e422 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java @@ -14,6 +14,7 @@ import com.badlogic.gdx.ApplicationAdapter; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Input; import com.badlogic.gdx.InputProcessor; +import com.badlogic.gdx.audio.Music; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.GL30; @@ -37,10 +38,8 @@ import com.etheller.warsmash.datasources.DataSource; import com.etheller.warsmash.datasources.DataSourceDescriptor; import com.etheller.warsmash.datasources.FolderDataSourceDescriptor; import com.etheller.warsmash.parsers.fdf.GameUI; -import com.etheller.warsmash.parsers.fdf.datamodel.AnchorDefinition; -import com.etheller.warsmash.parsers.fdf.datamodel.FramePoint; -import com.etheller.warsmash.parsers.fdf.frames.UIFrame; -import com.etheller.warsmash.units.Element; +import com.etheller.warsmash.parsers.jass.Jass2; +import com.etheller.warsmash.parsers.jass.Jass2.RootFrameListener; import com.etheller.warsmash.util.DataSourceFileHandle; import com.etheller.warsmash.util.ImageUtils; import com.etheller.warsmash.util.WarsmashConstants; @@ -120,6 +119,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv System.err.println("Renderer: " + renderer); final FolderDataSourceDescriptor war3mpq = new FolderDataSourceDescriptor("E:\\Backups\\Warcraft\\Data\\127"); + final FolderDataSourceDescriptor smashmpq = new FolderDataSourceDescriptor("..\\..\\resources"); // final FolderDataSourceDescriptor war3mpq = new FolderDataSourceDescriptor( // "D:\\NEEDS_ORGANIZING\\MPQBuild\\War3.mpq\\war3.mpq"); // final FolderDataSourceDescriptor war3xLocalmpq = new FolderDataSourceDescriptor( @@ -128,9 +128,8 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv // "E:\\Games\\Warcraft III Patch 1.31 Rebirth"); final FolderDataSourceDescriptor testingFolder = new FolderDataSourceDescriptor("E:\\Backups\\Warsmash\\Data"); final FolderDataSourceDescriptor currentFolder = new FolderDataSourceDescriptor("."); - this.codebase = new CompoundDataSourceDescriptor( - Arrays.asList(war3mpq, /* war3xLocalmpq, */ testingFolder, currentFolder)) - .createDataSource(); + this.codebase = new CompoundDataSourceDescriptor(Arrays.asList(war3mpq, smashmpq, + /* war3xLocalmpq, */ testingFolder, currentFolder)).createDataSource(); this.viewer = new War3MapViewer(this.codebase, this); this.viewer.worldScene.enableAudio(); @@ -232,11 +231,11 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv Gdx.input.setInputProcessor(this); -// final Music music = Gdx.audio -// .newMusic(new DataSourceFileHandle(this.viewer.dataSource, "Sound\\Music\\mp3Music\\DarkAgents.mp3")); -// music.setVolume(0.2f); -// music.setLooping(true); -// music.play(); + final Music music = Gdx.audio + .newMusic(new DataSourceFileHandle(this.viewer.dataSource, "Sound\\Music\\mp3Music\\DarkAgents.mp3")); + music.setVolume(0.2f); + music.setLooping(true); + music.play(); this.minimap = new Rectangle(18.75f, 13.75f, 278.75f, 276.25f); final float worldWidth = (this.viewer.terrain.columns - 1); @@ -255,32 +254,12 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.shapeRenderer = new ShapeRenderer(); this.talentTreeWindow = new Rectangle(100, 300, 1400, 800); - final Element skin = GameUI.loadSkin(this.viewer.dataSource, "Human"); - this.gameUI = new GameUI(this.viewer.dataSource, skin, this.uiViewport); - String timeIndicatorPath = skin.getField("TimeOfDayIndicator"); - if (!this.viewer.dataSource.has(timeIndicatorPath)) { - final int lastDotIndex = timeIndicatorPath.lastIndexOf('.'); - if (lastDotIndex >= 0) { - timeIndicatorPath = timeIndicatorPath.substring(0, lastDotIndex); + Jass2.loadJUI(this.codebase, this.uiViewport, new RootFrameListener() { + @Override + public void onCreate(final GameUI rootFrame) { + WarsmashGdxMapGame.this.gameUI = rootFrame; } - timeIndicatorPath += ".mdx"; - } - this.timeIndicator = (MdxModel) this.viewer.load(timeIndicatorPath, this.viewer.mapPathSolver, - this.viewer.solverParams); - final MdxComplexInstance timeIndicatorInstance = (MdxComplexInstance) this.timeIndicator.addInstance(); - timeIndicatorInstance.setScene(this.uiScene); - timeIndicatorInstance.setSequence(0); - timeIndicatorInstance.setSequenceLoopMode(2); - try { - this.gameUI.loadTOCFile("UI\\FrameDef\\FrameDef.toc"); - } - catch (final IOException e) { - throw new RuntimeException(e); - } - this.gameUI.createSimpleFrame("ConsoleUI", this.gameUI, 0); - final UIFrame resourceBarFrame = this.gameUI.createSimpleFrame("ResourceBarFrame", this.gameUI, 0); - resourceBarFrame.addAnchor(new AnchorDefinition(FramePoint.TOPRIGHT, 0, 0)); - this.gameUI.positionBounds(this.uiViewport); + }, "Scripts\\common.jui", "Scripts\\melee.jui"); } @Override diff --git a/core/src/com/etheller/warsmash/parsers/jass/Jass2.java b/core/src/com/etheller/warsmash/parsers/jass/Jass2.java new file mode 100644 index 0000000..c37b1e6 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/jass/Jass2.java @@ -0,0 +1,172 @@ +package com.etheller.warsmash.parsers.jass; + +import java.io.IOException; +import java.util.List; + +import org.antlr.v4.runtime.BaseErrorListener; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.RecognitionException; +import org.antlr.v4.runtime.Recognizer; + +import com.badlogic.gdx.utils.viewport.Viewport; +import com.etheller.interpreter.JassLexer; +import com.etheller.interpreter.JassParser; +import com.etheller.interpreter.ast.function.JassFunction; +import com.etheller.interpreter.ast.scope.GlobalScope; +import com.etheller.interpreter.ast.value.BooleanJassValue; +import com.etheller.interpreter.ast.value.HandleJassValue; +import com.etheller.interpreter.ast.value.JassValue; +import com.etheller.interpreter.ast.value.StringJassValue; +import com.etheller.interpreter.ast.value.visitor.IntegerJassValueVisitor; +import com.etheller.interpreter.ast.value.visitor.ObjectJassValueVisitor; +import com.etheller.interpreter.ast.value.visitor.RealJassValueVisitor; +import com.etheller.interpreter.ast.value.visitor.StringJassValueVisitor; +import com.etheller.interpreter.ast.visitors.JassProgramVisitor; +import com.etheller.warsmash.datasources.DataSource; +import com.etheller.warsmash.parsers.fdf.GameUI; +import com.etheller.warsmash.parsers.fdf.datamodel.AnchorDefinition; +import com.etheller.warsmash.parsers.fdf.datamodel.FramePoint; +import com.etheller.warsmash.parsers.fdf.frames.UIFrame; +import com.etheller.warsmash.units.Element; + +public class Jass2 { + public static final boolean REPORT_SYNTAX_ERRORS = true; + + public static JUIEnvironment loadJUI(final DataSource dataSource, final Viewport uiViewport, + final RootFrameListener rootFrameListener, final String... files) { + + final JassProgramVisitor jassProgramVisitor = new JassProgramVisitor(); + final JUIEnvironment environment = new JUIEnvironment(jassProgramVisitor, dataSource, uiViewport, + rootFrameListener); + for (final String jassFile : files) { + try { + JassLexer lexer; + try { + lexer = new JassLexer(CharStreams.fromStream(dataSource.getResourceAsStream(jassFile))); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + final JassParser parser = new JassParser(new CommonTokenStream(lexer)); +// parser.removeErrorListener(ConsoleErrorListener.INSTANCE); + parser.addErrorListener(new BaseErrorListener() { + @Override + public void syntaxError(final Recognizer recognizer, final Object offendingSymbol, + final int line, final int charPositionInLine, final String msg, + final RecognitionException e) { + if (!REPORT_SYNTAX_ERRORS) { + return; + } + + final String sourceName = String.format("%s:%d:%d: ", jassFile, line, charPositionInLine); + + System.err.println(sourceName + "line " + line + ":" + charPositionInLine + " " + msg); + } + }); + jassProgramVisitor.visit(parser.program()); + } + catch (final Exception e) { + e.printStackTrace(); + } + } + jassProgramVisitor.getJassNativeManager().checkUnregisteredNatives(); + return environment; + } + + public static interface RootFrameListener { + void onCreate(GameUI rootFrame); + } + + private static final class JUIEnvironment { + private GameUI gameUI; + private Element skin; + + public JUIEnvironment(final JassProgramVisitor jassProgramVisitor, final DataSource dataSource, + final Viewport uiViewport, final RootFrameListener rootFrameListener) { + jassProgramVisitor.getJassNativeManager().createNative("LogError", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope) { + final String stringValue = arguments.get(0).visit(StringJassValueVisitor.getInstance()); + System.err.println(stringValue); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ConvertFramePointType", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope) { + final int value = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + return new HandleJassValue(jassProgramVisitor.getGlobals().framePointType, + FramePoint.values()[value]); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("CreateRootFrame", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope) { + final String skinArg = arguments.get(0).visit(StringJassValueVisitor.getInstance()); + final Element skin = GameUI.loadSkin(dataSource, skinArg); + final GameUI gameUI = new GameUI(dataSource, skin, uiViewport); + JUIEnvironment.this.gameUI = gameUI; + JUIEnvironment.this.skin = skin; + rootFrameListener.onCreate(gameUI); + return new HandleJassValue(jassProgramVisitor.getGlobals().frameHandleType, gameUI); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("LoadTOCFile", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope) { + final String tocFileName = arguments.get(0).visit(StringJassValueVisitor.getInstance()); + try { + JUIEnvironment.this.gameUI.loadTOCFile(tocFileName); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + return BooleanJassValue.TRUE; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("CreateSimpleFrame", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope) { + final String templateName = arguments.get(0).visit(StringJassValueVisitor.getInstance()); + final UIFrame ownerFrame = arguments.get(1).visit(ObjectJassValueVisitor.getInstance()); + final int createContext = arguments.get(2).visit(IntegerJassValueVisitor.getInstance()); + + final UIFrame simpleFrame = JUIEnvironment.this.gameUI.createSimpleFrame(templateName, ownerFrame, + createContext); + + return new HandleJassValue(jassProgramVisitor.getGlobals().frameHandleType, simpleFrame); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("FrameSetAbsPoint", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope) { + final UIFrame frame = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final FramePoint framePoint = arguments.get(1) + .visit(ObjectJassValueVisitor.getInstance()); + final double x = arguments.get(2).visit(RealJassValueVisitor.getInstance()); + final double y = arguments.get(3).visit(RealJassValueVisitor.getInstance()); + + frame.addAnchor(new AnchorDefinition(framePoint, GameUI.convertX(uiViewport, (float) x), + GameUI.convertY(uiViewport, (float) y))); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("FramePositionBounds", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope) { + final UIFrame frame = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + frame.positionBounds(uiViewport); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("SkinGetField", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope) { + final String fieldName = arguments.get(0).visit(StringJassValueVisitor.getInstance()); + return new StringJassValue(JUIEnvironment.this.skin.getField(fieldName)); + } + }); + } + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/ModelViewer.java b/core/src/com/etheller/warsmash/viewer5/ModelViewer.java index b27546e..af93a7c 100644 --- a/core/src/com/etheller/warsmash/viewer5/ModelViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/ModelViewer.java @@ -283,7 +283,7 @@ public class ModelViewer { } public void update() { - final float dt = this.frameTime * 0.001f; + final float dt = Gdx.graphics.getRawDeltaTime();// this.frameTime * 0.001f; this.frame += 1; diff --git a/core/src/com/etheller/warsmash/viewer5/RawOpenGLTextureResource.java b/core/src/com/etheller/warsmash/viewer5/RawOpenGLTextureResource.java index 78869e8..1bd7311 100644 --- a/core/src/com/etheller/warsmash/viewer5/RawOpenGLTextureResource.java +++ b/core/src/com/etheller/warsmash/viewer5/RawOpenGLTextureResource.java @@ -55,6 +55,8 @@ public abstract class RawOpenGLTextureResource extends Texture { @Override public void internalBind() { this.viewer.gl.glBindTexture(this.target, this.handle); + this.viewer.gl.glTexParameteri(this.target, GL20.GL_TEXTURE_WRAP_S, this.wrapS); + this.viewer.gl.glTexParameteri(this.target, GL20.GL_TEXTURE_WRAP_T, this.wrapT); } @Override @@ -81,19 +83,12 @@ public abstract class RawOpenGLTextureResource extends Texture { public void setWrapS(final boolean wrapS) { this.wrapS = wrapS ? GL20.GL_REPEAT : GL20.GL_CLAMP_TO_EDGE; final GL20 gl = this.viewer.gl; - - gl.glBindTexture(this.target, this.handle); - gl.glTexParameteri(this.target, GL20.GL_TEXTURE_WRAP_S, this.wrapS); } @Override public void setWrapT(final boolean wrapT) { this.wrapT = wrapT ? GL20.GL_REPEAT : GL20.GL_CLAMP_TO_EDGE; final GL20 gl = this.viewer.gl; - - gl.glBindTexture(this.target, this.handle); - gl.glTexParameteri(this.target, GL20.GL_TEXTURE_WRAP_T, this.wrapT); - } public void update(final BufferedImage image) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java index 65712c3..fb80449 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java @@ -522,7 +522,7 @@ public class MdxComplexInstance extends ModelInstance { if (sequenceId != -1) { final Sequence sequence = model.sequences.get(sequenceId); final long[] interval = sequence.getInterval(); - final int frameTime = model.viewer.frameTime; + final int frameTime = (int) (dt * 1000); this.frame += frameTime; this.counter += frameTime; @@ -534,6 +534,16 @@ public class MdxComplexInstance extends ModelInstance { this.resetEventEmitters(); } + else if (this.sequenceLoopMode == 4) { // faux queued animation mode + final int framesPast = this.frame - (int) interval[1]; + + final List sequences = model.sequences; + this.sequence = (this.sequence + 1) % sequences.size(); + this.frame = (int) sequences.get(this.sequence).getInterval()[0] + framesPast; // TODO not cast + this.sequenceEnded = false; + this.resetEventEmitters(); + this.forced = true; + } else { this.frame = (int) interval[1]; // TODO not cast this.counter -= frameTime; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java index 8495a08..1bfaafb 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java @@ -156,14 +156,12 @@ public class MdxModel extends com.etheller.warsmash.viewer5.Model { final Texture viewerTexture = (Texture) viewer.load(path, pathSolver, solverParams); // When the texture will load, it will apply its wrap modes. - if (!viewerTexture.loaded) { - if ((flags & 0x1) != 0) { - viewerTexture.setWrapS(true); - } + if ((flags & 0x1) != 0) { + viewerTexture.setWrapS(true); + } - if ((flags & 0x2) != 0) { - viewerTexture.setWrapT(true); - } + if ((flags & 0x2) != 0) { + viewerTexture.setWrapT(true); } this.replaceables.add(replaceableId); diff --git a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java index 16e56f2..9845124 100644 --- a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java +++ b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java @@ -7,7 +7,7 @@ import org.lwjgl.opengl.GL33; import com.badlogic.gdx.backends.lwjgl.LwjglApplication; import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration; -import com.etheller.warsmash.WarsmashGdxMapGame; +import com.etheller.warsmash.WarsmashGdxGame; import com.etheller.warsmash.viewer5.gl.ANGLEInstancedArrays; import com.etheller.warsmash.viewer5.gl.DynamicShadowExtension; import com.etheller.warsmash.viewer5.gl.Extensions; @@ -62,6 +62,6 @@ public class DesktopLauncher { // final DisplayMode desktopDisplayMode = LwjglApplicationConfiguration.getDesktopDisplayMode(); // config.width = desktopDisplayMode.width; // config.height = desktopDisplayMode.height; - new LwjglApplication(new WarsmashGdxMapGame(), config); + new LwjglApplication(new WarsmashGdxGame(), config); } } diff --git a/jassparser/antlr-src/Jass.g4 b/jassparser/antlr-src/Jass.g4 index 595643c..6db646c 100644 --- a/jassparser/antlr-src/Jass.g4 +++ b/jassparser/antlr-src/Jass.g4 @@ -26,13 +26,13 @@ type : | ID ARRAY # ArrayType | - 'nothing' # NothingType + NOTHING # NothingType ; global : - type ID newlines # BasicGlobal + CONSTANT? type ID newlines # BasicGlobal | - type ID assignTail newlines # DefinitionGlobal + CONSTANT? type ID assignTail newlines # DefinitionGlobal ; assignTail: @@ -58,18 +58,20 @@ expression: functionExpression # FunctionCallExpression | '(' expression ')' # ParentheticalExpression + | + NOT expression # NotExpression ; functionExpression: ID '(' argsList ')' + | + ID '(' ')' ; argsList: expression # SingleArgument | expression ',' argsList # ListArgument - | - #EmptyArgument ; //#booleanExpression: @@ -84,6 +86,16 @@ statement: SET ID '[' expression ']' EQUALS expression newlines # ArrayedAssignmentStatement | RETURN expression newlines # ReturnStatement + | + IF ifStatementPartial # IfStatement + ; + +ifStatementPartial: + expression THEN newlines statements ENDIF newlines # SimpleIfStatement + | + expression THEN newlines statements ELSE newlines statements ENDIF newlines # IfElseStatement + | + expression THEN newlines statements ELSEIF ifStatementPartial # IfElseIfStatement ; param: @@ -94,7 +106,7 @@ paramList: | param ',' paramList # ListParameter | - 'nothing' # NothingParameter + NOTHING # NothingParameter ; globalsBlock : @@ -105,7 +117,7 @@ typeDefinitionBlock : ; nativeBlock: - NATIVE ID TAKES paramList RETURNS type newlines + CONSTANT? NATIVE ID TAKES paramList RETURNS type newlines ; block: @@ -115,20 +127,30 @@ block: ; functionBlock: - FUNCTION ID TAKES paramList RETURNS type newlines (statement)* ENDFUNCTION newlines + FUNCTION ID TAKES paramList RETURNS type newlines statements ENDFUNCTION newlines + ; + +statements: + (statement)* ; newlines: - NEWLINES + pnewlines | EOF; newlines_opt: - NEWLINES + pnewlines | EOF | ; + +pnewlines: + NEWLINE + | + NEWLINE newlines + ; EQUALS : '='; @@ -142,6 +164,7 @@ FUNCTION : 'function' ; // function TAKES : 'takes' ; // takes RETURNS : 'returns' ; ENDFUNCTION : 'endfunction' ; // endfunction +NOTHING : 'nothing' ; CALL : 'call' ; SET : 'set' ; @@ -153,6 +176,13 @@ TYPE : 'type'; EXTENDS : 'extends'; +IF : 'if'; +THEN : 'then'; +ELSE : 'else'; +ENDIF : 'endif'; +ELSEIF : 'elseif'; +CONSTANT : 'constant'; + STRING_LITERAL : ('"'.*?'"'); INTEGER : [0]|([1-9][0-9]*) ; @@ -161,9 +191,10 @@ NULL : 'null' ; TRUE : 'true' ; FALSE : 'false' ; +NOT : 'not'; + ID : ([a-zA-Z_][a-zA-Z_0-9]*) ; // match identifiers WS : [ \t]+ -> skip ; // skip spaces, tabs -NEWLINES : NEWLINE+; -fragment NEWLINE : '\r' '\n' | '\n' | '\r' | ('//'.*?'\n'); \ No newline at end of file +NEWLINE : '//'.*?'\r\n' | '//'.*?'\n' | '//'.*?'\r' | '\r' '\n' | '\n' | '\r'; diff --git a/jassparser/src/com/etheller/interpreter/ast/Assignable.java b/jassparser/src/com/etheller/interpreter/ast/Assignable.java index bf98b50..0bc7262 100644 --- a/jassparser/src/com/etheller/interpreter/ast/Assignable.java +++ b/jassparser/src/com/etheller/interpreter/ast/Assignable.java @@ -13,17 +13,18 @@ public class Assignable { } public void setValue(final JassValue value) { - if (value.visit(JassTypeGettingValueVisitor.getInstance()) != type) { - throw new RuntimeException("Incompatible types"); + final JassType valueType = value.visit(JassTypeGettingValueVisitor.getInstance()); + if (!this.type.isAssignableFrom(valueType)) { + throw new RuntimeException("Incompatible types " + valueType.getName() + " != " + this.type.getName()); } this.value = value; } public JassValue getValue() { - return value; + return this.value; } public JassType getType() { - return type; + return this.type; } } diff --git a/jassparser/src/com/etheller/interpreter/ast/expression/NotJassExpression.java b/jassparser/src/com/etheller/interpreter/ast/expression/NotJassExpression.java new file mode 100644 index 0000000..37f16d0 --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/expression/NotJassExpression.java @@ -0,0 +1,20 @@ +package com.etheller.interpreter.ast.expression; + +import com.etheller.interpreter.ast.scope.GlobalScope; +import com.etheller.interpreter.ast.scope.LocalScope; +import com.etheller.interpreter.ast.value.JassValue; +import com.etheller.interpreter.ast.value.visitor.BooleanJassValueVisitor; +import com.etheller.interpreter.ast.value.visitor.NotJassValueVisitor; + +public class NotJassExpression implements JassExpression { + private final JassExpression expression; + + public NotJassExpression(final JassExpression expression) { + this.expression = expression; + } + + @Override + public JassValue evaluate(final GlobalScope globalScope, final LocalScope localScope) { + return this.expression.evaluate(globalScope, localScope).visit(NotJassValueVisitor.getInstance()); + } +} diff --git a/jassparser/src/com/etheller/interpreter/ast/function/AbstractJassFunction.java b/jassparser/src/com/etheller/interpreter/ast/function/AbstractJassFunction.java index d132b75..ba8a557 100644 --- a/jassparser/src/com/etheller/interpreter/ast/function/AbstractJassFunction.java +++ b/jassparser/src/com/etheller/interpreter/ast/function/AbstractJassFunction.java @@ -6,6 +6,7 @@ import com.etheller.interpreter.ast.scope.GlobalScope; import com.etheller.interpreter.ast.scope.LocalScope; import com.etheller.interpreter.ast.value.JassType; import com.etheller.interpreter.ast.value.JassValue; +import com.etheller.interpreter.ast.value.visitor.JassTypeGettingValueVisitor; /** * Not a native @@ -24,15 +25,19 @@ public abstract class AbstractJassFunction implements JassFunction { @Override public final JassValue call(final List arguments, final GlobalScope globalScope) { - if (arguments.size() != parameters.size()) { + if (arguments.size() != this.parameters.size()) { throw new RuntimeException("Invalid number of arguments passed to function"); } final LocalScope localScope = new LocalScope(); - for (int i = 0; i < parameters.size(); i++) { - final JassParameter parameter = parameters.get(i); + for (int i = 0; i < this.parameters.size(); i++) { + final JassParameter parameter = this.parameters.get(i); final JassValue argument = arguments.get(i); if (!parameter.matchesType(argument)) { - throw new RuntimeException("Invalid type for specified argument"); + System.err.println( + parameter.getType() + " != " + argument.visit(JassTypeGettingValueVisitor.getInstance())); + throw new RuntimeException( + "Invalid type " + argument.visit(JassTypeGettingValueVisitor.getInstance()).getName() + + " for specified argument " + parameter.getType().getName()); } localScope.createLocal(parameter.getIdentifier(), parameter.getType(), argument); } diff --git a/jassparser/src/com/etheller/interpreter/ast/function/JassNativeManager.java b/jassparser/src/com/etheller/interpreter/ast/function/JassNativeManager.java index 59242b3..931c918 100644 --- a/jassparser/src/com/etheller/interpreter/ast/function/JassNativeManager.java +++ b/jassparser/src/com/etheller/interpreter/ast/function/JassNativeManager.java @@ -17,6 +17,10 @@ public class JassNativeManager { this.nameToNativeCode = new HashMap<>(); } + public void createNative(final String name, final JassFunction nativeCode) { + this.nameToNativeCode.put(name, nativeCode); + } + public void registerNativeCode(final String name, final List parameters, final JassType returnType, final GlobalScope globals) { if (this.registeredNativeNames.contains(name)) { diff --git a/jassparser/src/com/etheller/interpreter/ast/function/JassParameter.java b/jassparser/src/com/etheller/interpreter/ast/function/JassParameter.java index 1c59957..f9c460c 100644 --- a/jassparser/src/com/etheller/interpreter/ast/function/JassParameter.java +++ b/jassparser/src/com/etheller/interpreter/ast/function/JassParameter.java @@ -14,14 +14,15 @@ public class JassParameter { } public String getIdentifier() { - return identifier; + return this.identifier; } public JassType getType() { - return type; + return this.type; } public boolean matchesType(final JassValue value) { - return type == value.visit(JassTypeGettingValueVisitor.getInstance()); + final JassType valueType = value.visit(JassTypeGettingValueVisitor.getInstance()); + return this.type.isAssignableFrom(valueType); } } diff --git a/jassparser/src/com/etheller/interpreter/ast/scope/GlobalScope.java b/jassparser/src/com/etheller/interpreter/ast/scope/GlobalScope.java index 5ad1924..8e374c6 100644 --- a/jassparser/src/com/etheller/interpreter/ast/scope/GlobalScope.java +++ b/jassparser/src/com/etheller/interpreter/ast/scope/GlobalScope.java @@ -7,34 +7,74 @@ import com.etheller.interpreter.ast.Assignable; import com.etheller.interpreter.ast.function.JassFunction; import com.etheller.interpreter.ast.value.ArrayJassType; import com.etheller.interpreter.ast.value.ArrayJassValue; +import com.etheller.interpreter.ast.value.HandleJassType; import com.etheller.interpreter.ast.value.JassType; import com.etheller.interpreter.ast.value.JassValue; import com.etheller.interpreter.ast.value.PrimitiveJassType; import com.etheller.interpreter.ast.value.visitor.ArrayPrimitiveTypeVisitor; +import com.etheller.interpreter.ast.value.visitor.HandleJassTypeVisitor; +import com.etheller.interpreter.ast.value.visitor.HandleTypeSuperTypeLoadingVisitor; public final class GlobalScope { private final Map globals = new HashMap<>(); private final Map functions = new HashMap<>(); private final Map types = new HashMap<>(); + private final HandleTypeSuperTypeLoadingVisitor handleTypeSuperTypeLoadingVisitor = new HandleTypeSuperTypeLoadingVisitor(); + + public final HandleJassType handleType; + public final HandleJassType frameHandleType; + public final HandleJassType framePointType; + + private static int lineNumber; + + public GlobalScope() { + this.handleType = registerHandleType("handle");// the handle type + this.frameHandleType = registerHandleType("framehandle"); + this.framePointType = registerHandleType("framepointtype"); + registerPrimitiveType(JassType.BOOLEAN); + registerPrimitiveType(JassType.INTEGER); + registerPrimitiveType(JassType.CODE); + registerPrimitiveType(JassType.NOTHING); + registerPrimitiveType(JassType.REAL); + registerPrimitiveType(JassType.STRING); + } + + public static void setLineNumber(final int lineNo) { + lineNumber = lineNo; + } + + public static int getLineNumber() { + return lineNumber; + } + + private HandleJassType registerHandleType(final String name) { + final HandleJassType handleJassType = new HandleJassType(null, name); + this.types.put(name, handleJassType); + return handleJassType; + } + + private void registerPrimitiveType(final PrimitiveJassType type) { + this.types.put(type.getName(), type); + } public void createGlobalArray(final String name, final JassType type) { final Assignable assignable = new Assignable(type); assignable.setValue(new ArrayJassValue((ArrayJassType) type)); // TODO less bad code - globals.put(name, assignable); + this.globals.put(name, assignable); } public void createGlobal(final String name, final JassType type) { - globals.put(name, new Assignable(type)); + this.globals.put(name, new Assignable(type)); } public void createGlobal(final String name, final JassType type, final JassValue value) { final Assignable assignable = new Assignable(type); assignable.setValue(value); - globals.put(name, assignable); + this.globals.put(name, assignable); } public void setGlobal(final String name, final JassValue value) { - final Assignable assignable = globals.get(name); + final Assignable assignable = this.globals.get(name); if (assignable == null) { throw new RuntimeException("Undefined global: " + name); } @@ -45,7 +85,7 @@ public final class GlobalScope { } public JassValue getGlobal(final String name) { - final Assignable global = globals.get(name); + final Assignable global = this.globals.get(name); if (global == null) { throw new RuntimeException("Undefined global: " + name); } @@ -53,42 +93,51 @@ public final class GlobalScope { } public Assignable getAssignableGlobal(final String name) { - return globals.get(name); + return this.globals.get(name); } public void defineFunction(final String name, final JassFunction function) { - functions.put(name, function); + this.functions.put(name, function); } public JassFunction getFunctionByName(final String name) { - return functions.get(name); + return this.functions.get(name); } - public PrimitiveJassType parseType(final String text) { - if (text.equals("string")) { - return JassType.STRING; - } else if (text.equals("integer")) { - return JassType.INTEGER; - } else if (text.equals("boolean")) { - return JassType.BOOLEAN; - } else if (text.equals("real")) { - return JassType.REAL; - } else if (text.equals("code")) { - return JassType.CODE; - } else if (text.equals("nothing")) { - return JassType.NOTHING; - } else { + public JassType parseType(final String text) { + final JassType type = this.types.get(text); + if (type != null) { + return type; + } + else { throw new RuntimeException("Unknown type: " + text); } } public JassType parseArrayType(final String primitiveTypeName) { final String arrayTypeName = primitiveTypeName + " array"; - JassType arrayType = types.get(arrayTypeName); + JassType arrayType = this.types.get(arrayTypeName); if (arrayType == null) { arrayType = new ArrayJassType(parseType(primitiveTypeName)); - types.put(arrayTypeName, arrayType); + this.types.put(arrayTypeName, arrayType); } return arrayType; } + + public void loadTypeDefinition(final String type, final String supertype) { + final JassType superType = this.types.get(supertype); + if (superType != null) { + final HandleJassType handleSuperType = superType.visit(HandleJassTypeVisitor.getInstance()); + if (handleSuperType != null) { + final JassType jassType = this.types.get(type); + jassType.visit(this.handleTypeSuperTypeLoadingVisitor.reset(handleSuperType)); + } + else { + throw new RuntimeException("type " + type + " cannot extend primitive type " + supertype); + } + } + else { + throw new RuntimeException("type " + type + " cannot extend unknown type " + supertype); + } + } } diff --git a/jassparser/src/com/etheller/interpreter/ast/scope/LocalScope.java b/jassparser/src/com/etheller/interpreter/ast/scope/LocalScope.java index 8ab2f01..1aea3d7 100644 --- a/jassparser/src/com/etheller/interpreter/ast/scope/LocalScope.java +++ b/jassparser/src/com/etheller/interpreter/ast/scope/LocalScope.java @@ -11,17 +11,17 @@ public final class LocalScope { private final Map locals = new HashMap<>(); public void createLocal(final String name, final JassType type) { - locals.put(name, new Assignable(type)); + this.locals.put(name, new Assignable(type)); } public void createLocal(final String name, final JassType type, final JassValue value) { final Assignable assignable = new Assignable(type); assignable.setValue(value); - locals.put(name, assignable); + this.locals.put(name, assignable); } public void setLocal(final String name, final JassValue value) { - final Assignable assignable = locals.get(name); + final Assignable assignable = this.locals.get(name); if (assignable == null) { throw new RuntimeException("Undefined local variable: " + name); } @@ -29,7 +29,7 @@ public final class LocalScope { } public JassValue getLocal(final String name) { - final Assignable local = locals.get(name); + final Assignable local = this.locals.get(name); if (local == null) { throw new RuntimeException("Undefined local variable: " + name); } @@ -37,6 +37,6 @@ public final class LocalScope { } public Assignable getAssignableLocal(final String name) { - return locals.get(name); + return this.locals.get(name); } } diff --git a/jassparser/src/com/etheller/interpreter/ast/statement/JassArrayedAssignmentStatement.java b/jassparser/src/com/etheller/interpreter/ast/statement/JassArrayedAssignmentStatement.java index 501f5f6..73ccb82 100644 --- a/jassparser/src/com/etheller/interpreter/ast/statement/JassArrayedAssignmentStatement.java +++ b/jassparser/src/com/etheller/interpreter/ast/statement/JassArrayedAssignmentStatement.java @@ -13,9 +13,11 @@ public class JassArrayedAssignmentStatement implements JassStatement { private final String identifier; private final JassExpression indexExpression; private final JassExpression expression; + private final int lineNo; - public JassArrayedAssignmentStatement(final String identifier, final JassExpression indexExpression, - final JassExpression expression) { + public JassArrayedAssignmentStatement(final int lineNo, final String identifier, + final JassExpression indexExpression, final JassExpression expression) { + this.lineNo = lineNo; this.identifier = identifier; this.indexExpression = indexExpression; this.expression = expression; @@ -23,10 +25,11 @@ public class JassArrayedAssignmentStatement implements JassStatement { @Override public JassValue execute(final GlobalScope globalScope, final LocalScope localScope) { - Assignable variable = localScope.getAssignableLocal(identifier); - final JassValue index = indexExpression.evaluate(globalScope, localScope); + globalScope.setLineNumber(this.lineNo); + Assignable variable = localScope.getAssignableLocal(this.identifier); + final JassValue index = this.indexExpression.evaluate(globalScope, localScope); if (variable == null) { - variable = globalScope.getAssignableGlobal(identifier); + variable = globalScope.getAssignableGlobal(this.identifier); } if (variable.getValue() == null) { throw new RuntimeException("Unable to assign uninitialized array"); @@ -34,8 +37,9 @@ public class JassArrayedAssignmentStatement implements JassStatement { final ArrayJassValue arrayValue = variable.getValue().visit(ArrayJassValueVisitor.getInstance()); if (arrayValue != null) { arrayValue.set(index.visit(IntegerJassValueVisitor.getInstance()), - expression.evaluate(globalScope, localScope)); - } else { + this.expression.evaluate(globalScope, localScope)); + } + else { throw new RuntimeException("Not an array"); } return null; diff --git a/jassparser/src/com/etheller/interpreter/ast/statement/JassCallStatement.java b/jassparser/src/com/etheller/interpreter/ast/statement/JassCallStatement.java index 5a52634..23330e6 100644 --- a/jassparser/src/com/etheller/interpreter/ast/statement/JassCallStatement.java +++ b/jassparser/src/com/etheller/interpreter/ast/statement/JassCallStatement.java @@ -10,22 +10,25 @@ import com.etheller.interpreter.ast.scope.LocalScope; import com.etheller.interpreter.ast.value.JassValue; public class JassCallStatement implements JassStatement { + private final int lineNo; private final String functionName; private final List arguments; - public JassCallStatement(final String functionName, final List arguments) { + public JassCallStatement(final int lineNo, final String functionName, final List arguments) { + this.lineNo = lineNo; this.functionName = functionName; this.arguments = arguments; } @Override public JassValue execute(final GlobalScope globalScope, final LocalScope localScope) { - final JassFunction functionByName = globalScope.getFunctionByName(functionName); + globalScope.setLineNumber(this.lineNo); + final JassFunction functionByName = globalScope.getFunctionByName(this.functionName); if (functionByName == null) { - throw new RuntimeException("Undefined function: " + functionName); + throw new RuntimeException("Undefined function: " + this.functionName); } final List evaluatedExpressions = new ArrayList<>(); - for (final JassExpression expr : arguments) { + for (final JassExpression expr : this.arguments) { final JassValue evaluatedExpression = expr.evaluate(globalScope, localScope); evaluatedExpressions.add(evaluatedExpression); } diff --git a/jassparser/src/com/etheller/interpreter/ast/statement/JassIfElseIfStatement.java b/jassparser/src/com/etheller/interpreter/ast/statement/JassIfElseIfStatement.java new file mode 100644 index 0000000..ae6a2de --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/statement/JassIfElseIfStatement.java @@ -0,0 +1,42 @@ +package com.etheller.interpreter.ast.statement; + +import java.util.List; + +import com.etheller.interpreter.ast.expression.JassExpression; +import com.etheller.interpreter.ast.scope.GlobalScope; +import com.etheller.interpreter.ast.scope.LocalScope; +import com.etheller.interpreter.ast.value.JassValue; +import com.etheller.interpreter.ast.value.visitor.BooleanJassValueVisitor; + +public class JassIfElseIfStatement implements JassStatement { + private final int lineNo; + private final JassExpression condition; + private final List thenStatements; + private final JassStatement elseifTail; + + public JassIfElseIfStatement(final int lineNo, final JassExpression condition, + final List thenStatements, final JassStatement elseifTail) { + this.lineNo = lineNo; + this.condition = condition; + this.thenStatements = thenStatements; + this.elseifTail = elseifTail; + } + + @Override + public JassValue execute(final GlobalScope globalScope, final LocalScope localScope) { + globalScope.setLineNumber(this.lineNo); + if (this.condition.evaluate(globalScope, localScope).visit(BooleanJassValueVisitor.getInstance())) { + for (final JassStatement statement : this.thenStatements) { + final JassValue returnValue = statement.execute(globalScope, localScope); + if (returnValue != null) { + return returnValue; + } + } + } + else { + return this.elseifTail.execute(globalScope, localScope); + } + return null; + } + +} diff --git a/jassparser/src/com/etheller/interpreter/ast/statement/JassIfElseStatement.java b/jassparser/src/com/etheller/interpreter/ast/statement/JassIfElseStatement.java new file mode 100644 index 0000000..10912eb --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/statement/JassIfElseStatement.java @@ -0,0 +1,47 @@ +package com.etheller.interpreter.ast.statement; + +import java.util.List; + +import com.etheller.interpreter.ast.expression.JassExpression; +import com.etheller.interpreter.ast.scope.GlobalScope; +import com.etheller.interpreter.ast.scope.LocalScope; +import com.etheller.interpreter.ast.value.JassValue; +import com.etheller.interpreter.ast.value.visitor.BooleanJassValueVisitor; + +public class JassIfElseStatement implements JassStatement { + private final int lineNo; + private final JassExpression condition; + private final List thenStatements; + private final List elseStatements; + + public JassIfElseStatement(final int lineNo, final JassExpression condition, + final List thenStatements, final List elseStatements) { + this.lineNo = lineNo; + this.condition = condition; + this.thenStatements = thenStatements; + this.elseStatements = elseStatements; + } + + @Override + public JassValue execute(final GlobalScope globalScope, final LocalScope localScope) { + globalScope.setLineNumber(this.lineNo); + if (this.condition.evaluate(globalScope, localScope).visit(BooleanJassValueVisitor.getInstance())) { + for (final JassStatement statement : this.thenStatements) { + final JassValue returnValue = statement.execute(globalScope, localScope); + if (returnValue != null) { + return returnValue; + } + } + } + else { + for (final JassStatement statement : this.elseStatements) { + final JassValue returnValue = statement.execute(globalScope, localScope); + if (returnValue != null) { + return returnValue; + } + } + } + return null; + } + +} diff --git a/jassparser/src/com/etheller/interpreter/ast/statement/JassIfStatement.java b/jassparser/src/com/etheller/interpreter/ast/statement/JassIfStatement.java new file mode 100644 index 0000000..9d14c30 --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/statement/JassIfStatement.java @@ -0,0 +1,44 @@ +package com.etheller.interpreter.ast.statement; + +import java.util.List; + +import com.etheller.interpreter.ast.expression.JassExpression; +import com.etheller.interpreter.ast.scope.GlobalScope; +import com.etheller.interpreter.ast.scope.LocalScope; +import com.etheller.interpreter.ast.value.JassValue; +import com.etheller.interpreter.ast.value.visitor.BooleanJassValueVisitor; + +public class JassIfStatement implements JassStatement { + private final int lineNo; + private final JassExpression condition; + private final List thenStatements; + + public JassIfStatement(final int lineNo, final JassExpression condition, final List thenStatements) { + this.lineNo = lineNo; + this.condition = condition; + this.thenStatements = thenStatements; + } + + public JassExpression getCondition() { + return this.condition; + } + + public List getThenStatements() { + return this.thenStatements; + } + + @Override + public JassValue execute(final GlobalScope globalScope, final LocalScope localScope) { + globalScope.setLineNumber(this.lineNo); + if (this.condition.evaluate(globalScope, localScope).visit(BooleanJassValueVisitor.getInstance())) { + for (final JassStatement statement : this.thenStatements) { + final JassValue returnValue = statement.execute(globalScope, localScope); + if (returnValue != null) { + return returnValue; + } + } + } + return null; + } + +} diff --git a/jassparser/src/com/etheller/interpreter/ast/statement/JassReturnStatement.java b/jassparser/src/com/etheller/interpreter/ast/statement/JassReturnStatement.java index 404074f..1f8542e 100644 --- a/jassparser/src/com/etheller/interpreter/ast/statement/JassReturnStatement.java +++ b/jassparser/src/com/etheller/interpreter/ast/statement/JassReturnStatement.java @@ -6,15 +6,18 @@ import com.etheller.interpreter.ast.scope.LocalScope; import com.etheller.interpreter.ast.value.JassValue; public class JassReturnStatement implements JassStatement { + private final int lineNo; private final JassExpression expression; - public JassReturnStatement(final JassExpression expression) { + public JassReturnStatement(final int lineNo, final JassExpression expression) { + this.lineNo = lineNo; this.expression = expression; } @Override public JassValue execute(final GlobalScope globalScope, final LocalScope localScope) { - return expression.evaluate(globalScope, localScope); + globalScope.setLineNumber(this.lineNo); + return this.expression.evaluate(globalScope, localScope); } } diff --git a/jassparser/src/com/etheller/interpreter/ast/statement/JassSetStatement.java b/jassparser/src/com/etheller/interpreter/ast/statement/JassSetStatement.java index 35d182a..0b0cde7 100644 --- a/jassparser/src/com/etheller/interpreter/ast/statement/JassSetStatement.java +++ b/jassparser/src/com/etheller/interpreter/ast/statement/JassSetStatement.java @@ -7,21 +7,25 @@ import com.etheller.interpreter.ast.scope.LocalScope; import com.etheller.interpreter.ast.value.JassValue; public class JassSetStatement implements JassStatement { + private final int lineNo; private final String identifier; private final JassExpression expression; - public JassSetStatement(final String identifier, final JassExpression expression) { + public JassSetStatement(final int lineNo, final String identifier, final JassExpression expression) { + this.lineNo = lineNo; this.identifier = identifier; this.expression = expression; } @Override public JassValue execute(final GlobalScope globalScope, final LocalScope localScope) { - final Assignable local = localScope.getAssignableLocal(identifier); + globalScope.setLineNumber(this.lineNo); + final Assignable local = localScope.getAssignableLocal(this.identifier); if (local != null) { - local.setValue(expression.evaluate(globalScope, localScope)); - } else { - globalScope.setGlobal(identifier, expression.evaluate(globalScope, localScope)); + local.setValue(this.expression.evaluate(globalScope, localScope)); + } + else { + globalScope.setGlobal(this.identifier, this.expression.evaluate(globalScope, localScope)); } return null; } diff --git a/jassparser/src/com/etheller/interpreter/ast/value/ArrayJassType.java b/jassparser/src/com/etheller/interpreter/ast/value/ArrayJassType.java index 0027c3a..109a559 100644 --- a/jassparser/src/com/etheller/interpreter/ast/value/ArrayJassType.java +++ b/jassparser/src/com/etheller/interpreter/ast/value/ArrayJassType.java @@ -1,14 +1,26 @@ package com.etheller.interpreter.ast.value; public class ArrayJassType implements JassType { - private final PrimitiveJassType primitiveType; + private final JassType primitiveType; + private final String name; - public ArrayJassType(final PrimitiveJassType primitiveType) { + public ArrayJassType(final JassType primitiveType) { this.primitiveType = primitiveType; + this.name = primitiveType.getName() + " array"; } - public PrimitiveJassType getPrimitiveType() { - return primitiveType; + @Override + public boolean isAssignableFrom(final JassType value) { + return value == this; + } + + public JassType getPrimitiveType() { + return this.primitiveType; + } + + @Override + public String getName() { + return this.name; } @Override diff --git a/jassparser/src/com/etheller/interpreter/ast/value/BooleanJassValue.java b/jassparser/src/com/etheller/interpreter/ast/value/BooleanJassValue.java index e044000..adbeabb 100644 --- a/jassparser/src/com/etheller/interpreter/ast/value/BooleanJassValue.java +++ b/jassparser/src/com/etheller/interpreter/ast/value/BooleanJassValue.java @@ -1,6 +1,9 @@ package com.etheller.interpreter.ast.value; public class BooleanJassValue implements JassValue { + public static final BooleanJassValue TRUE = new BooleanJassValue(true); + public static final BooleanJassValue FALSE = new BooleanJassValue(false); + private final boolean value; public BooleanJassValue(final boolean value) { @@ -8,11 +11,20 @@ public class BooleanJassValue implements JassValue { } public boolean getValue() { - return value; + return this.value; } @Override public TYPE visit(final JassValueVisitor visitor) { return visitor.accept(this); } + + public static BooleanJassValue inverse(final BooleanJassValue value) { + if (value.value) { + return FALSE; + } + else { + return TRUE; + } + } } diff --git a/jassparser/src/com/etheller/interpreter/ast/value/HandleJassType.java b/jassparser/src/com/etheller/interpreter/ast/value/HandleJassType.java new file mode 100644 index 0000000..bd025fb --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/value/HandleJassType.java @@ -0,0 +1,43 @@ +package com.etheller.interpreter.ast.value; + +import com.etheller.interpreter.ast.value.visitor.SuperTypeVisitor; + +public class HandleJassType implements JassType { + private HandleJassType superType; + private final String name; + + public HandleJassType(final HandleJassType superType, final String name) { + this.superType = superType; + this.name = name; + } + + @Override + public boolean isAssignableFrom(JassType valueType) { + while (valueType != null) { + if (this == valueType) { + return true; + } + valueType = valueType.visit(SuperTypeVisitor.getInstance()); + } + return false; + } + + @Override + public String getName() { + return this.name; + } + + public HandleJassType getSuperType() { + return this.superType; + } + + public void setSuperType(final HandleJassType superType) { + this.superType = superType; + } + + @Override + public TYPE visit(final JassTypeVisitor visitor) { + return visitor.accept(this); + } + +} diff --git a/jassparser/src/com/etheller/interpreter/ast/value/HandleJassValue.java b/jassparser/src/com/etheller/interpreter/ast/value/HandleJassValue.java new file mode 100644 index 0000000..78032fd --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/value/HandleJassValue.java @@ -0,0 +1,25 @@ +package com.etheller.interpreter.ast.value; + +public class HandleJassValue implements JassValue { + private final HandleJassType type; + private final Object javaValue; + + public HandleJassValue(final HandleJassType type, final Object javaValue) { + this.type = type; + this.javaValue = javaValue; + } + + public HandleJassType getType() { + return this.type; + } + + public Object getJavaValue() { + return this.javaValue; + } + + @Override + public TYPE visit(final JassValueVisitor visitor) { + return visitor.accept(this); + } + +} diff --git a/jassparser/src/com/etheller/interpreter/ast/value/JassType.java b/jassparser/src/com/etheller/interpreter/ast/value/JassType.java index 7b1beb8..c72141e 100644 --- a/jassparser/src/com/etheller/interpreter/ast/value/JassType.java +++ b/jassparser/src/com/etheller/interpreter/ast/value/JassType.java @@ -3,10 +3,14 @@ package com.etheller.interpreter.ast.value; public interface JassType { TYPE visit(JassTypeVisitor visitor); + String getName(); // used for error messages + + boolean isAssignableFrom(JassType value); + public static final PrimitiveJassType INTEGER = new PrimitiveJassType("integer"); public static final PrimitiveJassType STRING = new PrimitiveJassType("string"); public static final PrimitiveJassType CODE = new PrimitiveJassType("code"); - public static final PrimitiveJassType REAL = new PrimitiveJassType("real"); + public static final PrimitiveJassType REAL = new RealJassType("real"); public static final PrimitiveJassType BOOLEAN = new PrimitiveJassType("boolean"); public static final PrimitiveJassType NOTHING = new PrimitiveJassType("nothing"); } diff --git a/jassparser/src/com/etheller/interpreter/ast/value/JassTypeVisitor.java b/jassparser/src/com/etheller/interpreter/ast/value/JassTypeVisitor.java index 031e921..4d25c5a 100644 --- a/jassparser/src/com/etheller/interpreter/ast/value/JassTypeVisitor.java +++ b/jassparser/src/com/etheller/interpreter/ast/value/JassTypeVisitor.java @@ -4,4 +4,6 @@ public interface JassTypeVisitor { TYPE accept(PrimitiveJassType primitiveType); TYPE accept(ArrayJassType arrayType); + + TYPE accept(HandleJassType type); } diff --git a/jassparser/src/com/etheller/interpreter/ast/value/JassValueVisitor.java b/jassparser/src/com/etheller/interpreter/ast/value/JassValueVisitor.java index e02f265..db03052 100644 --- a/jassparser/src/com/etheller/interpreter/ast/value/JassValueVisitor.java +++ b/jassparser/src/com/etheller/interpreter/ast/value/JassValueVisitor.java @@ -12,4 +12,6 @@ public interface JassValueVisitor { TYPE accept(CodeJassValue value); TYPE accept(ArrayJassValue value); + + TYPE accept(HandleJassValue value); } diff --git a/jassparser/src/com/etheller/interpreter/ast/value/PrimitiveJassType.java b/jassparser/src/com/etheller/interpreter/ast/value/PrimitiveJassType.java index 21ba0b0..38bd250 100644 --- a/jassparser/src/com/etheller/interpreter/ast/value/PrimitiveJassType.java +++ b/jassparser/src/com/etheller/interpreter/ast/value/PrimitiveJassType.java @@ -7,8 +7,14 @@ public class PrimitiveJassType implements JassType { this.name = name; } + @Override + public boolean isAssignableFrom(final JassType value) { + return value == this; + } + + @Override public String getName() { - return name; + return this.name; } @Override diff --git a/jassparser/src/com/etheller/interpreter/ast/value/RealJassType.java b/jassparser/src/com/etheller/interpreter/ast/value/RealJassType.java new file mode 100644 index 0000000..3e2d66d --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/value/RealJassType.java @@ -0,0 +1,16 @@ +package com.etheller.interpreter.ast.value; + +public class RealJassType extends PrimitiveJassType { + + public RealJassType(final String name) { + super(name); + } + + @Override + public boolean isAssignableFrom(final JassType value) { + if (value == JassType.INTEGER) { + return true; + } + return super.isAssignableFrom(value); + } +} diff --git a/jassparser/src/com/etheller/interpreter/ast/value/visitor/ArrayJassValueVisitor.java b/jassparser/src/com/etheller/interpreter/ast/value/visitor/ArrayJassValueVisitor.java index f3d43d7..68a75b5 100644 --- a/jassparser/src/com/etheller/interpreter/ast/value/visitor/ArrayJassValueVisitor.java +++ b/jassparser/src/com/etheller/interpreter/ast/value/visitor/ArrayJassValueVisitor.java @@ -3,6 +3,7 @@ package com.etheller.interpreter.ast.value.visitor; import com.etheller.interpreter.ast.value.ArrayJassValue; import com.etheller.interpreter.ast.value.BooleanJassValue; import com.etheller.interpreter.ast.value.CodeJassValue; +import com.etheller.interpreter.ast.value.HandleJassValue; import com.etheller.interpreter.ast.value.IntegerJassValue; import com.etheller.interpreter.ast.value.JassValueVisitor; import com.etheller.interpreter.ast.value.RealJassValue; @@ -45,4 +46,9 @@ public class ArrayJassValueVisitor implements JassValueVisitor { return value; } + @Override + public ArrayJassValue accept(final HandleJassValue value) { + return null; + } + } diff --git a/jassparser/src/com/etheller/interpreter/ast/value/visitor/ArrayPrimitiveTypeVisitor.java b/jassparser/src/com/etheller/interpreter/ast/value/visitor/ArrayPrimitiveTypeVisitor.java index 70b543c..11bb06e 100644 --- a/jassparser/src/com/etheller/interpreter/ast/value/visitor/ArrayPrimitiveTypeVisitor.java +++ b/jassparser/src/com/etheller/interpreter/ast/value/visitor/ArrayPrimitiveTypeVisitor.java @@ -1,10 +1,12 @@ package com.etheller.interpreter.ast.value.visitor; import com.etheller.interpreter.ast.value.ArrayJassType; +import com.etheller.interpreter.ast.value.HandleJassType; +import com.etheller.interpreter.ast.value.JassType; import com.etheller.interpreter.ast.value.JassTypeVisitor; import com.etheller.interpreter.ast.value.PrimitiveJassType; -public class ArrayPrimitiveTypeVisitor implements JassTypeVisitor { +public class ArrayPrimitiveTypeVisitor implements JassTypeVisitor { private static final ArrayPrimitiveTypeVisitor INSTANCE = new ArrayPrimitiveTypeVisitor(); public static ArrayPrimitiveTypeVisitor getInstance() { @@ -12,13 +14,18 @@ public class ArrayPrimitiveTypeVisitor implements JassTypeVisitor { + private static final BooleanJassValueVisitor INSTANCE = new BooleanJassValueVisitor(); + + public static BooleanJassValueVisitor getInstance() { + return INSTANCE; + } + + @Override + public Boolean accept(final IntegerJassValue value) { + throw new IllegalStateException("Unable to convert " + value + " to boolean"); + } + + @Override + public Boolean accept(final RealJassValue value) { + throw new IllegalStateException("Unable to convert " + value + " to boolean"); + } + + @Override + public Boolean accept(final BooleanJassValue value) { + return value.getValue(); + } + + @Override + public Boolean accept(final StringJassValue value) { + throw new IllegalStateException("Unable to convert " + value + " to boolean"); + } + + @Override + public Boolean accept(final CodeJassValue value) { + throw new IllegalStateException("Unable to convert " + value + " to boolean"); + } + + @Override + public Boolean accept(final ArrayJassValue value) { + throw new IllegalStateException("Unable to convert " + value + " to boolean"); + } + + @Override + public Boolean accept(final HandleJassValue value) { + throw new IllegalStateException("Unable to convert " + value + " to boolean"); + } + +} diff --git a/jassparser/src/com/etheller/interpreter/ast/value/visitor/HandleJassTypeVisitor.java b/jassparser/src/com/etheller/interpreter/ast/value/visitor/HandleJassTypeVisitor.java new file mode 100644 index 0000000..3f4e75a --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/value/visitor/HandleJassTypeVisitor.java @@ -0,0 +1,30 @@ +package com.etheller.interpreter.ast.value.visitor; + +import com.etheller.interpreter.ast.value.ArrayJassType; +import com.etheller.interpreter.ast.value.HandleJassType; +import com.etheller.interpreter.ast.value.JassTypeVisitor; +import com.etheller.interpreter.ast.value.PrimitiveJassType; + +public class HandleJassTypeVisitor implements JassTypeVisitor { + private static final HandleJassTypeVisitor INSTANCE = new HandleJassTypeVisitor(); + + public static HandleJassTypeVisitor getInstance() { + return INSTANCE; + } + + @Override + public HandleJassType accept(final PrimitiveJassType primitiveType) { + return null; + } + + @Override + public HandleJassType accept(final ArrayJassType arrayType) { + return null; + } + + @Override + public HandleJassType accept(final HandleJassType type) { + return type; + } + +} diff --git a/jassparser/src/com/etheller/interpreter/ast/value/visitor/HandleTypeSuperTypeLoadingVisitor.java b/jassparser/src/com/etheller/interpreter/ast/value/visitor/HandleTypeSuperTypeLoadingVisitor.java new file mode 100644 index 0000000..a2a4de7 --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/value/visitor/HandleTypeSuperTypeLoadingVisitor.java @@ -0,0 +1,32 @@ +package com.etheller.interpreter.ast.value.visitor; + +import com.etheller.interpreter.ast.value.ArrayJassType; +import com.etheller.interpreter.ast.value.HandleJassType; +import com.etheller.interpreter.ast.value.JassTypeVisitor; +import com.etheller.interpreter.ast.value.PrimitiveJassType; + +public class HandleTypeSuperTypeLoadingVisitor implements JassTypeVisitor { + private HandleJassType superType; + + public HandleTypeSuperTypeLoadingVisitor reset(final HandleJassType superType) { + this.superType = superType; + return this; + } + + @Override + public Void accept(final PrimitiveJassType primitiveType) { + return null; + } + + @Override + public Void accept(final ArrayJassType arrayType) { + return null; + } + + @Override + public Void accept(final HandleJassType type) { + type.setSuperType(this.superType); + return null; + } + +} diff --git a/jassparser/src/com/etheller/interpreter/ast/value/visitor/IntegerJassValueVisitor.java b/jassparser/src/com/etheller/interpreter/ast/value/visitor/IntegerJassValueVisitor.java index 6d99a02..2edae0e 100644 --- a/jassparser/src/com/etheller/interpreter/ast/value/visitor/IntegerJassValueVisitor.java +++ b/jassparser/src/com/etheller/interpreter/ast/value/visitor/IntegerJassValueVisitor.java @@ -3,6 +3,7 @@ package com.etheller.interpreter.ast.value.visitor; import com.etheller.interpreter.ast.value.ArrayJassValue; import com.etheller.interpreter.ast.value.BooleanJassValue; import com.etheller.interpreter.ast.value.CodeJassValue; +import com.etheller.interpreter.ast.value.HandleJassValue; import com.etheller.interpreter.ast.value.IntegerJassValue; import com.etheller.interpreter.ast.value.JassValueVisitor; import com.etheller.interpreter.ast.value.RealJassValue; @@ -45,4 +46,9 @@ public class IntegerJassValueVisitor implements JassValueVisitor { return 0; } + @Override + public Integer accept(final HandleJassValue value) { + return 0; + } + } diff --git a/jassparser/src/com/etheller/interpreter/ast/value/visitor/JassFunctionJassValueVisitor.java b/jassparser/src/com/etheller/interpreter/ast/value/visitor/JassFunctionJassValueVisitor.java index 6d9cf1b..ee5f6ba 100644 --- a/jassparser/src/com/etheller/interpreter/ast/value/visitor/JassFunctionJassValueVisitor.java +++ b/jassparser/src/com/etheller/interpreter/ast/value/visitor/JassFunctionJassValueVisitor.java @@ -4,6 +4,7 @@ import com.etheller.interpreter.ast.function.JassFunction; import com.etheller.interpreter.ast.value.ArrayJassValue; import com.etheller.interpreter.ast.value.BooleanJassValue; import com.etheller.interpreter.ast.value.CodeJassValue; +import com.etheller.interpreter.ast.value.HandleJassValue; import com.etheller.interpreter.ast.value.IntegerJassValue; import com.etheller.interpreter.ast.value.JassValueVisitor; import com.etheller.interpreter.ast.value.RealJassValue; @@ -46,4 +47,9 @@ public class JassFunctionJassValueVisitor implements JassValueVisitor { return value.getType(); } + @Override + public JassType accept(final HandleJassValue value) { + return value.getType(); + } + } diff --git a/jassparser/src/com/etheller/interpreter/ast/value/visitor/NotJassValueVisitor.java b/jassparser/src/com/etheller/interpreter/ast/value/visitor/NotJassValueVisitor.java new file mode 100644 index 0000000..f0d9f47 --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/value/visitor/NotJassValueVisitor.java @@ -0,0 +1,56 @@ +package com.etheller.interpreter.ast.value.visitor; + +import com.etheller.interpreter.ast.value.ArrayJassValue; +import com.etheller.interpreter.ast.value.BooleanJassValue; +import com.etheller.interpreter.ast.value.CodeJassValue; +import com.etheller.interpreter.ast.value.HandleJassValue; +import com.etheller.interpreter.ast.value.IntegerJassValue; +import com.etheller.interpreter.ast.value.JassValue; +import com.etheller.interpreter.ast.value.JassValueVisitor; +import com.etheller.interpreter.ast.value.RealJassValue; +import com.etheller.interpreter.ast.value.StringJassValue; + +public class NotJassValueVisitor implements JassValueVisitor { + private static final NotJassValueVisitor INSTANCE = new NotJassValueVisitor(); + + public static NotJassValueVisitor getInstance() { + return INSTANCE; + } + + @Override + public JassValue accept(final IntegerJassValue value) { + throw new IllegalStateException("Unable to apply not keyword to a variable of type integer"); + } + + @Override + public JassValue accept(final RealJassValue value) { + throw new IllegalStateException("Unable to apply not keyword to a variable of type real"); + } + + @Override + public JassValue accept(final BooleanJassValue value) { + return BooleanJassValue.inverse(value); + } + + @Override + public JassValue accept(final StringJassValue value) { + throw new IllegalStateException("Unable to apply not keyword to a variable of type string"); + } + + @Override + public JassValue accept(final CodeJassValue value) { + throw new IllegalStateException("Unable to apply not keyword to a variable of type code"); + } + + @Override + public JassValue accept(final ArrayJassValue value) { + throw new IllegalStateException("Unable to apply not keyword to a variable of an array type"); + } + + @Override + public JassValue accept(final HandleJassValue value) { + throw new IllegalStateException( + "Unable to apply not keyword to a variable of type " + value.getType().getName()); + } + +} diff --git a/jassparser/src/com/etheller/interpreter/ast/value/visitor/ObjectJassValueVisitor.java b/jassparser/src/com/etheller/interpreter/ast/value/visitor/ObjectJassValueVisitor.java new file mode 100644 index 0000000..d8da310 --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/value/visitor/ObjectJassValueVisitor.java @@ -0,0 +1,54 @@ +package com.etheller.interpreter.ast.value.visitor; + +import com.etheller.interpreter.ast.value.ArrayJassValue; +import com.etheller.interpreter.ast.value.BooleanJassValue; +import com.etheller.interpreter.ast.value.CodeJassValue; +import com.etheller.interpreter.ast.value.HandleJassValue; +import com.etheller.interpreter.ast.value.IntegerJassValue; +import com.etheller.interpreter.ast.value.JassValueVisitor; +import com.etheller.interpreter.ast.value.RealJassValue; +import com.etheller.interpreter.ast.value.StringJassValue; + +public class ObjectJassValueVisitor implements JassValueVisitor { + private static final ObjectJassValueVisitor INSTANCE = new ObjectJassValueVisitor(); + + public static ObjectJassValueVisitor getInstance() { + return (ObjectJassValueVisitor) INSTANCE; + } + + @Override + public T accept(final IntegerJassValue value) { + return null; + } + + @Override + public T accept(final RealJassValue value) { + return null; + } + + @Override + public T accept(final BooleanJassValue value) { + return null; + } + + @Override + public T accept(final StringJassValue value) { + return null; + } + + @Override + public T accept(final CodeJassValue value) { + return null; + } + + @Override + public T accept(final ArrayJassValue value) { + return null; + } + + @Override + public T accept(final HandleJassValue value) { + return (T) value.getJavaValue(); + } + +} diff --git a/jassparser/src/com/etheller/interpreter/ast/value/visitor/RealJassValueVisitor.java b/jassparser/src/com/etheller/interpreter/ast/value/visitor/RealJassValueVisitor.java new file mode 100644 index 0000000..f404581 --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/value/visitor/RealJassValueVisitor.java @@ -0,0 +1,54 @@ +package com.etheller.interpreter.ast.value.visitor; + +import com.etheller.interpreter.ast.value.ArrayJassValue; +import com.etheller.interpreter.ast.value.BooleanJassValue; +import com.etheller.interpreter.ast.value.CodeJassValue; +import com.etheller.interpreter.ast.value.HandleJassValue; +import com.etheller.interpreter.ast.value.IntegerJassValue; +import com.etheller.interpreter.ast.value.JassValueVisitor; +import com.etheller.interpreter.ast.value.RealJassValue; +import com.etheller.interpreter.ast.value.StringJassValue; + +public class RealJassValueVisitor implements JassValueVisitor { + private static final RealJassValueVisitor INSTANCE = new RealJassValueVisitor(); + + public static RealJassValueVisitor getInstance() { + return INSTANCE; + } + + @Override + public Double accept(final IntegerJassValue value) { + return Double.valueOf(value.getValue()); + } + + @Override + public Double accept(final RealJassValue value) { + return value.getValue(); + } + + @Override + public Double accept(final BooleanJassValue value) { + return 0.0; + } + + @Override + public Double accept(final StringJassValue value) { + return 0.0; + } + + @Override + public Double accept(final CodeJassValue value) { + return 0.0; + } + + @Override + public Double accept(final ArrayJassValue value) { + return 0.0; + } + + @Override + public Double accept(final HandleJassValue value) { + return 0.0; + } + +} diff --git a/jassparser/src/com/etheller/interpreter/ast/value/visitor/StringJassValueVisitor.java b/jassparser/src/com/etheller/interpreter/ast/value/visitor/StringJassValueVisitor.java index e589d0b..f9ef72f 100644 --- a/jassparser/src/com/etheller/interpreter/ast/value/visitor/StringJassValueVisitor.java +++ b/jassparser/src/com/etheller/interpreter/ast/value/visitor/StringJassValueVisitor.java @@ -3,6 +3,7 @@ package com.etheller.interpreter.ast.value.visitor; import com.etheller.interpreter.ast.value.ArrayJassValue; import com.etheller.interpreter.ast.value.BooleanJassValue; import com.etheller.interpreter.ast.value.CodeJassValue; +import com.etheller.interpreter.ast.value.HandleJassValue; import com.etheller.interpreter.ast.value.IntegerJassValue; import com.etheller.interpreter.ast.value.JassValueVisitor; import com.etheller.interpreter.ast.value.RealJassValue; @@ -45,4 +46,9 @@ public class StringJassValueVisitor implements JassValueVisitor { return null; } + @Override + public String accept(final HandleJassValue value) { + return null; + } + } diff --git a/jassparser/src/com/etheller/interpreter/ast/value/visitor/SuperTypeVisitor.java b/jassparser/src/com/etheller/interpreter/ast/value/visitor/SuperTypeVisitor.java new file mode 100644 index 0000000..2a9e336 --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/value/visitor/SuperTypeVisitor.java @@ -0,0 +1,30 @@ +package com.etheller.interpreter.ast.value.visitor; + +import com.etheller.interpreter.ast.value.ArrayJassType; +import com.etheller.interpreter.ast.value.HandleJassType; +import com.etheller.interpreter.ast.value.JassTypeVisitor; +import com.etheller.interpreter.ast.value.PrimitiveJassType; + +public class SuperTypeVisitor implements JassTypeVisitor { + private static final SuperTypeVisitor INSTANCE = new SuperTypeVisitor(); + + public static SuperTypeVisitor getInstance() { + return INSTANCE; + } + + @Override + public HandleJassType accept(final PrimitiveJassType primitiveType) { + return null; + } + + @Override + public HandleJassType accept(final ArrayJassType arrayType) { + return null; + } + + @Override + public HandleJassType accept(final HandleJassType type) { + return type.getSuperType(); + } + +} diff --git a/jassparser/src/com/etheller/interpreter/ast/visitors/JassArgumentsVisitor.java b/jassparser/src/com/etheller/interpreter/ast/visitors/JassArgumentsVisitor.java index ce9cdd5..cd44e48 100644 --- a/jassparser/src/com/etheller/interpreter/ast/visitors/JassArgumentsVisitor.java +++ b/jassparser/src/com/etheller/interpreter/ast/visitors/JassArgumentsVisitor.java @@ -1,11 +1,9 @@ package com.etheller.interpreter.ast.visitors; -import java.util.Collections; import java.util.LinkedList; import java.util.List; import com.etheller.interpreter.JassBaseVisitor; -import com.etheller.interpreter.JassParser.EmptyArgumentContext; import com.etheller.interpreter.JassParser.ListArgumentContext; import com.etheller.interpreter.JassParser.SingleArgumentContext; import com.etheller.interpreter.ast.expression.JassExpression; @@ -20,19 +18,14 @@ public class JassArgumentsVisitor extends JassBaseVisitor> @Override public List visitSingleArgument(final SingleArgumentContext ctx) { final List list = new LinkedList<>(); - list.add(argumentExpressionHandler.expressionVisitor.visit(ctx.expression())); + list.add(this.argumentExpressionHandler.expressionVisitor.visit(ctx.expression())); return list; } @Override public List visitListArgument(final ListArgumentContext ctx) { final List list = visit(ctx.argsList()); - list.add(0, argumentExpressionHandler.expressionVisitor.visit(ctx.expression())); + list.add(0, this.argumentExpressionHandler.expressionVisitor.visit(ctx.expression())); return list; } - - @Override - public List visitEmptyArgument(final EmptyArgumentContext ctx) { - return Collections.EMPTY_LIST; - } } diff --git a/jassparser/src/com/etheller/interpreter/ast/visitors/JassExpressionVisitor.java b/jassparser/src/com/etheller/interpreter/ast/visitors/JassExpressionVisitor.java index 21dcf35..0635d5e 100644 --- a/jassparser/src/com/etheller/interpreter/ast/visitors/JassExpressionVisitor.java +++ b/jassparser/src/com/etheller/interpreter/ast/visitors/JassExpressionVisitor.java @@ -1,11 +1,16 @@ package com.etheller.interpreter.ast.visitors; +import java.util.Collections; +import java.util.List; + import com.etheller.interpreter.JassBaseVisitor; +import com.etheller.interpreter.JassParser.ArgsListContext; import com.etheller.interpreter.JassParser.ArrayReferenceExpressionContext; import com.etheller.interpreter.JassParser.FalseExpressionContext; import com.etheller.interpreter.JassParser.FunctionCallExpressionContext; import com.etheller.interpreter.JassParser.FunctionReferenceExpressionContext; import com.etheller.interpreter.JassParser.IntegerLiteralExpressionContext; +import com.etheller.interpreter.JassParser.NotExpressionContext; import com.etheller.interpreter.JassParser.ParentheticalExpressionContext; import com.etheller.interpreter.JassParser.ReferenceExpressionContext; import com.etheller.interpreter.JassParser.StringLiteralExpressionContext; @@ -15,6 +20,7 @@ import com.etheller.interpreter.ast.expression.FunctionCallJassExpression; import com.etheller.interpreter.ast.expression.FunctionReferenceJassExpression; import com.etheller.interpreter.ast.expression.JassExpression; import com.etheller.interpreter.ast.expression.LiteralJassExpression; +import com.etheller.interpreter.ast.expression.NotJassExpression; import com.etheller.interpreter.ast.expression.ReferenceJassExpression; import com.etheller.interpreter.ast.value.BooleanJassValue; import com.etheller.interpreter.ast.value.IntegerJassValue; @@ -61,17 +67,24 @@ public class JassExpressionVisitor extends JassBaseVisitor { @Override public JassExpression visitFalseExpression(final FalseExpressionContext ctx) { - return new LiteralJassExpression(new BooleanJassValue(false)); + return new LiteralJassExpression(BooleanJassValue.FALSE); } @Override public JassExpression visitTrueExpression(final TrueExpressionContext ctx) { - return new LiteralJassExpression(new BooleanJassValue(true)); + return new LiteralJassExpression(BooleanJassValue.TRUE); + } + + @Override + public JassExpression visitNotExpression(final NotExpressionContext ctx) { + return new NotJassExpression(visit(ctx.expression())); } @Override public JassExpression visitFunctionCallExpression(final FunctionCallExpressionContext ctx) { - return new FunctionCallJassExpression(ctx.functionExpression().ID().getText(), - argumentExpressionHandler.argumentsVisitor.visit(ctx.functionExpression().argsList())); + final ArgsListContext argsList = ctx.functionExpression().argsList(); + final List arguments = argsList == null ? Collections.emptyList() + : this.argumentExpressionHandler.argumentsVisitor.visit(argsList); + return new FunctionCallJassExpression(ctx.functionExpression().ID().getText(), arguments); } } diff --git a/jassparser/src/com/etheller/interpreter/ast/visitors/JassGlobalsVisitor.java b/jassparser/src/com/etheller/interpreter/ast/visitors/JassGlobalsVisitor.java index 347d954..5273cd0 100644 --- a/jassparser/src/com/etheller/interpreter/ast/visitors/JassGlobalsVisitor.java +++ b/jassparser/src/com/etheller/interpreter/ast/visitors/JassGlobalsVisitor.java @@ -6,7 +6,6 @@ import com.etheller.interpreter.JassParser.DefinitionGlobalContext; import com.etheller.interpreter.ast.scope.GlobalScope; import com.etheller.interpreter.ast.scope.LocalScope; import com.etheller.interpreter.ast.value.JassType; -import com.etheller.interpreter.ast.value.PrimitiveJassType; import com.etheller.interpreter.ast.value.visitor.ArrayPrimitiveTypeVisitor; public class JassGlobalsVisitor extends JassBaseVisitor { @@ -24,25 +23,27 @@ public class JassGlobalsVisitor extends JassBaseVisitor { @Override public Void visitBasicGlobal(final BasicGlobalContext ctx) { - final JassType type = jassTypeVisitor.visit(ctx.type()); - final PrimitiveJassType arrayPrimType = type.visit(ArrayPrimitiveTypeVisitor.getInstance()); + final JassType type = this.jassTypeVisitor.visit(ctx.type()); + final JassType arrayPrimType = type.visit(ArrayPrimitiveTypeVisitor.getInstance()); if (arrayPrimType != null) { - globals.createGlobalArray(ctx.ID().getText(), type); - } else { - globals.createGlobal(ctx.ID().getText(), type); + this.globals.createGlobalArray(ctx.ID().getText(), type); + } + else { + this.globals.createGlobal(ctx.ID().getText(), type); } return null; } @Override public Void visitDefinitionGlobal(final DefinitionGlobalContext ctx) { - final JassType type = jassTypeVisitor.visit(ctx.type()); - final PrimitiveJassType arrayPrimType = type.visit(ArrayPrimitiveTypeVisitor.getInstance()); + final JassType type = this.jassTypeVisitor.visit(ctx.type()); + final JassType arrayPrimType = type.visit(ArrayPrimitiveTypeVisitor.getInstance()); if (arrayPrimType != null) { - globals.createGlobalArray(ctx.ID().getText(), type); - } else { - globals.createGlobal(ctx.ID().getText(), type, - jassExpressionVisitor.visit(ctx.assignTail().expression()).evaluate(globals, EMPTY_LOCAL_SCOPE)); + this.globals.createGlobalArray(ctx.ID().getText(), type); + } + else { + this.globals.createGlobal(ctx.ID().getText(), type, this.jassExpressionVisitor + .visit(ctx.assignTail().expression()).evaluate(this.globals, EMPTY_LOCAL_SCOPE)); } return null; } diff --git a/jassparser/src/com/etheller/interpreter/ast/visitors/JassProgramVisitor.java b/jassparser/src/com/etheller/interpreter/ast/visitors/JassProgramVisitor.java index 6162427..c113899 100644 --- a/jassparser/src/com/etheller/interpreter/ast/visitors/JassProgramVisitor.java +++ b/jassparser/src/com/etheller/interpreter/ast/visitors/JassProgramVisitor.java @@ -2,9 +2,7 @@ package com.etheller.interpreter.ast.visitors; import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; import java.util.List; -import java.util.Map; import com.etheller.interpreter.JassBaseVisitor; import com.etheller.interpreter.JassParser.BlockContext; @@ -22,7 +20,6 @@ import com.etheller.interpreter.ast.statement.JassStatement; public class JassProgramVisitor extends JassBaseVisitor { private final GlobalScope globals = new GlobalScope(); private final JassNativeManager jassNativeManager = new JassNativeManager(); - private final Map typeToSuperType = new HashMap<>(); private final JassTypeVisitor jassTypeVisitor = new JassTypeVisitor(this.globals); private final ArgumentExpressionHandler argumentExpressionHandler = new ArgumentExpressionHandler(); private final JassExpressionVisitor jassExpressionVisitor = new JassExpressionVisitor( @@ -45,24 +42,39 @@ public class JassProgramVisitor extends JassBaseVisitor { } } else if (ctx.nativeBlock() != null) { - this.jassNativeManager.registerNativeCode(ctx.nativeBlock().ID().getText(), + final String text = ctx.nativeBlock().ID().getText(); + System.out.println("Registering native: " + text); + this.jassNativeManager.registerNativeCode(text, this.jassParametersVisitor.visit(ctx.nativeBlock().paramList()), this.jassTypeVisitor.visit(ctx.nativeBlock().type()), this.globals); } return null; } + @Override + public Void visitFunctionBlock(final FunctionBlockContext ctx) { + final List statements = new ArrayList<>(); + for (final StatementContext statementContext : ctx.statements().statement()) { + statements.add(this.jassStatementVisitor.visit(statementContext)); + } + final UserJassFunction userJassFunction = new UserJassFunction(statements, + this.jassParametersVisitor.visit(ctx.paramList()), this.jassTypeVisitor.visit(ctx.type())); + this.globals.defineFunction(ctx.ID().getText(), userJassFunction); + return null; + } + @Override public Void visitProgram(final ProgramContext ctx) { for (final TypeDefinitionContext typeDefinitionContext : ctx.typeDefinitionBlock().typeDefinition()) { - this.typeToSuperType.put(typeDefinitionContext.ID(0).getText(), typeDefinitionContext.ID(1).getText()); + this.globals.loadTypeDefinition(typeDefinitionContext.ID(0).getText(), + typeDefinitionContext.ID(1).getText()); } for (final BlockContext blockContext : ctx.block()) { visit(blockContext); } for (final FunctionBlockContext functionBlockContext : ctx.functionBlock()) { final List statements = new ArrayList<>(); - for (final StatementContext statementContext : functionBlockContext.statement()) { + for (final StatementContext statementContext : functionBlockContext.statements().statement()) { statements.add(this.jassStatementVisitor.visit(statementContext)); } final UserJassFunction userJassFunction = new UserJassFunction(statements, @@ -72,7 +84,12 @@ public class JassProgramVisitor extends JassBaseVisitor { } final JassFunction mainFunction = this.globals.getFunctionByName("main"); if (mainFunction != null) { - mainFunction.call(Collections.EMPTY_LIST, this.globals); + try { + mainFunction.call(Collections.EMPTY_LIST, this.globals); + } + catch (final Exception exc) { + throw new RuntimeException("Exception on Line " + GlobalScope.getLineNumber(), exc); + } } return null; } diff --git a/jassparser/src/com/etheller/interpreter/ast/visitors/JassStatementVisitor.java b/jassparser/src/com/etheller/interpreter/ast/visitors/JassStatementVisitor.java index 44cabed..14ac829 100644 --- a/jassparser/src/com/etheller/interpreter/ast/visitors/JassStatementVisitor.java +++ b/jassparser/src/com/etheller/interpreter/ast/visitors/JassStatementVisitor.java @@ -1,12 +1,22 @@ package com.etheller.interpreter.ast.visitors; +import java.util.ArrayList; +import java.util.List; + import com.etheller.interpreter.JassBaseVisitor; import com.etheller.interpreter.JassParser.ArrayedAssignmentStatementContext; import com.etheller.interpreter.JassParser.CallStatementContext; +import com.etheller.interpreter.JassParser.IfElseIfStatementContext; +import com.etheller.interpreter.JassParser.IfElseStatementContext; import com.etheller.interpreter.JassParser.ReturnStatementContext; import com.etheller.interpreter.JassParser.SetStatementContext; +import com.etheller.interpreter.JassParser.SimpleIfStatementContext; +import com.etheller.interpreter.JassParser.StatementContext; import com.etheller.interpreter.ast.statement.JassArrayedAssignmentStatement; import com.etheller.interpreter.ast.statement.JassCallStatement; +import com.etheller.interpreter.ast.statement.JassIfElseIfStatement; +import com.etheller.interpreter.ast.statement.JassIfElseStatement; +import com.etheller.interpreter.ast.statement.JassIfStatement; import com.etheller.interpreter.ast.statement.JassReturnStatement; import com.etheller.interpreter.ast.statement.JassSetStatement; import com.etheller.interpreter.ast.statement.JassStatement; @@ -20,25 +30,62 @@ public class JassStatementVisitor extends JassBaseVisitor { @Override public JassStatement visitCallStatement(final CallStatementContext ctx) { - return new JassCallStatement(ctx.functionExpression().ID().getText(), - argumentExpressionHandler.argumentsVisitor.visit(ctx.functionExpression().argsList())); + return new JassCallStatement(ctx.getStart().getLine(), ctx.functionExpression().ID().getText(), + this.argumentExpressionHandler.argumentsVisitor.visit(ctx.functionExpression().argsList())); } @Override public JassStatement visitSetStatement(final SetStatementContext ctx) { - return new JassSetStatement(ctx.ID().getText(), - argumentExpressionHandler.expressionVisitor.visit(ctx.expression())); + return new JassSetStatement(ctx.getStart().getLine(), ctx.ID().getText(), + this.argumentExpressionHandler.expressionVisitor.visit(ctx.expression())); } @Override public JassStatement visitReturnStatement(final ReturnStatementContext ctx) { - return new JassReturnStatement(argumentExpressionHandler.expressionVisitor.visit(ctx.expression())); + return new JassReturnStatement(ctx.getStart().getLine(), + this.argumentExpressionHandler.expressionVisitor.visit(ctx.expression())); + } + + @Override + public JassStatement visitIfElseIfStatement(final IfElseIfStatementContext ctx) { + final List thenStatements = new ArrayList<>(); + for (final StatementContext statementCtx : ctx.statements().statement()) { + thenStatements.add(visit(statementCtx)); + } + final JassStatement elseIfTail = visit(ctx.ifStatementPartial()); + return new JassIfElseIfStatement(ctx.getStart().getLine(), + this.argumentExpressionHandler.expressionVisitor.visit(ctx.expression()), thenStatements, elseIfTail); + } + + @Override + public JassStatement visitIfElseStatement(final IfElseStatementContext ctx) { + final List thenStatements = new ArrayList<>(); + for (final StatementContext statementCtx : ctx.statements(0).statement()) { + thenStatements.add(visit(statementCtx)); + } + final List elseStatements = new ArrayList<>(); + for (final StatementContext statementCtx : ctx.statements(1).statement()) { + elseStatements.add(visit(statementCtx)); + } + return new JassIfElseStatement(ctx.getStart().getLine(), + this.argumentExpressionHandler.expressionVisitor.visit(ctx.expression()), thenStatements, + elseStatements); + } + + @Override + public JassStatement visitSimpleIfStatement(final SimpleIfStatementContext ctx) { + final List thenStatements = new ArrayList<>(); + for (final StatementContext statementCtx : ctx.statements().statement()) { + thenStatements.add(visit(statementCtx)); + } + return new JassIfStatement(ctx.getStart().getLine(), + this.argumentExpressionHandler.expressionVisitor.visit(ctx.expression()), thenStatements); } @Override public JassStatement visitArrayedAssignmentStatement(final ArrayedAssignmentStatementContext ctx) { - return new JassArrayedAssignmentStatement(ctx.ID().getText(), - argumentExpressionHandler.expressionVisitor.visit(ctx.expression(0)), - argumentExpressionHandler.expressionVisitor.visit(ctx.expression(1))); + return new JassArrayedAssignmentStatement(ctx.getStart().getLine(), ctx.ID().getText(), + this.argumentExpressionHandler.expressionVisitor.visit(ctx.expression(0)), + this.argumentExpressionHandler.expressionVisitor.visit(ctx.expression(1))); } } diff --git a/resources/Scripts/common.jui b/resources/Scripts/common.jui new file mode 100644 index 0000000..bcd74f7 --- /dev/null +++ b/resources/Scripts/common.jui @@ -0,0 +1,94 @@ +//============================================================================ +// User Interface scripts for the Warsmash mod engine. This is +// an attempt to get these defined in an external import that +// a map can override. Unfortunately, although I would like for +// this to be a JASS file, it is not very consistent with the +// notion of the JASS2 VM to define UI, because handles are +// mostly network synced. So, we will assume the contents +// of this file are run in a special client-only JASS VM +// once I implement networking, so that this script will not +// cause a desync as it would if it was in the standard game +// JASS files. For that reason, it will have '.jui' extension +// to signify it runs in this modified JASS VM, instead of +// standard '.j' as used for JASS2 script files. +// +// Right now there is some duplicated code from common.j -- +// maybe later on we will load that stuff first, then this +// one in the same variable space? +// + +type framehandle extends handle +type framepointtype extends handle + +native LogError takes string message returns nothing +constant native ConvertFramePointType takes integer i returns framepointtype + +globals + +//=================================================== +// UI API constants +//=================================================== + + constant framepointtype FRAMEPOINT_TOPLEFT = ConvertFramePointType(0) + constant framepointtype FRAMEPOINT_TOP = ConvertFramePointType(1) + constant framepointtype FRAMEPOINT_TOPRIGHT = ConvertFramePointType(2) + constant framepointtype FRAMEPOINT_LEFT = ConvertFramePointType(3) + constant framepointtype FRAMEPOINT_CENTER = ConvertFramePointType(4) + constant framepointtype FRAMEPOINT_RIGHT = ConvertFramePointType(5) + constant framepointtype FRAMEPOINT_BOTTOMLEFT = ConvertFramePointType(6) + constant framepointtype FRAMEPOINT_BOTTOM = ConvertFramePointType(7) + constant framepointtype FRAMEPOINT_BOTTOMRIGHT = ConvertFramePointType(8) +endglobals + +//=================================================== +// UI API +//=================================================== + +// Loads an entry from the file "UI\war3skins.txt" and returns +// it as the current GAMEUI. The default possible +// strings are "Human", "Orc", "NightElf", and "Undead". +// Some UI FDF templates will use this information as +// the source for lookup strings as a means to change +// their style. +// Calling this more than once will probably crash the game, +// or something, so only call it once on startup. +native CreateRootFrame takes string skinName returns framehandle + +// Loads the (T)able (O)f (C)ontents file. +// This must be a simple text document with each line +// having only a filepath of a .FDF frame template +// definition file to load. Unlike War3 engine, +// I do not require the file to include one extra +// blank line at the end. +// +// We typically call this first during UI setup, and +// only once for a given mod. +native LoadTOCFile takes string TOCFile returns framehandle + +// Spawn a SIMPLEFRAME element that was defined in a FDF +// template onto the screen. The "name" field must match +// the name of a template to spawn, loaded with LoadTOCFile. +// The create context is pretty pointless, but I think +// they use it so that they have an integer tag on the +// "Attack 1" and "Attack 2" ui components. I was trying +// to keep parity with the FDF UI APIs from 1.31, imagine +// that. +native CreateSimpleFrame takes string name, framehandle owner, integer createContext returns framehandle + +// Set the absolute point (often called Anchor) for the frame handle. +// See FDF template files for examples +native FrameSetAbsPoint takes framehandle frame, framepointtype point, real x, real y returns nothing + +// Created for Warsmash engine, not a part of 1.31 UI apis, +// and at some point it might be removed. Basically +// this function will apply Anchors and SetPoints assigned +// to the frame handle and all its children and resolve where +// they should go onscreen. Generally in my experience, +// War3 will do this automatically in their FDF system. +native FramePositionBounds takes framehandle frame returns nothing + +// Used to lookup fields in the Skin data, for example +// SkinGetField("TimeOfDayIndicator") will return the +// string "UI\\Console\\Human\\HumanUI-TimeIndicator.mdl" +// when the "Human" skin was loaded with CreateRootFrame +native SkinGetField takes string field returns string diff --git a/resources/Scripts/melee.jui b/resources/Scripts/melee.jui new file mode 100644 index 0000000..44823fb --- /dev/null +++ b/resources/Scripts/melee.jui @@ -0,0 +1,35 @@ + +globals +// Defaults for testing: + constant string SKIN = "Human" +// Major UI components + framehandle ROOT_FRAME + framehandle CONSOLE_UI + framehandle RESOURCE_BAR +endglobals + + +function main takes nothing returns nothing + // ================================= + // Load skins and templates + // ================================= + set ROOT_FRAME = CreateRootFrame(SKIN) + if not LoadTOCFile("UI\\FrameDef\\FrameDef.toc") then + call LogError("Unable to load FrameDef.toc") + endif + // ================================= + // Load major UI components + // ================================= + // Console UI is the background with the racial theme + set CONSOLE_UI = CreateSimpleFrame("ConsoleUI", ROOT_FRAME, 0) + // Resource bar is a 3 part bar with Gold, Lumber, and Food. + // Its template does not specify where to put it, so we must + // put it in the "TOPRIGHT" corner. + set RESOURCE_BAR = CreateSimpleFrame("ResourceBarFrame", CONSOLE_UI, 0) + call FrameSetAbsPoint(RESOURCE_BAR, FRAMEPOINT_TOPRIGHT, 0, 0) + + // Assemble the UI and resolve the location of every component that + // has Anchors and SetPoints (maybe in future version this call + // wont be necessary!) + call FramePositionBounds(ROOT_FRAME) +endfunction \ No newline at end of file From 93f91b0f4d78df12c7eb2ddc47344366f8fac363 Mon Sep 17 00:00:00 2001 From: Retera Date: Sun, 28 Jun 2020 02:43:17 -0400 Subject: [PATCH 038/116] Update for pathing --- core/src/com/etheller/warsmash/TestMain.java | 24 +- .../etheller/warsmash/WarsmashGdxMapGame.java | 46 ++-- .../etheller/warsmash/gameui/FDFGameUI.java | 11 - .../warsmash/parsers/mdlx/Sequence.java | 14 +- .../warsmash/parsers/w3x/War3Map.java | 10 + .../viewer5/handlers/mdx/MdxModel.java | 2 +- .../warsmash/viewer5/handlers/w3x/Doodad.java | 2 +- .../viewer5/handlers/w3x/StandSequence.java | 75 ++++++ .../viewer5/handlers/w3x/W3xShaders.java | 7 +- .../viewer5/handlers/w3x/War3MapViewer.java | 42 +++- .../handlers/w3x/environment/PathingGrid.java | 221 ++++++++++++++++++ .../handlers/w3x/environment/Terrain.java | 36 ++- .../handlers/w3x/rendersim/RenderUnit.java | 47 +++- .../handlers/w3x/simulation/COrder.java | 4 +- .../handlers/w3x/simulation/CSimulation.java | 18 +- .../w3x/simulation/CSimulationMapData.java | 10 + .../handlers/w3x/simulation/CUnit.java | 10 +- .../handlers/w3x/simulation/CUnitType.java | 26 +++ .../w3x/simulation/data/CUnitData.java | 11 +- .../w3x/simulation/orders/CAttackOrder.java | 5 +- .../simulation/orders/CDoNothingOrder.java | 5 +- .../w3x/simulation/orders/CMoveOrder.java | 157 +++++++++++-- .../pathing/CPathfindingProcessor.java | 156 +++++++++++++ .../warsmash/desktop/DesktopLauncher.java | 6 +- 24 files changed, 839 insertions(+), 106 deletions(-) delete mode 100644 core/src/com/etheller/warsmash/gameui/FDFGameUI.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulationMapData.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/pathing/CPathfindingProcessor.java diff --git a/core/src/com/etheller/warsmash/TestMain.java b/core/src/com/etheller/warsmash/TestMain.java index ae0767c..18f1c2d 100644 --- a/core/src/com/etheller/warsmash/TestMain.java +++ b/core/src/com/etheller/warsmash/TestMain.java @@ -1,29 +1,7 @@ package com.etheller.warsmash; -import java.awt.image.BufferedImage; -import java.io.IOException; -import java.io.InputStream; - -import javax.imageio.ImageIO; -import javax.swing.ImageIcon; -import javax.swing.JLabel; -import javax.swing.JOptionPane; - -import com.etheller.warsmash.datasources.DataSource; -import com.etheller.warsmash.datasources.MpqDataSourceDescriptor; - public class TestMain { public static void main(final String[] args) { - final MpqDataSourceDescriptor desc = new MpqDataSourceDescriptor("E:\\Backups\\Warcraft\\Data\\127\\Z.mpq"); - final DataSource createDataSource = desc.createDataSource(); - try { - final InputStream cliffZ = createDataSource.getResourceAsStream("ReplaceableTextures\\Cliff\\Cliff0.blp"); - final BufferedImage img = ImageIO.read(cliffZ); - JOptionPane.showMessageDialog(null, new JLabel(new ImageIcon(img))); - } - catch (final IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } + System.out.println(Integer.parseInt("4294967295")); } } diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java index 081e422..ea8cad2 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java @@ -14,7 +14,6 @@ import com.badlogic.gdx.ApplicationAdapter; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Input; import com.badlogic.gdx.InputProcessor; -import com.badlogic.gdx.audio.Music; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.GL30; @@ -135,7 +134,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.viewer.worldScene.enableAudio(); this.viewer.enableAudio(); try { - this.viewer.loadMap("American Colo EX 1.0 unpro.w3x"); + this.viewer.loadMap("Pathing.w3x"); } catch (final IOException e) { throw new RuntimeException(e); @@ -231,11 +230,11 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv Gdx.input.setInputProcessor(this); - final Music music = Gdx.audio - .newMusic(new DataSourceFileHandle(this.viewer.dataSource, "Sound\\Music\\mp3Music\\DarkAgents.mp3")); - music.setVolume(0.2f); - music.setLooping(true); - music.play(); +// final Music music = Gdx.audio.newMusic( +// new DataSourceFileHandle(this.viewer.dataSource, "Sound\\Music\\mp3Music\\War2IntroMusic.mp3")); +// music.setVolume(0.2f); +// music.setLooping(true); +// music.play(); this.minimap = new Rectangle(18.75f, 13.75f, 278.75f, 276.25f); final float worldWidth = (this.viewer.terrain.columns - 1); @@ -269,8 +268,9 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv final float deltaTime = Gdx.graphics.getDeltaTime(); Gdx.gl30.glBindVertexArray(WarsmashGdxGame.VAO); this.cameraManager.target.add(this.cameraVelocity.x * deltaTime, this.cameraVelocity.y * deltaTime, 0); - this.cameraManager.target.z = this.viewer.terrain.getGroundHeight(this.cameraManager.target.x, - this.cameraManager.target.y); + this.cameraManager.target.z = Math.max( + this.viewer.terrain.getGroundHeight(this.cameraManager.target.x, this.cameraManager.target.y), + this.viewer.terrain.getWaterHeight(this.cameraManager.target.x, this.cameraManager.target.y)); this.cameraManager.updateCamera(); this.portraitCameraManager.updateCamera(); this.viewer.updateAndRender(); @@ -454,10 +454,10 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.rotationSpeed = (float) (Math.PI / 180); this.zoomFactor = 0.1f; this.horizontalAngle = 0;// (float) (Math.PI / 2); - this.verticalAngle = (float) (Math.PI / 5); - this.distance = 1600; + this.verticalAngle = (float) Math.toRadians(34); + this.distance = 1650; this.position = new Vector3(); - this.target = new Vector3(0, 0, 50); + this.target = new Vector3(0, 0, 0); this.worldUp = new Vector3(0, 0, 1); this.vecHeap = new Vector3(); this.quatHeap = new Quaternion(); @@ -469,11 +469,6 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv } private void updateCamera() { - // Limit the vertical angle so it doesn't flip. - // Since the camera uses a quaternion, flips don't matter to it, but this feels - // better. - this.verticalAngle = (float) Math.min(Math.max(0.01, this.verticalAngle), Math.PI - 0.01); - this.quatHeap.idt(); this.quatHeap.setFromAxisRad(0, 0, 1, this.horizontalAngle); this.quatHeap2.idt(); @@ -504,6 +499,9 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.camera.perspective(this.modelCamera.fieldOfView * 0.75f, this.camera.getAspect(), this.modelCamera.nearClippingPlane, this.modelCamera.farClippingPlane); } + else { + this.camera.perspective(70, this.camera.getAspect(), 100, 5000); + } this.camera.moveToAndFace(this.position, this.target, this.worldUp); } @@ -666,13 +664,13 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv @Override public boolean scrolled(final int amount) { - this.cameraManager.verticalAngle -= amount / 10.f; - if (this.cameraManager.verticalAngle > (Math.PI / 2)) { - this.cameraManager.verticalAngle = (float) Math.PI / 2; - } - if (this.cameraManager.verticalAngle < (Math.PI / 5)) { - this.cameraManager.verticalAngle = (float) (Math.PI / 5); - } +// this.cameraManager.verticalAngle -= amount / 10.f; +// if (this.cameraManager.verticalAngle > (Math.PI / 2)) { +// this.cameraManager.verticalAngle = (float) Math.PI / 2; +// } +// if (this.cameraManager.verticalAngle < (Math.PI / 5)) { +// this.cameraManager.verticalAngle = (float) (Math.PI / 5); +// } return true; } diff --git a/core/src/com/etheller/warsmash/gameui/FDFGameUI.java b/core/src/com/etheller/warsmash/gameui/FDFGameUI.java deleted file mode 100644 index cad3521..0000000 --- a/core/src/com/etheller/warsmash/gameui/FDFGameUI.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.etheller.warsmash.gameui; - -import com.etheller.warsmash.parsers.fdf.datamodel.FrameTemplateEnvironment; - -public class FDFGameUI { - private final FrameTemplateEnvironment frameTemplateEnvironment; - - public FDFGameUI(final FrameTemplateEnvironment frameTemplateEnvironment) { - this.frameTemplateEnvironment = frameTemplateEnvironment; - } -} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/Sequence.java b/core/src/com/etheller/warsmash/parsers/mdlx/Sequence.java index 945a95c..9f89374 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/Sequence.java +++ b/core/src/com/etheller/warsmash/parsers/mdlx/Sequence.java @@ -114,18 +114,18 @@ public class Sequence implements MdlxBlock { private void populateTags() { this.primaryTags.clear(); this.secondaryTags.clear(); - for (final String token : this.name.split("\\s+")) { + TokenLoop: for (final String token : this.name.split("\\s+")) { final String upperCaseToken = token.toUpperCase(); for (final PrimaryTag primaryTag : PrimaryTag.values()) { if (upperCaseToken.equals(primaryTag.name())) { this.primaryTags.add(primaryTag); - continue; + continue TokenLoop; } } for (final SecondaryTag secondaryTag : SecondaryTag.values()) { if (upperCaseToken.equals(secondaryTag.name())) { this.secondaryTags.add(secondaryTag); - continue; + continue TokenLoop; } } break; @@ -159,4 +159,12 @@ public class Sequence implements MdlxBlock { public Extent getExtent() { return this.extent; } + + public EnumSet getPrimaryTags() { + return this.primaryTags; + } + + public EnumSet getSecondaryTags() { + return this.secondaryTags; + } } diff --git a/core/src/com/etheller/warsmash/parsers/w3x/War3Map.java b/core/src/com/etheller/warsmash/parsers/w3x/War3Map.java index f1e1182..b888c54 100644 --- a/core/src/com/etheller/warsmash/parsers/w3x/War3Map.java +++ b/core/src/com/etheller/warsmash/parsers/w3x/War3Map.java @@ -18,6 +18,7 @@ import com.etheller.warsmash.parsers.w3x.objectdata.Warcraft3MapObjectData; import com.etheller.warsmash.parsers.w3x.unitsdoo.War3MapUnitsDoo; import com.etheller.warsmash.parsers.w3x.w3e.War3MapW3e; import com.etheller.warsmash.parsers.w3x.w3i.War3MapW3i; +import com.etheller.warsmash.parsers.w3x.wpm.War3MapWpm; import com.google.common.io.LittleEndianDataInputStream; import mpq.MPQArchive; @@ -71,6 +72,15 @@ public class War3Map implements DataSource { return environment; } + public War3MapWpm readPathing() throws IOException { + War3MapWpm pathingMap; + try (LittleEndianDataInputStream stream = new LittleEndianDataInputStream( + this.dataSource.getResourceAsStream("war3map.wpm"))) { + pathingMap = new War3MapWpm(stream); + } + return pathingMap; + } + public War3MapDoo readDoodads() throws IOException { War3MapDoo doodadsFile; try (LittleEndianDataInputStream stream = new LittleEndianDataInputStream( diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java index 1bfaafb..27bac4f 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java @@ -58,7 +58,7 @@ public class MdxModel extends com.etheller.warsmash.viewer5.Model { @Override public ModelInstance createInstance(final int type) { - if ((type == 1) && false) { + if (type == 1) { return new MdxSimpleInstance(this); } else { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Doodad.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Doodad.java index ae34f40..d39df24 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Doodad.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Doodad.java @@ -19,7 +19,7 @@ public class Doodad { final boolean isSimple = row.readSLKTagBoolean("lightweight"); ModelInstance instance; - if (isSimple) { + if (isSimple && false) { instance = model.addInstance(1); } else { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/StandSequence.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/StandSequence.java index ddb44a1..61937a4 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/StandSequence.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/StandSequence.java @@ -1,11 +1,14 @@ package com.etheller.warsmash.viewer5.handlers.w3x; import java.util.ArrayList; +import java.util.EnumSet; import java.util.List; import com.etheller.warsmash.parsers.mdlx.Sequence; import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; +import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; +import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.SecondaryTag; public class StandSequence { @@ -26,6 +29,34 @@ public class StandSequence { return filtered; } + private static List filterSequences(final PrimaryTag type, final EnumSet tags, + final List sequences) { + final List filtered = new ArrayList<>(); + + for (int i = 0, l = sequences.size(); i < l; i++) { + final Sequence sequence = sequences.get(i); + if (sequence.getPrimaryTags().contains(type) && sequence.getSecondaryTags().containsAll(tags) + && tags.containsAll(sequence.getSecondaryTags())) { + for (final AnimationTokens.SecondaryTag secondaryTag : sequence.getSecondaryTags()) { + if (tags.contains(secondaryTag)) { + filtered.add(new IndexedSequence(sequence, i)); + } + } + } + } + if (filtered.isEmpty()) { + for (int i = 0, l = sequences.size(); i < l; i++) { + final Sequence sequence = sequences.get(i); + if (sequence.getPrimaryTags().contains(type) && sequence.getSecondaryTags().containsAll(tags) + && tags.containsAll(sequence.getSecondaryTags())) { + filtered.add(new IndexedSequence(sequence, i)); + } + } + } + + return filtered; + } + public static IndexedSequence selectSequence(final String type, final List sequences) { final List filtered = filterSequences(type, sequences); @@ -55,6 +86,36 @@ public class StandSequence { return sequence; } + public static IndexedSequence selectSequence(final AnimationTokens.PrimaryTag type, + final EnumSet tags, final List sequences) { + final List filtered = filterSequences(type, tags, sequences); + + filtered.sort(STAND_SEQUENCE_COMPARATOR); + + int i = 0; + for (final int l = filtered.size(); i < l; i++) { + final Sequence sequence = filtered.get(i).sequence; + final float rarity = sequence.getRarity(); + + if (rarity == 0) { + break; + } + + if ((Math.random() * 10) > rarity) { + return filtered.get(i); + } + } + + final int sequencesLeft = filtered.size() - i; + final int random = (int) (i + Math.floor(Math.random() * sequencesLeft)); + if (sequencesLeft <= 0) { + return null; // new IndexedSequence(null, 0); + } + final IndexedSequence sequence = filtered.get(random); + + return sequence; + } + public static void randomStandSequence(final MdxComplexInstance target) { final MdxModel model = (MdxModel) target.model; final List sequences = model.getSequences(); @@ -145,4 +206,18 @@ public class StandSequence { randomStandSequence(target); } } + + public static void randomSequence(final MdxComplexInstance target, final PrimaryTag animationName, + final EnumSet secondaryAnimationTags) { + final MdxModel model = (MdxModel) target.model; + final List sequences = model.getSequences(); + final IndexedSequence sequence = selectSequence(animationName, secondaryAnimationTags, sequences); + + if (sequence != null) { + target.setSequence(sequence.index); + } + else { + randomStandSequence(target); + } + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xShaders.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xShaders.java index 26c351c..87bce37 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xShaders.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xShaders.java @@ -17,6 +17,7 @@ public class W3xShaders { " varying vec2 v_uv;\r\n" + // " varying vec2 v_suv;\r\n" + // " varying vec3 v_normal;\r\n" + // + " varying float a_positionHeight;\r\n" + // " const float normalDist = 0.25;\r\n" + // " void main() {\r\n" + // " vec2 halfPixel = u_pixel * 0.5;\r\n" + // @@ -34,6 +35,7 @@ public class W3xShaders { " v_uv = a_uv;\r\n" + // " v_suv = base / u_size;\r\n" + // " gl_Position = u_mvp * vec4(a_position.xy, height * 128.0 + a_position.z, 1.0);\r\n" + // + " a_positionHeight = a_position.z;\r\n" + // " }\r\n" + // " "; @@ -44,6 +46,7 @@ public class W3xShaders { " varying vec2 v_uv;\r\n" + // " varying vec2 v_suv;\r\n" + // " varying vec3 v_normal;\r\n" + // + " varying float a_positionHeight;\r\n" + // " const vec3 lightDirection = normalize(vec3(-0.3, -0.3, 0.25));\r\n" + // " void main() {\r\n" + // " if (any(bvec4(lessThan(v_uv, vec2(0.0)), greaterThan(v_uv, vec2(1.0))))) {\r\n" + // @@ -52,7 +55,9 @@ public class W3xShaders { " vec4 color = texture2D(u_texture, clamp(v_uv, 0.0, 1.0)).rgba * u_color;\r\n" + // " float shadow = texture2D(u_shadowMap, v_suv).r;\r\n" + // " color.xyz *= clamp(dot(v_normal, lightDirection) + 0.45, 0.0, 1.0);\r\n" + // - " color.xyz *= 1.0 - shadow;\r\n" + // + " if (a_positionHeight <= 4.0) {;\r\n" + // + " color.xyz *= 1.0 - shadow;\r\n" + // + " };\r\n" + // " gl_FragColor = color;\r\n" + // " }\r\n" + // " "; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index 0a54aa5..a84e636 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -1,5 +1,6 @@ package com.etheller.warsmash.viewer5.handlers.w3x; +import java.awt.image.BufferedImage; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; @@ -35,6 +36,7 @@ import com.etheller.warsmash.parsers.w3x.objectdata.Warcraft3MapObjectData; import com.etheller.warsmash.parsers.w3x.unitsdoo.War3MapUnitsDoo; import com.etheller.warsmash.parsers.w3x.w3e.War3MapW3e; import com.etheller.warsmash.parsers.w3x.w3i.War3MapW3i; +import com.etheller.warsmash.parsers.w3x.wpm.War3MapWpm; import com.etheller.warsmash.units.DataTable; import com.etheller.warsmash.units.Element; import com.etheller.warsmash.units.manager.MutableObjectData; @@ -57,6 +59,7 @@ import com.etheller.warsmash.viewer5.gl.WebGL; import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxHandler; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; +import com.etheller.warsmash.viewer5.handlers.tga.TgaFile; import com.etheller.warsmash.viewer5.handlers.w3x.SplatModel.SplatMover; import com.etheller.warsmash.viewer5.handlers.w3x.environment.Terrain; import com.etheller.warsmash.viewer5.handlers.w3x.environment.Terrain.Splat; @@ -91,6 +94,8 @@ public class War3MapViewer extends ModelViewer { private static final War3ID UNIT_SELECT_HEIGHT = War3ID.fromString("uslz"); private static final War3ID UNIT_SOUNDSET = War3ID.fromString("usnd"); private static final War3ID ITEM_FILE = War3ID.fromString("ifil"); + private static final War3ID UNIT_PATHING = War3ID.fromString("upat"); + private static final War3ID DESTRUCTABLE_PATHING = War3ID.fromString("bptx"); private static final War3ID sloc = War3ID.fromString("sloc"); private static final LoadGenericCallback stringDataCallback = new StringDataCallbackImplementation(); private static final float[] rayHeap = new float[6]; @@ -146,6 +151,8 @@ public class War3MapViewer extends ModelViewer { private final Random seededRandom = new Random(1337L); + private final Map filePathToPathingMap = new HashMap<>(); + public War3MapViewer(final DataSource dataSource, final CanvasProvider canvas) { super(dataSource, canvas); this.gameDataSource = dataSource; @@ -279,7 +286,10 @@ public class War3MapViewer extends ModelViewer { final War3MapW3e terrainData = this.mapMpq.readEnvironment(); - this.terrain = new Terrain(terrainData, w3iFile, this.webGL, this.dataSource, worldEditStrings, this); + final War3MapWpm terrainPathing = this.mapMpq.readPathing(); + + this.terrain = new Terrain(terrainData, terrainPathing, w3iFile, this.webGL, this.dataSource, worldEditStrings, + this); final float[] centerOffset = terrainData.getCenterOffset(); final int[] mapSize = terrainData.getMapSize(); @@ -357,7 +367,7 @@ public class War3MapViewer extends ModelViewer { return simulationAttackProjectile; } - }); + }, this.terrain.pathingGrid); if (this.doodadsAndDestructiblesLoaded) { this.loadDoodadsAndDestructibles(modifications); @@ -382,8 +392,6 @@ public class War3MapViewer extends ModelViewer { } private void loadDoodadsAndDestructibles(final Warcraft3MapObjectData modifications) throws IOException { - final War3MapDoo dooFile = this.mapMpq.readDoodads(); - this.applyModificationFile(this.doodadsData, this.doodadMetaData, modifications.getDoodads(), WorldEditorDataType.DOODADS); this.applyModificationFile(this.doodadsData, this.destructableMetaData, modifications.getDestructibles(), @@ -422,6 +430,18 @@ public class War3MapViewer extends ModelViewer { this.terrain.addShadow(shadowString, doodad.getLocation()[0], doodad.getLocation()[1]); } + final String pathingTexture = row.readSLKTag("pathTex"); + if ((pathingTexture != null) && (pathingTexture.length() > 0) && !"_".equals(pathingTexture)) { + + BufferedImage bufferedImage = this.filePathToPathingMap.get(pathingTexture.toLowerCase()); + if (bufferedImage == null) { + bufferedImage = TgaFile.readTGA(pathingTexture, + this.mapMpq.getResourceAsStream(pathingTexture)); + this.filePathToPathingMap.put(pathingTexture.toLowerCase(), bufferedImage); + } + this.terrain.pathingGrid.blitPathingOverlayTexture(doodad.getLocation()[0], + doodad.getLocation()[1], (int) Math.toDegrees(doodad.getAngle()), bufferedImage); + } } // First see if the model is local. // Doodads referring to local models may have invalid variations, so if the @@ -580,6 +600,17 @@ public class War3MapViewer extends ModelViewer { if ((buildingShadow != null) && !"_".equals(buildingShadow)) { this.terrain.addShadow(buildingShadow, unit.getLocation()[0], unit.getLocation()[1]); } + final String pathingTexture = row.getFieldAsString(UNIT_PATHING, 0); + if ((pathingTexture != null) && (pathingTexture.length() > 0) && !"_".equals(pathingTexture)) { + BufferedImage bufferedImage = this.filePathToPathingMap.get(pathingTexture.toLowerCase()); + if (bufferedImage == null) { + bufferedImage = TgaFile.readTGA(pathingTexture, + this.mapMpq.getResourceAsStream(pathingTexture)); + this.filePathToPathingMap.put(pathingTexture.toLowerCase(), bufferedImage); + } + this.terrain.pathingGrid.blitPathingOverlayTexture(unit.getLocation()[0], unit.getLocation()[1], + (int) Math.toDegrees(unit.getAngle()), bufferedImage); + } final String soundName = row.getFieldAsString(UNIT_SOUNDSET, 0); UnitSoundset unitSoundset = soundsetNameToSoundset.get(soundName); @@ -816,7 +847,8 @@ public class War3MapViewer extends ModelViewer { RenderUnit entity = null; for (final RenderUnit unit : this.units) { final MdxComplexInstance instance = unit.instance; - if (instance.intersectRayWithCollision(gdxRayHeap, intersectionHeap)) { + if (instance.isVisible(this.worldScene.camera) + && instance.intersectRayWithCollision(gdxRayHeap, intersectionHeap)) { entity = unit; } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java new file mode 100644 index 0000000..3149744 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java @@ -0,0 +1,221 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.environment; + +import java.awt.image.BufferedImage; +import java.util.HashMap; +import java.util.Map; + +import com.etheller.warsmash.parsers.w3x.wpm.War3MapWpm; + +public class PathingGrid { + private static final Map movetpToMovementType = new HashMap<>(); + static { + for (final MovementType movementType : MovementType.values()) { + if (movementType != MovementType.DISABLED) { + movetpToMovementType.put(movementType.typeKey, movementType); + } + } + } + + private final short[] pathingGrid; + private final short[] dynamicPathingOverlay; // for buildings and trees + private final int[] pathingGridSizes; + private final float[] centerOffset; + + public PathingGrid(final War3MapWpm terrainPathing, final float[] centerOffset) { + this.centerOffset = centerOffset; + this.pathingGrid = terrainPathing.getPathing(); + this.pathingGridSizes = terrainPathing.getSize(); + this.dynamicPathingOverlay = new short[this.pathingGrid.length]; + } + + // this blit function is basically copied from HiveWE, maybe remember to mention + // that in credits as well: + // https://github.com/stijnherfst/HiveWE/blob/master/Base/PathingMap.cpp + public void blitPathingOverlayTexture(final float positionX, final float positionY, final int rotation, + final BufferedImage pathingTextureTga) { + final int divW = ((rotation % 180) != 0) ? pathingTextureTga.getHeight() : pathingTextureTga.getWidth(); + final int divH = ((rotation % 180) != 0) ? pathingTextureTga.getWidth() : pathingTextureTga.getHeight(); + for (int j = 0; j < pathingTextureTga.getHeight(); j++) { + for (int i = 0; i < pathingTextureTga.getWidth(); i++) { + int x = i; + int y = j; + + switch (rotation) { + case 90: + x = pathingTextureTga.getHeight() - 1 - j; + y = i; + break; + case 180: + x = pathingTextureTga.getWidth() - 1 - i; + y = pathingTextureTga.getHeight() - 1 - j; + break; + case 270: + x = j; + y = pathingTextureTga.getWidth() - 1 - i; + break; + } + // Width and height for centering change if rotation is not divisible by 180 + final int xx = (getCellX(positionX) + x) - (divW / 2); + final int yy = (getCellY(positionY) + y) - (divH / 2); + + if ((xx < 0) || (xx > (this.pathingGridSizes[0] - 1)) || (yy < 0) + || (yy > (this.pathingGridSizes[1] - 1))) { + continue; + } + + final int rgb = pathingTextureTga.getRGB(i, pathingTextureTga.getHeight() - 1 - j); + byte data = 0; + if ((rgb & 0xFF) > 250) { + data |= PathingFlags.UNBUILDABLE; + } + if (((rgb & 0xFF00) >> 8) > 250) { + data |= PathingFlags.UNFLYABLE; + } + if (((rgb & 0xFF0000) >> 16) > 250) { + data |= PathingFlags.UNWALKABLE; + } + this.dynamicPathingOverlay[(yy * this.pathingGridSizes[0]) + xx] |= data; + } + } + } + + public int getWidth() { + return this.pathingGridSizes[0]; + } + + public int getHeight() { + return this.pathingGridSizes[1]; + } + + public short getPathing(final float x, final float y) { + return getCellPathing(getCellX(x), getCellY(y)); + } + + public int getCellX(final float x) { + final float userCellSpaceX = (x - this.centerOffset[0]) / 32.0f; + final int cellX = (int) userCellSpaceX; + return cellX; + } + + public int getCellY(final float y) { + final float userCellSpaceY = (y - this.centerOffset[1]) / 32.0f; + final int cellY = (int) userCellSpaceY; + return cellY; + } + + public float getWorldX(final int cellX) { + return (cellX * 32f) + this.centerOffset[0] + 16f; + } + + public float getWorldY(final int cellY) { + return (cellY * 32f) + this.centerOffset[1] + 16f; + } + + public short getCellPathing(final int cellX, final int cellY) { + return (short) (this.pathingGrid[(cellY * this.pathingGridSizes[0]) + cellX] + | this.dynamicPathingOverlay[(cellY * this.pathingGridSizes[0]) + cellX]); + } + + public boolean isPathable(final float x, final float y, final PathingType pathingType) { + return !PathingFlags.isPathingFlag(getPathing(x, y), pathingType.preventionFlag); + } + + public boolean isPathable(final float x, final float y, final MovementType pathingType) { + return pathingType.isPathable(getPathing(x, y)); + } + + public boolean isCellPathable(final int x, final int y, final MovementType pathingType) { + return pathingType.isPathable(getCellPathing(x, y)); + } + + public static boolean isPathingFlag(final short pathingValue, final PathingType pathingType) { + return !PathingFlags.isPathingFlag(pathingValue, pathingType.preventionFlag); + } + + // movetp referring to the unit data field of the same name + public static MovementType getMovementType(final String movetp) { + return movetpToMovementType.get(movetp); + } + + public static final class PathingFlags { + public static int UNWALKABLE = 0x2; + public static int UNFLYABLE = 0x4; + public static int UNBUILDABLE = 0x8; + public static int UNSWIMABLE = 0x40; // PROBABLY, didn't confirm this flag value is accurate + + public static boolean isPathingFlag(final short pathingValue, final int flag) { + return (pathingValue & flag) != 0; + } + + private PathingFlags() { + } + } + + public static enum MovementType { + FOOT("foot") { + @Override + public boolean isPathable(final short pathingValue) { + return !PathingFlags.isPathingFlag(pathingValue, PathingFlags.UNWALKABLE); + } + }, + HORSE("horse") { + @Override + public boolean isPathable(final short pathingValue) { + return !PathingFlags.isPathingFlag(pathingValue, PathingFlags.UNWALKABLE); + } + }, + FLY("fly") { + @Override + public boolean isPathable(final short pathingValue) { + return !PathingFlags.isPathingFlag(pathingValue, PathingFlags.UNFLYABLE); + } + }, + HOVER("hover") { + @Override + public boolean isPathable(final short pathingValue) { + return !PathingFlags.isPathingFlag(pathingValue, PathingFlags.UNWALKABLE); + } + }, + FLOAT("float") { + @Override + public boolean isPathable(final short pathingValue) { + return !PathingFlags.isPathingFlag(pathingValue, PathingFlags.UNSWIMABLE); + } + }, + AMPHIBIOUS("amph") { + @Override + public boolean isPathable(final short pathingValue) { + return !PathingFlags.isPathingFlag(pathingValue, PathingFlags.UNWALKABLE) + || !PathingFlags.isPathingFlag(pathingValue, PathingFlags.UNSWIMABLE); + } + }, + DISABLED("") { + @Override + public boolean isPathable(final short pathingValue) { + return true; + } + }; + private final String typeKey; + + // TODO windwalk pathing type can walk through units but not through items + + private MovementType(final String typeKey) { + this.typeKey = typeKey; + } + + public abstract boolean isPathable(short pathingValue); + } + + public static enum PathingType { + WALKABLE(PathingFlags.UNWALKABLE), + FLYABLE(PathingFlags.UNFLYABLE), + BUILDABLE(PathingFlags.UNBUILDABLE), + SWIMMABLE(PathingFlags.UNSWIMABLE); + + private final int preventionFlag; + + private PathingType(final int preventionFlag) { + this.preventionFlag = preventionFlag; + } + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java index 284a61b..1d37073 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java @@ -31,6 +31,7 @@ import com.etheller.warsmash.datasources.DataSource; import com.etheller.warsmash.parsers.w3x.w3e.Corner; import com.etheller.warsmash.parsers.w3x.w3e.War3MapW3e; import com.etheller.warsmash.parsers.w3x.w3i.War3MapW3i; +import com.etheller.warsmash.parsers.w3x.wpm.War3MapWpm; import com.etheller.warsmash.units.DataTable; import com.etheller.warsmash.units.Element; import com.etheller.warsmash.units.StandardObjectData; @@ -124,8 +125,9 @@ public class Terrain { private final int testArrayBuffer; private final int testElementBuffer; - public Terrain(final War3MapW3e w3eFile, final War3MapW3i w3iFile, final WebGL webGL, final DataSource dataSource, - final WorldEditStrings worldEditStrings, final War3MapViewer viewer) throws IOException { + public Terrain(final War3MapW3e w3eFile, final War3MapWpm terrainPathing, final War3MapW3i w3iFile, + final WebGL webGL, final DataSource dataSource, final WorldEditStrings worldEditStrings, + final War3MapViewer viewer) throws IOException { this.webGL = webGL; this.viewer = viewer; this.camera = viewer.worldScene.camera; @@ -394,6 +396,7 @@ public class Terrain { this.waveBuilder = new WaveBuilder(this.mapSize, this.waterTable, viewer, this.corners, this.centerOffset, this.waterHeightOffset, w3eFile, w3iFile); + this.pathingGrid = new PathingGrid(terrainPathing, this.centerOffset); } public void createWaves() { @@ -1147,6 +1150,7 @@ public class Terrain { static Vector3 tmp2 = new Vector3(); static Vector3 tmp3 = new Vector3(); private final WaveBuilder waveBuilder; + public PathingGrid pathingGrid; /** * Intersects the given ray with list of triangles. Returns the nearest @@ -1246,6 +1250,34 @@ public class Terrain { return 0; } + public float getWaterHeight(final float x, final float y) { + final float userCellSpaceX = (x - this.centerOffset[0]) / 128.0f; + final float userCellSpaceY = (y - this.centerOffset[1]) / 128.0f; + final int cellX = (int) userCellSpaceX; + final int cellY = (int) userCellSpaceY; + + if ((cellX >= 0) && (cellX < (this.mapSize[0] - 1)) && (cellY >= 0) && (cellY < (this.mapSize[1] - 1))) { + final float bottomLeft = this.waterHeights[(cellY * this.columns) + cellX]; + final float bottomRight = this.waterHeights[(cellY * this.columns) + cellX + 1]; + final float topLeft = this.waterHeights[((cellY + 1) * this.columns) + cellX]; + final float topRight = this.waterHeights[((cellY + 1) * this.columns) + cellX + 1]; + final float sqX = userCellSpaceX - cellX; + final float sqY = userCellSpaceY - cellY; + float height; + + if ((sqX + sqY) < 1) { + height = bottomLeft + ((bottomRight - bottomLeft) * sqX) + ((topLeft - bottomLeft) * sqY); + } + else { + height = topRight + ((bottomRight - topRight) * (1 - sqY)) + ((topLeft - topRight) * (1 - sqX)); + } + + return ((height + this.waterHeightOffset) * 128.0f); + } + + return this.waterHeightOffset * 128.0f; + } + public static final class Splat { public List locations = new ArrayList<>(); public List> unitMapping = new ArrayList<>(); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java index d1977ac..c0a87b9 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java @@ -1,6 +1,7 @@ package com.etheller.warsmash.viewer5.handlers.w3x.rendersim; import java.util.ArrayList; +import java.util.EnumSet; import java.util.List; import com.badlogic.gdx.Gdx; @@ -12,11 +13,15 @@ import com.etheller.warsmash.util.RenderMathUtils; import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; +import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens; +import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; import com.etheller.warsmash.viewer5.handlers.w3x.IndexedSequence; import com.etheller.warsmash.viewer5.handlers.w3x.SplatModel.SplatMover; import com.etheller.warsmash.viewer5.handlers.w3x.StandSequence; import com.etheller.warsmash.viewer5.handlers.w3x.UnitSoundset; import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; +import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid; +import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.MovementType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.COrder; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; @@ -44,7 +49,7 @@ public class RenderUnit { public int playerIndex; private final CUnit simulationUnit; private COrder lastOrder; - private String lastOrderAnimation; + private AnimationTokens.PrimaryTag lastOrderAnimation; public SplatMover shadow; public SplatMover selectionCircle; private final List commandCardIcons = new ArrayList<>(); @@ -53,6 +58,11 @@ public class RenderUnit { private float y; private float facing; + private boolean swimming; + + private final EnumSet secondaryAnimationTags = EnumSet + .noneOf(AnimationTokens.SecondaryTag.class); + public RenderUnit(final War3MapViewer map, final MdxModel model, final MutableGameObject row, final com.etheller.warsmash.parsers.w3x.unitsdoo.Unit unit, final UnitSoundset soundset, final MdxModel portraitModel, final CUnit simulationUnit) { @@ -152,7 +162,30 @@ public class RenderUnit { final float y = this.y; final float dy = y - this.location[1]; this.location[1] = y; - this.location[2] = this.simulationUnit.getFlyHeight() + map.terrain.getGroundHeight(x, y); + final float groundHeight; + final MovementType movementType = this.simulationUnit.getUnitType().getMovementType(); + final short terrainPathing = map.terrain.pathingGrid.getPathing(x, y); + final boolean swimming = (movementType == MovementType.AMPHIBIOUS) + && PathingGrid.isPathingFlag(terrainPathing, PathingGrid.PathingType.SWIMMABLE) + && !PathingGrid.isPathingFlag(terrainPathing, PathingGrid.PathingType.WALKABLE); + if ((swimming) || (movementType == MovementType.FLOAT) || (movementType == MovementType.FLY) + || (movementType == MovementType.HOVER)) { + groundHeight = Math.max(map.terrain.getGroundHeight(x, y), map.terrain.getWaterHeight(x, y)); + } + else { + groundHeight = map.terrain.getGroundHeight(x, y); + } + boolean changedAnimationTags = false; + if (swimming && !this.swimming) { + this.secondaryAnimationTags.add(AnimationTokens.SecondaryTag.SWIM); + changedAnimationTags = true; + } + else if (!swimming && this.swimming) { + this.secondaryAnimationTags.remove(AnimationTokens.SecondaryTag.SWIM); + changedAnimationTags = true; + } + this.swimming = swimming; + this.location[2] = this.simulationUnit.getFlyHeight() + groundHeight; this.instance.moveTo(this.location); float simulationFacing = this.simulationUnit.getFacing(); if (simulationFacing < 0) { @@ -190,14 +223,16 @@ public class RenderUnit { else if (mdxComplexInstance.sequenceEnded || (mdxComplexInstance.sequence == -1) || (currentOrder != this.lastOrder) || ((currentOrder != null) && (currentOrder.getAnimationName() != null) - && !currentOrder.getAnimationName().equals(this.lastOrderAnimation))) { + && !currentOrder.getAnimationName().equals(this.lastOrderAnimation)) + || changedAnimationTags) { if (this.simulationUnit.getCurrentOrder() != null) { - final String animationName = this.simulationUnit.getCurrentOrder().getAnimationName(); - StandSequence.randomSequence(mdxComplexInstance, animationName); + final AnimationTokens.PrimaryTag animationName = this.simulationUnit.getCurrentOrder() + .getAnimationName(); + StandSequence.randomSequence(mdxComplexInstance, animationName, this.secondaryAnimationTags); this.lastOrderAnimation = animationName; } else { - StandSequence.randomStandSequence(mdxComplexInstance); + StandSequence.randomSequence(mdxComplexInstance, PrimaryTag.STAND, this.secondaryAnimationTags); } } this.lastOrder = currentOrder; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/COrder.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/COrder.java index d4e60f2..591f036 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/COrder.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/COrder.java @@ -1,5 +1,7 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation; +import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens; + public interface COrder { /** * Executes one step of game simulation of the current order, returning true if @@ -25,5 +27,5 @@ public interface COrder { * * @return */ - String getAnimationName(); + AnimationTokens.PrimaryTag getAnimationName(); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java index 708335c..ff91065 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java @@ -1,13 +1,16 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation; +import java.awt.Point; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import com.etheller.warsmash.units.manager.MutableObjectData; import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.data.CAbilityData; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.data.CUnitData; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.pathing.CPathfindingProcessor; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.projectile.CAttackProjectile; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.ProjectileCreator; @@ -19,10 +22,14 @@ public class CSimulation { private final HandleIdAllocator handleIdAllocator; private final ProjectileCreator projectileCreator; private int gameTurnTick = 0; + private final PathingGrid pathingGrid; + private final CPathfindingProcessor pathfindingProcessor; public CSimulation(final MutableObjectData parsedUnitData, final MutableObjectData parsedAbilityData, - final ProjectileCreator projectileCreator) { + final ProjectileCreator projectileCreator, final PathingGrid pathingGrid) { this.projectileCreator = projectileCreator; + this.pathingGrid = pathingGrid; + this.pathfindingProcessor = new CPathfindingProcessor(pathingGrid); this.unitData = new CUnitData(parsedUnitData); this.abilityData = new CAbilityData(parsedAbilityData); this.units = new ArrayList<>(); @@ -56,6 +63,15 @@ public class CSimulation { return projectile; } + public PathingGrid getPathingGrid() { + return this.pathingGrid; + } + + public List findNaiveSlowPath(final int startX, final int startY, final int goalX, final int goalY, + final PathingGrid.MovementType movementType) { + return this.pathfindingProcessor.findNaiveSlowPath(startX, startY, goalX, goalY, movementType); + } + public void update() { for (final CUnit unit : this.units) { unit.update(this); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulationMapData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulationMapData.java new file mode 100644 index 0000000..21c2147 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulationMapData.java @@ -0,0 +1,10 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation; + +/** + * Provides limited access to game map data when necessary for the game + * simulation logic. Do not add methods to this to query anything that isn't + * going to be network sync'ed. + */ +public interface CSimulationMapData { + short getTerrainPathing(float x, float y); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java index 356de5b..27491b5 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java @@ -23,10 +23,11 @@ public class CUnit extends CWidget { private COrder currentOrder; private final Queue orderQueue = new LinkedList<>(); + private final CUnitType unitType; public CUnit(final int handleId, final int playerIndex, final float x, final float y, final float life, final War3ID typeId, final float facing, final float mana, final int maximumLife, final int maximumMana, - final int speed, final float defaultFlyingHeight) { + final int speed, final CUnitType unitType) { super(handleId, x, y, life); this.typeId = typeId; this.facing = facing; @@ -34,7 +35,8 @@ public class CUnit extends CWidget { this.maximumLife = maximumLife; this.maximumMana = maximumMana; this.speed = speed; - this.flyHeight = defaultFlyingHeight; + this.flyHeight = unitType.getDefaultFlyingHeight(); + this.unitType = unitType; } public void add(final CSimulation simulation, final CAbility ability) { @@ -150,4 +152,8 @@ public class CUnit extends CWidget { this.playerIndex = playerIndex; } + public CUnitType getUnitType() { + return this.unitType; + } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java new file mode 100644 index 0000000..2571767 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java @@ -0,0 +1,26 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation; + +import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid; +import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.MovementType; + +/** + * The quick (symbol table instead of map) lookup for unit type values that we + * probably cannot change per unit instance. + */ +public class CUnitType { + private final PathingGrid.MovementType movementType; + private final float defaultFlyingHeight; + + public CUnitType(final MovementType movementType, final float defaultFlyingHeight) { + this.movementType = movementType; + this.defaultFlyingHeight = defaultFlyingHeight; + } + + public float getDefaultFlyingHeight() { + return this.defaultFlyingHeight; + } + + public PathingGrid.MovementType getMovementType() { + return this.movementType; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java index 9f96282..1ae1586 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java @@ -1,10 +1,15 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.data; +import java.util.HashMap; +import java.util.Map; + import com.etheller.warsmash.units.manager.MutableObjectData; import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityAttack; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityHoldPosition; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityMove; @@ -39,7 +44,9 @@ public class CUnitData { private static final War3ID ATTACK2_COOLDOWN = War3ID.fromString("ua2c"); private static final War3ID DEFENSE = War3ID.fromString("udef"); private static final War3ID MOVE_HEIGHT = War3ID.fromString("umvh"); + private static final War3ID MOVE_TYPE = War3ID.fromString("umvt"); private final MutableObjectData unitData; + private final Map unitIdToUnitType = new HashMap<>(); public CUnitData(final MutableObjectData unitData) { this.unitData = unitData; @@ -53,8 +60,10 @@ public class CUnitData { final int manaMaximum = unitType.getFieldAsInteger(MANA_MAXIMUM, 0); final int speed = unitType.getFieldAsInteger(MOVEMENT_SPEED_BASE, 0); final float moveHeight = unitType.getFieldAsFloat(MOVE_HEIGHT, 0); + final String movetp = unitType.getFieldAsString(MOVE_TYPE, 0); + final PathingGrid.MovementType movementType = PathingGrid.getMovementType(movetp); final CUnit unit = new CUnit(handleId, playerIndex, x, y, life, typeId, facing, manaInitial, life, manaMaximum, - speed, moveHeight); + speed, new CUnitType(movementType, moveHeight)); if (speed > 0) { unit.add(simulation, CAbilityMove.INSTANCE); unit.add(simulation, CAbilityPatrol.INSTANCE); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CAttackOrder.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CAttackOrder.java index 3513b06..ea9e5e9 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CAttackOrder.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CAttackOrder.java @@ -1,6 +1,7 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders; import com.etheller.warsmash.util.WarsmashConstants; +import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.COrder; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; @@ -80,8 +81,8 @@ public class CAttackOrder implements COrder { } @Override - public String getAnimationName() { - return "attack"; + public AnimationTokens.PrimaryTag getAnimationName() { + return AnimationTokens.PrimaryTag.ATTACK; } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CDoNothingOrder.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CDoNothingOrder.java index 230a6c1..a7f1b23 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CDoNothingOrder.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CDoNothingOrder.java @@ -1,5 +1,6 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders; +import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.COrder; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; @@ -21,8 +22,8 @@ public class CDoNothingOrder implements COrder { } @Override - public String getAnimationName() { - return "stand"; + public AnimationTokens.PrimaryTag getAnimationName() { + return AnimationTokens.PrimaryTag.STAND; } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CMoveOrder.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CMoveOrder.java index fad6cb4..f10b484 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CMoveOrder.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CMoveOrder.java @@ -1,6 +1,12 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders; +import java.awt.Point; +import java.util.List; + import com.etheller.warsmash.util.WarsmashConstants; +import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens; +import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid; +import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.MovementType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.COrder; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; @@ -11,6 +17,8 @@ public class CMoveOrder implements COrder { private final float targetX; private final float targetY; private boolean wasWithinPropWindow = false; + private List path = null; + private boolean recalculated = false; public CMoveOrder(final CUnit unit, final float targetX, final float targetY) { this.unit = unit; @@ -22,8 +30,82 @@ public class CMoveOrder implements COrder { public boolean update(final CSimulation simulation) { final float prevX = this.unit.getX(); final float prevY = this.unit.getY(); - final float deltaY = this.targetY - prevY; - final float deltaX = this.targetX - prevX; + + final MovementType movementType = this.unit.getUnitType().getMovementType(); + final PathingGrid pathingGrid = simulation.getPathingGrid(); + final int startCellX = pathingGrid.getCellX(prevX); + final int startCellY = pathingGrid.getCellY(prevY); + final int goalCellX = pathingGrid.getCellX(this.targetX); + final int goalCellY = pathingGrid.getCellY(this.targetY); + if (this.path == null) { + this.path = simulation.findNaiveSlowPath(startCellX, startCellY, goalCellX, goalCellY, movementType); + // check for smoothing + if (!this.path.isEmpty()) { + int lastX = startCellX; + int lastY = startCellY; + int smoothingGroupStartX = startCellX; + int smoothingGroupStartY = startCellY; + final Point firstPathElement = this.path.get(0); + double totalPathDistance = firstPathElement.distance(lastX, lastY); + lastX = firstPathElement.x; + lastY = firstPathElement.y; + int smoothingStartIndex = -1; + for (int i = 0; i < (this.path.size() - 1); i++) { + final Point nextPossiblePathElement = this.path.get(i + 1); + totalPathDistance += nextPossiblePathElement.distance(lastX, lastY); + if ((totalPathDistance < (1.25 + * nextPossiblePathElement.distance(smoothingGroupStartX, smoothingGroupStartY))) + && pathingGrid.isCellPathable((smoothingGroupStartX + nextPossiblePathElement.x) / 2, + (smoothingGroupStartY + nextPossiblePathElement.y) / 2, movementType)) { + if (smoothingStartIndex == -1) { + smoothingStartIndex = i; + } + } + else { + if (smoothingStartIndex != -1) { + for (int j = smoothingStartIndex; j < i; j++) { + final Point removed = this.path.remove(j); + } + i = smoothingStartIndex; + } + smoothingStartIndex = -1; + final Point smoothGroupNext = this.path.get(i); + smoothingGroupStartX = smoothGroupNext.x; + smoothingGroupStartY = smoothGroupNext.y; + totalPathDistance = nextPossiblePathElement.distance(smoothGroupNext); + } + lastX = nextPossiblePathElement.x; + lastY = nextPossiblePathElement.y; + } + if (smoothingStartIndex != -1) { + for (int j = smoothingStartIndex; j < (this.path.size() - 1); j++) { + final Point removed = this.path.remove(j); + } + } + } + } + float currentTargetX; + float currentTargetY; + if (this.path.size() < 2) { + currentTargetX = this.targetX; + currentTargetY = this.targetY; + } + else { + Point nextPathElement = this.path.get(0); + if ((this.path.size() > 2) && !this.recalculated) { + final Point secondPathElement = this.path.get(1); + if ((secondPathElement.distance(startCellX, startCellY) >= 5 /* 5 nodes */) + && (nextPathElement.distance(startCellX, startCellY) <= 2)) { + nextPathElement = secondPathElement; + this.path.remove(0); + } + } + currentTargetX = pathingGrid.getWorldX(nextPathElement.x); + currentTargetY = pathingGrid.getWorldY(nextPathElement.y); + } + + float deltaX = currentTargetX - prevX; + float deltaY = currentTargetY - prevY; final double goalAngleRad = Math.atan2(deltaY, deltaX); float goalAngle = (float) Math.toDegrees(goalAngleRad); if (goalAngle < 0) { @@ -56,19 +138,60 @@ public class CMoveOrder implements COrder { } if (absDelta < propulsionWindow) { final float speedTick = speed * WarsmashConstants.SIMULATION_STEP_TIME; - final float speedTickSq = speedTick * speedTick; - - if (((deltaX * deltaX) + (deltaY * deltaY)) <= speedTickSq) { - this.unit.setX(this.targetX); - this.unit.setY(this.targetY); - return true; + double continueDistance = speedTick; + do { + boolean done; + float nextX, nextY; + final double travelDistance = Math.sqrt((deltaX * deltaX) + (deltaY * deltaY)); + if (travelDistance <= continueDistance) { + nextX = currentTargetX; + nextY = currentTargetY; + continueDistance = continueDistance - travelDistance; + done = true; + } + else { + final double radianFacing = Math.toRadians(facing); + nextX = (prevX + (float) (Math.cos(radianFacing) * continueDistance)); + nextY = (prevY + (float) (Math.sin(radianFacing) * continueDistance)); + continueDistance = 0; + done = (pathingGrid.getCellX(nextX) == pathingGrid.getCellX(currentTargetX)) + && (pathingGrid.getCellY(nextY) == pathingGrid.getCellY(currentTargetY)); + } + final short terrainPathing = pathingGrid.getPathing(nextX, nextY); + if (movementType.isPathable(terrainPathing)) { + this.unit.setX(nextX); + this.unit.setY(nextY); + if (done) { + if (this.path.isEmpty()) { + return true; + } + else { + this.path.remove(0); + if (this.path.size() < 2) { + currentTargetX = this.targetX; + currentTargetY = this.targetY; + } + else { + final Point firstPathElement = this.path.get(0); + currentTargetX = pathingGrid.getWorldX(firstPathElement.x); + currentTargetY = pathingGrid.getWorldY(firstPathElement.y); + } + deltaY = currentTargetY - prevY; + deltaX = currentTargetX - prevX; + } + } + } + else { + this.path = simulation.findNaiveSlowPath(startCellX, startCellY, goalCellX, goalCellY, + movementType); + this.recalculated = true; + if (this.path.isEmpty()) { + return true; + } + } + this.wasWithinPropWindow = true; } - else { - final double radianFacing = Math.toRadians(facing); - this.unit.setX(prevX + (float) (Math.cos(radianFacing) * speedTick)); - this.unit.setY(prevY + (float) (Math.sin(radianFacing) * speedTick)); - } - this.wasWithinPropWindow = true; + while (continueDistance > 0); } else { // If this happens, the unit is facing the wrong way, and has to turn before @@ -85,11 +208,11 @@ public class CMoveOrder implements COrder { } @Override - public String getAnimationName() { + public AnimationTokens.PrimaryTag getAnimationName() { if (!this.wasWithinPropWindow) { - return "stand"; + return AnimationTokens.PrimaryTag.STAND; } - return "walk"; + return AnimationTokens.PrimaryTag.WALK; } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/pathing/CPathfindingProcessor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/pathing/CPathfindingProcessor.java new file mode 100644 index 0000000..43c40d8 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/pathing/CPathfindingProcessor.java @@ -0,0 +1,156 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.pathing; + +import java.awt.Point; +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedList; +import java.util.List; +import java.util.PriorityQueue; + +import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid; + +public class CPathfindingProcessor { + private final PathingGrid pathingGrid; + private final Node[][] nodes; + private Node goal; + + public CPathfindingProcessor(final PathingGrid pathingGrid) { + this.pathingGrid = pathingGrid; + this.nodes = new Node[pathingGrid.getHeight()][pathingGrid.getWidth()]; + for (int i = 0; i < this.nodes.length; i++) { + for (int j = 0; j < this.nodes[i].length; j++) { + this.nodes[i][j] = new Node(new Point(j, i)); + } + } + } + + /** + * Finds the path to a point using a naive, slow, and unoptimized algorithm. + * Does not have optimizations yet, do this for a bunch of units and it will + * probably lag like a walrus. The implementation here was created by reading + * the wikipedia article on A* to jog my memory from data structures class back + * in college, and is meant only as a first draft to get things working. + * + * + * @param start + * @param goal + * @return + */ + public List findNaiveSlowPath(final int startX, final int startY, final int goalX, final int goalY, + final PathingGrid.MovementType movementType) { + if ((startX == goalX) && (startY == goalY)) { + return Collections.emptyList(); + } + this.goal = this.nodes[goalY][goalX]; + final Node start = this.nodes[startY][startX]; + for (int i = 0; i < this.nodes.length; i++) { + for (int j = 0; j < this.nodes[i].length; j++) { + this.nodes[i][j].g = Float.POSITIVE_INFINITY; + this.nodes[i][j].f = Float.POSITIVE_INFINITY; + this.nodes[i][j].cameFrom = null; + } + } + start.g = 0; + start.f = h(start); + final PriorityQueue openSet = new PriorityQueue<>(new Comparator() { + @Override + public int compare(final Node a, final Node b) { + return Double.compare(f(a), f(b)); + } + }); + openSet.add(start); + + while (!openSet.isEmpty()) { + Node current = openSet.poll(); + if (current == this.goal) { + final LinkedList totalPath = new LinkedList<>(); + Direction lastCameFromDirection = null; + while (current.cameFrom != null) { + if ((lastCameFromDirection == null) || (current.cameFromDirection != lastCameFromDirection)) { + totalPath.addFirst(current.point); + lastCameFromDirection = current.cameFromDirection; + } + current = current.cameFrom; + } + return totalPath; + } + + for (final Direction direction : Direction.VALUES) { + final int x = current.point.x + direction.xOffset; + final int y = current.point.y + direction.yOffset; + if ((x >= 0) && (x < this.pathingGrid.getWidth()) && (y >= 0) && (y < this.pathingGrid.getHeight()) + && movementType.isPathable(this.pathingGrid.getCellPathing(x, y)) + && movementType.isPathable(this.pathingGrid.getCellPathing(current.point.x, y)) + && movementType.isPathable(this.pathingGrid.getCellPathing(x, current.point.y))) { + double turnCost; + if ((current.cameFromDirection != null) && (direction != current.cameFromDirection)) { + turnCost = 0.25; + } + else { + turnCost = 0; + } + final double tentativeScore = current.g + direction.length + turnCost; + final Node neighbor = this.nodes[y][x]; + if (tentativeScore < neighbor.g) { + neighbor.cameFrom = current; + neighbor.cameFromDirection = direction; + neighbor.g = tentativeScore; + neighbor.f = tentativeScore + h(neighbor); + if (!openSet.contains(neighbor)) { + openSet.add(neighbor); + } + } + } + } + } + return Collections.emptyList(); + } + + public double f(final Node n) { + return n.g + h(n); + } + + public double g(final Node n) { + return n.g; + } + + public float h(final Node n) { + return (float) n.point.distance(this.goal.point); + } + + public static final class Node { + public Direction cameFromDirection; + private final Point point; + private double f; + private double g; + private Node cameFrom; + + private Node(final Point point) { + this.point = point; + } + } + + private static enum Direction { + NORTH_WEST(-1, 1), + NORTH(0, 1), + NORTH_EAST(1, 1), + EAST(1, 0), + SOUTH_EAST(1, -1), + SOUTH(0, -1), + SOUTH_WEST(-1, -1), + WEST(-1, 0); + + public static final Direction[] VALUES = values(); + + private final int xOffset; + private final int yOffset; + private final double length; + + private Direction(final int xOffset, final int yOffset) { + this.xOffset = xOffset; + this.yOffset = yOffset; + final double sqrt = Math.sqrt((xOffset * xOffset) + (yOffset * yOffset)); + this.length = sqrt; + } + } +} diff --git a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java index 9845124..3b92967 100644 --- a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java +++ b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java @@ -7,7 +7,7 @@ import org.lwjgl.opengl.GL33; import com.badlogic.gdx.backends.lwjgl.LwjglApplication; import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration; -import com.etheller.warsmash.WarsmashGdxGame; +import com.etheller.warsmash.WarsmashGdxMapGame; import com.etheller.warsmash.viewer5.gl.ANGLEInstancedArrays; import com.etheller.warsmash.viewer5.gl.DynamicShadowExtension; import com.etheller.warsmash.viewer5.gl.Extensions; @@ -58,10 +58,10 @@ public class DesktopLauncher { config.gles30ContextMajorVersion = 3; config.gles30ContextMinorVersion = 3; config.samples = 16; -// config.fullscreen = false; +// config.fullscreen = true; // final DisplayMode desktopDisplayMode = LwjglApplicationConfiguration.getDesktopDisplayMode(); // config.width = desktopDisplayMode.width; // config.height = desktopDisplayMode.height; - new LwjglApplication(new WarsmashGdxGame(), config); + new LwjglApplication(new WarsmashGdxMapGame(), config); } } From d21b8be7282df6f5e22631639ed1a1a6024e3f22 Mon Sep 17 00:00:00 2001 From: Retera Date: Wed, 1 Jul 2020 00:27:14 -0400 Subject: [PATCH 039/116] No repeat sounds, pathing, collision size --- .../etheller/warsmash/WarsmashGdxMapGame.java | 57 ++++++++++++++----- .../warsmash/viewer5/AudioContext.java | 1 + .../warsmash/viewer5/gl/Extensions.java | 2 + .../viewer5/gl/SoundLengthExtension.java | 7 +++ .../viewer5/handlers/w3x/UnitSoundset.java | 23 +++++++- .../viewer5/handlers/w3x/W3xShaders.java | 5 +- .../handlers/w3x/environment/PathingGrid.java | 18 ++++++ .../handlers/w3x/simulation/CSimulation.java | 4 +- .../handlers/w3x/simulation/CUnitType.java | 8 ++- .../w3x/simulation/data/CUnitData.java | 4 +- .../w3x/simulation/orders/CMoveOrder.java | 27 +++------ .../pathing/CPathfindingProcessor.java | 10 ++-- .../warsmash/desktop/DesktopLauncher.java | 9 +++ 13 files changed, 130 insertions(+), 45 deletions(-) create mode 100644 core/src/com/etheller/warsmash/viewer5/gl/SoundLengthExtension.java diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java index ea8cad2..fed854a 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java @@ -50,6 +50,7 @@ import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; import com.etheller.warsmash.viewer5.handlers.mdx.ReplaceableIds; import com.etheller.warsmash.viewer5.handlers.tga.TgaFile; import com.etheller.warsmash.viewer5.handlers.w3x.StandSequence; +import com.etheller.warsmash.viewer5.handlers.w3x.UnitSoundset.UnitAckSound; import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.CommandCardIcon; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderUnit; @@ -82,6 +83,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv private final Vector2 projectionTemp1 = new Vector2(); private final Vector2 projectionTemp2 = new Vector2(); private RenderUnit selectedUnit; + private int selectedSoundCount = 0; private Texture activeButtonTexture; @@ -595,6 +597,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv if ((rayPickUnit != null) && (rayPickUnit.playerIndex != this.selectedUnit.playerIndex)) { if (this.viewer.orderSmart(rayPickUnit)) { StandSequence.randomPortraitTalkSequence(this.portraitInstance); + this.selectedSoundCount = 0; } } else { @@ -607,6 +610,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.viewer.terrain.logRomp(x, y); if (this.viewer.orderSmart(clickLocationTemp.x, clickLocationTemp.y)) { StandSequence.randomPortraitTalkSequence(this.portraitInstance); + this.selectedSoundCount = 0; } } } @@ -614,25 +618,50 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv final List selectedUnits = this.viewer.selectUnit(screenX, worldScreenY, false); if (!selectedUnits.isEmpty()) { final RenderUnit unit = selectedUnits.get(0); + final boolean selectionChanged = this.selectedUnit != unit; + boolean playedNewSound = false; + if (selectionChanged) { + this.selectedSoundCount = 0; + } this.selectedUnit = unit; if (unit.soundset != null) { - unit.soundset.what.play(this.viewer.worldScene.audioContext, unit.location[0], unit.location[1]); + UnitAckSound ackSoundToPlay = unit.soundset.what; + final int pissedSoundCount = unit.soundset.pissed.getSoundCount(); + int soundIndex; + if ((this.selectedSoundCount >= 3) && (pissedSoundCount > 0)) { + soundIndex = this.selectedSoundCount - 3; + ackSoundToPlay = unit.soundset.pissed; + } + else { + soundIndex = (int) (Math.random() * ackSoundToPlay.getSoundCount()); + } + if (ackSoundToPlay.play(this.viewer.worldScene.audioContext, unit.location[0], unit.location[1], + soundIndex)) { + this.selectedSoundCount++; + if ((this.selectedSoundCount - 3) >= pissedSoundCount) { + this.selectedSoundCount = 0; + } + playedNewSound = true; + } } - final MdxModel portraitModel = unit.portraitModel; - if (portraitModel != null) { - if (this.portraitInstance != null) { - this.portraitScene.removeInstance(this.portraitInstance); + if (selectionChanged) { + final MdxModel portraitModel = unit.portraitModel; + if (portraitModel != null) { + if (this.portraitInstance != null) { + this.portraitScene.removeInstance(this.portraitInstance); + } + this.portraitInstance = (MdxComplexInstance) portraitModel.addInstance(); + this.portraitInstance.setSequenceLoopMode(1); + this.portraitInstance.setScene(this.portraitScene); + this.portraitInstance.setVertexColor(unit.instance.vertexColor); + if (portraitModel.getCameras().size() > 0) { + this.portraitCameraManager.modelCamera = portraitModel.getCameras().get(0); + } + this.portraitInstance.setTeamColor(unit.playerIndex); } - this.portraitInstance = (MdxComplexInstance) portraitModel.addInstance(); - this.portraitInstance.setSequenceLoopMode(1); - this.portraitInstance.setScene(this.portraitScene); - this.portraitInstance.setVertexColor(unit.instance.vertexColor); - if (portraitModel.getCameras().size() > 0) { - this.portraitCameraManager.modelCamera = portraitModel.getCameras().get(0); - } - this.portraitInstance.setTeamColor(unit.playerIndex); + } + if (playedNewSound) { StandSequence.randomPortraitTalkSequence(this.portraitInstance); - } } else { diff --git a/core/src/com/etheller/warsmash/viewer5/AudioContext.java b/core/src/com/etheller/warsmash/viewer5/AudioContext.java index 77e5154..6ee37f0 100644 --- a/core/src/com/etheller/warsmash/viewer5/AudioContext.java +++ b/core/src/com/etheller/warsmash/viewer5/AudioContext.java @@ -3,6 +3,7 @@ package com.etheller.warsmash.viewer5; public class AudioContext { private boolean running = false; public Listener listener = new Listener(); + public long lastUnitResponseEndTimeMillis; public AudioDestination destination = new AudioDestination() { }; diff --git a/core/src/com/etheller/warsmash/viewer5/gl/Extensions.java b/core/src/com/etheller/warsmash/viewer5/gl/Extensions.java index 5096471..f9ed610 100644 --- a/core/src/com/etheller/warsmash/viewer5/gl/Extensions.java +++ b/core/src/com/etheller/warsmash/viewer5/gl/Extensions.java @@ -7,6 +7,8 @@ public class Extensions { public static WireframeExtension wireframeExtension; + public static SoundLengthExtension soundLengthExtension; + public static int GL_LINE = 0; public static int GL_FILL = 0; } diff --git a/core/src/com/etheller/warsmash/viewer5/gl/SoundLengthExtension.java b/core/src/com/etheller/warsmash/viewer5/gl/SoundLengthExtension.java new file mode 100644 index 0000000..56de8bd --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/gl/SoundLengthExtension.java @@ -0,0 +1,7 @@ +package com.etheller.warsmash.viewer5.gl; + +import com.badlogic.gdx.audio.Sound; + +public interface SoundLengthExtension { + float getDuration(Sound sound); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitSoundset.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitSoundset.java index b06238c..40d7253 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitSoundset.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitSoundset.java @@ -5,6 +5,7 @@ import java.util.List; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.audio.Sound; +import com.badlogic.gdx.utils.TimeUtils; import com.etheller.warsmash.datasources.DataSource; import com.etheller.warsmash.units.DataTable; import com.etheller.warsmash.units.Element; @@ -12,6 +13,7 @@ import com.etheller.warsmash.util.DataSourceFileHandle; import com.etheller.warsmash.viewer5.AudioBufferSource; import com.etheller.warsmash.viewer5.AudioContext; import com.etheller.warsmash.viewer5.AudioPanner; +import com.etheller.warsmash.viewer5.gl.Extensions; public class UnitSoundset { public final UnitAckSound what; @@ -84,9 +86,17 @@ public class UnitSoundset { this.distanceCutoff = distanceCutoff; } - public void play(final AudioContext audioContext, final float x, final float y) { + public boolean play(final AudioContext audioContext, final float x, final float y) { + return play(audioContext, x, y, (int) (Math.random() * this.sounds.size())); + } + + public boolean play(final AudioContext audioContext, final float x, final float y, final int index) { if (this.sounds.isEmpty()) { - return; + return false; + } + final long millisTime = TimeUtils.millis(); + if (millisTime < audioContext.lastUnitResponseEndTimeMillis) { + return false; } final AudioPanner panner = audioContext.createPanner(); @@ -99,12 +109,19 @@ public class UnitSoundset { panner.connect(audioContext.destination); // Source. - source.buffer = this.sounds.get((int) (Math.random() * this.sounds.size())); + source.buffer = this.sounds.get(index); source.connect(panner); // Make a sound. source.start(0); this.lastPlayedSound = source.buffer; + final float duration = Extensions.soundLengthExtension.getDuration(this.lastPlayedSound); + audioContext.lastUnitResponseEndTimeMillis = millisTime + (long) (1000 * duration); + return true; + } + + public int getSoundCount() { + return this.sounds.size(); } } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xShaders.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xShaders.java index 87bce37..a6424f0 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xShaders.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xShaders.java @@ -47,14 +47,15 @@ public class W3xShaders { " varying vec2 v_suv;\r\n" + // " varying vec3 v_normal;\r\n" + // " varying float a_positionHeight;\r\n" + // - " const vec3 lightDirection = normalize(vec3(-0.3, -0.3, 0.25));\r\n" + // + // " const vec3 lightDirection = normalize(vec3(-0.3, -0.3, 0.25));\r\n" + // " void main() {\r\n" + // " if (any(bvec4(lessThan(v_uv, vec2(0.0)), greaterThan(v_uv, vec2(1.0))))) {\r\n" + // " discard;\r\n" + // " }\r\n" + // " vec4 color = texture2D(u_texture, clamp(v_uv, 0.0, 1.0)).rgba * u_color;\r\n" + // " float shadow = texture2D(u_shadowMap, v_suv).r;\r\n" + // - " color.xyz *= clamp(dot(v_normal, lightDirection) + 0.45, 0.0, 1.0);\r\n" + // + // " color.xyz *= clamp(dot(v_normal, lightDirection) + 0.45, 0.0, 1.0);\r\n" + + // // " if (a_positionHeight <= 4.0) {;\r\n" + // " color.xyz *= 1.0 - shadow;\r\n" + // " };\r\n" + // diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java index 3149744..7a012f4 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java @@ -124,6 +124,24 @@ public class PathingGrid { return pathingType.isPathable(getPathing(x, y)); } + public boolean isPathable(final float x, final float y, final MovementType pathingType, final float collisionSize) { + if (collisionSize == 0f) { + return pathingType.isPathable(getPathing(x, y)); + } + for (int i = -1; i <= 1; i++) { + for (int j = -1; j <= 1; j++) { + if (!pathingType.isPathable(getPathing(x + (i * collisionSize), y + (j * collisionSize)))) { + return false; + } + } + } + return true; + } + + public boolean isCellPathable(final int x, final int y, final MovementType pathingType, final float collisionSize) { + return isPathable(getWorldX(x), getWorldY(y), pathingType, collisionSize); + } + public boolean isCellPathable(final int x, final int y, final MovementType pathingType) { return pathingType.isPathable(getCellPathing(x, y)); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java index ff91065..56d51ed 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java @@ -68,8 +68,8 @@ public class CSimulation { } public List findNaiveSlowPath(final int startX, final int startY, final int goalX, final int goalY, - final PathingGrid.MovementType movementType) { - return this.pathfindingProcessor.findNaiveSlowPath(startX, startY, goalX, goalY, movementType); + final PathingGrid.MovementType movementType, final float collisionSize) { + return this.pathfindingProcessor.findNaiveSlowPath(startX, startY, goalX, goalY, movementType, collisionSize); } public void update() { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java index 2571767..60063d1 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java @@ -10,10 +10,12 @@ import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.Moveme public class CUnitType { private final PathingGrid.MovementType movementType; private final float defaultFlyingHeight; + private final float collisionSize; - public CUnitType(final MovementType movementType, final float defaultFlyingHeight) { + public CUnitType(final MovementType movementType, final float defaultFlyingHeight, final float collisionSize) { this.movementType = movementType; this.defaultFlyingHeight = defaultFlyingHeight; + this.collisionSize = collisionSize; } public float getDefaultFlyingHeight() { @@ -23,4 +25,8 @@ public class CUnitType { public PathingGrid.MovementType getMovementType() { return this.movementType; } + + public float getCollisionSize() { + return this.collisionSize; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java index 1ae1586..ca65f8a 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java @@ -45,6 +45,7 @@ public class CUnitData { private static final War3ID DEFENSE = War3ID.fromString("udef"); private static final War3ID MOVE_HEIGHT = War3ID.fromString("umvh"); private static final War3ID MOVE_TYPE = War3ID.fromString("umvt"); + private static final War3ID COLLISION_SIZE = War3ID.fromString("ucol"); private final MutableObjectData unitData; private final Map unitIdToUnitType = new HashMap<>(); @@ -61,9 +62,10 @@ public class CUnitData { final int speed = unitType.getFieldAsInteger(MOVEMENT_SPEED_BASE, 0); final float moveHeight = unitType.getFieldAsFloat(MOVE_HEIGHT, 0); final String movetp = unitType.getFieldAsString(MOVE_TYPE, 0); + final float collisionSize = unitType.getFieldAsFloat(COLLISION_SIZE, 0); final PathingGrid.MovementType movementType = PathingGrid.getMovementType(movetp); final CUnit unit = new CUnit(handleId, playerIndex, x, y, life, typeId, facing, manaInitial, life, manaMaximum, - speed, new CUnitType(movementType, moveHeight)); + speed, new CUnitType(movementType, moveHeight, collisionSize)); if (speed > 0) { unit.add(simulation, CAbilityMove.INSTANCE); unit.add(simulation, CAbilityPatrol.INSTANCE); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CMoveOrder.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CMoveOrder.java index f10b484..f22eec9 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CMoveOrder.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CMoveOrder.java @@ -18,7 +18,6 @@ public class CMoveOrder implements COrder { private final float targetY; private boolean wasWithinPropWindow = false; private List path = null; - private boolean recalculated = false; public CMoveOrder(final CUnit unit, final float targetX, final float targetY) { this.unit = unit; @@ -33,12 +32,14 @@ public class CMoveOrder implements COrder { final MovementType movementType = this.unit.getUnitType().getMovementType(); final PathingGrid pathingGrid = simulation.getPathingGrid(); + final float collisionSize = this.unit.getUnitType().getCollisionSize(); final int startCellX = pathingGrid.getCellX(prevX); final int startCellY = pathingGrid.getCellY(prevY); final int goalCellX = pathingGrid.getCellX(this.targetX); final int goalCellY = pathingGrid.getCellY(this.targetY); if (this.path == null) { - this.path = simulation.findNaiveSlowPath(startCellX, startCellY, goalCellX, goalCellY, movementType); + this.path = simulation.findNaiveSlowPath(startCellX, startCellY, goalCellX, goalCellY, movementType, + collisionSize); // check for smoothing if (!this.path.isEmpty()) { int lastX = startCellX; @@ -53,7 +54,7 @@ public class CMoveOrder implements COrder { for (int i = 0; i < (this.path.size() - 1); i++) { final Point nextPossiblePathElement = this.path.get(i + 1); totalPathDistance += nextPossiblePathElement.distance(lastX, lastY); - if ((totalPathDistance < (1.25 + if ((totalPathDistance < (1.15 * nextPossiblePathElement.distance(smoothingGroupStartX, smoothingGroupStartY))) && pathingGrid.isCellPathable((smoothingGroupStartX + nextPossiblePathElement.x) / 2, (smoothingGroupStartY + nextPossiblePathElement.y) / 2, movementType)) { @@ -64,7 +65,7 @@ public class CMoveOrder implements COrder { else { if (smoothingStartIndex != -1) { for (int j = smoothingStartIndex; j < i; j++) { - final Point removed = this.path.remove(j); + this.path.remove(j); } i = smoothingStartIndex; } @@ -91,15 +92,7 @@ public class CMoveOrder implements COrder { currentTargetY = this.targetY; } else { - Point nextPathElement = this.path.get(0); - if ((this.path.size() > 2) && !this.recalculated) { - final Point secondPathElement = this.path.get(1); - if ((secondPathElement.distance(startCellX, startCellY) >= 5 /* 5 nodes */) - && (nextPathElement.distance(startCellX, startCellY) <= 2)) { - nextPathElement = secondPathElement; - this.path.remove(0); - } - } + final Point nextPathElement = this.path.get(0); currentTargetX = pathingGrid.getWorldX(nextPathElement.x); currentTargetY = pathingGrid.getWorldY(nextPathElement.y); } @@ -157,8 +150,7 @@ public class CMoveOrder implements COrder { done = (pathingGrid.getCellX(nextX) == pathingGrid.getCellX(currentTargetX)) && (pathingGrid.getCellY(nextY) == pathingGrid.getCellY(currentTargetY)); } - final short terrainPathing = pathingGrid.getPathing(nextX, nextY); - if (movementType.isPathable(terrainPathing)) { + if (pathingGrid.isPathable(nextX, nextY, movementType, collisionSize)) { this.unit.setX(nextX); this.unit.setY(nextY); if (done) { @@ -182,9 +174,8 @@ public class CMoveOrder implements COrder { } } else { - this.path = simulation.findNaiveSlowPath(startCellX, startCellY, goalCellX, goalCellY, - movementType); - this.recalculated = true; + this.path = simulation.findNaiveSlowPath(startCellX, startCellY, goalCellX, goalCellY, movementType, + collisionSize); if (this.path.isEmpty()) { return true; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/pathing/CPathfindingProcessor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/pathing/CPathfindingProcessor.java index 43c40d8..3491b6b 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/pathing/CPathfindingProcessor.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/pathing/CPathfindingProcessor.java @@ -31,13 +31,15 @@ public class CPathfindingProcessor { * the wikipedia article on A* to jog my memory from data structures class back * in college, and is meant only as a first draft to get things working. * + * @param collisionSize + * * * @param start * @param goal * @return */ public List findNaiveSlowPath(final int startX, final int startY, final int goalX, final int goalY, - final PathingGrid.MovementType movementType) { + final PathingGrid.MovementType movementType, final float collisionSize) { if ((startX == goalX) && (startY == goalY)) { return Collections.emptyList(); } @@ -79,9 +81,9 @@ public class CPathfindingProcessor { final int x = current.point.x + direction.xOffset; final int y = current.point.y + direction.yOffset; if ((x >= 0) && (x < this.pathingGrid.getWidth()) && (y >= 0) && (y < this.pathingGrid.getHeight()) - && movementType.isPathable(this.pathingGrid.getCellPathing(x, y)) - && movementType.isPathable(this.pathingGrid.getCellPathing(current.point.x, y)) - && movementType.isPathable(this.pathingGrid.getCellPathing(x, current.point.y))) { + && this.pathingGrid.isCellPathable(x, y, movementType, collisionSize) + && this.pathingGrid.isCellPathable(current.point.x, y, movementType, collisionSize) + && this.pathingGrid.isCellPathable(x, current.point.y, movementType, collisionSize)) { double turnCost; if ((current.cameFromDirection != null) && (direction != current.cameFromDirection)) { turnCost = 0.25; diff --git a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java index 3b92967..e6362ad 100644 --- a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java +++ b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java @@ -5,12 +5,15 @@ import org.lwjgl.opengl.GL31; import org.lwjgl.opengl.GL32; import org.lwjgl.opengl.GL33; +import com.badlogic.gdx.audio.Sound; import com.badlogic.gdx.backends.lwjgl.LwjglApplication; import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration; +import com.badlogic.gdx.backends.lwjgl.audio.OpenALSound; import com.etheller.warsmash.WarsmashGdxMapGame; import com.etheller.warsmash.viewer5.gl.ANGLEInstancedArrays; import com.etheller.warsmash.viewer5.gl.DynamicShadowExtension; import com.etheller.warsmash.viewer5.gl.Extensions; +import com.etheller.warsmash.viewer5.gl.SoundLengthExtension; import com.etheller.warsmash.viewer5.gl.WireframeExtension; public class DesktopLauncher { @@ -51,6 +54,12 @@ public class DesktopLauncher { GL11.glPolygonMode(face, mode); } }; + Extensions.soundLengthExtension = new SoundLengthExtension() { + @Override + public float getDuration(final Sound sound) { + return ((OpenALSound) sound).duration(); + } + }; Extensions.GL_LINE = GL11.GL_LINE; Extensions.GL_FILL = GL11.GL_FILL; final LwjglApplicationConfiguration config = new LwjglApplicationConfiguration(); From a4a072be4f1a135136ba751fcd2f164155ffbd3a Mon Sep 17 00:00:00 2001 From: Retera Date: Fri, 3 Jul 2020 00:28:23 -0400 Subject: [PATCH 040/116] Improve unit pathing with some work on adding units to the pathing grid, but it still has issues --- .../etheller/warsmash/WarsmashGdxMapGame.java | 14 +- .../viewer5/handlers/mdx/SetupGroups.java | 2 +- .../handlers/w3x/environment/PathingGrid.java | 135 +++++++- .../handlers/w3x/simulation/CSimulation.java | 7 +- .../handlers/w3x/simulation/CUnitType.java | 9 +- .../w3x/simulation/data/CUnitData.java | 3 +- .../w3x/simulation/orders/CMoveOrder.java | 315 +++++++++--------- .../pathing/CPathfindingProcessor.java | 103 ++++-- 8 files changed, 398 insertions(+), 190 deletions(-) diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java index fed854a..378c380 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java @@ -693,13 +693,13 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv @Override public boolean scrolled(final int amount) { -// this.cameraManager.verticalAngle -= amount / 10.f; -// if (this.cameraManager.verticalAngle > (Math.PI / 2)) { -// this.cameraManager.verticalAngle = (float) Math.PI / 2; -// } -// if (this.cameraManager.verticalAngle < (Math.PI / 5)) { -// this.cameraManager.verticalAngle = (float) (Math.PI / 5); -// } + this.cameraManager.verticalAngle -= amount / 10.f; + if (this.cameraManager.verticalAngle > (Math.PI / 2)) { + this.cameraManager.verticalAngle = (float) Math.PI / 2; + } + if (this.cameraManager.verticalAngle < (float) Math.toRadians(34)) { + this.cameraManager.verticalAngle = (float) Math.toRadians(34); + } return true; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SetupGroups.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SetupGroups.java index d502fe4..58f0bca 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SetupGroups.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SetupGroups.java @@ -109,7 +109,7 @@ public class SetupGroups { for (final Object object : objects) { // TODO reforged if ((object instanceof Batch /* || object instanceof ReforgedBatch */) || (object instanceof EmitterObject)) { - if ((currentGroup == null) || !matchingGroup(currentGroup, objects)) { + if ((currentGroup == null) || !matchingGroup(currentGroup, object)) { currentGroup = createMatchingGroup(model, object); translucentGroups.add(currentGroup); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java index 7a012f4..289bcfd 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java @@ -1,10 +1,13 @@ package com.etheller.warsmash.viewer5.handlers.w3x.environment; import java.awt.image.BufferedImage; +import java.util.Arrays; import java.util.HashMap; import java.util.Map; import com.etheller.warsmash.parsers.w3x.wpm.War3MapWpm; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitType; public class PathingGrid { private static final Map movetpToMovementType = new HashMap<>(); @@ -18,6 +21,10 @@ public class PathingGrid { private final short[] pathingGrid; private final short[] dynamicPathingOverlay; // for buildings and trees + private final short[] unitPathingOverlay; // for constantly moving units + private final short[] ignorePathingOverlay; // to prevent unit collide with self + private final int[] unitsAtLocationCount; // number of units at location, probably really inefficient, should + // probably change later private final int[] pathingGridSizes; private final float[] centerOffset; @@ -26,6 +33,53 @@ public class PathingGrid { this.pathingGrid = terrainPathing.getPathing(); this.pathingGridSizes = terrainPathing.getSize(); this.dynamicPathingOverlay = new short[this.pathingGrid.length]; + this.unitPathingOverlay = new short[this.pathingGrid.length]; + this.ignorePathingOverlay = new short[this.pathingGrid.length]; + Arrays.fill(this.ignorePathingOverlay, (short) 0xFFFF); + this.unitsAtLocationCount = new int[this.pathingGrid.length]; + } + + public void resetUnitCollisionPathing(final Iterable units) { + Arrays.fill(this.unitPathingOverlay, (short) 0); + Arrays.fill(this.unitsAtLocationCount, 0); + for (final CUnit unit : units) { + final CUnitType unitType = unit.getUnitType(); + final MovementType movementType = unitType.getMovementType(); + if (!unitType.isBuilding() && (movementType != null)) { + final float collisionSize = unitType.getCollisionSize(); + final float maxX = unit.getX() + collisionSize; + final float maxY = unit.getY() + collisionSize; + final short obstructionByte; + switch (movementType) { + case FLOAT: + obstructionByte = (short) (PathingGrid.PathingFlags.UNSWIMABLE); + break; + case AMPHIBIOUS: + obstructionByte = (short) (PathingGrid.PathingFlags.UNWALKABLE + | PathingGrid.PathingFlags.UNSWIMABLE); + break; + case FLY: + obstructionByte = (short) (PathingGrid.PathingFlags.UNFLYABLE); + break; + default: + case FOOT: + case DISABLED: + case HORSE: + case HOVER: + obstructionByte = (short) (PathingGrid.PathingFlags.UNWALKABLE); + break; + } + for (float minX = unit.getX() - collisionSize; minX < maxX; minX += 32f) { + for (float minY = unit.getY() - collisionSize; minY < maxY; minY += 32f) { + final int yy = getCellY(minY); + final int xx = getCellX(minX); + final int index = (yy * this.pathingGridSizes[0]) + xx; + this.unitPathingOverlay[index] |= obstructionByte; + this.unitsAtLocationCount[index]++; + } + } + } + } } // this blit function is basically copied from HiveWE, maybe remember to mention @@ -87,6 +141,12 @@ public class PathingGrid { return this.pathingGridSizes[1]; } + public boolean contains(final float x, final float y) { + final int cellX = getCellX(x); + final int cellY = getCellY(y); + return (cellX >= 0) && (cellY >= 0) && (cellX < this.pathingGrid[0]) && (cellY < this.pathingGrid[1]); + } + public short getPathing(final float x, final float y) { return getCellPathing(getCellX(x), getCellY(y)); } @@ -111,9 +171,30 @@ public class PathingGrid { return (cellY * 32f) + this.centerOffset[1] + 16f; } + public float getWorldXFromCorner(final int cornerX) { + return (cornerX * 32f) + this.centerOffset[0]; + } + + public float getWorldYFromCorner(final int cornerY) { + return (cornerY * 32f) + this.centerOffset[1]; + } + + public int getCornerX(final float x) { + final float userCellSpaceX = ((x + 16f) - this.centerOffset[0]) / 32.0f; + final int cellX = (int) userCellSpaceX; + return cellX; + } + + public int getCornerY(final float y) { + final float userCellSpaceY = ((y + 16f) - this.centerOffset[1]) / 32.0f; + final int cellY = (int) userCellSpaceY; + return cellY; + } + public short getCellPathing(final int cellX, final int cellY) { - return (short) (this.pathingGrid[(cellY * this.pathingGridSizes[0]) + cellX] - | this.dynamicPathingOverlay[(cellY * this.pathingGridSizes[0]) + cellX]); + final int index = (cellY * this.pathingGridSizes[0]) + cellX; + return (short) ((this.pathingGrid[index] | this.dynamicPathingOverlay[index] | this.unitPathingOverlay[index]) + & this.ignorePathingOverlay[index]); } public boolean isPathable(final float x, final float y, final PathingType pathingType) { @@ -124,20 +205,64 @@ public class PathingGrid { return pathingType.isPathable(getPathing(x, y)); } - public boolean isPathable(final float x, final float y, final MovementType pathingType, final float collisionSize) { + public boolean isPathable(final float unitX, final float unitY, final MovementType pathingType, + final float collisionSize) { if (collisionSize == 0f) { - return pathingType.isPathable(getPathing(x, y)); + return pathingType.isPathable(getPathing(unitX, unitY)); } for (int i = -1; i <= 1; i++) { for (int j = -1; j <= 1; j++) { - if (!pathingType.isPathable(getPathing(x + (i * collisionSize), y + (j * collisionSize)))) { + if (!pathingType.isPathable(getPathing(unitX + (i * collisionSize), unitY + (j * collisionSize)))) { return false; } } } +// final float maxX = unitX + collisionSize; +// final float maxY = unitY + collisionSize; +// for (float minX = unitX - collisionSize; minX < maxX; minX += 32f) { +// for (float minY = unitY - collisionSize; minY < maxY; minY += 32f) { +// if (!pathingType.isPathable(getPathing(minX, minY))) { +// return false; +// } +// } +// } return true; } + public boolean isUnitCell(final float queryX, final float queryY, final float unitX, final float unitY, + final MovementType movementType, final float collisionSize) { + final float maxX = unitX + collisionSize; + final float maxY = unitY + collisionSize; + final int cellX = getCellX(queryX); + final int cellY = getCellY(queryY); + for (float minX = unitX - collisionSize; minX < maxX; minX += 32f) { + for (float minY = unitY - collisionSize; minY < maxY; minY += 32f) { + final int yy = getCellY(minY); + final int xx = getCellX(minX); + if ((yy == cellY) && (xx == cellX)) { + return true; + } + } + } + return false; + } + + public void setUnitIgnore(final float unitX, final float unitY, final float collisionSize, final boolean ignore) { + final float maxX = unitX + collisionSize; + final float maxY = unitY + collisionSize; + final short ignoreFlag = (short) (ignore ? 0 : 0xFFFF); + for (float minX = unitX - collisionSize; minX < maxX; minX += 32f) { + for (float minY = unitY - collisionSize; minY < maxY; minY += 32f) { + final int cellY = getCellY(minY); + final int cellX = getCellX(minX); + final int index = (cellY * this.pathingGridSizes[0]) + cellX; + if (this.unitsAtLocationCount[index] == 1) { + this.ignorePathingOverlay[index] = ignoreFlag; + } + } + } + } + public boolean isCellPathable(final int x, final int y, final MovementType pathingType, final float collisionSize) { return isPathable(getWorldX(x), getWorldY(y), pathingType, collisionSize); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java index 56d51ed..e423fa8 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java @@ -1,6 +1,6 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation; -import java.awt.Point; +import java.awt.geom.Point2D; import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -67,12 +67,13 @@ public class CSimulation { return this.pathingGrid; } - public List findNaiveSlowPath(final int startX, final int startY, final int goalX, final int goalY, - final PathingGrid.MovementType movementType, final float collisionSize) { + public List findNaiveSlowPath(final float startX, final float startY, final float goalX, + final float goalY, final PathingGrid.MovementType movementType, final float collisionSize) { return this.pathfindingProcessor.findNaiveSlowPath(startX, startY, goalX, goalY, movementType, collisionSize); } public void update() { + this.pathingGrid.resetUnitCollisionPathing(this.units); for (final CUnit unit : this.units) { unit.update(this); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java index 60063d1..b175af5 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java @@ -8,11 +8,14 @@ import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.Moveme * probably cannot change per unit instance. */ public class CUnitType { + private final boolean building; private final PathingGrid.MovementType movementType; private final float defaultFlyingHeight; private final float collisionSize; - public CUnitType(final MovementType movementType, final float defaultFlyingHeight, final float collisionSize) { + public CUnitType(final boolean isBldg, final MovementType movementType, final float defaultFlyingHeight, + final float collisionSize) { + this.building = isBldg; this.movementType = movementType; this.defaultFlyingHeight = defaultFlyingHeight; this.collisionSize = collisionSize; @@ -29,4 +32,8 @@ public class CUnitType { public float getCollisionSize() { return this.collisionSize; } + + public boolean isBuilding() { + return this.building; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java index ca65f8a..435e083 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java @@ -63,9 +63,10 @@ public class CUnitData { final float moveHeight = unitType.getFieldAsFloat(MOVE_HEIGHT, 0); final String movetp = unitType.getFieldAsString(MOVE_TYPE, 0); final float collisionSize = unitType.getFieldAsFloat(COLLISION_SIZE, 0); + final boolean isBldg = unitType.getFieldAsBoolean(IS_BLDG, 0); final PathingGrid.MovementType movementType = PathingGrid.getMovementType(movetp); final CUnit unit = new CUnit(handleId, playerIndex, x, y, life, typeId, facing, manaInitial, life, manaMaximum, - speed, new CUnitType(movementType, moveHeight, collisionSize)); + speed, new CUnitType(isBldg, movementType, moveHeight, collisionSize)); if (speed > 0) { unit.add(simulation, CAbilityMove.INSTANCE); unit.add(simulation, CAbilityPatrol.INSTANCE); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CMoveOrder.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CMoveOrder.java index f22eec9..afdeade 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CMoveOrder.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CMoveOrder.java @@ -1,6 +1,6 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders; -import java.awt.Point; +import java.awt.geom.Point2D; import java.util.List; import com.etheller.warsmash.util.WarsmashConstants; @@ -11,18 +11,23 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.COrder; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityMove; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.pathing.CPathfindingProcessor; public class CMoveOrder implements COrder { private final CUnit unit; private final float targetX; private final float targetY; private boolean wasWithinPropWindow = false; - private List path = null; + private List path = null; + private final CPathfindingProcessor.GridMapping gridMapping; public CMoveOrder(final CUnit unit, final float targetX, final float targetY) { this.unit = unit; this.targetX = targetX; this.targetY = targetY; + this.gridMapping = CPathfindingProcessor.isCollisionSizeBetterSuitedForCorners( + unit.getUnitType().getCollisionSize()) ? CPathfindingProcessor.GridMapping.CORNERS + : CPathfindingProcessor.GridMapping.CELLS; } @Override @@ -33,164 +38,174 @@ public class CMoveOrder implements COrder { final MovementType movementType = this.unit.getUnitType().getMovementType(); final PathingGrid pathingGrid = simulation.getPathingGrid(); final float collisionSize = this.unit.getUnitType().getCollisionSize(); - final int startCellX = pathingGrid.getCellX(prevX); - final int startCellY = pathingGrid.getCellY(prevY); - final int goalCellX = pathingGrid.getCellX(this.targetX); - final int goalCellY = pathingGrid.getCellY(this.targetY); - if (this.path == null) { - this.path = simulation.findNaiveSlowPath(startCellX, startCellY, goalCellX, goalCellY, movementType, - collisionSize); - // check for smoothing - if (!this.path.isEmpty()) { - int lastX = startCellX; - int lastY = startCellY; - int smoothingGroupStartX = startCellX; - int smoothingGroupStartY = startCellY; - final Point firstPathElement = this.path.get(0); - double totalPathDistance = firstPathElement.distance(lastX, lastY); - lastX = firstPathElement.x; - lastY = firstPathElement.y; - int smoothingStartIndex = -1; - for (int i = 0; i < (this.path.size() - 1); i++) { - final Point nextPossiblePathElement = this.path.get(i + 1); - totalPathDistance += nextPossiblePathElement.distance(lastX, lastY); - if ((totalPathDistance < (1.15 - * nextPossiblePathElement.distance(smoothingGroupStartX, smoothingGroupStartY))) - && pathingGrid.isCellPathable((smoothingGroupStartX + nextPossiblePathElement.x) / 2, - (smoothingGroupStartY + nextPossiblePathElement.y) / 2, movementType)) { - if (smoothingStartIndex == -1) { - smoothingStartIndex = i; + pathingGrid.setUnitIgnore(prevX, prevY, collisionSize, true); + try { + final float startFloatingX = prevX; + final float startFloatingY = prevY; + final float goalFloatingX = this.targetX; + final float goalFloatingY = this.targetY; + if (this.path == null) { + this.path = simulation.findNaiveSlowPath(startFloatingX, startFloatingY, goalFloatingX, goalFloatingY, + movementType, collisionSize); + System.out.println("init path " + this.path); + // check for smoothing + if (!this.path.isEmpty()) { + float lastX = startFloatingX; + float lastY = startFloatingY; + float smoothingGroupStartX = startFloatingX; + float smoothingGroupStartY = startFloatingY; + final Point2D.Float firstPathElement = this.path.get(0); + double totalPathDistance = firstPathElement.distance(lastX, lastY); + lastX = firstPathElement.x; + lastY = firstPathElement.y; + int smoothingStartIndex = -1; + for (int i = 0; i < (this.path.size() - 1); i++) { + final Point2D.Float nextPossiblePathElement = this.path.get(i + 1); + totalPathDistance += nextPossiblePathElement.distance(lastX, lastY); + if ((totalPathDistance < (1.15 + * nextPossiblePathElement.distance(smoothingGroupStartX, smoothingGroupStartY))) + && pathingGrid.isPathable((smoothingGroupStartX + nextPossiblePathElement.x) / 2, + (smoothingGroupStartY + nextPossiblePathElement.y) / 2, movementType)) { + if (smoothingStartIndex == -1) { + smoothingStartIndex = i; + } + } + else { + if (smoothingStartIndex != -1) { + for (int j = smoothingStartIndex; j < i; j++) { + this.path.remove(j); + } + i = smoothingStartIndex; + } + smoothingStartIndex = -1; + final Point2D.Float smoothGroupNext = this.path.get(i); + smoothingGroupStartX = smoothGroupNext.x; + smoothingGroupStartY = smoothGroupNext.y; + totalPathDistance = nextPossiblePathElement.distance(smoothGroupNext); + } + lastX = nextPossiblePathElement.x; + lastY = nextPossiblePathElement.y; + } + if (smoothingStartIndex != -1) { + for (int j = smoothingStartIndex; j < (this.path.size() - 1); j++) { + final Point2D.Float removed = this.path.remove(j); + } + } + } + } + float currentTargetX; + float currentTargetY; + if (this.path.size() < 2) { + currentTargetX = this.targetX; + currentTargetY = this.targetY; + } + else { + final Point2D.Float nextPathElement = this.path.get(0); + currentTargetX = nextPathElement.x; + currentTargetY = nextPathElement.y; + } + + float deltaX = currentTargetX - prevX; + float deltaY = currentTargetY - prevY; + final double goalAngleRad = Math.atan2(deltaY, deltaX); + float goalAngle = (float) Math.toDegrees(goalAngleRad); + if (goalAngle < 0) { + goalAngle += 360; + } + float facing = this.unit.getFacing(); + float delta = goalAngle - facing; + final float propulsionWindow = simulation.getUnitData().getPropulsionWindow(this.unit.getTypeId()); + final float turnRate = simulation.getUnitData().getTurnRate(this.unit.getTypeId()); + final int speed = this.unit.getSpeed(); + + if (delta < -180) { + delta = 360 + delta; + } + if (delta > 180) { + delta = -360 + delta; + } + final float absDelta = Math.abs(delta); + + if ((absDelta <= 1.0) && (absDelta != 0)) { + this.unit.setFacing(goalAngle); + } + else { + float angleToAdd = Math.signum(delta) * (float) Math.toDegrees(turnRate); + if (absDelta < Math.abs(angleToAdd)) { + angleToAdd = delta; + } + facing += angleToAdd; + this.unit.setFacing(facing); + } + if (absDelta < propulsionWindow) { + final float speedTick = speed * WarsmashConstants.SIMULATION_STEP_TIME; + double continueDistance = speedTick; + do { + boolean done; + float nextX, nextY; + final double travelDistance = Math.sqrt((deltaX * deltaX) + (deltaY * deltaY)); + if (travelDistance <= continueDistance) { + nextX = currentTargetX; + nextY = currentTargetY; + continueDistance = continueDistance - travelDistance; + done = true; + } + else { + final double radianFacing = Math.toRadians(facing); + nextX = (prevX + (float) (Math.cos(radianFacing) * continueDistance)); + nextY = (prevY + (float) (Math.sin(radianFacing) * continueDistance)); + continueDistance = 0; + done = (this.gridMapping.getX(pathingGrid, nextX) == this.gridMapping.getX(pathingGrid, + currentTargetX)) + && (this.gridMapping.getY(pathingGrid, nextY) == this.gridMapping.getY(pathingGrid, + currentTargetY)); + } + if (pathingGrid.isPathable(nextX, nextY, movementType, ((int) collisionSize / 16) * 16)) { + this.unit.setX(nextX); + this.unit.setY(nextY); + if (done) { + if (this.path.isEmpty()) { + return true; + } + else { + this.path.remove(0); + if (this.path.size() < 2) { + currentTargetX = this.targetX; + currentTargetY = this.targetY; + } + else { + final Point2D.Float firstPathElement = this.path.get(0); + currentTargetX = firstPathElement.x; + currentTargetY = firstPathElement.y; + } + deltaY = currentTargetY - prevY; + deltaX = currentTargetX - prevX; + } } } else { - if (smoothingStartIndex != -1) { - for (int j = smoothingStartIndex; j < i; j++) { - this.path.remove(j); - } - i = smoothingStartIndex; - } - smoothingStartIndex = -1; - final Point smoothGroupNext = this.path.get(i); - smoothingGroupStartX = smoothGroupNext.x; - smoothingGroupStartY = smoothGroupNext.y; - totalPathDistance = nextPossiblePathElement.distance(smoothGroupNext); - } - lastX = nextPossiblePathElement.x; - lastY = nextPossiblePathElement.y; - } - if (smoothingStartIndex != -1) { - for (int j = smoothingStartIndex; j < (this.path.size() - 1); j++) { - final Point removed = this.path.remove(j); - } - } - } - } - float currentTargetX; - float currentTargetY; - if (this.path.size() < 2) { - currentTargetX = this.targetX; - currentTargetY = this.targetY; - } - else { - final Point nextPathElement = this.path.get(0); - currentTargetX = pathingGrid.getWorldX(nextPathElement.x); - currentTargetY = pathingGrid.getWorldY(nextPathElement.y); - } - - float deltaX = currentTargetX - prevX; - float deltaY = currentTargetY - prevY; - final double goalAngleRad = Math.atan2(deltaY, deltaX); - float goalAngle = (float) Math.toDegrees(goalAngleRad); - if (goalAngle < 0) { - goalAngle += 360; - } - float facing = this.unit.getFacing(); - float delta = goalAngle - facing; - final float propulsionWindow = simulation.getUnitData().getPropulsionWindow(this.unit.getTypeId()); - final float turnRate = simulation.getUnitData().getTurnRate(this.unit.getTypeId()); - final int speed = this.unit.getSpeed(); - - if (delta < -180) { - delta = 360 + delta; - } - if (delta > 180) { - delta = -360 + delta; - } - final float absDelta = Math.abs(delta); - - if ((absDelta <= 1.0) && (absDelta != 0)) { - this.unit.setFacing(goalAngle); - } - else { - float angleToAdd = Math.signum(delta) * (float) Math.toDegrees(turnRate); - if (absDelta < Math.abs(angleToAdd)) { - angleToAdd = delta; - } - facing += angleToAdd; - this.unit.setFacing(facing); - } - if (absDelta < propulsionWindow) { - final float speedTick = speed * WarsmashConstants.SIMULATION_STEP_TIME; - double continueDistance = speedTick; - do { - boolean done; - float nextX, nextY; - final double travelDistance = Math.sqrt((deltaX * deltaX) + (deltaY * deltaY)); - if (travelDistance <= continueDistance) { - nextX = currentTargetX; - nextY = currentTargetY; - continueDistance = continueDistance - travelDistance; - done = true; - } - else { - final double radianFacing = Math.toRadians(facing); - nextX = (prevX + (float) (Math.cos(radianFacing) * continueDistance)); - nextY = (prevY + (float) (Math.sin(radianFacing) * continueDistance)); - continueDistance = 0; - done = (pathingGrid.getCellX(nextX) == pathingGrid.getCellX(currentTargetX)) - && (pathingGrid.getCellY(nextY) == pathingGrid.getCellY(currentTargetY)); - } - if (pathingGrid.isPathable(nextX, nextY, movementType, collisionSize)) { - this.unit.setX(nextX); - this.unit.setY(nextY); - if (done) { + this.path = simulation.findNaiveSlowPath(startFloatingX, startFloatingY, goalFloatingX, + goalFloatingY, movementType, collisionSize); + System.out.println("new path " + this.path); if (this.path.isEmpty()) { return true; } - else { - this.path.remove(0); - if (this.path.size() < 2) { - currentTargetX = this.targetX; - currentTargetY = this.targetY; - } - else { - final Point firstPathElement = this.path.get(0); - currentTargetX = pathingGrid.getWorldX(firstPathElement.x); - currentTargetY = pathingGrid.getWorldY(firstPathElement.y); - } - deltaY = currentTargetY - prevY; - deltaX = currentTargetX - prevX; - } } + this.wasWithinPropWindow = true; } - else { - this.path = simulation.findNaiveSlowPath(startCellX, startCellY, goalCellX, goalCellY, movementType, - collisionSize); - if (this.path.isEmpty()) { - return true; - } - } - this.wasWithinPropWindow = true; + while (continueDistance > 0); + } + else { + // If this happens, the unit is facing the wrong way, and has to turn before + // moving. + this.wasWithinPropWindow = false; } - while (continueDistance > 0); - } - else { - // If this happens, the unit is facing the wrong way, and has to turn before - // moving. - this.wasWithinPropWindow = false; - } - return false; + return false; + } + finally { + pathingGrid.setUnitIgnore(prevX, prevY, collisionSize, false); + } } @Override diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/pathing/CPathfindingProcessor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/pathing/CPathfindingProcessor.java index 3491b6b..6e5276e 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/pathing/CPathfindingProcessor.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/pathing/CPathfindingProcessor.java @@ -1,6 +1,6 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.pathing; -import java.awt.Point; +import java.awt.geom.Point2D; import java.util.Collections; import java.util.Comparator; import java.util.LinkedList; @@ -12,14 +12,22 @@ import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid; public class CPathfindingProcessor { private final PathingGrid pathingGrid; private final Node[][] nodes; + private final Node[][] cornerNodes; private Node goal; public CPathfindingProcessor(final PathingGrid pathingGrid) { this.pathingGrid = pathingGrid; this.nodes = new Node[pathingGrid.getHeight()][pathingGrid.getWidth()]; + this.cornerNodes = new Node[pathingGrid.getHeight() + 1][pathingGrid.getWidth() + 1]; for (int i = 0; i < this.nodes.length; i++) { for (int j = 0; j < this.nodes[i].length; j++) { - this.nodes[i][j] = new Node(new Point(j, i)); + this.nodes[i][j] = new Node(new Point2D.Float(pathingGrid.getWorldX(j), pathingGrid.getWorldY(i))); + } + } + for (int i = 0; i < this.cornerNodes.length; i++) { + for (int j = 0; j < this.cornerNodes[i].length; j++) { + this.cornerNodes[i][j] = new Node( + new Point2D.Float(pathingGrid.getWorldXFromCorner(j), pathingGrid.getWorldYFromCorner(i))); } } } @@ -38,18 +46,33 @@ public class CPathfindingProcessor { * @param goal * @return */ - public List findNaiveSlowPath(final int startX, final int startY, final int goalX, final int goalY, - final PathingGrid.MovementType movementType, final float collisionSize) { + public List findNaiveSlowPath(final float startX, final float startY, final float goalX, + final float goalY, final PathingGrid.MovementType movementType, final float collisionSize) { + System.out.println("beginning findNaiveSlowPath for " + startX + "," + startY + "," + goalX + "," + goalY); if ((startX == goalX) && (startY == goalY)) { return Collections.emptyList(); } - this.goal = this.nodes[goalY][goalX]; - final Node start = this.nodes[startY][startX]; - for (int i = 0; i < this.nodes.length; i++) { - for (int j = 0; j < this.nodes[i].length; j++) { - this.nodes[i][j].g = Float.POSITIVE_INFINITY; - this.nodes[i][j].f = Float.POSITIVE_INFINITY; - this.nodes[i][j].cameFrom = null; + Node[][] searchGraph; + GridMapping gridMapping; + if (isCollisionSizeBetterSuitedForCorners(collisionSize)) { + searchGraph = this.cornerNodes; + gridMapping = GridMapping.CORNERS; + System.out.println("using corners"); + } + else { + searchGraph = this.nodes; + gridMapping = GridMapping.CELLS; + System.out.println("using cells"); + } + this.goal = searchGraph[gridMapping.getY(this.pathingGrid, goalY)][gridMapping.getX(this.pathingGrid, goalX)]; + final Node start = searchGraph[gridMapping.getY(this.pathingGrid, startY)][gridMapping.getX(this.pathingGrid, + startX)]; + for (int i = 0; i < searchGraph.length; i++) { + for (int j = 0; j < searchGraph[i].length; j++) { + final Node node = searchGraph[i][j]; + node.g = Float.POSITIVE_INFINITY; + node.f = Float.POSITIVE_INFINITY; + node.cameFrom = null; } } start.g = 0; @@ -65,7 +88,7 @@ public class CPathfindingProcessor { while (!openSet.isEmpty()) { Node current = openSet.poll(); if (current == this.goal) { - final LinkedList totalPath = new LinkedList<>(); + final LinkedList totalPath = new LinkedList<>(); Direction lastCameFromDirection = null; while (current.cameFrom != null) { if ((lastCameFromDirection == null) || (current.cameFromDirection != lastCameFromDirection)) { @@ -78,12 +101,11 @@ public class CPathfindingProcessor { } for (final Direction direction : Direction.VALUES) { - final int x = current.point.x + direction.xOffset; - final int y = current.point.y + direction.yOffset; - if ((x >= 0) && (x < this.pathingGrid.getWidth()) && (y >= 0) && (y < this.pathingGrid.getHeight()) - && this.pathingGrid.isCellPathable(x, y, movementType, collisionSize) - && this.pathingGrid.isCellPathable(current.point.x, y, movementType, collisionSize) - && this.pathingGrid.isCellPathable(x, current.point.y, movementType, collisionSize)) { + final float x = current.point.x + (direction.xOffset * 32); + final float y = current.point.y + (direction.yOffset * 32); + if (this.pathingGrid.contains(x, y) && this.pathingGrid.isPathable(x, y, movementType, collisionSize) + && this.pathingGrid.isPathable(current.point.x, y, movementType, collisionSize) + && this.pathingGrid.isPathable(x, current.point.y, movementType, collisionSize)) { double turnCost; if ((current.cameFromDirection != null) && (direction != current.cameFromDirection)) { turnCost = 0.25; @@ -91,8 +113,9 @@ public class CPathfindingProcessor { else { turnCost = 0; } - final double tentativeScore = current.g + direction.length + turnCost; - final Node neighbor = this.nodes[y][x]; + final double tentativeScore = current.g + ((direction.length + turnCost) * 32); + final Node neighbor = searchGraph[gridMapping.getY(this.pathingGrid, y)][gridMapping + .getX(this.pathingGrid, x)]; if (tentativeScore < neighbor.g) { neighbor.cameFrom = current; neighbor.cameFromDirection = direction; @@ -108,6 +131,10 @@ public class CPathfindingProcessor { return Collections.emptyList(); } + public static boolean isCollisionSizeBetterSuitedForCorners(final float collisionSize) { + return (((2 * (int) collisionSize) / 32) % 2) == 1; + } + public double f(final Node n) { return n.g + h(n); } @@ -122,12 +149,12 @@ public class CPathfindingProcessor { public static final class Node { public Direction cameFromDirection; - private final Point point; + private final Point2D.Float point; private double f; private double g; private Node cameFrom; - private Node(final Point point) { + private Node(final Point2D.Float point) { this.point = point; } } @@ -155,4 +182,36 @@ public class CPathfindingProcessor { this.length = sqrt; } } + + public static interface GridMapping { + int getX(PathingGrid grid, float worldX); + + int getY(PathingGrid grid, float worldY); + + public static final GridMapping CELLS = new GridMapping() { + @Override + public int getX(final PathingGrid grid, final float worldX) { + return grid.getCellX(worldX); + } + + @Override + public int getY(final PathingGrid grid, final float worldY) { + return grid.getCellY(worldY); + } + + }; + + public static final GridMapping CORNERS = new GridMapping() { + @Override + public int getX(final PathingGrid grid, final float worldX) { + return grid.getCornerX(worldX); + } + + @Override + public int getY(final PathingGrid grid, final float worldY) { + return grid.getCornerY(worldY); + } + + }; + } } From b2900f50fc6de40d4127670e8449b23441b01c81 Mon Sep 17 00:00:00 2001 From: Retera Date: Sun, 5 Jul 2020 10:39:43 -0400 Subject: [PATCH 041/116] More work on collisions to fix issues --- .../etheller/warsmash/WarsmashGdxMapGame.java | 48 ++- .../etheller/warsmash/parsers/jass/Jass2.java | 211 ++++++++++- .../parsers/jass/triggers/BoolExprAnd.java | 21 ++ .../jass/triggers/BoolExprCondition.java | 27 ++ .../parsers/jass/triggers/BoolExprFilter.java | 27 ++ .../parsers/jass/triggers/BoolExprNot.java | 19 + .../parsers/jass/triggers/BoolExprOr.java | 21 ++ .../parsers/jass/triggers/TriggerAction.java | 28 ++ .../jass/triggers/TriggerCondition.java | 28 ++ .../com/etheller/warsmash/util/Quadtree.java | 205 ++++++++++ .../viewer5/handlers/w3x/War3MapViewer.java | 161 +++----- .../handlers/w3x/environment/PathingGrid.java | 73 +--- .../handlers/w3x/rendersim/RenderUnit.java | 8 +- .../handlers/w3x/simulation/CSimulation.java | 23 +- .../handlers/w3x/simulation/CUnit.java | 37 ++ .../handlers/w3x/simulation/CWidget.java | 4 +- .../w3x/simulation/CWorldCollision.java | 112 ++++++ .../w3x/simulation/orders/CMoveOrder.java | 351 ++++++++++-------- .../pathing/CPathfindingProcessor.java | 130 ++++++- .../warsmash/desktop/DesktopLauncher.java | 9 +- .../expression/ArrayRefJassExpression.java | 13 +- .../FunctionCallJassExpression.java | 14 +- .../FunctionReferenceJassExpression.java | 8 +- .../ast/expression/JassExpression.java | 3 +- .../ast/expression/LiteralJassExpression.java | 6 +- .../ast/expression/NotJassExpression.java | 7 +- .../expression/ReferenceJassExpression.java | 8 +- .../ast/function/AbstractJassFunction.java | 8 +- .../ast/function/JassFunction.java | 3 +- .../ast/function/NativeJassFunction.java | 7 +- .../ast/function/UserJassFunction.java | 11 +- .../interpreter/ast/scope/GlobalScope.java | 14 +- .../ast/scope/TriggerExecutionScope.java | 16 + .../ast/scope/trigger/Trigger.java | 74 ++++ .../trigger/TriggerBooleanExpression.java | 8 + .../JassArrayedAssignmentStatement.java | 8 +- .../ast/statement/JassCallStatement.java | 8 +- .../ast/statement/JassIfElseIfStatement.java | 11 +- .../ast/statement/JassIfElseStatement.java | 11 +- .../ast/statement/JassIfStatement.java | 9 +- .../ast/statement/JassReturnStatement.java | 6 +- .../ast/statement/JassSetStatement.java | 8 +- .../ast/statement/JassStatement.java | 3 +- .../ast/value/BooleanJassValue.java | 9 + .../ast/visitors/JassGlobalsVisitor.java | 5 +- .../ast/visitors/JassProgramVisitor.java | 4 +- resources/Scripts/common.jui | 24 ++ resources/Scripts/melee.jui | 2 +- 48 files changed, 1371 insertions(+), 480 deletions(-) create mode 100644 core/src/com/etheller/warsmash/parsers/jass/triggers/BoolExprAnd.java create mode 100644 core/src/com/etheller/warsmash/parsers/jass/triggers/BoolExprCondition.java create mode 100644 core/src/com/etheller/warsmash/parsers/jass/triggers/BoolExprFilter.java create mode 100644 core/src/com/etheller/warsmash/parsers/jass/triggers/BoolExprNot.java create mode 100644 core/src/com/etheller/warsmash/parsers/jass/triggers/BoolExprOr.java create mode 100644 core/src/com/etheller/warsmash/parsers/jass/triggers/TriggerAction.java create mode 100644 core/src/com/etheller/warsmash/parsers/jass/triggers/TriggerCondition.java create mode 100644 core/src/com/etheller/warsmash/util/Quadtree.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWorldCollision.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/scope/TriggerExecutionScope.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/scope/trigger/Trigger.java create mode 100644 jassparser/src/com/etheller/interpreter/ast/scope/trigger/TriggerBooleanExpression.java diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java index 378c380..65cf307 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java @@ -66,7 +66,6 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv private final Rectangle tempRect = new Rectangle(); private CameraManager portraitCameraManager; - private MdxComplexInstance portraitInstance; private final float[] cameraPositionTemp = new float[3]; private final float[] cameraTargetTemp = new float[3]; @@ -136,13 +135,14 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.viewer.worldScene.enableAudio(); this.viewer.enableAudio(); try { - this.viewer.loadMap("Pathing.w3x"); + this.viewer.loadMap("Maps\\Campaign\\NightElf03.w3m"); } catch (final IOException e) { throw new RuntimeException(e); } this.cameraManager = new CameraManager(); + this.cameraManager.setupCamera(this.viewer.worldScene); System.out.println("Loaded"); @@ -272,7 +272,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.cameraManager.target.add(this.cameraVelocity.x * deltaTime, this.cameraVelocity.y * deltaTime, 0); this.cameraManager.target.z = Math.max( this.viewer.terrain.getGroundHeight(this.cameraManager.target.x, this.cameraManager.target.y), - this.viewer.terrain.getWaterHeight(this.cameraManager.target.x, this.cameraManager.target.y)); + this.viewer.terrain.getWaterHeight(this.cameraManager.target.x, this.cameraManager.target.y)) - 256; this.cameraManager.updateCamera(); this.portraitCameraManager.updateCamera(); this.viewer.updateAndRender(); @@ -283,9 +283,10 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv // this.font.draw(this.batch, Integer.toString(Gdx.graphics.getFramesPerSecond()), 0, 0); // this.batch.end(); - if ((this.portraitInstance != null) - && (this.portraitInstance.sequenceEnded || (this.portraitInstance.sequence == -1))) { - StandSequence.randomPortraitSequence(this.portraitInstance); + if ((this.portraitCameraManager.modelInstance != null) + && (this.portraitCameraManager.modelInstance.sequenceEnded + || (this.portraitCameraManager.modelInstance.sequence == -1))) { + StandSequence.randomPortraitSequence(this.portraitCameraManager.modelInstance); } Gdx.gl30.glDisable(GL30.GL_SCISSOR_TEST); @@ -429,6 +430,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv class CameraManager { public com.etheller.warsmash.viewer5.handlers.mdx.Camera modelCamera; + private MdxComplexInstance modelInstance; private CanvasProvider canvas; private Camera camera; private float moveSpeed; @@ -483,13 +485,9 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.position = this.position.add(this.target); if (this.modelCamera != null) { this.modelCamera.getPositionTranslation(WarsmashGdxMapGame.this.cameraPositionTemp, - WarsmashGdxMapGame.this.portraitInstance.sequence, - WarsmashGdxMapGame.this.portraitInstance.frame, - WarsmashGdxMapGame.this.portraitInstance.counter); + this.modelInstance.sequence, this.modelInstance.frame, this.modelInstance.counter); this.modelCamera.getTargetTranslation(WarsmashGdxMapGame.this.cameraTargetTemp, - WarsmashGdxMapGame.this.portraitInstance.sequence, - WarsmashGdxMapGame.this.portraitInstance.frame, - WarsmashGdxMapGame.this.portraitInstance.counter); + this.modelInstance.sequence, this.modelInstance.frame, this.modelInstance.counter); this.position.set(this.modelCamera.position); this.target.set(this.modelCamera.targetPosition); @@ -596,7 +594,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv final RenderUnit rayPickUnit = this.viewer.rayPickUnit(screenX, worldScreenY); if ((rayPickUnit != null) && (rayPickUnit.playerIndex != this.selectedUnit.playerIndex)) { if (this.viewer.orderSmart(rayPickUnit)) { - StandSequence.randomPortraitTalkSequence(this.portraitInstance); + StandSequence.randomPortraitTalkSequence(this.portraitCameraManager.modelInstance); this.selectedSoundCount = 0; } } @@ -609,7 +607,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv System.out.println(x + "," + y); this.viewer.terrain.logRomp(x, y); if (this.viewer.orderSmart(clickLocationTemp.x, clickLocationTemp.y)) { - StandSequence.randomPortraitTalkSequence(this.portraitInstance); + StandSequence.randomPortraitTalkSequence(this.portraitCameraManager.modelInstance); this.selectedSoundCount = 0; } } @@ -647,29 +645,29 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv if (selectionChanged) { final MdxModel portraitModel = unit.portraitModel; if (portraitModel != null) { - if (this.portraitInstance != null) { - this.portraitScene.removeInstance(this.portraitInstance); + if (this.portraitCameraManager.modelInstance != null) { + this.portraitScene.removeInstance(this.portraitCameraManager.modelInstance); } - this.portraitInstance = (MdxComplexInstance) portraitModel.addInstance(); - this.portraitInstance.setSequenceLoopMode(1); - this.portraitInstance.setScene(this.portraitScene); - this.portraitInstance.setVertexColor(unit.instance.vertexColor); + this.portraitCameraManager.modelInstance = (MdxComplexInstance) portraitModel.addInstance(); + this.portraitCameraManager.modelInstance.setSequenceLoopMode(1); + this.portraitCameraManager.modelInstance.setScene(this.portraitScene); + this.portraitCameraManager.modelInstance.setVertexColor(unit.instance.vertexColor); if (portraitModel.getCameras().size() > 0) { this.portraitCameraManager.modelCamera = portraitModel.getCameras().get(0); } - this.portraitInstance.setTeamColor(unit.playerIndex); + this.portraitCameraManager.modelInstance.setTeamColor(unit.playerIndex); } } if (playedNewSound) { - StandSequence.randomPortraitTalkSequence(this.portraitInstance); + StandSequence.randomPortraitTalkSequence(this.portraitCameraManager.modelInstance); } } else { this.selectedUnit = null; - if (this.portraitInstance != null) { - this.portraitScene.removeInstance(this.portraitInstance); + if (this.portraitCameraManager.modelInstance != null) { + this.portraitScene.removeInstance(this.portraitCameraManager.modelInstance); } - this.portraitInstance = null; + this.portraitCameraManager.modelInstance = null; this.portraitCameraManager.modelCamera = null; } } diff --git a/core/src/com/etheller/warsmash/parsers/jass/Jass2.java b/core/src/com/etheller/warsmash/parsers/jass/Jass2.java index c37b1e6..95e853f 100644 --- a/core/src/com/etheller/warsmash/parsers/jass/Jass2.java +++ b/core/src/com/etheller/warsmash/parsers/jass/Jass2.java @@ -14,11 +14,16 @@ import com.etheller.interpreter.JassLexer; import com.etheller.interpreter.JassParser; import com.etheller.interpreter.ast.function.JassFunction; import com.etheller.interpreter.ast.scope.GlobalScope; +import com.etheller.interpreter.ast.scope.TriggerExecutionScope; +import com.etheller.interpreter.ast.scope.trigger.Trigger; +import com.etheller.interpreter.ast.scope.trigger.TriggerBooleanExpression; import com.etheller.interpreter.ast.value.BooleanJassValue; +import com.etheller.interpreter.ast.value.HandleJassType; import com.etheller.interpreter.ast.value.HandleJassValue; import com.etheller.interpreter.ast.value.JassValue; import com.etheller.interpreter.ast.value.StringJassValue; import com.etheller.interpreter.ast.value.visitor.IntegerJassValueVisitor; +import com.etheller.interpreter.ast.value.visitor.JassFunctionJassValueVisitor; import com.etheller.interpreter.ast.value.visitor.ObjectJassValueVisitor; import com.etheller.interpreter.ast.value.visitor.RealJassValueVisitor; import com.etheller.interpreter.ast.value.visitor.StringJassValueVisitor; @@ -28,6 +33,13 @@ import com.etheller.warsmash.parsers.fdf.GameUI; import com.etheller.warsmash.parsers.fdf.datamodel.AnchorDefinition; import com.etheller.warsmash.parsers.fdf.datamodel.FramePoint; import com.etheller.warsmash.parsers.fdf.frames.UIFrame; +import com.etheller.warsmash.parsers.jass.triggers.BoolExprAnd; +import com.etheller.warsmash.parsers.jass.triggers.BoolExprCondition; +import com.etheller.warsmash.parsers.jass.triggers.BoolExprFilter; +import com.etheller.warsmash.parsers.jass.triggers.BoolExprNot; +import com.etheller.warsmash.parsers.jass.triggers.BoolExprOr; +import com.etheller.warsmash.parsers.jass.triggers.TriggerAction; +import com.etheller.warsmash.parsers.jass.triggers.TriggerCondition; import com.etheller.warsmash.units.Element; public class Jass2 { @@ -84,9 +96,19 @@ public class Jass2 { public JUIEnvironment(final JassProgramVisitor jassProgramVisitor, final DataSource dataSource, final Viewport uiViewport, final RootFrameListener rootFrameListener) { + final GlobalScope globals = jassProgramVisitor.getGlobals(); + final HandleJassType frameHandleType = globals.registerHandleType("framehandle"); + final HandleJassType framePointType = globals.registerHandleType("framepointtype"); + final HandleJassType triggerType = globals.registerHandleType("trigger"); + final HandleJassType triggerActionType = globals.registerHandleType("triggeraction"); + final HandleJassType triggerConditionType = globals.registerHandleType("triggercondition"); + final HandleJassType boolExprType = globals.registerHandleType("boolexpr"); + final HandleJassType conditionFuncType = globals.registerHandleType("conditionfunc"); + final HandleJassType filterType = globals.registerHandleType("filterfunc"); jassProgramVisitor.getJassNativeManager().createNative("LogError", new JassFunction() { @Override - public JassValue call(final List arguments, final GlobalScope globalScope) { + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { final String stringValue = arguments.get(0).visit(StringJassValueVisitor.getInstance()); System.err.println(stringValue); return null; @@ -94,27 +116,29 @@ public class Jass2 { }); jassProgramVisitor.getJassNativeManager().createNative("ConvertFramePointType", new JassFunction() { @Override - public JassValue call(final List arguments, final GlobalScope globalScope) { + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { final int value = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); - return new HandleJassValue(jassProgramVisitor.getGlobals().framePointType, - FramePoint.values()[value]); + return new HandleJassValue(framePointType, FramePoint.values()[value]); } }); jassProgramVisitor.getJassNativeManager().createNative("CreateRootFrame", new JassFunction() { @Override - public JassValue call(final List arguments, final GlobalScope globalScope) { + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { final String skinArg = arguments.get(0).visit(StringJassValueVisitor.getInstance()); final Element skin = GameUI.loadSkin(dataSource, skinArg); final GameUI gameUI = new GameUI(dataSource, skin, uiViewport); JUIEnvironment.this.gameUI = gameUI; JUIEnvironment.this.skin = skin; rootFrameListener.onCreate(gameUI); - return new HandleJassValue(jassProgramVisitor.getGlobals().frameHandleType, gameUI); + return new HandleJassValue(frameHandleType, gameUI); } }); jassProgramVisitor.getJassNativeManager().createNative("LoadTOCFile", new JassFunction() { @Override - public JassValue call(final List arguments, final GlobalScope globalScope) { + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { final String tocFileName = arguments.get(0).visit(StringJassValueVisitor.getInstance()); try { JUIEnvironment.this.gameUI.loadTOCFile(tocFileName); @@ -127,7 +151,8 @@ public class Jass2 { }); jassProgramVisitor.getJassNativeManager().createNative("CreateSimpleFrame", new JassFunction() { @Override - public JassValue call(final List arguments, final GlobalScope globalScope) { + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { final String templateName = arguments.get(0).visit(StringJassValueVisitor.getInstance()); final UIFrame ownerFrame = arguments.get(1).visit(ObjectJassValueVisitor.getInstance()); final int createContext = arguments.get(2).visit(IntegerJassValueVisitor.getInstance()); @@ -135,12 +160,13 @@ public class Jass2 { final UIFrame simpleFrame = JUIEnvironment.this.gameUI.createSimpleFrame(templateName, ownerFrame, createContext); - return new HandleJassValue(jassProgramVisitor.getGlobals().frameHandleType, simpleFrame); + return new HandleJassValue(frameHandleType, simpleFrame); } }); jassProgramVisitor.getJassNativeManager().createNative("FrameSetAbsPoint", new JassFunction() { @Override - public JassValue call(final List arguments, final GlobalScope globalScope) { + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { final UIFrame frame = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); final FramePoint framePoint = arguments.get(1) .visit(ObjectJassValueVisitor.getInstance()); @@ -154,7 +180,8 @@ public class Jass2 { }); jassProgramVisitor.getJassNativeManager().createNative("FramePositionBounds", new JassFunction() { @Override - public JassValue call(final List arguments, final GlobalScope globalScope) { + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { final UIFrame frame = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); frame.positionBounds(uiViewport); return null; @@ -162,11 +189,171 @@ public class Jass2 { }); jassProgramVisitor.getJassNativeManager().createNative("SkinGetField", new JassFunction() { @Override - public JassValue call(final List arguments, final GlobalScope globalScope) { + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { final String fieldName = arguments.get(0).visit(StringJassValueVisitor.getInstance()); return new StringJassValue(JUIEnvironment.this.skin.getField(fieldName)); } }); + jassProgramVisitor.getJassNativeManager().createNative("CreateTrigger", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + return new HandleJassValue(triggerType, new Trigger()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("DestroyTrigger", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Trigger trigger = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + trigger.destroy(); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("EnableTrigger", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Trigger trigger = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + trigger.setEnabled(true); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("DisableTrigger", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Trigger trigger = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + trigger.setEnabled(false); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("IsTriggerEnabled", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Trigger trigger = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + return BooleanJassValue.of(trigger.isEnabled()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("Condition", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final JassFunction func = arguments.get(0).visit(JassFunctionJassValueVisitor.getInstance()); + return new HandleJassValue(conditionFuncType, new BoolExprCondition(func)); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("Filter", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final JassFunction func = arguments.get(0).visit(JassFunctionJassValueVisitor.getInstance()); + return new HandleJassValue(filterType, new BoolExprFilter(func)); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("DestroyCondition", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final BoolExprCondition trigger = arguments.get(0) + .visit(ObjectJassValueVisitor.getInstance()); + System.err.println( + "DestroyCondition called but in Java we don't have a destructor, so we need to unregister later when that is implemented"); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("DestroyFilter", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final BoolExprFilter trigger = arguments.get(0) + .visit(ObjectJassValueVisitor.getInstance()); + System.err.println( + "DestroyFilter called but in Java we don't have a destructor, so we need to unregister later when that is implemented"); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("DestroyBoolExpr", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final TriggerBooleanExpression trigger = arguments.get(0) + .visit(ObjectJassValueVisitor.getInstance()); + System.err.println( + "DestroyBoolExpr called but in Java we don't have a destructor, so we need to unregister later when that is implemented"); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("And", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final TriggerBooleanExpression operandA = arguments.get(0) + .visit(ObjectJassValueVisitor.getInstance()); + final TriggerBooleanExpression operandB = arguments.get(1) + .visit(ObjectJassValueVisitor.getInstance()); + return new HandleJassValue(boolExprType, new BoolExprAnd(operandA, operandB)); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("Or", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final TriggerBooleanExpression operandA = arguments.get(0) + .visit(ObjectJassValueVisitor.getInstance()); + final TriggerBooleanExpression operandB = arguments.get(1) + .visit(ObjectJassValueVisitor.getInstance()); + return new HandleJassValue(boolExprType, new BoolExprOr(operandA, operandB)); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("Not", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final TriggerBooleanExpression operand = arguments.get(0) + .visit(ObjectJassValueVisitor.getInstance()); + return new HandleJassValue(boolExprType, new BoolExprNot(operand)); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("TriggerAddCondition", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Trigger whichTrigger = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final TriggerBooleanExpression condition = arguments.get(1) + .visit(ObjectJassValueVisitor.getInstance()); + final int index = whichTrigger.addCondition(condition); + return new HandleJassValue(triggerConditionType, + new TriggerCondition(condition, whichTrigger, index)); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("TriggerRemoveCondition", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Trigger whichTrigger = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final TriggerCondition condition = arguments.get(1) + .visit(ObjectJassValueVisitor.getInstance()); + if (condition.getTrigger() != whichTrigger) { + throw new IllegalArgumentException("Unable to remove condition, wrong trigger"); + } + whichTrigger.removeConditionAtIndex(condition.getConditionIndex()); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("TriggerAddAction", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Trigger whichTrigger = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final JassFunction actionFunc = arguments.get(1).visit(JassFunctionJassValueVisitor.getInstance()); + final int actionIndex = whichTrigger.addAction(actionFunc); + return new HandleJassValue(triggerActionType, + new TriggerAction(whichTrigger, actionFunc, actionIndex)); + } + }); } } } diff --git a/core/src/com/etheller/warsmash/parsers/jass/triggers/BoolExprAnd.java b/core/src/com/etheller/warsmash/parsers/jass/triggers/BoolExprAnd.java new file mode 100644 index 0000000..26deb4e --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/jass/triggers/BoolExprAnd.java @@ -0,0 +1,21 @@ +package com.etheller.warsmash.parsers.jass.triggers; + +import com.etheller.interpreter.ast.scope.GlobalScope; +import com.etheller.interpreter.ast.scope.TriggerExecutionScope; +import com.etheller.interpreter.ast.scope.trigger.TriggerBooleanExpression; + +public class BoolExprAnd implements TriggerBooleanExpression { + private final TriggerBooleanExpression operandA; + private final TriggerBooleanExpression operandB; + + public BoolExprAnd(final TriggerBooleanExpression operandA, final TriggerBooleanExpression operandB) { + this.operandA = operandA; + this.operandB = operandB; + } + + @Override + public boolean evaluate(final GlobalScope globalScope, final TriggerExecutionScope triggerScope) { + return this.operandA.evaluate(globalScope, triggerScope) && this.operandB.evaluate(globalScope, triggerScope); + } + +} diff --git a/core/src/com/etheller/warsmash/parsers/jass/triggers/BoolExprCondition.java b/core/src/com/etheller/warsmash/parsers/jass/triggers/BoolExprCondition.java new file mode 100644 index 0000000..34ef292 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/jass/triggers/BoolExprCondition.java @@ -0,0 +1,27 @@ +package com.etheller.warsmash.parsers.jass.triggers; + +import java.util.Collections; + +import com.etheller.interpreter.ast.function.JassFunction; +import com.etheller.interpreter.ast.scope.GlobalScope; +import com.etheller.interpreter.ast.scope.TriggerExecutionScope; +import com.etheller.interpreter.ast.scope.trigger.TriggerBooleanExpression; +import com.etheller.interpreter.ast.value.JassValue; +import com.etheller.interpreter.ast.value.visitor.BooleanJassValueVisitor; + +public class BoolExprCondition implements TriggerBooleanExpression { + private final JassFunction takesNothingReturnsBooleanFunction; + + public BoolExprCondition(final JassFunction returnsBooleanFunction) { + this.takesNothingReturnsBooleanFunction = returnsBooleanFunction; + } + + @Override + public boolean evaluate(final GlobalScope globalScope, final TriggerExecutionScope triggerScope) { + final JassValue booleanJassReturnValue = this.takesNothingReturnsBooleanFunction.call(Collections.EMPTY_LIST, + globalScope, triggerScope); + final Boolean booleanReturnValue = booleanJassReturnValue.visit(BooleanJassValueVisitor.getInstance()); + return booleanReturnValue.booleanValue(); + } + +} diff --git a/core/src/com/etheller/warsmash/parsers/jass/triggers/BoolExprFilter.java b/core/src/com/etheller/warsmash/parsers/jass/triggers/BoolExprFilter.java new file mode 100644 index 0000000..b3a6e27 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/jass/triggers/BoolExprFilter.java @@ -0,0 +1,27 @@ +package com.etheller.warsmash.parsers.jass.triggers; + +import java.util.Collections; + +import com.etheller.interpreter.ast.function.JassFunction; +import com.etheller.interpreter.ast.scope.GlobalScope; +import com.etheller.interpreter.ast.scope.TriggerExecutionScope; +import com.etheller.interpreter.ast.scope.trigger.TriggerBooleanExpression; +import com.etheller.interpreter.ast.value.JassValue; +import com.etheller.interpreter.ast.value.visitor.BooleanJassValueVisitor; + +public class BoolExprFilter implements TriggerBooleanExpression { + private final JassFunction takesNothingReturnsBooleanFunction; + + public BoolExprFilter(final JassFunction returnsBooleanFunction) { + this.takesNothingReturnsBooleanFunction = returnsBooleanFunction; + } + + @Override + public boolean evaluate(final GlobalScope globalScope, final TriggerExecutionScope triggerScope) { + final JassValue booleanJassReturnValue = this.takesNothingReturnsBooleanFunction.call(Collections.EMPTY_LIST, + globalScope, triggerScope); + final Boolean booleanReturnValue = booleanJassReturnValue.visit(BooleanJassValueVisitor.getInstance()); + return booleanReturnValue.booleanValue(); + } + +} diff --git a/core/src/com/etheller/warsmash/parsers/jass/triggers/BoolExprNot.java b/core/src/com/etheller/warsmash/parsers/jass/triggers/BoolExprNot.java new file mode 100644 index 0000000..0f2cc52 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/jass/triggers/BoolExprNot.java @@ -0,0 +1,19 @@ +package com.etheller.warsmash.parsers.jass.triggers; + +import com.etheller.interpreter.ast.scope.GlobalScope; +import com.etheller.interpreter.ast.scope.TriggerExecutionScope; +import com.etheller.interpreter.ast.scope.trigger.TriggerBooleanExpression; + +public class BoolExprNot implements TriggerBooleanExpression { + private final TriggerBooleanExpression operand; + + public BoolExprNot(final TriggerBooleanExpression operand) { + this.operand = operand; + } + + @Override + public boolean evaluate(final GlobalScope globalScope, final TriggerExecutionScope triggerScope) { + return this.operand.evaluate(globalScope, triggerScope); + } + +} diff --git a/core/src/com/etheller/warsmash/parsers/jass/triggers/BoolExprOr.java b/core/src/com/etheller/warsmash/parsers/jass/triggers/BoolExprOr.java new file mode 100644 index 0000000..a59cd83 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/jass/triggers/BoolExprOr.java @@ -0,0 +1,21 @@ +package com.etheller.warsmash.parsers.jass.triggers; + +import com.etheller.interpreter.ast.scope.GlobalScope; +import com.etheller.interpreter.ast.scope.TriggerExecutionScope; +import com.etheller.interpreter.ast.scope.trigger.TriggerBooleanExpression; + +public class BoolExprOr implements TriggerBooleanExpression { + private final TriggerBooleanExpression operandA; + private final TriggerBooleanExpression operandB; + + public BoolExprOr(final TriggerBooleanExpression operandA, final TriggerBooleanExpression operandB) { + this.operandA = operandA; + this.operandB = operandB; + } + + @Override + public boolean evaluate(final GlobalScope globalScope, final TriggerExecutionScope triggerScope) { + return this.operandA.evaluate(globalScope, triggerScope) || this.operandB.evaluate(globalScope, triggerScope); + } + +} diff --git a/core/src/com/etheller/warsmash/parsers/jass/triggers/TriggerAction.java b/core/src/com/etheller/warsmash/parsers/jass/triggers/TriggerAction.java new file mode 100644 index 0000000..7620f13 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/jass/triggers/TriggerAction.java @@ -0,0 +1,28 @@ +package com.etheller.warsmash.parsers.jass.triggers; + +import com.etheller.interpreter.ast.function.JassFunction; +import com.etheller.interpreter.ast.scope.trigger.Trigger; + +public class TriggerAction { + private final Trigger trigger; + private final JassFunction actionFunc; + private final int actionIndex; + + public TriggerAction(final Trigger trigger, final JassFunction actionFunc, final int actionIndex) { + this.trigger = trigger; + this.actionFunc = actionFunc; + this.actionIndex = actionIndex; + } + + public Trigger getTrigger() { + return this.trigger; + } + + public JassFunction getActionFunc() { + return this.actionFunc; + } + + public int getActionIndex() { + return this.actionIndex; + } +} diff --git a/core/src/com/etheller/warsmash/parsers/jass/triggers/TriggerCondition.java b/core/src/com/etheller/warsmash/parsers/jass/triggers/TriggerCondition.java new file mode 100644 index 0000000..8ee32d7 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/jass/triggers/TriggerCondition.java @@ -0,0 +1,28 @@ +package com.etheller.warsmash.parsers.jass.triggers; + +import com.etheller.interpreter.ast.scope.trigger.Trigger; +import com.etheller.interpreter.ast.scope.trigger.TriggerBooleanExpression; + +public class TriggerCondition { + private final TriggerBooleanExpression boolexpr; + private final Trigger trigger; + private final int conditionIndex; + + public TriggerCondition(final TriggerBooleanExpression boolexpr, final Trigger trigger, final int index) { + this.boolexpr = boolexpr; + this.trigger = trigger; + this.conditionIndex = index; + } + + public TriggerBooleanExpression getBoolexpr() { + return this.boolexpr; + } + + public Trigger getTrigger() { + return this.trigger; + } + + public int getConditionIndex() { + return this.conditionIndex; + } +} diff --git a/core/src/com/etheller/warsmash/util/Quadtree.java b/core/src/com/etheller/warsmash/util/Quadtree.java new file mode 100644 index 0000000..d3ae6d3 --- /dev/null +++ b/core/src/com/etheller/warsmash/util/Quadtree.java @@ -0,0 +1,205 @@ +package com.etheller.warsmash.util; + +import java.util.function.Consumer; + +import com.badlogic.gdx.math.Rectangle; +import com.badlogic.gdx.utils.Array; + +public class Quadtree { + private static final int MAX_DEPTH = 64; + private static final int SPLIT_THRESHOLD = 6; + + private final Rectangle bounds; + private Quadtree northeast; + private Quadtree northwest; + private Quadtree southwest; + private Quadtree southeast; + private final Array> nodes = new Array<>(); + private boolean leaf = true; + private final NodeAdder nodeAdder = new NodeAdder(); + private final UniqueNodeAdder uniqueNodeAdder = new UniqueNodeAdder(); + + public Quadtree(final Rectangle bounds) { + this.bounds = bounds; + } + + public void add(final T object, final Rectangle bounds) { + add(new Node(object, bounds), 0); + } + + public void remove(final T object, final Rectangle bounds) { + remove(object, bounds, null); + } + + public void translate(final T object, final Rectangle prevBoundsToUpdate, final float xShift, final float yShift) { + final Node node = remove(object, prevBoundsToUpdate, null); + prevBoundsToUpdate.x += xShift; + prevBoundsToUpdate.y += yShift; + add(node, 0); + } + + public boolean intersectsAnythingOtherThan(final T sourceObjectToIgnore, final Rectangle bounds) { + if (this.leaf) { + for (int i = 0; i < this.nodes.size; i++) { + final Node node = this.nodes.get(i); + if ((node.object != sourceObjectToIgnore) && node.bounds.overlaps(bounds)) { + return true; + } + } + return false; + } + else { + if (this.northeast.bounds.overlaps(bounds)) { + if (this.northeast.intersectsAnythingOtherThan(sourceObjectToIgnore, bounds)) { + return true; + } + } + if (this.northwest.bounds.overlaps(bounds)) { + if (this.northwest.intersectsAnythingOtherThan(sourceObjectToIgnore, bounds)) { + return true; + } + } + if (this.southwest.bounds.overlaps(bounds)) { + if (this.southwest.intersectsAnythingOtherThan(sourceObjectToIgnore, bounds)) { + return true; + } + } + if (this.southeast.bounds.overlaps(bounds)) { + if (this.southeast.intersectsAnythingOtherThan(sourceObjectToIgnore, bounds)) { + return true; + } + } + return false; + } + } + + private void add(final Node node, final int depth) { + if (this.leaf) { + if ((this.nodes.size >= SPLIT_THRESHOLD) && (depth < MAX_DEPTH)) { + split(depth); + // then dont return and add as a nonleaf + } + else { + this.nodes.add(node); + return; + } + } + if (this.northeast.bounds.overlaps(node.bounds)) { + this.northeast.add(node, depth + 1); + } + if (this.northwest.bounds.overlaps(node.bounds)) { + this.northwest.add(node, depth + 1); + } + if (this.southwest.bounds.overlaps(node.bounds)) { + this.southwest.add(node, depth + 1); + } + if (this.southeast.bounds.overlaps(node.bounds)) { + this.southeast.add(node, depth + 1); + } + } + + private void split(final int depth) { + final int splitDepth = depth + 1; + final float halfWidth = this.bounds.width / 2; + final float x = this.bounds.x; + final float xMidpoint = x + halfWidth; + final float halfHeight = this.bounds.height / 2; + final float y = this.bounds.y; + final float yMidpoint = y + halfHeight; + this.northeast = new Quadtree<>(new Rectangle(xMidpoint, yMidpoint, halfWidth, halfHeight)); + this.northwest = new Quadtree<>(new Rectangle(x, yMidpoint, halfWidth, halfHeight)); + this.southwest = new Quadtree<>(new Rectangle(x, y, halfWidth, halfHeight)); + this.southeast = new Quadtree<>(new Rectangle(xMidpoint, y, halfWidth, halfHeight)); + this.leaf = false; + this.nodes.forEach(this.nodeAdder.reset(splitDepth)); + this.nodes.clear(); + } + + private Node remove(final T object, final Rectangle bounds, final Quadtree parent) { + Node returnValue = null; + if (this.leaf) { + for (int i = 0; i < this.nodes.size; i++) { + if (this.nodes.get(i).object == object) { + returnValue = this.nodes.removeIndex(i); + break; + } + } + } + else { + if (this.northeast.bounds.overlaps(bounds)) { + returnValue = this.northeast.remove(object, bounds, this); + } + if (this.northwest.bounds.overlaps(bounds)) { + returnValue = this.northwest.remove(object, bounds, this); + } + if (this.southwest.bounds.overlaps(bounds)) { + returnValue = this.southwest.remove(object, bounds, this); + } + if (this.southeast.bounds.overlaps(bounds)) { + returnValue = this.southeast.remove(object, bounds, this); + } + mergeIfNecessary(); + } + return returnValue; + } + + private void mergeIfNecessary() { + if (this.northeast.leaf && this.northwest.leaf && this.southwest.leaf && this.southeast.leaf) { + final int children = this.northeast.nodes.size + this.northwest.nodes.size + this.southwest.nodes.size + + this.southeast.nodes.size; // might include duplicates + if (children <= SPLIT_THRESHOLD) { + this.leaf = true; + addAllUnique(this.northeast.nodes); + addAllUnique(this.northwest.nodes); + addAllUnique(this.southwest.nodes); + addAllUnique(this.southeast.nodes); + this.northeast = this.northwest = this.southwest = this.southeast = null; + } + } + } + + private void addAllUnique(final Array> nodes) { + nodes.forEach(this.uniqueNodeAdder); + } + + private static final class Node { + private final T object; + private final Rectangle bounds; + + public Node(final T object, final Rectangle bounds) { + this.object = object; + this.bounds = bounds; + } + } + + private final class NodeAdder implements Consumer> { + private int splitDepth; + + private NodeAdder reset(final int splitDepth) { + this.splitDepth = splitDepth; + return this; + } + + @Override + public void accept(final Node node) { + add(node, this.splitDepth); + } + } + + private final class UniqueNodeAdder implements Consumer> { + + private UniqueNodeAdder reset() { + return this; + } + + @Override + public void accept(final Node node) { + for (int i = 0; i < Quadtree.this.nodes.size; i++) { + if (Quadtree.this.nodes.get(i) == node) { + return; + } + } + Quadtree.this.nodes.add(node); + } + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index a84e636..fdba8bc 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -21,6 +21,7 @@ import org.apache.commons.compress.utils.IOUtils; import org.apache.commons.compress.utils.SeekableInMemoryByteChannel; import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.math.Vector3; import com.badlogic.gdx.math.collision.Ray; @@ -141,6 +142,7 @@ public class War3MapViewer extends ModelViewer { public List selModels = new ArrayList<>(); public List selected = new ArrayList<>(); private DataTable unitAckSoundsTable; + private DataTable miscData; private MdxComplexInstance confirmationInstance; public CSimulation simulation; private float updateTime; @@ -153,6 +155,8 @@ public class War3MapViewer extends ModelViewer { private final Map filePathToPathingMap = new HashMap<>(); + private final List selectionCircleSizes = new ArrayList<>(); + public War3MapViewer(final DataSource dataSource, final CanvasProvider canvas) { super(dataSource, canvas); this.gameDataSource = dataSource; @@ -223,7 +227,21 @@ public class War3MapViewer extends ModelViewer { try (InputStream terrainSlkStream = this.dataSource.getResourceAsStream("UI\\SoundInfo\\UnitAckSounds.slk")) { this.unitAckSoundsTable.readSLK(terrainSlkStream); } - + this.miscData = new DataTable(worldEditStrings); + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("UI\\MiscData.txt")) { + this.miscData.readTXT(miscDataTxtStream, true); + } + this.selectionCircleSizes.clear(); + final Element selectionCircleData = this.miscData.get("SelectionCircle"); + final int selectionCircleNumSizes = selectionCircleData.getFieldValue("NumSizes"); + for (int i = 0; i < selectionCircleNumSizes; i++) { + final String indexString = i < 10 ? "0" + i : Integer.toString(i); + final float size = selectionCircleData.getFieldFloatValue("Size" + indexString); + final String texture = selectionCircleData.getField("Texture" + indexString); + final String textureDotted = selectionCircleData.getField("TextureDotted" + indexString); + this.selectionCircleSizes.add(new SelectionCircleSize(size, texture, textureDotted)); + } + this.selectionCircleScaleFactor = selectionCircleData.getFieldFloatValue("ScaleFactor"); } public GenericResource loadMapGeneric(final String path, final FetchDataTypeName dataType, @@ -367,7 +385,8 @@ public class War3MapViewer extends ModelViewer { return simulationAttackProjectile; } - }, this.terrain.pathingGrid); + }, this.terrain.pathingGrid, + new Rectangle(centerOffset[0], centerOffset[1], (mapSize[0] * 128f) - 128, (mapSize[1] * 128f) - 128)); if (this.doodadsAndDestructiblesLoaded) { this.loadDoodadsAndDestructibles(modifications); @@ -770,27 +789,29 @@ public class War3MapViewer extends ModelViewer { final Map splats = new HashMap(); for (final RenderUnit unit : units) { if (unit.row != null) { - if (unit.radius > 0) { - final float radius = unit.radius; - String path; - // TODO these radius values must be read from UI\MiscData.txt instead - if (radius < 100) { - path = "ReplaceableTextures\\Selection\\SelectionCircleSmall.blp"; + if (unit.selectionScale > 0) { + final float selectionSize = unit.selectionScale * this.selectionCircleScaleFactor; + String path = null; + for (int i = 0; i < this.selectionCircleSizes.size(); i++) { + final SelectionCircleSize selectionCircleSize = this.selectionCircleSizes.get(i); + if ((selectionSize < selectionCircleSize.size) + || (i == (this.selectionCircleSizes.size() - 1))) { + path = selectionCircleSize.texture; + break; + } } - else if (radius < 300) { - path = "ReplaceableTextures\\Selection\\SelectionCircleMed.blp"; - } - else { - path = "ReplaceableTextures\\Selection\\SelectionCircleLarge.blp"; + if (!path.toLowerCase().endsWith(".blp")) { + path += ".blp"; } if (!splats.containsKey(path)) { splats.put(path, new Splat()); } final float x = unit.location[0]; final float y = unit.location[1]; + System.out.println("Selecting a unit at " + x + "," + y); final float z = unit.row.getFieldAsFloat(UNIT_SELECT_HEIGHT, 0); - splats.get(path).locations - .add(new float[] { x - radius, y - radius, x + radius, y + radius, z + 5 }); + splats.get(path).locations.add(new float[] { x - (selectionSize / 2), y - (selectionSize / 2), + x + (selectionSize / 2), y + (selectionSize / 2), z + 5 }); splats.get(path).unitMapping.add(new Consumer() { @Override public void accept(final SplatMover t) { @@ -875,109 +896,19 @@ public class War3MapViewer extends ModelViewer { return sel; } - public List selectUnitOld(final float x, final float y, final boolean toggle) { - final float[] ray = rayHeap; - mousePosHeap.set(x, y); - this.worldScene.camera.screenToWorldRay(ray, mousePosHeap); - final Vector3 dir = normalHeap; - dir.x = ray[3] - ray[0]; - dir.y = ray[4] - ray[1]; - dir.z = ray[5] - ray[2]; - dir.nor(); - // TODO good performance, do not create vectors on every check - final Vector3 eMid = new Vector3(); - final Vector3 eSize = new Vector3(); - final Vector3 rDir = new Vector3(); - - RenderUnit entity = null; - float entDist = 1e6f; - - for (final RenderUnit unit : this.units) { - final float radius = unit.radius; - final float[] location = unit.location; - final MdxComplexInstance instance = unit.instance; - eMid.set(0, 0, radius / 2); - eSize.set(radius, radius, radius); - - eMid.add(location[0], location[1], location[2]); - eMid.sub(ray[0], ray[1], ray[2]); - eMid.scl(1 / eSize.x, 1 / eSize.y, 1 / eSize.z); - rDir.x = dir.x / eSize.x; - rDir.y = dir.y / eSize.y; - rDir.z = dir.z / eSize.z; - final float dlen = rDir.len2(); - final float dp = Math.max(0, rDir.dot(eMid)) / dlen; - if (dp > entDist) { - continue; - } - rDir.scl(dp); - if (rDir.dst2(eMid) < 1.0) { - entity = unit; - entDist = dp; - } - } - List sel; - if (entity != null) { - if (toggle) { - sel = new ArrayList<>(this.selected); - final int idx = sel.indexOf(entity); - if (idx >= 0) { - sel.remove(idx); - } - else { - sel.add(entity); - } - } - else { - sel = Arrays.asList(entity); - } - } - else { - sel = Collections.emptyList(); - } - this.doSelectUnit(sel); - return sel; - } - public RenderUnit rayPickUnit(final float x, final float y) { final float[] ray = rayHeap; mousePosHeap.set(x, y); this.worldScene.camera.screenToWorldRay(ray, mousePosHeap); - final Vector3 dir = normalHeap; - dir.x = ray[3] - ray[0]; - dir.y = ray[4] - ray[1]; - dir.z = ray[5] - ray[2]; - dir.nor(); - // TODO good performance, do not create vectors on every check - final Vector3 eMid = new Vector3(); - final Vector3 eSize = new Vector3(); - final Vector3 rDir = new Vector3(); + gdxRayHeap.set(ray[0], ray[1], ray[2], ray[3] - ray[0], ray[4] - ray[1], ray[5] - ray[2]); + gdxRayHeap.direction.nor();// needed for libgdx RenderUnit entity = null; - float entDist = 1e6f; - for (final RenderUnit unit : this.units) { - final float radius = unit.radius; - final float[] location = unit.location; final MdxComplexInstance instance = unit.instance; - eMid.set(0, 0, radius / 2); - eSize.set(radius, radius, radius); - - eMid.add(location[0], location[1], location[2]); - eMid.sub(ray[0], ray[1], ray[2]); - eMid.scl(1 / eSize.x, 1 / eSize.y, 1 / eSize.z); - rDir.x = dir.x / eSize.x; - rDir.y = dir.y / eSize.y; - rDir.z = dir.z / eSize.z; - final float dlen = rDir.len2(); - final float dp = Math.max(0, rDir.dot(eMid)) / dlen; - if (dp > entDist) { - continue; - } - rDir.scl(dp); - if (rDir.dst2(eMid) < 1.0) { + if (instance.isVisible(this.worldScene.camera) + && instance.intersectRayWithCollision(gdxRayHeap, intersectionHeap)) { entity = unit; - entDist = dp; } } return entity; @@ -1047,6 +978,7 @@ public class War3MapViewer extends ModelViewer { } private static final int MAXIMUM_ACCEPTED = 1 << 30; + private float selectionCircleScaleFactor; /** * Returns a power of two size for the given target capacity. @@ -1135,4 +1067,15 @@ public class War3MapViewer extends ModelViewer { StandSequence.randomStandSequence(instance); } + private static final class SelectionCircleSize { + private final float size; + private final String texture; + private final String textureDotted; + + public SelectionCircleSize(final float size, final String texture, final String textureDotted) { + this.size = size; + this.texture = texture; + this.textureDotted = textureDotted; + } + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java index 289bcfd..9d20357 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java @@ -1,13 +1,10 @@ package com.etheller.warsmash.viewer5.handlers.w3x.environment; import java.awt.image.BufferedImage; -import java.util.Arrays; import java.util.HashMap; import java.util.Map; import com.etheller.warsmash.parsers.w3x.wpm.War3MapWpm; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitType; public class PathingGrid { private static final Map movetpToMovementType = new HashMap<>(); @@ -21,10 +18,6 @@ public class PathingGrid { private final short[] pathingGrid; private final short[] dynamicPathingOverlay; // for buildings and trees - private final short[] unitPathingOverlay; // for constantly moving units - private final short[] ignorePathingOverlay; // to prevent unit collide with self - private final int[] unitsAtLocationCount; // number of units at location, probably really inefficient, should - // probably change later private final int[] pathingGridSizes; private final float[] centerOffset; @@ -33,53 +26,6 @@ public class PathingGrid { this.pathingGrid = terrainPathing.getPathing(); this.pathingGridSizes = terrainPathing.getSize(); this.dynamicPathingOverlay = new short[this.pathingGrid.length]; - this.unitPathingOverlay = new short[this.pathingGrid.length]; - this.ignorePathingOverlay = new short[this.pathingGrid.length]; - Arrays.fill(this.ignorePathingOverlay, (short) 0xFFFF); - this.unitsAtLocationCount = new int[this.pathingGrid.length]; - } - - public void resetUnitCollisionPathing(final Iterable units) { - Arrays.fill(this.unitPathingOverlay, (short) 0); - Arrays.fill(this.unitsAtLocationCount, 0); - for (final CUnit unit : units) { - final CUnitType unitType = unit.getUnitType(); - final MovementType movementType = unitType.getMovementType(); - if (!unitType.isBuilding() && (movementType != null)) { - final float collisionSize = unitType.getCollisionSize(); - final float maxX = unit.getX() + collisionSize; - final float maxY = unit.getY() + collisionSize; - final short obstructionByte; - switch (movementType) { - case FLOAT: - obstructionByte = (short) (PathingGrid.PathingFlags.UNSWIMABLE); - break; - case AMPHIBIOUS: - obstructionByte = (short) (PathingGrid.PathingFlags.UNWALKABLE - | PathingGrid.PathingFlags.UNSWIMABLE); - break; - case FLY: - obstructionByte = (short) (PathingGrid.PathingFlags.UNFLYABLE); - break; - default: - case FOOT: - case DISABLED: - case HORSE: - case HOVER: - obstructionByte = (short) (PathingGrid.PathingFlags.UNWALKABLE); - break; - } - for (float minX = unit.getX() - collisionSize; minX < maxX; minX += 32f) { - for (float minY = unit.getY() - collisionSize; minY < maxY; minY += 32f) { - final int yy = getCellY(minY); - final int xx = getCellX(minX); - final int index = (yy * this.pathingGridSizes[0]) + xx; - this.unitPathingOverlay[index] |= obstructionByte; - this.unitsAtLocationCount[index]++; - } - } - } - } } // this blit function is basically copied from HiveWE, maybe remember to mention @@ -193,8 +139,7 @@ public class PathingGrid { public short getCellPathing(final int cellX, final int cellY) { final int index = (cellY * this.pathingGridSizes[0]) + cellX; - return (short) ((this.pathingGrid[index] | this.dynamicPathingOverlay[index] | this.unitPathingOverlay[index]) - & this.ignorePathingOverlay[index]); + return (short) (this.pathingGrid[index] | this.dynamicPathingOverlay[index]); } public boolean isPathable(final float x, final float y, final PathingType pathingType) { @@ -247,22 +192,6 @@ public class PathingGrid { return false; } - public void setUnitIgnore(final float unitX, final float unitY, final float collisionSize, final boolean ignore) { - final float maxX = unitX + collisionSize; - final float maxY = unitY + collisionSize; - final short ignoreFlag = (short) (ignore ? 0 : 0xFFFF); - for (float minX = unitX - collisionSize; minX < maxX; minX += 32f) { - for (float minY = unitY - collisionSize; minY < maxY; minY += 32f) { - final int cellY = getCellY(minY); - final int cellX = getCellX(minX); - final int index = (cellY * this.pathingGridSizes[0]) + cellX; - if (this.unitsAtLocationCount[index] == 1) { - this.ignorePathingOverlay[index] = ignoreFlag; - } - } - } - } - public boolean isCellPathable(final int x, final int y, final MovementType pathingType, final float collisionSize) { return isPathable(getWorldX(x), getWorldY(y), pathingType, collisionSize); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java index c0a87b9..948c1e2 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java @@ -43,7 +43,7 @@ public class RenderUnit { public final MdxComplexInstance instance; public final MutableGameObject row; public final float[] location = new float[3]; - public float radius; + public float selectionScale; public UnitSoundset soundset; public final MdxModel portraitModel; public int playerIndex; @@ -101,7 +101,7 @@ public class RenderUnit { (row.getFieldAsInteger(green, 0)) / 255f, (row.getFieldAsInteger(blue, 0)) / 255f }); instance.uniformScale(row.getFieldAsFloat(scale, 0)); - this.radius = row.getFieldAsFloat(War3MapViewer.UNIT_SELECT_SCALE, 0) * 36; + this.selectionScale = row.getFieldAsFloat(War3MapViewer.UNIT_SELECT_SCALE, 0); } this.instance = instance; @@ -148,7 +148,9 @@ public class RenderUnit { final float distanceToSimulation = (float) Math.sqrt((simDx * simDx) + (simDy * simDy)); final int speed = this.simulationUnit.getSpeed(); final float speedDelta = speed * deltaTime; - if (distanceToSimulation > speedDelta) { + if ((distanceToSimulation > speedDelta) && (deltaTime < 1.0)) { + // The 1.0 here says that after 1 second of lag, units just teleport to show + // where they actually are this.x += (speedDelta * simDx) / distanceToSimulation; this.y += (speedDelta * simDy) / distanceToSimulation; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java index e423fa8..0b3f0cc 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java @@ -5,6 +5,7 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import com.badlogic.gdx.math.Rectangle; import com.etheller.warsmash.units.manager.MutableObjectData; import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid; @@ -23,18 +24,20 @@ public class CSimulation { private final ProjectileCreator projectileCreator; private int gameTurnTick = 0; private final PathingGrid pathingGrid; + private final CWorldCollision worldCollision; private final CPathfindingProcessor pathfindingProcessor; public CSimulation(final MutableObjectData parsedUnitData, final MutableObjectData parsedAbilityData, - final ProjectileCreator projectileCreator, final PathingGrid pathingGrid) { + final ProjectileCreator projectileCreator, final PathingGrid pathingGrid, final Rectangle entireMapBounds) { this.projectileCreator = projectileCreator; this.pathingGrid = pathingGrid; - this.pathfindingProcessor = new CPathfindingProcessor(pathingGrid); this.unitData = new CUnitData(parsedUnitData); this.abilityData = new CAbilityData(parsedAbilityData); this.units = new ArrayList<>(); this.projectiles = new ArrayList<>(); this.handleIdAllocator = new HandleIdAllocator(); + this.worldCollision = new CWorldCollision(entireMapBounds); + this.pathfindingProcessor = new CPathfindingProcessor(pathingGrid, this.worldCollision); } public CUnitData getUnitData() { @@ -54,6 +57,9 @@ public class CSimulation { final CUnit unit = this.unitData.create(this, this.handleIdAllocator.createId(), playerIndex, typeId, x, y, facing); this.units.add(unit); + if (!unit.getUnitType().isBuilding()) { + this.worldCollision.addUnit(unit); + } return unit; } @@ -67,13 +73,14 @@ public class CSimulation { return this.pathingGrid; } - public List findNaiveSlowPath(final float startX, final float startY, final float goalX, - final float goalY, final PathingGrid.MovementType movementType, final float collisionSize) { - return this.pathfindingProcessor.findNaiveSlowPath(startX, startY, goalX, goalY, movementType, collisionSize); + public List findNaiveSlowPath(final CUnit ignoreIntersectionsWithThisUnit, final float startX, + final float startY, final Point2D.Float goal, final PathingGrid.MovementType movementType, + final float collisionSize) { + return this.pathfindingProcessor.findNaiveSlowPath(ignoreIntersectionsWithThisUnit, startX, startY, goal, + movementType, collisionSize); } public void update() { - this.pathingGrid.resetUnitCollisionPathing(this.units); for (final CUnit unit : this.units) { unit.update(this); } @@ -90,4 +97,8 @@ public class CSimulation { public int getGameTurnTick() { return this.gameTurnTick; } + + public CWorldCollision getWorldCollision() { + return this.worldCollision; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java index 27491b5..b67c0e9 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java @@ -5,6 +5,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Queue; +import com.badlogic.gdx.math.Rectangle; import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; @@ -25,6 +26,8 @@ public class CUnit extends CWidget { private final Queue orderQueue = new LinkedList<>(); private final CUnitType unitType; + private Rectangle collisionRectangle; + public CUnit(final int handleId, final int playerIndex, final float x, final float y, final float life, final War3ID typeId, final float facing, final float mana, final int maximumLife, final int maximumMana, final int speed, final CUnitType unitType) { @@ -156,4 +159,38 @@ public class CUnit extends CWidget { return this.unitType; } + public void setCollisionRectangle(final Rectangle collisionRectangle) { + this.collisionRectangle = collisionRectangle; + } + + public Rectangle getCollisionRectangle() { + return this.collisionRectangle; + } + + public void setX(final float newX, final CWorldCollision collision) { + final float prevX = getX(); + if (!this.unitType.isBuilding()) { + setX(newX); + collision.translate(this, newX - prevX, 0); + } + } + + public void setY(final float newY, final CWorldCollision collision) { + final float prevY = getY(); + if (!this.unitType.isBuilding()) { + setY(newY); + collision.translate(this, 0, newY - prevY); + } + } + + public void setPoint(final float newX, final float newY, final CWorldCollision collision) { + final float prevX = getX(); + final float prevY = getY(); + setX(newX); + setY(newY); + if (!this.unitType.isBuilding()) { + collision.translate(this, newX - prevX, newY - prevY); + } + } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWidget.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWidget.java index b59a667..3250aca 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWidget.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWidget.java @@ -29,11 +29,11 @@ public abstract class CWidget { return this.life; } - public void setX(final float x) { + protected void setX(final float x) { this.x = x; } - public void setY(final float y) { + protected void setY(final float y) { this.y = y; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWorldCollision.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWorldCollision.java new file mode 100644 index 0000000..ae90c5e --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWorldCollision.java @@ -0,0 +1,112 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation; + +import com.badlogic.gdx.math.Rectangle; +import com.etheller.warsmash.util.Quadtree; +import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.MovementType; + +public class CWorldCollision { + private final Quadtree groundUnitCollision; + private final Quadtree airUnitCollision; + private final Quadtree seaUnitCollision; + + public CWorldCollision(final Rectangle entireMapBounds) { + this.groundUnitCollision = new Quadtree<>(entireMapBounds); + this.airUnitCollision = new Quadtree<>(entireMapBounds); + this.seaUnitCollision = new Quadtree<>(entireMapBounds); + } + + public void addUnit(final CUnit unit) { + if (unit.getUnitType().isBuilding()) { + throw new IllegalArgumentException("Cannot add building to the CWorldCollision"); + } + Rectangle bounds = unit.getCollisionRectangle(); + if (bounds == null) { + final float collisionSize = unit.getUnitType().getCollisionSize(); + bounds = new Rectangle(unit.getX() - collisionSize, unit.getY() - collisionSize, collisionSize * 2, + collisionSize * 2); + unit.setCollisionRectangle(bounds); + } + final MovementType movementType = unit.getUnitType().getMovementType(); + if (movementType != null) { + switch (movementType) { + case AMPHIBIOUS: + this.seaUnitCollision.add(unit, bounds); + this.groundUnitCollision.add(unit, bounds); + break; + case FLOAT: + this.seaUnitCollision.add(unit, bounds); + break; + case FLY: + this.airUnitCollision.add(unit, bounds); + break; + default: + case DISABLED: + case FOOT: + case HORSE: + case HOVER: + this.groundUnitCollision.add(unit, bounds); + break; + } + } + } + + public boolean intersectsAnythingOtherThan(final Rectangle newPossibleRectangle, final CUnit sourceUnitToIgnore, + final MovementType movementType) { + if (movementType != null) { + switch (movementType) { + case AMPHIBIOUS: + if (this.seaUnitCollision.intersectsAnythingOtherThan(sourceUnitToIgnore, newPossibleRectangle)) { + return true; + } + if (this.groundUnitCollision.intersectsAnythingOtherThan(sourceUnitToIgnore, newPossibleRectangle)) { + return true; + } + return false; + case FLOAT: + return this.seaUnitCollision.intersectsAnythingOtherThan(sourceUnitToIgnore, newPossibleRectangle); + case FLY: + return this.airUnitCollision.intersectsAnythingOtherThan(sourceUnitToIgnore, newPossibleRectangle); + default: + case DISABLED: + case FOOT: + case HORSE: + case HOVER: + return this.groundUnitCollision.intersectsAnythingOtherThan(sourceUnitToIgnore, newPossibleRectangle); + } + } + return false; + } + + public void translate(final CUnit unit, final float xShift, final float yShift) { + if (unit.getUnitType().isBuilding()) { + throw new IllegalArgumentException("Cannot add building to the CWorldCollision"); + } + final MovementType movementType = unit.getUnitType().getMovementType(); + final Rectangle bounds = unit.getCollisionRectangle(); + if (movementType != null) { + switch (movementType) { + case AMPHIBIOUS: + final float oldX = bounds.x; + final float oldY = bounds.y; + this.seaUnitCollision.translate(unit, bounds, xShift, yShift); + bounds.x = oldX; + bounds.y = oldY; + this.groundUnitCollision.translate(unit, bounds, xShift, yShift); + break; + case FLOAT: + this.seaUnitCollision.translate(unit, bounds, xShift, yShift); + break; + case FLY: + this.airUnitCollision.translate(unit, bounds, xShift, yShift); + break; + default: + case DISABLED: + case FOOT: + case HORSE: + case HOVER: + this.groundUnitCollision.translate(unit, bounds, xShift, yShift); + break; + } + } + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CMoveOrder.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CMoveOrder.java index afdeade..4e770a3 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CMoveOrder.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CMoveOrder.java @@ -1,8 +1,10 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders; import java.awt.geom.Point2D; +import java.awt.geom.Point2D.Float; import java.util.List; +import com.badlogic.gdx.math.Rectangle; import com.etheller.warsmash.util.WarsmashConstants; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid; @@ -10,24 +12,24 @@ import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.Moveme import com.etheller.warsmash.viewer5.handlers.w3x.simulation.COrder; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWorldCollision; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityMove; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.pathing.CPathfindingProcessor; public class CMoveOrder implements COrder { + private static final Rectangle tempRect = new Rectangle(); private final CUnit unit; - private final float targetX; - private final float targetY; private boolean wasWithinPropWindow = false; private List path = null; private final CPathfindingProcessor.GridMapping gridMapping; + private final Point2D.Float target; public CMoveOrder(final CUnit unit, final float targetX, final float targetY) { this.unit = unit; - this.targetX = targetX; - this.targetY = targetY; this.gridMapping = CPathfindingProcessor.isCollisionSizeBetterSuitedForCorners( unit.getUnitType().getCollisionSize()) ? CPathfindingProcessor.GridMapping.CORNERS : CPathfindingProcessor.GridMapping.CELLS; + this.target = new Point2D.Float(targetX, targetY); } @Override @@ -37,175 +39,198 @@ public class CMoveOrder implements COrder { final MovementType movementType = this.unit.getUnitType().getMovementType(); final PathingGrid pathingGrid = simulation.getPathingGrid(); + final CWorldCollision worldCollision = simulation.getWorldCollision(); final float collisionSize = this.unit.getUnitType().getCollisionSize(); - pathingGrid.setUnitIgnore(prevX, prevY, collisionSize, true); - try { - final float startFloatingX = prevX; - final float startFloatingY = prevY; - final float goalFloatingX = this.targetX; - final float goalFloatingY = this.targetY; - if (this.path == null) { - this.path = simulation.findNaiveSlowPath(startFloatingX, startFloatingY, goalFloatingX, goalFloatingY, - movementType, collisionSize); - System.out.println("init path " + this.path); - // check for smoothing - if (!this.path.isEmpty()) { - float lastX = startFloatingX; - float lastY = startFloatingY; - float smoothingGroupStartX = startFloatingX; - float smoothingGroupStartY = startFloatingY; - final Point2D.Float firstPathElement = this.path.get(0); - double totalPathDistance = firstPathElement.distance(lastX, lastY); - lastX = firstPathElement.x; - lastY = firstPathElement.y; - int smoothingStartIndex = -1; - for (int i = 0; i < (this.path.size() - 1); i++) { - final Point2D.Float nextPossiblePathElement = this.path.get(i + 1); - totalPathDistance += nextPossiblePathElement.distance(lastX, lastY); - if ((totalPathDistance < (1.15 - * nextPossiblePathElement.distance(smoothingGroupStartX, smoothingGroupStartY))) - && pathingGrid.isPathable((smoothingGroupStartX + nextPossiblePathElement.x) / 2, - (smoothingGroupStartY + nextPossiblePathElement.y) / 2, movementType)) { - if (smoothingStartIndex == -1) { - smoothingStartIndex = i; - } - } - else { - if (smoothingStartIndex != -1) { - for (int j = smoothingStartIndex; j < i; j++) { - this.path.remove(j); - } - i = smoothingStartIndex; - } - smoothingStartIndex = -1; - final Point2D.Float smoothGroupNext = this.path.get(i); - smoothingGroupStartX = smoothGroupNext.x; - smoothingGroupStartY = smoothGroupNext.y; - totalPathDistance = nextPossiblePathElement.distance(smoothGroupNext); - } - lastX = nextPossiblePathElement.x; - lastY = nextPossiblePathElement.y; - } - if (smoothingStartIndex != -1) { - for (int j = smoothingStartIndex; j < (this.path.size() - 1); j++) { - final Point2D.Float removed = this.path.remove(j); - } - } - } - } - float currentTargetX; - float currentTargetY; - if (this.path.size() < 2) { - currentTargetX = this.targetX; - currentTargetY = this.targetY; - } - else { - final Point2D.Float nextPathElement = this.path.get(0); - currentTargetX = nextPathElement.x; - currentTargetY = nextPathElement.y; - } - - float deltaX = currentTargetX - prevX; - float deltaY = currentTargetY - prevY; - final double goalAngleRad = Math.atan2(deltaY, deltaX); - float goalAngle = (float) Math.toDegrees(goalAngleRad); - if (goalAngle < 0) { - goalAngle += 360; - } - float facing = this.unit.getFacing(); - float delta = goalAngle - facing; - final float propulsionWindow = simulation.getUnitData().getPropulsionWindow(this.unit.getTypeId()); - final float turnRate = simulation.getUnitData().getTurnRate(this.unit.getTypeId()); - final int speed = this.unit.getSpeed(); - - if (delta < -180) { - delta = 360 + delta; - } - if (delta > 180) { - delta = -360 + delta; - } - final float absDelta = Math.abs(delta); - - if ((absDelta <= 1.0) && (absDelta != 0)) { - this.unit.setFacing(goalAngle); - } - else { - float angleToAdd = Math.signum(delta) * (float) Math.toDegrees(turnRate); - if (absDelta < Math.abs(angleToAdd)) { - angleToAdd = delta; - } - facing += angleToAdd; - this.unit.setFacing(facing); - } - if (absDelta < propulsionWindow) { - final float speedTick = speed * WarsmashConstants.SIMULATION_STEP_TIME; - double continueDistance = speedTick; - do { - boolean done; - float nextX, nextY; - final double travelDistance = Math.sqrt((deltaX * deltaX) + (deltaY * deltaY)); - if (travelDistance <= continueDistance) { - nextX = currentTargetX; - nextY = currentTargetY; - continueDistance = continueDistance - travelDistance; - done = true; - } - else { - final double radianFacing = Math.toRadians(facing); - nextX = (prevX + (float) (Math.cos(radianFacing) * continueDistance)); - nextY = (prevY + (float) (Math.sin(radianFacing) * continueDistance)); - continueDistance = 0; - done = (this.gridMapping.getX(pathingGrid, nextX) == this.gridMapping.getX(pathingGrid, - currentTargetX)) - && (this.gridMapping.getY(pathingGrid, nextY) == this.gridMapping.getY(pathingGrid, - currentTargetY)); - } - if (pathingGrid.isPathable(nextX, nextY, movementType, ((int) collisionSize / 16) * 16)) { - this.unit.setX(nextX); - this.unit.setY(nextY); - if (done) { - if (this.path.isEmpty()) { - return true; - } - else { - this.path.remove(0); - if (this.path.size() < 2) { - currentTargetX = this.targetX; - currentTargetY = this.targetY; - } - else { - final Point2D.Float firstPathElement = this.path.get(0); - currentTargetX = firstPathElement.x; - currentTargetY = firstPathElement.y; - } - deltaY = currentTargetY - prevY; - deltaX = currentTargetX - prevX; - } + final float startFloatingX = prevX; + final float startFloatingY = prevY; + if (this.path == null) { + this.path = simulation.findNaiveSlowPath(this.unit, startFloatingX, startFloatingY, this.target, + movementType, collisionSize); + System.out.println("init path " + this.path); + // check for smoothing + if (!this.path.isEmpty()) { + this.path.add(this.target); + float lastX = startFloatingX; + float lastY = startFloatingY; + float smoothingGroupStartX = startFloatingX; + float smoothingGroupStartY = startFloatingY; + final Point2D.Float firstPathElement = this.path.get(0); + double totalPathDistance = firstPathElement.distance(lastX, lastY); + lastX = firstPathElement.x; + lastY = firstPathElement.y; + int smoothingStartIndex = -1; + for (int i = 0; i < (this.path.size() - 1); i++) { + final Point2D.Float nextPossiblePathElement = this.path.get(i + 1); + totalPathDistance += nextPossiblePathElement.distance(lastX, lastY); + if ((totalPathDistance < (1.15 + * nextPossiblePathElement.distance(smoothingGroupStartX, smoothingGroupStartY))) + && pathingGrid.isPathable((smoothingGroupStartX + nextPossiblePathElement.x) / 2, + (smoothingGroupStartY + nextPossiblePathElement.y) / 2, movementType)) { + if (smoothingStartIndex == -1) { + smoothingStartIndex = i; } } else { - this.path = simulation.findNaiveSlowPath(startFloatingX, startFloatingY, goalFloatingX, - goalFloatingY, movementType, collisionSize); - System.out.println("new path " + this.path); + if (smoothingStartIndex != -1) { + for (int j = i - 1; j >= smoothingStartIndex; j--) { + this.path.remove(j); + } + i = smoothingStartIndex; + } + smoothingStartIndex = -1; + final Point2D.Float smoothGroupNext = this.path.get(i); + smoothingGroupStartX = smoothGroupNext.x; + smoothingGroupStartY = smoothGroupNext.y; + totalPathDistance = nextPossiblePathElement.distance(smoothGroupNext); + } + lastX = nextPossiblePathElement.x; + lastY = nextPossiblePathElement.y; + } + if (smoothingStartIndex != -1) { + for (int j = smoothingStartIndex; j < (this.path.size() - 1); j++) { + final Point2D.Float removed = this.path.remove(j); + } + } + } + } + float currentTargetX; + float currentTargetY; + if (this.path.isEmpty()) { + currentTargetX = this.target.x; + currentTargetY = this.target.y; + } + else { + final Point2D.Float nextPathElement = this.path.get(0); + currentTargetX = nextPathElement.x; + currentTargetY = nextPathElement.y; + } + + float deltaX = currentTargetX - prevX; + float deltaY = currentTargetY - prevY; + double goalAngleRad = Math.atan2(deltaY, deltaX); + float goalAngle = (float) Math.toDegrees(goalAngleRad); + if (goalAngle < 0) { + goalAngle += 360; + } + float facing = this.unit.getFacing(); + float delta = goalAngle - facing; + final float propulsionWindow = simulation.getUnitData().getPropulsionWindow(this.unit.getTypeId()); + final float turnRate = simulation.getUnitData().getTurnRate(this.unit.getTypeId()); + final int speed = this.unit.getSpeed(); + + if (delta < -180) { + delta = 360 + delta; + } + if (delta > 180) { + delta = -360 + delta; + } + float absDelta = Math.abs(delta); + + if ((absDelta <= 1.0) && (absDelta != 0)) { + this.unit.setFacing(goalAngle); + } + else { + float angleToAdd = Math.signum(delta) * (float) Math.toDegrees(turnRate); + if (absDelta < Math.abs(angleToAdd)) { + angleToAdd = delta; + } + facing += angleToAdd; + this.unit.setFacing(facing); + } + if (absDelta < propulsionWindow) { + final float speedTick = speed * WarsmashConstants.SIMULATION_STEP_TIME; + double continueDistance = speedTick; + do { + boolean done; + float nextX, nextY; + final double travelDistance = Math.sqrt((deltaX * deltaX) + (deltaY * deltaY)); + if (travelDistance <= continueDistance) { + nextX = currentTargetX; + nextY = currentTargetY; + continueDistance = continueDistance - travelDistance; + done = true; + } + else { + final double radianFacing = Math.toRadians(facing); + nextX = (prevX + (float) (Math.cos(radianFacing) * continueDistance)); + nextY = (prevY + (float) (Math.sin(radianFacing) * continueDistance)); + continueDistance = 0; +// done = (this.gridMapping.getX(pathingGrid, nextX) == this.gridMapping.getX(pathingGrid, +// currentTargetX)) +// && (this.gridMapping.getY(pathingGrid, nextY) == this.gridMapping.getY(pathingGrid, +// currentTargetY)); + done = false; + } + tempRect.set(this.unit.getCollisionRectangle()); + tempRect.setCenter(nextX, nextY); + if (pathingGrid.isPathable(nextX, nextY, movementType, collisionSize)// ((int) collisionSize / 16) * 16 + && !worldCollision.intersectsAnythingOtherThan(tempRect, this.unit, movementType)) { + this.unit.setPoint(nextX, nextY, worldCollision); + if (done) { if (this.path.isEmpty()) { return true; } - } - this.wasWithinPropWindow = true; - } - while (continueDistance > 0); - } - else { - // If this happens, the unit is facing the wrong way, and has to turn before - // moving. - this.wasWithinPropWindow = false; - } + else { + final Float removed = this.path.remove(0); + System.out.println( + "We think we reached " + removed + " because are at " + nextX + "," + nextY); + if (this.path.isEmpty()) { + currentTargetX = this.target.x; + currentTargetY = this.target.y; + } + else { + final Point2D.Float firstPathElement = this.path.get(0); + currentTargetX = firstPathElement.x; + currentTargetY = firstPathElement.y; + } + deltaY = currentTargetY - prevY; + deltaX = currentTargetX - prevX; + System.out.println("new target: " + currentTargetX + "," + currentTargetY); + System.out.println("new delta: " + deltaX + "," + deltaY); + goalAngleRad = Math.atan2(deltaY, deltaX); + goalAngle = (float) Math.toDegrees(goalAngleRad); + if (goalAngle < 0) { + goalAngle += 360; + } + facing = this.unit.getFacing(); + delta = goalAngle - facing; - return false; + if (delta < -180) { + delta = 360 + delta; + } + if (delta > 180) { + delta = -360 + delta; + } + absDelta = Math.abs(delta); + if (absDelta >= propulsionWindow) { + this.wasWithinPropWindow = false; + return false; + } + } + } + } + else { + this.path = simulation.findNaiveSlowPath(this.unit, startFloatingX, startFloatingY, this.target, + movementType, collisionSize); + System.out.println("new path " + this.path); + if (this.path.isEmpty()) { + return true; + } + else { + this.path.add(this.target); + } + } + this.wasWithinPropWindow = true; + } + while (continueDistance > 0); } - finally { - pathingGrid.setUnitIgnore(prevX, prevY, collisionSize, false); + else { + // If this happens, the unit is facing the wrong way, and has to turn before + // moving. + this.wasWithinPropWindow = false; } + + return false; } @Override diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/pathing/CPathfindingProcessor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/pathing/CPathfindingProcessor.java index 6e5276e..5c79c45 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/pathing/CPathfindingProcessor.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/pathing/CPathfindingProcessor.java @@ -7,16 +7,22 @@ import java.util.LinkedList; import java.util.List; import java.util.PriorityQueue; +import com.badlogic.gdx.math.Rectangle; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWorldCollision; public class CPathfindingProcessor { + private static final Rectangle tempRect = new Rectangle(); private final PathingGrid pathingGrid; + private final CWorldCollision worldCollision; private final Node[][] nodes; private final Node[][] cornerNodes; private Node goal; - public CPathfindingProcessor(final PathingGrid pathingGrid) { + public CPathfindingProcessor(final PathingGrid pathingGrid, final CWorldCollision worldCollision) { this.pathingGrid = pathingGrid; + this.worldCollision = worldCollision; this.nodes = new Node[pathingGrid.getHeight()][pathingGrid.getWidth()]; this.cornerNodes = new Node[pathingGrid.getHeight() + 1][pathingGrid.getWidth() + 1]; for (int i = 0; i < this.nodes.length; i++) { @@ -46,12 +52,19 @@ public class CPathfindingProcessor { * @param goal * @return */ - public List findNaiveSlowPath(final float startX, final float startY, final float goalX, - final float goalY, final PathingGrid.MovementType movementType, final float collisionSize) { + public List findNaiveSlowPath(final CUnit ignoreIntersectionsWithThisUnit, final float startX, + final float startY, final Point2D.Float goal, final PathingGrid.MovementType movementType, + final float collisionSize) { + final float goalX = goal.x; + final float goalY = goal.y; + if (!this.pathingGrid.isPathable(goalX, goalY, movementType, collisionSize)) { + return Collections.emptyList(); + } System.out.println("beginning findNaiveSlowPath for " + startX + "," + startY + "," + goalX + "," + goalY); if ((startX == goalX) && (startY == goalY)) { return Collections.emptyList(); } + tempRect.set(0, 0, collisionSize * 2, collisionSize * 2); Node[][] searchGraph; GridMapping gridMapping; if (isCollisionSizeBetterSuitedForCorners(collisionSize)) { @@ -65,47 +78,116 @@ public class CPathfindingProcessor { System.out.println("using cells"); } this.goal = searchGraph[gridMapping.getY(this.pathingGrid, goalY)][gridMapping.getX(this.pathingGrid, goalX)]; - final Node start = searchGraph[gridMapping.getY(this.pathingGrid, startY)][gridMapping.getX(this.pathingGrid, - startX)]; + final int startGridY = gridMapping.getY(this.pathingGrid, startY); + final int startGridX = gridMapping.getX(this.pathingGrid, startX); for (int i = 0; i < searchGraph.length; i++) { for (int j = 0; j < searchGraph[i].length; j++) { final Node node = searchGraph[i][j]; node.g = Float.POSITIVE_INFINITY; node.f = Float.POSITIVE_INFINITY; node.cameFrom = null; + node.cameFromDirection = null; } } - start.g = 0; - start.f = h(start); final PriorityQueue openSet = new PriorityQueue<>(new Comparator() { @Override public int compare(final Node a, final Node b) { return Double.compare(f(a), f(b)); } }); - openSet.add(start); + + final Node start = searchGraph[startGridY][startGridX]; + int startGridMinX; + int startGridMinY; + int startGridMaxX; + int startGridMaxY; + if (startX > start.point.x) { + startGridMinX = startGridX; + startGridMaxX = startGridX + 1; + } + else if (startX < start.point.x) { + startGridMinX = startGridX - 1; + startGridMaxX = startGridX; + } + else { + startGridMinX = startGridX; + startGridMaxX = startGridX; + } + if (startY > start.point.y) { + startGridMinY = startGridY; + startGridMaxY = startGridY + 1; + } + else if (startY < start.point.y) { + startGridMinY = startGridY - 1; + startGridMaxY = startGridY; + } + else { + startGridMinY = startGridY; + startGridMaxY = startGridY; + } + for (int cellX = startGridMinX; cellX <= startGridMaxX; cellX++) { + for (int cellY = startGridMinY; cellY <= startGridMaxY; cellY++) { + if ((cellX >= 0) && (cellX < this.pathingGrid.getWidth()) && (cellY >= 0) + && (cellY < this.pathingGrid.getHeight())) { + final Node possibleNode = searchGraph[cellY][cellX]; + final float x = possibleNode.point.x; + final float y = possibleNode.point.y; + if (pathableBetween(ignoreIntersectionsWithThisUnit, startX, startY, movementType, collisionSize, x, + y)) { + + final double tentativeScore = possibleNode.point.distance(startX, startY); + possibleNode.g = tentativeScore; + possibleNode.f = tentativeScore + h(possibleNode); + openSet.add(possibleNode); + + } + } + } + } while (!openSet.isEmpty()) { Node current = openSet.poll(); if (current == this.goal) { final LinkedList totalPath = new LinkedList<>(); Direction lastCameFromDirection = null; - while (current.cameFrom != null) { - if ((lastCameFromDirection == null) || (current.cameFromDirection != lastCameFromDirection)) { - totalPath.addFirst(current.point); - lastCameFromDirection = current.cameFromDirection; - } + + if ((current.cameFrom != null) + && pathableBetween(ignoreIntersectionsWithThisUnit, current.cameFrom.point.x, + current.cameFrom.point.y, movementType, collisionSize, goalX, goalY)) { + // do some basic smoothing to walk straight to the goal if it is not obstructed, + // skipping the last grid location + totalPath.addFirst(goal); current = current.cameFrom; } + else { + totalPath.addFirst(goal); + totalPath.addFirst(current.point); + } + lastCameFromDirection = current.cameFromDirection; + Node lastNode = null; + while (current.cameFrom != null) { + lastNode = current; + current = current.cameFrom; + if ((lastCameFromDirection == null) || (current.cameFromDirection != lastCameFromDirection) + || (current.cameFromDirection == null)) { + if ((current.cameFromDirection != null) || (lastNode == null) + || !pathableBetween(ignoreIntersectionsWithThisUnit, startX, startY, movementType, + collisionSize, lastNode.point.x, lastNode.point.y)) { + // Add the point if it's not the first one, or if we can only complete + // the journey by specifically walking to the first one + totalPath.addFirst(current.point); + lastCameFromDirection = current.cameFromDirection; + } + } + } return totalPath; } for (final Direction direction : Direction.VALUES) { final float x = current.point.x + (direction.xOffset * 32); final float y = current.point.y + (direction.yOffset * 32); - if (this.pathingGrid.contains(x, y) && this.pathingGrid.isPathable(x, y, movementType, collisionSize) - && this.pathingGrid.isPathable(current.point.x, y, movementType, collisionSize) - && this.pathingGrid.isPathable(x, current.point.y, movementType, collisionSize)) { + if (this.pathingGrid.contains(x, y) && pathableBetween(ignoreIntersectionsWithThisUnit, current.point.x, + current.point.y, movementType, collisionSize, x, y)) { double turnCost; if ((current.cameFromDirection != null) && (direction != current.cameFromDirection)) { turnCost = 0.25; @@ -131,6 +213,22 @@ public class CPathfindingProcessor { return Collections.emptyList(); } + private boolean pathableBetween(final CUnit ignoreIntersectionsWithThisUnit, final float startX, final float startY, + final PathingGrid.MovementType movementType, final float collisionSize, final float x, final float y) { + return this.pathingGrid.isPathable(x, y, movementType, collisionSize) + && this.pathingGrid.isPathable(startX, y, movementType, collisionSize) + && this.pathingGrid.isPathable(x, startY, movementType, collisionSize) + && isPathableDynamically(x, y, ignoreIntersectionsWithThisUnit, movementType) + && isPathableDynamically(x, startY, ignoreIntersectionsWithThisUnit, movementType) + && isPathableDynamically(startX, y, ignoreIntersectionsWithThisUnit, movementType); + } + + private boolean isPathableDynamically(final float x, final float y, final CUnit ignoreIntersectionsWithThisUnit, + final PathingGrid.MovementType movementType) { + return !this.worldCollision.intersectsAnythingOtherThan(tempRect.setCenter(x, y), + ignoreIntersectionsWithThisUnit, movementType); + } + public static boolean isCollisionSizeBetterSuitedForCorners(final float collisionSize) { return (((2 * (int) collisionSize) / 32) % 2) == 1; } diff --git a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java index e6362ad..2404830 100644 --- a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java +++ b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java @@ -5,6 +5,7 @@ import org.lwjgl.opengl.GL31; import org.lwjgl.opengl.GL32; import org.lwjgl.opengl.GL33; +import com.badlogic.gdx.Graphics.DisplayMode; import com.badlogic.gdx.audio.Sound; import com.badlogic.gdx.backends.lwjgl.LwjglApplication; import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration; @@ -67,10 +68,10 @@ public class DesktopLauncher { config.gles30ContextMajorVersion = 3; config.gles30ContextMinorVersion = 3; config.samples = 16; -// config.fullscreen = true; -// final DisplayMode desktopDisplayMode = LwjglApplicationConfiguration.getDesktopDisplayMode(); -// config.width = desktopDisplayMode.width; -// config.height = desktopDisplayMode.height; + config.fullscreen = true; + final DisplayMode desktopDisplayMode = LwjglApplicationConfiguration.getDesktopDisplayMode(); + config.width = desktopDisplayMode.width; + config.height = desktopDisplayMode.height; new LwjglApplication(new WarsmashGdxMapGame(), config); } } diff --git a/jassparser/src/com/etheller/interpreter/ast/expression/ArrayRefJassExpression.java b/jassparser/src/com/etheller/interpreter/ast/expression/ArrayRefJassExpression.java index 1ae050e..175cc19 100644 --- a/jassparser/src/com/etheller/interpreter/ast/expression/ArrayRefJassExpression.java +++ b/jassparser/src/com/etheller/interpreter/ast/expression/ArrayRefJassExpression.java @@ -3,6 +3,7 @@ package com.etheller.interpreter.ast.expression; import com.etheller.interpreter.ast.Assignable; import com.etheller.interpreter.ast.scope.GlobalScope; import com.etheller.interpreter.ast.scope.LocalScope; +import com.etheller.interpreter.ast.scope.TriggerExecutionScope; import com.etheller.interpreter.ast.value.ArrayJassValue; import com.etheller.interpreter.ast.value.JassValue; import com.etheller.interpreter.ast.value.visitor.ArrayJassValueVisitor; @@ -18,11 +19,12 @@ public class ArrayRefJassExpression implements JassExpression { } @Override - public JassValue evaluate(final GlobalScope globalScope, final LocalScope localScope) { - Assignable variable = localScope.getAssignableLocal(identifier); - final JassValue index = indexExpression.evaluate(globalScope, localScope); + public JassValue evaluate(final GlobalScope globalScope, final LocalScope localScope, + final TriggerExecutionScope triggerScope) { + Assignable variable = localScope.getAssignableLocal(this.identifier); + final JassValue index = this.indexExpression.evaluate(globalScope, localScope, triggerScope); if (variable == null) { - variable = globalScope.getAssignableGlobal(identifier); + variable = globalScope.getAssignableGlobal(this.identifier); } if (variable.getValue() == null) { throw new RuntimeException("Unable to use subscript on uninitialized variable"); @@ -30,7 +32,8 @@ public class ArrayRefJassExpression implements JassExpression { final ArrayJassValue arrayValue = variable.getValue().visit(ArrayJassValueVisitor.getInstance()); if (arrayValue != null) { return arrayValue.get(index.visit(IntegerJassValueVisitor.getInstance())); - } else { + } + else { throw new RuntimeException("Not an array"); } } diff --git a/jassparser/src/com/etheller/interpreter/ast/expression/FunctionCallJassExpression.java b/jassparser/src/com/etheller/interpreter/ast/expression/FunctionCallJassExpression.java index 9cdece9..7d87b6a 100644 --- a/jassparser/src/com/etheller/interpreter/ast/expression/FunctionCallJassExpression.java +++ b/jassparser/src/com/etheller/interpreter/ast/expression/FunctionCallJassExpression.java @@ -6,6 +6,7 @@ import java.util.List; import com.etheller.interpreter.ast.function.JassFunction; import com.etheller.interpreter.ast.scope.GlobalScope; import com.etheller.interpreter.ast.scope.LocalScope; +import com.etheller.interpreter.ast.scope.TriggerExecutionScope; import com.etheller.interpreter.ast.value.JassValue; public class FunctionCallJassExpression implements JassExpression { @@ -18,17 +19,18 @@ public class FunctionCallJassExpression implements JassExpression { } @Override - public JassValue evaluate(final GlobalScope globalScope, final LocalScope localScope) { - final JassFunction functionByName = globalScope.getFunctionByName(functionName); + public JassValue evaluate(final GlobalScope globalScope, final LocalScope localScope, + final TriggerExecutionScope triggerScope) { + final JassFunction functionByName = globalScope.getFunctionByName(this.functionName); if (functionByName == null) { - throw new RuntimeException("Undefined function: " + functionName); + throw new RuntimeException("Undefined function: " + this.functionName); } final List evaluatedExpressions = new ArrayList<>(); - for (final JassExpression expr : arguments) { - final JassValue evaluatedExpression = expr.evaluate(globalScope, localScope); + for (final JassExpression expr : this.arguments) { + final JassValue evaluatedExpression = expr.evaluate(globalScope, localScope, triggerScope); evaluatedExpressions.add(evaluatedExpression); } - return functionByName.call(evaluatedExpressions, globalScope); + return functionByName.call(evaluatedExpressions, globalScope, triggerScope); } } diff --git a/jassparser/src/com/etheller/interpreter/ast/expression/FunctionReferenceJassExpression.java b/jassparser/src/com/etheller/interpreter/ast/expression/FunctionReferenceJassExpression.java index 0e9a2b9..3fac3ff 100644 --- a/jassparser/src/com/etheller/interpreter/ast/expression/FunctionReferenceJassExpression.java +++ b/jassparser/src/com/etheller/interpreter/ast/expression/FunctionReferenceJassExpression.java @@ -3,6 +3,7 @@ package com.etheller.interpreter.ast.expression; import com.etheller.interpreter.ast.function.JassFunction; import com.etheller.interpreter.ast.scope.GlobalScope; import com.etheller.interpreter.ast.scope.LocalScope; +import com.etheller.interpreter.ast.scope.TriggerExecutionScope; import com.etheller.interpreter.ast.value.CodeJassValue; import com.etheller.interpreter.ast.value.JassValue; @@ -14,10 +15,11 @@ public class FunctionReferenceJassExpression implements JassExpression { } @Override - public JassValue evaluate(final GlobalScope globalScope, final LocalScope localScope) { - final JassFunction functionByName = globalScope.getFunctionByName(identifier); + public JassValue evaluate(final GlobalScope globalScope, final LocalScope localScope, + final TriggerExecutionScope triggerScope) { + final JassFunction functionByName = globalScope.getFunctionByName(this.identifier); if (functionByName == null) { - throw new RuntimeException("Unable to find function: " + identifier); + throw new RuntimeException("Unable to find function: " + this.identifier); } return new CodeJassValue(functionByName); } diff --git a/jassparser/src/com/etheller/interpreter/ast/expression/JassExpression.java b/jassparser/src/com/etheller/interpreter/ast/expression/JassExpression.java index edc47af..d9e16ea 100644 --- a/jassparser/src/com/etheller/interpreter/ast/expression/JassExpression.java +++ b/jassparser/src/com/etheller/interpreter/ast/expression/JassExpression.java @@ -2,8 +2,9 @@ package com.etheller.interpreter.ast.expression; import com.etheller.interpreter.ast.scope.GlobalScope; import com.etheller.interpreter.ast.scope.LocalScope; +import com.etheller.interpreter.ast.scope.TriggerExecutionScope; import com.etheller.interpreter.ast.value.JassValue; public interface JassExpression { - JassValue evaluate(GlobalScope globalScope, LocalScope localScope); + JassValue evaluate(GlobalScope globalScope, LocalScope localScope, TriggerExecutionScope triggerScope); } diff --git a/jassparser/src/com/etheller/interpreter/ast/expression/LiteralJassExpression.java b/jassparser/src/com/etheller/interpreter/ast/expression/LiteralJassExpression.java index c989de6..426c3d3 100644 --- a/jassparser/src/com/etheller/interpreter/ast/expression/LiteralJassExpression.java +++ b/jassparser/src/com/etheller/interpreter/ast/expression/LiteralJassExpression.java @@ -2,6 +2,7 @@ package com.etheller.interpreter.ast.expression; import com.etheller.interpreter.ast.scope.GlobalScope; import com.etheller.interpreter.ast.scope.LocalScope; +import com.etheller.interpreter.ast.scope.TriggerExecutionScope; import com.etheller.interpreter.ast.value.JassValue; public class LiteralJassExpression implements JassExpression { @@ -12,8 +13,9 @@ public class LiteralJassExpression implements JassExpression { } @Override - public JassValue evaluate(final GlobalScope globalScope, final LocalScope localScope) { - return value; + public JassValue evaluate(final GlobalScope globalScope, final LocalScope localScope, + final TriggerExecutionScope triggerScope) { + return this.value; } } diff --git a/jassparser/src/com/etheller/interpreter/ast/expression/NotJassExpression.java b/jassparser/src/com/etheller/interpreter/ast/expression/NotJassExpression.java index 37f16d0..eff46df 100644 --- a/jassparser/src/com/etheller/interpreter/ast/expression/NotJassExpression.java +++ b/jassparser/src/com/etheller/interpreter/ast/expression/NotJassExpression.java @@ -2,8 +2,8 @@ package com.etheller.interpreter.ast.expression; import com.etheller.interpreter.ast.scope.GlobalScope; import com.etheller.interpreter.ast.scope.LocalScope; +import com.etheller.interpreter.ast.scope.TriggerExecutionScope; import com.etheller.interpreter.ast.value.JassValue; -import com.etheller.interpreter.ast.value.visitor.BooleanJassValueVisitor; import com.etheller.interpreter.ast.value.visitor.NotJassValueVisitor; public class NotJassExpression implements JassExpression { @@ -14,7 +14,8 @@ public class NotJassExpression implements JassExpression { } @Override - public JassValue evaluate(final GlobalScope globalScope, final LocalScope localScope) { - return this.expression.evaluate(globalScope, localScope).visit(NotJassValueVisitor.getInstance()); + public JassValue evaluate(final GlobalScope globalScope, final LocalScope localScope, + final TriggerExecutionScope triggerScope) { + return this.expression.evaluate(globalScope, localScope, triggerScope).visit(NotJassValueVisitor.getInstance()); } } diff --git a/jassparser/src/com/etheller/interpreter/ast/expression/ReferenceJassExpression.java b/jassparser/src/com/etheller/interpreter/ast/expression/ReferenceJassExpression.java index b952a4e..2df0831 100644 --- a/jassparser/src/com/etheller/interpreter/ast/expression/ReferenceJassExpression.java +++ b/jassparser/src/com/etheller/interpreter/ast/expression/ReferenceJassExpression.java @@ -3,6 +3,7 @@ package com.etheller.interpreter.ast.expression; import com.etheller.interpreter.ast.Assignable; import com.etheller.interpreter.ast.scope.GlobalScope; import com.etheller.interpreter.ast.scope.LocalScope; +import com.etheller.interpreter.ast.scope.TriggerExecutionScope; import com.etheller.interpreter.ast.value.JassValue; public class ReferenceJassExpression implements JassExpression { @@ -13,10 +14,11 @@ public class ReferenceJassExpression implements JassExpression { } @Override - public JassValue evaluate(final GlobalScope globalScope, final LocalScope localScope) { - final Assignable local = localScope.getAssignableLocal(identifier); + public JassValue evaluate(final GlobalScope globalScope, final LocalScope localScope, + final TriggerExecutionScope triggerScope) { + final Assignable local = localScope.getAssignableLocal(this.identifier); if (local == null) { - return globalScope.getGlobal(identifier); + return globalScope.getGlobal(this.identifier); } return local.getValue(); } diff --git a/jassparser/src/com/etheller/interpreter/ast/function/AbstractJassFunction.java b/jassparser/src/com/etheller/interpreter/ast/function/AbstractJassFunction.java index ba8a557..af9107f 100644 --- a/jassparser/src/com/etheller/interpreter/ast/function/AbstractJassFunction.java +++ b/jassparser/src/com/etheller/interpreter/ast/function/AbstractJassFunction.java @@ -4,6 +4,7 @@ import java.util.List; import com.etheller.interpreter.ast.scope.GlobalScope; import com.etheller.interpreter.ast.scope.LocalScope; +import com.etheller.interpreter.ast.scope.TriggerExecutionScope; import com.etheller.interpreter.ast.value.JassType; import com.etheller.interpreter.ast.value.JassValue; import com.etheller.interpreter.ast.value.visitor.JassTypeGettingValueVisitor; @@ -24,7 +25,8 @@ public abstract class AbstractJassFunction implements JassFunction { } @Override - public final JassValue call(final List arguments, final GlobalScope globalScope) { + public final JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { if (arguments.size() != this.parameters.size()) { throw new RuntimeException("Invalid number of arguments passed to function"); } @@ -41,9 +43,9 @@ public abstract class AbstractJassFunction implements JassFunction { } localScope.createLocal(parameter.getIdentifier(), parameter.getType(), argument); } - return innerCall(arguments, globalScope, localScope); + return innerCall(arguments, globalScope, triggerScope, localScope); } protected abstract JassValue innerCall(final List arguments, final GlobalScope globalScope, - final LocalScope localScope); + TriggerExecutionScope triggerScope, final LocalScope localScope); } diff --git a/jassparser/src/com/etheller/interpreter/ast/function/JassFunction.java b/jassparser/src/com/etheller/interpreter/ast/function/JassFunction.java index b5b2ec0..b4878ae 100644 --- a/jassparser/src/com/etheller/interpreter/ast/function/JassFunction.java +++ b/jassparser/src/com/etheller/interpreter/ast/function/JassFunction.java @@ -3,8 +3,9 @@ package com.etheller.interpreter.ast.function; import java.util.List; import com.etheller.interpreter.ast.scope.GlobalScope; +import com.etheller.interpreter.ast.scope.TriggerExecutionScope; import com.etheller.interpreter.ast.value.JassValue; public interface JassFunction { - JassValue call(List arguments, GlobalScope globalScope); + JassValue call(List arguments, GlobalScope globalScope, TriggerExecutionScope triggerScope); } diff --git a/jassparser/src/com/etheller/interpreter/ast/function/NativeJassFunction.java b/jassparser/src/com/etheller/interpreter/ast/function/NativeJassFunction.java index 14605bf..1cfbf0d 100644 --- a/jassparser/src/com/etheller/interpreter/ast/function/NativeJassFunction.java +++ b/jassparser/src/com/etheller/interpreter/ast/function/NativeJassFunction.java @@ -4,6 +4,7 @@ import java.util.List; import com.etheller.interpreter.ast.scope.GlobalScope; import com.etheller.interpreter.ast.scope.LocalScope; +import com.etheller.interpreter.ast.scope.TriggerExecutionScope; import com.etheller.interpreter.ast.value.JassType; import com.etheller.interpreter.ast.value.JassValue; @@ -15,12 +16,12 @@ public class NativeJassFunction extends AbstractJassFunction { final JassFunction impl) { super(parameters, returnType); this.name = name; - implementation = impl; + this.implementation = impl; } @Override protected JassValue innerCall(final List arguments, final GlobalScope globalScope, - final LocalScope localScope) { - return implementation.call(arguments, globalScope); + final TriggerExecutionScope triggerScope, final LocalScope localScope) { + return this.implementation.call(arguments, globalScope, triggerScope); } } diff --git a/jassparser/src/com/etheller/interpreter/ast/function/UserJassFunction.java b/jassparser/src/com/etheller/interpreter/ast/function/UserJassFunction.java index 105a6da..92dff1f 100644 --- a/jassparser/src/com/etheller/interpreter/ast/function/UserJassFunction.java +++ b/jassparser/src/com/etheller/interpreter/ast/function/UserJassFunction.java @@ -4,6 +4,7 @@ import java.util.List; import com.etheller.interpreter.ast.scope.GlobalScope; import com.etheller.interpreter.ast.scope.LocalScope; +import com.etheller.interpreter.ast.scope.TriggerExecutionScope; import com.etheller.interpreter.ast.statement.JassStatement; import com.etheller.interpreter.ast.value.JassType; import com.etheller.interpreter.ast.value.JassValue; @@ -26,17 +27,17 @@ public final class UserJassFunction extends AbstractJassFunction { @Override public JassValue innerCall(final List arguments, final GlobalScope globalScope, - final LocalScope localScope) { - for (final JassStatement statement : statements) { - final JassValue returnValue = statement.execute(globalScope, localScope); + final TriggerExecutionScope triggerScope, final LocalScope localScope) { + for (final JassStatement statement : this.statements) { + final JassValue returnValue = statement.execute(globalScope, localScope, triggerScope); if (returnValue != null) { - if (returnValue.visit(JassTypeGettingValueVisitor.getInstance()) != returnType) { + if (returnValue.visit(JassTypeGettingValueVisitor.getInstance()) != this.returnType) { throw new RuntimeException("Invalid return type"); } return returnValue; } } - if (JassType.NOTHING != returnType) { + if (JassType.NOTHING != this.returnType) { throw new RuntimeException("Invalid return type"); } return null; diff --git a/jassparser/src/com/etheller/interpreter/ast/scope/GlobalScope.java b/jassparser/src/com/etheller/interpreter/ast/scope/GlobalScope.java index 8e374c6..45262e5 100644 --- a/jassparser/src/com/etheller/interpreter/ast/scope/GlobalScope.java +++ b/jassparser/src/com/etheller/interpreter/ast/scope/GlobalScope.java @@ -22,15 +22,11 @@ public final class GlobalScope { private final HandleTypeSuperTypeLoadingVisitor handleTypeSuperTypeLoadingVisitor = new HandleTypeSuperTypeLoadingVisitor(); public final HandleJassType handleType; - public final HandleJassType frameHandleType; - public final HandleJassType framePointType; private static int lineNumber; public GlobalScope() { this.handleType = registerHandleType("handle");// the handle type - this.frameHandleType = registerHandleType("framehandle"); - this.framePointType = registerHandleType("framepointtype"); registerPrimitiveType(JassType.BOOLEAN); registerPrimitiveType(JassType.INTEGER); registerPrimitiveType(JassType.CODE); @@ -47,7 +43,7 @@ public final class GlobalScope { return lineNumber; } - private HandleJassType registerHandleType(final String name) { + public HandleJassType registerHandleType(final String name) { final HandleJassType handleJassType = new HandleJassType(null, name); this.types.put(name, handleJassType); return handleJassType; @@ -130,7 +126,13 @@ public final class GlobalScope { final HandleJassType handleSuperType = superType.visit(HandleJassTypeVisitor.getInstance()); if (handleSuperType != null) { final JassType jassType = this.types.get(type); - jassType.visit(this.handleTypeSuperTypeLoadingVisitor.reset(handleSuperType)); + if (jassType != null) { + jassType.visit(this.handleTypeSuperTypeLoadingVisitor.reset(handleSuperType)); + } + else { + throw new RuntimeException( + "unable to declare type " + type + " because it does not exist natively"); + } } else { throw new RuntimeException("type " + type + " cannot extend primitive type " + supertype); diff --git a/jassparser/src/com/etheller/interpreter/ast/scope/TriggerExecutionScope.java b/jassparser/src/com/etheller/interpreter/ast/scope/TriggerExecutionScope.java new file mode 100644 index 0000000..ded54e9 --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/scope/TriggerExecutionScope.java @@ -0,0 +1,16 @@ +package com.etheller.interpreter.ast.scope; + +import com.etheller.interpreter.ast.scope.trigger.Trigger; + +public class TriggerExecutionScope { + private final Trigger triggeringTrigger; + + public TriggerExecutionScope(final Trigger triggeringTrigger) { + this.triggeringTrigger = triggeringTrigger; + } + + public Trigger getTriggeringTrigger() { + return this.triggeringTrigger; + } + +} diff --git a/jassparser/src/com/etheller/interpreter/ast/scope/trigger/Trigger.java b/jassparser/src/com/etheller/interpreter/ast/scope/trigger/Trigger.java new file mode 100644 index 0000000..6c46ad3 --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/scope/trigger/Trigger.java @@ -0,0 +1,74 @@ +package com.etheller.interpreter.ast.scope.trigger; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import com.etheller.interpreter.ast.function.JassFunction; +import com.etheller.interpreter.ast.scope.GlobalScope; +import com.etheller.interpreter.ast.scope.TriggerExecutionScope; + +public class Trigger { + private final List conditions = new ArrayList<>(); + private final List actions = new ArrayList<>(); + private int evalCount; + private int execCount; + private boolean enabled = true; + // used for eval + private transient final TriggerExecutionScope triggerExecutionScope = new TriggerExecutionScope(this); + + public int addAction(final JassFunction function) { + final int index = this.actions.size(); + this.actions.add(function); + return index; + } + + public int addCondition(final TriggerBooleanExpression boolexpr) { + final int index = this.conditions.size(); + this.conditions.add(boolexpr); + return index; + } + + public void removeCondition(final TriggerBooleanExpression boolexpr) { + this.conditions.remove(boolexpr); + } + + public void removeConditionAtIndex(final int conditionIndex) { + this.conditions.remove(conditionIndex); + } + + public int getEvalCount() { + return this.evalCount; + } + + public int getExecCount() { + return this.execCount; + } + + public boolean evaluate(final GlobalScope globalScope, final TriggerExecutionScope triggerScope) { + for (final TriggerBooleanExpression condition : this.conditions) { + if (!condition.evaluate(globalScope, triggerScope)) { + return false; + } + } + return true; + } + + public void execute(final GlobalScope globalScope) { + for (final JassFunction action : this.actions) { + action.call(Collections.emptyList(), globalScope, this.triggerExecutionScope); + } + } + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(final boolean enabled) { + this.enabled = enabled; + } + + public void destroy() { + + } +} diff --git a/jassparser/src/com/etheller/interpreter/ast/scope/trigger/TriggerBooleanExpression.java b/jassparser/src/com/etheller/interpreter/ast/scope/trigger/TriggerBooleanExpression.java new file mode 100644 index 0000000..8463317 --- /dev/null +++ b/jassparser/src/com/etheller/interpreter/ast/scope/trigger/TriggerBooleanExpression.java @@ -0,0 +1,8 @@ +package com.etheller.interpreter.ast.scope.trigger; + +import com.etheller.interpreter.ast.scope.GlobalScope; +import com.etheller.interpreter.ast.scope.TriggerExecutionScope; + +public interface TriggerBooleanExpression { + boolean evaluate(GlobalScope globalScope, TriggerExecutionScope triggerScope); +} diff --git a/jassparser/src/com/etheller/interpreter/ast/statement/JassArrayedAssignmentStatement.java b/jassparser/src/com/etheller/interpreter/ast/statement/JassArrayedAssignmentStatement.java index 73ccb82..5b80156 100644 --- a/jassparser/src/com/etheller/interpreter/ast/statement/JassArrayedAssignmentStatement.java +++ b/jassparser/src/com/etheller/interpreter/ast/statement/JassArrayedAssignmentStatement.java @@ -4,6 +4,7 @@ import com.etheller.interpreter.ast.Assignable; import com.etheller.interpreter.ast.expression.JassExpression; import com.etheller.interpreter.ast.scope.GlobalScope; import com.etheller.interpreter.ast.scope.LocalScope; +import com.etheller.interpreter.ast.scope.TriggerExecutionScope; import com.etheller.interpreter.ast.value.ArrayJassValue; import com.etheller.interpreter.ast.value.JassValue; import com.etheller.interpreter.ast.value.visitor.ArrayJassValueVisitor; @@ -24,10 +25,11 @@ public class JassArrayedAssignmentStatement implements JassStatement { } @Override - public JassValue execute(final GlobalScope globalScope, final LocalScope localScope) { + public JassValue execute(final GlobalScope globalScope, final LocalScope localScope, + final TriggerExecutionScope triggerScope) { globalScope.setLineNumber(this.lineNo); Assignable variable = localScope.getAssignableLocal(this.identifier); - final JassValue index = this.indexExpression.evaluate(globalScope, localScope); + final JassValue index = this.indexExpression.evaluate(globalScope, localScope, triggerScope); if (variable == null) { variable = globalScope.getAssignableGlobal(this.identifier); } @@ -37,7 +39,7 @@ public class JassArrayedAssignmentStatement implements JassStatement { final ArrayJassValue arrayValue = variable.getValue().visit(ArrayJassValueVisitor.getInstance()); if (arrayValue != null) { arrayValue.set(index.visit(IntegerJassValueVisitor.getInstance()), - this.expression.evaluate(globalScope, localScope)); + this.expression.evaluate(globalScope, localScope, triggerScope)); } else { throw new RuntimeException("Not an array"); diff --git a/jassparser/src/com/etheller/interpreter/ast/statement/JassCallStatement.java b/jassparser/src/com/etheller/interpreter/ast/statement/JassCallStatement.java index 23330e6..a5e9635 100644 --- a/jassparser/src/com/etheller/interpreter/ast/statement/JassCallStatement.java +++ b/jassparser/src/com/etheller/interpreter/ast/statement/JassCallStatement.java @@ -7,6 +7,7 @@ import com.etheller.interpreter.ast.expression.JassExpression; import com.etheller.interpreter.ast.function.JassFunction; import com.etheller.interpreter.ast.scope.GlobalScope; import com.etheller.interpreter.ast.scope.LocalScope; +import com.etheller.interpreter.ast.scope.TriggerExecutionScope; import com.etheller.interpreter.ast.value.JassValue; public class JassCallStatement implements JassStatement { @@ -21,7 +22,8 @@ public class JassCallStatement implements JassStatement { } @Override - public JassValue execute(final GlobalScope globalScope, final LocalScope localScope) { + public JassValue execute(final GlobalScope globalScope, final LocalScope localScope, + final TriggerExecutionScope triggerScope) { globalScope.setLineNumber(this.lineNo); final JassFunction functionByName = globalScope.getFunctionByName(this.functionName); if (functionByName == null) { @@ -29,10 +31,10 @@ public class JassCallStatement implements JassStatement { } final List evaluatedExpressions = new ArrayList<>(); for (final JassExpression expr : this.arguments) { - final JassValue evaluatedExpression = expr.evaluate(globalScope, localScope); + final JassValue evaluatedExpression = expr.evaluate(globalScope, localScope, triggerScope); evaluatedExpressions.add(evaluatedExpression); } - functionByName.call(evaluatedExpressions, globalScope); + functionByName.call(evaluatedExpressions, globalScope, triggerScope); // throw away return value return null; } diff --git a/jassparser/src/com/etheller/interpreter/ast/statement/JassIfElseIfStatement.java b/jassparser/src/com/etheller/interpreter/ast/statement/JassIfElseIfStatement.java index ae6a2de..5f1f208 100644 --- a/jassparser/src/com/etheller/interpreter/ast/statement/JassIfElseIfStatement.java +++ b/jassparser/src/com/etheller/interpreter/ast/statement/JassIfElseIfStatement.java @@ -5,6 +5,7 @@ import java.util.List; import com.etheller.interpreter.ast.expression.JassExpression; import com.etheller.interpreter.ast.scope.GlobalScope; import com.etheller.interpreter.ast.scope.LocalScope; +import com.etheller.interpreter.ast.scope.TriggerExecutionScope; import com.etheller.interpreter.ast.value.JassValue; import com.etheller.interpreter.ast.value.visitor.BooleanJassValueVisitor; @@ -23,18 +24,20 @@ public class JassIfElseIfStatement implements JassStatement { } @Override - public JassValue execute(final GlobalScope globalScope, final LocalScope localScope) { + public JassValue execute(final GlobalScope globalScope, final LocalScope localScope, + final TriggerExecutionScope triggerScope) { globalScope.setLineNumber(this.lineNo); - if (this.condition.evaluate(globalScope, localScope).visit(BooleanJassValueVisitor.getInstance())) { + if (this.condition.evaluate(globalScope, localScope, triggerScope) + .visit(BooleanJassValueVisitor.getInstance())) { for (final JassStatement statement : this.thenStatements) { - final JassValue returnValue = statement.execute(globalScope, localScope); + final JassValue returnValue = statement.execute(globalScope, localScope, triggerScope); if (returnValue != null) { return returnValue; } } } else { - return this.elseifTail.execute(globalScope, localScope); + return this.elseifTail.execute(globalScope, localScope, triggerScope); } return null; } diff --git a/jassparser/src/com/etheller/interpreter/ast/statement/JassIfElseStatement.java b/jassparser/src/com/etheller/interpreter/ast/statement/JassIfElseStatement.java index 10912eb..20b9719 100644 --- a/jassparser/src/com/etheller/interpreter/ast/statement/JassIfElseStatement.java +++ b/jassparser/src/com/etheller/interpreter/ast/statement/JassIfElseStatement.java @@ -5,6 +5,7 @@ import java.util.List; import com.etheller.interpreter.ast.expression.JassExpression; import com.etheller.interpreter.ast.scope.GlobalScope; import com.etheller.interpreter.ast.scope.LocalScope; +import com.etheller.interpreter.ast.scope.TriggerExecutionScope; import com.etheller.interpreter.ast.value.JassValue; import com.etheller.interpreter.ast.value.visitor.BooleanJassValueVisitor; @@ -23,11 +24,13 @@ public class JassIfElseStatement implements JassStatement { } @Override - public JassValue execute(final GlobalScope globalScope, final LocalScope localScope) { + public JassValue execute(final GlobalScope globalScope, final LocalScope localScope, + final TriggerExecutionScope triggerScope) { globalScope.setLineNumber(this.lineNo); - if (this.condition.evaluate(globalScope, localScope).visit(BooleanJassValueVisitor.getInstance())) { + if (this.condition.evaluate(globalScope, localScope, triggerScope) + .visit(BooleanJassValueVisitor.getInstance())) { for (final JassStatement statement : this.thenStatements) { - final JassValue returnValue = statement.execute(globalScope, localScope); + final JassValue returnValue = statement.execute(globalScope, localScope, triggerScope); if (returnValue != null) { return returnValue; } @@ -35,7 +38,7 @@ public class JassIfElseStatement implements JassStatement { } else { for (final JassStatement statement : this.elseStatements) { - final JassValue returnValue = statement.execute(globalScope, localScope); + final JassValue returnValue = statement.execute(globalScope, localScope, triggerScope); if (returnValue != null) { return returnValue; } diff --git a/jassparser/src/com/etheller/interpreter/ast/statement/JassIfStatement.java b/jassparser/src/com/etheller/interpreter/ast/statement/JassIfStatement.java index 9d14c30..abe42a0 100644 --- a/jassparser/src/com/etheller/interpreter/ast/statement/JassIfStatement.java +++ b/jassparser/src/com/etheller/interpreter/ast/statement/JassIfStatement.java @@ -5,6 +5,7 @@ import java.util.List; import com.etheller.interpreter.ast.expression.JassExpression; import com.etheller.interpreter.ast.scope.GlobalScope; import com.etheller.interpreter.ast.scope.LocalScope; +import com.etheller.interpreter.ast.scope.TriggerExecutionScope; import com.etheller.interpreter.ast.value.JassValue; import com.etheller.interpreter.ast.value.visitor.BooleanJassValueVisitor; @@ -28,11 +29,13 @@ public class JassIfStatement implements JassStatement { } @Override - public JassValue execute(final GlobalScope globalScope, final LocalScope localScope) { + public JassValue execute(final GlobalScope globalScope, final LocalScope localScope, + final TriggerExecutionScope triggerScope) { globalScope.setLineNumber(this.lineNo); - if (this.condition.evaluate(globalScope, localScope).visit(BooleanJassValueVisitor.getInstance())) { + if (this.condition.evaluate(globalScope, localScope, triggerScope) + .visit(BooleanJassValueVisitor.getInstance())) { for (final JassStatement statement : this.thenStatements) { - final JassValue returnValue = statement.execute(globalScope, localScope); + final JassValue returnValue = statement.execute(globalScope, localScope, triggerScope); if (returnValue != null) { return returnValue; } diff --git a/jassparser/src/com/etheller/interpreter/ast/statement/JassReturnStatement.java b/jassparser/src/com/etheller/interpreter/ast/statement/JassReturnStatement.java index 1f8542e..adb1b4e 100644 --- a/jassparser/src/com/etheller/interpreter/ast/statement/JassReturnStatement.java +++ b/jassparser/src/com/etheller/interpreter/ast/statement/JassReturnStatement.java @@ -3,6 +3,7 @@ package com.etheller.interpreter.ast.statement; import com.etheller.interpreter.ast.expression.JassExpression; import com.etheller.interpreter.ast.scope.GlobalScope; import com.etheller.interpreter.ast.scope.LocalScope; +import com.etheller.interpreter.ast.scope.TriggerExecutionScope; import com.etheller.interpreter.ast.value.JassValue; public class JassReturnStatement implements JassStatement { @@ -15,9 +16,10 @@ public class JassReturnStatement implements JassStatement { } @Override - public JassValue execute(final GlobalScope globalScope, final LocalScope localScope) { + public JassValue execute(final GlobalScope globalScope, final LocalScope localScope, + final TriggerExecutionScope triggerScope) { globalScope.setLineNumber(this.lineNo); - return this.expression.evaluate(globalScope, localScope); + return this.expression.evaluate(globalScope, localScope, triggerScope); } } diff --git a/jassparser/src/com/etheller/interpreter/ast/statement/JassSetStatement.java b/jassparser/src/com/etheller/interpreter/ast/statement/JassSetStatement.java index 0b0cde7..e8249d4 100644 --- a/jassparser/src/com/etheller/interpreter/ast/statement/JassSetStatement.java +++ b/jassparser/src/com/etheller/interpreter/ast/statement/JassSetStatement.java @@ -4,6 +4,7 @@ import com.etheller.interpreter.ast.Assignable; import com.etheller.interpreter.ast.expression.JassExpression; import com.etheller.interpreter.ast.scope.GlobalScope; import com.etheller.interpreter.ast.scope.LocalScope; +import com.etheller.interpreter.ast.scope.TriggerExecutionScope; import com.etheller.interpreter.ast.value.JassValue; public class JassSetStatement implements JassStatement { @@ -18,14 +19,15 @@ public class JassSetStatement implements JassStatement { } @Override - public JassValue execute(final GlobalScope globalScope, final LocalScope localScope) { + public JassValue execute(final GlobalScope globalScope, final LocalScope localScope, + final TriggerExecutionScope triggerScope) { globalScope.setLineNumber(this.lineNo); final Assignable local = localScope.getAssignableLocal(this.identifier); if (local != null) { - local.setValue(this.expression.evaluate(globalScope, localScope)); + local.setValue(this.expression.evaluate(globalScope, localScope, triggerScope)); } else { - globalScope.setGlobal(this.identifier, this.expression.evaluate(globalScope, localScope)); + globalScope.setGlobal(this.identifier, this.expression.evaluate(globalScope, localScope, triggerScope)); } return null; } diff --git a/jassparser/src/com/etheller/interpreter/ast/statement/JassStatement.java b/jassparser/src/com/etheller/interpreter/ast/statement/JassStatement.java index 196f415..5c2099d 100644 --- a/jassparser/src/com/etheller/interpreter/ast/statement/JassStatement.java +++ b/jassparser/src/com/etheller/interpreter/ast/statement/JassStatement.java @@ -2,10 +2,11 @@ package com.etheller.interpreter.ast.statement; import com.etheller.interpreter.ast.scope.GlobalScope; import com.etheller.interpreter.ast.scope.LocalScope; +import com.etheller.interpreter.ast.scope.TriggerExecutionScope; import com.etheller.interpreter.ast.value.JassValue; public interface JassStatement { // When a value is returned, this indicates a RETURN statement, // and will end outer execution - JassValue execute(GlobalScope globalScope, LocalScope localScope); + JassValue execute(GlobalScope globalScope, LocalScope localScope, TriggerExecutionScope triggerScope); } diff --git a/jassparser/src/com/etheller/interpreter/ast/value/BooleanJassValue.java b/jassparser/src/com/etheller/interpreter/ast/value/BooleanJassValue.java index adbeabb..602a8f5 100644 --- a/jassparser/src/com/etheller/interpreter/ast/value/BooleanJassValue.java +++ b/jassparser/src/com/etheller/interpreter/ast/value/BooleanJassValue.java @@ -27,4 +27,13 @@ public class BooleanJassValue implements JassValue { return TRUE; } } + + public static BooleanJassValue of(final boolean flag) { + if (flag) { + return TRUE; + } + else { + return FALSE; + } + } } diff --git a/jassparser/src/com/etheller/interpreter/ast/visitors/JassGlobalsVisitor.java b/jassparser/src/com/etheller/interpreter/ast/visitors/JassGlobalsVisitor.java index 5273cd0..f5fcfc2 100644 --- a/jassparser/src/com/etheller/interpreter/ast/visitors/JassGlobalsVisitor.java +++ b/jassparser/src/com/etheller/interpreter/ast/visitors/JassGlobalsVisitor.java @@ -42,8 +42,9 @@ public class JassGlobalsVisitor extends JassBaseVisitor { this.globals.createGlobalArray(ctx.ID().getText(), type); } else { - this.globals.createGlobal(ctx.ID().getText(), type, this.jassExpressionVisitor - .visit(ctx.assignTail().expression()).evaluate(this.globals, EMPTY_LOCAL_SCOPE)); + this.globals.createGlobal(ctx.ID().getText(), type, + this.jassExpressionVisitor.visit(ctx.assignTail().expression()).evaluate(this.globals, + EMPTY_LOCAL_SCOPE, JassProgramVisitor.EMPTY_TRIGGER_SCOPE)); } return null; } diff --git a/jassparser/src/com/etheller/interpreter/ast/visitors/JassProgramVisitor.java b/jassparser/src/com/etheller/interpreter/ast/visitors/JassProgramVisitor.java index c113899..2c5e0d2 100644 --- a/jassparser/src/com/etheller/interpreter/ast/visitors/JassProgramVisitor.java +++ b/jassparser/src/com/etheller/interpreter/ast/visitors/JassProgramVisitor.java @@ -15,9 +15,11 @@ import com.etheller.interpreter.ast.function.JassFunction; import com.etheller.interpreter.ast.function.JassNativeManager; import com.etheller.interpreter.ast.function.UserJassFunction; import com.etheller.interpreter.ast.scope.GlobalScope; +import com.etheller.interpreter.ast.scope.TriggerExecutionScope; import com.etheller.interpreter.ast.statement.JassStatement; public class JassProgramVisitor extends JassBaseVisitor { + public static final TriggerExecutionScope EMPTY_TRIGGER_SCOPE = new TriggerExecutionScope(null); private final GlobalScope globals = new GlobalScope(); private final JassNativeManager jassNativeManager = new JassNativeManager(); private final JassTypeVisitor jassTypeVisitor = new JassTypeVisitor(this.globals); @@ -85,7 +87,7 @@ public class JassProgramVisitor extends JassBaseVisitor { final JassFunction mainFunction = this.globals.getFunctionByName("main"); if (mainFunction != null) { try { - mainFunction.call(Collections.EMPTY_LIST, this.globals); + mainFunction.call(Collections.EMPTY_LIST, this.globals, EMPTY_TRIGGER_SCOPE); } catch (final Exception exc) { throw new RuntimeException("Exception on Line " + GlobalScope.getLineNumber(), exc); diff --git a/resources/Scripts/common.jui b/resources/Scripts/common.jui index bcd74f7..1a969fb 100644 --- a/resources/Scripts/common.jui +++ b/resources/Scripts/common.jui @@ -19,6 +19,11 @@ type framehandle extends handle type framepointtype extends handle +type trigger extends handle +type triggeraction extends handle +type triggercondition extends handle +type boolexpr extends handle +type conditionfunc extends boolexpr native LogError takes string message returns nothing constant native ConvertFramePointType takes integer i returns framepointtype @@ -92,3 +97,22 @@ native FramePositionBounds takes framehandle frame returns // string "UI\\Console\\Human\\HumanUI-TimeIndicator.mdl" // when the "Human" skin was loaded with CreateRootFrame native SkinGetField takes string field returns string + +//============================================================================ +// Native trigger interface +// +native CreateTrigger takes nothing returns trigger +native DestroyTrigger takes trigger whichTrigger returns nothing +native EnableTrigger takes trigger whichTrigger returns nothing +native DisableTrigger takes trigger whichTrigger returns nothing +native IsTriggerEnabled takes trigger whichTrigger returns boolean + +native TriggerAddCondition takes trigger whichTrigger, boolexpr condition returns triggercondition +native TriggerRemoveCondition takes trigger whichTrigger, triggercondition whichCondition returns nothing +native TriggerClearConditions takes trigger whichTrigger returns nothing + +native TriggerAddAction takes trigger whichTrigger, code actionFunc returns triggeraction +native TriggerRemoveAction takes trigger whichTrigger, triggeraction whichAction returns nothing +native TriggerClearActions takes trigger whichTrigger returns nothing +native TriggerEvaluate takes trigger whichTrigger returns boolean +native TriggerExecute takes trigger whichTrigger returns nothing \ No newline at end of file diff --git a/resources/Scripts/melee.jui b/resources/Scripts/melee.jui index 44823fb..2186536 100644 --- a/resources/Scripts/melee.jui +++ b/resources/Scripts/melee.jui @@ -1,7 +1,7 @@ globals // Defaults for testing: - constant string SKIN = "Human" + constant string SKIN = "NightElf" // Major UI components framehandle ROOT_FRAME framehandle CONSOLE_UI From d5a1ee643d9a752c834fe0a658b159b66ff800b7 Mon Sep 17 00:00:00 2001 From: Retera Date: Sun, 5 Jul 2020 15:10:24 -0400 Subject: [PATCH 042/116] Fix pathing grid issue on certain maps --- .../warsmash/viewer5/handlers/w3x/environment/PathingGrid.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java index 9d20357..aa66ed7 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java @@ -90,7 +90,7 @@ public class PathingGrid { public boolean contains(final float x, final float y) { final int cellX = getCellX(x); final int cellY = getCellY(y); - return (cellX >= 0) && (cellY >= 0) && (cellX < this.pathingGrid[0]) && (cellY < this.pathingGrid[1]); + return (cellX >= 0) && (cellY >= 0) && (cellX < this.pathingGridSizes[0]) && (cellY < this.pathingGridSizes[1]); } public short getPathing(final float x, final float y) { From dcb68ba6c91ae38d5de252c83f22702fcf82dbd4 Mon Sep 17 00:00:00 2001 From: Retera Date: Sat, 25 Jul 2020 20:31:10 -0400 Subject: [PATCH 043/116] Checkpoint with more updates and a decent single unit pathing experience --- .../etheller/warsmash/WarsmashGdxMapGame.java | 53 +- .../etheller/warsmash/parsers/fdf/GameUI.java | 1 + .../warsmash/parsers/w3x/w3i/Player.java | 8 +- .../warsmash/parsers/w3x/w3i/War3MapW3i.java | 58 +- .../etheller/warsmash/units/DataTable.java | 11 +- .../warsmash/util/DataSourceFileHandle.java | 5 + .../warsmash/util/WorldEditStrings.java | 20 +- .../com/etheller/warsmash/viewer5/Scene.java | 2 +- .../warsmash/viewer5/handlers/mdx/Sd.java | 2 +- .../viewer5/handlers/w3x/TerrainDoodad.java | 5 +- .../viewer5/handlers/w3x/War3MapViewer.java | 301 +-- .../w3x/environment/GroundTexture.java | 3 + .../handlers/w3x/environment/PathingGrid.java | 5 +- .../handlers/w3x/environment/Shapes.java | 2 +- .../handlers/w3x/environment/Terrain.java | 8 +- .../handlers/w3x/rendersim/RenderUnit.java | 13 +- .../w3x/simulation/orders/CMoveOrder.java | 14 +- .../pathing/CPathfindingProcessor.java | 3 +- .../warsmash/desktop/DesktopLauncher.java | 18 +- .../warsmash/jassparser/JassBaseVisitor.java | 317 +++ .../warsmash/jassparser/JassLexer.java | 224 ++ .../warsmash/jassparser/JassParser.java | 1969 +++++++++++++++++ .../warsmash/jassparser/JassVisitor.java | 302 +++ .../ast/visitors/JassExpressionVisitor.java | 5 +- resources/Scripts/common.jui | 15 +- 25 files changed, 3153 insertions(+), 211 deletions(-) create mode 100644 jassparser/build/generated-src/com/etheller/warsmash/jassparser/JassBaseVisitor.java create mode 100644 jassparser/build/generated-src/com/etheller/warsmash/jassparser/JassLexer.java create mode 100644 jassparser/build/generated-src/com/etheller/warsmash/jassparser/JassParser.java create mode 100644 jassparser/build/generated-src/com/etheller/warsmash/jassparser/JassVisitor.java diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java index 65cf307..9ae08f2 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java @@ -4,7 +4,7 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.IntBuffer; -import java.util.Arrays; +import java.util.ArrayList; import java.util.LinkedList; import java.util.List; @@ -36,9 +36,12 @@ import com.etheller.warsmash.datasources.CompoundDataSourceDescriptor; import com.etheller.warsmash.datasources.DataSource; import com.etheller.warsmash.datasources.DataSourceDescriptor; import com.etheller.warsmash.datasources.FolderDataSourceDescriptor; +import com.etheller.warsmash.datasources.MpqDataSourceDescriptor; import com.etheller.warsmash.parsers.fdf.GameUI; import com.etheller.warsmash.parsers.jass.Jass2; import com.etheller.warsmash.parsers.jass.Jass2.RootFrameListener; +import com.etheller.warsmash.units.DataTable; +import com.etheller.warsmash.units.Element; import com.etheller.warsmash.util.DataSourceFileHandle; import com.etheller.warsmash.util.ImageUtils; import com.etheller.warsmash.util.WarsmashConstants; @@ -97,6 +100,11 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv private final List messages = new LinkedList<>(); private MdxModel timeIndicator; + private final DataTable warsmashIni; + + public WarsmashGdxMapGame(final DataTable warsmashIni) { + this.warsmashIni = warsmashIni; + } /* * (non-Javadoc) @@ -118,24 +126,32 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv final String renderer = Gdx.gl.glGetString(GL20.GL_RENDERER); System.err.println("Renderer: " + renderer); - final FolderDataSourceDescriptor war3mpq = new FolderDataSourceDescriptor("E:\\Backups\\Warcraft\\Data\\127"); - final FolderDataSourceDescriptor smashmpq = new FolderDataSourceDescriptor("..\\..\\resources"); -// final FolderDataSourceDescriptor war3mpq = new FolderDataSourceDescriptor( -// "D:\\NEEDS_ORGANIZING\\MPQBuild\\War3.mpq\\war3.mpq"); -// final FolderDataSourceDescriptor war3xLocalmpq = new FolderDataSourceDescriptor( -// "D:\\NEEDS_ORGANIZING\\MPQBuild\\War3xLocal.mpq\\enus-war3local.mpq"); -// final FolderDataSourceDescriptor rebirth = new FolderDataSourceDescriptor( -// "E:\\Games\\Warcraft III Patch 1.31 Rebirth"); - final FolderDataSourceDescriptor testingFolder = new FolderDataSourceDescriptor("E:\\Backups\\Warsmash\\Data"); - final FolderDataSourceDescriptor currentFolder = new FolderDataSourceDescriptor("."); - this.codebase = new CompoundDataSourceDescriptor(Arrays.asList(war3mpq, smashmpq, - /* war3xLocalmpq, */ testingFolder, currentFolder)).createDataSource(); + final Element dataSourcesConfig = this.warsmashIni.get("DataSources"); + final int dataSourcesCount = dataSourcesConfig.getFieldValue("Count"); + final List dataSourcesList = new ArrayList<>(); + for (int i = 0; i < dataSourcesCount; i++) { + final String type = dataSourcesConfig.getField("Type" + (i < 10 ? "0" : "") + i); + final String path = dataSourcesConfig.getField("Path" + (i < 10 ? "0" : "") + i); + switch (type) { + case "Folder": { + dataSourcesList.add(new FolderDataSourceDescriptor(path)); + break; + } + case "MPQ": { + dataSourcesList.add(new MpqDataSourceDescriptor(path)); + break; + } + default: + throw new RuntimeException("Unknown data source type: " + type); + } + } + this.codebase = new CompoundDataSourceDescriptor(dataSourcesList).createDataSource(); this.viewer = new War3MapViewer(this.codebase, this); this.viewer.worldScene.enableAudio(); this.viewer.enableAudio(); try { - this.viewer.loadMap("Maps\\Campaign\\NightElf03.w3m"); + this.viewer.loadMap(this.warsmashIni.get("Map").getField("FilePath")); } catch (final IOException e) { throw new RuntimeException(e); @@ -249,8 +265,10 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.minimap.y + ((this.minimap.height - minimapFilledHeight) / 2), minimapFilledWidth, minimapFilledHeight); - this.cameraManager.target.x = this.viewer.startLocations[0].x; - this.cameraManager.target.y = this.viewer.startLocations[0].y; + if (this.viewer.startLocations[0] != null) { + this.cameraManager.target.x = this.viewer.startLocations[0].x; + this.cameraManager.target.y = this.viewer.startLocations[0].y; + } this.shapeRenderer = new ShapeRenderer(); this.talentTreeWindow = new Rectangle(100, 300, 1400, 800); @@ -592,7 +610,8 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv } if (button == Input.Buttons.RIGHT) { final RenderUnit rayPickUnit = this.viewer.rayPickUnit(screenX, worldScreenY); - if ((rayPickUnit != null) && (rayPickUnit.playerIndex != this.selectedUnit.playerIndex)) { + if ((rayPickUnit != null) && (this.selectedUnit != null) + && (rayPickUnit.playerIndex != this.selectedUnit.playerIndex)) { if (this.viewer.orderSmart(rayPickUnit)) { StandSequence.randomPortraitTalkSequence(this.portraitCameraManager.modelInstance); this.selectedSoundCount = 0; diff --git a/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java index 76baf89..64c4989 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java @@ -71,6 +71,7 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { final DataSourceFDFParserBuilder dataSourceFDFParserBuilder = new DataSourceFDFParserBuilder(this.dataSource); final FrameDefinitionVisitor fdfVisitor = new FrameDefinitionVisitor(this.templates, dataSourceFDFParserBuilder); + System.err.println("Loading TOC file: " + tocFilePath); try (BufferedReader reader = new BufferedReader( new InputStreamReader(this.dataSource.getResourceAsStream(tocFilePath)))) { String line; diff --git a/core/src/com/etheller/warsmash/parsers/w3x/w3i/Player.java b/core/src/com/etheller/warsmash/parsers/w3x/w3i/Player.java index 91b8465..889bfac 100644 --- a/core/src/com/etheller/warsmash/parsers/w3x/w3i/Player.java +++ b/core/src/com/etheller/warsmash/parsers/w3x/w3i/Player.java @@ -19,8 +19,10 @@ public class Player { private final float[] startLocation = new float[2]; private long allyLowPriorities; private long allyHighPriorities; + private long enemyLowPrioritiesFlags; + private long enemyHighPrioritiesFlags; - public void load(final LittleEndianDataInputStream stream) throws IOException { + public void load(final LittleEndianDataInputStream stream, final int version) throws IOException { this.id = ParseUtils.readWar3ID(stream); this.type = stream.readInt(); this.race = stream.readInt(); @@ -29,6 +31,10 @@ public class Player { ParseUtils.readFloatArray(stream, this.startLocation); this.allyLowPriorities = ParseUtils.readUInt32(stream); this.allyHighPriorities = ParseUtils.readUInt32(stream); + if (version > 30) { + enemyLowPrioritiesFlags = ParseUtils.readUInt32(stream); + enemyHighPrioritiesFlags = ParseUtils.readUInt32(stream); + } } public void save(final LittleEndianDataOutputStream stream) throws IOException { diff --git a/core/src/com/etheller/warsmash/parsers/w3x/w3i/War3MapW3i.java b/core/src/com/etheller/warsmash/parsers/w3x/w3i/War3MapW3i.java index 266c4e1..32c6df7 100644 --- a/core/src/com/etheller/warsmash/parsers/w3x/w3i/War3MapW3i.java +++ b/core/src/com/etheller/warsmash/parsers/w3x/w3i/War3MapW3i.java @@ -43,7 +43,7 @@ public class War3MapW3i { private String soundEnvironment; private char lightEnvironmentTileset; private final short[] waterVertexColor = new short[4]; - private final short[] unknown2 = new short[4]; + private final short[] unknown2ProbablyLua = new short[4]; private final List players = new ArrayList<>(); private final List forces = new ArrayList<>(); private final List upgradeAvailabilityChanges = new ArrayList<>(); @@ -106,13 +106,17 @@ public class War3MapW3i { } if (this.version > 27) { - ParseUtils.readUInt8Array(stream, this.unknown2); + ParseUtils.readUInt8Array(stream, this.unknown2ProbablyLua); + } + if (this.version > 30) { + final long supportedModes = ParseUtils.readUInt32(stream); + final long gameDataVersion = ParseUtils.readUInt32(stream); } for (int i = 0, l = stream.readInt(); i < l; i++) { final Player player = new Player(); - player.load(stream); + player.load(stream, this.version); this.players.add(player); } @@ -125,37 +129,49 @@ public class War3MapW3i { this.forces.add(force); } - for (int i = 0, l = stream.readInt(); i < l; i++) { - final UpgradeAvailabilityChange upgradeAvailabilityChange = new UpgradeAvailabilityChange(); + if (stream.available() == 1) { + // some kind of really stupid protected map??? + return; + } + if (stream.available() > 0) { + for (int i = 0, l = stream.readInt(); i < l; i++) { + final UpgradeAvailabilityChange upgradeAvailabilityChange = new UpgradeAvailabilityChange(); - upgradeAvailabilityChange.load(stream); + upgradeAvailabilityChange.load(stream); - this.upgradeAvailabilityChanges.add(upgradeAvailabilityChange); + this.upgradeAvailabilityChanges.add(upgradeAvailabilityChange); + } } - for (int i = 0, l = stream.readInt(); i < l; i++) { - final TechAvailabilityChange techAvailabilityChange = new TechAvailabilityChange(); + if (stream.available() > 0) { + for (int i = 0, l = stream.readInt(); i < l; i++) { + final TechAvailabilityChange techAvailabilityChange = new TechAvailabilityChange(); - techAvailabilityChange.load(stream); + techAvailabilityChange.load(stream); - this.techAvailabilityChanges.add(techAvailabilityChange); + this.techAvailabilityChanges.add(techAvailabilityChange); + } } - for (int i = 0, l = stream.readInt(); i < l; i++) { - final RandomUnitTable randomUnitTable = new RandomUnitTable(); + if (stream.available() > 0) { + for (int i = 0, l = stream.readInt(); i < l; i++) { + final RandomUnitTable randomUnitTable = new RandomUnitTable(); - randomUnitTable.load(stream); + randomUnitTable.load(stream); - this.randomUnitTables.add(randomUnitTable); + this.randomUnitTables.add(randomUnitTable); + } } if (this.version > 24) { - for (int i = 0, l = stream.readInt(); i < l; i++) { - final RandomItemTable randomItemTable = new RandomItemTable(); + if (stream.available() > 0) { + for (int i = 0, l = stream.readInt(); i < l; i++) { + final RandomItemTable randomItemTable = new RandomItemTable(); - randomItemTable.load(stream); + randomItemTable.load(stream); - this.randomItemTables.add(randomItemTable); + this.randomItemTables.add(randomItemTable); + } } } } @@ -209,7 +225,7 @@ public class War3MapW3i { } if (this.version > 27) { - ParseUtils.writeUInt8Array(stream, this.unknown2); + ParseUtils.writeUInt8Array(stream, this.unknown2ProbablyLua); } ParseUtils.writeUInt32(stream, this.players.size()); @@ -411,7 +427,7 @@ public class War3MapW3i { } public short[] getUnknown2() { - return this.unknown2; + return this.unknown2ProbablyLua; } public List getPlayers() { diff --git a/core/src/com/etheller/warsmash/units/DataTable.java b/core/src/com/etheller/warsmash/units/DataTable.java index 16e8919..b92404a 100644 --- a/core/src/com/etheller/warsmash/units/DataTable.java +++ b/core/src/com/etheller/warsmash/units/DataTable.java @@ -6,6 +6,7 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.util.Arrays; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Map; @@ -168,7 +169,7 @@ public class DataTable implements ObjectData { flipMode = true; } int rowStartCount = 0; - final String[] dataNames = new String[colCount]; + String[] dataNames = new String[colCount]; int col = 0; int lastFieldId = 0; while ((input = reader.readLine()) != null) { @@ -219,6 +220,9 @@ public class DataTable implements ObjectData { } final int quotationIndex = kInput.indexOf("\""); + if ((fieldId - 1) >= dataNames.length) { + dataNames = Arrays.copyOf(dataNames, fieldId); + } if (quotationIndex == -1) { dataNames[fieldId - 1] = kInput.substring(eIndex + 1); } @@ -249,8 +253,11 @@ public class DataTable implements ObjectData { } final int quotationIndex = kInput.indexOf("\""); + if ((fieldId - 1) >= dataNames.length) { + dataNames = Arrays.copyOf(dataNames, fieldId); + } if (quotationIndex == -1) { - dataNames[fieldId - 1] = kInput.substring(eIndex + 1); + dataNames[fieldId - 1] = kInput.substring(eIndex + 1, kInput.length()); } else { dataNames[fieldId - 1] = kInput.substring(quotationIndex + 1, kInput.lastIndexOf("\"")); diff --git a/core/src/com/etheller/warsmash/util/DataSourceFileHandle.java b/core/src/com/etheller/warsmash/util/DataSourceFileHandle.java index 0742560..e9ef934 100644 --- a/core/src/com/etheller/warsmash/util/DataSourceFileHandle.java +++ b/core/src/com/etheller/warsmash/util/DataSourceFileHandle.java @@ -14,6 +14,11 @@ public class DataSourceFileHandle extends FileHandle { this.dataSource = dataSource; } + @Override + public String path() { + return file().getPath(); + } + @Override public InputStream read() { try { diff --git a/core/src/com/etheller/warsmash/util/WorldEditStrings.java b/core/src/com/etheller/warsmash/util/WorldEditStrings.java index 8347d2c..40c746e 100644 --- a/core/src/com/etheller/warsmash/util/WorldEditStrings.java +++ b/core/src/com/etheller/warsmash/util/WorldEditStrings.java @@ -14,12 +14,14 @@ public class WorldEditStrings implements StringBundle { private ResourceBundle bundlegs; public WorldEditStrings(final DataSource dataSource) { - try (InputStream fis = dataSource.getResourceAsStream("UI\\WorldEditStrings.txt"); - InputStreamReader reader = new InputStreamReader(fis, "utf-8")) { - this.bundle = new PropertyResourceBundle(reader); - } - catch (final IOException e) { - throw new RuntimeException(e); + if (dataSource.has("UI\\WorldEditStrings.txt")) { + try (InputStream fis = dataSource.getResourceAsStream("UI\\WorldEditStrings.txt"); + InputStreamReader reader = new InputStreamReader(fis, "utf-8")) { + this.bundle = new PropertyResourceBundle(reader); + } + catch (final IOException e) { + throw new RuntimeException(e); + } } try (InputStream fis = dataSource.getResourceAsStream("UI\\WorldEditGameStrings.txt"); InputStreamReader reader = new InputStreamReader(fis, "utf-8")) { @@ -49,6 +51,9 @@ public class WorldEditStrings implements StringBundle { } private String internalGetString(final String key) { + if (this.bundle == null) { + return this.bundlegs.getString(key.toUpperCase()); + } try { String string = this.bundle.getString(key.toUpperCase()); if ((string.charAt(0) == '"') && (string.length() >= 2) && (string.charAt(string.length() - 1) == '"')) { @@ -63,6 +68,9 @@ public class WorldEditStrings implements StringBundle { @Override public String getStringCaseSensitive(final String key) { + if (this.bundle == null) { + return this.bundlegs.getString(key); + } try { return this.bundle.getString(key); } diff --git a/core/src/com/etheller/warsmash/viewer5/Scene.java b/core/src/com/etheller/warsmash/viewer5/Scene.java index 48a99ec..930e8d6 100644 --- a/core/src/com/etheller/warsmash/viewer5/Scene.java +++ b/core/src/com/etheller/warsmash/viewer5/Scene.java @@ -303,7 +303,7 @@ public abstract class Scene { private static final class InstanceDepthComparator implements Comparator { @Override public int compare(final ModelInstance o1, final ModelInstance o2) { - return (int) Math.signum(o2.depth - o1.depth); + return -Float.compare(o2.depth, o1.depth); } } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Sd.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Sd.java index 523e1cb..2633c09 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Sd.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Sd.java @@ -122,7 +122,7 @@ public abstract class Sd { return this.globalSequence.getValue(out, this.globalSequence.end == 0 ? 0 : counter % this.globalSequence.end); } - else if ((sequence != -1) && (this.sequences.size() > 0)) { + else if ((sequence != -1) && (this.sequences.size() > sequence)) { return this.sequences.get(sequence).getValue(out, frame); } else { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/TerrainDoodad.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/TerrainDoodad.java index 6fa9fe7..9db6c47 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/TerrainDoodad.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/TerrainDoodad.java @@ -17,10 +17,11 @@ public class TerrainDoodad { final MdxSimpleInstance instance = (MdxSimpleInstance) model.addInstance(1); locationHeap[0] = (doodad.getLocation()[0] * 128) + centerOffset[0] + 128; - locationHeap[0] = (doodad.getLocation()[1] * 128) + centerOffset[1] + 128; + locationHeap[1] = (doodad.getLocation()[1] * 128) + centerOffset[1] + 128; instance.move(locationHeap); - instance.rotate(new Quaternion().setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, row.readSLKTagFloat("fixedRot"))); + instance.rotate(new Quaternion().setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, + (float) Math.toRadians(row.readSLKTagFloat("fixedRot")))); instance.setScene(map.worldScene); this.instance = instance; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index fdba8bc..b12ee51 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -454,12 +454,21 @@ public class War3MapViewer extends ModelViewer { BufferedImage bufferedImage = this.filePathToPathingMap.get(pathingTexture.toLowerCase()); if (bufferedImage == null) { - bufferedImage = TgaFile.readTGA(pathingTexture, - this.mapMpq.getResourceAsStream(pathingTexture)); - this.filePathToPathingMap.put(pathingTexture.toLowerCase(), bufferedImage); + if (this.mapMpq.has(pathingTexture)) { + try { + bufferedImage = TgaFile.readTGA(pathingTexture, + this.mapMpq.getResourceAsStream(pathingTexture)); + this.filePathToPathingMap.put(pathingTexture.toLowerCase(), bufferedImage); + } + catch (final Exception exc) { + exc.printStackTrace(); + } + } + } + if (bufferedImage != null) { + this.terrain.pathingGrid.blitPathingOverlayTexture(doodad.getLocation()[0], + doodad.getLocation()[1], (int) Math.toDegrees(doodad.getAngle()), bufferedImage); } - this.terrain.pathingGrid.blitPathingOverlayTexture(doodad.getLocation()[0], - doodad.getLocation()[1], (int) Math.toDegrees(doodad.getAngle()), bufferedImage); } } // First see if the model is local. @@ -488,7 +497,7 @@ public class War3MapViewer extends ModelViewer { // Cliff/Terrain doodads. for (final com.etheller.warsmash.parsers.w3x.doo.TerrainDoodad doodad : doo.getTerrainDoodads()) { final MutableGameObject row = modifications.getDoodads().get(doodad.getId()); - String file = row.readSLKTag("file"); + String file = "UI\\Feedback\\WaypointFlags\\WaypointFlag.mdx";// row.readSLKTag("file"); if (file.toLowerCase().endsWith(".mdl")) { file = file.substring(0, file.length() - 4); } @@ -514,43 +523,96 @@ public class War3MapViewer extends ModelViewer { private void loadUnitsAndItems(final Warcraft3MapObjectData modifications) throws IOException { final War3Map mpq = this.mapMpq; - final War3MapUnitsDoo dooFile = mpq.readUnits(); + if (this.dataSource.has("war3mapUnits.doo")) { + final War3MapUnitsDoo dooFile = mpq.readUnits(); - final Map soundsetNameToSoundset = new HashMap<>(); + final Map soundsetNameToSoundset = new HashMap<>(); - // Collect the units and items data. - UnitSoundset soundset = null; - for (final com.etheller.warsmash.parsers.w3x.unitsdoo.Unit unit : dooFile.getUnits()) { - MutableGameObject row = null; - String path = null; - Splat unitShadowSplat = null; + // Collect the units and items data. + UnitSoundset soundset = null; + for (final com.etheller.warsmash.parsers.w3x.unitsdoo.Unit unit : dooFile.getUnits()) { + MutableGameObject row = null; + String path = null; + Splat unitShadowSplat = null; - // Hardcoded? - WorldEditorDataType type = null; - if (sloc.equals(unit.getId())) { + // Hardcoded? + WorldEditorDataType type = null; + if (sloc.equals(unit.getId())) { // path = "Objects\\StartLocation\\StartLocation.mdx"; - type = null; /// ?????? - this.startLocations[unit.getPlayer()] = new Vector2(unit.getLocation()[0], unit.getLocation()[1]); - } - else { - row = modifications.getUnits().get(unit.getId()); - if (row == null) { - row = modifications.getItems().get(unit.getId()); - if (row != null) { - type = WorldEditorDataType.ITEM; - path = row.getFieldAsString(ITEM_FILE, 0); + type = null; /// ?????? + this.startLocations[unit.getPlayer()] = new Vector2(unit.getLocation()[0], unit.getLocation()[1]); + } + else { + row = modifications.getUnits().get(unit.getId()); + if (row == null) { + row = modifications.getItems().get(unit.getId()); + if (row != null) { + type = WorldEditorDataType.ITEM; + path = row.getFieldAsString(ITEM_FILE, 0); + + if (path.toLowerCase().endsWith(".mdl") || path.toLowerCase().endsWith(".mdx")) { + path = path.substring(0, path.length() - 4); + } + + final String unitShadow = "Shadow"; + if ((unitShadow != null) && !"_".equals(unitShadow)) { + final String texture = "ReplaceableTextures\\Shadows\\" + unitShadow + ".blp"; + final float shadowX = 50; + final float shadowY = 50; + final float shadowWidth = 128; + final float shadowHeight = 128; + if (!this.terrain.splats.containsKey(texture)) { + final Splat splat = new Splat(); + splat.opacity = 0.5f; + this.terrain.splats.put(texture, splat); + } + final float x = unit.getLocation()[0] - shadowX; + final float y = unit.getLocation()[1] - shadowY; + this.terrain.splats.get(texture).locations + .add(new float[] { x, y, x + shadowWidth, y + shadowHeight, 3 }); + unitShadowSplat = this.terrain.splats.get(texture); + } + + path += ".mdx"; + } + } + else { + type = WorldEditorDataType.UNITS; + path = row.getFieldAsString(UNIT_FILE, 0); if (path.toLowerCase().endsWith(".mdl") || path.toLowerCase().endsWith(".mdx")) { path = path.substring(0, path.length() - 4); } + if ((row.readSLKTagInt("fileVerFlags") == 2) && this.dataSource.has(path + "_V1.mdx")) { + path += "_V1"; + } - final String unitShadow = "Shadow"; + path += ".mdx"; + + final String uberSplat = row.getFieldAsString(UBER_SPLAT, 0); + if (uberSplat != null) { + final Element uberSplatInfo = this.terrain.uberSplatTable.get(uberSplat); + if (uberSplatInfo != null) { + final String texturePath = uberSplatInfo.getField("Dir") + "\\" + + uberSplatInfo.getField("file") + ".blp"; + if (!this.terrain.splats.containsKey(texturePath)) { + this.terrain.splats.put(texturePath, new Splat()); + } + final float x = unit.getLocation()[0]; + final float y = unit.getLocation()[1]; + final float s = uberSplatInfo.getFieldFloatValue("Scale"); + this.terrain.splats.get(texturePath).locations + .add(new float[] { x - s, y - s, x + s, y + s, 1 }); + } + } + + final String unitShadow = row.getFieldAsString(UNIT_SHADOW, 0); if ((unitShadow != null) && !"_".equals(unitShadow)) { final String texture = "ReplaceableTextures\\Shadows\\" + unitShadow + ".blp"; - final float shadowX = 50; - final float shadowY = 50; - final float shadowWidth = 128; - final float shadowHeight = 128; + final float shadowX = row.getFieldAsFloat(UNIT_SHADOW_X, 0); + final float shadowY = row.getFieldAsFloat(UNIT_SHADOW_Y, 0); + final float shadowWidth = row.getFieldAsFloat(UNIT_SHADOW_W, 0); + final float shadowHeight = row.getFieldAsFloat(UNIT_SHADOW_H, 0); if (!this.terrain.splats.containsKey(texture)) { final Splat splat = new Splat(); splat.opacity = 0.5f; @@ -563,134 +625,83 @@ public class War3MapViewer extends ModelViewer { unitShadowSplat = this.terrain.splats.get(texture); } - path += ".mdx"; - } - } - else { - type = WorldEditorDataType.UNITS; - path = row.getFieldAsString(UNIT_FILE, 0); - - if (path.toLowerCase().endsWith(".mdl") || path.toLowerCase().endsWith(".mdx")) { - path = path.substring(0, path.length() - 4); - } - if ((row.readSLKTagInt("fileVerFlags") == 2) && this.dataSource.has(path + "_V1.mdx")) { - path += "_V1"; - } - - path += ".mdx"; - - final String uberSplat = row.getFieldAsString(UBER_SPLAT, 0); - if (uberSplat != null) { - final Element uberSplatInfo = this.terrain.uberSplatTable.get(uberSplat); - if (uberSplatInfo != null) { - final String texturePath = uberSplatInfo.getField("Dir") + "\\" - + uberSplatInfo.getField("file") + ".blp"; - if (!this.terrain.splats.containsKey(texturePath)) { - this.terrain.splats.put(texturePath, new Splat()); + final String buildingShadow = row.getFieldAsString(BUILDING_SHADOW, 0); + if ((buildingShadow != null) && !"_".equals(buildingShadow)) { + this.terrain.addShadow(buildingShadow, unit.getLocation()[0], unit.getLocation()[1]); + } + final String pathingTexture = row.getFieldAsString(UNIT_PATHING, 0); + if ((pathingTexture != null) && (pathingTexture.length() > 0) && !"_".equals(pathingTexture)) { + BufferedImage bufferedImage = this.filePathToPathingMap.get(pathingTexture.toLowerCase()); + if (bufferedImage == null) { + bufferedImage = TgaFile.readTGA(pathingTexture, + this.mapMpq.getResourceAsStream(pathingTexture)); + this.filePathToPathingMap.put(pathingTexture.toLowerCase(), bufferedImage); } - final float x = unit.getLocation()[0]; - final float y = unit.getLocation()[1]; - final float s = uberSplatInfo.getFieldFloatValue("Scale"); - this.terrain.splats.get(texturePath).locations - .add(new float[] { x - s, y - s, x + s, y + s, 1 }); + this.terrain.pathingGrid.blitPathingOverlayTexture(unit.getLocation()[0], + unit.getLocation()[1], (int) Math.toDegrees(unit.getAngle()), bufferedImage); } - } - final String unitShadow = row.getFieldAsString(UNIT_SHADOW, 0); - if ((unitShadow != null) && !"_".equals(unitShadow)) { - final String texture = "ReplaceableTextures\\Shadows\\" + unitShadow + ".blp"; - final float shadowX = row.getFieldAsFloat(UNIT_SHADOW_X, 0); - final float shadowY = row.getFieldAsFloat(UNIT_SHADOW_Y, 0); - final float shadowWidth = row.getFieldAsFloat(UNIT_SHADOW_W, 0); - final float shadowHeight = row.getFieldAsFloat(UNIT_SHADOW_H, 0); - if (!this.terrain.splats.containsKey(texture)) { - final Splat splat = new Splat(); - splat.opacity = 0.5f; - this.terrain.splats.put(texture, splat); + final String soundName = row.getFieldAsString(UNIT_SOUNDSET, 0); + UnitSoundset unitSoundset = soundsetNameToSoundset.get(soundName); + if (unitSoundset == null) { + unitSoundset = new UnitSoundset(this.dataSource, this.unitAckSoundsTable, soundName); + soundsetNameToSoundset.put(soundName, unitSoundset); } - final float x = unit.getLocation()[0] - shadowX; - final float y = unit.getLocation()[1] - shadowY; - this.terrain.splats.get(texture).locations - .add(new float[] { x, y, x + shadowWidth, y + shadowHeight, 3 }); - unitShadowSplat = this.terrain.splats.get(texture); + soundset = unitSoundset; } - - final String buildingShadow = row.getFieldAsString(BUILDING_SHADOW, 0); - if ((buildingShadow != null) && !"_".equals(buildingShadow)) { - this.terrain.addShadow(buildingShadow, unit.getLocation()[0], unit.getLocation()[1]); - } - final String pathingTexture = row.getFieldAsString(UNIT_PATHING, 0); - if ((pathingTexture != null) && (pathingTexture.length() > 0) && !"_".equals(pathingTexture)) { - BufferedImage bufferedImage = this.filePathToPathingMap.get(pathingTexture.toLowerCase()); - if (bufferedImage == null) { - bufferedImage = TgaFile.readTGA(pathingTexture, - this.mapMpq.getResourceAsStream(pathingTexture)); - this.filePathToPathingMap.put(pathingTexture.toLowerCase(), bufferedImage); - } - this.terrain.pathingGrid.blitPathingOverlayTexture(unit.getLocation()[0], unit.getLocation()[1], - (int) Math.toDegrees(unit.getAngle()), bufferedImage); - } - - final String soundName = row.getFieldAsString(UNIT_SOUNDSET, 0); - UnitSoundset unitSoundset = soundsetNameToSoundset.get(soundName); - if (unitSoundset == null) { - unitSoundset = new UnitSoundset(this.dataSource, this.unitAckSoundsTable, soundName); - soundsetNameToSoundset.put(soundName, unitSoundset); - } - soundset = unitSoundset; } - } - if (path != null) { - final MdxModel model = (MdxModel) this.load(path, this.mapPathSolver, this.solverParams); - MdxModel portraitModel; - final String portraitPath = path.substring(0, path.length() - 4) + "_portrait.mdx"; - if (this.dataSource.has(portraitPath)) { - portraitModel = (MdxModel) this.load(portraitPath, this.mapPathSolver, this.solverParams); - } - else { - portraitModel = model; - } - if (type == WorldEditorDataType.UNITS) { - float angle; - if (this.simulation.getUnitData().isBuilding(row.getAlias())) { - // TODO pretty sure 270 is a Gameplay Constants value that should be dynamically - // loaded - angle = 270.0f; + if (path != null) { + final MdxModel model = (MdxModel) this.load(path, this.mapPathSolver, this.solverParams); + MdxModel portraitModel; + final String portraitPath = path.substring(0, path.length() - 4) + "_portrait.mdx"; + if (this.dataSource.has(portraitPath)) { + portraitModel = (MdxModel) this.load(portraitPath, this.mapPathSolver, this.solverParams); } else { - angle = (float) Math.toDegrees(unit.getAngle()); + portraitModel = model; } - final CUnit simulationUnit = this.simulation.createUnit(row.getAlias(), unit.getPlayer(), - unit.getLocation()[0], unit.getLocation()[1], angle); - final RenderUnit renderUnit = new RenderUnit(this, model, row, unit, soundset, portraitModel, - simulationUnit); - this.units.add(renderUnit); - if (unitShadowSplat != null) { - unitShadowSplat.unitMapping.add(new Consumer() { - @Override - public void accept(final SplatMover t) { - renderUnit.shadow = t; - } - }); + if (type == WorldEditorDataType.UNITS) { + float angle; + if (this.simulation.getUnitData().isBuilding(row.getAlias())) { + // TODO pretty sure 270 is a Gameplay Constants value that should be dynamically + // loaded + angle = 270.0f; + } + else { + angle = (float) Math.toDegrees(unit.getAngle()); + } + final CUnit simulationUnit = this.simulation.createUnit(row.getAlias(), unit.getPlayer(), + unit.getLocation()[0], unit.getLocation()[1], angle); + final RenderUnit renderUnit = new RenderUnit(this, model, row, unit, soundset, portraitModel, + simulationUnit); + this.units.add(renderUnit); + if (unitShadowSplat != null) { + unitShadowSplat.unitMapping.add(new Consumer() { + @Override + public void accept(final SplatMover t) { + renderUnit.shadow = t; + } + }); + } + } + else { + this.items.add(new RenderItem(this, model, row, unit, soundset, portraitModel)); // TODO store + // somewhere + if (unitShadowSplat != null) { + unitShadowSplat.unitMapping.add(new Consumer() { + @Override + public void accept(final SplatMover t) { + + } + }); + } } } else { - this.items.add(new RenderItem(this, model, row, unit, soundset, portraitModel)); // TODO store - // somewhere - if (unitShadowSplat != null) { - unitShadowSplat.unitMapping.add(new Consumer() { - @Override - public void accept(final SplatMover t) { - - } - }); - } + System.err.println("Unknown unit ID: " + unit.getId()); } } - else { - System.err.println("Unknown unit ID: " + unit.getId()); - } } this.terrain.loadSplats(); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/GroundTexture.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/GroundTexture.java index 7df3a89..86c04b1 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/GroundTexture.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/GroundTexture.java @@ -20,6 +20,9 @@ public class GroundTexture { if (path.toLowerCase().endsWith(".blp")) { try (InputStream stream = dataSource.getResourceAsStream(path)) { final BufferedImage image = ImageIO.read(stream); + if (image == null) { + throw new IllegalStateException("Missing ground texture: " + path); + } final Buffer buffer = ImageUtils.getTextureBuffer(ImageUtils.forceBufferedImagesRGB(image)); final int width = image.getWidth(); final int height = image.getHeight(); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java index aa66ed7..9667f49 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java @@ -157,7 +157,10 @@ public class PathingGrid { } for (int i = -1; i <= 1; i++) { for (int j = -1; j <= 1; j++) { - if (!pathingType.isPathable(getPathing(unitX + (i * collisionSize), unitY + (j * collisionSize)))) { + final float unitPathingX = unitX + (i * collisionSize); + final float unitPathingY = unitY + (j * collisionSize); + if (!contains(unitPathingX, unitPathingY) + || !pathingType.isPathable(getPathing(unitPathingX, unitPathingY))) { return false; } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Shapes.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Shapes.java index a227be9..588a682 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Shapes.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Shapes.java @@ -15,7 +15,7 @@ public class Shapes { float[][] quadVertices = { { 1, 1 }, { 0, 1 }, { 0, 0 }, { 1, 0 } }; - int[][] quadIndices = { { 0, 3, 1 }, { 1, 3, 2 } }; + int[][] quadIndices = { { 1, 3, 0 }, { 2, 3, 1 } }; public void init() { this.vertexBuffer = Gdx.gl30.glGenBuffer(); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java index 1d37073..0634a34 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java @@ -236,6 +236,9 @@ public class Terrain { final String texFile = cliffInfo.getField("texFile"); try (InputStream imageStream = dataSource.getResourceAsStream(texDir + "\\" + texFile + texturesExt)) { final BufferedImage image = ImageIO.read(imageStream); + if (image == null) { + throw new IllegalStateException("Missing cliff texture: " + texDir + "\\" + texFile + texturesExt); + } this.cliffTextures.add(new UnloadedTexture(image.getWidth(), image.getHeight(), ImageUtils.getTextureBuffer(ImageUtils.forceBufferedImagesRGB(image)), cliffInfo.getField("cliffModelDir"), cliffInfo.getField("rampModelDir"))); @@ -811,8 +814,11 @@ public class Terrain { this.webGL.useShaderProgram(this.groundShader); final GL30 gl = Gdx.gl30; - gl.glDisable(GL30.GL_CULL_FACE); + gl.glEnable(GL20.GL_CULL_FACE); gl.glDisable(GL30.GL_BLEND); + gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA); + gl.glEnable(GL20.GL_DEPTH_TEST); + gl.glDepthMask(true); gl.glUniformMatrix4fv(this.groundShader.getUniformLocation("MVP"), 1, false, this.camera.viewProjectionMatrix.val, 0); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java index 948c1e2..74458f3 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java @@ -60,6 +60,8 @@ public class RenderUnit { private boolean swimming; + private boolean alreadyPlayedDeath = false; + private final EnumSet secondaryAnimationTags = EnumSet .noneOf(AnimationTokens.SecondaryTag.class); @@ -217,9 +219,16 @@ public class RenderUnit { if (this.simulationUnit.getLife() <= 0) { final MdxModel model = (MdxModel) mdxComplexInstance.model; final List sequences = model.getSequences(); - final IndexedSequence sequence = StandSequence.selectSequence("death", sequences); - if ((sequence != null) && (mdxComplexInstance.sequence != sequence.index)) { + IndexedSequence sequence = StandSequence.selectSequence("death", sequences); + if (!this.alreadyPlayedDeath && (sequence != null) && (mdxComplexInstance.sequence != sequence.index)) { mdxComplexInstance.setSequence(sequence.index); + this.alreadyPlayedDeath = true; + } + else if (mdxComplexInstance.sequenceEnded && this.alreadyPlayedDeath) { + sequence = StandSequence.selectSequence("dissipate", sequences); + if ((sequence != null) && (mdxComplexInstance.sequence != sequence.index)) { + mdxComplexInstance.setSequence(sequence.index); + } } } else if (mdxComplexInstance.sequenceEnded || (mdxComplexInstance.sequence == -1) diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CMoveOrder.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CMoveOrder.java index 4e770a3..b51a412 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CMoveOrder.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CMoveOrder.java @@ -45,7 +45,7 @@ public class CMoveOrder implements COrder { final float startFloatingY = prevY; if (this.path == null) { this.path = simulation.findNaiveSlowPath(this.unit, startFloatingX, startFloatingY, this.target, - movementType, collisionSize); + movementType == null ? MovementType.FOOT : movementType, collisionSize); System.out.println("init path " + this.path); // check for smoothing if (!this.path.isEmpty()) { @@ -65,7 +65,8 @@ public class CMoveOrder implements COrder { if ((totalPathDistance < (1.15 * nextPossiblePathElement.distance(smoothingGroupStartX, smoothingGroupStartY))) && pathingGrid.isPathable((smoothingGroupStartX + nextPossiblePathElement.x) / 2, - (smoothingGroupStartY + nextPossiblePathElement.y) / 2, movementType)) { + (smoothingGroupStartY + nextPossiblePathElement.y) / 2, + movementType == null ? MovementType.DISABLED : movementType)) { if (smoothingStartIndex == -1) { smoothingStartIndex = i; } @@ -163,8 +164,11 @@ public class CMoveOrder implements COrder { } tempRect.set(this.unit.getCollisionRectangle()); tempRect.setCenter(nextX, nextY); - if (pathingGrid.isPathable(nextX, nextY, movementType, collisionSize)// ((int) collisionSize / 16) * 16 - && !worldCollision.intersectsAnythingOtherThan(tempRect, this.unit, movementType)) { + if ((movementType == null) || (pathingGrid.isPathable(nextX, nextY, movementType, collisionSize)// ((int) + // collisionSize + // / 16) + // * 16 + && !worldCollision.intersectsAnythingOtherThan(tempRect, this.unit, movementType))) { this.unit.setPoint(nextX, nextY, worldCollision); if (done) { if (this.path.isEmpty()) { @@ -211,7 +215,7 @@ public class CMoveOrder implements COrder { } else { this.path = simulation.findNaiveSlowPath(this.unit, startFloatingX, startFloatingY, this.target, - movementType, collisionSize); + movementType == null ? MovementType.FOOT : movementType, collisionSize); System.out.println("new path " + this.path); if (this.path.isEmpty()) { return true; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/pathing/CPathfindingProcessor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/pathing/CPathfindingProcessor.java index 5c79c45..885e4d5 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/pathing/CPathfindingProcessor.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/pathing/CPathfindingProcessor.java @@ -57,7 +57,8 @@ public class CPathfindingProcessor { final float collisionSize) { final float goalX = goal.x; final float goalY = goal.y; - if (!this.pathingGrid.isPathable(goalX, goalY, movementType, collisionSize)) { + if (!this.pathingGrid.isPathable(goalX, goalY, movementType, collisionSize) + || !isPathableDynamically(goalX, goalY, ignoreIntersectionsWithThisUnit, movementType)) { return Collections.emptyList(); } System.out.println("beginning findNaiveSlowPath for " + startX + "," + startY + "," + goalX + "," + goalY); diff --git a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java index 2404830..f6538ff 100644 --- a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java +++ b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java @@ -1,5 +1,9 @@ package com.etheller.warsmash.desktop; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; + import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL31; import org.lwjgl.opengl.GL32; @@ -11,6 +15,8 @@ import com.badlogic.gdx.backends.lwjgl.LwjglApplication; import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration; import com.badlogic.gdx.backends.lwjgl.audio.OpenALSound; import com.etheller.warsmash.WarsmashGdxMapGame; +import com.etheller.warsmash.units.DataTable; +import com.etheller.warsmash.util.StringBundle; import com.etheller.warsmash.viewer5.gl.ANGLEInstancedArrays; import com.etheller.warsmash.viewer5.gl.DynamicShadowExtension; import com.etheller.warsmash.viewer5.gl.Extensions; @@ -61,6 +67,16 @@ public class DesktopLauncher { return ((OpenALSound) sound).duration(); } }; + final DataTable warsmashIni = new DataTable(StringBundle.EMPTY); + try (FileInputStream warsmashIniInputStream = new FileInputStream("warsmash.ini")) { + warsmashIni.readTXT(warsmashIniInputStream, true); + } + catch (final FileNotFoundException e) { + throw new RuntimeException(e); + } + catch (final IOException e) { + throw new RuntimeException(e); + } Extensions.GL_LINE = GL11.GL_LINE; Extensions.GL_FILL = GL11.GL_FILL; final LwjglApplicationConfiguration config = new LwjglApplicationConfiguration(); @@ -72,6 +88,6 @@ public class DesktopLauncher { final DisplayMode desktopDisplayMode = LwjglApplicationConfiguration.getDesktopDisplayMode(); config.width = desktopDisplayMode.width; config.height = desktopDisplayMode.height; - new LwjglApplication(new WarsmashGdxMapGame(), config); + new LwjglApplication(new WarsmashGdxMapGame(warsmashIni), config); } } diff --git a/jassparser/build/generated-src/com/etheller/warsmash/jassparser/JassBaseVisitor.java b/jassparser/build/generated-src/com/etheller/warsmash/jassparser/JassBaseVisitor.java new file mode 100644 index 0000000..90b541a --- /dev/null +++ b/jassparser/build/generated-src/com/etheller/warsmash/jassparser/JassBaseVisitor.java @@ -0,0 +1,317 @@ +// Generated from Jass.g4 by ANTLR 4.7 + + package com.etheller.interpreter; + +import org.antlr.v4.runtime.tree.AbstractParseTreeVisitor; + +/** + * This class provides an empty implementation of {@link JassVisitor}, + * which can be extended to create a visitor which only needs to handle a subset + * of the available methods. + * + * @param The return type of the visit operation. Use {@link Void} for + * operations with no return type. + */ +public class JassBaseVisitor extends AbstractParseTreeVisitor implements JassVisitor { + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitProgram(JassParser.ProgramContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitTypeDefinition(JassParser.TypeDefinitionContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitBasicType(JassParser.BasicTypeContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitArrayType(JassParser.ArrayTypeContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitNothingType(JassParser.NothingTypeContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitBasicGlobal(JassParser.BasicGlobalContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitDefinitionGlobal(JassParser.DefinitionGlobalContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitAssignTail(JassParser.AssignTailContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitReferenceExpression(JassParser.ReferenceExpressionContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitStringLiteralExpression(JassParser.StringLiteralExpressionContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitIntegerLiteralExpression(JassParser.IntegerLiteralExpressionContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitFunctionReferenceExpression(JassParser.FunctionReferenceExpressionContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitNullExpression(JassParser.NullExpressionContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitTrueExpression(JassParser.TrueExpressionContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitFalseExpression(JassParser.FalseExpressionContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitArrayReferenceExpression(JassParser.ArrayReferenceExpressionContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitFunctionCallExpression(JassParser.FunctionCallExpressionContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitParentheticalExpression(JassParser.ParentheticalExpressionContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitNotExpression(JassParser.NotExpressionContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitFunctionExpression(JassParser.FunctionExpressionContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitSingleArgument(JassParser.SingleArgumentContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitListArgument(JassParser.ListArgumentContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitCallStatement(JassParser.CallStatementContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitSetStatement(JassParser.SetStatementContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitArrayedAssignmentStatement(JassParser.ArrayedAssignmentStatementContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitReturnStatement(JassParser.ReturnStatementContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitIfStatement(JassParser.IfStatementContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitSimpleIfStatement(JassParser.SimpleIfStatementContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitIfElseStatement(JassParser.IfElseStatementContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitIfElseIfStatement(JassParser.IfElseIfStatementContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitParam(JassParser.ParamContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitSingleParameter(JassParser.SingleParameterContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitListParameter(JassParser.ListParameterContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitNothingParameter(JassParser.NothingParameterContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitGlobalsBlock(JassParser.GlobalsBlockContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitTypeDefinitionBlock(JassParser.TypeDefinitionBlockContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitNativeBlock(JassParser.NativeBlockContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitBlock(JassParser.BlockContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitFunctionBlock(JassParser.FunctionBlockContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitStatements(JassParser.StatementsContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitNewlines(JassParser.NewlinesContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitNewlines_opt(JassParser.Newlines_optContext ctx) { return visitChildren(ctx); } + /** + * {@inheritDoc} + * + *

The default implementation returns the result of calling + * {@link #visitChildren} on {@code ctx}.

+ */ + @Override public T visitPnewlines(JassParser.PnewlinesContext ctx) { return visitChildren(ctx); } +} \ No newline at end of file diff --git a/jassparser/build/generated-src/com/etheller/warsmash/jassparser/JassLexer.java b/jassparser/build/generated-src/com/etheller/warsmash/jassparser/JassLexer.java new file mode 100644 index 0000000..9f02f08 --- /dev/null +++ b/jassparser/build/generated-src/com/etheller/warsmash/jassparser/JassLexer.java @@ -0,0 +1,224 @@ +// Generated from Jass.g4 by ANTLR 4.7 + + package com.etheller.interpreter; + +import org.antlr.v4.runtime.Lexer; +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.Token; +import org.antlr.v4.runtime.TokenStream; +import org.antlr.v4.runtime.*; +import org.antlr.v4.runtime.atn.*; +import org.antlr.v4.runtime.dfa.DFA; +import org.antlr.v4.runtime.misc.*; + +@SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast"}) +public class JassLexer extends Lexer { + static { RuntimeMetaData.checkVersion("4.7", RuntimeMetaData.VERSION); } + + protected static final DFA[] _decisionToDFA; + protected static final PredictionContextCache _sharedContextCache = + new PredictionContextCache(); + public static final int + T__0=1, T__1=2, T__2=3, T__3=4, T__4=5, EQUALS=6, GLOBALS=7, ENDGLOBALS=8, + NATIVE=9, FUNCTION=10, TAKES=11, RETURNS=12, ENDFUNCTION=13, NOTHING=14, + CALL=15, SET=16, RETURN=17, ARRAY=18, TYPE=19, EXTENDS=20, IF=21, THEN=22, + ELSE=23, ENDIF=24, ELSEIF=25, CONSTANT=26, STRING_LITERAL=27, INTEGER=28, + NULL=29, TRUE=30, FALSE=31, NOT=32, ID=33, WS=34, NEWLINE=35; + public static String[] channelNames = { + "DEFAULT_TOKEN_CHANNEL", "HIDDEN" + }; + + public static String[] modeNames = { + "DEFAULT_MODE" + }; + + public static final String[] ruleNames = { + "T__0", "T__1", "T__2", "T__3", "T__4", "EQUALS", "GLOBALS", "ENDGLOBALS", + "NATIVE", "FUNCTION", "TAKES", "RETURNS", "ENDFUNCTION", "NOTHING", "CALL", + "SET", "RETURN", "ARRAY", "TYPE", "EXTENDS", "IF", "THEN", "ELSE", "ENDIF", + "ELSEIF", "CONSTANT", "STRING_LITERAL", "INTEGER", "NULL", "TRUE", "FALSE", + "NOT", "ID", "WS", "NEWLINE" + }; + + private static final String[] _LITERAL_NAMES = { + null, "'['", "']'", "'('", "')'", "','", "'='", "'globals'", "'endglobals'", + "'native'", "'function'", "'takes'", "'returns'", "'endfunction'", "'nothing'", + "'call'", "'set'", "'return'", "'array'", "'type'", "'extends'", "'if'", + "'then'", "'else'", "'endif'", "'elseif'", "'constant'", null, null, "'null'", + "'true'", "'false'", "'not'" + }; + private static final String[] _SYMBOLIC_NAMES = { + null, null, null, null, null, null, "EQUALS", "GLOBALS", "ENDGLOBALS", + "NATIVE", "FUNCTION", "TAKES", "RETURNS", "ENDFUNCTION", "NOTHING", "CALL", + "SET", "RETURN", "ARRAY", "TYPE", "EXTENDS", "IF", "THEN", "ELSE", "ENDIF", + "ELSEIF", "CONSTANT", "STRING_LITERAL", "INTEGER", "NULL", "TRUE", "FALSE", + "NOT", "ID", "WS", "NEWLINE" + }; + public static final Vocabulary VOCABULARY = new VocabularyImpl(_LITERAL_NAMES, _SYMBOLIC_NAMES); + + /** + * @deprecated Use {@link #VOCABULARY} instead. + */ + @Deprecated + public static final String[] tokenNames; + static { + tokenNames = new String[_SYMBOLIC_NAMES.length]; + for (int i = 0; i < tokenNames.length; i++) { + tokenNames[i] = VOCABULARY.getLiteralName(i); + if (tokenNames[i] == null) { + tokenNames[i] = VOCABULARY.getSymbolicName(i); + } + + if (tokenNames[i] == null) { + tokenNames[i] = ""; + } + } + } + + @Override + @Deprecated + public String[] getTokenNames() { + return tokenNames; + } + + @Override + + public Vocabulary getVocabulary() { + return VOCABULARY; + } + + + public JassLexer(CharStream input) { + super(input); + _interp = new LexerATNSimulator(this,_ATN,_decisionToDFA,_sharedContextCache); + } + + @Override + public String getGrammarFileName() { return "Jass.g4"; } + + @Override + public String[] getRuleNames() { return ruleNames; } + + @Override + public String getSerializedATN() { return _serializedATN; } + + @Override + public String[] getChannelNames() { return channelNames; } + + @Override + public String[] getModeNames() { return modeNames; } + + @Override + public ATN getATN() { return _ATN; } + + public static final String _serializedATN = + "\3\u608b\ua72a\u8133\ub9ed\u417c\u3be7\u7786\u5964\2%\u0139\b\1\4\2\t"+ + "\2\4\3\t\3\4\4\t\4\4\5\t\5\4\6\t\6\4\7\t\7\4\b\t\b\4\t\t\t\4\n\t\n\4\13"+ + "\t\13\4\f\t\f\4\r\t\r\4\16\t\16\4\17\t\17\4\20\t\20\4\21\t\21\4\22\t\22"+ + "\4\23\t\23\4\24\t\24\4\25\t\25\4\26\t\26\4\27\t\27\4\30\t\30\4\31\t\31"+ + "\4\32\t\32\4\33\t\33\4\34\t\34\4\35\t\35\4\36\t\36\4\37\t\37\4 \t \4!"+ + "\t!\4\"\t\"\4#\t#\4$\t$\3\2\3\2\3\3\3\3\3\4\3\4\3\5\3\5\3\6\3\6\3\7\3"+ + "\7\3\b\3\b\3\b\3\b\3\b\3\b\3\b\3\b\3\t\3\t\3\t\3\t\3\t\3\t\3\t\3\t\3\t"+ + "\3\t\3\t\3\n\3\n\3\n\3\n\3\n\3\n\3\n\3\13\3\13\3\13\3\13\3\13\3\13\3\13"+ + "\3\13\3\13\3\f\3\f\3\f\3\f\3\f\3\f\3\r\3\r\3\r\3\r\3\r\3\r\3\r\3\r\3\16"+ + "\3\16\3\16\3\16\3\16\3\16\3\16\3\16\3\16\3\16\3\16\3\16\3\17\3\17\3\17"+ + "\3\17\3\17\3\17\3\17\3\17\3\20\3\20\3\20\3\20\3\20\3\21\3\21\3\21\3\21"+ + "\3\22\3\22\3\22\3\22\3\22\3\22\3\22\3\23\3\23\3\23\3\23\3\23\3\23\3\24"+ + "\3\24\3\24\3\24\3\24\3\25\3\25\3\25\3\25\3\25\3\25\3\25\3\25\3\26\3\26"+ + "\3\26\3\27\3\27\3\27\3\27\3\27\3\30\3\30\3\30\3\30\3\30\3\31\3\31\3\31"+ + "\3\31\3\31\3\31\3\32\3\32\3\32\3\32\3\32\3\32\3\32\3\33\3\33\3\33\3\33"+ + "\3\33\3\33\3\33\3\33\3\33\3\34\3\34\7\34\u00e3\n\34\f\34\16\34\u00e6\13"+ + "\34\3\34\3\34\3\35\3\35\3\35\7\35\u00ed\n\35\f\35\16\35\u00f0\13\35\5"+ + "\35\u00f2\n\35\3\36\3\36\3\36\3\36\3\36\3\37\3\37\3\37\3\37\3\37\3 \3"+ + " \3 \3 \3 \3 \3!\3!\3!\3!\3\"\3\"\7\"\u010a\n\"\f\"\16\"\u010d\13\"\3"+ + "#\6#\u0110\n#\r#\16#\u0111\3#\3#\3$\3$\3$\3$\7$\u011a\n$\f$\16$\u011d"+ + "\13$\3$\3$\3$\3$\3$\3$\7$\u0125\n$\f$\16$\u0128\13$\3$\3$\3$\3$\3$\7$"+ + "\u012f\n$\f$\16$\u0132\13$\3$\3$\3$\3$\5$\u0138\n$\6\u00e4\u011b\u0126"+ + "\u0130\2%\3\3\5\4\7\5\t\6\13\7\r\b\17\t\21\n\23\13\25\f\27\r\31\16\33"+ + "\17\35\20\37\21!\22#\23%\24\'\25)\26+\27-\30/\31\61\32\63\33\65\34\67"+ + "\359\36;\37= ?!A\"C#E$G%\3\2\t\3\2\62\62\3\2\63;\3\2\62;\5\2C\\aac|\6"+ + "\2\62;C\\aac|\4\2\13\13\"\"\4\2\f\f\17\17\2\u0144\2\3\3\2\2\2\2\5\3\2"+ + "\2\2\2\7\3\2\2\2\2\t\3\2\2\2\2\13\3\2\2\2\2\r\3\2\2\2\2\17\3\2\2\2\2\21"+ + "\3\2\2\2\2\23\3\2\2\2\2\25\3\2\2\2\2\27\3\2\2\2\2\31\3\2\2\2\2\33\3\2"+ + "\2\2\2\35\3\2\2\2\2\37\3\2\2\2\2!\3\2\2\2\2#\3\2\2\2\2%\3\2\2\2\2\'\3"+ + "\2\2\2\2)\3\2\2\2\2+\3\2\2\2\2-\3\2\2\2\2/\3\2\2\2\2\61\3\2\2\2\2\63\3"+ + "\2\2\2\2\65\3\2\2\2\2\67\3\2\2\2\29\3\2\2\2\2;\3\2\2\2\2=\3\2\2\2\2?\3"+ + "\2\2\2\2A\3\2\2\2\2C\3\2\2\2\2E\3\2\2\2\2G\3\2\2\2\3I\3\2\2\2\5K\3\2\2"+ + "\2\7M\3\2\2\2\tO\3\2\2\2\13Q\3\2\2\2\rS\3\2\2\2\17U\3\2\2\2\21]\3\2\2"+ + "\2\23h\3\2\2\2\25o\3\2\2\2\27x\3\2\2\2\31~\3\2\2\2\33\u0086\3\2\2\2\35"+ + "\u0092\3\2\2\2\37\u009a\3\2\2\2!\u009f\3\2\2\2#\u00a3\3\2\2\2%\u00aa\3"+ + "\2\2\2\'\u00b0\3\2\2\2)\u00b5\3\2\2\2+\u00bd\3\2\2\2-\u00c0\3\2\2\2/\u00c5"+ + "\3\2\2\2\61\u00ca\3\2\2\2\63\u00d0\3\2\2\2\65\u00d7\3\2\2\2\67\u00e0\3"+ + "\2\2\29\u00f1\3\2\2\2;\u00f3\3\2\2\2=\u00f8\3\2\2\2?\u00fd\3\2\2\2A\u0103"+ + "\3\2\2\2C\u0107\3\2\2\2E\u010f\3\2\2\2G\u0137\3\2\2\2IJ\7]\2\2J\4\3\2"+ + "\2\2KL\7_\2\2L\6\3\2\2\2MN\7*\2\2N\b\3\2\2\2OP\7+\2\2P\n\3\2\2\2QR\7."+ + "\2\2R\f\3\2\2\2ST\7?\2\2T\16\3\2\2\2UV\7i\2\2VW\7n\2\2WX\7q\2\2XY\7d\2"+ + "\2YZ\7c\2\2Z[\7n\2\2[\\\7u\2\2\\\20\3\2\2\2]^\7g\2\2^_\7p\2\2_`\7f\2\2"+ + "`a\7i\2\2ab\7n\2\2bc\7q\2\2cd\7d\2\2de\7c\2\2ef\7n\2\2fg\7u\2\2g\22\3"+ + "\2\2\2hi\7p\2\2ij\7c\2\2jk\7v\2\2kl\7k\2\2lm\7x\2\2mn\7g\2\2n\24\3\2\2"+ + "\2op\7h\2\2pq\7w\2\2qr\7p\2\2rs\7e\2\2st\7v\2\2tu\7k\2\2uv\7q\2\2vw\7"+ + "p\2\2w\26\3\2\2\2xy\7v\2\2yz\7c\2\2z{\7m\2\2{|\7g\2\2|}\7u\2\2}\30\3\2"+ + "\2\2~\177\7t\2\2\177\u0080\7g\2\2\u0080\u0081\7v\2\2\u0081\u0082\7w\2"+ + "\2\u0082\u0083\7t\2\2\u0083\u0084\7p\2\2\u0084\u0085\7u\2\2\u0085\32\3"+ + "\2\2\2\u0086\u0087\7g\2\2\u0087\u0088\7p\2\2\u0088\u0089\7f\2\2\u0089"+ + "\u008a\7h\2\2\u008a\u008b\7w\2\2\u008b\u008c\7p\2\2\u008c\u008d\7e\2\2"+ + "\u008d\u008e\7v\2\2\u008e\u008f\7k\2\2\u008f\u0090\7q\2\2\u0090\u0091"+ + "\7p\2\2\u0091\34\3\2\2\2\u0092\u0093\7p\2\2\u0093\u0094\7q\2\2\u0094\u0095"+ + "\7v\2\2\u0095\u0096\7j\2\2\u0096\u0097\7k\2\2\u0097\u0098\7p\2\2\u0098"+ + "\u0099\7i\2\2\u0099\36\3\2\2\2\u009a\u009b\7e\2\2\u009b\u009c\7c\2\2\u009c"+ + "\u009d\7n\2\2\u009d\u009e\7n\2\2\u009e \3\2\2\2\u009f\u00a0\7u\2\2\u00a0"+ + "\u00a1\7g\2\2\u00a1\u00a2\7v\2\2\u00a2\"\3\2\2\2\u00a3\u00a4\7t\2\2\u00a4"+ + "\u00a5\7g\2\2\u00a5\u00a6\7v\2\2\u00a6\u00a7\7w\2\2\u00a7\u00a8\7t\2\2"+ + "\u00a8\u00a9\7p\2\2\u00a9$\3\2\2\2\u00aa\u00ab\7c\2\2\u00ab\u00ac\7t\2"+ + "\2\u00ac\u00ad\7t\2\2\u00ad\u00ae\7c\2\2\u00ae\u00af\7{\2\2\u00af&\3\2"+ + "\2\2\u00b0\u00b1\7v\2\2\u00b1\u00b2\7{\2\2\u00b2\u00b3\7r\2\2\u00b3\u00b4"+ + "\7g\2\2\u00b4(\3\2\2\2\u00b5\u00b6\7g\2\2\u00b6\u00b7\7z\2\2\u00b7\u00b8"+ + "\7v\2\2\u00b8\u00b9\7g\2\2\u00b9\u00ba\7p\2\2\u00ba\u00bb\7f\2\2\u00bb"+ + "\u00bc\7u\2\2\u00bc*\3\2\2\2\u00bd\u00be\7k\2\2\u00be\u00bf\7h\2\2\u00bf"+ + ",\3\2\2\2\u00c0\u00c1\7v\2\2\u00c1\u00c2\7j\2\2\u00c2\u00c3\7g\2\2\u00c3"+ + "\u00c4\7p\2\2\u00c4.\3\2\2\2\u00c5\u00c6\7g\2\2\u00c6\u00c7\7n\2\2\u00c7"+ + "\u00c8\7u\2\2\u00c8\u00c9\7g\2\2\u00c9\60\3\2\2\2\u00ca\u00cb\7g\2\2\u00cb"+ + "\u00cc\7p\2\2\u00cc\u00cd\7f\2\2\u00cd\u00ce\7k\2\2\u00ce\u00cf\7h\2\2"+ + "\u00cf\62\3\2\2\2\u00d0\u00d1\7g\2\2\u00d1\u00d2\7n\2\2\u00d2\u00d3\7"+ + "u\2\2\u00d3\u00d4\7g\2\2\u00d4\u00d5\7k\2\2\u00d5\u00d6\7h\2\2\u00d6\64"+ + "\3\2\2\2\u00d7\u00d8\7e\2\2\u00d8\u00d9\7q\2\2\u00d9\u00da\7p\2\2\u00da"+ + "\u00db\7u\2\2\u00db\u00dc\7v\2\2\u00dc\u00dd\7c\2\2\u00dd\u00de\7p\2\2"+ + "\u00de\u00df\7v\2\2\u00df\66\3\2\2\2\u00e0\u00e4\7$\2\2\u00e1\u00e3\13"+ + "\2\2\2\u00e2\u00e1\3\2\2\2\u00e3\u00e6\3\2\2\2\u00e4\u00e5\3\2\2\2\u00e4"+ + "\u00e2\3\2\2\2\u00e5\u00e7\3\2\2\2\u00e6\u00e4\3\2\2\2\u00e7\u00e8\7$"+ + "\2\2\u00e88\3\2\2\2\u00e9\u00f2\t\2\2\2\u00ea\u00ee\t\3\2\2\u00eb\u00ed"+ + "\t\4\2\2\u00ec\u00eb\3\2\2\2\u00ed\u00f0\3\2\2\2\u00ee\u00ec\3\2\2\2\u00ee"+ + "\u00ef\3\2\2\2\u00ef\u00f2\3\2\2\2\u00f0\u00ee\3\2\2\2\u00f1\u00e9\3\2"+ + "\2\2\u00f1\u00ea\3\2\2\2\u00f2:\3\2\2\2\u00f3\u00f4\7p\2\2\u00f4\u00f5"+ + "\7w\2\2\u00f5\u00f6\7n\2\2\u00f6\u00f7\7n\2\2\u00f7<\3\2\2\2\u00f8\u00f9"+ + "\7v\2\2\u00f9\u00fa\7t\2\2\u00fa\u00fb\7w\2\2\u00fb\u00fc\7g\2\2\u00fc"+ + ">\3\2\2\2\u00fd\u00fe\7h\2\2\u00fe\u00ff\7c\2\2\u00ff\u0100\7n\2\2\u0100"+ + "\u0101\7u\2\2\u0101\u0102\7g\2\2\u0102@\3\2\2\2\u0103\u0104\7p\2\2\u0104"+ + "\u0105\7q\2\2\u0105\u0106\7v\2\2\u0106B\3\2\2\2\u0107\u010b\t\5\2\2\u0108"+ + "\u010a\t\6\2\2\u0109\u0108\3\2\2\2\u010a\u010d\3\2\2\2\u010b\u0109\3\2"+ + "\2\2\u010b\u010c\3\2\2\2\u010cD\3\2\2\2\u010d\u010b\3\2\2\2\u010e\u0110"+ + "\t\7\2\2\u010f\u010e\3\2\2\2\u0110\u0111\3\2\2\2\u0111\u010f\3\2\2\2\u0111"+ + "\u0112\3\2\2\2\u0112\u0113\3\2\2\2\u0113\u0114\b#\2\2\u0114F\3\2\2\2\u0115"+ + "\u0116\7\61\2\2\u0116\u0117\7\61\2\2\u0117\u011b\3\2\2\2\u0118\u011a\13"+ + "\2\2\2\u0119\u0118\3\2\2\2\u011a\u011d\3\2\2\2\u011b\u011c\3\2\2\2\u011b"+ + "\u0119\3\2\2\2\u011c\u011e\3\2\2\2\u011d\u011b\3\2\2\2\u011e\u011f\7\17"+ + "\2\2\u011f\u0138\7\f\2\2\u0120\u0121\7\61\2\2\u0121\u0122\7\61\2\2\u0122"+ + "\u0126\3\2\2\2\u0123\u0125\13\2\2\2\u0124\u0123\3\2\2\2\u0125\u0128\3"+ + "\2\2\2\u0126\u0127\3\2\2\2\u0126\u0124\3\2\2\2\u0127\u0129\3\2\2\2\u0128"+ + "\u0126\3\2\2\2\u0129\u0138\7\f\2\2\u012a\u012b\7\61\2\2\u012b\u012c\7"+ + "\61\2\2\u012c\u0130\3\2\2\2\u012d\u012f\13\2\2\2\u012e\u012d\3\2\2\2\u012f"+ + "\u0132\3\2\2\2\u0130\u0131\3\2\2\2\u0130\u012e\3\2\2\2\u0131\u0133\3\2"+ + "\2\2\u0132\u0130\3\2\2\2\u0133\u0138\7\17\2\2\u0134\u0135\7\17\2\2\u0135"+ + "\u0138\7\f\2\2\u0136\u0138\t\b\2\2\u0137\u0115\3\2\2\2\u0137\u0120\3\2"+ + "\2\2\u0137\u012a\3\2\2\2\u0137\u0134\3\2\2\2\u0137\u0136\3\2\2\2\u0138"+ + "H\3\2\2\2\f\2\u00e4\u00ee\u00f1\u010b\u0111\u011b\u0126\u0130\u0137\3"+ + "\b\2\2"; + public static final ATN _ATN = + new ATNDeserializer().deserialize(_serializedATN.toCharArray()); + static { + _decisionToDFA = new DFA[_ATN.getNumberOfDecisions()]; + for (int i = 0; i < _ATN.getNumberOfDecisions(); i++) { + _decisionToDFA[i] = new DFA(_ATN.getDecisionState(i), i); + } + } +} \ No newline at end of file diff --git a/jassparser/build/generated-src/com/etheller/warsmash/jassparser/JassParser.java b/jassparser/build/generated-src/com/etheller/warsmash/jassparser/JassParser.java new file mode 100644 index 0000000..886a399 --- /dev/null +++ b/jassparser/build/generated-src/com/etheller/warsmash/jassparser/JassParser.java @@ -0,0 +1,1969 @@ +// Generated from Jass.g4 by ANTLR 4.7 + + package com.etheller.interpreter; + +import org.antlr.v4.runtime.atn.*; +import org.antlr.v4.runtime.dfa.DFA; +import org.antlr.v4.runtime.*; +import org.antlr.v4.runtime.misc.*; +import org.antlr.v4.runtime.tree.*; +import java.util.List; +import java.util.Iterator; +import java.util.ArrayList; + +@SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast"}) +public class JassParser extends Parser { + static { RuntimeMetaData.checkVersion("4.7", RuntimeMetaData.VERSION); } + + protected static final DFA[] _decisionToDFA; + protected static final PredictionContextCache _sharedContextCache = + new PredictionContextCache(); + public static final int + T__0=1, T__1=2, T__2=3, T__3=4, T__4=5, EQUALS=6, GLOBALS=7, ENDGLOBALS=8, + NATIVE=9, FUNCTION=10, TAKES=11, RETURNS=12, ENDFUNCTION=13, NOTHING=14, + CALL=15, SET=16, RETURN=17, ARRAY=18, TYPE=19, EXTENDS=20, IF=21, THEN=22, + ELSE=23, ENDIF=24, ELSEIF=25, CONSTANT=26, STRING_LITERAL=27, INTEGER=28, + NULL=29, TRUE=30, FALSE=31, NOT=32, ID=33, WS=34, NEWLINE=35; + public static final int + RULE_program = 0, RULE_typeDefinition = 1, RULE_type = 2, RULE_global = 3, + RULE_assignTail = 4, RULE_expression = 5, RULE_functionExpression = 6, + RULE_argsList = 7, RULE_statement = 8, RULE_ifStatementPartial = 9, RULE_param = 10, + RULE_paramList = 11, RULE_globalsBlock = 12, RULE_typeDefinitionBlock = 13, + RULE_nativeBlock = 14, RULE_block = 15, RULE_functionBlock = 16, RULE_statements = 17, + RULE_newlines = 18, RULE_newlines_opt = 19, RULE_pnewlines = 20; + public static final String[] ruleNames = { + "program", "typeDefinition", "type", "global", "assignTail", "expression", + "functionExpression", "argsList", "statement", "ifStatementPartial", "param", + "paramList", "globalsBlock", "typeDefinitionBlock", "nativeBlock", "block", + "functionBlock", "statements", "newlines", "newlines_opt", "pnewlines" + }; + + private static final String[] _LITERAL_NAMES = { + null, "'['", "']'", "'('", "')'", "','", "'='", "'globals'", "'endglobals'", + "'native'", "'function'", "'takes'", "'returns'", "'endfunction'", "'nothing'", + "'call'", "'set'", "'return'", "'array'", "'type'", "'extends'", "'if'", + "'then'", "'else'", "'endif'", "'elseif'", "'constant'", null, null, "'null'", + "'true'", "'false'", "'not'" + }; + private static final String[] _SYMBOLIC_NAMES = { + null, null, null, null, null, null, "EQUALS", "GLOBALS", "ENDGLOBALS", + "NATIVE", "FUNCTION", "TAKES", "RETURNS", "ENDFUNCTION", "NOTHING", "CALL", + "SET", "RETURN", "ARRAY", "TYPE", "EXTENDS", "IF", "THEN", "ELSE", "ENDIF", + "ELSEIF", "CONSTANT", "STRING_LITERAL", "INTEGER", "NULL", "TRUE", "FALSE", + "NOT", "ID", "WS", "NEWLINE" + }; + public static final Vocabulary VOCABULARY = new VocabularyImpl(_LITERAL_NAMES, _SYMBOLIC_NAMES); + + /** + * @deprecated Use {@link #VOCABULARY} instead. + */ + @Deprecated + public static final String[] tokenNames; + static { + tokenNames = new String[_SYMBOLIC_NAMES.length]; + for (int i = 0; i < tokenNames.length; i++) { + tokenNames[i] = VOCABULARY.getLiteralName(i); + if (tokenNames[i] == null) { + tokenNames[i] = VOCABULARY.getSymbolicName(i); + } + + if (tokenNames[i] == null) { + tokenNames[i] = ""; + } + } + } + + @Override + @Deprecated + public String[] getTokenNames() { + return tokenNames; + } + + @Override + + public Vocabulary getVocabulary() { + return VOCABULARY; + } + + @Override + public String getGrammarFileName() { return "Jass.g4"; } + + @Override + public String[] getRuleNames() { return ruleNames; } + + @Override + public String getSerializedATN() { return _serializedATN; } + + @Override + public ATN getATN() { return _ATN; } + + public JassParser(TokenStream input) { + super(input); + _interp = new ParserATNSimulator(this,_ATN,_decisionToDFA,_sharedContextCache); + } + public static class ProgramContext extends ParserRuleContext { + public NewlinesContext newlines() { + return getRuleContext(NewlinesContext.class,0); + } + public Newlines_optContext newlines_opt() { + return getRuleContext(Newlines_optContext.class,0); + } + public TypeDefinitionBlockContext typeDefinitionBlock() { + return getRuleContext(TypeDefinitionBlockContext.class,0); + } + public List block() { + return getRuleContexts(BlockContext.class); + } + public BlockContext block(int i) { + return getRuleContext(BlockContext.class,i); + } + public List functionBlock() { + return getRuleContexts(FunctionBlockContext.class); + } + public FunctionBlockContext functionBlock(int i) { + return getRuleContext(FunctionBlockContext.class,i); + } + public ProgramContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_program; } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitProgram(this); + else return visitor.visitChildren(this); + } + } + + public final ProgramContext program() throws RecognitionException { + ProgramContext _localctx = new ProgramContext(_ctx, getState()); + enterRule(_localctx, 0, RULE_program); + int _la; + try { + setState(57); + _errHandler.sync(this); + switch ( getInterpreter().adaptivePredict(_input,2,_ctx) ) { + case 1: + enterOuterAlt(_localctx, 1); + { + setState(42); + newlines(); + } + break; + case 2: + enterOuterAlt(_localctx, 2); + { + setState(43); + newlines_opt(); + setState(44); + typeDefinitionBlock(); + setState(48); + _errHandler.sync(this); + _la = _input.LA(1); + while ((((_la) & ~0x3f) == 0 && ((1L << _la) & ((1L << GLOBALS) | (1L << NATIVE) | (1L << CONSTANT))) != 0)) { + { + { + setState(45); + block(); + } + } + setState(50); + _errHandler.sync(this); + _la = _input.LA(1); + } + setState(54); + _errHandler.sync(this); + _la = _input.LA(1); + while (_la==FUNCTION) { + { + { + setState(51); + functionBlock(); + } + } + setState(56); + _errHandler.sync(this); + _la = _input.LA(1); + } + } + break; + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + public static class TypeDefinitionContext extends ParserRuleContext { + public TerminalNode TYPE() { return getToken(JassParser.TYPE, 0); } + public List ID() { return getTokens(JassParser.ID); } + public TerminalNode ID(int i) { + return getToken(JassParser.ID, i); + } + public TerminalNode EXTENDS() { return getToken(JassParser.EXTENDS, 0); } + public NewlinesContext newlines() { + return getRuleContext(NewlinesContext.class,0); + } + public TypeDefinitionContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_typeDefinition; } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitTypeDefinition(this); + else return visitor.visitChildren(this); + } + } + + public final TypeDefinitionContext typeDefinition() throws RecognitionException { + TypeDefinitionContext _localctx = new TypeDefinitionContext(_ctx, getState()); + enterRule(_localctx, 2, RULE_typeDefinition); + try { + enterOuterAlt(_localctx, 1); + { + setState(59); + match(TYPE); + setState(60); + match(ID); + setState(61); + match(EXTENDS); + setState(62); + match(ID); + setState(63); + newlines(); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + public static class TypeContext extends ParserRuleContext { + public TypeContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_type; } + + public TypeContext() { } + public void copyFrom(TypeContext ctx) { + super.copyFrom(ctx); + } + } + public static class ArrayTypeContext extends TypeContext { + public TerminalNode ID() { return getToken(JassParser.ID, 0); } + public TerminalNode ARRAY() { return getToken(JassParser.ARRAY, 0); } + public ArrayTypeContext(TypeContext ctx) { copyFrom(ctx); } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitArrayType(this); + else return visitor.visitChildren(this); + } + } + public static class BasicTypeContext extends TypeContext { + public TerminalNode ID() { return getToken(JassParser.ID, 0); } + public BasicTypeContext(TypeContext ctx) { copyFrom(ctx); } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitBasicType(this); + else return visitor.visitChildren(this); + } + } + public static class NothingTypeContext extends TypeContext { + public TerminalNode NOTHING() { return getToken(JassParser.NOTHING, 0); } + public NothingTypeContext(TypeContext ctx) { copyFrom(ctx); } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitNothingType(this); + else return visitor.visitChildren(this); + } + } + + public final TypeContext type() throws RecognitionException { + TypeContext _localctx = new TypeContext(_ctx, getState()); + enterRule(_localctx, 4, RULE_type); + try { + setState(69); + _errHandler.sync(this); + switch ( getInterpreter().adaptivePredict(_input,3,_ctx) ) { + case 1: + _localctx = new BasicTypeContext(_localctx); + enterOuterAlt(_localctx, 1); + { + setState(65); + match(ID); + } + break; + case 2: + _localctx = new ArrayTypeContext(_localctx); + enterOuterAlt(_localctx, 2); + { + setState(66); + match(ID); + setState(67); + match(ARRAY); + } + break; + case 3: + _localctx = new NothingTypeContext(_localctx); + enterOuterAlt(_localctx, 3); + { + setState(68); + match(NOTHING); + } + break; + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + public static class GlobalContext extends ParserRuleContext { + public GlobalContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_global; } + + public GlobalContext() { } + public void copyFrom(GlobalContext ctx) { + super.copyFrom(ctx); + } + } + public static class DefinitionGlobalContext extends GlobalContext { + public TypeContext type() { + return getRuleContext(TypeContext.class,0); + } + public TerminalNode ID() { return getToken(JassParser.ID, 0); } + public AssignTailContext assignTail() { + return getRuleContext(AssignTailContext.class,0); + } + public NewlinesContext newlines() { + return getRuleContext(NewlinesContext.class,0); + } + public TerminalNode CONSTANT() { return getToken(JassParser.CONSTANT, 0); } + public DefinitionGlobalContext(GlobalContext ctx) { copyFrom(ctx); } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitDefinitionGlobal(this); + else return visitor.visitChildren(this); + } + } + public static class BasicGlobalContext extends GlobalContext { + public TypeContext type() { + return getRuleContext(TypeContext.class,0); + } + public TerminalNode ID() { return getToken(JassParser.ID, 0); } + public NewlinesContext newlines() { + return getRuleContext(NewlinesContext.class,0); + } + public TerminalNode CONSTANT() { return getToken(JassParser.CONSTANT, 0); } + public BasicGlobalContext(GlobalContext ctx) { copyFrom(ctx); } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitBasicGlobal(this); + else return visitor.visitChildren(this); + } + } + + public final GlobalContext global() throws RecognitionException { + GlobalContext _localctx = new GlobalContext(_ctx, getState()); + enterRule(_localctx, 6, RULE_global); + int _la; + try { + setState(86); + _errHandler.sync(this); + switch ( getInterpreter().adaptivePredict(_input,6,_ctx) ) { + case 1: + _localctx = new BasicGlobalContext(_localctx); + enterOuterAlt(_localctx, 1); + { + setState(72); + _errHandler.sync(this); + _la = _input.LA(1); + if (_la==CONSTANT) { + { + setState(71); + match(CONSTANT); + } + } + + setState(74); + type(); + setState(75); + match(ID); + setState(76); + newlines(); + } + break; + case 2: + _localctx = new DefinitionGlobalContext(_localctx); + enterOuterAlt(_localctx, 2); + { + setState(79); + _errHandler.sync(this); + _la = _input.LA(1); + if (_la==CONSTANT) { + { + setState(78); + match(CONSTANT); + } + } + + setState(81); + type(); + setState(82); + match(ID); + setState(83); + assignTail(); + setState(84); + newlines(); + } + break; + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + public static class AssignTailContext extends ParserRuleContext { + public TerminalNode EQUALS() { return getToken(JassParser.EQUALS, 0); } + public ExpressionContext expression() { + return getRuleContext(ExpressionContext.class,0); + } + public AssignTailContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_assignTail; } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitAssignTail(this); + else return visitor.visitChildren(this); + } + } + + public final AssignTailContext assignTail() throws RecognitionException { + AssignTailContext _localctx = new AssignTailContext(_ctx, getState()); + enterRule(_localctx, 8, RULE_assignTail); + try { + enterOuterAlt(_localctx, 1); + { + setState(88); + match(EQUALS); + setState(89); + expression(); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + public static class ExpressionContext extends ParserRuleContext { + public ExpressionContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_expression; } + + public ExpressionContext() { } + public void copyFrom(ExpressionContext ctx) { + super.copyFrom(ctx); + } + } + public static class TrueExpressionContext extends ExpressionContext { + public TerminalNode TRUE() { return getToken(JassParser.TRUE, 0); } + public TrueExpressionContext(ExpressionContext ctx) { copyFrom(ctx); } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitTrueExpression(this); + else return visitor.visitChildren(this); + } + } + public static class ParentheticalExpressionContext extends ExpressionContext { + public ExpressionContext expression() { + return getRuleContext(ExpressionContext.class,0); + } + public ParentheticalExpressionContext(ExpressionContext ctx) { copyFrom(ctx); } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitParentheticalExpression(this); + else return visitor.visitChildren(this); + } + } + public static class StringLiteralExpressionContext extends ExpressionContext { + public TerminalNode STRING_LITERAL() { return getToken(JassParser.STRING_LITERAL, 0); } + public StringLiteralExpressionContext(ExpressionContext ctx) { copyFrom(ctx); } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitStringLiteralExpression(this); + else return visitor.visitChildren(this); + } + } + public static class IntegerLiteralExpressionContext extends ExpressionContext { + public TerminalNode INTEGER() { return getToken(JassParser.INTEGER, 0); } + public IntegerLiteralExpressionContext(ExpressionContext ctx) { copyFrom(ctx); } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitIntegerLiteralExpression(this); + else return visitor.visitChildren(this); + } + } + public static class ReferenceExpressionContext extends ExpressionContext { + public TerminalNode ID() { return getToken(JassParser.ID, 0); } + public ReferenceExpressionContext(ExpressionContext ctx) { copyFrom(ctx); } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitReferenceExpression(this); + else return visitor.visitChildren(this); + } + } + public static class FunctionReferenceExpressionContext extends ExpressionContext { + public TerminalNode FUNCTION() { return getToken(JassParser.FUNCTION, 0); } + public TerminalNode ID() { return getToken(JassParser.ID, 0); } + public FunctionReferenceExpressionContext(ExpressionContext ctx) { copyFrom(ctx); } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitFunctionReferenceExpression(this); + else return visitor.visitChildren(this); + } + } + public static class NotExpressionContext extends ExpressionContext { + public TerminalNode NOT() { return getToken(JassParser.NOT, 0); } + public ExpressionContext expression() { + return getRuleContext(ExpressionContext.class,0); + } + public NotExpressionContext(ExpressionContext ctx) { copyFrom(ctx); } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitNotExpression(this); + else return visitor.visitChildren(this); + } + } + public static class ArrayReferenceExpressionContext extends ExpressionContext { + public TerminalNode ID() { return getToken(JassParser.ID, 0); } + public ExpressionContext expression() { + return getRuleContext(ExpressionContext.class,0); + } + public ArrayReferenceExpressionContext(ExpressionContext ctx) { copyFrom(ctx); } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitArrayReferenceExpression(this); + else return visitor.visitChildren(this); + } + } + public static class FunctionCallExpressionContext extends ExpressionContext { + public FunctionExpressionContext functionExpression() { + return getRuleContext(FunctionExpressionContext.class,0); + } + public FunctionCallExpressionContext(ExpressionContext ctx) { copyFrom(ctx); } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitFunctionCallExpression(this); + else return visitor.visitChildren(this); + } + } + public static class NullExpressionContext extends ExpressionContext { + public TerminalNode NULL() { return getToken(JassParser.NULL, 0); } + public NullExpressionContext(ExpressionContext ctx) { copyFrom(ctx); } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitNullExpression(this); + else return visitor.visitChildren(this); + } + } + public static class FalseExpressionContext extends ExpressionContext { + public TerminalNode FALSE() { return getToken(JassParser.FALSE, 0); } + public FalseExpressionContext(ExpressionContext ctx) { copyFrom(ctx); } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitFalseExpression(this); + else return visitor.visitChildren(this); + } + } + + public final ExpressionContext expression() throws RecognitionException { + ExpressionContext _localctx = new ExpressionContext(_ctx, getState()); + enterRule(_localctx, 10, RULE_expression); + try { + setState(111); + _errHandler.sync(this); + switch ( getInterpreter().adaptivePredict(_input,7,_ctx) ) { + case 1: + _localctx = new ReferenceExpressionContext(_localctx); + enterOuterAlt(_localctx, 1); + { + setState(91); + match(ID); + } + break; + case 2: + _localctx = new StringLiteralExpressionContext(_localctx); + enterOuterAlt(_localctx, 2); + { + setState(92); + match(STRING_LITERAL); + } + break; + case 3: + _localctx = new IntegerLiteralExpressionContext(_localctx); + enterOuterAlt(_localctx, 3); + { + setState(93); + match(INTEGER); + } + break; + case 4: + _localctx = new FunctionReferenceExpressionContext(_localctx); + enterOuterAlt(_localctx, 4); + { + setState(94); + match(FUNCTION); + setState(95); + match(ID); + } + break; + case 5: + _localctx = new NullExpressionContext(_localctx); + enterOuterAlt(_localctx, 5); + { + setState(96); + match(NULL); + } + break; + case 6: + _localctx = new TrueExpressionContext(_localctx); + enterOuterAlt(_localctx, 6); + { + setState(97); + match(TRUE); + } + break; + case 7: + _localctx = new FalseExpressionContext(_localctx); + enterOuterAlt(_localctx, 7); + { + setState(98); + match(FALSE); + } + break; + case 8: + _localctx = new ArrayReferenceExpressionContext(_localctx); + enterOuterAlt(_localctx, 8); + { + setState(99); + match(ID); + setState(100); + match(T__0); + setState(101); + expression(); + setState(102); + match(T__1); + } + break; + case 9: + _localctx = new FunctionCallExpressionContext(_localctx); + enterOuterAlt(_localctx, 9); + { + setState(104); + functionExpression(); + } + break; + case 10: + _localctx = new ParentheticalExpressionContext(_localctx); + enterOuterAlt(_localctx, 10); + { + setState(105); + match(T__2); + setState(106); + expression(); + setState(107); + match(T__3); + } + break; + case 11: + _localctx = new NotExpressionContext(_localctx); + enterOuterAlt(_localctx, 11); + { + setState(109); + match(NOT); + setState(110); + expression(); + } + break; + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + public static class FunctionExpressionContext extends ParserRuleContext { + public TerminalNode ID() { return getToken(JassParser.ID, 0); } + public ArgsListContext argsList() { + return getRuleContext(ArgsListContext.class,0); + } + public FunctionExpressionContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_functionExpression; } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitFunctionExpression(this); + else return visitor.visitChildren(this); + } + } + + public final FunctionExpressionContext functionExpression() throws RecognitionException { + FunctionExpressionContext _localctx = new FunctionExpressionContext(_ctx, getState()); + enterRule(_localctx, 12, RULE_functionExpression); + try { + setState(121); + _errHandler.sync(this); + switch ( getInterpreter().adaptivePredict(_input,8,_ctx) ) { + case 1: + enterOuterAlt(_localctx, 1); + { + setState(113); + match(ID); + setState(114); + match(T__2); + setState(115); + argsList(); + setState(116); + match(T__3); + } + break; + case 2: + enterOuterAlt(_localctx, 2); + { + setState(118); + match(ID); + setState(119); + match(T__2); + setState(120); + match(T__3); + } + break; + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + public static class ArgsListContext extends ParserRuleContext { + public ArgsListContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_argsList; } + + public ArgsListContext() { } + public void copyFrom(ArgsListContext ctx) { + super.copyFrom(ctx); + } + } + public static class SingleArgumentContext extends ArgsListContext { + public ExpressionContext expression() { + return getRuleContext(ExpressionContext.class,0); + } + public SingleArgumentContext(ArgsListContext ctx) { copyFrom(ctx); } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitSingleArgument(this); + else return visitor.visitChildren(this); + } + } + public static class ListArgumentContext extends ArgsListContext { + public ExpressionContext expression() { + return getRuleContext(ExpressionContext.class,0); + } + public ArgsListContext argsList() { + return getRuleContext(ArgsListContext.class,0); + } + public ListArgumentContext(ArgsListContext ctx) { copyFrom(ctx); } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitListArgument(this); + else return visitor.visitChildren(this); + } + } + + public final ArgsListContext argsList() throws RecognitionException { + ArgsListContext _localctx = new ArgsListContext(_ctx, getState()); + enterRule(_localctx, 14, RULE_argsList); + try { + setState(128); + _errHandler.sync(this); + switch ( getInterpreter().adaptivePredict(_input,9,_ctx) ) { + case 1: + _localctx = new SingleArgumentContext(_localctx); + enterOuterAlt(_localctx, 1); + { + setState(123); + expression(); + } + break; + case 2: + _localctx = new ListArgumentContext(_localctx); + enterOuterAlt(_localctx, 2); + { + setState(124); + expression(); + setState(125); + match(T__4); + setState(126); + argsList(); + } + break; + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + public static class StatementContext extends ParserRuleContext { + public StatementContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_statement; } + + public StatementContext() { } + public void copyFrom(StatementContext ctx) { + super.copyFrom(ctx); + } + } + public static class ArrayedAssignmentStatementContext extends StatementContext { + public TerminalNode SET() { return getToken(JassParser.SET, 0); } + public TerminalNode ID() { return getToken(JassParser.ID, 0); } + public List expression() { + return getRuleContexts(ExpressionContext.class); + } + public ExpressionContext expression(int i) { + return getRuleContext(ExpressionContext.class,i); + } + public TerminalNode EQUALS() { return getToken(JassParser.EQUALS, 0); } + public NewlinesContext newlines() { + return getRuleContext(NewlinesContext.class,0); + } + public ArrayedAssignmentStatementContext(StatementContext ctx) { copyFrom(ctx); } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitArrayedAssignmentStatement(this); + else return visitor.visitChildren(this); + } + } + public static class IfStatementContext extends StatementContext { + public TerminalNode IF() { return getToken(JassParser.IF, 0); } + public IfStatementPartialContext ifStatementPartial() { + return getRuleContext(IfStatementPartialContext.class,0); + } + public IfStatementContext(StatementContext ctx) { copyFrom(ctx); } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitIfStatement(this); + else return visitor.visitChildren(this); + } + } + public static class ReturnStatementContext extends StatementContext { + public TerminalNode RETURN() { return getToken(JassParser.RETURN, 0); } + public ExpressionContext expression() { + return getRuleContext(ExpressionContext.class,0); + } + public NewlinesContext newlines() { + return getRuleContext(NewlinesContext.class,0); + } + public ReturnStatementContext(StatementContext ctx) { copyFrom(ctx); } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitReturnStatement(this); + else return visitor.visitChildren(this); + } + } + public static class CallStatementContext extends StatementContext { + public TerminalNode CALL() { return getToken(JassParser.CALL, 0); } + public FunctionExpressionContext functionExpression() { + return getRuleContext(FunctionExpressionContext.class,0); + } + public NewlinesContext newlines() { + return getRuleContext(NewlinesContext.class,0); + } + public CallStatementContext(StatementContext ctx) { copyFrom(ctx); } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitCallStatement(this); + else return visitor.visitChildren(this); + } + } + public static class SetStatementContext extends StatementContext { + public TerminalNode SET() { return getToken(JassParser.SET, 0); } + public TerminalNode ID() { return getToken(JassParser.ID, 0); } + public TerminalNode EQUALS() { return getToken(JassParser.EQUALS, 0); } + public ExpressionContext expression() { + return getRuleContext(ExpressionContext.class,0); + } + public NewlinesContext newlines() { + return getRuleContext(NewlinesContext.class,0); + } + public SetStatementContext(StatementContext ctx) { copyFrom(ctx); } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitSetStatement(this); + else return visitor.visitChildren(this); + } + } + + public final StatementContext statement() throws RecognitionException { + StatementContext _localctx = new StatementContext(_ctx, getState()); + enterRule(_localctx, 16, RULE_statement); + try { + setState(155); + _errHandler.sync(this); + switch ( getInterpreter().adaptivePredict(_input,10,_ctx) ) { + case 1: + _localctx = new CallStatementContext(_localctx); + enterOuterAlt(_localctx, 1); + { + setState(130); + match(CALL); + setState(131); + functionExpression(); + setState(132); + newlines(); + } + break; + case 2: + _localctx = new SetStatementContext(_localctx); + enterOuterAlt(_localctx, 2); + { + setState(134); + match(SET); + setState(135); + match(ID); + setState(136); + match(EQUALS); + setState(137); + expression(); + setState(138); + newlines(); + } + break; + case 3: + _localctx = new ArrayedAssignmentStatementContext(_localctx); + enterOuterAlt(_localctx, 3); + { + setState(140); + match(SET); + setState(141); + match(ID); + setState(142); + match(T__0); + setState(143); + expression(); + setState(144); + match(T__1); + setState(145); + match(EQUALS); + setState(146); + expression(); + setState(147); + newlines(); + } + break; + case 4: + _localctx = new ReturnStatementContext(_localctx); + enterOuterAlt(_localctx, 4); + { + setState(149); + match(RETURN); + setState(150); + expression(); + setState(151); + newlines(); + } + break; + case 5: + _localctx = new IfStatementContext(_localctx); + enterOuterAlt(_localctx, 5); + { + setState(153); + match(IF); + setState(154); + ifStatementPartial(); + } + break; + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + public static class IfStatementPartialContext extends ParserRuleContext { + public IfStatementPartialContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_ifStatementPartial; } + + public IfStatementPartialContext() { } + public void copyFrom(IfStatementPartialContext ctx) { + super.copyFrom(ctx); + } + } + public static class IfElseIfStatementContext extends IfStatementPartialContext { + public ExpressionContext expression() { + return getRuleContext(ExpressionContext.class,0); + } + public TerminalNode THEN() { return getToken(JassParser.THEN, 0); } + public NewlinesContext newlines() { + return getRuleContext(NewlinesContext.class,0); + } + public StatementsContext statements() { + return getRuleContext(StatementsContext.class,0); + } + public TerminalNode ELSEIF() { return getToken(JassParser.ELSEIF, 0); } + public IfStatementPartialContext ifStatementPartial() { + return getRuleContext(IfStatementPartialContext.class,0); + } + public IfElseIfStatementContext(IfStatementPartialContext ctx) { copyFrom(ctx); } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitIfElseIfStatement(this); + else return visitor.visitChildren(this); + } + } + public static class IfElseStatementContext extends IfStatementPartialContext { + public ExpressionContext expression() { + return getRuleContext(ExpressionContext.class,0); + } + public TerminalNode THEN() { return getToken(JassParser.THEN, 0); } + public List newlines() { + return getRuleContexts(NewlinesContext.class); + } + public NewlinesContext newlines(int i) { + return getRuleContext(NewlinesContext.class,i); + } + public List statements() { + return getRuleContexts(StatementsContext.class); + } + public StatementsContext statements(int i) { + return getRuleContext(StatementsContext.class,i); + } + public TerminalNode ELSE() { return getToken(JassParser.ELSE, 0); } + public TerminalNode ENDIF() { return getToken(JassParser.ENDIF, 0); } + public IfElseStatementContext(IfStatementPartialContext ctx) { copyFrom(ctx); } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitIfElseStatement(this); + else return visitor.visitChildren(this); + } + } + public static class SimpleIfStatementContext extends IfStatementPartialContext { + public ExpressionContext expression() { + return getRuleContext(ExpressionContext.class,0); + } + public TerminalNode THEN() { return getToken(JassParser.THEN, 0); } + public List newlines() { + return getRuleContexts(NewlinesContext.class); + } + public NewlinesContext newlines(int i) { + return getRuleContext(NewlinesContext.class,i); + } + public StatementsContext statements() { + return getRuleContext(StatementsContext.class,0); + } + public TerminalNode ENDIF() { return getToken(JassParser.ENDIF, 0); } + public SimpleIfStatementContext(IfStatementPartialContext ctx) { copyFrom(ctx); } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitSimpleIfStatement(this); + else return visitor.visitChildren(this); + } + } + + public final IfStatementPartialContext ifStatementPartial() throws RecognitionException { + IfStatementPartialContext _localctx = new IfStatementPartialContext(_ctx, getState()); + enterRule(_localctx, 18, RULE_ifStatementPartial); + try { + setState(181); + _errHandler.sync(this); + switch ( getInterpreter().adaptivePredict(_input,11,_ctx) ) { + case 1: + _localctx = new SimpleIfStatementContext(_localctx); + enterOuterAlt(_localctx, 1); + { + setState(157); + expression(); + setState(158); + match(THEN); + setState(159); + newlines(); + setState(160); + statements(); + setState(161); + match(ENDIF); + setState(162); + newlines(); + } + break; + case 2: + _localctx = new IfElseStatementContext(_localctx); + enterOuterAlt(_localctx, 2); + { + setState(164); + expression(); + setState(165); + match(THEN); + setState(166); + newlines(); + setState(167); + statements(); + setState(168); + match(ELSE); + setState(169); + newlines(); + setState(170); + statements(); + setState(171); + match(ENDIF); + setState(172); + newlines(); + } + break; + case 3: + _localctx = new IfElseIfStatementContext(_localctx); + enterOuterAlt(_localctx, 3); + { + setState(174); + expression(); + setState(175); + match(THEN); + setState(176); + newlines(); + setState(177); + statements(); + setState(178); + match(ELSEIF); + setState(179); + ifStatementPartial(); + } + break; + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + public static class ParamContext extends ParserRuleContext { + public TypeContext type() { + return getRuleContext(TypeContext.class,0); + } + public TerminalNode ID() { return getToken(JassParser.ID, 0); } + public ParamContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_param; } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitParam(this); + else return visitor.visitChildren(this); + } + } + + public final ParamContext param() throws RecognitionException { + ParamContext _localctx = new ParamContext(_ctx, getState()); + enterRule(_localctx, 20, RULE_param); + try { + enterOuterAlt(_localctx, 1); + { + setState(183); + type(); + setState(184); + match(ID); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + public static class ParamListContext extends ParserRuleContext { + public ParamListContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_paramList; } + + public ParamListContext() { } + public void copyFrom(ParamListContext ctx) { + super.copyFrom(ctx); + } + } + public static class NothingParameterContext extends ParamListContext { + public TerminalNode NOTHING() { return getToken(JassParser.NOTHING, 0); } + public NothingParameterContext(ParamListContext ctx) { copyFrom(ctx); } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitNothingParameter(this); + else return visitor.visitChildren(this); + } + } + public static class SingleParameterContext extends ParamListContext { + public ParamContext param() { + return getRuleContext(ParamContext.class,0); + } + public SingleParameterContext(ParamListContext ctx) { copyFrom(ctx); } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitSingleParameter(this); + else return visitor.visitChildren(this); + } + } + public static class ListParameterContext extends ParamListContext { + public ParamContext param() { + return getRuleContext(ParamContext.class,0); + } + public ParamListContext paramList() { + return getRuleContext(ParamListContext.class,0); + } + public ListParameterContext(ParamListContext ctx) { copyFrom(ctx); } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitListParameter(this); + else return visitor.visitChildren(this); + } + } + + public final ParamListContext paramList() throws RecognitionException { + ParamListContext _localctx = new ParamListContext(_ctx, getState()); + enterRule(_localctx, 22, RULE_paramList); + try { + setState(192); + _errHandler.sync(this); + switch ( getInterpreter().adaptivePredict(_input,12,_ctx) ) { + case 1: + _localctx = new SingleParameterContext(_localctx); + enterOuterAlt(_localctx, 1); + { + setState(186); + param(); + } + break; + case 2: + _localctx = new ListParameterContext(_localctx); + enterOuterAlt(_localctx, 2); + { + setState(187); + param(); + setState(188); + match(T__4); + setState(189); + paramList(); + } + break; + case 3: + _localctx = new NothingParameterContext(_localctx); + enterOuterAlt(_localctx, 3); + { + setState(191); + match(NOTHING); + } + break; + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + public static class GlobalsBlockContext extends ParserRuleContext { + public TerminalNode GLOBALS() { return getToken(JassParser.GLOBALS, 0); } + public List newlines() { + return getRuleContexts(NewlinesContext.class); + } + public NewlinesContext newlines(int i) { + return getRuleContext(NewlinesContext.class,i); + } + public TerminalNode ENDGLOBALS() { return getToken(JassParser.ENDGLOBALS, 0); } + public List global() { + return getRuleContexts(GlobalContext.class); + } + public GlobalContext global(int i) { + return getRuleContext(GlobalContext.class,i); + } + public GlobalsBlockContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_globalsBlock; } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitGlobalsBlock(this); + else return visitor.visitChildren(this); + } + } + + public final GlobalsBlockContext globalsBlock() throws RecognitionException { + GlobalsBlockContext _localctx = new GlobalsBlockContext(_ctx, getState()); + enterRule(_localctx, 24, RULE_globalsBlock); + int _la; + try { + enterOuterAlt(_localctx, 1); + { + setState(194); + match(GLOBALS); + setState(195); + newlines(); + setState(199); + _errHandler.sync(this); + _la = _input.LA(1); + while ((((_la) & ~0x3f) == 0 && ((1L << _la) & ((1L << NOTHING) | (1L << CONSTANT) | (1L << ID))) != 0)) { + { + { + setState(196); + global(); + } + } + setState(201); + _errHandler.sync(this); + _la = _input.LA(1); + } + setState(202); + match(ENDGLOBALS); + setState(203); + newlines(); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + public static class TypeDefinitionBlockContext extends ParserRuleContext { + public List typeDefinition() { + return getRuleContexts(TypeDefinitionContext.class); + } + public TypeDefinitionContext typeDefinition(int i) { + return getRuleContext(TypeDefinitionContext.class,i); + } + public TypeDefinitionBlockContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_typeDefinitionBlock; } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitTypeDefinitionBlock(this); + else return visitor.visitChildren(this); + } + } + + public final TypeDefinitionBlockContext typeDefinitionBlock() throws RecognitionException { + TypeDefinitionBlockContext _localctx = new TypeDefinitionBlockContext(_ctx, getState()); + enterRule(_localctx, 26, RULE_typeDefinitionBlock); + int _la; + try { + enterOuterAlt(_localctx, 1); + { + setState(208); + _errHandler.sync(this); + _la = _input.LA(1); + while (_la==TYPE) { + { + { + setState(205); + typeDefinition(); + } + } + setState(210); + _errHandler.sync(this); + _la = _input.LA(1); + } + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + public static class NativeBlockContext extends ParserRuleContext { + public TerminalNode NATIVE() { return getToken(JassParser.NATIVE, 0); } + public TerminalNode ID() { return getToken(JassParser.ID, 0); } + public TerminalNode TAKES() { return getToken(JassParser.TAKES, 0); } + public ParamListContext paramList() { + return getRuleContext(ParamListContext.class,0); + } + public TerminalNode RETURNS() { return getToken(JassParser.RETURNS, 0); } + public TypeContext type() { + return getRuleContext(TypeContext.class,0); + } + public NewlinesContext newlines() { + return getRuleContext(NewlinesContext.class,0); + } + public TerminalNode CONSTANT() { return getToken(JassParser.CONSTANT, 0); } + public NativeBlockContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_nativeBlock; } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitNativeBlock(this); + else return visitor.visitChildren(this); + } + } + + public final NativeBlockContext nativeBlock() throws RecognitionException { + NativeBlockContext _localctx = new NativeBlockContext(_ctx, getState()); + enterRule(_localctx, 28, RULE_nativeBlock); + int _la; + try { + enterOuterAlt(_localctx, 1); + { + setState(212); + _errHandler.sync(this); + _la = _input.LA(1); + if (_la==CONSTANT) { + { + setState(211); + match(CONSTANT); + } + } + + setState(214); + match(NATIVE); + setState(215); + match(ID); + setState(216); + match(TAKES); + setState(217); + paramList(); + setState(218); + match(RETURNS); + setState(219); + type(); + setState(220); + newlines(); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + public static class BlockContext extends ParserRuleContext { + public GlobalsBlockContext globalsBlock() { + return getRuleContext(GlobalsBlockContext.class,0); + } + public NativeBlockContext nativeBlock() { + return getRuleContext(NativeBlockContext.class,0); + } + public BlockContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_block; } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitBlock(this); + else return visitor.visitChildren(this); + } + } + + public final BlockContext block() throws RecognitionException { + BlockContext _localctx = new BlockContext(_ctx, getState()); + enterRule(_localctx, 30, RULE_block); + try { + setState(224); + _errHandler.sync(this); + switch (_input.LA(1)) { + case GLOBALS: + enterOuterAlt(_localctx, 1); + { + setState(222); + globalsBlock(); + } + break; + case NATIVE: + case CONSTANT: + enterOuterAlt(_localctx, 2); + { + setState(223); + nativeBlock(); + } + break; + default: + throw new NoViableAltException(this); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + public static class FunctionBlockContext extends ParserRuleContext { + public TerminalNode FUNCTION() { return getToken(JassParser.FUNCTION, 0); } + public TerminalNode ID() { return getToken(JassParser.ID, 0); } + public TerminalNode TAKES() { return getToken(JassParser.TAKES, 0); } + public ParamListContext paramList() { + return getRuleContext(ParamListContext.class,0); + } + public TerminalNode RETURNS() { return getToken(JassParser.RETURNS, 0); } + public TypeContext type() { + return getRuleContext(TypeContext.class,0); + } + public List newlines() { + return getRuleContexts(NewlinesContext.class); + } + public NewlinesContext newlines(int i) { + return getRuleContext(NewlinesContext.class,i); + } + public StatementsContext statements() { + return getRuleContext(StatementsContext.class,0); + } + public TerminalNode ENDFUNCTION() { return getToken(JassParser.ENDFUNCTION, 0); } + public FunctionBlockContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_functionBlock; } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitFunctionBlock(this); + else return visitor.visitChildren(this); + } + } + + public final FunctionBlockContext functionBlock() throws RecognitionException { + FunctionBlockContext _localctx = new FunctionBlockContext(_ctx, getState()); + enterRule(_localctx, 32, RULE_functionBlock); + try { + enterOuterAlt(_localctx, 1); + { + setState(226); + match(FUNCTION); + setState(227); + match(ID); + setState(228); + match(TAKES); + setState(229); + paramList(); + setState(230); + match(RETURNS); + setState(231); + type(); + setState(232); + newlines(); + setState(233); + statements(); + setState(234); + match(ENDFUNCTION); + setState(235); + newlines(); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + public static class StatementsContext extends ParserRuleContext { + public List statement() { + return getRuleContexts(StatementContext.class); + } + public StatementContext statement(int i) { + return getRuleContext(StatementContext.class,i); + } + public StatementsContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_statements; } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitStatements(this); + else return visitor.visitChildren(this); + } + } + + public final StatementsContext statements() throws RecognitionException { + StatementsContext _localctx = new StatementsContext(_ctx, getState()); + enterRule(_localctx, 34, RULE_statements); + int _la; + try { + enterOuterAlt(_localctx, 1); + { + setState(240); + _errHandler.sync(this); + _la = _input.LA(1); + while ((((_la) & ~0x3f) == 0 && ((1L << _la) & ((1L << CALL) | (1L << SET) | (1L << RETURN) | (1L << IF))) != 0)) { + { + { + setState(237); + statement(); + } + } + setState(242); + _errHandler.sync(this); + _la = _input.LA(1); + } + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + public static class NewlinesContext extends ParserRuleContext { + public PnewlinesContext pnewlines() { + return getRuleContext(PnewlinesContext.class,0); + } + public TerminalNode EOF() { return getToken(JassParser.EOF, 0); } + public NewlinesContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_newlines; } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitNewlines(this); + else return visitor.visitChildren(this); + } + } + + public final NewlinesContext newlines() throws RecognitionException { + NewlinesContext _localctx = new NewlinesContext(_ctx, getState()); + enterRule(_localctx, 36, RULE_newlines); + try { + setState(245); + _errHandler.sync(this); + switch (_input.LA(1)) { + case NEWLINE: + enterOuterAlt(_localctx, 1); + { + setState(243); + pnewlines(); + } + break; + case EOF: + enterOuterAlt(_localctx, 2); + { + setState(244); + match(EOF); + } + break; + default: + throw new NoViableAltException(this); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + public static class Newlines_optContext extends ParserRuleContext { + public PnewlinesContext pnewlines() { + return getRuleContext(PnewlinesContext.class,0); + } + public TerminalNode EOF() { return getToken(JassParser.EOF, 0); } + public Newlines_optContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_newlines_opt; } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitNewlines_opt(this); + else return visitor.visitChildren(this); + } + } + + public final Newlines_optContext newlines_opt() throws RecognitionException { + Newlines_optContext _localctx = new Newlines_optContext(_ctx, getState()); + enterRule(_localctx, 38, RULE_newlines_opt); + try { + setState(250); + _errHandler.sync(this); + switch ( getInterpreter().adaptivePredict(_input,19,_ctx) ) { + case 1: + enterOuterAlt(_localctx, 1); + { + setState(247); + pnewlines(); + } + break; + case 2: + enterOuterAlt(_localctx, 2); + { + setState(248); + match(EOF); + } + break; + case 3: + enterOuterAlt(_localctx, 3); + { + } + break; + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + public static class PnewlinesContext extends ParserRuleContext { + public TerminalNode NEWLINE() { return getToken(JassParser.NEWLINE, 0); } + public NewlinesContext newlines() { + return getRuleContext(NewlinesContext.class,0); + } + public PnewlinesContext(ParserRuleContext parent, int invokingState) { + super(parent, invokingState); + } + @Override public int getRuleIndex() { return RULE_pnewlines; } + @Override + public T accept(ParseTreeVisitor visitor) { + if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitPnewlines(this); + else return visitor.visitChildren(this); + } + } + + public final PnewlinesContext pnewlines() throws RecognitionException { + PnewlinesContext _localctx = new PnewlinesContext(_ctx, getState()); + enterRule(_localctx, 40, RULE_pnewlines); + try { + setState(255); + _errHandler.sync(this); + switch ( getInterpreter().adaptivePredict(_input,20,_ctx) ) { + case 1: + enterOuterAlt(_localctx, 1); + { + setState(252); + match(NEWLINE); + } + break; + case 2: + enterOuterAlt(_localctx, 2); + { + setState(253); + match(NEWLINE); + setState(254); + newlines(); + } + break; + } + } + catch (RecognitionException re) { + _localctx.exception = re; + _errHandler.reportError(this, re); + _errHandler.recover(this, re); + } + finally { + exitRule(); + } + return _localctx; + } + + public static final String _serializedATN = + "\3\u608b\ua72a\u8133\ub9ed\u417c\u3be7\u7786\u5964\3%\u0104\4\2\t\2\4"+ + "\3\t\3\4\4\t\4\4\5\t\5\4\6\t\6\4\7\t\7\4\b\t\b\4\t\t\t\4\n\t\n\4\13\t"+ + "\13\4\f\t\f\4\r\t\r\4\16\t\16\4\17\t\17\4\20\t\20\4\21\t\21\4\22\t\22"+ + "\4\23\t\23\4\24\t\24\4\25\t\25\4\26\t\26\3\2\3\2\3\2\3\2\7\2\61\n\2\f"+ + "\2\16\2\64\13\2\3\2\7\2\67\n\2\f\2\16\2:\13\2\5\2<\n\2\3\3\3\3\3\3\3\3"+ + "\3\3\3\3\3\4\3\4\3\4\3\4\5\4H\n\4\3\5\5\5K\n\5\3\5\3\5\3\5\3\5\3\5\5\5"+ + "R\n\5\3\5\3\5\3\5\3\5\3\5\5\5Y\n\5\3\6\3\6\3\6\3\7\3\7\3\7\3\7\3\7\3\7"+ + "\3\7\3\7\3\7\3\7\3\7\3\7\3\7\3\7\3\7\3\7\3\7\3\7\3\7\3\7\5\7r\n\7\3\b"+ + "\3\b\3\b\3\b\3\b\3\b\3\b\3\b\5\b|\n\b\3\t\3\t\3\t\3\t\3\t\5\t\u0083\n"+ + "\t\3\n\3\n\3\n\3\n\3\n\3\n\3\n\3\n\3\n\3\n\3\n\3\n\3\n\3\n\3\n\3\n\3\n"+ + "\3\n\3\n\3\n\3\n\3\n\3\n\3\n\3\n\5\n\u009e\n\n\3\13\3\13\3\13\3\13\3\13"+ + "\3\13\3\13\3\13\3\13\3\13\3\13\3\13\3\13\3\13\3\13\3\13\3\13\3\13\3\13"+ + "\3\13\3\13\3\13\3\13\3\13\5\13\u00b8\n\13\3\f\3\f\3\f\3\r\3\r\3\r\3\r"+ + "\3\r\3\r\5\r\u00c3\n\r\3\16\3\16\3\16\7\16\u00c8\n\16\f\16\16\16\u00cb"+ + "\13\16\3\16\3\16\3\16\3\17\7\17\u00d1\n\17\f\17\16\17\u00d4\13\17\3\20"+ + "\5\20\u00d7\n\20\3\20\3\20\3\20\3\20\3\20\3\20\3\20\3\20\3\21\3\21\5\21"+ + "\u00e3\n\21\3\22\3\22\3\22\3\22\3\22\3\22\3\22\3\22\3\22\3\22\3\22\3\23"+ + "\7\23\u00f1\n\23\f\23\16\23\u00f4\13\23\3\24\3\24\5\24\u00f8\n\24\3\25"+ + "\3\25\3\25\5\25\u00fd\n\25\3\26\3\26\3\26\5\26\u0102\n\26\3\26\2\2\27"+ + "\2\4\6\b\n\f\16\20\22\24\26\30\32\34\36 \"$&(*\2\2\2\u0113\2;\3\2\2\2"+ + "\4=\3\2\2\2\6G\3\2\2\2\bX\3\2\2\2\nZ\3\2\2\2\fq\3\2\2\2\16{\3\2\2\2\20"+ + "\u0082\3\2\2\2\22\u009d\3\2\2\2\24\u00b7\3\2\2\2\26\u00b9\3\2\2\2\30\u00c2"+ + "\3\2\2\2\32\u00c4\3\2\2\2\34\u00d2\3\2\2\2\36\u00d6\3\2\2\2 \u00e2\3\2"+ + "\2\2\"\u00e4\3\2\2\2$\u00f2\3\2\2\2&\u00f7\3\2\2\2(\u00fc\3\2\2\2*\u0101"+ + "\3\2\2\2,<\5&\24\2-.\5(\25\2.\62\5\34\17\2/\61\5 \21\2\60/\3\2\2\2\61"+ + "\64\3\2\2\2\62\60\3\2\2\2\62\63\3\2\2\2\638\3\2\2\2\64\62\3\2\2\2\65\67"+ + "\5\"\22\2\66\65\3\2\2\2\67:\3\2\2\28\66\3\2\2\289\3\2\2\29<\3\2\2\2:8"+ + "\3\2\2\2;,\3\2\2\2;-\3\2\2\2<\3\3\2\2\2=>\7\25\2\2>?\7#\2\2?@\7\26\2\2"+ + "@A\7#\2\2AB\5&\24\2B\5\3\2\2\2CH\7#\2\2DE\7#\2\2EH\7\24\2\2FH\7\20\2\2"+ + "GC\3\2\2\2GD\3\2\2\2GF\3\2\2\2H\7\3\2\2\2IK\7\34\2\2JI\3\2\2\2JK\3\2\2"+ + "\2KL\3\2\2\2LM\5\6\4\2MN\7#\2\2NO\5&\24\2OY\3\2\2\2PR\7\34\2\2QP\3\2\2"+ + "\2QR\3\2\2\2RS\3\2\2\2ST\5\6\4\2TU\7#\2\2UV\5\n\6\2VW\5&\24\2WY\3\2\2"+ + "\2XJ\3\2\2\2XQ\3\2\2\2Y\t\3\2\2\2Z[\7\b\2\2[\\\5\f\7\2\\\13\3\2\2\2]r"+ + "\7#\2\2^r\7\35\2\2_r\7\36\2\2`a\7\f\2\2ar\7#\2\2br\7\37\2\2cr\7 \2\2d"+ + "r\7!\2\2ef\7#\2\2fg\7\3\2\2gh\5\f\7\2hi\7\4\2\2ir\3\2\2\2jr\5\16\b\2k"+ + "l\7\5\2\2lm\5\f\7\2mn\7\6\2\2nr\3\2\2\2op\7\"\2\2pr\5\f\7\2q]\3\2\2\2"+ + "q^\3\2\2\2q_\3\2\2\2q`\3\2\2\2qb\3\2\2\2qc\3\2\2\2qd\3\2\2\2qe\3\2\2\2"+ + "qj\3\2\2\2qk\3\2\2\2qo\3\2\2\2r\r\3\2\2\2st\7#\2\2tu\7\5\2\2uv\5\20\t"+ + "\2vw\7\6\2\2w|\3\2\2\2xy\7#\2\2yz\7\5\2\2z|\7\6\2\2{s\3\2\2\2{x\3\2\2"+ + "\2|\17\3\2\2\2}\u0083\5\f\7\2~\177\5\f\7\2\177\u0080\7\7\2\2\u0080\u0081"+ + "\5\20\t\2\u0081\u0083\3\2\2\2\u0082}\3\2\2\2\u0082~\3\2\2\2\u0083\21\3"+ + "\2\2\2\u0084\u0085\7\21\2\2\u0085\u0086\5\16\b\2\u0086\u0087\5&\24\2\u0087"+ + "\u009e\3\2\2\2\u0088\u0089\7\22\2\2\u0089\u008a\7#\2\2\u008a\u008b\7\b"+ + "\2\2\u008b\u008c\5\f\7\2\u008c\u008d\5&\24\2\u008d\u009e\3\2\2\2\u008e"+ + "\u008f\7\22\2\2\u008f\u0090\7#\2\2\u0090\u0091\7\3\2\2\u0091\u0092\5\f"+ + "\7\2\u0092\u0093\7\4\2\2\u0093\u0094\7\b\2\2\u0094\u0095\5\f\7\2\u0095"+ + "\u0096\5&\24\2\u0096\u009e\3\2\2\2\u0097\u0098\7\23\2\2\u0098\u0099\5"+ + "\f\7\2\u0099\u009a\5&\24\2\u009a\u009e\3\2\2\2\u009b\u009c\7\27\2\2\u009c"+ + "\u009e\5\24\13\2\u009d\u0084\3\2\2\2\u009d\u0088\3\2\2\2\u009d\u008e\3"+ + "\2\2\2\u009d\u0097\3\2\2\2\u009d\u009b\3\2\2\2\u009e\23\3\2\2\2\u009f"+ + "\u00a0\5\f\7\2\u00a0\u00a1\7\30\2\2\u00a1\u00a2\5&\24\2\u00a2\u00a3\5"+ + "$\23\2\u00a3\u00a4\7\32\2\2\u00a4\u00a5\5&\24\2\u00a5\u00b8\3\2\2\2\u00a6"+ + "\u00a7\5\f\7\2\u00a7\u00a8\7\30\2\2\u00a8\u00a9\5&\24\2\u00a9\u00aa\5"+ + "$\23\2\u00aa\u00ab\7\31\2\2\u00ab\u00ac\5&\24\2\u00ac\u00ad\5$\23\2\u00ad"+ + "\u00ae\7\32\2\2\u00ae\u00af\5&\24\2\u00af\u00b8\3\2\2\2\u00b0\u00b1\5"+ + "\f\7\2\u00b1\u00b2\7\30\2\2\u00b2\u00b3\5&\24\2\u00b3\u00b4\5$\23\2\u00b4"+ + "\u00b5\7\33\2\2\u00b5\u00b6\5\24\13\2\u00b6\u00b8\3\2\2\2\u00b7\u009f"+ + "\3\2\2\2\u00b7\u00a6\3\2\2\2\u00b7\u00b0\3\2\2\2\u00b8\25\3\2\2\2\u00b9"+ + "\u00ba\5\6\4\2\u00ba\u00bb\7#\2\2\u00bb\27\3\2\2\2\u00bc\u00c3\5\26\f"+ + "\2\u00bd\u00be\5\26\f\2\u00be\u00bf\7\7\2\2\u00bf\u00c0\5\30\r\2\u00c0"+ + "\u00c3\3\2\2\2\u00c1\u00c3\7\20\2\2\u00c2\u00bc\3\2\2\2\u00c2\u00bd\3"+ + "\2\2\2\u00c2\u00c1\3\2\2\2\u00c3\31\3\2\2\2\u00c4\u00c5\7\t\2\2\u00c5"+ + "\u00c9\5&\24\2\u00c6\u00c8\5\b\5\2\u00c7\u00c6\3\2\2\2\u00c8\u00cb\3\2"+ + "\2\2\u00c9\u00c7\3\2\2\2\u00c9\u00ca\3\2\2\2\u00ca\u00cc\3\2\2\2\u00cb"+ + "\u00c9\3\2\2\2\u00cc\u00cd\7\n\2\2\u00cd\u00ce\5&\24\2\u00ce\33\3\2\2"+ + "\2\u00cf\u00d1\5\4\3\2\u00d0\u00cf\3\2\2\2\u00d1\u00d4\3\2\2\2\u00d2\u00d0"+ + "\3\2\2\2\u00d2\u00d3\3\2\2\2\u00d3\35\3\2\2\2\u00d4\u00d2\3\2\2\2\u00d5"+ + "\u00d7\7\34\2\2\u00d6\u00d5\3\2\2\2\u00d6\u00d7\3\2\2\2\u00d7\u00d8\3"+ + "\2\2\2\u00d8\u00d9\7\13\2\2\u00d9\u00da\7#\2\2\u00da\u00db\7\r\2\2\u00db"+ + "\u00dc\5\30\r\2\u00dc\u00dd\7\16\2\2\u00dd\u00de\5\6\4\2\u00de\u00df\5"+ + "&\24\2\u00df\37\3\2\2\2\u00e0\u00e3\5\32\16\2\u00e1\u00e3\5\36\20\2\u00e2"+ + "\u00e0\3\2\2\2\u00e2\u00e1\3\2\2\2\u00e3!\3\2\2\2\u00e4\u00e5\7\f\2\2"+ + "\u00e5\u00e6\7#\2\2\u00e6\u00e7\7\r\2\2\u00e7\u00e8\5\30\r\2\u00e8\u00e9"+ + "\7\16\2\2\u00e9\u00ea\5\6\4\2\u00ea\u00eb\5&\24\2\u00eb\u00ec\5$\23\2"+ + "\u00ec\u00ed\7\17\2\2\u00ed\u00ee\5&\24\2\u00ee#\3\2\2\2\u00ef\u00f1\5"+ + "\22\n\2\u00f0\u00ef\3\2\2\2\u00f1\u00f4\3\2\2\2\u00f2\u00f0\3\2\2\2\u00f2"+ + "\u00f3\3\2\2\2\u00f3%\3\2\2\2\u00f4\u00f2\3\2\2\2\u00f5\u00f8\5*\26\2"+ + "\u00f6\u00f8\7\2\2\3\u00f7\u00f5\3\2\2\2\u00f7\u00f6\3\2\2\2\u00f8\'\3"+ + "\2\2\2\u00f9\u00fd\5*\26\2\u00fa\u00fd\7\2\2\3\u00fb\u00fd\3\2\2\2\u00fc"+ + "\u00f9\3\2\2\2\u00fc\u00fa\3\2\2\2\u00fc\u00fb\3\2\2\2\u00fd)\3\2\2\2"+ + "\u00fe\u0102\7%\2\2\u00ff\u0100\7%\2\2\u0100\u0102\5&\24\2\u0101\u00fe"+ + "\3\2\2\2\u0101\u00ff\3\2\2\2\u0102+\3\2\2\2\27\628;GJQXq{\u0082\u009d"+ + "\u00b7\u00c2\u00c9\u00d2\u00d6\u00e2\u00f2\u00f7\u00fc\u0101"; + public static final ATN _ATN = + new ATNDeserializer().deserialize(_serializedATN.toCharArray()); + static { + _decisionToDFA = new DFA[_ATN.getNumberOfDecisions()]; + for (int i = 0; i < _ATN.getNumberOfDecisions(); i++) { + _decisionToDFA[i] = new DFA(_ATN.getDecisionState(i), i); + } + } +} \ No newline at end of file diff --git a/jassparser/build/generated-src/com/etheller/warsmash/jassparser/JassVisitor.java b/jassparser/build/generated-src/com/etheller/warsmash/jassparser/JassVisitor.java new file mode 100644 index 0000000..ce63888 --- /dev/null +++ b/jassparser/build/generated-src/com/etheller/warsmash/jassparser/JassVisitor.java @@ -0,0 +1,302 @@ +// Generated from Jass.g4 by ANTLR 4.7 + + package com.etheller.interpreter; + +import org.antlr.v4.runtime.tree.ParseTreeVisitor; + +/** + * This interface defines a complete generic visitor for a parse tree produced + * by {@link JassParser}. + * + * @param The return type of the visit operation. Use {@link Void} for + * operations with no return type. + */ +public interface JassVisitor extends ParseTreeVisitor { + /** + * Visit a parse tree produced by {@link JassParser#program}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitProgram(JassParser.ProgramContext ctx); + /** + * Visit a parse tree produced by {@link JassParser#typeDefinition}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitTypeDefinition(JassParser.TypeDefinitionContext ctx); + /** + * Visit a parse tree produced by the {@code BasicType} + * labeled alternative in {@link JassParser#type}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitBasicType(JassParser.BasicTypeContext ctx); + /** + * Visit a parse tree produced by the {@code ArrayType} + * labeled alternative in {@link JassParser#type}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitArrayType(JassParser.ArrayTypeContext ctx); + /** + * Visit a parse tree produced by the {@code NothingType} + * labeled alternative in {@link JassParser#type}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitNothingType(JassParser.NothingTypeContext ctx); + /** + * Visit a parse tree produced by the {@code BasicGlobal} + * labeled alternative in {@link JassParser#global}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitBasicGlobal(JassParser.BasicGlobalContext ctx); + /** + * Visit a parse tree produced by the {@code DefinitionGlobal} + * labeled alternative in {@link JassParser#global}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitDefinitionGlobal(JassParser.DefinitionGlobalContext ctx); + /** + * Visit a parse tree produced by {@link JassParser#assignTail}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitAssignTail(JassParser.AssignTailContext ctx); + /** + * Visit a parse tree produced by the {@code ReferenceExpression} + * labeled alternative in {@link JassParser#expression}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitReferenceExpression(JassParser.ReferenceExpressionContext ctx); + /** + * Visit a parse tree produced by the {@code StringLiteralExpression} + * labeled alternative in {@link JassParser#expression}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitStringLiteralExpression(JassParser.StringLiteralExpressionContext ctx); + /** + * Visit a parse tree produced by the {@code IntegerLiteralExpression} + * labeled alternative in {@link JassParser#expression}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitIntegerLiteralExpression(JassParser.IntegerLiteralExpressionContext ctx); + /** + * Visit a parse tree produced by the {@code FunctionReferenceExpression} + * labeled alternative in {@link JassParser#expression}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitFunctionReferenceExpression(JassParser.FunctionReferenceExpressionContext ctx); + /** + * Visit a parse tree produced by the {@code NullExpression} + * labeled alternative in {@link JassParser#expression}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitNullExpression(JassParser.NullExpressionContext ctx); + /** + * Visit a parse tree produced by the {@code TrueExpression} + * labeled alternative in {@link JassParser#expression}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitTrueExpression(JassParser.TrueExpressionContext ctx); + /** + * Visit a parse tree produced by the {@code FalseExpression} + * labeled alternative in {@link JassParser#expression}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitFalseExpression(JassParser.FalseExpressionContext ctx); + /** + * Visit a parse tree produced by the {@code ArrayReferenceExpression} + * labeled alternative in {@link JassParser#expression}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitArrayReferenceExpression(JassParser.ArrayReferenceExpressionContext ctx); + /** + * Visit a parse tree produced by the {@code FunctionCallExpression} + * labeled alternative in {@link JassParser#expression}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitFunctionCallExpression(JassParser.FunctionCallExpressionContext ctx); + /** + * Visit a parse tree produced by the {@code ParentheticalExpression} + * labeled alternative in {@link JassParser#expression}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitParentheticalExpression(JassParser.ParentheticalExpressionContext ctx); + /** + * Visit a parse tree produced by the {@code NotExpression} + * labeled alternative in {@link JassParser#expression}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitNotExpression(JassParser.NotExpressionContext ctx); + /** + * Visit a parse tree produced by {@link JassParser#functionExpression}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitFunctionExpression(JassParser.FunctionExpressionContext ctx); + /** + * Visit a parse tree produced by the {@code SingleArgument} + * labeled alternative in {@link JassParser#argsList}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitSingleArgument(JassParser.SingleArgumentContext ctx); + /** + * Visit a parse tree produced by the {@code ListArgument} + * labeled alternative in {@link JassParser#argsList}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitListArgument(JassParser.ListArgumentContext ctx); + /** + * Visit a parse tree produced by the {@code CallStatement} + * labeled alternative in {@link JassParser#statement}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitCallStatement(JassParser.CallStatementContext ctx); + /** + * Visit a parse tree produced by the {@code SetStatement} + * labeled alternative in {@link JassParser#statement}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitSetStatement(JassParser.SetStatementContext ctx); + /** + * Visit a parse tree produced by the {@code ArrayedAssignmentStatement} + * labeled alternative in {@link JassParser#statement}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitArrayedAssignmentStatement(JassParser.ArrayedAssignmentStatementContext ctx); + /** + * Visit a parse tree produced by the {@code ReturnStatement} + * labeled alternative in {@link JassParser#statement}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitReturnStatement(JassParser.ReturnStatementContext ctx); + /** + * Visit a parse tree produced by the {@code IfStatement} + * labeled alternative in {@link JassParser#statement}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitIfStatement(JassParser.IfStatementContext ctx); + /** + * Visit a parse tree produced by the {@code SimpleIfStatement} + * labeled alternative in {@link JassParser#ifStatementPartial}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitSimpleIfStatement(JassParser.SimpleIfStatementContext ctx); + /** + * Visit a parse tree produced by the {@code IfElseStatement} + * labeled alternative in {@link JassParser#ifStatementPartial}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitIfElseStatement(JassParser.IfElseStatementContext ctx); + /** + * Visit a parse tree produced by the {@code IfElseIfStatement} + * labeled alternative in {@link JassParser#ifStatementPartial}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitIfElseIfStatement(JassParser.IfElseIfStatementContext ctx); + /** + * Visit a parse tree produced by {@link JassParser#param}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitParam(JassParser.ParamContext ctx); + /** + * Visit a parse tree produced by the {@code SingleParameter} + * labeled alternative in {@link JassParser#paramList}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitSingleParameter(JassParser.SingleParameterContext ctx); + /** + * Visit a parse tree produced by the {@code ListParameter} + * labeled alternative in {@link JassParser#paramList}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitListParameter(JassParser.ListParameterContext ctx); + /** + * Visit a parse tree produced by the {@code NothingParameter} + * labeled alternative in {@link JassParser#paramList}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitNothingParameter(JassParser.NothingParameterContext ctx); + /** + * Visit a parse tree produced by {@link JassParser#globalsBlock}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitGlobalsBlock(JassParser.GlobalsBlockContext ctx); + /** + * Visit a parse tree produced by {@link JassParser#typeDefinitionBlock}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitTypeDefinitionBlock(JassParser.TypeDefinitionBlockContext ctx); + /** + * Visit a parse tree produced by {@link JassParser#nativeBlock}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitNativeBlock(JassParser.NativeBlockContext ctx); + /** + * Visit a parse tree produced by {@link JassParser#block}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitBlock(JassParser.BlockContext ctx); + /** + * Visit a parse tree produced by {@link JassParser#functionBlock}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitFunctionBlock(JassParser.FunctionBlockContext ctx); + /** + * Visit a parse tree produced by {@link JassParser#statements}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitStatements(JassParser.StatementsContext ctx); + /** + * Visit a parse tree produced by {@link JassParser#newlines}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitNewlines(JassParser.NewlinesContext ctx); + /** + * Visit a parse tree produced by {@link JassParser#newlines_opt}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitNewlines_opt(JassParser.Newlines_optContext ctx); + /** + * Visit a parse tree produced by {@link JassParser#pnewlines}. + * @param ctx the parse tree + * @return the visitor result + */ + T visitPnewlines(JassParser.PnewlinesContext ctx); +} \ No newline at end of file diff --git a/jassparser/src/com/etheller/interpreter/ast/visitors/JassExpressionVisitor.java b/jassparser/src/com/etheller/interpreter/ast/visitors/JassExpressionVisitor.java index 0635d5e..cb5e077 100644 --- a/jassparser/src/com/etheller/interpreter/ast/visitors/JassExpressionVisitor.java +++ b/jassparser/src/com/etheller/interpreter/ast/visitors/JassExpressionVisitor.java @@ -46,8 +46,9 @@ public class JassExpressionVisitor extends JassBaseVisitor { @Override public JassExpression visitStringLiteralExpression(final StringLiteralExpressionContext ctx) { final String stringLiteralText = ctx.STRING_LITERAL().getText(); - return new LiteralJassExpression( - new StringJassValue(stringLiteralText.substring(1, stringLiteralText.length() - 1))); + final String parsedString = stringLiteralText.substring(1, stringLiteralText.length() - 1).replace("\\\\", + "\\"); + return new LiteralJassExpression(new StringJassValue(parsedString)); } @Override diff --git a/resources/Scripts/common.jui b/resources/Scripts/common.jui index 1a969fb..37012a3 100644 --- a/resources/Scripts/common.jui +++ b/resources/Scripts/common.jui @@ -115,4 +115,17 @@ native TriggerAddAction takes trigger whichTrigger, code actionFunc returns native TriggerRemoveAction takes trigger whichTrigger, triggeraction whichAction returns nothing native TriggerClearActions takes trigger whichTrigger returns nothing native TriggerEvaluate takes trigger whichTrigger returns boolean -native TriggerExecute takes trigger whichTrigger returns nothing \ No newline at end of file +native TriggerExecute takes trigger whichTrigger returns nothing + + +//============================================================================ +// Boolean Expr API ( for compositing trigger conditions and unit filter funcs...) +//============================================================================ +native And takes boolexpr operandA, boolexpr operandB returns boolexpr +native Or takes boolexpr operandA, boolexpr operandB returns boolexpr +native Not takes boolexpr operand returns boolexpr +native Condition takes code func returns conditionfunc +native DestroyCondition takes conditionfunc c returns nothing +native Filter takes code func returns filterfunc +native DestroyFilter takes filterfunc f returns nothing +native DestroyBoolExpr takes boolexpr e returns nothing \ No newline at end of file From 5abb9e3fd9cac0bc16d88a5bf20fb6836b8c69a7 Mon Sep 17 00:00:00 2001 From: Retera Date: Wed, 12 Aug 2020 04:17:42 -0400 Subject: [PATCH 044/116] Add warsmash ini, change model test file to load night elf campaign screen instead of arthas illidan fight --- core/assets/warsmash.ini | 13 +++++++++++++ core/src/com/etheller/warsmash/WarsmashGdxGame.java | 7 ++++--- 2 files changed, 17 insertions(+), 3 deletions(-) create mode 100644 core/assets/warsmash.ini diff --git a/core/assets/warsmash.ini b/core/assets/warsmash.ini new file mode 100644 index 0000000..88e3a85 --- /dev/null +++ b/core/assets/warsmash.ini @@ -0,0 +1,13 @@ +[DataSources] +Count=4 +Type00=Folder +Path00="E:\Backups\Warcraft\Data\127" +Type01=Folder +Path01="..\..\resources" +Type02=Folder +Path02="E:\Backups\Warsmash\Data" +Type03=Folder +Path03="." + +[Map] +FilePath="RuinedGround.w3x" \ No newline at end of file diff --git a/core/src/com/etheller/warsmash/WarsmashGdxGame.java b/core/src/com/etheller/warsmash/WarsmashGdxGame.java index 3dff1f4..81d8a76 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxGame.java @@ -74,7 +74,8 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide this.cameraManager = new CameraManager(); this.cameraManager.setupCamera(scene); - this.mainModel = (MdxModel) this.viewer.load("Doodads\\Cinematic\\ArthasIllidanFight\\ArthasIllidanFight.mdx", +// this.mainModel = (MdxModel) this.viewer.load("Doodads\\Cinematic\\ArthasIllidanFight\\ArthasIllidanFight.mdx", + this.mainModel = (MdxModel) this.viewer.load("UI\\Glues\\SinglePlayer\\NightElf_Exp\\NightElf_Exp.mdx", // this.mainModel = (MdxModel) this.viewer.load("Abilities\\Spells\\Orc\\FeralSpirit\\feralspirittarget.mdx", new PathSolver() { @Override @@ -94,7 +95,7 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide this.mainInstance.setScene(scene); - final int animIndex = 1; + final int animIndex = 0; this.modelCamera = this.mainModel.cameras.get(animIndex); this.mainInstance.setSequence(animIndex); @@ -363,7 +364,7 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide this.cameraManager.horizontalAngle = 0; } } - this.modelCamera = this.mainModel.cameras.get(this.mainInstance.sequence); +// this.modelCamera = this.mainModel.cameras.get(this.mainInstance.sequence); this.cameraManager.updateCamera(); this.viewer.updateAndRender(); From bf16b4f698f5b26049b0db055ae5ba8998fb4779 Mon Sep 17 00:00:00 2001 From: Retera Date: Fri, 4 Sep 2020 08:29:37 -0400 Subject: [PATCH 045/116] Damage and armor UI and attack animation fixes --- core/assets/warsmash.ini | 14 +- .../etheller/warsmash/WarsmashGdxMapGame.java | 195 +- .../etheller/warsmash/parsers/fdf/GameUI.java | 203 +- .../fdf/frames/AbstractRenderableFrame.java | 68 +- .../parsers/fdf/frames/AbstractUIFrame.java | 6 +- .../warsmash/parsers/fdf/frames/SetPoint.java | 40 + .../parsers/fdf/frames/SpriteFrame.java | 52 + .../parsers/fdf/frames/StringFrame.java | 72 + .../parsers/fdf/frames/TextureFrame.java | 40 +- .../warsmash/parsers/fdf/frames/UIFrame.java | 14 +- .../etheller/warsmash/parsers/jass/Jass2.java | 111 +- .../warsmash/parsers/mdlx/AnimatedObject.java | 6 +- .../warsmash/util/FastNumberFormat.java | 24 + .../handlers/mdx/MdxComplexInstance.java | 7 +- .../warsmash/viewer5/handlers/w3x/Doodad.java | 41 +- .../viewer5/handlers/w3x/TerrainDoodad.java | 20 +- .../viewer5/handlers/w3x/War3MapViewer.java | 72 +- .../w3x/environment/RenderCorner.java | 1 + .../handlers/w3x/environment/Terrain.java | 29 +- .../rendersim/OrientationInterpolation.java | 61 + .../handlers/w3x/rendersim/ParseBogus.java | 49 + .../handlers/w3x/rendersim/ParseBogus2.java | 60 + .../handlers/w3x/rendersim/ParseBogus3.java | 19 + .../handlers/w3x/rendersim/ParseBogus4.java | 80 + .../w3x/rendersim/RenderAttackProjectile.java | 5 +- .../handlers/w3x/rendersim/RenderUnit.java | 168 +- .../w3x/simulation/CDestructable.java | 4 + .../w3x/simulation/CGameplayConstants.java | 21 + .../handlers/w3x/simulation/CItem.java | 4 + .../handlers/w3x/simulation/COrder.java | 10 - .../handlers/w3x/simulation/CSimulation.java | 16 +- .../handlers/w3x/simulation/CUnit.java | 43 +- .../simulation/CUnitAnimationListener.java | 16 + .../w3x/simulation/CUnitClassification.java | 67 + .../handlers/w3x/simulation/CUnitType.java | 59 +- .../handlers/w3x/simulation/CWidget.java | 2 + .../simulation/abilities/CAbilityAttack.java | 2 +- .../w3x/simulation/combat/CAttackType.java | 31 + .../w3x/simulation/combat/CDefenseType.java | 27 + .../w3x/simulation/combat/CRegenType.java | 13 + .../w3x/simulation/combat/CTargetType.java | 139 ++ .../w3x/simulation/combat/CUpgradeClass.java | 13 + .../w3x/simulation/combat/CWeaponType.java | 16 + .../w3x/simulation/combat/CodeKeyType.java | 7 + .../combat/attacks/CUnitAttack.java | 198 ++ .../combat/attacks/CUnitAttackInstant.java | 31 + .../combat/attacks/CUnitAttackMissile.java | 62 + .../attacks/CUnitAttackMissileBounce.java | 43 + .../attacks/CUnitAttackMissileLine.java | 43 + .../attacks/CUnitAttackMissileSplash.java | 85 + .../combat/attacks/CUnitAttackNormal.java | 21 + .../projectile/CAttackProjectile.java | 2 +- .../w3x/simulation/data/CUnitData.java | 240 +- .../w3x/simulation/orders/CAttackOrder.java | 46 +- .../simulation/orders/CDoNothingOrder.java | 6 - .../w3x/simulation/orders/CMoveOrder.java | 23 +- .../simulation/util/ProjectileCreator.java | 2 +- .../viewer5/handlers/w3x/ui/MeleeUI.java | 372 ++++ .../warsmash/desktop/DesktopLauncher.java | 10 +- .../fdf/datamodel/FrameDefinition.java | 18 + .../fields/visitor/GetFontFieldVisitor.java | 57 + .../visitor/GetTextJustifyFieldVisitor.java | 57 + jassparser/.gitignore | 1 + jassparser/antlr-src/Jass.g4 | 2 +- .../warsmash/jassparser/JassBaseVisitor.java | 317 --- .../warsmash/jassparser/JassLexer.java | 224 -- .../warsmash/jassparser/JassParser.java | 1969 ----------------- .../warsmash/jassparser/JassVisitor.java | 302 --- resources/Scripts/common.jui | 31 + resources/Scripts/melee.jui | 40 +- resources/UI/FrameDef/SmashFrameDef.toc | 2 + .../UI/FrameDef/SmashUI/SmashConsoleUI.fdf | 81 + .../FrameDef/SmashUI/TimeOfDayIndicator.fdf | 6 + .../UI/FrameDef/SmashUI/UnitPortrait.fdf | 44 + resources/UI/FrameDef/UI/SimpleInfoPanel.fdf | 447 ++++ 75 files changed, 3638 insertions(+), 3091 deletions(-) create mode 100644 core/src/com/etheller/warsmash/parsers/fdf/frames/SetPoint.java create mode 100644 core/src/com/etheller/warsmash/parsers/fdf/frames/SpriteFrame.java create mode 100644 core/src/com/etheller/warsmash/parsers/fdf/frames/StringFrame.java create mode 100644 core/src/com/etheller/warsmash/util/FastNumberFormat.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/OrientationInterpolation.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ParseBogus.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ParseBogus2.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ParseBogus3.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ParseBogus4.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CGameplayConstants.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitAnimationListener.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitClassification.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/CAttackType.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/CDefenseType.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/CRegenType.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/CTargetType.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/CUpgradeClass.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/CWeaponType.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/CodeKeyType.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttack.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackInstant.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissile.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileBounce.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileLine.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileSplash.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackNormal.java rename core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/{ => combat}/projectile/CAttackProjectile.java (95%) create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/visitor/GetFontFieldVisitor.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/visitor/GetTextJustifyFieldVisitor.java create mode 100644 jassparser/.gitignore delete mode 100644 jassparser/build/generated-src/com/etheller/warsmash/jassparser/JassBaseVisitor.java delete mode 100644 jassparser/build/generated-src/com/etheller/warsmash/jassparser/JassLexer.java delete mode 100644 jassparser/build/generated-src/com/etheller/warsmash/jassparser/JassParser.java delete mode 100644 jassparser/build/generated-src/com/etheller/warsmash/jassparser/JassVisitor.java create mode 100644 resources/UI/FrameDef/SmashFrameDef.toc create mode 100644 resources/UI/FrameDef/SmashUI/SmashConsoleUI.fdf create mode 100644 resources/UI/FrameDef/SmashUI/TimeOfDayIndicator.fdf create mode 100644 resources/UI/FrameDef/SmashUI/UnitPortrait.fdf create mode 100644 resources/UI/FrameDef/UI/SimpleInfoPanel.fdf diff --git a/core/assets/warsmash.ini b/core/assets/warsmash.ini index 88e3a85..276c5ba 100644 --- a/core/assets/warsmash.ini +++ b/core/assets/warsmash.ini @@ -1,13 +1,15 @@ [DataSources] -Count=4 +Count=5 Type00=Folder -Path00="E:\Backups\Warcraft\Data\127" +Path00="E:\Backups\Warcraft III 1.30 but dead\War3mod.mpq" Type01=Folder -Path01="..\..\resources" +Path01="E:\Backups\Warcraft\Data\127" Type02=Folder -Path02="E:\Backups\Warsmash\Data" +Path02="..\..\resources" Type03=Folder -Path03="." +Path03="E:\Backups\Warsmash\Data" +Type04=Folder +Path04="." [Map] -FilePath="RuinedGround.w3x" \ No newline at end of file +FilePath="PitchRoll.w3x" \ No newline at end of file diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java index 9ae08f2..db4ca3d 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java @@ -38,7 +38,6 @@ import com.etheller.warsmash.datasources.DataSourceDescriptor; import com.etheller.warsmash.datasources.FolderDataSourceDescriptor; import com.etheller.warsmash.datasources.MpqDataSourceDescriptor; import com.etheller.warsmash.parsers.fdf.GameUI; -import com.etheller.warsmash.parsers.jass.Jass2; import com.etheller.warsmash.parsers.jass.Jass2.RootFrameListener; import com.etheller.warsmash.units.DataTable; import com.etheller.warsmash.units.Element; @@ -52,15 +51,19 @@ import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; import com.etheller.warsmash.viewer5.handlers.mdx.ReplaceableIds; import com.etheller.warsmash.viewer5.handlers.tga.TgaFile; -import com.etheller.warsmash.viewer5.handlers.w3x.StandSequence; import com.etheller.warsmash.viewer5.handlers.w3x.UnitSoundset.UnitAckSound; import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.CommandCardIcon; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.COrder; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityStop; +import com.etheller.warsmash.viewer5.handlers.w3x.ui.MeleeUI; public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProvider, InputProcessor { + private static final float HORIZONTAL_MAXIMUM = (float) Math.toRadians(56); + private static final float HORIZONTAL_MINIMUM = (float) -Math.toRadians(56); + private static final double HORIZONTAL_ANGLE_INCREMENT = Math.PI / 60; + private static final Vector3 clickLocationTemp = new Vector3(); private static final Vector2 clickLocationTemp2 = new Vector2(); private DataSource codebase; @@ -68,10 +71,6 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv private CameraManager cameraManager; private final Rectangle tempRect = new Rectangle(); - private CameraManager portraitCameraManager; - private final float[] cameraPositionTemp = new float[3]; - private final float[] cameraTargetTemp = new float[3]; - // libGDX stuff private OrthographicCamera uiCamera; private BitmapFont font; @@ -82,8 +81,6 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv private GlyphLayout glyphLayout; private Texture consoleUITexture; - private final Vector2 projectionTemp1 = new Vector2(); - private final Vector2 projectionTemp2 = new Vector2(); private RenderUnit selectedUnit; private int selectedSoundCount = 0; @@ -165,17 +162,11 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv Gdx.gl30.glClearColor(0.0f, 0.0f, 0.0f, 1); // TODO remove white background Gdx.gl30.glEnable(GL30.GL_SCISSOR_TEST); - this.portraitScene = this.viewer.addSimpleScene(); - this.portraitCameraManager = new CameraManager(); - this.portraitCameraManager.setupCamera(this.portraitScene); - this.uiScene = this.viewer.addSimpleScene(); this.uiScene.alpha = true; // this.mainModel = (MdxModel) this.viewer.load("UI\\Glues\\MainMenu\\MainMenu3D_exp\\MainMenu3D_exp.mdx", - this.portraitScene.camera.viewport(new Rectangle(100, 0, 6400, 48)); - // libGDX stuff final float w = Gdx.graphics.getWidth(); final float h = Gdx.graphics.getHeight(); @@ -196,7 +187,6 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.font24 = fontGenerator.generateFont(fontParam); fontParam.size = 20; this.font20 = fontGenerator.generateFont(fontParam); - fontGenerator.dispose(); this.glyphLayout = new GlyphLayout(); // Constructs a new OrthographicCamera, using the given viewport width and @@ -209,8 +199,6 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.uiCamera.position.set(this.uiCamera.viewportWidth / 2f, this.uiCamera.viewportHeight / 2f, 0); this.uiCamera.update(); - positionPortrait(); - this.batch = new SpriteBatch(); // this.consoleUITexture = new Texture(new DataSourceFileHandle(this.viewer.dataSource, "AlphaUi.png")); @@ -273,12 +261,22 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.shapeRenderer = new ShapeRenderer(); this.talentTreeWindow = new Rectangle(100, 300, 1400, 800); - Jass2.loadJUI(this.codebase, this.uiViewport, new RootFrameListener() { - @Override - public void onCreate(final GameUI rootFrame) { - WarsmashGdxMapGame.this.gameUI = rootFrame; - } - }, "Scripts\\common.jui", "Scripts\\melee.jui"); +// Jass2.loadJUI(this.codebase, this.uiViewport, fontGenerator, this.uiScene, this.viewer, +// new RootFrameListener() { +// @Override +// public void onCreate(final GameUI rootFrame) { +// WarsmashGdxMapGame.this.gameUI = rootFrame; +// } +// }, "Scripts\\common.jui", "Scripts\\melee.jui"); + + this.meleeUI = new MeleeUI(this.codebase, this.uiViewport, fontGenerator, this.uiScene, this.viewer, + new RootFrameListener() { + @Override + public void onCreate(final GameUI rootFrame) { + } + }); + this.meleeUI.main(); + fontGenerator.dispose(); } @Override @@ -292,7 +290,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.viewer.terrain.getGroundHeight(this.cameraManager.target.x, this.cameraManager.target.y), this.viewer.terrain.getWaterHeight(this.cameraManager.target.x, this.cameraManager.target.y)) - 256; this.cameraManager.updateCamera(); - this.portraitCameraManager.updateCamera(); + this.meleeUI.updatePortrait(); this.viewer.updateAndRender(); // gl.glDrawElements(GL20.GL_TRIANGLES, this.elements, GL20.GL_UNSIGNED_SHORT, this.faceOffset); @@ -301,12 +299,6 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv // this.font.draw(this.batch, Integer.toString(Gdx.graphics.getFramesPerSecond()), 0, 0); // this.batch.end(); - if ((this.portraitCameraManager.modelInstance != null) - && (this.portraitCameraManager.modelInstance.sequenceEnded - || (this.portraitCameraManager.modelInstance.sequence == -1))) { - StandSequence.randomPortraitSequence(this.portraitCameraManager.modelInstance); - } - Gdx.gl30.glDisable(GL30.GL_SCISSOR_TEST); Gdx.gl30.glDisable(GL30.GL_CULL_FACE); @@ -317,7 +309,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.uiViewport.apply(); this.batch.setProjectionMatrix(this.uiCamera.combined); this.batch.begin(); - this.gameUI.render(this.batch); + this.meleeUI.render(this.batch, this.font20, this.glyphLayout); this.font.setColor(Color.YELLOW); final String fpsString = "FPS: " + Gdx.graphics.getFramesPerSecond(); this.glyphLayout.setText(this.font, fpsString); @@ -428,25 +420,12 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.uiScene.camera.viewport(this.tempRect); this.uiScene.camera.ortho(0f, 0.8f, 0f, 0.6f, -1f, 1); - positionPortrait(); + this.meleeUI.resize(); } - private void positionPortrait() { - this.projectionTemp1.x = 422; - this.projectionTemp1.y = 57; - this.projectionTemp2.x = 422 + 167; - this.projectionTemp2.y = 57 + 170; - this.uiViewport.project(this.projectionTemp1); - this.uiViewport.project(this.projectionTemp2); - - this.tempRect.x = this.projectionTemp1.x; - this.tempRect.y = this.projectionTemp1.y; - this.tempRect.width = this.projectionTemp2.x - this.projectionTemp1.x; - this.tempRect.height = this.projectionTemp2.y - this.projectionTemp1.y; - this.portraitScene.camera.viewport(this.tempRect); - } - - class CameraManager { + public static class CameraManager { + private final float[] cameraPositionTemp = new float[3]; + private final float[] cameraTargetTemp = new float[3]; public com.etheller.warsmash.viewer5.handlers.mdx.Camera modelCamera; private MdxComplexInstance modelInstance; private CanvasProvider canvas; @@ -456,6 +435,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv private float zoomFactor; private float horizontalAngle; private float verticalAngle; + private float verticalAngleAcceleration; private float distance; private Vector3 position; private Vector3 target; @@ -463,17 +443,19 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv private Vector3 vecHeap; private Quaternion quatHeap; private Quaternion quatHeap2; + private boolean insertDown; + private boolean deleteDown; // An orbit camera setup example. // Left mouse button controls the orbit itself. // The right mouse button allows to move the camera and the point it's looking // at on the XY plane. // Scrolling zooms in and out. - private void setupCamera(final Scene scene) { + public void setupCamera(final Scene scene) { this.canvas = scene.viewer.canvas; this.camera = scene.camera; this.moveSpeed = 2; - this.rotationSpeed = (float) (Math.PI / 180); + this.rotationSpeed = (float) HORIZONTAL_ANGLE_INCREMENT; this.zoomFactor = 0.1f; this.horizontalAngle = 0;// (float) (Math.PI / 2); this.verticalAngle = (float) Math.toRadians(34); @@ -490,10 +472,45 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv // cameraUpdate(); } - private void updateCamera() { + public void updateCamera() { this.quatHeap.idt(); this.quatHeap.setFromAxisRad(0, 0, 1, this.horizontalAngle); + if (this.insertDown && !this.deleteDown) { + this.horizontalAngle -= HORIZONTAL_ANGLE_INCREMENT; + if (this.horizontalAngle < HORIZONTAL_MINIMUM) { + this.horizontalAngle = HORIZONTAL_MINIMUM; + } + } + else if (this.deleteDown && !this.insertDown) { + this.horizontalAngle += HORIZONTAL_ANGLE_INCREMENT; + if (this.horizontalAngle > HORIZONTAL_MAXIMUM) { + this.horizontalAngle = HORIZONTAL_MAXIMUM; + } + } + else { + if (Math.abs(this.horizontalAngle) < HORIZONTAL_ANGLE_INCREMENT) { + this.horizontalAngle = 0; + } + else { + this.horizontalAngle -= HORIZONTAL_ANGLE_INCREMENT * Math.signum(this.horizontalAngle); + } + } + this.quatHeap2.idt(); + this.verticalAngle += this.verticalAngleAcceleration; + this.verticalAngleAcceleration *= 0.975f; + if (this.verticalAngle > ((Math.PI / 2) - Math.toRadians(17))) { + this.verticalAngle = (float) ((Math.PI / 2) - Math.toRadians(17)); + if (this.verticalAngleAcceleration > 0) { + this.verticalAngleAcceleration = 0; + } + } + if (this.verticalAngle < (float) Math.toRadians(34)) { + this.verticalAngle = (float) Math.toRadians(34); + if (this.verticalAngleAcceleration < 0) { + this.verticalAngleAcceleration = 0; + } + } this.quatHeap2.setFromAxisRad(1, 0, 0, this.verticalAngle); this.quatHeap.mul(this.quatHeap2); @@ -502,18 +519,16 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.position.scl(this.distance); this.position = this.position.add(this.target); if (this.modelCamera != null) { - this.modelCamera.getPositionTranslation(WarsmashGdxMapGame.this.cameraPositionTemp, - this.modelInstance.sequence, this.modelInstance.frame, this.modelInstance.counter); - this.modelCamera.getTargetTranslation(WarsmashGdxMapGame.this.cameraTargetTemp, - this.modelInstance.sequence, this.modelInstance.frame, this.modelInstance.counter); + this.modelCamera.getPositionTranslation(this.cameraPositionTemp, this.modelInstance.sequence, + this.modelInstance.frame, this.modelInstance.counter); + this.modelCamera.getTargetTranslation(this.cameraTargetTemp, this.modelInstance.sequence, + this.modelInstance.frame, this.modelInstance.counter); this.position.set(this.modelCamera.position); this.target.set(this.modelCamera.targetPosition); - this.position.add(WarsmashGdxMapGame.this.cameraPositionTemp[0], - WarsmashGdxMapGame.this.cameraPositionTemp[1], WarsmashGdxMapGame.this.cameraPositionTemp[2]); - this.target.add(WarsmashGdxMapGame.this.cameraTargetTemp[0], - WarsmashGdxMapGame.this.cameraTargetTemp[1], WarsmashGdxMapGame.this.cameraTargetTemp[2]); + this.position.add(this.cameraPositionTemp[0], this.cameraPositionTemp[1], this.cameraPositionTemp[2]); + this.target.add(this.cameraTargetTemp[0], this.cameraTargetTemp[1], this.cameraTargetTemp[2]); this.camera.perspective(this.modelCamera.fieldOfView * 0.75f, this.camera.getAspect(), this.modelCamera.nearClippingPlane, this.modelCamera.farClippingPlane); } @@ -524,6 +539,16 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.camera.moveToAndFace(this.position, this.target, this.worldUp); } + public void setModelInstance(final MdxComplexInstance modelInstance, final MdxModel portraitModel) { + this.modelInstance = modelInstance; + if (modelInstance == null) { + this.modelCamera = null; + } + else if ((portraitModel != null) && (portraitModel.getCameras().size() > 0)) { + this.modelCamera = portraitModel.getCameras().get(0); + } + } + // private void cameraUpdate() { // // } @@ -531,11 +556,10 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv private final float cameraSpeed = 4096.0f; // per second private final Vector2 cameraVelocity = new Vector2(); - private Scene portraitScene; private Texture minimapTexture; private Rectangle talentTreeWindow; - private GameUI gameUI; private Scene uiScene; + private MeleeUI meleeUI; @Override public boolean keyDown(final int keycode) { @@ -551,6 +575,12 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv else if (keycode == Input.Keys.UP) { this.cameraVelocity.y = this.cameraSpeed; } + else if (keycode == Input.Keys.INSERT) { + this.cameraManager.insertDown = true; + } + else if (keycode == Input.Keys.FORWARD_DEL) { + this.cameraManager.deleteDown = true; + } return true; } @@ -568,6 +598,12 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv else if (keycode == Input.Keys.UP) { this.cameraVelocity.y = 0; } + else if (keycode == Input.Keys.INSERT) { + this.cameraManager.insertDown = false; + } + else if (keycode == Input.Keys.FORWARD_DEL) { + this.cameraManager.deleteDown = false; + } return true; } @@ -613,7 +649,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv if ((rayPickUnit != null) && (this.selectedUnit != null) && (rayPickUnit.playerIndex != this.selectedUnit.playerIndex)) { if (this.viewer.orderSmart(rayPickUnit)) { - StandSequence.randomPortraitTalkSequence(this.portraitCameraManager.modelInstance); + this.meleeUI.portraitTalk(); this.selectedSoundCount = 0; } } @@ -626,7 +662,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv System.out.println(x + "," + y); this.viewer.terrain.logRomp(x, y); if (this.viewer.orderSmart(clickLocationTemp.x, clickLocationTemp.y)) { - StandSequence.randomPortraitTalkSequence(this.portraitCameraManager.modelInstance); + this.meleeUI.portraitTalk(); this.selectedSoundCount = 0; } } @@ -662,32 +698,15 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv } } if (selectionChanged) { - final MdxModel portraitModel = unit.portraitModel; - if (portraitModel != null) { - if (this.portraitCameraManager.modelInstance != null) { - this.portraitScene.removeInstance(this.portraitCameraManager.modelInstance); - } - this.portraitCameraManager.modelInstance = (MdxComplexInstance) portraitModel.addInstance(); - this.portraitCameraManager.modelInstance.setSequenceLoopMode(1); - this.portraitCameraManager.modelInstance.setScene(this.portraitScene); - this.portraitCameraManager.modelInstance.setVertexColor(unit.instance.vertexColor); - if (portraitModel.getCameras().size() > 0) { - this.portraitCameraManager.modelCamera = portraitModel.getCameras().get(0); - } - this.portraitCameraManager.modelInstance.setTeamColor(unit.playerIndex); - } + this.meleeUI.selectUnit(unit); } if (playedNewSound) { - StandSequence.randomPortraitTalkSequence(this.portraitCameraManager.modelInstance); + this.meleeUI.portraitTalk(); } } else { this.selectedUnit = null; - if (this.portraitCameraManager.modelInstance != null) { - this.portraitScene.removeInstance(this.portraitCameraManager.modelInstance); - } - this.portraitCameraManager.modelInstance = null; - this.portraitCameraManager.modelCamera = null; + this.meleeUI.selectUnit(null); } } return false; @@ -710,12 +729,12 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv @Override public boolean scrolled(final int amount) { - this.cameraManager.verticalAngle -= amount / 10.f; - if (this.cameraManager.verticalAngle > (Math.PI / 2)) { - this.cameraManager.verticalAngle = (float) Math.PI / 2; + this.cameraManager.verticalAngleAcceleration -= amount / 100.f; + if (this.cameraManager.verticalAngleAcceleration > (Math.PI / 128)) { + this.cameraManager.verticalAngleAcceleration = (float) (Math.PI / 128); } - if (this.cameraManager.verticalAngle < (float) Math.toRadians(34)) { - this.cameraManager.verticalAngle = (float) Math.toRadians(34); + if (this.cameraManager.verticalAngleAcceleration < (-Math.PI / 128)) { + this.cameraManager.verticalAngleAcceleration = -(float) (Math.PI / 128); } return true; } diff --git a/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java index 64c4989..fbf8f2f 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java @@ -7,43 +7,66 @@ import java.io.InputStreamReader; import java.util.HashMap; import java.util.Map; +import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.Texture; -import com.badlogic.gdx.graphics.g2d.TextureRegion; +import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator; +import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator.FreeTypeFontParameter; +import com.badlogic.gdx.utils.viewport.FitViewport; import com.badlogic.gdx.utils.viewport.Viewport; import com.etheller.warsmash.datasources.DataSource; import com.etheller.warsmash.fdfparser.FDFParser; import com.etheller.warsmash.fdfparser.FrameDefinitionVisitor; import com.etheller.warsmash.parsers.fdf.datamodel.AnchorDefinition; +import com.etheller.warsmash.parsers.fdf.datamodel.FontDefinition; import com.etheller.warsmash.parsers.fdf.datamodel.FrameClass; import com.etheller.warsmash.parsers.fdf.datamodel.FrameDefinition; import com.etheller.warsmash.parsers.fdf.datamodel.FrameTemplateEnvironment; +import com.etheller.warsmash.parsers.fdf.datamodel.SetPointDefinition; +import com.etheller.warsmash.parsers.fdf.datamodel.TextJustify; import com.etheller.warsmash.parsers.fdf.datamodel.Vector4Definition; import com.etheller.warsmash.parsers.fdf.frames.AbstractUIFrame; +import com.etheller.warsmash.parsers.fdf.frames.SetPoint; import com.etheller.warsmash.parsers.fdf.frames.SimpleFrame; +import com.etheller.warsmash.parsers.fdf.frames.SpriteFrame; +import com.etheller.warsmash.parsers.fdf.frames.StringFrame; import com.etheller.warsmash.parsers.fdf.frames.TextureFrame; import com.etheller.warsmash.parsers.fdf.frames.UIFrame; import com.etheller.warsmash.units.DataTable; import com.etheller.warsmash.units.Element; import com.etheller.warsmash.util.ImageUtils; import com.etheller.warsmash.util.StringBundle; +import com.etheller.warsmash.viewer5.Scene; +import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; +import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; public final class GameUI extends AbstractUIFrame implements UIFrame { - private final DataSource dataSource; private final Element skin; private final Viewport viewport; + private final Scene uiScene; + private final War3MapViewer modelViewer; private final FrameTemplateEnvironment templates; private final Map pathToTexture = new HashMap<>(); private final boolean autoPosition = false; + private final FreeTypeFontGenerator fontGenerator; + private final FreeTypeFontParameter fontParam; + private final Map nameToFrame = new HashMap<>(); + private final Viewport fdfCoordinateResolutionDummyViewport; - public GameUI(final DataSource dataSource, final Element skin, final Viewport viewport) { + public GameUI(final DataSource dataSource, final Element skin, final Viewport viewport, + final FreeTypeFontGenerator fontGenerator, final Scene uiScene, final War3MapViewer modelViewer) { super("GameUI", null); this.dataSource = dataSource; this.skin = skin; this.viewport = viewport; + this.uiScene = uiScene; + this.modelViewer = modelViewer; this.renderBounds.set(0, 0, viewport.getWorldWidth(), viewport.getWorldHeight()); this.templates = new FrameTemplateEnvironment(); - + this.fontGenerator = fontGenerator; + this.fontParam = new FreeTypeFontParameter(); + this.fdfCoordinateResolutionDummyViewport = new FitViewport(0.8f, 0.6f); } public static Element loadSkin(final DataSource dataSource, final String skin) { @@ -67,6 +90,27 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { return userSkin; } + public static Element loadSkin(final DataSource dataSource, final int skinIndex) { + final DataTable skinsTable = new DataTable(StringBundle.EMPTY); + try (InputStream stream = dataSource.getResourceAsStream("UI\\war3skins.txt")) { + skinsTable.readTXT(stream, true); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + final Element main = skinsTable.get("Main"); + final String skinsField = main.getField("Skins"); + final String[] skins = skinsField.split(","); + final Element defaultSkin = skinsTable.get("Default"); + final Element userSkin = skinsTable.get(skins[skinIndex]); + for (final String key : defaultSkin.keySet()) { + if (!userSkin.hasField(key)) { + userSkin.setField(key, defaultSkin.getField(key)); + } + } + return userSkin; + } + public void loadTOCFile(final String tocFilePath) throws IOException { final DataSourceFDFParserBuilder dataSourceFDFParserBuilder = new DataSourceFDFParserBuilder(this.dataSource); final FrameDefinitionVisitor fdfVisitor = new FrameDefinitionVisitor(this.templates, @@ -85,14 +129,20 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { } } - public UIFrame createFrame(final String name, final UIFrame owner, final int priority, final int createContext) { - throw new UnsupportedOperationException("Not yet implemented"); + public String getSkinField(String file) { + if ((file != null) && this.skin.hasField(file)) { + file = this.skin.getField(file); + } + else { + throw new IllegalStateException("Decorated file name lookup not available: " + file); + } + return file; } - public UIFrame createSimpleFrame(final String name, final UIFrame owner, final int createContext) { + public UIFrame createFrame(final String name, final UIFrame owner, final int priority, final int createContext) { final FrameDefinition frameDefinition = this.templates.getFrame(name); if (frameDefinition.getFrameClass() == FrameClass.Frame) { - if ("SIMPLEFRAME".equals(frameDefinition.getFrameType())) { + if ("SPRITE".equals(frameDefinition.getFrameType())) { final UIFrame inflated = inflate(frameDefinition, owner, null); if (this.autoPosition) { inflated.positionBounds(this.viewport); @@ -101,67 +151,150 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { return inflated; } } + throw new UnsupportedOperationException("Not yet implemented"); + } + + public UIFrame createSimpleFrame(final String name, final UIFrame owner, final int createContext) { + final FrameDefinition frameDefinition = this.templates.getFrame(name); + if (frameDefinition.getFrameClass() == FrameClass.Frame) { + final UIFrame inflated = inflate(frameDefinition, owner, null); + if (this.autoPosition) { + inflated.positionBounds(this.viewport); + } + add(inflated); + return inflated; + } return null; } public UIFrame inflate(final FrameDefinition frameDefinition, final UIFrame parent, final FrameDefinition parentDefinitionIfAvailable) { UIFrame inflatedFrame = null; + BitmapFont frameFont = null; + Viewport viewport2 = this.viewport; switch (frameDefinition.getFrameClass()) { case Frame: if ("SIMPLEFRAME".equals(frameDefinition.getFrameType())) { final SimpleFrame simpleFrame = new SimpleFrame(frameDefinition.getName(), parent); + // TODO: we should not need to put ourselves in this map 2x + this.nameToFrame.put(frameDefinition.getName(), simpleFrame); for (final FrameDefinition childDefinition : frameDefinition.getInnerFrames()) { simpleFrame.add(inflate(childDefinition, simpleFrame, frameDefinition)); } inflatedFrame = simpleFrame; } + else if ("SPRITE".equals(frameDefinition.getFrameType())) { + final SpriteFrame spriteFrame = new SpriteFrame(frameDefinition.getName(), parent, this.uiScene); + String backgroundArt = frameDefinition.getString("BackgroundArt"); + if (frameDefinition.has("DecorateFileNames") || ((parentDefinitionIfAvailable != null) + && parentDefinitionIfAvailable.has("DecorateFileNames"))) { + if (this.skin.hasField(backgroundArt)) { + backgroundArt = this.skin.getField(backgroundArt); + } + else { + throw new IllegalStateException("Decorated file name lookup not available: " + backgroundArt); + } + } + if (backgroundArt.toLowerCase().endsWith(".mdl") || backgroundArt.toLowerCase().endsWith(".mdx")) { + backgroundArt = backgroundArt.substring(0, backgroundArt.length() - 4); + } + backgroundArt += ".mdx"; + final MdxModel model = (MdxModel) this.modelViewer.load(backgroundArt, this.modelViewer.mapPathSolver, + this.modelViewer.solverParams); + spriteFrame.setModel(model); + spriteFrame.setSequence(0); + viewport2 = this.fdfCoordinateResolutionDummyViewport; + inflatedFrame = spriteFrame; + } break; case Layer: // NOT HANDLED YET break; case String: - break; - case Texture: - String file = frameDefinition.getString("File"); - if (frameDefinition.has("DecorateFileNames") || ((parentDefinitionIfAvailable != null) - && parentDefinitionIfAvailable.has("DecorateFileNames"))) { - if (this.skin.hasField(file)) { - file = this.skin.getField(file); - } - else { - throw new IllegalStateException("Decorated file name lookup not available: " + file); - } + final Float textLength = frameDefinition.getFloat("TextLength"); + TextJustify justifyH = frameDefinition.getTextJustify("FontJustificationH"); + if (justifyH == null) { + justifyH = TextJustify.CENTER; } - final Texture texture = loadTexture(file); - final Vector4Definition texCoord = frameDefinition.getVector4("TexCoord"); - final TextureRegion texRegion; - if (texCoord != null) { - texRegion = new TextureRegion(texture, texCoord.getX(), texCoord.getZ(), texCoord.getY(), - texCoord.getW()); + TextJustify justifyV = frameDefinition.getTextJustify("FontJustificationV"); + if (justifyV == null) { + justifyV = TextJustify.MIDDLE; + } + + Color fontColor; + final Vector4Definition fontColorDefinition = frameDefinition.getVector4("FontColor"); + if (fontColorDefinition == null) { + fontColor = Color.WHITE; } else { - texRegion = new TextureRegion(texture); + fontColor = new Color(fontColorDefinition.getX(), fontColorDefinition.getY(), + fontColorDefinition.getZ(), fontColorDefinition.getW()); } - final TextureFrame textureFrame = new TextureFrame(frameDefinition.getName(), parent, texRegion); + final FontDefinition font = frameDefinition.getFont("Font"); + this.fontParam.size = (int) convertY(viewport2, font.getFontSize()); + if (this.fontParam.size == 0) { + this.fontParam.size = 24; + } + frameFont = this.fontGenerator.generateFont(this.fontParam); + final StringFrame stringFrame = new StringFrame(frameDefinition.getName(), parent, fontColor, justifyH, + justifyV, frameFont); + inflatedFrame = stringFrame; + String text = frameDefinition.getString("Text"); + if (text != null) { + final String decoratedString = this.templates.getDecoratedString(text); + if (decoratedString != text) { + text = decoratedString; + } + stringFrame.setText(text); + } + break; + case Texture: + final String file = frameDefinition.getString("File"); + final boolean decorateFileNames = frameDefinition.has("DecorateFileNames") + || ((parentDefinitionIfAvailable != null) && parentDefinitionIfAvailable.has("DecorateFileNames")); + final Vector4Definition texCoord = frameDefinition.getVector4("TexCoord"); + final TextureFrame textureFrame = new TextureFrame(frameDefinition.getName(), parent, decorateFileNames, + texCoord); + textureFrame.setTexture(file, this); inflatedFrame = textureFrame; break; default: break; } if (inflatedFrame != null) { - final Float width = frameDefinition.getFloat("Width"); + if (frameDefinition.has("SetAllPoints")) { + inflatedFrame.setSetAllPoints(true); + } + Float width = frameDefinition.getFloat("Width"); if (width != null) { - inflatedFrame.setWidth(convertX(this.viewport, width)); + inflatedFrame.setWidth(convertX(viewport2, width)); + } + else { + width = frameDefinition.getFloat("TextLength"); + if (width != null) { + if (frameFont != null) { + inflatedFrame.setWidth(convertX(viewport2, width * frameFont.getSpaceWidth())); + } + } } final Float height = frameDefinition.getFloat("Height"); if (height != null) { - inflatedFrame.setHeight(convertY(this.viewport, height)); + inflatedFrame.setHeight(convertY(viewport2, height)); + } + else if (frameDefinition.getFont("Font") != null) { + inflatedFrame.setHeight(convertY(viewport2, frameDefinition.getFont("Font").getFontSize())); } for (final AnchorDefinition anchor : frameDefinition.getAnchors()) { - inflatedFrame.addAnchor(new AnchorDefinition(anchor.getMyPoint(), - convertX(this.viewport, anchor.getX()), convertY(this.viewport, anchor.getY()))); + inflatedFrame.addAnchor(new AnchorDefinition(anchor.getMyPoint(), convertX(viewport2, anchor.getX()), + convertY(viewport2, anchor.getY()))); } + for (final SetPointDefinition setPointDefinition : frameDefinition.getSetPoints()) { + inflatedFrame.addSetPoint(new SetPoint(setPointDefinition.getMyPoint(), + getFrameByName(setPointDefinition.getOther(), 0 /* TODO: createContext */), + setPointDefinition.getOtherPoint(), convertX(viewport2, setPointDefinition.getX()), + convertY(viewport2, setPointDefinition.getY()))); + } + this.nameToFrame.put(frameDefinition.getName(), inflatedFrame); } else { // TODO in production throw some kind of exception here @@ -174,6 +307,10 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { throw new UnsupportedOperationException("Not yet implemented"); } + public UIFrame getFrameByName(final String name, final int createContext) { + return this.nameToFrame.get(name); + } + public static float convertX(final Viewport viewport, final float fdfX) { return (fdfX / 0.8f) * viewport.getWorldWidth(); } @@ -182,7 +319,7 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { return (fdfY / 0.6f) * viewport.getWorldHeight(); } - private Texture loadTexture(String path) { + public Texture loadTexture(String path) { if (!path.contains(".")) { path = path + ".blp"; } diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java index 65e1aae..46af7b3 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java @@ -3,28 +3,41 @@ package com.etheller.warsmash.parsers.fdf.frames; import java.util.ArrayList; import java.util.List; +import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.g2d.GlyphLayout; +import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.utils.viewport.Viewport; import com.etheller.warsmash.parsers.fdf.datamodel.AnchorDefinition; import com.etheller.warsmash.parsers.fdf.datamodel.FramePoint; public abstract class AbstractRenderableFrame implements UIFrame { + private static final boolean DEBUG_LOG = true; protected String name; protected UIFrame parent; - protected boolean visible; + protected boolean visible = true; protected int level; protected final Rectangle renderBounds = new Rectangle(0, 0, 0, 0); // in libgdx rendering space protected List anchors = new ArrayList<>(); + protected List setPoints = new ArrayList<>(); + private boolean setAllPoints; public AbstractRenderableFrame(final String name, final UIFrame parent) { this.name = name; this.parent = parent; } + @Override + public void setSetAllPoints(final boolean setAllPoints) { + this.setAllPoints = setAllPoints; + } + + @Override public void setWidth(final float width) { this.renderBounds.width = width; } + @Override public void setHeight(final float height) { this.renderBounds.height = height; } @@ -133,6 +146,7 @@ public abstract class AbstractRenderableFrame implements UIFrame { } } + @Override public void setFramePointX(final FramePoint framePoint, final float x) { if (this.renderBounds.width == 0) { this.renderBounds.x = x; @@ -192,6 +206,7 @@ public abstract class AbstractRenderableFrame implements UIFrame { } } + @Override public void setFramePointY(final FramePoint framePoint, final float y) { if (this.renderBounds.height == 0) { this.renderBounds.y = y; @@ -235,6 +250,11 @@ public abstract class AbstractRenderableFrame implements UIFrame { this.anchors.add(anchorDefinition); } + @Override + public void addSetPoint(final SetPoint setPointDefinition) { + this.setPoints.add(setPointDefinition); + } + @Override public void positionBounds(final Viewport viewport) { if (this.parent == null) { @@ -244,7 +264,7 @@ public abstract class AbstractRenderableFrame implements UIFrame { if ("ResourceBarFrame".equals(this.name)) { System.out.println("doing resource bar"); } - if (this.anchors.isEmpty()) { + if (this.anchors.isEmpty() && this.setPoints.isEmpty()) { this.renderBounds.x = this.parent.getFramePointX(FramePoint.LEFT); this.renderBounds.y = this.parent.getFramePointY(FramePoint.BOTTOM); } @@ -254,19 +274,35 @@ public abstract class AbstractRenderableFrame implements UIFrame { final float parentPointY = this.parent.getFramePointY(anchor.getMyPoint()); setFramePointX(anchor.getMyPoint(), parentPointX + anchor.getX()); setFramePointY(anchor.getMyPoint(), parentPointY + anchor.getY()); - System.out.println(getClass().getSimpleName() + ":" + this.name + " anchoring to: " + anchor); + if (DEBUG_LOG) { + System.out.println(getClass().getSimpleName() + ":" + this.name + " anchoring to: " + anchor); + } + } + for (final SetPoint setPoint : this.setPoints) { + final UIFrame other = setPoint.getOther(); + if (other == null) { + continue; + } + final float parentPointX = other.getFramePointX(setPoint.getOtherPoint()); + final float parentPointY = other.getFramePointY(setPoint.getOtherPoint()); + setFramePointX(setPoint.getMyPoint(), parentPointX + setPoint.getX()); + setFramePointY(setPoint.getMyPoint(), parentPointY + setPoint.getY()); } } - if (this.renderBounds.width == 0) { - this.renderBounds.width = this.parent.getFramePointX(FramePoint.RIGHT) - - this.parent.getFramePointX(FramePoint.LEFT); + if (this.setAllPoints) { + if (this.renderBounds.width == 0) { + this.renderBounds.width = this.parent.getFramePointX(FramePoint.RIGHT) + - this.parent.getFramePointX(FramePoint.LEFT); + } + if (this.renderBounds.height == 0) { + this.renderBounds.height = this.parent.getFramePointY(FramePoint.TOP) + - this.parent.getFramePointY(FramePoint.BOTTOM); + } } - if (this.renderBounds.height == 0) { - this.renderBounds.height = this.parent.getFramePointY(FramePoint.TOP) - - this.parent.getFramePointY(FramePoint.BOTTOM); + if (DEBUG_LOG) { + System.out.println( + getClass().getSimpleName() + ":" + this.name + " finishing position bounds: " + this.renderBounds); } - System.out.println( - getClass().getSimpleName() + ":" + this.name + " finishing position bounds: " + this.renderBounds); innerPositionBounds(viewport); } @@ -280,6 +316,7 @@ public abstract class AbstractRenderableFrame implements UIFrame { return this.level; } + @Override public void setVisible(final boolean visible) { this.visible = visible; } @@ -288,4 +325,13 @@ public abstract class AbstractRenderableFrame implements UIFrame { this.level = level; } + @Override + public final void render(final SpriteBatch batch, final BitmapFont baseFont, final GlyphLayout glyphLayout) { + if (this.visible) { + internalRender(batch, baseFont, glyphLayout); + } + } + + protected abstract void internalRender(SpriteBatch batch, BitmapFont baseFont, GlyphLayout glyphLayout); + } diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractUIFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractUIFrame.java index a203fdb..eb2889d 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractUIFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractUIFrame.java @@ -3,6 +3,8 @@ package com.etheller.warsmash.parsers.fdf.frames; import java.util.ArrayList; import java.util.List; +import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.g2d.GlyphLayout; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.utils.viewport.Viewport; @@ -21,9 +23,9 @@ public abstract class AbstractUIFrame extends AbstractRenderableFrame implements } @Override - public void render(final SpriteBatch batch) { + protected void internalRender(final SpriteBatch batch, final BitmapFont baseFont, final GlyphLayout glyphLayout) { for (final UIFrame childFrame : this.childFrames) { - childFrame.render(batch); + childFrame.render(batch, baseFont, glyphLayout); } } diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/SetPoint.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/SetPoint.java new file mode 100644 index 0000000..0c54c20 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/SetPoint.java @@ -0,0 +1,40 @@ +package com.etheller.warsmash.parsers.fdf.frames; + +import com.etheller.warsmash.parsers.fdf.datamodel.FramePoint; + +public class SetPoint { + private final FramePoint myPoint; + private final UIFrame other; + private final FramePoint otherPoint; + private final float x; + private final float y; + + public SetPoint(final FramePoint myPoint, final UIFrame other, final FramePoint otherPoint, final float x, + final float y) { + this.myPoint = myPoint; + this.other = other; + this.otherPoint = otherPoint; + this.x = x; + this.y = y; + } + + public FramePoint getMyPoint() { + return this.myPoint; + } + + public UIFrame getOther() { + return this.other; + } + + public FramePoint getOtherPoint() { + return this.otherPoint; + } + + public float getX() { + return this.x; + } + + public float getY() { + return this.y; + } +} diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/SpriteFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/SpriteFrame.java new file mode 100644 index 0000000..1cfbd53 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/SpriteFrame.java @@ -0,0 +1,52 @@ +package com.etheller.warsmash.parsers.fdf.frames; + +import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.g2d.GlyphLayout; +import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.badlogic.gdx.utils.viewport.Viewport; +import com.etheller.warsmash.viewer5.Scene; +import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; +import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; + +public class SpriteFrame extends AbstractRenderableFrame { + + private final Scene scene; + private MdxComplexInstance instance; + + public SpriteFrame(final String name, final UIFrame parent, final Scene scene) { + super(name, parent); + this.scene = scene; + } + + public void setModel(final MdxModel model) { + if (this.instance != null) { + this.scene.removeInstance(this.instance); + } + if (model != null) { + this.instance = (MdxComplexInstance) model.addInstance(); + this.instance.setSequenceLoopMode(1); + this.instance.setScene(this.scene); + this.instance.setLocation(this.renderBounds.x, this.renderBounds.y, 0); + } + else { + this.instance = null; + } + } + + @Override + protected void internalRender(final SpriteBatch batch, final BitmapFont baseFont, final GlyphLayout glyphLayout) { + + } + + @Override + protected void innerPositionBounds(final Viewport viewport) { + + } + + public void setSequence(final int index) { + if (this.instance != null) { + this.instance.setSequence(index); + } + } + +} diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/StringFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/StringFrame.java new file mode 100644 index 0000000..92fa4a6 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/StringFrame.java @@ -0,0 +1,72 @@ +package com.etheller.warsmash.parsers.fdf.frames; + +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.g2d.GlyphLayout; +import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.badlogic.gdx.utils.viewport.Viewport; +import com.etheller.warsmash.parsers.fdf.datamodel.TextJustify; + +public class StringFrame extends AbstractRenderableFrame { + private Color color; + private String text = "Default string"; + private final TextJustify justifyH; + private final TextJustify justifyV; + private final BitmapFont frameFont; + + public StringFrame(final String name, final UIFrame parent, final Color color, final TextJustify justifyH, + final TextJustify justifyV, final BitmapFont frameFont) { + super(name, parent); + this.color = color; + this.justifyH = justifyH; + this.justifyV = justifyV; + this.frameFont = frameFont; + this.text = name; + } + + public void setText(final String text) { + this.text = text; + } + + public void setColor(final Color color) { + this.color = color; + } + + @Override + protected void internalRender(final SpriteBatch batch, final BitmapFont baseFont, final GlyphLayout glyphLayout) { + this.frameFont.setColor(this.color); + glyphLayout.setText(this.frameFont, this.text); + final float x; + switch (this.justifyH) { + case CENTER: + x = this.renderBounds.x + ((this.renderBounds.width - glyphLayout.width) / 2); + break; + case RIGHT: + x = (this.renderBounds.x + this.renderBounds.width) - glyphLayout.width; + break; + case LEFT: + default: + x = this.renderBounds.x; + break; + } + final float y; + switch (this.justifyV) { + case MIDDLE: + y = this.renderBounds.y + ((this.renderBounds.height + this.frameFont.getLineHeight()) / 2); + break; + case TOP: + y = (this.renderBounds.y + this.renderBounds.height); + break; + case BOTTOM: + default: + y = this.renderBounds.y + this.frameFont.getLineHeight(); + break; + } + this.frameFont.draw(batch, this.text, x, y); + } + + @Override + protected void innerPositionBounds(final Viewport viewport) { + } + +} diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/TextureFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/TextureFrame.java index 65b291b..da90653 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/TextureFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/TextureFrame.java @@ -1,19 +1,28 @@ package com.etheller.warsmash.parsers.fdf.frames; +import com.badlogic.gdx.graphics.Texture; +import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.g2d.GlyphLayout; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.utils.viewport.Viewport; +import com.etheller.warsmash.parsers.fdf.GameUI; +import com.etheller.warsmash.parsers.fdf.datamodel.Vector4Definition; public class TextureFrame extends AbstractRenderableFrame { - private final TextureRegion texture; + private TextureRegion texture; + private final boolean decorateFileNames; + private final Vector4Definition texCoord; - public TextureFrame(final String name, final UIFrame parent, final TextureRegion texture) { + public TextureFrame(final String name, final UIFrame parent, final boolean decorateFileNames, + final Vector4Definition texCoord) { super(name, parent); - this.texture = texture; + this.decorateFileNames = decorateFileNames; + this.texCoord = texCoord; } @Override - public void render(final SpriteBatch batch) { + protected void internalRender(final SpriteBatch batch, final BitmapFont baseFont, final GlyphLayout glyphLayout) { batch.draw(this.texture, this.renderBounds.x, this.renderBounds.y, this.renderBounds.width, this.renderBounds.height); } @@ -22,4 +31,27 @@ public class TextureFrame extends AbstractRenderableFrame { protected void innerPositionBounds(final Viewport viewport) { } + public void setTexture(String file, final GameUI gameUI) { + if (this.decorateFileNames) { + file = gameUI.getSkinField(file); + } + final Texture texture = gameUI.loadTexture(file); + setTexture(texture); + } + + public void setTexture(final Texture texture) { + final TextureRegion texRegion; + if (this.texCoord != null) { + texRegion = new TextureRegion(texture, this.texCoord.getX(), this.texCoord.getZ(), this.texCoord.getY(), + this.texCoord.getW()); + } + else { + texRegion = new TextureRegion(texture); + } + this.texture = texRegion; + } + + public void setTexture(final TextureRegion texture) { + this.texture = texture; + } } diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/UIFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/UIFrame.java index 8f20224..0615caa 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/UIFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/UIFrame.java @@ -1,22 +1,34 @@ package com.etheller.warsmash.parsers.fdf.frames; +import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.g2d.GlyphLayout; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.utils.viewport.Viewport; import com.etheller.warsmash.parsers.fdf.datamodel.AnchorDefinition; import com.etheller.warsmash.parsers.fdf.datamodel.FramePoint; public interface UIFrame { - public void render(SpriteBatch batch); + public void render(SpriteBatch batch, BitmapFont baseFont, GlyphLayout glyphLayout); public float getFramePointX(FramePoint framePoint); public float getFramePointY(FramePoint framePoint); + void setFramePointX(final FramePoint framePoint, final float x); + + void setFramePointY(final FramePoint framePoint, final float y); + void positionBounds(final Viewport viewport); void addAnchor(final AnchorDefinition anchorDefinition); + void addSetPoint(SetPoint setPointDefinition); + void setWidth(final float width); void setHeight(final float height); + + void setSetAllPoints(boolean setAllPoints); + + void setVisible(boolean visible); } diff --git a/core/src/com/etheller/warsmash/parsers/jass/Jass2.java b/core/src/com/etheller/warsmash/parsers/jass/Jass2.java index 95e853f..e19a5a0 100644 --- a/core/src/com/etheller/warsmash/parsers/jass/Jass2.java +++ b/core/src/com/etheller/warsmash/parsers/jass/Jass2.java @@ -9,6 +9,8 @@ import org.antlr.v4.runtime.CommonTokenStream; import org.antlr.v4.runtime.RecognitionException; import org.antlr.v4.runtime.Recognizer; +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator; import com.badlogic.gdx.utils.viewport.Viewport; import com.etheller.interpreter.JassLexer; import com.etheller.interpreter.JassParser; @@ -20,6 +22,7 @@ import com.etheller.interpreter.ast.scope.trigger.TriggerBooleanExpression; import com.etheller.interpreter.ast.value.BooleanJassValue; import com.etheller.interpreter.ast.value.HandleJassType; import com.etheller.interpreter.ast.value.HandleJassValue; +import com.etheller.interpreter.ast.value.IntegerJassValue; import com.etheller.interpreter.ast.value.JassValue; import com.etheller.interpreter.ast.value.StringJassValue; import com.etheller.interpreter.ast.value.visitor.IntegerJassValueVisitor; @@ -32,6 +35,8 @@ import com.etheller.warsmash.datasources.DataSource; import com.etheller.warsmash.parsers.fdf.GameUI; import com.etheller.warsmash.parsers.fdf.datamodel.AnchorDefinition; import com.etheller.warsmash.parsers.fdf.datamodel.FramePoint; +import com.etheller.warsmash.parsers.fdf.frames.SetPoint; +import com.etheller.warsmash.parsers.fdf.frames.StringFrame; import com.etheller.warsmash.parsers.fdf.frames.UIFrame; import com.etheller.warsmash.parsers.jass.triggers.BoolExprAnd; import com.etheller.warsmash.parsers.jass.triggers.BoolExprCondition; @@ -41,16 +46,19 @@ import com.etheller.warsmash.parsers.jass.triggers.BoolExprOr; import com.etheller.warsmash.parsers.jass.triggers.TriggerAction; import com.etheller.warsmash.parsers.jass.triggers.TriggerCondition; import com.etheller.warsmash.units.Element; +import com.etheller.warsmash.viewer5.Scene; +import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; public class Jass2 { public static final boolean REPORT_SYNTAX_ERRORS = true; public static JUIEnvironment loadJUI(final DataSource dataSource, final Viewport uiViewport, + final FreeTypeFontGenerator fontGenerator, final Scene uiScene, final War3MapViewer war3MapViewer, final RootFrameListener rootFrameListener, final String... files) { final JassProgramVisitor jassProgramVisitor = new JassProgramVisitor(); - final JUIEnvironment environment = new JUIEnvironment(jassProgramVisitor, dataSource, uiViewport, - rootFrameListener); + final JUIEnvironment environment = new JUIEnvironment(jassProgramVisitor, dataSource, uiViewport, fontGenerator, + uiScene, war3MapViewer, rootFrameListener); for (final String jassFile : files) { try { JassLexer lexer; @@ -95,7 +103,8 @@ public class Jass2 { private Element skin; public JUIEnvironment(final JassProgramVisitor jassProgramVisitor, final DataSource dataSource, - final Viewport uiViewport, final RootFrameListener rootFrameListener) { + final Viewport uiViewport, final FreeTypeFontGenerator fontGenerator, final Scene uiScene, + final War3MapViewer war3MapViewer, final RootFrameListener rootFrameListener) { final GlobalScope globals = jassProgramVisitor.getGlobals(); final HandleJassType frameHandleType = globals.registerHandleType("framehandle"); final HandleJassType framePointType = globals.registerHandleType("framepointtype"); @@ -128,7 +137,8 @@ public class Jass2 { final TriggerExecutionScope triggerScope) { final String skinArg = arguments.get(0).visit(StringJassValueVisitor.getInstance()); final Element skin = GameUI.loadSkin(dataSource, skinArg); - final GameUI gameUI = new GameUI(dataSource, skin, uiViewport); + final GameUI gameUI = new GameUI(dataSource, skin, uiViewport, fontGenerator, uiScene, + war3MapViewer); JUIEnvironment.this.gameUI = gameUI; JUIEnvironment.this.skin = skin; rootFrameListener.onCreate(gameUI); @@ -163,7 +173,33 @@ public class Jass2 { return new HandleJassValue(frameHandleType, simpleFrame); } }); - jassProgramVisitor.getJassNativeManager().createNative("FrameSetAbsPoint", new JassFunction() { + jassProgramVisitor.getJassNativeManager().createNative("CreateFrame", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final String templateName = arguments.get(0).visit(StringJassValueVisitor.getInstance()); + final UIFrame ownerFrame = arguments.get(1).visit(ObjectJassValueVisitor.getInstance()); + final int priority = arguments.get(2).visit(IntegerJassValueVisitor.getInstance()); + final int createContext = arguments.get(3).visit(IntegerJassValueVisitor.getInstance()); + + final UIFrame simpleFrame = JUIEnvironment.this.gameUI.createFrame(templateName, ownerFrame, + priority, createContext); + + return new HandleJassValue(frameHandleType, simpleFrame); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetFrameByName", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final String templateName = arguments.get(0).visit(StringJassValueVisitor.getInstance()); + final int createContext = arguments.get(1).visit(IntegerJassValueVisitor.getInstance()); + + final UIFrame simpleFrame = JUIEnvironment.this.gameUI.getFrameByName(templateName, createContext); + return new HandleJassValue(frameHandleType, simpleFrame); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("FrameSetAnchor", new JassFunction() { @Override public JassValue call(final List arguments, final GlobalScope globalScope, final TriggerExecutionScope triggerScope) { @@ -178,6 +214,71 @@ public class Jass2 { return null; } }); + jassProgramVisitor.getJassNativeManager().createNative("FrameSetAbsPoint", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final UIFrame frame = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final FramePoint framePoint = arguments.get(1) + .visit(ObjectJassValueVisitor.getInstance()); + final double x = arguments.get(2).visit(RealJassValueVisitor.getInstance()); + final double y = arguments.get(3).visit(RealJassValueVisitor.getInstance()); + + frame.setFramePointX(framePoint, GameUI.convertX(uiViewport, (float) x)); + frame.setFramePointY(framePoint, GameUI.convertY(uiViewport, (float) y)); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("FrameSetPoint", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final UIFrame frame = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final FramePoint framePoint = arguments.get(1) + .visit(ObjectJassValueVisitor.getInstance()); + final UIFrame otherFrame = arguments.get(2).visit(ObjectJassValueVisitor.getInstance()); + final FramePoint otherPoint = arguments.get(3) + .visit(ObjectJassValueVisitor.getInstance()); + final double x = arguments.get(2).visit(RealJassValueVisitor.getInstance()); + final double y = arguments.get(3).visit(RealJassValueVisitor.getInstance()); + + frame.addSetPoint(new SetPoint(framePoint, otherFrame, otherPoint, + GameUI.convertX(uiViewport, (float) x), GameUI.convertY(uiViewport, (float) y))); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("FrameSetText", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final StringFrame frame = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final String text = arguments.get(1).visit(StringJassValueVisitor.getInstance()); + + frame.setText(text); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("FrameSetTextColor", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final StringFrame frame = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final int colorInt = arguments.get(1).visit(IntegerJassValueVisitor.getInstance()); + frame.setColor(new Color(colorInt)); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ConvertColor", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final int a = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + final int r = arguments.get(1).visit(IntegerJassValueVisitor.getInstance()); + final int g = arguments.get(2).visit(IntegerJassValueVisitor.getInstance()); + final int b = arguments.get(3).visit(IntegerJassValueVisitor.getInstance()); + return new IntegerJassValue(a | (b << 8) | (g << 16) | (r << 24)); + } + }); jassProgramVisitor.getJassNativeManager().createNative("FramePositionBounds", new JassFunction() { @Override public JassValue call(final List arguments, final GlobalScope globalScope, diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/AnimatedObject.java b/core/src/com/etheller/warsmash/parsers/mdlx/AnimatedObject.java index e665208..8d93db5 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/AnimatedObject.java +++ b/core/src/com/etheller/warsmash/parsers/mdlx/AnimatedObject.java @@ -25,7 +25,11 @@ public abstract class AnimatedObject implements Chunk, MdlxBlock { public void readTimelines(final LittleEndianDataInputStream stream, long size) throws IOException { while (size > 0) { final War3ID name = new War3ID(Integer.reverseBytes(stream.readInt())); - final Timeline timeline = AnimationMap.ID_TO_TAG.get(name).getImplementation().createTimeline(); + final AnimationMap animationMap = AnimationMap.ID_TO_TAG.get(name); + if (animationMap == null) { + throw new IllegalStateException("Unable to parse: '" + name + "'"); + } + final Timeline timeline = animationMap.getImplementation().createTimeline(); timeline.readMdx(stream, name); diff --git a/core/src/com/etheller/warsmash/util/FastNumberFormat.java b/core/src/com/etheller/warsmash/util/FastNumberFormat.java new file mode 100644 index 0000000..8741b4d --- /dev/null +++ b/core/src/com/etheller/warsmash/util/FastNumberFormat.java @@ -0,0 +1,24 @@ +package com.etheller.warsmash.util; + +public class FastNumberFormat { + private static final StringBuilder RECYCLE_STRING_BUILDER = new StringBuilder(); + + public static String formatWholeNumber(final float value) { + int intValue = (int) value; + RECYCLE_STRING_BUILDER.setLength(0); + do { + RECYCLE_STRING_BUILDER.append(intValue % 10); + intValue /= 10; + } + while (intValue > 0); + final int len = RECYCLE_STRING_BUILDER.length(); + final int halfLength = len / 2; + for (int i = 0; i < halfLength; i++) { + final char swapCharA = RECYCLE_STRING_BUILDER.charAt(i); + final char swapCharB = RECYCLE_STRING_BUILDER.charAt(len - 1 - i); + RECYCLE_STRING_BUILDER.setCharAt(len - 1 - i, swapCharA); + RECYCLE_STRING_BUILDER.setCharAt(i, swapCharB); + } + return RECYCLE_STRING_BUILDER.toString(); + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java index fb80449..ded730c 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java @@ -63,6 +63,7 @@ public class MdxComplexInstance extends ModelInstance { public FloatBuffer worldMatricesCopyHeap; public DataTexture boneTexture; public Texture[] replaceableTextures = new Texture[WarsmashConstants.REPLACEABLE_TEXTURE_LIMIT]; + private float animationSpeed = 1.0f; public MdxComplexInstance(final MdxModel model) { super(model); @@ -522,7 +523,7 @@ public class MdxComplexInstance extends ModelInstance { if (sequenceId != -1) { final Sequence sequence = model.sequences.get(sequenceId); final long[] interval = sequence.getInterval(); - final int frameTime = (int) (dt * 1000); + final int frameTime = (int) (dt * 1000 * this.animationSpeed); this.frame += frameTime; this.counter += frameTime; @@ -713,4 +714,8 @@ public class MdxComplexInstance extends ModelInstance { } return false; } + + public void setAnimationSpeed(final float speedRatio) { + this.animationSpeed = speedRatio; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Doodad.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Doodad.java index d39df24..f969f0c 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Doodad.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Doodad.java @@ -9,13 +9,19 @@ import com.etheller.warsmash.viewer5.ModelInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; public class Doodad { + private static final int SAMPLE_RADIUS = 4; private static final War3ID TEX_FILE = War3ID.fromString("btxf"); private static final War3ID TEX_ID = War3ID.fromString("btxi"); public final ModelInstance instance; private final MutableGameObject row; + private final float maxPitch; + private final float maxRoll; public Doodad(final War3MapViewer map, final MdxModel model, final MutableGameObject row, - final com.etheller.warsmash.parsers.w3x.doo.Doodad doodad, final WorldEditorDataType type) { + final com.etheller.warsmash.parsers.w3x.doo.Doodad doodad, final WorldEditorDataType type, + final float maxPitch, final float maxRoll) { + this.maxPitch = maxPitch; + this.maxRoll = maxRoll; final boolean isSimple = row.readSLKTagBoolean("lightweight"); ModelInstance instance; @@ -27,7 +33,38 @@ public class Doodad { } instance.move(doodad.getLocation()); - instance.rotate(new Quaternion().setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, doodad.getAngle())); + // TODO: the following pitch/roll system is a heuristic, and we probably want to + // revisit it later. + // Specifically, I was pretty convinced that whichever is applied first + // (pitch/roll) should be used to do a projection onto the already-tilted plane + // to find the angle used for the other of the two + // (instead of measuring down from an imaginary flat ground plane, as we do + // currently). + final float facingRadians = doodad.getAngle(); + float pitch, roll; + final float x = doodad.getLocation()[0]; + final float y = doodad.getLocation()[1]; + final float pitchSampleForwardX = x + (SAMPLE_RADIUS * (float) Math.cos(facingRadians)); + final float pitchSampleForwardY = y + (SAMPLE_RADIUS * (float) Math.sin(facingRadians)); + final float pitchSampleBackwardX = x - (SAMPLE_RADIUS * (float) Math.cos(facingRadians)); + final float pitchSampleBackwardY = y - (SAMPLE_RADIUS * (float) Math.sin(facingRadians)); + final float pitchSampleGroundHeight1 = map.terrain.getGroundHeight(pitchSampleBackwardX, pitchSampleBackwardY); + final float pitchSampleGorundHeight2 = map.terrain.getGroundHeight(pitchSampleForwardX, pitchSampleForwardY); + pitch = Math.min(maxPitch, + (float) Math.atan2(pitchSampleGorundHeight2 - pitchSampleGroundHeight1, SAMPLE_RADIUS * 2)); + final double leftOfFacingAngle = facingRadians + (Math.PI / 2); + final float rollSampleForwardX = x + (SAMPLE_RADIUS * (float) Math.cos(leftOfFacingAngle)); + final float rollSampleForwardY = y + (SAMPLE_RADIUS * (float) Math.sin(leftOfFacingAngle)); + final float rollSampleBackwardX = x - (SAMPLE_RADIUS * (float) Math.cos(leftOfFacingAngle)); + final float rollSampleBackwardY = y - (SAMPLE_RADIUS * (float) Math.sin(leftOfFacingAngle)); + final float rollSampleGroundHeight1 = map.terrain.getGroundHeight(rollSampleBackwardX, rollSampleBackwardY); + final float rollSampleGroundHeight2 = map.terrain.getGroundHeight(rollSampleForwardX, rollSampleForwardY); + roll = Math.min(maxRoll, + (float) Math.atan2(rollSampleGroundHeight2 - rollSampleGroundHeight1, SAMPLE_RADIUS * 2)); + instance.rotate(new Quaternion().setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, facingRadians)); + instance.rotate(new Quaternion().setFromAxisRad(RenderMathUtils.VEC3_UNIT_Y, -pitch)); + instance.rotate(new Quaternion().setFromAxisRad(RenderMathUtils.VEC3_UNIT_X, roll)); +// instance.rotate(new Quaternion().setEulerAnglesRad(facingRadians, 0, 0)); instance.scale(doodad.getScale()); if (type == WorldEditorDataType.DOODADS) { final float defScale = row.readSLKTagFloat("defScale"); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/TerrainDoodad.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/TerrainDoodad.java index 9db6c47..eda3fc4 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/TerrainDoodad.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/TerrainDoodad.java @@ -1,23 +1,31 @@ package com.etheller.warsmash.viewer5.handlers.w3x; +import java.awt.image.BufferedImage; + import com.badlogic.gdx.math.Quaternion; import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; import com.etheller.warsmash.util.RenderMathUtils; +import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; -import com.etheller.warsmash.viewer5.handlers.mdx.MdxSimpleInstance; public class TerrainDoodad { private static final float[] locationHeap = new float[3]; - public final MdxSimpleInstance instance; + public final MdxComplexInstance instance; private final MutableGameObject row; public TerrainDoodad(final War3MapViewer map, final MdxModel model, final MutableGameObject row, - final com.etheller.warsmash.parsers.w3x.doo.TerrainDoodad doodad) { + final com.etheller.warsmash.parsers.w3x.doo.TerrainDoodad doodad, final BufferedImage pathingTextureImage) { final float[] centerOffset = map.terrain.centerOffset; - final MdxSimpleInstance instance = (MdxSimpleInstance) model.addInstance(1); + final MdxComplexInstance instance = (MdxComplexInstance) model.addInstance(0); - locationHeap[0] = (doodad.getLocation()[0] * 128) + centerOffset[0] + 128; - locationHeap[1] = (doodad.getLocation()[1] * 128) + centerOffset[1] + 128; + final int textureWidth = pathingTextureImage.getWidth(); + final int textureHeight = pathingTextureImage.getHeight(); + final int textureWidthTerrainCells = textureWidth / 4; + final int textureHeightTerrainCells = textureHeight / 4; + final int minCellX = ((int) doodad.getLocation()[0]); + final int minCellY = ((int) doodad.getLocation()[1]); + locationHeap[0] = ((minCellX * 128) + (textureWidthTerrainCells * 64) + centerOffset[0]); + locationHeap[1] = ((minCellY * 128) + (textureHeightTerrainCells * 64) + centerOffset[1]); instance.move(locationHeap); instance.rotate(new Quaternion().setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index b12ee51..7b9a16b 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -69,11 +69,12 @@ import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderItem; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitClassification; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityAttack; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityMove; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.projectile.CAttackProjectile; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.projectile.CAttackProjectile; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.BooleanAbilityActivationReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.CWidgetAbilityTargetCheckReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.PointAbilityTargetCheckReceiver; @@ -143,6 +144,7 @@ public class War3MapViewer extends ModelViewer { public List selected = new ArrayList<>(); private DataTable unitAckSoundsTable; private DataTable miscData; + private DataTable unitGlobalStrings; private MdxComplexInstance confirmationInstance; public CSimulation simulation; private float updateTime; @@ -231,6 +233,20 @@ public class War3MapViewer extends ModelViewer { try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("UI\\MiscData.txt")) { this.miscData.readTXT(miscDataTxtStream, true); } + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\MiscData.txt")) { + this.miscData.readTXT(miscDataTxtStream, true); + } + this.unitGlobalStrings = new DataTable(worldEditStrings); + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\UnitGlobalStrings.txt")) { + this.unitGlobalStrings.readTXT(miscDataTxtStream, true); + } + final Element categories = this.unitGlobalStrings.get("Categories"); + for (final CUnitClassification unitClassification : CUnitClassification.values()) { + if (unitClassification.getLocaleKey() != null) { + final String displayName = categories.getField(unitClassification.getLocaleKey()); + unitClassification.setDisplayName(displayName); + } + } this.selectionCircleSizes.clear(); final Element selectionCircleData = this.miscData.get("SelectionCircle"); final int selectionCircleNumSizes = selectionCircleData.getFieldValue("NumSizes"); @@ -335,7 +351,7 @@ public class War3MapViewer extends ModelViewer { } final Warcraft3MapObjectData modifications = this.mapMpq.readModifications(); - this.simulation = new CSimulation(modifications.getUnits(), modifications.getAbilities(), + this.simulation = new CSimulation(this.miscData, modifications.getUnits(), modifications.getAbilities(), new ProjectileCreator() { @Override public CAttackProjectile create(final CSimulation simulation, final CUnit source, @@ -443,6 +459,8 @@ public class War3MapViewer extends ModelViewer { fileVar += ".mdx"; + final float maxPitch = row.readSLKTagFloat("maxPitch"); + final float maxRoll = row.readSLKTagFloat("maxRoll"); if (type == WorldEditorDataType.DESTRUCTIBLES) { final String shadowString = row.readSLKTag("shadow"); if ((shadowString != null) && (shadowString.length() > 0) && !"_".equals(shadowString)) { @@ -490,14 +508,18 @@ public class War3MapViewer extends ModelViewer { model = (MdxModel) this.load(fileVar, this.mapPathSolver, this.solverParams); } - this.doodads.add(new Doodad(this, model, row, doodad, type)); + this.doodads.add(new Doodad(this, model, row, doodad, type, maxPitch, maxRoll)); } } // Cliff/Terrain doodads. for (final com.etheller.warsmash.parsers.w3x.doo.TerrainDoodad doodad : doo.getTerrainDoodads()) { final MutableGameObject row = modifications.getDoodads().get(doodad.getId()); - String file = "UI\\Feedback\\WaypointFlags\\WaypointFlag.mdx";// row.readSLKTag("file"); + String file = row.readSLKTag("file");// + if ("".equals(file)) { + final String blaBla = row.readSLKTag("file"); + System.out.println("bla"); + } if (file.toLowerCase().endsWith(".mdl")) { file = file.substring(0, file.length() - 4); } @@ -506,7 +528,47 @@ public class War3MapViewer extends ModelViewer { } final MdxModel model = (MdxModel) this.load(file, this.mapPathSolver, this.solverParams); - this.terrainDoodads.add(new TerrainDoodad(this, model, row, doodad)); + final String pathingTexture = row.readSLKTag("pathTex"); + BufferedImage pathingTextureImage; + if ((pathingTexture != null) && (pathingTexture.length() > 0) && !"_".equals(pathingTexture)) { + + pathingTextureImage = this.filePathToPathingMap.get(pathingTexture.toLowerCase()); + if (pathingTextureImage == null) { + if (this.mapMpq.has(pathingTexture)) { + try { + pathingTextureImage = TgaFile.readTGA(pathingTexture, + this.mapMpq.getResourceAsStream(pathingTexture)); + this.filePathToPathingMap.put(pathingTexture.toLowerCase(), pathingTextureImage); + } + catch (final Exception exc) { + exc.printStackTrace(); + } + } + } + } + else { + pathingTextureImage = null; + } + if (pathingTextureImage != null) { + // blit out terrain cells under this TerrainDoodad + final int textureWidth = pathingTextureImage.getWidth(); + final int textureHeight = pathingTextureImage.getHeight(); + final int textureWidthTerrainCells = textureWidth / 4; + final int textureHeightTerrainCells = textureHeight / 4; + final int minCellX = ((int) doodad.getLocation()[0]); + final int minCellY = ((int) doodad.getLocation()[1]); + final int maxCellX = (minCellX + textureWidthTerrainCells) - 1; + final int maxCellY = (minCellY + textureHeightTerrainCells) - 1; + for (int j = minCellY; j <= maxCellY; j++) { + for (int i = minCellX; i <= maxCellX; i++) { + this.terrain.removeTerrainCellWithoutFlush(i, j); + } + } + this.terrain.flushRemovedTerrainCells(); + } + + System.out.println("Loading terrain doodad: " + file); + this.terrainDoodads.add(new TerrainDoodad(this, model, row, doodad, pathingTextureImage)); } this.doodadsReady = true; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/RenderCorner.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/RenderCorner.java index f974f84..421385e 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/RenderCorner.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/RenderCorner.java @@ -7,6 +7,7 @@ public class RenderCorner extends Corner { public boolean romp; public float rampAdjust; public float depth; + public boolean hideCliff; public RenderCorner(final Corner corner) { super(corner); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java index 0634a34..c353fe4 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java @@ -544,7 +544,7 @@ public class Terrain { final RenderCorner topLeft = this.corners[i][j + 1]; final RenderCorner topRight = this.corners[i + 1][j + 1]; - if (bottomLeft.cliff) { + if (bottomLeft.cliff && !bottomLeft.hideCliff) { final int base = Math.min(Math.min(bottomLeft.getLayerHeight(), bottomRight.getLayerHeight()), Math.min(topLeft.getLayerHeight(), topRight.getLayerHeight())); @@ -672,6 +672,33 @@ public class Terrain { uploadGroundTexture(); } + public void removeTerrainCell(final int i, final int j) { + this.groundTextureList[(((j * (this.columns - 1)) + i) * 4) + 3] |= 0b1000000000000000; + this.corners[i][j].hideCliff = true; + uploadGroundTexture(); + try { + updateCliffMeshes(new Rectangle(i - 1, j - 1, 1, 1)); // TODO does this work? + } + catch (final IOException e) { + throw new RuntimeException(e); + } + } + + public void removeTerrainCellWithoutFlush(final int i, final int j) { + this.groundTextureList[(((j * (this.columns - 1)) + i) * 4) + 3] |= 0b1000000000000000; + this.corners[i][j].hideCliff = true; + } + + public void flushRemovedTerrainCells() { + uploadGroundTexture(); + try { + updateCliffMeshes(new Rectangle(0, 0, this.columns - 1, this.rows - 1)); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + } + private void uploadGroundTexture() { if (this.groundTextureData != -1) { Gdx.gl30.glBindTexture(GL30.GL_TEXTURE_2D, this.groundTextureData); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/OrientationInterpolation.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/OrientationInterpolation.java new file mode 100644 index 0000000..e761e7c --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/OrientationInterpolation.java @@ -0,0 +1,61 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.rendersim; + +/** + * We observe this table during gameplay but I haven't found it anywhere in the + * data yet. So, I'm making my own. + */ +public enum OrientationInterpolation { + OI0(0.07f, 0.2f, 999f), + OI1(0.03f, 0.1f, 0.04f), + OI2(0.015f, 1.0f, 999), + OI3(0.005f, 0.1f, 0.0043f), + OI4(0.04f, 0.15f, 0.01f), + OI5(0.05f, 0.18f, 0.015f), + OI6(0.1f, 0.3f, 0.1f), + OI7(0.003f, 0.08f, 0.0027f), + OI8(0.001f, 0.05f, 0.001f); + + public static OrientationInterpolation[] VALUES = values(); + + private float startingAcceleration; + private float maxVelocity; + private float endingNegativeAcceleration; + private float endingAccelCutoff; + private float startingAccelCutoff; + + private OrientationInterpolation(final float startingAcceleration, final float maxVelocity, + final float endingNegativeAcceleration) { + this.startingAcceleration = startingAcceleration; + this.maxVelocity = maxVelocity; + this.endingNegativeAcceleration = endingNegativeAcceleration; + this.endingAccelCutoff = endingAccelCutoff(maxVelocity, endingNegativeAcceleration); + this.startingAccelCutoff = endingAccelCutoff(maxVelocity, startingAcceleration); + } + + public float getStartingAcceleration() { + return this.startingAcceleration; + } + + public float getMaxVelocity() { + return this.maxVelocity; + } + + public float getEndingNegativeAcceleration() { + return this.endingNegativeAcceleration; + } + + public float getEndingAccelCutoff() { + return this.endingAccelCutoff; + } + + public float getStartingAccelCutoff() { + return this.startingAccelCutoff; + } + + private static float endingAccelCutoff(final float maxVelocity, final float endingAccel) { + final float endingAccelFinishingTime = maxVelocity / endingAccel; + final float endingDistanceRequired = (maxVelocity * endingAccelFinishingTime) + - ((endingAccel / 2) * (endingAccelFinishingTime * endingAccelFinishingTime)); + return endingDistanceRequired; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ParseBogus.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ParseBogus.java new file mode 100644 index 0000000..1a764b4 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ParseBogus.java @@ -0,0 +1,49 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.rendersim; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.List; + +public class ParseBogus { + + public static void main(final String[] args) { + final File bogusDataFile = new File("E:\\Games\\Warcraft III Patch 1.22\\Logs\\MyReteraTest.pre"); + final float[][] cubeIndexToData = new float[9][44]; + try { + final List allLines = Files.readAllLines(bogusDataFile.toPath()); + for (final String line : allLines) { + final int cubeStringIndex = line.indexOf("Cube"); + if (cubeStringIndex != -1) { + final int colonStringIndex = line.indexOf(':'); + final int cubeIndex = Integer.parseInt(line.substring(cubeStringIndex + 4, colonStringIndex)); + final int dataIndex = Integer.parseInt(line.substring(line.indexOf('"') + 1, cubeStringIndex)); + final float value = Float.parseFloat(line.substring(colonStringIndex + 1, line.indexOf(".txt"))); + cubeIndexToData[cubeIndex][dataIndex] = value; + } + } + } + catch (final IOException e) { + e.printStackTrace(); + } + final boolean[] spun = new boolean[9]; + for (int j = 0; j < cubeIndexToData[0].length; j++) { + final StringBuilder sb = new StringBuilder(); + for (int i = 0; i < cubeIndexToData.length; i++) { + if (sb.length() > 0) { + sb.append(','); + } + float value = cubeIndexToData[i][j]; + if (value > 280) { + spun[i] = true; + } + else if ((value < 100) && spun[i]) { + value += 360; + } + sb.append(Math.toRadians(value)); + } + System.out.println(sb); + } + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ParseBogus2.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ParseBogus2.java new file mode 100644 index 0000000..da2e022 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ParseBogus2.java @@ -0,0 +1,60 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.rendersim; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.List; + +public class ParseBogus2 { + + private static final int _383 = 280; + + public static void main(final String[] args) { + final float[][] cubeIndexToData = new float[9][_383]; + for (int fileIdx = 0; fileIdx < _383; fileIdx++) { + final File bogusDataFile = new File( + "E:\\Games\\Warcraft III Patch 1.22\\Logs\\MyReteraTest" + (fileIdx + 1) + ".pre"); + try { + final List allLines = Files.readAllLines(bogusDataFile.toPath()); + for (final String line : allLines) { + final int cubeStringIndex = line.indexOf("Cube"); + if (cubeStringIndex != -1) { + final int colonStringIndex = line.indexOf(':'); + final int cubeIndex = Integer.parseInt(line.substring(cubeStringIndex + 4, colonStringIndex)); + final int dataIndex = Integer.parseInt(line.substring(line.indexOf('"') + 1, cubeStringIndex)); + final float value = Float + .parseFloat(line.substring(colonStringIndex + 1, line.indexOf(".txt"))); + cubeIndexToData[cubeIndex][dataIndex] = value; + } + } + } + catch (final IOException e) { + e.printStackTrace(); + } + } + final boolean[] spun = new boolean[9]; + for (int j = 0; j < cubeIndexToData[0].length; j++) { + if ((j % 3) == 0) { + final StringBuilder sb = new StringBuilder(); + for (int i = 0; i < cubeIndexToData.length; i++) { + if (sb.length() > 0) { + sb.append(','); + } + float value = cubeIndexToData[i][j]; +// if (value > 280) { +// spun[i] = true; +// } +// else if ((value < 100) && spun[i]) { +// value += 360; +// } + if ((j / 3) < 32) { + value += 360; + } + sb.append(Math.toRadians(value)); + } + System.out.println(sb); + } + } + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ParseBogus3.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ParseBogus3.java new file mode 100644 index 0000000..f239eec --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ParseBogus3.java @@ -0,0 +1,19 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.rendersim; + +public class ParseBogus3 { + public static float endingAccelCutoff(final float maxVelocity, final float endingAccel) { + final float endingAccelFinishingTime = maxVelocity / endingAccel; + final float endingDistanceRequired = (maxVelocity * endingAccelFinishingTime) + - ((endingAccel / 2) * (endingAccelFinishingTime * endingAccelFinishingTime)); + return endingDistanceRequired; + } + + public static void main(final String[] args) { + System.out.println(endingAccelCutoff(0.1f, 0.04f)); + + for (final OrientationInterpolation oi : OrientationInterpolation.values()) { + System.out.println(oi + ": " + oi.getStartingAcceleration() + "," + oi.getMaxVelocity() + "," + + oi.getEndingNegativeAcceleration() + "->" + oi.getEndingAccelCutoff()); + } + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ParseBogus4.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ParseBogus4.java new file mode 100644 index 0000000..054e481 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ParseBogus4.java @@ -0,0 +1,80 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.rendersim; + +import java.util.Arrays; + +public class ParseBogus4 { + + public static void main(final String[] args) { + + final OrientationInterpolation[] interpolations = OrientationInterpolation.values(); + final float[] velocities = new float[interpolations.length]; + final float[] signs = new float[interpolations.length]; + Arrays.fill(signs, -1); + signs[1] = signs[6] = 1; + final float[] angles = new float[interpolations.length]; + Arrays.fill(angles, (float) ((3 * Math.PI) / 2)); + final boolean[] spun = new boolean[interpolations.length]; + + for (int j = 0; j < 140; j++) { + final StringBuilder sb = new StringBuilder(); + for (int i = 0; i < interpolations.length; i++) { + float simulationFacing = 90; + if (simulationFacing < 0) { + simulationFacing += 360; + } + float renderFacing = (float) Math.toDegrees(angles[i]); + if (renderFacing < 0) { + renderFacing += 360; + } + float facingDelta = simulationFacing - renderFacing; + if (facingDelta < -180) { + facingDelta = 360 + facingDelta; + } + if (facingDelta > 180) { + facingDelta = -360 + facingDelta; + } + final float absoluteFacingDelta = Math.abs(facingDelta); + + final float absoluteFacingDeltaRadians = (float) Math.toRadians(absoluteFacingDelta); + float acceleration; + final boolean endPhase = (absoluteFacingDeltaRadians <= interpolations[i].getEndingAccelCutoff()) + && ((velocities[i] * signs[i]) > 0); + if (endPhase) { + acceleration = -interpolations[i].getEndingNegativeAcceleration() * signs[i]; + if (((velocities[i] + acceleration) * signs[i]) <= 0.001) { + velocities[i] = absoluteFacingDeltaRadians * signs[i] * 0.25f; + } + else { + velocities[i] = velocities[i] + acceleration; + } + } + else { + acceleration = interpolations[i].getStartingAcceleration() * signs[i]; + velocities[i] = velocities[i] + acceleration; + } + if ((velocities[i] * signs[i]) > interpolations[i].getMaxVelocity()) { + velocities[i] = interpolations[i].getMaxVelocity() * signs[i]; + } + + float angleToAdd = (float) (/* Math.signum(facingDelta) **/ Math + .toDegrees(velocities[i]) /* * deltaTime */); + if (absoluteFacingDelta < Math.abs(angleToAdd)) { + angleToAdd = facingDelta; + } + double newDegreesAngle = (((Math.toDegrees(angles[i]) + angleToAdd) % 360) + 360) % 360; + if (newDegreesAngle > 280) { + spun[i] = true; + } + else if ((newDegreesAngle < 100) && spun[i]) { + newDegreesAngle += 360; + } + angles[i] = (float) Math.toRadians(newDegreesAngle); + if (sb.length() > 0) { + sb.append(','); + } + sb.append(angles[i]); + } + System.out.println(sb.toString()); + } + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackProjectile.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackProjectile.java index a2557bd..4293f71 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackProjectile.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackProjectile.java @@ -10,7 +10,7 @@ import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; import com.etheller.warsmash.viewer5.handlers.w3x.IndexedSequence; import com.etheller.warsmash.viewer5.handlers.w3x.StandSequence; import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.projectile.CAttackProjectile; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.projectile.CAttackProjectile; public class RenderAttackProjectile { private static final Quaternion pitchHeap = new Quaternion(); @@ -45,7 +45,8 @@ public class RenderAttackProjectile { final float d2DToTarget = (float) StrictMath.sqrt((dxToTarget * dxToTarget) + (dyToTarget * dyToTarget)); final float startingDistance = d2DToTarget + this.totalTravelDistance; this.targetHeight = (war3MapViewer.terrain.getGroundHeight(targetX, targetY) - + this.simulationProjectile.getTarget().getFlyHeight()); + + this.simulationProjectile.getTarget().getFlyHeight() + + this.simulationProjectile.getTarget().getImpactZ()); this.arcPeakHeight = arc * startingDistance; this.yaw = (float) StrictMath.atan2(dyToTarget, dxToTarget); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java index 74458f3..318f948 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java @@ -2,11 +2,12 @@ package com.etheller.warsmash.viewer5.handlers.w3x.rendersim; import java.util.ArrayList; import java.util.EnumSet; +import java.util.LinkedList; import java.util.List; +import java.util.Queue; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.math.Quaternion; -import com.etheller.warsmash.parsers.mdlx.Sequence; import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; import com.etheller.warsmash.util.ImageUtils; import com.etheller.warsmash.util.RenderMathUtils; @@ -15,15 +16,15 @@ import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; -import com.etheller.warsmash.viewer5.handlers.w3x.IndexedSequence; +import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.SecondaryTag; import com.etheller.warsmash.viewer5.handlers.w3x.SplatModel.SplatMover; import com.etheller.warsmash.viewer5.handlers.w3x.StandSequence; import com.etheller.warsmash.viewer5.handlers.w3x.UnitSoundset; import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.MovementType; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.COrder; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitAnimationListener; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityAttack; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityHoldPosition; @@ -32,13 +33,13 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityP import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityStop; public class RenderUnit { - private static final double GLOBAL_TURN_RATE = Math.toDegrees(7f); private static final Quaternion tempQuat = new Quaternion(); private static final War3ID RED = War3ID.fromString("uclr"); private static final War3ID GREEN = War3ID.fromString("uclg"); private static final War3ID BLUE = War3ID.fromString("uclb"); private static final War3ID MODEL_SCALE = War3ID.fromString("usca"); private static final War3ID MOVE_HEIGHT = War3ID.fromString("umvh"); + private static final War3ID ORIENTATION_INTERPOLATION = War3ID.fromString("uori"); private static final float[] heapZ = new float[3]; public final MdxComplexInstance instance; public final MutableGameObject row; @@ -48,8 +49,6 @@ public class RenderUnit { public final MdxModel portraitModel; public int playerIndex; private final CUnit simulationUnit; - private COrder lastOrder; - private AnimationTokens.PrimaryTag lastOrderAnimation; public SplatMover shadow; public SplatMover selectionCircle; private final List commandCardIcons = new ArrayList<>(); @@ -60,10 +59,11 @@ public class RenderUnit { private boolean swimming; - private boolean alreadyPlayedDeath = false; + private final boolean alreadyPlayedDeath = false; - private final EnumSet secondaryAnimationTags = EnumSet - .noneOf(AnimationTokens.SecondaryTag.class); + private final UnitAnimationListenerImpl unitAnimationListenerImpl; + private OrientationInterpolation orientationInterpolation; + private float currentTurnVelocity = 0; public RenderUnit(final War3MapViewer map, final MdxModel model, final MutableGameObject row, final com.etheller.warsmash.parsers.w3x.unitsdoo.Unit unit, final UnitSoundset soundset, @@ -82,9 +82,11 @@ public class RenderUnit { this.y = simulationUnit.getY(); instance.rotate(tempQuat.setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, angle)); instance.scale(unit.getScale()); - this.playerIndex = unit.getPlayer(); + this.playerIndex = unit.getPlayer() & 0xFFFF; instance.setTeamColor(this.playerIndex); instance.setScene(map.worldScene); + this.unitAnimationListenerImpl = new UnitAnimationListenerImpl(instance); + simulationUnit.setUnitAnimationListener(this.unitAnimationListenerImpl); if (row != null) { heapZ[2] = simulationUnit.getFlyHeight(); @@ -104,6 +106,12 @@ public class RenderUnit { instance.uniformScale(row.getFieldAsFloat(scale, 0)); this.selectionScale = row.getFieldAsFloat(War3MapViewer.UNIT_SELECT_SCALE, 0); + int orientationInterpolationOrdinal = row.getFieldAsInteger(ORIENTATION_INTERPOLATION, 0); + if ((orientationInterpolationOrdinal < 0) + || (orientationInterpolationOrdinal >= OrientationInterpolation.VALUES.length)) { + orientationInterpolationOrdinal = 0; + } + this.orientationInterpolation = OrientationInterpolation.VALUES[orientationInterpolationOrdinal]; } this.instance = instance; @@ -179,14 +187,11 @@ public class RenderUnit { else { groundHeight = map.terrain.getGroundHeight(x, y); } - boolean changedAnimationTags = false; if (swimming && !this.swimming) { - this.secondaryAnimationTags.add(AnimationTokens.SecondaryTag.SWIM); - changedAnimationTags = true; + this.unitAnimationListenerImpl.addSecondaryTag(AnimationTokens.SecondaryTag.SWIM); } else if (!swimming && this.swimming) { - this.secondaryAnimationTags.remove(AnimationTokens.SecondaryTag.SWIM); - changedAnimationTags = true; + this.unitAnimationListenerImpl.removeSecondaryTag(AnimationTokens.SecondaryTag.SWIM); } this.swimming = swimming; this.location[2] = this.simulationUnit.getFlyHeight() + groundHeight; @@ -207,52 +212,41 @@ public class RenderUnit { facingDelta = -360 + facingDelta; } final float absoluteFacingDelta = Math.abs(facingDelta); - float angleToAdd = (float) (Math.signum(facingDelta) * GLOBAL_TURN_RATE * deltaTime); + final float turningSign = Math.signum(facingDelta); + + final float absoluteFacingDeltaRadians = (float) Math.toRadians(absoluteFacingDelta); + float acceleration; + final boolean endPhase = (absoluteFacingDeltaRadians <= this.orientationInterpolation.getEndingAccelCutoff()) + && ((this.currentTurnVelocity * turningSign) > 0); + if (endPhase) { + this.currentTurnVelocity = (1 + - ((this.orientationInterpolation.getEndingAccelCutoff() - absoluteFacingDeltaRadians) + / this.orientationInterpolation.getEndingAccelCutoff())) + * (this.orientationInterpolation.getMaxVelocity()) * turningSign; + } + else { + acceleration = this.orientationInterpolation.getStartingAcceleration() * turningSign; + this.currentTurnVelocity = this.currentTurnVelocity + acceleration; + } + if ((this.currentTurnVelocity * turningSign) > this.orientationInterpolation.getMaxVelocity()) { + this.currentTurnVelocity = this.orientationInterpolation.getMaxVelocity() * turningSign; + } + float angleToAdd = (float) ((Math.toDegrees(this.currentTurnVelocity) * deltaTime) / 0.03f); + if (absoluteFacingDelta < Math.abs(angleToAdd)) { angleToAdd = facingDelta; + this.currentTurnVelocity = 0.0f; } this.facing = (((this.facing + angleToAdd) % 360) + 360) % 360; this.instance.setLocalRotation(tempQuat.setFromAxis(RenderMathUtils.VEC3_UNIT_Z, this.facing)); map.worldScene.instanceMoved(this.instance, this.location[0], this.location[1]); - final MdxComplexInstance mdxComplexInstance = this.instance; - final COrder currentOrder = this.simulationUnit.getCurrentOrder(); - if (this.simulationUnit.getLife() <= 0) { - final MdxModel model = (MdxModel) mdxComplexInstance.model; - final List sequences = model.getSequences(); - IndexedSequence sequence = StandSequence.selectSequence("death", sequences); - if (!this.alreadyPlayedDeath && (sequence != null) && (mdxComplexInstance.sequence != sequence.index)) { - mdxComplexInstance.setSequence(sequence.index); - this.alreadyPlayedDeath = true; - } - else if (mdxComplexInstance.sequenceEnded && this.alreadyPlayedDeath) { - sequence = StandSequence.selectSequence("dissipate", sequences); - if ((sequence != null) && (mdxComplexInstance.sequence != sequence.index)) { - mdxComplexInstance.setSequence(sequence.index); - } - } - } - else if (mdxComplexInstance.sequenceEnded || (mdxComplexInstance.sequence == -1) - || (currentOrder != this.lastOrder) - || ((currentOrder != null) && (currentOrder.getAnimationName() != null) - && !currentOrder.getAnimationName().equals(this.lastOrderAnimation)) - || changedAnimationTags) { - if (this.simulationUnit.getCurrentOrder() != null) { - final AnimationTokens.PrimaryTag animationName = this.simulationUnit.getCurrentOrder() - .getAnimationName(); - StandSequence.randomSequence(mdxComplexInstance, animationName, this.secondaryAnimationTags); - this.lastOrderAnimation = animationName; - } - else { - StandSequence.randomSequence(mdxComplexInstance, PrimaryTag.STAND, this.secondaryAnimationTags); - } - } - this.lastOrder = currentOrder; if (this.shadow != null) { this.shadow.move(dx, dy, map.terrain.centerOffset); } if (this.selectionCircle != null) { this.selectionCircle.move(dx, dy, map.terrain.centerOffset); } + this.unitAnimationListenerImpl.update(); } public CUnit getSimulationUnit() { @@ -262,4 +256,80 @@ public class RenderUnit { public List getCommandCardIcons() { return this.commandCardIcons; } + + private static final class UnitAnimationListenerImpl implements CUnitAnimationListener { + private final MdxComplexInstance instance; + private final EnumSet secondaryAnimationTags = EnumSet + .noneOf(AnimationTokens.SecondaryTag.class); + private final EnumSet recycleSet = EnumSet + .noneOf(AnimationTokens.SecondaryTag.class); + private PrimaryTag currentAnimation; + private EnumSet currentAnimationSecondaryTags; + private float currentSpeedRatio; + private final Queue animationQueue = new LinkedList<>(); + + public UnitAnimationListenerImpl(final MdxComplexInstance instance) { + this.instance = instance; + } + + public void addSecondaryTag(final AnimationTokens.SecondaryTag tag) { + this.secondaryAnimationTags.add(tag); + playAnimation(true, this.currentAnimation, this.currentAnimationSecondaryTags, this.currentSpeedRatio); + } + + public void removeSecondaryTag(final AnimationTokens.SecondaryTag tag) { + this.secondaryAnimationTags.remove(tag); + playAnimation(true, this.currentAnimation, this.currentAnimationSecondaryTags, this.currentSpeedRatio); + } + + @Override + public void playAnimation(final boolean force, final PrimaryTag animationName, + final EnumSet secondaryAnimationTags, final float speedRatio) { + this.animationQueue.clear(); + if (force || (animationName != this.currentAnimation)) { + this.currentAnimation = animationName; + this.currentAnimationSecondaryTags = secondaryAnimationTags; + this.currentSpeedRatio = speedRatio; + this.recycleSet.clear(); + this.recycleSet.addAll(this.secondaryAnimationTags); + this.recycleSet.addAll(secondaryAnimationTags); + this.instance.setAnimationSpeed(speedRatio); + StandSequence.randomSequence(this.instance, animationName, this.recycleSet); + } + } + + @Override + public void queueAnimation(final PrimaryTag animationName, final EnumSet secondaryAnimationTags) { + this.animationQueue.add(new QueuedAnimation(animationName, secondaryAnimationTags)); + } + + public void update() { + if (this.instance.sequenceEnded || (this.instance.sequence == -1)) { + // animation done + if ((this.instance.sequence != -1) && (((MdxModel) this.instance.model).getSequences() + .get(this.instance.sequence).getFlags() == 0)) { + // animation is a looping animation + playAnimation(true, this.currentAnimation, this.currentAnimationSecondaryTags, + this.currentSpeedRatio); + } + else { + final QueuedAnimation nextAnimation = this.animationQueue.poll(); + if (nextAnimation != null) { + playAnimation(true, nextAnimation.animationName, nextAnimation.secondaryAnimationTags, 1.0f); + } + } + } + } + + } + + private static final class QueuedAnimation { + private final PrimaryTag animationName; + private final EnumSet secondaryAnimationTags; + + public QueuedAnimation(final PrimaryTag animationName, final EnumSet secondaryAnimationTags) { + this.animationName = animationName; + this.secondaryAnimationTags = secondaryAnimationTags; + } + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CDestructable.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CDestructable.java index 754f81d..f221c03 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CDestructable.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CDestructable.java @@ -11,4 +11,8 @@ public class CDestructable extends CWidget { return 0; } + @Override + public float getImpactZ() { + return 0; // TODO maybe from DestructableType + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CGameplayConstants.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CGameplayConstants.java new file mode 100644 index 0000000..3567cc6 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CGameplayConstants.java @@ -0,0 +1,21 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation; + +import com.etheller.warsmash.units.DataTable; +import com.etheller.warsmash.units.Element; + +/** + * Stores some gameplay constants at runtime in a java object (symbol table) to + * maybe be faster than a map. + */ +public class CGameplayConstants { + private final float attackHalfAngle; + + public CGameplayConstants(final DataTable parsedDataTable) { + final Element miscData = parsedDataTable.get("Misc"); + this.attackHalfAngle = (miscData.getFieldFloatValue("AttackHalfAngle")); // TODO use + } + + public float getAttackHalfAngle() { + return this.attackHalfAngle; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CItem.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CItem.java index 73de42b..81b69b9 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CItem.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CItem.java @@ -16,4 +16,8 @@ public class CItem extends CWidget { return 0; } + @Override + public float getImpactZ() { + return 0; // TODO probably from ItemType + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/COrder.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/COrder.java index 591f036..b7821a1 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/COrder.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/COrder.java @@ -1,7 +1,5 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation; -import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens; - public interface COrder { /** * Executes one step of game simulation of the current order, returning true if @@ -20,12 +18,4 @@ public interface COrder { * @return */ int getOrderId(); - - /** - * Gets the animation name used for visuals. Calling this function should not - * impact the game state of the CSimulation in any way. - * - * @return - */ - AnimationTokens.PrimaryTag getAnimationName(); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java index 0b3f0cc..61ca42c 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java @@ -6,13 +6,14 @@ import java.util.Iterator; import java.util.List; import com.badlogic.gdx.math.Rectangle; +import com.etheller.warsmash.units.DataTable; import com.etheller.warsmash.units.manager.MutableObjectData; import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.projectile.CAttackProjectile; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.data.CAbilityData; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.data.CUnitData; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.pathing.CPathfindingProcessor; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.projectile.CAttackProjectile; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.ProjectileCreator; public class CSimulation { @@ -21,14 +22,17 @@ public class CSimulation { private final List units; private final List projectiles; private final HandleIdAllocator handleIdAllocator; - private final ProjectileCreator projectileCreator; + private transient final ProjectileCreator projectileCreator; private int gameTurnTick = 0; private final PathingGrid pathingGrid; private final CWorldCollision worldCollision; private final CPathfindingProcessor pathfindingProcessor; + private final CGameplayConstants gameplayConstants; - public CSimulation(final MutableObjectData parsedUnitData, final MutableObjectData parsedAbilityData, - final ProjectileCreator projectileCreator, final PathingGrid pathingGrid, final Rectangle entireMapBounds) { + public CSimulation(final DataTable miscData, final MutableObjectData parsedUnitData, + final MutableObjectData parsedAbilityData, final ProjectileCreator projectileCreator, + final PathingGrid pathingGrid, final Rectangle entireMapBounds) { + this.gameplayConstants = new CGameplayConstants(miscData); this.projectileCreator = projectileCreator; this.pathingGrid = pathingGrid; this.unitData = new CUnitData(parsedUnitData); @@ -101,4 +105,8 @@ public class CSimulation { public CWorldCollision getWorldCollision() { return this.worldCollision; } + + public CGameplayConstants getGameplayConstants() { + return this.gameplayConstants; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java index b67c0e9..36f83b0 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java @@ -1,13 +1,17 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation; import java.util.ArrayList; +import java.util.EnumSet; import java.util.LinkedList; import java.util.List; import java.util.Queue; import com.badlogic.gdx.math.Rectangle; import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType; public class CUnit extends CWidget { private War3ID typeId; @@ -16,6 +20,7 @@ public class CUnit extends CWidget { private int maximumLife; private int maximumMana; private int speed; + private final int defense; private int cooldownEndTime = 0; private float flyHeight; private int playerIndex; @@ -28,9 +33,13 @@ public class CUnit extends CWidget { private Rectangle collisionRectangle; + private final EnumSet classifications = EnumSet.noneOf(CUnitClassification.class); + + private transient CUnitAnimationListener unitAnimationListener; + public CUnit(final int handleId, final int playerIndex, final float x, final float y, final float life, final War3ID typeId, final float facing, final float mana, final int maximumLife, final int maximumMana, - final int speed, final CUnitType unitType) { + final int speed, final int defense, final CUnitType unitType) { super(handleId, x, y, life); this.typeId = typeId; this.facing = facing; @@ -38,8 +47,19 @@ public class CUnit extends CWidget { this.maximumLife = maximumLife; this.maximumMana = maximumMana; this.speed = speed; + this.defense = defense; this.flyHeight = unitType.getDefaultFlyingHeight(); this.unitType = unitType; + this.classifications.addAll(unitType.getClassifications()); + } + + public void setUnitAnimationListener(final CUnitAnimationListener unitAnimationListener) { + this.unitAnimationListener = unitAnimationListener; + this.unitAnimationListener.playAnimation(true, PrimaryTag.STAND, CUnitAnimationListener.EMPTY, 1.0f); + } + + public CUnitAnimationListener getUnitAnimationListener() { + return this.unitAnimationListener; } public void add(final CSimulation simulation, final CAbility ability) { @@ -110,6 +130,10 @@ public class CUnit extends CWidget { // item from order queue this.currentOrder = this.orderQueue.poll(); } + if (this.currentOrder == null) { + // maybe order "stop" here + this.unitAnimationListener.playAnimation(true, PrimaryTag.STAND, CUnitAnimationListener.EMPTY, 1.0f); + } } } @@ -193,4 +217,21 @@ public class CUnit extends CWidget { } } + public EnumSet getClassifications() { + return this.classifications; + } + + public int getDefense() { + return this.defense; + } + + public void damage(final CUnit source, final CAttackType attackType, final CWeaponType weaponType, + final int damage) { + + } + + @Override + public float getImpactZ() { + return this.unitType.getImpactZ(); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitAnimationListener.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitAnimationListener.java new file mode 100644 index 0000000..db7f9d0 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitAnimationListener.java @@ -0,0 +1,16 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation; + +import java.util.EnumSet; + +import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; +import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.SecondaryTag; + +public interface CUnitAnimationListener { + EnumSet EMPTY = EnumSet.noneOf(SecondaryTag.class); + EnumSet READY = EnumSet.of(SecondaryTag.READY); + + void playAnimation(boolean force, final PrimaryTag animationName, + final EnumSet secondaryAnimationTags, float speedRatio); + + void queueAnimation(final PrimaryTag animationName, final EnumSet secondaryAnimationTags); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitClassification.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitClassification.java new file mode 100644 index 0000000..d603983 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitClassification.java @@ -0,0 +1,67 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation; + +import java.util.HashMap; +import java.util.Map; + +/** + * We think in the original WC3 sourcecode, these are probably referred to as + * "Unit Types", but the community turn of phrase "Unit Type" has come to refer + * to what WC3 sourcecode calls "Unit Class", hence this is now named "Unit + * Classification" instead of "Unit Type" or "Unit Class" to disambiguate. This + * is consistent with the World Editor naming: "Stats - Unit Classification". + */ +public enum CUnitClassification { + GIANT("giant", "GiantClass"), + UNDEAD("undead", "UndeadClass"), + SUMMONED("summoned"), + MECHANICAL("mechanical", "MechanicalClass"), + PEON("peon"), + SAPPER("sapper"), + TOWNHALL("townhall"), + TREE("tree"), + WARD("ward"), + ANCIENT("ancient"), + STANDON("standon"), + NEURAL("neutral"), + TAUREN("tauren", "TaurenClass"); + private static final Map UNIT_EDITOR_KEY_TO_CLASSIFICATION = new HashMap<>(); + static { + for (final CUnitClassification unitClassification : values()) { + UNIT_EDITOR_KEY_TO_CLASSIFICATION.put(unitClassification.getUnitDataKey(), unitClassification); + } + } + + private String localeKey; + private String unitDataKey; + private String displayName; + + private CUnitClassification(final String unitDataKey, final String localeKey) { + this.unitDataKey = unitDataKey; + this.localeKey = localeKey; + } + + private CUnitClassification(final String unitDataKey) { + this.unitDataKey = unitDataKey; + this.localeKey = null; + } + + public String getUnitDataKey() { + return this.unitDataKey; + } + + public String getLocaleKey() { + return this.localeKey; + } + + public String getDisplayName() { + return this.displayName; + } + + public void setDisplayName(final String displayName) { + this.displayName = displayName; + } + + public static CUnitClassification parseUnitClassification(final String unitEditorKey) { + return UNIT_EDITOR_KEY_TO_CLASSIFICATION.get(unitEditorKey.toLowerCase()); + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java index b175af5..7970023 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java @@ -1,24 +1,51 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation; +import java.util.EnumSet; +import java.util.List; + import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.MovementType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CDefenseType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; /** * The quick (symbol table instead of map) lookup for unit type values that we * probably cannot change per unit instance. */ public class CUnitType { + private final String name; private final boolean building; private final PathingGrid.MovementType movementType; private final float defaultFlyingHeight; private final float collisionSize; + private final EnumSet classifications; + private final List attacks; + private final String armorType; // used for audio + private final boolean raise; + private final boolean decay; + private final CDefenseType defenseType; + private final float impactZ; - public CUnitType(final boolean isBldg, final MovementType movementType, final float defaultFlyingHeight, - final float collisionSize) { + public CUnitType(final String name, final boolean isBldg, final MovementType movementType, + final float defaultFlyingHeight, final float collisionSize, + final EnumSet classifications, final List attacks, final String armorType, + final boolean raise, final boolean decay, final CDefenseType defenseType, final float impactZ) { + this.name = name; this.building = isBldg; this.movementType = movementType; this.defaultFlyingHeight = defaultFlyingHeight; this.collisionSize = collisionSize; + this.classifications = classifications; + this.attacks = attacks; + this.armorType = armorType; + this.raise = raise; + this.decay = decay; + this.defenseType = defenseType; + this.impactZ = impactZ; + } + + public String getName() { + return this.name; } public float getDefaultFlyingHeight() { @@ -36,4 +63,32 @@ public class CUnitType { public boolean isBuilding() { return this.building; } + + public EnumSet getClassifications() { + return this.classifications; + } + + public List getAttacks() { + return this.attacks; + } + + public boolean isRaise() { + return this.raise; + } + + public boolean isDecay() { + return this.decay; + } + + public String getArmorType() { + return this.armorType; + } + + public CDefenseType getDefenseType() { + return this.defenseType; + } + + public float getImpactZ() { + return this.impactZ; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWidget.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWidget.java index 3250aca..38dd2f9 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWidget.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWidget.java @@ -47,4 +47,6 @@ public abstract class CWidget { public abstract float getFlyHeight(); + public abstract float getImpactZ(); + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java index 3968632..8cc10d2 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java @@ -47,7 +47,7 @@ public class CAbilityAttack implements CAbility { @Override public void onOrder(final CSimulation game, final CUnit caster, final CWidget target, final boolean queue) { - caster.order(new CAttackOrder(caster, target), queue); + caster.order(new CAttackOrder(caster, caster.getUnitType().getAttacks().get(0), target), queue); } @Override diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/CAttackType.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/CAttackType.java new file mode 100644 index 0000000..13c6c68 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/CAttackType.java @@ -0,0 +1,31 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat; + +public enum CAttackType implements CodeKeyType { + UNKNOWN, + NORMAL, + PIERCE, + SIEGE, + SPELLS, + CHAOS, + MAGIC, + HERO; + + private String codeKey; + + private CAttackType() { + String name = name(); + if (name.equals("SPELLS")) { + name = "MAGIC"; + } + this.codeKey = name.charAt(0) + name.substring(1).toLowerCase(); + } + + @Override + public String getCodeKey() { + return this.codeKey; + } + + public static CAttackType parseAttackType(final String attackTypeString) { + return valueOf(attackTypeString.toUpperCase()); + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/CDefenseType.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/CDefenseType.java new file mode 100644 index 0000000..b23382b --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/CDefenseType.java @@ -0,0 +1,27 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat; + +public enum CDefenseType implements CodeKeyType { + NONE, + NORMAL, + SMALL, + MEDIUM, + LARGE, + FORT, + HERO, + DIVINE; + + private String codeKey; + + private CDefenseType() { + this.codeKey = name().charAt(0) + name().substring(1).toLowerCase(); + } + + @Override + public String getCodeKey() { + return this.codeKey; + } + + public static CDefenseType parseDefenseType(final String typeString) { + return valueOf(typeString.toUpperCase()); + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/CRegenType.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/CRegenType.java new file mode 100644 index 0000000..b303272 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/CRegenType.java @@ -0,0 +1,13 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat; + +public enum CRegenType { + NONE, + ALWAYS, + BLIGHT, + DAY, + NIGHT; + + public static CRegenType parseRegenType(final String typeString) { + return valueOf(typeString.toUpperCase()); + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/CTargetType.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/CTargetType.java new file mode 100644 index 0000000..5fb484a --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/CTargetType.java @@ -0,0 +1,139 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat; + +import java.util.EnumSet; + +public enum CTargetType { + AIR, + ALIVE, + ALLIES, + DEAD, + DEBRIS, + ENEMIES, + GROUND, + HERO, + INVULNERABLE, + ITEM, + MECHANICAL, + NEUTRAL, + NONE, + NONHERO, + NONSAPPER, + NOTSELF, + ORGANIC, + PLAYERUNITS, + SAPPER, + SELF, + STRUCTURE, + TERRAIN, + TREE, + VULNERABLE, + WALL, + WARD, + ANCIENT, + NONANCIENT, + FRIEND, + BRIDGE, + DECORATION; + + public static CTargetType parseTargetType(final String targetTypeString) { + if (targetTypeString == null) { + return null; + } + switch (targetTypeString.toLowerCase()) { + case "air": + return AIR; + case "alive": + case "aliv": + return ALIVE; + case "allies": + case "alli": + case "ally": + return ALLIES; + case "dead": + return DEAD; + case "debris": + case "debr": + return DEBRIS; + case "enemies": + case "enem": + case "enemy": + return ENEMIES; + case "ground": + case "grou": + return GROUND; + case "hero": + return HERO; + case "invulnerable": + case "invu": + return INVULNERABLE; + case "item": + return ITEM; + case "mechanical": + case "mech": + return MECHANICAL; + case "neutral": + case "neut": + return NEUTRAL; + case "none": + return NONE; + case "nonhero": + case "nonh": + return NONHERO; + case "nonsapper": + return NONSAPPER; + case "notself": + case "nots": + return NOTSELF; + case "organic": + case "orga": + return ORGANIC; + case "player": + case "play": + return PLAYERUNITS; + case "sapper": + return SAPPER; + case "self": + return SELF; + case "structure": + case "stru": + return STRUCTURE; + case "terrain": + case "terr": + return TERRAIN; + case "tree": + return TREE; + case "vulnerable": + case "vuln": + return VULNERABLE; + case "wall": + return WALL; + case "ward": + return WARD; + case "ancient": + return ANCIENT; + case "nonancient": + return NONANCIENT; + case "friend": + case "frie": + return FRIEND; + case "bridge": + return BRIDGE; + case "decoration": + case "deco": + return DECORATION; + default: + return null; + } + } + + public static EnumSet parseTargetTypeSet(final String targetTypeString) { + final EnumSet types = EnumSet.noneOf(CTargetType.class); + for (final String type : targetTypeString.split(",")) { + final CTargetType parsedType = parseTargetType(type); + if (parsedType != null) { + types.add(parsedType); + } + } + return types; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/CUpgradeClass.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/CUpgradeClass.java new file mode 100644 index 0000000..3112bf6 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/CUpgradeClass.java @@ -0,0 +1,13 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat; + +public enum CUpgradeClass { + ARMOR, + ARTILLERY, + MELEE, + RANGED, + CASTER; + + public static CUpgradeClass parseUpgradeClass(final String upgradeClassString) { + return valueOf(upgradeClassString.toUpperCase()); + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/CWeaponType.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/CWeaponType.java new file mode 100644 index 0000000..6a9d634 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/CWeaponType.java @@ -0,0 +1,16 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat; + +public enum CWeaponType { + NORMAL, + INSTANT, + ARTILLERY, + ALINE, + MISSILE, + MSPLASH, + MBOUNCE, + MLINE; + + public static CWeaponType parseWeaponType(final String weaponTypeString) { + return valueOf(weaponTypeString.toUpperCase()); + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/CodeKeyType.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/CodeKeyType.java new file mode 100644 index 0000000..d727a02 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/CodeKeyType.java @@ -0,0 +1,7 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat; + +public interface CodeKeyType { + String getCodeKey(); + + int ordinal(); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttack.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttack.java new file mode 100644 index 0000000..d72530a --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttack.java @@ -0,0 +1,198 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks; + +import java.util.EnumSet; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType; + +/** + * The base class for unit-data-based combat attacks. + * + * I really wanted to split this out into sub classes based on weapon type, but + * I came to realize the Ballista in RoC probably had the spill distance effect + * & area of effect both after it upgrades Impaling Bolt, and this would point + * out that the behaviors were not mutually exclusive. + * + * Then I reviewed it and decided that in RoC, the Impaling Bolts upgrade did + * not interact with the damage spill combat settings from the UnitWeapons.slk, + * because many of those settings did not exist. So I will attempt to emulate + * these attacks as best as possible. + */ +public class CUnitAttack { + private float animationBackswingPoint; + private float animationDamagePoint; + private CAttackType attackType; + private float cooldownTime; + private int damageBase; + private int damageDice; + private int damageSidesPerDie; + private int damageUpgradeAmount; + private int range; + private float rangeMotionBuffer; + private boolean showUI; + private EnumSet targetsAllowed; + private String weaponSound; + private CWeaponType weaponType; + + // calculate + private int minDamage; + private int maxDamage; + + public CUnitAttack(final float animationBackswingPoint, final float animationDamagePoint, + final CAttackType attackType, final float cooldownTime, final int damageBase, final int damageDice, + final int damageSidesPerDie, final int damageUpgradeAmount, final int range, final float rangeMotionBuffer, + final boolean showUI, final EnumSet targetsAllowed, final String weaponSound, + final CWeaponType weaponType) { + this.animationBackswingPoint = animationBackswingPoint; + this.animationDamagePoint = animationDamagePoint; + this.attackType = attackType; + this.cooldownTime = cooldownTime; + this.damageBase = damageBase; + this.damageDice = damageDice; + this.damageSidesPerDie = damageSidesPerDie; + this.damageUpgradeAmount = damageUpgradeAmount; + this.range = range; + this.rangeMotionBuffer = rangeMotionBuffer; + this.showUI = showUI; + this.targetsAllowed = targetsAllowed; + this.weaponSound = weaponSound; + this.weaponType = weaponType; + computeDerivedFields(); + } + + private void computeDerivedFields() { + this.minDamage = this.damageBase + this.damageDice; + this.maxDamage = this.damageBase + (this.damageDice * this.damageSidesPerDie); + if (this.minDamage < 0) { + this.minDamage = 0; + } + if (this.maxDamage < 0) { + this.maxDamage = 0; + } + } + + public float getAnimationBackswingPoint() { + return this.animationBackswingPoint; + } + + public float getAnimationDamagePoint() { + return this.animationDamagePoint; + } + + public CAttackType getAttackType() { + return this.attackType; + } + + public float getCooldownTime() { + return this.cooldownTime; + } + + public int getDamageBase() { + return this.damageBase; + } + + public int getDamageDice() { + return this.damageDice; + } + + public int getDamageSidesPerDie() { + return this.damageSidesPerDie; + } + + public int getDamageUpgradeAmount() { + return this.damageUpgradeAmount; + } + + public int getRange() { + return this.range; + } + + public float getRangeMotionBuffer() { + return this.rangeMotionBuffer; + } + + public boolean isShowUI() { + return this.showUI; + } + + public EnumSet getTargetsAllowed() { + return this.targetsAllowed; + } + + public String getWeaponSound() { + return this.weaponSound; + } + + public CWeaponType getWeaponType() { + return this.weaponType; + } + + public void setAnimationBackswingPoint(final float animationBackswingPoint) { + this.animationBackswingPoint = animationBackswingPoint; + } + + public void setAnimationDamagePoint(final float animationDamagePoint) { + this.animationDamagePoint = animationDamagePoint; + } + + public void setAttackType(final CAttackType attackType) { + this.attackType = attackType; + } + + public void setCooldownTime(final float cooldownTime) { + this.cooldownTime = cooldownTime; + } + + public void setDamageBase(final int damageBase) { + this.damageBase = damageBase; + computeDerivedFields(); + } + + public void setDamageDice(final int damageDice) { + this.damageDice = damageDice; + computeDerivedFields(); + } + + public void setDamageSidesPerDie(final int damageSidesPerDie) { + this.damageSidesPerDie = damageSidesPerDie; + computeDerivedFields(); + } + + public void setDamageUpgradeAmount(final int damageUpgradeAmount) { + this.damageUpgradeAmount = damageUpgradeAmount; + } + + public void setRange(final int range) { + this.range = range; + } + + public void setRangeMotionBuffer(final float rangeMotionBuffer) { + this.rangeMotionBuffer = rangeMotionBuffer; + } + + public void setShowUI(final boolean showUI) { + this.showUI = showUI; + } + + public void setTargetsAllowed(final EnumSet targetsAllowed) { + this.targetsAllowed = targetsAllowed; + } + + public void setWeaponSound(final String weaponSound) { + this.weaponSound = weaponSound; + } + + public void setWeaponType(final CWeaponType weaponType) { + this.weaponType = weaponType; + } + + public int getMinDamage() { + return this.minDamage; + } + + public int getMaxDamage() { + return this.maxDamage; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackInstant.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackInstant.java new file mode 100644 index 0000000..8e9ab2e --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackInstant.java @@ -0,0 +1,31 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks; + +import java.util.EnumSet; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType; + +public class CUnitAttackInstant extends CUnitAttack { + private String projectileArt; + + public CUnitAttackInstant(final float animationBackswingPoint, final float animationDamagePoint, + final CAttackType attackType, final float cooldownTime, final int damageBase, final int damageDice, + final int damageSidesPerDie, final int damageUpgradeAmount, final int range, final float rangeMotionBuffer, + final boolean showUI, final EnumSet targetsAllowed, final String weaponSound, + final CWeaponType weaponType, final String projectileArt) { + super(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, damageBase, damageDice, + damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, targetsAllowed, weaponSound, + weaponType); + this.projectileArt = projectileArt; + } + + public String getProjectileArt() { + return this.projectileArt; + } + + public void setProjectileArt(final String projectileArt) { + this.projectileArt = projectileArt; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissile.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissile.java new file mode 100644 index 0000000..9397295 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissile.java @@ -0,0 +1,62 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks; + +import java.util.EnumSet; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType; + +public class CUnitAttackMissile extends CUnitAttack { + private float projectileArc; + private String projectileArt; + private boolean projectileHomingEnabled; + private int projectileSpeed; + + public CUnitAttackMissile(final float animationBackswingPoint, final float animationDamagePoint, + final CAttackType attackType, final float cooldownTime, final int damageBase, final int damageDice, + final int damageSidesPerDie, final int damageUpgradeAmount, final int range, final float rangeMotionBuffer, + final boolean showUI, final EnumSet targetsAllowed, final String weaponSound, + final CWeaponType weaponType, final float projectileArc, final String projectileArt, + final boolean projectileHomingEnabled, final int projectileSpeed) { + super(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, damageBase, damageDice, + damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, targetsAllowed, weaponSound, + weaponType); + this.projectileArc = projectileArc; + this.projectileArt = projectileArt; + this.projectileHomingEnabled = projectileHomingEnabled; + this.projectileSpeed = projectileSpeed; + } + + public float getProjectileArc() { + return this.projectileArc; + } + + public String getProjectileArt() { + return this.projectileArt; + } + + public boolean isProjectileHomingEnabled() { + return this.projectileHomingEnabled; + } + + public int getProjectileSpeed() { + return this.projectileSpeed; + } + + public void setProjectileArc(final float projectileArc) { + this.projectileArc = projectileArc; + } + + public void setProjectileArt(final String projectileArt) { + this.projectileArt = projectileArt; + } + + public void setProjectileHomingEnabled(final boolean projectileHomingEnabled) { + this.projectileHomingEnabled = projectileHomingEnabled; + } + + public void setProjectileSpeed(final int projectileSpeed) { + this.projectileSpeed = projectileSpeed; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileBounce.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileBounce.java new file mode 100644 index 0000000..474ca3f --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileBounce.java @@ -0,0 +1,43 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks; + +import java.util.EnumSet; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType; + +public class CUnitAttackMissileBounce extends CUnitAttackMissile { + private float damageLossFactor; + private int maximumNumberOfTargets; + + public CUnitAttackMissileBounce(final float animationBackswingPoint, final float animationDamagePoint, + final CAttackType attackType, final float cooldownTime, final int damageBase, final int damageDice, + final int damageSidesPerDie, final int damageUpgradeAmount, final int range, final float rangeMotionBuffer, + final boolean showUI, final EnumSet targetsAllowed, final String weaponSound, + final CWeaponType weaponType, final float projectileArc, final String projectileArt, + final boolean projectileHomingEnabled, final int projectileSpeed, final float damageLossFactor, + final int maximumNumberOfTargets) { + super(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, damageBase, damageDice, + damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, targetsAllowed, weaponSound, + weaponType, projectileArc, projectileArt, projectileHomingEnabled, projectileSpeed); + this.damageLossFactor = damageLossFactor; + this.maximumNumberOfTargets = maximumNumberOfTargets; + } + + public float getDamageLossFactor() { + return this.damageLossFactor; + } + + public int getMaximumNumberOfTargets() { + return this.maximumNumberOfTargets; + } + + public void setDamageLossFactor(final float damageLossFactor) { + this.damageLossFactor = damageLossFactor; + } + + public void setMaximumNumberOfTargets(final int maximumNumberOfTargets) { + this.maximumNumberOfTargets = maximumNumberOfTargets; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileLine.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileLine.java new file mode 100644 index 0000000..6d4dbeb --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileLine.java @@ -0,0 +1,43 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks; + +import java.util.EnumSet; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType; + +public class CUnitAttackMissileLine extends CUnitAttackMissile { + private float damageSpillDistance; + private float damageSpillRadius; + + public CUnitAttackMissileLine(final float animationBackswingPoint, final float animationDamagePoint, + final CAttackType attackType, final float cooldownTime, final int damageBase, final int damageDice, + final int damageSidesPerDie, final int damageUpgradeAmount, final int range, final float rangeMotionBuffer, + final boolean showUI, final EnumSet targetsAllowed, final String weaponSound, + final CWeaponType weaponType, final float projectileArc, final String projectileArt, + final boolean projectileHomingEnabled, final int projectileSpeed, final float damageSpillDistance, + final float damageSpillRadius) { + super(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, damageBase, damageDice, + damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, targetsAllowed, weaponSound, + weaponType, projectileArc, projectileArt, projectileHomingEnabled, projectileSpeed); + this.damageSpillDistance = damageSpillDistance; + this.damageSpillRadius = damageSpillRadius; + } + + public float getDamageSpillDistance() { + return this.damageSpillDistance; + } + + public float getDamageSpillRadius() { + return this.damageSpillRadius; + } + + public void setDamageSpillDistance(final float damageSpillDistance) { + this.damageSpillDistance = damageSpillDistance; + } + + public void setDamageSpillRadius(final float damageSpillRadius) { + this.damageSpillRadius = damageSpillRadius; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileSplash.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileSplash.java new file mode 100644 index 0000000..a9e636e --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileSplash.java @@ -0,0 +1,85 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks; + +import java.util.EnumSet; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType; + +public class CUnitAttackMissileSplash extends CUnitAttackMissile { + private int areaOfEffectFullDamage; + private int areaOfEffectMediumDamage; + private int areaOfEffectSmallDamage; + private EnumSet areaOfEffectTargets; + private float damageFactorMedium; + private float damageFactorSmall; + + public CUnitAttackMissileSplash(final float animationBackswingPoint, final float animationDamagePoint, + final CAttackType attackType, final float cooldownTime, final int damageBase, final int damageDice, + final int damageSidesPerDie, final int damageUpgradeAmount, final int range, final float rangeMotionBuffer, + final boolean showUI, final EnumSet targetsAllowed, final String weaponSound, + final CWeaponType weaponType, final float projectileArc, final String projectileArt, + final boolean projectileHomingEnabled, final int projectileSpeed, final int areaOfEffectFullDamage, + final int areaOfEffectMediumDamage, final int areaOfEffectSmallDamage, + final EnumSet areaOfEffectTargets, final float damageFactorMedium, + final float damageFactorSmall) { + super(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, damageBase, damageDice, + damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, targetsAllowed, weaponSound, + weaponType, projectileArc, projectileArt, projectileHomingEnabled, projectileSpeed); + this.areaOfEffectFullDamage = areaOfEffectFullDamage; + this.areaOfEffectMediumDamage = areaOfEffectMediumDamage; + this.areaOfEffectSmallDamage = areaOfEffectSmallDamage; + this.areaOfEffectTargets = areaOfEffectTargets; + this.damageFactorMedium = damageFactorMedium; + this.damageFactorSmall = damageFactorSmall; + } + + public int getAreaOfEffectFullDamage() { + return this.areaOfEffectFullDamage; + } + + public int getAreaOfEffectMediumDamage() { + return this.areaOfEffectMediumDamage; + } + + public int getAreaOfEffectSmallDamage() { + return this.areaOfEffectSmallDamage; + } + + public EnumSet getAreaOfEffectTargets() { + return this.areaOfEffectTargets; + } + + public float getDamageFactorMedium() { + return this.damageFactorMedium; + } + + public float getDamageFactorSmall() { + return this.damageFactorSmall; + } + + public void setAreaOfEffectFullDamage(final int areaOfEffectFullDamage) { + this.areaOfEffectFullDamage = areaOfEffectFullDamage; + } + + public void setAreaOfEffectMediumDamage(final int areaOfEffectMediumDamage) { + this.areaOfEffectMediumDamage = areaOfEffectMediumDamage; + } + + public void setAreaOfEffectSmallDamage(final int areaOfEffectSmallDamage) { + this.areaOfEffectSmallDamage = areaOfEffectSmallDamage; + } + + public void setAreaOfEffectTargets(final EnumSet areaOfEffectTargets) { + this.areaOfEffectTargets = areaOfEffectTargets; + } + + public void setDamageFactorMedium(final float damageFactorMedium) { + this.damageFactorMedium = damageFactorMedium; + } + + public void setDamageFactorSmall(final float damageFactorSmall) { + this.damageFactorSmall = damageFactorSmall; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackNormal.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackNormal.java new file mode 100644 index 0000000..9dca88c --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackNormal.java @@ -0,0 +1,21 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks; + +import java.util.EnumSet; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType; + +public class CUnitAttackNormal extends CUnitAttack { + + public CUnitAttackNormal(final float animationBackswingPoint, final float animationDamagePoint, + final CAttackType attackType, final float cooldownTime, final int damageBase, final int damageDice, + final int damageSidesPerDie, final int damageUpgradeAmount, final int range, final float rangeMotionBuffer, + final boolean showUI, final EnumSet targetsAllowed, final String weaponSound, + final CWeaponType weaponType) { + super(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, damageBase, damageDice, + damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, targetsAllowed, weaponSound, + weaponType); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/projectile/CAttackProjectile.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/projectile/CAttackProjectile.java similarity index 95% rename from core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/projectile/CAttackProjectile.java rename to core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/projectile/CAttackProjectile.java index 4246f3c..99d1b71 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/projectile/CAttackProjectile.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/projectile/CAttackProjectile.java @@ -1,4 +1,4 @@ -package com.etheller.warsmash.viewer5.handlers.w3x.simulation.projectile; +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.projectile; import com.etheller.warsmash.util.WarsmashConstants; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java index 435e083..ed7bc48 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java @@ -1,6 +1,9 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.data; +import java.util.ArrayList; +import java.util.EnumSet; import java.util.HashMap; +import java.util.List; import java.util.Map; import com.etheller.warsmash.units.manager.MutableObjectData; @@ -9,12 +12,24 @@ import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitClassification; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityAttack; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityHoldPosition; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityMove; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityPatrol; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityStop; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CDefenseType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackInstant; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackMissile; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackMissileBounce; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackMissileLine; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackMissileSplash; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackNormal; public class CUnitData { private static final War3ID MANA_INITIAL_AMOUNT = War3ID.fromString("umpi"); @@ -28,24 +43,76 @@ public class CUnitData { private static final War3ID PROJECTILE_LAUNCH_X = War3ID.fromString("ulpx"); private static final War3ID PROJECTILE_LAUNCH_Y = War3ID.fromString("ulpy"); private static final War3ID PROJECTILE_LAUNCH_Z = War3ID.fromString("ulpz"); + private static final War3ID ATTACKS_ENABLED = War3ID.fromString("uaen"); + private static final War3ID ATTACK1_BACKSWING_POINT = War3ID.fromString("ubs1"); + private static final War3ID ATTACK1_DAMAGE_POINT = War3ID.fromString("udp1"); + private static final War3ID ATTACK1_AREA_OF_EFFECT_FULL_DMG = War3ID.fromString("ua1f"); + private static final War3ID ATTACK1_AREA_OF_EFFECT_HALF_DMG = War3ID.fromString("ua1h"); + private static final War3ID ATTACK1_AREA_OF_EFFECT_QUARTER_DMG = War3ID.fromString("ua1q"); + private static final War3ID ATTACK1_AREA_OF_EFFECT_TARGETS = War3ID.fromString("ua1p"); + private static final War3ID ATTACK1_ATTACK_TYPE = War3ID.fromString("ua1t"); + private static final War3ID ATTACK1_COOLDOWN = War3ID.fromString("ua1c"); private static final War3ID ATTACK1_DMG_BASE = War3ID.fromString("ua1b"); + private static final War3ID ATTACK1_DAMAGE_FACTOR_HALF = War3ID.fromString("uhd1"); + private static final War3ID ATTACK1_DAMAGE_FACTOR_QUARTER = War3ID.fromString("uqd1"); + private static final War3ID ATTACK1_DAMAGE_LOSS_FACTOR = War3ID.fromString("udl1"); private static final War3ID ATTACK1_DMG_DICE = War3ID.fromString("ua1d"); private static final War3ID ATTACK1_DMG_SIDES_PER_DIE = War3ID.fromString("ua1s"); - private static final War3ID ATTACK1_PROJECTILE_SPEED = War3ID.fromString("ua1z"); - private static final War3ID ATTACK1_MISSILE_ART = War3ID.fromString("ua1m"); + private static final War3ID ATTACK1_DMG_SPILL_DIST = War3ID.fromString("usd1"); + private static final War3ID ATTACK1_DMG_SPILL_RADIUS = War3ID.fromString("usr1"); + private static final War3ID ATTACK1_DMG_UPGRADE_AMT = War3ID.fromString("udu1"); + private static final War3ID ATTACK1_TARGET_COUNT = War3ID.fromString("utc1"); private static final War3ID ATTACK1_PROJECTILE_ARC = War3ID.fromString("uma1"); - private static final War3ID ATTACK1_COOLDOWN = War3ID.fromString("ua1c"); + private static final War3ID ATTACK1_MISSILE_ART = War3ID.fromString("ua1m"); + private static final War3ID ATTACK1_PROJECTILE_HOMING_ENABLED = War3ID.fromString("umh1"); + private static final War3ID ATTACK1_PROJECTILE_SPEED = War3ID.fromString("ua1z"); + private static final War3ID ATTACK1_RANGE = War3ID.fromString("ua1r"); + private static final War3ID ATTACK1_RANGE_MOTION_BUFFER = War3ID.fromString("urb1"); + private static final War3ID ATTACK1_SHOW_UI = War3ID.fromString("uwu1"); + private static final War3ID ATTACK1_TARGETS_ALLOWED = War3ID.fromString("ua1g"); + private static final War3ID ATTACK1_WEAPON_SOUND = War3ID.fromString("ucs1"); + private static final War3ID ATTACK1_WEAPON_TYPE = War3ID.fromString("ua1w"); + + private static final War3ID ATTACK2_BACKSWING_POINT = War3ID.fromString("ubs2"); + private static final War3ID ATTACK2_DAMAGE_POINT = War3ID.fromString("udp2"); + private static final War3ID ATTACK2_AREA_OF_EFFECT_FULL_DMG = War3ID.fromString("ua2f"); + private static final War3ID ATTACK2_AREA_OF_EFFECT_HALF_DMG = War3ID.fromString("ua2h"); + private static final War3ID ATTACK2_AREA_OF_EFFECT_QUARTER_DMG = War3ID.fromString("ua2q"); + private static final War3ID ATTACK2_AREA_OF_EFFECT_TARGETS = War3ID.fromString("ua2p"); + private static final War3ID ATTACK2_ATTACK_TYPE = War3ID.fromString("ua2t"); + private static final War3ID ATTACK2_COOLDOWN = War3ID.fromString("ua2c"); private static final War3ID ATTACK2_DMG_BASE = War3ID.fromString("ua2b"); + private static final War3ID ATTACK2_DAMAGE_FACTOR_HALF = War3ID.fromString("uhd2"); + private static final War3ID ATTACK2_DAMAGE_FACTOR_QUARTER = War3ID.fromString("uqd2"); + private static final War3ID ATTACK2_DAMAGE_LOSS_FACTOR = War3ID.fromString("udl2"); private static final War3ID ATTACK2_DMG_DICE = War3ID.fromString("ua2d"); private static final War3ID ATTACK2_DMG_SIDES_PER_DIE = War3ID.fromString("ua2s"); - private static final War3ID ATTACK2_PROJECTILE_SPEED = War3ID.fromString("ua2z"); - private static final War3ID ATTACK2_MISSILE_ART = War3ID.fromString("ua2m"); + private static final War3ID ATTACK2_DMG_SPILL_DIST = War3ID.fromString("usd2"); + private static final War3ID ATTACK2_DMG_SPILL_RADIUS = War3ID.fromString("usr2"); + private static final War3ID ATTACK2_DMG_UPGRADE_AMT = War3ID.fromString("udu2"); + private static final War3ID ATTACK2_TARGET_COUNT = War3ID.fromString("utc2"); private static final War3ID ATTACK2_PROJECTILE_ARC = War3ID.fromString("uma2"); - private static final War3ID ATTACK2_COOLDOWN = War3ID.fromString("ua2c"); + private static final War3ID ATTACK2_MISSILE_ART = War3ID.fromString("ua2m"); + private static final War3ID ATTACK2_PROJECTILE_HOMING_ENABLED = War3ID.fromString("umh2"); + private static final War3ID ATTACK2_PROJECTILE_SPEED = War3ID.fromString("ua2z"); + private static final War3ID ATTACK2_RANGE = War3ID.fromString("ua2r"); + private static final War3ID ATTACK2_RANGE_MOTION_BUFFER = War3ID.fromString("urb2"); + private static final War3ID ATTACK2_SHOW_UI = War3ID.fromString("uwu2"); + private static final War3ID ATTACK2_TARGETS_ALLOWED = War3ID.fromString("ua2g"); + private static final War3ID ATTACK2_WEAPON_SOUND = War3ID.fromString("ucs2"); + private static final War3ID ATTACK2_WEAPON_TYPE = War3ID.fromString("ua2w"); + + private static final War3ID PROJECTILE_IMPACT_Z = War3ID.fromString("uimz"); + + private static final War3ID DEATH_TYPE = War3ID.fromString("udea"); + private static final War3ID ARMOR_TYPE = War3ID.fromString("uarm"); + private static final War3ID DEFENSE = War3ID.fromString("udef"); + private static final War3ID DEFENSE_TYPE = War3ID.fromString("udty"); private static final War3ID MOVE_HEIGHT = War3ID.fromString("umvh"); private static final War3ID MOVE_TYPE = War3ID.fromString("umvt"); private static final War3ID COLLISION_SIZE = War3ID.fromString("ucol"); + private static final War3ID CLASSIFICATION = War3ID.fromString("utyp"); private final MutableObjectData unitData; private final Map unitIdToUnitType = new HashMap<>(); @@ -65,8 +132,113 @@ public class CUnitData { final float collisionSize = unitType.getFieldAsFloat(COLLISION_SIZE, 0); final boolean isBldg = unitType.getFieldAsBoolean(IS_BLDG, 0); final PathingGrid.MovementType movementType = PathingGrid.getMovementType(movetp); + final String classificationString = unitType.getFieldAsString(CLASSIFICATION, 0); + final String unitName = unitType.getFieldAsString(NAME, 0); + final EnumSet classifications = EnumSet.noneOf(CUnitClassification.class); + if (classificationString != null) { + final String[] classificationValues = classificationString.split(","); + for (final String unitEditorKey : classificationValues) { + final CUnitClassification unitClassification = CUnitClassification + .parseUnitClassification(unitEditorKey); + if (unitClassification != null) { + classifications.add(unitClassification); + } + } + } + final List attacks = new ArrayList<>(); + final int attacksEnabled = unitType.getFieldAsInteger(ATTACKS_ENABLED, 0); + if ((attacksEnabled & 0x1) != 0) { + // attack one + final float animationBackswingPoint = unitType.getFieldAsFloat(ATTACK1_BACKSWING_POINT, 0); + final float animationDamagePoint = unitType.getFieldAsFloat(ATTACK1_DAMAGE_POINT, 0); + final int areaOfEffectFullDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_FULL_DMG, 0); + final int areaOfEffectMediumDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_HALF_DMG, 0); + final int areaOfEffectSmallDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_QUARTER_DMG, 0); + final EnumSet areaOfEffectTargets = CTargetType + .parseTargetTypeSet(unitType.getFieldAsString(ATTACK1_AREA_OF_EFFECT_TARGETS, 0)); + final CAttackType attackType = CAttackType + .parseAttackType(unitType.getFieldAsString(ATTACK1_ATTACK_TYPE, 0)); + final float cooldownTime = unitType.getFieldAsFloat(ATTACK1_COOLDOWN, 0); + final int damageBase = unitType.getFieldAsInteger(ATTACK1_DMG_BASE, 0); + final float damageFactorMedium = unitType.getFieldAsFloat(ATTACK1_DAMAGE_FACTOR_HALF, 0); + final float damageFactorSmall = unitType.getFieldAsFloat(ATTACK1_DAMAGE_FACTOR_QUARTER, 0); + final float damageLossFactor = unitType.getFieldAsFloat(ATTACK1_DAMAGE_LOSS_FACTOR, 0); + final int damageDice = unitType.getFieldAsInteger(ATTACK1_DMG_DICE, 0); + final int damageSidesPerDie = unitType.getFieldAsInteger(ATTACK1_DMG_SIDES_PER_DIE, 0); + final float damageSpillDistance = unitType.getFieldAsFloat(ATTACK1_DMG_SPILL_DIST, 0); + final float damageSpillRadius = unitType.getFieldAsFloat(ATTACK1_DMG_SPILL_RADIUS, 0); + final int damageUpgradeAmount = unitType.getFieldAsInteger(ATTACK1_DMG_UPGRADE_AMT, 0); + final int maximumNumberOfTargets = unitType.getFieldAsInteger(ATTACK1_TARGET_COUNT, 0); + final float projectileArc = unitType.getFieldAsFloat(ATTACK1_PROJECTILE_ARC, 0); + final String projectileArt = unitType.getFieldAsString(ATTACK1_MISSILE_ART, 0); + final boolean projectileHomingEnabled = unitType.getFieldAsBoolean(ATTACK1_PROJECTILE_HOMING_ENABLED, 0); + final int projectileSpeed = unitType.getFieldAsInteger(ATTACK1_PROJECTILE_SPEED, 0); + final int range = unitType.getFieldAsInteger(ATTACK1_RANGE, 0); + final float rangeMotionBuffer = unitType.getFieldAsFloat(ATTACK1_RANGE_MOTION_BUFFER, 0); + final boolean showUI = unitType.getFieldAsBoolean(ATTACK1_SHOW_UI, 0); + final EnumSet targetsAllowed = CTargetType + .parseTargetTypeSet(unitType.getFieldAsString(ATTACK1_TARGETS_ALLOWED, 0)); + final String weaponSound = unitType.getFieldAsString(ATTACK1_WEAPON_SOUND, 0); + final CWeaponType weaponType = CWeaponType + .parseWeaponType(unitType.getFieldAsString(ATTACK1_WEAPON_TYPE, 0)); + attacks.add(createAttack(animationBackswingPoint, animationDamagePoint, areaOfEffectFullDamage, + areaOfEffectMediumDamage, areaOfEffectSmallDamage, areaOfEffectTargets, attackType, cooldownTime, + damageBase, damageFactorMedium, damageFactorSmall, damageLossFactor, damageDice, damageSidesPerDie, + damageSpillDistance, damageSpillRadius, damageUpgradeAmount, maximumNumberOfTargets, projectileArc, + projectileArt, projectileHomingEnabled, projectileSpeed, range, rangeMotionBuffer, showUI, + targetsAllowed, weaponSound, weaponType)); + } + if ((attacksEnabled & 0x2) != 0) { + // attack two + final float animationBackswingPoint = unitType.getFieldAsFloat(ATTACK2_BACKSWING_POINT, 0); + final float animationDamagePoint = unitType.getFieldAsFloat(ATTACK2_DAMAGE_POINT, 0); + final int areaOfEffectFullDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_FULL_DMG, 0); + final int areaOfEffectMediumDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_HALF_DMG, 0); + final int areaOfEffectSmallDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_QUARTER_DMG, 0); + final EnumSet areaOfEffectTargets = CTargetType + .parseTargetTypeSet(unitType.getFieldAsString(ATTACK2_AREA_OF_EFFECT_TARGETS, 0)); + final CAttackType attackType = CAttackType + .parseAttackType(unitType.getFieldAsString(ATTACK2_ATTACK_TYPE, 0)); + final float cooldownTime = unitType.getFieldAsFloat(ATTACK2_COOLDOWN, 0); + final int damageBase = unitType.getFieldAsInteger(ATTACK2_DMG_BASE, 0); + final float damageFactorMedium = unitType.getFieldAsFloat(ATTACK2_DAMAGE_FACTOR_HALF, 0); + final float damageFactorSmall = unitType.getFieldAsFloat(ATTACK2_DAMAGE_FACTOR_QUARTER, 0); + final float damageLossFactor = unitType.getFieldAsFloat(ATTACK2_DAMAGE_LOSS_FACTOR, 0); + final int damageDice = unitType.getFieldAsInteger(ATTACK2_DMG_DICE, 0); + final int damageSidesPerDie = unitType.getFieldAsInteger(ATTACK2_DMG_SIDES_PER_DIE, 0); + final float damageSpillDistance = unitType.getFieldAsFloat(ATTACK2_DMG_SPILL_DIST, 0); + final float damageSpillRadius = unitType.getFieldAsFloat(ATTACK2_DMG_SPILL_RADIUS, 0); + final int damageUpgradeAmount = unitType.getFieldAsInteger(ATTACK2_DMG_UPGRADE_AMT, 0); + final int maximumNumberOfTargets = unitType.getFieldAsInteger(ATTACK2_TARGET_COUNT, 0); + final float projectileArc = unitType.getFieldAsFloat(ATTACK2_PROJECTILE_ARC, 0); + final String projectileArt = unitType.getFieldAsString(ATTACK2_MISSILE_ART, 0); + final boolean projectileHomingEnabled = unitType.getFieldAsBoolean(ATTACK2_PROJECTILE_HOMING_ENABLED, 0); + final int projectileSpeed = unitType.getFieldAsInteger(ATTACK2_PROJECTILE_SPEED, 0); + final int range = unitType.getFieldAsInteger(ATTACK2_RANGE, 0); + final float rangeMotionBuffer = unitType.getFieldAsFloat(ATTACK2_RANGE_MOTION_BUFFER, 0); + final boolean showUI = unitType.getFieldAsBoolean(ATTACK2_SHOW_UI, 0); + final EnumSet targetsAllowed = CTargetType + .parseTargetTypeSet(unitType.getFieldAsString(ATTACK2_TARGETS_ALLOWED, 0)); + final String weaponSound = unitType.getFieldAsString(ATTACK2_WEAPON_SOUND, 0); + final CWeaponType weaponType = CWeaponType + .parseWeaponType(unitType.getFieldAsString(ATTACK2_WEAPON_TYPE, 0)); + attacks.add(createAttack(animationBackswingPoint, animationDamagePoint, areaOfEffectFullDamage, + areaOfEffectMediumDamage, areaOfEffectSmallDamage, areaOfEffectTargets, attackType, cooldownTime, + damageBase, damageFactorMedium, damageFactorSmall, damageLossFactor, damageDice, damageSidesPerDie, + damageSpillDistance, damageSpillRadius, damageUpgradeAmount, maximumNumberOfTargets, projectileArc, + projectileArt, projectileHomingEnabled, projectileSpeed, range, rangeMotionBuffer, showUI, + targetsAllowed, weaponSound, weaponType)); + } + final int deathType = unitType.getFieldAsInteger(DEATH_TYPE, 0); + final boolean raise = (deathType & 0x1) != 0; + final boolean decay = (deathType & 0x2) != 0; + final String armorType = unitType.getFieldAsString(ARMOR_TYPE, 0); + final int defense = unitType.getFieldAsInteger(DEFENSE, 0); + final float impactZ = unitType.getFieldAsFloat(PROJECTILE_IMPACT_Z, 0); + final CDefenseType defenseType = CDefenseType.parseDefenseType(unitType.getFieldAsString(DEFENSE_TYPE, 0)); final CUnit unit = new CUnit(handleId, playerIndex, x, y, life, typeId, facing, manaInitial, life, manaMaximum, - speed, new CUnitType(isBldg, movementType, moveHeight, collisionSize)); + speed, defense, new CUnitType(unitName, isBldg, movementType, moveHeight, collisionSize, + classifications, attacks, armorType, raise, decay, defenseType, impactZ)); if (speed > 0) { unit.add(simulation, CAbilityMove.INSTANCE); unit.add(simulation, CAbilityPatrol.INSTANCE); @@ -81,6 +253,60 @@ public class CUnitData { return unit; } + private CUnitAttack createAttack(final float animationBackswingPoint, final float animationDamagePoint, + final int areaOfEffectFullDamage, final int areaOfEffectMediumDamage, final int areaOfEffectSmallDamage, + final EnumSet areaOfEffectTargets, final CAttackType attackType, final float cooldownTime, + final int damageBase, final float damageFactorMedium, final float damageFactorSmall, + final float damageLossFactor, final int damageDice, final int damageSidesPerDie, + final float damageSpillDistance, final float damageSpillRadius, final int damageUpgradeAmount, + final int maximumNumberOfTargets, final float projectileArc, final String projectileArt, + final boolean projectileHomingEnabled, final int projectileSpeed, final int range, + final float rangeMotionBuffer, final boolean showUI, final EnumSet targetsAllowed, + final String weaponSound, final CWeaponType weaponType) { + final CUnitAttack attack; + switch (weaponType) { + case MISSILE: + attack = new CUnitAttackMissile(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, + damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, + targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, projectileHomingEnabled, + projectileSpeed); + break; + case MBOUNCE: + attack = new CUnitAttackMissileBounce(animationBackswingPoint, animationDamagePoint, attackType, + cooldownTime, damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, + rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, + projectileHomingEnabled, projectileSpeed, damageLossFactor, maximumNumberOfTargets); + break; + case MSPLASH: + case ARTILLERY: + attack = new CUnitAttackMissileSplash(animationBackswingPoint, animationDamagePoint, attackType, + cooldownTime, damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, + rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, + projectileHomingEnabled, projectileSpeed, areaOfEffectFullDamage, areaOfEffectMediumDamage, + areaOfEffectSmallDamage, areaOfEffectTargets, damageFactorMedium, damageFactorSmall); + break; + case MLINE: + case ALINE: + attack = new CUnitAttackMissileLine(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, + damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, + targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, projectileHomingEnabled, + projectileSpeed, damageSpillDistance, damageSpillRadius); + break; + case INSTANT: + attack = new CUnitAttackInstant(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, + damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, + targetsAllowed, weaponSound, weaponType, projectileArt); + break; + default: + case NORMAL: + attack = new CUnitAttackNormal(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, + damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, + targetsAllowed, weaponSound, weaponType); + break; + } + return attack; + } + public float getPropulsionWindow(final War3ID unitTypeId) { return this.unitData.get(unitTypeId).getFieldAsFloat(PROPULSION_WINDOW, 0); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CAttackOrder.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CAttackOrder.java index ea9e5e9..0987753 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CAttackOrder.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CAttackOrder.java @@ -1,20 +1,25 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders; import com.etheller.warsmash.util.WarsmashConstants; -import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens; +import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.COrder; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitAnimationListener; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityAttack; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; public class CAttackOrder implements COrder { private final CUnit unit; private boolean wasWithinPropWindow = false; + private final CUnitAttack unitAttack; private final CWidget target; + private int backswingLaunchTime; - public CAttackOrder(final CUnit unit, final CWidget target) { + public CAttackOrder(final CUnit unit, final CUnitAttack unitAttack, final CWidget target) { this.unit = unit; + this.unitAttack = unitAttack; this.target = target; } @@ -31,9 +36,8 @@ public class CAttackOrder implements COrder { } float facing = this.unit.getFacing(); float delta = goalAngle - facing; - final float propulsionWindow = simulation.getUnitData().getPropulsionWindow(this.unit.getTypeId()); + final float propulsionWindow = simulation.getGameplayConstants().getAttackHalfAngle(); final float turnRate = simulation.getUnitData().getTurnRate(this.unit.getTypeId()); - final int speed = this.unit.getSpeed(); if (delta < -180) { delta = 360 + delta; @@ -65,11 +69,30 @@ public class CAttackOrder implements COrder { final int cooldownEndTime = this.unit.getCooldownEndTime(); final int currentTurnTick = simulation.getGameTurnTick(); - if (currentTurnTick >= cooldownEndTime) { - final float a1Cooldown = simulation.getUnitData().getA1Cooldown(this.unit.getTypeId()); - final int a1CooldownSteps = (int) (a1Cooldown / WarsmashConstants.SIMULATION_STEP_TIME); - this.unit.setCooldownEndTime(currentTurnTick + a1CooldownSteps); - simulation.createProjectile(this.unit, 0, this.target); + if (this.wasWithinPropWindow) { + if (this.backswingLaunchTime != 0) { + if (currentTurnTick >= this.backswingLaunchTime) { + simulation.createProjectile(this.unit, 0, this.target); + this.backswingLaunchTime = 0; + } + } + else if (currentTurnTick >= cooldownEndTime) { + final float cooldownTime = this.unitAttack.getCooldownTime(); + final float animationBackswingPoint = this.unitAttack.getAnimationBackswingPoint(); + final int a1CooldownSteps = (int) (cooldownTime / WarsmashConstants.SIMULATION_STEP_TIME); + final int a1BackswingSteps = (int) (animationBackswingPoint / WarsmashConstants.SIMULATION_STEP_TIME); + final int a1DamagePointSteps = (int) (this.unitAttack.getAnimationDamagePoint() + / WarsmashConstants.SIMULATION_STEP_TIME); + this.unit.setCooldownEndTime(currentTurnTick + a1CooldownSteps); + this.backswingLaunchTime = currentTurnTick + a1DamagePointSteps; + this.unit.getUnitAnimationListener().playAnimation(true, PrimaryTag.ATTACK, + CUnitAnimationListener.EMPTY, 1.0f); + this.unit.getUnitAnimationListener().queueAnimation(PrimaryTag.STAND, CUnitAnimationListener.READY); + } + } + else { + this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.STAND, CUnitAnimationListener.READY, + 1.0f); } return false; @@ -80,9 +103,4 @@ public class CAttackOrder implements COrder { return CAbilityAttack.ORDER_ID; } - @Override - public AnimationTokens.PrimaryTag getAnimationName() { - return AnimationTokens.PrimaryTag.ATTACK; - } - } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CDoNothingOrder.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CDoNothingOrder.java index a7f1b23..9067fa7 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CDoNothingOrder.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CDoNothingOrder.java @@ -1,6 +1,5 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders; -import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.COrder; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; @@ -21,9 +20,4 @@ public class CDoNothingOrder implements COrder { return this.orderId; } - @Override - public AnimationTokens.PrimaryTag getAnimationName() { - return AnimationTokens.PrimaryTag.STAND; - } - } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CMoveOrder.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CMoveOrder.java index b51a412..1516005 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CMoveOrder.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CMoveOrder.java @@ -6,12 +6,13 @@ import java.util.List; import com.badlogic.gdx.math.Rectangle; import com.etheller.warsmash.util.WarsmashConstants; -import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens; +import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.MovementType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.COrder; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitAnimationListener; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWorldCollision; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityMove; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.pathing.CPathfindingProcessor; @@ -207,6 +208,10 @@ public class CMoveOrder implements COrder { } absDelta = Math.abs(delta); if (absDelta >= propulsionWindow) { + if (this.wasWithinPropWindow) { + this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.STAND, + CUnitAnimationListener.EMPTY, 1.0f); + } this.wasWithinPropWindow = false; return false; } @@ -224,6 +229,10 @@ public class CMoveOrder implements COrder { this.path.add(this.target); } } + if (!this.wasWithinPropWindow) { + this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.WALK, + CUnitAnimationListener.EMPTY, 1.0f); + } this.wasWithinPropWindow = true; } while (continueDistance > 0); @@ -231,6 +240,10 @@ public class CMoveOrder implements COrder { else { // If this happens, the unit is facing the wrong way, and has to turn before // moving. + if (this.wasWithinPropWindow) { + this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.STAND, + CUnitAnimationListener.EMPTY, 1.0f); + } this.wasWithinPropWindow = false; } @@ -242,12 +255,4 @@ public class CMoveOrder implements COrder { return CAbilityMove.ORDER_ID; } - @Override - public AnimationTokens.PrimaryTag getAnimationName() { - if (!this.wasWithinPropWindow) { - return AnimationTokens.PrimaryTag.STAND; - } - return AnimationTokens.PrimaryTag.WALK; - } - } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/ProjectileCreator.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/ProjectileCreator.java index 1f182c9..be22c6a 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/ProjectileCreator.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/ProjectileCreator.java @@ -3,7 +3,7 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.util; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.projectile.CAttackProjectile; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.projectile.CAttackProjectile; public interface ProjectileCreator { CAttackProjectile create(CSimulation simulation, CUnit source, int attackIndex, CWidget target); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java new file mode 100644 index 0000000..96bd077 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java @@ -0,0 +1,372 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.ui; + +import java.io.IOException; + +import com.badlogic.gdx.graphics.Texture; +import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.g2d.GlyphLayout; +import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator; +import com.badlogic.gdx.math.Rectangle; +import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.utils.viewport.Viewport; +import com.etheller.warsmash.WarsmashGdxMapGame; +import com.etheller.warsmash.datasources.DataSource; +import com.etheller.warsmash.parsers.fdf.GameUI; +import com.etheller.warsmash.parsers.fdf.datamodel.AnchorDefinition; +import com.etheller.warsmash.parsers.fdf.datamodel.FramePoint; +import com.etheller.warsmash.parsers.fdf.frames.SetPoint; +import com.etheller.warsmash.parsers.fdf.frames.StringFrame; +import com.etheller.warsmash.parsers.fdf.frames.TextureFrame; +import com.etheller.warsmash.parsers.fdf.frames.UIFrame; +import com.etheller.warsmash.parsers.jass.Jass2.RootFrameListener; +import com.etheller.warsmash.util.FastNumberFormat; +import com.etheller.warsmash.viewer5.Scene; +import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; +import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; +import com.etheller.warsmash.viewer5.handlers.w3x.StandSequence; +import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; +import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitClassification; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CDefenseType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CodeKeyType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; + +public class MeleeUI { + private final DataSource dataSource; + private final Viewport uiViewport; + private final FreeTypeFontGenerator fontGenerator; + private final Scene uiScene; + private final War3MapViewer war3MapViewer; + private final RootFrameListener rootFrameListener; + private GameUI rootFrame; + private UIFrame consoleUI; + private UIFrame resourceBar; + private UIFrame timeIndicator; + private UIFrame unitPortrait; + private StringFrame unitLifeText; + private StringFrame unitManaText; + private Portrait portrait; + private final Rectangle tempRect = new Rectangle(); + private final Vector2 projectionTemp1 = new Vector2(); + private final Vector2 projectionTemp2 = new Vector2(); + private UIFrame simpleInfoPanelUnitDetail; + private StringFrame simpleNameValue; + private StringFrame simpleClassValue; + private StringFrame simpleBuildingActionLabel; + private UIFrame attack1Icon; + private TextureFrame attack1IconBackdrop; + private StringFrame attack1InfoPanelIconValue; + private StringFrame attack1InfoPanelIconLevel; + private UIFrame attack2Icon; + private TextureFrame attack2IconBackdrop; + private StringFrame attack2InfoPanelIconValue; + private StringFrame attack2InfoPanelIconLevel; + private UIFrame armorIcon; + private TextureFrame armorIconBackdrop; + private StringFrame armorInfoPanelIconValue; + private StringFrame armorInfoPanelIconLevel; + private InfoPanelIconBackdrops damageBackdrops; + private InfoPanelIconBackdrops defenseBackdrops; + + public MeleeUI(final DataSource dataSource, final Viewport uiViewport, final FreeTypeFontGenerator fontGenerator, + final Scene uiScene, final War3MapViewer war3MapViewer, final RootFrameListener rootFrameListener) { + this.dataSource = dataSource; + this.uiViewport = uiViewport; + this.fontGenerator = fontGenerator; + this.uiScene = uiScene; + this.war3MapViewer = war3MapViewer; + this.rootFrameListener = rootFrameListener; + + } + + /** + * Called "main" because this was originally written in JASS so that maps could + * override it, and I may convert it back to the JASS at some point. + */ + public void main() { + // ================================= + // Load skins and templates + // ================================= + this.rootFrame = new GameUI(this.dataSource, GameUI.loadSkin(this.dataSource, 1), this.uiViewport, + this.fontGenerator, this.uiScene, this.war3MapViewer); + try { + this.rootFrame.loadTOCFile("UI\\FrameDef\\FrameDef.toc"); + } + catch (final IOException exc) { + throw new IllegalStateException("Unable to load FrameDef.toc", exc); + } + try { + this.rootFrame.loadTOCFile("UI\\FrameDef\\SmashFrameDef.toc"); + } + catch (final IOException exc) { + throw new IllegalStateException("Unable to load SmashFrameDef.toc", exc); + } + this.damageBackdrops = new InfoPanelIconBackdrops(CAttackType.values(), this.rootFrame, "Damage", "Neutral"); + this.defenseBackdrops = new InfoPanelIconBackdrops(CDefenseType.values(), this.rootFrame, "Armor", "Neutral"); + + // ================================= + // Load major UI components + // ================================= + // Console UI is the background with the racial theme + this.consoleUI = this.rootFrame.createSimpleFrame("ConsoleUI", this.rootFrame, 0); + this.consoleUI.setSetAllPoints(true); + + // Resource bar is a 3 part bar with Gold, Lumber, and Food. + // Its template does not specify where to put it, so we must + // put it in the "TOPRIGHT" corner. + this.resourceBar = this.rootFrame.createSimpleFrame("ResourceBarFrame", this.consoleUI, 0); + this.resourceBar.addSetPoint(new SetPoint(FramePoint.TOPRIGHT, this.consoleUI, FramePoint.TOPRIGHT, 0, 0)); + + // Create the Time Indicator (clock) + this.timeIndicator = this.rootFrame.createFrame("TimeOfDayIndicator", this.rootFrame, 0, 0); + + // Create the unit portrait stuff + this.portrait = new Portrait(this.war3MapViewer); + positionPortrait(); + this.unitPortrait = this.rootFrame.createSimpleFrame("UnitPortrait", this.consoleUI, 0); + this.unitLifeText = (StringFrame) this.rootFrame.getFrameByName("UnitPortraitHitPointText", 0); + this.unitManaText = (StringFrame) this.rootFrame.getFrameByName("UnitPortraitManaPointText", 0); + + this.simpleInfoPanelUnitDetail = this.rootFrame.createSimpleFrame("SimpleInfoPanelUnitDetail", this.consoleUI, + 0); + this.simpleInfoPanelUnitDetail + .addAnchor(new AnchorDefinition(FramePoint.BOTTOM, 0, GameUI.convertY(this.uiViewport, 0.0f))); + this.simpleInfoPanelUnitDetail.setWidth(GameUI.convertY(this.uiViewport, 0.180f)); + this.simpleInfoPanelUnitDetail.setHeight(GameUI.convertY(this.uiViewport, 0.105f)); + this.simpleNameValue = (StringFrame) this.rootFrame.getFrameByName("SimpleNameValue", 0); + this.simpleClassValue = (StringFrame) this.rootFrame.getFrameByName("SimpleClassValue", 0); + this.simpleBuildingActionLabel = (StringFrame) this.rootFrame.getFrameByName("SimpleBuildingActionLabel", 0); + + this.attack1Icon = this.rootFrame.createSimpleFrame("SimpleInfoPanelIconDamage", this.simpleInfoPanelUnitDetail, + 0); + this.attack1Icon.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, + FramePoint.TOPLEFT, 0, GameUI.convertY(this.uiViewport, -0.030125f))); + this.attack1IconBackdrop = (TextureFrame) this.rootFrame.getFrameByName("InfoPanelIconBackdrop", 0); + this.attack1InfoPanelIconValue = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconValue", 0); + this.attack1InfoPanelIconLevel = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconLevel", 0); + + this.attack2Icon = this.rootFrame.createSimpleFrame("SimpleInfoPanelIconDamage", this.simpleInfoPanelUnitDetail, + 1); + this.attack2Icon + .addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, FramePoint.TOPLEFT, + GameUI.convertX(this.uiViewport, 0.1f), GameUI.convertY(this.uiViewport, -0.030125f))); + this.attack2IconBackdrop = (TextureFrame) this.rootFrame.getFrameByName("InfoPanelIconBackdrop", 1); + this.attack2InfoPanelIconValue = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconValue", 1); + this.attack2InfoPanelIconLevel = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconLevel", 1); + + this.armorIcon = this.rootFrame.createSimpleFrame("SimpleInfoPanelIconArmor", this.simpleInfoPanelUnitDetail, + 1); + this.armorIcon.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, FramePoint.TOPLEFT, + GameUI.convertX(this.uiViewport, 0f), GameUI.convertY(this.uiViewport, -0.06025f))); + this.armorIconBackdrop = (TextureFrame) this.rootFrame.getFrameByName("InfoPanelIconBackdrop", 0); + this.armorInfoPanelIconValue = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconValue", 0); + this.armorInfoPanelIconLevel = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconLevel", 0); + + this.rootFrame.positionBounds(this.uiViewport); + selectUnit(null); + } + + public void updatePortrait() { + this.portrait.update(); + } + + public void render(final SpriteBatch batch, final BitmapFont font20, final GlyphLayout glyphLayout) { + this.rootFrame.render(batch, font20, glyphLayout); + } + + public void portraitTalk() { + this.portrait.talk(); + } + + private static final class Portrait { + private MdxComplexInstance modelInstance; + private final WarsmashGdxMapGame.CameraManager portraitCameraManager; + private final Scene portraitScene; + + public Portrait(final War3MapViewer war3MapViewer) { + this.portraitScene = war3MapViewer.addSimpleScene(); + this.portraitCameraManager = new WarsmashGdxMapGame.CameraManager(); + this.portraitCameraManager.setupCamera(this.portraitScene); + this.portraitScene.camera.viewport(new Rectangle(100, 0, 6400, 48)); + } + + public void update() { + this.portraitCameraManager.updateCamera(); + if ((this.modelInstance != null) + && (this.modelInstance.sequenceEnded || (this.modelInstance.sequence == -1))) { + StandSequence.randomPortraitSequence(this.modelInstance); + } + } + + public void talk() { + StandSequence.randomPortraitTalkSequence(this.modelInstance); + } + + public void setSelectedUnit(final RenderUnit unit) { + if (unit == null) { + if (this.modelInstance != null) { + this.portraitScene.removeInstance(this.modelInstance); + } + this.modelInstance = null; + this.portraitCameraManager.setModelInstance(null, null); + } + else { + final MdxModel portraitModel = unit.portraitModel; + if (portraitModel != null) { + if (this.modelInstance != null) { + this.portraitScene.removeInstance(this.modelInstance); + } + this.modelInstance = (MdxComplexInstance) portraitModel.addInstance(); + this.portraitCameraManager.setModelInstance(this.modelInstance, portraitModel); + this.modelInstance.setSequenceLoopMode(1); + this.modelInstance.setScene(this.portraitScene); + this.modelInstance.setVertexColor(unit.instance.vertexColor); + this.modelInstance.setTeamColor(unit.playerIndex); + } + } + } + } + + public void selectUnit(final RenderUnit unit) { + this.portrait.setSelectedUnit(unit); + if (unit == null) { + this.simpleNameValue.setText(""); + this.unitLifeText.setText(""); + this.unitManaText.setText(""); + this.simpleClassValue.setText(""); + this.simpleBuildingActionLabel.setText(""); + this.attack1Icon.setVisible(false); + this.attack2Icon.setVisible(false); + this.attack1InfoPanelIconLevel.setText(""); + this.attack2InfoPanelIconLevel.setText(""); + this.armorIcon.setVisible(false); + this.armorInfoPanelIconLevel.setText(""); + } + else { + this.simpleNameValue.setText(unit.getSimulationUnit().getUnitType().getName()); + String classText = null; + for (final CUnitClassification classification : unit.getSimulationUnit().getClassifications()) { + if (classification.getDisplayName() != null) { + classText = classification.getDisplayName(); + } + } + if (classText != null) { + this.simpleClassValue.setText(classText); + } + else { + this.simpleClassValue.setText(""); + } + this.unitLifeText.setText(FastNumberFormat.formatWholeNumber(unit.getSimulationUnit().getLife()) + " / " + + unit.getSimulationUnit().getMaximumLife()); + final int maximumMana = unit.getSimulationUnit().getMaximumMana(); + if (maximumMana > 0) { + this.unitManaText.setText( + FastNumberFormat.formatWholeNumber(unit.getSimulationUnit().getMana()) + " / " + maximumMana); + } + else { + this.unitManaText.setText(""); + } + this.simpleBuildingActionLabel.setText(""); + + if (unit.getSimulationUnit().getUnitType().getAttacks().size() > 0) { + final CUnitAttack attackOne = unit.getSimulationUnit().getUnitType().getAttacks().get(0); + this.attack1Icon.setVisible(attackOne.isShowUI()); + this.attack1IconBackdrop.setTexture(this.damageBackdrops.getTexture(attackOne.getAttackType())); + this.attack1InfoPanelIconValue.setText(attackOne.getMinDamage() + " - " + attackOne.getMaxDamage()); + if (unit.getSimulationUnit().getUnitType().getAttacks().size() > 1) { + final CUnitAttack attackTwo = unit.getSimulationUnit().getUnitType().getAttacks().get(1); + this.attack2Icon.setVisible(attackTwo.isShowUI()); + this.attack2IconBackdrop.setTexture(this.damageBackdrops.getTexture(attackTwo.getAttackType())); + this.attack2InfoPanelIconValue.setText(attackTwo.getMinDamage() + " - " + attackTwo.getMaxDamage()); + } + else { + this.attack2Icon.setVisible(false); + } + } + else { + this.attack1Icon.setVisible(false); + this.attack2Icon.setVisible(false); + } + + this.armorIcon.setVisible(true); + this.armorIconBackdrop.setTexture( + this.defenseBackdrops.getTexture(unit.getSimulationUnit().getUnitType().getDefenseType())); + this.armorInfoPanelIconValue.setText(Integer.toString(unit.getSimulationUnit().getDefense())); + } + } + + public void resize() { + positionPortrait(); + } + + public void positionPortrait() { + this.projectionTemp1.x = 422; + this.projectionTemp1.y = 57; + this.projectionTemp2.x = 422 + 167; + this.projectionTemp2.y = 57 + 170; + this.uiViewport.project(this.projectionTemp1); + this.uiViewport.project(this.projectionTemp2); + + this.tempRect.x = this.projectionTemp1.x; + this.tempRect.y = this.projectionTemp1.y; + this.tempRect.width = this.projectionTemp2.x - this.projectionTemp1.x; + this.tempRect.height = this.projectionTemp2.y - this.projectionTemp1.y; + this.portrait.portraitScene.camera.viewport(this.tempRect); + } + + private static final class InfoPanelIconBackdrops { + private final Texture[] damageBackdropTextures; + + public InfoPanelIconBackdrops(final CodeKeyType[] attackTypes, final GameUI gameUI, final String prefix, + final String suffix) { + this.damageBackdropTextures = new Texture[attackTypes.length]; + for (int index = 0; index < attackTypes.length; index++) { + final CodeKeyType attackType = attackTypes[index]; + String skinLookupKey = "InfoPanelIcon" + prefix + attackType.getCodeKey() + suffix; + try { + this.damageBackdropTextures[index] = gameUI.loadTexture(gameUI.getSkinField(skinLookupKey)); + } + catch (final Exception exc) { + skinLookupKey = "InfoPanelIcon" + prefix + attackType.getCodeKey(); + this.damageBackdropTextures[index] = gameUI.loadTexture(gameUI.getSkinField(skinLookupKey)); + } + } + } + + public Texture getTexture(final CodeKeyType attackType) { + if (attackType != null) { + final int ordinal = attackType.ordinal(); + if ((ordinal >= 0) && (ordinal < this.damageBackdropTextures.length)) { + return this.damageBackdropTextures[ordinal]; + } + } + return this.damageBackdropTextures[0]; + } + + private static String getSuffix(final CAttackType attackType) { + switch (attackType) { + case CHAOS: + return "Chaos"; + case HERO: + return "Hero"; + case MAGIC: + return "Magic"; + case NORMAL: + return "Normal"; + case PIERCE: + return "Pierce"; + case SIEGE: + return "Siege"; + case SPELLS: + return "Magic"; + case UNKNOWN: + return "Unknown"; + default: + throw new IllegalArgumentException("Unknown attack type: " + attackType); + } + + } + } +} diff --git a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java index f6538ff..2c7fba2 100644 --- a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java +++ b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java @@ -84,10 +84,12 @@ public class DesktopLauncher { config.gles30ContextMajorVersion = 3; config.gles30ContextMinorVersion = 3; config.samples = 16; - config.fullscreen = true; - final DisplayMode desktopDisplayMode = LwjglApplicationConfiguration.getDesktopDisplayMode(); - config.width = desktopDisplayMode.width; - config.height = desktopDisplayMode.height; + config.fullscreen = false; + if (config.fullscreen) { + final DisplayMode desktopDisplayMode = LwjglApplicationConfiguration.getDesktopDisplayMode(); + config.width = desktopDisplayMode.width; + config.height = desktopDisplayMode.height; + } new LwjglApplication(new WarsmashGdxMapGame(warsmashIni), config); } } diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FrameDefinition.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FrameDefinition.java index b4f58c5..b655c6d 100644 --- a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FrameDefinition.java +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FrameDefinition.java @@ -9,7 +9,9 @@ import java.util.Set; import com.etheller.warsmash.parsers.fdf.datamodel.fields.FrameDefinitionField; import com.etheller.warsmash.parsers.fdf.datamodel.fields.visitor.GetFloatFieldVisitor; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.visitor.GetFontFieldVisitor; import com.etheller.warsmash.parsers.fdf.datamodel.fields.visitor.GetStringFieldVisitor; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.visitor.GetTextJustifyFieldVisitor; import com.etheller.warsmash.parsers.fdf.datamodel.fields.visitor.GetVector4FieldVisitor; /** @@ -123,4 +125,20 @@ public class FrameDefinition { } return null; } + + public FontDefinition getFont(final String id) { + final FrameDefinitionField frameDefinitionField = this.nameToField.get(id); + if (frameDefinitionField != null) { + return frameDefinitionField.visit(GetFontFieldVisitor.INSTANCE); + } + return null; + } + + public TextJustify getTextJustify(final String id) { + final FrameDefinitionField frameDefinitionField = this.nameToField.get(id); + if (frameDefinitionField != null) { + return frameDefinitionField.visit(GetTextJustifyFieldVisitor.INSTANCE); + } + return null; + } } diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/visitor/GetFontFieldVisitor.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/visitor/GetFontFieldVisitor.java new file mode 100644 index 0000000..44f9ece --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/visitor/GetFontFieldVisitor.java @@ -0,0 +1,57 @@ +package com.etheller.warsmash.parsers.fdf.datamodel.fields.visitor; + +import com.etheller.warsmash.parsers.fdf.datamodel.FontDefinition; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.FloatFrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.FontFrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.FrameDefinitionFieldVisitor; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.StringFrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.StringPairFrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.TextJustifyFrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.Vector2FrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.Vector3FrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.Vector4FrameDefinitionField; + +public class GetFontFieldVisitor implements FrameDefinitionFieldVisitor { + public static GetFontFieldVisitor INSTANCE = new GetFontFieldVisitor(); + + @Override + public FontDefinition accept(final StringFrameDefinitionField field) { + return null; + } + + @Override + public FontDefinition accept(final StringPairFrameDefinitionField field) { + return null; + } + + @Override + public FontDefinition accept(final FloatFrameDefinitionField field) { + return null; + } + + @Override + public FontDefinition accept(final Vector3FrameDefinitionField field) { + return null; + } + + @Override + public FontDefinition accept(final Vector4FrameDefinitionField field) { + return null; + } + + @Override + public FontDefinition accept(final Vector2FrameDefinitionField field) { + return null; + } + + @Override + public FontDefinition accept(final FontFrameDefinitionField field) { + return field.getValue(); + } + + @Override + public FontDefinition accept(final TextJustifyFrameDefinitionField field) { + return null; + } + +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/visitor/GetTextJustifyFieldVisitor.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/visitor/GetTextJustifyFieldVisitor.java new file mode 100644 index 0000000..bb45533 --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/visitor/GetTextJustifyFieldVisitor.java @@ -0,0 +1,57 @@ +package com.etheller.warsmash.parsers.fdf.datamodel.fields.visitor; + +import com.etheller.warsmash.parsers.fdf.datamodel.TextJustify; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.FloatFrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.FontFrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.FrameDefinitionFieldVisitor; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.StringFrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.StringPairFrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.TextJustifyFrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.Vector2FrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.Vector3FrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.Vector4FrameDefinitionField; + +public class GetTextJustifyFieldVisitor implements FrameDefinitionFieldVisitor { + public static GetTextJustifyFieldVisitor INSTANCE = new GetTextJustifyFieldVisitor(); + + @Override + public TextJustify accept(final StringFrameDefinitionField field) { + return null; + } + + @Override + public TextJustify accept(final StringPairFrameDefinitionField field) { + return null; + } + + @Override + public TextJustify accept(final FloatFrameDefinitionField field) { + return null; + } + + @Override + public TextJustify accept(final Vector3FrameDefinitionField field) { + return null; + } + + @Override + public TextJustify accept(final Vector4FrameDefinitionField field) { + return null; + } + + @Override + public TextJustify accept(final Vector2FrameDefinitionField field) { + return null; + } + + @Override + public TextJustify accept(final FontFrameDefinitionField field) { + return null; + } + + @Override + public TextJustify accept(final TextJustifyFrameDefinitionField field) { + return field.getValue(); + } + +} diff --git a/jassparser/.gitignore b/jassparser/.gitignore new file mode 100644 index 0000000..84c048a --- /dev/null +++ b/jassparser/.gitignore @@ -0,0 +1 @@ +/build/ diff --git a/jassparser/antlr-src/Jass.g4 b/jassparser/antlr-src/Jass.g4 index 6db646c..cab4935 100644 --- a/jassparser/antlr-src/Jass.g4 +++ b/jassparser/antlr-src/Jass.g4 @@ -19,7 +19,7 @@ program : typeDefinition : TYPE ID EXTENDS ID newlines - ; + ; type : ID # BasicType diff --git a/jassparser/build/generated-src/com/etheller/warsmash/jassparser/JassBaseVisitor.java b/jassparser/build/generated-src/com/etheller/warsmash/jassparser/JassBaseVisitor.java deleted file mode 100644 index 90b541a..0000000 --- a/jassparser/build/generated-src/com/etheller/warsmash/jassparser/JassBaseVisitor.java +++ /dev/null @@ -1,317 +0,0 @@ -// Generated from Jass.g4 by ANTLR 4.7 - - package com.etheller.interpreter; - -import org.antlr.v4.runtime.tree.AbstractParseTreeVisitor; - -/** - * This class provides an empty implementation of {@link JassVisitor}, - * which can be extended to create a visitor which only needs to handle a subset - * of the available methods. - * - * @param The return type of the visit operation. Use {@link Void} for - * operations with no return type. - */ -public class JassBaseVisitor extends AbstractParseTreeVisitor implements JassVisitor { - /** - * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

- */ - @Override public T visitProgram(JassParser.ProgramContext ctx) { return visitChildren(ctx); } - /** - * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

- */ - @Override public T visitTypeDefinition(JassParser.TypeDefinitionContext ctx) { return visitChildren(ctx); } - /** - * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

- */ - @Override public T visitBasicType(JassParser.BasicTypeContext ctx) { return visitChildren(ctx); } - /** - * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

- */ - @Override public T visitArrayType(JassParser.ArrayTypeContext ctx) { return visitChildren(ctx); } - /** - * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

- */ - @Override public T visitNothingType(JassParser.NothingTypeContext ctx) { return visitChildren(ctx); } - /** - * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

- */ - @Override public T visitBasicGlobal(JassParser.BasicGlobalContext ctx) { return visitChildren(ctx); } - /** - * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

- */ - @Override public T visitDefinitionGlobal(JassParser.DefinitionGlobalContext ctx) { return visitChildren(ctx); } - /** - * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

- */ - @Override public T visitAssignTail(JassParser.AssignTailContext ctx) { return visitChildren(ctx); } - /** - * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

- */ - @Override public T visitReferenceExpression(JassParser.ReferenceExpressionContext ctx) { return visitChildren(ctx); } - /** - * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

- */ - @Override public T visitStringLiteralExpression(JassParser.StringLiteralExpressionContext ctx) { return visitChildren(ctx); } - /** - * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

- */ - @Override public T visitIntegerLiteralExpression(JassParser.IntegerLiteralExpressionContext ctx) { return visitChildren(ctx); } - /** - * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

- */ - @Override public T visitFunctionReferenceExpression(JassParser.FunctionReferenceExpressionContext ctx) { return visitChildren(ctx); } - /** - * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

- */ - @Override public T visitNullExpression(JassParser.NullExpressionContext ctx) { return visitChildren(ctx); } - /** - * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

- */ - @Override public T visitTrueExpression(JassParser.TrueExpressionContext ctx) { return visitChildren(ctx); } - /** - * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

- */ - @Override public T visitFalseExpression(JassParser.FalseExpressionContext ctx) { return visitChildren(ctx); } - /** - * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

- */ - @Override public T visitArrayReferenceExpression(JassParser.ArrayReferenceExpressionContext ctx) { return visitChildren(ctx); } - /** - * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

- */ - @Override public T visitFunctionCallExpression(JassParser.FunctionCallExpressionContext ctx) { return visitChildren(ctx); } - /** - * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

- */ - @Override public T visitParentheticalExpression(JassParser.ParentheticalExpressionContext ctx) { return visitChildren(ctx); } - /** - * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

- */ - @Override public T visitNotExpression(JassParser.NotExpressionContext ctx) { return visitChildren(ctx); } - /** - * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

- */ - @Override public T visitFunctionExpression(JassParser.FunctionExpressionContext ctx) { return visitChildren(ctx); } - /** - * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

- */ - @Override public T visitSingleArgument(JassParser.SingleArgumentContext ctx) { return visitChildren(ctx); } - /** - * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

- */ - @Override public T visitListArgument(JassParser.ListArgumentContext ctx) { return visitChildren(ctx); } - /** - * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

- */ - @Override public T visitCallStatement(JassParser.CallStatementContext ctx) { return visitChildren(ctx); } - /** - * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

- */ - @Override public T visitSetStatement(JassParser.SetStatementContext ctx) { return visitChildren(ctx); } - /** - * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

- */ - @Override public T visitArrayedAssignmentStatement(JassParser.ArrayedAssignmentStatementContext ctx) { return visitChildren(ctx); } - /** - * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

- */ - @Override public T visitReturnStatement(JassParser.ReturnStatementContext ctx) { return visitChildren(ctx); } - /** - * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

- */ - @Override public T visitIfStatement(JassParser.IfStatementContext ctx) { return visitChildren(ctx); } - /** - * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

- */ - @Override public T visitSimpleIfStatement(JassParser.SimpleIfStatementContext ctx) { return visitChildren(ctx); } - /** - * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

- */ - @Override public T visitIfElseStatement(JassParser.IfElseStatementContext ctx) { return visitChildren(ctx); } - /** - * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

- */ - @Override public T visitIfElseIfStatement(JassParser.IfElseIfStatementContext ctx) { return visitChildren(ctx); } - /** - * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

- */ - @Override public T visitParam(JassParser.ParamContext ctx) { return visitChildren(ctx); } - /** - * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

- */ - @Override public T visitSingleParameter(JassParser.SingleParameterContext ctx) { return visitChildren(ctx); } - /** - * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

- */ - @Override public T visitListParameter(JassParser.ListParameterContext ctx) { return visitChildren(ctx); } - /** - * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

- */ - @Override public T visitNothingParameter(JassParser.NothingParameterContext ctx) { return visitChildren(ctx); } - /** - * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

- */ - @Override public T visitGlobalsBlock(JassParser.GlobalsBlockContext ctx) { return visitChildren(ctx); } - /** - * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

- */ - @Override public T visitTypeDefinitionBlock(JassParser.TypeDefinitionBlockContext ctx) { return visitChildren(ctx); } - /** - * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

- */ - @Override public T visitNativeBlock(JassParser.NativeBlockContext ctx) { return visitChildren(ctx); } - /** - * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

- */ - @Override public T visitBlock(JassParser.BlockContext ctx) { return visitChildren(ctx); } - /** - * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

- */ - @Override public T visitFunctionBlock(JassParser.FunctionBlockContext ctx) { return visitChildren(ctx); } - /** - * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

- */ - @Override public T visitStatements(JassParser.StatementsContext ctx) { return visitChildren(ctx); } - /** - * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

- */ - @Override public T visitNewlines(JassParser.NewlinesContext ctx) { return visitChildren(ctx); } - /** - * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

- */ - @Override public T visitNewlines_opt(JassParser.Newlines_optContext ctx) { return visitChildren(ctx); } - /** - * {@inheritDoc} - * - *

The default implementation returns the result of calling - * {@link #visitChildren} on {@code ctx}.

- */ - @Override public T visitPnewlines(JassParser.PnewlinesContext ctx) { return visitChildren(ctx); } -} \ No newline at end of file diff --git a/jassparser/build/generated-src/com/etheller/warsmash/jassparser/JassLexer.java b/jassparser/build/generated-src/com/etheller/warsmash/jassparser/JassLexer.java deleted file mode 100644 index 9f02f08..0000000 --- a/jassparser/build/generated-src/com/etheller/warsmash/jassparser/JassLexer.java +++ /dev/null @@ -1,224 +0,0 @@ -// Generated from Jass.g4 by ANTLR 4.7 - - package com.etheller.interpreter; - -import org.antlr.v4.runtime.Lexer; -import org.antlr.v4.runtime.CharStream; -import org.antlr.v4.runtime.Token; -import org.antlr.v4.runtime.TokenStream; -import org.antlr.v4.runtime.*; -import org.antlr.v4.runtime.atn.*; -import org.antlr.v4.runtime.dfa.DFA; -import org.antlr.v4.runtime.misc.*; - -@SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast"}) -public class JassLexer extends Lexer { - static { RuntimeMetaData.checkVersion("4.7", RuntimeMetaData.VERSION); } - - protected static final DFA[] _decisionToDFA; - protected static final PredictionContextCache _sharedContextCache = - new PredictionContextCache(); - public static final int - T__0=1, T__1=2, T__2=3, T__3=4, T__4=5, EQUALS=6, GLOBALS=7, ENDGLOBALS=8, - NATIVE=9, FUNCTION=10, TAKES=11, RETURNS=12, ENDFUNCTION=13, NOTHING=14, - CALL=15, SET=16, RETURN=17, ARRAY=18, TYPE=19, EXTENDS=20, IF=21, THEN=22, - ELSE=23, ENDIF=24, ELSEIF=25, CONSTANT=26, STRING_LITERAL=27, INTEGER=28, - NULL=29, TRUE=30, FALSE=31, NOT=32, ID=33, WS=34, NEWLINE=35; - public static String[] channelNames = { - "DEFAULT_TOKEN_CHANNEL", "HIDDEN" - }; - - public static String[] modeNames = { - "DEFAULT_MODE" - }; - - public static final String[] ruleNames = { - "T__0", "T__1", "T__2", "T__3", "T__4", "EQUALS", "GLOBALS", "ENDGLOBALS", - "NATIVE", "FUNCTION", "TAKES", "RETURNS", "ENDFUNCTION", "NOTHING", "CALL", - "SET", "RETURN", "ARRAY", "TYPE", "EXTENDS", "IF", "THEN", "ELSE", "ENDIF", - "ELSEIF", "CONSTANT", "STRING_LITERAL", "INTEGER", "NULL", "TRUE", "FALSE", - "NOT", "ID", "WS", "NEWLINE" - }; - - private static final String[] _LITERAL_NAMES = { - null, "'['", "']'", "'('", "')'", "','", "'='", "'globals'", "'endglobals'", - "'native'", "'function'", "'takes'", "'returns'", "'endfunction'", "'nothing'", - "'call'", "'set'", "'return'", "'array'", "'type'", "'extends'", "'if'", - "'then'", "'else'", "'endif'", "'elseif'", "'constant'", null, null, "'null'", - "'true'", "'false'", "'not'" - }; - private static final String[] _SYMBOLIC_NAMES = { - null, null, null, null, null, null, "EQUALS", "GLOBALS", "ENDGLOBALS", - "NATIVE", "FUNCTION", "TAKES", "RETURNS", "ENDFUNCTION", "NOTHING", "CALL", - "SET", "RETURN", "ARRAY", "TYPE", "EXTENDS", "IF", "THEN", "ELSE", "ENDIF", - "ELSEIF", "CONSTANT", "STRING_LITERAL", "INTEGER", "NULL", "TRUE", "FALSE", - "NOT", "ID", "WS", "NEWLINE" - }; - public static final Vocabulary VOCABULARY = new VocabularyImpl(_LITERAL_NAMES, _SYMBOLIC_NAMES); - - /** - * @deprecated Use {@link #VOCABULARY} instead. - */ - @Deprecated - public static final String[] tokenNames; - static { - tokenNames = new String[_SYMBOLIC_NAMES.length]; - for (int i = 0; i < tokenNames.length; i++) { - tokenNames[i] = VOCABULARY.getLiteralName(i); - if (tokenNames[i] == null) { - tokenNames[i] = VOCABULARY.getSymbolicName(i); - } - - if (tokenNames[i] == null) { - tokenNames[i] = ""; - } - } - } - - @Override - @Deprecated - public String[] getTokenNames() { - return tokenNames; - } - - @Override - - public Vocabulary getVocabulary() { - return VOCABULARY; - } - - - public JassLexer(CharStream input) { - super(input); - _interp = new LexerATNSimulator(this,_ATN,_decisionToDFA,_sharedContextCache); - } - - @Override - public String getGrammarFileName() { return "Jass.g4"; } - - @Override - public String[] getRuleNames() { return ruleNames; } - - @Override - public String getSerializedATN() { return _serializedATN; } - - @Override - public String[] getChannelNames() { return channelNames; } - - @Override - public String[] getModeNames() { return modeNames; } - - @Override - public ATN getATN() { return _ATN; } - - public static final String _serializedATN = - "\3\u608b\ua72a\u8133\ub9ed\u417c\u3be7\u7786\u5964\2%\u0139\b\1\4\2\t"+ - "\2\4\3\t\3\4\4\t\4\4\5\t\5\4\6\t\6\4\7\t\7\4\b\t\b\4\t\t\t\4\n\t\n\4\13"+ - "\t\13\4\f\t\f\4\r\t\r\4\16\t\16\4\17\t\17\4\20\t\20\4\21\t\21\4\22\t\22"+ - "\4\23\t\23\4\24\t\24\4\25\t\25\4\26\t\26\4\27\t\27\4\30\t\30\4\31\t\31"+ - "\4\32\t\32\4\33\t\33\4\34\t\34\4\35\t\35\4\36\t\36\4\37\t\37\4 \t \4!"+ - "\t!\4\"\t\"\4#\t#\4$\t$\3\2\3\2\3\3\3\3\3\4\3\4\3\5\3\5\3\6\3\6\3\7\3"+ - "\7\3\b\3\b\3\b\3\b\3\b\3\b\3\b\3\b\3\t\3\t\3\t\3\t\3\t\3\t\3\t\3\t\3\t"+ - "\3\t\3\t\3\n\3\n\3\n\3\n\3\n\3\n\3\n\3\13\3\13\3\13\3\13\3\13\3\13\3\13"+ - "\3\13\3\13\3\f\3\f\3\f\3\f\3\f\3\f\3\r\3\r\3\r\3\r\3\r\3\r\3\r\3\r\3\16"+ - "\3\16\3\16\3\16\3\16\3\16\3\16\3\16\3\16\3\16\3\16\3\16\3\17\3\17\3\17"+ - "\3\17\3\17\3\17\3\17\3\17\3\20\3\20\3\20\3\20\3\20\3\21\3\21\3\21\3\21"+ - "\3\22\3\22\3\22\3\22\3\22\3\22\3\22\3\23\3\23\3\23\3\23\3\23\3\23\3\24"+ - "\3\24\3\24\3\24\3\24\3\25\3\25\3\25\3\25\3\25\3\25\3\25\3\25\3\26\3\26"+ - "\3\26\3\27\3\27\3\27\3\27\3\27\3\30\3\30\3\30\3\30\3\30\3\31\3\31\3\31"+ - "\3\31\3\31\3\31\3\32\3\32\3\32\3\32\3\32\3\32\3\32\3\33\3\33\3\33\3\33"+ - "\3\33\3\33\3\33\3\33\3\33\3\34\3\34\7\34\u00e3\n\34\f\34\16\34\u00e6\13"+ - "\34\3\34\3\34\3\35\3\35\3\35\7\35\u00ed\n\35\f\35\16\35\u00f0\13\35\5"+ - "\35\u00f2\n\35\3\36\3\36\3\36\3\36\3\36\3\37\3\37\3\37\3\37\3\37\3 \3"+ - " \3 \3 \3 \3 \3!\3!\3!\3!\3\"\3\"\7\"\u010a\n\"\f\"\16\"\u010d\13\"\3"+ - "#\6#\u0110\n#\r#\16#\u0111\3#\3#\3$\3$\3$\3$\7$\u011a\n$\f$\16$\u011d"+ - "\13$\3$\3$\3$\3$\3$\3$\7$\u0125\n$\f$\16$\u0128\13$\3$\3$\3$\3$\3$\7$"+ - "\u012f\n$\f$\16$\u0132\13$\3$\3$\3$\3$\5$\u0138\n$\6\u00e4\u011b\u0126"+ - "\u0130\2%\3\3\5\4\7\5\t\6\13\7\r\b\17\t\21\n\23\13\25\f\27\r\31\16\33"+ - "\17\35\20\37\21!\22#\23%\24\'\25)\26+\27-\30/\31\61\32\63\33\65\34\67"+ - "\359\36;\37= ?!A\"C#E$G%\3\2\t\3\2\62\62\3\2\63;\3\2\62;\5\2C\\aac|\6"+ - "\2\62;C\\aac|\4\2\13\13\"\"\4\2\f\f\17\17\2\u0144\2\3\3\2\2\2\2\5\3\2"+ - "\2\2\2\7\3\2\2\2\2\t\3\2\2\2\2\13\3\2\2\2\2\r\3\2\2\2\2\17\3\2\2\2\2\21"+ - "\3\2\2\2\2\23\3\2\2\2\2\25\3\2\2\2\2\27\3\2\2\2\2\31\3\2\2\2\2\33\3\2"+ - "\2\2\2\35\3\2\2\2\2\37\3\2\2\2\2!\3\2\2\2\2#\3\2\2\2\2%\3\2\2\2\2\'\3"+ - "\2\2\2\2)\3\2\2\2\2+\3\2\2\2\2-\3\2\2\2\2/\3\2\2\2\2\61\3\2\2\2\2\63\3"+ - "\2\2\2\2\65\3\2\2\2\2\67\3\2\2\2\29\3\2\2\2\2;\3\2\2\2\2=\3\2\2\2\2?\3"+ - "\2\2\2\2A\3\2\2\2\2C\3\2\2\2\2E\3\2\2\2\2G\3\2\2\2\3I\3\2\2\2\5K\3\2\2"+ - "\2\7M\3\2\2\2\tO\3\2\2\2\13Q\3\2\2\2\rS\3\2\2\2\17U\3\2\2\2\21]\3\2\2"+ - "\2\23h\3\2\2\2\25o\3\2\2\2\27x\3\2\2\2\31~\3\2\2\2\33\u0086\3\2\2\2\35"+ - "\u0092\3\2\2\2\37\u009a\3\2\2\2!\u009f\3\2\2\2#\u00a3\3\2\2\2%\u00aa\3"+ - "\2\2\2\'\u00b0\3\2\2\2)\u00b5\3\2\2\2+\u00bd\3\2\2\2-\u00c0\3\2\2\2/\u00c5"+ - "\3\2\2\2\61\u00ca\3\2\2\2\63\u00d0\3\2\2\2\65\u00d7\3\2\2\2\67\u00e0\3"+ - "\2\2\29\u00f1\3\2\2\2;\u00f3\3\2\2\2=\u00f8\3\2\2\2?\u00fd\3\2\2\2A\u0103"+ - "\3\2\2\2C\u0107\3\2\2\2E\u010f\3\2\2\2G\u0137\3\2\2\2IJ\7]\2\2J\4\3\2"+ - "\2\2KL\7_\2\2L\6\3\2\2\2MN\7*\2\2N\b\3\2\2\2OP\7+\2\2P\n\3\2\2\2QR\7."+ - "\2\2R\f\3\2\2\2ST\7?\2\2T\16\3\2\2\2UV\7i\2\2VW\7n\2\2WX\7q\2\2XY\7d\2"+ - "\2YZ\7c\2\2Z[\7n\2\2[\\\7u\2\2\\\20\3\2\2\2]^\7g\2\2^_\7p\2\2_`\7f\2\2"+ - "`a\7i\2\2ab\7n\2\2bc\7q\2\2cd\7d\2\2de\7c\2\2ef\7n\2\2fg\7u\2\2g\22\3"+ - "\2\2\2hi\7p\2\2ij\7c\2\2jk\7v\2\2kl\7k\2\2lm\7x\2\2mn\7g\2\2n\24\3\2\2"+ - "\2op\7h\2\2pq\7w\2\2qr\7p\2\2rs\7e\2\2st\7v\2\2tu\7k\2\2uv\7q\2\2vw\7"+ - "p\2\2w\26\3\2\2\2xy\7v\2\2yz\7c\2\2z{\7m\2\2{|\7g\2\2|}\7u\2\2}\30\3\2"+ - "\2\2~\177\7t\2\2\177\u0080\7g\2\2\u0080\u0081\7v\2\2\u0081\u0082\7w\2"+ - "\2\u0082\u0083\7t\2\2\u0083\u0084\7p\2\2\u0084\u0085\7u\2\2\u0085\32\3"+ - "\2\2\2\u0086\u0087\7g\2\2\u0087\u0088\7p\2\2\u0088\u0089\7f\2\2\u0089"+ - "\u008a\7h\2\2\u008a\u008b\7w\2\2\u008b\u008c\7p\2\2\u008c\u008d\7e\2\2"+ - "\u008d\u008e\7v\2\2\u008e\u008f\7k\2\2\u008f\u0090\7q\2\2\u0090\u0091"+ - "\7p\2\2\u0091\34\3\2\2\2\u0092\u0093\7p\2\2\u0093\u0094\7q\2\2\u0094\u0095"+ - "\7v\2\2\u0095\u0096\7j\2\2\u0096\u0097\7k\2\2\u0097\u0098\7p\2\2\u0098"+ - "\u0099\7i\2\2\u0099\36\3\2\2\2\u009a\u009b\7e\2\2\u009b\u009c\7c\2\2\u009c"+ - "\u009d\7n\2\2\u009d\u009e\7n\2\2\u009e \3\2\2\2\u009f\u00a0\7u\2\2\u00a0"+ - "\u00a1\7g\2\2\u00a1\u00a2\7v\2\2\u00a2\"\3\2\2\2\u00a3\u00a4\7t\2\2\u00a4"+ - "\u00a5\7g\2\2\u00a5\u00a6\7v\2\2\u00a6\u00a7\7w\2\2\u00a7\u00a8\7t\2\2"+ - "\u00a8\u00a9\7p\2\2\u00a9$\3\2\2\2\u00aa\u00ab\7c\2\2\u00ab\u00ac\7t\2"+ - "\2\u00ac\u00ad\7t\2\2\u00ad\u00ae\7c\2\2\u00ae\u00af\7{\2\2\u00af&\3\2"+ - "\2\2\u00b0\u00b1\7v\2\2\u00b1\u00b2\7{\2\2\u00b2\u00b3\7r\2\2\u00b3\u00b4"+ - "\7g\2\2\u00b4(\3\2\2\2\u00b5\u00b6\7g\2\2\u00b6\u00b7\7z\2\2\u00b7\u00b8"+ - "\7v\2\2\u00b8\u00b9\7g\2\2\u00b9\u00ba\7p\2\2\u00ba\u00bb\7f\2\2\u00bb"+ - "\u00bc\7u\2\2\u00bc*\3\2\2\2\u00bd\u00be\7k\2\2\u00be\u00bf\7h\2\2\u00bf"+ - ",\3\2\2\2\u00c0\u00c1\7v\2\2\u00c1\u00c2\7j\2\2\u00c2\u00c3\7g\2\2\u00c3"+ - "\u00c4\7p\2\2\u00c4.\3\2\2\2\u00c5\u00c6\7g\2\2\u00c6\u00c7\7n\2\2\u00c7"+ - "\u00c8\7u\2\2\u00c8\u00c9\7g\2\2\u00c9\60\3\2\2\2\u00ca\u00cb\7g\2\2\u00cb"+ - "\u00cc\7p\2\2\u00cc\u00cd\7f\2\2\u00cd\u00ce\7k\2\2\u00ce\u00cf\7h\2\2"+ - "\u00cf\62\3\2\2\2\u00d0\u00d1\7g\2\2\u00d1\u00d2\7n\2\2\u00d2\u00d3\7"+ - "u\2\2\u00d3\u00d4\7g\2\2\u00d4\u00d5\7k\2\2\u00d5\u00d6\7h\2\2\u00d6\64"+ - "\3\2\2\2\u00d7\u00d8\7e\2\2\u00d8\u00d9\7q\2\2\u00d9\u00da\7p\2\2\u00da"+ - "\u00db\7u\2\2\u00db\u00dc\7v\2\2\u00dc\u00dd\7c\2\2\u00dd\u00de\7p\2\2"+ - "\u00de\u00df\7v\2\2\u00df\66\3\2\2\2\u00e0\u00e4\7$\2\2\u00e1\u00e3\13"+ - "\2\2\2\u00e2\u00e1\3\2\2\2\u00e3\u00e6\3\2\2\2\u00e4\u00e5\3\2\2\2\u00e4"+ - "\u00e2\3\2\2\2\u00e5\u00e7\3\2\2\2\u00e6\u00e4\3\2\2\2\u00e7\u00e8\7$"+ - "\2\2\u00e88\3\2\2\2\u00e9\u00f2\t\2\2\2\u00ea\u00ee\t\3\2\2\u00eb\u00ed"+ - "\t\4\2\2\u00ec\u00eb\3\2\2\2\u00ed\u00f0\3\2\2\2\u00ee\u00ec\3\2\2\2\u00ee"+ - "\u00ef\3\2\2\2\u00ef\u00f2\3\2\2\2\u00f0\u00ee\3\2\2\2\u00f1\u00e9\3\2"+ - "\2\2\u00f1\u00ea\3\2\2\2\u00f2:\3\2\2\2\u00f3\u00f4\7p\2\2\u00f4\u00f5"+ - "\7w\2\2\u00f5\u00f6\7n\2\2\u00f6\u00f7\7n\2\2\u00f7<\3\2\2\2\u00f8\u00f9"+ - "\7v\2\2\u00f9\u00fa\7t\2\2\u00fa\u00fb\7w\2\2\u00fb\u00fc\7g\2\2\u00fc"+ - ">\3\2\2\2\u00fd\u00fe\7h\2\2\u00fe\u00ff\7c\2\2\u00ff\u0100\7n\2\2\u0100"+ - "\u0101\7u\2\2\u0101\u0102\7g\2\2\u0102@\3\2\2\2\u0103\u0104\7p\2\2\u0104"+ - "\u0105\7q\2\2\u0105\u0106\7v\2\2\u0106B\3\2\2\2\u0107\u010b\t\5\2\2\u0108"+ - "\u010a\t\6\2\2\u0109\u0108\3\2\2\2\u010a\u010d\3\2\2\2\u010b\u0109\3\2"+ - "\2\2\u010b\u010c\3\2\2\2\u010cD\3\2\2\2\u010d\u010b\3\2\2\2\u010e\u0110"+ - "\t\7\2\2\u010f\u010e\3\2\2\2\u0110\u0111\3\2\2\2\u0111\u010f\3\2\2\2\u0111"+ - "\u0112\3\2\2\2\u0112\u0113\3\2\2\2\u0113\u0114\b#\2\2\u0114F\3\2\2\2\u0115"+ - "\u0116\7\61\2\2\u0116\u0117\7\61\2\2\u0117\u011b\3\2\2\2\u0118\u011a\13"+ - "\2\2\2\u0119\u0118\3\2\2\2\u011a\u011d\3\2\2\2\u011b\u011c\3\2\2\2\u011b"+ - "\u0119\3\2\2\2\u011c\u011e\3\2\2\2\u011d\u011b\3\2\2\2\u011e\u011f\7\17"+ - "\2\2\u011f\u0138\7\f\2\2\u0120\u0121\7\61\2\2\u0121\u0122\7\61\2\2\u0122"+ - "\u0126\3\2\2\2\u0123\u0125\13\2\2\2\u0124\u0123\3\2\2\2\u0125\u0128\3"+ - "\2\2\2\u0126\u0127\3\2\2\2\u0126\u0124\3\2\2\2\u0127\u0129\3\2\2\2\u0128"+ - "\u0126\3\2\2\2\u0129\u0138\7\f\2\2\u012a\u012b\7\61\2\2\u012b\u012c\7"+ - "\61\2\2\u012c\u0130\3\2\2\2\u012d\u012f\13\2\2\2\u012e\u012d\3\2\2\2\u012f"+ - "\u0132\3\2\2\2\u0130\u0131\3\2\2\2\u0130\u012e\3\2\2\2\u0131\u0133\3\2"+ - "\2\2\u0132\u0130\3\2\2\2\u0133\u0138\7\17\2\2\u0134\u0135\7\17\2\2\u0135"+ - "\u0138\7\f\2\2\u0136\u0138\t\b\2\2\u0137\u0115\3\2\2\2\u0137\u0120\3\2"+ - "\2\2\u0137\u012a\3\2\2\2\u0137\u0134\3\2\2\2\u0137\u0136\3\2\2\2\u0138"+ - "H\3\2\2\2\f\2\u00e4\u00ee\u00f1\u010b\u0111\u011b\u0126\u0130\u0137\3"+ - "\b\2\2"; - public static final ATN _ATN = - new ATNDeserializer().deserialize(_serializedATN.toCharArray()); - static { - _decisionToDFA = new DFA[_ATN.getNumberOfDecisions()]; - for (int i = 0; i < _ATN.getNumberOfDecisions(); i++) { - _decisionToDFA[i] = new DFA(_ATN.getDecisionState(i), i); - } - } -} \ No newline at end of file diff --git a/jassparser/build/generated-src/com/etheller/warsmash/jassparser/JassParser.java b/jassparser/build/generated-src/com/etheller/warsmash/jassparser/JassParser.java deleted file mode 100644 index 886a399..0000000 --- a/jassparser/build/generated-src/com/etheller/warsmash/jassparser/JassParser.java +++ /dev/null @@ -1,1969 +0,0 @@ -// Generated from Jass.g4 by ANTLR 4.7 - - package com.etheller.interpreter; - -import org.antlr.v4.runtime.atn.*; -import org.antlr.v4.runtime.dfa.DFA; -import org.antlr.v4.runtime.*; -import org.antlr.v4.runtime.misc.*; -import org.antlr.v4.runtime.tree.*; -import java.util.List; -import java.util.Iterator; -import java.util.ArrayList; - -@SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast"}) -public class JassParser extends Parser { - static { RuntimeMetaData.checkVersion("4.7", RuntimeMetaData.VERSION); } - - protected static final DFA[] _decisionToDFA; - protected static final PredictionContextCache _sharedContextCache = - new PredictionContextCache(); - public static final int - T__0=1, T__1=2, T__2=3, T__3=4, T__4=5, EQUALS=6, GLOBALS=7, ENDGLOBALS=8, - NATIVE=9, FUNCTION=10, TAKES=11, RETURNS=12, ENDFUNCTION=13, NOTHING=14, - CALL=15, SET=16, RETURN=17, ARRAY=18, TYPE=19, EXTENDS=20, IF=21, THEN=22, - ELSE=23, ENDIF=24, ELSEIF=25, CONSTANT=26, STRING_LITERAL=27, INTEGER=28, - NULL=29, TRUE=30, FALSE=31, NOT=32, ID=33, WS=34, NEWLINE=35; - public static final int - RULE_program = 0, RULE_typeDefinition = 1, RULE_type = 2, RULE_global = 3, - RULE_assignTail = 4, RULE_expression = 5, RULE_functionExpression = 6, - RULE_argsList = 7, RULE_statement = 8, RULE_ifStatementPartial = 9, RULE_param = 10, - RULE_paramList = 11, RULE_globalsBlock = 12, RULE_typeDefinitionBlock = 13, - RULE_nativeBlock = 14, RULE_block = 15, RULE_functionBlock = 16, RULE_statements = 17, - RULE_newlines = 18, RULE_newlines_opt = 19, RULE_pnewlines = 20; - public static final String[] ruleNames = { - "program", "typeDefinition", "type", "global", "assignTail", "expression", - "functionExpression", "argsList", "statement", "ifStatementPartial", "param", - "paramList", "globalsBlock", "typeDefinitionBlock", "nativeBlock", "block", - "functionBlock", "statements", "newlines", "newlines_opt", "pnewlines" - }; - - private static final String[] _LITERAL_NAMES = { - null, "'['", "']'", "'('", "')'", "','", "'='", "'globals'", "'endglobals'", - "'native'", "'function'", "'takes'", "'returns'", "'endfunction'", "'nothing'", - "'call'", "'set'", "'return'", "'array'", "'type'", "'extends'", "'if'", - "'then'", "'else'", "'endif'", "'elseif'", "'constant'", null, null, "'null'", - "'true'", "'false'", "'not'" - }; - private static final String[] _SYMBOLIC_NAMES = { - null, null, null, null, null, null, "EQUALS", "GLOBALS", "ENDGLOBALS", - "NATIVE", "FUNCTION", "TAKES", "RETURNS", "ENDFUNCTION", "NOTHING", "CALL", - "SET", "RETURN", "ARRAY", "TYPE", "EXTENDS", "IF", "THEN", "ELSE", "ENDIF", - "ELSEIF", "CONSTANT", "STRING_LITERAL", "INTEGER", "NULL", "TRUE", "FALSE", - "NOT", "ID", "WS", "NEWLINE" - }; - public static final Vocabulary VOCABULARY = new VocabularyImpl(_LITERAL_NAMES, _SYMBOLIC_NAMES); - - /** - * @deprecated Use {@link #VOCABULARY} instead. - */ - @Deprecated - public static final String[] tokenNames; - static { - tokenNames = new String[_SYMBOLIC_NAMES.length]; - for (int i = 0; i < tokenNames.length; i++) { - tokenNames[i] = VOCABULARY.getLiteralName(i); - if (tokenNames[i] == null) { - tokenNames[i] = VOCABULARY.getSymbolicName(i); - } - - if (tokenNames[i] == null) { - tokenNames[i] = ""; - } - } - } - - @Override - @Deprecated - public String[] getTokenNames() { - return tokenNames; - } - - @Override - - public Vocabulary getVocabulary() { - return VOCABULARY; - } - - @Override - public String getGrammarFileName() { return "Jass.g4"; } - - @Override - public String[] getRuleNames() { return ruleNames; } - - @Override - public String getSerializedATN() { return _serializedATN; } - - @Override - public ATN getATN() { return _ATN; } - - public JassParser(TokenStream input) { - super(input); - _interp = new ParserATNSimulator(this,_ATN,_decisionToDFA,_sharedContextCache); - } - public static class ProgramContext extends ParserRuleContext { - public NewlinesContext newlines() { - return getRuleContext(NewlinesContext.class,0); - } - public Newlines_optContext newlines_opt() { - return getRuleContext(Newlines_optContext.class,0); - } - public TypeDefinitionBlockContext typeDefinitionBlock() { - return getRuleContext(TypeDefinitionBlockContext.class,0); - } - public List block() { - return getRuleContexts(BlockContext.class); - } - public BlockContext block(int i) { - return getRuleContext(BlockContext.class,i); - } - public List functionBlock() { - return getRuleContexts(FunctionBlockContext.class); - } - public FunctionBlockContext functionBlock(int i) { - return getRuleContext(FunctionBlockContext.class,i); - } - public ProgramContext(ParserRuleContext parent, int invokingState) { - super(parent, invokingState); - } - @Override public int getRuleIndex() { return RULE_program; } - @Override - public T accept(ParseTreeVisitor visitor) { - if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitProgram(this); - else return visitor.visitChildren(this); - } - } - - public final ProgramContext program() throws RecognitionException { - ProgramContext _localctx = new ProgramContext(_ctx, getState()); - enterRule(_localctx, 0, RULE_program); - int _la; - try { - setState(57); - _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,2,_ctx) ) { - case 1: - enterOuterAlt(_localctx, 1); - { - setState(42); - newlines(); - } - break; - case 2: - enterOuterAlt(_localctx, 2); - { - setState(43); - newlines_opt(); - setState(44); - typeDefinitionBlock(); - setState(48); - _errHandler.sync(this); - _la = _input.LA(1); - while ((((_la) & ~0x3f) == 0 && ((1L << _la) & ((1L << GLOBALS) | (1L << NATIVE) | (1L << CONSTANT))) != 0)) { - { - { - setState(45); - block(); - } - } - setState(50); - _errHandler.sync(this); - _la = _input.LA(1); - } - setState(54); - _errHandler.sync(this); - _la = _input.LA(1); - while (_la==FUNCTION) { - { - { - setState(51); - functionBlock(); - } - } - setState(56); - _errHandler.sync(this); - _la = _input.LA(1); - } - } - break; - } - } - catch (RecognitionException re) { - _localctx.exception = re; - _errHandler.reportError(this, re); - _errHandler.recover(this, re); - } - finally { - exitRule(); - } - return _localctx; - } - - public static class TypeDefinitionContext extends ParserRuleContext { - public TerminalNode TYPE() { return getToken(JassParser.TYPE, 0); } - public List ID() { return getTokens(JassParser.ID); } - public TerminalNode ID(int i) { - return getToken(JassParser.ID, i); - } - public TerminalNode EXTENDS() { return getToken(JassParser.EXTENDS, 0); } - public NewlinesContext newlines() { - return getRuleContext(NewlinesContext.class,0); - } - public TypeDefinitionContext(ParserRuleContext parent, int invokingState) { - super(parent, invokingState); - } - @Override public int getRuleIndex() { return RULE_typeDefinition; } - @Override - public T accept(ParseTreeVisitor visitor) { - if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitTypeDefinition(this); - else return visitor.visitChildren(this); - } - } - - public final TypeDefinitionContext typeDefinition() throws RecognitionException { - TypeDefinitionContext _localctx = new TypeDefinitionContext(_ctx, getState()); - enterRule(_localctx, 2, RULE_typeDefinition); - try { - enterOuterAlt(_localctx, 1); - { - setState(59); - match(TYPE); - setState(60); - match(ID); - setState(61); - match(EXTENDS); - setState(62); - match(ID); - setState(63); - newlines(); - } - } - catch (RecognitionException re) { - _localctx.exception = re; - _errHandler.reportError(this, re); - _errHandler.recover(this, re); - } - finally { - exitRule(); - } - return _localctx; - } - - public static class TypeContext extends ParserRuleContext { - public TypeContext(ParserRuleContext parent, int invokingState) { - super(parent, invokingState); - } - @Override public int getRuleIndex() { return RULE_type; } - - public TypeContext() { } - public void copyFrom(TypeContext ctx) { - super.copyFrom(ctx); - } - } - public static class ArrayTypeContext extends TypeContext { - public TerminalNode ID() { return getToken(JassParser.ID, 0); } - public TerminalNode ARRAY() { return getToken(JassParser.ARRAY, 0); } - public ArrayTypeContext(TypeContext ctx) { copyFrom(ctx); } - @Override - public T accept(ParseTreeVisitor visitor) { - if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitArrayType(this); - else return visitor.visitChildren(this); - } - } - public static class BasicTypeContext extends TypeContext { - public TerminalNode ID() { return getToken(JassParser.ID, 0); } - public BasicTypeContext(TypeContext ctx) { copyFrom(ctx); } - @Override - public T accept(ParseTreeVisitor visitor) { - if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitBasicType(this); - else return visitor.visitChildren(this); - } - } - public static class NothingTypeContext extends TypeContext { - public TerminalNode NOTHING() { return getToken(JassParser.NOTHING, 0); } - public NothingTypeContext(TypeContext ctx) { copyFrom(ctx); } - @Override - public T accept(ParseTreeVisitor visitor) { - if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitNothingType(this); - else return visitor.visitChildren(this); - } - } - - public final TypeContext type() throws RecognitionException { - TypeContext _localctx = new TypeContext(_ctx, getState()); - enterRule(_localctx, 4, RULE_type); - try { - setState(69); - _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,3,_ctx) ) { - case 1: - _localctx = new BasicTypeContext(_localctx); - enterOuterAlt(_localctx, 1); - { - setState(65); - match(ID); - } - break; - case 2: - _localctx = new ArrayTypeContext(_localctx); - enterOuterAlt(_localctx, 2); - { - setState(66); - match(ID); - setState(67); - match(ARRAY); - } - break; - case 3: - _localctx = new NothingTypeContext(_localctx); - enterOuterAlt(_localctx, 3); - { - setState(68); - match(NOTHING); - } - break; - } - } - catch (RecognitionException re) { - _localctx.exception = re; - _errHandler.reportError(this, re); - _errHandler.recover(this, re); - } - finally { - exitRule(); - } - return _localctx; - } - - public static class GlobalContext extends ParserRuleContext { - public GlobalContext(ParserRuleContext parent, int invokingState) { - super(parent, invokingState); - } - @Override public int getRuleIndex() { return RULE_global; } - - public GlobalContext() { } - public void copyFrom(GlobalContext ctx) { - super.copyFrom(ctx); - } - } - public static class DefinitionGlobalContext extends GlobalContext { - public TypeContext type() { - return getRuleContext(TypeContext.class,0); - } - public TerminalNode ID() { return getToken(JassParser.ID, 0); } - public AssignTailContext assignTail() { - return getRuleContext(AssignTailContext.class,0); - } - public NewlinesContext newlines() { - return getRuleContext(NewlinesContext.class,0); - } - public TerminalNode CONSTANT() { return getToken(JassParser.CONSTANT, 0); } - public DefinitionGlobalContext(GlobalContext ctx) { copyFrom(ctx); } - @Override - public T accept(ParseTreeVisitor visitor) { - if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitDefinitionGlobal(this); - else return visitor.visitChildren(this); - } - } - public static class BasicGlobalContext extends GlobalContext { - public TypeContext type() { - return getRuleContext(TypeContext.class,0); - } - public TerminalNode ID() { return getToken(JassParser.ID, 0); } - public NewlinesContext newlines() { - return getRuleContext(NewlinesContext.class,0); - } - public TerminalNode CONSTANT() { return getToken(JassParser.CONSTANT, 0); } - public BasicGlobalContext(GlobalContext ctx) { copyFrom(ctx); } - @Override - public T accept(ParseTreeVisitor visitor) { - if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitBasicGlobal(this); - else return visitor.visitChildren(this); - } - } - - public final GlobalContext global() throws RecognitionException { - GlobalContext _localctx = new GlobalContext(_ctx, getState()); - enterRule(_localctx, 6, RULE_global); - int _la; - try { - setState(86); - _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,6,_ctx) ) { - case 1: - _localctx = new BasicGlobalContext(_localctx); - enterOuterAlt(_localctx, 1); - { - setState(72); - _errHandler.sync(this); - _la = _input.LA(1); - if (_la==CONSTANT) { - { - setState(71); - match(CONSTANT); - } - } - - setState(74); - type(); - setState(75); - match(ID); - setState(76); - newlines(); - } - break; - case 2: - _localctx = new DefinitionGlobalContext(_localctx); - enterOuterAlt(_localctx, 2); - { - setState(79); - _errHandler.sync(this); - _la = _input.LA(1); - if (_la==CONSTANT) { - { - setState(78); - match(CONSTANT); - } - } - - setState(81); - type(); - setState(82); - match(ID); - setState(83); - assignTail(); - setState(84); - newlines(); - } - break; - } - } - catch (RecognitionException re) { - _localctx.exception = re; - _errHandler.reportError(this, re); - _errHandler.recover(this, re); - } - finally { - exitRule(); - } - return _localctx; - } - - public static class AssignTailContext extends ParserRuleContext { - public TerminalNode EQUALS() { return getToken(JassParser.EQUALS, 0); } - public ExpressionContext expression() { - return getRuleContext(ExpressionContext.class,0); - } - public AssignTailContext(ParserRuleContext parent, int invokingState) { - super(parent, invokingState); - } - @Override public int getRuleIndex() { return RULE_assignTail; } - @Override - public T accept(ParseTreeVisitor visitor) { - if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitAssignTail(this); - else return visitor.visitChildren(this); - } - } - - public final AssignTailContext assignTail() throws RecognitionException { - AssignTailContext _localctx = new AssignTailContext(_ctx, getState()); - enterRule(_localctx, 8, RULE_assignTail); - try { - enterOuterAlt(_localctx, 1); - { - setState(88); - match(EQUALS); - setState(89); - expression(); - } - } - catch (RecognitionException re) { - _localctx.exception = re; - _errHandler.reportError(this, re); - _errHandler.recover(this, re); - } - finally { - exitRule(); - } - return _localctx; - } - - public static class ExpressionContext extends ParserRuleContext { - public ExpressionContext(ParserRuleContext parent, int invokingState) { - super(parent, invokingState); - } - @Override public int getRuleIndex() { return RULE_expression; } - - public ExpressionContext() { } - public void copyFrom(ExpressionContext ctx) { - super.copyFrom(ctx); - } - } - public static class TrueExpressionContext extends ExpressionContext { - public TerminalNode TRUE() { return getToken(JassParser.TRUE, 0); } - public TrueExpressionContext(ExpressionContext ctx) { copyFrom(ctx); } - @Override - public T accept(ParseTreeVisitor visitor) { - if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitTrueExpression(this); - else return visitor.visitChildren(this); - } - } - public static class ParentheticalExpressionContext extends ExpressionContext { - public ExpressionContext expression() { - return getRuleContext(ExpressionContext.class,0); - } - public ParentheticalExpressionContext(ExpressionContext ctx) { copyFrom(ctx); } - @Override - public T accept(ParseTreeVisitor visitor) { - if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitParentheticalExpression(this); - else return visitor.visitChildren(this); - } - } - public static class StringLiteralExpressionContext extends ExpressionContext { - public TerminalNode STRING_LITERAL() { return getToken(JassParser.STRING_LITERAL, 0); } - public StringLiteralExpressionContext(ExpressionContext ctx) { copyFrom(ctx); } - @Override - public T accept(ParseTreeVisitor visitor) { - if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitStringLiteralExpression(this); - else return visitor.visitChildren(this); - } - } - public static class IntegerLiteralExpressionContext extends ExpressionContext { - public TerminalNode INTEGER() { return getToken(JassParser.INTEGER, 0); } - public IntegerLiteralExpressionContext(ExpressionContext ctx) { copyFrom(ctx); } - @Override - public T accept(ParseTreeVisitor visitor) { - if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitIntegerLiteralExpression(this); - else return visitor.visitChildren(this); - } - } - public static class ReferenceExpressionContext extends ExpressionContext { - public TerminalNode ID() { return getToken(JassParser.ID, 0); } - public ReferenceExpressionContext(ExpressionContext ctx) { copyFrom(ctx); } - @Override - public T accept(ParseTreeVisitor visitor) { - if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitReferenceExpression(this); - else return visitor.visitChildren(this); - } - } - public static class FunctionReferenceExpressionContext extends ExpressionContext { - public TerminalNode FUNCTION() { return getToken(JassParser.FUNCTION, 0); } - public TerminalNode ID() { return getToken(JassParser.ID, 0); } - public FunctionReferenceExpressionContext(ExpressionContext ctx) { copyFrom(ctx); } - @Override - public T accept(ParseTreeVisitor visitor) { - if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitFunctionReferenceExpression(this); - else return visitor.visitChildren(this); - } - } - public static class NotExpressionContext extends ExpressionContext { - public TerminalNode NOT() { return getToken(JassParser.NOT, 0); } - public ExpressionContext expression() { - return getRuleContext(ExpressionContext.class,0); - } - public NotExpressionContext(ExpressionContext ctx) { copyFrom(ctx); } - @Override - public T accept(ParseTreeVisitor visitor) { - if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitNotExpression(this); - else return visitor.visitChildren(this); - } - } - public static class ArrayReferenceExpressionContext extends ExpressionContext { - public TerminalNode ID() { return getToken(JassParser.ID, 0); } - public ExpressionContext expression() { - return getRuleContext(ExpressionContext.class,0); - } - public ArrayReferenceExpressionContext(ExpressionContext ctx) { copyFrom(ctx); } - @Override - public T accept(ParseTreeVisitor visitor) { - if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitArrayReferenceExpression(this); - else return visitor.visitChildren(this); - } - } - public static class FunctionCallExpressionContext extends ExpressionContext { - public FunctionExpressionContext functionExpression() { - return getRuleContext(FunctionExpressionContext.class,0); - } - public FunctionCallExpressionContext(ExpressionContext ctx) { copyFrom(ctx); } - @Override - public T accept(ParseTreeVisitor visitor) { - if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitFunctionCallExpression(this); - else return visitor.visitChildren(this); - } - } - public static class NullExpressionContext extends ExpressionContext { - public TerminalNode NULL() { return getToken(JassParser.NULL, 0); } - public NullExpressionContext(ExpressionContext ctx) { copyFrom(ctx); } - @Override - public T accept(ParseTreeVisitor visitor) { - if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitNullExpression(this); - else return visitor.visitChildren(this); - } - } - public static class FalseExpressionContext extends ExpressionContext { - public TerminalNode FALSE() { return getToken(JassParser.FALSE, 0); } - public FalseExpressionContext(ExpressionContext ctx) { copyFrom(ctx); } - @Override - public T accept(ParseTreeVisitor visitor) { - if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitFalseExpression(this); - else return visitor.visitChildren(this); - } - } - - public final ExpressionContext expression() throws RecognitionException { - ExpressionContext _localctx = new ExpressionContext(_ctx, getState()); - enterRule(_localctx, 10, RULE_expression); - try { - setState(111); - _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,7,_ctx) ) { - case 1: - _localctx = new ReferenceExpressionContext(_localctx); - enterOuterAlt(_localctx, 1); - { - setState(91); - match(ID); - } - break; - case 2: - _localctx = new StringLiteralExpressionContext(_localctx); - enterOuterAlt(_localctx, 2); - { - setState(92); - match(STRING_LITERAL); - } - break; - case 3: - _localctx = new IntegerLiteralExpressionContext(_localctx); - enterOuterAlt(_localctx, 3); - { - setState(93); - match(INTEGER); - } - break; - case 4: - _localctx = new FunctionReferenceExpressionContext(_localctx); - enterOuterAlt(_localctx, 4); - { - setState(94); - match(FUNCTION); - setState(95); - match(ID); - } - break; - case 5: - _localctx = new NullExpressionContext(_localctx); - enterOuterAlt(_localctx, 5); - { - setState(96); - match(NULL); - } - break; - case 6: - _localctx = new TrueExpressionContext(_localctx); - enterOuterAlt(_localctx, 6); - { - setState(97); - match(TRUE); - } - break; - case 7: - _localctx = new FalseExpressionContext(_localctx); - enterOuterAlt(_localctx, 7); - { - setState(98); - match(FALSE); - } - break; - case 8: - _localctx = new ArrayReferenceExpressionContext(_localctx); - enterOuterAlt(_localctx, 8); - { - setState(99); - match(ID); - setState(100); - match(T__0); - setState(101); - expression(); - setState(102); - match(T__1); - } - break; - case 9: - _localctx = new FunctionCallExpressionContext(_localctx); - enterOuterAlt(_localctx, 9); - { - setState(104); - functionExpression(); - } - break; - case 10: - _localctx = new ParentheticalExpressionContext(_localctx); - enterOuterAlt(_localctx, 10); - { - setState(105); - match(T__2); - setState(106); - expression(); - setState(107); - match(T__3); - } - break; - case 11: - _localctx = new NotExpressionContext(_localctx); - enterOuterAlt(_localctx, 11); - { - setState(109); - match(NOT); - setState(110); - expression(); - } - break; - } - } - catch (RecognitionException re) { - _localctx.exception = re; - _errHandler.reportError(this, re); - _errHandler.recover(this, re); - } - finally { - exitRule(); - } - return _localctx; - } - - public static class FunctionExpressionContext extends ParserRuleContext { - public TerminalNode ID() { return getToken(JassParser.ID, 0); } - public ArgsListContext argsList() { - return getRuleContext(ArgsListContext.class,0); - } - public FunctionExpressionContext(ParserRuleContext parent, int invokingState) { - super(parent, invokingState); - } - @Override public int getRuleIndex() { return RULE_functionExpression; } - @Override - public T accept(ParseTreeVisitor visitor) { - if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitFunctionExpression(this); - else return visitor.visitChildren(this); - } - } - - public final FunctionExpressionContext functionExpression() throws RecognitionException { - FunctionExpressionContext _localctx = new FunctionExpressionContext(_ctx, getState()); - enterRule(_localctx, 12, RULE_functionExpression); - try { - setState(121); - _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,8,_ctx) ) { - case 1: - enterOuterAlt(_localctx, 1); - { - setState(113); - match(ID); - setState(114); - match(T__2); - setState(115); - argsList(); - setState(116); - match(T__3); - } - break; - case 2: - enterOuterAlt(_localctx, 2); - { - setState(118); - match(ID); - setState(119); - match(T__2); - setState(120); - match(T__3); - } - break; - } - } - catch (RecognitionException re) { - _localctx.exception = re; - _errHandler.reportError(this, re); - _errHandler.recover(this, re); - } - finally { - exitRule(); - } - return _localctx; - } - - public static class ArgsListContext extends ParserRuleContext { - public ArgsListContext(ParserRuleContext parent, int invokingState) { - super(parent, invokingState); - } - @Override public int getRuleIndex() { return RULE_argsList; } - - public ArgsListContext() { } - public void copyFrom(ArgsListContext ctx) { - super.copyFrom(ctx); - } - } - public static class SingleArgumentContext extends ArgsListContext { - public ExpressionContext expression() { - return getRuleContext(ExpressionContext.class,0); - } - public SingleArgumentContext(ArgsListContext ctx) { copyFrom(ctx); } - @Override - public T accept(ParseTreeVisitor visitor) { - if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitSingleArgument(this); - else return visitor.visitChildren(this); - } - } - public static class ListArgumentContext extends ArgsListContext { - public ExpressionContext expression() { - return getRuleContext(ExpressionContext.class,0); - } - public ArgsListContext argsList() { - return getRuleContext(ArgsListContext.class,0); - } - public ListArgumentContext(ArgsListContext ctx) { copyFrom(ctx); } - @Override - public T accept(ParseTreeVisitor visitor) { - if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitListArgument(this); - else return visitor.visitChildren(this); - } - } - - public final ArgsListContext argsList() throws RecognitionException { - ArgsListContext _localctx = new ArgsListContext(_ctx, getState()); - enterRule(_localctx, 14, RULE_argsList); - try { - setState(128); - _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,9,_ctx) ) { - case 1: - _localctx = new SingleArgumentContext(_localctx); - enterOuterAlt(_localctx, 1); - { - setState(123); - expression(); - } - break; - case 2: - _localctx = new ListArgumentContext(_localctx); - enterOuterAlt(_localctx, 2); - { - setState(124); - expression(); - setState(125); - match(T__4); - setState(126); - argsList(); - } - break; - } - } - catch (RecognitionException re) { - _localctx.exception = re; - _errHandler.reportError(this, re); - _errHandler.recover(this, re); - } - finally { - exitRule(); - } - return _localctx; - } - - public static class StatementContext extends ParserRuleContext { - public StatementContext(ParserRuleContext parent, int invokingState) { - super(parent, invokingState); - } - @Override public int getRuleIndex() { return RULE_statement; } - - public StatementContext() { } - public void copyFrom(StatementContext ctx) { - super.copyFrom(ctx); - } - } - public static class ArrayedAssignmentStatementContext extends StatementContext { - public TerminalNode SET() { return getToken(JassParser.SET, 0); } - public TerminalNode ID() { return getToken(JassParser.ID, 0); } - public List expression() { - return getRuleContexts(ExpressionContext.class); - } - public ExpressionContext expression(int i) { - return getRuleContext(ExpressionContext.class,i); - } - public TerminalNode EQUALS() { return getToken(JassParser.EQUALS, 0); } - public NewlinesContext newlines() { - return getRuleContext(NewlinesContext.class,0); - } - public ArrayedAssignmentStatementContext(StatementContext ctx) { copyFrom(ctx); } - @Override - public T accept(ParseTreeVisitor visitor) { - if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitArrayedAssignmentStatement(this); - else return visitor.visitChildren(this); - } - } - public static class IfStatementContext extends StatementContext { - public TerminalNode IF() { return getToken(JassParser.IF, 0); } - public IfStatementPartialContext ifStatementPartial() { - return getRuleContext(IfStatementPartialContext.class,0); - } - public IfStatementContext(StatementContext ctx) { copyFrom(ctx); } - @Override - public T accept(ParseTreeVisitor visitor) { - if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitIfStatement(this); - else return visitor.visitChildren(this); - } - } - public static class ReturnStatementContext extends StatementContext { - public TerminalNode RETURN() { return getToken(JassParser.RETURN, 0); } - public ExpressionContext expression() { - return getRuleContext(ExpressionContext.class,0); - } - public NewlinesContext newlines() { - return getRuleContext(NewlinesContext.class,0); - } - public ReturnStatementContext(StatementContext ctx) { copyFrom(ctx); } - @Override - public T accept(ParseTreeVisitor visitor) { - if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitReturnStatement(this); - else return visitor.visitChildren(this); - } - } - public static class CallStatementContext extends StatementContext { - public TerminalNode CALL() { return getToken(JassParser.CALL, 0); } - public FunctionExpressionContext functionExpression() { - return getRuleContext(FunctionExpressionContext.class,0); - } - public NewlinesContext newlines() { - return getRuleContext(NewlinesContext.class,0); - } - public CallStatementContext(StatementContext ctx) { copyFrom(ctx); } - @Override - public T accept(ParseTreeVisitor visitor) { - if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitCallStatement(this); - else return visitor.visitChildren(this); - } - } - public static class SetStatementContext extends StatementContext { - public TerminalNode SET() { return getToken(JassParser.SET, 0); } - public TerminalNode ID() { return getToken(JassParser.ID, 0); } - public TerminalNode EQUALS() { return getToken(JassParser.EQUALS, 0); } - public ExpressionContext expression() { - return getRuleContext(ExpressionContext.class,0); - } - public NewlinesContext newlines() { - return getRuleContext(NewlinesContext.class,0); - } - public SetStatementContext(StatementContext ctx) { copyFrom(ctx); } - @Override - public T accept(ParseTreeVisitor visitor) { - if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitSetStatement(this); - else return visitor.visitChildren(this); - } - } - - public final StatementContext statement() throws RecognitionException { - StatementContext _localctx = new StatementContext(_ctx, getState()); - enterRule(_localctx, 16, RULE_statement); - try { - setState(155); - _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,10,_ctx) ) { - case 1: - _localctx = new CallStatementContext(_localctx); - enterOuterAlt(_localctx, 1); - { - setState(130); - match(CALL); - setState(131); - functionExpression(); - setState(132); - newlines(); - } - break; - case 2: - _localctx = new SetStatementContext(_localctx); - enterOuterAlt(_localctx, 2); - { - setState(134); - match(SET); - setState(135); - match(ID); - setState(136); - match(EQUALS); - setState(137); - expression(); - setState(138); - newlines(); - } - break; - case 3: - _localctx = new ArrayedAssignmentStatementContext(_localctx); - enterOuterAlt(_localctx, 3); - { - setState(140); - match(SET); - setState(141); - match(ID); - setState(142); - match(T__0); - setState(143); - expression(); - setState(144); - match(T__1); - setState(145); - match(EQUALS); - setState(146); - expression(); - setState(147); - newlines(); - } - break; - case 4: - _localctx = new ReturnStatementContext(_localctx); - enterOuterAlt(_localctx, 4); - { - setState(149); - match(RETURN); - setState(150); - expression(); - setState(151); - newlines(); - } - break; - case 5: - _localctx = new IfStatementContext(_localctx); - enterOuterAlt(_localctx, 5); - { - setState(153); - match(IF); - setState(154); - ifStatementPartial(); - } - break; - } - } - catch (RecognitionException re) { - _localctx.exception = re; - _errHandler.reportError(this, re); - _errHandler.recover(this, re); - } - finally { - exitRule(); - } - return _localctx; - } - - public static class IfStatementPartialContext extends ParserRuleContext { - public IfStatementPartialContext(ParserRuleContext parent, int invokingState) { - super(parent, invokingState); - } - @Override public int getRuleIndex() { return RULE_ifStatementPartial; } - - public IfStatementPartialContext() { } - public void copyFrom(IfStatementPartialContext ctx) { - super.copyFrom(ctx); - } - } - public static class IfElseIfStatementContext extends IfStatementPartialContext { - public ExpressionContext expression() { - return getRuleContext(ExpressionContext.class,0); - } - public TerminalNode THEN() { return getToken(JassParser.THEN, 0); } - public NewlinesContext newlines() { - return getRuleContext(NewlinesContext.class,0); - } - public StatementsContext statements() { - return getRuleContext(StatementsContext.class,0); - } - public TerminalNode ELSEIF() { return getToken(JassParser.ELSEIF, 0); } - public IfStatementPartialContext ifStatementPartial() { - return getRuleContext(IfStatementPartialContext.class,0); - } - public IfElseIfStatementContext(IfStatementPartialContext ctx) { copyFrom(ctx); } - @Override - public T accept(ParseTreeVisitor visitor) { - if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitIfElseIfStatement(this); - else return visitor.visitChildren(this); - } - } - public static class IfElseStatementContext extends IfStatementPartialContext { - public ExpressionContext expression() { - return getRuleContext(ExpressionContext.class,0); - } - public TerminalNode THEN() { return getToken(JassParser.THEN, 0); } - public List newlines() { - return getRuleContexts(NewlinesContext.class); - } - public NewlinesContext newlines(int i) { - return getRuleContext(NewlinesContext.class,i); - } - public List statements() { - return getRuleContexts(StatementsContext.class); - } - public StatementsContext statements(int i) { - return getRuleContext(StatementsContext.class,i); - } - public TerminalNode ELSE() { return getToken(JassParser.ELSE, 0); } - public TerminalNode ENDIF() { return getToken(JassParser.ENDIF, 0); } - public IfElseStatementContext(IfStatementPartialContext ctx) { copyFrom(ctx); } - @Override - public T accept(ParseTreeVisitor visitor) { - if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitIfElseStatement(this); - else return visitor.visitChildren(this); - } - } - public static class SimpleIfStatementContext extends IfStatementPartialContext { - public ExpressionContext expression() { - return getRuleContext(ExpressionContext.class,0); - } - public TerminalNode THEN() { return getToken(JassParser.THEN, 0); } - public List newlines() { - return getRuleContexts(NewlinesContext.class); - } - public NewlinesContext newlines(int i) { - return getRuleContext(NewlinesContext.class,i); - } - public StatementsContext statements() { - return getRuleContext(StatementsContext.class,0); - } - public TerminalNode ENDIF() { return getToken(JassParser.ENDIF, 0); } - public SimpleIfStatementContext(IfStatementPartialContext ctx) { copyFrom(ctx); } - @Override - public T accept(ParseTreeVisitor visitor) { - if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitSimpleIfStatement(this); - else return visitor.visitChildren(this); - } - } - - public final IfStatementPartialContext ifStatementPartial() throws RecognitionException { - IfStatementPartialContext _localctx = new IfStatementPartialContext(_ctx, getState()); - enterRule(_localctx, 18, RULE_ifStatementPartial); - try { - setState(181); - _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,11,_ctx) ) { - case 1: - _localctx = new SimpleIfStatementContext(_localctx); - enterOuterAlt(_localctx, 1); - { - setState(157); - expression(); - setState(158); - match(THEN); - setState(159); - newlines(); - setState(160); - statements(); - setState(161); - match(ENDIF); - setState(162); - newlines(); - } - break; - case 2: - _localctx = new IfElseStatementContext(_localctx); - enterOuterAlt(_localctx, 2); - { - setState(164); - expression(); - setState(165); - match(THEN); - setState(166); - newlines(); - setState(167); - statements(); - setState(168); - match(ELSE); - setState(169); - newlines(); - setState(170); - statements(); - setState(171); - match(ENDIF); - setState(172); - newlines(); - } - break; - case 3: - _localctx = new IfElseIfStatementContext(_localctx); - enterOuterAlt(_localctx, 3); - { - setState(174); - expression(); - setState(175); - match(THEN); - setState(176); - newlines(); - setState(177); - statements(); - setState(178); - match(ELSEIF); - setState(179); - ifStatementPartial(); - } - break; - } - } - catch (RecognitionException re) { - _localctx.exception = re; - _errHandler.reportError(this, re); - _errHandler.recover(this, re); - } - finally { - exitRule(); - } - return _localctx; - } - - public static class ParamContext extends ParserRuleContext { - public TypeContext type() { - return getRuleContext(TypeContext.class,0); - } - public TerminalNode ID() { return getToken(JassParser.ID, 0); } - public ParamContext(ParserRuleContext parent, int invokingState) { - super(parent, invokingState); - } - @Override public int getRuleIndex() { return RULE_param; } - @Override - public T accept(ParseTreeVisitor visitor) { - if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitParam(this); - else return visitor.visitChildren(this); - } - } - - public final ParamContext param() throws RecognitionException { - ParamContext _localctx = new ParamContext(_ctx, getState()); - enterRule(_localctx, 20, RULE_param); - try { - enterOuterAlt(_localctx, 1); - { - setState(183); - type(); - setState(184); - match(ID); - } - } - catch (RecognitionException re) { - _localctx.exception = re; - _errHandler.reportError(this, re); - _errHandler.recover(this, re); - } - finally { - exitRule(); - } - return _localctx; - } - - public static class ParamListContext extends ParserRuleContext { - public ParamListContext(ParserRuleContext parent, int invokingState) { - super(parent, invokingState); - } - @Override public int getRuleIndex() { return RULE_paramList; } - - public ParamListContext() { } - public void copyFrom(ParamListContext ctx) { - super.copyFrom(ctx); - } - } - public static class NothingParameterContext extends ParamListContext { - public TerminalNode NOTHING() { return getToken(JassParser.NOTHING, 0); } - public NothingParameterContext(ParamListContext ctx) { copyFrom(ctx); } - @Override - public T accept(ParseTreeVisitor visitor) { - if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitNothingParameter(this); - else return visitor.visitChildren(this); - } - } - public static class SingleParameterContext extends ParamListContext { - public ParamContext param() { - return getRuleContext(ParamContext.class,0); - } - public SingleParameterContext(ParamListContext ctx) { copyFrom(ctx); } - @Override - public T accept(ParseTreeVisitor visitor) { - if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitSingleParameter(this); - else return visitor.visitChildren(this); - } - } - public static class ListParameterContext extends ParamListContext { - public ParamContext param() { - return getRuleContext(ParamContext.class,0); - } - public ParamListContext paramList() { - return getRuleContext(ParamListContext.class,0); - } - public ListParameterContext(ParamListContext ctx) { copyFrom(ctx); } - @Override - public T accept(ParseTreeVisitor visitor) { - if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitListParameter(this); - else return visitor.visitChildren(this); - } - } - - public final ParamListContext paramList() throws RecognitionException { - ParamListContext _localctx = new ParamListContext(_ctx, getState()); - enterRule(_localctx, 22, RULE_paramList); - try { - setState(192); - _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,12,_ctx) ) { - case 1: - _localctx = new SingleParameterContext(_localctx); - enterOuterAlt(_localctx, 1); - { - setState(186); - param(); - } - break; - case 2: - _localctx = new ListParameterContext(_localctx); - enterOuterAlt(_localctx, 2); - { - setState(187); - param(); - setState(188); - match(T__4); - setState(189); - paramList(); - } - break; - case 3: - _localctx = new NothingParameterContext(_localctx); - enterOuterAlt(_localctx, 3); - { - setState(191); - match(NOTHING); - } - break; - } - } - catch (RecognitionException re) { - _localctx.exception = re; - _errHandler.reportError(this, re); - _errHandler.recover(this, re); - } - finally { - exitRule(); - } - return _localctx; - } - - public static class GlobalsBlockContext extends ParserRuleContext { - public TerminalNode GLOBALS() { return getToken(JassParser.GLOBALS, 0); } - public List newlines() { - return getRuleContexts(NewlinesContext.class); - } - public NewlinesContext newlines(int i) { - return getRuleContext(NewlinesContext.class,i); - } - public TerminalNode ENDGLOBALS() { return getToken(JassParser.ENDGLOBALS, 0); } - public List global() { - return getRuleContexts(GlobalContext.class); - } - public GlobalContext global(int i) { - return getRuleContext(GlobalContext.class,i); - } - public GlobalsBlockContext(ParserRuleContext parent, int invokingState) { - super(parent, invokingState); - } - @Override public int getRuleIndex() { return RULE_globalsBlock; } - @Override - public T accept(ParseTreeVisitor visitor) { - if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitGlobalsBlock(this); - else return visitor.visitChildren(this); - } - } - - public final GlobalsBlockContext globalsBlock() throws RecognitionException { - GlobalsBlockContext _localctx = new GlobalsBlockContext(_ctx, getState()); - enterRule(_localctx, 24, RULE_globalsBlock); - int _la; - try { - enterOuterAlt(_localctx, 1); - { - setState(194); - match(GLOBALS); - setState(195); - newlines(); - setState(199); - _errHandler.sync(this); - _la = _input.LA(1); - while ((((_la) & ~0x3f) == 0 && ((1L << _la) & ((1L << NOTHING) | (1L << CONSTANT) | (1L << ID))) != 0)) { - { - { - setState(196); - global(); - } - } - setState(201); - _errHandler.sync(this); - _la = _input.LA(1); - } - setState(202); - match(ENDGLOBALS); - setState(203); - newlines(); - } - } - catch (RecognitionException re) { - _localctx.exception = re; - _errHandler.reportError(this, re); - _errHandler.recover(this, re); - } - finally { - exitRule(); - } - return _localctx; - } - - public static class TypeDefinitionBlockContext extends ParserRuleContext { - public List typeDefinition() { - return getRuleContexts(TypeDefinitionContext.class); - } - public TypeDefinitionContext typeDefinition(int i) { - return getRuleContext(TypeDefinitionContext.class,i); - } - public TypeDefinitionBlockContext(ParserRuleContext parent, int invokingState) { - super(parent, invokingState); - } - @Override public int getRuleIndex() { return RULE_typeDefinitionBlock; } - @Override - public T accept(ParseTreeVisitor visitor) { - if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitTypeDefinitionBlock(this); - else return visitor.visitChildren(this); - } - } - - public final TypeDefinitionBlockContext typeDefinitionBlock() throws RecognitionException { - TypeDefinitionBlockContext _localctx = new TypeDefinitionBlockContext(_ctx, getState()); - enterRule(_localctx, 26, RULE_typeDefinitionBlock); - int _la; - try { - enterOuterAlt(_localctx, 1); - { - setState(208); - _errHandler.sync(this); - _la = _input.LA(1); - while (_la==TYPE) { - { - { - setState(205); - typeDefinition(); - } - } - setState(210); - _errHandler.sync(this); - _la = _input.LA(1); - } - } - } - catch (RecognitionException re) { - _localctx.exception = re; - _errHandler.reportError(this, re); - _errHandler.recover(this, re); - } - finally { - exitRule(); - } - return _localctx; - } - - public static class NativeBlockContext extends ParserRuleContext { - public TerminalNode NATIVE() { return getToken(JassParser.NATIVE, 0); } - public TerminalNode ID() { return getToken(JassParser.ID, 0); } - public TerminalNode TAKES() { return getToken(JassParser.TAKES, 0); } - public ParamListContext paramList() { - return getRuleContext(ParamListContext.class,0); - } - public TerminalNode RETURNS() { return getToken(JassParser.RETURNS, 0); } - public TypeContext type() { - return getRuleContext(TypeContext.class,0); - } - public NewlinesContext newlines() { - return getRuleContext(NewlinesContext.class,0); - } - public TerminalNode CONSTANT() { return getToken(JassParser.CONSTANT, 0); } - public NativeBlockContext(ParserRuleContext parent, int invokingState) { - super(parent, invokingState); - } - @Override public int getRuleIndex() { return RULE_nativeBlock; } - @Override - public T accept(ParseTreeVisitor visitor) { - if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitNativeBlock(this); - else return visitor.visitChildren(this); - } - } - - public final NativeBlockContext nativeBlock() throws RecognitionException { - NativeBlockContext _localctx = new NativeBlockContext(_ctx, getState()); - enterRule(_localctx, 28, RULE_nativeBlock); - int _la; - try { - enterOuterAlt(_localctx, 1); - { - setState(212); - _errHandler.sync(this); - _la = _input.LA(1); - if (_la==CONSTANT) { - { - setState(211); - match(CONSTANT); - } - } - - setState(214); - match(NATIVE); - setState(215); - match(ID); - setState(216); - match(TAKES); - setState(217); - paramList(); - setState(218); - match(RETURNS); - setState(219); - type(); - setState(220); - newlines(); - } - } - catch (RecognitionException re) { - _localctx.exception = re; - _errHandler.reportError(this, re); - _errHandler.recover(this, re); - } - finally { - exitRule(); - } - return _localctx; - } - - public static class BlockContext extends ParserRuleContext { - public GlobalsBlockContext globalsBlock() { - return getRuleContext(GlobalsBlockContext.class,0); - } - public NativeBlockContext nativeBlock() { - return getRuleContext(NativeBlockContext.class,0); - } - public BlockContext(ParserRuleContext parent, int invokingState) { - super(parent, invokingState); - } - @Override public int getRuleIndex() { return RULE_block; } - @Override - public T accept(ParseTreeVisitor visitor) { - if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitBlock(this); - else return visitor.visitChildren(this); - } - } - - public final BlockContext block() throws RecognitionException { - BlockContext _localctx = new BlockContext(_ctx, getState()); - enterRule(_localctx, 30, RULE_block); - try { - setState(224); - _errHandler.sync(this); - switch (_input.LA(1)) { - case GLOBALS: - enterOuterAlt(_localctx, 1); - { - setState(222); - globalsBlock(); - } - break; - case NATIVE: - case CONSTANT: - enterOuterAlt(_localctx, 2); - { - setState(223); - nativeBlock(); - } - break; - default: - throw new NoViableAltException(this); - } - } - catch (RecognitionException re) { - _localctx.exception = re; - _errHandler.reportError(this, re); - _errHandler.recover(this, re); - } - finally { - exitRule(); - } - return _localctx; - } - - public static class FunctionBlockContext extends ParserRuleContext { - public TerminalNode FUNCTION() { return getToken(JassParser.FUNCTION, 0); } - public TerminalNode ID() { return getToken(JassParser.ID, 0); } - public TerminalNode TAKES() { return getToken(JassParser.TAKES, 0); } - public ParamListContext paramList() { - return getRuleContext(ParamListContext.class,0); - } - public TerminalNode RETURNS() { return getToken(JassParser.RETURNS, 0); } - public TypeContext type() { - return getRuleContext(TypeContext.class,0); - } - public List newlines() { - return getRuleContexts(NewlinesContext.class); - } - public NewlinesContext newlines(int i) { - return getRuleContext(NewlinesContext.class,i); - } - public StatementsContext statements() { - return getRuleContext(StatementsContext.class,0); - } - public TerminalNode ENDFUNCTION() { return getToken(JassParser.ENDFUNCTION, 0); } - public FunctionBlockContext(ParserRuleContext parent, int invokingState) { - super(parent, invokingState); - } - @Override public int getRuleIndex() { return RULE_functionBlock; } - @Override - public T accept(ParseTreeVisitor visitor) { - if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitFunctionBlock(this); - else return visitor.visitChildren(this); - } - } - - public final FunctionBlockContext functionBlock() throws RecognitionException { - FunctionBlockContext _localctx = new FunctionBlockContext(_ctx, getState()); - enterRule(_localctx, 32, RULE_functionBlock); - try { - enterOuterAlt(_localctx, 1); - { - setState(226); - match(FUNCTION); - setState(227); - match(ID); - setState(228); - match(TAKES); - setState(229); - paramList(); - setState(230); - match(RETURNS); - setState(231); - type(); - setState(232); - newlines(); - setState(233); - statements(); - setState(234); - match(ENDFUNCTION); - setState(235); - newlines(); - } - } - catch (RecognitionException re) { - _localctx.exception = re; - _errHandler.reportError(this, re); - _errHandler.recover(this, re); - } - finally { - exitRule(); - } - return _localctx; - } - - public static class StatementsContext extends ParserRuleContext { - public List statement() { - return getRuleContexts(StatementContext.class); - } - public StatementContext statement(int i) { - return getRuleContext(StatementContext.class,i); - } - public StatementsContext(ParserRuleContext parent, int invokingState) { - super(parent, invokingState); - } - @Override public int getRuleIndex() { return RULE_statements; } - @Override - public T accept(ParseTreeVisitor visitor) { - if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitStatements(this); - else return visitor.visitChildren(this); - } - } - - public final StatementsContext statements() throws RecognitionException { - StatementsContext _localctx = new StatementsContext(_ctx, getState()); - enterRule(_localctx, 34, RULE_statements); - int _la; - try { - enterOuterAlt(_localctx, 1); - { - setState(240); - _errHandler.sync(this); - _la = _input.LA(1); - while ((((_la) & ~0x3f) == 0 && ((1L << _la) & ((1L << CALL) | (1L << SET) | (1L << RETURN) | (1L << IF))) != 0)) { - { - { - setState(237); - statement(); - } - } - setState(242); - _errHandler.sync(this); - _la = _input.LA(1); - } - } - } - catch (RecognitionException re) { - _localctx.exception = re; - _errHandler.reportError(this, re); - _errHandler.recover(this, re); - } - finally { - exitRule(); - } - return _localctx; - } - - public static class NewlinesContext extends ParserRuleContext { - public PnewlinesContext pnewlines() { - return getRuleContext(PnewlinesContext.class,0); - } - public TerminalNode EOF() { return getToken(JassParser.EOF, 0); } - public NewlinesContext(ParserRuleContext parent, int invokingState) { - super(parent, invokingState); - } - @Override public int getRuleIndex() { return RULE_newlines; } - @Override - public T accept(ParseTreeVisitor visitor) { - if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitNewlines(this); - else return visitor.visitChildren(this); - } - } - - public final NewlinesContext newlines() throws RecognitionException { - NewlinesContext _localctx = new NewlinesContext(_ctx, getState()); - enterRule(_localctx, 36, RULE_newlines); - try { - setState(245); - _errHandler.sync(this); - switch (_input.LA(1)) { - case NEWLINE: - enterOuterAlt(_localctx, 1); - { - setState(243); - pnewlines(); - } - break; - case EOF: - enterOuterAlt(_localctx, 2); - { - setState(244); - match(EOF); - } - break; - default: - throw new NoViableAltException(this); - } - } - catch (RecognitionException re) { - _localctx.exception = re; - _errHandler.reportError(this, re); - _errHandler.recover(this, re); - } - finally { - exitRule(); - } - return _localctx; - } - - public static class Newlines_optContext extends ParserRuleContext { - public PnewlinesContext pnewlines() { - return getRuleContext(PnewlinesContext.class,0); - } - public TerminalNode EOF() { return getToken(JassParser.EOF, 0); } - public Newlines_optContext(ParserRuleContext parent, int invokingState) { - super(parent, invokingState); - } - @Override public int getRuleIndex() { return RULE_newlines_opt; } - @Override - public T accept(ParseTreeVisitor visitor) { - if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitNewlines_opt(this); - else return visitor.visitChildren(this); - } - } - - public final Newlines_optContext newlines_opt() throws RecognitionException { - Newlines_optContext _localctx = new Newlines_optContext(_ctx, getState()); - enterRule(_localctx, 38, RULE_newlines_opt); - try { - setState(250); - _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,19,_ctx) ) { - case 1: - enterOuterAlt(_localctx, 1); - { - setState(247); - pnewlines(); - } - break; - case 2: - enterOuterAlt(_localctx, 2); - { - setState(248); - match(EOF); - } - break; - case 3: - enterOuterAlt(_localctx, 3); - { - } - break; - } - } - catch (RecognitionException re) { - _localctx.exception = re; - _errHandler.reportError(this, re); - _errHandler.recover(this, re); - } - finally { - exitRule(); - } - return _localctx; - } - - public static class PnewlinesContext extends ParserRuleContext { - public TerminalNode NEWLINE() { return getToken(JassParser.NEWLINE, 0); } - public NewlinesContext newlines() { - return getRuleContext(NewlinesContext.class,0); - } - public PnewlinesContext(ParserRuleContext parent, int invokingState) { - super(parent, invokingState); - } - @Override public int getRuleIndex() { return RULE_pnewlines; } - @Override - public T accept(ParseTreeVisitor visitor) { - if ( visitor instanceof JassVisitor ) return ((JassVisitor)visitor).visitPnewlines(this); - else return visitor.visitChildren(this); - } - } - - public final PnewlinesContext pnewlines() throws RecognitionException { - PnewlinesContext _localctx = new PnewlinesContext(_ctx, getState()); - enterRule(_localctx, 40, RULE_pnewlines); - try { - setState(255); - _errHandler.sync(this); - switch ( getInterpreter().adaptivePredict(_input,20,_ctx) ) { - case 1: - enterOuterAlt(_localctx, 1); - { - setState(252); - match(NEWLINE); - } - break; - case 2: - enterOuterAlt(_localctx, 2); - { - setState(253); - match(NEWLINE); - setState(254); - newlines(); - } - break; - } - } - catch (RecognitionException re) { - _localctx.exception = re; - _errHandler.reportError(this, re); - _errHandler.recover(this, re); - } - finally { - exitRule(); - } - return _localctx; - } - - public static final String _serializedATN = - "\3\u608b\ua72a\u8133\ub9ed\u417c\u3be7\u7786\u5964\3%\u0104\4\2\t\2\4"+ - "\3\t\3\4\4\t\4\4\5\t\5\4\6\t\6\4\7\t\7\4\b\t\b\4\t\t\t\4\n\t\n\4\13\t"+ - "\13\4\f\t\f\4\r\t\r\4\16\t\16\4\17\t\17\4\20\t\20\4\21\t\21\4\22\t\22"+ - "\4\23\t\23\4\24\t\24\4\25\t\25\4\26\t\26\3\2\3\2\3\2\3\2\7\2\61\n\2\f"+ - "\2\16\2\64\13\2\3\2\7\2\67\n\2\f\2\16\2:\13\2\5\2<\n\2\3\3\3\3\3\3\3\3"+ - "\3\3\3\3\3\4\3\4\3\4\3\4\5\4H\n\4\3\5\5\5K\n\5\3\5\3\5\3\5\3\5\3\5\5\5"+ - "R\n\5\3\5\3\5\3\5\3\5\3\5\5\5Y\n\5\3\6\3\6\3\6\3\7\3\7\3\7\3\7\3\7\3\7"+ - "\3\7\3\7\3\7\3\7\3\7\3\7\3\7\3\7\3\7\3\7\3\7\3\7\3\7\3\7\5\7r\n\7\3\b"+ - "\3\b\3\b\3\b\3\b\3\b\3\b\3\b\5\b|\n\b\3\t\3\t\3\t\3\t\3\t\5\t\u0083\n"+ - "\t\3\n\3\n\3\n\3\n\3\n\3\n\3\n\3\n\3\n\3\n\3\n\3\n\3\n\3\n\3\n\3\n\3\n"+ - "\3\n\3\n\3\n\3\n\3\n\3\n\3\n\3\n\5\n\u009e\n\n\3\13\3\13\3\13\3\13\3\13"+ - "\3\13\3\13\3\13\3\13\3\13\3\13\3\13\3\13\3\13\3\13\3\13\3\13\3\13\3\13"+ - "\3\13\3\13\3\13\3\13\3\13\5\13\u00b8\n\13\3\f\3\f\3\f\3\r\3\r\3\r\3\r"+ - "\3\r\3\r\5\r\u00c3\n\r\3\16\3\16\3\16\7\16\u00c8\n\16\f\16\16\16\u00cb"+ - "\13\16\3\16\3\16\3\16\3\17\7\17\u00d1\n\17\f\17\16\17\u00d4\13\17\3\20"+ - "\5\20\u00d7\n\20\3\20\3\20\3\20\3\20\3\20\3\20\3\20\3\20\3\21\3\21\5\21"+ - "\u00e3\n\21\3\22\3\22\3\22\3\22\3\22\3\22\3\22\3\22\3\22\3\22\3\22\3\23"+ - "\7\23\u00f1\n\23\f\23\16\23\u00f4\13\23\3\24\3\24\5\24\u00f8\n\24\3\25"+ - "\3\25\3\25\5\25\u00fd\n\25\3\26\3\26\3\26\5\26\u0102\n\26\3\26\2\2\27"+ - "\2\4\6\b\n\f\16\20\22\24\26\30\32\34\36 \"$&(*\2\2\2\u0113\2;\3\2\2\2"+ - "\4=\3\2\2\2\6G\3\2\2\2\bX\3\2\2\2\nZ\3\2\2\2\fq\3\2\2\2\16{\3\2\2\2\20"+ - "\u0082\3\2\2\2\22\u009d\3\2\2\2\24\u00b7\3\2\2\2\26\u00b9\3\2\2\2\30\u00c2"+ - "\3\2\2\2\32\u00c4\3\2\2\2\34\u00d2\3\2\2\2\36\u00d6\3\2\2\2 \u00e2\3\2"+ - "\2\2\"\u00e4\3\2\2\2$\u00f2\3\2\2\2&\u00f7\3\2\2\2(\u00fc\3\2\2\2*\u0101"+ - "\3\2\2\2,<\5&\24\2-.\5(\25\2.\62\5\34\17\2/\61\5 \21\2\60/\3\2\2\2\61"+ - "\64\3\2\2\2\62\60\3\2\2\2\62\63\3\2\2\2\638\3\2\2\2\64\62\3\2\2\2\65\67"+ - "\5\"\22\2\66\65\3\2\2\2\67:\3\2\2\28\66\3\2\2\289\3\2\2\29<\3\2\2\2:8"+ - "\3\2\2\2;,\3\2\2\2;-\3\2\2\2<\3\3\2\2\2=>\7\25\2\2>?\7#\2\2?@\7\26\2\2"+ - "@A\7#\2\2AB\5&\24\2B\5\3\2\2\2CH\7#\2\2DE\7#\2\2EH\7\24\2\2FH\7\20\2\2"+ - "GC\3\2\2\2GD\3\2\2\2GF\3\2\2\2H\7\3\2\2\2IK\7\34\2\2JI\3\2\2\2JK\3\2\2"+ - "\2KL\3\2\2\2LM\5\6\4\2MN\7#\2\2NO\5&\24\2OY\3\2\2\2PR\7\34\2\2QP\3\2\2"+ - "\2QR\3\2\2\2RS\3\2\2\2ST\5\6\4\2TU\7#\2\2UV\5\n\6\2VW\5&\24\2WY\3\2\2"+ - "\2XJ\3\2\2\2XQ\3\2\2\2Y\t\3\2\2\2Z[\7\b\2\2[\\\5\f\7\2\\\13\3\2\2\2]r"+ - "\7#\2\2^r\7\35\2\2_r\7\36\2\2`a\7\f\2\2ar\7#\2\2br\7\37\2\2cr\7 \2\2d"+ - "r\7!\2\2ef\7#\2\2fg\7\3\2\2gh\5\f\7\2hi\7\4\2\2ir\3\2\2\2jr\5\16\b\2k"+ - "l\7\5\2\2lm\5\f\7\2mn\7\6\2\2nr\3\2\2\2op\7\"\2\2pr\5\f\7\2q]\3\2\2\2"+ - "q^\3\2\2\2q_\3\2\2\2q`\3\2\2\2qb\3\2\2\2qc\3\2\2\2qd\3\2\2\2qe\3\2\2\2"+ - "qj\3\2\2\2qk\3\2\2\2qo\3\2\2\2r\r\3\2\2\2st\7#\2\2tu\7\5\2\2uv\5\20\t"+ - "\2vw\7\6\2\2w|\3\2\2\2xy\7#\2\2yz\7\5\2\2z|\7\6\2\2{s\3\2\2\2{x\3\2\2"+ - "\2|\17\3\2\2\2}\u0083\5\f\7\2~\177\5\f\7\2\177\u0080\7\7\2\2\u0080\u0081"+ - "\5\20\t\2\u0081\u0083\3\2\2\2\u0082}\3\2\2\2\u0082~\3\2\2\2\u0083\21\3"+ - "\2\2\2\u0084\u0085\7\21\2\2\u0085\u0086\5\16\b\2\u0086\u0087\5&\24\2\u0087"+ - "\u009e\3\2\2\2\u0088\u0089\7\22\2\2\u0089\u008a\7#\2\2\u008a\u008b\7\b"+ - "\2\2\u008b\u008c\5\f\7\2\u008c\u008d\5&\24\2\u008d\u009e\3\2\2\2\u008e"+ - "\u008f\7\22\2\2\u008f\u0090\7#\2\2\u0090\u0091\7\3\2\2\u0091\u0092\5\f"+ - "\7\2\u0092\u0093\7\4\2\2\u0093\u0094\7\b\2\2\u0094\u0095\5\f\7\2\u0095"+ - "\u0096\5&\24\2\u0096\u009e\3\2\2\2\u0097\u0098\7\23\2\2\u0098\u0099\5"+ - "\f\7\2\u0099\u009a\5&\24\2\u009a\u009e\3\2\2\2\u009b\u009c\7\27\2\2\u009c"+ - "\u009e\5\24\13\2\u009d\u0084\3\2\2\2\u009d\u0088\3\2\2\2\u009d\u008e\3"+ - "\2\2\2\u009d\u0097\3\2\2\2\u009d\u009b\3\2\2\2\u009e\23\3\2\2\2\u009f"+ - "\u00a0\5\f\7\2\u00a0\u00a1\7\30\2\2\u00a1\u00a2\5&\24\2\u00a2\u00a3\5"+ - "$\23\2\u00a3\u00a4\7\32\2\2\u00a4\u00a5\5&\24\2\u00a5\u00b8\3\2\2\2\u00a6"+ - "\u00a7\5\f\7\2\u00a7\u00a8\7\30\2\2\u00a8\u00a9\5&\24\2\u00a9\u00aa\5"+ - "$\23\2\u00aa\u00ab\7\31\2\2\u00ab\u00ac\5&\24\2\u00ac\u00ad\5$\23\2\u00ad"+ - "\u00ae\7\32\2\2\u00ae\u00af\5&\24\2\u00af\u00b8\3\2\2\2\u00b0\u00b1\5"+ - "\f\7\2\u00b1\u00b2\7\30\2\2\u00b2\u00b3\5&\24\2\u00b3\u00b4\5$\23\2\u00b4"+ - "\u00b5\7\33\2\2\u00b5\u00b6\5\24\13\2\u00b6\u00b8\3\2\2\2\u00b7\u009f"+ - "\3\2\2\2\u00b7\u00a6\3\2\2\2\u00b7\u00b0\3\2\2\2\u00b8\25\3\2\2\2\u00b9"+ - "\u00ba\5\6\4\2\u00ba\u00bb\7#\2\2\u00bb\27\3\2\2\2\u00bc\u00c3\5\26\f"+ - "\2\u00bd\u00be\5\26\f\2\u00be\u00bf\7\7\2\2\u00bf\u00c0\5\30\r\2\u00c0"+ - "\u00c3\3\2\2\2\u00c1\u00c3\7\20\2\2\u00c2\u00bc\3\2\2\2\u00c2\u00bd\3"+ - "\2\2\2\u00c2\u00c1\3\2\2\2\u00c3\31\3\2\2\2\u00c4\u00c5\7\t\2\2\u00c5"+ - "\u00c9\5&\24\2\u00c6\u00c8\5\b\5\2\u00c7\u00c6\3\2\2\2\u00c8\u00cb\3\2"+ - "\2\2\u00c9\u00c7\3\2\2\2\u00c9\u00ca\3\2\2\2\u00ca\u00cc\3\2\2\2\u00cb"+ - "\u00c9\3\2\2\2\u00cc\u00cd\7\n\2\2\u00cd\u00ce\5&\24\2\u00ce\33\3\2\2"+ - "\2\u00cf\u00d1\5\4\3\2\u00d0\u00cf\3\2\2\2\u00d1\u00d4\3\2\2\2\u00d2\u00d0"+ - "\3\2\2\2\u00d2\u00d3\3\2\2\2\u00d3\35\3\2\2\2\u00d4\u00d2\3\2\2\2\u00d5"+ - "\u00d7\7\34\2\2\u00d6\u00d5\3\2\2\2\u00d6\u00d7\3\2\2\2\u00d7\u00d8\3"+ - "\2\2\2\u00d8\u00d9\7\13\2\2\u00d9\u00da\7#\2\2\u00da\u00db\7\r\2\2\u00db"+ - "\u00dc\5\30\r\2\u00dc\u00dd\7\16\2\2\u00dd\u00de\5\6\4\2\u00de\u00df\5"+ - "&\24\2\u00df\37\3\2\2\2\u00e0\u00e3\5\32\16\2\u00e1\u00e3\5\36\20\2\u00e2"+ - "\u00e0\3\2\2\2\u00e2\u00e1\3\2\2\2\u00e3!\3\2\2\2\u00e4\u00e5\7\f\2\2"+ - "\u00e5\u00e6\7#\2\2\u00e6\u00e7\7\r\2\2\u00e7\u00e8\5\30\r\2\u00e8\u00e9"+ - "\7\16\2\2\u00e9\u00ea\5\6\4\2\u00ea\u00eb\5&\24\2\u00eb\u00ec\5$\23\2"+ - "\u00ec\u00ed\7\17\2\2\u00ed\u00ee\5&\24\2\u00ee#\3\2\2\2\u00ef\u00f1\5"+ - "\22\n\2\u00f0\u00ef\3\2\2\2\u00f1\u00f4\3\2\2\2\u00f2\u00f0\3\2\2\2\u00f2"+ - "\u00f3\3\2\2\2\u00f3%\3\2\2\2\u00f4\u00f2\3\2\2\2\u00f5\u00f8\5*\26\2"+ - "\u00f6\u00f8\7\2\2\3\u00f7\u00f5\3\2\2\2\u00f7\u00f6\3\2\2\2\u00f8\'\3"+ - "\2\2\2\u00f9\u00fd\5*\26\2\u00fa\u00fd\7\2\2\3\u00fb\u00fd\3\2\2\2\u00fc"+ - "\u00f9\3\2\2\2\u00fc\u00fa\3\2\2\2\u00fc\u00fb\3\2\2\2\u00fd)\3\2\2\2"+ - "\u00fe\u0102\7%\2\2\u00ff\u0100\7%\2\2\u0100\u0102\5&\24\2\u0101\u00fe"+ - "\3\2\2\2\u0101\u00ff\3\2\2\2\u0102+\3\2\2\2\27\628;GJQXq{\u0082\u009d"+ - "\u00b7\u00c2\u00c9\u00d2\u00d6\u00e2\u00f2\u00f7\u00fc\u0101"; - public static final ATN _ATN = - new ATNDeserializer().deserialize(_serializedATN.toCharArray()); - static { - _decisionToDFA = new DFA[_ATN.getNumberOfDecisions()]; - for (int i = 0; i < _ATN.getNumberOfDecisions(); i++) { - _decisionToDFA[i] = new DFA(_ATN.getDecisionState(i), i); - } - } -} \ No newline at end of file diff --git a/jassparser/build/generated-src/com/etheller/warsmash/jassparser/JassVisitor.java b/jassparser/build/generated-src/com/etheller/warsmash/jassparser/JassVisitor.java deleted file mode 100644 index ce63888..0000000 --- a/jassparser/build/generated-src/com/etheller/warsmash/jassparser/JassVisitor.java +++ /dev/null @@ -1,302 +0,0 @@ -// Generated from Jass.g4 by ANTLR 4.7 - - package com.etheller.interpreter; - -import org.antlr.v4.runtime.tree.ParseTreeVisitor; - -/** - * This interface defines a complete generic visitor for a parse tree produced - * by {@link JassParser}. - * - * @param The return type of the visit operation. Use {@link Void} for - * operations with no return type. - */ -public interface JassVisitor extends ParseTreeVisitor { - /** - * Visit a parse tree produced by {@link JassParser#program}. - * @param ctx the parse tree - * @return the visitor result - */ - T visitProgram(JassParser.ProgramContext ctx); - /** - * Visit a parse tree produced by {@link JassParser#typeDefinition}. - * @param ctx the parse tree - * @return the visitor result - */ - T visitTypeDefinition(JassParser.TypeDefinitionContext ctx); - /** - * Visit a parse tree produced by the {@code BasicType} - * labeled alternative in {@link JassParser#type}. - * @param ctx the parse tree - * @return the visitor result - */ - T visitBasicType(JassParser.BasicTypeContext ctx); - /** - * Visit a parse tree produced by the {@code ArrayType} - * labeled alternative in {@link JassParser#type}. - * @param ctx the parse tree - * @return the visitor result - */ - T visitArrayType(JassParser.ArrayTypeContext ctx); - /** - * Visit a parse tree produced by the {@code NothingType} - * labeled alternative in {@link JassParser#type}. - * @param ctx the parse tree - * @return the visitor result - */ - T visitNothingType(JassParser.NothingTypeContext ctx); - /** - * Visit a parse tree produced by the {@code BasicGlobal} - * labeled alternative in {@link JassParser#global}. - * @param ctx the parse tree - * @return the visitor result - */ - T visitBasicGlobal(JassParser.BasicGlobalContext ctx); - /** - * Visit a parse tree produced by the {@code DefinitionGlobal} - * labeled alternative in {@link JassParser#global}. - * @param ctx the parse tree - * @return the visitor result - */ - T visitDefinitionGlobal(JassParser.DefinitionGlobalContext ctx); - /** - * Visit a parse tree produced by {@link JassParser#assignTail}. - * @param ctx the parse tree - * @return the visitor result - */ - T visitAssignTail(JassParser.AssignTailContext ctx); - /** - * Visit a parse tree produced by the {@code ReferenceExpression} - * labeled alternative in {@link JassParser#expression}. - * @param ctx the parse tree - * @return the visitor result - */ - T visitReferenceExpression(JassParser.ReferenceExpressionContext ctx); - /** - * Visit a parse tree produced by the {@code StringLiteralExpression} - * labeled alternative in {@link JassParser#expression}. - * @param ctx the parse tree - * @return the visitor result - */ - T visitStringLiteralExpression(JassParser.StringLiteralExpressionContext ctx); - /** - * Visit a parse tree produced by the {@code IntegerLiteralExpression} - * labeled alternative in {@link JassParser#expression}. - * @param ctx the parse tree - * @return the visitor result - */ - T visitIntegerLiteralExpression(JassParser.IntegerLiteralExpressionContext ctx); - /** - * Visit a parse tree produced by the {@code FunctionReferenceExpression} - * labeled alternative in {@link JassParser#expression}. - * @param ctx the parse tree - * @return the visitor result - */ - T visitFunctionReferenceExpression(JassParser.FunctionReferenceExpressionContext ctx); - /** - * Visit a parse tree produced by the {@code NullExpression} - * labeled alternative in {@link JassParser#expression}. - * @param ctx the parse tree - * @return the visitor result - */ - T visitNullExpression(JassParser.NullExpressionContext ctx); - /** - * Visit a parse tree produced by the {@code TrueExpression} - * labeled alternative in {@link JassParser#expression}. - * @param ctx the parse tree - * @return the visitor result - */ - T visitTrueExpression(JassParser.TrueExpressionContext ctx); - /** - * Visit a parse tree produced by the {@code FalseExpression} - * labeled alternative in {@link JassParser#expression}. - * @param ctx the parse tree - * @return the visitor result - */ - T visitFalseExpression(JassParser.FalseExpressionContext ctx); - /** - * Visit a parse tree produced by the {@code ArrayReferenceExpression} - * labeled alternative in {@link JassParser#expression}. - * @param ctx the parse tree - * @return the visitor result - */ - T visitArrayReferenceExpression(JassParser.ArrayReferenceExpressionContext ctx); - /** - * Visit a parse tree produced by the {@code FunctionCallExpression} - * labeled alternative in {@link JassParser#expression}. - * @param ctx the parse tree - * @return the visitor result - */ - T visitFunctionCallExpression(JassParser.FunctionCallExpressionContext ctx); - /** - * Visit a parse tree produced by the {@code ParentheticalExpression} - * labeled alternative in {@link JassParser#expression}. - * @param ctx the parse tree - * @return the visitor result - */ - T visitParentheticalExpression(JassParser.ParentheticalExpressionContext ctx); - /** - * Visit a parse tree produced by the {@code NotExpression} - * labeled alternative in {@link JassParser#expression}. - * @param ctx the parse tree - * @return the visitor result - */ - T visitNotExpression(JassParser.NotExpressionContext ctx); - /** - * Visit a parse tree produced by {@link JassParser#functionExpression}. - * @param ctx the parse tree - * @return the visitor result - */ - T visitFunctionExpression(JassParser.FunctionExpressionContext ctx); - /** - * Visit a parse tree produced by the {@code SingleArgument} - * labeled alternative in {@link JassParser#argsList}. - * @param ctx the parse tree - * @return the visitor result - */ - T visitSingleArgument(JassParser.SingleArgumentContext ctx); - /** - * Visit a parse tree produced by the {@code ListArgument} - * labeled alternative in {@link JassParser#argsList}. - * @param ctx the parse tree - * @return the visitor result - */ - T visitListArgument(JassParser.ListArgumentContext ctx); - /** - * Visit a parse tree produced by the {@code CallStatement} - * labeled alternative in {@link JassParser#statement}. - * @param ctx the parse tree - * @return the visitor result - */ - T visitCallStatement(JassParser.CallStatementContext ctx); - /** - * Visit a parse tree produced by the {@code SetStatement} - * labeled alternative in {@link JassParser#statement}. - * @param ctx the parse tree - * @return the visitor result - */ - T visitSetStatement(JassParser.SetStatementContext ctx); - /** - * Visit a parse tree produced by the {@code ArrayedAssignmentStatement} - * labeled alternative in {@link JassParser#statement}. - * @param ctx the parse tree - * @return the visitor result - */ - T visitArrayedAssignmentStatement(JassParser.ArrayedAssignmentStatementContext ctx); - /** - * Visit a parse tree produced by the {@code ReturnStatement} - * labeled alternative in {@link JassParser#statement}. - * @param ctx the parse tree - * @return the visitor result - */ - T visitReturnStatement(JassParser.ReturnStatementContext ctx); - /** - * Visit a parse tree produced by the {@code IfStatement} - * labeled alternative in {@link JassParser#statement}. - * @param ctx the parse tree - * @return the visitor result - */ - T visitIfStatement(JassParser.IfStatementContext ctx); - /** - * Visit a parse tree produced by the {@code SimpleIfStatement} - * labeled alternative in {@link JassParser#ifStatementPartial}. - * @param ctx the parse tree - * @return the visitor result - */ - T visitSimpleIfStatement(JassParser.SimpleIfStatementContext ctx); - /** - * Visit a parse tree produced by the {@code IfElseStatement} - * labeled alternative in {@link JassParser#ifStatementPartial}. - * @param ctx the parse tree - * @return the visitor result - */ - T visitIfElseStatement(JassParser.IfElseStatementContext ctx); - /** - * Visit a parse tree produced by the {@code IfElseIfStatement} - * labeled alternative in {@link JassParser#ifStatementPartial}. - * @param ctx the parse tree - * @return the visitor result - */ - T visitIfElseIfStatement(JassParser.IfElseIfStatementContext ctx); - /** - * Visit a parse tree produced by {@link JassParser#param}. - * @param ctx the parse tree - * @return the visitor result - */ - T visitParam(JassParser.ParamContext ctx); - /** - * Visit a parse tree produced by the {@code SingleParameter} - * labeled alternative in {@link JassParser#paramList}. - * @param ctx the parse tree - * @return the visitor result - */ - T visitSingleParameter(JassParser.SingleParameterContext ctx); - /** - * Visit a parse tree produced by the {@code ListParameter} - * labeled alternative in {@link JassParser#paramList}. - * @param ctx the parse tree - * @return the visitor result - */ - T visitListParameter(JassParser.ListParameterContext ctx); - /** - * Visit a parse tree produced by the {@code NothingParameter} - * labeled alternative in {@link JassParser#paramList}. - * @param ctx the parse tree - * @return the visitor result - */ - T visitNothingParameter(JassParser.NothingParameterContext ctx); - /** - * Visit a parse tree produced by {@link JassParser#globalsBlock}. - * @param ctx the parse tree - * @return the visitor result - */ - T visitGlobalsBlock(JassParser.GlobalsBlockContext ctx); - /** - * Visit a parse tree produced by {@link JassParser#typeDefinitionBlock}. - * @param ctx the parse tree - * @return the visitor result - */ - T visitTypeDefinitionBlock(JassParser.TypeDefinitionBlockContext ctx); - /** - * Visit a parse tree produced by {@link JassParser#nativeBlock}. - * @param ctx the parse tree - * @return the visitor result - */ - T visitNativeBlock(JassParser.NativeBlockContext ctx); - /** - * Visit a parse tree produced by {@link JassParser#block}. - * @param ctx the parse tree - * @return the visitor result - */ - T visitBlock(JassParser.BlockContext ctx); - /** - * Visit a parse tree produced by {@link JassParser#functionBlock}. - * @param ctx the parse tree - * @return the visitor result - */ - T visitFunctionBlock(JassParser.FunctionBlockContext ctx); - /** - * Visit a parse tree produced by {@link JassParser#statements}. - * @param ctx the parse tree - * @return the visitor result - */ - T visitStatements(JassParser.StatementsContext ctx); - /** - * Visit a parse tree produced by {@link JassParser#newlines}. - * @param ctx the parse tree - * @return the visitor result - */ - T visitNewlines(JassParser.NewlinesContext ctx); - /** - * Visit a parse tree produced by {@link JassParser#newlines_opt}. - * @param ctx the parse tree - * @return the visitor result - */ - T visitNewlines_opt(JassParser.Newlines_optContext ctx); - /** - * Visit a parse tree produced by {@link JassParser#pnewlines}. - * @param ctx the parse tree - * @return the visitor result - */ - T visitPnewlines(JassParser.PnewlinesContext ctx); -} \ No newline at end of file diff --git a/resources/Scripts/common.jui b/resources/Scripts/common.jui index 37012a3..34f4ec7 100644 --- a/resources/Scripts/common.jui +++ b/resources/Scripts/common.jui @@ -80,10 +80,28 @@ native LoadTOCFile takes string TOCFile returns fra // that. native CreateSimpleFrame takes string name, framehandle owner, integer createContext returns framehandle +// Spawn a FRAME element that was defined in an FDF +// template onto the screen. The "name" field must +// match the name of a template to spawn, loaded with LoadTOCFile. +// As noted on CreateSimpleFrame, for now createContext is pointless. +// Likewise for priority -- until it fixed. +native CreateFrame takes string name, framehandle owner, integer priority, integer createContext returns framehandle + + // Set the absolute point (often called Anchor) for the frame handle. // See FDF template files for examples +native FrameSetAnchor takes framehandle frame, framepointtype point, real x, real y returns nothing + +// Tasyen said: "takes one point of a Frame unbound that point and places it to a specific coordinates on the screen." native FrameSetAbsPoint takes framehandle frame, framepointtype point, real x, real y returns nothing +// Set the relative point (called SetPoint in FDF templates) for the frame handle. +// See FDF template files for examples +// Tasyen said: places a point of FrameA relative to a point of FrameB. When FrameB moves FrameA's point will keep this rule and moves with it. +// Note for Project Warsmash: "When FrameB moves..." might not be true... call FramePositionBounds() for now +native FrameSetPoint takes framehandle frame, framepointtype point, framehandle relative, framepointtype relativePoint, real x, real y returns nothing + + // Created for Warsmash engine, not a part of 1.31 UI apis, // and at some point it might be removed. Basically // this function will apply Anchors and SetPoints assigned @@ -98,6 +116,19 @@ native FramePositionBounds takes framehandle frame returns // when the "Human" skin was loaded with CreateRootFrame native SkinGetField takes string field returns string +// Sets the text value on a String Frame, currently it crashes otherwise +native FrameSetText takes framehandle frame, string text returns nothing + +// Sets the text color on a String Frame, currently it crashes otherwise +native FrameSetTextColor takes framehandle frame, integer color returns nothing + +native ConvertColor takes integer a, integer r, integer g, integer b returns integer + + +// Gets a previously created Frame using its name ( from an FDF template file that was +// previously spawned ). See previous notes about createContext. Mostly pointless? +native GetFrameByName takes string name, integer createContext returns framehandle + //============================================================================ // Native trigger interface // diff --git a/resources/Scripts/melee.jui b/resources/Scripts/melee.jui index 2186536..9d3c9a4 100644 --- a/resources/Scripts/melee.jui +++ b/resources/Scripts/melee.jui @@ -6,6 +6,15 @@ globals framehandle ROOT_FRAME framehandle CONSOLE_UI framehandle RESOURCE_BAR + framehandle RESOURCE_BAR_GOLD_TEXT + framehandle RESOURCE_BAR_LUMBER_TEXT + framehandle RESOURCE_BAR_SUPPLY_TEXT + framehandle RESOURCE_BAR_UPKEEP_TEXT + framehandle TIME_INDICATOR + framehandle SIMPLE_INFO_PANEL_UNIT_DETAIL + framehandle UNIT_PORTRAIT + framehandle UNIT_LIFE_TEXT + framehandle UNIT_MANA_TEXT endglobals @@ -17,6 +26,9 @@ function main takes nothing returns nothing if not LoadTOCFile("UI\\FrameDef\\FrameDef.toc") then call LogError("Unable to load FrameDef.toc") endif + if not LoadTOCFile("UI\\FrameDef\\SmashFrameDef.toc") then + call LogError("Unable to load SmashFrameDef.toc") + endif // ================================= // Load major UI components // ================================= @@ -26,7 +38,33 @@ function main takes nothing returns nothing // Its template does not specify where to put it, so we must // put it in the "TOPRIGHT" corner. set RESOURCE_BAR = CreateSimpleFrame("ResourceBarFrame", CONSOLE_UI, 0) - call FrameSetAbsPoint(RESOURCE_BAR, FRAMEPOINT_TOPRIGHT, 0, 0) + call FrameSetPoint(RESOURCE_BAR, FRAMEPOINT_TOPRIGHT, CONSOLE_UI, FRAMEPOINT_TOPRIGHT, 0, 0) + + // Create the Time Indicator (clock) + set TIME_INDICATOR = CreateFrame("TimeOfDayIndicator", ROOT_FRAME, 0, 0) + + // Create the unit portrait stuff (for now this doesn't actually create the 3D, only HP/mana) + set UNIT_PORTRAIT = CreateSimpleFrame("UnitPortrait", CONSOLE_UI, 0) + set UNIT_LIFE_TEXT = GetFrameByName("UnitPortraitHitPointText", 0) + set UNIT_MANA_TEXT = GetFrameByName("UnitPortraitManaPointText", 0) + + // Set default values + call FrameSetText(UNIT_LIFE_TEXT, "706 / 725") + call FrameSetText(UNIT_MANA_TEXT, "405 / 405") + + + // Retrieve inflated sub-frames and store references + set RESOURCE_BAR_GOLD_TEXT = GetFrameByName("ResourceBarGoldText", 0) + set RESOURCE_BAR_LUMBER_TEXT = GetFrameByName("ResourceBarLumberText", 0) + set RESOURCE_BAR_SUPPLY_TEXT = GetFrameByName("ResourceBarSupplyText", 0) + set RESOURCE_BAR_UPKEEP_TEXT = GetFrameByName("ResourceBarUpkeepText", 0) + + // Set default values + call FrameSetText(RESOURCE_BAR_GOLD_TEXT, "500") + call FrameSetText(RESOURCE_BAR_LUMBER_TEXT, "150") + call FrameSetText(RESOURCE_BAR_SUPPLY_TEXT, "5/10") + call FrameSetText(RESOURCE_BAR_UPKEEP_TEXT, "No Upkeep") + call FrameSetTextColor(RESOURCE_BAR_UPKEEP_TEXT, ConvertColor(255, 0, 255, 0)) // Assemble the UI and resolve the location of every component that // has Anchors and SetPoints (maybe in future version this call diff --git a/resources/UI/FrameDef/SmashFrameDef.toc b/resources/UI/FrameDef/SmashFrameDef.toc new file mode 100644 index 0000000..a67d6b6 --- /dev/null +++ b/resources/UI/FrameDef/SmashFrameDef.toc @@ -0,0 +1,2 @@ +UI\FrameDef\SmashUI\TimeOfDayIndicator.fdf +UI\FrameDef\SmashUI\UnitPortrait.fdf diff --git a/resources/UI/FrameDef/SmashUI/SmashConsoleUI.fdf b/resources/UI/FrameDef/SmashUI/SmashConsoleUI.fdf new file mode 100644 index 0000000..9ba9949 --- /dev/null +++ b/resources/UI/FrameDef/SmashUI/SmashConsoleUI.fdf @@ -0,0 +1,81 @@ +// I had to override this because the Blizzard version is missing the "ConsoleTexture01Top" names. +Frame "SIMPLEFRAME" "ConsoleUI" { + DecorateFileNames, + + // The top of the UI console + Texture "ConsoleTexture01Top" { + File "ConsoleTexture01", + Width 0.256, + Height 0.032, + TexCoord 0, 1, 0, 0.125, + AlphaMode "ALPHAKEY", + Anchor TOPLEFT,0,0, + } + Texture "ConsoleTexture02Top" { + File "ConsoleTexture02", + Width 0.087, + Height 0.032, + TexCoord 0, 0.33984375, 0, 0.125, + AlphaMode "ALPHAKEY", + Anchor TOPLEFT,0.256, 0, + } + Texture "ConsoleTexture02Top" { + File "ConsoleTexture02", + Width 0.053, + Height 0.032, + TexCoord 0.79296875, 1, 0, 0.125, + AlphaMode "ALPHAKEY", + Anchor TOPRIGHT,-0.288, 0, + } + Texture "ConsoleTexture03Top" { + File "ConsoleTexture03", + Width 0.256, + Height 0.032, + TexCoord 0, 1, 0, 0.125, + AlphaMode "ALPHAKEY", + Anchor TOPRIGHT,-0.032, 0, + } + Texture "ConsoleTexture04Top" { + File "ConsoleTexture04", + Width 0.032, + Height 0.032, + TexCoord 0, 1, 0, 0.125, + AlphaMode "ALPHAKEY", + Anchor TOPRIGHT,0,0, + } + + // The bottom of the UI console + Texture "ConsoleTexture01Bottom" { + File "ConsoleTexture01", + Width 0.256, + Height 0.176, + TexCoord 0, 1, 0.3125, 1, + AlphaMode "ALPHAKEY", + Anchor BOTTOMLEFT,0,0, + } + Texture "ConsoleTexture02Bottom" { + File "ConsoleTexture02", + Width 0.256, + Height 0.15, + TexCoord 0, 1, 0.4140625, 1, + AlphaMode "ALPHAKEY", + Anchor BOTTOMLEFT,0.256,0, + } + Texture "ConsoleTexture03Bottom" { + File "ConsoleTexture03", + Width 0.256, + Height 0.176, + TexCoord 0, 1, 0.3125, 1, + AlphaMode "ALPHAKEY", + Anchor BOTTOMRIGHT,-0.032,0.0, + } + Texture "ConsoleTexture04Bottom" { + File "ConsoleTexture04", + Width 0.032, + Height 0.176, + TexCoord 0, 1, 0.3125, 1, + AlphaMode "ALPHAKEY", + Anchor BOTTOMRIGHT,0,0, + } + +} diff --git a/resources/UI/FrameDef/SmashUI/TimeOfDayIndicator.fdf b/resources/UI/FrameDef/SmashUI/TimeOfDayIndicator.fdf new file mode 100644 index 0000000..9480e6e --- /dev/null +++ b/resources/UI/FrameDef/SmashUI/TimeOfDayIndicator.fdf @@ -0,0 +1,6 @@ + +Frame "SPRITE" "TimeOfDayIndicator" { + DecorateFileNames, + BackgroundArt "TimeOfDayIndicator", + SetPoint BOTTOMLEFT,"ConsoleUI",BOTTOMLEFT,0,0, +} \ No newline at end of file diff --git a/resources/UI/FrameDef/SmashUI/UnitPortrait.fdf b/resources/UI/FrameDef/SmashUI/UnitPortrait.fdf new file mode 100644 index 0000000..ce7f91b --- /dev/null +++ b/resources/UI/FrameDef/SmashUI/UnitPortrait.fdf @@ -0,0 +1,44 @@ +/* + * UnitPortrait.fdf + * --------------------- + * Right now the actual 3d portrait is hardcoded like the + * original game, eventually that should be a config file + * like this so that a map can override it. + */ + +String "UnitPortraitTextTemplate" { + Font "MasterFont",0.011, + Height 0.01640625, + TextLength 20, +} + +Frame "SIMPLEFRAME" "UnitPortrait" { + DecorateFileNames, + SetPoint BOTTOMLEFT,"ConsoleUI",BOTTOMLEFT,0.211,0, + Width 0.0835, + Height 0.114, + + Frame "SIMPLEFRAME" "UnitPortraitModel" { + DecorateFileNames, + SetPoint BOTTOM,"UnitPortrait",BOTTOM,0,0.0285, + Width 0.0835, + Height 0.085, + + //Texture { + //File "IdlePeon", + // AlphaMode "ALPHAKEY", + //} + } + + String "UnitPortraitHitPointText" INHERITS "UnitPortraitTextTemplate" { + Anchor BOTTOM, 0, 0.0115, + FontJustificationH JUSTIFYCENTER, + FontColor 0.0 1.0 0.0 1.0, + } + + String "UnitPortraitManaPointText" INHERITS "UnitPortraitTextTemplate" { + Anchor BOTTOM, 0, -0.0030, + FontJustificationH JUSTIFYCENTER, + FontColor 1.0 1.0 1.0 1.0, + } +} \ No newline at end of file diff --git a/resources/UI/FrameDef/UI/SimpleInfoPanel.fdf b/resources/UI/FrameDef/UI/SimpleInfoPanel.fdf new file mode 100644 index 0000000..de6fa8d --- /dev/null +++ b/resources/UI/FrameDef/UI/SimpleInfoPanel.fdf @@ -0,0 +1,447 @@ + +// --- TEXT ----------------------------------------------------------------------- + + +String "SimpleInfoPanelTitleTextTemplate" { + FontColor 1.0 1.0 1.0 1.0, + FontShadowColor 0.0 0.0 0.0 0.9, + FontShadowOffset 0.002 -0.002, + Font "InfoPanelTextFont",0.013, +} +String "SimpleInfoPanelTitleTextDisabledTemplate" INHERITS "SimpleInfoPanelTitleTextTemplate" { + FontColor 0.2 0.2 0.2 1.0, +} + +String "SimpleInfoPanelDescriptionTextTemplate" { + FontColor 0.99 0.827 0.0705 1.0, + FontShadowColor 0.0 0.0 0.0 0.9, + FontShadowOffset 0.001 -0.001, + Font "InfoPanelTextFont",0.01, +} +String "SimpleInfoPanelDescriptionHighlightTextTemplate" INHERITS "SimpleInfoPanelDescriptionTextTemplate" { + FontColor 1.0 1.0 1.0 1.0, +} +String "SimpleInfoPanelDescriptionDisabledTextTemplate" INHERITS "SimpleInfoPanelDescriptionTextTemplate" { + FontColor 0.2 0.2 0.2 1.0, +} + +String "SimpleInfoPanelLabelTextTemplate" { + FontJustificationH JUSTIFYLEFT, + FontJustificationV JUSTIFYTOP, + FontColor 0.99 0.827 0.0705 1.0, + FontShadowColor 0.0 0.0 0.0 0.9, + FontShadowOffset 0.001 -0.001, + Font "InfoPanelTextFont",0.0085, +} +String "SimpleInfoPanelLabelHighlightTextTemplate" INHERITS "SimpleInfoPanelLabelTextTemplate" { + FontColor 1.0 1.0 1.0 1.0, +} +String "SimpleInfoPanelLabelDisabledTextTemplate" INHERITS "SimpleInfoPanelLabelTextTemplate" { + FontColor 0.2 0.2 0.2 1.0, +} + +String "SimpleInfoPanelValueTextTemplate" INHERITS "SimpleInfoPanelLabelTextTemplate" { + FontColor 1.0 1.0 1.0 1.0, +} + +String "SimpleInfoPanelAttributeTextTemplate" { + FontColor 1.0 1.0 1.0 1.0, + FontShadowColor 0.0 0.0 0.0 0.9, + FontShadowOffset 0.001 -0.001, + Font "InfoPanelTextFont",0.009, +} +String "SimpleInfoPanelAttributeDisabledTextTemplate" INHERITS "SimpleInfoPanelAttributeTextTemplate" { + FontColor 0.2 0.2 0.2 1.0, +} + +Texture "InfoPanelIconTemplate" { + Width 0.032, + Height 0.032, + Anchor TOPLEFT, 0.004, -0.001, +} + +Texture "ResourceIconTemplate" { + Width 0.014, + Height 0.014, +} + +String "ResourceTextTemplate" INHERITS "SimpleInfoPanelValueTextTemplate" { + Font "InfoPanelTextFont", 0.0085, +} + +// -- FRAMES ---------------------------------------------------------------- + +Frame "SIMPLEFRAME" "SimpleInfoPanelUnitDetail" { + UseActiveContext, + SetAllPoints, + DecorateFileNames, + + // --- unit name frame -------------------------------------------------- + String "SimpleNameValue" INHERITS "SimpleInfoPanelTitleTextTemplate" { + Anchor TOP,0,0, + } + + // --- hero level bar --------------------------------------------------- + Frame "SIMPLESTATUSBAR" "SimpleHeroLevelBar" { + UseActiveContext, + SetPoint TOP, "SimpleNameValue", BOTTOM, 0.0, -0.0015, + Height 0.015625, + } + + // --- timed life bar ---------------------------------------------------- + Frame "SIMPLESTATUSBAR" "SimpleProgressIndicator" { + UseActiveContext, + SetPoint TOP, "SimpleNameValue", BOTTOM, 0.0, -0.0015, + Height 0.015625, + } + + // --- building build queue panel ------------------------------------------------- + Frame "SIMPLESTATUSBAR" "SimpleBuildTimeIndicator" { + UseActiveContext, + SetPoint TOPLEFT, "SimpleInfoPanelUnitDetail", TOPLEFT, 0.061250, -0.038125, + } + + String "SimpleBuildingActionLabel" INHERITS "SimpleInfoPanelDescriptionTextTemplate" { + SetPoint CENTER, "SimpleInfoPanelUnitDetail", TOPLEFT, 0.11375, -0.029875, + Text "Retarded text", + } + + // --- unit stats panel ------------------------------------------------- + // This is required to make sure the class text appears above the status bars. + Frame "SIMPLEFRAME" "SimpleUnitStatsPanel" { + UseActiveContext, + SetAllPoints, + DecorateFileNames, + + // --- class ------------------------------------------------------------ + String "SimpleClassValue" INHERITS "SimpleInfoPanelValueTextTemplate" { + SetPoint TOP, "SimpleNameValue", BOTTOM, 0.0, -0.0055, + FontJustificationH JUSTIFYCENTER, + } + } +} + +Frame "SIMPLEFRAME" "SimpleInfoPanelCargoDetail" { + UseActiveContext, + SetAllPoints, + DecorateFileNames, + + // --- unit name frame -------------------------------------------------- + String "SimpleHoldNameValue" INHERITS "SimpleInfoPanelTitleTextTemplate" { + Anchor TOP,0,0, + } + + String "SimpleHoldDescriptionValue" INHERITS "SimpleInfoPanelDescriptionTextTemplate" { + SetPoint TOP, "SimpleHoldNameValue", BOTTOM, 0.0, -0.007, + Width 0.188, + } +} + +Frame "SIMPLEFRAME" "SimpleInfoPanelBuildingDetail" { + UseActiveContext, + SetAllPoints, + DecorateFileNames, + + String "SimpleBuildingNameValue" INHERITS "SimpleInfoPanelTitleTextTemplate" { + Anchor TOP,0,0, + } + + String "SimpleBuildingDescriptionValue" INHERITS "SimpleInfoPanelDescriptionTextTemplate" { + SetPoint TOP, "SimpleBuildingNameValue", BOTTOM, 0.0, -0.007, + Width 0.188, + } + + // --- building build queue panel ------------------------------------------------- + Frame "SIMPLESTATUSBAR" "SimpleBuildTimeIndicator" { + UseActiveContext, + SetPoint TOPLEFT, "SimpleInfoPanelBuildingDetail", TOPLEFT, 0.061250, -0.038125, + } + + String "SimpleBuildingActionLabel" INHERITS "SimpleInfoPanelDescriptionTextTemplate" { + SetPoint CENTER, "SimpleInfoPanelBuildingDetail", TOPLEFT, 0.11375, -0.029875, + Text "Retarded text", + } + + Layer "ARTWORK" { + Texture "SimpleBuildQueueBackdrop" { + SetPoint BOTTOMLEFT, "SimpleInfoPanelBuildingDetail", BOTTOMLEFT, 0.0, 0.0, + SetPoint BOTTOMRIGHT, "SimpleInfoPanelBuildingDetail", BOTTOMRIGHT, 0.0, 0.0, + Height 0.1, + File "BuildQueueBackdrop", + } + } +} + +Frame "SIMPLEFRAME" "SimpleInfoPanelItemDetail" { + UseActiveContext, + SetAllPoints, + DecorateFileNames, + + // --- item name frame -------------------------------------------------- + String "SimpleItemNameValue" INHERITS "SimpleInfoPanelTitleTextTemplate" { + Anchor TOP,0,0, + } + + // --- item description frame ------------------------------------------- + String "SimpleItemDescriptionValue" INHERITS "SimpleInfoPanelDescriptionTextTemplate" { + Width 0.188, + SetPoint TOP, "SimpleItemNameValue", BOTTOM, 0.0, -0.008, + } +} + +Frame "SIMPLEFRAME" "SimpleInfoPanelDestructableDetail" { + UseActiveContext, + SetAllPoints, + DecorateFileNames, + + // --- destructable name frame -------------------------------------------------- + String "SimpleDestructableNameValue" INHERITS "SimpleInfoPanelTitleTextTemplate" { + Anchor TOP,0,0, + } + + // --- destructable description frame ------------------------------------------- + //String "SimpleDestructableDescriptionValue" INHERITS "SimpleInfoPanelDescriptionTextTemplate" { + // Width 0.188, + // SetPoint TOP, "SimpleDestructableNameValue", BOTTOM, 0.0, -0.008, + //} +} + +Frame "SIMPLEFRAME" "SimpleInfoPanelIconDamage" { + UseActiveContext, + SetAllPoints, + DecorateFileNames, + Height 0.03125, + + // --- icon ------------------------------------------------------------- + Texture "InfoPanelIconBackdrop" INHERITS "InfoPanelIconTemplate" { + File "HeroStrengthIcon", + } + + // --- icon # ----------------------------------------------------------- + String "InfoPanelIconLevel" INHERITS "SimpleInfoPanelAttributeTextTemplate" { + SetPoint CENTER, "InfoPanelIconBackdrop", BOTTOMRIGHT, -0.007625, 0.006875, + } + + // --- label ------------------------------------------------------------ + String "InfoPanelIconLabel" INHERITS "SimpleInfoPanelLabelTextTemplate" { + SetPoint TOPLEFT, "InfoPanelIconBackdrop", TOPRIGHT, 0.0, -0.003, + Text "COLON_DAMAGE", + } + + // --- value ------------------------------------------------------------ + String "InfoPanelIconValue" INHERITS "SimpleInfoPanelValueTextTemplate" { + SetPoint TOPLEFT, "InfoPanelIconLabel", BOTTOMLEFT, 0.005, -0.003, + } +} + +Frame "SIMPLEFRAME" "SimpleInfoPanelIconArmor" { + UseActiveContext, + SetAllPoints, + DecorateFileNames, + Height 0.03125, + + // --- icon ------------------------------------------------------------- + Texture "InfoPanelIconBackdrop" INHERITS "InfoPanelIconTemplate" { + File "HeroStrengthIcon", + } + + // --- icon # ----------------------------------------------------------- + String "InfoPanelIconLevel" INHERITS "SimpleInfoPanelAttributeTextTemplate" { + SetPoint CENTER, "InfoPanelIconBackdrop", BOTTOMRIGHT, -0.007625, 0.006875, + } + + // --- label ------------------------------------------------------------ + String "InfoPanelIconLabel" INHERITS "SimpleInfoPanelLabelTextTemplate" { + SetPoint TOPLEFT, "InfoPanelIconBackdrop", TOPRIGHT, 0.0, -0.003, + Text "COLON_ARMOR", + } + + // --- value ------------------------------------------------------------ + String "InfoPanelIconValue" INHERITS "SimpleInfoPanelValueTextTemplate" { + SetPoint TOPLEFT, "InfoPanelIconLabel", BOTTOMLEFT, 0.005, -0.003, + } +} + +Frame "SIMPLEFRAME" "SimpleInfoPanelIconRank" { + UseActiveContext, + SetAllPoints, + DecorateFileNames, + Height 0.03125, + + // --- icon ------------------------------------------------------------- + Texture "InfoPanelIconBackdrop" INHERITS "InfoPanelIconTemplate" { + File "HeroStrengthIcon", + } + + // --- icon # ----------------------------------------------------------- + String "InfoPanelIconLevel" INHERITS "SimpleInfoPanelAttributeTextTemplate" { + SetPoint CENTER, "InfoPanelIconBackdrop", BOTTOMRIGHT, -0.007625, 0.006875, + } + + // --- label ------------------------------------------------------------ + String "InfoPanelIconLabel" INHERITS "SimpleInfoPanelLabelTextTemplate" { + SetPoint TOPLEFT, "InfoPanelIconBackdrop", TOPRIGHT, 0.0, -0.003, + Text "COLON_RANK", + } + + // --- value ------------------------------------------------------------ + String "InfoPanelIconValue" INHERITS "SimpleInfoPanelValueTextTemplate" { + SetPoint TOPLEFT, "InfoPanelIconLabel", BOTTOMLEFT, 0.005, -0.003, + } +} + +Frame "SIMPLEFRAME" "SimpleInfoPanelIconFood" { + UseActiveContext, + SetAllPoints, + DecorateFileNames, + Height 0.03125, + + // --- icon ------------------------------------------------------------- + Texture "InfoPanelIconBackdrop" INHERITS "InfoPanelIconTemplate" { + File "InfoPanelIconFood", + } + + // --- icon # ----------------------------------------------------------- + String "InfoPanelIconLevel" INHERITS "SimpleInfoPanelAttributeTextTemplate" { + SetPoint CENTER, "InfoPanelIconBackdrop", BOTTOMRIGHT, -0.007625, 0.006875, + } + + // --- label ------------------------------------------------------------ + String "InfoPanelIconLabel" INHERITS "SimpleInfoPanelLabelTextTemplate" { + SetPoint TOPLEFT, "InfoPanelIconBackdrop", TOPRIGHT, 0.0, -0.003, + Text "COLON_FOOD", + } + + // --- value ------------------------------------------------------------ + String "InfoPanelIconValue" INHERITS "SimpleInfoPanelValueTextTemplate" { + SetPoint TOPLEFT, "InfoPanelIconLabel", BOTTOMLEFT, 0.005, -0.003, + } +} + +Frame "SIMPLEFRAME" "SimpleInfoPanelIconGold" { + UseActiveContext, + SetAllPoints, + DecorateFileNames, + Height 0.03125, + + // --- icon ------------------------------------------------------------- + Texture "InfoPanelIconBackdrop" INHERITS "InfoPanelIconTemplate" { + File "InfoPanelIconGold", + } + + // --- icon # ----------------------------------------------------------- + String "InfoPanelIconLevel" INHERITS "SimpleInfoPanelAttributeTextTemplate" { + SetPoint CENTER, "InfoPanelIconBackdrop", BOTTOMRIGHT, -0.007625, 0.006875, + } + + // --- label ------------------------------------------------------------ + String "InfoPanelIconLabel" INHERITS "SimpleInfoPanelLabelTextTemplate" { + SetPoint TOPLEFT, "InfoPanelIconBackdrop", TOPRIGHT, 0.0, -0.003, + Text "COLON_GOLD", + } + + // --- value ------------------------------------------------------------ + String "InfoPanelIconValue" INHERITS "SimpleInfoPanelValueTextTemplate" { + SetPoint TOPLEFT, "InfoPanelIconLabel", BOTTOMLEFT, 0.005, -0.003, + } +} + +Frame "SIMPLEFRAME" "SimpleInfoPanelIconHero" { + UseActiveContext, + SetAllPoints, + DecorateFileNames, + Height 0.0625, + + // --- icon ------------------------------------------------------------- + Texture "InfoPanelIconHeroIcon" INHERITS "InfoPanelIconTemplate" { + File "HeroStrengthIcon", + Anchor LEFT, 0.004, 0.0, + } + + Frame "SIMPLEFRAME" "SimpleInfoPanelIconHeroText" { + UseActiveContext, + DecorateFileNames, + SetPoint LEFT, "InfoPanelIconHeroIcon", RIGHT, 0.0, 0.0, + SetPoint RIGHT, "SimpleInfoPanelIconHero", RIGHT, 0.0, 0.0, + SetPoint TOP, "SimpleInfoPanelIconHero", TOP, 0.0, 0.0, + SetPoint BOTTOM, "SimpleInfoPanelIconHero", BOTTOM, 0.0, 0.0, + + // --- strength --------------------------------------------------------- + String "InfoPanelIconHeroStrengthLabel" INHERITS "SimpleInfoPanelLabelTextTemplate" { + Anchor TOPLEFT, 0.0, -0.003, + Text "COLON_STRENGTH", + } + + String "InfoPanelIconHeroStrengthValue" INHERITS "SimpleInfoPanelValueTextTemplate" { + SetPoint TOPLEFT, "InfoPanelIconHeroStrengthLabel", BOTTOMLEFT, 0.005, 0.0, + } + + // --- agility ---------------------------------------------------------- + String "InfoPanelIconHeroAgilityLabel" INHERITS "SimpleInfoPanelLabelTextTemplate" { + SetPoint TOPLEFT, "InfoPanelIconHeroStrengthValue", BOTTOMLEFT, -0.005, -0.004, + Text "COLON_AGILITY", + } + + String "InfoPanelIconHeroAgilityValue" INHERITS "SimpleInfoPanelValueTextTemplate" { + SetPoint TOPLEFT, "InfoPanelIconHeroAgilityLabel", BOTTOMLEFT, 0.005, 0.0, + } + + // --- intellect -------------------------------------------------------- + String "InfoPanelIconHeroIntellectLabel" INHERITS "SimpleInfoPanelLabelTextTemplate" { + SetPoint TOPLEFT, "InfoPanelIconHeroAgilityValue", BOTTOMLEFT, -0.005, -0.004, + Text "COLON_INTELLECT", + } + + String "InfoPanelIconHeroIntellectValue" INHERITS "SimpleInfoPanelValueTextTemplate" { + SetPoint TOPLEFT, "InfoPanelIconHeroIntellectLabel", BOTTOMLEFT, 0.005, 0.0, + } + } +} + +Frame "SIMPLEFRAME" "SimpleInfoPanelIconAlly" { + UseActiveContext, + DecorateFileNames, + Height 0.0625, + + // --- title ------------------------------------------------------------ + String "InfoPanelIconAllyTitle" INHERITS "SimpleInfoPanelLabelTextTemplate" { + SetPoint TOPLEFT, "SimpleInfoPanelIconAlly", TOPLEFT, 0.0, 0.0, + Text "ALLY_RESOURCES", + } + + // --- gold ------------------------------------------------------------- + Texture "InfoPanelIconAllyGoldIcon" INHERITS "ResourceIconTemplate" { + SetPoint TOPLEFT, "SimpleInfoPanelIconAlly", TOPLEFT, 0.0, -0.009, + File "GoldIcon", + } + + String "InfoPanelIconAllyGoldValue" INHERITS "ResourceTextTemplate" { + SetPoint LEFT, "InfoPanelIconAllyGoldIcon", RIGHT, 0.005, 0.0, + } + + // --- wood ------------------------------------------------------------- + Texture "InfoPanelIconAllyWoodIcon" INHERITS "ResourceIconTemplate" { + SetPoint TOPLEFT, "InfoPanelIconAllyGoldIcon", BOTTOMLEFT, 0.0, 0.0, + File "LumberIcon", + } + + String "InfoPanelIconAllyWoodValue" INHERITS "ResourceTextTemplate" { + SetPoint LEFT, "InfoPanelIconAllyWoodIcon", RIGHT, 0.005, 0.0, + } + + // --- food ------------------------------------------------------------- + Texture "InfoPanelIconAllyFoodIcon" INHERITS "ResourceIconTemplate" { + SetPoint TOPLEFT, "InfoPanelIconAllyWoodIcon", BOTTOMLEFT, 0.0, 0.0, + File "SupplyIcon", + } + + String "InfoPanelIconAllyFoodValue" INHERITS "ResourceTextTemplate" { + SetPoint LEFT, "InfoPanelIconAllyFoodIcon", RIGHT, 0.005, 0.0, + } + + // --- upkeep ----------------------------------------------------------- + String "InfoPanelIconAllyUpkeep" INHERITS "SimpleInfoPanelValueTextTemplate" { + SetPoint TOPLEFT, "InfoPanelIconAllyFoodValue", BOTTOMLEFT, 0.0, -0.005, + Text "Upkeep", + } +} \ No newline at end of file From cfa5f952de2b199446f65f3ffec86265cdf61ce6 Mon Sep 17 00:00:00 2001 From: Retera Date: Tue, 22 Sep 2020 22:30:07 -0400 Subject: [PATCH 046/116] Update to include race, basic target types, better combat UI and behaviors --- core/assets/warsmash.ini | 30 +- core/assets/warsmash131.ini | 17 ++ .../etheller/warsmash/WarsmashGdxGame.java | 87 +++--- .../etheller/warsmash/WarsmashGdxMapGame.java | 164 +++++------ .../etheller/warsmash/parsers/fdf/GameUI.java | 24 +- .../parsers/fdf/frames/SpriteFrame.java | 3 +- .../parsers/fdf/frames/TextureFrame.java | 4 +- .../warsmash/parsers/w3x/w3i/Player.java | 44 ++- .../com/etheller/warsmash/util/Quadtree.java | 16 +- .../warsmash/util/QuadtreeIntersector.java | 13 + .../warsmash/viewer5/AudioContext.java | 1 - .../handlers/mdx/AttachmentInstance.java | 2 +- .../mdx/EventObjectEmitterObject.java | 25 +- .../viewer5/handlers/mdx/EventObjectSnd.java | 3 + .../handlers/mdx/MdxComplexInstance.java | 37 ++- .../viewer5/handlers/mdx/SdSequence.java | 6 +- .../handlers/mdx/SequenceLoopMode.java | 9 + .../viewer5/handlers/tga/TgaFile.java | 6 +- .../viewer5/handlers/w3x/AnimationTokens.java | 2 + ...{StandSequence.java => SequenceUtils.java} | 24 +- .../viewer5/handlers/w3x/SplatModel.java | 106 ++++++- .../viewer5/handlers/w3x/UnitAckSound.java | 109 +++++++ .../viewer5/handlers/w3x/UnitSound.java | 120 ++++++++ .../viewer5/handlers/w3x/UnitSoundset.java | 128 +-------- .../viewer5/handlers/w3x/War3MapViewer.java | 203 +++++++++---- .../handlers/w3x/environment/PathingGrid.java | 9 +- .../handlers/w3x/environment/Terrain.java | 7 + .../w3x/rendersim/RenderAttackInstant.java | 40 +++ .../w3x/rendersim/RenderAttackProjectile.java | 51 ++-- .../handlers/w3x/rendersim/RenderEffect.java | 7 + .../handlers/w3x/rendersim/RenderUnit.java | 93 +++++- .../w3x/simulation/CDestructable.java | 17 ++ .../w3x/simulation/CGameplayConstants.java | 64 ++++- .../handlers/w3x/simulation/CItem.java | 16 ++ .../handlers/w3x/simulation/CPlayer.java | 35 --- .../handlers/w3x/simulation/CSimulation.java | 105 +++++-- .../handlers/w3x/simulation/CUnit.java | 266 +++++++++++++++++- .../simulation/CUnitAnimationListener.java | 8 +- .../w3x/simulation/CUnitEnumFunction.java | 5 + .../w3x/simulation/CUnitStateListener.java | 17 ++ .../handlers/w3x/simulation/CUnitType.java | 26 +- .../handlers/w3x/simulation/CWidget.java | 18 +- .../w3x/simulation/CWorldCollision.java | 169 +++++++++-- .../simulation/abilities/CAbilityAttack.java | 15 +- .../w3x/simulation/combat/CAttackType.java | 19 +- .../w3x/simulation/combat/CDefenseType.java | 2 + .../combat/attacks/CUnitAttack.java | 6 +- .../combat/attacks/CUnitAttackInstant.java | 9 + .../combat/attacks/CUnitAttackMissile.java | 13 + .../attacks/CUnitAttackMissileBounce.java | 75 ++++- .../attacks/CUnitAttackMissileSplash.java | 67 +++++ .../combat/attacks/CUnitAttackNormal.java | 8 + .../combat/projectile/CAttackProjectile.java | 43 ++- .../w3x/simulation/data/CUnitData.java | 237 ++++++++-------- .../w3x/simulation/orders/CAttackOrder.java | 69 ++++- .../w3x/simulation/orders/CMoveOrder.java | 123 ++++++-- .../pathing/CPathfindingProcessor.java | 115 ++++++-- .../w3x/simulation/players/CAllianceType.java | 14 + .../w3x/simulation/players/CMapControl.java | 10 + .../w3x/simulation/players/CPlayer.java | 109 +++++++ .../{ => players}/CPlayerController.java | 2 +- .../w3x/simulation/players/CRace.java | 30 ++ .../simulation/players/CRacePreference.java | 11 + .../simulation/util/ProjectileCreator.java | 10 - .../util/SimulationRenderController.java | 19 ++ .../viewer5/handlers/w3x/ui/MeleeUI.java | 105 ++++++- .../warsmash/desktop/DesktopLauncher.java | 10 +- 67 files changed, 2513 insertions(+), 744 deletions(-) create mode 100644 core/assets/warsmash131.ini create mode 100644 core/src/com/etheller/warsmash/util/QuadtreeIntersector.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/SequenceLoopMode.java rename core/src/com/etheller/warsmash/viewer5/handlers/w3x/{StandSequence.java => SequenceUtils.java} (87%) create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitAckSound.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitSound.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackInstant.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderEffect.java delete mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CPlayer.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitEnumFunction.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitStateListener.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CAllianceType.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CMapControl.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayer.java rename core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/{ => players}/CPlayerController.java (78%) create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CRace.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CRacePreference.java delete mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/ProjectileCreator.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java diff --git a/core/assets/warsmash.ini b/core/assets/warsmash.ini index 276c5ba..549c548 100644 --- a/core/assets/warsmash.ini +++ b/core/assets/warsmash.ini @@ -1,15 +1,23 @@ [DataSources] -Count=5 -Type00=Folder -Path00="E:\Backups\Warcraft III 1.30 but dead\War3mod.mpq" -Type01=Folder -Path01="E:\Backups\Warcraft\Data\127" -Type02=Folder -Path02="..\..\resources" -Type03=Folder -Path03="E:\Backups\Warsmash\Data" +Count=7 +Type00=MPQ +Path00="E:\Games\Warcraft III Patch 1.22\war3.mpq" +Type01=MPQ +Path01="E:\Games\Warcraft III Patch 1.22\War3x.mpq" +Type02=MPQ +Path02="E:\Games\Warcraft III Patch 1.22\War3xlocal.mpq" +Type03=MPQ +Path03="E:\Games\Warcraft III Patch 1.22\War3Patch.mpq" Type04=Folder -Path04="." +Path04="..\..\resources" +Type05=Folder +Path05="E:\Backups\Warsmash\Data" +Type06=Folder +Path06="." [Map] -FilePath="PitchRoll.w3x" \ No newline at end of file +//FilePath="CombatUnitTests.w3x" +FilePath="PitchRoll.w3x" +//FilePath="PlayerPeasants.w3m" +//FilePath="FireLord.w3x" +//FilePath="Maps\Campaign\NightElf03.w3m" \ No newline at end of file diff --git a/core/assets/warsmash131.ini b/core/assets/warsmash131.ini new file mode 100644 index 0000000..452f706 --- /dev/null +++ b/core/assets/warsmash131.ini @@ -0,0 +1,17 @@ +[DataSources] +Count=5 +Type00=Folder +Path00="E:\Games\Warcraft III CASC 1.31\war3.w3mod" +Type01=Folder +Path01="E:\Games\Warcraft III CASC 1.31\war3.w3mod\_locales\enus.w3mod" +Type02=Folder +Path02="..\..\resources" +Type03=Folder +Path03="E:\Backups\Warsmash\Data" +Type04=Folder +Path04="." + +[Map] +FilePath="PitchRoll.w3x" +//FilePath="ReforgedGeorgeVacation.w3x" +//FilePath="Maps\Campaign\NightElf03.w3m" \ No newline at end of file diff --git a/core/src/com/etheller/warsmash/WarsmashGdxGame.java b/core/src/com/etheller/warsmash/WarsmashGdxGame.java index 81d8a76..3b3121d 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxGame.java @@ -7,7 +7,6 @@ import java.util.Arrays; import com.badlogic.gdx.ApplicationAdapter; import com.badlogic.gdx.Gdx; -import com.badlogic.gdx.audio.Music; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.SpriteBatch; @@ -19,7 +18,6 @@ import com.etheller.warsmash.datasources.DataSource; import com.etheller.warsmash.datasources.DataSourceDescriptor; import com.etheller.warsmash.datasources.FolderDataSourceDescriptor; import com.etheller.warsmash.parsers.mdlx.Sequence; -import com.etheller.warsmash.util.DataSourceFileHandle; import com.etheller.warsmash.viewer5.Camera; import com.etheller.warsmash.viewer5.CanvasProvider; import com.etheller.warsmash.viewer5.ModelViewer; @@ -29,6 +27,7 @@ import com.etheller.warsmash.viewer5.SolvedPath; import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxHandler; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; +import com.etheller.warsmash.viewer5.handlers.mdx.SequenceLoopMode; public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvider { private static final boolean SPIN = false; @@ -68,21 +67,21 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide this.viewer.addHandler(new MdxHandler()); this.viewer.enableAudio(); - final Scene scene = this.viewer.addWorldScene(); -// scene.enableAudio(); + final Scene scene = this.viewer.addSimpleScene(); + scene.enableAudio(); this.cameraManager = new CameraManager(); this.cameraManager.setupCamera(scene); // this.mainModel = (MdxModel) this.viewer.load("Doodads\\Cinematic\\ArthasIllidanFight\\ArthasIllidanFight.mdx", - this.mainModel = (MdxModel) this.viewer.load("UI\\Glues\\SinglePlayer\\NightElf_Exp\\NightElf_Exp.mdx", +// this.mainModel = (MdxModel) this.viewer.load("UI\\Glues\\SinglePlayer\\NightElf_Exp\\NightElf_Exp.mdx", // this.mainModel = (MdxModel) this.viewer.load("Abilities\\Spells\\Orc\\FeralSpirit\\feralspirittarget.mdx", - new PathSolver() { - @Override - public SolvedPath solve(final String src, final Object solverParams) { - return new SolvedPath(src, src.substring(src.lastIndexOf('.')), true); - } - }, null); +// new PathSolver() { +// @Override +// public SolvedPath solve(final String src, final Object solverParams) { +// return new SolvedPath(src, src.substring(src.lastIndexOf('.')), true); +// } +// }, null); // final EventObjectEmitterObject evt = this.mainModel.getEventObjects().get(1); // for (final Sequence seq : this.mainModel.getSequences()) { @@ -91,18 +90,20 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide // System.out.println(Arrays.toString(evt.keyFrames)); // System.out.println(evt.name); - this.mainInstance = (MdxComplexInstance) this.mainModel.addInstance(0); +// this.mainInstance = (MdxComplexInstance) this.mainModel.addInstance(0); - this.mainInstance.setScene(scene); - - final int animIndex = 0; - this.modelCamera = this.mainModel.cameras.get(animIndex); - this.mainInstance.setSequence(animIndex); - - this.mainInstance.setSequenceLoopMode(4); +// this.mainInstance.setScene(scene); +// +// final int animIndex = 0; +// this.modelCamera = this.mainModel.cameras.get(animIndex); +// this.mainInstance.setSequence(animIndex); +// +// this.mainInstance.setSequenceLoopMode(SequenceLoopMode.LOOP_TO_NEXT_ANIMATION); // acolytesHarvestingSceneJoke2(scene); + singleModelScene(scene, "Buildings\\Undead\\Necropolis\\Necropolis.mdx", "birth"); + System.out.println("Loaded"); Gdx.gl30.glClearColor(0.5f, 0.5f, 0.5f, 1); // TODO remove white background @@ -141,7 +142,7 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide } instance3.setSequence(animIndex); - instance3.setSequenceLoopMode(2); + instance3.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); } private void singleModelScene(final Scene scene, final String path, final String animName) { @@ -160,11 +161,12 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide for (final Sequence s : model2.getSequences()) { if (s.getName().toLowerCase().startsWith(animName)) { animIndex = model2.getSequences().indexOf(s); + break; } } instance3.setSequence(animIndex); - instance3.setSequenceLoopMode(2); + instance3.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); } private void acolytesHarvestingScene(final Scene scene) { @@ -196,7 +198,7 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide } acolyteInstance.setSequence(animIndex); - acolyteInstance.setSequenceLoopMode(2); + acolyteInstance.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); final double angle = ((Math.PI * 2) / 5) * i; acolyteInstance.localLocation.x = (float) Math.cos(angle) * 256; @@ -209,7 +211,7 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide effectInstance.setSequence(1); - effectInstance.setSequenceLoopMode(2); + effectInstance.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); effectInstance.localLocation.x = (float) Math.cos(angle) * 256; effectInstance.localLocation.y = (float) Math.sin(angle) * 256; effectInstance.localRotation.setFromAxisRad(0, 0, 1, (float) (angle)); @@ -228,7 +230,7 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide mineInstance.setSequence(2); - mineInstance.setSequenceLoopMode(2); + mineInstance.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); } private void acolytesHarvestingSceneJoke2(final Scene scene) { @@ -260,7 +262,7 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide } acolyteInstance.setSequence(animIndex); - acolyteInstance.setSequenceLoopMode(2); + acolyteInstance.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); final double angle = ((Math.PI * 2) / 5) * i; acolyteInstance.localLocation.x = (float) Math.cos(angle) * 256; @@ -273,7 +275,7 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide effectInstance.setSequence(1); - effectInstance.setSequenceLoopMode(2); + effectInstance.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); effectInstance.localLocation.x = (float) Math.cos(angle) * 256; effectInstance.localLocation.y = (float) Math.sin(angle) * 256; effectInstance.localRotation.setFromAxisRad(0, 0, 1, (float) (angle)); @@ -295,7 +297,7 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide mineInstance.localScale.y = 2; mineInstance.localScale.z = 2; - mineInstance.setSequenceLoopMode(2); + mineInstance.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); final MdxModel mineModel2 = (MdxModel) this.viewer .load("abilities\\spells\\undead\\unsummon\\unsummontarget.mdx", new PathSolver() { @Override @@ -309,7 +311,7 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide mineInstance2.setSequence(0); - mineInstance2.setSequenceLoopMode(2); + mineInstance2.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); } private void makeFourHundred(final Scene scene, final MdxModel model2) { @@ -323,7 +325,7 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide final int animIndex = i % model2.getSequences().size(); instance3.setSequence(animIndex); - instance3.setSequenceLoopMode(2); + instance3.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); } } @@ -339,7 +341,7 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide final int animIndex = i % model2.getSequences().size(); instance3.setSequence(animIndex); - instance3.setSequenceLoopMode(2); + instance3.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); } } @@ -353,13 +355,13 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide private com.etheller.warsmash.viewer5.handlers.mdx.Camera modelCamera; private final float[] cameraPositionTemp = new float[3]; private final float[] cameraTargetTemp = new float[3]; - private boolean firstFrame = true; + private final boolean firstFrame = true; @Override public void render() { Gdx.gl30.glBindVertexArray(VAO); if (SPIN) { - this.cameraManager.horizontalAngle += 0.01; + this.cameraManager.horizontalAngle += 0.0001; if (this.cameraManager.horizontalAngle > (2 * Math.PI)) { this.cameraManager.horizontalAngle = 0; } @@ -384,14 +386,14 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide this.mainInstance.setSequence(sequence); this.mainInstance.frame += (int) (Gdx.graphics.getRawDeltaTime() * 1000); } - if (this.firstFrame) { - final Music music = Gdx.audio.newMusic(new DataSourceFileHandle(this.viewer.dataSource, - "Sound\\Ambient\\DoodadEffects\\FinalCinematic.mp3")); - music.setVolume(0.2f); - music.setLooping(true); - music.play(); - this.firstFrame = false; - } +// if (this.firstFrame) { +// final Music music = Gdx.audio.newMusic(new DataSourceFileHandle(this.viewer.dataSource, +// "Sound\\Ambient\\DoodadEffects\\FinalCinematic.mp3")); +// music.setVolume(0.2f); +// music.setLooping(true); +// music.play(); +// this.firstFrame = false; +// } } @Override @@ -445,7 +447,7 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide this.zoomFactor = 0.1f; this.horizontalAngle = (float) (Math.PI / 2); this.verticalAngle = (float) (Math.PI / 4); - this.distance = 1000; + this.distance = 500; this.position = new Vector3(); this.target = new Vector3(0, 0, 50); this.worldUp = new Vector3(0, 0, 1); @@ -503,6 +505,9 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide WarsmashGdxGame.this.modelCamera.nearClippingPlane, WarsmashGdxGame.this.modelCamera.farClippingPlane); } + else { + this.camera.perspective(70, this.camera.getAspect(), 100, 5000); + } this.camera.moveToAndFace(this.position, this.target, this.worldUp); } diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java index db4ca3d..e7aa3ea 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java @@ -30,8 +30,7 @@ import com.badlogic.gdx.math.Quaternion; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.math.Vector3; -import com.badlogic.gdx.utils.viewport.FitViewport; -import com.badlogic.gdx.utils.viewport.Viewport; +import com.badlogic.gdx.utils.viewport.ExtendViewport; import com.etheller.warsmash.datasources.CompoundDataSourceDescriptor; import com.etheller.warsmash.datasources.DataSource; import com.etheller.warsmash.datasources.DataSourceDescriptor; @@ -51,12 +50,9 @@ import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; import com.etheller.warsmash.viewer5.handlers.mdx.ReplaceableIds; import com.etheller.warsmash.viewer5.handlers.tga.TgaFile; -import com.etheller.warsmash.viewer5.handlers.w3x.UnitSoundset.UnitAckSound; +import com.etheller.warsmash.viewer5.handlers.w3x.UnitSound; import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; -import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.CommandCardIcon; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderUnit; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.COrder; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityStop; import com.etheller.warsmash.viewer5.handlers.w3x.ui.MeleeUI; public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProvider, InputProcessor { @@ -74,18 +70,14 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv // libGDX stuff private OrthographicCamera uiCamera; private BitmapFont font; - private BitmapFont font24; private BitmapFont font20; private SpriteBatch batch; - private Viewport uiViewport; + private ExtendViewport uiViewport; private GlyphLayout glyphLayout; private Texture consoleUITexture; - private RenderUnit selectedUnit; private int selectedSoundCount = 0; - private Texture activeButtonTexture; - private Rectangle minimap; private Rectangle minimapFilledArea; @@ -171,20 +163,11 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv final float w = Gdx.graphics.getWidth(); final float h = Gdx.graphics.getHeight(); - this.tempRect.x = 0; - this.tempRect.y = 0; - this.tempRect.width = w; - this.tempRect.height = h; - this.uiScene.camera.viewport(this.tempRect); - this.uiScene.camera.ortho(0, 0.8f, 0, 0.6f, 0, 1); - final FreeTypeFontGenerator fontGenerator = new FreeTypeFontGenerator( new DataSourceFileHandle(this.viewer.dataSource, "fonts\\FRIZQT__.TTF")); final FreeTypeFontParameter fontParam = new FreeTypeFontParameter(); fontParam.size = 32; this.font = fontGenerator.generateFont(fontParam); - fontParam.size = 24; - this.font24 = fontGenerator.generateFont(fontParam); fontParam.size = 20; this.font20 = fontGenerator.generateFont(fontParam); this.glyphLayout = new GlyphLayout(); @@ -193,10 +176,10 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv // height // Height is multiplied by aspect ratio. this.uiCamera = new OrthographicCamera(); - this.uiViewport = new FitViewport(1600, 1200, this.uiCamera); + this.uiViewport = new ExtendViewport(1600, 1200, this.uiCamera); this.uiViewport.update(Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); - this.uiCamera.position.set(this.uiCamera.viewportWidth / 2f, this.uiCamera.viewportHeight / 2f, 0); + this.uiCamera.position.set(this.uiViewport.getMinWorldWidth() / 2, this.uiViewport.getMinWorldHeight() / 2, 0); this.uiCamera.update(); this.batch = new SpriteBatch(); @@ -223,9 +206,6 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv } } - this.activeButtonTexture = ImageUtils.getBLPTexture(this.viewer.dataSource, - "UI\\Widgets\\Console\\Human\\CommandButton\\human-activebutton.blp"); - for (int i = 0; i < this.teamColors.length; i++) { this.teamColors[i] = ImageUtils.getBLPTexture(this.viewer.dataSource, "ReplaceableTextures\\" + ReplaceableIds.getPathString(1) + ReplaceableIds.getIdString(i) + ".blp"); @@ -236,8 +216,8 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv Gdx.input.setInputProcessor(this); -// final Music music = Gdx.audio.newMusic( -// new DataSourceFileHandle(this.viewer.dataSource, "Sound\\Music\\mp3Music\\War2IntroMusic.mp3")); +// final Music music = Gdx.audio +// .newMusic(new DataSourceFileHandle(this.viewer.dataSource, "Sound\\Music\\mp3Music\\Undead2.mp3")); // music.setVolume(0.2f); // music.setLooping(true); // music.play(); @@ -277,6 +257,27 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv }); this.meleeUI.main(); fontGenerator.dispose(); + + updateUIScene(); + + this.meleeUI.resize(); + } + + private void updateUIScene() { + this.tempRect.x = this.uiViewport.getScreenX(); + this.tempRect.y = this.uiViewport.getScreenY(); + this.tempRect.width = this.uiViewport.getScreenWidth(); + this.tempRect.height = this.uiViewport.getScreenHeight(); + this.uiScene.camera.viewport(this.tempRect); + final float worldWidth = this.uiViewport.getWorldWidth(); + final float worldHeight = this.uiViewport.getWorldHeight(); + final float xScale = worldWidth / this.uiViewport.getMinWorldWidth(); + final float yScale = worldHeight / this.uiViewport.getMinWorldHeight(); + final float uiSceneWidth = 0.8f * xScale; + final float uiSceneHeight = 0.6f * yScale; + final float uiSceneX = ((0.8f - uiSceneWidth) / 2); + final float uiSceneY = ((0.6f - uiSceneHeight) / 2); + this.uiScene.camera.ortho(uiSceneX, uiSceneWidth + uiSceneX, uiSceneY, uiSceneHeight + uiSceneY, -1f, 1); } @Override @@ -313,32 +314,11 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.font.setColor(Color.YELLOW); final String fpsString = "FPS: " + Gdx.graphics.getFramesPerSecond(); this.glyphLayout.setText(this.font, fpsString); - this.font.draw(this.batch, fpsString, (this.uiViewport.getWorldWidth() - this.glyphLayout.width) / 2, 1100); + this.font.draw(this.batch, fpsString, (this.uiViewport.getMinWorldWidth() - this.glyphLayout.width) / 2, 1100); this.batch.draw(this.minimapTexture, this.minimap.x, this.minimap.y, this.minimap.width, this.minimap.height); - if (this.selectedUnit != null) { - int messageIndex = 0; - for (final Message message : this.messages) { - this.font20.draw(this.batch, message.text, 100, 400 + (25 * (messageIndex++))); - } - this.font20.setColor(Color.WHITE); - - final COrder currentOrder = this.selectedUnit.getSimulationUnit().getCurrentOrder(); - for (final CommandCardIcon commandCardIcon : this.selectedUnit.getCommandCardIcons()) { - this.batch.draw(commandCardIcon.getTexture(), 1235 + (86.8f * commandCardIcon.getX()), - 190 - (88 * commandCardIcon.getY()), 78f, 78f); - if (((currentOrder != null) && (currentOrder.getOrderId() == commandCardIcon.getOrderId())) - || ((currentOrder == null) && (commandCardIcon.getOrderId() == CAbilityStop.ORDER_ID))) { - final int blendDstFunc = this.batch.getBlendDstFunc(); - final int blendSrcFunc = this.batch.getBlendSrcFunc(); - this.batch.setBlendFunction(GL20.GL_SRC_ALPHA, GL20.GL_ONE); - this.batch.draw(this.activeButtonTexture, 1235 + (86.8f * commandCardIcon.getX()), - 190 - (88 * commandCardIcon.getY()), 78f, 78f); - this.batch.setBlendFunction(blendSrcFunc, blendDstFunc); - } - } - } + final Rectangle playableMapArea = this.viewer.terrain.getPlayableMapArea(); for (final RenderUnit unit : this.viewer.units) { if (unit.playerIndex >= WarsmashConstants.MAX_PLAYERS) { System.err.println(unit.row.getName() + " at ( " + unit.location[0] + ", " + unit.location[1] + " )" @@ -347,10 +327,12 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv } final Texture minimapIcon = this.teamColors[unit.playerIndex]; this.batch.draw(minimapIcon, - this.minimapFilledArea.x + (((unit.location[0] - this.viewer.terrain.centerOffset[0]) - / ((this.viewer.terrain.columns - 1) * 128f)) * this.minimapFilledArea.width), - this.minimapFilledArea.y + (((unit.location[1] - this.viewer.terrain.centerOffset[1]) - / ((this.viewer.terrain.rows - 1) * 128f)) * this.minimapFilledArea.height), + this.minimapFilledArea.x + + (((unit.location[0] - playableMapArea.getX()) / (playableMapArea.getWidth())) + * this.minimapFilledArea.width), + this.minimapFilledArea.y + + (((unit.location[1] - playableMapArea.getY()) / (playableMapArea.getHeight())) + * this.minimapFilledArea.height), 4, 4); } this.batch.end(); @@ -411,14 +393,9 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv final float portraitTestHeight = (100 / 480f) * height; this.uiViewport.update(width, height); - this.uiCamera.position.set(this.uiCamera.viewportWidth / 2, this.uiCamera.viewportHeight / 2, 0); + this.uiCamera.position.set(this.uiViewport.getMinWorldWidth() / 2, this.uiViewport.getMinWorldHeight() / 2, 0); - this.tempRect.x = this.uiViewport.getScreenX(); - this.tempRect.y = this.uiViewport.getScreenY(); - this.tempRect.width = this.uiViewport.getScreenWidth(); - this.tempRect.height = this.uiViewport.getScreenHeight(); - this.uiScene.camera.viewport(this.tempRect); - this.uiScene.camera.ortho(0f, 0.8f, 0f, 0.6f, -1f, 1); + updateUIScene(); this.meleeUI.resize(); } @@ -621,20 +598,6 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv clickLocationTemp2.y = screenY; this.uiViewport.unproject(clickLocationTemp2); - if (this.selectedUnit != null) { - for (final CommandCardIcon commandCardIcon : this.selectedUnit.getCommandCardIcons()) { - if (new Rectangle(1235 + (86.8f * commandCardIcon.getX()), 190 - (88 * commandCardIcon.getY()), 78f, - 78f).contains(clickLocationTemp2)) { - if (button == Input.Buttons.RIGHT) { - this.messages.add(new Message(Gdx.input.getCurrentEventTime(), "Right mouse click")); - } - else { - this.messages.add(new Message(Gdx.input.getCurrentEventTime(), "Left mouse click")); - } - return true; - } - } - } if (this.minimapFilledArea.contains(clickLocationTemp2.x, clickLocationTemp2.y)) { final float clickX = (clickLocationTemp2.x - this.minimapFilledArea.x) / this.minimapFilledArea.width; final float clickY = (clickLocationTemp2.y - this.minimapFilledArea.y) / this.minimapFilledArea.height; @@ -646,24 +609,32 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv } if (button == Input.Buttons.RIGHT) { final RenderUnit rayPickUnit = this.viewer.rayPickUnit(screenX, worldScreenY); - if ((rayPickUnit != null) && (this.selectedUnit != null) - && (rayPickUnit.playerIndex != this.selectedUnit.playerIndex)) { - if (this.viewer.orderSmart(rayPickUnit)) { - this.meleeUI.portraitTalk(); - this.selectedSoundCount = 0; + if (this.meleeUI.getSelectedUnit() != null) { + if ((rayPickUnit != null) && (rayPickUnit.playerIndex != this.meleeUI.getSelectedUnit().playerIndex) + && !rayPickUnit.getSimulationUnit().isDead()) { + if (this.viewer.orderSmart(rayPickUnit)) { + if (this.meleeUI.getSelectedUnit().soundset.yesAttack.playUnitResponse( + this.viewer.worldScene.audioContext, this.meleeUI.getSelectedUnit())) { + this.meleeUI.portraitTalk(); + } + this.selectedSoundCount = 0; + } } - } - else { - this.viewer.getClickLocation(clickLocationTemp, screenX, (int) worldScreenY); - System.out.println(clickLocationTemp); - this.viewer.showConfirmation(clickLocationTemp, 0, 1, 0); - final int x = (int) ((clickLocationTemp.x - this.viewer.terrain.centerOffset[0]) / 128); - final int y = (int) ((clickLocationTemp.y - this.viewer.terrain.centerOffset[1]) / 128); - System.out.println(x + "," + y); - this.viewer.terrain.logRomp(x, y); - if (this.viewer.orderSmart(clickLocationTemp.x, clickLocationTemp.y)) { - this.meleeUI.portraitTalk(); - this.selectedSoundCount = 0; + else { + this.viewer.getClickLocation(clickLocationTemp, screenX, (int) worldScreenY); + System.out.println(clickLocationTemp); + this.viewer.showConfirmation(clickLocationTemp, 0, 1, 0); + final int x = (int) ((clickLocationTemp.x - this.viewer.terrain.centerOffset[0]) / 128); + final int y = (int) ((clickLocationTemp.y - this.viewer.terrain.centerOffset[1]) / 128); + System.out.println(x + "," + y); + this.viewer.terrain.logRomp(x, y); + if (this.viewer.orderSmart(clickLocationTemp.x, clickLocationTemp.y)) { + if (this.meleeUI.getSelectedUnit().soundset.yes.playUnitResponse( + this.viewer.worldScene.audioContext, this.meleeUI.getSelectedUnit())) { + this.meleeUI.portraitTalk(); + } + this.selectedSoundCount = 0; + } } } } @@ -671,14 +642,13 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv final List selectedUnits = this.viewer.selectUnit(screenX, worldScreenY, false); if (!selectedUnits.isEmpty()) { final RenderUnit unit = selectedUnits.get(0); - final boolean selectionChanged = this.selectedUnit != unit; + final boolean selectionChanged = this.meleeUI.getSelectedUnit() != unit; boolean playedNewSound = false; if (selectionChanged) { this.selectedSoundCount = 0; } - this.selectedUnit = unit; if (unit.soundset != null) { - UnitAckSound ackSoundToPlay = unit.soundset.what; + UnitSound ackSoundToPlay = unit.soundset.what; final int pissedSoundCount = unit.soundset.pissed.getSoundCount(); int soundIndex; if ((this.selectedSoundCount >= 3) && (pissedSoundCount > 0)) { @@ -688,8 +658,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv else { soundIndex = (int) (Math.random() * ackSoundToPlay.getSoundCount()); } - if (ackSoundToPlay.play(this.viewer.worldScene.audioContext, unit.location[0], unit.location[1], - soundIndex)) { + if (ackSoundToPlay.playUnitResponse(this.viewer.worldScene.audioContext, unit, soundIndex)) { this.selectedSoundCount++; if ((this.selectedSoundCount - 3) >= pissedSoundCount) { this.selectedSoundCount = 0; @@ -705,7 +674,6 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv } } else { - this.selectedUnit = null; this.meleeUI.selectUnit(null); } } diff --git a/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java index fbf8f2f..994aa5b 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java @@ -12,6 +12,7 @@ import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator; import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator.FreeTypeFontParameter; +import com.badlogic.gdx.utils.viewport.ExtendViewport; import com.badlogic.gdx.utils.viewport.FitViewport; import com.badlogic.gdx.utils.viewport.Viewport; import com.etheller.warsmash.datasources.DataSource; @@ -62,7 +63,13 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { this.viewport = viewport; this.uiScene = uiScene; this.modelViewer = modelViewer; - this.renderBounds.set(0, 0, viewport.getWorldWidth(), viewport.getWorldHeight()); + if (viewport instanceof ExtendViewport) { + this.renderBounds.set(0, 0, ((ExtendViewport) viewport).getMinWorldWidth(), + ((ExtendViewport) viewport).getMinWorldHeight()); + } + else { + this.renderBounds.set(0, 0, viewport.getWorldWidth(), viewport.getWorldHeight()); + } this.templates = new FrameTemplateEnvironment(); this.fontGenerator = fontGenerator; this.fontParam = new FreeTypeFontParameter(); @@ -312,10 +319,16 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { } public static float convertX(final Viewport viewport, final float fdfX) { + if (viewport instanceof ExtendViewport) { + return (fdfX / 0.8f) * ((ExtendViewport) viewport).getMinWorldWidth(); + } return (fdfX / 0.8f) * viewport.getWorldWidth(); } public static float convertY(final Viewport viewport, final float fdfY) { + if (viewport instanceof ExtendViewport) { + return (fdfY / 0.6f) * ((ExtendViewport) viewport).getMinWorldHeight(); + } return (fdfY / 0.6f) * viewport.getWorldHeight(); } @@ -325,8 +338,13 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { } Texture texture = this.pathToTexture.get(path); if (texture == null) { - texture = ImageUtils.getBLPTexture(this.dataSource, path); - this.pathToTexture.put(path, texture); + try { + texture = ImageUtils.getBLPTexture(this.dataSource, path); + this.pathToTexture.put(path, texture); + } + catch (final Exception exc) { + + } } return texture; } diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/SpriteFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/SpriteFrame.java index 1cfbd53..b783df6 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/SpriteFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/SpriteFrame.java @@ -7,6 +7,7 @@ import com.badlogic.gdx.utils.viewport.Viewport; import com.etheller.warsmash.viewer5.Scene; import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; +import com.etheller.warsmash.viewer5.handlers.mdx.SequenceLoopMode; public class SpriteFrame extends AbstractRenderableFrame { @@ -24,7 +25,7 @@ public class SpriteFrame extends AbstractRenderableFrame { } if (model != null) { this.instance = (MdxComplexInstance) model.addInstance(); - this.instance.setSequenceLoopMode(1); + this.instance.setSequenceLoopMode(SequenceLoopMode.MODEL_LOOP); this.instance.setScene(this.scene); this.instance.setLocation(this.renderBounds.x, this.renderBounds.y, 0); } diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/TextureFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/TextureFrame.java index da90653..61ba209 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/TextureFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/TextureFrame.java @@ -36,7 +36,9 @@ public class TextureFrame extends AbstractRenderableFrame { file = gameUI.getSkinField(file); } final Texture texture = gameUI.loadTexture(file); - setTexture(texture); + if (texture != null) { + setTexture(texture); + } } public void setTexture(final Texture texture) { diff --git a/core/src/com/etheller/warsmash/parsers/w3x/w3i/Player.java b/core/src/com/etheller/warsmash/parsers/w3x/w3i/Player.java index 889bfac..35653c4 100644 --- a/core/src/com/etheller/warsmash/parsers/w3x/w3i/Player.java +++ b/core/src/com/etheller/warsmash/parsers/w3x/w3i/Player.java @@ -32,8 +32,8 @@ public class Player { this.allyLowPriorities = ParseUtils.readUInt32(stream); this.allyHighPriorities = ParseUtils.readUInt32(stream); if (version > 30) { - enemyLowPrioritiesFlags = ParseUtils.readUInt32(stream); - enemyHighPrioritiesFlags = ParseUtils.readUInt32(stream); + this.enemyLowPrioritiesFlags = ParseUtils.readUInt32(stream); + this.enemyHighPrioritiesFlags = ParseUtils.readUInt32(stream); } } @@ -51,4 +51,44 @@ public class Player { public int getByteLength() { return 33 + this.name.length(); } + + public War3ID getId() { + return this.id; + } + + public int getType() { + return this.type; + } + + public int getRace() { + return this.race; + } + + public int getIsFixedStartPosition() { + return this.isFixedStartPosition; + } + + public String getName() { + return this.name; + } + + public float[] getStartLocation() { + return this.startLocation; + } + + public long getAllyLowPriorities() { + return this.allyLowPriorities; + } + + public long getAllyHighPriorities() { + return this.allyHighPriorities; + } + + public long getEnemyLowPrioritiesFlags() { + return this.enemyLowPrioritiesFlags; + } + + public long getEnemyHighPrioritiesFlags() { + return this.enemyHighPrioritiesFlags; + } } diff --git a/core/src/com/etheller/warsmash/util/Quadtree.java b/core/src/com/etheller/warsmash/util/Quadtree.java index d3ae6d3..939a03f 100644 --- a/core/src/com/etheller/warsmash/util/Quadtree.java +++ b/core/src/com/etheller/warsmash/util/Quadtree.java @@ -38,34 +38,36 @@ public class Quadtree { add(node, 0); } - public boolean intersectsAnythingOtherThan(final T sourceObjectToIgnore, final Rectangle bounds) { + public boolean intersect(final Rectangle bounds, final QuadtreeIntersector intersector) { if (this.leaf) { for (int i = 0; i < this.nodes.size; i++) { final Node node = this.nodes.get(i); - if ((node.object != sourceObjectToIgnore) && node.bounds.overlaps(bounds)) { - return true; + if (node.bounds.overlaps(bounds)) { + if (intersector.onIntersect(node.object)) { + return true; + } } } return false; } else { if (this.northeast.bounds.overlaps(bounds)) { - if (this.northeast.intersectsAnythingOtherThan(sourceObjectToIgnore, bounds)) { + if (this.northeast.intersect(bounds, intersector)) { return true; } } if (this.northwest.bounds.overlaps(bounds)) { - if (this.northwest.intersectsAnythingOtherThan(sourceObjectToIgnore, bounds)) { + if (this.northwest.intersect(bounds, intersector)) { return true; } } if (this.southwest.bounds.overlaps(bounds)) { - if (this.southwest.intersectsAnythingOtherThan(sourceObjectToIgnore, bounds)) { + if (this.southwest.intersect(bounds, intersector)) { return true; } } if (this.southeast.bounds.overlaps(bounds)) { - if (this.southeast.intersectsAnythingOtherThan(sourceObjectToIgnore, bounds)) { + if (this.southeast.intersect(bounds, intersector)) { return true; } } diff --git a/core/src/com/etheller/warsmash/util/QuadtreeIntersector.java b/core/src/com/etheller/warsmash/util/QuadtreeIntersector.java new file mode 100644 index 0000000..fa7a890 --- /dev/null +++ b/core/src/com/etheller/warsmash/util/QuadtreeIntersector.java @@ -0,0 +1,13 @@ +package com.etheller.warsmash.util; + +public interface QuadtreeIntersector { + /** + * Handles what to do when the intersector finds an intersecting object, + * returning true if we should stop the intersection test and process no more + * objects. + * + * @param intersectingObject + * @return + */ + boolean onIntersect(T intersectingObject); +} diff --git a/core/src/com/etheller/warsmash/viewer5/AudioContext.java b/core/src/com/etheller/warsmash/viewer5/AudioContext.java index 6ee37f0..77e5154 100644 --- a/core/src/com/etheller/warsmash/viewer5/AudioContext.java +++ b/core/src/com/etheller/warsmash/viewer5/AudioContext.java @@ -3,7 +3,6 @@ package com.etheller.warsmash.viewer5; public class AudioContext { private boolean running = false; public Listener listener = new Listener(); - public long lastUnitResponseEndTimeMillis; public AudioDestination destination = new AudioDestination() { }; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/AttachmentInstance.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/AttachmentInstance.java index cbabcc7..5b31ca5 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/AttachmentInstance.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/AttachmentInstance.java @@ -13,7 +13,7 @@ public class AttachmentInstance implements UpdatableObject { final MdxModel internalModel = attachment.internalModel; final MdxComplexInstance internalInstance = (MdxComplexInstance) internalModel.addInstance(); - internalInstance.setSequenceLoopMode(2); + internalInstance.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); internalInstance.dontInheritScaling = false; internalInstance.hide(); internalInstance.setParent(instance.nodes[attachment.objectId]); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectEmitterObject.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectEmitterObject.java index bf1663e..be44cf7 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectEmitterObject.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectEmitterObject.java @@ -279,20 +279,27 @@ public class EventObjectEmitterObject extends GenericObject implements EmitterOb final String[] fileNames = ((String) animSoundsRow.get("FileNames")).split(","); final GenericResource[] resources = new GenericResource[fileNames.length]; for (int i = 0; i < fileNames.length; i++) { - final String pathString = pathSolver.solve( - ((String) animSoundsRow.get("DirectoryBase")) + fileNames[i], - model.solverParams).finalSrc; - final GenericResource genericResource = viewer.loadGeneric(pathString, - FetchDataTypeName.ARRAY_BUFFER, new LoadGenericSoundCallback(pathString)); - if (genericResource == null) { - throw new IllegalStateException("Null sound: " + fileNames[i]); + final String path = ((String) animSoundsRow.get("DirectoryBase")) + fileNames[i]; + try { + final String pathString = pathSolver.solve(path, model.solverParams).finalSrc; + final GenericResource genericResource = viewer.loadGeneric(pathString, + FetchDataTypeName.ARRAY_BUFFER, new LoadGenericSoundCallback(pathString)); + if (genericResource == null) { + System.err.println("Null sound: " + fileNames[i]); + } + resources[i] = genericResource; + } + catch (final Exception exc) { + System.err.println("Failed to load sound: " + path); + exc.printStackTrace(); } - resources[i] = genericResource; } // TODO JS async removed for (final GenericResource resource : resources) { - this.decodedBuffers.add((Sound) resource.data); + if (resource != null) { + this.decodedBuffers.add((Sound) resource.data); + } } this.ok = true; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectSnd.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectSnd.java index f7bf38a..575fea3 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectSnd.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectSnd.java @@ -29,6 +29,9 @@ public class EventObjectSnd extends EmittedObject decodedBuffers = emitterObject.decodedBuffers; + if (decodedBuffers.isEmpty()) { + return; + } final AudioPanner panner = audioContext.createPanner(); final AudioBufferSource source = audioContext.createBufferSource(); final Vector3 location = node.worldLocation; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java index ded730c..16887a9 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java @@ -42,10 +42,11 @@ public class MdxComplexInstance extends ModelInstance { public MdxNode[] nodes; public SkeletalNode[] sortedNodes; public int frame = 0; + public float floatingFrame = 0; // Global sequences public int counter = 0; public int sequence = -1; - public int sequenceLoopMode = 0; + public SequenceLoopMode sequenceLoopMode = SequenceLoopMode.NEVER_LOOP; public boolean sequenceEnded = false; public float[] vertexColor = { 1, 1, 1, 1 }; // Particles do not spawn when the sequence is -1, or when the sequence finished @@ -523,34 +524,40 @@ public class MdxComplexInstance extends ModelInstance { if (sequenceId != -1) { final Sequence sequence = model.sequences.get(sequenceId); final long[] interval = sequence.getInterval(); - final int frameTime = (int) (dt * 1000 * this.animationSpeed); + final float frameTime = (dt * 1000 * this.animationSpeed); - this.frame += frameTime; - this.counter += frameTime; + final int lastIntegerFrame = this.frame; + this.floatingFrame += frameTime; + this.frame = (int) this.floatingFrame; + final int integerFrameTime = this.frame - lastIntegerFrame; + this.counter += integerFrameTime; this.allowParticleSpawn = true; - if (this.frame >= interval[1]) { - if ((this.sequenceLoopMode == 2) || ((this.sequenceLoopMode == 1) && (sequence.getFlags() == 0))) { - this.frame = (int) interval[0]; // TODO not cast + if (this.floatingFrame >= interval[1]) { + if ((this.sequenceLoopMode == SequenceLoopMode.ALWAYS_LOOP) + || ((this.sequenceLoopMode == SequenceLoopMode.MODEL_LOOP) && (sequence.getFlags() == 0))) { + this.floatingFrame = this.frame = (int) interval[0]; // TODO not cast this.resetEventEmitters(); } - else if (this.sequenceLoopMode == 4) { // faux queued animation mode - final int framesPast = this.frame - (int) interval[1]; + else if (this.sequenceLoopMode == SequenceLoopMode.LOOP_TO_NEXT_ANIMATION) { // faux queued animation + // mode + final float framesPast = this.floatingFrame - interval[1]; final List sequences = model.sequences; this.sequence = (this.sequence + 1) % sequences.size(); - this.frame = (int) sequences.get(this.sequence).getInterval()[0] + framesPast; // TODO not cast + this.floatingFrame = sequences.get(this.sequence).getInterval()[0] + framesPast; // TODO not cast + this.frame = (int) this.floatingFrame; this.sequenceEnded = false; this.resetEventEmitters(); this.forced = true; } else { - this.frame = (int) interval[1]; // TODO not cast - this.counter -= frameTime; + this.floatingFrame = this.frame = (int) interval[1]; // TODO not cast + this.counter -= integerFrameTime; this.allowParticleSpawn = false; } - if (this.sequenceLoopMode == 3) { + if (this.sequenceLoopMode == SequenceLoopMode.NEVER_LOOP_AND_HIDE_WHEN_DONE) { hide(); } @@ -636,10 +643,12 @@ public class MdxComplexInstance extends ModelInstance { if ((id < 0) || (id > (sequences.size() - 1))) { this.sequence = -1; this.frame = 0; + this.floatingFrame = 0; this.allowParticleSpawn = false; } else { this.frame = (int) sequences.get(id).getInterval()[0]; // TODO not cast + this.floatingFrame = this.frame; this.sequenceEnded = false; } @@ -656,7 +665,7 @@ public class MdxComplexInstance extends ModelInstance { * and 2 to always loop. 3 was added by Retera as "hide after done" for gameplay * spawned effects */ - public MdxComplexInstance setSequenceLoopMode(final int mode) { + public MdxComplexInstance setSequenceLoopMode(final SequenceLoopMode mode) { this.sequenceLoopMode = mode; return this; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SdSequence.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SdSequence.java index 1a6801d..beb0523 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SdSequence.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SdSequence.java @@ -109,11 +109,11 @@ public final class SdSequence { // with the default value. if (framesBuilder.get(framesBuilder.size() - 1) != end) { framesBuilder.add(end); - valuesBuilder.add(valuesBuilder.get(0)); + valuesBuilder.add(valuesBuilder.get(valuesBuilder.size() - 1)); if (interpolationType > 1) { - inTansBuilder.add(inTansBuilder.get(0)); - outTansBuilder.add(outTansBuilder.get(0)); + inTansBuilder.add(inTansBuilder.get(inTansBuilder.size() - 1)); + outTansBuilder.add(outTansBuilder.get(outTansBuilder.size() - 1)); } } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SequenceLoopMode.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SequenceLoopMode.java new file mode 100644 index 0000000..16c249a --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SequenceLoopMode.java @@ -0,0 +1,9 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +public enum SequenceLoopMode { + NEVER_LOOP, + MODEL_LOOP, + ALWAYS_LOOP, + NEVER_LOOP_AND_HIDE_WHEN_DONE, // used by spawned effects + LOOP_TO_NEXT_ANIMATION; // used by the Arthas vs Illidan tech demo +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/tga/TgaFile.java b/core/src/com/etheller/warsmash/viewer5/handlers/tga/TgaFile.java index f7a766b..8ac69dc 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/tga/TgaFile.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/tga/TgaFile.java @@ -24,7 +24,7 @@ public class TgaFile { /** * Read a TGA image from a file - * + * * @param file * @return * @throws FileNotFoundException @@ -37,7 +37,7 @@ public class TgaFile { /** * Read a TGA image from an input stream. - * + * * @param name * @param stream * @return @@ -142,7 +142,7 @@ public class TgaFile { /** * Write a BufferedImage to a TGA file BufferedImages should be TYPE_INT_ARGB or * TYPE_INT_RGB - * + * * @param src * @param file * @throws IOException diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/AnimationTokens.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/AnimationTokens.java index bbed7f5..3d8c34c 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/AnimationTokens.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/AnimationTokens.java @@ -19,6 +19,7 @@ public class AnimationTokens { public static enum SecondaryTag { ALTERNATE, ALTERNATEEX, + BONE, CHAIN, CHANNEL, COMPLETE, @@ -56,6 +57,7 @@ public class AnimationTokens { SMALL, SPIKED, SPIN, + SPELL, SWIM, TALK, THIRD, diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/StandSequence.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SequenceUtils.java similarity index 87% rename from core/src/com/etheller/warsmash/viewer5/handlers/w3x/StandSequence.java rename to core/src/com/etheller/warsmash/viewer5/handlers/w3x/SequenceUtils.java index 61937a4..6dcc083 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/StandSequence.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SequenceUtils.java @@ -10,7 +10,11 @@ import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.SecondaryTag; -public class StandSequence { +public class SequenceUtils { + public static final EnumSet EMPTY = EnumSet.noneOf(SecondaryTag.class); + public static final EnumSet READY = EnumSet.of(SecondaryTag.READY); + public static final EnumSet FLESH = EnumSet.of(SecondaryTag.FLESH); + public static final EnumSet BONE = EnumSet.of(SecondaryTag.BONE); private static final StandSequenceComparator STAND_SEQUENCE_COMPARATOR = new StandSequenceComparator(); @@ -87,7 +91,8 @@ public class StandSequence { } public static IndexedSequence selectSequence(final AnimationTokens.PrimaryTag type, - final EnumSet tags, final List sequences) { + final EnumSet tags, final List sequences, + final boolean allowRarityVariations) { final List filtered = filterSequences(type, tags, sequences); filtered.sort(STAND_SEQUENCE_COMPARATOR); @@ -101,7 +106,7 @@ public class StandSequence { break; } - if ((Math.random() * 10) > rarity) { + if (((Math.random() * 10) > rarity) && allowRarityVariations) { return filtered.get(i); } } @@ -207,17 +212,22 @@ public class StandSequence { } } - public static void randomSequence(final MdxComplexInstance target, final PrimaryTag animationName, - final EnumSet secondaryAnimationTags) { + public static Sequence randomSequence(final MdxComplexInstance target, final PrimaryTag animationName, + final EnumSet secondaryAnimationTags, final boolean allowRarityVariations) { final MdxModel model = (MdxModel) target.model; final List sequences = model.getSequences(); - final IndexedSequence sequence = selectSequence(animationName, secondaryAnimationTags, sequences); + final IndexedSequence sequence = selectSequence(animationName, secondaryAnimationTags, sequences, + allowRarityVariations); if (sequence != null) { target.setSequence(sequence.index); + return sequence.sequence; } else { - randomStandSequence(target); + if (!secondaryAnimationTags.isEmpty()) { + return randomSequence(target, animationName, EMPTY, allowRarityVariations); + } + return null; } } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SplatModel.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SplatModel.java index bec4e5c..df693ab 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SplatModel.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SplatModel.java @@ -24,6 +24,8 @@ public class SplatModel { private final Texture texture; private final List batches; public final float[] color; + private final List locations; + private final List splatInstances; public SplatModel(final GL30 gl, final Texture texture, final List locations, final float[] centerOffset, final List> unitMapping) { @@ -31,16 +33,49 @@ public class SplatModel { this.batches = new ArrayList<>(); this.color = new float[] { 1, 1, 1, 1 }; + this.locations = locations; + if ((unitMapping != null) && (unitMapping.size() > 0)) { + this.splatInstances = new ArrayList<>(); + for (int i = 0; i < unitMapping.size(); i++) { + this.splatInstances.add(new SplatMover(this)); + } + } + else { + this.splatInstances = null; + } + loadBatches(gl, centerOffset); + if ((unitMapping != null) && (unitMapping.size() > 0)) { + if (this.splatInstances.size() != unitMapping.size()) { + throw new IllegalStateException(); + } + for (int i = 0; i < this.splatInstances.size(); i++) { + unitMapping.get(i).accept(this.splatInstances.get(i)); + } + } + } + + public void compact(final GL30 gl, final float[] centerOffset) { + // delete all the batches + for (final Batch b : this.batches) { + // Vertices + gl.glDeleteBuffer(b.vertexBuffer); + + // Faces. + gl.glDeleteBuffer(b.faceBuffer); + } + this.batches.clear(); + + loadBatches(gl, centerOffset); + } + + private void loadBatches(final GL30 gl, final float[] centerOffset) { final List vertices = new ArrayList<>(); final List uvs = new ArrayList<>(); final List indices = new ArrayList<>(); final List batchRenderUnits = new ArrayList<>(); - final int instances = locations.size(); + final int instances = this.locations.size(); for (int idx = 0; idx < instances; ++idx) { - final Consumer unit = ((unitMapping != null) && (idx < unitMapping.size())) - ? unitMapping.get(idx) - : null; - final float[] locs = locations.get(idx); + final float[] locs = this.locations.get(idx); final float x0 = locs[0]; final float y0 = locs[1]; final float x1 = locs[2]; @@ -61,7 +96,8 @@ public class SplatModel { * ((int) Math.ceil((x1 - x0) / 128.0) + 1); int start = vertices.size(); - final SplatMover splatMover = unit == null ? null : new SplatMover(start * 3 * 4, indices.size() * 6 * 2); + final SplatMover splatMover = (this.splatInstances == null) ? null + : this.splatInstances.get(idx).reset(start * 3 * 4, indices.size() * 6 * 2, idx); final int numVertsToCrate = splatMover == null ? newVerts : maxPossibleVerts; if (numVertsToCrate > MAX_VERTICES) { @@ -124,8 +160,7 @@ public class SplatModel { } } } - if (unit != null) { - unit.accept(splatMover); + if (this.splatInstances != null) { batchRenderUnits.add(splatMover); while (splatMover.indices.size() < maxPossibleFaces) { @@ -140,7 +175,6 @@ public class SplatModel { if (indices.size() > 0) { this.addBatch(gl, vertices, uvs, indices, batchRenderUnits); } - } private void addBatch(final GL30 gl, final List vertices, final List uvs, @@ -213,17 +247,28 @@ public class SplatModel { public float uvYScale; public float uvXScale; private int vertexBuffer; - private final int startOffset; - private final int start; + private int startOffset; + private int start; private final List vertices = new ArrayList<>(); private final List uvs = new ArrayList<>(); private final List indices = new ArrayList<>(); - private final int indicesStartOffset; + private int indicesStartOffset; + private int index; + private final SplatModel splatModel; - private SplatMover(final int i, final int indicesStartOffset) { + private SplatMover(final SplatModel splatModel) { + this.splatModel = splatModel; + } + + private SplatMover reset(final int i, final int indicesStartOffset, final int index) { this.startOffset = i; this.indicesStartOffset = indicesStartOffset; this.start = i / 12; + this.index = index; + this.vertices.clear(); + this.uvs.clear(); + this.indices.clear(); + return this; } public void move(final float deltaX, final float deltaY, final float[] centerOffset) { @@ -325,5 +370,40 @@ public class SplatModel { gl.glBufferSubData(GL30.GL_ARRAY_BUFFER, this.uvsOffset + ((this.startOffset / 3) * 2), 4 * 2 * this.uvs.size(), RenderMathUtils.wrap(this.uvs)); } + + public void destroy(final GL30 gl, final float[] centerOffset) { + this.splatModel.locations.remove(this.index); + this.splatModel.splatInstances.remove(this.index); + this.splatModel.compact(gl, centerOffset); + } + + public void hide() { + // does not remove the shadow, just makes it not show, so it would still be + // using GPU resources + final GL30 gl = Gdx.gl30; + for (final float[] vertex : this.vertices) { + for (int i = 0; i < vertex.length; i++) { + vertex[i] = 0.0f; + } + } + for (final int[] indices : this.indices) { + for (int i = 0; i < indices.length; i++) { + indices[i] = 0; + } + } + for (final float[] uv : this.uvs) { + for (int i = 0; i < uv.length; i++) { + uv[i] = 0; + } + } + gl.glBindBuffer(GL30.GL_ARRAY_BUFFER, this.vertexBuffer); + gl.glBufferSubData(GL30.GL_ARRAY_BUFFER, this.startOffset, 4 * 3 * this.vertices.size(), + RenderMathUtils.wrap(this.vertices)); + gl.glBindBuffer(GL30.GL_ELEMENT_ARRAY_BUFFER, this.faceBuffer); + gl.glBufferSubData(GL30.GL_ELEMENT_ARRAY_BUFFER, this.indicesStartOffset, 6 * 2 * this.indices.size(), + RenderMathUtils.wrapFaces(this.indices)); + gl.glBufferSubData(GL30.GL_ARRAY_BUFFER, this.uvsOffset + ((this.startOffset / 3) * 2), + 4 * 2 * this.uvs.size(), RenderMathUtils.wrap(this.uvs)); + } } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitAckSound.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitAckSound.java new file mode 100644 index 0000000..9bd79e8 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitAckSound.java @@ -0,0 +1,109 @@ +package com.etheller.warsmash.viewer5.handlers.w3x; + +import java.util.ArrayList; +import java.util.List; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.audio.Sound; +import com.badlogic.gdx.utils.TimeUtils; +import com.etheller.warsmash.datasources.DataSource; +import com.etheller.warsmash.units.DataTable; +import com.etheller.warsmash.units.Element; +import com.etheller.warsmash.util.DataSourceFileHandle; +import com.etheller.warsmash.viewer5.AudioBufferSource; +import com.etheller.warsmash.viewer5.AudioContext; +import com.etheller.warsmash.viewer5.AudioPanner; +import com.etheller.warsmash.viewer5.gl.Extensions; +import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderUnit; + +public final class UnitAckSound { + private static final UnitAckSound SILENT = new UnitAckSound(0, 0, 0, 0, 0, 0); + + private final List sounds = new ArrayList<>(); + private final float volume; + private final float pitch; + private final float pitchVariation; + private final float minDistance; + private final float maxDistance; + private final float distanceCutoff; + + private Sound lastPlayedSound; + + public static UnitAckSound create(final DataSource dataSource, final DataTable unitAckSounds, final String soundName, + final String soundType) { + final Element row = unitAckSounds.get(soundName + soundType); + if (row == null) { + return SILENT; + } + final String fileNames = row.getField("FileNames"); + String directoryBase = row.getField("DirectoryBase"); + if ((directoryBase.length() > 1) && !directoryBase.endsWith("\\")) { + directoryBase += "\\"; + } + final float volume = row.getFieldFloatValue("Volume"); + final float pitch = row.getFieldFloatValue("Pitch"); + final float pitchVariation = row.getFieldFloatValue("PitchVariance"); + final float minDistance = row.getFieldFloatValue("MinDistance"); + final float maxDistance = row.getFieldFloatValue("MaxDistance"); + final float distanceCutoff = row.getFieldFloatValue("DistanceCutoff"); + final UnitAckSound sound = new UnitAckSound(volume, pitch, pitchVariation, minDistance, maxDistance, distanceCutoff); + for (final String fileName : fileNames.split(",")) { + String filePath = directoryBase + fileName; + if (!filePath.toLowerCase().endsWith(".wav")) { + filePath += ".wav"; + } + if (dataSource.has(filePath)) { + sound.sounds.add(Gdx.audio.newSound(new DataSourceFileHandle(dataSource, filePath))); + } + } + return sound; + } + + public UnitAckSound(final float volume, final float pitch, final float pitchVariation, final float minDistance, + final float maxDistance, final float distanceCutoff) { + this.volume = volume; + this.pitch = pitch; + this.pitchVariation = pitchVariation; + this.minDistance = minDistance; + this.maxDistance = maxDistance; + this.distanceCutoff = distanceCutoff; + } + + public boolean play(final AudioContext audioContext, final RenderUnit unit) { + return play(audioContext, unit, (int) (Math.random() * this.sounds.size())); + } + + public boolean play(final AudioContext audioContext, final RenderUnit unit, final int index) { + if (this.sounds.isEmpty()) { + return false; + } + final long millisTime = TimeUtils.millis(); + if (millisTime < unit.lastUnitResponseEndTimeMillis) { + return false; + } + + final AudioPanner panner = audioContext.createPanner(); + final AudioBufferSource source = audioContext.createBufferSource(); + + // Panner settings + panner.setPosition(unit.location[0], unit.location[1], unit.location[2]); + panner.maxDistance = this.distanceCutoff; + panner.refDistance = this.minDistance; + panner.connect(audioContext.destination); + + // Source. + source.buffer = this.sounds.get(index); + source.connect(panner); + + // Make a sound. + source.start(0); + this.lastPlayedSound = source.buffer; + final float duration = Extensions.soundLengthExtension.getDuration(this.lastPlayedSound); + unit.lastUnitResponseEndTimeMillis = millisTime + (long) (1000 * duration); + return true; + } + + public int getSoundCount() { + return this.sounds.size(); + } +} \ No newline at end of file diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitSound.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitSound.java new file mode 100644 index 0000000..68a991e --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitSound.java @@ -0,0 +1,120 @@ +package com.etheller.warsmash.viewer5.handlers.w3x; + +import java.util.ArrayList; +import java.util.List; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.audio.Sound; +import com.badlogic.gdx.utils.TimeUtils; +import com.etheller.warsmash.datasources.DataSource; +import com.etheller.warsmash.units.DataTable; +import com.etheller.warsmash.units.Element; +import com.etheller.warsmash.util.DataSourceFileHandle; +import com.etheller.warsmash.viewer5.AudioBufferSource; +import com.etheller.warsmash.viewer5.AudioContext; +import com.etheller.warsmash.viewer5.AudioPanner; +import com.etheller.warsmash.viewer5.gl.Extensions; +import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderUnit; + +public final class UnitSound { + private static final UnitSound SILENT = new UnitSound(0, 0, 0, 0, 0, 0); + + private final List sounds = new ArrayList<>(); + private final float volume; + private final float pitch; + private final float pitchVariation; + private final float minDistance; + private final float maxDistance; + private final float distanceCutoff; + + private Sound lastPlayedSound; + + public static UnitSound create(final DataSource dataSource, final DataTable unitAckSounds, final String soundName, + final String soundType) { + final Element row = unitAckSounds.get(soundName + soundType); + if (row == null) { + return SILENT; + } + final String fileNames = row.getField("FileNames"); + String directoryBase = row.getField("DirectoryBase"); + if ((directoryBase.length() > 1) && !directoryBase.endsWith("\\")) { + directoryBase += "\\"; + } + final float volume = row.getFieldFloatValue("Volume"); + final float pitch = row.getFieldFloatValue("Pitch"); + final float pitchVariation = row.getFieldFloatValue("PitchVariance"); + final float minDistance = row.getFieldFloatValue("MinDistance"); + final float maxDistance = row.getFieldFloatValue("MaxDistance"); + final float distanceCutoff = row.getFieldFloatValue("DistanceCutoff"); + final UnitSound sound = new UnitSound(volume, pitch, pitchVariation, minDistance, maxDistance, distanceCutoff); + for (final String fileName : fileNames.split(",")) { + String filePath = directoryBase + fileName; + if (!filePath.toLowerCase().endsWith(".wav")) { + filePath += ".wav"; + } + if (dataSource.has(filePath)) { + sound.sounds.add(Gdx.audio.newSound(new DataSourceFileHandle(dataSource, filePath))); + } + } + return sound; + } + + public UnitSound(final float volume, final float pitch, final float pitchVariation, final float minDistance, + final float maxDistance, final float distanceCutoff) { + this.volume = volume; + this.pitch = pitch; + this.pitchVariation = pitchVariation; + this.minDistance = minDistance; + this.maxDistance = maxDistance; + this.distanceCutoff = distanceCutoff; + } + + public boolean playUnitResponse(final AudioContext audioContext, final RenderUnit unit) { + return playUnitResponse(audioContext, unit, (int) (Math.random() * this.sounds.size())); + } + + public boolean playUnitResponse(final AudioContext audioContext, final RenderUnit unit, final int index) { + final long millisTime = TimeUtils.millis(); + if (millisTime < unit.lastUnitResponseEndTimeMillis) { + return false; + } + if (play(audioContext, unit.location[0], unit.location[1])) { + final float duration = Extensions.soundLengthExtension.getDuration(this.lastPlayedSound); + unit.lastUnitResponseEndTimeMillis = millisTime + (long) (1000 * duration); + return true; + } + return false; + } + + public boolean play(final AudioContext audioContext, final float x, final float y) { + return play(audioContext, x, y, (int) (Math.random() * this.sounds.size())); + } + + public boolean play(final AudioContext audioContext, final float x, final float y, final int index) { + if (this.sounds.isEmpty()) { + return false; + } + + final AudioPanner panner = audioContext.createPanner(); + final AudioBufferSource source = audioContext.createBufferSource(); + + // Panner settings + panner.setPosition(x, y, 0); + panner.maxDistance = this.distanceCutoff; + panner.refDistance = this.minDistance; + panner.connect(audioContext.destination); + + // Source. + source.buffer = this.sounds.get(index); + source.connect(panner); + + // Make a sound. + source.start(0); + this.lastPlayedSound = source.buffer; + return true; + } + + public int getSoundCount() { + return this.sounds.size(); + } +} \ No newline at end of file diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitSoundset.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitSoundset.java index 40d7253..70553ef 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitSoundset.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitSoundset.java @@ -1,127 +1,23 @@ package com.etheller.warsmash.viewer5.handlers.w3x; -import java.util.ArrayList; -import java.util.List; - -import com.badlogic.gdx.Gdx; -import com.badlogic.gdx.audio.Sound; -import com.badlogic.gdx.utils.TimeUtils; import com.etheller.warsmash.datasources.DataSource; import com.etheller.warsmash.units.DataTable; -import com.etheller.warsmash.units.Element; -import com.etheller.warsmash.util.DataSourceFileHandle; -import com.etheller.warsmash.viewer5.AudioBufferSource; -import com.etheller.warsmash.viewer5.AudioContext; -import com.etheller.warsmash.viewer5.AudioPanner; -import com.etheller.warsmash.viewer5.gl.Extensions; public class UnitSoundset { - public final UnitAckSound what; - public final UnitAckSound pissed; - public final UnitAckSound yesAttack; - public final UnitAckSound yes; - public final UnitAckSound ready; - public final UnitAckSound warcry; + public final UnitSound what; + public final UnitSound pissed; + public final UnitSound yesAttack; + public final UnitSound yes; + public final UnitSound ready; + public final UnitSound warcry; public UnitSoundset(final DataSource dataSource, final DataTable unitAckSounds, final String soundName) { - this.what = UnitAckSound.create(dataSource, unitAckSounds, soundName, "What"); - this.pissed = UnitAckSound.create(dataSource, unitAckSounds, soundName, "Pissed"); - this.yesAttack = UnitAckSound.create(dataSource, unitAckSounds, soundName, "YesAttack"); - this.yes = UnitAckSound.create(dataSource, unitAckSounds, soundName, "Yes"); - this.ready = UnitAckSound.create(dataSource, unitAckSounds, soundName, "Ready"); - this.warcry = UnitAckSound.create(dataSource, unitAckSounds, soundName, "Warcry"); + this.what = UnitSound.create(dataSource, unitAckSounds, soundName, "What"); + this.pissed = UnitSound.create(dataSource, unitAckSounds, soundName, "Pissed"); + this.yesAttack = UnitSound.create(dataSource, unitAckSounds, soundName, "YesAttack"); + this.yes = UnitSound.create(dataSource, unitAckSounds, soundName, "Yes"); + this.ready = UnitSound.create(dataSource, unitAckSounds, soundName, "Ready"); + this.warcry = UnitSound.create(dataSource, unitAckSounds, soundName, "Warcry"); } - public static final class UnitAckSound { - private static final UnitAckSound SILENT = new UnitAckSound(0, 0, 0, 0, 0, 0); - - private final List sounds = new ArrayList<>(); - private final float volume; - private final float pitch; - private final float pitchVariation; - private final float minDistance; - private final float maxDistance; - private final float distanceCutoff; - - private Sound lastPlayedSound; - - public static UnitAckSound create(final DataSource dataSource, final DataTable unitAckSounds, - final String soundName, final String soundType) { - final Element row = unitAckSounds.get(soundName + soundType); - if (row == null) { - return SILENT; - } - final String fileNames = row.getField("FileNames"); - String directoryBase = row.getField("DirectoryBase"); - if ((directoryBase.length() > 1) && !directoryBase.endsWith("\\")) { - directoryBase += "\\"; - } - final float volume = row.getFieldFloatValue("Volume"); - final float pitch = row.getFieldFloatValue("Pitch"); - final float pitchVariation = row.getFieldFloatValue("PitchVariance"); - final float minDistance = row.getFieldFloatValue("MinDistance"); - final float maxDistance = row.getFieldFloatValue("MaxDistance"); - final float distanceCutoff = row.getFieldFloatValue("DistanceCutoff"); - final UnitAckSound sound = new UnitAckSound(volume, pitch, pitchVariation, minDistance, maxDistance, - distanceCutoff); - for (final String fileName : fileNames.split(",")) { - String filePath = directoryBase + fileName; - if (!filePath.toLowerCase().endsWith(".wav")) { - filePath += ".wav"; - } - if (dataSource.has(filePath)) { - sound.sounds.add(Gdx.audio.newSound(new DataSourceFileHandle(dataSource, filePath))); - } - } - return sound; - } - - public UnitAckSound(final float volume, final float pitch, final float pitchVariation, final float minDistance, - final float maxDistance, final float distanceCutoff) { - this.volume = volume; - this.pitch = pitch; - this.pitchVariation = pitchVariation; - this.minDistance = minDistance; - this.maxDistance = maxDistance; - this.distanceCutoff = distanceCutoff; - } - - public boolean play(final AudioContext audioContext, final float x, final float y) { - return play(audioContext, x, y, (int) (Math.random() * this.sounds.size())); - } - - public boolean play(final AudioContext audioContext, final float x, final float y, final int index) { - if (this.sounds.isEmpty()) { - return false; - } - final long millisTime = TimeUtils.millis(); - if (millisTime < audioContext.lastUnitResponseEndTimeMillis) { - return false; - } - - final AudioPanner panner = audioContext.createPanner(); - final AudioBufferSource source = audioContext.createBufferSource(); - - // Panner settings - panner.setPosition(x, y, 0); - panner.maxDistance = this.distanceCutoff; - panner.refDistance = this.minDistance; - panner.connect(audioContext.destination); - - // Source. - source.buffer = this.sounds.get(index); - source.connect(panner); - - // Make a sound. - source.start(0); - this.lastPlayedSound = source.buffer; - final float duration = Extensions.soundLengthExtension.getDuration(this.lastPlayedSound); - audioContext.lastUnitResponseEndTimeMillis = millisTime + (long) (1000 * duration); - return true; - } - - public int getSoundCount() { - return this.sounds.size(); - } - } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index 7b9a16b..30f82e5 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -17,6 +17,8 @@ import java.util.Map; import java.util.Random; import java.util.function.Consumer; +import javax.imageio.ImageIO; + import org.apache.commons.compress.utils.IOUtils; import org.apache.commons.compress.utils.SeekableInMemoryByteChannel; @@ -60,11 +62,14 @@ import com.etheller.warsmash.viewer5.gl.WebGL; import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxHandler; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; +import com.etheller.warsmash.viewer5.handlers.mdx.SequenceLoopMode; import com.etheller.warsmash.viewer5.handlers.tga.TgaFile; import com.etheller.warsmash.viewer5.handlers.w3x.SplatModel.SplatMover; import com.etheller.warsmash.viewer5.handlers.w3x.environment.Terrain; import com.etheller.warsmash.viewer5.handlers.w3x.environment.Terrain.Splat; +import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderAttackInstant; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderAttackProjectile; +import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderEffect; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderItem; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; @@ -74,11 +79,13 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityAttack; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityMove; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackInstant; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackMissile; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.projectile.CAttackProjectile; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.BooleanAbilityActivationReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.CWidgetAbilityTargetCheckReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.PointAbilityTargetCheckReceiver; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.ProjectileCreator; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.SimulationRenderController; import mpq.MPQArchive; import mpq.MPQException; @@ -129,7 +136,7 @@ public class War3MapViewer extends ModelViewer { public MappedData unitMetaData = new MappedData(); public List units = new ArrayList<>(); public List items = new ArrayList<>(); - public List projectiles = new ArrayList<>(); + public List projectiles = new ArrayList<>(); public boolean unitsReady; public War3Map mapMpq; public PathSolver mapPathSolver = PathSolver.DEFAULT; @@ -143,6 +150,7 @@ public class War3MapViewer extends ModelViewer { public List selModels = new ArrayList<>(); public List selected = new ArrayList<>(); private DataTable unitAckSoundsTable; + private DataTable unitCombatSoundsTable; private DataTable miscData; private DataTable unitGlobalStrings; private MdxComplexInstance confirmationInstance; @@ -159,6 +167,8 @@ public class War3MapViewer extends ModelViewer { private final List selectionCircleSizes = new ArrayList<>(); + private final Map unitToRenderPeer = new HashMap<>(); + public War3MapViewer(final DataSource dataSource, final CanvasProvider canvas) { super(dataSource, canvas); this.gameDataSource = dataSource; @@ -229,6 +239,11 @@ public class War3MapViewer extends ModelViewer { try (InputStream terrainSlkStream = this.dataSource.getResourceAsStream("UI\\SoundInfo\\UnitAckSounds.slk")) { this.unitAckSoundsTable.readSLK(terrainSlkStream); } + this.unitCombatSoundsTable = new DataTable(worldEditStrings); + try (InputStream terrainSlkStream = this.dataSource + .getResourceAsStream("UI\\SoundInfo\\UnitCombatSounds.slk")) { + this.unitCombatSoundsTable.readSLK(terrainSlkStream); + } this.miscData = new DataTable(worldEditStrings); try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("UI\\MiscData.txt")) { this.miscData.readTXT(miscDataTxtStream, true); @@ -236,6 +251,9 @@ public class War3MapViewer extends ModelViewer { try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\MiscData.txt")) { this.miscData.readTXT(miscDataTxtStream, true); } + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\MiscGame.txt")) { + this.miscData.readTXT(miscDataTxtStream, true); + } this.unitGlobalStrings = new DataTable(worldEditStrings); try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\UnitGlobalStrings.txt")) { this.unitGlobalStrings.readTXT(miscDataTxtStream, true); @@ -294,7 +312,8 @@ public class War3MapViewer extends ModelViewer { try (InputStream mapStream = compoundDataSource.getResourceAsStream(tileset + ".mpq")) { if (mapStream == null) { tilesetSource = new CompoundDataSource(Arrays.asList(compoundDataSource, - new SubdirDataSource(compoundDataSource, tileset + ".mpq/"))); + new SubdirDataSource(compoundDataSource, tileset + ".mpq/"), + new SubdirDataSource(compoundDataSource, "_tilesets/" + tileset + ".w3mod/"))); } else { final byte[] mapData = IOUtils.toByteArray(mapStream); @@ -306,7 +325,8 @@ public class War3MapViewer extends ModelViewer { } catch (final IOException exc) { tilesetSource = new CompoundDataSource( - Arrays.asList(compoundDataSource, new SubdirDataSource(compoundDataSource, tileset + ".mpq/"))); + Arrays.asList(compoundDataSource, new SubdirDataSource(compoundDataSource, tileset + ".mpq/"), + new SubdirDataSource(compoundDataSource, "_tilesets/" + tileset + ".w3mod/"))); } } catch (final MPQException e) { @@ -339,7 +359,7 @@ public class War3MapViewer extends ModelViewer { final MdxModel confirmation = (MdxModel) load("UI\\Feedback\\Confirmation\\Confirmation.mdx", PathSolver.DEFAULT, null); this.confirmationInstance = (MdxComplexInstance) confirmation.addInstance(); - this.confirmationInstance.setSequenceLoopMode(3); + this.confirmationInstance.setSequenceLoopMode(SequenceLoopMode.NEVER_LOOP_AND_HIDE_WHEN_DONE); this.confirmationInstance.setSequence(0); this.confirmationInstance.setScene(this.worldScene); @@ -352,57 +372,124 @@ public class War3MapViewer extends ModelViewer { final Warcraft3MapObjectData modifications = this.mapMpq.readModifications(); this.simulation = new CSimulation(this.miscData, modifications.getUnits(), modifications.getAbilities(), - new ProjectileCreator() { + new SimulationRenderController() { + private final Map keyToCombatSound = new HashMap<>(); + @Override - public CAttackProjectile create(final CSimulation simulation, final CUnit source, - final int attackIndex, final CWidget target) { + public CAttackProjectile createAttackProjectile(final CSimulation simulation, final float launchX, + final float launchY, final float launchFacing, final CUnit source, + final CUnitAttackMissile unitAttack, final CWidget target, final float damage, + final int bounceIndex) { final War3ID typeId = source.getTypeId(); - final int a1ProjectileSpeed = simulation.getUnitData().getA1ProjectileSpeed(typeId); - final float a1ProjectileArc = simulation.getUnitData().getA1ProjectileArc(typeId); - String a1MissileArt = simulation.getUnitData().getA1MissileArt(typeId); - final int a1MinDamage = simulation.getUnitData().getA1MinDamage(typeId); - final int a1MaxDamage = simulation.getUnitData().getA1MaxDamage(typeId); + final int projectileSpeed = unitAttack.getProjectileSpeed(); + final float projectileArc = unitAttack.getProjectileArc(); + String missileArt = unitAttack.getProjectileArt(); final float projectileLaunchX = simulation.getUnitData().getProjectileLaunchX(typeId); final float projectileLaunchY = simulation.getUnitData().getProjectileLaunchY(typeId); final float projectileLaunchZ = simulation.getUnitData().getProjectileLaunchZ(typeId); - final int damage = War3MapViewer.this.seededRandom.nextInt(a1MaxDamage - a1MinDamage) - + a1MinDamage; - if (a1MissileArt.toLowerCase().endsWith(".mdl")) { - a1MissileArt = a1MissileArt.substring(0, a1MissileArt.length() - 4); + if (missileArt.toLowerCase().endsWith(".mdl")) { + missileArt = missileArt.substring(0, missileArt.length() - 4); } - if (!a1MissileArt.toLowerCase().endsWith(".mdx")) { - a1MissileArt += ".mdx"; + if (!missileArt.toLowerCase().endsWith(".mdx")) { + missileArt += ".mdx"; } - final float facing = (float) Math.toRadians(source.getFacing()); + final float facing = launchFacing; final float sinFacing = (float) Math.sin(facing); final float cosFacing = (float) Math.cos(facing); - final float x = (source.getX() + (projectileLaunchY * cosFacing)) - - (projectileLaunchX * sinFacing); - final float y = source.getY() + (projectileLaunchY * sinFacing) - + (projectileLaunchX * cosFacing); + final float x = (launchX + (projectileLaunchY * cosFacing)) + (projectileLaunchX * sinFacing); + final float y = (launchY + (projectileLaunchY * sinFacing)) - (projectileLaunchX * cosFacing); final float height = War3MapViewer.this.terrain.getGroundHeight(x, y) + source.getFlyHeight() + projectileLaunchZ; final CAttackProjectile simulationAttackProjectile = new CAttackProjectile(x, y, - a1ProjectileSpeed, target, source, damage); + projectileSpeed, target, source, damage, unitAttack, bounceIndex); - final MdxModel model = (MdxModel) load(a1MissileArt, War3MapViewer.this.mapPathSolver, + final MdxModel model = (MdxModel) load(missileArt, War3MapViewer.this.mapPathSolver, War3MapViewer.this.solverParams); final MdxComplexInstance modelInstance = (MdxComplexInstance) model.addInstance(); modelInstance.setTeamColor(source.getPlayerIndex()); modelInstance.setScene(War3MapViewer.this.worldScene); - StandSequence.randomBirthSequence(modelInstance); + if (bounceIndex == 0) { + SequenceUtils.randomBirthSequence(modelInstance); + } + else { + SequenceUtils.randomStandSequence(modelInstance); + } modelInstance.setLocation(x, y, height); final RenderAttackProjectile renderAttackProjectile = new RenderAttackProjectile( - simulationAttackProjectile, modelInstance, height, a1ProjectileArc, War3MapViewer.this); + simulationAttackProjectile, modelInstance, height, projectileArc, War3MapViewer.this); War3MapViewer.this.projectiles.add(renderAttackProjectile); return simulationAttackProjectile; } + + @Override + public void createInstantAttackEffect(final CSimulation cSimulation, final CUnit source, + final CUnitAttackInstant unitAttack, final CWidget target) { + final War3ID typeId = source.getTypeId(); + + String missileArt = unitAttack.getProjectileArt(); + final float projectileLaunchX = War3MapViewer.this.simulation.getUnitData() + .getProjectileLaunchX(typeId); + final float projectileLaunchY = War3MapViewer.this.simulation.getUnitData() + .getProjectileLaunchY(typeId); + if (missileArt.toLowerCase().endsWith(".mdl")) { + missileArt = missileArt.substring(0, missileArt.length() - 4); + } + if (!missileArt.toLowerCase().endsWith(".mdx")) { + missileArt += ".mdx"; + } + final float facing = (float) Math.toRadians(source.getFacing()); + final float sinFacing = (float) Math.sin(facing); + final float cosFacing = (float) Math.cos(facing); + final float x = (source.getX() + (projectileLaunchY * cosFacing)) + + (projectileLaunchX * sinFacing); + final float y = (source.getY() + (projectileLaunchY * sinFacing)) + - (projectileLaunchX * cosFacing); + + final float targetX = target.getX(); + final float targetY = target.getY(); + final float angleToTarget = (float) Math.atan2(targetY - y, targetX - x); + + final float height = War3MapViewer.this.terrain.getGroundHeight(targetX, targetY) + + target.getFlyHeight() + target.getImpactZ(); + + final MdxModel model = (MdxModel) load(missileArt, War3MapViewer.this.mapPathSolver, + War3MapViewer.this.solverParams); + final MdxComplexInstance modelInstance = (MdxComplexInstance) model.addInstance(); + modelInstance.setTeamColor(source.getPlayerIndex()); + modelInstance.setScene(War3MapViewer.this.worldScene); + SequenceUtils.randomBirthSequence(modelInstance); + modelInstance.setLocation(targetX, targetY, height); + War3MapViewer.this.projectiles + .add(new RenderAttackInstant(modelInstance, War3MapViewer.this, angleToTarget)); + } + + @Override + public void spawnUnitDamageSound(final CUnit damagedUnit, final String weaponSound, + final String armorType) { + final String key = weaponSound + armorType; + UnitSound combatSound = this.keyToCombatSound.get(key); + if (combatSound == null) { + combatSound = UnitSound.create(War3MapViewer.this.dataSource, + War3MapViewer.this.unitCombatSoundsTable, weaponSound, armorType); + this.keyToCombatSound.put(key, combatSound); + } + combatSound.play(War3MapViewer.this.worldScene.audioContext, damagedUnit.getX(), + damagedUnit.getY()); + } + + @Override + public void removeUnit(final CUnit unit) { + final RenderUnit renderUnit = War3MapViewer.this.unitToRenderPeer.remove(unit); + War3MapViewer.this.units.remove(renderUnit); + War3MapViewer.this.worldScene.removeInstance(renderUnit.instance); + } }, this.terrain.pathingGrid, - new Rectangle(centerOffset[0], centerOffset[1], (mapSize[0] * 128f) - 128, (mapSize[1] * 128f) - 128)); + new Rectangle(centerOffset[0], centerOffset[1], (mapSize[0] * 128f) - 128, (mapSize[1] * 128f) - 128), + this.seededRandom, w3iFile.getPlayers()); if (this.doodadsAndDestructiblesLoaded) { this.loadDoodadsAndDestructibles(modifications); @@ -596,6 +683,7 @@ public class War3MapViewer extends ModelViewer { MutableGameObject row = null; String path = null; Splat unitShadowSplat = null; + BufferedImage buildingPathingPixelMap = null; // Hardcoded? WorldEditorDataType type = null; @@ -693,14 +781,23 @@ public class War3MapViewer extends ModelViewer { } final String pathingTexture = row.getFieldAsString(UNIT_PATHING, 0); if ((pathingTexture != null) && (pathingTexture.length() > 0) && !"_".equals(pathingTexture)) { - BufferedImage bufferedImage = this.filePathToPathingMap.get(pathingTexture.toLowerCase()); - if (bufferedImage == null) { - bufferedImage = TgaFile.readTGA(pathingTexture, - this.mapMpq.getResourceAsStream(pathingTexture)); - this.filePathToPathingMap.put(pathingTexture.toLowerCase(), bufferedImage); + buildingPathingPixelMap = this.filePathToPathingMap.get(pathingTexture.toLowerCase()); + if (buildingPathingPixelMap == null) { + if (pathingTexture.toLowerCase().endsWith(".tga")) { + buildingPathingPixelMap = TgaFile.readTGA(pathingTexture, + this.mapMpq.getResourceAsStream(pathingTexture)); + } + else { + try (InputStream stream = this.mapMpq.getResourceAsStream(pathingTexture)) { + buildingPathingPixelMap = ImageIO.read(stream); + System.out.println("LOADING BLP PATHING: " + pathingTexture); + } + } + this.filePathToPathingMap.put(pathingTexture.toLowerCase(), buildingPathingPixelMap); } this.terrain.pathingGrid.blitPathingOverlayTexture(unit.getLocation()[0], - unit.getLocation()[1], (int) Math.toDegrees(unit.getAngle()), bufferedImage); + unit.getLocation()[1], (int) Math.toDegrees(unit.getAngle()), + buildingPathingPixelMap); } final String soundName = row.getFieldAsString(UNIT_SOUNDSET, 0); @@ -724,19 +821,12 @@ public class War3MapViewer extends ModelViewer { portraitModel = model; } if (type == WorldEditorDataType.UNITS) { - float angle; - if (this.simulation.getUnitData().isBuilding(row.getAlias())) { - // TODO pretty sure 270 is a Gameplay Constants value that should be dynamically - // loaded - angle = 270.0f; - } - else { - angle = (float) Math.toDegrees(unit.getAngle()); - } + final float angle = (float) Math.toDegrees(unit.getAngle()); final CUnit simulationUnit = this.simulation.createUnit(row.getAlias(), unit.getPlayer(), - unit.getLocation()[0], unit.getLocation()[1], angle); + unit.getLocation()[0], unit.getLocation()[1], angle, buildingPathingPixelMap); final RenderUnit renderUnit = new RenderUnit(this, model, row, unit, soundset, portraitModel, simulationUnit); + this.unitToRenderPeer.put(simulationUnit, renderUnit); this.units.add(renderUnit); if (unitShadowSplat != null) { unitShadowSplat.unitMapping.add(new Consumer() { @@ -782,10 +872,10 @@ public class War3MapViewer extends ModelViewer { for (final RenderUnit unit : this.units) { unit.updateAnimations(this); } - final Iterator projectileIterator = this.projectiles.iterator(); + final Iterator projectileIterator = this.projectiles.iterator(); while (projectileIterator.hasNext()) { - final RenderAttackProjectile projectile = projectileIterator.next(); - if (projectile.updateAnimations(this)) { + final RenderEffect projectile = projectileIterator.next(); + if (projectile.updateAnimations(this, Gdx.graphics.getDeltaTime())) { projectileIterator.remove(); } } @@ -793,7 +883,7 @@ public class War3MapViewer extends ModelViewer { final MdxComplexInstance instance = item.instance; final MdxComplexInstance mdxComplexInstance = instance; if (mdxComplexInstance.sequenceEnded || (mdxComplexInstance.sequence == -1)) { - StandSequence.randomStandSequence(mdxComplexInstance); + SequenceUtils.randomStandSequence(mdxComplexInstance); } } for (final Doodad item : this.doodads) { @@ -801,12 +891,13 @@ public class War3MapViewer extends ModelViewer { if ((instance instanceof MdxComplexInstance) && (instance != this.confirmationInstance)) { final MdxComplexInstance mdxComplexInstance = (MdxComplexInstance) instance; if (mdxComplexInstance.sequenceEnded || (mdxComplexInstance.sequence == -1)) { - StandSequence.randomStandSequence(mdxComplexInstance); + SequenceUtils.randomStandSequence(mdxComplexInstance); } } } - this.updateTime += Gdx.graphics.getRawDeltaTime(); + final float rawDeltaTime = Gdx.graphics.getRawDeltaTime(); + this.updateTime += rawDeltaTime; while (this.updateTime >= WarsmashConstants.SIMULATION_STEP_TIME) { this.updateTime -= WarsmashConstants.SIMULATION_STEP_TIME; this.simulation.update(); @@ -942,7 +1033,8 @@ public class War3MapViewer extends ModelViewer { for (final RenderUnit unit : this.units) { final MdxComplexInstance instance = unit.instance; if (instance.isVisible(this.worldScene.camera) - && instance.intersectRayWithCollision(gdxRayHeap, intersectionHeap)) { + && instance.intersectRayWithCollision(gdxRayHeap, intersectionHeap) + && !unit.getSimulationUnit().isDead()) { entity = unit; } } @@ -1081,7 +1173,6 @@ public class War3MapViewer extends ModelViewer { final Vector2 target = PointAbilityTargetCheckReceiver.INSTANCE.getTarget(); if (target != null) { ability.onOrder(this.simulation, unit.getSimulationUnit(), mousePosHeap, false); - unit.soundset.yes.play(this.worldScene.audioContext, unit.location[0], unit.location[1]); ordered = true; } else { @@ -1114,8 +1205,6 @@ public class War3MapViewer extends ModelViewer { final CWidget targetWidget = CWidgetAbilityTargetCheckReceiver.INSTANCE.getTarget(); if (targetWidget != null) { ability.onOrder(this.simulation, unit.getSimulationUnit(), targetWidget, false); - unit.soundset.yesAttack.play(this.worldScene.audioContext, unit.location[0], - unit.location[1]); ordered = true; } else { @@ -1136,8 +1225,8 @@ public class War3MapViewer extends ModelViewer { } public void standOnRepeat(final MdxComplexInstance instance) { - instance.setSequenceLoopMode(2); - StandSequence.randomStandSequence(instance); + instance.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + SequenceUtils.randomStandSequence(instance); } private static final class SelectionCircleSize { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java index 9667f49..8345ebc 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java @@ -31,8 +31,9 @@ public class PathingGrid { // this blit function is basically copied from HiveWE, maybe remember to mention // that in credits as well: // https://github.com/stijnherfst/HiveWE/blob/master/Base/PathingMap.cpp - public void blitPathingOverlayTexture(final float positionX, final float positionY, final int rotation, + public void blitPathingOverlayTexture(final float positionX, final float positionY, final int rotationInput, final BufferedImage pathingTextureTga) { + final int rotation = (rotationInput + 450) % 360; final int divW = ((rotation % 180) != 0) ? pathingTextureTga.getHeight() : pathingTextureTga.getWidth(); final int divH = ((rotation % 180) != 0) ? pathingTextureTga.getWidth() : pathingTextureTga.getHeight(); for (int j = 0; j < pathingTextureTga.getHeight(); j++) { @@ -65,13 +66,13 @@ public class PathingGrid { final int rgb = pathingTextureTga.getRGB(i, pathingTextureTga.getHeight() - 1 - j); byte data = 0; - if ((rgb & 0xFF) > 250) { + if ((rgb & 0xFF) > 127) { data |= PathingFlags.UNBUILDABLE; } - if (((rgb & 0xFF00) >> 8) > 250) { + if (((rgb & 0xFF00) >>> 8) > 127) { data |= PathingFlags.UNFLYABLE; } - if (((rgb & 0xFF0000) >> 16) > 250) { + if (((rgb & 0xFF0000) >>> 16) > 127) { data |= PathingFlags.UNWALKABLE; } this.dynamicPathingOverlay[(yy * this.pathingGridSizes[0]) + xx] |= data; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java index c353fe4..4799364 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java @@ -383,6 +383,8 @@ public class Terrain { (this.mapBounds[2] * 128.0f) + this.centerOffset[1], ((this.columns - this.mapBounds[1] - 1) * 128.0f) + this.centerOffset[0], ((this.rows - this.mapBounds[3] - 1) * 128.0f) + this.centerOffset[1] }; + this.shaderMapBoundsRectangle = new Rectangle(this.shaderMapBounds[0], this.shaderMapBounds[1], + this.shaderMapBounds[2] - this.shaderMapBounds[0], this.shaderMapBounds[3] - this.shaderMapBounds[1]); this.mapSize = w3eFile.getMapSize(); this.softwareGroundMesh = new SoftwareGroundMesh(this.groundHeights, this.groundCornerHeights, this.centerOffset, width, height); @@ -1184,6 +1186,7 @@ public class Terrain { static Vector3 tmp3 = new Vector3(); private final WaveBuilder waveBuilder; public PathingGrid pathingGrid; + private final Rectangle shaderMapBoundsRectangle; /** * Intersects the given ray with list of triangles. Returns the nearest @@ -1392,4 +1395,8 @@ public class Terrain { } // TODO why do we use floor if we can use int cast? return this.corners[(int) Math.floor(x)][(int) Math.floor(y)].getBoundary() == 0; } + + public Rectangle getPlayableMapArea() { + return this.shaderMapBoundsRectangle; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackInstant.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackInstant.java new file mode 100644 index 0000000..bde7317 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackInstant.java @@ -0,0 +1,40 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.rendersim; + +import java.util.List; + +import com.etheller.warsmash.parsers.mdlx.Sequence; +import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; +import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; +import com.etheller.warsmash.viewer5.handlers.mdx.SequenceLoopMode; +import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; +import com.etheller.warsmash.viewer5.handlers.w3x.IndexedSequence; +import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; +import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; + +public class RenderAttackInstant implements RenderEffect { + private final MdxComplexInstance modelInstance; + + public RenderAttackInstant(final MdxComplexInstance modelInstance, final War3MapViewer war3MapViewer, + final float yaw) { + this.modelInstance = modelInstance; + final MdxModel model = (MdxModel) this.modelInstance.model; + final List sequences = model.getSequences(); + final IndexedSequence sequence = SequenceUtils.selectSequence(PrimaryTag.DEATH, SequenceUtils.EMPTY, sequences, + true); + if (sequence != null) { + this.modelInstance.setSequenceLoopMode(SequenceLoopMode.NEVER_LOOP); + this.modelInstance.setSequence(sequence.index); + } + this.modelInstance.localRotation.setFromAxisRad(0, 0, 1, yaw); + } + + @Override + public boolean updateAnimations(final War3MapViewer war3MapViewer, final float deltaTime) { + + final boolean everythingDone = this.modelInstance.sequenceEnded; + if (everythingDone) { + war3MapViewer.worldScene.removeInstance(this.modelInstance); + } + return everythingDone; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackProjectile.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackProjectile.java index 4293f71..a96dccb 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackProjectile.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackProjectile.java @@ -2,17 +2,19 @@ package com.etheller.warsmash.viewer5.handlers.w3x.rendersim; import java.util.List; -import com.badlogic.gdx.Gdx; import com.badlogic.gdx.math.Quaternion; import com.etheller.warsmash.parsers.mdlx.Sequence; import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; +import com.etheller.warsmash.viewer5.handlers.mdx.SequenceLoopMode; +import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; import com.etheller.warsmash.viewer5.handlers.w3x.IndexedSequence; -import com.etheller.warsmash.viewer5.handlers.w3x.StandSequence; +import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.projectile.CAttackProjectile; -public class RenderAttackProjectile { +public class RenderAttackProjectile implements RenderEffect { private static final Quaternion pitchHeap = new Quaternion(); private final CAttackProjectile simulationProjectile; @@ -29,6 +31,8 @@ public class RenderAttackProjectile { private float yaw; private float pitch; + private boolean done = false; + private float deathTimeElapsed; public RenderAttackProjectile(final CAttackProjectile simulationProjectile, final MdxComplexInstance modelInstance, final float z, final float arc, final War3MapViewer war3MapViewer) { @@ -44,25 +48,32 @@ public class RenderAttackProjectile { final float dyToTarget = targetY - this.y; final float d2DToTarget = (float) StrictMath.sqrt((dxToTarget * dxToTarget) + (dyToTarget * dyToTarget)); final float startingDistance = d2DToTarget + this.totalTravelDistance; + float impactZ = this.simulationProjectile.getTarget().getImpactZ(); + if (simulationProjectile.getUnitAttack().getWeaponType() == CWeaponType.ARTILLERY) { + impactZ = 0; + } this.targetHeight = (war3MapViewer.terrain.getGroundHeight(targetX, targetY) - + this.simulationProjectile.getTarget().getFlyHeight() - + this.simulationProjectile.getTarget().getImpactZ()); + + this.simulationProjectile.getTarget().getFlyHeight() + impactZ); this.arcPeakHeight = arc * startingDistance; this.yaw = (float) StrictMath.atan2(dyToTarget, dxToTarget); } - public boolean updateAnimations(final War3MapViewer war3MapViewer) { - if (this.simulationProjectile.isDone()) { + @Override + public boolean updateAnimations(final War3MapViewer war3MapViewer, final float deltaTime) { + final boolean wasDone = this.done; + if (this.done = this.simulationProjectile.isDone()) { final MdxModel model = (MdxModel) this.modelInstance.model; final List sequences = model.getSequences(); - final IndexedSequence sequence = StandSequence.selectSequence("death", sequences); - if ((sequence != null) && (this.modelInstance.sequence != sequence.index)) { + final IndexedSequence sequence = SequenceUtils.selectSequence(PrimaryTag.DEATH, SequenceUtils.EMPTY, + sequences, true); + if ((sequence != null) && this.done && !wasDone) { + this.modelInstance.setSequenceLoopMode(SequenceLoopMode.NEVER_LOOP); this.modelInstance.setSequence(sequence.index); } } else { if (this.modelInstance.sequenceEnded || (this.modelInstance.sequence == -1)) { - StandSequence.randomStandSequence(this.modelInstance); + SequenceUtils.randomStandSequence(this.modelInstance); } } final float simX = this.simulationProjectile.getX(); @@ -70,13 +81,12 @@ public class RenderAttackProjectile { final float simDx = simX - this.x; final float simDy = simY - this.y; final float simD = (float) StrictMath.sqrt((simDx * simDx) + (simDy * simDy)); - final float deltaTime = Gdx.graphics.getDeltaTime(); final float speed = StrictMath.min(simD, this.simulationProjectile.getSpeed() * deltaTime); if (simD > 0) { this.x = this.x + ((speed * simDx) / simD); this.y = this.y + ((speed * simDy) / simD); - final float targetX = this.simulationProjectile.getTarget().getX(); - final float targetY = this.simulationProjectile.getTarget().getY(); + final float targetX = this.simulationProjectile.getTargetX(); + final float targetY = this.simulationProjectile.getTargetY(); final float dxToTarget = targetX - this.x; final float dyToTarget = targetY - this.y; final float d2DToTarget = (float) StrictMath.sqrt((dxToTarget * dxToTarget) + (dyToTarget * dyToTarget)); @@ -94,10 +104,16 @@ public class RenderAttackProjectile { final float arcCurrentHeight = currentHeightPercentage * this.arcPeakHeight; this.z = this.startingHeight + dz + arcCurrentHeight; - this.yaw = (float) StrictMath.atan2(dyToTarget, dxToTarget); + if (!this.done) { + this.yaw = (float) StrictMath.atan2(dyToTarget, dxToTarget); - final float slope = (-2 * (normPeakDist) * this.arcPeakHeight) / halfStartingDistance; - this.pitch = (float) StrictMath.atan2(slope + d1z, 1); + final float slope = (-2 * (normPeakDist) * this.arcPeakHeight) / halfStartingDistance; + this.pitch = (float) StrictMath.atan2(slope + d1z, 1); + } + } + if (this.done) { + this.pitch = 0; + this.deathTimeElapsed += deltaTime; } this.modelInstance.setLocation(this.x, this.y, this.z); @@ -105,7 +121,8 @@ public class RenderAttackProjectile { this.modelInstance.rotate(pitchHeap.setFromAxisRad(0, -1, 0, this.pitch)); war3MapViewer.worldScene.instanceMoved(this.modelInstance, this.x, this.y); - final boolean everythingDone = this.simulationProjectile.isDone() && this.modelInstance.sequenceEnded; + final boolean everythingDone = this.simulationProjectile.isDone() && (this.modelInstance.sequenceEnded + || (this.deathTimeElapsed >= war3MapViewer.simulation.getGameplayConstants().getBulletDeathTime())); if (everythingDone) { war3MapViewer.worldScene.removeInstance(this.modelInstance); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderEffect.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderEffect.java new file mode 100644 index 0000000..653b006 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderEffect.java @@ -0,0 +1,7 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.rendersim; + +import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; + +public interface RenderEffect { + boolean updateAnimations(final War3MapViewer war3MapViewer, float deltaTime); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java index 318f948..8691db1 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java @@ -8,6 +8,7 @@ import java.util.Queue; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.math.Quaternion; +import com.etheller.warsmash.parsers.mdlx.Sequence; import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; import com.etheller.warsmash.util.ImageUtils; import com.etheller.warsmash.util.RenderMathUtils; @@ -17,8 +18,8 @@ import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.SecondaryTag; +import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; import com.etheller.warsmash.viewer5.handlers.w3x.SplatModel.SplatMover; -import com.etheller.warsmash.viewer5.handlers.w3x.StandSequence; import com.etheller.warsmash.viewer5.handlers.w3x.UnitSoundset; import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid; @@ -40,6 +41,7 @@ public class RenderUnit { private static final War3ID MODEL_SCALE = War3ID.fromString("usca"); private static final War3ID MOVE_HEIGHT = War3ID.fromString("umvh"); private static final War3ID ORIENTATION_INTERPOLATION = War3ID.fromString("uori"); + private static final War3ID ANIM_PROPS = War3ID.fromString("uani"); private static final float[] heapZ = new float[3]; public final MdxComplexInstance instance; public final MutableGameObject row; @@ -59,11 +61,14 @@ public class RenderUnit { private boolean swimming; - private final boolean alreadyPlayedDeath = false; + private boolean dead = false; private final UnitAnimationListenerImpl unitAnimationListenerImpl; private OrientationInterpolation orientationInterpolation; private float currentTurnVelocity = 0; + public long lastUnitResponseEndTimeMillis; + private boolean corpse; + private boolean boneCorpse; public RenderUnit(final War3MapViewer map, final MdxModel model, final MutableGameObject row, final com.etheller.warsmash.parsers.w3x.unitsdoo.Unit unit, final UnitSoundset soundset, @@ -87,6 +92,16 @@ public class RenderUnit { instance.setScene(map.worldScene); this.unitAnimationListenerImpl = new UnitAnimationListenerImpl(instance); simulationUnit.setUnitAnimationListener(this.unitAnimationListenerImpl); + final String requiredAnimationNames = row.getFieldAsString(ANIM_PROPS, 0); + TokenLoop: for (final String animationName : requiredAnimationNames.split(",")) { + final String upperCaseToken = animationName.toUpperCase(); + for (final SecondaryTag secondaryTag : SecondaryTag.values()) { + if (upperCaseToken.equals(secondaryTag.name())) { + this.unitAnimationListenerImpl.addSecondaryTag(secondaryTag); + continue TokenLoop; + } + } + } if (row != null) { heapZ[2] = simulationUnit.getFlyHeight(); @@ -194,6 +209,31 @@ public class RenderUnit { this.unitAnimationListenerImpl.removeSecondaryTag(AnimationTokens.SecondaryTag.SWIM); } this.swimming = swimming; + final boolean dead = this.simulationUnit.isDead(); + final boolean corpse = this.simulationUnit.isCorpse(); + final boolean boneCorpse = this.simulationUnit.isBoneCorpse(); + if (dead && !this.dead) { + this.unitAnimationListenerImpl.playAnimation(true, PrimaryTag.DEATH, SequenceUtils.EMPTY, 1.0f, true); + if (this.shadow != null) { + this.shadow.destroy(Gdx.gl30, map.terrain.centerOffset); + this.shadow = null; + } + if (this.selectionCircle != null) { + this.selectionCircle.destroy(Gdx.gl30, map.terrain.centerOffset); + this.selectionCircle = null; + } + } + if (boneCorpse && !this.boneCorpse) { + this.unitAnimationListenerImpl.playAnimationWithDuration(true, PrimaryTag.DECAY, SequenceUtils.BONE, + map.simulation.getGameplayConstants().getBoneDecayTime(), true); + } + else if (corpse && !this.corpse) { + this.unitAnimationListenerImpl.playAnimationWithDuration(true, PrimaryTag.DECAY, SequenceUtils.FLESH, + map.simulation.getGameplayConstants().getDecayTime(), true); + } + this.dead = dead; + this.corpse = corpse; + this.boneCorpse = boneCorpse; this.location[2] = this.simulationUnit.getFlyHeight() + groundHeight; this.instance.moveTo(this.location); float simulationFacing = this.simulationUnit.getFacing(); @@ -266,6 +306,7 @@ public class RenderUnit { private PrimaryTag currentAnimation; private EnumSet currentAnimationSecondaryTags; private float currentSpeedRatio; + private boolean currentlyAllowingRarityVariations; private final Queue animationQueue = new LinkedList<>(); public UnitAnimationListenerImpl(final MdxComplexInstance instance) { @@ -274,33 +315,59 @@ public class RenderUnit { public void addSecondaryTag(final AnimationTokens.SecondaryTag tag) { this.secondaryAnimationTags.add(tag); - playAnimation(true, this.currentAnimation, this.currentAnimationSecondaryTags, this.currentSpeedRatio); + playAnimation(true, this.currentAnimation, this.currentAnimationSecondaryTags, this.currentSpeedRatio, + this.currentlyAllowingRarityVariations); } public void removeSecondaryTag(final AnimationTokens.SecondaryTag tag) { this.secondaryAnimationTags.remove(tag); - playAnimation(true, this.currentAnimation, this.currentAnimationSecondaryTags, this.currentSpeedRatio); + playAnimation(true, this.currentAnimation, this.currentAnimationSecondaryTags, this.currentSpeedRatio, + this.currentlyAllowingRarityVariations); } @Override public void playAnimation(final boolean force, final PrimaryTag animationName, - final EnumSet secondaryAnimationTags, final float speedRatio) { + final EnumSet secondaryAnimationTags, final float speedRatio, + final boolean allowRarityVariations) { this.animationQueue.clear(); if (force || (animationName != this.currentAnimation)) { this.currentAnimation = animationName; this.currentAnimationSecondaryTags = secondaryAnimationTags; this.currentSpeedRatio = speedRatio; + this.currentlyAllowingRarityVariations = allowRarityVariations; this.recycleSet.clear(); this.recycleSet.addAll(this.secondaryAnimationTags); this.recycleSet.addAll(secondaryAnimationTags); this.instance.setAnimationSpeed(speedRatio); - StandSequence.randomSequence(this.instance, animationName, this.recycleSet); + SequenceUtils.randomSequence(this.instance, animationName, this.recycleSet, allowRarityVariations); + } + } + + public void playAnimationWithDuration(final boolean force, final PrimaryTag animationName, + final EnumSet secondaryAnimationTags, final float duration, + final boolean allowRarityVariations) { + this.animationQueue.clear(); + if (force || (animationName != this.currentAnimation)) { + this.currentAnimation = animationName; + this.currentAnimationSecondaryTags = secondaryAnimationTags; + this.currentlyAllowingRarityVariations = allowRarityVariations; + this.recycleSet.clear(); + this.recycleSet.addAll(this.secondaryAnimationTags); + this.recycleSet.addAll(secondaryAnimationTags); + final Sequence sequence = SequenceUtils.randomSequence(this.instance, animationName, this.recycleSet, + allowRarityVariations); + if (sequence != null) { + this.currentSpeedRatio = ((sequence.getInterval()[1] - sequence.getInterval()[0]) / 1000.0f) + / duration; + this.instance.setAnimationSpeed(this.currentSpeedRatio); + } } } @Override - public void queueAnimation(final PrimaryTag animationName, final EnumSet secondaryAnimationTags) { - this.animationQueue.add(new QueuedAnimation(animationName, secondaryAnimationTags)); + public void queueAnimation(final PrimaryTag animationName, final EnumSet secondaryAnimationTags, + final boolean allowRarityVariations) { + this.animationQueue.add(new QueuedAnimation(animationName, secondaryAnimationTags, allowRarityVariations)); } public void update() { @@ -310,12 +377,13 @@ public class RenderUnit { .get(this.instance.sequence).getFlags() == 0)) { // animation is a looping animation playAnimation(true, this.currentAnimation, this.currentAnimationSecondaryTags, - this.currentSpeedRatio); + this.currentSpeedRatio, this.currentlyAllowingRarityVariations); } else { final QueuedAnimation nextAnimation = this.animationQueue.poll(); if (nextAnimation != null) { - playAnimation(true, nextAnimation.animationName, nextAnimation.secondaryAnimationTags, 1.0f); + playAnimation(true, nextAnimation.animationName, nextAnimation.secondaryAnimationTags, 1.0f, + nextAnimation.allowRarityVariations); } } } @@ -326,10 +394,13 @@ public class RenderUnit { private static final class QueuedAnimation { private final PrimaryTag animationName; private final EnumSet secondaryAnimationTags; + private final boolean allowRarityVariations; - public QueuedAnimation(final PrimaryTag animationName, final EnumSet secondaryAnimationTags) { + public QueuedAnimation(final PrimaryTag animationName, final EnumSet secondaryAnimationTags, + final boolean allowRarityVariations) { this.animationName = animationName; this.secondaryAnimationTags = secondaryAnimationTags; + this.allowRarityVariations = allowRarityVariations; } } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CDestructable.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CDestructable.java index f221c03..4620684 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CDestructable.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CDestructable.java @@ -1,5 +1,10 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation; +import java.util.EnumSet; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; + public class CDestructable extends CWidget { public CDestructable(final int handleId, final float x, final float y, final float life) { @@ -15,4 +20,16 @@ public class CDestructable extends CWidget { public float getImpactZ() { return 0; // TODO maybe from DestructableType } + + @Override + public void damage(final CSimulation simulation, final CUnit source, final CAttackType attackType, + final String weaponType, final float damage) { + this.life -= damage; + } + + @Override + public boolean canBeTargetedBy(final CSimulation simulation, final CUnit source, + final EnumSet targetsAllowed) { + return false; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CGameplayConstants.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CGameplayConstants.java index 3567cc6..12a1a9a 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CGameplayConstants.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CGameplayConstants.java @@ -1,7 +1,11 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation; +import java.util.Arrays; + import com.etheller.warsmash.units.DataTable; import com.etheller.warsmash.units.Element; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CDefenseType; /** * Stores some gameplay constants at runtime in a java object (symbol table) to @@ -9,13 +13,71 @@ import com.etheller.warsmash.units.Element; */ public class CGameplayConstants { private final float attackHalfAngle; + private final float[][] damageBonusTable; + private final float maxCollisionRadius; + private final float decayTime; + private final float boneDecayTime; + private final float bulletDeathTime; + private final float closeEnoughRange; public CGameplayConstants(final DataTable parsedDataTable) { final Element miscData = parsedDataTable.get("Misc"); - this.attackHalfAngle = (miscData.getFieldFloatValue("AttackHalfAngle")); // TODO use + // TODO use radians for half angle + this.attackHalfAngle = (float) Math.toDegrees(miscData.getFieldFloatValue("AttackHalfAngle")); + this.maxCollisionRadius = miscData.getFieldFloatValue("MaxCollisionRadius"); + this.decayTime = miscData.getFieldFloatValue("DecayTime"); + this.boneDecayTime = miscData.getFieldFloatValue("BoneDecayTime"); + this.bulletDeathTime = miscData.getFieldFloatValue("BulletDeathTime"); + this.closeEnoughRange = miscData.getFieldFloatValue("CloseEnoughRange"); + + final CDefenseType[] defenseTypeOrder = { CDefenseType.SMALL, CDefenseType.MEDIUM, CDefenseType.LARGE, + CDefenseType.FORT, CDefenseType.NORMAL, CDefenseType.HERO, CDefenseType.DIVINE, CDefenseType.NONE, }; + this.damageBonusTable = new float[CAttackType.values().length][defenseTypeOrder.length]; + for (int i = 0; i < CAttackType.VALUES.length; i++) { + Arrays.fill(this.damageBonusTable[i], 1.0f); + final CAttackType attackType = CAttackType.VALUES[i]; + final String damageBonus = miscData.getField("DamageBonus" + attackType.getDamageKey()); + final String[] damageComponents = damageBonus.split(","); + for (int j = 0; j < damageComponents.length; j++) { + if (damageComponents[j].length() > 0) { + final CDefenseType defenseType = defenseTypeOrder[j]; + try { + this.damageBonusTable[i][defenseType.ordinal()] = Float.parseFloat(damageComponents[j]); +// System.out.println(attackType + ":" + defenseType + ": " + damageComponents[j]); + } + catch (final NumberFormatException e) { + throw new RuntimeException("DamageBonus" + attackType.getDamageKey(), e); + } + } + } + } } public float getAttackHalfAngle() { return this.attackHalfAngle; } + + public float getDamageRatioAgainst(final CAttackType attackType, final CDefenseType defenseType) { + return this.damageBonusTable[attackType.ordinal()][defenseType.ordinal()]; + } + + public float getMaxCollisionRadius() { + return this.maxCollisionRadius; + } + + public float getDecayTime() { + return this.decayTime; + } + + public float getBoneDecayTime() { + return this.boneDecayTime; + } + + public float getBulletDeathTime() { + return this.bulletDeathTime; + } + + public float getCloseEnoughRange() { + return this.closeEnoughRange; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CItem.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CItem.java index 81b69b9..3d167a1 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CItem.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CItem.java @@ -1,6 +1,10 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation; +import java.util.EnumSet; + import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; public class CItem extends CWidget { @@ -20,4 +24,16 @@ public class CItem extends CWidget { public float getImpactZ() { return 0; // TODO probably from ItemType } + + @Override + public void damage(final CSimulation simulation, final CUnit source, final CAttackType attackType, + final String weaponType, final float damage) { + this.life -= damage; + } + + @Override + public boolean canBeTargetedBy(final CSimulation simulation, final CUnit source, + final EnumSet targetsAllowed) { + return targetsAllowed.contains(CTargetType.ITEM); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CPlayer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CPlayer.java deleted file mode 100644 index 1afe88a..0000000 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CPlayer.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.etheller.warsmash.viewer5.handlers.w3x.simulation; - -public class CPlayer { - private int id; - private int gold; - private int lumber; - - public CPlayer(final int id) { - this.id = id; - } - - public int getId() { - return this.id; - } - - public int getGold() { - return this.gold; - } - - public int getLumber() { - return this.lumber; - } - - public void setId(final int id) { - this.id = id; - } - - public void setGold(final int gold) { - this.gold = gold; - } - - public void setLumber(final int lumber) { - this.lumber = lumber; - } -} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java index 61ca42c..75c81e7 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java @@ -1,47 +1,82 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation; import java.awt.geom.Point2D; +import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.Random; import com.badlogic.gdx.math.Rectangle; +import com.etheller.warsmash.parsers.w3x.w3i.Player; import com.etheller.warsmash.units.DataTable; import com.etheller.warsmash.units.manager.MutableObjectData; import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.util.WarsmashConstants; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackInstant; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackMissile; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.projectile.CAttackProjectile; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.data.CAbilityData; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.data.CUnitData; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.pathing.CPathfindingProcessor; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.ProjectileCreator; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CMapControl; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayer; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CRace; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.SimulationRenderController; public class CSimulation { private final CUnitData unitData; private final CAbilityData abilityData; private final List units; + private final List players; private final List projectiles; + private final List newProjectiles; private final HandleIdAllocator handleIdAllocator; - private transient final ProjectileCreator projectileCreator; + private transient final SimulationRenderController simulationRenderController; private int gameTurnTick = 0; private final PathingGrid pathingGrid; private final CWorldCollision worldCollision; private final CPathfindingProcessor pathfindingProcessor; private final CGameplayConstants gameplayConstants; + private final Random seededRandom; public CSimulation(final DataTable miscData, final MutableObjectData parsedUnitData, - final MutableObjectData parsedAbilityData, final ProjectileCreator projectileCreator, - final PathingGrid pathingGrid, final Rectangle entireMapBounds) { + final MutableObjectData parsedAbilityData, final SimulationRenderController simulationRenderController, + final PathingGrid pathingGrid, final Rectangle entireMapBounds, final Random seededRandom, + final List playerInfos) { this.gameplayConstants = new CGameplayConstants(miscData); - this.projectileCreator = projectileCreator; + this.simulationRenderController = simulationRenderController; this.pathingGrid = pathingGrid; this.unitData = new CUnitData(parsedUnitData); this.abilityData = new CAbilityData(parsedAbilityData); this.units = new ArrayList<>(); this.projectiles = new ArrayList<>(); + this.newProjectiles = new ArrayList<>(); this.handleIdAllocator = new HandleIdAllocator(); - this.worldCollision = new CWorldCollision(entireMapBounds); + this.worldCollision = new CWorldCollision(entireMapBounds, this.gameplayConstants.getMaxCollisionRadius()); this.pathfindingProcessor = new CPathfindingProcessor(pathingGrid, this.worldCollision); + this.seededRandom = seededRandom; + this.players = new ArrayList<>(); + for (int i = 0; i < WarsmashConstants.MAX_PLAYERS; i++) { + if (i < playerInfos.size()) { + final Player playerInfo = playerInfos.get(i); + this.players.add(new CPlayer(playerInfo.getId().getValue(), CMapControl.values()[playerInfo.getType()], + playerInfo.getName(), CRace.parseRace(playerInfo.getRace()), playerInfo.getStartLocation())); + } + else { + this.players.add(new CPlayer(i, CMapControl.NONE, "Default string", CRace.OTHER, new float[] { 0, 0 })); + } + } + this.players.add(new CPlayer(this.players.size(), CMapControl.NEUTRAL, + miscData.getLocalizedString("WESTRING_PLAYER_NA"), CRace.OTHER, new float[] { 0, 0 })); + this.players.add(new CPlayer(this.players.size(), CMapControl.NEUTRAL, + miscData.getLocalizedString("WESTRING_PLAYER_NV"), CRace.OTHER, new float[] { 0, 0 })); + this.players.add(new CPlayer(this.players.size(), CMapControl.NEUTRAL, + miscData.getLocalizedString("WESTRING_PLAYER_NE"), CRace.OTHER, new float[] { 0, 0 })); + this.players.add(new CPlayer(this.players.size(), CMapControl.NEUTRAL, + miscData.getLocalizedString("WESTRING_PLAYER_NP"), CRace.OTHER, new float[] { 0, 0 })); + } public CUnitData getUnitData() { @@ -57,36 +92,48 @@ public class CSimulation { } public CUnit createUnit(final War3ID typeId, final int playerIndex, final float x, final float y, - final float facing) { - final CUnit unit = this.unitData.create(this, this.handleIdAllocator.createId(), playerIndex, typeId, x, y, - facing); + final float facing, final BufferedImage buildingPathingPixelMap) { + final CUnit unit = this.unitData.create(this, playerIndex, this.handleIdAllocator.createId(), typeId, x, y, + facing, buildingPathingPixelMap); this.units.add(unit); - if (!unit.getUnitType().isBuilding()) { - this.worldCollision.addUnit(unit); - } + this.worldCollision.addUnit(unit); return unit; } - public CAttackProjectile createProjectile(final CUnit source, final int attackIndex, final CWidget target) { - final CAttackProjectile projectile = this.projectileCreator.create(this, source, attackIndex, target); - this.projectiles.add(projectile); + public CAttackProjectile createProjectile(final CUnit source, final float launchX, final float launchY, + final float launchFacing, final CUnitAttackMissile attack, final CWidget target, final float damage, + final int bounceIndex) { + final CAttackProjectile projectile = this.simulationRenderController.createAttackProjectile(this, launchX, + launchY, launchFacing, source, attack, target, damage, bounceIndex); + this.newProjectiles.add(projectile); return projectile; } + public void createInstantAttackEffect(final CUnit source, final CUnitAttackInstant attack, final CWidget target) { + this.simulationRenderController.createInstantAttackEffect(this, source, attack, target); + } + public PathingGrid getPathingGrid() { return this.pathingGrid; } - public List findNaiveSlowPath(final CUnit ignoreIntersectionsWithThisUnit, final float startX, - final float startY, final Point2D.Float goal, final PathingGrid.MovementType movementType, - final float collisionSize) { - return this.pathfindingProcessor.findNaiveSlowPath(ignoreIntersectionsWithThisUnit, startX, startY, goal, - movementType, collisionSize); + public List findNaiveSlowPath(final CUnit ignoreIntersectionsWithThisUnit, + final CUnit ignoreIntersectionsWithThisSecondUnit, final float startX, final float startY, + final Point2D.Float goal, final PathingGrid.MovementType movementType, final float collisionSize, + final boolean allowSmoothing) { + return this.pathfindingProcessor.findNaiveSlowPath(ignoreIntersectionsWithThisUnit, + ignoreIntersectionsWithThisSecondUnit, startX, startY, goal, movementType, collisionSize, + allowSmoothing); } public void update() { - for (final CUnit unit : this.units) { - unit.update(this); + final Iterator unitIterator = this.units.iterator(); + while (unitIterator.hasNext()) { + final CUnit unit = unitIterator.next(); + if (unit.update(this)) { + unitIterator.remove(); + this.simulationRenderController.removeUnit(unit); + } } final Iterator projectileIterator = this.projectiles.iterator(); while (projectileIterator.hasNext()) { @@ -95,6 +142,8 @@ public class CSimulation { projectileIterator.remove(); } } + this.projectiles.addAll(this.newProjectiles); + this.newProjectiles.clear(); this.gameTurnTick++; } @@ -109,4 +158,16 @@ public class CSimulation { public CGameplayConstants getGameplayConstants() { return this.gameplayConstants; } + + public Random getSeededRandom() { + return this.seededRandom; + } + + public void unitDamageEvent(final CUnit damagedUnit, final String weaponSound, final String armorType) { + this.simulationRenderController.spawnUnitDamageSound(damagedUnit, weaponSound, armorType); + } + + public CPlayer getPlayer(final int index) { + return this.players.get(index); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java index 36f83b0..a13b533 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java @@ -1,5 +1,6 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation; +import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.EnumSet; import java.util.LinkedList; @@ -8,10 +9,15 @@ import java.util.Queue; import com.badlogic.gdx.math.Rectangle; import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.util.WarsmashConstants; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; +import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitStateListener.CUnitStateNotifier; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CAllianceType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayer; public class CUnit extends CWidget { private War3ID typeId; @@ -35,12 +41,22 @@ public class CUnit extends CWidget { private final EnumSet classifications = EnumSet.noneOf(CUnitClassification.class); + private int deathTurnTick; + private boolean corpse; + private boolean boneCorpse; + private transient CUnitAnimationListener unitAnimationListener; + // if you use triggers for this then the transient tag here becomes really + // questionable -- it already was -- but I meant for those to inform us + // which fields shouldn't be persisted if we do game state save later + private transient CUnitStateNotifier stateNotifier = new CUnitStateNotifier(); + public CUnit(final int handleId, final int playerIndex, final float x, final float y, final float life, final War3ID typeId, final float facing, final float mana, final int maximumLife, final int maximumMana, final int speed, final int defense, final CUnitType unitType) { super(handleId, x, y, life); + this.playerIndex = playerIndex; this.typeId = typeId; this.facing = facing; this.mana = mana; @@ -55,7 +71,7 @@ public class CUnit extends CWidget { public void setUnitAnimationListener(final CUnitAnimationListener unitAnimationListener) { this.unitAnimationListener = unitAnimationListener; - this.unitAnimationListener.playAnimation(true, PrimaryTag.STAND, CUnitAnimationListener.EMPTY, 1.0f); + this.unitAnimationListener.playAnimation(true, PrimaryTag.STAND, SequenceUtils.EMPTY, 1.0f, true); } public CUnitAnimationListener getUnitAnimationListener() { @@ -121,10 +137,48 @@ public class CUnit extends CWidget { } /** - * Updates one tick of simulation logic. + * Updates one tick of simulation logic and return true if it's time to remove + * this unit from the game. */ - public void update(final CSimulation game) { - if (this.currentOrder != null) { + public boolean update(final CSimulation game) { + if (isDead()) { + if (this.collisionRectangle != null) { + // Moved this here because doing it on "kill" was able to happen in some cases + // while also iterating over the units that are in the collision system, and + // then it hit the "writing while iterating" problem. + game.getWorldCollision().removeUnit(this); + } + final int gameTurnTick = game.getGameTurnTick(); + if (!this.corpse) { + if (gameTurnTick > (this.deathTurnTick + + (int) (this.unitType.getDeathTime() / WarsmashConstants.SIMULATION_STEP_TIME))) { + this.corpse = true; + if (!this.unitType.isRaise()) { + this.boneCorpse = true; + // start final phase immediately for "cant raise" case + } + if (!this.unitType.isDecay()) { + // if we dont raise AND dont decay, then now that death anim is over + // we just delete the unit + return true; + } + this.deathTurnTick = gameTurnTick; + } + } + else if (!this.boneCorpse) { + if (game.getGameTurnTick() > (this.deathTurnTick + (int) (game.getGameplayConstants().getDecayTime() + / WarsmashConstants.SIMULATION_STEP_TIME))) { + this.boneCorpse = true; + this.deathTurnTick = gameTurnTick; + } + } + else if (game + .getGameTurnTick() > (this.deathTurnTick + (int) (game.getGameplayConstants().getBoneDecayTime() + / WarsmashConstants.SIMULATION_STEP_TIME))) { + return true; + } + } + else if (this.currentOrder != null) { if (this.currentOrder.update(game)) { // remove current order, because it's completed, polling next // item from order queue @@ -132,12 +186,16 @@ public class CUnit extends CWidget { } if (this.currentOrder == null) { // maybe order "stop" here - this.unitAnimationListener.playAnimation(true, PrimaryTag.STAND, CUnitAnimationListener.EMPTY, 1.0f); + this.unitAnimationListener.playAnimation(true, PrimaryTag.STAND, SequenceUtils.EMPTY, 1.0f, true); } } + return false; } public void order(final COrder order, final boolean queue) { + if (isDead()) { + return; + } if (queue && (this.currentOrder != null)) { this.orderQueue.add(order); } @@ -225,13 +283,199 @@ public class CUnit extends CWidget { return this.defense; } - public void damage(final CUnit source, final CAttackType attackType, final CWeaponType weaponType, - final int damage) { - - } - @Override public float getImpactZ() { return this.unitType.getImpactZ(); } + + public double distance(final CWidget target) { + double dx = Math.abs(target.getX() - getX()); + double dy = Math.abs(target.getY() - getY()); + final float thisCollisionSize = this.unitType.getCollisionSize(); + float targetCollisionSize; + if (target instanceof CUnit) { + final CUnitType targetUnitType = ((CUnit) target).getUnitType(); + targetCollisionSize = targetUnitType.getCollisionSize(); + } + else { + targetCollisionSize = 0; // TODO destructable collision size here + } + if (dx < 0) { + dx = 0; + } + if (dy < 0) { + dy = 0; + } + + double groundDistance = StrictMath.sqrt((dx * dx) + (dy * dy)) - thisCollisionSize - targetCollisionSize; + if (groundDistance < 0) { + groundDistance = 0; + } + return groundDistance; + } + + public double distance(final float x, final float y) { + double dx = Math.abs(x - getX()); + double dy = Math.abs(y - getY()); + final float thisCollisionSize = this.unitType.getCollisionSize(); + if (dx < 0) { + dx = 0; + } + if (dy < 0) { + dy = 0; + } + + double groundDistance = StrictMath.sqrt((dx * dx) + (dy * dy)) - thisCollisionSize; + if (groundDistance < 0) { + groundDistance = 0; + } + return groundDistance; + } + + @Override + public void damage(final CSimulation simulation, final CUnit source, final CAttackType attackType, + final String weaponType, final float damage) { + final boolean wasDead = isDead(); + final float damageRatioFromArmorClass = simulation.getGameplayConstants().getDamageRatioAgainst(attackType, + this.unitType.getDefenseType()); + final float damageRatioFromDefense; + if (this.defense >= 0) { + damageRatioFromDefense = 1f - (float) (((this.defense) * 0.06) / (1 + (0.06 * this.defense))); + } + else { + damageRatioFromDefense = 2f - (float) StrictMath.pow(0.94, -this.defense); + } + final float trueDamage = damageRatioFromArmorClass * damageRatioFromDefense * damage; + this.life -= trueDamage; + simulation.unitDamageEvent(this, weaponType, this.unitType.getArmorType()); + this.stateNotifier.lifeChanged(); + if (!wasDead && isDead() && !this.unitType.isBuilding()) { + kill(simulation); + } + } + + private void kill(final CSimulation simulation) { + this.currentOrder = null; + this.orderQueue.clear(); + this.deathTurnTick = simulation.getGameTurnTick(); + } + + public boolean canReach(final CWidget target, final float range) { + final double distance = distance(target); + if (target instanceof CUnit) { + final CUnit targetUnit = (CUnit) target; + final CUnitType targetUnitType = targetUnit.getUnitType(); + if (targetUnitType.isBuilding() && (targetUnitType.getBuildingPathingPixelMap() != null)) { + final float relativeOffsetX = getX() - target.getX(); + final float relativeOffsetY = getY() - target.getY(); + final int rotation = ((int) targetUnit.getFacing() + 450) % 360; + final BufferedImage buildingPathingPixelMap = targetUnitType.getBuildingPathingPixelMap(); + final int gridWidth = ((rotation % 180) != 0) ? buildingPathingPixelMap.getHeight() + : buildingPathingPixelMap.getWidth(); + final int gridHeight = ((rotation % 180) != 0) ? buildingPathingPixelMap.getWidth() + : buildingPathingPixelMap.getHeight(); + final int relativeGridX = (int) Math.floor(relativeOffsetX / 32f) + (gridWidth / 2); + final int relativeGridY = (int) Math.floor(relativeOffsetY / 32f) + (gridHeight / 2); + final int rangeInCells = (int) Math.floor(range / 32f); + final int rangeInCellsSquare = rangeInCells * rangeInCells; + int minCheckX = relativeGridX - rangeInCells; + int minCheckY = relativeGridY - rangeInCells; + int maxCheckX = relativeGridX + rangeInCells; + int maxCheckY = relativeGridY + rangeInCells; + if ((minCheckX < gridWidth) && (maxCheckX >= 0)) { + if ((minCheckY < gridHeight) && (maxCheckY >= 0)) { + if (minCheckX < 0) { + minCheckX = 0; + } + if (minCheckY < 0) { + minCheckY = 0; + } + if (maxCheckX > (gridWidth - 1)) { + maxCheckX = gridWidth - 1; + } + if (maxCheckY > (gridHeight - 1)) { + maxCheckY = gridHeight - 1; + } + for (int checkX = minCheckX; checkX <= maxCheckX; checkX++) { + for (int checkY = minCheckY; checkY <= maxCheckY; checkY++) { + final int dx = relativeGridX - checkX; + final int dy = relativeGridY - checkY; + if (((dx * dx) + (dy * dy)) <= rangeInCellsSquare) { + if (((getRGBFromPixelData(buildingPathingPixelMap, checkX, checkY, rotation) + & 0xFF0000) >>> 16) > 127) { + return true; + } + } + } + } + } + } + } + } + return distance <= range; + } + + private int getRGBFromPixelData(final BufferedImage buildingPathingPixelMap, final int checkX, final int checkY, + final int rotation) { + + // Below: y is downwards (:() + int x; + int y; + switch (rotation) { + case 90: + x = checkY; + y = buildingPathingPixelMap.getWidth() - 1 - checkX; + break; + case 180: + x = buildingPathingPixelMap.getWidth() - 1 - checkX; + y = buildingPathingPixelMap.getHeight() - 1 - checkY; + break; + case 270: + x = buildingPathingPixelMap.getHeight() - 1 - checkY; + y = checkX; + break; + default: + case 0: + x = checkX; + y = checkY; + } + return buildingPathingPixelMap.getRGB(x, buildingPathingPixelMap.getHeight() - 1 - y); + } + + public void addStateListener(final CUnitStateListener listener) { + this.stateNotifier.subscribe(listener); + } + + public void removeStateListener(final CUnitStateListener listener) { + this.stateNotifier.unsubscribe(listener); + } + + public boolean isCorpse() { + return this.corpse; + } + + public boolean isBoneCorpse() { + return this.boneCorpse; + } + + @Override + public boolean canBeTargetedBy(final CSimulation simulation, final CUnit source, + final EnumSet targetsAllowed) { + if (targetsAllowed.containsAll(this.unitType.getTargetedAs())) { + final int sourcePlayerIndex = source.getPlayerIndex(); + final CPlayer sourcePlayer = simulation.getPlayer(sourcePlayerIndex); + if (!targetsAllowed.contains(CTargetType.ENEMIES) + || !sourcePlayer.hasAlliance(this.playerIndex, CAllianceType.PASSIVE)) { + if (isDead()) { + if (this.unitType.isRaise() && this.unitType.isDecay() && isBoneCorpse()) { + return targetsAllowed.contains(CTargetType.DEAD); + } + } + else { + return !targetsAllowed.contains(CTargetType.DEAD) || targetsAllowed.contains(CTargetType.ALIVE); + } + } + } + return false; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitAnimationListener.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitAnimationListener.java index db7f9d0..28dd9da 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitAnimationListener.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitAnimationListener.java @@ -6,11 +6,9 @@ import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.SecondaryTag; public interface CUnitAnimationListener { - EnumSet EMPTY = EnumSet.noneOf(SecondaryTag.class); - EnumSet READY = EnumSet.of(SecondaryTag.READY); - void playAnimation(boolean force, final PrimaryTag animationName, - final EnumSet secondaryAnimationTags, float speedRatio); + final EnumSet secondaryAnimationTags, float speedRatio, boolean allowRarityVariations); - void queueAnimation(final PrimaryTag animationName, final EnumSet secondaryAnimationTags); + void queueAnimation(final PrimaryTag animationName, final EnumSet secondaryAnimationTags, + boolean allowRarityVariations); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitEnumFunction.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitEnumFunction.java new file mode 100644 index 0000000..1863c2c --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitEnumFunction.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation; + +public interface CUnitEnumFunction { + boolean call(CUnit unit); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitStateListener.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitStateListener.java new file mode 100644 index 0000000..7109037 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitStateListener.java @@ -0,0 +1,17 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation; + +import com.etheller.warsmash.util.SubscriberSetNotifier; + +public interface CUnitStateListener { + void lifeChanged(); // hp (current) changes + + public static final class CUnitStateNotifier extends SubscriberSetNotifier + implements CUnitStateListener { + @Override + public void lifeChanged() { + for (final CUnitStateListener listener : set) { + listener.lifeChanged(); + } + } + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java index 7970023..3d84425 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java @@ -1,11 +1,13 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation; +import java.awt.image.BufferedImage; import java.util.EnumSet; import java.util.List; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.MovementType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CDefenseType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; /** @@ -25,11 +27,18 @@ public class CUnitType { private final boolean decay; private final CDefenseType defenseType; private final float impactZ; + private final float deathTime; + + // TODO: this should probably not be stored as game state, i.e., is it really + // game data? can we store it in a cleaner way? + private final BufferedImage buildingPathingPixelMap; + private final EnumSet targetedAs; public CUnitType(final String name, final boolean isBldg, final MovementType movementType, final float defaultFlyingHeight, final float collisionSize, final EnumSet classifications, final List attacks, final String armorType, - final boolean raise, final boolean decay, final CDefenseType defenseType, final float impactZ) { + final boolean raise, final boolean decay, final CDefenseType defenseType, final float impactZ, + final BufferedImage buildingPathingPixelMap, final float deathTime, final EnumSet targetedAs) { this.name = name; this.building = isBldg; this.movementType = movementType; @@ -42,6 +51,9 @@ public class CUnitType { this.decay = decay; this.defenseType = defenseType; this.impactZ = impactZ; + this.buildingPathingPixelMap = buildingPathingPixelMap; + this.deathTime = deathTime; + this.targetedAs = targetedAs; } public String getName() { @@ -91,4 +103,16 @@ public class CUnitType { public float getImpactZ() { return this.impactZ; } + + public BufferedImage getBuildingPathingPixelMap() { + return this.buildingPathingPixelMap; + } + + public float getDeathTime() { + return this.deathTime; + } + + public EnumSet getTargetedAs() { + return this.targetedAs; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWidget.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWidget.java index 38dd2f9..a78217d 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWidget.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWidget.java @@ -1,10 +1,15 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation; +import java.util.EnumSet; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; + public abstract class CWidget { private final int handleId; private float x; private float y; - private float life; + protected float life; public CWidget(final int handleId, final float x, final float y, final float life) { this.handleId = handleId; @@ -41,12 +46,17 @@ public abstract class CWidget { this.life = life; } - public void damage(final CUnit source, final int damage) { - this.life -= damage; - } + public abstract void damage(final CSimulation simulation, final CUnit source, final CAttackType attackType, + final String weaponType, final float damage); public abstract float getFlyHeight(); public abstract float getImpactZ(); + public boolean isDead() { + return this.life <= 0; + } + + public abstract boolean canBeTargetedBy(CSimulation simulation, CUnit source, + final EnumSet targetsAllowed); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWorldCollision.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWorldCollision.java index ae90c5e..72735c0 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWorldCollision.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWorldCollision.java @@ -1,77 +1,145 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation; +import java.util.HashSet; +import java.util.Set; + import com.badlogic.gdx.math.Rectangle; import com.etheller.warsmash.util.Quadtree; +import com.etheller.warsmash.util.QuadtreeIntersector; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.MovementType; public class CWorldCollision { private final Quadtree groundUnitCollision; private final Quadtree airUnitCollision; private final Quadtree seaUnitCollision; + private final Quadtree buildingUnitCollision; + private final float maxCollisionRadius; + private final AnyUnitExceptTwoIntersector anyUnitExceptTwoIntersector; + private final EachUnitOnlyOnceIntersector eachUnitOnlyOnceIntersector; - public CWorldCollision(final Rectangle entireMapBounds) { + public CWorldCollision(final Rectangle entireMapBounds, final float maxCollisionRadius) { this.groundUnitCollision = new Quadtree<>(entireMapBounds); this.airUnitCollision = new Quadtree<>(entireMapBounds); this.seaUnitCollision = new Quadtree<>(entireMapBounds); + this.buildingUnitCollision = new Quadtree<>(entireMapBounds); + this.maxCollisionRadius = maxCollisionRadius; + this.anyUnitExceptTwoIntersector = new AnyUnitExceptTwoIntersector(); + this.eachUnitOnlyOnceIntersector = new EachUnitOnlyOnceIntersector(); } public void addUnit(final CUnit unit) { - if (unit.getUnitType().isBuilding()) { - throw new IllegalArgumentException("Cannot add building to the CWorldCollision"); - } Rectangle bounds = unit.getCollisionRectangle(); if (bounds == null) { - final float collisionSize = unit.getUnitType().getCollisionSize(); + final float collisionSize = Math.min(this.maxCollisionRadius, unit.getUnitType().getCollisionSize()); bounds = new Rectangle(unit.getX() - collisionSize, unit.getY() - collisionSize, collisionSize * 2, collisionSize * 2); unit.setCollisionRectangle(bounds); } - final MovementType movementType = unit.getUnitType().getMovementType(); - if (movementType != null) { - switch (movementType) { - case AMPHIBIOUS: - this.seaUnitCollision.add(unit, bounds); - this.groundUnitCollision.add(unit, bounds); - break; - case FLOAT: - this.seaUnitCollision.add(unit, bounds); - break; - case FLY: - this.airUnitCollision.add(unit, bounds); - break; - default: - case DISABLED: - case FOOT: - case HORSE: - case HOVER: - this.groundUnitCollision.add(unit, bounds); - break; + if (unit.getUnitType().isBuilding()) { + // buildings are here so that we can include them when enumerating all units in + // a rect, but they don't really move dynamically, this is kind of pointless + this.buildingUnitCollision.add(unit, bounds); + } + else { + final MovementType movementType = unit.getUnitType().getMovementType(); + if (movementType != null) { + switch (movementType) { + case AMPHIBIOUS: + this.seaUnitCollision.add(unit, bounds); + this.groundUnitCollision.add(unit, bounds); + break; + case FLOAT: + this.seaUnitCollision.add(unit, bounds); + break; + case FLY: + this.airUnitCollision.add(unit, bounds); + break; + default: + case DISABLED: + case FOOT: + case HORSE: + case HOVER: + this.groundUnitCollision.add(unit, bounds); + break; + } } } } + public void removeUnit(final CUnit unit) { + final Rectangle bounds = unit.getCollisionRectangle(); + if (bounds != null) { + if (unit.getUnitType().isBuilding()) { + this.buildingUnitCollision.remove(unit, bounds); + } + else { + final MovementType movementType = unit.getUnitType().getMovementType(); + if (movementType != null) { + switch (movementType) { + case AMPHIBIOUS: + this.seaUnitCollision.remove(unit, bounds); + this.groundUnitCollision.remove(unit, bounds); + break; + case FLOAT: + this.seaUnitCollision.remove(unit, bounds); + break; + case FLY: + this.airUnitCollision.remove(unit, bounds); + break; + default: + case DISABLED: + case FOOT: + case HORSE: + case HOVER: + this.groundUnitCollision.remove(unit, bounds); + break; + } + } + } + } + unit.setCollisionRectangle(null); + } + + public void enumUnitsInRect(final Rectangle rect, final CUnitEnumFunction callback) { + this.eachUnitOnlyOnceIntersector.reset(callback); + this.groundUnitCollision.intersect(rect, this.eachUnitOnlyOnceIntersector); + this.airUnitCollision.intersect(rect, this.eachUnitOnlyOnceIntersector); + this.seaUnitCollision.intersect(rect, this.eachUnitOnlyOnceIntersector); + this.buildingUnitCollision.intersect(rect, this.eachUnitOnlyOnceIntersector); + } + public boolean intersectsAnythingOtherThan(final Rectangle newPossibleRectangle, final CUnit sourceUnitToIgnore, final MovementType movementType) { + return intersectsAnythingOtherThan(newPossibleRectangle, sourceUnitToIgnore, null, movementType); + } + + public boolean intersectsAnythingOtherThan(final Rectangle newPossibleRectangle, final CUnit sourceUnitToIgnore, + final CUnit sourceSecondUnitToIgnore, final MovementType movementType) { if (movementType != null) { switch (movementType) { case AMPHIBIOUS: - if (this.seaUnitCollision.intersectsAnythingOtherThan(sourceUnitToIgnore, newPossibleRectangle)) { + if (this.seaUnitCollision.intersect(newPossibleRectangle, + this.anyUnitExceptTwoIntersector.reset(sourceUnitToIgnore, sourceSecondUnitToIgnore))) { return true; } - if (this.groundUnitCollision.intersectsAnythingOtherThan(sourceUnitToIgnore, newPossibleRectangle)) { + if (this.groundUnitCollision.intersect(newPossibleRectangle, + this.anyUnitExceptTwoIntersector.reset(sourceUnitToIgnore, sourceSecondUnitToIgnore))) { return true; } return false; case FLOAT: - return this.seaUnitCollision.intersectsAnythingOtherThan(sourceUnitToIgnore, newPossibleRectangle); + return this.seaUnitCollision.intersect(newPossibleRectangle, + this.anyUnitExceptTwoIntersector.reset(sourceUnitToIgnore, sourceSecondUnitToIgnore)); case FLY: - return this.airUnitCollision.intersectsAnythingOtherThan(sourceUnitToIgnore, newPossibleRectangle); + return this.airUnitCollision.intersect(newPossibleRectangle, + this.anyUnitExceptTwoIntersector.reset(sourceUnitToIgnore, sourceSecondUnitToIgnore)); default: case DISABLED: case FOOT: case HORSE: case HOVER: - return this.groundUnitCollision.intersectsAnythingOtherThan(sourceUnitToIgnore, newPossibleRectangle); + return this.groundUnitCollision.intersect(newPossibleRectangle, + this.anyUnitExceptTwoIntersector.reset(sourceUnitToIgnore, sourceSecondUnitToIgnore)); } } return false; @@ -109,4 +177,45 @@ public class CWorldCollision { } } } + + private static final class AnyUnitExceptTwoIntersector implements QuadtreeIntersector { + private CUnit firstUnit; + private CUnit secondUnit; + + public AnyUnitExceptTwoIntersector reset(final CUnit firstUnit, final CUnit secondUnit) { + this.firstUnit = firstUnit; + this.secondUnit = secondUnit; + return this; + } + + @Override + public boolean onIntersect(final CUnit intersectingObject) { + return (intersectingObject != this.firstUnit) && (intersectingObject != this.secondUnit); + } + } + + private static final class EachUnitOnlyOnceIntersector implements QuadtreeIntersector { + private CUnitEnumFunction consumerDelegate; + private final Set intersectedUnits = new HashSet<>(); + private boolean done; + + public EachUnitOnlyOnceIntersector reset(final CUnitEnumFunction consumerDelegate) { + this.consumerDelegate = consumerDelegate; + this.intersectedUnits.clear(); + this.done = false; + return this; + } + + @Override + public boolean onIntersect(final CUnit intersectingObject) { + if (this.done) { + return true; + } + if (this.intersectedUnits.add(intersectingObject)) { + this.done = this.consumerDelegate.call(intersectingObject); + return this.done; + } + return false; + } + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java index 8cc10d2..f4ad9ef 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java @@ -1,11 +1,14 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities; import com.badlogic.gdx.math.Vector2; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.COrder; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.StringsToExternalizeLater; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.CAttackOrder; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.CMoveOrder; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver.TargetType; @@ -47,7 +50,17 @@ public class CAbilityAttack implements CAbility { @Override public void onOrder(final CSimulation game, final CUnit caster, final CWidget target, final boolean queue) { - caster.order(new CAttackOrder(caster, caster.getUnitType().getAttacks().get(0), target), queue); + COrder order = null; + for (final CUnitAttack attack : caster.getUnitType().getAttacks()) { + if (target.canBeTargetedBy(game, caster, attack.getTargetsAllowed())) { + order = new CAttackOrder(caster, attack, target); + break; + } + } + if (order == null) { + order = new CMoveOrder(caster, target.getX(), target.getY()); + } + caster.order(order, queue); } @Override diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/CAttackType.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/CAttackType.java index 13c6c68..6da4cbb 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/CAttackType.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/CAttackType.java @@ -10,14 +10,21 @@ public enum CAttackType implements CodeKeyType { MAGIC, HERO; + public static CAttackType[] VALUES = values(); + private String codeKey; + private String damageKey; private CAttackType() { - String name = name(); - if (name.equals("SPELLS")) { - name = "MAGIC"; + final String name = name(); + final String computedCodeKey = name.charAt(0) + name.substring(1).toLowerCase(); + if (computedCodeKey.equals("Spells")) { + this.codeKey = "Magic"; } - this.codeKey = name.charAt(0) + name.substring(1).toLowerCase(); + else { + this.codeKey = computedCodeKey; + } + this.damageKey = this.codeKey; } @Override @@ -25,6 +32,10 @@ public enum CAttackType implements CodeKeyType { return this.codeKey; } + public String getDamageKey() { + return this.damageKey; + } + public static CAttackType parseAttackType(final String attackTypeString) { return valueOf(attackTypeString.toUpperCase()); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/CDefenseType.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/CDefenseType.java index b23382b..e300eb3 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/CDefenseType.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/CDefenseType.java @@ -10,6 +10,8 @@ public enum CDefenseType implements CodeKeyType { HERO, DIVINE; + public static CDefenseType[] VALUES = values(); + private String codeKey; private CDefenseType() { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttack.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttack.java index d72530a..b979cd5 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttack.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttack.java @@ -2,6 +2,9 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks; import java.util.EnumSet; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType; @@ -19,7 +22,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType; * because many of those settings did not exist. So I will attempt to emulate * these attacks as best as possible. */ -public class CUnitAttack { +public abstract class CUnitAttack { private float animationBackswingPoint; private float animationDamagePoint; private CAttackType attackType; @@ -195,4 +198,5 @@ public class CUnitAttack { return this.maxDamage; } + public abstract void launch(CSimulation simulation, CUnit unit, CWidget target, float damage); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackInstant.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackInstant.java index 8e9ab2e..80df2f1 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackInstant.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackInstant.java @@ -2,6 +2,9 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks; import java.util.EnumSet; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType; @@ -28,4 +31,10 @@ public class CUnitAttackInstant extends CUnitAttack { this.projectileArt = projectileArt; } + @Override + public void launch(final CSimulation simulation, final CUnit unit, final CWidget target, final float damage) { + simulation.createInstantAttackEffect(unit, this, target); + target.damage(simulation, unit, getAttackType(), getWeaponSound(), damage); + } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissile.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissile.java index 9397295..f42c34c 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissile.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissile.java @@ -2,6 +2,9 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks; import java.util.EnumSet; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType; @@ -59,4 +62,14 @@ public class CUnitAttackMissile extends CUnitAttack { this.projectileSpeed = projectileSpeed; } + @Override + public void launch(final CSimulation simulation, final CUnit unit, final CWidget target, final float damage) { + simulation.createProjectile(unit, unit.getX(), unit.getY(), (float) Math.toRadians(unit.getFacing()), this, + target, damage, 0); + } + + public void doDamage(final CSimulation cSimulation, final CUnit source, final CWidget target, final float damage, + final float x, final float y, final int bounceIndex) { + target.damage(cSimulation, source, getAttackType(), getWeaponSound(), damage); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileBounce.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileBounce.java index 474ca3f..0014ef2 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileBounce.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileBounce.java @@ -2,6 +2,11 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks; import java.util.EnumSet; +import com.badlogic.gdx.math.Rectangle; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitEnumFunction; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType; @@ -9,6 +14,8 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType; public class CUnitAttackMissileBounce extends CUnitAttackMissile { private float damageLossFactor; private int maximumNumberOfTargets; + private final int areaOfEffectFullDamage; + private final EnumSet areaOfEffectTargets; public CUnitAttackMissileBounce(final float animationBackswingPoint, final float animationDamagePoint, final CAttackType attackType, final float cooldownTime, final int damageBase, final int damageDice, @@ -16,12 +23,15 @@ public class CUnitAttackMissileBounce extends CUnitAttackMissile { final boolean showUI, final EnumSet targetsAllowed, final String weaponSound, final CWeaponType weaponType, final float projectileArc, final String projectileArt, final boolean projectileHomingEnabled, final int projectileSpeed, final float damageLossFactor, - final int maximumNumberOfTargets) { + final int maximumNumberOfTargets, final int areaOfEffectFullDamage, + final EnumSet areaOfEffectTargets) { super(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, projectileHomingEnabled, projectileSpeed); this.damageLossFactor = damageLossFactor; this.maximumNumberOfTargets = maximumNumberOfTargets; + this.areaOfEffectFullDamage = areaOfEffectFullDamage; + this.areaOfEffectTargets = areaOfEffectTargets; } public float getDamageLossFactor() { @@ -40,4 +50,67 @@ public class CUnitAttackMissileBounce extends CUnitAttackMissile { this.maximumNumberOfTargets = maximumNumberOfTargets; } + @Override + public void doDamage(final CSimulation cSimulation, final CUnit source, final CWidget target, final float damage, + final float x, final float y, final int bounceIndex) { + super.doDamage(cSimulation, source, target, damage, x, y, bounceIndex); + final int nextBounceIndex = bounceIndex + 1; + if (nextBounceIndex != this.maximumNumberOfTargets) { + BounceMissileConsumer.INSTANCE.nextBounce(cSimulation, source, target, this, x, y, damage, nextBounceIndex); + } + } + + private static final class BounceMissileConsumer implements CUnitEnumFunction { + private static final BounceMissileConsumer INSTANCE = new BounceMissileConsumer(); + private final Rectangle rect = new Rectangle(); + private CUnitAttackMissileBounce attack; + private CSimulation simulation; + private CUnit source; + private CWidget target; + private float x; + private float y; + private float damage; + private int bounceIndex; + private boolean launched = false; + + public void nextBounce(final CSimulation simulation, final CUnit source, final CWidget target, + final CUnitAttackMissileBounce attack, final float x, final float y, final float damage, + final int bounceIndex) { + this.simulation = simulation; + this.source = source; + this.target = target; + this.attack = attack; + this.x = x; + this.y = y; + this.damage = damage; + this.bounceIndex = bounceIndex; + this.launched = false; + final float doubleMaxArea = attack.areaOfEffectFullDamage + + (this.simulation.getGameplayConstants().getCloseEnoughRange() * 2); + final float maxArea = doubleMaxArea / 2; + this.rect.set(x - maxArea, y - maxArea, doubleMaxArea, doubleMaxArea); + simulation.getWorldCollision().enumUnitsInRect(this.rect, this); + + } + + @Override + public boolean call(final CUnit enumUnit) { + if (enumUnit == this.target) { + return false; + } + if (enumUnit.canBeTargetedBy(this.simulation, this.source, this.attack.areaOfEffectTargets)) { + if (this.launched) { + throw new IllegalStateException("already launched"); + } + final float dx = enumUnit.getX() - this.x; + final float dy = enumUnit.getY() - this.y; + final float angle = (float) Math.atan2(dy, dx); + this.simulation.createProjectile(this.source, this.x, this.y, angle, this.attack, enumUnit, + this.damage * (1.0f - this.attack.damageLossFactor), this.bounceIndex); + this.launched = true; + return true; + } + return false; + } + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileSplash.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileSplash.java index a9e636e..453204e 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileSplash.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileSplash.java @@ -2,6 +2,11 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks; import java.util.EnumSet; +import com.badlogic.gdx.math.Rectangle; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitEnumFunction; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType; @@ -82,4 +87,66 @@ public class CUnitAttackMissileSplash extends CUnitAttackMissile { this.damageFactorSmall = damageFactorSmall; } + @Override + public void doDamage(final CSimulation cSimulation, final CUnit source, final CWidget target, final float damage, + final float x, final float y, final int bounceIndex) { + SplashDamageConsumer.INSTANCE.doDamage(cSimulation, source, target, this, x, y, damage); + if ((getWeaponType() != CWeaponType.ARTILLERY) && !SplashDamageConsumer.INSTANCE.hitTarget) { + super.doDamage(cSimulation, source, target, damage * this.damageFactorSmall, x, y, bounceIndex); + } + } + + private static final class SplashDamageConsumer implements CUnitEnumFunction { + private static final SplashDamageConsumer INSTANCE = new SplashDamageConsumer(); + private final Rectangle rect = new Rectangle(); + private CUnitAttackMissileSplash attack; + private CSimulation simulation; + private CUnit source; + private CWidget target; + private float x; + private float y; + private float damage; + private boolean hitTarget; + + public void doDamage(final CSimulation simulation, final CUnit source, final CWidget target, + final CUnitAttackMissileSplash attack, final float x, final float y, final float damage) { + this.simulation = simulation; + this.source = source; + this.target = target; + this.attack = attack; + this.x = x; + this.y = y; + this.damage = damage; + this.hitTarget = false; + final float doubleMaxArea = attack.areaOfEffectSmallDamage + + (this.simulation.getGameplayConstants().getCloseEnoughRange() * 2); + final float maxArea = doubleMaxArea / 2; + this.rect.set(x - maxArea, y - maxArea, doubleMaxArea, doubleMaxArea); + simulation.getWorldCollision().enumUnitsInRect(this.rect, this); + } + + @Override + public boolean call(final CUnit enumUnit) { + if (enumUnit.canBeTargetedBy(this.simulation, this.source, this.attack.areaOfEffectTargets)) { + final double distance = enumUnit.distance(this.x, this.y) + - this.simulation.getGameplayConstants().getCloseEnoughRange(); + if (distance <= (this.attack.areaOfEffectFullDamage / 2)) { + enumUnit.damage(this.simulation, this.source, this.attack.getAttackType(), + this.attack.getWeaponSound(), this.damage); + } + else if (distance <= (this.attack.areaOfEffectMediumDamage / 2)) { + enumUnit.damage(this.simulation, this.source, this.attack.getAttackType(), + this.attack.getWeaponSound(), this.damage * this.attack.damageFactorMedium); + } + else if (distance <= (this.attack.areaOfEffectSmallDamage / 2)) { + enumUnit.damage(this.simulation, this.source, this.attack.getAttackType(), + this.attack.getWeaponSound(), this.damage * this.attack.damageFactorSmall); + } + if (enumUnit == this.target) { + this.hitTarget = true; + } + } + return false; + } + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackNormal.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackNormal.java index 9dca88c..211671e 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackNormal.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackNormal.java @@ -2,6 +2,9 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks; import java.util.EnumSet; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType; @@ -18,4 +21,9 @@ public class CUnitAttackNormal extends CUnitAttack { weaponType); } + @Override + public void launch(final CSimulation simulation, final CUnit unit, final CWidget target, final float damage) { + target.damage(simulation, unit, getAttackType(), getWeaponSound(), damage); + } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/projectile/CAttackProjectile.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/projectile/CAttackProjectile.java index 99d1b71..6b0353a 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/projectile/CAttackProjectile.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/projectile/CAttackProjectile.java @@ -4,29 +4,39 @@ import com.etheller.warsmash.util.WarsmashConstants; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackMissile; public class CAttackProjectile { private float x; private float y; + private final float initialTargetX; + private final float initialTargetY; private final float speed; private final CWidget target; private boolean done; private final CUnit source; - private final int damage; + private final float damage; + private final CUnitAttackMissile unitAttack; + private final int bounceIndex; public CAttackProjectile(final float x, final float y, final float speed, final CWidget target, final CUnit source, - final int damage) { + final float damage, final CUnitAttackMissile unitAttack, final int bounceIndex) { this.x = x; this.y = y; this.speed = speed; this.target = target; this.source = source; this.damage = damage; + this.unitAttack = unitAttack; + this.bounceIndex = bounceIndex; + this.initialTargetX = target.getX(); + this.initialTargetY = target.getY(); } public boolean update(final CSimulation cSimulation) { - final float tx = this.target.getX(); - final float ty = this.target.getY(); + final float tx = getTargetX(); + final float ty = getTargetY(); final float sx = this.x; final float sy = this.y; final float dtsx = tx - sx; @@ -39,7 +49,8 @@ public class CAttackProjectile { float travelDistance = Math.min(c, this.speed * WarsmashConstants.SIMULATION_STEP_TIME); if (c <= travelDistance) { if (!this.done) { - this.target.damage(this.source, this.damage); + this.unitAttack.doDamage(cSimulation, this.source, this.target, this.damage, this.x, this.y, + this.bounceIndex); } this.done = true; travelDistance = c; @@ -73,4 +84,26 @@ public class CAttackProjectile { public boolean isDone() { return this.done; } + + public CUnitAttackMissile getUnitAttack() { + return this.unitAttack; + } + + public float getTargetX() { + if (this.unitAttack.isProjectileHomingEnabled() && (this.unitAttack.getWeaponType() != CWeaponType.ARTILLERY)) { + return this.target.getX(); + } + else { + return this.initialTargetX; + } + } + + public float getTargetY() { + if (this.unitAttack.isProjectileHomingEnabled() && (this.unitAttack.getWeaponType() != CWeaponType.ARTILLERY)) { + return this.target.getY(); + } + else { + return this.initialTargetY; + } + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java index ed7bc48..f89f4cf 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java @@ -1,5 +1,6 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.data; +import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.EnumSet; import java.util.HashMap; @@ -113,6 +114,8 @@ public class CUnitData { private static final War3ID MOVE_TYPE = War3ID.fromString("umvt"); private static final War3ID COLLISION_SIZE = War3ID.fromString("ucol"); private static final War3ID CLASSIFICATION = War3ID.fromString("utyp"); + private static final War3ID DEATH_TIME = War3ID.fromString("udtm"); + private static final War3ID TARGETED_AS = War3ID.fromString("utar"); private final MutableObjectData unitData; private final Map unitIdToUnitType = new HashMap<>(); @@ -121,124 +124,137 @@ public class CUnitData { } public CUnit create(final CSimulation simulation, final int playerIndex, final int handleId, final War3ID typeId, - final float x, final float y, final float facing) { + final float x, final float y, final float facing, final BufferedImage buildingPathingPixelMap) { final MutableGameObject unitType = this.unitData.get(typeId); final int life = unitType.getFieldAsInteger(HIT_POINT_MAXIMUM, 0); final int manaInitial = unitType.getFieldAsInteger(MANA_INITIAL_AMOUNT, 0); final int manaMaximum = unitType.getFieldAsInteger(MANA_MAXIMUM, 0); final int speed = unitType.getFieldAsInteger(MOVEMENT_SPEED_BASE, 0); - final float moveHeight = unitType.getFieldAsFloat(MOVE_HEIGHT, 0); - final String movetp = unitType.getFieldAsString(MOVE_TYPE, 0); - final float collisionSize = unitType.getFieldAsFloat(COLLISION_SIZE, 0); - final boolean isBldg = unitType.getFieldAsBoolean(IS_BLDG, 0); - final PathingGrid.MovementType movementType = PathingGrid.getMovementType(movetp); - final String classificationString = unitType.getFieldAsString(CLASSIFICATION, 0); - final String unitName = unitType.getFieldAsString(NAME, 0); - final EnumSet classifications = EnumSet.noneOf(CUnitClassification.class); - if (classificationString != null) { - final String[] classificationValues = classificationString.split(","); - for (final String unitEditorKey : classificationValues) { - final CUnitClassification unitClassification = CUnitClassification - .parseUnitClassification(unitEditorKey); - if (unitClassification != null) { - classifications.add(unitClassification); + final int defense = unitType.getFieldAsInteger(DEFENSE, 0); + + CUnitType unitTypeInstance = this.unitIdToUnitType.get(typeId); + if (unitTypeInstance == null) { + final float moveHeight = unitType.getFieldAsFloat(MOVE_HEIGHT, 0); + final String movetp = unitType.getFieldAsString(MOVE_TYPE, 0); + final float collisionSize = unitType.getFieldAsFloat(COLLISION_SIZE, 0); + final boolean isBldg = unitType.getFieldAsBoolean(IS_BLDG, 0); + final PathingGrid.MovementType movementType = PathingGrid.getMovementType(movetp); + final String unitName = unitType.getFieldAsString(NAME, 0); + final EnumSet targetedAs = CTargetType + .parseTargetTypeSet(unitType.getFieldAsString(TARGETED_AS, 0)); + final String classificationString = unitType.getFieldAsString(CLASSIFICATION, 0); + final EnumSet classifications = EnumSet.noneOf(CUnitClassification.class); + if (classificationString != null) { + final String[] classificationValues = classificationString.split(","); + for (final String unitEditorKey : classificationValues) { + final CUnitClassification unitClassification = CUnitClassification + .parseUnitClassification(unitEditorKey); + if (unitClassification != null) { + classifications.add(unitClassification); + } } } + final List attacks = new ArrayList<>(); + final int attacksEnabled = unitType.getFieldAsInteger(ATTACKS_ENABLED, 0); + if ((attacksEnabled & 0x1) != 0) { + // attack one + final float animationBackswingPoint = unitType.getFieldAsFloat(ATTACK1_BACKSWING_POINT, 0); + final float animationDamagePoint = unitType.getFieldAsFloat(ATTACK1_DAMAGE_POINT, 0); + final int areaOfEffectFullDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_FULL_DMG, 0); + final int areaOfEffectMediumDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_HALF_DMG, 0); + final int areaOfEffectSmallDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_QUARTER_DMG, 0); + final EnumSet areaOfEffectTargets = CTargetType + .parseTargetTypeSet(unitType.getFieldAsString(ATTACK1_AREA_OF_EFFECT_TARGETS, 0)); + final CAttackType attackType = CAttackType + .parseAttackType(unitType.getFieldAsString(ATTACK1_ATTACK_TYPE, 0)); + final float cooldownTime = unitType.getFieldAsFloat(ATTACK1_COOLDOWN, 0); + final int damageBase = unitType.getFieldAsInteger(ATTACK1_DMG_BASE, 0); + final float damageFactorMedium = unitType.getFieldAsFloat(ATTACK1_DAMAGE_FACTOR_HALF, 0); + final float damageFactorSmall = unitType.getFieldAsFloat(ATTACK1_DAMAGE_FACTOR_QUARTER, 0); + final float damageLossFactor = unitType.getFieldAsFloat(ATTACK1_DAMAGE_LOSS_FACTOR, 0); + final int damageDice = unitType.getFieldAsInteger(ATTACK1_DMG_DICE, 0); + final int damageSidesPerDie = unitType.getFieldAsInteger(ATTACK1_DMG_SIDES_PER_DIE, 0); + final float damageSpillDistance = unitType.getFieldAsFloat(ATTACK1_DMG_SPILL_DIST, 0); + final float damageSpillRadius = unitType.getFieldAsFloat(ATTACK1_DMG_SPILL_RADIUS, 0); + final int damageUpgradeAmount = unitType.getFieldAsInteger(ATTACK1_DMG_UPGRADE_AMT, 0); + final int maximumNumberOfTargets = unitType.getFieldAsInteger(ATTACK1_TARGET_COUNT, 0); + final float projectileArc = unitType.getFieldAsFloat(ATTACK1_PROJECTILE_ARC, 0); + final String projectileArt = unitType.getFieldAsString(ATTACK1_MISSILE_ART, 0); + final boolean projectileHomingEnabled = unitType.getFieldAsBoolean(ATTACK1_PROJECTILE_HOMING_ENABLED, + 0); + final int projectileSpeed = unitType.getFieldAsInteger(ATTACK1_PROJECTILE_SPEED, 0); + final int range = unitType.getFieldAsInteger(ATTACK1_RANGE, 0); + final float rangeMotionBuffer = unitType.getFieldAsFloat(ATTACK1_RANGE_MOTION_BUFFER, 0); + final boolean showUI = unitType.getFieldAsBoolean(ATTACK1_SHOW_UI, 0); + final EnumSet targetsAllowed = CTargetType + .parseTargetTypeSet(unitType.getFieldAsString(ATTACK1_TARGETS_ALLOWED, 0)); + final String weaponSound = unitType.getFieldAsString(ATTACK1_WEAPON_SOUND, 0); + final CWeaponType weaponType = CWeaponType + .parseWeaponType(unitType.getFieldAsString(ATTACK1_WEAPON_TYPE, 0)); + attacks.add(createAttack(animationBackswingPoint, animationDamagePoint, areaOfEffectFullDamage, + areaOfEffectMediumDamage, areaOfEffectSmallDamage, areaOfEffectTargets, attackType, + cooldownTime, damageBase, damageFactorMedium, damageFactorSmall, damageLossFactor, damageDice, + damageSidesPerDie, damageSpillDistance, damageSpillRadius, damageUpgradeAmount, + maximumNumberOfTargets, projectileArc, projectileArt, projectileHomingEnabled, projectileSpeed, + range, rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType)); + } + if ((attacksEnabled & 0x2) != 0) { + // attack two + final float animationBackswingPoint = unitType.getFieldAsFloat(ATTACK2_BACKSWING_POINT, 0); + final float animationDamagePoint = unitType.getFieldAsFloat(ATTACK2_DAMAGE_POINT, 0); + final int areaOfEffectFullDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_FULL_DMG, 0); + final int areaOfEffectMediumDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_HALF_DMG, 0); + final int areaOfEffectSmallDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_QUARTER_DMG, 0); + final EnumSet areaOfEffectTargets = CTargetType + .parseTargetTypeSet(unitType.getFieldAsString(ATTACK2_AREA_OF_EFFECT_TARGETS, 0)); + final CAttackType attackType = CAttackType + .parseAttackType(unitType.getFieldAsString(ATTACK2_ATTACK_TYPE, 0)); + final float cooldownTime = unitType.getFieldAsFloat(ATTACK2_COOLDOWN, 0); + final int damageBase = unitType.getFieldAsInteger(ATTACK2_DMG_BASE, 0); + final float damageFactorMedium = unitType.getFieldAsFloat(ATTACK2_DAMAGE_FACTOR_HALF, 0); + final float damageFactorSmall = unitType.getFieldAsFloat(ATTACK2_DAMAGE_FACTOR_QUARTER, 0); + final float damageLossFactor = unitType.getFieldAsFloat(ATTACK2_DAMAGE_LOSS_FACTOR, 0); + final int damageDice = unitType.getFieldAsInteger(ATTACK2_DMG_DICE, 0); + final int damageSidesPerDie = unitType.getFieldAsInteger(ATTACK2_DMG_SIDES_PER_DIE, 0); + final float damageSpillDistance = unitType.getFieldAsFloat(ATTACK2_DMG_SPILL_DIST, 0); + final float damageSpillRadius = unitType.getFieldAsFloat(ATTACK2_DMG_SPILL_RADIUS, 0); + final int damageUpgradeAmount = unitType.getFieldAsInteger(ATTACK2_DMG_UPGRADE_AMT, 0); + final int maximumNumberOfTargets = unitType.getFieldAsInteger(ATTACK2_TARGET_COUNT, 0); + final float projectileArc = unitType.getFieldAsFloat(ATTACK2_PROJECTILE_ARC, 0); + final String projectileArt = unitType.getFieldAsString(ATTACK2_MISSILE_ART, 0); + final boolean projectileHomingEnabled = unitType.getFieldAsBoolean(ATTACK2_PROJECTILE_HOMING_ENABLED, + 0); + final int projectileSpeed = unitType.getFieldAsInteger(ATTACK2_PROJECTILE_SPEED, 0); + final int range = unitType.getFieldAsInteger(ATTACK2_RANGE, 0); + final float rangeMotionBuffer = unitType.getFieldAsFloat(ATTACK2_RANGE_MOTION_BUFFER, 0); + final boolean showUI = unitType.getFieldAsBoolean(ATTACK2_SHOW_UI, 0); + final EnumSet targetsAllowed = CTargetType + .parseTargetTypeSet(unitType.getFieldAsString(ATTACK2_TARGETS_ALLOWED, 0)); + final String weaponSound = unitType.getFieldAsString(ATTACK2_WEAPON_SOUND, 0); + final CWeaponType weaponType = CWeaponType + .parseWeaponType(unitType.getFieldAsString(ATTACK2_WEAPON_TYPE, 0)); + attacks.add(createAttack(animationBackswingPoint, animationDamagePoint, areaOfEffectFullDamage, + areaOfEffectMediumDamage, areaOfEffectSmallDamage, areaOfEffectTargets, attackType, + cooldownTime, damageBase, damageFactorMedium, damageFactorSmall, damageLossFactor, damageDice, + damageSidesPerDie, damageSpillDistance, damageSpillRadius, damageUpgradeAmount, + maximumNumberOfTargets, projectileArc, projectileArt, projectileHomingEnabled, projectileSpeed, + range, rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType)); + } + final int deathType = unitType.getFieldAsInteger(DEATH_TYPE, 0); + final boolean raise = (deathType & 0x1) != 0; + final boolean decay = (deathType & 0x2) != 0; + final String armorType = unitType.getFieldAsString(ARMOR_TYPE, 0); + final float impactZ = unitType.getFieldAsFloat(PROJECTILE_IMPACT_Z, 0); + final CDefenseType defenseType = CDefenseType.parseDefenseType(unitType.getFieldAsString(DEFENSE_TYPE, 0)); + final float deathTime = unitType.getFieldAsFloat(DEATH_TIME, 0); + unitTypeInstance = new CUnitType(unitName, isBldg, movementType, moveHeight, collisionSize, classifications, + attacks, armorType, raise, decay, defenseType, impactZ, buildingPathingPixelMap, deathTime, + targetedAs); + this.unitIdToUnitType.put(typeId, unitTypeInstance); } - final List attacks = new ArrayList<>(); - final int attacksEnabled = unitType.getFieldAsInteger(ATTACKS_ENABLED, 0); - if ((attacksEnabled & 0x1) != 0) { - // attack one - final float animationBackswingPoint = unitType.getFieldAsFloat(ATTACK1_BACKSWING_POINT, 0); - final float animationDamagePoint = unitType.getFieldAsFloat(ATTACK1_DAMAGE_POINT, 0); - final int areaOfEffectFullDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_FULL_DMG, 0); - final int areaOfEffectMediumDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_HALF_DMG, 0); - final int areaOfEffectSmallDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_QUARTER_DMG, 0); - final EnumSet areaOfEffectTargets = CTargetType - .parseTargetTypeSet(unitType.getFieldAsString(ATTACK1_AREA_OF_EFFECT_TARGETS, 0)); - final CAttackType attackType = CAttackType - .parseAttackType(unitType.getFieldAsString(ATTACK1_ATTACK_TYPE, 0)); - final float cooldownTime = unitType.getFieldAsFloat(ATTACK1_COOLDOWN, 0); - final int damageBase = unitType.getFieldAsInteger(ATTACK1_DMG_BASE, 0); - final float damageFactorMedium = unitType.getFieldAsFloat(ATTACK1_DAMAGE_FACTOR_HALF, 0); - final float damageFactorSmall = unitType.getFieldAsFloat(ATTACK1_DAMAGE_FACTOR_QUARTER, 0); - final float damageLossFactor = unitType.getFieldAsFloat(ATTACK1_DAMAGE_LOSS_FACTOR, 0); - final int damageDice = unitType.getFieldAsInteger(ATTACK1_DMG_DICE, 0); - final int damageSidesPerDie = unitType.getFieldAsInteger(ATTACK1_DMG_SIDES_PER_DIE, 0); - final float damageSpillDistance = unitType.getFieldAsFloat(ATTACK1_DMG_SPILL_DIST, 0); - final float damageSpillRadius = unitType.getFieldAsFloat(ATTACK1_DMG_SPILL_RADIUS, 0); - final int damageUpgradeAmount = unitType.getFieldAsInteger(ATTACK1_DMG_UPGRADE_AMT, 0); - final int maximumNumberOfTargets = unitType.getFieldAsInteger(ATTACK1_TARGET_COUNT, 0); - final float projectileArc = unitType.getFieldAsFloat(ATTACK1_PROJECTILE_ARC, 0); - final String projectileArt = unitType.getFieldAsString(ATTACK1_MISSILE_ART, 0); - final boolean projectileHomingEnabled = unitType.getFieldAsBoolean(ATTACK1_PROJECTILE_HOMING_ENABLED, 0); - final int projectileSpeed = unitType.getFieldAsInteger(ATTACK1_PROJECTILE_SPEED, 0); - final int range = unitType.getFieldAsInteger(ATTACK1_RANGE, 0); - final float rangeMotionBuffer = unitType.getFieldAsFloat(ATTACK1_RANGE_MOTION_BUFFER, 0); - final boolean showUI = unitType.getFieldAsBoolean(ATTACK1_SHOW_UI, 0); - final EnumSet targetsAllowed = CTargetType - .parseTargetTypeSet(unitType.getFieldAsString(ATTACK1_TARGETS_ALLOWED, 0)); - final String weaponSound = unitType.getFieldAsString(ATTACK1_WEAPON_SOUND, 0); - final CWeaponType weaponType = CWeaponType - .parseWeaponType(unitType.getFieldAsString(ATTACK1_WEAPON_TYPE, 0)); - attacks.add(createAttack(animationBackswingPoint, animationDamagePoint, areaOfEffectFullDamage, - areaOfEffectMediumDamage, areaOfEffectSmallDamage, areaOfEffectTargets, attackType, cooldownTime, - damageBase, damageFactorMedium, damageFactorSmall, damageLossFactor, damageDice, damageSidesPerDie, - damageSpillDistance, damageSpillRadius, damageUpgradeAmount, maximumNumberOfTargets, projectileArc, - projectileArt, projectileHomingEnabled, projectileSpeed, range, rangeMotionBuffer, showUI, - targetsAllowed, weaponSound, weaponType)); - } - if ((attacksEnabled & 0x2) != 0) { - // attack two - final float animationBackswingPoint = unitType.getFieldAsFloat(ATTACK2_BACKSWING_POINT, 0); - final float animationDamagePoint = unitType.getFieldAsFloat(ATTACK2_DAMAGE_POINT, 0); - final int areaOfEffectFullDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_FULL_DMG, 0); - final int areaOfEffectMediumDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_HALF_DMG, 0); - final int areaOfEffectSmallDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_QUARTER_DMG, 0); - final EnumSet areaOfEffectTargets = CTargetType - .parseTargetTypeSet(unitType.getFieldAsString(ATTACK2_AREA_OF_EFFECT_TARGETS, 0)); - final CAttackType attackType = CAttackType - .parseAttackType(unitType.getFieldAsString(ATTACK2_ATTACK_TYPE, 0)); - final float cooldownTime = unitType.getFieldAsFloat(ATTACK2_COOLDOWN, 0); - final int damageBase = unitType.getFieldAsInteger(ATTACK2_DMG_BASE, 0); - final float damageFactorMedium = unitType.getFieldAsFloat(ATTACK2_DAMAGE_FACTOR_HALF, 0); - final float damageFactorSmall = unitType.getFieldAsFloat(ATTACK2_DAMAGE_FACTOR_QUARTER, 0); - final float damageLossFactor = unitType.getFieldAsFloat(ATTACK2_DAMAGE_LOSS_FACTOR, 0); - final int damageDice = unitType.getFieldAsInteger(ATTACK2_DMG_DICE, 0); - final int damageSidesPerDie = unitType.getFieldAsInteger(ATTACK2_DMG_SIDES_PER_DIE, 0); - final float damageSpillDistance = unitType.getFieldAsFloat(ATTACK2_DMG_SPILL_DIST, 0); - final float damageSpillRadius = unitType.getFieldAsFloat(ATTACK2_DMG_SPILL_RADIUS, 0); - final int damageUpgradeAmount = unitType.getFieldAsInteger(ATTACK2_DMG_UPGRADE_AMT, 0); - final int maximumNumberOfTargets = unitType.getFieldAsInteger(ATTACK2_TARGET_COUNT, 0); - final float projectileArc = unitType.getFieldAsFloat(ATTACK2_PROJECTILE_ARC, 0); - final String projectileArt = unitType.getFieldAsString(ATTACK2_MISSILE_ART, 0); - final boolean projectileHomingEnabled = unitType.getFieldAsBoolean(ATTACK2_PROJECTILE_HOMING_ENABLED, 0); - final int projectileSpeed = unitType.getFieldAsInteger(ATTACK2_PROJECTILE_SPEED, 0); - final int range = unitType.getFieldAsInteger(ATTACK2_RANGE, 0); - final float rangeMotionBuffer = unitType.getFieldAsFloat(ATTACK2_RANGE_MOTION_BUFFER, 0); - final boolean showUI = unitType.getFieldAsBoolean(ATTACK2_SHOW_UI, 0); - final EnumSet targetsAllowed = CTargetType - .parseTargetTypeSet(unitType.getFieldAsString(ATTACK2_TARGETS_ALLOWED, 0)); - final String weaponSound = unitType.getFieldAsString(ATTACK2_WEAPON_SOUND, 0); - final CWeaponType weaponType = CWeaponType - .parseWeaponType(unitType.getFieldAsString(ATTACK2_WEAPON_TYPE, 0)); - attacks.add(createAttack(animationBackswingPoint, animationDamagePoint, areaOfEffectFullDamage, - areaOfEffectMediumDamage, areaOfEffectSmallDamage, areaOfEffectTargets, attackType, cooldownTime, - damageBase, damageFactorMedium, damageFactorSmall, damageLossFactor, damageDice, damageSidesPerDie, - damageSpillDistance, damageSpillRadius, damageUpgradeAmount, maximumNumberOfTargets, projectileArc, - projectileArt, projectileHomingEnabled, projectileSpeed, range, rangeMotionBuffer, showUI, - targetsAllowed, weaponSound, weaponType)); - } - final int deathType = unitType.getFieldAsInteger(DEATH_TYPE, 0); - final boolean raise = (deathType & 0x1) != 0; - final boolean decay = (deathType & 0x2) != 0; - final String armorType = unitType.getFieldAsString(ARMOR_TYPE, 0); - final int defense = unitType.getFieldAsInteger(DEFENSE, 0); - final float impactZ = unitType.getFieldAsFloat(PROJECTILE_IMPACT_Z, 0); - final CDefenseType defenseType = CDefenseType.parseDefenseType(unitType.getFieldAsString(DEFENSE_TYPE, 0)); + final CUnit unit = new CUnit(handleId, playerIndex, x, y, life, typeId, facing, manaInitial, life, manaMaximum, - speed, defense, new CUnitType(unitName, isBldg, movementType, moveHeight, collisionSize, - classifications, attacks, armorType, raise, decay, defenseType, impactZ)); + speed, defense, unitTypeInstance); if (speed > 0) { unit.add(simulation, CAbilityMove.INSTANCE); unit.add(simulation, CAbilityPatrol.INSTANCE); @@ -275,7 +291,8 @@ public class CUnitData { attack = new CUnitAttackMissileBounce(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, - projectileHomingEnabled, projectileSpeed, damageLossFactor, maximumNumberOfTargets); + projectileHomingEnabled, projectileSpeed, damageLossFactor, maximumNumberOfTargets, + areaOfEffectFullDamage, areaOfEffectTargets); break; case MSPLASH: case ARTILLERY: diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CAttackOrder.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CAttackOrder.java index 0987753..f3bf6ce 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CAttackOrder.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CAttackOrder.java @@ -2,10 +2,10 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders; import com.etheller.warsmash.util.WarsmashConstants; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; +import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.COrder; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitAnimationListener; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityAttack; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; @@ -15,16 +15,52 @@ public class CAttackOrder implements COrder { private boolean wasWithinPropWindow = false; private final CUnitAttack unitAttack; private final CWidget target; - private int backswingLaunchTime; + private int damagePointLaunchTime; + private int backSwingTime; + private COrder moveOrder; + private int thisOrderCooldownEndTime; + private boolean wasInRange = false; public CAttackOrder(final CUnit unit, final CUnitAttack unitAttack, final CWidget target) { this.unit = unit; this.unitAttack = unitAttack; this.target = target; + createMoveOrder(unit, target); + } + + private void createMoveOrder(final CUnit unit, final CWidget target) { + if ((target instanceof CUnit) && !(((CUnit) target).getUnitType().isBuilding())) { + this.moveOrder = new CMoveOrder(unit, (CUnit) target); + } + else { + this.moveOrder = new CMoveOrder(unit, target.getX(), target.getY()); + } } @Override public boolean update(final CSimulation simulation) { + if (this.target.isDead() + || !this.target.canBeTargetedBy(simulation, this.unit, this.unitAttack.getTargetsAllowed())) { + return true; + } + float range = this.unitAttack.getRange(); + if ((this.target instanceof CUnit) && (((CUnit) this.target).getCurrentOrder() instanceof CMoveOrder) + && (this.damagePointLaunchTime != 0 /* + * only apply range motion buffer if they were already in range and + * attacked + */)) { + range += this.unitAttack.getRangeMotionBuffer(); + } + if (!this.unit.canReach(this.target, range)) { + if (this.moveOrder.update(simulation)) { + return true; // we just cant reach them + } + this.wasInRange = false; + this.damagePointLaunchTime = 0; + this.thisOrderCooldownEndTime = 0; + return false; + } + this.wasInRange = true; final float prevX = this.unit.getX(); final float prevY = this.unit.getY(); final float deltaY = this.target.getY() - prevY; @@ -70,10 +106,13 @@ public class CAttackOrder implements COrder { final int cooldownEndTime = this.unit.getCooldownEndTime(); final int currentTurnTick = simulation.getGameTurnTick(); if (this.wasWithinPropWindow) { - if (this.backswingLaunchTime != 0) { - if (currentTurnTick >= this.backswingLaunchTime) { - simulation.createProjectile(this.unit, 0, this.target); - this.backswingLaunchTime = 0; + if (this.damagePointLaunchTime != 0) { + if (currentTurnTick >= this.damagePointLaunchTime) { + final int minDamage = this.unitAttack.getMinDamage(); + final int maxDamage = this.unitAttack.getMaxDamage(); + final int damage = simulation.getSeededRandom().nextInt(maxDamage - minDamage) + minDamage; + this.unitAttack.launch(simulation, this.unit, this.target, damage); + this.damagePointLaunchTime = 0; } } else if (currentTurnTick >= cooldownEndTime) { @@ -84,15 +123,21 @@ public class CAttackOrder implements COrder { final int a1DamagePointSteps = (int) (this.unitAttack.getAnimationDamagePoint() / WarsmashConstants.SIMULATION_STEP_TIME); this.unit.setCooldownEndTime(currentTurnTick + a1CooldownSteps); - this.backswingLaunchTime = currentTurnTick + a1DamagePointSteps; - this.unit.getUnitAnimationListener().playAnimation(true, PrimaryTag.ATTACK, - CUnitAnimationListener.EMPTY, 1.0f); - this.unit.getUnitAnimationListener().queueAnimation(PrimaryTag.STAND, CUnitAnimationListener.READY); + this.thisOrderCooldownEndTime = currentTurnTick + a1CooldownSteps; + this.damagePointLaunchTime = currentTurnTick + a1DamagePointSteps; + this.backSwingTime = currentTurnTick + a1DamagePointSteps + a1BackswingSteps; + this.unit.getUnitAnimationListener().playAnimation(true, PrimaryTag.ATTACK, SequenceUtils.EMPTY, 1.0f, + true); + this.unit.getUnitAnimationListener().queueAnimation(PrimaryTag.STAND, SequenceUtils.READY, false); + } + else if ((currentTurnTick >= this.thisOrderCooldownEndTime)) { + this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.STAND, SequenceUtils.READY, 1.0f, + false); } } else { - this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.STAND, CUnitAnimationListener.READY, - 1.0f); + this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.STAND, SequenceUtils.READY, 1.0f, + false); } return false; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CMoveOrder.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CMoveOrder.java index 1516005..83677cd 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CMoveOrder.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CMoveOrder.java @@ -7,12 +7,12 @@ import java.util.List; import com.badlogic.gdx.math.Rectangle; import com.etheller.warsmash.util.WarsmashConstants; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; +import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.MovementType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.COrder; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitAnimationListener; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWorldCollision; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityMove; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.pathing.CPathfindingProcessor; @@ -24,6 +24,8 @@ public class CMoveOrder implements COrder { private List path = null; private final CPathfindingProcessor.GridMapping gridMapping; private final Point2D.Float target; + private int searchCycles = 0; + private CUnit followUnit; public CMoveOrder(final CUnit unit, final float targetX, final float targetY) { this.unit = unit; @@ -33,6 +35,15 @@ public class CMoveOrder implements COrder { this.target = new Point2D.Float(targetX, targetY); } + public CMoveOrder(final CUnit unit, final CUnit followUnit) { + this.unit = unit; + this.gridMapping = CPathfindingProcessor.isCollisionSizeBetterSuitedForCorners( + unit.getUnitType().getCollisionSize()) ? CPathfindingProcessor.GridMapping.CORNERS + : CPathfindingProcessor.GridMapping.CELLS; + this.target = new Point2D.Float(followUnit.getX(), followUnit.getY()); + this.followUnit = followUnit; + } + @Override public boolean update(final CSimulation simulation) { final float prevX = this.unit.getX(); @@ -45,12 +56,15 @@ public class CMoveOrder implements COrder { final float startFloatingX = prevX; final float startFloatingY = prevY; if (this.path == null) { - this.path = simulation.findNaiveSlowPath(this.unit, startFloatingX, startFloatingY, this.target, - movementType == null ? MovementType.FOOT : movementType, collisionSize); + if (this.followUnit != null) { + this.target.x = this.followUnit.getX(); + this.target.y = this.followUnit.getY(); + } + this.path = simulation.findNaiveSlowPath(this.unit, this.followUnit, startFloatingX, startFloatingY, + this.target, movementType == null ? MovementType.FOOT : movementType, collisionSize, true); System.out.println("init path " + this.path); // check for smoothing if (!this.path.isEmpty()) { - this.path.add(this.target); float lastX = startFloatingX; float lastY = startFloatingY; float smoothingGroupStartX = startFloatingX; @@ -95,16 +109,40 @@ public class CMoveOrder implements COrder { } } } + else if ((this.followUnit != null) && (this.path.size() > 1) && (this.target.distance(this.followUnit.getX(), + this.followUnit.getY()) > (0.1 * this.target.distance(this.unit.getX(), this.unit.getY())))) { + this.target.x = this.followUnit.getX(); + this.target.y = this.followUnit.getY(); + this.path = simulation.findNaiveSlowPath(this.unit, this.followUnit, startFloatingX, startFloatingY, + this.target, movementType == null ? MovementType.FOOT : movementType, collisionSize, + this.searchCycles < 4); + System.out.println("new path (for target) " + this.path); + if (this.path.isEmpty()) { + return true; + } + } float currentTargetX; float currentTargetY; if (this.path.isEmpty()) { - currentTargetX = this.target.x; - currentTargetY = this.target.y; + if (this.followUnit != null) { + currentTargetX = this.followUnit.getX(); + currentTargetY = this.followUnit.getY(); + } + else { + currentTargetX = this.target.x; + currentTargetY = this.target.y; + } } else { - final Point2D.Float nextPathElement = this.path.get(0); - currentTargetX = nextPathElement.x; - currentTargetY = nextPathElement.y; + if ((this.followUnit != null) && (this.path.size() == 1)) { + currentTargetX = this.followUnit.getX(); + currentTargetY = this.followUnit.getY(); + } + else { + final Point2D.Float nextPathElement = this.path.get(0); + currentTargetX = nextPathElement.x; + currentTargetY = nextPathElement.y; + } } float deltaX = currentTargetX - prevX; @@ -172,24 +210,46 @@ public class CMoveOrder implements COrder { && !worldCollision.intersectsAnythingOtherThan(tempRect, this.unit, movementType))) { this.unit.setPoint(nextX, nextY, worldCollision); if (done) { + // if we're making headway along the path then it's OK to start thinking fast + // again + if (travelDistance > 0) { + this.searchCycles = 0; + } if (this.path.isEmpty()) { return true; } else { + System.out.println(this.path); final Float removed = this.path.remove(0); System.out.println( - "We think we reached " + removed + " because are at " + nextX + "," + nextY); - if (this.path.isEmpty()) { - currentTargetX = this.target.x; - currentTargetY = this.target.y; + "We think we reached " + removed + " because we are at " + nextX + "," + nextY); + final boolean emptyPath = this.path.isEmpty(); + if (emptyPath) { + if (this.followUnit != null) { + currentTargetX = this.followUnit.getX(); + currentTargetY = this.followUnit.getY(); + } + else { + currentTargetX = this.target.x; + currentTargetY = this.target.y; + } } else { - final Point2D.Float firstPathElement = this.path.get(0); - currentTargetX = firstPathElement.x; - currentTargetY = firstPathElement.y; + if ((this.followUnit != null) && (this.path.size() == 1)) { + currentTargetX = this.followUnit.getX(); + currentTargetY = this.followUnit.getY(); + } + else { + final Point2D.Float firstPathElement = this.path.get(0); + currentTargetX = firstPathElement.x; + currentTargetY = firstPathElement.y; + } + } + deltaY = currentTargetY - nextY; + deltaX = currentTargetX - nextX; + if ((deltaX == 0.000f) && (deltaY == 0.000f) && this.path.isEmpty()) { + return true; } - deltaY = currentTargetY - prevY; - deltaX = currentTargetX - prevX; System.out.println("new target: " + currentTargetX + "," + currentTargetY); System.out.println("new delta: " + deltaX + "," + deltaY); goalAngleRad = Math.atan2(deltaY, deltaX); @@ -210,7 +270,7 @@ public class CMoveOrder implements COrder { if (absDelta >= propulsionWindow) { if (this.wasWithinPropWindow) { this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.STAND, - CUnitAnimationListener.EMPTY, 1.0f); + SequenceUtils.EMPTY, 1.0f, true); } this.wasWithinPropWindow = false; return false; @@ -219,20 +279,21 @@ public class CMoveOrder implements COrder { } } else { - this.path = simulation.findNaiveSlowPath(this.unit, startFloatingX, startFloatingY, this.target, - movementType == null ? MovementType.FOOT : movementType, collisionSize); + if (this.followUnit != null) { + this.target.x = this.followUnit.getX(); + this.target.y = this.followUnit.getY(); + } + this.path = simulation.findNaiveSlowPath(this.unit, this.followUnit, startFloatingX, startFloatingY, + this.target, movementType == null ? MovementType.FOOT : movementType, collisionSize, + this.searchCycles < 4); + this.searchCycles++; System.out.println("new path " + this.path); - if (this.path.isEmpty()) { + if (this.path.isEmpty() || (this.searchCycles > 5)) { return true; } - else { - this.path.add(this.target); - } - } - if (!this.wasWithinPropWindow) { - this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.WALK, - CUnitAnimationListener.EMPTY, 1.0f); } + this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.WALK, SequenceUtils.EMPTY, 1.0f, + true); this.wasWithinPropWindow = true; } while (continueDistance > 0); @@ -241,8 +302,8 @@ public class CMoveOrder implements COrder { // If this happens, the unit is facing the wrong way, and has to turn before // moving. if (this.wasWithinPropWindow) { - this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.STAND, - CUnitAnimationListener.EMPTY, 1.0f); + this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.STAND, SequenceUtils.EMPTY, 1.0f, + true); } this.wasWithinPropWindow = false; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/pathing/CPathfindingProcessor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/pathing/CPathfindingProcessor.java index 885e4d5..c9fe992 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/pathing/CPathfindingProcessor.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/pathing/CPathfindingProcessor.java @@ -1,6 +1,7 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.pathing; import java.awt.geom.Point2D; +import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.LinkedList; @@ -18,7 +19,8 @@ public class CPathfindingProcessor { private final CWorldCollision worldCollision; private final Node[][] nodes; private final Node[][] cornerNodes; - private Node goal; + private final Node[] goalSet = new Node[4]; + private int goals = 0; public CPathfindingProcessor(final PathingGrid pathingGrid, final CWorldCollision worldCollision) { this.pathingGrid = pathingGrid; @@ -54,12 +56,21 @@ public class CPathfindingProcessor { */ public List findNaiveSlowPath(final CUnit ignoreIntersectionsWithThisUnit, final float startX, final float startY, final Point2D.Float goal, final PathingGrid.MovementType movementType, - final float collisionSize) { + final float collisionSize, final boolean allowSmoothing) { + return findNaiveSlowPath(ignoreIntersectionsWithThisUnit, null, startX, startY, goal, movementType, + collisionSize, allowSmoothing); + } + + public List findNaiveSlowPath(final CUnit ignoreIntersectionsWithThisUnit, + final CUnit ignoreIntersectionsWithThisSecondUnit, final float startX, final float startY, + final Point2D.Float goal, final PathingGrid.MovementType movementType, final float collisionSize, + final boolean allowSmoothing) { final float goalX = goal.x; final float goalY = goal.y; - if (!this.pathingGrid.isPathable(goalX, goalY, movementType, collisionSize) - || !isPathableDynamically(goalX, goalY, ignoreIntersectionsWithThisUnit, movementType)) { - return Collections.emptyList(); + float weightForHittingWalls = 1E9f; + if (!this.pathingGrid.isPathable(goalX, goalY, movementType, collisionSize) || !isPathableDynamically(goalX, + goalY, ignoreIntersectionsWithThisUnit, ignoreIntersectionsWithThisSecondUnit, movementType)) { + weightForHittingWalls = 5E2f; } System.out.println("beginning findNaiveSlowPath for " + startX + "," + startY + "," + goalX + "," + goalY); if ((startX == goalX) && (startY == goalY)) { @@ -78,7 +89,20 @@ public class CPathfindingProcessor { gridMapping = GridMapping.CELLS; System.out.println("using cells"); } - this.goal = searchGraph[gridMapping.getY(this.pathingGrid, goalY)][gridMapping.getX(this.pathingGrid, goalX)]; + final int goalCellY = gridMapping.getY(this.pathingGrid, goalY); + final int goalCellX = gridMapping.getX(this.pathingGrid, goalX); + final Node mostLikelyGoal = searchGraph[goalCellY][goalCellX]; + final double bestGoalDistance = mostLikelyGoal.point.distance(goalX, goalY); + Arrays.fill(this.goalSet, null); + this.goals = 0; + for (int i = goalCellX - 1; i <= (goalCellX + 1); i++) { + for (int j = goalCellY - 1; j <= (goalCellY + 1); j++) { + final Node possibleGoal = searchGraph[j][i]; + if (possibleGoal.point.distance(goalX, goalY) <= bestGoalDistance) { + this.goalSet[this.goals++] = possibleGoal; + } + } + } final int startGridY = gridMapping.getY(this.pathingGrid, startY); final int startGridX = gridMapping.getX(this.pathingGrid, startX); for (int i = 0; i < searchGraph.length; i++) { @@ -133,8 +157,8 @@ public class CPathfindingProcessor { final Node possibleNode = searchGraph[cellY][cellX]; final float x = possibleNode.point.x; final float y = possibleNode.point.y; - if (pathableBetween(ignoreIntersectionsWithThisUnit, startX, startY, movementType, collisionSize, x, - y)) { + if (pathableBetween(ignoreIntersectionsWithThisUnit, ignoreIntersectionsWithThisSecondUnit, startX, + startY, movementType, collisionSize, x, y)) { final double tentativeScore = possibleNode.point.distance(startX, startY); possibleNode.g = tentativeScore; @@ -142,19 +166,33 @@ public class CPathfindingProcessor { openSet.add(possibleNode); } + else { + final double tentativeScore = weightForHittingWalls; + possibleNode.g = tentativeScore; + possibleNode.f = tentativeScore + h(possibleNode); + openSet.add(possibleNode); + + } } } } while (!openSet.isEmpty()) { Node current = openSet.poll(); - if (current == this.goal) { + if (isGoal(current)) { final LinkedList totalPath = new LinkedList<>(); Direction lastCameFromDirection = null; if ((current.cameFrom != null) - && pathableBetween(ignoreIntersectionsWithThisUnit, current.cameFrom.point.x, - current.cameFrom.point.y, movementType, collisionSize, goalX, goalY)) { + && pathableBetween(ignoreIntersectionsWithThisUnit, ignoreIntersectionsWithThisSecondUnit, + current.point.x, current.point.y, movementType, collisionSize, goalX, goalY) + && pathableBetween(ignoreIntersectionsWithThisUnit, ignoreIntersectionsWithThisSecondUnit, + current.cameFrom.point.x, current.cameFrom.point.y, movementType, collisionSize, + current.point.x, current.point.y) + && pathableBetween(ignoreIntersectionsWithThisUnit, ignoreIntersectionsWithThisSecondUnit, + current.cameFrom.point.x, current.cameFrom.point.y, movementType, collisionSize, goalX, + goalY) + && allowSmoothing) { // do some basic smoothing to walk straight to the goal if it is not obstructed, // skipping the last grid location totalPath.addFirst(goal); @@ -172,8 +210,16 @@ public class CPathfindingProcessor { if ((lastCameFromDirection == null) || (current.cameFromDirection != lastCameFromDirection) || (current.cameFromDirection == null)) { if ((current.cameFromDirection != null) || (lastNode == null) - || !pathableBetween(ignoreIntersectionsWithThisUnit, startX, startY, movementType, - collisionSize, lastNode.point.x, lastNode.point.y)) { + || !pathableBetween(ignoreIntersectionsWithThisUnit, + ignoreIntersectionsWithThisSecondUnit, startX, startY, movementType, + collisionSize, current.point.x, current.point.y) + || !pathableBetween(ignoreIntersectionsWithThisUnit, + ignoreIntersectionsWithThisSecondUnit, current.point.x, current.point.y, + movementType, collisionSize, lastNode.point.x, lastNode.point.y) + || !pathableBetween(ignoreIntersectionsWithThisUnit, + ignoreIntersectionsWithThisSecondUnit, startX, startY, movementType, + collisionSize, lastNode.point.x, lastNode.point.y) + || !allowSmoothing) { // Add the point if it's not the first one, or if we can only complete // the journey by specifically walking to the first one totalPath.addFirst(current.point); @@ -187,8 +233,7 @@ public class CPathfindingProcessor { for (final Direction direction : Direction.VALUES) { final float x = current.point.x + (direction.xOffset * 32); final float y = current.point.y + (direction.yOffset * 32); - if (this.pathingGrid.contains(x, y) && pathableBetween(ignoreIntersectionsWithThisUnit, current.point.x, - current.point.y, movementType, collisionSize, x, y)) { + if (this.pathingGrid.contains(x, y)) { double turnCost; if ((current.cameFromDirection != null) && (direction != current.cameFromDirection)) { turnCost = 0.25; @@ -196,7 +241,11 @@ public class CPathfindingProcessor { else { turnCost = 0; } - final double tentativeScore = current.g + ((direction.length + turnCost) * 32); + double tentativeScore = current.g + ((direction.length + turnCost) * 32); + if (!pathableBetween(ignoreIntersectionsWithThisUnit, ignoreIntersectionsWithThisSecondUnit, + current.point.x, current.point.y, movementType, collisionSize, x, y)) { + tentativeScore += (direction.length) * weightForHittingWalls; + } final Node neighbor = searchGraph[gridMapping.getY(this.pathingGrid, y)][gridMapping .getX(this.pathingGrid, x)]; if (tentativeScore < neighbor.g) { @@ -214,20 +263,24 @@ public class CPathfindingProcessor { return Collections.emptyList(); } - private boolean pathableBetween(final CUnit ignoreIntersectionsWithThisUnit, final float startX, final float startY, + private boolean pathableBetween(final CUnit ignoreIntersectionsWithThisUnit, + final CUnit ignoreIntersectionsWithThisSecondUnit, final float startX, final float startY, final PathingGrid.MovementType movementType, final float collisionSize, final float x, final float y) { return this.pathingGrid.isPathable(x, y, movementType, collisionSize) && this.pathingGrid.isPathable(startX, y, movementType, collisionSize) && this.pathingGrid.isPathable(x, startY, movementType, collisionSize) - && isPathableDynamically(x, y, ignoreIntersectionsWithThisUnit, movementType) - && isPathableDynamically(x, startY, ignoreIntersectionsWithThisUnit, movementType) - && isPathableDynamically(startX, y, ignoreIntersectionsWithThisUnit, movementType); + && isPathableDynamically(x, y, ignoreIntersectionsWithThisUnit, ignoreIntersectionsWithThisSecondUnit, + movementType) + && isPathableDynamically(x, startY, ignoreIntersectionsWithThisUnit, + ignoreIntersectionsWithThisSecondUnit, movementType) + && isPathableDynamically(startX, y, ignoreIntersectionsWithThisUnit, + ignoreIntersectionsWithThisSecondUnit, movementType); } private boolean isPathableDynamically(final float x, final float y, final CUnit ignoreIntersectionsWithThisUnit, - final PathingGrid.MovementType movementType) { + final CUnit ignoreIntersectionsWithThisSecondUnit, final PathingGrid.MovementType movementType) { return !this.worldCollision.intersectsAnythingOtherThan(tempRect.setCenter(x, y), - ignoreIntersectionsWithThisUnit, movementType); + ignoreIntersectionsWithThisUnit, ignoreIntersectionsWithThisSecondUnit, movementType); } public static boolean isCollisionSizeBetterSuitedForCorners(final float collisionSize) { @@ -242,8 +295,24 @@ public class CPathfindingProcessor { return n.g; } + private boolean isGoal(final Node n) { + for (int i = 0; i < this.goals; i++) { + if (n == this.goalSet[i]) { + return true; + } + } + return false; + } + public float h(final Node n) { - return (float) n.point.distance(this.goal.point); + float bestDistance = 0; + for (int i = 0; i < this.goals; i++) { + final float possibleDistance = (float) n.point.distance(this.goalSet[i].point); + if (possibleDistance > bestDistance) { + bestDistance = possibleDistance; // always overestimate + } + } + return bestDistance; } public static final class Node { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CAllianceType.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CAllianceType.java new file mode 100644 index 0000000..8587028 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CAllianceType.java @@ -0,0 +1,14 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.players; + +public enum CAllianceType { + PASSIVE, + HELP_REQUEST, + HELP_RESPONSE, + SHARED_XP, + SHARED_SPELLS, + SHARED_VISION, + SHARED_CONTROL, + SHARED_ADVANCED_CONTROL, + RESCUABLE, + SHARED_VISION_FORCED; +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CMapControl.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CMapControl.java new file mode 100644 index 0000000..0d7f4a1 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CMapControl.java @@ -0,0 +1,10 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.players; + +public enum CMapControl { + USER, + COMPUTER, + RESCUABLE, + NEUTRAL, + CREEP, + NONE; +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayer.java new file mode 100644 index 0000000..cc3f424 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayer.java @@ -0,0 +1,109 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.players; + +import java.util.EnumSet; + +import com.etheller.warsmash.util.WarsmashConstants; + +public class CPlayer { + private final int id; + private int colorIndex; + private final CMapControl controlType; + private String name; + private final CRace race; + private final float[] startLocation; + private final EnumSet racePrefs; + private int gold; + private int lumber; + private final EnumSet[] alliances = new EnumSet[WarsmashConstants.MAX_PLAYERS]; + + public CPlayer(final int id, final CMapControl controlType, final String name, final CRace race, + final float[] startLocation) { + this.id = id; + this.colorIndex = id; + this.controlType = controlType; + this.name = name; + this.race = race; + this.startLocation = startLocation; + this.racePrefs = EnumSet.noneOf(CRacePreference.class); + for (int i = 0; i < this.alliances.length; i++) { + if (i == this.id) { + // player is fully allied with self + this.alliances[i] = EnumSet.allOf(CAllianceType.class); + } + else { + this.alliances[i] = EnumSet.noneOf(CAllianceType.class); + } + } + } + + public int getId() { + return this.id; + } + + public int getColorIndex() { + return this.colorIndex; + } + + public CMapControl getControlType() { + return this.controlType; + } + + public String getName() { + return this.name; + } + + public void setName(final String name) { + this.name = name; + } + + public CRace getRace() { + return this.race; + } + + public boolean isRacePrefSet(final CRacePreference racePref) { + return this.racePrefs.contains(racePref); + } + + public void setAlliance(final CPlayer otherPlayer, final CAllianceType allianceType, final boolean value) { + final EnumSet alliancesWithOtherPlayer = this.alliances[otherPlayer.getId()]; + if (value) { + alliancesWithOtherPlayer.add(allianceType); + } + else { + alliancesWithOtherPlayer.remove(allianceType); + } + } + + public boolean hasAlliance(final CPlayer otherPlayer, final CAllianceType allianceType) { + return hasAlliance(otherPlayer.getId(), allianceType); + } + + public boolean hasAlliance(final int otherPlayerIndex, final CAllianceType allianceType) { + final EnumSet alliancesWithOtherPlayer = this.alliances[otherPlayerIndex]; + return alliancesWithOtherPlayer.contains(allianceType); + } + + public int getGold() { + return this.gold; + } + + public int getLumber() { + return this.lumber; + } + + public float[] getStartLocation() { + return this.startLocation; + } + + public void setGold(final int gold) { + this.gold = gold; + } + + public void setLumber(final int lumber) { + this.lumber = lumber; + } + + public void setColorIndex(final int colorIndex) { + this.colorIndex = colorIndex; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CPlayerController.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerController.java similarity index 78% rename from core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CPlayerController.java rename to core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerController.java index 5378300..fc482c1 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CPlayerController.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerController.java @@ -1,4 +1,4 @@ -package com.etheller.warsmash.viewer5.handlers.w3x.simulation; +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.players; public interface CPlayerController { boolean issueTargetOrder(int unitHandleId, int orderId, int targetHandleId); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CRace.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CRace.java new file mode 100644 index 0000000..26bb76a --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CRace.java @@ -0,0 +1,30 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.players; + +public enum CRace { + HUMAN(1), + ORC(2), + UNDEAD(3), + NIGHTELF(4), + DEMON(5), + OTHER(7); + + private int id; + + private CRace(final int id) { + this.id = id; + } + + public int getId() { + return this.id; + } + + public static CRace parseRace(final int race) { + // TODO: this is bad time complexity (slow) but we're only doing it on startup + for (final CRace raceEnum : values()) { + if (raceEnum.getId() == race) { + return raceEnum; + } + } + return null; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CRacePreference.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CRacePreference.java new file mode 100644 index 0000000..7086929 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CRacePreference.java @@ -0,0 +1,11 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.players; + +public enum CRacePreference { + HUMAN, + ORC, + NIGHTELF, + UNDEAD, + DEMON, + RANDOM, + USER_SELECTABLE; +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/ProjectileCreator.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/ProjectileCreator.java deleted file mode 100644 index be22c6a..0000000 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/ProjectileCreator.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.etheller.warsmash.viewer5.handlers.w3x.simulation.util; - -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.projectile.CAttackProjectile; - -public interface ProjectileCreator { - CAttackProjectile create(CSimulation simulation, CUnit source, int attackIndex, CWidget target); -} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java new file mode 100644 index 0000000..43f0993 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java @@ -0,0 +1,19 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.util; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackInstant; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackMissile; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.projectile.CAttackProjectile; + +public interface SimulationRenderController { + CAttackProjectile createAttackProjectile(CSimulation simulation, float launchX, float launchY, float launchFacing, + CUnit source, CUnitAttackMissile attack, CWidget target, float damage, int bounceIndex); + + void createInstantAttackEffect(CSimulation cSimulation, CUnit source, CUnitAttackInstant attack, CWidget target); + + void spawnUnitDamageSound(CUnit damagedUnit, final String weaponSound, String armorType); + + void removeUnit(CUnit unit); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java index 96bd077..e41a302 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java @@ -2,6 +2,8 @@ package com.etheller.warsmash.viewer5.handlers.w3x.ui; import java.io.IOException; +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.GlyphLayout; @@ -21,19 +23,25 @@ import com.etheller.warsmash.parsers.fdf.frames.TextureFrame; import com.etheller.warsmash.parsers.fdf.frames.UIFrame; import com.etheller.warsmash.parsers.jass.Jass2.RootFrameListener; import com.etheller.warsmash.util.FastNumberFormat; +import com.etheller.warsmash.util.ImageUtils; import com.etheller.warsmash.viewer5.Scene; import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; -import com.etheller.warsmash.viewer5.handlers.w3x.StandSequence; +import com.etheller.warsmash.viewer5.handlers.mdx.SequenceLoopMode; +import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; +import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.CommandCardIcon; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.COrder; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitClassification; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitStateListener; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityStop; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CDefenseType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CodeKeyType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; -public class MeleeUI { +public class MeleeUI implements CUnitStateListener { private final DataSource dataSource; private final Viewport uiViewport; private final FreeTypeFontGenerator fontGenerator; @@ -43,6 +51,10 @@ public class MeleeUI { private GameUI rootFrame; private UIFrame consoleUI; private UIFrame resourceBar; + private StringFrame resourceBarGoldText; + private StringFrame resourceBarLumberText; + private StringFrame resourceBarSupplyText; + private StringFrame resourceBarUpkeepText; private UIFrame timeIndicator; private UIFrame unitPortrait; private StringFrame unitLifeText; @@ -69,6 +81,10 @@ public class MeleeUI { private StringFrame armorInfoPanelIconLevel; private InfoPanelIconBackdrops damageBackdrops; private InfoPanelIconBackdrops defenseBackdrops; + private RenderUnit selectedUnit; + + // TODO remove this & replace with FDF + private final Texture activeButtonTexture; public MeleeUI(final DataSource dataSource, final Viewport uiViewport, final FreeTypeFontGenerator fontGenerator, final Scene uiScene, final War3MapViewer war3MapViewer, final RootFrameListener rootFrameListener) { @@ -79,6 +95,8 @@ public class MeleeUI { this.war3MapViewer = war3MapViewer; this.rootFrameListener = rootFrameListener; + this.activeButtonTexture = ImageUtils.getBLPTexture(war3MapViewer.mapMpq, + "UI\\Widgets\\Console\\Human\\CommandButton\\human-activebutton.blp"); } /** @@ -89,7 +107,7 @@ public class MeleeUI { // ================================= // Load skins and templates // ================================= - this.rootFrame = new GameUI(this.dataSource, GameUI.loadSkin(this.dataSource, 1), this.uiViewport, + this.rootFrame = new GameUI(this.dataSource, GameUI.loadSkin(this.dataSource, 3), this.uiViewport, this.fontGenerator, this.uiScene, this.war3MapViewer); try { this.rootFrame.loadTOCFile("UI\\FrameDef\\FrameDef.toc"); @@ -118,6 +136,16 @@ public class MeleeUI { // put it in the "TOPRIGHT" corner. this.resourceBar = this.rootFrame.createSimpleFrame("ResourceBarFrame", this.consoleUI, 0); this.resourceBar.addSetPoint(new SetPoint(FramePoint.TOPRIGHT, this.consoleUI, FramePoint.TOPRIGHT, 0, 0)); + this.resourceBarGoldText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarGoldText", 0); + this.resourceBarGoldText.setText("500"); + this.resourceBarLumberText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarLumberText", 0); + this.resourceBarLumberText.setText("150"); + this.resourceBarSupplyText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarSupplyText", 0); + this.resourceBarSupplyText.setText("153/100"); + this.resourceBarSupplyText.setColor(Color.RED); + this.resourceBarUpkeepText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarUpkeepText", 0); + this.resourceBarUpkeepText.setText("High Upkeep"); + this.resourceBarUpkeepText.setColor(Color.RED); // Create the Time Indicator (clock) this.timeIndicator = this.rootFrame.createFrame("TimeOfDayIndicator", this.rootFrame, 0, 0); @@ -174,6 +202,25 @@ public class MeleeUI { public void render(final SpriteBatch batch, final BitmapFont font20, final GlyphLayout glyphLayout) { this.rootFrame.render(batch, font20, glyphLayout); + + if (this.selectedUnit != null) { + font20.setColor(Color.WHITE); + + final COrder currentOrder = this.selectedUnit.getSimulationUnit().getCurrentOrder(); + for (final CommandCardIcon commandCardIcon : this.selectedUnit.getCommandCardIcons()) { + batch.draw(commandCardIcon.getTexture(), 1235 + (86.8f * commandCardIcon.getX()), + 190 - (88 * commandCardIcon.getY()), 78f, 78f); + if (((currentOrder != null) && (currentOrder.getOrderId() == commandCardIcon.getOrderId())) + || ((currentOrder == null) && (commandCardIcon.getOrderId() == CAbilityStop.ORDER_ID))) { + final int blendDstFunc = batch.getBlendDstFunc(); + final int blendSrcFunc = batch.getBlendSrcFunc(); + batch.setBlendFunction(GL20.GL_SRC_ALPHA, GL20.GL_ONE); + batch.draw(this.activeButtonTexture, 1235 + (86.8f * commandCardIcon.getX()), + 190 - (88 * commandCardIcon.getY()), 78f, 78f); + batch.setBlendFunction(blendSrcFunc, blendDstFunc); + } + } + } } public void portraitTalk() { @@ -196,12 +243,12 @@ public class MeleeUI { this.portraitCameraManager.updateCamera(); if ((this.modelInstance != null) && (this.modelInstance.sequenceEnded || (this.modelInstance.sequence == -1))) { - StandSequence.randomPortraitSequence(this.modelInstance); + SequenceUtils.randomPortraitSequence(this.modelInstance); } } public void talk() { - StandSequence.randomPortraitTalkSequence(this.modelInstance); + SequenceUtils.randomPortraitTalkSequence(this.modelInstance); } public void setSelectedUnit(final RenderUnit unit) { @@ -220,7 +267,7 @@ public class MeleeUI { } this.modelInstance = (MdxComplexInstance) portraitModel.addInstance(); this.portraitCameraManager.setModelInstance(this.modelInstance, portraitModel); - this.modelInstance.setSequenceLoopMode(1); + this.modelInstance.setSequenceLoopMode(SequenceLoopMode.MODEL_LOOP); this.modelInstance.setScene(this.portraitScene); this.modelInstance.setVertexColor(unit.instance.vertexColor); this.modelInstance.setTeamColor(unit.playerIndex); @@ -229,8 +276,15 @@ public class MeleeUI { } } - public void selectUnit(final RenderUnit unit) { + public void selectUnit(RenderUnit unit) { + if ((unit != null) && unit.getSimulationUnit().isDead()) { + unit = null; + } + if (this.selectedUnit != null) { + this.selectedUnit.getSimulationUnit().removeStateListener(this); + } this.portrait.setSelectedUnit(unit); + this.selectedUnit = unit; if (unit == null) { this.simpleNameValue.setText(""); this.unitLifeText.setText(""); @@ -245,6 +299,7 @@ public class MeleeUI { this.armorInfoPanelIconLevel.setText(""); } else { + unit.getSimulationUnit().addStateListener(this); this.simpleNameValue.setText(unit.getSimulationUnit().getUnitType().getName()); String classText = null; for (final CUnitClassification classification : unit.getSimulationUnit().getClassifications()) { @@ -291,8 +346,13 @@ public class MeleeUI { } this.armorIcon.setVisible(true); - this.armorIconBackdrop.setTexture( - this.defenseBackdrops.getTexture(unit.getSimulationUnit().getUnitType().getDefenseType())); + final Texture defenseTexture = this.defenseBackdrops + .getTexture(unit.getSimulationUnit().getUnitType().getDefenseType()); + if (defenseTexture == null) { + throw new RuntimeException( + unit.getSimulationUnit().getUnitType().getDefenseType() + " can't find texture!"); + } + this.armorIconBackdrop.setTexture(defenseTexture); this.armorInfoPanelIconValue.setText(Integer.toString(unit.getSimulationUnit().getDefense())); } } @@ -309,8 +369,8 @@ public class MeleeUI { this.uiViewport.project(this.projectionTemp1); this.uiViewport.project(this.projectionTemp2); - this.tempRect.x = this.projectionTemp1.x; - this.tempRect.y = this.projectionTemp1.y; + this.tempRect.x = this.projectionTemp1.x + this.uiViewport.getScreenX(); + this.tempRect.y = this.projectionTemp1.y + this.uiViewport.getScreenY(); this.tempRect.width = this.projectionTemp2.x - this.projectionTemp1.x; this.tempRect.height = this.projectionTemp2.y - this.projectionTemp1.y; this.portrait.portraitScene.camera.viewport(this.tempRect); @@ -325,10 +385,11 @@ public class MeleeUI { for (int index = 0; index < attackTypes.length; index++) { final CodeKeyType attackType = attackTypes[index]; String skinLookupKey = "InfoPanelIcon" + prefix + attackType.getCodeKey() + suffix; - try { - this.damageBackdropTextures[index] = gameUI.loadTexture(gameUI.getSkinField(skinLookupKey)); + final Texture suffixTexture = gameUI.loadTexture(gameUI.getSkinField(skinLookupKey)); + if (suffixTexture != null) { + this.damageBackdropTextures[index] = suffixTexture; } - catch (final Exception exc) { + else { skinLookupKey = "InfoPanelIcon" + prefix + attackType.getCodeKey(); this.damageBackdropTextures[index] = gameUI.loadTexture(gameUI.getSkinField(skinLookupKey)); } @@ -369,4 +430,20 @@ public class MeleeUI { } } + + @Override + public void lifeChanged() { + if (this.selectedUnit.getSimulationUnit().isDead()) { + selectUnit(null); + } + else { + this.unitLifeText + .setText(FastNumberFormat.formatWholeNumber(this.selectedUnit.getSimulationUnit().getLife()) + " / " + + this.selectedUnit.getSimulationUnit().getMaximumLife()); + } + } + + public RenderUnit getSelectedUnit() { + return this.selectedUnit; + } } diff --git a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java index 2c7fba2..5ad7d58 100644 --- a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java +++ b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java @@ -84,8 +84,14 @@ public class DesktopLauncher { config.gles30ContextMajorVersion = 3; config.gles30ContextMinorVersion = 3; config.samples = 16; - config.fullscreen = false; - if (config.fullscreen) { + config.vSyncEnabled = false; + config.foregroundFPS = 0; + config.backgroundFPS = 0; + if ((arg.length > 0) && "-windowed".equals(arg[0])) { + config.fullscreen = false; + } + else { + config.fullscreen = true; final DisplayMode desktopDisplayMode = LwjglApplicationConfiguration.getDesktopDisplayMode(); config.width = desktopDisplayMode.width; config.height = desktopDisplayMode.height; From 59d350dd9e17a2a13160f5c34bb08431aa1ff562 Mon Sep 17 00:00:00 2001 From: Retera Date: Sat, 26 Sep 2020 16:02:20 -0400 Subject: [PATCH 047/116] Add game clock functionality and begin work on lighting system --- core/assets/warsmash.ini | 3 +- .../etheller/warsmash/WarsmashGdxGame.java | 3 +- .../etheller/warsmash/WarsmashGdxMapGame.java | 203 +++++++++++------- .../etheller/warsmash/parsers/fdf/GameUI.java | 1 - .../parsers/fdf/frames/SpriteFrame.java | 18 ++ .../etheller/warsmash/units/GameObject.java | 7 + .../warsmash/units/HashedGameObject.java | 12 ++ .../warsmash/units/StandardObjectData.java | 11 + .../warsmash/util/RenderMathUtils.java | 124 +++++++++++ .../warsmash/viewer5/ModelInstance.java | 4 + .../warsmash/viewer5/ModelViewer.java | 4 +- .../com/etheller/warsmash/viewer5/Scene.java | 11 + .../warsmash/viewer5/SceneLightInstance.java | 5 + .../warsmash/viewer5/SceneLightManager.java | 7 + .../viewer5/handlers/mdx/CollisionShape.java | 20 ++ .../warsmash/viewer5/handlers/mdx/Geoset.java | 7 +- .../warsmash/viewer5/handlers/mdx/Light.java | 30 ++- .../viewer5/handlers/mdx/LightInstance.java | 78 +++++++ .../handlers/mdx/MdxComplexInstance.java | 51 ++++- .../handlers/mdx/MdxSimpleInstance.java | 5 + .../viewer5/handlers/mdx/MdxViewer.java | 20 ++ .../viewer5/handlers/mdx/SetupGeosets.java | 3 +- .../viewer5/handlers/w3x/SequenceUtils.java | 6 +- .../viewer5/handlers/w3x/W3xSceneLight.java | 9 + .../handlers/w3x/W3xSceneLightManager.java | 35 +++ .../viewer5/handlers/w3x/War3MapViewer.java | 81 ++++--- .../handlers/w3x/environment/Terrain.java | 63 +----- .../w3x/simulation/CGameplayConstants.java | 25 +++ .../handlers/w3x/simulation/CSimulation.java | 8 + .../w3x/simulation/data/CUnitData.java | 36 ++-- .../viewer5/handlers/w3x/ui/MeleeUI.java | 43 +++- 31 files changed, 734 insertions(+), 199 deletions(-) create mode 100644 core/src/com/etheller/warsmash/viewer5/SceneLightInstance.java create mode 100644 core/src/com/etheller/warsmash/viewer5/SceneLightManager.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/LightInstance.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxViewer.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xSceneLight.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xSceneLightManager.java diff --git a/core/assets/warsmash.ini b/core/assets/warsmash.ini index 549c548..c828594 100644 --- a/core/assets/warsmash.ini +++ b/core/assets/warsmash.ini @@ -17,7 +17,8 @@ Path06="." [Map] //FilePath="CombatUnitTests.w3x" -FilePath="PitchRoll.w3x" +//FilePath="PitchRoll.w3x" +FilePath="PeonStartingBase.w3x" //FilePath="PlayerPeasants.w3m" //FilePath="FireLord.w3x" //FilePath="Maps\Campaign\NightElf03.w3m" \ No newline at end of file diff --git a/core/src/com/etheller/warsmash/WarsmashGdxGame.java b/core/src/com/etheller/warsmash/WarsmashGdxGame.java index 3b3121d..674083a 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxGame.java @@ -27,6 +27,7 @@ import com.etheller.warsmash.viewer5.SolvedPath; import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxHandler; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; +import com.etheller.warsmash.viewer5.handlers.mdx.MdxViewer; import com.etheller.warsmash.viewer5.handlers.mdx.SequenceLoopMode; public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvider { @@ -62,7 +63,7 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide final FolderDataSourceDescriptor currentFolder = new FolderDataSourceDescriptor("."); this.codebase = new CompoundDataSourceDescriptor( Arrays.asList(war3mpq, testingFolder, currentFolder)).createDataSource(); - this.viewer = new ModelViewer(this.codebase, this); + this.viewer = new MdxViewer(this.codebase, this); this.viewer.addHandler(new MdxHandler()); this.viewer.enableAudio(); diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java index e7aa3ea..0818154 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java @@ -56,15 +56,13 @@ import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderUnit; import com.etheller.warsmash.viewer5.handlers.w3x.ui.MeleeUI; public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProvider, InputProcessor { - private static final float HORIZONTAL_MAXIMUM = (float) Math.toRadians(56); - private static final float HORIZONTAL_MINIMUM = (float) -Math.toRadians(56); private static final double HORIZONTAL_ANGLE_INCREMENT = Math.PI / 60; private static final Vector3 clickLocationTemp = new Vector3(); private static final Vector2 clickLocationTemp2 = new Vector2(); private DataSource codebase; private War3MapViewer viewer; - private CameraManager cameraManager; + private GameCameraManager cameraManager; private final Rectangle tempRect = new Rectangle(); // libGDX stuff @@ -146,7 +144,20 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv throw new RuntimeException(e); } - this.cameraManager = new CameraManager(); + final Element cameraData = this.viewer.miscData.get("Camera"); + final Element cameraListenerData = this.viewer.miscData.get("Listener"); + final CameraPreset[] cameraPresets = new CameraPreset[6]; + for (int i = 0; i < cameraPresets.length; i++) { + cameraPresets[i] = new CameraPreset(cameraData.getFieldFloatValue("AOA", i), + cameraData.getFieldFloatValue("FOV", i), cameraData.getFieldFloatValue("Rotation", i), + cameraData.getFieldFloatValue("Rotation", i + cameraPresets.length), + cameraData.getFieldFloatValue("Rotation", i + (cameraPresets.length * 2)), + cameraData.getFieldFloatValue("Distance", i), cameraData.getFieldFloatValue("FarZ", i), + cameraData.getFieldFloatValue("NearZ", i), cameraData.getFieldFloatValue("Height", i), + cameraListenerData.getFieldFloatValue("ListenerDistance", i), + cameraListenerData.getFieldFloatValue("ListenerAOA", i)); + } + this.cameraManager = new GameCameraManager(cameraPresets); this.cameraManager.setupCamera(this.viewer.worldScene); @@ -287,9 +298,10 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv final float deltaTime = Gdx.graphics.getDeltaTime(); Gdx.gl30.glBindVertexArray(WarsmashGdxGame.VAO); this.cameraManager.target.add(this.cameraVelocity.x * deltaTime, this.cameraVelocity.y * deltaTime, 0); - this.cameraManager.target.z = Math.max( + this.cameraManager.target.z = (Math.max( this.viewer.terrain.getGroundHeight(this.cameraManager.target.x, this.cameraManager.target.y), - this.viewer.terrain.getWaterHeight(this.cameraManager.target.x, this.cameraManager.target.y)) - 256; + this.viewer.terrain.getWaterHeight(this.cameraManager.target.x, this.cameraManager.target.y)) - 256) + + this.cameraManager.presets[this.cameraManager.currentPreset].height + 256; this.cameraManager.updateCamera(); this.meleeUI.updatePortrait(); this.viewer.updateAndRender(); @@ -400,28 +412,26 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.meleeUI.resize(); } - public static class CameraManager { - private final float[] cameraPositionTemp = new float[3]; - private final float[] cameraTargetTemp = new float[3]; - public com.etheller.warsmash.viewer5.handlers.mdx.Camera modelCamera; - private MdxComplexInstance modelInstance; - private CanvasProvider canvas; - private Camera camera; - private float moveSpeed; - private float rotationSpeed; - private float zoomFactor; - private float horizontalAngle; - private float verticalAngle; - private float verticalAngleAcceleration; - private float distance; - private Vector3 position; - private Vector3 target; - private Vector3 worldUp; - private Vector3 vecHeap; - private Quaternion quatHeap; - private Quaternion quatHeap2; - private boolean insertDown; - private boolean deleteDown; + public static abstract class CameraManager { + protected final float[] cameraPositionTemp = new float[3]; + protected final float[] cameraTargetTemp = new float[3]; + protected CanvasProvider canvas; + protected Camera camera; + protected float moveSpeed; + protected float rotationSpeed; + protected float zoomFactor; + protected float horizontalAngle; + protected float verticalAngle; + protected float distance; + protected Vector3 position; + protected Vector3 target; + protected Vector3 worldUp; + protected Vector3 vecHeap; + protected Quaternion quatHeap; + protected Quaternion quatHeap2; + + public CameraManager() { + } // An orbit camera setup example. // Left mouse button controls the orbit itself. @@ -449,45 +459,58 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv // cameraUpdate(); } + public abstract void updateCamera(); + +// private void cameraUpdate() { +// +// } + } + + public static final class GameCameraManager extends CameraManager { + private final CameraPreset[] presets; + private int currentPreset = 0; + + protected boolean insertDown; + protected boolean deleteDown; + + public GameCameraManager(final CameraPreset[] presets) { + this.presets = presets; + } + + @Override + public void updateCamera() { + this.quatHeap2.idt(); + final CameraPreset cameraPreset = this.presets[this.currentPreset]; + this.quatHeap.idt(); + this.horizontalAngle = (float) Math + .toRadians(cameraPreset.getRotation(this.insertDown, this.deleteDown) - 90); + this.quatHeap.setFromAxisRad(0, 0, 1, this.horizontalAngle); + this.distance = Math.max(1200, cameraPreset.distance); + this.verticalAngle = (float) Math.toRadians(Math.min(335, cameraPreset.aoa) - 270); + this.quatHeap2.setFromAxisRad(1, 0, 0, this.verticalAngle); + this.quatHeap.mul(this.quatHeap2); + + this.position.set(0, 0, 1); + this.quatHeap.transform(this.position); + this.position.nor(); + this.position.scl(this.distance); + this.position = this.position.add(this.target); + this.camera.perspective((float) Math.toRadians(cameraPreset.fov / 2), this.camera.getAspect(), + cameraPreset.nearZ, cameraPreset.farZ); + + this.camera.moveToAndFace(this.position, this.target, this.worldUp); + } + } + + public static final class PortraitCameraManager extends CameraManager { + public com.etheller.warsmash.viewer5.handlers.mdx.Camera modelCamera; + protected MdxComplexInstance modelInstance; + + @Override public void updateCamera() { this.quatHeap.idt(); this.quatHeap.setFromAxisRad(0, 0, 1, this.horizontalAngle); - if (this.insertDown && !this.deleteDown) { - this.horizontalAngle -= HORIZONTAL_ANGLE_INCREMENT; - if (this.horizontalAngle < HORIZONTAL_MINIMUM) { - this.horizontalAngle = HORIZONTAL_MINIMUM; - } - } - else if (this.deleteDown && !this.insertDown) { - this.horizontalAngle += HORIZONTAL_ANGLE_INCREMENT; - if (this.horizontalAngle > HORIZONTAL_MAXIMUM) { - this.horizontalAngle = HORIZONTAL_MAXIMUM; - } - } - else { - if (Math.abs(this.horizontalAngle) < HORIZONTAL_ANGLE_INCREMENT) { - this.horizontalAngle = 0; - } - else { - this.horizontalAngle -= HORIZONTAL_ANGLE_INCREMENT * Math.signum(this.horizontalAngle); - } - } - this.quatHeap2.idt(); - this.verticalAngle += this.verticalAngleAcceleration; - this.verticalAngleAcceleration *= 0.975f; - if (this.verticalAngle > ((Math.PI / 2) - Math.toRadians(17))) { - this.verticalAngle = (float) ((Math.PI / 2) - Math.toRadians(17)); - if (this.verticalAngleAcceleration > 0) { - this.verticalAngleAcceleration = 0; - } - } - if (this.verticalAngle < (float) Math.toRadians(34)) { - this.verticalAngle = (float) Math.toRadians(34); - if (this.verticalAngleAcceleration < 0) { - this.verticalAngleAcceleration = 0; - } - } this.quatHeap2.setFromAxisRad(1, 0, 0, this.verticalAngle); this.quatHeap.mul(this.quatHeap2); @@ -525,10 +548,6 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.modelCamera = portraitModel.getCameras().get(0); } } - -// private void cameraUpdate() { -// -// } } private final float cameraSpeed = 4096.0f; // per second @@ -697,12 +716,12 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv @Override public boolean scrolled(final int amount) { - this.cameraManager.verticalAngleAcceleration -= amount / 100.f; - if (this.cameraManager.verticalAngleAcceleration > (Math.PI / 128)) { - this.cameraManager.verticalAngleAcceleration = (float) (Math.PI / 128); + this.cameraManager.currentPreset -= amount; + if (this.cameraManager.currentPreset < 0) { + this.cameraManager.currentPreset = 0; } - if (this.cameraManager.verticalAngleAcceleration < (-Math.PI / 128)) { - this.cameraManager.verticalAngleAcceleration = -(float) (Math.PI / 128); + if (this.cameraManager.currentPreset >= this.cameraManager.presets.length) { + this.cameraManager.currentPreset = this.cameraManager.presets.length - 1; } return true; } @@ -716,4 +735,44 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.text = text; } } + + private static class CameraPreset { + private final float aoa; + private final float fov; + private final float rotation; + private final float rotationInsert; + private final float rotationDelete; + private final float distance; + private final float farZ; + private final float nearZ; + private final float height; + private final float listenerDistance; + private final float listenerAOA; + + public CameraPreset(final float aoa, final float fov, final float rotation, final float rotationInsert, + final float rotationDelete, final float distance, final float farZ, final float nearZ, + final float height, final float listenerDistance, final float listenerAOA) { + this.aoa = aoa; + this.fov = fov; + this.rotation = rotation; + this.rotationInsert = rotationInsert; + this.rotationDelete = rotationDelete; + this.distance = distance; + this.farZ = farZ; + this.nearZ = nearZ; + this.height = height; + this.listenerDistance = listenerDistance; + this.listenerAOA = listenerAOA; + } + + public float getRotation(final boolean insertDown, final boolean deleteDown) { + if (insertDown && !deleteDown) { + return this.rotationInsert; + } + if (!insertDown && deleteDown) { + return this.rotationDelete; + } + return this.rotation; + } + } } diff --git a/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java index 994aa5b..048720d 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java @@ -209,7 +209,6 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { final MdxModel model = (MdxModel) this.modelViewer.load(backgroundArt, this.modelViewer.mapPathSolver, this.modelViewer.solverParams); spriteFrame.setModel(model); - spriteFrame.setSequence(0); viewport2 = this.fdfCoordinateResolutionDummyViewport; inflatedFrame = spriteFrame; } diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/SpriteFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/SpriteFrame.java index b783df6..28fb028 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/SpriteFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/SpriteFrame.java @@ -50,4 +50,22 @@ public class SpriteFrame extends AbstractRenderableFrame { } } + public void setAnimationSpeed(final float speedRatio) { + if (this.instance != null) { + this.instance.setAnimationSpeed(speedRatio); + } + } + + public void setFrame(final int animationFrame) { + if (this.instance != null) { + this.instance.setFrame(animationFrame); + } + } + + public void setFrameByRatio(final float ratioOfAnimationCompleted) { + if (this.instance != null) { + this.instance.setFrameByRatio(ratioOfAnimationCompleted); + } + } + } diff --git a/core/src/com/etheller/warsmash/units/GameObject.java b/core/src/com/etheller/warsmash/units/GameObject.java index e630a4a..bc5f5d0 100644 --- a/core/src/com/etheller/warsmash/units/GameObject.java +++ b/core/src/com/etheller/warsmash/units/GameObject.java @@ -20,6 +20,8 @@ public interface GameObject { public float getFieldFloatValue(String field); + public float getFieldFloatValue(String field, int index); + public List getFieldAsList(String field, ObjectData objectData); public String getId(); @@ -75,6 +77,11 @@ public interface GameObject { return 0; } + @Override + public float getFieldFloatValue(final String field, final int index) { + return 0; + } + @Override public List getFieldAsList(final String field, final ObjectData objectData) { return Collections.emptyList(); diff --git a/core/src/com/etheller/warsmash/units/HashedGameObject.java b/core/src/com/etheller/warsmash/units/HashedGameObject.java index a138344..326a500 100644 --- a/core/src/com/etheller/warsmash/units/HashedGameObject.java +++ b/core/src/com/etheller/warsmash/units/HashedGameObject.java @@ -79,6 +79,18 @@ public abstract class HashedGameObject implements GameObject { return i; } + @Override + public float getFieldFloatValue(final String field, final int index) { + float i = 0; + try { + i = Float.parseFloat(getField(field, index)); + } + catch (final NumberFormatException e) { + + } + return i; + } + @Override public void setField(final String field, final String value, final int index) { final StringKey key = new StringKey(field); diff --git a/core/src/com/etheller/warsmash/units/StandardObjectData.java b/core/src/com/etheller/warsmash/units/StandardObjectData.java index 34c71f7..913f982 100644 --- a/core/src/com/etheller/warsmash/units/StandardObjectData.java +++ b/core/src/com/etheller/warsmash/units/StandardObjectData.java @@ -504,6 +504,17 @@ public class StandardObjectData { return 0f; } + @Override + public float getFieldFloatValue(final String field, final int index) { + for (final DataTable table : this.dataSource.getTables()) { + final Element element = table.get(this.id); + if ((element != null) && element.hasField(field)) { + return element.getFieldFloatValue(field, index); + } + } + return 0f; + } + /* * (non-Javadoc) I'm not entirely sure this is still safe to use * diff --git a/core/src/com/etheller/warsmash/util/RenderMathUtils.java b/core/src/com/etheller/warsmash/util/RenderMathUtils.java index 6c554fd..5030190 100644 --- a/core/src/com/etheller/warsmash/util/RenderMathUtils.java +++ b/core/src/com/etheller/warsmash/util/RenderMathUtils.java @@ -8,10 +8,12 @@ import java.nio.IntBuffer; import java.nio.ShortBuffer; import java.util.List; +import com.badlogic.gdx.math.Intersector; import com.badlogic.gdx.math.Matrix4; import com.badlogic.gdx.math.Quaternion; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Vector3; +import com.badlogic.gdx.math.collision.Ray; public enum RenderMathUtils { ; @@ -451,6 +453,128 @@ public enum RenderMathUtils { return out; } + static Vector3 best = new Vector3(); + static Vector3 tmp = new Vector3(); + static Vector3 tmp1 = new Vector3(); + static Vector3 tmp2 = new Vector3(); + static Vector3 tmp3 = new Vector3(); + + /** + * Intersects the given ray with list of triangles. Returns the nearest + * intersection point in intersection + * + * @param ray The ray + * @param vertices the vertices + * @param indices the indices, each successive 3 shorts index the 3 + * vertices of a triangle + * @param vertexSize the size of a vertex in floats + * @param intersection The nearest intersection point (optional) + * @return Whether the ray and the triangles intersect. + */ + public static boolean intersectRayTriangles(final Ray ray, final float[] vertices, final int[] indices, + final int vertexSize, final Vector3 intersection) { + float min_dist = Float.MAX_VALUE; + boolean hit = false; + + if ((indices.length % 3) != 0) { + throw new RuntimeException("triangle list size is not a multiple of 3"); + } + + for (int i = 0; i < indices.length; i += 3) { + final int i1 = indices[i] * vertexSize; + final int i2 = indices[i + 1] * vertexSize; + final int i3 = indices[i + 2] * vertexSize; + + final boolean result = Intersector.intersectRayTriangle(ray, + tmp1.set(vertices[i1], vertices[i1 + 1], vertices[i1 + 2]), + tmp2.set(vertices[i2], vertices[i2 + 1], vertices[i2 + 2]), + tmp3.set(vertices[i3], vertices[i3 + 1], vertices[i3 + 2]), tmp); + + if (result == true) { + final float dist = ray.origin.dst2(tmp); + if (dist < min_dist) { + min_dist = dist; + best.set(tmp); + hit = true; + } + } + } + + if (hit == false) { + return false; + } + else { + if (intersection != null) { + intersection.set(best); + } + return true; + } + } + + /** + * Intersects the given ray with list of triangles. Returns the nearest + * intersection point in intersection + * + * @param ray The ray + * @param vertices the vertices + * @param indices the indices, each successive 3 shorts index the 3 + * vertices of a triangle + * @param vertexSize the size of a vertex in floats + * @param intersection The nearest intersection point (optional) + * @return Whether the ray and the triangles intersect. + */ + public static boolean intersectRayTriangles(final Ray ray, final float[] vertices, final int[] indices, + final int vertexSize, final Vector3 worldLocation, final float facingRadians, final Vector3 intersection) { + float min_dist = Float.MAX_VALUE; + boolean hit = false; + + if ((indices.length % 3) != 0) { + throw new RuntimeException("triangle list size is not a multiple of 3"); + } + + final float facingX_X = (float) Math.cos(facingRadians); + final float facingX_Y = (float) Math.sin(facingRadians); + final double halfPi = Math.PI / 2; + final float facingY_X = (float) Math.cos(facingRadians + halfPi); + final float facingY_Y = (float) Math.sin(facingRadians + halfPi); + for (int i = 0; i < indices.length; i += 3) { + final int i1 = indices[i] * vertexSize; + final int i2 = indices[i + 1] * vertexSize; + final int i3 = indices[i + 2] * vertexSize; + + final boolean result = Intersector.intersectRayTriangle(ray, + tmp1.set((vertices[i1] * facingX_X) + (vertices[i1 + 1] * facingY_X) + worldLocation.x, + (vertices[i1] * facingX_Y) + (vertices[i1 + 1] * facingY_Y) + worldLocation.y, + vertices[i1 + 2] + worldLocation.z), + tmp2.set((vertices[i2] * facingX_X) + (vertices[i2 + 1] * facingY_X) + worldLocation.x, + (vertices[i2] * facingX_Y) + (vertices[i2 + 1] * facingY_Y) + worldLocation.y, + vertices[i2 + 2] + worldLocation.z), + tmp3.set((vertices[i3] * facingX_X) + (vertices[i3 + 1] * facingY_X) + worldLocation.x, + (vertices[i3] * facingX_Y) + (vertices[i3 + 1] * facingY_Y) + worldLocation.y, + vertices[i3 + 2] + worldLocation.z), + tmp); + + if (result == true) { + final float dist = ray.origin.dst2(tmp); + if (dist < min_dist) { + min_dist = dist; + best.set(tmp); + hit = true; + } + } + } + + if (hit == false) { + return false; + } + else { + if (intersection != null) { + intersection.set(best); + } + return true; + } + } + // ==== All of the following "wrap" calls are horribly inefficient. Eventually // they should be removed entirely with better design. // Until that happens, be sure to only call them during setup and not while the diff --git a/core/src/com/etheller/warsmash/viewer5/ModelInstance.java b/core/src/com/etheller/warsmash/viewer5/ModelInstance.java index 2c4f334..d9d527e 100644 --- a/core/src/com/etheller/warsmash/viewer5/ModelInstance.java +++ b/core/src/com/etheller/warsmash/viewer5/ModelInstance.java @@ -77,9 +77,13 @@ public abstract class ModelInstance extends Node { } } + this.updateLights(scene); + this.updateFrame = this.model.viewer.frame; } + protected abstract void updateLights(Scene scene2); + public boolean setScene(final Scene scene) { return scene.addInstance(this); } diff --git a/core/src/com/etheller/warsmash/viewer5/ModelViewer.java b/core/src/com/etheller/warsmash/viewer5/ModelViewer.java index af93a7c..00ada9c 100644 --- a/core/src/com/etheller/warsmash/viewer5/ModelViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/ModelViewer.java @@ -20,7 +20,7 @@ import com.etheller.warsmash.viewer5.gl.WebGL; import com.etheller.warsmash.viewer5.handlers.ResourceHandler; import com.etheller.warsmash.viewer5.handlers.ResourceHandlerConstructionParams; -public class ModelViewer { +public abstract class ModelViewer { public DataSource dataSource; public final CanvasProvider canvas; public List resources; @@ -366,4 +366,6 @@ public class ModelViewer { private void onResourceLoadError() { System.err.println("error, this, InvalidHandler, FailedToLoad"); } + + public abstract SceneLightManager createLightManager(); } diff --git a/core/src/com/etheller/warsmash/viewer5/Scene.java b/core/src/com/etheller/warsmash/viewer5/Scene.java index 930e8d6..fda3f6f 100644 --- a/core/src/com/etheller/warsmash/viewer5/Scene.java +++ b/core/src/com/etheller/warsmash/viewer5/Scene.java @@ -58,6 +58,7 @@ public abstract class Scene { * If true, alpha works as usual. */ public boolean alpha = false; + private final SceneLightManager lightManager; public Scene(final ModelViewer viewer) { final CanvasProvider canvas = viewer.canvas; @@ -85,6 +86,8 @@ public abstract class Scene { this.instanceDepthComparator = new InstanceDepthComparator(); this.visibleCells = 0; this.visibleInstances = 0; + + this.lightManager = this.viewer.createLightManager(); } public boolean enableAudio() { @@ -300,6 +303,14 @@ public abstract class Scene { } } + public void addLight(final SceneLightInstance lightInstance) { + this.lightManager.add(lightInstance); + } + + public void removeLight(final SceneLightInstance lightInstance) { + this.lightManager.remove(lightInstance); + } + private static final class InstanceDepthComparator implements Comparator { @Override public int compare(final ModelInstance o1, final ModelInstance o2) { diff --git a/core/src/com/etheller/warsmash/viewer5/SceneLightInstance.java b/core/src/com/etheller/warsmash/viewer5/SceneLightInstance.java new file mode 100644 index 0000000..8a94922 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/SceneLightInstance.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.viewer5; + +public interface SceneLightInstance { + +} diff --git a/core/src/com/etheller/warsmash/viewer5/SceneLightManager.java b/core/src/com/etheller/warsmash/viewer5/SceneLightManager.java new file mode 100644 index 0000000..407f083 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/SceneLightManager.java @@ -0,0 +1,7 @@ +package com.etheller.warsmash.viewer5; + +public interface SceneLightManager { + public void add(final SceneLightInstance lightInstance); + + public void remove(final SceneLightInstance lightInstance); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/CollisionShape.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/CollisionShape.java index d5db9d1..a6e1a6a 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/CollisionShape.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/CollisionShape.java @@ -5,6 +5,8 @@ import com.badlogic.gdx.math.Matrix4; import com.badlogic.gdx.math.Vector3; import com.badlogic.gdx.math.collision.BoundingBox; import com.badlogic.gdx.math.collision.Ray; +import com.etheller.warsmash.util.RenderMathUtils; +import com.etheller.warsmash.viewer5.GenericNode; public class CollisionShape extends GenericObject { private static Vector3 intersectHeap = new Vector3(); @@ -87,4 +89,22 @@ public class CollisionShape extends GenericObject { return Intersector.intersectRaySphere(ray, intersectHeap, this.radius, intersection); } } + + public static boolean intersectRayTriangles(final Ray ray, final GenericNode mdxNode, final float[] vertices, + final int[] indices, final int vertexSize, final Vector3 intersection) { + intersectMatrixHeap.set(mdxNode.worldMatrix); + Matrix4.inv(intersectMatrixHeap.val); + intersectHeap.set(ray.origin); + intersectHeap2.set(ray.direction); + intersectHeap2.add(ray.origin); + intersectHeap.prj(intersectMatrixHeap); + intersectHeap2.prj(intersectMatrixHeap); + intersectHeap2.sub(intersectHeap); + intersectRayHeap.set(intersectHeap, intersectHeap2); + if (RenderMathUtils.intersectRayTriangles(intersectRayHeap, vertices, indices, vertexSize, intersection)) { + intersection.prj(mdxNode.worldMatrix); + return true; + } + return false; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Geoset.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Geoset.java index b3975f1..a52c46b 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Geoset.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Geoset.java @@ -24,10 +24,13 @@ public class Geoset { private final int openGLSkinType; private final int skinStride; private final int boneCountOffsetBytes; + public final boolean unselectable; + public final com.etheller.warsmash.parsers.mdlx.Geoset mdlxGeoset; public Geoset(final MdxModel model, final int index, final int positionOffset, final int normalOffset, final int uvOffset, final int skinOffset, final int faceOffset, final int vertices, final int elements, - final int openGLSkinType, final int skinStride, final int boneCountOffsetBytes) { + final int openGLSkinType, final int skinStride, final int boneCountOffsetBytes, final boolean unselectable, + final com.etheller.warsmash.parsers.mdlx.Geoset mdlxGeoset) { this.model = model; this.index = index; this.positionOffset = positionOffset; @@ -40,6 +43,8 @@ public class Geoset { this.openGLSkinType = openGLSkinType; this.skinStride = skinStride; this.boneCountOffsetBytes = boneCountOffsetBytes; + this.unselectable = unselectable; + this.mdlxGeoset = mdlxGeoset; for (final GeosetAnimation geosetAnimation : model.getGeosetAnimations()) { if (geosetAnimation.geosetId == index) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Light.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Light.java index c3a9179..d6bd2cd 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Light.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Light.java @@ -4,7 +4,7 @@ import com.etheller.warsmash.parsers.mdlx.AnimationMap; public class Light extends GenericObject { - private final int type; + private final Type type; private final float[] attenuation; private final float[] color; private final float intensity; @@ -14,7 +14,18 @@ public class Light extends GenericObject { public Light(final MdxModel model, final com.etheller.warsmash.parsers.mdlx.Light light, final int index) { super(model, light, index); - this.type = light.getType(); + switch (light.getType()) { + case 0: + this.type = Type.OMNIDIRECTIONAL; + break; + case 2: + this.type = Type.DIRECTIONAL; + break; + default: + case 1: + this.type = Type.AMBIENT; + break; + } this.attenuation = light.getAttenuation(); this.color = light.getColor(); this.intensity = light.getIntensity(); @@ -22,6 +33,10 @@ public class Light extends GenericObject { this.ambientIntensity = light.getAmbientIntensity(); } + public Type getType() { + return this.type; + } + public int getAttenuationStart(final float[] out, final int sequence, final int frame, final int counter) { return this.getScalarValue(out, AnimationMap.KLAS.getWar3id(), sequence, frame, counter, this.attenuation[0]); } @@ -45,4 +60,15 @@ public class Light extends GenericObject { public int getAmbientColor(final float[] out, final int sequence, final int frame, final int counter) { return this.getVectorValue(out, AnimationMap.KLBC.getWar3id(), sequence, frame, counter, this.ambientColor); } + + public static enum Type { + // Omnidirectional light used for in-game sun + OMNIDIRECTIONAL, + // Directional light used for torches in the game world, and similar objects + // that "glow" + DIRECTIONAL, + // Directional ambient light used for torches in the game world, and similar + // objects that "glow" + AMBIENT; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/LightInstance.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/LightInstance.java new file mode 100644 index 0000000..c7c5b59 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/LightInstance.java @@ -0,0 +1,78 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +import java.nio.FloatBuffer; + +import com.etheller.warsmash.viewer5.Scene; +import com.etheller.warsmash.viewer5.SceneLightInstance; +import com.etheller.warsmash.viewer5.UpdatableObject; + +public class LightInstance implements UpdatableObject, SceneLightInstance { + private static final float[] vectorHeap = new float[3]; + private static final float[] scalarHeap = new float[1]; + protected final MdxNode node; + protected final Light light; + private boolean visible; + private boolean loadedInScene; + + public LightInstance(final MdxComplexInstance instance, final Light light) { + this.node = instance.nodes[light.index]; + this.light = light; + } + + public void bind(final int offset, final FloatBuffer floatBuffer, final int sequence, final int frame, + final int counter) { + this.light.getAttenuationStart(scalarHeap, sequence, frame, counter); + final float attenuationStart = scalarHeap[0]; + this.light.getAttenuationEnd(scalarHeap, sequence, frame, counter); + final float attenuationEnd = scalarHeap[0]; + this.light.getIntensity(scalarHeap, sequence, frame, counter); + final float intensity = scalarHeap[0]; + this.light.getColor(vectorHeap, sequence, frame, counter); + final float colorRed = vectorHeap[0]; + final float colorGreen = vectorHeap[1]; + final float colorBlue = vectorHeap[2]; + this.light.getAmbientIntensity(scalarHeap, sequence, frame, counter); + final float ambientIntensity = scalarHeap[0]; + this.light.getAmbientColor(vectorHeap, sequence, frame, counter); + final float ambientColorRed = vectorHeap[0]; + final float ambientColorGreen = vectorHeap[1]; + final float ambientColorBlue = vectorHeap[2]; + floatBuffer.put(offset, this.node.worldLocation.x); + floatBuffer.put(offset + 1, this.node.worldLocation.y); + floatBuffer.put(offset + 2, this.node.worldLocation.z); + // I use some padding to make the memory structure of the light be a 4x4 float + // grid, when somebody who actually has experience with this stuff comes along + // to change this to something smart, maybe they'll remove the padding if it's + // not necessary. I'm basing how I implement this on how Ghostwolf did + // BoneTexture + floatBuffer.put(offset + 3, 0); + floatBuffer.put(offset + 4, this.light.getType().ordinal()); + floatBuffer.put(offset + 5, attenuationStart); + floatBuffer.put(offset + 6, attenuationEnd); + floatBuffer.put(offset + 7, 0); + floatBuffer.put(offset + 8, colorRed); + floatBuffer.put(offset + 9, colorGreen); + floatBuffer.put(offset + 10, colorBlue); + floatBuffer.put(offset + 11, intensity); + floatBuffer.put(offset + 12, ambientColorRed); + floatBuffer.put(offset + 13, ambientColorGreen); + floatBuffer.put(offset + 14, ambientColorBlue); + floatBuffer.put(offset + 15, ambientIntensity); + } + + @Override + public void update(final float dt, final boolean visible) { + this.visible = visible; + } + + public void update(final Scene scene) { + if (this.loadedInScene != this.visible) { + if (this.visible) { + scene.addLight(this); + } + else { + scene.removeLight(this); + } + } + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java index 16887a9..b7eb675 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java @@ -34,6 +34,7 @@ public class MdxComplexInstance extends ModelInstance { private static final float[] alphaHeap = new float[1]; private static final long[] textureIdHeap = new long[1]; + public List lights = new ArrayList<>(); public List attachments = new ArrayList<>(); public List particleEmitters = new ArrayList<>(); public List particleEmitters2 = new ArrayList<>(); @@ -106,7 +107,9 @@ public class MdxComplexInstance extends ModelInstance { } for (final Light light : model.lights) { - this.initNode(this.nodes, this.nodes[nodeIndex++], light); + final LightInstance lightInstance = new LightInstance(this, light); + this.lights.add(lightInstance); + this.initNode(this.nodes, this.nodes[nodeIndex++], light, lightInstance); } for (final Helper helper : model.helpers) { @@ -601,6 +604,13 @@ public class MdxComplexInstance extends ModelInstance { } + @Override + protected void updateLights(final Scene scene) { + for (final LightInstance light : this.lights) { + light.update(scene); + } + } + /** * Set the team color of this instance. */ @@ -715,10 +725,28 @@ public class MdxComplexInstance extends ModelInstance { * @param ray */ public boolean intersectRayWithCollision(final Ray ray, final Vector3 intersection) { - for (final CollisionShape collisionShape : ((MdxModel) this.model).collisionShapes) { - final MdxNode mdxNode = this.nodes[collisionShape.index]; - if (collisionShape.checkIntersect(ray, mdxNode, intersection)) { - return true; + final MdxModel mdxModel = (MdxModel) this.model; + final List collisionShapes = mdxModel.collisionShapes; + if (collisionShapes.isEmpty()) { + for (final Geoset geoset : mdxModel.geosets) { + if (!geoset.unselectable) { + geoset.getAlpha(alphaHeap, this.sequence, this.frame, this.counter); + if (alphaHeap[0] > 0) { + final com.etheller.warsmash.parsers.mdlx.Geoset mdlxGeoset = geoset.mdlxGeoset; + if (CollisionShape.intersectRayTriangles(ray, this, mdlxGeoset.getVertices(), + mdlxGeoset.getFaces(), 3, intersection)) { + return true; + } + } + } + } + } + else { + for (final CollisionShape collisionShape : collisionShapes) { + final MdxNode mdxNode = this.nodes[collisionShape.index]; + if (collisionShape.checkIntersect(ray, mdxNode, intersection)) { + return true; + } } } return false; @@ -727,4 +755,17 @@ public class MdxComplexInstance extends ModelInstance { public void setAnimationSpeed(final float speedRatio) { this.animationSpeed = speedRatio; } + + public void setFrame(final int frame) { + this.frame = frame; + this.floatingFrame = frame; + } + + public void setFrameByRatio(final float ratioOfAnimationCompleted) { + final Sequence currentlyPlayingSequence = ((MdxModel) this.model).sequences.get(this.sequence); + this.floatingFrame = currentlyPlayingSequence.getInterval()[0] + + ((currentlyPlayingSequence.getInterval()[1] - currentlyPlayingSequence.getInterval()[0]) + * ratioOfAnimationCompleted); + this.frame = (int) this.floatingFrame; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxSimpleInstance.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxSimpleInstance.java index 50190e1..b176f1e 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxSimpleInstance.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxSimpleInstance.java @@ -6,6 +6,7 @@ import com.etheller.warsmash.viewer5.BatchedInstance; import com.etheller.warsmash.viewer5.Model; import com.etheller.warsmash.viewer5.PathSolver; import com.etheller.warsmash.viewer5.RenderBatch; +import com.etheller.warsmash.viewer5.Scene; import com.etheller.warsmash.viewer5.Texture; import com.etheller.warsmash.viewer5.TextureMapper; @@ -32,6 +33,10 @@ public class MdxSimpleInstance extends BatchedInstance { public void renderTranslucent() { } + @Override + protected void updateLights(final Scene scene2) { + } + @Override public void load() { } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxViewer.java new file mode 100644 index 0000000..990205b --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxViewer.java @@ -0,0 +1,20 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +import com.etheller.warsmash.datasources.DataSource; +import com.etheller.warsmash.viewer5.CanvasProvider; +import com.etheller.warsmash.viewer5.ModelViewer; +import com.etheller.warsmash.viewer5.SceneLightManager; + +public class MdxViewer extends ModelViewer { + + public MdxViewer(final DataSource dataSource, final CanvasProvider canvas) { + super(dataSource, canvas); + } + + @Override + public SceneLightManager createLightManager() { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SetupGeosets.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SetupGeosets.java index ad214d8..fe5b4d7 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SetupGeosets.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SetupGeosets.java @@ -155,9 +155,10 @@ public class SetupGeosets { } } + final boolean unselectable = geoset.getSelectionFlags() == 4; final Geoset vGeoset = new Geoset(model, model.getGeosets().size(), positionOffset, normalOffset, uvOffset, skinOffset, faceOffset, vertices, faces.length, openGLSkinType, skinStride, - boneCountOffsetBytes); + boneCountOffsetBytes, unselectable, geoset); model.getGeosets().add(vGeoset); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SequenceUtils.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SequenceUtils.java index 6dcc083..b819645 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SequenceUtils.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SequenceUtils.java @@ -67,6 +67,7 @@ public class SequenceUtils { filtered.sort(STAND_SEQUENCE_COMPARATOR); int i = 0; + final double randomRoll = Math.random() * 100; for (final int l = filtered.size(); i < l; i++) { final Sequence sequence = filtered.get(i).sequence; final float rarity = sequence.getRarity(); @@ -75,7 +76,7 @@ public class SequenceUtils { break; } - if ((Math.random() * 10) > rarity) { + if (randomRoll < (10 - rarity)) { return filtered.get(i); } } @@ -98,6 +99,7 @@ public class SequenceUtils { filtered.sort(STAND_SEQUENCE_COMPARATOR); int i = 0; + final double randomRoll = Math.random() * 100; for (final int l = filtered.size(); i < l; i++) { final Sequence sequence = filtered.get(i).sequence; final float rarity = sequence.getRarity(); @@ -106,7 +108,7 @@ public class SequenceUtils { break; } - if (((Math.random() * 10) > rarity) && allowRarityVariations) { + if ((randomRoll < (10 - rarity)) && allowRarityVariations) { return filtered.get(i); } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xSceneLight.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xSceneLight.java new file mode 100644 index 0000000..1bc01c4 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xSceneLight.java @@ -0,0 +1,9 @@ +package com.etheller.warsmash.viewer5.handlers.w3x; + +public class W3xSceneLight { + + public static enum Type { + OMNIDIRECTIONAL, + DIRECTIONAL; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xSceneLightManager.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xSceneLightManager.java new file mode 100644 index 0000000..a46c9a3 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xSceneLightManager.java @@ -0,0 +1,35 @@ +package com.etheller.warsmash.viewer5.handlers.w3x; + +import java.util.ArrayList; +import java.util.List; + +import com.etheller.warsmash.viewer5.SceneLightInstance; +import com.etheller.warsmash.viewer5.SceneLightManager; +import com.etheller.warsmash.viewer5.gl.DataTexture; +import com.etheller.warsmash.viewer5.handlers.mdx.LightInstance; + +public class W3xSceneLightManager implements SceneLightManager { + public final List lights; + private final DataTexture unitLightsTexture; + private final DataTexture terrainLightsTexture; + + public W3xSceneLightManager(final War3MapViewer viewer) { + this.lights = new ArrayList<>(); + this.unitLightsTexture = new DataTexture(viewer.gl, 4, 4, 1); + this.terrainLightsTexture = new DataTexture(viewer.gl, 4, 4, 1); + } + + @Override + public void add(final SceneLightInstance lightInstance) { + // TODO redesign to avoid cast + final LightInstance mdxLight = (LightInstance) lightInstance; + this.lights.add(mdxLight); + } + + @Override + public void remove(final SceneLightInstance lightInstance) { + // TODO redesign to avoid cast + final LightInstance mdxLight = (LightInstance) lightInstance; + this.lights.remove(mdxLight); + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index 30f82e5..a49b942 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -42,10 +42,12 @@ import com.etheller.warsmash.parsers.w3x.w3i.War3MapW3i; import com.etheller.warsmash.parsers.w3x.wpm.War3MapWpm; import com.etheller.warsmash.units.DataTable; import com.etheller.warsmash.units.Element; +import com.etheller.warsmash.units.StandardObjectData; import com.etheller.warsmash.units.manager.MutableObjectData; import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; import com.etheller.warsmash.units.manager.MutableObjectData.WorldEditorDataType; import com.etheller.warsmash.util.MappedData; +import com.etheller.warsmash.util.RenderMathUtils; import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.util.WarsmashConstants; import com.etheller.warsmash.util.WorldEditStrings; @@ -56,6 +58,7 @@ import com.etheller.warsmash.viewer5.ModelInstance; import com.etheller.warsmash.viewer5.ModelViewer; import com.etheller.warsmash.viewer5.PathSolver; import com.etheller.warsmash.viewer5.Scene; +import com.etheller.warsmash.viewer5.SceneLightManager; import com.etheller.warsmash.viewer5.Texture; import com.etheller.warsmash.viewer5.WorldScene; import com.etheller.warsmash.viewer5.gl.WebGL; @@ -118,7 +121,6 @@ public class War3MapViewer extends ModelViewer { public SolverParams solverParams = new SolverParams(); public WorldScene worldScene; public boolean anyReady; - public boolean terrainCliffsAndWaterLoaded; public MappedData terrainData = new MappedData(); public MappedData cliffTypesData = new MappedData(); public MappedData waterData = new MappedData(); @@ -151,9 +153,11 @@ public class War3MapViewer extends ModelViewer { public List selected = new ArrayList<>(); private DataTable unitAckSoundsTable; private DataTable unitCombatSoundsTable; - private DataTable miscData; + public DataTable miscData; private DataTable unitGlobalStrings; private MdxComplexInstance confirmationInstance; + private MdxComplexInstance dncUnit; + private MdxComplexInstance dncTerrain; public CSimulation simulation; private float updateTime; @@ -195,7 +199,6 @@ public class War3MapViewer extends ModelViewer { stringDataCallback); // == when loaded, which is always in our system == - this.terrainCliffsAndWaterLoaded = true; this.terrainData.load(terrain.data.toString()); this.cliffTypesData.load(cliffTypes.data.toString()); this.waterData.load(water.data.toString()); @@ -254,6 +257,9 @@ public class War3MapViewer extends ModelViewer { try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\MiscGame.txt")) { this.miscData.readTXT(miscDataTxtStream, true); } + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("UI\\SoundInfo\\MiscData.txt")) { + this.miscData.readTXT(miscDataTxtStream, true); + } this.unitGlobalStrings = new DataTable(worldEditStrings); try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\UnitGlobalStrings.txt")) { this.unitGlobalStrings.readTXT(miscDataTxtStream, true); @@ -342,8 +348,11 @@ public class War3MapViewer extends ModelViewer { final War3MapWpm terrainPathing = this.mapMpq.readPathing(); + final StandardObjectData standardObjectData = new StandardObjectData(this.dataSource); + this.worldEditData = standardObjectData.getWorldEditData(); + this.terrain = new Terrain(terrainData, terrainPathing, w3iFile, this.webGL, this.dataSource, worldEditStrings, - this); + this, this.worldEditData); final float[] centerOffset = terrainData.getCenterOffset(); final int[] mapSize = terrainData.getMapSize(); @@ -363,13 +372,6 @@ public class War3MapViewer extends ModelViewer { this.confirmationInstance.setSequence(0); this.confirmationInstance.setScene(this.worldScene); - if (this.terrainCliffsAndWaterLoaded) { - this.loadTerrainCliffsAndWater(terrainData); - } - else { - throw new IllegalStateException("transcription of JS has not loaded a map and has no JS async promises"); - } - final Warcraft3MapObjectData modifications = this.mapMpq.readModifications(); this.simulation = new CSimulation(this.miscData, modifications.getUnits(), modifications.getAbilities(), new SimulationRenderController() { @@ -388,12 +390,7 @@ public class War3MapViewer extends ModelViewer { final float projectileLaunchY = simulation.getUnitData().getProjectileLaunchY(typeId); final float projectileLaunchZ = simulation.getUnitData().getProjectileLaunchZ(typeId); - if (missileArt.toLowerCase().endsWith(".mdl")) { - missileArt = missileArt.substring(0, missileArt.length() - 4); - } - if (!missileArt.toLowerCase().endsWith(".mdx")) { - missileArt += ".mdx"; - } + missileArt = mdx(missileArt); final float facing = launchFacing; final float sinFacing = (float) Math.sin(facing); final float cosFacing = (float) Math.cos(facing); @@ -435,12 +432,7 @@ public class War3MapViewer extends ModelViewer { .getProjectileLaunchX(typeId); final float projectileLaunchY = War3MapViewer.this.simulation.getUnitData() .getProjectileLaunchY(typeId); - if (missileArt.toLowerCase().endsWith(".mdl")) { - missileArt = missileArt.substring(0, missileArt.length() - 4); - } - if (!missileArt.toLowerCase().endsWith(".mdx")) { - missileArt += ".mdx"; - } + missileArt = mdx(missileArt); final float facing = (float) Math.toRadians(source.getFacing()); final float sinFacing = (float) Math.sin(facing); final float cosFacing = (float) Math.cos(facing); @@ -507,9 +499,17 @@ public class War3MapViewer extends ModelViewer { this.terrain.initShadows(); this.terrain.createWaves(); + + loadLightsAndShading(tileset); } - private void loadTerrainCliffsAndWater(final War3MapW3e w3e) { + private void loadLightsAndShading(final char tileset) { + // TODO this should be set by the war3map.j actually, not by the tileset, so the + // call to set day night models is just for testing to make the test look pretty + final Element defaultTerrainLights = this.worldEditData.get("TerrainLights"); + final Element defaultUnitLights = this.worldEditData.get("UnitLights"); + setDayNightModels(defaultTerrainLights.getField(Character.toString(tileset)), + defaultUnitLights.getField(Character.toString(tileset))); } @@ -902,6 +902,10 @@ public class War3MapViewer extends ModelViewer { this.updateTime -= WarsmashConstants.SIMULATION_STEP_TIME; this.simulation.update(); } + this.dncTerrain.setFrameByRatio( + this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours()); + this.dncUnit.setFrameByRatio( + this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours()); } } @@ -1007,7 +1011,7 @@ public class War3MapViewer extends ModelViewer { this.worldScene.camera.screenToWorldRay(ray, mousePosHeap); gdxRayHeap.set(ray[0], ray[1], ray[2], ray[3] - ray[0], ray[4] - ray[1], ray[5] - ray[2]); gdxRayHeap.direction.nor();// needed for libgdx - Terrain.intersectRayTriangles(gdxRayHeap, this.terrain.softwareGroundMesh.vertices, + RenderMathUtils.intersectRayTriangles(gdxRayHeap, this.terrain.softwareGroundMesh.vertices, this.terrain.softwareGroundMesh.indices, 3, out); } @@ -1144,6 +1148,7 @@ public class War3MapViewer extends ModelViewer { private static final int MAXIMUM_ACCEPTED = 1 << 30; private float selectionCircleScaleFactor; + private DataTable worldEditData; /** * Returns a power of two size for the given target capacity. @@ -1240,4 +1245,30 @@ public class War3MapViewer extends ModelViewer { this.textureDotted = textureDotted; } } + + public void setDayNightModels(final String terrainDNCFile, final String unitDNCFile) { + final MdxModel terrainDNCModel = (MdxModel) load(mdx(terrainDNCFile), PathSolver.DEFAULT, null); + this.dncTerrain = (MdxComplexInstance) terrainDNCModel.addInstance(); + this.dncTerrain.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + this.dncTerrain.setSequence(0); + final MdxModel unitDNCModel = (MdxModel) load(mdx(unitDNCFile), PathSolver.DEFAULT, null); + this.dncUnit = (MdxComplexInstance) unitDNCModel.addInstance(); + this.dncUnit.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + this.dncUnit.setSequence(0); + } + + private static String mdx(String mdxPath) { + if (mdxPath.toLowerCase().endsWith(".mdl")) { + mdxPath = mdxPath.substring(0, mdxPath.length() - 4); + } + if (!mdxPath.toLowerCase().endsWith(".mdx")) { + mdxPath += ".mdx"; + } + return mdxPath; + } + + @Override + public SceneLightManager createLightManager() { + return new W3xSceneLightManager(this); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java index 4799364..ca66d73 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java @@ -26,7 +26,6 @@ import com.badlogic.gdx.math.Intersector; import com.badlogic.gdx.math.Matrix4; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Vector3; -import com.badlogic.gdx.math.collision.Ray; import com.etheller.warsmash.datasources.DataSource; import com.etheller.warsmash.parsers.w3x.w3e.Corner; import com.etheller.warsmash.parsers.w3x.w3e.War3MapW3e; @@ -34,7 +33,6 @@ import com.etheller.warsmash.parsers.w3x.w3i.War3MapW3i; import com.etheller.warsmash.parsers.w3x.wpm.War3MapWpm; import com.etheller.warsmash.units.DataTable; import com.etheller.warsmash.units.Element; -import com.etheller.warsmash.units.StandardObjectData; import com.etheller.warsmash.util.ImageUtils; import com.etheller.warsmash.util.RenderMathUtils; import com.etheller.warsmash.util.War3ID; @@ -127,7 +125,7 @@ public class Terrain { public Terrain(final War3MapW3e w3eFile, final War3MapWpm terrainPathing, final War3MapW3i w3iFile, final WebGL webGL, final DataSource dataSource, final WorldEditStrings worldEditStrings, - final War3MapViewer viewer) throws IOException { + final War3MapViewer viewer, final DataTable worldEditData) throws IOException { this.webGL = webGL; this.viewer = viewer; this.camera = viewer.worldScene.camera; @@ -221,8 +219,6 @@ public class Terrain { this.groundTextureToId.put(groundTile.asStringValue(), this.groundTextures.size() - 1); } - final StandardObjectData standardObjectData = new StandardObjectData(dataSource); - final DataTable worldEditData = standardObjectData.getWorldEditData(); final Element tilesets = worldEditData.get("TileSets"); this.blightTextureIndex = this.groundTextures.size(); @@ -1179,67 +1175,10 @@ public class Terrain { // return out; // } - static Vector3 best = new Vector3(); - static Vector3 tmp = new Vector3(); - static Vector3 tmp1 = new Vector3(); - static Vector3 tmp2 = new Vector3(); - static Vector3 tmp3 = new Vector3(); private final WaveBuilder waveBuilder; public PathingGrid pathingGrid; private final Rectangle shaderMapBoundsRectangle; - /** - * Intersects the given ray with list of triangles. Returns the nearest - * intersection point in intersection - * - * @param ray The ray - * @param vertices the vertices - * @param indices the indices, each successive 3 shorts index the 3 - * vertices of a triangle - * @param vertexSize the size of a vertex in floats - * @param intersection The nearest intersection point (optional) - * @return Whether the ray and the triangles intersect. - */ - public static boolean intersectRayTriangles(final Ray ray, final float[] vertices, final int[] indices, - final int vertexSize, final Vector3 intersection) { - float min_dist = Float.MAX_VALUE; - boolean hit = false; - - if ((indices.length % 3) != 0) { - throw new RuntimeException("triangle list size is not a multiple of 3"); - } - - for (int i = 0; i < indices.length; i += 3) { - final int i1 = indices[i] * vertexSize; - final int i2 = indices[i + 1] * vertexSize; - final int i3 = indices[i + 2] * vertexSize; - - final boolean result = Intersector.intersectRayTriangle(ray, - tmp1.set(vertices[i1], vertices[i1 + 1], vertices[i1 + 2]), - tmp2.set(vertices[i2], vertices[i2 + 1], vertices[i2 + 2]), - tmp3.set(vertices[i3], vertices[i3 + 1], vertices[i3 + 2]), tmp); - - if (result == true) { - final float dist = ray.origin.dst2(tmp); - if (dist < min_dist) { - min_dist = dist; - best.set(tmp); - hit = true; - } - } - } - - if (hit == false) { - return false; - } - else { - if (intersection != null) { - intersection.set(best); - } - return true; - } - } - private static final class UnloadedTexture { private final int width; private final int height; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CGameplayConstants.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CGameplayConstants.java index 12a1a9a..9960c94 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CGameplayConstants.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CGameplayConstants.java @@ -19,6 +19,10 @@ public class CGameplayConstants { private final float boneDecayTime; private final float bulletDeathTime; private final float closeEnoughRange; + private final float dawnTimeGameHours; + private final float duskTimeGameHours; + private final float gameDayHours; + private final float gameDayLength; public CGameplayConstants(final DataTable parsedDataTable) { final Element miscData = parsedDataTable.get("Misc"); @@ -30,6 +34,11 @@ public class CGameplayConstants { this.bulletDeathTime = miscData.getFieldFloatValue("BulletDeathTime"); this.closeEnoughRange = miscData.getFieldFloatValue("CloseEnoughRange"); + this.dawnTimeGameHours = miscData.getFieldFloatValue("Dawn"); + this.duskTimeGameHours = miscData.getFieldFloatValue("Dusk"); + this.gameDayHours = miscData.getFieldFloatValue("DayHours"); + this.gameDayLength = miscData.getFieldFloatValue("DayLength"); + final CDefenseType[] defenseTypeOrder = { CDefenseType.SMALL, CDefenseType.MEDIUM, CDefenseType.LARGE, CDefenseType.FORT, CDefenseType.NORMAL, CDefenseType.HERO, CDefenseType.DIVINE, CDefenseType.NONE, }; this.damageBonusTable = new float[CAttackType.values().length][defenseTypeOrder.length]; @@ -80,4 +89,20 @@ public class CGameplayConstants { public float getCloseEnoughRange() { return this.closeEnoughRange; } + + public float getGameDayHours() { + return this.gameDayHours; + } + + public float getGameDayLength() { + return this.gameDayLength; + } + + public float getDawnTimeGameHours() { + return this.dawnTimeGameHours; + } + + public float getDuskTimeGameHours() { + return this.duskTimeGameHours; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java index 75c81e7..913b0cd 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java @@ -40,6 +40,7 @@ public class CSimulation { private final CPathfindingProcessor pathfindingProcessor; private final CGameplayConstants gameplayConstants; private final Random seededRandom; + private float currentGameDayTimeElapsed; public CSimulation(final DataTable miscData, final MutableObjectData parsedUnitData, final MutableObjectData parsedAbilityData, final SimulationRenderController simulationRenderController, @@ -145,6 +146,13 @@ public class CSimulation { this.projectiles.addAll(this.newProjectiles); this.newProjectiles.clear(); this.gameTurnTick++; + this.currentGameDayTimeElapsed = (this.currentGameDayTimeElapsed + WarsmashConstants.SIMULATION_STEP_TIME) + % this.gameplayConstants.getGameDayLength(); + } + + public float getGameTimeOfDay() { + return (this.currentGameDayTimeElapsed / this.gameplayConstants.getGameDayLength()) + * this.gameplayConstants.getGameDayHours(); } public int getGameTurnTick() { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java index f89f4cf..7b6128b 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java @@ -132,6 +132,26 @@ public class CUnitData { final int speed = unitType.getFieldAsInteger(MOVEMENT_SPEED_BASE, 0); final int defense = unitType.getFieldAsInteger(DEFENSE, 0); + final CUnitType unitTypeInstance = getUnitTypeInstance(typeId, buildingPathingPixelMap, unitType); + + final CUnit unit = new CUnit(handleId, playerIndex, x, y, life, typeId, facing, manaInitial, life, manaMaximum, + speed, defense, unitTypeInstance); + if (speed > 0) { + unit.add(simulation, CAbilityMove.INSTANCE); + unit.add(simulation, CAbilityPatrol.INSTANCE); + unit.add(simulation, CAbilityHoldPosition.INSTANCE); + unit.add(simulation, CAbilityStop.INSTANCE); + } + final int dmgDice1 = unitType.getFieldAsInteger(ATTACK1_DMG_DICE, 0); + final int dmgDice2 = unitType.getFieldAsInteger(ATTACK2_DMG_DICE, 0); + if ((dmgDice1 != 0) || (dmgDice2 != 0)) { + unit.add(simulation, CAbilityAttack.INSTANCE); + } + return unit; + } + + private CUnitType getUnitTypeInstance(final War3ID typeId, final BufferedImage buildingPathingPixelMap, + final MutableGameObject unitType) { CUnitType unitTypeInstance = this.unitIdToUnitType.get(typeId); if (unitTypeInstance == null) { final float moveHeight = unitType.getFieldAsFloat(MOVE_HEIGHT, 0); @@ -252,21 +272,7 @@ public class CUnitData { targetedAs); this.unitIdToUnitType.put(typeId, unitTypeInstance); } - - final CUnit unit = new CUnit(handleId, playerIndex, x, y, life, typeId, facing, manaInitial, life, manaMaximum, - speed, defense, unitTypeInstance); - if (speed > 0) { - unit.add(simulation, CAbilityMove.INSTANCE); - unit.add(simulation, CAbilityPatrol.INSTANCE); - unit.add(simulation, CAbilityHoldPosition.INSTANCE); - unit.add(simulation, CAbilityStop.INSTANCE); - } - final int dmgDice1 = unitType.getFieldAsInteger(ATTACK1_DMG_DICE, 0); - final int dmgDice2 = unitType.getFieldAsInteger(ATTACK2_DMG_DICE, 0); - if ((dmgDice1 != 0) || (dmgDice2 != 0)) { - unit.add(simulation, CAbilityAttack.INSTANCE); - } - return unit; + return unitTypeInstance; } private CUnitAttack createAttack(final float animationBackswingPoint, final float animationDamagePoint, diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java index e41a302..a21a4af 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java @@ -18,6 +18,7 @@ import com.etheller.warsmash.parsers.fdf.GameUI; import com.etheller.warsmash.parsers.fdf.datamodel.AnchorDefinition; import com.etheller.warsmash.parsers.fdf.datamodel.FramePoint; import com.etheller.warsmash.parsers.fdf.frames.SetPoint; +import com.etheller.warsmash.parsers.fdf.frames.SpriteFrame; import com.etheller.warsmash.parsers.fdf.frames.StringFrame; import com.etheller.warsmash.parsers.fdf.frames.TextureFrame; import com.etheller.warsmash.parsers.fdf.frames.UIFrame; @@ -55,7 +56,7 @@ public class MeleeUI implements CUnitStateListener { private StringFrame resourceBarLumberText; private StringFrame resourceBarSupplyText; private StringFrame resourceBarUpkeepText; - private UIFrame timeIndicator; + private SpriteFrame timeIndicator; private UIFrame unitPortrait; private StringFrame unitLifeText; private StringFrame unitManaText; @@ -107,7 +108,7 @@ public class MeleeUI implements CUnitStateListener { // ================================= // Load skins and templates // ================================= - this.rootFrame = new GameUI(this.dataSource, GameUI.loadSkin(this.dataSource, 3), this.uiViewport, + this.rootFrame = new GameUI(this.dataSource, GameUI.loadSkin(this.dataSource, 0), this.uiViewport, this.fontGenerator, this.uiScene, this.war3MapViewer); try { this.rootFrame.loadTOCFile("UI\\FrameDef\\FrameDef.toc"); @@ -148,7 +149,9 @@ public class MeleeUI implements CUnitStateListener { this.resourceBarUpkeepText.setColor(Color.RED); // Create the Time Indicator (clock) - this.timeIndicator = this.rootFrame.createFrame("TimeOfDayIndicator", this.rootFrame, 0, 0); + this.timeIndicator = (SpriteFrame) this.rootFrame.createFrame("TimeOfDayIndicator", this.rootFrame, 0, 0); + this.timeIndicator.setSequence(0); // play the stand + this.timeIndicator.setAnimationSpeed(0.0f); // do not advance automatically // Create the unit portrait stuff this.portrait = new Portrait(this.war3MapViewer); @@ -221,6 +224,9 @@ public class MeleeUI implements CUnitStateListener { } } } + + this.timeIndicator.setFrameByRatio(this.war3MapViewer.simulation.getGameTimeOfDay() + / this.war3MapViewer.simulation.getGameplayConstants().getGameDayHours()); } public void portraitTalk() { @@ -229,12 +235,12 @@ public class MeleeUI implements CUnitStateListener { private static final class Portrait { private MdxComplexInstance modelInstance; - private final WarsmashGdxMapGame.CameraManager portraitCameraManager; + private final WarsmashGdxMapGame.PortraitCameraManager portraitCameraManager; private final Scene portraitScene; public Portrait(final War3MapViewer war3MapViewer) { this.portraitScene = war3MapViewer.addSimpleScene(); - this.portraitCameraManager = new WarsmashGdxMapGame.CameraManager(); + this.portraitCameraManager = new WarsmashGdxMapGame.PortraitCameraManager(); this.portraitCameraManager.setupCamera(this.portraitScene); this.portraitScene.camera.viewport(new Rectangle(100, 0, 6400, 48)); } @@ -303,6 +309,11 @@ public class MeleeUI implements CUnitStateListener { this.simpleNameValue.setText(unit.getSimulationUnit().getUnitType().getName()); String classText = null; for (final CUnitClassification classification : unit.getSimulationUnit().getClassifications()) { + if ((classification == CUnitClassification.MECHANICAL) + && unit.getSimulationUnit().getUnitType().isBuilding()) { + // buildings dont display MECHANICAL + continue; + } if (classification.getDisplayName() != null) { classText = classification.getDisplayName(); } @@ -325,7 +336,11 @@ public class MeleeUI implements CUnitStateListener { } this.simpleBuildingActionLabel.setText(""); - if (unit.getSimulationUnit().getUnitType().getAttacks().size() > 0) { + final boolean anyAttacks = unit.getSimulationUnit().getUnitType().getAttacks().size() > 0; + final UIFrame localArmorIcon; + final TextureFrame localArmorIconBackdrop; + final StringFrame localArmorInfoPanelIconValue; + if (anyAttacks) { final CUnitAttack attackOne = unit.getSimulationUnit().getUnitType().getAttacks().get(0); this.attack1Icon.setVisible(attackOne.isShowUI()); this.attack1IconBackdrop.setTexture(this.damageBackdrops.getTexture(attackOne.getAttackType())); @@ -339,21 +354,29 @@ public class MeleeUI implements CUnitStateListener { else { this.attack2Icon.setVisible(false); } + + localArmorIcon = this.armorIcon; + localArmorIconBackdrop = this.armorIconBackdrop; + localArmorInfoPanelIconValue = this.armorInfoPanelIconValue; } else { - this.attack1Icon.setVisible(false); + this.armorIcon.setVisible(false); this.attack2Icon.setVisible(false); + + localArmorIcon = this.attack1Icon; + localArmorIconBackdrop = this.attack1IconBackdrop; + localArmorInfoPanelIconValue = this.attack1InfoPanelIconValue; } - this.armorIcon.setVisible(true); + localArmorIcon.setVisible(true); final Texture defenseTexture = this.defenseBackdrops .getTexture(unit.getSimulationUnit().getUnitType().getDefenseType()); if (defenseTexture == null) { throw new RuntimeException( unit.getSimulationUnit().getUnitType().getDefenseType() + " can't find texture!"); } - this.armorIconBackdrop.setTexture(defenseTexture); - this.armorInfoPanelIconValue.setText(Integer.toString(unit.getSimulationUnit().getDefense())); + localArmorIconBackdrop.setTexture(defenseTexture); + localArmorInfoPanelIconValue.setText(Integer.toString(unit.getSimulationUnit().getDefense())); } } From b0b0745af932083feaa75bffeb17d11214ee5299 Mon Sep 17 00:00:00 2001 From: Retera Date: Sun, 27 Sep 2020 02:58:04 -0400 Subject: [PATCH 048/116] Upgrade scene to have light manager --- .../com/etheller/warsmash/viewer5/Scene.java | 4 ++ .../viewer5/handlers/mdx/BatchGroup.java | 7 ++ .../viewer5/handlers/mdx/LightInstance.java | 8 ++- .../handlers/w3x/W3xSceneLightManager.java | 72 +++++++++++++++++++ .../viewer5/handlers/w3x/War3MapViewer.java | 4 +- 5 files changed, 91 insertions(+), 4 deletions(-) diff --git a/core/src/com/etheller/warsmash/viewer5/Scene.java b/core/src/com/etheller/warsmash/viewer5/Scene.java index fda3f6f..f4c3a4a 100644 --- a/core/src/com/etheller/warsmash/viewer5/Scene.java +++ b/core/src/com/etheller/warsmash/viewer5/Scene.java @@ -317,4 +317,8 @@ public abstract class Scene { return -Float.compare(o2.depth, o1.depth); } } + + public SceneLightManager getLightManager() { + return this.lightManager; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/BatchGroup.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/BatchGroup.java index 72f23a1..17aaea3 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/BatchGroup.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/BatchGroup.java @@ -12,6 +12,7 @@ import com.etheller.warsmash.viewer5.Texture; import com.etheller.warsmash.viewer5.gl.DataTexture; import com.etheller.warsmash.viewer5.gl.WebGL; import com.etheller.warsmash.viewer5.handlers.w3x.DynamicShadowManager; +import com.etheller.warsmash.viewer5.handlers.w3x.W3xSceneLightManager; public class BatchGroup extends GenericGroup { @@ -36,6 +37,7 @@ public class BatchGroup extends GenericGroup { final WebGL webGL = viewer.webGL; final boolean isExtended = this.isExtended; final ShaderProgram shader; + final W3xSceneLightManager lightManager = (W3xSceneLightManager) scene.getLightManager(); if (isExtended) { if (DynamicShadowManager.IS_SHADOW_MAPPING) { @@ -59,6 +61,11 @@ public class BatchGroup extends GenericGroup { shader.setUniformMatrix("u_mvp", mvp); final DataTexture boneTexture = instance.boneTexture; + final DataTexture unitLightsTexture = lightManager.getUnitLightsTexture(); + + unitLightsTexture.bind(16); + shader.setUniformi("u_lightTexture", 16); + shader.setUniformf("u_lightCount", unitLightsTexture.getHeight()); // Instances of models with no bones don't have a bone texture. if (boneTexture != null) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/LightInstance.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/LightInstance.java index c7c5b59..ca95984 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/LightInstance.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/LightInstance.java @@ -13,14 +13,18 @@ public class LightInstance implements UpdatableObject, SceneLightInstance { protected final Light light; private boolean visible; private boolean loadedInScene; + private final MdxComplexInstance instance; public LightInstance(final MdxComplexInstance instance, final Light light) { + this.instance = instance; this.node = instance.nodes[light.index]; this.light = light; } - public void bind(final int offset, final FloatBuffer floatBuffer, final int sequence, final int frame, - final int counter) { + public void bind(final int offset, final FloatBuffer floatBuffer) { + final int sequence = this.instance.sequence; + final int frame = this.instance.frame; + final int counter = this.instance.counter; this.light.getAttenuationStart(scalarHeap, sequence, frame, counter); final float attenuationStart = scalarHeap[0]; this.light.getAttenuationEnd(scalarHeap, sequence, frame, counter); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xSceneLightManager.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xSceneLightManager.java index a46c9a3..e5512cf 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xSceneLightManager.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xSceneLightManager.java @@ -1,5 +1,8 @@ package com.etheller.warsmash.viewer5.handlers.w3x; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; import java.util.ArrayList; import java.util.List; @@ -10,13 +13,19 @@ import com.etheller.warsmash.viewer5.handlers.mdx.LightInstance; public class W3xSceneLightManager implements SceneLightManager { public final List lights; + private FloatBuffer lightDataCopyHeap; private final DataTexture unitLightsTexture; private final DataTexture terrainLightsTexture; + private final War3MapViewer viewer; + private int terrainLightCount; + private int unitLightCount; public W3xSceneLightManager(final War3MapViewer viewer) { + this.viewer = viewer; this.lights = new ArrayList<>(); this.unitLightsTexture = new DataTexture(viewer.gl, 4, 4, 1); this.terrainLightsTexture = new DataTexture(viewer.gl, 4, 4, 1); + this.lightDataCopyHeap = ByteBuffer.allocateDirect(16 * 1 * 4).order(ByteOrder.nativeOrder()).asFloatBuffer(); } @Override @@ -24,6 +33,7 @@ public class W3xSceneLightManager implements SceneLightManager { // TODO redesign to avoid cast final LightInstance mdxLight = (LightInstance) lightInstance; this.lights.add(mdxLight); + rebind(); } @Override @@ -31,5 +41,67 @@ public class W3xSceneLightManager implements SceneLightManager { // TODO redesign to avoid cast final LightInstance mdxLight = (LightInstance) lightInstance; this.lights.remove(mdxLight); + rebind(); + } + + private void rebind() { + final int numberOfLights = this.lights.size() + 1; + final int bytesNeeded = numberOfLights * 4 * 16; + if (bytesNeeded > (this.lightDataCopyHeap.capacity() * 4)) { + this.lightDataCopyHeap = ByteBuffer.allocateDirect(bytesNeeded).order(ByteOrder.nativeOrder()) + .asFloatBuffer(); + } + + this.unitLightCount = 0; + this.lightDataCopyHeap.clear(); + int offset = 0; + if (this.viewer.dncUnit != null) { + if (!this.viewer.dncUnit.lights.isEmpty()) { + this.viewer.dncUnit.lights.get(0).bind(0, this.lightDataCopyHeap); + offset += 16; + this.unitLightCount++; + } + } + for (final LightInstance light : this.lights) { + light.bind(offset, this.lightDataCopyHeap); + offset += 16; + this.unitLightCount++; + } + this.lightDataCopyHeap.flip(); + this.unitLightsTexture.bindAndUpdate(this.lightDataCopyHeap, 16, this.unitLightCount); + + this.terrainLightCount = 0; + this.lightDataCopyHeap.clear(); + offset = 0; + if (this.viewer.dncTerrain != null) { + if (!this.viewer.dncTerrain.lights.isEmpty()) { + this.viewer.dncTerrain.lights.get(0).bind(0, this.lightDataCopyHeap); + offset += 16; + this.terrainLightCount++; + } + } + for (final LightInstance light : this.lights) { + light.bind(offset, this.lightDataCopyHeap); + offset += 16; + this.terrainLightCount++; + } + this.lightDataCopyHeap.flip(); + this.terrainLightsTexture.bindAndUpdate(this.lightDataCopyHeap, 16, this.terrainLightCount); + } + + public DataTexture getUnitLightsTexture() { + return this.unitLightsTexture; + } + + public int getUnitLightCount() { + return this.unitLightCount; + } + + public DataTexture getTerrainLightsTexture() { + return this.terrainLightsTexture; + } + + public int getTerrainLightCount() { + return this.terrainLightCount; } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index a49b942..eca9dff 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -156,8 +156,8 @@ public class War3MapViewer extends ModelViewer { public DataTable miscData; private DataTable unitGlobalStrings; private MdxComplexInstance confirmationInstance; - private MdxComplexInstance dncUnit; - private MdxComplexInstance dncTerrain; + public MdxComplexInstance dncUnit; + public MdxComplexInstance dncTerrain; public CSimulation simulation; private float updateTime; From 843b04b4a8addd55f9643874193b93b3d8eeae29 Mon Sep 17 00:00:00 2001 From: Retera Date: Tue, 6 Oct 2020 02:19:25 -0400 Subject: [PATCH 049/116] Update with lighting system, and after test playing Projection Revolution. --- core/assets/warsmash.ini | 34 +++--- core/assets/warsmash127.ini | 25 ++++ core/assets/warsmash131.ini | 8 +- .../etheller/warsmash/WarsmashGdxMapGame.java | 23 ++-- .../etheller/warsmash/parsers/fdf/GameUI.java | 22 ++++ .../parsers/fdf/frames/TextureFrame.java | 7 ++ .../warsmash/parsers/w3x/w3i/Player.java | 9 +- .../etheller/warsmash/units/DataTable.java | 4 +- .../etheller/warsmash/util/ImageUtils.java | 20 ++- .../etheller/warsmash/util/MappedData.java | 3 +- .../etheller/warsmash/util/ParseUtils.java | 11 ++ .../warsmash/util/RenderMathUtils.java | 1 + .../warsmash/viewer5/ModelViewer.java | 6 +- .../com/etheller/warsmash/viewer5/Scene.java | 6 +- .../warsmash/viewer5/SceneLightManager.java | 2 + .../etheller/warsmash/viewer5/Shaders.java | 59 +++++++++ .../warsmash/viewer5/SimpleScene.java | 4 +- .../warsmash/viewer5/SkeletalNode.java | 39 +++++- .../etheller/warsmash/viewer5/WorldScene.java | 4 +- .../warsmash/viewer5/gl/DataTexture.java | 2 +- .../viewer5/handlers/mdx/BatchGroup.java | 7 +- .../warsmash/viewer5/handlers/mdx/Geoset.java | 2 +- .../warsmash/viewer5/handlers/mdx/Light.java | 13 +- .../viewer5/handlers/mdx/LightInstance.java | 43 +++++-- .../handlers/mdx/MdxComplexInstance.java | 35 +++--- .../viewer5/handlers/mdx/MdxHandler.java | 8 +- .../viewer5/handlers/mdx/MdxShaders.java | 74 ++--------- .../viewer5/handlers/mdx/MdxViewer.java | 2 +- .../viewer5/handlers/mdx/SdSequence.java | 42 ++++++- .../viewer5/handlers/w3x/Destructable.java | 26 ++++ .../warsmash/viewer5/handlers/w3x/Doodad.java | 16 ++- .../viewer5/handlers/w3x/SplatModel.java | 5 +- .../handlers/w3x/W3xSceneLightManager.java | 104 +--------------- .../w3x/W3xScenePortraitLightManager.java | 90 ++++++++++++++ .../w3x/W3xSceneWorldLightManager.java | 112 +++++++++++++++++ .../viewer5/handlers/w3x/W3xShaders.java | 15 ++- .../viewer5/handlers/w3x/War3MapViewer.java | 98 +++++++++++---- .../handlers/w3x/environment/CliffMesh.java | 10 +- .../w3x/environment/GroundTexture.java | 83 ++++++++----- .../handlers/w3x/environment/Terrain.java | 73 +++++++++-- .../w3x/environment/TerrainShaders.java | 57 +++++---- .../handlers/w3x/rendersim/RenderUnit.java | 49 +++++--- .../w3x/simulation/CGameplayConstants.java | 6 + .../handlers/w3x/simulation/CSimulation.java | 2 +- .../handlers/w3x/simulation/CUnit.java | 85 ++++++++++++- .../w3x/simulation/CUnitEnumFunction.java | 6 + .../handlers/w3x/simulation/CUnitType.java | 15 ++- .../w3x/simulation/combat/CAttackType.java | 6 +- .../w3x/simulation/combat/CDefenseType.java | 6 +- .../w3x/simulation/data/CAbilityData.java | 16 +++ .../w3x/simulation/data/CUnitData.java | 7 +- .../w3x/simulation/orders/CAttackOrder.java | 115 +++++++++++------- .../viewer5/handlers/w3x/ui/MeleeUI.java | 11 +- .../warsmash/desktop/DesktopLauncher.java | 6 +- resources/UI/FrameDef/SmashFrameDef.toc | 1 + .../UI/FrameDef/SmashUI/InventoryCover.fdf | 14 +++ 56 files changed, 1121 insertions(+), 428 deletions(-) create mode 100644 core/assets/warsmash127.ini create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/Destructable.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xScenePortraitLightManager.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xSceneWorldLightManager.java create mode 100644 resources/UI/FrameDef/SmashUI/InventoryCover.fdf diff --git a/core/assets/warsmash.ini b/core/assets/warsmash.ini index c828594..e20aad9 100644 --- a/core/assets/warsmash.ini +++ b/core/assets/warsmash.ini @@ -1,24 +1,26 @@ +// This is the Warsmash INI file for Project Revolution +// PRSCMOD + [DataSources] -Count=7 +Count=9 Type00=MPQ -Path00="E:\Games\Warcraft III Patch 1.22\war3.mpq" +Path00="E:\Games\Warcraft III Project Revolution\War3\The Sheep Attack\war3.mpq" Type01=MPQ -Path01="E:\Games\Warcraft III Patch 1.22\War3x.mpq" +Path01="E:\Games\Warcraft III Project Revolution\War3\The Sheep Attack\War3x.mpq" Type02=MPQ -Path02="E:\Games\Warcraft III Patch 1.22\War3xlocal.mpq" +Path02="E:\Games\Warcraft III Project Revolution\War3\The Sheep Attack\War3xlocal.mpq" Type03=MPQ -Path03="E:\Games\Warcraft III Patch 1.22\War3Patch.mpq" -Type04=Folder -Path04="..\..\resources" -Type05=Folder -Path05="E:\Backups\Warsmash\Data" +Path03="E:\Games\Warcraft III Project Revolution\War3\The Sheep Attack\war3patch.mpq" +Type04=MPQ +Path04="E:\Games\Warcraft III Project Revolution\PRSCMOD\Revolution.mpq" +Type05=MPQ +Path05="E:\Games\Warcraft III Project Revolution\PRSCMOD\Sound.mpq" Type06=Folder -Path06="." +Path06="E:\Games\Warcraft III Project Revolution\ProjectRevolusmash" +Type07=Folder +Path07="..\..\resources" +Type08=Folder +Path08="E:\Games\Warcraft III Project Revolution\PRSCMOD\PR-Maps" [Map] -//FilePath="CombatUnitTests.w3x" -//FilePath="PitchRoll.w3x" -FilePath="PeonStartingBase.w3x" -//FilePath="PlayerPeasants.w3m" -//FilePath="FireLord.w3x" -//FilePath="Maps\Campaign\NightElf03.w3m" \ No newline at end of file +FilePath="ProjectRevolusmash.w3x" \ No newline at end of file diff --git a/core/assets/warsmash127.ini b/core/assets/warsmash127.ini new file mode 100644 index 0000000..cbfd76d --- /dev/null +++ b/core/assets/warsmash127.ini @@ -0,0 +1,25 @@ +[DataSources] +Count=7 +Type00=MPQ +Path00="E:\Games\Warcraft III Patch 1.22\war3.mpq" +Type01=MPQ +Path01="E:\Games\Warcraft III Patch 1.22\War3x.mpq" +Type02=MPQ +Path02="E:\Games\Warcraft III Patch 1.22\War3xlocal.mpq" +Type03=MPQ +Path03="E:\Games\Warcraft III Patch 1.22\War3Patch.mpq" +Type04=Folder +Path04="..\..\resources" +Type05=Folder +Path05="E:\Backups\Warsmash\Data" +Type06=Folder +Path06="." + +[Map] +//FilePath="CombatUnitTests.w3x" +//FilePath="PitchRoll.w3x" +FilePath="PeonStartingBase.w3x" +//FilePath="DungeonGoldMine.w3m" +//FilePath="PlayerPeasants.w3m" +//FilePath="FireLord.w3x" +//FilePath="Maps\Campaign\NightElf03.w3m" \ No newline at end of file diff --git a/core/assets/warsmash131.ini b/core/assets/warsmash131.ini index 452f706..af6c427 100644 --- a/core/assets/warsmash131.ini +++ b/core/assets/warsmash131.ini @@ -12,6 +12,10 @@ Type04=Folder Path04="." [Map] -FilePath="PitchRoll.w3x" +//FilePath="PitchRoll.w3x" //FilePath="ReforgedGeorgeVacation.w3x" -//FilePath="Maps\Campaign\NightElf03.w3m" \ No newline at end of file +//FilePath="Maps\Campaign\NightElf03.w3m" +//FilePath="PrivateDontShare/Cult 8.w3x" +//FilePath="TorchLight2.w3x" +FilePath="OrcAssault.w3x" +//FilePath="PeonStartingBase.w3x" diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java index 0818154..41bef64 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java @@ -14,6 +14,7 @@ import com.badlogic.gdx.ApplicationAdapter; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Input; import com.badlogic.gdx.InputProcessor; +import com.badlogic.gdx.audio.Music; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.GL30; @@ -227,15 +228,10 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv Gdx.input.setInputProcessor(this); -// final Music music = Gdx.audio -// .newMusic(new DataSourceFileHandle(this.viewer.dataSource, "Sound\\Music\\mp3Music\\Undead2.mp3")); -// music.setVolume(0.2f); -// music.setLooping(true); -// music.play(); - this.minimap = new Rectangle(18.75f, 13.75f, 278.75f, 276.25f); - final float worldWidth = (this.viewer.terrain.columns - 1); - final float worldHeight = this.viewer.terrain.rows - 1; + final Rectangle playableMapArea = this.viewer.terrain.getPlayableMapArea(); + final float worldWidth = playableMapArea.getWidth(); + final float worldHeight = playableMapArea.getHeight(); final float worldSize = Math.max(worldWidth, worldHeight); final float minimapFilledWidth = (worldWidth / worldSize) * this.minimap.width; final float minimapFilledHeight = (worldHeight / worldSize) * this.minimap.height; @@ -264,6 +260,16 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv new RootFrameListener() { @Override public void onCreate(final GameUI rootFrame) { + WarsmashGdxMapGame.this.viewer.setGameUI(rootFrame); + + final String musicField = rootFrame.getSkinField("Music_V1"); + final String[] musics = musicField.split(";"); + final String musicPath = musics[(int) (Math.random() * musics.length)]; + final Music music = Gdx.audio.newMusic( + new DataSourceFileHandle(WarsmashGdxMapGame.this.viewer.dataSource, musicPath)); + music.setVolume(0.2f); + music.setLooping(true); + music.play(); } }); this.meleeUI.main(); @@ -272,6 +278,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv updateUIScene(); this.meleeUI.resize(); + resize(Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); } private void updateUIScene() { diff --git a/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java index 048720d..4d05e0c 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java @@ -54,6 +54,7 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { private final FreeTypeFontParameter fontParam; private final Map nameToFrame = new HashMap<>(); private final Viewport fdfCoordinateResolutionDummyViewport; + private final DataTable skinData; public GameUI(final DataSource dataSource, final Element skin, final Viewport viewport, final FreeTypeFontGenerator fontGenerator, final Scene uiScene, final War3MapViewer modelViewer) { @@ -74,6 +75,23 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { this.fontGenerator = fontGenerator; this.fontParam = new FreeTypeFontParameter(); this.fdfCoordinateResolutionDummyViewport = new FitViewport(0.8f, 0.6f); + this.skinData = new DataTable(modelViewer.getWorldEditStrings()); + try { + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\CommandFunc.txt")) { + this.skinData.readTXT(miscDataTxtStream, true); + } + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\CommandStrings.txt")) { + this.skinData.readTXT(miscDataTxtStream, true); + } + if (this.dataSource.has("war3mapSkin.txt")) { + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("war3mapSkin.txt")) { + this.skinData.readTXT(miscDataTxtStream, true); + } + } + } + catch (final IOException e) { + throw new RuntimeException(e); + } } public static Element loadSkin(final DataSource dataSource, final String skin) { @@ -146,6 +164,10 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { return file; } + public DataTable getSkinData() { + return this.skinData; + } + public UIFrame createFrame(final String name, final UIFrame owner, final int priority, final int createContext) { final FrameDefinition frameDefinition = this.templates.getFrame(name); if (frameDefinition.getFrameClass() == FrameClass.Frame) { diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/TextureFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/TextureFrame.java index 61ba209..c3f5190 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/TextureFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/TextureFrame.java @@ -23,6 +23,9 @@ public class TextureFrame extends AbstractRenderableFrame { @Override protected void internalRender(final SpriteBatch batch, final BitmapFont baseFont, final GlyphLayout glyphLayout) { + if (this.texture == null) { + return; + } batch.draw(this.texture, this.renderBounds.x, this.renderBounds.y, this.renderBounds.width, this.renderBounds.height); } @@ -42,6 +45,10 @@ public class TextureFrame extends AbstractRenderableFrame { } public void setTexture(final Texture texture) { + if (texture == null) { + this.texture = null; + return; + } final TextureRegion texRegion; if (this.texCoord != null) { texRegion = new TextureRegion(texture, this.texCoord.getX(), this.texCoord.getZ(), this.texCoord.getY(), diff --git a/core/src/com/etheller/warsmash/parsers/w3x/w3i/Player.java b/core/src/com/etheller/warsmash/parsers/w3x/w3i/Player.java index 35653c4..5ab3d0a 100644 --- a/core/src/com/etheller/warsmash/parsers/w3x/w3i/Player.java +++ b/core/src/com/etheller/warsmash/parsers/w3x/w3i/Player.java @@ -3,7 +3,6 @@ package com.etheller.warsmash.parsers.w3x.w3i; import java.io.IOException; import com.etheller.warsmash.util.ParseUtils; -import com.etheller.warsmash.util.War3ID; import com.google.common.io.LittleEndianDataInputStream; import com.google.common.io.LittleEndianDataOutputStream; @@ -11,7 +10,7 @@ import com.google.common.io.LittleEndianDataOutputStream; * A player. */ public class Player { - private War3ID id; + private int id; private int type; private int race; private int isFixedStartPosition; @@ -23,7 +22,7 @@ public class Player { private long enemyHighPrioritiesFlags; public void load(final LittleEndianDataInputStream stream, final int version) throws IOException { - this.id = ParseUtils.readWar3ID(stream); + this.id = (int) ParseUtils.readUInt32(stream); this.type = stream.readInt(); this.race = stream.readInt(); this.isFixedStartPosition = stream.readInt(); @@ -38,7 +37,7 @@ public class Player { } public void save(final LittleEndianDataOutputStream stream) throws IOException { - ParseUtils.writeWar3ID(stream, this.id); + ParseUtils.writeUInt32(stream, this.id); stream.writeInt(this.type); stream.writeInt(this.race); stream.writeInt(this.isFixedStartPosition); @@ -52,7 +51,7 @@ public class Player { return 33 + this.name.length(); } - public War3ID getId() { + public int getId() { return this.id; } diff --git a/core/src/com/etheller/warsmash/units/DataTable.java b/core/src/com/etheller/warsmash/units/DataTable.java index b92404a..319ab95 100644 --- a/core/src/com/etheller/warsmash/units/DataTable.java +++ b/core/src/com/etheller/warsmash/units/DataTable.java @@ -15,7 +15,7 @@ import java.util.Set; import com.etheller.warsmash.util.StringBundle; public class DataTable implements ObjectData { - private static final boolean DEBUG = false; + public static boolean DEBUG = false; Map dataTable = new LinkedHashMap<>(); @@ -182,7 +182,7 @@ public class DataTable implements ObjectData { if (input.startsWith("O;")) { continue; } - if (input.contains("X1;")) { + if (input.contains("X1;") || input.endsWith(";X1")) { rowStartCount++; col = 0; } diff --git a/core/src/com/etheller/warsmash/util/ImageUtils.java b/core/src/com/etheller/warsmash/util/ImageUtils.java index 392b092..9a75d85 100644 --- a/core/src/com/etheller/warsmash/util/ImageUtils.java +++ b/core/src/com/etheller/warsmash/util/ImageUtils.java @@ -19,6 +19,7 @@ import com.badlogic.gdx.graphics.Pixmap; import com.badlogic.gdx.graphics.Pixmap.Format; import com.badlogic.gdx.graphics.Texture; import com.etheller.warsmash.datasources.DataSource; +import com.etheller.warsmash.viewer5.handlers.tga.TgaFile; /** * Uses AWT stuff @@ -26,14 +27,27 @@ import com.etheller.warsmash.datasources.DataSource; */ public final class ImageUtils { private static final int BYTES_PER_PIXEL = 4; + public static final String DEFAULT_ICON_PATH = "ReplaceableTextures\\CommandButtons\\BTNTemp.blp"; public static Texture getBLPTexture(final DataSource dataSource, final String path) { try { try (final InputStream resourceAsStream = dataSource.getResourceAsStream(path)) { - if (resourceAsStream == null) { - throw new IllegalStateException("missing resource: " + path); + if ((resourceAsStream == null) || path.endsWith(".tga")) { + final String tgaPath = path.substring(0, path.length() - 4) + ".tga"; + try (final InputStream tgaStream = dataSource.getResourceAsStream(tgaPath)) { + if (tgaStream == null) { + throw new IllegalStateException("missing resource: " + path); + } + else { + return ImageUtils.getTexture(TgaFile.readTGA(tgaPath, tgaStream)); + } + } } - return ImageUtils.getTexture(ImageIO.read(resourceAsStream)); + final BufferedImage image = ImageIO.read(resourceAsStream); + if (image == null) { + throw new IllegalStateException("corrupt resource: " + path); + } + return ImageUtils.getTexture(image); } } diff --git a/core/src/com/etheller/warsmash/util/MappedData.java b/core/src/com/etheller/warsmash/util/MappedData.java index 6c17021..ed92769 100644 --- a/core/src/com/etheller/warsmash/util/MappedData.java +++ b/core/src/com/etheller/warsmash/util/MappedData.java @@ -49,7 +49,8 @@ public class MappedData { final MappedDataRow mapped = this.map.get(name); for (int j = 0, k = header.size(); j < k; j++) { - String key = (String) header.get(j); + final Object headerObj = header.get(j); + String key = headerObj == null ? null : headerObj.toString(); // UnitBalance.slk doesn't define the name of one row. if (key == null) { diff --git a/core/src/com/etheller/warsmash/util/ParseUtils.java b/core/src/com/etheller/warsmash/util/ParseUtils.java index 41e43cb..89737fd 100644 --- a/core/src/com/etheller/warsmash/util/ParseUtils.java +++ b/core/src/com/etheller/warsmash/util/ParseUtils.java @@ -175,4 +175,15 @@ public class ParseUtils { stream.write(nameBytes); stream.write(0); } + + public static float[] flipRGB(final float[] color) { + final float r = color[0]; + color[0] = color[2]; + color[2] = r; + return color; + } + + public static float[] newFlippedRGB(final float[] color) { + return new float[] { color[2], color[1], color[0] }; + } } diff --git a/core/src/com/etheller/warsmash/util/RenderMathUtils.java b/core/src/com/etheller/warsmash/util/RenderMathUtils.java index 5030190..5238433 100644 --- a/core/src/com/etheller/warsmash/util/RenderMathUtils.java +++ b/core/src/com/etheller/warsmash/util/RenderMathUtils.java @@ -27,6 +27,7 @@ public enum RenderMathUtils { public static final float[] FLOAT_VEC3_ZERO = new float[] { 0, 0, 0 }; public static final float[] FLOAT_QUAT_DEFAULT = new float[] { 0, 0, 0, 1 }; public static final float[] FLOAT_VEC3_ONE = new float[] { 1, 1, 1 }; + public static final float HALF_PI = (float) (Math.PI / 2.0); // copied from ghostwolf and // https://www.blend4web.com/api_doc/libs_gl-matrix2.js.html diff --git a/core/src/com/etheller/warsmash/viewer5/ModelViewer.java b/core/src/com/etheller/warsmash/viewer5/ModelViewer.java index 00ada9c..1800d2b 100644 --- a/core/src/com/etheller/warsmash/viewer5/ModelViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/ModelViewer.java @@ -104,7 +104,7 @@ public abstract class ModelViewer { } public Scene addSimpleScene() { - final Scene scene = new SimpleScene(this); + final Scene scene = new SimpleScene(this, createLightManager(true)); this.scenes.add(scene); @@ -112,7 +112,7 @@ public abstract class ModelViewer { } public WorldScene addWorldScene() { - final WorldScene scene = new WorldScene(this); + final WorldScene scene = new WorldScene(this, createLightManager(false)); this.scenes.add(scene); @@ -367,5 +367,5 @@ public abstract class ModelViewer { System.err.println("error, this, InvalidHandler, FailedToLoad"); } - public abstract SceneLightManager createLightManager(); + public abstract SceneLightManager createLightManager(boolean simple); } diff --git a/core/src/com/etheller/warsmash/viewer5/Scene.java b/core/src/com/etheller/warsmash/viewer5/Scene.java index f4c3a4a..0896e5e 100644 --- a/core/src/com/etheller/warsmash/viewer5/Scene.java +++ b/core/src/com/etheller/warsmash/viewer5/Scene.java @@ -60,7 +60,7 @@ public abstract class Scene { public boolean alpha = false; private final SceneLightManager lightManager; - public Scene(final ModelViewer viewer) { + public Scene(final ModelViewer viewer, final SceneLightManager lightManager) { final CanvasProvider canvas = viewer.canvas; this.viewer = viewer; this.camera = new Camera(); @@ -87,7 +87,7 @@ public abstract class Scene { this.visibleCells = 0; this.visibleInstances = 0; - this.lightManager = this.viewer.createLightManager(); + this.lightManager = lightManager; } public boolean enableAudio() { @@ -218,6 +218,8 @@ public abstract class Scene { this.emitterObjectUpdater.update(dt); this.updatedParticles = this.emitterObjectUpdater.objects.size(); + + this.lightManager.update(); } protected abstract void innerUpdate(float dt, int frame); diff --git a/core/src/com/etheller/warsmash/viewer5/SceneLightManager.java b/core/src/com/etheller/warsmash/viewer5/SceneLightManager.java index 407f083..efd69f4 100644 --- a/core/src/com/etheller/warsmash/viewer5/SceneLightManager.java +++ b/core/src/com/etheller/warsmash/viewer5/SceneLightManager.java @@ -4,4 +4,6 @@ public interface SceneLightManager { public void add(final SceneLightInstance lightInstance); public void remove(final SceneLightInstance lightInstance); + + public void update(); } diff --git a/core/src/com/etheller/warsmash/viewer5/Shaders.java b/core/src/com/etheller/warsmash/viewer5/Shaders.java index 16d4c77..9981c2b 100644 --- a/core/src/com/etheller/warsmash/viewer5/Shaders.java +++ b/core/src/com/etheller/warsmash/viewer5/Shaders.java @@ -55,4 +55,63 @@ public class Shaders { " return vec3(quat_transform(q, v.xy), v.z);\r\n" + // " }\r\n" + // " "; + + public static String lightSystem(final String normalName, final String positionName, final String lightTexture, + final String lightCount, final boolean terrain) { + return " vec3 lightFactor = vec3(0.0,0.0,0.0);\r\n" + // + " float lightIndex = 0.0;\r\n" + // + " for(float lightIndex = 0.0; lightIndex < " + lightCount + "; lightIndex += 1.0) {\r\n" + // + " float rowPos = (lightIndex + 0.5) / " + lightCount + ";\r\n" + // + " vec4 lightPosition = texture2D(" + lightTexture + ", vec2(0.125, rowPos));\r\n" + // + " vec3 lightExtra = texture2D(" + lightTexture + ", vec2(0.375, rowPos)).xyz;\r\n" + // + " vec4 lightColor = texture2D(" + lightTexture + ", vec2(0.625, rowPos));\r\n" + // + " vec4 lightAmbColor = texture2D(" + lightTexture + ", vec2(0.875, rowPos));\r\n" + // + " if(lightExtra.x > 1.5) {\r\n" + // + " // Ambient light;\r\n" + // + " float dist = length(" + positionName + " - vec3(lightPosition." + (terrain ? "xyw" : "xyz") + + "));\r\n" + // + " float attenuationStart = lightExtra.y;\r\n" + // + " float attenuationEnd = lightExtra.z;\r\n" + // + " if( dist <= attenuationEnd ) {\r\n" + // + " float attenuationDist = clamp((dist-attenuationStart), 0.001, (attenuationEnd-attenuationStart));\r\n" + + // + " float attenuationFactor = 1.0/(attenuationDist);\r\n" + // + " lightFactor += attenuationFactor * lightAmbColor.a * lightAmbColor.rgb;\r\n" + // + " \r\n" + // + " }\r\n" + // + " } else if(lightExtra.x > 0.5) {\r\n" + // + " // Directional (sun) light;\r\n" + // + " vec3 lightDirection = vec3(lightPosition.xyz);\r\n" + // + " vec3 lightFactorContribution = lightColor.a * lightColor.rgb * clamp(dot(" + normalName + + ", lightDirection), 0.0, 1.0);\r\n" + // + " if(lightFactorContribution.r > 1.0 || lightFactorContribution.g > 1.0 || lightFactorContribution.b > 1.0) {\r\n" + + // + " lightFactorContribution = normalize(lightFactorContribution);\r\n" + // + " }\r\n" + // + " lightFactor += lightFactorContribution + lightAmbColor.a * lightAmbColor.rgb;\r\n" + // + " } else {\r\n" + // + " // Omnidirectional light;\r\n" + // + " vec3 deltaBtwn = " + positionName + " - lightPosition.xyz;\r\n" + // + " float dist = length(" + positionName + " - vec3(lightPosition." + (terrain ? "xyw" : "xyz") + + "));\r\n" + // + " float attenuationStart = lightExtra.y;\r\n" + // + " float attenuationEnd = lightExtra.z;\r\n" + // + " if( dist <= attenuationEnd ) {\r\n" + // + " float attenuationDist = clamp((dist-attenuationStart), 0.001, (attenuationEnd-attenuationStart));\r\n" + + // + " float attenuationFactor = 1.0/(attenuationDist);\r\n" + // + " vec3 lightDirection = -deltaBtwn;\r\n" + // + " vec3 lightFactorContribution = attenuationFactor * lightColor.a * lightColor.rgb * clamp(dot(" + + normalName + ", lightDirection), 0.0, 1.0);\r\n" + // + " if(lightFactorContribution.r > 1.0 || lightFactorContribution.g > 1.0 || lightFactorContribution.b > 1.0) {\r\n" + + // + " lightFactorContribution = normalize(lightFactorContribution);\r\n" + // + " }\r\n" + // + " lightFactor += lightFactorContribution + attenuationFactor * lightAmbColor.a * lightAmbColor.rgb;\r\n" + + // + " \r\n" + // + " }\r\n" + // + " }\r\n" + // + " }\r\n"; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/SimpleScene.java b/core/src/com/etheller/warsmash/viewer5/SimpleScene.java index 9436e27..6f7d7c1 100644 --- a/core/src/com/etheller/warsmash/viewer5/SimpleScene.java +++ b/core/src/com/etheller/warsmash/viewer5/SimpleScene.java @@ -6,8 +6,8 @@ import java.util.List; public class SimpleScene extends Scene { private final List allInstances = new ArrayList<>(); - public SimpleScene(final ModelViewer viewer) { - super(viewer); + public SimpleScene(final ModelViewer viewer, final SceneLightManager lightManager) { + super(viewer, lightManager); this.visibleCells = 1; this.visibleInstances = 0; } diff --git a/core/src/com/etheller/warsmash/viewer5/SkeletalNode.java b/core/src/com/etheller/warsmash/viewer5/SkeletalNode.java index 6d971de..bf6a231 100644 --- a/core/src/com/etheller/warsmash/viewer5/SkeletalNode.java +++ b/core/src/com/etheller/warsmash/viewer5/SkeletalNode.java @@ -8,7 +8,8 @@ import com.badlogic.gdx.math.Vector3; import com.etheller.warsmash.util.RenderMathUtils; public abstract class SkeletalNode extends GenericNode { - protected static final Vector3 locationHeap = new Vector3(); + protected static final Vector3 cameraRayHeap = new Vector3(); + protected static final Vector3 billboardAxisHeap = new Vector3(); protected static final Quaternion rotationHeap = new Quaternion(); protected static final Quaternion rotationHeap2 = new Quaternion(); protected static final Vector3 scalingHeap = new Vector3(); @@ -16,9 +17,9 @@ public abstract class SkeletalNode extends GenericNode { public UpdatableObject object; public boolean billboarded; - public final boolean billboardedX; - public final boolean billboardedY; - public final boolean billboardedZ; + public boolean billboardedX; + public boolean billboardedY; + public boolean billboardedZ; public SkeletalNode() { this.pivot = new Vector3(); @@ -98,6 +99,36 @@ public abstract class SkeletalNode extends GenericNode { this.convertBasis(computedRotation); } + else if (this.billboardedX) { + final Camera camera = scene.camera; + computedRotation = rotationHeap; + cameraRayHeap.set(camera.billboardedVectors[6]); + computedRotation.set(this.parent.inverseWorldRotation); + computedRotation.transform(cameraRayHeap); + billboardAxisHeap.set(1, 0, 0); + final float angle = (float) Math.atan2(cameraRayHeap.z, cameraRayHeap.y); + computedRotation.setFromAxisRad(billboardAxisHeap, angle); + } + else if (this.billboardedY) { + final Camera camera = scene.camera; + computedRotation = rotationHeap; + cameraRayHeap.set(camera.billboardedVectors[6]); + computedRotation.set(this.parent.inverseWorldRotation); + computedRotation.transform(cameraRayHeap); + billboardAxisHeap.set(0, 1, 0); + final float angle = (float) Math.atan2(cameraRayHeap.z, -cameraRayHeap.x); + computedRotation.setFromAxisRad(billboardAxisHeap, angle); + } + else if (this.billboardedZ) { + final Camera camera = scene.camera; + computedRotation = rotationHeap; + cameraRayHeap.set(camera.billboardedVectors[6]); + computedRotation.set(this.parent.inverseWorldRotation); + computedRotation.transform(cameraRayHeap); + billboardAxisHeap.set(0, 0, 1); + final float angle = (float) Math.atan2(cameraRayHeap.y, cameraRayHeap.x); + computedRotation.setFromAxisRad(billboardAxisHeap, angle); + } else { computedRotation = this.localRotation; } diff --git a/core/src/com/etheller/warsmash/viewer5/WorldScene.java b/core/src/com/etheller/warsmash/viewer5/WorldScene.java index 1e31686..e00d9b0 100644 --- a/core/src/com/etheller/warsmash/viewer5/WorldScene.java +++ b/core/src/com/etheller/warsmash/viewer5/WorldScene.java @@ -22,8 +22,8 @@ public class WorldScene extends Scene { public Grid grid; - public WorldScene(final ModelViewer viewer) { - super(viewer); + public WorldScene(final ModelViewer viewer, final SceneLightManager lightManager) { + super(viewer, lightManager); this.grid = new Grid(-100000, -100000, 200000, 200000, 200000, 200000); } diff --git a/core/src/com/etheller/warsmash/viewer5/gl/DataTexture.java b/core/src/com/etheller/warsmash/viewer5/gl/DataTexture.java index 744bb41..f19e50c 100644 --- a/core/src/com/etheller/warsmash/viewer5/gl/DataTexture.java +++ b/core/src/com/etheller/warsmash/viewer5/gl/DataTexture.java @@ -28,7 +28,7 @@ public class DataTexture { this.reserve(width, height); } - private void reserve(final int width, final int height) { + public void reserve(final int width, final int height) { if ((this.width < width) || (this.height < height)) { final GL20 gl = this.gl; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/BatchGroup.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/BatchGroup.java index 17aaea3..df93d5e 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/BatchGroup.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/BatchGroup.java @@ -63,9 +63,9 @@ public class BatchGroup extends GenericGroup { final DataTexture boneTexture = instance.boneTexture; final DataTexture unitLightsTexture = lightManager.getUnitLightsTexture(); - unitLightsTexture.bind(16); - shader.setUniformi("u_lightTexture", 16); - shader.setUniformf("u_lightCount", unitLightsTexture.getHeight()); + unitLightsTexture.bind(14); + shader.setUniformi("u_lightTexture", 14); + shader.setUniformf("u_lightCount", unitLightsTexture.getHeight() - 0.5f); // Instances of models with no bones don't have a bone texture. if (boneTexture != null) { @@ -103,6 +103,7 @@ public class BatchGroup extends GenericGroup { shader.setUniform4fv("u_geosetColor", geosetColor, 0, geosetColor.length); shader.setUniformf("u_layerAlpha", layerAlpha); + shader.setUniformf("u_unshaded", layer.unshaded); shader.setUniform2fv("u_uvTrans", uvAnim, 0, 2); shader.setUniform2fv("u_uvRot", uvAnim, 2, 2); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Geoset.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Geoset.java index a52c46b..3c0d730 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Geoset.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Geoset.java @@ -106,7 +106,7 @@ public class Geoset { public void bind(final ShaderProgram shader, final int coordId) { // TODO use indices instead of strings for attributes shader.setVertexAttribute("a_position", 3, GL20.GL_FLOAT, false, 0, this.positionOffset); -// shader.setVertexAttribute("a_normal", 3, GL20.GL_FLOAT, false, 0, this.normalOffset); + shader.setVertexAttribute("a_normal", 3, GL20.GL_FLOAT, false, 0, this.normalOffset); shader.setVertexAttribute("a_uv", 2, GL20.GL_FLOAT, false, 0, this.uvOffset + (coordId * this.vertices * 8)); shader.setVertexAttribute("a_bones", 4, this.openGLSkinType, false, this.skinStride, this.skinOffset); shader.setVertexAttribute("a_boneNumber", 1, this.openGLSkinType, false, this.skinStride, diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Light.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Light.java index d6bd2cd..cc1d564 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Light.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Light.java @@ -18,13 +18,15 @@ public class Light extends GenericObject { case 0: this.type = Type.OMNIDIRECTIONAL; break; - case 2: + case 1: this.type = Type.DIRECTIONAL; break; - default: - case 1: + case 2: this.type = Type.AMBIENT; break; + default: + this.type = Type.DIRECTIONAL; + break; } this.attenuation = light.getAttenuation(); this.color = light.getColor(); @@ -61,6 +63,11 @@ public class Light extends GenericObject { return this.getVectorValue(out, AnimationMap.KLBC.getWar3id(), sequence, frame, counter, this.ambientColor); } + @Override + public int getVisibility(final float[] out, final int sequence, final int frame, final int counter) { + return this.getScalarValue(out, AnimationMap.KLAV.getWar3id(), sequence, frame, counter, 1); + } + public static enum Type { // Omnidirectional light used for in-game sun OMNIDIRECTIONAL, diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/LightInstance.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/LightInstance.java index ca95984..aade9ac 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/LightInstance.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/LightInstance.java @@ -2,11 +2,15 @@ package com.etheller.warsmash.viewer5.handlers.mdx; import java.nio.FloatBuffer; +import com.badlogic.gdx.math.Matrix4; +import com.badlogic.gdx.math.Vector3; import com.etheller.warsmash.viewer5.Scene; import com.etheller.warsmash.viewer5.SceneLightInstance; import com.etheller.warsmash.viewer5.UpdatableObject; public class LightInstance implements UpdatableObject, SceneLightInstance { + private static final Matrix4 matrix4Heap = new Matrix4(); + private static final Vector3 vector3Heap = new Vector3(); private static final float[] vectorHeap = new float[3]; private static final float[] scalarHeap = new float[1]; protected final MdxNode node; @@ -41,15 +45,28 @@ public class LightInstance implements UpdatableObject, SceneLightInstance { final float ambientColorRed = vectorHeap[0]; final float ambientColorGreen = vectorHeap[1]; final float ambientColorBlue = vectorHeap[2]; - floatBuffer.put(offset, this.node.worldLocation.x); - floatBuffer.put(offset + 1, this.node.worldLocation.y); - floatBuffer.put(offset + 2, this.node.worldLocation.z); + switch (this.light.getType()) { + case AMBIENT: + case OMNIDIRECTIONAL: + floatBuffer.put(offset, this.node.worldLocation.x); + floatBuffer.put(offset + 1, this.node.worldLocation.y); + floatBuffer.put(offset + 2, this.node.worldLocation.z); + break; + case DIRECTIONAL: + vector3Heap.set(0, 0, 1); + this.node.localRotation.transform(vector3Heap); + vector3Heap.nor(); + floatBuffer.put(offset, vector3Heap.x); + floatBuffer.put(offset + 1, vector3Heap.y); + floatBuffer.put(offset + 2, vector3Heap.z); + break; + } // I use some padding to make the memory structure of the light be a 4x4 float // grid, when somebody who actually has experience with this stuff comes along // to change this to something smart, maybe they'll remove the padding if it's // not necessary. I'm basing how I implement this on how Ghostwolf did // BoneTexture - floatBuffer.put(offset + 3, 0); + floatBuffer.put(offset + 3, this.instance.worldLocation.z); floatBuffer.put(offset + 4, this.light.getType().ordinal()); floatBuffer.put(offset + 5, attenuationStart); floatBuffer.put(offset + 6, attenuationEnd); @@ -66,16 +83,20 @@ public class LightInstance implements UpdatableObject, SceneLightInstance { @Override public void update(final float dt, final boolean visible) { - this.visible = visible; } public void update(final Scene scene) { - if (this.loadedInScene != this.visible) { - if (this.visible) { - scene.addLight(this); - } - else { - scene.removeLight(this); + this.light.getVisibility(scalarHeap, this.instance.sequence, this.instance.frame, this.instance.counter); + this.visible = scalarHeap[0] > 0; + if (scene != null) { + if (this.loadedInScene != this.visible) { + if (this.visible) { + scene.addLight(this); + } + else { + scene.removeLight(this); + } + this.loadedInScene = this.visible; } } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java index b7eb675..b480bfc 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java @@ -242,13 +242,16 @@ public class MdxComplexInstance extends ModelInstance { /// TODO: single-axis billboarding if (genericObject.billboarded != 0) { node.billboarded = true; - } // else if (genericObject.billboardedX) { - // node.billboardedX = true; - // } else if (genericObject.billboardedY) { - // node.billboardedY = true; - // } else if (genericObject.billboardedZ) { - // node.billboardedZ = true; - // } + } + else if (genericObject.billboardedX != 0) { + node.billboardedX = true; + } + else if (genericObject.billboardedY != 0) { + node.billboardedY = true; + } + else if (genericObject.billboardedZ != 0) { + node.billboardedZ = true; + } if (object != null) { node.object = object; @@ -724,10 +727,16 @@ public class MdxComplexInstance extends ModelInstance { * * @param ray */ - public boolean intersectRayWithCollision(final Ray ray, final Vector3 intersection) { + public boolean intersectRayWithCollision(final Ray ray, final Vector3 intersection, final boolean alwaysUseMesh) { final MdxModel mdxModel = (MdxModel) this.model; final List collisionShapes = mdxModel.collisionShapes; - if (collisionShapes.isEmpty()) { + for (final CollisionShape collisionShape : collisionShapes) { + final MdxNode mdxNode = this.nodes[collisionShape.index]; + if (collisionShape.checkIntersect(ray, mdxNode, intersection)) { + return true; + } + } + if (collisionShapes.isEmpty() || alwaysUseMesh) { for (final Geoset geoset : mdxModel.geosets) { if (!geoset.unselectable) { geoset.getAlpha(alphaHeap, this.sequence, this.frame, this.counter); @@ -741,14 +750,6 @@ public class MdxComplexInstance extends ModelInstance { } } } - else { - for (final CollisionShape collisionShape : collisionShapes) { - final MdxNode mdxNode = this.nodes[collisionShape.index]; - if (collisionShape.checkIntersect(ray, mdxNode, intersection)) { - return true; - } - } - } return false; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxHandler.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxHandler.java index ba50313..921a24b 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxHandler.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxHandler.java @@ -24,13 +24,13 @@ public class MdxHandler extends ModelHandler { viewer.addHandler(new BlpHandler()); viewer.addHandler(new TgaHandler()); - Shaders.complex = viewer.webGL.createShaderProgram(MdxShaders.vsComplexUnshaded, MdxShaders.fsComplex); - Shaders.extended = viewer.webGL.createShaderProgram("#define EXTENDED_BONES\r\n" + MdxShaders.vsComplexUnshaded, + Shaders.complex = viewer.webGL.createShaderProgram(MdxShaders.vsComplex, MdxShaders.fsComplex); + Shaders.extended = viewer.webGL.createShaderProgram("#define EXTENDED_BONES\r\n" + MdxShaders.vsComplex, MdxShaders.fsComplex); - Shaders.complexShadowMap = viewer.webGL.createShaderProgram(MdxShaders.vsComplexUnshaded, + Shaders.complexShadowMap = viewer.webGL.createShaderProgram(MdxShaders.vsComplex, MdxShaders.fsComplexShadowMap); Shaders.extendedShadowMap = viewer.webGL.createShaderProgram( - "#define EXTENDED_BONES\r\n" + MdxShaders.vsComplexUnshaded, MdxShaders.fsComplexShadowMap); + "#define EXTENDED_BONES\r\n" + MdxShaders.vsComplex, MdxShaders.fsComplexShadowMap); Shaders.particles = viewer.webGL.createShaderProgram(MdxShaders.vsParticles, MdxShaders.fsParticles); Shaders.simple = viewer.webGL.createShaderProgram(MdxShaders.vsSimple, MdxShaders.fsSimple); // Shaders.hd = viewer.webGL.createShaderProgram(MdxShaders.vsHd, MdxShaders.fsHd); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxShaders.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxShaders.java index 1124992..4cea40e 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxShaders.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxShaders.java @@ -91,6 +91,7 @@ public class MdxShaders { " uniform vec2 u_uvRot;\r\n" + // " uniform float u_uvScale;\r\n" + // " uniform bool u_hasBones;\r\n" + // + " uniform bool u_unshaded;\r\n" + // " attribute vec3 a_position;\r\n" + // " attribute vec3 a_normal;\r\n" + // " attribute vec2 a_uv;\r\n" + // @@ -103,6 +104,8 @@ public class MdxShaders { " varying vec4 v_color;\r\n" + // " varying vec4 v_uvTransRot;\r\n" + // " varying float v_uvScale;\r\n" + // + " uniform sampler2D u_lightTexture;\r\n" + // + " uniform float u_lightCount;\r\n" + // Shaders.boneTexture + "\r\n" + // " void transform(inout vec3 position, inout vec3 normal) {\r\n" + // " // For the broken models out there, since the game supports this.\r\n" + // @@ -110,8 +113,8 @@ public class MdxShaders { " vec4 position4 = vec4(position, 1.0);\r\n" + // " vec4 normal4 = vec4(normal, 0.0);\r\n" + // " mat4 bone;\r\n" + // - " vec4 p;\r\n" + // - " vec4 n;\r\n" + // + " vec4 p = vec4(0.0,0.0,0.0,0.0);\r\n" + // + " vec4 n = vec4(0.0,0.0,0.0,0.0);\r\n" + // " for (int i = 0; i < 4; i++) {\r\n" + // " if (a_bones[i] > 0.0) {\r\n" + // " bone = fetchMatrix(a_bones[i] - 1.0, 0.0);\r\n" + // @@ -130,6 +133,8 @@ public class MdxShaders { " #endif\r\n" + // " position = p.xyz / a_boneNumber;\r\n" + // " normal = normalize(n.xyz);\r\n" + // + " } else {\r\n" + // + " position.x += 100.0;\r\n" + // " }\r\n" + // "\r\n" + // " }\r\n" + // @@ -144,65 +149,10 @@ public class MdxShaders { " v_uvTransRot = vec4(u_uvTrans, u_uvRot);\r\n" + // " v_uvScale = u_uvScale;\r\n" + // " gl_Position = u_mvp * vec4(position, 1.0);\r\n" + // - " }"; - - public static final String vsComplexUnshaded = " uniform mat4 u_mvp;\r\n" + // - " uniform vec4 u_vertexColor;\r\n" + // - " uniform vec4 u_geosetColor;\r\n" + // - " uniform float u_layerAlpha;\r\n" + // - " uniform vec2 u_uvTrans;\r\n" + // - " uniform vec2 u_uvRot;\r\n" + // - " uniform float u_uvScale;\r\n" + // - " uniform bool u_hasBones;\r\n" + // - " attribute vec3 a_position;\r\n" + // - " attribute vec2 a_uv;\r\n" + // - " attribute vec4 a_bones;\r\n" + // - " #ifdef EXTENDED_BONES\r\n" + // - " attribute vec4 a_extendedBones;\r\n" + // - " #endif\r\n" + // - " attribute float a_boneNumber;\r\n" + // - " varying vec2 v_uv;\r\n" + // - " varying vec4 v_color;\r\n" + // - " varying vec4 v_uvTransRot;\r\n" + // - " varying float v_uvScale;\r\n" + // - Shaders.boneTexture + "\r\n" + // - " void transform(inout vec3 position) {\r\n" + // - " // For the broken models out there, since the game supports this.\r\n" + // - " if (a_boneNumber > 0.0) {\r\n" + // - " vec4 position4 = vec4(position, 1.0);\r\n" + // - " mat4 bone;\r\n" + // - " vec4 p = vec4(0.0,0.0,0.0,0.0);\r\n" + // - " for (int i = 0; i < 4; i++) {\r\n" + // - " if (a_bones[i] > 0.0) {\r\n" + // - " bone = fetchMatrix(a_bones[i] - 1.0, 0.0);\r\n" + // - " p += bone * position4;\r\n" + // - " }\r\n" + // - " }\r\n" + // - " #ifdef EXTENDED_BONES\r\n" + // - " for (int i = 0; i < 4; i++) {\r\n" + // - " if (a_extendedBones[i] > 0.0) {\r\n" + // - " bone = fetchMatrix(a_extendedBones[i] - 1.0, 0.0);\r\n" + // - " p += bone * position4;\r\n" + // - " }\r\n" + // - " }\r\n" + // - " #endif\r\n" + // - " position = p.xyz / a_boneNumber;\r\n" + // -// " position.x *= fetchMatrix(0.0, 0.0)[0][0];\r\n" + // - " } else {\r\n" + // - " position.x += 100.0;\r\n" + // + " if(!u_unshaded) {\r\n" + // + Shaders.lightSystem("normal", "position", "u_lightTexture", "u_lightCount", false) + "\r\n" + // + " v_color.xyz *= clamp(lightFactor, 0.0, 1.0);\r\n" + // " }\r\n" + // - "\r\n" + // - " }\r\n" + // - " void main() {\r\n" + // - " vec3 position = a_position;\r\n" + // - " if (u_hasBones) {\r\n" + // - " transform(position);\r\n" + // - " }\r\n" + // - " v_uv = a_uv;\r\n" + // - " v_color = u_vertexColor * u_geosetColor.bgra * vec4(1.0, 1.0, 1.0, u_layerAlpha);\r\n" + // - " v_uvTransRot = vec4(u_uvTrans, u_uvRot);\r\n" + // - " v_uvScale = u_uvScale;\r\n" + // - " gl_Position = u_mvp * vec4(position, 1.0);\r\n" + // " }"; public static final String fsComplex = Shaders.quatTransform + "\r\n\r\n" + // @@ -231,9 +181,6 @@ public class MdxShaders { " if (u_filterMode >= 5.0 && color.a < 0.02) {\r\n" + // " discard;\r\n" + // " }\r\n" + // - " // if (!u_unshaded) {\r\n" + // - " // color *= clamp(dot(v_normal, lightDirection) + 0.45, 0.0, 1.0);\r\n" + // - " // }\r\n" + // " gl_FragColor = color;\r\n" + // " }"; @@ -245,6 +192,7 @@ public class MdxShaders { " varying vec4 v_color;\r\n" + // " varying vec4 v_uvTransRot;\r\n" + // " varying float v_uvScale;\r\n" + // + " varying vec3 v_normal;\r\n" + // // " layout(location = 0) out float fragmentdepth;\r\n" + // " void main() {\r\n" + // " vec2 uv = v_uv;\r\n" + // diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxViewer.java index 990205b..f1942fb 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxViewer.java @@ -12,7 +12,7 @@ public class MdxViewer extends ModelViewer { } @Override - public SceneLightManager createLightManager() { + public SceneLightManager createLightManager(final boolean simple) { // TODO Auto-generated method stub return null; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SdSequence.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SdSequence.java index beb0523..956e693 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SdSequence.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SdSequence.java @@ -3,7 +3,9 @@ package com.etheller.warsmash.viewer5.handlers.mdx; import java.util.ArrayList; import java.util.Arrays; +import com.etheller.warsmash.parsers.mdlx.AnimationMap; import com.etheller.warsmash.parsers.mdlx.timeline.Timeline; +import com.etheller.warsmash.util.ParseUtils; import com.etheller.warsmash.util.RenderMathUtils; public final class SdSequence { @@ -30,9 +32,9 @@ public final class SdSequence { final int interpolationType = sd.interpolationType; final long[] frames = timeline.getFrames(); - final TYPE[] values = timeline.getValues(); - final TYPE[] inTans = timeline.getInTans(); - final TYPE[] outTans = timeline.getOutTans(); + final TYPE[] values = getValues(timeline); + final TYPE[] inTans = getInTans(timeline); + final TYPE[] outTans = getOutTans(timeline); final TYPE defval = sd.defval; // When using a global sequence, where the first key is outside of the @@ -47,7 +49,12 @@ public final class SdSequence { // This fixes problems spread over many models, e.g. HeroMountainKing // (compare in WE and in Magos). if (isGlobalSequence && (frames.length > 0) && (frames[0] > end)) { - framesBuilder.add(frames[0]); + if (start == end) { + framesBuilder.add(start); + } + else { + framesBuilder.add(frames[0]); + } valuesBuilder.add(values[0]); } @@ -127,6 +134,33 @@ public final class SdSequence { this.outTans = outTansBuilder.toArray(arrayDescriptor.create(outTansBuilder.size())); } + private TYPE[] getValues(final Timeline timeline) { + final TYPE[] values = timeline.getValues(); + return fixTimelineArray(timeline, values); + } + + private TYPE[] getOutTans(final Timeline timeline) { + final TYPE[] outTans = timeline.getOutTans(); + return fixTimelineArray(timeline, outTans); + } + + private TYPE[] getInTans(final Timeline timeline) { + final TYPE[] inTans = timeline.getInTans(); + return fixTimelineArray(timeline, inTans); + } + + private TYPE[] fixTimelineArray(final Timeline timeline, final TYPE[] values) { + if (timeline.getName().equals(AnimationMap.KLAC.getWar3id()) + || timeline.getName().equals(AnimationMap.KLBC.getWar3id())) { + final float[][] flippedColorData = new float[values.length][3]; + for (int i = 0; i < values.length; i++) { + flippedColorData[i] = ParseUtils.newFlippedRGB((float[]) values[i]); + } + return (TYPE[]) flippedColorData; + } + return values; + } + // private TYPE[] makeArray(final int size) { // return (TYPE[]) new Object[size]; // } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Destructable.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Destructable.java new file mode 100644 index 0000000..212e452 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Destructable.java @@ -0,0 +1,26 @@ +package com.etheller.warsmash.viewer5.handlers.w3x; + +import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; +import com.etheller.warsmash.units.manager.MutableObjectData.WorldEditorDataType; +import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; +import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; + +public class Destructable extends Doodad { + + private final float life; + + public Destructable(final War3MapViewer map, final MdxModel model, final MutableGameObject row, + final com.etheller.warsmash.parsers.w3x.doo.Doodad doodad, final WorldEditorDataType type, + final float maxPitch, final float maxRoll, final float life) { + super(map, model, row, doodad, type, maxPitch, maxRoll); + this.life = life; + } + + @Override + public PrimaryTag getAnimation() { + if (this.life <= 0) { + return PrimaryTag.DEATH; + } + return super.getAnimation(); + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Doodad.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Doodad.java index f969f0c..5e815d1 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Doodad.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Doodad.java @@ -6,7 +6,10 @@ import com.etheller.warsmash.units.manager.MutableObjectData.WorldEditorDataType import com.etheller.warsmash.util.RenderMathUtils; import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.ModelInstance; +import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; +import com.etheller.warsmash.viewer5.handlers.mdx.SequenceLoopMode; +import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; public class Doodad { private static final int SAMPLE_RADIUS = 4; @@ -30,6 +33,7 @@ public class Doodad { } else { instance = model.addInstance(); + ((MdxComplexInstance) instance).setSequenceLoopMode(SequenceLoopMode.NEVER_LOOP); } instance.move(doodad.getLocation()); @@ -50,8 +54,8 @@ public class Doodad { final float pitchSampleBackwardY = y - (SAMPLE_RADIUS * (float) Math.sin(facingRadians)); final float pitchSampleGroundHeight1 = map.terrain.getGroundHeight(pitchSampleBackwardX, pitchSampleBackwardY); final float pitchSampleGorundHeight2 = map.terrain.getGroundHeight(pitchSampleForwardX, pitchSampleForwardY); - pitch = Math.min(maxPitch, - (float) Math.atan2(pitchSampleGorundHeight2 - pitchSampleGroundHeight1, SAMPLE_RADIUS * 2)); + pitch = Math.max(-maxPitch, Math.min(maxPitch, + (float) Math.atan2(pitchSampleGorundHeight2 - pitchSampleGroundHeight1, SAMPLE_RADIUS * 2))); final double leftOfFacingAngle = facingRadians + (Math.PI / 2); final float rollSampleForwardX = x + (SAMPLE_RADIUS * (float) Math.cos(leftOfFacingAngle)); final float rollSampleForwardY = y + (SAMPLE_RADIUS * (float) Math.sin(leftOfFacingAngle)); @@ -59,8 +63,8 @@ public class Doodad { final float rollSampleBackwardY = y - (SAMPLE_RADIUS * (float) Math.sin(leftOfFacingAngle)); final float rollSampleGroundHeight1 = map.terrain.getGroundHeight(rollSampleBackwardX, rollSampleBackwardY); final float rollSampleGroundHeight2 = map.terrain.getGroundHeight(rollSampleForwardX, rollSampleForwardY); - roll = Math.min(maxRoll, - (float) Math.atan2(rollSampleGroundHeight2 - rollSampleGroundHeight1, SAMPLE_RADIUS * 2)); + roll = Math.max(-maxRoll, Math.min(maxRoll, + (float) Math.atan2(rollSampleGroundHeight2 - rollSampleGroundHeight1, SAMPLE_RADIUS * 2))); instance.rotate(new Quaternion().setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, facingRadians)); instance.rotate(new Quaternion().setFromAxisRad(RenderMathUtils.VEC3_UNIT_Y, -pitch)); instance.rotate(new Quaternion().setFromAxisRad(RenderMathUtils.VEC3_UNIT_X, roll)); @@ -86,4 +90,8 @@ public class Doodad { this.instance = instance; this.row = row; } + + public PrimaryTag getAnimation() { + return PrimaryTag.STAND; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SplatModel.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SplatModel.java index df693ab..fa89ac6 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SplatModel.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SplatModel.java @@ -26,10 +26,12 @@ public class SplatModel { public final float[] color; private final List locations; private final List splatInstances; + private final boolean unshaded; public SplatModel(final GL30 gl, final Texture texture, final List locations, final float[] centerOffset, - final List> unitMapping) { + final List> unitMapping, final boolean unshaded) { this.texture = texture; + this.unshaded = unshaded; this.batches = new ArrayList<>(); this.color = new float[] { 1, 1, 1, 1 }; @@ -205,6 +207,7 @@ public class SplatModel { gl.glActiveTexture(GL30.GL_TEXTURE1); gl.glBindTexture(GL30.GL_TEXTURE_2D, this.texture.getGlHandle()); + shader.setUniformi("u_show_lighting", this.unshaded ? 0 : 1); shader.setUniform4fv("u_color", this.color, 0, 4); for (final Batch b : this.batches) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xSceneLightManager.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xSceneLightManager.java index e5512cf..282d947 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xSceneLightManager.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xSceneLightManager.java @@ -1,107 +1,13 @@ package com.etheller.warsmash.viewer5.handlers.w3x; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.FloatBuffer; -import java.util.ArrayList; -import java.util.List; - -import com.etheller.warsmash.viewer5.SceneLightInstance; -import com.etheller.warsmash.viewer5.SceneLightManager; import com.etheller.warsmash.viewer5.gl.DataTexture; -import com.etheller.warsmash.viewer5.handlers.mdx.LightInstance; -public class W3xSceneLightManager implements SceneLightManager { - public final List lights; - private FloatBuffer lightDataCopyHeap; - private final DataTexture unitLightsTexture; - private final DataTexture terrainLightsTexture; - private final War3MapViewer viewer; - private int terrainLightCount; - private int unitLightCount; +public interface W3xSceneLightManager { + public DataTexture getUnitLightsTexture(); - public W3xSceneLightManager(final War3MapViewer viewer) { - this.viewer = viewer; - this.lights = new ArrayList<>(); - this.unitLightsTexture = new DataTexture(viewer.gl, 4, 4, 1); - this.terrainLightsTexture = new DataTexture(viewer.gl, 4, 4, 1); - this.lightDataCopyHeap = ByteBuffer.allocateDirect(16 * 1 * 4).order(ByteOrder.nativeOrder()).asFloatBuffer(); - } + public int getUnitLightCount(); - @Override - public void add(final SceneLightInstance lightInstance) { - // TODO redesign to avoid cast - final LightInstance mdxLight = (LightInstance) lightInstance; - this.lights.add(mdxLight); - rebind(); - } + public DataTexture getTerrainLightsTexture(); - @Override - public void remove(final SceneLightInstance lightInstance) { - // TODO redesign to avoid cast - final LightInstance mdxLight = (LightInstance) lightInstance; - this.lights.remove(mdxLight); - rebind(); - } - - private void rebind() { - final int numberOfLights = this.lights.size() + 1; - final int bytesNeeded = numberOfLights * 4 * 16; - if (bytesNeeded > (this.lightDataCopyHeap.capacity() * 4)) { - this.lightDataCopyHeap = ByteBuffer.allocateDirect(bytesNeeded).order(ByteOrder.nativeOrder()) - .asFloatBuffer(); - } - - this.unitLightCount = 0; - this.lightDataCopyHeap.clear(); - int offset = 0; - if (this.viewer.dncUnit != null) { - if (!this.viewer.dncUnit.lights.isEmpty()) { - this.viewer.dncUnit.lights.get(0).bind(0, this.lightDataCopyHeap); - offset += 16; - this.unitLightCount++; - } - } - for (final LightInstance light : this.lights) { - light.bind(offset, this.lightDataCopyHeap); - offset += 16; - this.unitLightCount++; - } - this.lightDataCopyHeap.flip(); - this.unitLightsTexture.bindAndUpdate(this.lightDataCopyHeap, 16, this.unitLightCount); - - this.terrainLightCount = 0; - this.lightDataCopyHeap.clear(); - offset = 0; - if (this.viewer.dncTerrain != null) { - if (!this.viewer.dncTerrain.lights.isEmpty()) { - this.viewer.dncTerrain.lights.get(0).bind(0, this.lightDataCopyHeap); - offset += 16; - this.terrainLightCount++; - } - } - for (final LightInstance light : this.lights) { - light.bind(offset, this.lightDataCopyHeap); - offset += 16; - this.terrainLightCount++; - } - this.lightDataCopyHeap.flip(); - this.terrainLightsTexture.bindAndUpdate(this.lightDataCopyHeap, 16, this.terrainLightCount); - } - - public DataTexture getUnitLightsTexture() { - return this.unitLightsTexture; - } - - public int getUnitLightCount() { - return this.unitLightCount; - } - - public DataTexture getTerrainLightsTexture() { - return this.terrainLightsTexture; - } - - public int getTerrainLightCount() { - return this.terrainLightCount; - } + public int getTerrainLightCount(); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xScenePortraitLightManager.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xScenePortraitLightManager.java new file mode 100644 index 0000000..a643de6 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xScenePortraitLightManager.java @@ -0,0 +1,90 @@ +package com.etheller.warsmash.viewer5.handlers.w3x; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; +import java.util.ArrayList; +import java.util.List; + +import com.etheller.warsmash.viewer5.SceneLightInstance; +import com.etheller.warsmash.viewer5.SceneLightManager; +import com.etheller.warsmash.viewer5.gl.DataTexture; +import com.etheller.warsmash.viewer5.handlers.mdx.LightInstance; + +public class W3xScenePortraitLightManager implements SceneLightManager, W3xSceneLightManager { + public final List lights; + private FloatBuffer lightDataCopyHeap; + private final DataTexture unitLightsTexture; + private final War3MapViewer viewer; + private int unitLightCount; + + public W3xScenePortraitLightManager(final War3MapViewer viewer) { + this.viewer = viewer; + this.lights = new ArrayList<>(); + this.unitLightsTexture = new DataTexture(viewer.gl, 4, 4, 1); + this.lightDataCopyHeap = ByteBuffer.allocateDirect(16 * 1 * 4).order(ByteOrder.nativeOrder()).asFloatBuffer(); + } + + @Override + public void add(final SceneLightInstance lightInstance) { + // TODO redesign to avoid cast + final LightInstance mdxLight = (LightInstance) lightInstance; + this.lights.add(mdxLight); + } + + @Override + public void remove(final SceneLightInstance lightInstance) { + // TODO redesign to avoid cast + final LightInstance mdxLight = (LightInstance) lightInstance; + this.lights.remove(mdxLight); + } + + @Override + public void update() { + final int numberOfLights = this.lights.size() + 1; + final int bytesNeeded = numberOfLights * 4 * 16; + if (bytesNeeded > (this.lightDataCopyHeap.capacity() * 4)) { + this.lightDataCopyHeap = ByteBuffer.allocateDirect(bytesNeeded).order(ByteOrder.nativeOrder()) + .asFloatBuffer(); + this.unitLightsTexture.reserve(4, numberOfLights); + } + + this.unitLightCount = 0; + this.lightDataCopyHeap.clear(); + int offset = 0; + if (this.viewer.dncTarget != null) { + if (!this.viewer.dncTarget.lights.isEmpty()) { + this.viewer.dncTarget.lights.get(0).bind(0, this.lightDataCopyHeap); + offset += 16; + this.unitLightCount++; + } + } + for (final LightInstance light : this.lights) { + light.bind(offset, this.lightDataCopyHeap); + offset += 16; + this.unitLightCount++; + } + this.lightDataCopyHeap.limit(offset); + this.unitLightsTexture.bindAndUpdate(this.lightDataCopyHeap, 4, this.unitLightCount); + } + + @Override + public DataTexture getUnitLightsTexture() { + return this.unitLightsTexture; + } + + @Override + public int getUnitLightCount() { + return this.unitLightCount; + } + + @Override + public DataTexture getTerrainLightsTexture() { + return null; + } + + @Override + public int getTerrainLightCount() { + return 0; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xSceneWorldLightManager.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xSceneWorldLightManager.java new file mode 100644 index 0000000..e8c4ccb --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xSceneWorldLightManager.java @@ -0,0 +1,112 @@ +package com.etheller.warsmash.viewer5.handlers.w3x; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; +import java.util.ArrayList; +import java.util.List; + +import com.etheller.warsmash.viewer5.SceneLightInstance; +import com.etheller.warsmash.viewer5.SceneLightManager; +import com.etheller.warsmash.viewer5.gl.DataTexture; +import com.etheller.warsmash.viewer5.handlers.mdx.LightInstance; + +public class W3xSceneWorldLightManager implements SceneLightManager, W3xSceneLightManager { + public final List lights; + private FloatBuffer lightDataCopyHeap; + private final DataTexture unitLightsTexture; + private final DataTexture terrainLightsTexture; + private final War3MapViewer viewer; + private int terrainLightCount; + private int unitLightCount; + + public W3xSceneWorldLightManager(final War3MapViewer viewer) { + this.viewer = viewer; + this.lights = new ArrayList<>(); + this.unitLightsTexture = new DataTexture(viewer.gl, 4, 4, 1); + this.terrainLightsTexture = new DataTexture(viewer.gl, 4, 4, 1); + this.lightDataCopyHeap = ByteBuffer.allocateDirect(16 * 1 * 4).order(ByteOrder.nativeOrder()).asFloatBuffer(); + } + + @Override + public void add(final SceneLightInstance lightInstance) { + // TODO redesign to avoid cast + final LightInstance mdxLight = (LightInstance) lightInstance; + this.lights.add(mdxLight); + } + + @Override + public void remove(final SceneLightInstance lightInstance) { + // TODO redesign to avoid cast + final LightInstance mdxLight = (LightInstance) lightInstance; + this.lights.remove(mdxLight); + } + + @Override + public void update() { + final int numberOfLights = this.lights.size() + 1; + final int bytesNeeded = numberOfLights * 4 * 16; + if (bytesNeeded > (this.lightDataCopyHeap.capacity() * 4)) { + this.lightDataCopyHeap = ByteBuffer.allocateDirect(bytesNeeded).order(ByteOrder.nativeOrder()) + .asFloatBuffer(); + this.unitLightsTexture.reserve(4, numberOfLights); + this.terrainLightsTexture.reserve(4, numberOfLights); + } + + this.unitLightCount = 0; + this.lightDataCopyHeap.clear(); + int offset = 0; + if (this.viewer.dncUnit != null) { + if (!this.viewer.dncUnit.lights.isEmpty()) { + this.viewer.dncUnit.lights.get(0).bind(0, this.lightDataCopyHeap); + offset += 16; + this.unitLightCount++; + } + } + for (final LightInstance light : this.lights) { + light.bind(offset, this.lightDataCopyHeap); + offset += 16; + this.unitLightCount++; + } + this.lightDataCopyHeap.limit(offset); + this.unitLightsTexture.bindAndUpdate(this.lightDataCopyHeap, 4, this.unitLightCount); + + this.terrainLightCount = 0; + this.lightDataCopyHeap.clear(); + offset = 0; + if (this.viewer.dncTerrain != null) { + if (!this.viewer.dncTerrain.lights.isEmpty()) { + this.viewer.dncTerrain.lights.get(0).bind(0, this.lightDataCopyHeap); + offset += 16; + this.terrainLightCount++; + } + } + for (final LightInstance light : this.lights) { + light.bind(offset, this.lightDataCopyHeap); + offset += 16; + this.terrainLightCount++; + } + this.lightDataCopyHeap.limit(offset); + this.terrainLightsTexture.bindAndUpdate(this.lightDataCopyHeap, 4, this.terrainLightCount); + } + + @Override + public DataTexture getUnitLightsTexture() { + return this.unitLightsTexture; + } + + @Override + public int getUnitLightCount() { + return this.unitLightCount; + } + + @Override + public DataTexture getTerrainLightsTexture() { + return this.terrainLightsTexture; + } + + @Override + public int getTerrainLightCount() { + return this.terrainLightCount; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xShaders.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xShaders.java index a6424f0..6b04d75 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xShaders.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xShaders.java @@ -1,5 +1,7 @@ package com.etheller.warsmash.viewer5.handlers.w3x; +import com.etheller.warsmash.viewer5.Shaders; + public class W3xShaders { public static final class UberSplat { private UberSplat() { @@ -12,12 +14,15 @@ public class W3xShaders { " uniform vec2 u_size;\r\n" + // " uniform vec2 u_shadowPixel;\r\n" + // " uniform vec2 u_centerOffset;\r\n" + // + " uniform sampler2D lightTexture;\r\n" + // + " uniform float lightCount;\r\n" + // " attribute vec3 a_position;\r\n" + // " attribute vec2 a_uv;\r\n" + // " varying vec2 v_uv;\r\n" + // " varying vec2 v_suv;\r\n" + // " varying vec3 v_normal;\r\n" + // " varying float a_positionHeight;\r\n" + // + " varying vec3 shadeColor;\r\n" + // " const float normalDist = 0.25;\r\n" + // " void main() {\r\n" + // " vec2 halfPixel = u_pixel * 0.5;\r\n" + // @@ -34,8 +39,11 @@ public class W3xShaders { " v_normal = normalize(vec3(hL - hR, hD - hU, normalDist * 2.0));\r\n" + // " v_uv = a_uv;\r\n" + // " v_suv = base / u_size;\r\n" + // - " gl_Position = u_mvp * vec4(a_position.xy, height * 128.0 + a_position.z, 1.0);\r\n" + // + " vec3 myposition = vec3(a_position.xy, height * 128.0 + a_position.z);\r\n" + // + " gl_Position = u_mvp * vec4(myposition.xyz, 1.0);\r\n" + // " a_positionHeight = a_position.z;\r\n" + // + Shaders.lightSystem("v_normal", "myposition", "lightTexture", "lightCount", true) + "\r\n" + // + " shadeColor = clamp(lightFactor, 0.0, 1.0);\r\n" + // " }\r\n" + // " "; @@ -43,10 +51,12 @@ public class W3xShaders { " uniform sampler2D u_texture;\r\n" + // " uniform sampler2D u_shadowMap;\r\n" + // " uniform vec4 u_color;\r\n" + // + " uniform bool u_show_lighting;\r\n" + // " varying vec2 v_uv;\r\n" + // " varying vec2 v_suv;\r\n" + // " varying vec3 v_normal;\r\n" + // " varying float a_positionHeight;\r\n" + // + " varying vec3 shadeColor;\r\n" + // // " const vec3 lightDirection = normalize(vec3(-0.3, -0.3, 0.25));\r\n" + // " void main() {\r\n" + // " if (any(bvec4(lessThan(v_uv, vec2(0.0)), greaterThan(v_uv, vec2(1.0))))) {\r\n" + // @@ -59,6 +69,9 @@ public class W3xShaders { " if (a_positionHeight <= 4.0) {;\r\n" + // " color.xyz *= 1.0 - shadow;\r\n" + // " };\r\n" + // + " if (u_show_lighting) {;\r\n" + // + " color.xyz *= shadeColor;\r\n" + // + " };\r\n" + // " gl_FragColor = color;\r\n" + // " }\r\n" + // " "; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index eca9dff..c2b19ef 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -33,6 +33,7 @@ import com.etheller.warsmash.datasources.CompoundDataSource; import com.etheller.warsmash.datasources.DataSource; import com.etheller.warsmash.datasources.MpqDataSource; import com.etheller.warsmash.datasources.SubdirDataSource; +import com.etheller.warsmash.parsers.fdf.GameUI; import com.etheller.warsmash.parsers.w3x.War3Map; import com.etheller.warsmash.parsers.w3x.doo.War3MapDoo; import com.etheller.warsmash.parsers.w3x.objectdata.Warcraft3MapObjectData; @@ -147,7 +148,7 @@ public class War3MapViewer extends ModelViewer { public Terrain terrain; public int renderPathing = 0; - public int renderLighting = 0; + public int renderLighting = 1; public List selModels = new ArrayList<>(); public List selected = new ArrayList<>(); @@ -158,6 +159,7 @@ public class War3MapViewer extends ModelViewer { private MdxComplexInstance confirmationInstance; public MdxComplexInstance dncUnit; public MdxComplexInstance dncTerrain; + public MdxComplexInstance dncTarget; public CSimulation simulation; private float updateTime; @@ -172,6 +174,7 @@ public class War3MapViewer extends ModelViewer { private final List selectionCircleSizes = new ArrayList<>(); private final Map unitToRenderPeer = new HashMap<>(); + private GameUI gameUI; public War3MapViewer(final DataSource dataSource, final CanvasProvider canvas) { super(dataSource, canvas); @@ -260,6 +263,11 @@ public class War3MapViewer extends ModelViewer { try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("UI\\SoundInfo\\MiscData.txt")) { this.miscData.readTXT(miscDataTxtStream, true); } + if (this.dataSource.has("war3mapMisc.txt")) { + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("war3mapMisc.txt")) { + this.miscData.readTXT(miscDataTxtStream, true); + } + } this.unitGlobalStrings = new DataTable(worldEditStrings); try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\UnitGlobalStrings.txt")) { this.unitGlobalStrings.readTXT(miscDataTxtStream, true); @@ -339,8 +347,8 @@ public class War3MapViewer extends ModelViewer { throw new RuntimeException(e); } setDataSource(tilesetSource); - final WorldEditStrings worldEditStrings = new WorldEditStrings(this.dataSource); - loadSLKs(worldEditStrings); + this.worldEditStrings = new WorldEditStrings(this.dataSource); + loadSLKs(this.worldEditStrings); this.solverParams.tileset = Character.toLowerCase(tileset); @@ -351,8 +359,8 @@ public class War3MapViewer extends ModelViewer { final StandardObjectData standardObjectData = new StandardObjectData(this.dataSource); this.worldEditData = standardObjectData.getWorldEditData(); - this.terrain = new Terrain(terrainData, terrainPathing, w3iFile, this.webGL, this.dataSource, worldEditStrings, - this, this.worldEditData); + this.terrain = new Terrain(terrainData, terrainPathing, w3iFile, this.webGL, this.dataSource, + this.worldEditStrings, this, this.worldEditData); final float[] centerOffset = terrainData.getCenterOffset(); final int[] mapSize = terrainData.getMapSize(); @@ -595,7 +603,13 @@ public class War3MapViewer extends ModelViewer { model = (MdxModel) this.load(fileVar, this.mapPathSolver, this.solverParams); } - this.doodads.add(new Doodad(this, model, row, doodad, type, maxPitch, maxRoll)); + if (type == WorldEditorDataType.DESTRUCTIBLES) { + this.doodads + .add(new Destructable(this, model, row, doodad, type, maxPitch, maxRoll, doodad.getLife())); + } + else { + this.doodads.add(new Doodad(this, model, row, doodad, type, maxPitch, maxRoll)); + } } } @@ -763,16 +777,18 @@ public class War3MapViewer extends ModelViewer { final float shadowY = row.getFieldAsFloat(UNIT_SHADOW_Y, 0); final float shadowWidth = row.getFieldAsFloat(UNIT_SHADOW_W, 0); final float shadowHeight = row.getFieldAsFloat(UNIT_SHADOW_H, 0); - if (!this.terrain.splats.containsKey(texture)) { - final Splat splat = new Splat(); - splat.opacity = 0.5f; - this.terrain.splats.put(texture, splat); + if (this.mapMpq.has(texture)) { + if (!this.terrain.splats.containsKey(texture)) { + final Splat splat = new Splat(); + splat.opacity = 0.5f; + this.terrain.splats.put(texture, splat); + } + final float x = unit.getLocation()[0] - shadowX; + final float y = unit.getLocation()[1] - shadowY; + this.terrain.splats.get(texture).locations + .add(new float[] { x, y, x + shadowWidth, y + shadowHeight, 3 }); + unitShadowSplat = this.terrain.splats.get(texture); } - final float x = unit.getLocation()[0] - shadowX; - final float y = unit.getLocation()[1] - shadowY; - this.terrain.splats.get(texture).locations - .add(new float[] { x, y, x + shadowWidth, y + shadowHeight, 3 }); - unitShadowSplat = this.terrain.splats.get(texture); } final String buildingShadow = row.getFieldAsString(BUILDING_SHADOW, 0); @@ -890,8 +906,11 @@ public class War3MapViewer extends ModelViewer { final ModelInstance instance = item.instance; if ((instance instanceof MdxComplexInstance) && (instance != this.confirmationInstance)) { final MdxComplexInstance mdxComplexInstance = (MdxComplexInstance) instance; - if (mdxComplexInstance.sequenceEnded || (mdxComplexInstance.sequence == -1)) { - SequenceUtils.randomStandSequence(mdxComplexInstance); + if ((mdxComplexInstance.sequence == -1) + || (mdxComplexInstance.sequenceEnded && (((MdxModel) mdxComplexInstance.model).sequences + .get(mdxComplexInstance.sequence).getFlags() == 0))) { + SequenceUtils.randomSequence(mdxComplexInstance, item.getAnimation(), SequenceUtils.EMPTY, + true); } } } @@ -904,8 +923,13 @@ public class War3MapViewer extends ModelViewer { } this.dncTerrain.setFrameByRatio( this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours()); + this.dncTerrain.update(rawDeltaTime, null); this.dncUnit.setFrameByRatio( this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours()); + this.dncUnit.update(rawDeltaTime, null); + this.dncTarget.setFrameByRatio( + this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours()); + this.dncTarget.update(rawDeltaTime, null); } } @@ -995,7 +1019,7 @@ public class War3MapViewer extends ModelViewer { final String path = entry.getKey(); final Splat locations = entry.getValue(); final SplatModel model = new SplatModel(Gdx.gl30, (Texture) load(path, PathSolver.DEFAULT, null), - locations.locations, this.terrain.centerOffset, locations.unitMapping); + locations.locations, this.terrain.centerOffset, locations.unitMapping, true); model.color[0] = 0; model.color[1] = 1; model.color[2] = 0; @@ -1037,7 +1061,8 @@ public class War3MapViewer extends ModelViewer { for (final RenderUnit unit : this.units) { final MdxComplexInstance instance = unit.instance; if (instance.isVisible(this.worldScene.camera) - && instance.intersectRayWithCollision(gdxRayHeap, intersectionHeap) + && instance.intersectRayWithCollision(gdxRayHeap, intersectionHeap, + unit.getSimulationUnit().getUnitType().isBuilding()) && !unit.getSimulationUnit().isDead()) { entity = unit; } @@ -1075,8 +1100,8 @@ public class War3MapViewer extends ModelViewer { RenderUnit entity = null; for (final RenderUnit unit : this.units) { final MdxComplexInstance instance = unit.instance; - if (instance.isVisible(this.worldScene.camera) - && instance.intersectRayWithCollision(gdxRayHeap, intersectionHeap)) { + if (instance.isVisible(this.worldScene.camera) && instance.intersectRayWithCollision(gdxRayHeap, + intersectionHeap, unit.getSimulationUnit().getUnitType().isBuilding())) { entity = unit; } } @@ -1149,6 +1174,7 @@ public class War3MapViewer extends ModelViewer { private static final int MAXIMUM_ACCEPTED = 1 << 30; private float selectionCircleScaleFactor; private DataTable worldEditData; + private WorldEditStrings worldEditStrings; /** * Returns a power of two size for the given target capacity. @@ -1255,6 +1281,12 @@ public class War3MapViewer extends ModelViewer { this.dncUnit = (MdxComplexInstance) unitDNCModel.addInstance(); this.dncUnit.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); this.dncUnit.setSequence(0); + final MdxModel targetDNCModel = (MdxModel) load( + mdx("Environment\\DNC\\DNCLordaeron\\DNCLordaeronTarget\\DNCLordaeronTarget.mdl"), PathSolver.DEFAULT, + null); + this.dncTarget = (MdxComplexInstance) targetDNCModel.addInstance(); + this.dncTarget.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + this.dncTarget.setSequence(0); } private static String mdx(String mdxPath) { @@ -1268,7 +1300,27 @@ public class War3MapViewer extends ModelViewer { } @Override - public SceneLightManager createLightManager() { - return new W3xSceneLightManager(this); + public SceneLightManager createLightManager(final boolean simple) { + if (simple) { + return new W3xScenePortraitLightManager(this); + } + else { + return new W3xSceneWorldLightManager(this); + } + } + + public WorldEditStrings getWorldEditStrings() { + return this.worldEditStrings; + } + + public void setGameUI(final GameUI gameUI) { + this.gameUI = gameUI; + for (final RenderUnit unit : this.units) { + unit.initAbilityUI(this); + } + } + + public GameUI getGameUI() { + return this.gameUI; } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/CliffMesh.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/CliffMesh.java index 00ed08b..ab8411e 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/CliffMesh.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/CliffMesh.java @@ -87,18 +87,18 @@ public class CliffMesh { this.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.uvBuffer); this.gl.glVertexAttribPointer(1, 2, GL20.GL_FLOAT, false, 0, 0); -// this.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.normalBuffer); -// this.gl.glVertexAttribPointer(2, 3, GL20.GL_FLOAT, false, 0, 0); + this.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.normalBuffer); + this.gl.glVertexAttribPointer(2, 3, GL20.GL_FLOAT, false, 0, 0); this.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.instanceBuffer); - this.gl.glVertexAttribPointer(2, 4, GL20.GL_FLOAT, false, 0, 0); - this.gl.glVertexAttribDivisor(2, 1); + this.gl.glVertexAttribPointer(3, 4, GL20.GL_FLOAT, false, 0, 0); + this.gl.glVertexAttribDivisor(3, 1); this.gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, this.indexBuffer); this.gl.glDrawElementsInstanced(GL20.GL_TRIANGLES, this.indices, GL30.GL_UNSIGNED_SHORT, 0, this.renderJobs.remaining() / 4); - this.gl.glVertexAttribDivisor(2, 0); // ToDo use vao + this.gl.glVertexAttribDivisor(3, 0); // ToDo use vao this.renderJobs.clear(); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/GroundTexture.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/GroundTexture.java index 86c04b1..5eb7e27 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/GroundTexture.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/GroundTexture.java @@ -10,6 +10,7 @@ import javax.imageio.ImageIO; import com.badlogic.gdx.graphics.GL30; import com.etheller.warsmash.datasources.DataSource; import com.etheller.warsmash.util.ImageUtils; +import com.etheller.warsmash.viewer5.handlers.tga.TgaFile; public class GroundTexture { public int id; @@ -19,43 +20,61 @@ public class GroundTexture { public GroundTexture(final String path, final DataSource dataSource, final GL30 gl) throws IOException { if (path.toLowerCase().endsWith(".blp")) { try (InputStream stream = dataSource.getResourceAsStream(path)) { - final BufferedImage image = ImageIO.read(stream); - if (image == null) { - throw new IllegalStateException("Missing ground texture: " + path); - } - final Buffer buffer = ImageUtils.getTextureBuffer(ImageUtils.forceBufferedImagesRGB(image)); - final int width = image.getWidth(); - final int height = image.getHeight(); - - this.tileSize = (int) (height * 0.25); - this.extended = (width > height); - - this.id = gl.glGenTexture(); - gl.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.id); - gl.glTexImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, GL30.GL_RGBA8, this.tileSize, this.tileSize, - this.extended ? 32 : 16, 0, GL30.GL_RGBA, GL30.GL_UNSIGNED_BYTE, null); - gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_LINEAR_MIPMAP_LINEAR); - gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_WRAP_S, GL30.GL_CLAMP_TO_EDGE); - gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_WRAP_T, GL30.GL_CLAMP_TO_EDGE); - - gl.glPixelStorei(GL30.GL_UNPACK_ROW_LENGTH, width); - for (int y = 0; y < 4; y++) { - for (int x = 0; x < 4; x++) { - buffer.position(((y * this.tileSize * width) + (x * this.tileSize)) * 4); - gl.glTexSubImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, 0, 0, (y * 4) + x, this.tileSize, this.tileSize, - 1, GL30.GL_RGBA, GL30.GL_UNSIGNED_BYTE, buffer); - - if (this.extended) { - buffer.position(((y * this.tileSize * width) + ((x + 4) * this.tileSize)) * 4); - gl.glTexSubImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, 0, 0, (y * 4) + x + 16, this.tileSize, - this.tileSize, 1, GL30.GL_RGBA, GL30.GL_UNSIGNED_BYTE, buffer); + if (stream == null) { + final String tgaPath = path.substring(0, path.length() - 4) + ".tga"; + try (final InputStream tgaStream = dataSource.getResourceAsStream(tgaPath)) { + if (tgaStream != null) { + final BufferedImage tgaData = TgaFile.readTGA(tgaPath, tgaStream); + loadImage(path, gl, tgaData); + } + else { + throw new IllegalStateException("Missing ground texture: " + path); } } } - gl.glPixelStorei(GL30.GL_UNPACK_ROW_LENGTH, 0); - gl.glGenerateMipmap(GL30.GL_TEXTURE_2D_ARRAY); + else { + final BufferedImage image = ImageIO.read(stream); + loadImage(path, gl, image); + } } } } + + private void loadImage(final String path, final GL30 gl, final BufferedImage image) { + if (image == null) { + throw new IllegalStateException("Missing ground texture: " + path); + } + final Buffer buffer = ImageUtils.getTextureBuffer(ImageUtils.forceBufferedImagesRGB(image)); + final int width = image.getWidth(); + final int height = image.getHeight(); + + this.tileSize = (int) (height * 0.25); + this.extended = (width > height); + + this.id = gl.glGenTexture(); + gl.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.id); + gl.glTexImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, GL30.GL_RGBA8, this.tileSize, this.tileSize, + this.extended ? 32 : 16, 0, GL30.GL_RGBA, GL30.GL_UNSIGNED_BYTE, null); + gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_LINEAR_MIPMAP_LINEAR); + gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_WRAP_S, GL30.GL_CLAMP_TO_EDGE); + gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_WRAP_T, GL30.GL_CLAMP_TO_EDGE); + + gl.glPixelStorei(GL30.GL_UNPACK_ROW_LENGTH, width); + for (int y = 0; y < 4; y++) { + for (int x = 0; x < 4; x++) { + buffer.position(((y * this.tileSize * width) + (x * this.tileSize)) * 4); + gl.glTexSubImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, 0, 0, (y * 4) + x, this.tileSize, this.tileSize, 1, + GL30.GL_RGBA, GL30.GL_UNSIGNED_BYTE, buffer); + + if (this.extended) { + buffer.position(((y * this.tileSize * width) + ((x + 4) * this.tileSize)) * 4); + gl.glTexSubImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, 0, 0, (y * 4) + x + 16, this.tileSize, + this.tileSize, 1, GL30.GL_RGBA, GL30.GL_UNSIGNED_BYTE, buffer); + } + } + } + gl.glPixelStorei(GL30.GL_UNPACK_ROW_LENGTH, 0); + gl.glGenerateMipmap(GL30.GL_TEXTURE_2D_ARRAY); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java index ca66d73..e9a17f2 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java @@ -41,12 +41,15 @@ import com.etheller.warsmash.viewer5.Camera; import com.etheller.warsmash.viewer5.PathSolver; import com.etheller.warsmash.viewer5.RawOpenGLTextureResource; import com.etheller.warsmash.viewer5.Texture; +import com.etheller.warsmash.viewer5.gl.DataTexture; import com.etheller.warsmash.viewer5.gl.Extensions; import com.etheller.warsmash.viewer5.gl.WebGL; +import com.etheller.warsmash.viewer5.handlers.tga.TgaFile; import com.etheller.warsmash.viewer5.handlers.w3x.DynamicShadowManager; import com.etheller.warsmash.viewer5.handlers.w3x.SplatModel; import com.etheller.warsmash.viewer5.handlers.w3x.SplatModel.SplatMover; import com.etheller.warsmash.viewer5.handlers.w3x.Variations; +import com.etheller.warsmash.viewer5.handlers.w3x.W3xSceneLightManager; import com.etheller.warsmash.viewer5.handlers.w3x.W3xShaders; import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; @@ -174,9 +177,16 @@ public class Terrain { final char tileset = w3eFile.getTileset(); final Element waterInfo = this.waterTable.get(tileset + "Sha"); - this.waterHeightOffset = waterInfo.getFieldFloatValue("height"); - this.waterTextureCount = waterInfo.getFieldValue("numTex"); - this.waterIncreasePerFrame = waterInfo.getFieldValue("texRate") / 60f; + if (waterInfo != null) { + this.waterHeightOffset = waterInfo.getFieldFloatValue("height"); + this.waterTextureCount = waterInfo.getFieldValue("numTex"); + this.waterIncreasePerFrame = waterInfo.getFieldValue("texRate") / 60f; + } + else { + this.waterHeightOffset = 0; + this.waterTextureCount = 0; + this.waterIncreasePerFrame = 0; + } loadWaterColor(this.minShallowColor, "Smin", waterInfo); loadWaterColor(this.maxShallowColor, "Smax", waterInfo); @@ -213,6 +223,9 @@ public class Terrain { // Ground textures for (final War3ID groundTile : w3eFile.getGroundTiles()) { final Element terrainTileInfo = this.terrainTable.get(groundTile.asStringValue()); + if (terrainTileInfo == null) { + throw new RuntimeException("No terrain info for: " + groundTile.asStringValue()); + } final String dir = terrainTileInfo.getField("dir"); final String file = terrainTileInfo.getField("file"); this.groundTextures.add(new GroundTexture(dir + "\\" + file + texturesExt, dataSource, Gdx.gl30)); @@ -231,9 +244,25 @@ public class Terrain { final String texDir = cliffInfo.getField("texDir"); final String texFile = cliffInfo.getField("texFile"); try (InputStream imageStream = dataSource.getResourceAsStream(texDir + "\\" + texFile + texturesExt)) { - final BufferedImage image = ImageIO.read(imageStream); - if (image == null) { - throw new IllegalStateException("Missing cliff texture: " + texDir + "\\" + texFile + texturesExt); + final BufferedImage image; + if (imageStream == null) { + final String tgaPath = texDir + "\\" + texFile + ".tga"; + try (final InputStream tgaStream = dataSource.getResourceAsStream(tgaPath)) { + if (tgaStream != null) { + image = TgaFile.readTGA(tgaPath, tgaStream); + } + else { + throw new IllegalStateException( + "Missing cliff texture: " + texDir + "\\" + texFile + texturesExt); + } + } + } + else { + image = ImageIO.read(imageStream); + if (image == null) { + throw new IllegalStateException( + "Missing cliff texture: " + texDir + "\\" + texFile + texturesExt); + } } this.cliffTextures.add(new UnloadedTexture(image.getWidth(), image.getHeight(), ImageUtils.getTextureBuffer(ImageUtils.forceBufferedImagesRGB(image)), @@ -796,7 +825,7 @@ public class Terrain { private static void loadWaterColor(final float[] out, final String prefix, final Element waterInfo) { for (int i = 0; i < colorTags.length; i++) { final String colorTag = colorTags[i]; - out[i] = waterInfo.getFieldFloatValue(prefix + "_" + colorTag) / 255f; + out[i] = waterInfo == null ? 0.0f : waterInfo.getFieldFloatValue(prefix + "_" + colorTag) / 255f; } } @@ -856,6 +885,13 @@ public class Terrain { gl.glUniform1f(this.groundShader.getUniformLocation("centerOffsetX"), this.centerOffset[0]); gl.glUniform1f(this.groundShader.getUniformLocation("centerOffsetY"), this.centerOffset[1]); + final W3xSceneLightManager lightManager = (W3xSceneLightManager) this.viewer.worldScene.getLightManager(); + final DataTexture unitLightsTexture = lightManager.getTerrainLightsTexture(); + + unitLightsTexture.bind(21); + gl.glUniform1i(this.groundShader.getUniformLocation("lightTexture"), 21); + gl.glUniform1f(this.groundShader.getUniformLocation("lightCount"), unitLightsTexture.getHeight() - 0.5f); + gl.glUniformMatrix4fv(this.groundShader.getUniformLocation("DepthBiasMVP"), 1, false, dynamicShadowManager.getDepthBiasMVP().val, 0); @@ -949,6 +985,13 @@ public class Terrain { gl.glActiveTexture(GL30.GL_TEXTURE2); gl.glBindTexture(GL30.GL_TEXTURE_2D, this.shadowMap); + final W3xSceneLightManager lightManager = (W3xSceneLightManager) this.viewer.worldScene.getLightManager(); + final DataTexture terrainLightsTexture = lightManager.getTerrainLightsTexture(); + + terrainLightsTexture.bind(21); + gl.glUniform1i(shader.getUniformLocation("lightTexture"), 21); + gl.glUniform1f(shader.getUniformLocation("lightCount"), terrainLightsTexture.getHeight() - 0.5f); + // Render the cliffs for (final SplatModel splat : this.uberSplatModels) { splat.render(gl, shader); @@ -976,6 +1019,13 @@ public class Terrain { gl.glUniform1f(this.waterShader.getUniformLocation("centerOffsetY"), this.centerOffset[1]); gl.glUniform4fv(9, 1, this.shaderMapBounds, 0); + final W3xSceneLightManager lightManager = (W3xSceneLightManager) this.viewer.worldScene.getLightManager(); + final DataTexture unitLightsTexture = lightManager.getTerrainLightsTexture(); + + unitLightsTexture.bind(21); + gl.glUniform1i(this.waterShader.getUniformLocation("lightTexture"), 21); + gl.glUniform1f(this.waterShader.getUniformLocation("lightCount"), unitLightsTexture.getHeight() - 0.5f); + gl.glActiveTexture(GL30.GL_TEXTURE0); gl.glBindTexture(GL30.GL_TEXTURE_2D, this.waterHeight); gl.glActiveTexture(GL30.GL_TEXTURE1); @@ -1025,6 +1075,13 @@ public class Terrain { gl.glUniform1i(1, this.viewer.renderPathing); gl.glUniform1i(2, this.viewer.renderLighting); + final W3xSceneLightManager lightManager = (W3xSceneLightManager) this.viewer.worldScene.getLightManager(); + final DataTexture unitLightsTexture = lightManager.getTerrainLightsTexture(); + + unitLightsTexture.bind(21); + gl.glUniform1i(this.cliffShader.getUniformLocation("lightTexture"), 21); + gl.glUniform1f(this.cliffShader.getUniformLocation("lightCount"), unitLightsTexture.getHeight() - 0.5f); + this.cliffShader.setUniformi("shadowMap", 2); gl.glActiveTexture(GL30.GL_TEXTURE2); gl.glBindTexture(GL30.GL_TEXTURE_2D, this.shadowMap); @@ -1266,7 +1323,7 @@ public class Terrain { final SplatModel splatModel = new SplatModel(Gdx.gl30, (Texture) this.viewer.load(path, PathSolver.DEFAULT, null), splat.locations, this.centerOffset, - splat.unitMapping); + splat.unitMapping, false); splatModel.color[3] = splat.opacity; this.uberSplatModels.add(splatModel); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/TerrainShaders.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/TerrainShaders.java index ec3b62e..43897a2 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/TerrainShaders.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/TerrainShaders.java @@ -1,5 +1,7 @@ package com.etheller.warsmash.viewer5.handlers.w3x.environment; +import com.etheller.warsmash.viewer5.Shaders; + /** * Mostly copied from HiveWE! */ @@ -12,8 +14,8 @@ public class TerrainShaders { "\r\n" + // "layout (location = 0) in vec3 vPosition;\r\n" + // "layout (location = 1) in vec2 vUV;\r\n" + // -// "layout (location = 2) in vec3 vNormal;\r\n" + // - "layout (location = 2) in vec4 vOffset;\r\n" + // + "layout (location = 2) in vec3 vNormal;\r\n" + // + "layout (location = 3) in vec4 vOffset;\r\n" + // "\r\n" + // "layout (location = 0) uniform mat4 MVP;\r\n" + // "\r\n" + // @@ -21,12 +23,15 @@ public class TerrainShaders { "layout (binding = 3) uniform sampler2D shadowMap;\r\n" + // "layout (location = 3) uniform float centerOffsetX;\r\n" + // "layout (location = 4) uniform float centerOffsetY;\r\n" + // + "layout (location = 5) uniform sampler2D lightTexture;\r\n" + // + "layout (location = 6) uniform float lightCount;\r\n" + // "\r\n" + // "layout (location = 0) out vec3 UV;\r\n" + // "layout (location = 1) out vec3 Normal;\r\n" + // "layout (location = 2) out vec2 pathing_map_uv;\r\n" + // "layout (location = 3) out vec3 position;\r\n" + // "layout (location = 4) out vec2 v_suv;\r\n" + // + "layout (location = 5) out vec3 shadeColor;\r\n" + // "\r\n" + // "void main() {\r\n" + // " pathing_map_uv = (vec2(vPosition.x + 128, vPosition.y) / 128 + vOffset.xy) * 4;\r\n" + // @@ -52,9 +57,11 @@ public class TerrainShaders { " float hR = texelFetch(height_texture, height_pos + off.xz, 0).r;\r\n" + // " float hD = texelFetch(height_texture, height_pos - off.zy, 0).r;\r\n" + // " float hU = texelFetch(height_texture, height_pos + off.zy, 0).r;\r\n" + // - " vec3 terrain_normal = normalize(vec3(hL - hR, hD - hU, 2.0));\r\n" + // + " vec3 terrain_normal = normalize(vNormal);//vec3(hL - hR, hD - hU, 2.0)+vNormal);\r\n" + // "\r\n" + // " Normal = terrain_normal;\r\n" + // + Shaders.lightSystem("Normal", "myposition.xyz", "lightTexture", "lightCount", true) + "\r\n" + // + " shadeColor = clamp(lightFactor, 0.0, 1.0);\r\n" + // "}"; public static final String frag = "#version 450 core\r\n" + // @@ -70,6 +77,7 @@ public class TerrainShaders { "layout (location = 1) in vec3 Normal;\r\n" + // "layout (location = 2) in vec2 pathing_map_uv;\r\n" + // "layout (location = 4) in vec2 v_suv;\r\n" + // + "layout (location = 5) in vec3 shadeColor;\r\n" + // "\r\n" + // "out vec4 color;\r\n" + // "\r\n" + // @@ -79,10 +87,7 @@ public class TerrainShaders { " float shadow = texture2D(shadowMap, v_suv).r;\r\n" + // " color.rgb *= (1.0 - shadow);\r\n" + // " if (show_lighting) {\r\n" + // - " vec3 light_direction = vec3(-0.3, -0.3, 0.25);\r\n" + // - " light_direction = normalize(light_direction);\r\n" + // - "\r\n" + // - " color.rgb *= clamp(dot(Normal, light_direction) + 0.45, 0, 1);\r\n" + // + " color.rgb *= shadeColor;\r\n" + // " }\r\n" + // "\r\n" + // " uvec4 byte = texelFetch(pathing_map_static, ivec2(pathing_map_uv), 0);\r\n" + // @@ -128,14 +133,16 @@ public class TerrainShaders { "layout (binding = 2) uniform usampler2D terrain_texture_list;\r\n" + // "layout (location = 4) uniform float centerOffsetX;\r\n" + // "layout (location = 5) uniform float centerOffsetY;\r\n" + // + "layout (location = 7) uniform sampler2D lightTexture;\r\n" + // + "layout (location = 8) uniform float lightCount;\r\n" + // "\r\n" + // "layout (location = 0) out vec2 UV;\r\n" + // "layout (location = 1) out flat uvec4 texture_indices;\r\n" + // "layout (location = 2) out vec2 pathing_map_uv;\r\n" + // - "layout (location = 3) out vec3 normal;\r\n" + // "layout (location = 4) out vec3 position;\r\n" + // "layout (location = 5) out vec3 ShadowCoord;\r\n" + // "layout (location = 6) out vec2 v_suv;\r\n" + // + "layout (location = 7) out vec3 shadeColor;\r\n" + // "\r\n" + // "void main() { \r\n" + // " ivec2 size = textureSize(terrain_texture_list, 0);\r\n" + // @@ -149,15 +156,16 @@ public class TerrainShaders { " float hR = texelFetch(height_texture, height_pos + off.xz, 0).r;\r\n" + // " float hD = texelFetch(height_texture, height_pos - off.zy, 0).r;\r\n" + // " float hU = texelFetch(height_texture, height_pos + off.zy, 0).r;\r\n" + // - " normal = normalize(vec3(hL - hR, hD - hU, 2.0));\r\n" + // + " vec3 normal = normalize(vec3(hL - hR, hD - hU, 2.0));\r\n" + // "\r\n" + // " UV = vec2(vPosition.x, 1 - vPosition.y);\r\n" + // " texture_indices = texelFetch(terrain_texture_list, pos, 0);\r\n" + // " pathing_map_uv = (vPosition + pos) * 4; \r\n" + // "\r\n" + // " // Cliff culling\r\n" + // - " position = vec3((vPosition.x + pos.x)*128.0 + centerOffsetX, (vPosition.y + pos.y)*128.0 + centerOffsetY, height.r*128.0);\r\n" + " vec3 positionWorld = vec3((vPosition.x + pos.x)*128.0 + centerOffsetX, (vPosition.y + pos.y)*128.0 + centerOffsetY, height.r*128.0);\r\n" + // + " position = positionWorld;\r\n" + // " gl_Position = ((texture_indices.a & 32768) == 0) ? MVP * vec4(position.xyz, 1) : vec4(2.0, 0.0, 0.0, 1.0);\r\n" + // " ShadowCoord = (((texture_indices.a & 32768) == 0) ? DepthBiasMVP * vec4(position.xyz, 1) : vec4(2.0, 0.0, 0.0, 1.0)).xyz;\r\n" @@ -165,6 +173,8 @@ public class TerrainShaders { " v_suv = (vPosition + pos) / size;\r\n" + // " position.x = (position.x - centerOffsetX) / (size.x * 128.0);\r\n" + // " position.y = (position.y - centerOffsetY) / (size.y * 128.0);\r\n" + // + Shaders.lightSystem("normal", "positionWorld", "lightTexture", "lightCount", true) + "\r\n" + // + " shadeColor = clamp(lightFactor, 0.0, 1.0);\r\n" + // "}"; public static final String frag = "#version 450 core\r\n" + // @@ -197,10 +207,10 @@ public class TerrainShaders { "layout (location = 0) in vec2 UV;\r\n" + // "layout (location = 1) in flat uvec4 texture_indices;\r\n" + // "layout (location = 2) in vec2 pathing_map_uv;\r\n" + // - "layout (location = 3) in vec3 normal;\r\n" + // "layout (location = 4) in vec3 position;\r\n" + // "layout (location = 5) in vec3 ShadowCoord;\r\n" + // "layout (location = 6) in vec2 v_suv;\r\n" + // + "layout (location = 7) in vec3 shadeColor;\r\n" + // "\r\n" + // "layout (location = 0) out vec4 color;\r\n" + // // "layout (location = 1) out vec4 position;\r\n" + // @@ -263,14 +273,12 @@ public class TerrainShaders { // " if ( texture2D(shadowMap, ShadowCoord.xy).z > ShadowCoord.z ) {\r\n" + // // " visibility = 0.5;\r\n" + // // " }\r\n" + // - " color = vec4(color.xyz * (1.0 - shadow), 1.0);\r\n" + // "\r\n" + // -// " if (show_lighting) {\r\n" + // -// " vec3 light_direction = vec3(-0.3, -0.3, 0.25);\r\n" + // -// " light_direction = normalize(light_direction);\r\n" + // -// "\r\n" + // -// " color.rgb *= clamp(dot(normal, light_direction) + 0.45, 0, 1);\r\n" + // -// " }\r\n" + // + " if (show_lighting) {\r\n" + // + " color = vec4(color.xyz * (1.0 - shadow) * shadeColor, 1.0);\r\n" + // + " } else {\r\n" + // + " color = vec4(color.xyz * (1.0 - shadow), 1.0);\r\n" + // + " }\r\n" + // // "\r\n" + // // " if (show_pathing_map) {\r\n" + // // " uint byte_static = texelFetch(pathing_map_static, ivec2(pathing_map_uv), 0).r;\r\n" + // @@ -415,10 +423,13 @@ public class TerrainShaders { "layout (location = 3) uniform vec4 deep_color_min;\r\n" + // "layout (location = 4) uniform vec4 deep_color_max;\r\n" + // "layout (location = 5) uniform float water_offset;\r\n" + // + "layout (location = 10) uniform sampler2D lightTexture;\r\n" + // + "layout (location = 11) uniform float lightCount;\r\n" + // "\r\n" + // "out vec2 UV;\r\n" + // "out vec4 Color;\r\n" + // "out vec2 position;\r\n" + // + "out vec3 shadeColor;\r\n" + // "\r\n" + // "const float min_depth = 10.f / 128;\r\n" + // "const float deeplevel = 64.f / 128;\r\n" + // @@ -437,8 +448,9 @@ public class TerrainShaders { "\r\n" + // " position = vec2((vPosition.x + pos.x)*128.0 + centerOffsetX, (vPosition.y + pos.y)*128.0 + centerOffsetY);\r\n" + // - " gl_Position = is_water ? MVP * vec4(position.xy, water_height*128.0, 1) : vec4(2.0, 0.0, 0.0, 1.0);\r\n" - + // + " vec4 myposition = vec4(position.xy, water_height*128.0, 1);\r\n" + // + " vec3 Normal = vec3(0,0,1);\r\n" + // + " gl_Position = is_water ? MVP * myposition : vec4(2.0, 0.0, 0.0, 1.0);\r\n" + // "\r\n" + // " UV = vec2((vPosition.x + pos.x%2)/2.0, (vPosition.y + pos.y%2)/2.0);\r\n" + // "\r\n" + // @@ -451,6 +463,8 @@ public class TerrainShaders { " value = clamp(value - deeplevel, 0.f, maxdepth - deeplevel) / (maxdepth - deeplevel);\r\n" + // " Color = deep_color_min * (1.f - value) + deep_color_max * value;\r\n" + // " }\r\n" + // + Shaders.lightSystem("Normal", "myposition.xyz", "lightTexture", "lightCount", true) + "\r\n" + // + " shadeColor = clamp(lightFactor, 0.0, 1.0);\r\n" + // " }"; public static final String frag = "#version 450 core\r\n" + // @@ -465,13 +479,14 @@ public class TerrainShaders { "in vec2 UV;\r\n" + // "in vec4 Color;\r\n" + // "in vec2 position;\r\n" + // + "in vec3 shadeColor;\r\n" + // "\r\n" + // "out vec4 outColor;\r\n" + // "\r\n" + // "void main() {\r\n" + // " vec2 d2 = min(position - mapBounds.xy, mapBounds.zw - position);\r\n" + // " float d1 = clamp(min(d2.x, d2.y) / 64.0 + 1.0, 0.0, 1.0) * 0.8 + 0.2;;\r\n" + // - " outColor = texture(water_textures, vec3(UV, current_texture)) * vec4(Color.rgb * d1, Color.a);\r\n" + " outColor = texture(water_textures, vec3(UV, current_texture)) * vec4(Color.rgb * d1 * shadeColor, Color.a);\r\n" + // "}"; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java index 8691db1..5812b73 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java @@ -8,7 +8,9 @@ import java.util.Queue; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.math.Quaternion; +import com.etheller.warsmash.parsers.fdf.GameUI; import com.etheller.warsmash.parsers.mdlx.Sequence; +import com.etheller.warsmash.units.Element; import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; import com.etheller.warsmash.util.ImageUtils; import com.etheller.warsmash.util.RenderMathUtils; @@ -34,6 +36,12 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityP import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityStop; public class RenderUnit { + private static final War3ID ABILITY_MOVE = War3ID.fromString("Amov"); + private static final War3ID ABILITY_STOP = War3ID.fromString("Astp"); + private static final War3ID ABILITY_HOLD_POSITION = War3ID.fromString("Ahol"); + private static final War3ID ABILITY_PATROL = War3ID.fromString("Apat"); + private static final War3ID ABILITY_ATTACK = War3ID.fromString("Aatk"); + private static final Quaternion tempQuat = new Quaternion(); private static final War3ID RED = War3ID.fromString("uclr"); private static final War3ID GREEN = War3ID.fromString("uclg"); @@ -133,33 +141,44 @@ public class RenderUnit { this.row = row; this.soundset = soundset; - for (final CAbility ability : simulationUnit.getAbilities()) { + } + + public void initAbilityUI(final War3MapViewer map) { + for (final CAbility ability : this.simulationUnit.getAbilities()) { if (ability instanceof CAbilityMove) { + final GameUI gameUI = map.getGameUI(); + final Element moveCommand = gameUI.getSkinData().get("CmdMove"); + final String moveCommandArt = gameUI.getSkinField(moveCommand.getField("Art")); this.commandCardIcons.add(new CommandCardIcon(0, 0, - ImageUtils.getBLPTexture(map.dataSource, "ReplaceableTextures\\CommandButtons\\BTNMove.blp"), - ability.getOrderId())); + ImageUtils.getBLPTexture(map.dataSource, moveCommandArt), ability.getOrderId())); } else if (ability instanceof CAbilityAttack) { + final GameUI gameUI = map.getGameUI(); + final Element command = gameUI.getSkinData().get("CmdAttack"); + final String commandArt = gameUI.getSkinField(command.getField("Art")); this.commandCardIcons.add(new CommandCardIcon(3, 0, - ImageUtils.getBLPTexture(map.dataSource, "ReplaceableTextures\\CommandButtons\\BTNAttack.blp"), - ability.getOrderId())); + ImageUtils.getBLPTexture(map.dataSource, commandArt), ability.getOrderId())); } else if (ability instanceof CAbilityHoldPosition) { - this.commandCardIcons - .add(new CommandCardIcon(2, 0, - ImageUtils.getBLPTexture(map.dataSource, - "ReplaceableTextures\\CommandButtons\\BTNHoldPosition.blp"), - ability.getOrderId())); + final GameUI gameUI = map.getGameUI(); + final Element command = gameUI.getSkinData().get("CmdHoldPos"); + final String commandArt = gameUI.getSkinField(command.getField("Art")); + this.commandCardIcons.add(new CommandCardIcon(2, 0, + ImageUtils.getBLPTexture(map.dataSource, commandArt), ability.getOrderId())); } else if (ability instanceof CAbilityPatrol) { + final GameUI gameUI = map.getGameUI(); + final Element command = gameUI.getSkinData().get("CmdPatrol"); + final String commandArt = gameUI.getSkinField(command.getField("Art")); this.commandCardIcons.add(new CommandCardIcon(0, 1, - ImageUtils.getBLPTexture(map.dataSource, "ReplaceableTextures\\CommandButtons\\BTNPatrol.blp"), - ability.getOrderId())); + ImageUtils.getBLPTexture(map.dataSource, commandArt), ability.getOrderId())); } else if (ability instanceof CAbilityStop) { + final GameUI gameUI = map.getGameUI(); + final Element command = gameUI.getSkinData().get("CmdStop"); + final String commandArt = gameUI.getSkinField(command.getField("Art")); this.commandCardIcons.add(new CommandCardIcon(1, 0, - ImageUtils.getBLPTexture(map.dataSource, "ReplaceableTextures\\CommandButtons\\BTNStop.blp"), - ability.getOrderId())); + ImageUtils.getBLPTexture(map.dataSource, commandArt), ability.getOrderId())); } } } @@ -225,7 +244,7 @@ public class RenderUnit { } if (boneCorpse && !this.boneCorpse) { this.unitAnimationListenerImpl.playAnimationWithDuration(true, PrimaryTag.DECAY, SequenceUtils.BONE, - map.simulation.getGameplayConstants().getBoneDecayTime(), true); + this.simulationUnit.getEndingDecayTime(map.simulation), true); } else if (corpse && !this.corpse) { this.unitAnimationListenerImpl.playAnimationWithDuration(true, PrimaryTag.DECAY, SequenceUtils.FLESH, diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CGameplayConstants.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CGameplayConstants.java index 9960c94..9b70d09 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CGameplayConstants.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CGameplayConstants.java @@ -23,6 +23,7 @@ public class CGameplayConstants { private final float duskTimeGameHours; private final float gameDayHours; private final float gameDayLength; + private final float structureDecayTime; public CGameplayConstants(final DataTable parsedDataTable) { final Element miscData = parsedDataTable.get("Misc"); @@ -31,6 +32,7 @@ public class CGameplayConstants { this.maxCollisionRadius = miscData.getFieldFloatValue("MaxCollisionRadius"); this.decayTime = miscData.getFieldFloatValue("DecayTime"); this.boneDecayTime = miscData.getFieldFloatValue("BoneDecayTime"); + this.structureDecayTime = miscData.getFieldFloatValue("StructureDecayTime"); this.bulletDeathTime = miscData.getFieldFloatValue("BulletDeathTime"); this.closeEnoughRange = miscData.getFieldFloatValue("CloseEnoughRange"); @@ -105,4 +107,8 @@ public class CGameplayConstants { public float getDuskTimeGameHours() { return this.duskTimeGameHours; } + + public float getStructureDecayTime() { + return this.structureDecayTime; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java index 913b0cd..3e95c5e 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java @@ -62,7 +62,7 @@ public class CSimulation { for (int i = 0; i < WarsmashConstants.MAX_PLAYERS; i++) { if (i < playerInfos.size()) { final Player playerInfo = playerInfos.get(i); - this.players.add(new CPlayer(playerInfo.getId().getValue(), CMapControl.values()[playerInfo.getType()], + this.players.add(new CPlayer(playerInfo.getId(), CMapControl.values()[playerInfo.getType()], playerInfo.getName(), CRace.parseRace(playerInfo.getRace()), playerInfo.getStartLocation())); } else { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java index a13b533..9f6f623 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java @@ -16,10 +16,13 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitStateListener. import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.CAttackOrder; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CAllianceType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayer; public class CUnit extends CWidget { + private static final Rectangle tempRect = new Rectangle(); private War3ID typeId; private float facing; // degrees private float mana; @@ -51,6 +54,8 @@ public class CUnit extends CWidget { // questionable -- it already was -- but I meant for those to inform us // which fields shouldn't be persisted if we do game state save later private transient CUnitStateNotifier stateNotifier = new CUnitStateNotifier(); + private final float acquisitionRange; + private transient static AutoAttackTargetFinderEnum autoAttackTargetFinderEnum = new AutoAttackTargetFinderEnum(); public CUnit(final int handleId, final int playerIndex, final float x, final float y, final float life, final War3ID typeId, final float facing, final float mana, final int maximumLife, final int maximumMana, @@ -67,6 +72,7 @@ public class CUnit extends CWidget { this.flyHeight = unitType.getDefaultFlyingHeight(); this.unitType = unitType; this.classifications.addAll(unitType.getClassifications()); + this.acquisitionRange = unitType.getDefaultAcquisitionRange(); } public void setUnitAnimationListener(final CUnitAnimationListener unitAnimationListener) { @@ -172,9 +178,8 @@ public class CUnit extends CWidget { this.deathTurnTick = gameTurnTick; } } - else if (game - .getGameTurnTick() > (this.deathTurnTick + (int) (game.getGameplayConstants().getBoneDecayTime() - / WarsmashConstants.SIMULATION_STEP_TIME))) { + else if (game.getGameTurnTick() > (this.deathTurnTick + + (int) (getEndingDecayTime(game) / WarsmashConstants.SIMULATION_STEP_TIME))) { return true; } } @@ -189,9 +194,33 @@ public class CUnit extends CWidget { this.unitAnimationListener.playAnimation(true, PrimaryTag.STAND, SequenceUtils.EMPTY, 1.0f, true); } } + else { + // check to auto acquire targets + if (!this.unitType.getAttacks().isEmpty()) { + if (this.collisionRectangle != null) { + tempRect.set(this.collisionRectangle); + } + else { + tempRect.set(this.getX(), this.getY(), 0, 0); + } + final float halfSize = this.acquisitionRange; + tempRect.x -= halfSize; + tempRect.y -= halfSize; + tempRect.width += halfSize * 2; + tempRect.height += halfSize * 2; + game.getWorldCollision().enumUnitsInRect(tempRect, autoAttackTargetFinderEnum.reset(game, this)); + } + } return false; } + public float getEndingDecayTime(final CSimulation game) { + if (this.unitType.isBuilding()) { + return game.getGameplayConstants().getStructureDecayTime(); + } + return game.getGameplayConstants().getBoneDecayTime(); + } + public void order(final COrder order, final boolean queue) { if (isDead()) { return; @@ -349,8 +378,23 @@ public class CUnit extends CWidget { this.life -= trueDamage; simulation.unitDamageEvent(this, weaponType, this.unitType.getArmorType()); this.stateNotifier.lifeChanged(); - if (!wasDead && isDead() && !this.unitType.isBuilding()) { - kill(simulation); + if (isDead()) { + if (!wasDead && !this.unitType.isBuilding()) { + kill(simulation); + } + } + else { + if (this.currentOrder == null) { + if (!simulation.getPlayer(getPlayerIndex()).hasAlliance(source.getPlayerIndex(), + CAllianceType.PASSIVE)) { + for (final CUnitAttack attack : this.unitType.getAttacks()) { + if (source.canBeTargetedBy(simulation, this, attack.getTargetsAllowed())) { + this.order(new CAttackOrder(this, attack, source), false); + break; + } + } + } + } } } @@ -478,4 +522,35 @@ public class CUnit extends CWidget { } return false; } + + public boolean isMovementDisabled() { + return this.unitType.isBuilding(); + } + + private static final class AutoAttackTargetFinderEnum implements CUnitEnumFunction { + private CSimulation game; + private CUnit source; + + private AutoAttackTargetFinderEnum reset(final CSimulation game, final CUnit source) { + this.game = game; + this.source = source; + return this; + } + + @Override + public boolean call(final CUnit unit) { + if (!this.game.getPlayer(this.source.getPlayerIndex()).hasAlliance(unit.getPlayerIndex(), + CAllianceType.PASSIVE)) { + for (final CUnitAttack attack : this.source.unitType.getAttacks()) { + if (this.source.canReach(unit, this.source.acquisitionRange) + && unit.canBeTargetedBy(this.game, this.source, attack.getTargetsAllowed()) + && (this.source.distance(unit) >= this.source.getUnitType().getMinimumAttackRange())) { + this.source.order(new CAttackOrder(this.source, attack, unit), false); + return true; + } + } + } + return false; + } + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitEnumFunction.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitEnumFunction.java index 1863c2c..3f0392d 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitEnumFunction.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitEnumFunction.java @@ -1,5 +1,11 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation; public interface CUnitEnumFunction { + /** + * Operates on a unit, returning true if we should stop execution. + * + * @param unit + * @return + */ boolean call(CUnit unit); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java index 3d84425..1a95891 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java @@ -33,12 +33,15 @@ public class CUnitType { // game data? can we store it in a cleaner way? private final BufferedImage buildingPathingPixelMap; private final EnumSet targetedAs; + private final float defaultAcquisitionRange; + private final float minimumAttackRange; public CUnitType(final String name, final boolean isBldg, final MovementType movementType, final float defaultFlyingHeight, final float collisionSize, final EnumSet classifications, final List attacks, final String armorType, final boolean raise, final boolean decay, final CDefenseType defenseType, final float impactZ, - final BufferedImage buildingPathingPixelMap, final float deathTime, final EnumSet targetedAs) { + final BufferedImage buildingPathingPixelMap, final float deathTime, final EnumSet targetedAs, + final float defaultAcquisitionRange, final float minimumAttackRange) { this.name = name; this.building = isBldg; this.movementType = movementType; @@ -54,6 +57,8 @@ public class CUnitType { this.buildingPathingPixelMap = buildingPathingPixelMap; this.deathTime = deathTime; this.targetedAs = targetedAs; + this.defaultAcquisitionRange = defaultAcquisitionRange; + this.minimumAttackRange = minimumAttackRange; } public String getName() { @@ -115,4 +120,12 @@ public class CUnitType { public EnumSet getTargetedAs() { return this.targetedAs; } + + public float getDefaultAcquisitionRange() { + return this.defaultAcquisitionRange; + } + + public float getMinimumAttackRange() { + return this.minimumAttackRange; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/CAttackType.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/CAttackType.java index 6da4cbb..cd540b8 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/CAttackType.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/CAttackType.java @@ -37,6 +37,10 @@ public enum CAttackType implements CodeKeyType { } public static CAttackType parseAttackType(final String attackTypeString) { - return valueOf(attackTypeString.toUpperCase()); + final String upperCaseAttackType = attackTypeString.toUpperCase(); + if ("SEIGE".equals(upperCaseAttackType)) { + return SIEGE; + } + return valueOf(upperCaseAttackType); } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/CDefenseType.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/CDefenseType.java index e300eb3..1360513 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/CDefenseType.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/CDefenseType.java @@ -24,6 +24,10 @@ public enum CDefenseType implements CodeKeyType { } public static CDefenseType parseDefenseType(final String typeString) { - return valueOf(typeString.toUpperCase()); + final String upperCaseTypeString = typeString.toUpperCase(); + if (upperCaseTypeString.equals("HEAVY")) { + return LARGE; + } + return valueOf(upperCaseTypeString); } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CAbilityData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CAbilityData.java index 1a4e939..7188d3c 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CAbilityData.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CAbilityData.java @@ -1,11 +1,27 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.data; import com.etheller.warsmash.units.manager.MutableObjectData; +import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; +import com.etheller.warsmash.util.ImageUtils; +import com.etheller.warsmash.util.War3ID; public class CAbilityData { + private static final War3ID ABILITY_ICON = War3ID.fromString("aart"); private final MutableObjectData abilityData; public CAbilityData(final MutableObjectData abilityData) { this.abilityData = abilityData; } + + public String getIconPath(final War3ID id, final int level) { + final MutableGameObject mutableGameObject = this.abilityData.get(id); + if (mutableGameObject == null) { + return ImageUtils.DEFAULT_ICON_PATH; + } + final String iconPath = mutableGameObject.getFieldAsString(ABILITY_ICON, level); + if ((iconPath == null) || "".equals(iconPath)) { + return ImageUtils.DEFAULT_ICON_PATH; + } + return iconPath; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java index 7b6128b..a790f44 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java @@ -103,6 +103,9 @@ public class CUnitData { private static final War3ID ATTACK2_WEAPON_SOUND = War3ID.fromString("ucs2"); private static final War3ID ATTACK2_WEAPON_TYPE = War3ID.fromString("ua2w"); + private static final War3ID ACQUISITION_RANGE = War3ID.fromString("uacq"); + private static final War3ID MINIMUM_ATTACK_RANGE = War3ID.fromString("uamn"); + private static final War3ID PROJECTILE_IMPACT_Z = War3ID.fromString("uimz"); private static final War3ID DEATH_TYPE = War3ID.fromString("udea"); @@ -160,6 +163,8 @@ public class CUnitData { final boolean isBldg = unitType.getFieldAsBoolean(IS_BLDG, 0); final PathingGrid.MovementType movementType = PathingGrid.getMovementType(movetp); final String unitName = unitType.getFieldAsString(NAME, 0); + final float acquisitionRange = unitType.getFieldAsFloat(ACQUISITION_RANGE, 0); + final float minimumAttackRange = unitType.getFieldAsFloat(MINIMUM_ATTACK_RANGE, 0); final EnumSet targetedAs = CTargetType .parseTargetTypeSet(unitType.getFieldAsString(TARGETED_AS, 0)); final String classificationString = unitType.getFieldAsString(CLASSIFICATION, 0); @@ -269,7 +274,7 @@ public class CUnitData { final float deathTime = unitType.getFieldAsFloat(DEATH_TIME, 0); unitTypeInstance = new CUnitType(unitName, isBldg, movementType, moveHeight, collisionSize, classifications, attacks, armorType, raise, decay, defenseType, impactZ, buildingPathingPixelMap, deathTime, - targetedAs); + targetedAs, acquisitionRange, minimumAttackRange); this.unitIdToUnitType.put(typeId, unitTypeInstance); } return unitTypeInstance; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CAttackOrder.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CAttackOrder.java index f3bf6ce..29d4887 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CAttackOrder.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CAttackOrder.java @@ -29,11 +29,16 @@ public class CAttackOrder implements COrder { } private void createMoveOrder(final CUnit unit, final CWidget target) { - if ((target instanceof CUnit) && !(((CUnit) target).getUnitType().isBuilding())) { - this.moveOrder = new CMoveOrder(unit, (CUnit) target); + if (!unit.isMovementDisabled()) { // TODO: Check mobility instead + if ((target instanceof CUnit) && !(((CUnit) target).getUnitType().isBuilding())) { + this.moveOrder = new CMoveOrder(unit, (CUnit) target); + } + else { + this.moveOrder = new CMoveOrder(unit, target.getX(), target.getY()); + } } else { - this.moveOrder = new CMoveOrder(unit, target.getX(), target.getY()); + this.moveOrder = null; } } @@ -51,7 +56,11 @@ public class CAttackOrder implements COrder { */)) { range += this.unitAttack.getRangeMotionBuffer(); } - if (!this.unit.canReach(this.target, range)) { + if (!this.unit.canReach(this.target, range) + || (this.unit.distance(this.target) < this.unit.getUnitType().getMinimumAttackRange())) { + if (this.moveOrder == null) { + return true; + } if (this.moveOrder.update(simulation)) { return true; // we just cant reach them } @@ -61,46 +70,51 @@ public class CAttackOrder implements COrder { return false; } this.wasInRange = true; - final float prevX = this.unit.getX(); - final float prevY = this.unit.getY(); - final float deltaY = this.target.getY() - prevY; - final float deltaX = this.target.getX() - prevX; - final double goalAngleRad = Math.atan2(deltaY, deltaX); - float goalAngle = (float) Math.toDegrees(goalAngleRad); - if (goalAngle < 0) { - goalAngle += 360; - } - float facing = this.unit.getFacing(); - float delta = goalAngle - facing; - final float propulsionWindow = simulation.getGameplayConstants().getAttackHalfAngle(); - final float turnRate = simulation.getUnitData().getTurnRate(this.unit.getTypeId()); - - if (delta < -180) { - delta = 360 + delta; - } - if (delta > 180) { - delta = -360 + delta; - } - final float absDelta = Math.abs(delta); - - if ((absDelta <= 1.0) && (absDelta != 0)) { - this.unit.setFacing(goalAngle); - } - else { - float angleToAdd = Math.signum(delta) * (float) Math.toDegrees(turnRate); - if (absDelta < Math.abs(angleToAdd)) { - angleToAdd = delta; + if (!this.unit.isMovementDisabled()) { + final float prevX = this.unit.getX(); + final float prevY = this.unit.getY(); + final float deltaY = this.target.getY() - prevY; + final float deltaX = this.target.getX() - prevX; + final double goalAngleRad = Math.atan2(deltaY, deltaX); + float goalAngle = (float) Math.toDegrees(goalAngleRad); + if (goalAngle < 0) { + goalAngle += 360; + } + float facing = this.unit.getFacing(); + float delta = goalAngle - facing; + final float propulsionWindow = simulation.getGameplayConstants().getAttackHalfAngle(); + final float turnRate = simulation.getUnitData().getTurnRate(this.unit.getTypeId()); + + if (delta < -180) { + delta = 360 + delta; + } + if (delta > 180) { + delta = -360 + delta; + } + final float absDelta = Math.abs(delta); + + if ((absDelta <= 1.0) && (absDelta != 0)) { + this.unit.setFacing(goalAngle); + } + else { + float angleToAdd = Math.signum(delta) * (float) Math.toDegrees(turnRate); + if (absDelta < Math.abs(angleToAdd)) { + angleToAdd = delta; + } + facing += angleToAdd; + this.unit.setFacing(facing); + } + if (absDelta < propulsionWindow) { + this.wasWithinPropWindow = true; + } + else { + // If this happens, the unit is facing the wrong way, and has to turn before + // moving. + this.wasWithinPropWindow = false; } - facing += angleToAdd; - this.unit.setFacing(facing); - } - if (absDelta < propulsionWindow) { - this.wasWithinPropWindow = true; } else { - // If this happens, the unit is facing the wrong way, and has to turn before - // moving. - this.wasWithinPropWindow = false; + this.wasWithinPropWindow = true; } final int cooldownEndTime = this.unit.getCooldownEndTime(); @@ -108,9 +122,22 @@ public class CAttackOrder implements COrder { if (this.wasWithinPropWindow) { if (this.damagePointLaunchTime != 0) { if (currentTurnTick >= this.damagePointLaunchTime) { - final int minDamage = this.unitAttack.getMinDamage(); - final int maxDamage = this.unitAttack.getMaxDamage(); - final int damage = simulation.getSeededRandom().nextInt(maxDamage - minDamage) + minDamage; + int minDamage = this.unitAttack.getMinDamage(); + final int maxDamage = Math.max(0, this.unitAttack.getMaxDamage()); + if (minDamage > maxDamage) { + minDamage = maxDamage; + } + final int damage; + if (maxDamage == 0) { + damage = 0; + } + else if (minDamage == maxDamage) { + damage = minDamage; + } + else { + damage = simulation.getSeededRandom().nextInt(maxDamage - minDamage) + minDamage; + } + System.out.println(damage + " from " + minDamage + " to " + maxDamage); this.unitAttack.launch(simulation, this.unit, this.target, damage); this.damagePointLaunchTime = 0; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java index a21a4af..4a0e87d 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java @@ -86,6 +86,7 @@ public class MeleeUI implements CUnitStateListener { // TODO remove this & replace with FDF private final Texture activeButtonTexture; + private UIFrame inventoryCover; public MeleeUI(final DataSource dataSource, final Viewport uiViewport, final FreeTypeFontGenerator fontGenerator, final Scene uiScene, final War3MapViewer war3MapViewer, final RootFrameListener rootFrameListener) { @@ -110,6 +111,7 @@ public class MeleeUI implements CUnitStateListener { // ================================= this.rootFrame = new GameUI(this.dataSource, GameUI.loadSkin(this.dataSource, 0), this.uiViewport, this.fontGenerator, this.uiScene, this.war3MapViewer); + this.rootFrameListener.onCreate(this.rootFrame); try { this.rootFrame.loadTOCFile("UI\\FrameDef\\FrameDef.toc"); } @@ -142,11 +144,10 @@ public class MeleeUI implements CUnitStateListener { this.resourceBarLumberText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarLumberText", 0); this.resourceBarLumberText.setText("150"); this.resourceBarSupplyText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarSupplyText", 0); - this.resourceBarSupplyText.setText("153/100"); - this.resourceBarSupplyText.setColor(Color.RED); + this.resourceBarSupplyText.setText("12/100"); this.resourceBarUpkeepText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarUpkeepText", 0); - this.resourceBarUpkeepText.setText("High Upkeep"); - this.resourceBarUpkeepText.setColor(Color.RED); + this.resourceBarUpkeepText.setText("No Upkeep"); + this.resourceBarUpkeepText.setColor(Color.GREEN); // Create the Time Indicator (clock) this.timeIndicator = (SpriteFrame) this.rootFrame.createFrame("TimeOfDayIndicator", this.rootFrame, 0, 0); @@ -195,6 +196,8 @@ public class MeleeUI implements CUnitStateListener { this.armorInfoPanelIconValue = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconValue", 0); this.armorInfoPanelIconLevel = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconLevel", 0); + this.inventoryCover = this.rootFrame.createSimpleFrame("SmashConsoleInventoryCover", this.rootFrame, 0); + this.rootFrame.positionBounds(this.uiViewport); selectUnit(null); } diff --git a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java index 5ad7d58..1eaf29d 100644 --- a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java +++ b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java @@ -84,9 +84,9 @@ public class DesktopLauncher { config.gles30ContextMajorVersion = 3; config.gles30ContextMinorVersion = 3; config.samples = 16; - config.vSyncEnabled = false; - config.foregroundFPS = 0; - config.backgroundFPS = 0; +// config.vSyncEnabled = false; +// config.foregroundFPS = 0; +// config.backgroundFPS = 0; if ((arg.length > 0) && "-windowed".equals(arg[0])) { config.fullscreen = false; } diff --git a/resources/UI/FrameDef/SmashFrameDef.toc b/resources/UI/FrameDef/SmashFrameDef.toc index a67d6b6..71053d7 100644 --- a/resources/UI/FrameDef/SmashFrameDef.toc +++ b/resources/UI/FrameDef/SmashFrameDef.toc @@ -1,2 +1,3 @@ UI\FrameDef\SmashUI\TimeOfDayIndicator.fdf UI\FrameDef\SmashUI\UnitPortrait.fdf +UI\FrameDef\SmashUI\InventoryCover.fdf diff --git a/resources/UI/FrameDef/SmashUI/InventoryCover.fdf b/resources/UI/FrameDef/SmashUI/InventoryCover.fdf new file mode 100644 index 0000000..9bad994 --- /dev/null +++ b/resources/UI/FrameDef/SmashUI/InventoryCover.fdf @@ -0,0 +1,14 @@ +Frame "SIMPLEFRAME" "SmashConsoleInventoryCover" { + DecorateFileNames, + SetAllPoints, + + // The top of the UI console + Texture "SmashConsoleInventoryCoverTexture" { + File "ConsoleInventoryCoverTexture", + Width 0.128, + Height 0.256, + AlphaMode "ALPHAKEY", + Anchor BOTTOMLEFT,0.472,0.0, + } + +} From 3dc8aff71640fee8ad0794d3136f2fff5fc9a952 Mon Sep 17 00:00:00 2001 From: Retera Date: Thu, 8 Oct 2020 02:32:20 -0400 Subject: [PATCH 050/116] Update bounding to fix phoenix attack --- core/assets/warsmash.ini | 36 +++++++++---------- core/assets/warsmash127.ini | 25 ------------- core/assets/warsmash131.ini | 3 +- core/assets/warsmashPRSCMOD.ini | 26 ++++++++++++++ .../viewer5/handlers/mdx/MdxModel.java | 9 ++++- 5 files changed, 54 insertions(+), 45 deletions(-) delete mode 100644 core/assets/warsmash127.ini create mode 100644 core/assets/warsmashPRSCMOD.ini diff --git a/core/assets/warsmash.ini b/core/assets/warsmash.ini index e20aad9..6e6fe50 100644 --- a/core/assets/warsmash.ini +++ b/core/assets/warsmash.ini @@ -1,26 +1,26 @@ -// This is the Warsmash INI file for Project Revolution -// PRSCMOD - [DataSources] -Count=9 +Count=7 Type00=MPQ -Path00="E:\Games\Warcraft III Project Revolution\War3\The Sheep Attack\war3.mpq" +Path00="E:\Games\Warcraft III Patch 1.22\war3.mpq" Type01=MPQ -Path01="E:\Games\Warcraft III Project Revolution\War3\The Sheep Attack\War3x.mpq" +Path01="E:\Games\Warcraft III Patch 1.22\War3x.mpq" Type02=MPQ -Path02="E:\Games\Warcraft III Project Revolution\War3\The Sheep Attack\War3xlocal.mpq" +Path02="E:\Games\Warcraft III Patch 1.22\War3xlocal.mpq" Type03=MPQ -Path03="E:\Games\Warcraft III Project Revolution\War3\The Sheep Attack\war3patch.mpq" -Type04=MPQ -Path04="E:\Games\Warcraft III Project Revolution\PRSCMOD\Revolution.mpq" -Type05=MPQ -Path05="E:\Games\Warcraft III Project Revolution\PRSCMOD\Sound.mpq" +Path03="E:\Games\Warcraft III Patch 1.22\War3Patch.mpq" +Type04=Folder +Path04="..\..\resources" +Type05=Folder +Path05="E:\Backups\Warsmash\Data" Type06=Folder -Path06="E:\Games\Warcraft III Project Revolution\ProjectRevolusmash" -Type07=Folder -Path07="..\..\resources" -Type08=Folder -Path08="E:\Games\Warcraft III Project Revolution\PRSCMOD\PR-Maps" +Path06="." [Map] -FilePath="ProjectRevolusmash.w3x" \ No newline at end of file +//FilePath="CombatUnitTests.w3x" +//FilePath="PitchRoll.w3x" +//FilePath="PeonStartingBase.w3x" +//FilePath="DungeonGoldMine.w3m" +//FilePath="PlayerPeasants.w3m" +//FilePath="FireLord.w3x" +//FilePath="Maps\Campaign\NightElf03.w3m" +FilePath="PhoenixAttack.w3x" \ No newline at end of file diff --git a/core/assets/warsmash127.ini b/core/assets/warsmash127.ini deleted file mode 100644 index cbfd76d..0000000 --- a/core/assets/warsmash127.ini +++ /dev/null @@ -1,25 +0,0 @@ -[DataSources] -Count=7 -Type00=MPQ -Path00="E:\Games\Warcraft III Patch 1.22\war3.mpq" -Type01=MPQ -Path01="E:\Games\Warcraft III Patch 1.22\War3x.mpq" -Type02=MPQ -Path02="E:\Games\Warcraft III Patch 1.22\War3xlocal.mpq" -Type03=MPQ -Path03="E:\Games\Warcraft III Patch 1.22\War3Patch.mpq" -Type04=Folder -Path04="..\..\resources" -Type05=Folder -Path05="E:\Backups\Warsmash\Data" -Type06=Folder -Path06="." - -[Map] -//FilePath="CombatUnitTests.w3x" -//FilePath="PitchRoll.w3x" -FilePath="PeonStartingBase.w3x" -//FilePath="DungeonGoldMine.w3m" -//FilePath="PlayerPeasants.w3m" -//FilePath="FireLord.w3x" -//FilePath="Maps\Campaign\NightElf03.w3m" \ No newline at end of file diff --git a/core/assets/warsmash131.ini b/core/assets/warsmash131.ini index af6c427..0133118 100644 --- a/core/assets/warsmash131.ini +++ b/core/assets/warsmash131.ini @@ -17,5 +17,6 @@ Path04="." //FilePath="Maps\Campaign\NightElf03.w3m" //FilePath="PrivateDontShare/Cult 8.w3x" //FilePath="TorchLight2.w3x" -FilePath="OrcAssault.w3x" +//FilePath="OrcAssault.w3x" //FilePath="PeonStartingBase.w3x" +FilePath="PhoenixAttack.w3x" diff --git a/core/assets/warsmashPRSCMOD.ini b/core/assets/warsmashPRSCMOD.ini new file mode 100644 index 0000000..e20aad9 --- /dev/null +++ b/core/assets/warsmashPRSCMOD.ini @@ -0,0 +1,26 @@ +// This is the Warsmash INI file for Project Revolution +// PRSCMOD + +[DataSources] +Count=9 +Type00=MPQ +Path00="E:\Games\Warcraft III Project Revolution\War3\The Sheep Attack\war3.mpq" +Type01=MPQ +Path01="E:\Games\Warcraft III Project Revolution\War3\The Sheep Attack\War3x.mpq" +Type02=MPQ +Path02="E:\Games\Warcraft III Project Revolution\War3\The Sheep Attack\War3xlocal.mpq" +Type03=MPQ +Path03="E:\Games\Warcraft III Project Revolution\War3\The Sheep Attack\war3patch.mpq" +Type04=MPQ +Path04="E:\Games\Warcraft III Project Revolution\PRSCMOD\Revolution.mpq" +Type05=MPQ +Path05="E:\Games\Warcraft III Project Revolution\PRSCMOD\Sound.mpq" +Type06=Folder +Path06="E:\Games\Warcraft III Project Revolution\ProjectRevolusmash" +Type07=Folder +Path07="..\..\resources" +Type08=Folder +Path08="E:\Games\Warcraft III Project Revolution\PRSCMOD\PR-Maps" + +[Map] +FilePath="ProjectRevolusmash.w3x" \ No newline at end of file diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java index 27bac4f..eac8ed4 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java @@ -87,7 +87,14 @@ public class MdxModel extends com.etheller.warsmash.viewer5.Model { // Initialize the bounds. final Extent extent = parser.getExtent(); - this.bounds.fromExtents(extent.getMin(), extent.getMax()); + final float[] min = extent.getMin(); + final float[] max = extent.getMax(); + for (int i = 0; i < 3; i++) { + if (min[i] > max[i]) { + min[i] = max[i] = 0; + } + } + this.bounds.fromExtents(min, max); // Sequences this.sequences.addAll(parser.getSequences()); From faf1cb4b72ed10a2a0a282a3a7c0296e1eb9e4b5 Mon Sep 17 00:00:00 2001 From: Retera Date: Tue, 13 Oct 2020 01:19:09 -0400 Subject: [PATCH 051/116] Some fun updates to lighting system --- core/assets/warsmash.ini | 14 ++++---- .../etheller/warsmash/viewer5/Shaders.java | 35 +++++++++---------- .../handlers/mdx/MdxComplexInstance.java | 12 ++++--- .../viewer5/handlers/mdx/SdSequence.java | 10 +++--- .../w3x/W3xScenePortraitLightManager.java | 6 ++-- .../viewer5/handlers/w3x/War3MapViewer.java | 6 ++++ 6 files changed, 47 insertions(+), 36 deletions(-) diff --git a/core/assets/warsmash.ini b/core/assets/warsmash.ini index 6e6fe50..e2a1669 100644 --- a/core/assets/warsmash.ini +++ b/core/assets/warsmash.ini @@ -1,17 +1,17 @@ [DataSources] Count=7 Type00=MPQ -Path00="E:\Games\Warcraft III Patch 1.22\war3.mpq" +Path00="D:\Games\Warcraft III Patch 1.22\war3.mpq" Type01=MPQ -Path01="E:\Games\Warcraft III Patch 1.22\War3x.mpq" +Path01="D:\Games\Warcraft III Patch 1.22\War3x.mpq" Type02=MPQ -Path02="E:\Games\Warcraft III Patch 1.22\War3xlocal.mpq" +Path02="D:\Games\Warcraft III Patch 1.22\War3xlocal.mpq" Type03=MPQ -Path03="E:\Games\Warcraft III Patch 1.22\War3Patch.mpq" +Path03="D:\Games\Warcraft III Patch 1.22\War3Patch.mpq" Type04=Folder Path04="..\..\resources" Type05=Folder -Path05="E:\Backups\Warsmash\Data" +Path05="D:\Backups\Warsmash\Data" Type06=Folder Path06="." @@ -23,4 +23,6 @@ Path06="." //FilePath="PlayerPeasants.w3m" //FilePath="FireLord.w3x" //FilePath="Maps\Campaign\NightElf03.w3m" -FilePath="PhoenixAttack.w3x" \ No newline at end of file +//FilePath="PhoenixAttack.w3x" +FilePath="LightEnvironmentTest.w3x" +//FilePath="TorchLight2.w3x" \ No newline at end of file diff --git a/core/src/com/etheller/warsmash/viewer5/Shaders.java b/core/src/com/etheller/warsmash/viewer5/Shaders.java index 9981c2b..2adfbf8 100644 --- a/core/src/com/etheller/warsmash/viewer5/Shaders.java +++ b/core/src/com/etheller/warsmash/viewer5/Shaders.java @@ -86,32 +86,31 @@ public class Shaders { + ", lightDirection), 0.0, 1.0);\r\n" + // " if(lightFactorContribution.r > 1.0 || lightFactorContribution.g > 1.0 || lightFactorContribution.b > 1.0) {\r\n" + // - " lightFactorContribution = normalize(lightFactorContribution);\r\n" + // + " lightFactorContribution = clamp(lightFactorContribution, 0.0, 1.0);\r\n" + // " }\r\n" + // " lightFactor += lightFactorContribution + lightAmbColor.a * lightAmbColor.rgb;\r\n" + // " } else {\r\n" + // " // Omnidirectional light;\r\n" + // " vec3 deltaBtwn = " + positionName + " - lightPosition.xyz;\r\n" + // - " float dist = length(" + positionName + " - vec3(lightPosition." + (terrain ? "xyw" : "xyz") - + "));\r\n" + // - " float attenuationStart = lightExtra.y;\r\n" + // - " float attenuationEnd = lightExtra.z;\r\n" + // - " if( dist <= attenuationEnd ) {\r\n" + // - " float attenuationDist = clamp((dist-attenuationStart), 0.001, (attenuationEnd-attenuationStart));\r\n" - + // - " float attenuationFactor = 1.0/(attenuationDist);\r\n" + // - " vec3 lightDirection = -deltaBtwn;\r\n" + // - " vec3 lightFactorContribution = attenuationFactor * lightColor.a * lightColor.rgb * clamp(dot(" + " float dist = length(" + positionName + " - vec3(lightPosition." + (terrain ? "xyz" : "xyz") + + ")) / 64.0 + 1.0;\r\n" + // + " vec3 lightDirection = normalize(-deltaBtwn);\r\n" + // + " vec3 lightFactorContribution = (lightColor.a/(pow(dist, 2.0))) * lightColor.rgb * clamp(dot(" + normalName + ", lightDirection), 0.0, 1.0);\r\n" + // - " if(lightFactorContribution.r > 1.0 || lightFactorContribution.g > 1.0 || lightFactorContribution.b > 1.0) {\r\n" + " if(lightFactorContribution.r > 1.0 || lightFactorContribution.g > 1.0 || lightFactorContribution.b > 1.0) {\r\n" + // - " lightFactorContribution = normalize(lightFactorContribution);\r\n" + // - " }\r\n" + // - " lightFactor += lightFactorContribution + attenuationFactor * lightAmbColor.a * lightAmbColor.rgb;\r\n" - + // - " \r\n" + // + " lightFactorContribution = clamp(lightFactorContribution, 0.0, 1.0);\r\n" + // " }\r\n" + // + " lightFactor += lightFactorContribution + (lightAmbColor.a/(pow(dist, 2.0))) * lightAmbColor.rgb;\r\n" + + // " }\r\n" + // - " }\r\n"; + " }\r\n";// + // + +// " vec4 sRGB = vec4(lightFactor, 1.0);" + // +// " bvec4 cutoff = lessThan(sRGB, vec4(0.04045));" + // +// " vec4 higher = pow((sRGB + vec4(0.055))/vec4(1.055), vec4(2.4));" + // +// " vec4 lower = sRGB/vec4(12.92);" + // +// "" + // +// " lightFactor = (higher * (vec4(1.0) - vec4(cutoff)) + lower * vec4(cutoff)).xyz;"; } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java index b480bfc..b08c9de 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java @@ -763,10 +763,12 @@ public class MdxComplexInstance extends ModelInstance { } public void setFrameByRatio(final float ratioOfAnimationCompleted) { - final Sequence currentlyPlayingSequence = ((MdxModel) this.model).sequences.get(this.sequence); - this.floatingFrame = currentlyPlayingSequence.getInterval()[0] - + ((currentlyPlayingSequence.getInterval()[1] - currentlyPlayingSequence.getInterval()[0]) - * ratioOfAnimationCompleted); - this.frame = (int) this.floatingFrame; + if (this.sequence != -1) { + final Sequence currentlyPlayingSequence = ((MdxModel) this.model).sequences.get(this.sequence); + this.floatingFrame = currentlyPlayingSequence.getInterval()[0] + + ((currentlyPlayingSequence.getInterval()[1] - currentlyPlayingSequence.getInterval()[0]) + * ratioOfAnimationCompleted); + this.frame = (int) this.floatingFrame; + } } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SdSequence.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SdSequence.java index 956e693..58c91da 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SdSequence.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SdSequence.java @@ -102,7 +102,8 @@ public final class SdSequence { if (!this.constant) { // If there is no opening keyframe for this sequence, inject one // with the default value. - if (framesBuilder.get(0) != start) { + final boolean hasStart = framesBuilder.get(0) == start; + if (!hasStart) { framesBuilder.add(start); valuesBuilder.add(defval); @@ -116,11 +117,12 @@ public final class SdSequence { // with the default value. if (framesBuilder.get(framesBuilder.size() - 1) != end) { framesBuilder.add(end); - valuesBuilder.add(valuesBuilder.get(valuesBuilder.size() - 1)); + final int sourceIndex = hasStart ? 0 : (valuesBuilder.size() - 1); + valuesBuilder.add(valuesBuilder.get(sourceIndex)); if (interpolationType > 1) { - inTansBuilder.add(inTansBuilder.get(inTansBuilder.size() - 1)); - outTansBuilder.add(outTansBuilder.get(outTansBuilder.size() - 1)); + inTansBuilder.add(inTansBuilder.get(sourceIndex)); + outTansBuilder.add(outTansBuilder.get(sourceIndex)); } } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xScenePortraitLightManager.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xScenePortraitLightManager.java index a643de6..d5fa535 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xScenePortraitLightManager.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xScenePortraitLightManager.java @@ -52,9 +52,9 @@ public class W3xScenePortraitLightManager implements SceneLightManager, W3xScene this.unitLightCount = 0; this.lightDataCopyHeap.clear(); int offset = 0; - if (this.viewer.dncTarget != null) { - if (!this.viewer.dncTarget.lights.isEmpty()) { - this.viewer.dncTarget.lights.get(0).bind(0, this.lightDataCopyHeap); + if (this.viewer.dncUnitDay != null) { + if (!this.viewer.dncUnitDay.lights.isEmpty()) { + this.viewer.dncUnitDay.lights.get(0).bind(0, this.lightDataCopyHeap); offset += 16; this.unitLightCount++; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index c2b19ef..eeda60f 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -158,6 +158,7 @@ public class War3MapViewer extends ModelViewer { private DataTable unitGlobalStrings; private MdxComplexInstance confirmationInstance; public MdxComplexInstance dncUnit; + public MdxComplexInstance dncUnitDay; public MdxComplexInstance dncTerrain; public MdxComplexInstance dncTarget; public CSimulation simulation; @@ -927,6 +928,8 @@ public class War3MapViewer extends ModelViewer { this.dncUnit.setFrameByRatio( this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours()); this.dncUnit.update(rawDeltaTime, null); + this.dncUnitDay.setFrameByRatio(0.5f); + this.dncUnitDay.update(rawDeltaTime, null); this.dncTarget.setFrameByRatio( this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours()); this.dncTarget.update(rawDeltaTime, null); @@ -1281,6 +1284,9 @@ public class War3MapViewer extends ModelViewer { this.dncUnit = (MdxComplexInstance) unitDNCModel.addInstance(); this.dncUnit.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); this.dncUnit.setSequence(0); + this.dncUnitDay = (MdxComplexInstance) unitDNCModel.addInstance(); + this.dncUnitDay.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + this.dncUnitDay.setSequence(0); final MdxModel targetDNCModel = (MdxModel) load( mdx("Environment\\DNC\\DNCLordaeron\\DNCLordaeronTarget\\DNCLordaeronTarget.mdl"), PathSolver.DEFAULT, null); From e919f359049a5103375b7e8fb0e1b57737f5dd2d Mon Sep 17 00:00:00 2001 From: Retera Date: Tue, 20 Oct 2020 08:39:35 -0400 Subject: [PATCH 052/116] Update with fixes for ui --- core/assets/warsmash.ini | 8 +- .../etheller/warsmash/WarsmashGdxMapGame.java | 528 +++++------------- .../etheller/warsmash/parsers/fdf/GameUI.java | 63 ++- .../fdf/frames/AbstractRenderableFrame.java | 23 +- .../parsers/fdf/frames/AbstractUIFrame.java | 11 + .../parsers/fdf/frames/SpriteFrame.java | 61 +- .../warsmash/parsers/fdf/frames/UIFrame.java | 4 + .../warsmash/viewer5/ModelInstance.java | 2 + .../com/etheller/warsmash/viewer5/Scene.java | 3 +- .../etheller/warsmash/viewer5/Shaders.java | 7 +- .../viewer5/handlers/mdx/AnimatedObject.java | 2 +- .../viewer5/handlers/mdx/BatchGroup.java | 3 +- .../viewer5/handlers/mdx/GenericObject.java | 2 +- .../viewer5/handlers/mdx/LightInstance.java | 14 +- .../handlers/mdx/MdxComplexInstance.java | 7 + .../viewer5/handlers/mdx/MdxShaders.java | 8 +- .../handlers/mdx/MdxSimpleInstance.java | 4 + .../viewer5/handlers/mdx/Particle2.java | 7 +- .../handlers/mdx/TextureAnimation.java | 2 +- .../viewer5/handlers/w3x/AnimationTokens.java | 2 +- ...tructable.java => RenderDestructable.java} | 4 +- .../w3x/{Doodad.java => RenderDoodad.java} | 4 +- .../viewer5/handlers/w3x/SequenceUtils.java | 4 + .../w3x/W3xScenePortraitLightManager.java | 40 +- .../viewer5/handlers/w3x/W3xShaders.java | 10 +- .../viewer5/handlers/w3x/War3MapViewer.java | 111 ++-- .../handlers/w3x/camera/CameraManager.java | 62 ++ .../w3x/camera/CameraPanControls.java | 10 + .../handlers/w3x/camera/CameraPreset.java | 85 +++ .../handlers/w3x/camera/CameraRates.java | 20 + .../w3x/camera/GameCameraManager.java | 154 +++++ .../w3x/camera/PortraitCameraManager.java | 52 ++ .../handlers/w3x/environment/Terrain.java | 14 +- .../w3x/environment/TerrainShaders.java | 14 +- .../w3x/rendersim/CommandCardIcon.java | 33 -- .../handlers/w3x/rendersim/RenderUnit.java | 93 ++- .../w3x/rendersim/RenderUnitTypeData.java | 25 + .../w3x/rendersim/ability/AbilityDataUI.java | 112 ++++ .../w3x/rendersim/ability/AbilityIconUI.java | 25 + .../w3x/rendersim/ability/IconUI.java | 34 ++ .../commandbuttons/AbilityCommandButton.java | 94 ++++ .../commandbuttons/BasicCommandButton.java | 95 ++++ .../commandbuttons/CommandButton.java | 38 ++ .../commandbuttons/CommandButtonListener.java | 38 ++ .../CommandCardPopulatingAbilityVisitor.java | 51 ++ .../handlers/w3x/simulation/CSimulation.java | 25 +- .../handlers/w3x/simulation/CUnit.java | 9 +- .../w3x/simulation/abilities/CAbility.java | 8 +- .../simulation/abilities/CAbilityAttack.java | 58 +- .../abilities/CAbilityHoldPosition.java | 69 --- .../simulation/abilities/CAbilityMove.java | 80 ++- .../simulation/abilities/CAbilityPatrol.java | 70 --- .../simulation/abilities/CAbilityStop.java | 69 --- .../abilities/CAbilityToggleableView.java | 5 + .../simulation/abilities/CAbilityView.java | 13 +- .../simulation/abilities/CAbilityVisitor.java | 15 + .../w3x/simulation/data/CUnitData.java | 22 +- .../w3x/simulation/orders/CAttackOrder.java | 11 +- .../w3x/simulation/orders/CMoveOrder.java | 10 +- .../w3x/simulation/orders/CPatrolOrder.java | 58 ++ .../w3x/simulation/orders/OrderIds.java | 457 +++++++++++++++ .../util/AbilityTargetCheckReceiver.java | 2 + .../CWidgetAbilityTargetCheckReceiver.java | 5 + .../util/PointAbilityTargetCheckReceiver.java | 6 +- .../handlers/w3x/ui/CommandCardIcon.java | 88 +++ .../viewer5/handlers/w3x/ui/MeleeUI.java | 270 +++++++-- .../handlers/w3x/ui/MeleeUIMinimap.java | 62 ++ 67 files changed, 2516 insertions(+), 879 deletions(-) rename core/src/com/etheller/warsmash/viewer5/handlers/w3x/{Destructable.java => RenderDestructable.java} (82%) rename core/src/com/etheller/warsmash/viewer5/handlers/w3x/{Doodad.java => RenderDoodad.java} (97%) create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/camera/CameraManager.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/camera/CameraPanControls.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/camera/CameraPreset.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/camera/CameraRates.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/camera/GameCameraManager.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/camera/PortraitCameraManager.java delete mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/CommandCardIcon.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnitTypeData.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityDataUI.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityIconUI.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/IconUI.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/AbilityCommandButton.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/BasicCommandButton.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandButton.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandButtonListener.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java delete mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityHoldPosition.java delete mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityPatrol.java delete mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityStop.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityToggleableView.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityVisitor.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CPatrolOrder.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/OrderIds.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUIMinimap.java diff --git a/core/assets/warsmash.ini b/core/assets/warsmash.ini index e2a1669..ec2d2f3 100644 --- a/core/assets/warsmash.ini +++ b/core/assets/warsmash.ini @@ -24,5 +24,9 @@ Path06="." //FilePath="FireLord.w3x" //FilePath="Maps\Campaign\NightElf03.w3m" //FilePath="PhoenixAttack.w3x" -FilePath="LightEnvironmentTest.w3x" -//FilePath="TorchLight2.w3x" \ No newline at end of file +//FilePath="LightEnvironmentTest.w3x" +//FilePath="TorchLight2.w3x" +//FilePath="OrcAssault.w3x" +//FilePath="FrostyVsFarm.w3m" +//FilePath="ModelTest.w3x" +FilePath="SpinningSample.w3x" \ No newline at end of file diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java index 41bef64..71542ff 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java @@ -1,15 +1,13 @@ package com.etheller.warsmash; import java.io.IOException; +import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.IntBuffer; import java.util.ArrayList; -import java.util.LinkedList; import java.util.List; -import javax.imageio.ImageIO; - import com.badlogic.gdx.ApplicationAdapter; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Input; @@ -26,8 +24,7 @@ import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator; import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator.FreeTypeFontParameter; import com.badlogic.gdx.graphics.glutils.ShapeRenderer; -import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType; -import com.badlogic.gdx.math.Quaternion; +import com.badlogic.gdx.math.Matrix4; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.math.Vector3; @@ -43,27 +40,29 @@ import com.etheller.warsmash.units.DataTable; import com.etheller.warsmash.units.Element; import com.etheller.warsmash.util.DataSourceFileHandle; import com.etheller.warsmash.util.ImageUtils; -import com.etheller.warsmash.util.WarsmashConstants; -import com.etheller.warsmash.viewer5.Camera; import com.etheller.warsmash.viewer5.CanvasProvider; +import com.etheller.warsmash.viewer5.Model; +import com.etheller.warsmash.viewer5.ModelInstance; +import com.etheller.warsmash.viewer5.ModelViewer; +import com.etheller.warsmash.viewer5.PathSolver; +import com.etheller.warsmash.viewer5.RenderBatch; import com.etheller.warsmash.viewer5.Scene; -import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; +import com.etheller.warsmash.viewer5.TextureMapper; +import com.etheller.warsmash.viewer5.handlers.ModelHandler; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; -import com.etheller.warsmash.viewer5.handlers.mdx.ReplaceableIds; -import com.etheller.warsmash.viewer5.handlers.tga.TgaFile; import com.etheller.warsmash.viewer5.handlers.w3x.UnitSound; import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; +import com.etheller.warsmash.viewer5.handlers.w3x.camera.CameraPreset; +import com.etheller.warsmash.viewer5.handlers.w3x.camera.CameraRates; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderUnit; import com.etheller.warsmash.viewer5.handlers.w3x.ui.MeleeUI; public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProvider, InputProcessor { - private static final double HORIZONTAL_ANGLE_INCREMENT = Math.PI / 60; - + private static final boolean ENABLE_MUSIC = false; private static final Vector3 clickLocationTemp = new Vector3(); private static final Vector2 clickLocationTemp2 = new Vector2(); private DataSource codebase; private War3MapViewer viewer; - private GameCameraManager cameraManager; private final Rectangle tempRect = new Rectangle(); // libGDX stuff @@ -74,22 +73,18 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv private ExtendViewport uiViewport; private GlyphLayout glyphLayout; - private Texture consoleUITexture; private int selectedSoundCount = 0; - private Rectangle minimap; - private Rectangle minimapFilledArea; - - private final Texture[] teamColors = new Texture[WarsmashConstants.MAX_PLAYERS]; private Texture solidGreenTexture; private ShapeRenderer shapeRenderer; - private boolean showTalentTree; - private final List messages = new LinkedList<>(); private MdxModel timeIndicator; private final DataTable warsmashIni; + private Scene uiScene; + private MeleeUI meleeUI; + public WarsmashGdxMapGame(final DataTable warsmashIni) { this.warsmashIni = warsmashIni; } @@ -158,14 +153,12 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv cameraListenerData.getFieldFloatValue("ListenerDistance", i), cameraListenerData.getFieldFloatValue("ListenerAOA", i)); } - this.cameraManager = new GameCameraManager(cameraPresets); - - this.cameraManager.setupCamera(this.viewer.worldScene); System.out.println("Loaded"); Gdx.gl30.glClearColor(0.0f, 0.0f, 0.0f, 1); // TODO remove white background Gdx.gl30.glEnable(GL30.GL_SCISSOR_TEST); + final Scene portraitScene = this.viewer.addSimpleScene(); this.uiScene = this.viewer.addSimpleScene(); this.uiScene.alpha = true; @@ -197,56 +190,13 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.batch = new SpriteBatch(); // this.consoleUITexture = new Texture(new DataSourceFileHandle(this.viewer.dataSource, "AlphaUi.png")); - if (this.viewer.dataSource.has("war3mapMap.tga")) { - try { - this.minimapTexture = ImageUtils.getTextureNoColorCorrection(TgaFile.readTGA("war3mapMap.tga", - this.viewer.dataSource.getResourceAsStream("war3mapMap.tga"))); - } - catch (final IOException e) { - System.err.println("Could not load minimap TGA file"); - e.printStackTrace(); - } - } - else if (this.viewer.dataSource.has("war3mapMap.blp")) { - try { - this.minimapTexture = ImageUtils - .getTexture(ImageIO.read(this.viewer.dataSource.getResourceAsStream("war3mapMap.blp"))); - } - catch (final IOException e) { - System.err.println("Could not load minimap BLP file"); - e.printStackTrace(); - } - } - - for (int i = 0; i < this.teamColors.length; i++) { - this.teamColors[i] = ImageUtils.getBLPTexture(this.viewer.dataSource, - "ReplaceableTextures\\" + ReplaceableIds.getPathString(1) + ReplaceableIds.getIdString(i) + ".blp"); - } this.solidGreenTexture = ImageUtils.getBLPTexture(this.viewer.dataSource, "ReplaceableTextures\\TeamColor\\TeamColor06.blp"); Gdx.input.setInputProcessor(this); - this.minimap = new Rectangle(18.75f, 13.75f, 278.75f, 276.25f); - final Rectangle playableMapArea = this.viewer.terrain.getPlayableMapArea(); - final float worldWidth = playableMapArea.getWidth(); - final float worldHeight = playableMapArea.getHeight(); - final float worldSize = Math.max(worldWidth, worldHeight); - final float minimapFilledWidth = (worldWidth / worldSize) * this.minimap.width; - final float minimapFilledHeight = (worldHeight / worldSize) * this.minimap.height; - - this.minimapFilledArea = new Rectangle(this.minimap.x + ((this.minimap.width - minimapFilledWidth) / 2), - this.minimap.y + ((this.minimap.height - minimapFilledHeight) / 2), minimapFilledWidth, - minimapFilledHeight); - - if (this.viewer.startLocations[0] != null) { - this.cameraManager.target.x = this.viewer.startLocations[0].x; - this.cameraManager.target.y = this.viewer.startLocations[0].y; - } - this.shapeRenderer = new ShapeRenderer(); - this.talentTreeWindow = new Rectangle(100, 300, 1400, 800); // Jass2.loadJUI(this.codebase, this.uiViewport, fontGenerator, this.uiScene, this.viewer, // new RootFrameListener() { @@ -255,30 +205,45 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv // WarsmashGdxMapGame.this.gameUI = rootFrame; // } // }, "Scripts\\common.jui", "Scripts\\melee.jui"); - - this.meleeUI = new MeleeUI(this.codebase, this.uiViewport, fontGenerator, this.uiScene, this.viewer, - new RootFrameListener() { + final Element cameraRatesElement = this.viewer.miscData.get("CameraRates"); + final CameraRates cameraRates = new CameraRates(cameraRatesElement.getFieldFloatValue("AOA"), + cameraRatesElement.getFieldFloatValue("FOV"), cameraRatesElement.getFieldFloatValue("Rotation"), + cameraRatesElement.getFieldFloatValue("Distance"), cameraRatesElement.getFieldFloatValue("Forward"), + cameraRatesElement.getFieldFloatValue("Strafe")); + this.meleeUI = new MeleeUI(this.codebase, this.uiViewport, fontGenerator, this.uiScene, portraitScene, + cameraPresets, cameraRates, this.viewer, new RootFrameListener() { @Override public void onCreate(final GameUI rootFrame) { WarsmashGdxMapGame.this.viewer.setGameUI(rootFrame); - final String musicField = rootFrame.getSkinField("Music_V1"); - final String[] musics = musicField.split(";"); - final String musicPath = musics[(int) (Math.random() * musics.length)]; - final Music music = Gdx.audio.newMusic( - new DataSourceFileHandle(WarsmashGdxMapGame.this.viewer.dataSource, musicPath)); - music.setVolume(0.2f); - music.setLooping(true); - music.play(); + if (ENABLE_MUSIC) { + final String musicField = rootFrame.getSkinField("Music_V1"); + final String[] musics = musicField.split(";"); + final String musicPath = musics[(int) (Math.random() * musics.length)]; + final Music music = Gdx.audio.newMusic( + new DataSourceFileHandle(WarsmashGdxMapGame.this.viewer.dataSource, musicPath)); + music.setVolume(0.2f); + music.setLooping(true); + music.play(); + } } }); + final ModelInstance libgdxContentInstance = new LibGDXContentLayerModel(null, this.viewer, "", + this.viewer.mapPathSolver, "").addInstance(); + libgdxContentInstance.setScene(this.uiScene); this.meleeUI.main(); fontGenerator.dispose(); updateUIScene(); - this.meleeUI.resize(); resize(Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); + + try { + this.viewer.loadAfterUI(); + } + catch (final IOException e) { + throw new RuntimeException(e); + } } private void updateUIScene() { @@ -304,21 +269,9 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv Gdx.gl30.glEnable(GL30.GL_SCISSOR_TEST); final float deltaTime = Gdx.graphics.getDeltaTime(); Gdx.gl30.glBindVertexArray(WarsmashGdxGame.VAO); - this.cameraManager.target.add(this.cameraVelocity.x * deltaTime, this.cameraVelocity.y * deltaTime, 0); - this.cameraManager.target.z = (Math.max( - this.viewer.terrain.getGroundHeight(this.cameraManager.target.x, this.cameraManager.target.y), - this.viewer.terrain.getWaterHeight(this.cameraManager.target.x, this.cameraManager.target.y)) - 256) - + this.cameraManager.presets[this.cameraManager.currentPreset].height + 256; - this.cameraManager.updateCamera(); - this.meleeUI.updatePortrait(); + this.meleeUI.update(deltaTime); this.viewer.updateAndRender(); -// gl.glDrawElements(GL20.GL_TRIANGLES, this.elements, GL20.GL_UNSIGNED_SHORT, this.faceOffset); - -// this.batch.begin(); -// this.font.draw(this.batch, Integer.toString(Gdx.graphics.getFramesPerSecond()), 0, 0); -// this.batch.end(); - Gdx.gl30.glDisable(GL30.GL_SCISSOR_TEST); Gdx.gl30.glDisable(GL30.GL_CULL_FACE); @@ -326,6 +279,18 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.viewer.webGL.useShaderProgram(null); Gdx.gl30.glActiveTexture(GL30.GL_TEXTURE0); + } + + private void renderLibGDXContent() { + + Gdx.gl30.glDisable(GL30.GL_SCISSOR_TEST); + + Gdx.gl30.glDisable(GL30.GL_CULL_FACE); + + this.viewer.webGL.useShaderProgram(null); + + Gdx.gl30.glActiveTexture(GL30.GL_TEXTURE0); + this.uiViewport.apply(); this.batch.setProjectionMatrix(this.uiCamera.combined); this.batch.begin(); @@ -334,54 +299,10 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv final String fpsString = "FPS: " + Gdx.graphics.getFramesPerSecond(); this.glyphLayout.setText(this.font, fpsString); this.font.draw(this.batch, fpsString, (this.uiViewport.getMinWorldWidth() - this.glyphLayout.width) / 2, 1100); - - this.batch.draw(this.minimapTexture, this.minimap.x, this.minimap.y, this.minimap.width, this.minimap.height); - - final Rectangle playableMapArea = this.viewer.terrain.getPlayableMapArea(); - for (final RenderUnit unit : this.viewer.units) { - if (unit.playerIndex >= WarsmashConstants.MAX_PLAYERS) { - System.err.println(unit.row.getName() + " at ( " + unit.location[0] + ", " + unit.location[1] + " )" - + " with " + unit.playerIndex); - unit.playerIndex -= 12; - } - final Texture minimapIcon = this.teamColors[unit.playerIndex]; - this.batch.draw(minimapIcon, - this.minimapFilledArea.x - + (((unit.location[0] - playableMapArea.getX()) / (playableMapArea.getWidth())) - * this.minimapFilledArea.width), - this.minimapFilledArea.y - + (((unit.location[1] - playableMapArea.getY()) / (playableMapArea.getHeight())) - * this.minimapFilledArea.height), - 4, 4); - } this.batch.end(); - if (this.showTalentTree) { - this.shapeRenderer.setProjectionMatrix(this.uiCamera.combined); - - this.shapeRenderer.setColor(Color.BLACK); - this.shapeRenderer.begin(ShapeType.Filled); - this.shapeRenderer.rect(this.talentTreeWindow.x, this.talentTreeWindow.y, this.talentTreeWindow.width, - this.talentTreeWindow.height); - this.shapeRenderer.end(); - - this.shapeRenderer.setColor(Color.YELLOW); - this.shapeRenderer.begin(ShapeType.Line); - this.shapeRenderer.rect(100, 300, 1400, 800); - this.shapeRenderer.end(); - - this.batch.begin(); - - this.font.setColor(Color.YELLOW); - final String title = "Mage Talent Tree"; - this.glyphLayout.setText(this.font, title); - this.font.draw(this.batch, title, - this.talentTreeWindow.x + ((this.talentTreeWindow.width - this.glyphLayout.width) / 2), - (this.talentTreeWindow.y + this.talentTreeWindow.height) - 45); - - this.batch.end(); - } - + Gdx.gl30.glEnable(GL30.GL_SCISSOR_TEST); + Gdx.gl30.glBindVertexArray(WarsmashGdxGame.VAO); } @Override @@ -401,212 +322,34 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv @Override public void resize(final int width, final int height) { super.resize(width, height); + + this.uiViewport.update(width, height); + this.uiCamera.position.set(this.uiViewport.getMinWorldWidth() / 2, this.uiViewport.getMinWorldHeight() / 2, 0); + + this.meleeUI.resize(setupWorldFrameViewport(width, height)); + updateUIScene(); + + } + + private Rectangle setupWorldFrameViewport(final int width, final int height) { this.tempRect.x = 0; this.tempRect.width = width; final float topHeight = 0.02666f * height; final float bottomHeight = 0.21333f * height; this.tempRect.y = (int) bottomHeight; this.tempRect.height = height - (int) (topHeight + bottomHeight); - this.cameraManager.camera.viewport(this.tempRect); - final float portraitTestWidth = (100 / 640f) * width; - final float portraitTestHeight = (100 / 480f) * height; - - this.uiViewport.update(width, height); - this.uiCamera.position.set(this.uiViewport.getMinWorldWidth() / 2, this.uiViewport.getMinWorldHeight() / 2, 0); - - updateUIScene(); - - this.meleeUI.resize(); + return this.tempRect; } - public static abstract class CameraManager { - protected final float[] cameraPositionTemp = new float[3]; - protected final float[] cameraTargetTemp = new float[3]; - protected CanvasProvider canvas; - protected Camera camera; - protected float moveSpeed; - protected float rotationSpeed; - protected float zoomFactor; - protected float horizontalAngle; - protected float verticalAngle; - protected float distance; - protected Vector3 position; - protected Vector3 target; - protected Vector3 worldUp; - protected Vector3 vecHeap; - protected Quaternion quatHeap; - protected Quaternion quatHeap2; - - public CameraManager() { - } - - // An orbit camera setup example. - // Left mouse button controls the orbit itself. - // The right mouse button allows to move the camera and the point it's looking - // at on the XY plane. - // Scrolling zooms in and out. - public void setupCamera(final Scene scene) { - this.canvas = scene.viewer.canvas; - this.camera = scene.camera; - this.moveSpeed = 2; - this.rotationSpeed = (float) HORIZONTAL_ANGLE_INCREMENT; - this.zoomFactor = 0.1f; - this.horizontalAngle = 0;// (float) (Math.PI / 2); - this.verticalAngle = (float) Math.toRadians(34); - this.distance = 1650; - this.position = new Vector3(); - this.target = new Vector3(0, 0, 0); - this.worldUp = new Vector3(0, 0, 1); - this.vecHeap = new Vector3(); - this.quatHeap = new Quaternion(); - this.quatHeap2 = new Quaternion(); - - updateCamera(); - -// cameraUpdate(); - } - - public abstract void updateCamera(); - -// private void cameraUpdate() { -// -// } - } - - public static final class GameCameraManager extends CameraManager { - private final CameraPreset[] presets; - private int currentPreset = 0; - - protected boolean insertDown; - protected boolean deleteDown; - - public GameCameraManager(final CameraPreset[] presets) { - this.presets = presets; - } - - @Override - public void updateCamera() { - this.quatHeap2.idt(); - final CameraPreset cameraPreset = this.presets[this.currentPreset]; - this.quatHeap.idt(); - this.horizontalAngle = (float) Math - .toRadians(cameraPreset.getRotation(this.insertDown, this.deleteDown) - 90); - this.quatHeap.setFromAxisRad(0, 0, 1, this.horizontalAngle); - this.distance = Math.max(1200, cameraPreset.distance); - this.verticalAngle = (float) Math.toRadians(Math.min(335, cameraPreset.aoa) - 270); - this.quatHeap2.setFromAxisRad(1, 0, 0, this.verticalAngle); - this.quatHeap.mul(this.quatHeap2); - - this.position.set(0, 0, 1); - this.quatHeap.transform(this.position); - this.position.nor(); - this.position.scl(this.distance); - this.position = this.position.add(this.target); - this.camera.perspective((float) Math.toRadians(cameraPreset.fov / 2), this.camera.getAspect(), - cameraPreset.nearZ, cameraPreset.farZ); - - this.camera.moveToAndFace(this.position, this.target, this.worldUp); - } - } - - public static final class PortraitCameraManager extends CameraManager { - public com.etheller.warsmash.viewer5.handlers.mdx.Camera modelCamera; - protected MdxComplexInstance modelInstance; - - @Override - public void updateCamera() { - this.quatHeap.idt(); - this.quatHeap.setFromAxisRad(0, 0, 1, this.horizontalAngle); - this.quatHeap2.idt(); - this.quatHeap2.setFromAxisRad(1, 0, 0, this.verticalAngle); - this.quatHeap.mul(this.quatHeap2); - - this.position.set(0, 0, 1); - this.quatHeap.transform(this.position); - this.position.scl(this.distance); - this.position = this.position.add(this.target); - if (this.modelCamera != null) { - this.modelCamera.getPositionTranslation(this.cameraPositionTemp, this.modelInstance.sequence, - this.modelInstance.frame, this.modelInstance.counter); - this.modelCamera.getTargetTranslation(this.cameraTargetTemp, this.modelInstance.sequence, - this.modelInstance.frame, this.modelInstance.counter); - - this.position.set(this.modelCamera.position); - this.target.set(this.modelCamera.targetPosition); - - this.position.add(this.cameraPositionTemp[0], this.cameraPositionTemp[1], this.cameraPositionTemp[2]); - this.target.add(this.cameraTargetTemp[0], this.cameraTargetTemp[1], this.cameraTargetTemp[2]); - this.camera.perspective(this.modelCamera.fieldOfView * 0.75f, this.camera.getAspect(), - this.modelCamera.nearClippingPlane, this.modelCamera.farClippingPlane); - } - else { - this.camera.perspective(70, this.camera.getAspect(), 100, 5000); - } - - this.camera.moveToAndFace(this.position, this.target, this.worldUp); - } - - public void setModelInstance(final MdxComplexInstance modelInstance, final MdxModel portraitModel) { - this.modelInstance = modelInstance; - if (modelInstance == null) { - this.modelCamera = null; - } - else if ((portraitModel != null) && (portraitModel.getCameras().size() > 0)) { - this.modelCamera = portraitModel.getCameras().get(0); - } - } - } - - private final float cameraSpeed = 4096.0f; // per second - private final Vector2 cameraVelocity = new Vector2(); - private Texture minimapTexture; - private Rectangle talentTreeWindow; - private Scene uiScene; - private MeleeUI meleeUI; - @Override public boolean keyDown(final int keycode) { - if (keycode == Input.Keys.LEFT) { - this.cameraVelocity.x = -this.cameraSpeed; - } - else if (keycode == Input.Keys.RIGHT) { - this.cameraVelocity.x = this.cameraSpeed; - } - else if (keycode == Input.Keys.DOWN) { - this.cameraVelocity.y = -this.cameraSpeed; - } - else if (keycode == Input.Keys.UP) { - this.cameraVelocity.y = this.cameraSpeed; - } - else if (keycode == Input.Keys.INSERT) { - this.cameraManager.insertDown = true; - } - else if (keycode == Input.Keys.FORWARD_DEL) { - this.cameraManager.deleteDown = true; - } + this.meleeUI.keyDown(keycode); return true; } @Override public boolean keyUp(final int keycode) { - if (keycode == Input.Keys.LEFT) { - this.cameraVelocity.x = 0; - } - else if (keycode == Input.Keys.RIGHT) { - this.cameraVelocity.x = 0; - } - else if (keycode == Input.Keys.DOWN) { - this.cameraVelocity.y = 0; - } - else if (keycode == Input.Keys.UP) { - this.cameraVelocity.y = 0; - } - else if (keycode == Input.Keys.INSERT) { - this.cameraManager.insertDown = false; - } - else if (keycode == Input.Keys.FORWARD_DEL) { - this.cameraManager.deleteDown = false; - } + this.meleeUI.keyUp(keycode); return true; } @@ -624,13 +367,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv clickLocationTemp2.y = screenY; this.uiViewport.unproject(clickLocationTemp2); - if (this.minimapFilledArea.contains(clickLocationTemp2.x, clickLocationTemp2.y)) { - final float clickX = (clickLocationTemp2.x - this.minimapFilledArea.x) / this.minimapFilledArea.width; - final float clickY = (clickLocationTemp2.y - this.minimapFilledArea.y) / this.minimapFilledArea.height; - this.cameraManager.target.x = (clickX * this.viewer.terrain.columns * 128) - + this.viewer.terrain.centerOffset[0]; - this.cameraManager.target.y = (clickY * this.viewer.terrain.rows * 128) - + this.viewer.terrain.centerOffset[1]; + if (this.meleeUI.touchDown(clickLocationTemp2.x, clickLocationTemp2.y, button)) { return false; } if (button == Input.Buttons.RIGHT) { @@ -723,13 +460,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv @Override public boolean scrolled(final int amount) { - this.cameraManager.currentPreset -= amount; - if (this.cameraManager.currentPreset < 0) { - this.cameraManager.currentPreset = 0; - } - if (this.cameraManager.currentPreset >= this.cameraManager.presets.length) { - this.cameraManager.currentPreset = this.cameraManager.presets.length - 1; - } + this.meleeUI.scrolled(amount); return true; } @@ -743,43 +474,88 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv } } - private static class CameraPreset { - private final float aoa; - private final float fov; - private final float rotation; - private final float rotationInsert; - private final float rotationDelete; - private final float distance; - private final float farZ; - private final float nearZ; - private final float height; - private final float listenerDistance; - private final float listenerAOA; + private class LibGDXContentLayerModelInstance extends ModelInstance { - public CameraPreset(final float aoa, final float fov, final float rotation, final float rotationInsert, - final float rotationDelete, final float distance, final float farZ, final float nearZ, - final float height, final float listenerDistance, final float listenerAOA) { - this.aoa = aoa; - this.fov = fov; - this.rotation = rotation; - this.rotationInsert = rotationInsert; - this.rotationDelete = rotationDelete; - this.distance = distance; - this.farZ = farZ; - this.nearZ = nearZ; - this.height = height; - this.listenerDistance = listenerDistance; - this.listenerAOA = listenerAOA; + public LibGDXContentLayerModelInstance(final Model model) { + super(model); } - public float getRotation(final boolean insertDown, final boolean deleteDown) { - if (insertDown && !deleteDown) { - return this.rotationInsert; - } - if (!insertDown && deleteDown) { - return this.rotationDelete; - } - return this.rotation; + @Override + public void updateAnimations(final float dt) { + } + + @Override + public void clearEmittedObjects() { + + } + + @Override + protected void updateLights(final Scene scene2) { + + } + + @Override + public void renderOpaque(final Matrix4 mvp) { + + } + + @Override + public void renderTranslucent() { + renderLibGDXContent(); + } + + @Override + public void load() { + } + + @Override + protected RenderBatch getBatch(final TextureMapper textureMapper2) { + throw new UnsupportedOperationException("NOT API"); + } + + @Override + public void setReplaceableTexture(final int replaceableTextureId, final String replaceableTextureFile) { + + } + + @Override + public boolean isBatched() { + return super.isBatched(); + } + + @Override + protected void removeLights(final Scene scene2) { + // TODO Auto-generated method stub + + } + + } + + private class LibGDXContentLayerModel extends Model { + + public LibGDXContentLayerModel(final ModelHandler handler, final ModelViewer viewer, final String extension, + final PathSolver pathSolver, final String fetchUrl) { + super(handler, viewer, extension, pathSolver, fetchUrl); + this.ok = true; + } + + @Override + protected ModelInstance createInstance(final int type) { + return new LibGDXContentLayerModelInstance(this); + } + + @Override + protected void lateLoad() { + } + + @Override + protected void load(final InputStream src, final Object options) { + } + + @Override + protected void error(final Exception e) { + } + } } diff --git a/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java index 4d05e0c..1d0be0c 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java @@ -164,6 +164,13 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { return file; } + public String trySkinField(String file) { + if ((file != null) && this.skin.hasField(file)) { + file = this.skin.getField(file); + } + return file; + } + public DataTable getSkinData() { return this.skinData; } @@ -196,6 +203,14 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { return null; } + public TextureFrame createTextureFrame(final String name, final UIFrame parent, final boolean decorateFileNames, + final Vector4Definition texCoord) { + final TextureFrame textureFrame = new TextureFrame(name, parent, decorateFileNames, texCoord); + this.nameToFrame.put(name, textureFrame); + add(textureFrame); + return textureFrame; + } + public UIFrame inflate(final FrameDefinition frameDefinition, final UIFrame parent, final FrameDefinition parentDefinitionIfAvailable) { UIFrame inflatedFrame = null; @@ -213,7 +228,8 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { inflatedFrame = simpleFrame; } else if ("SPRITE".equals(frameDefinition.getFrameType())) { - final SpriteFrame spriteFrame = new SpriteFrame(frameDefinition.getName(), parent, this.uiScene); + final SpriteFrame spriteFrame = new SpriteFrame(frameDefinition.getName(), parent, this.uiScene, + viewport2); String backgroundArt = frameDefinition.getString("BackgroundArt"); if (frameDefinition.has("DecorateFileNames") || ((parentDefinitionIfAvailable != null) && parentDefinitionIfAvailable.has("DecorateFileNames"))) { @@ -224,13 +240,9 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { throw new IllegalStateException("Decorated file name lookup not available: " + backgroundArt); } } - if (backgroundArt.toLowerCase().endsWith(".mdl") || backgroundArt.toLowerCase().endsWith(".mdx")) { - backgroundArt = backgroundArt.substring(0, backgroundArt.length() - 4); + if (backgroundArt != null) { + setSpriteFrameModel(spriteFrame, backgroundArt); } - backgroundArt += ".mdx"; - final MdxModel model = (MdxModel) this.modelViewer.load(backgroundArt, this.modelViewer.mapPathSolver, - this.modelViewer.solverParams); - spriteFrame.setModel(model); viewport2 = this.fdfCoordinateResolutionDummyViewport; inflatedFrame = spriteFrame; } @@ -330,9 +342,24 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { return inflatedFrame; } + public void setSpriteFrameModel(final SpriteFrame spriteFrame, String backgroundArt) { + if (backgroundArt.toLowerCase().endsWith(".mdl") || backgroundArt.toLowerCase().endsWith(".mdx")) { + backgroundArt = backgroundArt.substring(0, backgroundArt.length() - 4); + } + backgroundArt += ".mdx"; + final MdxModel model = (MdxModel) this.modelViewer.load(backgroundArt, this.modelViewer.mapPathSolver, + this.modelViewer.solverParams); + spriteFrame.setModel(model); + } + public UIFrame createFrameByType(final String typeName, final String name, final UIFrame owner, final String inherits, final int createContext) { - throw new UnsupportedOperationException("Not yet implemented"); + // TODO idk what inherits is doing yet, and I didn't implement createContext yet + // even though it looked like just mapping/indexing on int + final FrameDefinition frameDefinition = new FrameDefinition(FrameClass.Frame, typeName, name); + final UIFrame inflatedFrame = inflate(frameDefinition, owner, null); + add(inflatedFrame); + return inflatedFrame; } public UIFrame getFrameByName(final String name, final int createContext) { @@ -353,6 +380,20 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { return (fdfY / 0.6f) * viewport.getWorldHeight(); } + public static float unconvertX(final Viewport viewport, final float nonFdfX) { + if (viewport instanceof ExtendViewport) { + return (nonFdfX / ((ExtendViewport) viewport).getMinWorldWidth()) * 0.8f; + } + return (nonFdfX / viewport.getWorldWidth()) * 0.8f; + } + + public static float unconvertY(final Viewport viewport, final float nonFdfY) { + if (viewport instanceof ExtendViewport) { + return (nonFdfY / ((ExtendViewport) viewport).getMinWorldHeight()) * 0.6f; + } + return (nonFdfY / viewport.getWorldHeight()) * 0.6f; + } + public Texture loadTexture(String path) { if (!path.contains(".")) { path = path + ".blp"; @@ -374,4 +415,10 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { public final void positionBounds(final Viewport viewport) { innerPositionBounds(viewport); } + + @Override + public void add(final UIFrame childFrame) { + super.add(childFrame); + this.nameToFrame.put(childFrame.getName(), childFrame); + } } diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java index 46af7b3..893401f 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java @@ -1,6 +1,7 @@ package com.etheller.warsmash.parsers.fdf.frames; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import com.badlogic.gdx.graphics.g2d.BitmapFont; @@ -252,6 +253,15 @@ public abstract class AbstractRenderableFrame implements UIFrame { @Override public void addSetPoint(final SetPoint setPointDefinition) { + // TODO this is O(N) in the number of SetPoints, and that + // is not good performance. + final Iterator iterator = this.setPoints.iterator(); + while (iterator.hasNext()) { + final SetPoint setPoint = iterator.next(); + if (setPoint.getMyPoint() == setPointDefinition.getMyPoint()) { + iterator.remove(); + } + } this.setPoints.add(setPointDefinition); } @@ -261,9 +271,6 @@ public abstract class AbstractRenderableFrame implements UIFrame { // TODO this is a bit of a hack, remove later return; } - if ("ResourceBarFrame".equals(this.name)) { - System.out.println("doing resource bar"); - } if (this.anchors.isEmpty() && this.setPoints.isEmpty()) { this.renderBounds.x = this.parent.getFramePointX(FramePoint.LEFT); this.renderBounds.y = this.parent.getFramePointY(FramePoint.BOTTOM); @@ -334,4 +341,14 @@ public abstract class AbstractRenderableFrame implements UIFrame { protected abstract void internalRender(SpriteBatch batch, BitmapFont baseFont, GlyphLayout glyphLayout); + @Override + public UIFrame touchDown(final float screenX, final float screenY, final int button) { + return null; + } + + @Override + public String getName() { + return this.name; + } + } diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractUIFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractUIFrame.java index eb2889d..8a7bbb7 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractUIFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractUIFrame.java @@ -35,4 +35,15 @@ public abstract class AbstractUIFrame extends AbstractRenderableFrame implements childFrame.positionBounds(viewport); } } + + @Override + public UIFrame touchDown(final float screenX, final float screenY, final int button) { + for (final UIFrame childFrame : this.childFrames) { + final UIFrame clickedChild = childFrame.touchDown(screenX, screenY, button); + if (clickedChild != null) { + return clickedChild; + } + } + return super.touchDown(screenX, screenY, button); + } } diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/SpriteFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/SpriteFrame.java index 28fb028..f569e66 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/SpriteFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/SpriteFrame.java @@ -4,19 +4,26 @@ import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.GlyphLayout; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.utils.viewport.Viewport; +import com.etheller.warsmash.parsers.fdf.GameUI; +import com.etheller.warsmash.parsers.fdf.datamodel.FramePoint; import com.etheller.warsmash.viewer5.Scene; import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; import com.etheller.warsmash.viewer5.handlers.mdx.SequenceLoopMode; +import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; +import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; public class SpriteFrame extends AbstractRenderableFrame { private final Scene scene; private MdxComplexInstance instance; + private float zDepth; + private final Viewport uiViewport; - public SpriteFrame(final String name, final UIFrame parent, final Scene scene) { + public SpriteFrame(final String name, final UIFrame parent, final Scene scene, final Viewport uiViewport) { super(name, parent); this.scene = scene; + this.uiViewport = uiViewport; } public void setModel(final MdxModel model) { @@ -27,21 +34,44 @@ public class SpriteFrame extends AbstractRenderableFrame { this.instance = (MdxComplexInstance) model.addInstance(); this.instance.setSequenceLoopMode(SequenceLoopMode.MODEL_LOOP); this.instance.setScene(this.scene); - this.instance.setLocation(this.renderBounds.x, this.renderBounds.y, 0); + this.instance.setLocation(this.renderBounds.x, this.renderBounds.y, this.zDepth); } else { this.instance = null; } } + @Override + public void setVisible(final boolean visible) { + super.setVisible(visible); + updateInstanceLocation(this.uiViewport); + } + @Override protected void internalRender(final SpriteBatch batch, final BitmapFont baseFont, final GlyphLayout glyphLayout) { } @Override - protected void innerPositionBounds(final Viewport viewport) { + public void setFramePointX(final FramePoint framePoint, final float x) { + super.setFramePointX(framePoint, x); + updateInstanceLocation(this.uiViewport); + } + @Override + public void setFramePointY(final FramePoint framePoint, final float y) { + super.setFramePointY(framePoint, y); + updateInstanceLocation(this.uiViewport); + } + + public void setZDepth(final float depth) { + this.zDepth = depth; + updateInstanceLocation(this.uiViewport); + } + + @Override + protected void innerPositionBounds(final Viewport viewport) { + updateInstanceLocation(viewport); } public void setSequence(final int index) { @@ -50,6 +80,18 @@ public class SpriteFrame extends AbstractRenderableFrame { } } + public void setSequence(final String animationName) { + if (this.instance != null) { + SequenceUtils.randomSequence(this.instance, animationName.toLowerCase()); + } + } + + public void setSequence(final PrimaryTag animationName) { + if (this.instance != null) { + SequenceUtils.randomSequence(this.instance, animationName); + } + } + public void setAnimationSpeed(final float speedRatio) { if (this.instance != null) { this.instance.setAnimationSpeed(speedRatio); @@ -68,4 +110,17 @@ public class SpriteFrame extends AbstractRenderableFrame { } } + private void updateInstanceLocation(final Viewport viewport) { + if (this.instance != null) { + this.instance.setLocation(GameUI.unconvertX(viewport, this.renderBounds.x), + GameUI.unconvertY(viewport, this.renderBounds.y), this.zDepth); + if (isVisible()) { + this.instance.show(); + } + else { + this.instance.hide(); + } + } + } + } diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/UIFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/UIFrame.java index 0615caa..b6ceca7 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/UIFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/UIFrame.java @@ -31,4 +31,8 @@ public interface UIFrame { void setSetAllPoints(boolean setAllPoints); void setVisible(boolean visible); + + UIFrame touchDown(float screenX, float screenY, int button); + + String getName(); } diff --git a/core/src/com/etheller/warsmash/viewer5/ModelInstance.java b/core/src/com/etheller/warsmash/viewer5/ModelInstance.java index d9d527e..9d75fae 100644 --- a/core/src/com/etheller/warsmash/viewer5/ModelInstance.java +++ b/core/src/com/etheller/warsmash/viewer5/ModelInstance.java @@ -145,4 +145,6 @@ public abstract class ModelInstance extends Node { protected abstract RenderBatch getBatch(TextureMapper textureMapper2); public abstract void setReplaceableTexture(int replaceableTextureId, String replaceableTextureFile); + + protected abstract void removeLights(Scene scene2); } diff --git a/core/src/com/etheller/warsmash/viewer5/Scene.java b/core/src/com/etheller/warsmash/viewer5/Scene.java index 0896e5e..c34aac5 100644 --- a/core/src/com/etheller/warsmash/viewer5/Scene.java +++ b/core/src/com/etheller/warsmash/viewer5/Scene.java @@ -147,6 +147,7 @@ public abstract class Scene { public boolean removeInstance(final ModelInstance instance) { if (instance.scene == this) { + instance.removeLights(this); innerRemove(instance); instance.scene = null; @@ -219,7 +220,6 @@ public abstract class Scene { this.emitterObjectUpdater.update(dt); this.updatedParticles = this.emitterObjectUpdater.objects.size(); - this.lightManager.update(); } protected abstract void innerUpdate(float dt, int frame); @@ -239,6 +239,7 @@ public abstract class Scene { gl.glDepthMask(true); gl.glClear(GL20.GL_DEPTH_BUFFER_BIT | GL20.GL_COLOR_BUFFER_BIT); } + this.lightManager.update(); } public void renderOpaque() { diff --git a/core/src/com/etheller/warsmash/viewer5/Shaders.java b/core/src/com/etheller/warsmash/viewer5/Shaders.java index 2adfbf8..6171fb3 100644 --- a/core/src/com/etheller/warsmash/viewer5/Shaders.java +++ b/core/src/com/etheller/warsmash/viewer5/Shaders.java @@ -57,11 +57,10 @@ public class Shaders { " "; public static String lightSystem(final String normalName, final String positionName, final String lightTexture, - final String lightCount, final boolean terrain) { + final String lightTextureHeight, final String lightCount, final boolean terrain) { return " vec3 lightFactor = vec3(0.0,0.0,0.0);\r\n" + // - " float lightIndex = 0.0;\r\n" + // - " for(float lightIndex = 0.0; lightIndex < " + lightCount + "; lightIndex += 1.0) {\r\n" + // - " float rowPos = (lightIndex + 0.5) / " + lightCount + ";\r\n" + // + " for(float lightIndex = 0.5; lightIndex < " + lightCount + "; lightIndex += 1.0) {\r\n" + // + " float rowPos = (lightIndex) / " + lightTextureHeight + ";\r\n" + // " vec4 lightPosition = texture2D(" + lightTexture + ", vec2(0.125, rowPos));\r\n" + // " vec3 lightExtra = texture2D(" + lightTexture + ", vec2(0.375, rowPos)).xyz;\r\n" + // " vec4 lightColor = texture2D(" + lightTexture + ", vec2(0.625, rowPos));\r\n" + // diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/AnimatedObject.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/AnimatedObject.java index 6a43a20..ce47a6c 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/AnimatedObject.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/AnimatedObject.java @@ -69,7 +69,7 @@ public class AnimatedObject { return -1; } - public int getQuadValue(final float[] out, final War3ID name, final int sequence, final int frame, + public int getQuatValue(final float[] out, final War3ID name, final int sequence, final int frame, final int counter, final float[] defaultValue) { if (sequence != -1) { final Sd animation = this.timelines.get(name); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/BatchGroup.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/BatchGroup.java index df93d5e..3fe6d19 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/BatchGroup.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/BatchGroup.java @@ -65,7 +65,8 @@ public class BatchGroup extends GenericGroup { unitLightsTexture.bind(14); shader.setUniformi("u_lightTexture", 14); - shader.setUniformf("u_lightCount", unitLightsTexture.getHeight() - 0.5f); + shader.setUniformf("u_lightCount", lightManager.getUnitLightCount()); + shader.setUniformf("u_lightTextureHeight", unitLightsTexture.getHeight()); // Instances of models with no bones don't have a bone texture. if (boneTexture != null) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GenericObject.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GenericObject.java index 952a124..849e85c 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GenericObject.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GenericObject.java @@ -128,7 +128,7 @@ public class GenericObject extends AnimatedObject implements GenericIndexed { } public int getRotation(final float[] out, final int sequence, final int frame, final int counter) { - return this.getQuadValue(out, AnimationMap.KGRT.getWar3id(), sequence, frame, counter, + return this.getQuatValue(out, AnimationMap.KGRT.getWar3id(), sequence, frame, counter, RenderMathUtils.FLOAT_QUAT_DEFAULT); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/LightInstance.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/LightInstance.java index aade9ac..4e5a26f 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/LightInstance.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/LightInstance.java @@ -88,15 +88,23 @@ public class LightInstance implements UpdatableObject, SceneLightInstance { public void update(final Scene scene) { this.light.getVisibility(scalarHeap, this.instance.sequence, this.instance.frame, this.instance.counter); this.visible = scalarHeap[0] > 0; + updateVisibility(scene, this.visible); + } + + public void remove(final Scene scene) { + updateVisibility(scene, false); + } + + private void updateVisibility(final Scene scene, final boolean visible) { if (scene != null) { - if (this.loadedInScene != this.visible) { - if (this.visible) { + if (this.loadedInScene != visible) { + if (visible) { scene.addLight(this); } else { scene.removeLight(this); } - this.loadedInScene = this.visible; + this.loadedInScene = visible; } } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java index b08c9de..b9e3b8f 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java @@ -614,6 +614,13 @@ public class MdxComplexInstance extends ModelInstance { } } + @Override + protected void removeLights(final Scene scene2) { + for (final LightInstance light : this.lights) { + light.remove(this.scene); + } + } + /** * Set the team color of this instance. */ diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxShaders.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxShaders.java index 4cea40e..7e7cb8c 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxShaders.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxShaders.java @@ -83,7 +83,9 @@ public class MdxShaders { " gl_FragColor = color;\r\n" + // " }\r\n"; - public static final String vsComplex = " uniform mat4 u_mvp;\r\n" + // + public static final String vsComplex = "\r\n" + // + "\r\n" + // + " uniform mat4 u_mvp;\r\n" + // " uniform vec4 u_vertexColor;\r\n" + // " uniform vec4 u_geosetColor;\r\n" + // " uniform float u_layerAlpha;\r\n" + // @@ -106,6 +108,7 @@ public class MdxShaders { " varying float v_uvScale;\r\n" + // " uniform sampler2D u_lightTexture;\r\n" + // " uniform float u_lightCount;\r\n" + // + " uniform float u_lightTextureHeight;\r\n" + // Shaders.boneTexture + "\r\n" + // " void transform(inout vec3 position, inout vec3 normal) {\r\n" + // " // For the broken models out there, since the game supports this.\r\n" + // @@ -150,7 +153,8 @@ public class MdxShaders { " v_uvScale = u_uvScale;\r\n" + // " gl_Position = u_mvp * vec4(position, 1.0);\r\n" + // " if(!u_unshaded) {\r\n" + // - Shaders.lightSystem("normal", "position", "u_lightTexture", "u_lightCount", false) + "\r\n" + // + Shaders.lightSystem("normal", "position", "u_lightTexture", "u_lightTextureHeight", "u_lightCount", false) + + "\r\n" + // " v_color.xyz *= clamp(lightFactor, 0.0, 1.0);\r\n" + // " }\r\n" + // " }"; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxSimpleInstance.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxSimpleInstance.java index b176f1e..f56d476 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxSimpleInstance.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxSimpleInstance.java @@ -37,6 +37,10 @@ public class MdxSimpleInstance extends BatchedInstance { protected void updateLights(final Scene scene2) { } + @Override + protected void removeLights(final Scene scene2) { + } + @Override public void load() { } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Particle2.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Particle2.java index b125d8a..91f5e6f 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Particle2.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Particle2.java @@ -67,13 +67,12 @@ public class Particle2 extends EmittedObject lights; private FloatBuffer lightDataCopyHeap; private final DataTexture unitLightsTexture; - private final War3MapViewer viewer; private int unitLightCount; - public W3xScenePortraitLightManager(final War3MapViewer viewer) { + public W3xScenePortraitLightManager(final War3MapViewer viewer, final Vector3 lightDirection) { this.viewer = viewer; + this.hardcodedLightDirection = lightDirection; this.lights = new ArrayList<>(); this.unitLightsTexture = new DataTexture(viewer.gl, 4, 4, 1); this.lightDataCopyHeap = ByteBuffer.allocateDirect(16 * 1 * 4).order(ByteOrder.nativeOrder()).asFloatBuffer(); @@ -52,13 +56,35 @@ public class W3xScenePortraitLightManager implements SceneLightManager, W3xScene this.unitLightCount = 0; this.lightDataCopyHeap.clear(); int offset = 0; - if (this.viewer.dncUnitDay != null) { - if (!this.viewer.dncUnitDay.lights.isEmpty()) { - this.viewer.dncUnitDay.lights.get(0).bind(0, this.lightDataCopyHeap); - offset += 16; - this.unitLightCount++; + if (this.hardcodedLightDirection == null) { + if (this.viewer.dncTarget != null) { + if (!this.viewer.dncTarget.lights.isEmpty()) { + this.viewer.dncTarget.lights.get(0).bind(0, this.lightDataCopyHeap); + offset += 16; + this.unitLightCount++; + } } } + else { + this.lightDataCopyHeap.put(offset, this.hardcodedLightDirection.y); + this.lightDataCopyHeap.put(offset + 1, -this.hardcodedLightDirection.x); + this.lightDataCopyHeap.put(offset + 2, -this.hardcodedLightDirection.z); + this.lightDataCopyHeap.put(offset + 3, -this.hardcodedLightDirection.z); + this.lightDataCopyHeap.put(offset + 4, Light.Type.DIRECTIONAL.ordinal()); + this.lightDataCopyHeap.put(offset + 5, 1); + this.lightDataCopyHeap.put(offset + 6, 2); + this.lightDataCopyHeap.put(offset + 7, 0); + this.lightDataCopyHeap.put(offset + 8, 1); + this.lightDataCopyHeap.put(offset + 9, 1); + this.lightDataCopyHeap.put(offset + 10, 1); + this.lightDataCopyHeap.put(offset + 11, 1); + this.lightDataCopyHeap.put(offset + 12, 1); + this.lightDataCopyHeap.put(offset + 13, 1); + this.lightDataCopyHeap.put(offset + 14, 1); + this.lightDataCopyHeap.put(offset + 15, 0.3f); + offset += 16; + this.unitLightCount++; + } for (final LightInstance light : this.lights) { light.bind(offset, this.lightDataCopyHeap); offset += 16; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xShaders.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xShaders.java index 6b04d75..c56fd50 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xShaders.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xShaders.java @@ -8,14 +8,16 @@ public class W3xShaders { } public static final String vert = "\r\n" + // + "\r\n" + // " uniform mat4 u_mvp;\r\n" + // " uniform sampler2D u_heightMap;\r\n" + // " uniform vec2 u_pixel;\r\n" + // " uniform vec2 u_size;\r\n" + // " uniform vec2 u_shadowPixel;\r\n" + // " uniform vec2 u_centerOffset;\r\n" + // - " uniform sampler2D lightTexture;\r\n" + // - " uniform float lightCount;\r\n" + // + " uniform sampler2D u_lightTexture;\r\n" + // + " uniform float u_lightCount;\r\n" + // + " uniform float u_lightTextureHeight;\r\n" + // " attribute vec3 a_position;\r\n" + // " attribute vec2 a_uv;\r\n" + // " varying vec2 v_uv;\r\n" + // @@ -42,7 +44,9 @@ public class W3xShaders { " vec3 myposition = vec3(a_position.xy, height * 128.0 + a_position.z);\r\n" + // " gl_Position = u_mvp * vec4(myposition.xyz, 1.0);\r\n" + // " a_positionHeight = a_position.z;\r\n" + // - Shaders.lightSystem("v_normal", "myposition", "lightTexture", "lightCount", true) + "\r\n" + // + Shaders.lightSystem("v_normal", "myposition", "u_lightTexture", "u_lightTextureHeight", "u_lightCount", + true) + + "\r\n" + // " shadeColor = clamp(lightFactor, 0.0, 1.0);\r\n" + // " }\r\n" + // " "; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index eeda60f..1a797b9 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -76,6 +76,8 @@ import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderAttackProjecti import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderEffect; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderItem; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderUnitTypeData; +import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.AbilityDataUI; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitClassification; @@ -86,6 +88,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityM import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackInstant; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackMissile; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.projectile.CAttackProjectile; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.BooleanAbilityActivationReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.CWidgetAbilityTargetCheckReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.PointAbilityTargetCheckReceiver; @@ -109,6 +112,9 @@ public class War3MapViewer extends ModelViewer { private static final War3ID ITEM_FILE = War3ID.fromString("ifil"); private static final War3ID UNIT_PATHING = War3ID.fromString("upat"); private static final War3ID DESTRUCTABLE_PATHING = War3ID.fromString("bptx"); + private static final War3ID ELEVATION_SAMPLE_RADIUS = War3ID.fromString("uerd"); + private static final War3ID MAX_PITCH = War3ID.fromString("umxp"); + private static final War3ID MAX_ROLL = War3ID.fromString("umxr"); private static final War3ID sloc = War3ID.fromString("sloc"); private static final LoadGenericCallback stringDataCallback = new StringDataCallbackImplementation(); private static final float[] rayHeap = new float[6]; @@ -131,7 +137,7 @@ public class War3MapViewer extends ModelViewer { public MappedData doodadsData = new MappedData(); public MappedData doodadMetaData = new MappedData(); public MappedData destructableMetaData = new MappedData(); - public List doodads = new ArrayList<>(); + public List doodads = new ArrayList<>(); public List terrainDoodads = new ArrayList<>(); public boolean doodadsReady; public boolean unitsAndItemsLoaded; @@ -164,6 +170,7 @@ public class War3MapViewer extends ModelViewer { public CSimulation simulation; private float updateTime; + // for World Editor, I think public Vector2[] startLocations = new Vector2[WarsmashConstants.MAX_PLAYERS]; private final DynamicShadowManager dynamicShadowManager = new DynamicShadowManager(); @@ -175,7 +182,9 @@ public class War3MapViewer extends ModelViewer { private final List selectionCircleSizes = new ArrayList<>(); private final Map unitToRenderPeer = new HashMap<>(); + private final Map unitIdToTypeData = new HashMap<>(); private GameUI gameUI; + private Vector3 lightDirection; public War3MapViewer(final DataSource dataSource, final CanvasProvider canvas) { super(dataSource, canvas); @@ -269,6 +278,11 @@ public class War3MapViewer extends ModelViewer { this.miscData.readTXT(miscDataTxtStream, true); } } + final Element light = this.miscData.get("Light"); + final float lightX = light.getFieldFloatValue("Direction", 0); + final float lightY = light.getFieldFloatValue("Direction", 1); + final float lightZ = light.getFieldFloatValue("Direction", 2); + this.lightDirection = new Vector3(lightX, lightY, lightZ).nor(); this.unitGlobalStrings = new DataTable(worldEditStrings); try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\UnitGlobalStrings.txt")) { this.unitGlobalStrings.readTXT(miscDataTxtStream, true); @@ -381,9 +395,9 @@ public class War3MapViewer extends ModelViewer { this.confirmationInstance.setSequence(0); this.confirmationInstance.setScene(this.worldScene); - final Warcraft3MapObjectData modifications = this.mapMpq.readModifications(); - this.simulation = new CSimulation(this.miscData, modifications.getUnits(), modifications.getAbilities(), - new SimulationRenderController() { + this.allObjectData = this.mapMpq.readModifications(); + this.simulation = new CSimulation(this.miscData, this.allObjectData.getUnits(), + this.allObjectData.getAbilities(), new SimulationRenderController() { private final Map keyToCombatSound = new HashMap<>(); @Override @@ -493,25 +507,39 @@ public class War3MapViewer extends ModelViewer { this.seededRandom, w3iFile.getPlayers()); if (this.doodadsAndDestructiblesLoaded) { - this.loadDoodadsAndDestructibles(modifications); + this.loadDoodadsAndDestructibles(this.allObjectData); } else { throw new IllegalStateException("transcription of JS has not loaded a map and has no JS async promises"); } - if (this.unitsAndItemsLoaded) { - this.loadUnitsAndItems(modifications); - } - else { - throw new IllegalStateException("transcription of JS has not loaded a map and has no JS async promises"); - } - - this.terrain.initShadows(); this.terrain.createWaves(); loadLightsAndShading(tileset); } + /** + * Loads the map information that should be loaded after UI, such as units, who + * need to be able to setup their UI counterparts (icons, etc) for their + * abilities while loading. This allows the dynamic creation of units while the + * game is playing to better share code with the startup sequence's creation of + * units. + * + * @throws IOException + */ + public void loadAfterUI() throws IOException { + if (this.unitsAndItemsLoaded) { + this.loadUnitsAndItems(this.allObjectData); + } + else { + throw new IllegalStateException("transcription of JS has not loaded a map and has no JS async promises"); + } + + // After we finish loading units, we need to update & create the stored shadow + // information for all unit shadows + this.terrain.initShadows(); + } + private void loadLightsAndShading(final char tileset) { // TODO this should be set by the war3map.j actually, not by the tileset, so the // call to set day night models is just for testing to make the test look pretty @@ -605,11 +633,11 @@ public class War3MapViewer extends ModelViewer { } if (type == WorldEditorDataType.DESTRUCTIBLES) { - this.doodads - .add(new Destructable(this, model, row, doodad, type, maxPitch, maxRoll, doodad.getLife())); + this.doodads.add(new RenderDestructable(this, model, row, doodad, type, maxPitch, maxRoll, + doodad.getLife())); } else { - this.doodads.add(new Doodad(this, model, row, doodad, type, maxPitch, maxRoll)); + this.doodads.add(new RenderDoodad(this, model, row, doodad, type, maxPitch, maxRoll)); } } } @@ -841,8 +869,9 @@ public class War3MapViewer extends ModelViewer { final float angle = (float) Math.toDegrees(unit.getAngle()); final CUnit simulationUnit = this.simulation.createUnit(row.getAlias(), unit.getPlayer(), unit.getLocation()[0], unit.getLocation()[1], angle, buildingPathingPixelMap); + final RenderUnitTypeData typeData = getUnitTypeData(unit.getId(), row); final RenderUnit renderUnit = new RenderUnit(this, model, row, unit, soundset, portraitModel, - simulationUnit); + simulationUnit, typeData); this.unitToRenderPeer.put(simulationUnit, renderUnit); this.units.add(renderUnit); if (unitShadowSplat != null) { @@ -879,6 +908,16 @@ public class War3MapViewer extends ModelViewer { this.anyReady = true; } + private RenderUnitTypeData getUnitTypeData(final War3ID key, final MutableGameObject row) { + RenderUnitTypeData unitTypeData = this.unitIdToTypeData.get(key); + if (unitTypeData == null) { + unitTypeData = new RenderUnitTypeData(row.getFieldAsFloat(MAX_PITCH, 0), row.getFieldAsFloat(MAX_ROLL, 0), + row.getFieldAsFloat(ELEVATION_SAMPLE_RADIUS, 0)); + this.unitIdToTypeData.put(key, unitTypeData); + } + return unitTypeData; + } + @Override public void update() { if (this.anyReady) { @@ -903,13 +942,15 @@ public class War3MapViewer extends ModelViewer { SequenceUtils.randomStandSequence(mdxComplexInstance); } } - for (final Doodad item : this.doodads) { + for (final RenderDoodad item : this.doodads) { final ModelInstance instance = item.instance; - if ((instance instanceof MdxComplexInstance) && (instance != this.confirmationInstance)) { + if (instance instanceof MdxComplexInstance) { final MdxComplexInstance mdxComplexInstance = (MdxComplexInstance) instance; if ((mdxComplexInstance.sequence == -1) - || (mdxComplexInstance.sequenceEnded && (((MdxModel) mdxComplexInstance.model).sequences - .get(mdxComplexInstance.sequence).getFlags() == 0))) { + || (mdxComplexInstance.sequenceEnded/* + * && (((MdxModel) mdxComplexInstance.model).sequences + * .get(mdxComplexInstance.sequence).getFlags() == 0) + */)) { SequenceUtils.randomSequence(mdxComplexInstance, item.getAnimation(), SequenceUtils.EMPTY, true); } @@ -1178,6 +1219,8 @@ public class War3MapViewer extends ModelViewer { private float selectionCircleScaleFactor; private DataTable worldEditData; private WorldEditStrings worldEditStrings; + private Warcraft3MapObjectData allObjectData; + private AbilityDataUI abilityDataUI; /** * Returns a power of two size for the given target capacity. @@ -1199,14 +1242,15 @@ public class War3MapViewer extends ModelViewer { for (final RenderUnit unit : this.selected) { for (final CAbility ability : unit.getSimulationUnit().getAbilities()) { if (ability instanceof CAbilityMove) { - ability.checkCanUse(this.simulation, unit.getSimulationUnit(), + ability.checkCanUse(this.simulation, unit.getSimulationUnit(), OrderIds.smart, BooleanAbilityActivationReceiver.INSTANCE); if (BooleanAbilityActivationReceiver.INSTANCE.isOk()) { - ability.checkCanTarget(this.simulation, unit.getSimulationUnit(), mousePosHeap, + ability.checkCanTarget(this.simulation, unit.getSimulationUnit(), OrderIds.smart, mousePosHeap, PointAbilityTargetCheckReceiver.INSTANCE); final Vector2 target = PointAbilityTargetCheckReceiver.INSTANCE.getTarget(); if (target != null) { - ability.onOrder(this.simulation, unit.getSimulationUnit(), mousePosHeap, false); + ability.onOrder(this.simulation, unit.getSimulationUnit(), OrderIds.smart, mousePosHeap, + false); ordered = true; } else { @@ -1231,14 +1275,15 @@ public class War3MapViewer extends ModelViewer { for (final RenderUnit unit : this.selected) { for (final CAbility ability : unit.getSimulationUnit().getAbilities()) { if (ability instanceof CAbilityAttack) { - ability.checkCanUse(this.simulation, unit.getSimulationUnit(), + ability.checkCanUse(this.simulation, unit.getSimulationUnit(), OrderIds.smart, BooleanAbilityActivationReceiver.INSTANCE); if (BooleanAbilityActivationReceiver.INSTANCE.isOk()) { - ability.checkCanTarget(this.simulation, unit.getSimulationUnit(), target.getSimulationUnit(), - CWidgetAbilityTargetCheckReceiver.INSTANCE); + ability.checkCanTarget(this.simulation, unit.getSimulationUnit(), OrderIds.smart, + target.getSimulationUnit(), CWidgetAbilityTargetCheckReceiver.INSTANCE); final CWidget targetWidget = CWidgetAbilityTargetCheckReceiver.INSTANCE.getTarget(); if (targetWidget != null) { - ability.onOrder(this.simulation, unit.getSimulationUnit(), targetWidget, false); + ability.onOrder(this.simulation, unit.getSimulationUnit(), OrderIds.smart, targetWidget, + false); ordered = true; } else { @@ -1308,7 +1353,7 @@ public class War3MapViewer extends ModelViewer { @Override public SceneLightManager createLightManager(final boolean simple) { if (simple) { - return new W3xScenePortraitLightManager(this); + return new W3xScenePortraitLightManager(this, this.lightDirection); } else { return new W3xSceneWorldLightManager(this); @@ -1321,12 +1366,14 @@ public class War3MapViewer extends ModelViewer { public void setGameUI(final GameUI gameUI) { this.gameUI = gameUI; - for (final RenderUnit unit : this.units) { - unit.initAbilityUI(this); - } + this.abilityDataUI = new AbilityDataUI(this.allObjectData.getAbilities(), gameUI); } public GameUI getGameUI() { return this.gameUI; } + + public AbilityDataUI getAbilityDataUI() { + return this.abilityDataUI; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/camera/CameraManager.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/camera/CameraManager.java new file mode 100644 index 0000000..dc84859 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/camera/CameraManager.java @@ -0,0 +1,62 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.camera; + +import com.badlogic.gdx.math.Quaternion; +import com.badlogic.gdx.math.Vector3; +import com.etheller.warsmash.viewer5.Camera; +import com.etheller.warsmash.viewer5.CanvasProvider; +import com.etheller.warsmash.viewer5.Scene; + +public abstract class CameraManager { + private static final double HORIZONTAL_ANGLE_INCREMENT = Math.PI / 60; + protected final float[] cameraPositionTemp = new float[3]; + protected final float[] cameraTargetTemp = new float[3]; + protected CanvasProvider canvas; + protected Camera camera; + protected float moveSpeed; + protected float rotationSpeed; + protected float zoomFactor; + protected float horizontalAngle; + protected float verticalAngle; + protected float distance; + protected Vector3 position; + public Vector3 target; + protected Vector3 worldUp; + protected Vector3 vecHeap; + protected Quaternion quatHeap; + protected Quaternion quatHeap2; + + public CameraManager() { + } + + // An orbit camera setup example. + // Left mouse button controls the orbit itself. + // The right mouse button allows to move the camera and the point it's looking + // at on the XY plane. + // Scrolling zooms in and out. + public void setupCamera(final Scene scene) { + this.canvas = scene.viewer.canvas; + this.camera = scene.camera; + this.moveSpeed = 2; + this.rotationSpeed = (float) HORIZONTAL_ANGLE_INCREMENT; + this.zoomFactor = 0.1f; + this.horizontalAngle = 0;// (float) (Math.PI / 2); + this.verticalAngle = (float) Math.toRadians(34); + this.distance = 1650; + this.position = new Vector3(); + this.target = new Vector3(0, 0, 0); + this.worldUp = new Vector3(0, 0, 1); + this.vecHeap = new Vector3(); + this.quatHeap = new Quaternion(); + this.quatHeap2 = new Quaternion(); + + updateCamera(); + +// cameraUpdate(); + } + + public abstract void updateCamera(); + +// private void cameraUpdate() { +// +// } +} \ No newline at end of file diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/camera/CameraPanControls.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/camera/CameraPanControls.java new file mode 100644 index 0000000..964dbb9 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/camera/CameraPanControls.java @@ -0,0 +1,10 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.camera; + +public final class CameraPanControls { + protected boolean down; + protected boolean up; + protected boolean left; + protected boolean right; + protected boolean insertDown; + protected boolean deleteDown; +} \ No newline at end of file diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/camera/CameraPreset.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/camera/CameraPreset.java new file mode 100644 index 0000000..92ed43c --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/camera/CameraPreset.java @@ -0,0 +1,85 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.camera; + +public class CameraPreset { + private final float aoa; + private final float fov; + private final float rotation; + private final float rotationInsert; + private final float rotationDelete; + private final float distance; + private final float farZ; + private final float nearZ; + private final float height; + private final float listenerDistance; + private final float listenerAOA; + + public CameraPreset(final float aoa, final float fov, final float rotation, final float rotationInsert, + final float rotationDelete, final float distance, final float farZ, final float nearZ, final float height, + final float listenerDistance, final float listenerAOA) { + this.aoa = aoa; + this.fov = fov; + this.rotation = rotation; + this.rotationInsert = rotationInsert; + this.rotationDelete = rotationDelete; + this.distance = distance; + this.farZ = farZ; + this.nearZ = nearZ; + this.height = height; + this.listenerDistance = listenerDistance; + this.listenerAOA = listenerAOA; + } + + public float getRotation(final boolean insertDown, final boolean deleteDown) { + if (insertDown && !deleteDown) { + return this.rotationInsert; + } + if (!insertDown && deleteDown) { + return this.rotationDelete; + } + return this.rotation; + } + + public float getHeight() { + return this.height; + } + + public float getAoa() { + return this.aoa; + } + + public float getFov() { + return this.fov; + } + + public float getRotation() { + return this.rotation; + } + + public float getRotationInsert() { + return this.rotationInsert; + } + + public float getRotationDelete() { + return this.rotationDelete; + } + + public float getDistance() { + return this.distance; + } + + public float getFarZ() { + return this.farZ; + } + + public float getNearZ() { + return this.nearZ; + } + + public float getListenerDistance() { + return this.listenerDistance; + } + + public float getListenerAOA() { + return this.listenerAOA; + } +} \ No newline at end of file diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/camera/CameraRates.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/camera/CameraRates.java new file mode 100644 index 0000000..430d4e8 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/camera/CameraRates.java @@ -0,0 +1,20 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.camera; + +public class CameraRates { + public final float aoa; + public final float fov; + public final float rotation; + public final float distance; + public final float forward; + public final float strafe; + + public CameraRates(final float aoa, final float fov, final float rotation, final float distance, + final float forward, final float strafe) { + this.aoa = aoa; + this.fov = fov; + this.rotation = rotation; + this.distance = distance; + this.forward = forward; + this.strafe = strafe; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/camera/GameCameraManager.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/camera/GameCameraManager.java new file mode 100644 index 0000000..013b276 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/camera/GameCameraManager.java @@ -0,0 +1,154 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.camera; + +import com.badlogic.gdx.Input; +import com.badlogic.gdx.math.Rectangle; + +public final class GameCameraManager extends CameraManager { + private final CameraPreset[] presets; + private final CameraRates cameraRates; + public final CameraPanControls cameraPanControls; + private int currentPreset = 0; + + public GameCameraManager(final CameraPreset[] presets, final CameraRates cameraRates) { + this.presets = presets; + this.cameraRates = cameraRates; + this.cameraPanControls = new CameraPanControls(); + } + + @Override + public void updateCamera() { + this.quatHeap2.idt(); + final CameraPreset cameraPreset = this.presets[this.currentPreset]; + this.quatHeap.idt(); + this.horizontalAngle = (float) Math.toRadians( + cameraPreset.getRotation(this.cameraPanControls.insertDown, this.cameraPanControls.deleteDown) - 90); + this.quatHeap.setFromAxisRad(0, 0, 1, this.horizontalAngle); + this.distance = Math.max(1200, cameraPreset.getDistance()); + this.verticalAngle = (float) Math.toRadians(Math.min(335, cameraPreset.getAoa()) - 270); + this.quatHeap2.setFromAxisRad(1, 0, 0, this.verticalAngle); + this.quatHeap.mul(this.quatHeap2); + + this.position.set(0, 0, 1); + this.quatHeap.transform(this.position); + this.position.nor(); + this.position.scl(this.distance); + this.position = this.position.add(this.target); + this.camera.perspective((float) Math.toRadians(cameraPreset.getFov() / 2), this.camera.getAspect(), + cameraPreset.getNearZ(), cameraPreset.getFarZ()); + + this.camera.moveToAndFace(this.position, this.target, this.worldUp); + } + + public void resize(final Rectangle viewport) { + this.camera.viewport(viewport); + } + + public void applyVelocity(final float deltaTime, boolean up, boolean down, boolean left, boolean right) { + final float velocityX; + final float velocityY; + up |= this.cameraPanControls.up; + down |= this.cameraPanControls.down; + left |= this.cameraPanControls.left; + right |= this.cameraPanControls.right; + if (up) { + if (down) { + velocityY = 0; + } + else { + velocityY = this.cameraRates.forward; + } + } + else if (down) { + velocityY = -this.cameraRates.forward; + } + else { + velocityY = 0; + } + if (right) { + if (left) { + velocityX = 0; + } + else { + velocityX = this.cameraRates.strafe; + } + } + else if (left) { + velocityX = -this.cameraRates.strafe; + } + else { + velocityX = 0; + } + this.target.add(velocityX * deltaTime, velocityY * deltaTime, 0); + + } + + public void updateTargetZ(final float groundHeight) { + this.target.z = groundHeight + this.presets[this.currentPreset].getHeight(); + } + + public void scrolled(final int amount) { + this.currentPreset -= amount; + if (this.currentPreset < 0) { + this.currentPreset = 0; + } + if (this.currentPreset >= this.presets.length) { + this.currentPreset = this.presets.length - 1; + } + } + + public boolean keyDown(final int keycode) { + if (keycode == Input.Keys.LEFT) { + this.cameraPanControls.left = true; + return true; + } + else if (keycode == Input.Keys.RIGHT) { + this.cameraPanControls.right = true; + return true; + } + else if (keycode == Input.Keys.DOWN) { + this.cameraPanControls.down = true; + return true; + } + else if (keycode == Input.Keys.UP) { + this.cameraPanControls.up = true; + return true; + } + else if (keycode == Input.Keys.INSERT) { + this.cameraPanControls.insertDown = true; + return true; + } + else if (keycode == Input.Keys.FORWARD_DEL) { + this.cameraPanControls.deleteDown = true; + return true; + } + return false; + } + + public boolean keyUp(final int keycode) { + if (keycode == Input.Keys.LEFT) { + this.cameraPanControls.left = false; + return true; + } + else if (keycode == Input.Keys.RIGHT) { + this.cameraPanControls.right = false; + return true; + } + else if (keycode == Input.Keys.DOWN) { + this.cameraPanControls.down = false; + return true; + } + else if (keycode == Input.Keys.UP) { + this.cameraPanControls.up = false; + return true; + } + else if (keycode == Input.Keys.INSERT) { + this.cameraPanControls.insertDown = false; + return true; + } + else if (keycode == Input.Keys.FORWARD_DEL) { + this.cameraPanControls.deleteDown = false; + return true; + } + return false; + } +} \ No newline at end of file diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/camera/PortraitCameraManager.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/camera/PortraitCameraManager.java new file mode 100644 index 0000000..4ecf106 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/camera/PortraitCameraManager.java @@ -0,0 +1,52 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.camera; + +import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; +import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; + +public final class PortraitCameraManager extends CameraManager { + public com.etheller.warsmash.viewer5.handlers.mdx.Camera modelCamera; + protected MdxComplexInstance modelInstance; + + @Override + public void updateCamera() { + this.quatHeap.idt(); + this.quatHeap.setFromAxisRad(0, 0, 1, this.horizontalAngle); + this.quatHeap2.idt(); + this.quatHeap2.setFromAxisRad(1, 0, 0, this.verticalAngle); + this.quatHeap.mul(this.quatHeap2); + + this.position.set(0, 0, 1); + this.quatHeap.transform(this.position); + this.position.scl(this.distance); + this.position = this.position.add(this.target); + if (this.modelCamera != null) { + this.modelCamera.getPositionTranslation(this.cameraPositionTemp, this.modelInstance.sequence, + this.modelInstance.frame, this.modelInstance.counter); + this.modelCamera.getTargetTranslation(this.cameraTargetTemp, this.modelInstance.sequence, + this.modelInstance.frame, this.modelInstance.counter); + + this.position.set(this.modelCamera.position); + this.target.set(this.modelCamera.targetPosition); + + this.position.add(this.cameraPositionTemp[0], this.cameraPositionTemp[1], this.cameraPositionTemp[2]); + this.target.add(this.cameraTargetTemp[0], this.cameraTargetTemp[1], this.cameraTargetTemp[2]); + this.camera.perspective(this.modelCamera.fieldOfView * 0.75f, this.camera.getAspect(), + this.modelCamera.nearClippingPlane, this.modelCamera.farClippingPlane); + } + else { + this.camera.perspective(70, this.camera.getAspect(), 100, 5000); + } + + this.camera.moveToAndFace(this.position, this.target, this.worldUp); + } + + public void setModelInstance(final MdxComplexInstance modelInstance, final MdxModel portraitModel) { + this.modelInstance = modelInstance; + if (modelInstance == null) { + this.modelCamera = null; + } + else if ((portraitModel != null) && (portraitModel.getCameras().size() > 0)) { + this.modelCamera = portraitModel.getCameras().get(0); + } + } +} \ No newline at end of file diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java index e9a17f2..08edfec 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java @@ -890,7 +890,8 @@ public class Terrain { unitLightsTexture.bind(21); gl.glUniform1i(this.groundShader.getUniformLocation("lightTexture"), 21); - gl.glUniform1f(this.groundShader.getUniformLocation("lightCount"), unitLightsTexture.getHeight() - 0.5f); + gl.glUniform1f(this.groundShader.getUniformLocation("lightCount"), lightManager.getTerrainLightCount()); + gl.glUniform1f(this.groundShader.getUniformLocation("lightTextureHeight"), unitLightsTexture.getHeight()); gl.glUniformMatrix4fv(this.groundShader.getUniformLocation("DepthBiasMVP"), 1, false, dynamicShadowManager.getDepthBiasMVP().val, 0); @@ -989,8 +990,9 @@ public class Terrain { final DataTexture terrainLightsTexture = lightManager.getTerrainLightsTexture(); terrainLightsTexture.bind(21); - gl.glUniform1i(shader.getUniformLocation("lightTexture"), 21); - gl.glUniform1f(shader.getUniformLocation("lightCount"), terrainLightsTexture.getHeight() - 0.5f); + gl.glUniform1i(shader.getUniformLocation("u_lightTexture"), 21); + gl.glUniform1f(shader.getUniformLocation("u_lightCount"), lightManager.getTerrainLightCount()); + gl.glUniform1f(shader.getUniformLocation("u_lightTextureHeight"), terrainLightsTexture.getHeight()); // Render the cliffs for (final SplatModel splat : this.uberSplatModels) { @@ -1024,7 +1026,8 @@ public class Terrain { unitLightsTexture.bind(21); gl.glUniform1i(this.waterShader.getUniformLocation("lightTexture"), 21); - gl.glUniform1f(this.waterShader.getUniformLocation("lightCount"), unitLightsTexture.getHeight() - 0.5f); + gl.glUniform1f(this.waterShader.getUniformLocation("lightCount"), lightManager.getTerrainLightCount()); + gl.glUniform1f(this.waterShader.getUniformLocation("lightCountHeight"), unitLightsTexture.getHeight()); gl.glActiveTexture(GL30.GL_TEXTURE0); gl.glBindTexture(GL30.GL_TEXTURE_2D, this.waterHeight); @@ -1080,7 +1083,8 @@ public class Terrain { unitLightsTexture.bind(21); gl.glUniform1i(this.cliffShader.getUniformLocation("lightTexture"), 21); - gl.glUniform1f(this.cliffShader.getUniformLocation("lightCount"), unitLightsTexture.getHeight() - 0.5f); + gl.glUniform1f(this.cliffShader.getUniformLocation("lightCount"), lightManager.getTerrainLightCount()); + gl.glUniform1f(this.cliffShader.getUniformLocation("lightTextureHeight"), unitLightsTexture.getHeight()); this.cliffShader.setUniformi("shadowMap", 2); gl.glActiveTexture(GL30.GL_TEXTURE2); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/TerrainShaders.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/TerrainShaders.java index 43897a2..3771d07 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/TerrainShaders.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/TerrainShaders.java @@ -25,6 +25,7 @@ public class TerrainShaders { "layout (location = 4) uniform float centerOffsetY;\r\n" + // "layout (location = 5) uniform sampler2D lightTexture;\r\n" + // "layout (location = 6) uniform float lightCount;\r\n" + // + "layout (location = 7) uniform float lightTextureHeight;\r\n" + // "\r\n" + // "layout (location = 0) out vec3 UV;\r\n" + // "layout (location = 1) out vec3 Normal;\r\n" + // @@ -60,7 +61,9 @@ public class TerrainShaders { " vec3 terrain_normal = normalize(vNormal);//vec3(hL - hR, hD - hU, 2.0)+vNormal);\r\n" + // "\r\n" + // " Normal = terrain_normal;\r\n" + // - Shaders.lightSystem("Normal", "myposition.xyz", "lightTexture", "lightCount", true) + "\r\n" + // + Shaders.lightSystem("Normal", "myposition.xyz", "lightTexture", "lightTextureHeight", "lightCount", + true) + + "\r\n" + // " shadeColor = clamp(lightFactor, 0.0, 1.0);\r\n" + // "}"; @@ -135,6 +138,7 @@ public class TerrainShaders { "layout (location = 5) uniform float centerOffsetY;\r\n" + // "layout (location = 7) uniform sampler2D lightTexture;\r\n" + // "layout (location = 8) uniform float lightCount;\r\n" + // + "layout (location = 9) uniform float lightTextureHeight;\r\n" + // "\r\n" + // "layout (location = 0) out vec2 UV;\r\n" + // "layout (location = 1) out flat uvec4 texture_indices;\r\n" + // @@ -173,7 +177,8 @@ public class TerrainShaders { " v_suv = (vPosition + pos) / size;\r\n" + // " position.x = (position.x - centerOffsetX) / (size.x * 128.0);\r\n" + // " position.y = (position.y - centerOffsetY) / (size.y * 128.0);\r\n" + // - Shaders.lightSystem("normal", "positionWorld", "lightTexture", "lightCount", true) + "\r\n" + // + Shaders.lightSystem("normal", "positionWorld", "lightTexture", "lightTextureHeight", "lightCount", true) + + "\r\n" + // " shadeColor = clamp(lightFactor, 0.0, 1.0);\r\n" + // "}"; @@ -425,6 +430,7 @@ public class TerrainShaders { "layout (location = 5) uniform float water_offset;\r\n" + // "layout (location = 10) uniform sampler2D lightTexture;\r\n" + // "layout (location = 11) uniform float lightCount;\r\n" + // + "layout (location = 12) uniform float lightTextureHeight;\r\n" + // "\r\n" + // "out vec2 UV;\r\n" + // "out vec4 Color;\r\n" + // @@ -463,7 +469,9 @@ public class TerrainShaders { " value = clamp(value - deeplevel, 0.f, maxdepth - deeplevel) / (maxdepth - deeplevel);\r\n" + // " Color = deep_color_min * (1.f - value) + deep_color_max * value;\r\n" + // " }\r\n" + // - Shaders.lightSystem("Normal", "myposition.xyz", "lightTexture", "lightCount", true) + "\r\n" + // + Shaders.lightSystem("Normal", "myposition.xyz", "lightTexture", "lightTextureHeight", "lightCount", + true) + + "\r\n" + // " shadeColor = clamp(lightFactor, 0.0, 1.0);\r\n" + // " }"; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/CommandCardIcon.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/CommandCardIcon.java deleted file mode 100644 index 4d702a6..0000000 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/CommandCardIcon.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.etheller.warsmash.viewer5.handlers.w3x.rendersim; - -import com.badlogic.gdx.graphics.Texture; - -public class CommandCardIcon { - private final int x; - private final int y; - private final Texture texture; - private final int orderId; - - public CommandCardIcon(final int x, final int y, final Texture texture, final int orderId) { - this.x = x; - this.y = y; - this.texture = texture; - this.orderId = orderId; - } - - public int getX() { - return this.x; - } - - public int getY() { - return this.y; - } - - public Texture getTexture() { - return this.texture; - } - - public int getOrderId() { - return this.orderId; - } -} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java index 5812b73..8fc6aed 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java @@ -1,18 +1,13 @@ package com.etheller.warsmash.viewer5.handlers.w3x.rendersim; -import java.util.ArrayList; import java.util.EnumSet; import java.util.LinkedList; -import java.util.List; import java.util.Queue; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.math.Quaternion; -import com.etheller.warsmash.parsers.fdf.GameUI; import com.etheller.warsmash.parsers.mdlx.Sequence; -import com.etheller.warsmash.units.Element; import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; -import com.etheller.warsmash.util.ImageUtils; import com.etheller.warsmash.util.RenderMathUtils; import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; @@ -26,22 +21,14 @@ import com.etheller.warsmash.viewer5.handlers.w3x.UnitSoundset; import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.MovementType; +import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.AbilityDataUI; +import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.commandbuttons.CommandButtonListener; +import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.commandbuttons.CommandCardPopulatingAbilityVisitor; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitAnimationListener; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityAttack; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityHoldPosition; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityMove; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityPatrol; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityStop; public class RenderUnit { - private static final War3ID ABILITY_MOVE = War3ID.fromString("Amov"); - private static final War3ID ABILITY_STOP = War3ID.fromString("Astp"); - private static final War3ID ABILITY_HOLD_POSITION = War3ID.fromString("Ahol"); - private static final War3ID ABILITY_PATROL = War3ID.fromString("Apat"); - private static final War3ID ABILITY_ATTACK = War3ID.fromString("Aatk"); - private static final Quaternion tempQuat = new Quaternion(); private static final War3ID RED = War3ID.fromString("uclr"); private static final War3ID GREEN = War3ID.fromString("uclg"); @@ -61,7 +48,6 @@ public class RenderUnit { private final CUnit simulationUnit; public SplatMover shadow; public SplatMover selectionCircle; - private final List commandCardIcons = new ArrayList<>(); private float x; private float y; @@ -77,12 +63,14 @@ public class RenderUnit { public long lastUnitResponseEndTimeMillis; private boolean corpse; private boolean boneCorpse; + private final RenderUnitTypeData typeData; public RenderUnit(final War3MapViewer map, final MdxModel model, final MutableGameObject row, final com.etheller.warsmash.parsers.w3x.unitsdoo.Unit unit, final UnitSoundset soundset, - final MdxModel portraitModel, final CUnit simulationUnit) { + final MdxModel portraitModel, final CUnit simulationUnit, final RenderUnitTypeData typeData) { this.portraitModel = portraitModel; this.simulationUnit = simulationUnit; + this.typeData = typeData; final MdxComplexInstance instance = (MdxComplexInstance) model.addInstance(); final float[] location = unit.getLocation(); @@ -143,43 +131,10 @@ public class RenderUnit { } - public void initAbilityUI(final War3MapViewer map) { + public void populateCommandCard(final CommandButtonListener commandButtonListener, + final AbilityDataUI abilityDataUI) { for (final CAbility ability : this.simulationUnit.getAbilities()) { - if (ability instanceof CAbilityMove) { - final GameUI gameUI = map.getGameUI(); - final Element moveCommand = gameUI.getSkinData().get("CmdMove"); - final String moveCommandArt = gameUI.getSkinField(moveCommand.getField("Art")); - this.commandCardIcons.add(new CommandCardIcon(0, 0, - ImageUtils.getBLPTexture(map.dataSource, moveCommandArt), ability.getOrderId())); - } - else if (ability instanceof CAbilityAttack) { - final GameUI gameUI = map.getGameUI(); - final Element command = gameUI.getSkinData().get("CmdAttack"); - final String commandArt = gameUI.getSkinField(command.getField("Art")); - this.commandCardIcons.add(new CommandCardIcon(3, 0, - ImageUtils.getBLPTexture(map.dataSource, commandArt), ability.getOrderId())); - } - else if (ability instanceof CAbilityHoldPosition) { - final GameUI gameUI = map.getGameUI(); - final Element command = gameUI.getSkinData().get("CmdHoldPos"); - final String commandArt = gameUI.getSkinField(command.getField("Art")); - this.commandCardIcons.add(new CommandCardIcon(2, 0, - ImageUtils.getBLPTexture(map.dataSource, commandArt), ability.getOrderId())); - } - else if (ability instanceof CAbilityPatrol) { - final GameUI gameUI = map.getGameUI(); - final Element command = gameUI.getSkinData().get("CmdPatrol"); - final String commandArt = gameUI.getSkinField(command.getField("Art")); - this.commandCardIcons.add(new CommandCardIcon(0, 1, - ImageUtils.getBLPTexture(map.dataSource, commandArt), ability.getOrderId())); - } - else if (ability instanceof CAbilityStop) { - final GameUI gameUI = map.getGameUI(); - final Element command = gameUI.getSkinData().get("CmdStop"); - final String commandArt = gameUI.getSkinField(command.getField("Art")); - this.commandCardIcons.add(new CommandCardIcon(1, 0, - ImageUtils.getBLPTexture(map.dataSource, commandArt), ability.getOrderId())); - } + ability.visit(CommandCardPopulatingAbilityVisitor.INSTANCE.reset(commandButtonListener, abilityDataUI)); } } @@ -298,6 +253,32 @@ public class RenderUnit { } this.facing = (((this.facing + angleToAdd) % 360) + 360) % 360; this.instance.setLocalRotation(tempQuat.setFromAxis(RenderMathUtils.VEC3_UNIT_Z, this.facing)); + + final float facingRadians = (float) Math.toRadians(this.facing); + final float maxPitch = this.typeData.getMaxPitch(); + final float maxRoll = this.typeData.getMaxRoll(); + final float sampleRadius = this.typeData.getElevationSampleRadius(); + float pitch, roll; + final float pitchSampleForwardX = x + (sampleRadius * (float) Math.cos(facingRadians)); + final float pitchSampleForwardY = y + (sampleRadius * (float) Math.sin(facingRadians)); + final float pitchSampleBackwardX = x - (sampleRadius * (float) Math.cos(facingRadians)); + final float pitchSampleBackwardY = y - (sampleRadius * (float) Math.sin(facingRadians)); + final float pitchSampleGroundHeight1 = map.terrain.getGroundHeight(pitchSampleBackwardX, pitchSampleBackwardY); + final float pitchSampleGorundHeight2 = map.terrain.getGroundHeight(pitchSampleForwardX, pitchSampleForwardY); + pitch = Math.max(-maxPitch, Math.min(maxPitch, + (float) Math.atan2(pitchSampleGorundHeight2 - pitchSampleGroundHeight1, sampleRadius * 2))); + final double leftOfFacingAngle = facingRadians + (Math.PI / 2); + final float rollSampleForwardX = x + (sampleRadius * (float) Math.cos(leftOfFacingAngle)); + final float rollSampleForwardY = y + (sampleRadius * (float) Math.sin(leftOfFacingAngle)); + final float rollSampleBackwardX = x - (sampleRadius * (float) Math.cos(leftOfFacingAngle)); + final float rollSampleBackwardY = y - (sampleRadius * (float) Math.sin(leftOfFacingAngle)); + final float rollSampleGroundHeight1 = map.terrain.getGroundHeight(rollSampleBackwardX, rollSampleBackwardY); + final float rollSampleGroundHeight2 = map.terrain.getGroundHeight(rollSampleForwardX, rollSampleForwardY); + roll = Math.max(-maxRoll, Math.min(maxRoll, + (float) Math.atan2(rollSampleGroundHeight2 - rollSampleGroundHeight1, sampleRadius * 2))); + this.instance.rotate(tempQuat.setFromAxisRad(RenderMathUtils.VEC3_UNIT_Y, -pitch)); + this.instance.rotate(tempQuat.setFromAxisRad(RenderMathUtils.VEC3_UNIT_X, roll)); + map.worldScene.instanceMoved(this.instance, this.location[0], this.location[1]); if (this.shadow != null) { this.shadow.move(dx, dy, map.terrain.centerOffset); @@ -312,10 +293,6 @@ public class RenderUnit { return this.simulationUnit; } - public List getCommandCardIcons() { - return this.commandCardIcons; - } - private static final class UnitAnimationListenerImpl implements CUnitAnimationListener { private final MdxComplexInstance instance; private final EnumSet secondaryAnimationTags = EnumSet diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnitTypeData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnitTypeData.java new file mode 100644 index 0000000..649ae3b --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnitTypeData.java @@ -0,0 +1,25 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.rendersim; + +public class RenderUnitTypeData { + private final float maxPitch; + private final float maxRoll; + private final float sampleRadius; + + public RenderUnitTypeData(final float maxPitch, final float maxRoll, final float sampleRadius) { + this.maxPitch = maxPitch; + this.maxRoll = maxRoll; + this.sampleRadius = sampleRadius; + } + + public float getMaxPitch() { + return this.maxPitch; + } + + public float getMaxRoll() { + return this.maxRoll; + } + + public float getElevationSampleRadius() { + return this.sampleRadius; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityDataUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityDataUI.java new file mode 100644 index 0000000..9871acf --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityDataUI.java @@ -0,0 +1,112 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability; + +import java.util.HashMap; +import java.util.Map; + +import com.badlogic.gdx.graphics.Texture; +import com.etheller.warsmash.parsers.fdf.GameUI; +import com.etheller.warsmash.units.Element; +import com.etheller.warsmash.units.manager.MutableObjectData; +import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; +import com.etheller.warsmash.util.War3ID; + +public class AbilityDataUI { + // Standard ability icon fields + private static final War3ID ICON_NORMAL_X = War3ID.fromString("abpx"); + private static final War3ID ICON_NORMAL_Y = War3ID.fromString("abpy"); + private static final War3ID ICON_NORMAL = War3ID.fromString("aart"); + private static final War3ID ICON_TURN_OFF = War3ID.fromString("auar"); + private static final War3ID ICON_TURN_OFF_X = War3ID.fromString("aubx"); + private static final War3ID ICON_TURN_OFF_Y = War3ID.fromString("auby"); + private static final War3ID ICON_RESEARCH = War3ID.fromString("arar"); + private static final War3ID ICON_RESEARCH_X = War3ID.fromString("arpx"); + private static final War3ID ICON_RESEARCH_Y = War3ID.fromString("arpy"); + + private final Map rawcodeToUI = new HashMap<>(); + private final IconUI moveUI; + private final IconUI stopUI; + private final IconUI holdPosUI; + private final IconUI patrolUI; + private final IconUI attackUI; + private final IconUI attackGroundUI; + + public AbilityDataUI(final MutableObjectData abilityData, final GameUI gameUI) { + final String disabledPrefix = gameUI.getSkinField("CommandButtonDisabledArtPath"); + for (final War3ID alias : abilityData.keySet()) { + final MutableGameObject abilityTypeData = abilityData.get(alias); + final String iconResearchPath = gameUI.trySkinField(abilityTypeData.getFieldAsString(ICON_RESEARCH, 0)); + final String iconNormalPath = gameUI.trySkinField(abilityTypeData.getFieldAsString(ICON_NORMAL, 0)); + final String iconTurnOffPath = gameUI.trySkinField(abilityTypeData.getFieldAsString(ICON_TURN_OFF, 0)); + final int iconResearchX = abilityTypeData.getFieldAsInteger(ICON_RESEARCH_X, 0); + final int iconResearchY = abilityTypeData.getFieldAsInteger(ICON_RESEARCH_Y, 0); + final int iconNormalX = abilityTypeData.getFieldAsInteger(ICON_NORMAL_X, 0); + final int iconNormalY = abilityTypeData.getFieldAsInteger(ICON_NORMAL_Y, 0); + final int iconTurnOffX = abilityTypeData.getFieldAsInteger(ICON_TURN_OFF_X, 0); + final int iconTurnOffY = abilityTypeData.getFieldAsInteger(ICON_TURN_OFF_Y, 0); + final Texture iconResearch = gameUI.loadTexture(iconResearchPath); + final Texture iconResearchDisabled = gameUI.loadTexture(disable(iconResearchPath, disabledPrefix)); + final Texture iconNormal = gameUI.loadTexture(iconNormalPath); + final Texture iconNormalDisabled = gameUI.loadTexture(disable(iconNormalPath, disabledPrefix)); + final Texture iconTurnOff = gameUI.loadTexture(iconTurnOffPath); + final Texture iconTurnOffDisabled = gameUI.loadTexture(disable(iconTurnOffPath, disabledPrefix)); + this.rawcodeToUI.put(alias, + new AbilityIconUI(new IconUI(iconResearch, iconResearchDisabled, iconResearchX, iconResearchY), + new IconUI(iconNormal, iconNormalDisabled, iconNormalX, iconNormalY), + new IconUI(iconTurnOff, iconTurnOffDisabled, iconTurnOffX, iconTurnOffY))); + } + this.moveUI = createBuiltInIconUI(gameUI, "CmdMove", disabledPrefix); + this.stopUI = createBuiltInIconUI(gameUI, "CmdStop", disabledPrefix); + this.holdPosUI = createBuiltInIconUI(gameUI, "CmdHoldPos", disabledPrefix); + this.patrolUI = createBuiltInIconUI(gameUI, "CmdPatrol", disabledPrefix); + this.attackUI = createBuiltInIconUI(gameUI, "CmdAttack", disabledPrefix); + this.attackGroundUI = createBuiltInIconUI(gameUI, "CmdAttackGround", disabledPrefix); + } + + private IconUI createBuiltInIconUI(final GameUI gameUI, final String key, final String disabledPrefix) { + final Element builtInAbility = gameUI.getSkinData().get(key); + final String iconPath = gameUI.trySkinField(builtInAbility.getField("Art")); + final Texture icon = gameUI.loadTexture(iconPath); + final Texture iconDisabled = gameUI.loadTexture(disable(iconPath, disabledPrefix)); + final int buttonPositionX = builtInAbility.getFieldValue("Buttonpos", 0); + final int buttonPositionY = builtInAbility.getFieldValue("Buttonpos", 1); + return new IconUI(icon, iconDisabled, buttonPositionX, buttonPositionY); + } + + public AbilityIconUI getUI(final War3ID rawcode) { + return this.rawcodeToUI.get(rawcode); + } + + private static String disable(final String path, final String disabledPrefix) { + final int slashIndex = path.lastIndexOf('\\'); + String name = path; + if (slashIndex != -1) { + name = path.substring(slashIndex + 1); + } + return disabledPrefix + name; + } + + public IconUI getMoveUI() { + return this.moveUI; + } + + public IconUI getStopUI() { + return this.stopUI; + } + + public IconUI getHoldPosUI() { + return this.holdPosUI; + } + + public IconUI getPatrolUI() { + return this.patrolUI; + } + + public IconUI getAttackUI() { + return this.attackUI; + } + + public IconUI getAttackGroundUI() { + return this.attackGroundUI; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityIconUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityIconUI.java new file mode 100644 index 0000000..d75e395 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityIconUI.java @@ -0,0 +1,25 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability; + +public class AbilityIconUI { + private final IconUI learnIconUI; + private final IconUI onIconUI; + private final IconUI offIconUI; + + public AbilityIconUI(final IconUI learnIconUI, final IconUI onIconUI, final IconUI offIconUI) { + this.learnIconUI = learnIconUI; + this.onIconUI = onIconUI; + this.offIconUI = offIconUI; + } + + public IconUI getLearnIconUI() { + return this.learnIconUI; + } + + public IconUI getOnIconUI() { + return this.onIconUI; + } + + public IconUI getOffIconUI() { + return this.offIconUI; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/IconUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/IconUI.java new file mode 100644 index 0000000..1cfbdb3 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/IconUI.java @@ -0,0 +1,34 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability; + +import com.badlogic.gdx.graphics.Texture; + +public class IconUI { + private final Texture icon; + private final Texture iconDisabled; + private final int buttonPositionX; + private final int buttonPositionY; + + public IconUI(final Texture icon, final Texture iconDisabled, final int buttonPositionX, + final int buttonPositionY) { + this.icon = icon; + this.iconDisabled = iconDisabled; + this.buttonPositionX = buttonPositionX; + this.buttonPositionY = buttonPositionY; + } + + public Texture getIcon() { + return this.icon; + } + + public Texture getIconDisabled() { + return this.iconDisabled; + } + + public int getButtonPositionX() { + return this.buttonPositionX; + } + + public int getButtonPositionY() { + return this.buttonPositionY; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/AbilityCommandButton.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/AbilityCommandButton.java new file mode 100644 index 0000000..82b19aa --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/AbilityCommandButton.java @@ -0,0 +1,94 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.rendersim.commandbuttons; + +import com.badlogic.gdx.graphics.Texture; +import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.AbilityIconUI; + +public class AbilityCommandButton implements CommandButton { + private final AbilityIconUI abilityIconUI; + private final int orderId; + + public AbilityCommandButton(final AbilityIconUI abilityIconUI, final int orderId) { + this.abilityIconUI = abilityIconUI; + this.orderId = orderId; + } + + @Override + public String getToolTip() { + return null; + } + + @Override + public String getUberTip() { + return null; + } + + @Override + public int getLumberCost() { + return 0; + } + + @Override + public int getGoldCost() { + return 0; + } + + @Override + public int getManaCost() { + return 0; + } + + @Override + public int getFoodCost() { + return 0; + } + + @Override + public Texture getIcon() { + return this.abilityIconUI.getOnIconUI().getIcon(); + } + + @Override + public Texture getDisabledIcon() { + return this.abilityIconUI.getOnIconUI().getIconDisabled(); + } + + @Override + public boolean isEnabled() { + return true; + } + + @Override + public float getCooldown() { + return 0; + } + + @Override + public float getCooldownRemaining() { + return 0; + } + + @Override + public boolean isAutoCastCapable() { + return false; + } + + @Override + public boolean isAutoCastActive() { + return false; + } + + @Override + public int getButtonPositionX() { + return this.abilityIconUI.getOnIconUI().getButtonPositionX(); + } + + @Override + public int getButtonPositionY() { + return this.abilityIconUI.getOnIconUI().getButtonPositionY(); + } + + @Override + public int getOrderId() { + return this.orderId; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/BasicCommandButton.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/BasicCommandButton.java new file mode 100644 index 0000000..c9cc906 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/BasicCommandButton.java @@ -0,0 +1,95 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.rendersim.commandbuttons; + +import com.badlogic.gdx.graphics.Texture; +import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.IconUI; + +public class BasicCommandButton implements CommandButton { + private final IconUI iconUI; + private final int orderId; + + public BasicCommandButton(final IconUI iconUI, final int orderId) { + this.iconUI = iconUI; + this.orderId = orderId; + } + + @Override + public String getToolTip() { + return null; + } + + @Override + public String getUberTip() { + return null; + } + + @Override + public int getLumberCost() { + return 0; + } + + @Override + public int getGoldCost() { + return 0; + } + + @Override + public int getManaCost() { + return 0; + } + + @Override + public int getFoodCost() { + return 0; + } + + @Override + public Texture getIcon() { + return this.iconUI.getIcon(); + } + + @Override + public Texture getDisabledIcon() { + return this.iconUI.getIconDisabled(); + } + + @Override + public boolean isEnabled() { + return true; + } + + @Override + public float getCooldown() { + return 0; + } + + @Override + public float getCooldownRemaining() { + return 0; + } + + @Override + public boolean isAutoCastCapable() { + return false; + } + + @Override + public boolean isAutoCastActive() { + return false; + } + + @Override + public int getButtonPositionX() { + return this.iconUI.getButtonPositionX(); + } + + @Override + public int getButtonPositionY() { + return this.iconUI.getButtonPositionY(); + } + + @Override + public int getOrderId() { + return this.orderId; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandButton.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandButton.java new file mode 100644 index 0000000..af4e9c9 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandButton.java @@ -0,0 +1,38 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.rendersim.commandbuttons; + +import com.badlogic.gdx.graphics.Texture; + +public interface CommandButton { + String getToolTip(); + + String getUberTip(); + + int getLumberCost(); + + int getGoldCost(); + + int getManaCost(); + + int getFoodCost(); + + Texture getIcon(); + + Texture getDisabledIcon(); + + boolean isEnabled(); + + float getCooldown(); + + float getCooldownRemaining(); + + boolean isAutoCastCapable(); + + boolean isAutoCastActive(); + + int getButtonPositionX(); + + int getButtonPositionY(); + + int getOrderId(); + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandButtonListener.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandButtonListener.java new file mode 100644 index 0000000..04c2414 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandButtonListener.java @@ -0,0 +1,38 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.rendersim.commandbuttons; + +import com.badlogic.gdx.graphics.Texture; + +public interface CommandButtonListener { +// String getToolTip(); +// +// String getUberTip(); +// +// int getLumberCost(); +// +// int getGoldCost(); +// +// int getManaCost(); +// +// int getFoodCost(); +// +// Texture getIcon(); +// +// Texture getDisabledIcon(); +// +// boolean isEnabled(); +// +// float getCooldown(); +// +// float getCooldownRemaining(); +// +// boolean isAutoCastCapable(); +// +// boolean isAutoCastActive(); +// +// int getButtonPositionX(); +// +// int getButtonPositionY(); +// +// int getOrderId(); + void commandButton(int buttonPositionX, int buttonPositionY, Texture icon, int orderId); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java new file mode 100644 index 0000000..a919955 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java @@ -0,0 +1,51 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.rendersim.commandbuttons; + +import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.AbilityDataUI; +import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.IconUI; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityAttack; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityMove; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; + +public class CommandCardPopulatingAbilityVisitor implements CAbilityVisitor { + public static final CommandCardPopulatingAbilityVisitor INSTANCE = new CommandCardPopulatingAbilityVisitor(); + + private CommandButtonListener commandButtonListener; + private AbilityDataUI abilityDataUI; + private boolean hasStop; + + public CommandCardPopulatingAbilityVisitor reset(final CommandButtonListener commandButtonListener, + final AbilityDataUI abilityDataUI) { + this.commandButtonListener = commandButtonListener; + this.abilityDataUI = abilityDataUI; + this.hasStop = false; + return this; + } + + @Override + public Void accept(final CAbilityAttack ability) { + addCommandButton(this.abilityDataUI.getAttackUI(), ability.getHandleId(), OrderIds.attack); + if (!this.hasStop) { + this.hasStop = true; + addCommandButton(this.abilityDataUI.getStopUI(), -1, OrderIds.stop); + } + return null; + } + + @Override + public Void accept(final CAbilityMove ability) { + addCommandButton(this.abilityDataUI.getMoveUI(), ability.getHandleId(), OrderIds.move); + addCommandButton(this.abilityDataUI.getHoldPosUI(), ability.getHandleId(), OrderIds.holdposition); + addCommandButton(this.abilityDataUI.getPatrolUI(), ability.getHandleId(), OrderIds.patrol); + if (!this.hasStop) { + this.hasStop = true; + addCommandButton(this.abilityDataUI.getStopUI(), -1, OrderIds.stop); + } + return null; + } + + private void addCommandButton(final IconUI iconUI, final int handleId, final int orderId) { + this.commandButtonListener.commandButton(iconUI.getButtonPositionX(), iconUI.getButtonPositionY(), + iconUI.getIcon(), orderId); + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java index 3e95c5e..5d214c3 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java @@ -20,6 +20,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.projectile.C import com.etheller.warsmash.viewer5.handlers.w3x.simulation.data.CAbilityData; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.data.CUnitData; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.pathing.CPathfindingProcessor; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CAllianceType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CMapControl; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayer; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CRace; @@ -59,15 +60,17 @@ public class CSimulation { this.pathfindingProcessor = new CPathfindingProcessor(pathingGrid, this.worldCollision); this.seededRandom = seededRandom; this.players = new ArrayList<>(); - for (int i = 0; i < WarsmashConstants.MAX_PLAYERS; i++) { + for (int i = 0; i < (WarsmashConstants.MAX_PLAYERS - 4); i++) { + CPlayer newPlayer; if (i < playerInfos.size()) { final Player playerInfo = playerInfos.get(i); - this.players.add(new CPlayer(playerInfo.getId(), CMapControl.values()[playerInfo.getType()], - playerInfo.getName(), CRace.parseRace(playerInfo.getRace()), playerInfo.getStartLocation())); + newPlayer = new CPlayer(playerInfo.getId(), CMapControl.values()[playerInfo.getType()], + playerInfo.getName(), CRace.parseRace(playerInfo.getRace()), playerInfo.getStartLocation()); } else { - this.players.add(new CPlayer(i, CMapControl.NONE, "Default string", CRace.OTHER, new float[] { 0, 0 })); + newPlayer = new CPlayer(i, CMapControl.NONE, "Default string", CRace.OTHER, new float[] { 0, 0 }); } + this.players.add(newPlayer); } this.players.add(new CPlayer(this.players.size(), CMapControl.NEUTRAL, miscData.getLocalizedString("WESTRING_PLAYER_NA"), CRace.OTHER, new float[] { 0, 0 })); @@ -75,8 +78,14 @@ public class CSimulation { miscData.getLocalizedString("WESTRING_PLAYER_NV"), CRace.OTHER, new float[] { 0, 0 })); this.players.add(new CPlayer(this.players.size(), CMapControl.NEUTRAL, miscData.getLocalizedString("WESTRING_PLAYER_NE"), CRace.OTHER, new float[] { 0, 0 })); - this.players.add(new CPlayer(this.players.size(), CMapControl.NEUTRAL, - miscData.getLocalizedString("WESTRING_PLAYER_NP"), CRace.OTHER, new float[] { 0, 0 })); + final CPlayer neutralPassive = new CPlayer(this.players.size(), CMapControl.NEUTRAL, + miscData.getLocalizedString("WESTRING_PLAYER_NP"), CRace.OTHER, new float[] { 0, 0 }); + this.players.add(neutralPassive); + for (int i = 0; i < WarsmashConstants.MAX_PLAYERS; i++) { + final CPlayer cPlayer = this.players.get(i); + cPlayer.setAlliance(neutralPassive, CAllianceType.PASSIVE, true); + neutralPassive.setAlliance(cPlayer, CAllianceType.PASSIVE, true); + } } @@ -94,8 +103,8 @@ public class CSimulation { public CUnit createUnit(final War3ID typeId, final int playerIndex, final float x, final float y, final float facing, final BufferedImage buildingPathingPixelMap) { - final CUnit unit = this.unitData.create(this, playerIndex, this.handleIdAllocator.createId(), typeId, x, y, - facing, buildingPathingPixelMap); + final CUnit unit = this.unitData.create(this, playerIndex, typeId, x, y, facing, buildingPathingPixelMap, + this.simulationRenderController, this.handleIdAllocator); this.units.add(unit); this.worldCollision.addUnit(unit); return unit; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java index 9f6f623..a5a7d1a 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java @@ -18,6 +18,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.CAttackOrder; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CAllianceType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayer; @@ -389,7 +390,7 @@ public class CUnit extends CWidget { CAllianceType.PASSIVE)) { for (final CUnitAttack attack : this.unitType.getAttacks()) { if (source.canBeTargetedBy(simulation, this, attack.getTargetsAllowed())) { - this.order(new CAttackOrder(this, attack, source), false); + this.order(new CAttackOrder(this, attack, OrderIds.attack, source), false); break; } } @@ -527,6 +528,10 @@ public class CUnit extends CWidget { return this.unitType.isBuilding(); } + public float getAcquisitionRange() { + return this.acquisitionRange; + } + private static final class AutoAttackTargetFinderEnum implements CUnitEnumFunction { private CSimulation game; private CUnit source; @@ -545,7 +550,7 @@ public class CUnit extends CWidget { if (this.source.canReach(unit, this.source.acquisitionRange) && unit.canBeTargetedBy(this.game, this.source, attack.getTargetsAllowed()) && (this.source.distance(unit) >= this.source.getUnitType().getMinimumAttackRange())) { - this.source.order(new CAttackOrder(this.source, attack, unit), false); + this.source.order(new CAttackOrder(this.source, attack, OrderIds.attack, unit), false); return true; } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbility.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbility.java index ac615e7..9b5db10 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbility.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbility.java @@ -12,11 +12,9 @@ public interface CAbility extends CAbilityView { /* should fire when ability removed from unit */ void onRemove(CSimulation game, CUnit unit); - void onOrder(CSimulation game, CUnit caster, CWidget target, boolean queue); + void onOrder(CSimulation game, CUnit caster, int orderId, CWidget target, boolean queue); - void onOrder(CSimulation game, CUnit caster, Vector2 point, boolean queue); + void onOrder(CSimulation game, CUnit caster, int orderId, Vector2 point, boolean queue); - void onOrderNoTarget(CSimulation game, CUnit caster, boolean queue); - - int getOrderId(); + void onOrderNoTarget(CSimulation game, CUnit caster, int orderId, boolean queue); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java index f4ad9ef..26d07c3 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java @@ -9,33 +9,56 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.StringsToExternaliz import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.CAttackOrder; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.CMoveOrder; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver.TargetType; public class CAbilityAttack implements CAbility { - public static final int ORDER_ID = 860000; // fake, later will use WC3 one probably - public static final CAbilityAttack INSTANCE = new CAbilityAttack(); + private final int handleId; + + public CAbilityAttack(final int handleId) { + this.handleId = handleId; + } @Override - public void checkCanUse(final CSimulation game, final CUnit unit, final AbilityActivationReceiver receiver) { + public void checkCanUse(final CSimulation game, final CUnit unit, final int orderId, + final AbilityActivationReceiver receiver) { receiver.useOk(); } @Override - public void checkCanTarget(final CSimulation game, final CUnit unit, final CWidget target, + public void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, final CWidget target, final AbilityTargetCheckReceiver receiver) { - receiver.targetOk(target); + if ((orderId == OrderIds.smart) || (orderId == OrderIds.attack)) { + boolean canTarget = false; + for (final CUnitAttack attack : unit.getUnitType().getAttacks()) { + if (target.canBeTargetedBy(game, unit, attack.getTargetsAllowed())) { + canTarget = true; + break; + } + } + if (canTarget) { + receiver.targetOk(target); + } + else { + // TODO obviously we should later support better warnings here + receiver.mustTargetType(TargetType.UNIT); + } + } + else { + receiver.orderIdNotAccepted(); + } } @Override - public void checkCanTarget(final CSimulation game, final CUnit unit, final Vector2 target, + public void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, final Vector2 target, final AbilityTargetCheckReceiver receiver) { receiver.mustTargetType(TargetType.UNIT); } @Override - public void checkCanTargetNoTarget(final CSimulation game, final CUnit unit, + public void checkCanTargetNoTarget(final CSimulation game, final CUnit unit, final int orderId, final AbilityTargetCheckReceiver receiver) { receiver.mustTargetType(TargetType.UNIT); } @@ -49,33 +72,40 @@ public class CAbilityAttack implements CAbility { } @Override - public void onOrder(final CSimulation game, final CUnit caster, final CWidget target, final boolean queue) { + public void onOrder(final CSimulation game, final CUnit caster, final int orderId, final CWidget target, + final boolean queue) { COrder order = null; for (final CUnitAttack attack : caster.getUnitType().getAttacks()) { if (target.canBeTargetedBy(game, caster, attack.getTargetsAllowed())) { - order = new CAttackOrder(caster, attack, target); + order = new CAttackOrder(caster, attack, orderId, target); break; } } if (order == null) { - order = new CMoveOrder(caster, target.getX(), target.getY()); + order = new CMoveOrder(caster, orderId, target.getX(), target.getY()); } caster.order(order, queue); } @Override - public void onOrder(final CSimulation game, final CUnit caster, final Vector2 target, final boolean queue) { + public void onOrder(final CSimulation game, final CUnit caster, final int orderId, final Vector2 target, + final boolean queue) { throw new IllegalArgumentException(StringsToExternalizeLater.MUST_TARGET_WIDGET); } @Override - public void onOrderNoTarget(final CSimulation game, final CUnit caster, final boolean queue) { + public void onOrderNoTarget(final CSimulation game, final CUnit caster, final int orderId, final boolean queue) { throw new IllegalArgumentException(StringsToExternalizeLater.MUST_TARGET_WIDGET); } @Override - public int getOrderId() { - return ORDER_ID; + public T visit(final CAbilityVisitor visitor) { + return visitor.accept(this); + } + + @Override + public int getHandleId() { + return this.handleId; } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityHoldPosition.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityHoldPosition.java deleted file mode 100644 index 0ede105..0000000 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityHoldPosition.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities; - -import com.badlogic.gdx.math.Vector2; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.CDoNothingOrder; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver.TargetType; - -public class CAbilityHoldPosition implements CAbility { - public static final int ORDER_ID = 860002; // fake, later will use WC3 one probably - public static CAbilityHoldPosition INSTANCE = new CAbilityHoldPosition(); - - @Override - public void checkCanUse(final CSimulation game, final CUnit unit, final AbilityActivationReceiver receiver) { - receiver.useOk(); - } - - @Override - public void checkCanTarget(final CSimulation game, final CUnit unit, final CWidget target, - final AbilityTargetCheckReceiver receiver) { - receiver.mustTargetType(TargetType.NO_TARGET); - } - - @Override - public void checkCanTarget(final CSimulation game, final CUnit unit, final Vector2 target, - final AbilityTargetCheckReceiver receiver) { - receiver.mustTargetType(TargetType.NO_TARGET); - } - - @Override - public void checkCanTargetNoTarget(final CSimulation game, final CUnit unit, - final AbilityTargetCheckReceiver receiver) { - receiver.targetOk(null); - } - - @Override - public void onAdd(final CSimulation game, final CUnit unit) { - - } - - @Override - public void onRemove(final CSimulation game, final CUnit unit) { - - } - - @Override - public void onOrder(final CSimulation game, final CUnit caster, final CWidget target, final boolean queue) { - caster.order(new CDoNothingOrder(getOrderId()), queue); - } - - @Override - public void onOrder(final CSimulation game, final CUnit caster, final Vector2 target, final boolean queue) { - caster.order(new CDoNothingOrder(getOrderId()), queue); - } - - @Override - public void onOrderNoTarget(final CSimulation game, final CUnit caster, final boolean queue) { - caster.order(new CDoNothingOrder(getOrderId()), queue); - } - - @Override - public int getOrderId() { - return ORDER_ID; - } - -} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityMove.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityMove.java index 2fb4d21..6b71c20 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityMove.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityMove.java @@ -6,35 +6,70 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.StringsToExternalizeLater; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.CMoveOrder; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.CPatrolOrder; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver.TargetType; public class CAbilityMove implements CAbility { - public static final int ORDER_ID = 859999; // fake, later will use WC3 one probably - public static CAbilityMove INSTANCE = new CAbilityMove(); + private final int handleId; + + public CAbilityMove(final int handleId) { + this.handleId = handleId; + } @Override - public void checkCanUse(final CSimulation game, final CUnit unit, final AbilityActivationReceiver receiver) { + public void checkCanUse(final CSimulation game, final CUnit unit, final int orderId, + final AbilityActivationReceiver receiver) { receiver.useOk(); } @Override - public void checkCanTarget(final CSimulation game, final CUnit unit, final CWidget target, + public void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, final CWidget target, final AbilityTargetCheckReceiver receiver) { - receiver.mustTargetType(TargetType.POINT); + switch (orderId) { + case OrderIds.smart: + case OrderIds.patrol: + if (target instanceof CUnit) { + receiver.targetOk(target); + } + else { + receiver.mustTargetType(TargetType.UNIT_OR_POINT); + } + break; + default: + receiver.orderIdNotAccepted(); + break; + } } @Override - public void checkCanTarget(final CSimulation game, final CUnit unit, final Vector2 target, + public void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, final Vector2 target, final AbilityTargetCheckReceiver receiver) { - receiver.targetOk(target); + switch (orderId) { + case OrderIds.smart: + case OrderIds.move: + case OrderIds.patrol: + receiver.targetOk(target); + break; + default: + receiver.orderIdNotAccepted(); + break; + } } @Override - public void checkCanTargetNoTarget(final CSimulation game, final CUnit unit, + public void checkCanTargetNoTarget(final CSimulation game, final CUnit unit, final int orderId, final AbilityTargetCheckReceiver receiver) { - receiver.mustTargetType(TargetType.POINT); + switch (orderId) { + case OrderIds.holdposition: + receiver.targetOk(null); + break; + default: + receiver.orderIdNotAccepted(); + break; + } } @Override @@ -48,23 +83,30 @@ public class CAbilityMove implements CAbility { } @Override - public void onOrder(final CSimulation game, final CUnit caster, final CWidget target, final boolean queue) { + public void onOrder(final CSimulation game, final CUnit caster, final int orderId, final CWidget target, + final boolean queue) { + caster.order(new CPatrolOrder(caster, orderId, (CUnit) target), queue); + } + + @Override + public void onOrder(final CSimulation game, final CUnit caster, final int orderId, final Vector2 target, + final boolean queue) { + caster.order(new CMoveOrder(caster, orderId, target.x, target.y), queue); + } + + @Override + public void onOrderNoTarget(final CSimulation game, final CUnit caster, final int orderId, final boolean queue) { throw new IllegalArgumentException(StringsToExternalizeLater.MUST_TARGET_POINT); } @Override - public void onOrder(final CSimulation game, final CUnit caster, final Vector2 target, final boolean queue) { - caster.order(new CMoveOrder(caster, target.x, target.y), queue); + public T visit(final CAbilityVisitor visitor) { + return visitor.accept(this); } @Override - public void onOrderNoTarget(final CSimulation game, final CUnit caster, final boolean queue) { - throw new IllegalArgumentException(StringsToExternalizeLater.MUST_TARGET_POINT); - } - - @Override - public int getOrderId() { - return ORDER_ID; + public int getHandleId() { + return this.handleId; } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityPatrol.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityPatrol.java deleted file mode 100644 index 5d08daa..0000000 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityPatrol.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities; - -import com.badlogic.gdx.math.Vector2; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.StringsToExternalizeLater; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.CMoveOrder; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver.TargetType; - -public class CAbilityPatrol implements CAbility { - public static final int ORDER_ID = 860001; // fake, later will use WC3 one probably - public static CAbilityPatrol INSTANCE = new CAbilityPatrol(); - - @Override - public void checkCanUse(final CSimulation game, final CUnit unit, final AbilityActivationReceiver receiver) { - receiver.useOk(); - } - - @Override - public void checkCanTarget(final CSimulation game, final CUnit unit, final CWidget target, - final AbilityTargetCheckReceiver receiver) { - receiver.mustTargetType(TargetType.POINT); - } - - @Override - public void checkCanTarget(final CSimulation game, final CUnit unit, final Vector2 target, - final AbilityTargetCheckReceiver receiver) { - receiver.targetOk(target); - } - - @Override - public void checkCanTargetNoTarget(final CSimulation game, final CUnit unit, - final AbilityTargetCheckReceiver receiver) { - receiver.mustTargetType(TargetType.POINT); - } - - @Override - public void onAdd(final CSimulation game, final CUnit unit) { - - } - - @Override - public void onRemove(final CSimulation game, final CUnit unit) { - - } - - @Override - public void onOrder(final CSimulation game, final CUnit caster, final CWidget target, final boolean queue) { - throw new IllegalArgumentException(StringsToExternalizeLater.MUST_TARGET_POINT); - } - - @Override - public void onOrder(final CSimulation game, final CUnit caster, final Vector2 target, final boolean queue) { - caster.order(new CMoveOrder(caster, target.x, target.y), queue); - } - - @Override - public void onOrderNoTarget(final CSimulation game, final CUnit caster, final boolean queue) { - throw new IllegalArgumentException(StringsToExternalizeLater.MUST_TARGET_POINT); - } - - @Override - public int getOrderId() { - return ORDER_ID; - } - -} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityStop.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityStop.java deleted file mode 100644 index 352d47b..0000000 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityStop.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities; - -import com.badlogic.gdx.math.Vector2; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.CDoNothingOrder; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver.TargetType; - -public class CAbilityStop implements CAbility { - public static final int ORDER_ID = 860003; // fake, later will use WC3 one probably - public static CAbilityStop INSTANCE = new CAbilityStop(); - - @Override - public void checkCanUse(final CSimulation game, final CUnit unit, final AbilityActivationReceiver receiver) { - receiver.useOk(); - } - - @Override - public void checkCanTarget(final CSimulation game, final CUnit unit, final CWidget target, - final AbilityTargetCheckReceiver receiver) { - receiver.mustTargetType(TargetType.NO_TARGET); - } - - @Override - public void checkCanTarget(final CSimulation game, final CUnit unit, final Vector2 target, - final AbilityTargetCheckReceiver receiver) { - receiver.mustTargetType(TargetType.NO_TARGET); - } - - @Override - public void checkCanTargetNoTarget(final CSimulation game, final CUnit unit, - final AbilityTargetCheckReceiver receiver) { - receiver.targetOk(null); - } - - @Override - public void onAdd(final CSimulation game, final CUnit unit) { - - } - - @Override - public void onRemove(final CSimulation game, final CUnit unit) { - - } - - @Override - public void onOrder(final CSimulation game, final CUnit caster, final CWidget target, final boolean queue) { - caster.order(new CDoNothingOrder(getOrderId()), queue); - } - - @Override - public void onOrder(final CSimulation game, final CUnit caster, final Vector2 target, final boolean queue) { - caster.order(new CDoNothingOrder(getOrderId()), queue); - } - - @Override - public void onOrderNoTarget(final CSimulation game, final CUnit caster, final boolean queue) { - caster.order(new CDoNothingOrder(getOrderId()), queue); - } - - @Override - public int getOrderId() { - return ORDER_ID; - } - -} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityToggleableView.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityToggleableView.java new file mode 100644 index 0000000..810dd9f --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityToggleableView.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities; + +public interface CAbilityToggleableView extends CAbilityView { + boolean isActive(); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityView.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityView.java index ad90fd0..8ef4b1a 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityView.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityView.java @@ -8,12 +8,17 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivat import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; public interface CAbilityView { - void checkCanUse(CSimulation game, CUnit unit, AbilityActivationReceiver receiver); + void checkCanUse(CSimulation game, CUnit unit, int orderId, AbilityActivationReceiver receiver); - void checkCanTarget(CSimulation game, CUnit unit, CWidget target, AbilityTargetCheckReceiver receiver); + void checkCanTarget(CSimulation game, CUnit unit, int orderId, CWidget target, + AbilityTargetCheckReceiver receiver); - void checkCanTarget(CSimulation game, CUnit unit, Vector2 target, AbilityTargetCheckReceiver receiver); + void checkCanTarget(CSimulation game, CUnit unit, int orderId, Vector2 target, + AbilityTargetCheckReceiver receiver); - void checkCanTargetNoTarget(CSimulation game, CUnit unit, AbilityTargetCheckReceiver receiver); + void checkCanTargetNoTarget(CSimulation game, CUnit unit, int orderId, AbilityTargetCheckReceiver receiver); + int getHandleId(); + + T visit(CAbilityVisitor visitor); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityVisitor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityVisitor.java new file mode 100644 index 0000000..626aec9 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityVisitor.java @@ -0,0 +1,15 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities; + +/** + * A visitor for the lowest level inherent types of an ability. It's a bit of a + * design clash to have the notion of an ability visitor pattern while also + * having any arbitrary number of "ability types" defined in config files. But + * the way that we will handle this for now will be with the notion of a generic + * ability (one whose UI information and behaviors come from a rawcode) versus + * abilities with engine-level type information (move, stop, attack). + */ +public interface CAbilityVisitor { + T accept(CAbilityAttack ability); + + T accept(CAbilityMove ability); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java index a790f44..a9ebc8d 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java @@ -15,11 +15,9 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitClassification; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.HandleIdAllocator; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityAttack; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityHoldPosition; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityMove; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityPatrol; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityStop; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CDefenseType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; @@ -31,6 +29,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUni import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackMissileLine; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackMissileSplash; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackNormal; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.SimulationRenderController; public class CUnitData { private static final War3ID MANA_INITIAL_AMOUNT = War3ID.fromString("umpi"); @@ -126,9 +125,11 @@ public class CUnitData { this.unitData = unitData; } - public CUnit create(final CSimulation simulation, final int playerIndex, final int handleId, final War3ID typeId, - final float x, final float y, final float facing, final BufferedImage buildingPathingPixelMap) { + public CUnit create(final CSimulation simulation, final int playerIndex, final War3ID typeId, final float x, + final float y, final float facing, final BufferedImage buildingPathingPixelMap, + final SimulationRenderController simulationRenderController, final HandleIdAllocator handleIdAllocator) { final MutableGameObject unitType = this.unitData.get(typeId); + final int handleId = handleIdAllocator.createId(); final int life = unitType.getFieldAsInteger(HIT_POINT_MAXIMUM, 0); final int manaInitial = unitType.getFieldAsInteger(MANA_INITIAL_AMOUNT, 0); final int manaMaximum = unitType.getFieldAsInteger(MANA_MAXIMUM, 0); @@ -140,15 +141,10 @@ public class CUnitData { final CUnit unit = new CUnit(handleId, playerIndex, x, y, life, typeId, facing, manaInitial, life, manaMaximum, speed, defense, unitTypeInstance); if (speed > 0) { - unit.add(simulation, CAbilityMove.INSTANCE); - unit.add(simulation, CAbilityPatrol.INSTANCE); - unit.add(simulation, CAbilityHoldPosition.INSTANCE); - unit.add(simulation, CAbilityStop.INSTANCE); + unit.add(simulation, new CAbilityMove(handleIdAllocator.createId())); } - final int dmgDice1 = unitType.getFieldAsInteger(ATTACK1_DMG_DICE, 0); - final int dmgDice2 = unitType.getFieldAsInteger(ATTACK2_DMG_DICE, 0); - if ((dmgDice1 != 0) || (dmgDice2 != 0)) { - unit.add(simulation, CAbilityAttack.INSTANCE); + if (!unitTypeInstance.getAttacks().isEmpty()) { + unit.add(simulation, new CAbilityAttack(handleIdAllocator.createId())); } return unit; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CAttackOrder.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CAttackOrder.java index 29d4887..7bc942d 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CAttackOrder.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CAttackOrder.java @@ -7,11 +7,11 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.COrder; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityAttack; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; public class CAttackOrder implements COrder { private final CUnit unit; + private final int orderId; private boolean wasWithinPropWindow = false; private final CUnitAttack unitAttack; private final CWidget target; @@ -21,9 +21,10 @@ public class CAttackOrder implements COrder { private int thisOrderCooldownEndTime; private boolean wasInRange = false; - public CAttackOrder(final CUnit unit, final CUnitAttack unitAttack, final CWidget target) { + public CAttackOrder(final CUnit unit, final CUnitAttack unitAttack, final int orderId, final CWidget target) { this.unit = unit; this.unitAttack = unitAttack; + this.orderId = orderId; this.target = target; createMoveOrder(unit, target); } @@ -31,10 +32,10 @@ public class CAttackOrder implements COrder { private void createMoveOrder(final CUnit unit, final CWidget target) { if (!unit.isMovementDisabled()) { // TODO: Check mobility instead if ((target instanceof CUnit) && !(((CUnit) target).getUnitType().isBuilding())) { - this.moveOrder = new CMoveOrder(unit, (CUnit) target); + this.moveOrder = new CMoveOrder(unit, this.orderId, (CUnit) target); } else { - this.moveOrder = new CMoveOrder(unit, target.getX(), target.getY()); + this.moveOrder = new CMoveOrder(unit, this.orderId, target.getX(), target.getY()); } } else { @@ -172,7 +173,7 @@ public class CAttackOrder implements COrder { @Override public int getOrderId() { - return CAbilityAttack.ORDER_ID; + return this.orderId; } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CMoveOrder.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CMoveOrder.java index 83677cd..57b0e38 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CMoveOrder.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CMoveOrder.java @@ -14,12 +14,12 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.COrder; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWorldCollision; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityMove; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.pathing.CPathfindingProcessor; public class CMoveOrder implements COrder { private static final Rectangle tempRect = new Rectangle(); private final CUnit unit; + private final int orderId; private boolean wasWithinPropWindow = false; private List path = null; private final CPathfindingProcessor.GridMapping gridMapping; @@ -27,16 +27,18 @@ public class CMoveOrder implements COrder { private int searchCycles = 0; private CUnit followUnit; - public CMoveOrder(final CUnit unit, final float targetX, final float targetY) { + public CMoveOrder(final CUnit unit, final int orderId, final float targetX, final float targetY) { this.unit = unit; + this.orderId = orderId; this.gridMapping = CPathfindingProcessor.isCollisionSizeBetterSuitedForCorners( unit.getUnitType().getCollisionSize()) ? CPathfindingProcessor.GridMapping.CORNERS : CPathfindingProcessor.GridMapping.CELLS; this.target = new Point2D.Float(targetX, targetY); } - public CMoveOrder(final CUnit unit, final CUnit followUnit) { + public CMoveOrder(final CUnit unit, final int orderId, final CUnit followUnit) { this.unit = unit; + this.orderId = orderId; this.gridMapping = CPathfindingProcessor.isCollisionSizeBetterSuitedForCorners( unit.getUnitType().getCollisionSize()) ? CPathfindingProcessor.GridMapping.CORNERS : CPathfindingProcessor.GridMapping.CELLS; @@ -313,7 +315,7 @@ public class CMoveOrder implements COrder { @Override public int getOrderId() { - return CAbilityMove.ORDER_ID; + return this.orderId; } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CPatrolOrder.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CPatrolOrder.java new file mode 100644 index 0000000..078e83f --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CPatrolOrder.java @@ -0,0 +1,58 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.COrder; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; + +public class CPatrolOrder implements COrder { + private final CUnit unit; + private final int orderId; + private final CWidget target; + private COrder moveOrder; + + public CPatrolOrder(final CUnit unit, final int orderId, final CUnit target) { + this.unit = unit; + this.orderId = orderId; + this.target = target; + createMoveOrder(unit, target); + } + + private void createMoveOrder(final CUnit unit, final CUnit target) { + if (!unit.isMovementDisabled()) { // TODO: Check mobility instead + if ((target instanceof CUnit) && !(target.getUnitType().isBuilding())) { + this.moveOrder = new CMoveOrder(unit, this.orderId, target); + } + else { + this.moveOrder = new CMoveOrder(unit, this.orderId, target.getX(), target.getY()); + } + } + else { + this.moveOrder = null; + } + } + + @Override + public boolean update(final CSimulation simulation) { + if (this.target.isDead()) { + return true; + } + final float range = this.unit.getAcquisitionRange(); + if (!this.unit.canReach(this.target, range)) { + if (this.moveOrder == null) { + return true; + } + if (this.moveOrder.update(simulation)) { + return true; // we just cant reach them + } + return false; + } + return false; + } + + @Override + public int getOrderId() { + return this.orderId; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/OrderIds.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/OrderIds.java new file mode 100644 index 0000000..ee9022d --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/OrderIds.java @@ -0,0 +1,457 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders; + +/** + * Thanks to the Wurst guys for this list of ids, taken from this link: + * https://github.com/wurstscript/WurstStdlib2/blob/master/wurst/_wurst/assets/Orders.wurst + * + * The original code ported to create this Java file is licensed under the + * Apache License; you can read more at the link above. + * + */ +public class OrderIds { + ;/** + * This is an order with no target that opens up the build menu of a unit that + * can build structures. + */ + ; + public static final int buildmenu = 851994; + /** + * 851976 (cancel): This is an order with no target that is like a click on a + * cancel button. We used to be able to catch cancel clicks with this id back + * then but this id doesn't seem to work any more. + */ + public static final int cancel = 851976; + /** + * An item targeted order that move the target item to a certain inventory slot + * of the ordered hero. + */ + public static final int itemdrag00 = 852002; + /** + * An item targeted order that move the target item to a certain inventory slot + * of the ordered hero. + */ + public static final int itemdrag01 = 852003; + /** + * An item targeted order that move the target item to a certain inventory slot + * of the ordered hero. + */ + public static final int itemdrag02 = 852004; + /** + * An item targeted order that move the target item to a certain inventory slot + * of the ordered hero. + */ + public static final int itemdrag03 = 852005; + /** + * An item targeted order that move the target item to a certain inventory slot + * of the ordered hero. + */ + public static final int itemdrag04 = 852006; + /** + * An item targeted order that move the target item to a certain inventory slot + * of the ordered hero. + */ + public static final int itemdrag05 = 852007; + /** + * An order that will make the ordered hero use the item in a certain inventory + * slot. If it's an order with no target or object or point targeted depends on + * the type of item. + */ + public static final int itemuse00 = 852008; + /** + * An order that will make the ordered hero use the item in a certain inventory + * slot. If it's an order with no target or object or point targeted depends on + * the type of item. + */ + public static final int itemuse01 = 852009; + /** + * An order that will make the ordered hero use the item in a certain inventory + * slot. If it's an order with no target or object or point targeted depends on + * the type of item. + */ + public static final int itemuse02 = 852010; + /** + * An order that will make the ordered hero use the item in a certain inventory + * slot. If it's an order with no target or object or point targeted depends on + * the type of item. + */ + public static final int itemuse03 = 852011; + /** + * An order that will make the ordered hero use the item in a certain inventory + * slot. If it's an order with no target or object or point targeted depends on + * the type of item. + */ + public static final int itemuse04 = 852012; + /** + * An order that will make the ordered hero use the item in a certain inventory + * slot. If it's an order with no target or object or point targeted depends on + * the type of item. + */ + public static final int itemuse05 = 852013; + /** + * Order for AIaa ability, which blizzard made for tome of attack, but never + * used it. But it can actually change caster's base attack! + */ + public static final int tomeOfAttack = 852259; + /** This is a point or object targeted order that is like a right click. */ + public static final int smart = 851971; + /** + * This is an order with no target that opens the skill menu of heroes. If it is + * issued for a normal unit with triggers it will black out the command card for + * this unit, the command card will revert to normal after reselecting the unit. + */ + public static final int skillmenu = 852000; + /** + * This order is issued to units that get stunned by a spell, for example War + * Stomp (AOws). This is probably a hold position + hold fire order. The ordered + * unit will be unable to move and attack. + */ + public static final int stunned = 851973; + public static final int wandOfIllusion = 852274; + public static final int absorb = 852529; + public static final int acidbomb = 852662; + public static final int acolyteharvest = 852185; + public static final int ambush = 852131; + public static final int ancestralspirit = 852490; + public static final int ancestralspirittarget = 852491; + public static final int animatedead = 852217; + public static final int antimagicshell = 852186; + public static final int attack = 851983; + public static final int attackground = 851984; + public static final int attackonce = 851985; + public static final int attributemodskill = 852576; + public static final int auraunholy = 852215; + public static final int auravampiric = 852216; + public static final int autodispel = 852132; + public static final int autodispeloff = 852134; + public static final int autodispelon = 852133; + public static final int autoentangle = 852505; + public static final int autoentangleinstant = 852506; + public static final int autoharvestgold = 852021; + public static final int autoharvestlumber = 852022; + public static final int avatar = 852086; + public static final int avengerform = 852531; + public static final int awaken = 852466; + public static final int banish = 852486; + public static final int barkskin = 852135; + public static final int barkskinoff = 852137; + public static final int barkskinon = 852136; + public static final int battleroar = 852599; + public static final int battlestations = 852099; + public static final int bearform = 852138; + public static final int berserk = 852100; + public static final int blackarrow = 852577; + public static final int blackarrowoff = 852579; + public static final int blackarrowon = 852578; + public static final int blight = 852187; + public static final int blink = 852525; + public static final int blizzard = 852089; + public static final int bloodlust = 852101; + public static final int bloodlustoff = 852103; + public static final int bloodluston = 852102; + public static final int board = 852043; + public static final int breathoffire = 852580; + public static final int breathoffrost = 852560; + public static final int build = 851994; + public static final int burrow = 852533; + public static final int cannibalize = 852188; + public static final int carrionscarabs = 852551; + public static final int carrionscarabsinstant = 852554; + public static final int carrionscarabsoff = 852553; + public static final int carrionscarabson = 852552; + public static final int carrionswarm = 852218; + public static final int chainlightning = 852119; + public static final int channel = 852600; + public static final int charm = 852581; + public static final int chemicalrage = 852663; + public static final int cloudoffog = 852473; + public static final int clusterrockets = 852652; + public static final int coldarrows = 852244; + public static final int coldarrowstarg = 852243; + public static final int controlmagic = 852474; + public static final int corporealform = 852493; + public static final int corrosivebreath = 852140; + public static final int coupleinstant = 852508; + public static final int coupletarget = 852507; + public static final int creepanimatedead = 852246; + public static final int creepdevour = 852247; + public static final int creepheal = 852248; + public static final int creephealoff = 852250; + public static final int creephealon = 852249; + public static final int creepthunderbolt = 852252; + public static final int creepthunderclap = 852253; + public static final int cripple = 852189; + public static final int curse = 852190; + public static final int curseoff = 852192; + public static final int curseon = 852191; + public static final int cyclone = 852144; + public static final int darkconversion = 852228; + public static final int darkportal = 852229; + public static final int darkritual = 852219; + public static final int darksummoning = 852220; + public static final int deathanddecay = 852221; + public static final int deathcoil = 852222; + public static final int deathpact = 852223; + public static final int decouple = 852509; + public static final int defend = 852055; + public static final int detectaoe = 852015; + public static final int detonate = 852145; + public static final int devour = 852104; + public static final int devourmagic = 852536; + public static final int disassociate = 852240; + public static final int disenchant = 852495; + public static final int dismount = 852470; + public static final int dispel = 852057; + public static final int divineshield = 852090; + public static final int doom = 852583; + public static final int drain = 852487; + public static final int dreadlordinferno = 852224; + public static final int dropitem = 852001; + public static final int drunkenhaze = 852585; + public static final int earthquake = 852121; + public static final int eattree = 852146; + public static final int elementalfury = 852586; + public static final int ensnare = 852106; + public static final int ensnareoff = 852108; + public static final int ensnareon = 852107; + public static final int entangle = 852147; + public static final int entangleinstant = 852148; + public static final int entanglingroots = 852171; + public static final int etherealform = 852496; + public static final int evileye = 852105; + public static final int faeriefire = 852149; + public static final int faeriefireoff = 852151; + public static final int faeriefireon = 852150; + public static final int fanofknives = 852526; + public static final int farsight = 852122; + public static final int fingerofdeath = 852230; + public static final int firebolt = 852231; + public static final int flamestrike = 852488; + public static final int flamingarrows = 852174; + public static final int flamingarrowstarg = 852173; + public static final int flamingattack = 852540; + public static final int flamingattacktarg = 852539; + public static final int flare = 852060; + public static final int forceboard = 852044; + public static final int forceofnature = 852176; + public static final int forkedlightning = 852587; + public static final int freezingbreath = 852195; + public static final int frenzy = 852561; + public static final int frenzyoff = 852563; + public static final int frenzyon = 852562; + public static final int frostarmor = 852225; + public static final int frostarmoroff = 852459; + public static final int frostarmoron = 852458; + public static final int frostnova = 852226; + public static final int getitem = 851981; + public static final int gold2lumber = 852233; + public static final int grabtree = 852511; + public static final int harvest = 852018; + public static final int heal = 852063; + public static final int healingspray = 852664; + public static final int healingward = 852109; + public static final int healingwave = 852501; + public static final int healoff = 852065; + public static final int healon = 852064; + public static final int hex = 852502; + public static final int holdposition = 851993; + public static final int holybolt = 852092; + public static final int howlofterror = 852588; + public static final int humanbuild = 851995; + public static final int immolation = 852177; + public static final int impale = 852555; + public static final int incineratearrow = 852670; + public static final int incineratearrowoff = 852672; + public static final int incineratearrowon = 852671; + public static final int inferno = 852232; + public static final int innerfire = 852066; + public static final int innerfireoff = 852068; + public static final int innerfireon = 852067; + public static final int instant = 852200; + public static final int invisibility = 852069; + public static final int lavamonster = 852667; + public static final int lightningshield = 852110; + public static final int load = 852046; + public static final int loadarcher = 852142; + public static final int loadcorpse = 852050; + public static final int loadcorpseinstant = 852053; + public static final int locustswarm = 852556; + public static final int lumber2gold = 852234; + public static final int magicdefense = 852478; + public static final int magicleash = 852480; + public static final int magicundefense = 852479; + public static final int manaburn = 852179; + public static final int manaflareoff = 852513; + public static final int manaflareon = 852512; + public static final int manashieldoff = 852590; + public static final int manashieldon = 852589; + public static final int massteleport = 852093; + public static final int mechanicalcritter = 852564; + public static final int metamorphosis = 852180; + public static final int militia = 852072; + public static final int militiaconvert = 852071; + public static final int militiaoff = 852073; + public static final int militiaunconvert = 852651; + public static final int mindrot = 852565; + public static final int mirrorimage = 852123; + public static final int monsoon = 852591; + public static final int mount = 852469; + public static final int mounthippogryph = 852143; + public static final int move = 851986; + public static final int moveAI = 851988; + public static final int nagabuild = 852467; + public static final int neutraldetectaoe = 852023; + public static final int neutralinteract = 852566; + public static final int neutralspell = 852630; + public static final int nightelfbuild = 851997; + public static final int orcbuild = 851996; + public static final int parasite = 852601; + public static final int parasiteoff = 852603; + public static final int parasiteon = 852602; + public static final int patrol = 851990; + public static final int phaseshift = 852514; + public static final int phaseshiftinstant = 852517; + public static final int phaseshiftoff = 852516; + public static final int phaseshifton = 852515; + public static final int phoenixfire = 852481; + public static final int phoenixmorph = 852482; + public static final int poisonarrows = 852255; + public static final int poisonarrowstarg = 852254; + public static final int polymorph = 852074; + public static final int possession = 852196; + public static final int preservation = 852568; + public static final int purge = 852111; + public static final int rainofchaos = 852237; + public static final int rainoffire = 852238; + public static final int raisedead = 852197; + public static final int raisedeadoff = 852199; + public static final int raisedeadon = 852198; + public static final int ravenform = 852155; + public static final int recharge = 852157; + public static final int rechargeoff = 852159; + public static final int rechargeon = 852158; + public static final int rejuvination = 852160; + public static final int renew = 852161; + public static final int renewoff = 852163; + public static final int renewon = 852162; + public static final int repair = 852024; + public static final int repairoff = 852026; + public static final int repairon = 852025; + public static final int replenish = 852542; + public static final int replenishlife = 852545; + public static final int replenishlifeoff = 852547; + public static final int replenishlifeon = 852546; + public static final int replenishmana = 852548; + public static final int replenishmanaoff = 852550; + public static final int replenishmanaon = 852549; + public static final int replenishoff = 852544; + public static final int replenishon = 852543; + public static final int request_hero = 852239; + public static final int requestsacrifice = 852201; + public static final int restoration = 852202; + public static final int restorationoff = 852204; + public static final int restorationon = 852203; + public static final int resumebuild = 851999; + public static final int resumeharvesting = 852017; + public static final int resurrection = 852094; + public static final int returnresources = 852020; + public static final int revenge = 852241; + public static final int revive = 852039; + public static final int roar = 852164; + public static final int robogoblin = 852656; + public static final int root = 852165; + public static final int sacrifice = 852205; + public static final int sanctuary = 852569; + public static final int scout = 852181; + public static final int selfdestruct = 852040; + public static final int selfdestructoff = 852042; + public static final int selfdestructon = 852041; + public static final int sentinel = 852182; + public static final int setrally = 851980; + public static final int shadowsight = 852570; + public static final int shadowstrike = 852527; + public static final int shockwave = 852125; + public static final int silence = 852592; + public static final int sleep = 852227; + public static final int slow = 852075; + public static final int slowoff = 852077; + public static final int slowon = 852076; + public static final int soulburn = 852668; + public static final int soulpreservation = 852242; + public static final int spellshield = 852571; + public static final int spellshieldaoe = 852572; + public static final int spellsteal = 852483; + public static final int spellstealoff = 852485; + public static final int spellstealon = 852484; + public static final int spies = 852235; + public static final int spiritlink = 852499; + public static final int spiritofvengeance = 852528; + public static final int spirittroll = 852573; + public static final int spiritwolf = 852126; + public static final int stampede = 852593; + public static final int standdown = 852113; + public static final int starfall = 852183; + public static final int stasistrap = 852114; + public static final int steal = 852574; + public static final int stomp = 852127; + public static final int stoneform = 852206; + public static final int stop = 851972; + public static final int submerge = 852604; + public static final int summonfactory = 852658; + public static final int summongrizzly = 852594; + public static final int summonphoenix = 852489; + public static final int summonquillbeast = 852595; + public static final int summonwareagle = 852596; + public static final int tankdroppilot = 852079; + public static final int tankloadpilot = 852080; + public static final int tankpilot = 852081; + public static final int taunt = 852520; + public static final int thunderbolt = 852095; + public static final int thunderclap = 852096; + public static final int tornado = 852597; + public static final int townbelloff = 852083; + public static final int townbellon = 852082; + public static final int tranquility = 852184; + public static final int transmute = 852665; + public static final int unavatar = 852087; + public static final int unavengerform = 852532; + public static final int unbearform = 852139; + public static final int unburrow = 852534; + public static final int uncoldarrows = 852245; + public static final int uncorporealform = 852494; + public static final int undeadbuild = 851998; + public static final int undefend = 852056; + public static final int undivineshield = 852091; + public static final int unetherealform = 852497; + public static final int unflamingarrows = 852175; + public static final int unflamingattack = 852541; + public static final int unholyfrenzy = 852209; + public static final int unimmolation = 852178; + public static final int unload = 852047; + public static final int unloadall = 852048; + public static final int unloadallcorpses = 852054; + public static final int unloadallinstant = 852049; + public static final int unpoisonarrows = 852256; + public static final int unravenform = 852156; + public static final int unrobogoblin = 852657; + public static final int unroot = 852166; + public static final int unstableconcoction = 852500; + public static final int unstoneform = 852207; + public static final int unsubmerge = 852605; + public static final int unsummon = 852210; + public static final int unwindwalk = 852130; + public static final int vengeance = 852521; + public static final int vengeanceinstant = 852524; + public static final int vengeanceoff = 852523; + public static final int vengeanceon = 852522; + public static final int volcano = 852669; + public static final int voodoo = 852503; + public static final int ward = 852504; + public static final int waterelemental = 852097; + public static final int wateryminion = 852598; + public static final int web = 852211; + public static final int weboff = 852213; + public static final int webon = 852212; + public static final int whirlwind = 852128; + public static final int windwalk = 852129; + public static final int wispharvest = 852214; +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/AbilityTargetCheckReceiver.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/AbilityTargetCheckReceiver.java index 0faccd5..2d11d29 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/AbilityTargetCheckReceiver.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/AbilityTargetCheckReceiver.java @@ -17,6 +17,8 @@ public interface AbilityTargetCheckReceiver { void targetNotInPlayableMap(); + void orderIdNotAccepted(); + public static enum TeamType { ALLIED, ENEMY, diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/CWidgetAbilityTargetCheckReceiver.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/CWidgetAbilityTargetCheckReceiver.java index 94bce87..aa960e4 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/CWidgetAbilityTargetCheckReceiver.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/CWidgetAbilityTargetCheckReceiver.java @@ -48,6 +48,11 @@ public class CWidgetAbilityTargetCheckReceiver implements AbilityTargetCheckRece this.target = null; } + @Override + public void orderIdNotAccepted() { + this.target = null; + } + public CWidget getTarget() { return this.target; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/PointAbilityTargetCheckReceiver.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/PointAbilityTargetCheckReceiver.java index 6c4721a..ad86eb7 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/PointAbilityTargetCheckReceiver.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/PointAbilityTargetCheckReceiver.java @@ -20,7 +20,6 @@ public class PointAbilityTargetCheckReceiver implements AbilityTargetCheckReceiv @Override public void mustTargetType(final TargetType correctType) { this.target = null; - } @Override @@ -48,6 +47,11 @@ public class PointAbilityTargetCheckReceiver implements AbilityTargetCheckReceiv this.target = null; } + @Override + public void orderIdNotAccepted() { + this.target = null; + } + public Vector2 getTarget() { return this.target; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java new file mode 100644 index 0000000..2449cb6 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java @@ -0,0 +1,88 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.ui; + +import com.badlogic.gdx.graphics.Texture; +import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.g2d.GlyphLayout; +import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.badlogic.gdx.utils.viewport.Viewport; +import com.etheller.warsmash.parsers.fdf.frames.AbstractRenderableFrame; +import com.etheller.warsmash.parsers.fdf.frames.SpriteFrame; +import com.etheller.warsmash.parsers.fdf.frames.TextureFrame; +import com.etheller.warsmash.parsers.fdf.frames.UIFrame; +import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; +import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.commandbuttons.CommandButton; + +public class CommandCardIcon extends AbstractRenderableFrame { + + private TextureFrame iconFrame; + private SpriteFrame cooldownFrame; + private SpriteFrame autocastFrame; + private CommandButton commandButton; + + public CommandCardIcon(final String name, final UIFrame parent) { + super(name, parent); + } + + public void set(final TextureFrame iconFrame, final SpriteFrame cooldownFrame, final SpriteFrame autocastFrame) { + this.iconFrame = iconFrame; + this.cooldownFrame = cooldownFrame; + this.autocastFrame = autocastFrame; + } + + public void setCommandButton(final CommandButton commandButton) { + this.commandButton = commandButton; + if (commandButton == null) { + this.iconFrame.setVisible(false); + this.cooldownFrame.setVisible(false); + this.autocastFrame.setVisible(false); + } + else { + if (commandButton.isEnabled()) { + this.iconFrame.setTexture(commandButton.getIcon()); + } + else { + this.iconFrame.setTexture(commandButton.getDisabledIcon()); + } + if (commandButton.getCooldownRemaining() <= 0) { + this.cooldownFrame.setVisible(false); + } + else { + this.cooldownFrame.setVisible(true); + this.cooldownFrame.setSequence(PrimaryTag.STAND); + this.cooldownFrame.setAnimationSpeed(commandButton.getCooldown()); + this.cooldownFrame + .setFrameByRatio(1 - (commandButton.getCooldownRemaining() / commandButton.getCooldown())); + } + this.autocastFrame.setVisible(commandButton.isAutoCastActive()); + } + } + + public void setCommandButtonData(final Texture texture, final int orderId) { + this.iconFrame.setVisible(true); + this.cooldownFrame.setVisible(false); + this.autocastFrame.setVisible(false); + this.iconFrame.setTexture(texture); + } + + @Override + protected void innerPositionBounds(final Viewport viewport) { + this.iconFrame.positionBounds(viewport); + this.cooldownFrame.positionBounds(viewport); + this.autocastFrame.positionBounds(viewport); + } + + @Override + protected void internalRender(final SpriteBatch batch, final BitmapFont baseFont, final GlyphLayout glyphLayout) { + this.iconFrame.render(batch, baseFont, glyphLayout); + this.cooldownFrame.render(batch, baseFont, glyphLayout); + this.autocastFrame.render(batch, baseFont, glyphLayout); + } + + @Override + public UIFrame touchDown(final float screenX, final float screenY, final int button) { + if (this.renderBounds.contains(screenX, screenY)) { + return this; + } + return null; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java index 4a0e87d..1c57b9a 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java @@ -2,8 +2,10 @@ package com.etheller.warsmash.viewer5.handlers.w3x.ui; import java.io.IOException; +import javax.imageio.ImageIO; + +import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.Color; -import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.GlyphLayout; @@ -12,7 +14,6 @@ import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.utils.viewport.Viewport; -import com.etheller.warsmash.WarsmashGdxMapGame; import com.etheller.warsmash.datasources.DataSource; import com.etheller.warsmash.parsers.fdf.GameUI; import com.etheller.warsmash.parsers.fdf.datamodel.AnchorDefinition; @@ -25,28 +26,39 @@ import com.etheller.warsmash.parsers.fdf.frames.UIFrame; import com.etheller.warsmash.parsers.jass.Jass2.RootFrameListener; import com.etheller.warsmash.util.FastNumberFormat; import com.etheller.warsmash.util.ImageUtils; +import com.etheller.warsmash.util.WarsmashConstants; import com.etheller.warsmash.viewer5.Scene; import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; +import com.etheller.warsmash.viewer5.handlers.mdx.ReplaceableIds; import com.etheller.warsmash.viewer5.handlers.mdx.SequenceLoopMode; +import com.etheller.warsmash.viewer5.handlers.tga.TgaFile; import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; -import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.CommandCardIcon; +import com.etheller.warsmash.viewer5.handlers.w3x.camera.CameraPreset; +import com.etheller.warsmash.viewer5.handlers.w3x.camera.CameraRates; +import com.etheller.warsmash.viewer5.handlers.w3x.camera.GameCameraManager; +import com.etheller.warsmash.viewer5.handlers.w3x.camera.PortraitCameraManager; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderUnit; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.COrder; +import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.commandbuttons.CommandButtonListener; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitClassification; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitStateListener; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityStop; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CDefenseType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CodeKeyType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; -public class MeleeUI implements CUnitStateListener { +public class MeleeUI implements CUnitStateListener, CommandButtonListener { + private static final int COMMAND_CARD_WIDTH = 4; + private static final int COMMAND_CARD_HEIGHT = 3; + + private static final Vector2 screenCoordsVector = new Vector2(); private final DataSource dataSource; private final Viewport uiViewport; private final FreeTypeFontGenerator fontGenerator; private final Scene uiScene; + private final Scene portraitScene; + private final GameCameraManager cameraManager; private final War3MapViewer war3MapViewer; private final RootFrameListener rootFrameListener; private GameUI rootFrame; @@ -82,23 +94,72 @@ public class MeleeUI implements CUnitStateListener { private StringFrame armorInfoPanelIconLevel; private InfoPanelIconBackdrops damageBackdrops; private InfoPanelIconBackdrops defenseBackdrops; + + private final CommandCardIcon[][] commandCard = new CommandCardIcon[COMMAND_CARD_HEIGHT][COMMAND_CARD_WIDTH]; + private RenderUnit selectedUnit; // TODO remove this & replace with FDF private final Texture activeButtonTexture; private UIFrame inventoryCover; + private SpriteFrame cursorFrame; + private MeleeUIMinimap meleeUIMinimap; public MeleeUI(final DataSource dataSource, final Viewport uiViewport, final FreeTypeFontGenerator fontGenerator, - final Scene uiScene, final War3MapViewer war3MapViewer, final RootFrameListener rootFrameListener) { + final Scene uiScene, final Scene portraitScene, final CameraPreset[] cameraPresets, + final CameraRates cameraRates, final War3MapViewer war3MapViewer, + final RootFrameListener rootFrameListener) { this.dataSource = dataSource; this.uiViewport = uiViewport; this.fontGenerator = fontGenerator; this.uiScene = uiScene; + this.portraitScene = portraitScene; this.war3MapViewer = war3MapViewer; this.rootFrameListener = rootFrameListener; + this.cameraManager = new GameCameraManager(cameraPresets, cameraRates); + + this.cameraManager.setupCamera(war3MapViewer.worldScene); + if (this.war3MapViewer.startLocations[0] != null) { + this.cameraManager.target.x = this.war3MapViewer.startLocations[0].x; + this.cameraManager.target.y = this.war3MapViewer.startLocations[0].y; + } + this.activeButtonTexture = ImageUtils.getBLPTexture(war3MapViewer.mapMpq, "UI\\Widgets\\Console\\Human\\CommandButton\\human-activebutton.blp"); + + } + + private MeleeUIMinimap createMinimap(final War3MapViewer war3MapViewer) { + final Rectangle minimapDisplayArea = new Rectangle(18.75f, 13.75f, 278.75f, 276.25f); + Texture minimapTexture = null; + if (war3MapViewer.dataSource.has("war3mapMap.tga")) { + try { + minimapTexture = ImageUtils.getTextureNoColorCorrection(TgaFile.readTGA("war3mapMap.tga", + war3MapViewer.dataSource.getResourceAsStream("war3mapMap.tga"))); + } + catch (final IOException e) { + System.err.println("Could not load minimap TGA file"); + e.printStackTrace(); + } + } + else if (war3MapViewer.dataSource.has("war3mapMap.blp")) { + try { + minimapTexture = ImageUtils + .getTexture(ImageIO.read(war3MapViewer.dataSource.getResourceAsStream("war3mapMap.blp"))); + } + catch (final IOException e) { + System.err.println("Could not load minimap BLP file"); + e.printStackTrace(); + } + } + final Texture[] teamColors = new Texture[WarsmashConstants.MAX_PLAYERS]; + for (int i = 0; i < teamColors.length; i++) { + teamColors[i] = ImageUtils.getBLPTexture(war3MapViewer.dataSource, + "ReplaceableTextures\\" + ReplaceableIds.getPathString(1) + ReplaceableIds.getIdString(i) + ".blp"); + } + final Rectangle playableMapArea = war3MapViewer.terrain.getPlayableMapArea(); + return new MeleeUIMinimap(minimapDisplayArea, playableMapArea, minimapTexture, teamColors); } /** @@ -109,7 +170,7 @@ public class MeleeUI implements CUnitStateListener { // ================================= // Load skins and templates // ================================= - this.rootFrame = new GameUI(this.dataSource, GameUI.loadSkin(this.dataSource, 0), this.uiViewport, + this.rootFrame = new GameUI(this.dataSource, GameUI.loadSkin(this.dataSource, 3), this.uiViewport, this.fontGenerator, this.uiScene, this.war3MapViewer); this.rootFrameListener.onCreate(this.rootFrame); try { @@ -155,7 +216,7 @@ public class MeleeUI implements CUnitStateListener { this.timeIndicator.setAnimationSpeed(0.0f); // do not advance automatically // Create the unit portrait stuff - this.portrait = new Portrait(this.war3MapViewer); + this.portrait = new Portrait(this.war3MapViewer, this.portraitScene); positionPortrait(); this.unitPortrait = this.rootFrame.createSimpleFrame("UnitPortrait", this.consoleUI, 0); this.unitLifeText = (StringFrame) this.rootFrame.getFrameByName("UnitPortraitHitPointText", 0); @@ -198,36 +259,122 @@ public class MeleeUI implements CUnitStateListener { this.inventoryCover = this.rootFrame.createSimpleFrame("SmashConsoleInventoryCover", this.rootFrame, 0); + int commandButtonIndex = 0; + for (int j = 0; j < COMMAND_CARD_HEIGHT; j++) { + for (int i = 0; i < COMMAND_CARD_WIDTH; i++) { + final CommandCardIcon commandCardIcon = new CommandCardIcon("SmashCommandButton_" + commandButtonIndex, + this.rootFrame); + this.rootFrame.add(commandCardIcon); + final TextureFrame iconFrame = this.rootFrame.createTextureFrame( + "SmashCommandButton_" + (commandButtonIndex) + "_Icon", this.rootFrame, false, null); + final SpriteFrame cooldownFrame = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", + "SmashCommandButton_" + (commandButtonIndex) + "_Cooldown", this.rootFrame, "", 0); + final SpriteFrame autocastFrame = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", + "SmashCommandButton_" + (commandButtonIndex) + "_Autocast", this.rootFrame, "", 0); + iconFrame.addAnchor(new AnchorDefinition(FramePoint.BOTTOMLEFT, + GameUI.convertX(this.uiViewport, 0.6175f + (0.0434f * i)), + GameUI.convertY(this.uiViewport, 0.095f - (0.044f * j)))); + iconFrame.setWidth(GameUI.convertX(this.uiViewport, 0.039f)); + iconFrame.setHeight(GameUI.convertY(this.uiViewport, 0.039f)); + iconFrame.setTexture(ImageUtils.DEFAULT_ICON_PATH, this.rootFrame); + cooldownFrame.addSetPoint(new SetPoint(FramePoint.CENTER, iconFrame, FramePoint.CENTER, 0, 0)); + this.rootFrame.setSpriteFrameModel(cooldownFrame, this.rootFrame.getSkinField("CommandButtonCooldown")); + cooldownFrame.setWidth(GameUI.convertX(this.uiViewport, 0.039f)); + cooldownFrame.setHeight(GameUI.convertY(this.uiViewport, 0.039f)); + autocastFrame.addSetPoint(new SetPoint(FramePoint.CENTER, iconFrame, FramePoint.CENTER, 0, 0)); + this.rootFrame.setSpriteFrameModel(autocastFrame, this.rootFrame.getSkinField("CommandButtonAutocast")); + autocastFrame.setWidth(GameUI.convertX(this.uiViewport, 0.039f)); + autocastFrame.setHeight(GameUI.convertY(this.uiViewport, 0.039f)); + commandCardIcon.set(iconFrame, cooldownFrame, autocastFrame); + this.commandCard[j][i] = commandCardIcon; + commandCardIcon.setCommandButton(null); + commandButtonIndex++; + } + } + + this.cursorFrame = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", "SmashCursorFrame", this.rootFrame, + "", 0); + this.rootFrame.setSpriteFrameModel(this.cursorFrame, this.rootFrame.getSkinField("Cursor")); + this.cursorFrame.setSequence("Normal"); + this.cursorFrame.setZDepth(1.0f); + Gdx.input.setCursorCatched(true); + + this.meleeUIMinimap = createMinimap(this.war3MapViewer); + this.rootFrame.positionBounds(this.uiViewport); selectUnit(null); } - public void updatePortrait() { + public void update(final float deltaTime) { this.portrait.update(); + + int mouseX = Gdx.input.getX(); + int mouseY = Gdx.input.getY(); + final int minX = this.uiViewport.getScreenX(); + final int maxX = minX + this.uiViewport.getScreenWidth(); + final int minY = this.uiViewport.getScreenY(); + final int maxY = minY + this.uiViewport.getScreenHeight(); + final boolean left = mouseX <= (minX + 3); + final boolean right = mouseX >= (maxX - 3); + final boolean up = mouseY <= (minY + 3); + final boolean down = mouseY >= (maxY - 3); + this.cameraManager.applyVelocity(deltaTime, up, down, left, right); + + mouseX = Math.max(minX, Math.min(maxX, mouseX)); + mouseY = Math.max(minY, Math.min(maxY, mouseY)); + Gdx.input.setCursorPosition(mouseX, mouseY); + + screenCoordsVector.set(mouseX, mouseY); + this.uiViewport.unproject(screenCoordsVector); + this.cursorFrame.setFramePointX(FramePoint.LEFT, screenCoordsVector.x); + this.cursorFrame.setFramePointY(FramePoint.BOTTOM, screenCoordsVector.y); + + if (down) { + if (left) { + this.cursorFrame.setSequence("Scroll Down Left"); + } + else if (right) { + this.cursorFrame.setSequence("Scroll Down Right"); + } + else { + this.cursorFrame.setSequence("Scroll Down"); + } + } + else if (up) { + if (left) { + this.cursorFrame.setSequence("Scroll Up Left"); + } + else if (right) { + this.cursorFrame.setSequence("Scroll Up Right"); + } + else { + this.cursorFrame.setSequence("Scroll Up"); + } + } + else if (left) { + this.cursorFrame.setSequence("Scroll Left"); + } + else if (right) { + this.cursorFrame.setSequence("Scroll Right"); + } + else { + this.cursorFrame.setSequence("Normal"); + } + final float groundHeight = Math.max( + this.war3MapViewer.terrain.getGroundHeight(this.cameraManager.target.x, this.cameraManager.target.y), + this.war3MapViewer.terrain.getWaterHeight(this.cameraManager.target.x, this.cameraManager.target.y)); + this.cameraManager.updateTargetZ(groundHeight); + this.cameraManager.updateCamera(); } public void render(final SpriteBatch batch, final BitmapFont font20, final GlyphLayout glyphLayout) { this.rootFrame.render(batch, font20, glyphLayout); - if (this.selectedUnit != null) { font20.setColor(Color.WHITE); - final COrder currentOrder = this.selectedUnit.getSimulationUnit().getCurrentOrder(); - for (final CommandCardIcon commandCardIcon : this.selectedUnit.getCommandCardIcons()) { - batch.draw(commandCardIcon.getTexture(), 1235 + (86.8f * commandCardIcon.getX()), - 190 - (88 * commandCardIcon.getY()), 78f, 78f); - if (((currentOrder != null) && (currentOrder.getOrderId() == commandCardIcon.getOrderId())) - || ((currentOrder == null) && (commandCardIcon.getOrderId() == CAbilityStop.ORDER_ID))) { - final int blendDstFunc = batch.getBlendDstFunc(); - final int blendSrcFunc = batch.getBlendSrcFunc(); - batch.setBlendFunction(GL20.GL_SRC_ALPHA, GL20.GL_ONE); - batch.draw(this.activeButtonTexture, 1235 + (86.8f * commandCardIcon.getX()), - 190 - (88 * commandCardIcon.getY()), 78f, 78f); - batch.setBlendFunction(blendSrcFunc, blendDstFunc); - } - } } + this.meleeUIMinimap.render(batch, this.war3MapViewer.units); this.timeIndicator.setFrameByRatio(this.war3MapViewer.simulation.getGameTimeOfDay() / this.war3MapViewer.simulation.getGameplayConstants().getGameDayHours()); } @@ -238,12 +385,12 @@ public class MeleeUI implements CUnitStateListener { private static final class Portrait { private MdxComplexInstance modelInstance; - private final WarsmashGdxMapGame.PortraitCameraManager portraitCameraManager; + private final PortraitCameraManager portraitCameraManager; private final Scene portraitScene; - public Portrait(final War3MapViewer war3MapViewer) { - this.portraitScene = war3MapViewer.addSimpleScene(); - this.portraitCameraManager = new WarsmashGdxMapGame.PortraitCameraManager(); + public Portrait(final War3MapViewer war3MapViewer, final Scene portraitScene) { + this.portraitScene = portraitScene; + this.portraitCameraManager = new PortraitCameraManager(); this.portraitCameraManager.setupCamera(this.portraitScene); this.portraitScene.camera.viewport(new Rectangle(100, 0, 6400, 48)); } @@ -294,6 +441,11 @@ public class MeleeUI implements CUnitStateListener { } this.portrait.setSelectedUnit(unit); this.selectedUnit = unit; + for (int j = 0; j < COMMAND_CARD_HEIGHT; j++) { + for (int i = 0; i < COMMAND_CARD_WIDTH; i++) { + this.commandCard[j][i].setCommandButton(null); + } + } if (unit == null) { this.simpleNameValue.setText(""); this.unitLifeText.setText(""); @@ -340,9 +492,9 @@ public class MeleeUI implements CUnitStateListener { this.simpleBuildingActionLabel.setText(""); final boolean anyAttacks = unit.getSimulationUnit().getUnitType().getAttacks().size() > 0; - final UIFrame localArmorIcon; - final TextureFrame localArmorIconBackdrop; - final StringFrame localArmorInfoPanelIconValue; + final UIFrame localArmorIcon = this.armorIcon; + final TextureFrame localArmorIconBackdrop = this.armorIconBackdrop; + final StringFrame localArmorInfoPanelIconValue = this.armorInfoPanelIconValue; if (anyAttacks) { final CUnitAttack attackOne = unit.getSimulationUnit().getUnitType().getAttacks().get(0); this.attack1Icon.setVisible(attackOne.isShowUI()); @@ -358,17 +510,18 @@ public class MeleeUI implements CUnitStateListener { this.attack2Icon.setVisible(false); } - localArmorIcon = this.armorIcon; - localArmorIconBackdrop = this.armorIconBackdrop; - localArmorInfoPanelIconValue = this.armorInfoPanelIconValue; + this.armorIcon.addSetPoint( + new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, FramePoint.TOPLEFT, + GameUI.convertX(this.uiViewport, 0f), GameUI.convertY(this.uiViewport, -0.06025f))); + this.armorIcon.positionBounds(this.uiViewport); } else { - this.armorIcon.setVisible(false); + this.attack1Icon.setVisible(false); this.attack2Icon.setVisible(false); - localArmorIcon = this.attack1Icon; - localArmorIconBackdrop = this.attack1IconBackdrop; - localArmorInfoPanelIconValue = this.attack1InfoPanelIconValue; + this.armorIcon.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, + FramePoint.TOPLEFT, 0, GameUI.convertY(this.uiViewport, -0.030125f))); + this.armorIcon.positionBounds(this.uiViewport); } localArmorIcon.setVisible(true); @@ -380,10 +533,21 @@ public class MeleeUI implements CUnitStateListener { } localArmorIconBackdrop.setTexture(defenseTexture); localArmorInfoPanelIconValue.setText(Integer.toString(unit.getSimulationUnit().getDefense())); + unit.populateCommandCard(this, this.war3MapViewer.getAbilityDataUI()); } } - public void resize() { + @Override + public void commandButton(final int buttonPositionX, final int buttonPositionY, final Texture icon, + final int orderId) { + final int x = Math.max(0, Math.min(COMMAND_CARD_WIDTH - 1, buttonPositionX)); + final int y = Math.max(0, Math.min(COMMAND_CARD_HEIGHT - 1, buttonPositionY)); + this.commandCard[y][x].setCommandButtonData(icon, orderId); + + } + + public void resize(final Rectangle viewport) { + this.cameraManager.resize(viewport); positionPortrait(); } @@ -472,4 +636,30 @@ public class MeleeUI implements CUnitStateListener { public RenderUnit getSelectedUnit() { return this.selectedUnit; } + + public boolean keyDown(final int keycode) { + return this.cameraManager.keyDown(keycode); + } + + public boolean keyUp(final int keycode) { + return this.cameraManager.keyUp(keycode); + } + + public void scrolled(final int amount) { + this.cameraManager.scrolled(amount); + } + + public boolean touchDown(final float screenX, final float screenY, final int button) { + if (this.meleeUIMinimap.containsMouse(screenX, screenY)) { + final Vector2 worldPoint = this.meleeUIMinimap.getWorldPointFromScreen(screenX, screenY); + this.cameraManager.target.x = worldPoint.x; + this.cameraManager.target.y = worldPoint.y; + return true; + } + screenCoordsVector.set(screenX, screenY); + this.uiViewport.unproject(screenCoordsVector); + this.rootFrame.touchDown(GameUI.unconvertX(this.uiViewport, screenCoordsVector.x), + GameUI.unconvertY(this.uiViewport, screenCoordsVector.y), button); + return false; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUIMinimap.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUIMinimap.java new file mode 100644 index 0000000..fcaf00f --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUIMinimap.java @@ -0,0 +1,62 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.ui; + +import com.badlogic.gdx.graphics.Texture; +import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.badlogic.gdx.math.Rectangle; +import com.badlogic.gdx.math.Vector2; +import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderUnit; + +public class MeleeUIMinimap { + private final Rectangle minimap; + private final Rectangle minimapFilledArea; + private final Texture minimapTexture; + private final Rectangle playableMapArea; + private final Texture[] teamColors; + + public MeleeUIMinimap(final Rectangle displayArea, final Rectangle playableMapArea, final Texture minimapTexture, + final Texture[] teamColors) { + this.playableMapArea = playableMapArea; + this.minimapTexture = minimapTexture; + this.teamColors = teamColors; + this.minimap = displayArea; + final float worldWidth = playableMapArea.getWidth(); + final float worldHeight = playableMapArea.getHeight(); + final float worldSize = Math.max(worldWidth, worldHeight); + final float minimapFilledWidth = (worldWidth / worldSize) * this.minimap.width; + final float minimapFilledHeight = (worldHeight / worldSize) * this.minimap.height; + + this.minimapFilledArea = new Rectangle(this.minimap.x + ((this.minimap.width - minimapFilledWidth) / 2), + this.minimap.y + ((this.minimap.height - minimapFilledHeight) / 2), minimapFilledWidth, + minimapFilledHeight); + } + + public void render(final SpriteBatch batch, final Iterable units) { + batch.draw(this.minimapTexture, this.minimap.x, this.minimap.y, this.minimap.width, this.minimap.height); + + for (final RenderUnit unit : units) { + final Texture minimapIcon = this.teamColors[unit.playerIndex]; + batch.draw(minimapIcon, + this.minimapFilledArea.x + + (((unit.location[0] - this.playableMapArea.getX()) / (this.playableMapArea.getWidth())) + * this.minimapFilledArea.width), + this.minimapFilledArea.y + + (((unit.location[1] - this.playableMapArea.getY()) / (this.playableMapArea.getHeight())) + * this.minimapFilledArea.height), + 4, 4); + } + } + + public Vector2 getWorldPointFromScreen(final float screenX, final float screenY) { + final Rectangle filledArea = this.minimapFilledArea; + final float clickX = (screenX - filledArea.x) / filledArea.width; + final float clickY = (screenY - filledArea.y) / filledArea.height; + final float worldX = (clickX * this.playableMapArea.width) + this.playableMapArea.x; + final float worldY = (clickY * this.playableMapArea.height) + this.playableMapArea.y; + return new Vector2(worldX, worldY); + + } + + public boolean containsMouse(final float x, final float y) { + return this.minimapFilledArea.contains(x, y); + } +} From 7cea3e3fcd4262f33805434701d71528e986d336 Mon Sep 17 00:00:00 2001 From: Retera Date: Fri, 23 Oct 2020 22:35:25 -0400 Subject: [PATCH 053/116] Support for walkable destructables --- .../etheller/warsmash/WarsmashGdxMapGame.java | 6 +- .../com/etheller/warsmash/util/Quadtree.java | 37 + .../com/etheller/warsmash/viewer5/Bounds.java | 4 + .../mdx/EventObjectEmitterObject.java | 577 ++-- .../handlers/mdx/MdxComplexInstance.java | 1492 +++++----- .../viewer5/handlers/mdx/MdxHandler.java | 4 +- .../viewer5/handlers/mdx/SdSequence.java | 3 + .../handlers/w3x/RenderDestructable.java | 41 +- .../viewer5/handlers/w3x/RenderDoodad.java | 138 +- .../viewer5/handlers/w3x/War3MapViewer.java | 2526 +++++++++-------- .../handlers/w3x/environment/Terrain.java | 19 +- .../w3x/environment/TerrainShaders.java | 10 +- .../handlers/w3x/rendersim/RenderUnit.java | 737 ++--- .../w3x/simulation/data/CUnitData.java | 714 ++--- .../w3x/simulation/orders/CAttackOrder.java | 1 - .../pathing/CPathfindingProcessor.java | 4 +- .../warsmash/desktop/DesktopLauncher.java | 5 +- 17 files changed, 3237 insertions(+), 3081 deletions(-) diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java index 71542ff..1363ca3 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java @@ -38,6 +38,7 @@ import com.etheller.warsmash.parsers.fdf.GameUI; import com.etheller.warsmash.parsers.jass.Jass2.RootFrameListener; import com.etheller.warsmash.units.DataTable; import com.etheller.warsmash.units.Element; +import com.etheller.warsmash.units.HashedGameObject; import com.etheller.warsmash.util.DataSourceFileHandle; import com.etheller.warsmash.util.ImageUtils; import com.etheller.warsmash.viewer5.CanvasProvider; @@ -141,7 +142,10 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv } final Element cameraData = this.viewer.miscData.get("Camera"); - final Element cameraListenerData = this.viewer.miscData.get("Listener"); + Element cameraListenerData = this.viewer.miscData.get("Listener"); + if(cameraListenerData==null) { + cameraListenerData = new Element("Listener", new DataTable(null)); + } final CameraPreset[] cameraPresets = new CameraPreset[6]; for (int i = 0; i < cameraPresets.length; i++) { cameraPresets[i] = new CameraPreset(cameraData.getFieldFloatValue("AOA", i), diff --git a/core/src/com/etheller/warsmash/util/Quadtree.java b/core/src/com/etheller/warsmash/util/Quadtree.java index 939a03f..9489fd8 100644 --- a/core/src/com/etheller/warsmash/util/Quadtree.java +++ b/core/src/com/etheller/warsmash/util/Quadtree.java @@ -75,6 +75,43 @@ public class Quadtree { } } + public boolean intersect(float x, float y, final QuadtreeIntersector intersector) { + if (this.leaf) { + for (int i = 0; i < this.nodes.size; i++) { + final Node node = this.nodes.get(i); + if (node.bounds.contains(x, y)) { + if (intersector.onIntersect(node.object)) { + return true; + } + } + } + return false; + } + else { + if (this.northeast.bounds.contains(x, y)) { + if (this.northeast.intersect(x, y, intersector)) { + return true; + } + } + if (this.northwest.bounds.contains(x, y)) { + if (this.northwest.intersect(x, y, intersector)) { + return true; + } + } + if (this.southwest.bounds.contains(x, y)) { + if (this.southwest.intersect(x, y, intersector)) { + return true; + } + } + if (this.southeast.bounds.contains(x, y)) { + if (this.southeast.intersect(x, y, intersector)) { + return true; + } + } + return false; + } + } + private void add(final Node node, final int depth) { if (this.leaf) { if ((this.nodes.size >= SPLIT_THRESHOLD) && (depth < MAX_DEPTH)) { diff --git a/core/src/com/etheller/warsmash/viewer5/Bounds.java b/core/src/com/etheller/warsmash/viewer5/Bounds.java index 82413c7..153d483 100644 --- a/core/src/com/etheller/warsmash/viewer5/Bounds.java +++ b/core/src/com/etheller/warsmash/viewer5/Bounds.java @@ -31,4 +31,8 @@ public class Bounds { public boolean intersectRayFast(final Ray ray) { return Intersector.intersectRayBoundsFast(ray, this.boundingBox); } + + public BoundingBox getBoundingBox() { + return boundingBox; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectEmitterObject.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectEmitterObject.java index be44cf7..68d07a5 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectEmitterObject.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectEmitterObject.java @@ -22,343 +22,328 @@ import com.etheller.warsmash.viewer5.Texture; import com.etheller.warsmash.viewer5.handlers.EmitterObject; public class EventObjectEmitterObject extends GenericObject implements EmitterObject { - private static final class LoadGenericSoundCallback implements LoadGenericCallback { - private final String filename; + private static final class LoadGenericSoundCallback implements LoadGenericCallback { + private final String filename; - public LoadGenericSoundCallback(final String filename) { - this.filename = filename; - } + public LoadGenericSoundCallback(final String filename) { + this.filename = filename; + } - @Override - public Object call(final InputStream data) { - final FileHandle temp = new FileHandle(this.filename) { - @Override - public InputStream read() { - return data; - }; - }; - if (data != null) { - return Gdx.audio.newSound(temp); - } - else { - System.err.println("Warning: missing sound file: " + this.filename); - return null; - } - } - } + @Override + public Object call(final InputStream data) { + final FileHandle temp = new FileHandle(this.filename) { + @Override + public InputStream read() { + return data; + } - private static final LoadGenericCallback mappedDataCallback = new LoadGenericCallback() { + ; + }; + if (data != null) { + return Gdx.audio.newSound(temp); + } else { + System.err.println("Warning: missing sound file: " + this.filename); + return null; + } + } + } - @Override - public Object call(final InputStream data) { - final StringBuilder stringBuilder = new StringBuilder(); - try (BufferedReader reader = new BufferedReader(new InputStreamReader(data, "utf-8"))) { - String line; - while ((line = reader.readLine()) != null) { - stringBuilder.append(line); - stringBuilder.append("\n"); - } - } - catch (final UnsupportedEncodingException e) { - throw new RuntimeException(e); - } - catch (final IOException e) { - throw new RuntimeException(e); - } - return new MappedData(stringBuilder.toString()); - } - }; + private static final LoadGenericCallback mappedDataCallback = new LoadGenericCallback() { - private int geometryEmitterType = -1; - public final String type; - private final String id; - public final long[] keyFrames; - private long globalSequence = -1; - private final long[] defval = { 1 }; - public MdxModel internalModel; - public Texture internalTexture; - public float[][] colors; - public float[] intervalTimes; - public float scale; - public int columns; - public int rows; - public float lifeSpan; - public int blendSrc; - public int blendDst; - public float[][] intervals; - public float distanceCutoff; - private float maxDistance; - public float minDistance; - private float pitch; - private float pitchVariance; - private float volume; - public List decodedBuffers = new ArrayList<>(); - /** - * If this is an SPL/UBR emitter object, ok will be set to true if the tables - * are loaded. - * - * This is because, like the other geometry emitters, it is fine to use them - * even if the textures don't load. - * - * The particles will simply be black. - */ - private boolean ok = false; + @Override + public Object call(final InputStream data) { + final StringBuilder stringBuilder = new StringBuilder(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(data, "utf-8"))) { + String line; + while ((line = reader.readLine()) != null) { + stringBuilder.append(line); + stringBuilder.append("\n"); + } + } catch (final UnsupportedEncodingException e) { + throw new RuntimeException(e); + } catch (final IOException e) { + throw new RuntimeException(e); + } + return new MappedData(stringBuilder.toString()); + } + }; - public EventObjectEmitterObject(final MdxModel model, - final com.etheller.warsmash.parsers.mdlx.EventObject eventObject, final int index) { - super(model, eventObject, index); + private int geometryEmitterType = -1; + public final String type; + private final String id; + public final long[] keyFrames; + private long globalSequence = -1; + private final long[] defval = {1}; + public MdxModel internalModel; + public Texture internalTexture; + public float[][] colors; + public float[] intervalTimes; + public float scale; + public int columns; + public int rows; + public float lifeSpan; + public int blendSrc; + public int blendDst; + public float[][] intervals; + public float distanceCutoff; + private float maxDistance; + public float minDistance; + private float pitch; + private float pitchVariance; + private float volume; + public List decodedBuffers = new ArrayList<>(); + /** + * If this is an SPL/UBR emitter object, ok will be set to true if the tables + * are loaded. + *

+ * This is because, like the other geometry emitters, it is fine to use them + * even if the textures don't load. + *

+ * The particles will simply be black. + */ + private boolean ok = false; - final ModelViewer viewer = model.viewer; - final String name = eventObject.getName(); - String type = name.substring(0, 3); - final String id = name.substring(4); + public EventObjectEmitterObject(final MdxModel model, + final com.etheller.warsmash.parsers.mdlx.EventObject eventObject, final int index) { + super(model, eventObject, index); - // Same thing - if ("FPT".equals(type)) { - type = "SPL"; - } + final ModelViewer viewer = model.viewer; + final String name = eventObject.getName(); + String type = name.substring(0, 3); + final String id = name.substring(4); - if ("SPL".equals(type)) { - this.geometryEmitterType = GeometryEmitterFuncs.EMITTER_SPLAT; - } - else if ("UBR".equals(type)) { - this.geometryEmitterType = GeometryEmitterFuncs.EMITTER_UBERSPLAT; - } - else if ("SPN".equals(type)) { - this.geometryEmitterType = GeometryEmitterFuncs.EMITTER_SPN; - } + // Same thing + if ("FPT".equals(type)) { + type = "SPL"; + } - this.type = type; - this.id = id; - this.keyFrames = eventObject.getKeyFrames(); + if ("SPL".equals(type)) { + this.geometryEmitterType = GeometryEmitterFuncs.EMITTER_SPLAT; + } else if ("UBR".equals(type)) { + this.geometryEmitterType = GeometryEmitterFuncs.EMITTER_UBERSPLAT; + } else if ("SPN".equals(type)) { + this.geometryEmitterType = GeometryEmitterFuncs.EMITTER_SPN; + } - final int globalSequenceId = eventObject.getGlobalSequenceId(); - if (globalSequenceId != -1) { - this.globalSequence = model.getGlobalSequences().get(globalSequenceId); - } + this.type = type; + this.id = id; + this.keyFrames = eventObject.getKeyFrames(); - final List tables = new ArrayList<>(); - final PathSolver pathSolver = model.pathSolver; - final Object solverParams = model.solverParams; + final int globalSequenceId = eventObject.getGlobalSequenceId(); + if (globalSequenceId != -1) { + this.globalSequence = model.getGlobalSequences().get(globalSequenceId); + } - if ("SPN".equals(type)) { - tables.add(viewer.loadGeneric(pathSolver.solve("Splats\\SpawnData.slk", solverParams).finalSrc, - FetchDataTypeName.SLK, mappedDataCallback)); - } - else if ("SPL".equals(type)) { - tables.add(viewer.loadGeneric(pathSolver.solve("Splats\\SplatData.slk", solverParams).finalSrc, - FetchDataTypeName.SLK, mappedDataCallback)); - } - else if ("UBR".equals(type)) { - tables.add(viewer.loadGeneric(pathSolver.solve("Splats\\UberSplatData.slk", solverParams).finalSrc, - FetchDataTypeName.SLK, mappedDataCallback)); - } - else if ("SND".equals(type)) { - if (!model.reforged) { - tables.add(viewer.loadGeneric(pathSolver.solve("UI\\SoundInfo\\AnimLookups.slk", solverParams).finalSrc, - FetchDataTypeName.SLK, mappedDataCallback)); - } - tables.add(viewer.loadGeneric(pathSolver.solve("UI\\SoundInfo\\AnimSounds.slk", solverParams).finalSrc, - FetchDataTypeName.SLK, mappedDataCallback)); - } - else { - // Units\Critters\BlackStagMale\BlackStagMale.mdx has an event object named - // "Point01". - return; - } + final List tables = new ArrayList<>(); + final PathSolver pathSolver = model.pathSolver; + final Object solverParams = model.solverParams; - // TODO I am scrapping some async stuff with promises here from the JS and - // calling load - this.load(tables); - } + if ("SPN".equals(type)) { + tables.add(viewer.loadGeneric(pathSolver.solve("Splats\\SpawnData.slk", solverParams).finalSrc, + FetchDataTypeName.SLK, mappedDataCallback)); + } else if ("SPL".equals(type)) { + tables.add(viewer.loadGeneric(pathSolver.solve("Splats\\SplatData.slk", solverParams).finalSrc, + FetchDataTypeName.SLK, mappedDataCallback)); + } else if ("UBR".equals(type)) { + tables.add(viewer.loadGeneric(pathSolver.solve("Splats\\UberSplatData.slk", solverParams).finalSrc, + FetchDataTypeName.SLK, mappedDataCallback)); + } else if ("SND".equals(type)) { + if (!model.reforged) { + tables.add(viewer.loadGeneric(pathSolver.solve("UI\\SoundInfo\\AnimLookups.slk", solverParams).finalSrc, + FetchDataTypeName.SLK, mappedDataCallback)); + } + tables.add(viewer.loadGeneric(pathSolver.solve("UI\\SoundInfo\\AnimSounds.slk", solverParams).finalSrc, + FetchDataTypeName.SLK, mappedDataCallback)); + } else { + // Units\Critters\BlackStagMale\BlackStagMale.mdx has an event object named + // "Point01". + return; + } - private float getFloat(final MappedDataRow row, final String name) { - final Float x = (Float) row.get(name); - if (x == null) { - return Float.NaN; - } - else { - return x.floatValue(); - } - } + // TODO I am scrapping some async stuff with promises here from the JS and + // calling load + this.load(tables); + } - private int getInt(final MappedDataRow row, final String name) { - return getInt(row, name, Integer.MIN_VALUE); - } + private float getFloat(final MappedDataRow row, final String name) { + final Float x = (Float) row.get(name); + if (x == null) { + return Float.NaN; + } else { + return x.floatValue(); + } + } - private int getInt(final MappedDataRow row, final String name, final int defaultValue) { - final Number x = (Number) row.get(name); - if (x == null) { - return defaultValue; - } - else { - return x.intValue(); - } - } + private int getInt(final MappedDataRow row, final String name) { + return getInt(row, name, Integer.MIN_VALUE); + } - private void load(final List tables) { - final MappedData firstTable = (MappedData) tables.get(0).data; - final MappedDataRow row = firstTable.getRow(this.id.trim()); + private int getInt(final MappedDataRow row, final String name, final int defaultValue) { + final Number x = (Number) row.get(name); + if (x == null) { + return defaultValue; + } else { + return x.intValue(); + } + } - if (row != null) { - final MdxModel model = this.model; - final ModelViewer viewer = model.viewer; - final PathSolver pathSolver = model.pathSolver; + private void load(final List tables) { + final MappedData firstTable = (MappedData) tables.get(0).data; + if (firstTable == null) { + return; + } + final MappedDataRow row = firstTable.getRow(this.id.trim()); - if ("SPN".equals(this.type)) { - this.internalModel = (MdxModel) viewer.load(((String) row.get("Model")).replace(".mdl", ".mdx"), - pathSolver, model.solverParams); + if (row != null) { + final MdxModel model = this.model; + final ModelViewer viewer = model.viewer; + final PathSolver pathSolver = model.pathSolver; - if (this.internalModel != null) { - // TODO javascript async code removed here + if ("SPN".equals(this.type)) { + this.internalModel = (MdxModel) viewer.load(((String) row.get("Model")).replace(".mdl", ".mdx"), + pathSolver, model.solverParams); + + if (this.internalModel != null) { + // TODO javascript async code removed here // this.internalModel.whenLoaded((model) => this.ok = model.ok) - this.ok = this.internalModel.ok; - } - } - else if ("SPL".equals(this.type) || "UBR".equals(this.type)) { - final String texturesExt = model.reforged ? ".dds" : ".blp"; + this.ok = this.internalModel.ok; + } + } else if ("SPL".equals(this.type) || "UBR".equals(this.type)) { + final String texturesExt = model.reforged ? ".dds" : ".blp"; - this.internalTexture = (Texture) viewer.load( - "ReplaceableTextures\\Splats\\" + row.get("file") + texturesExt, pathSolver, - model.solverParams); + this.internalTexture = (Texture) viewer.load( + "ReplaceableTextures\\Splats\\" + row.get("file") + texturesExt, pathSolver, + model.solverParams); - this.scale = getFloat(row, "Scale"); - this.colors = new float[][] { - { getFloat(row, "StartR"), getFloat(row, "StartG"), getFloat(row, "StartB"), - getFloat(row, "StartA") }, - { getFloat(row, "MiddleR"), getFloat(row, "MiddleG"), getFloat(row, "MiddleB"), - getFloat(row, "MiddleA") }, - { getFloat(row, "EndR"), getFloat(row, "EndG"), getFloat(row, "EndB"), - getFloat(row, "EndA") } }; + this.scale = getFloat(row, "Scale"); + this.colors = new float[][]{ + {getFloat(row, "StartR"), getFloat(row, "StartG"), getFloat(row, "StartB"), + getFloat(row, "StartA")}, + {getFloat(row, "MiddleR"), getFloat(row, "MiddleG"), getFloat(row, "MiddleB"), + getFloat(row, "MiddleA")}, + {getFloat(row, "EndR"), getFloat(row, "EndG"), getFloat(row, "EndB"), + getFloat(row, "EndA")}}; - if ("SPL".equals(this.type)) { - this.columns = getInt(row, "Columns"); - this.rows = getInt(row, "Rows"); - this.lifeSpan = getFloat(row, "Lifespan") + getFloat(row, "Decay"); - this.intervalTimes = new float[] { getFloat(row, "Lifespan"), getFloat(row, "Decay") }; - this.intervals = new float[][] { - { getFloat(row, "UVLifespanStart"), getFloat(row, "UVLifespanEnd"), - getFloat(row, "LifespanRepeat") }, - { getFloat(row, "UVDecayStart"), getFloat(row, "UVDecayEnd"), - getFloat(row, "DecayRepeat") }, }; - } - else { - this.columns = 1; - this.rows = 1; - this.lifeSpan = getFloat(row, "BirthTime") + getFloat(row, "PauseTime") + getFloat(row, "Decay"); - this.intervalTimes = new float[] { getFloat(row, "BirthTime"), getFloat(row, "PauseTime"), - getFloat(row, "Decay") }; - } + if ("SPL".equals(this.type)) { + this.columns = getInt(row, "Columns"); + this.rows = getInt(row, "Rows"); + this.lifeSpan = getFloat(row, "Lifespan") + getFloat(row, "Decay"); + this.intervalTimes = new float[]{getFloat(row, "Lifespan"), getFloat(row, "Decay")}; + this.intervals = new float[][]{ + {getFloat(row, "UVLifespanStart"), getFloat(row, "UVLifespanEnd"), + getFloat(row, "LifespanRepeat")}, + {getFloat(row, "UVDecayStart"), getFloat(row, "UVDecayEnd"), + getFloat(row, "DecayRepeat")},}; + } else { + this.columns = 1; + this.rows = 1; + this.lifeSpan = getFloat(row, "BirthTime") + getFloat(row, "PauseTime") + getFloat(row, "Decay"); + this.intervalTimes = new float[]{getFloat(row, "BirthTime"), getFloat(row, "PauseTime"), + getFloat(row, "Decay")}; + } - final int[] blendModes = FilterMode - .emitterFilterMode(com.etheller.warsmash.parsers.mdlx.ParticleEmitter2.FilterMode - .fromId(getInt(row, "BlendMode"))); + final int[] blendModes = FilterMode + .emitterFilterMode(com.etheller.warsmash.parsers.mdlx.ParticleEmitter2.FilterMode + .fromId(getInt(row, "BlendMode"))); - this.blendSrc = blendModes[0]; - this.blendDst = blendModes[1]; + this.blendSrc = blendModes[0]; + this.blendDst = blendModes[1]; - this.ok = true; - } - else if ("SND".equals(this.type)) { - // Only load sounds if audio is enabled. - // This is mostly to save on bandwidth and loading time, especially when loading - // full maps. - if (viewer.audioEnabled) { - final MappedData animSounds = (MappedData) tables.get(1).data; + this.ok = true; + } else if ("SND".equals(this.type)) { + // Only load sounds if audio is enabled. + // This is mostly to save on bandwidth and loading time, especially when loading + // full maps. + if (viewer.audioEnabled) { + final MappedData animSounds = (MappedData) tables.get(1).data; - final MappedDataRow animSoundsRow = animSounds.getRow((String) row.get("SoundLabel")); + final MappedDataRow animSoundsRow = animSounds.getRow((String) row.get("SoundLabel")); - if (animSoundsRow != null) { - this.distanceCutoff = getFloat(animSoundsRow, "DistanceCutoff"); - this.maxDistance = getFloat(animSoundsRow, "MaxDistance"); - this.minDistance = getFloat(animSoundsRow, "MinDistance"); - this.pitch = getFloat(animSoundsRow, "Pitch"); - this.pitchVariance = getFloat(animSoundsRow, "PitchVariance"); - this.volume = getFloat(animSoundsRow, "Volume"); + if (animSoundsRow != null) { + this.distanceCutoff = getFloat(animSoundsRow, "DistanceCutoff"); + this.maxDistance = getFloat(animSoundsRow, "MaxDistance"); + this.minDistance = getFloat(animSoundsRow, "MinDistance"); + this.pitch = getFloat(animSoundsRow, "Pitch"); + this.pitchVariance = getFloat(animSoundsRow, "PitchVariance"); + this.volume = getFloat(animSoundsRow, "Volume"); - final String[] fileNames = ((String) animSoundsRow.get("FileNames")).split(","); - final GenericResource[] resources = new GenericResource[fileNames.length]; - for (int i = 0; i < fileNames.length; i++) { - final String path = ((String) animSoundsRow.get("DirectoryBase")) + fileNames[i]; - try { - final String pathString = pathSolver.solve(path, model.solverParams).finalSrc; - final GenericResource genericResource = viewer.loadGeneric(pathString, - FetchDataTypeName.ARRAY_BUFFER, new LoadGenericSoundCallback(pathString)); - if (genericResource == null) { - System.err.println("Null sound: " + fileNames[i]); - } - resources[i] = genericResource; - } - catch (final Exception exc) { - System.err.println("Failed to load sound: " + path); - exc.printStackTrace(); - } - } + final String[] fileNames = ((String) animSoundsRow.get("FileNames")).split(","); + final GenericResource[] resources = new GenericResource[fileNames.length]; + for (int i = 0; i < fileNames.length; i++) { + final String path = ((String) animSoundsRow.get("DirectoryBase")) + fileNames[i]; + try { + final String pathString = pathSolver.solve(path, model.solverParams).finalSrc; + final GenericResource genericResource = viewer.loadGeneric(pathString, + FetchDataTypeName.ARRAY_BUFFER, new LoadGenericSoundCallback(pathString)); + if (genericResource == null) { + System.err.println("Null sound: " + fileNames[i]); + } + resources[i] = genericResource; + } catch (final Exception exc) { + System.err.println("Failed to load sound: " + path); + exc.printStackTrace(); + } + } - // TODO JS async removed - for (final GenericResource resource : resources) { - if (resource != null) { - this.decodedBuffers.add((Sound) resource.data); - } - } - this.ok = true; - } - } - } - else { - System.err.println("Unknown event object type: " + this.type + this.id); - } - } - else { - System.err.println("Unknown event object ID: " + this.type + this.id); - } - } + // TODO JS async removed + for (final GenericResource resource : resources) { + if (resource != null) { + this.decodedBuffers.add((Sound) resource.data); + } + } + this.ok = true; + } + } + } else { + System.err.println("Unknown event object type: " + this.type + this.id); + } + } else { + System.err.println("Unknown event object ID: " + this.type + this.id); + } + } - public int getValue(final long[] out, final MdxComplexInstance instance) { - if (this.globalSequence != -1) { + public int getValue(final long[] out, final MdxComplexInstance instance) { + if (this.globalSequence != -1) { - return this.getValueAtTime(out, instance.counter % this.globalSequence, 0, this.globalSequence); - } - else if (instance.sequence != -1) { - final long[] interval = this.model.getSequences().get(instance.sequence).getInterval(); + return this.getValueAtTime(out, instance.counter % this.globalSequence, 0, this.globalSequence); + } else if (instance.sequence != -1) { + final long[] interval = this.model.getSequences().get(instance.sequence).getInterval(); - return this.getValueAtTime(out, instance.frame, interval[0], interval[1]); - } - else { - out[0] = this.defval[0]; + return this.getValueAtTime(out, instance.frame, interval[0], interval[1]); + } else { + out[0] = this.defval[0]; - return -1; - } - } + return -1; + } + } - public int getValueAtTime(final long[] out, final long frame, final long start, final long end) { - if ((frame >= start) && (frame <= end)) { - for (int i = this.keyFrames.length - 1; i > -1; i--) { - if (this.keyFrames[i] < start) { - out[0] = 0; + public int getValueAtTime(final long[] out, final long frame, final long start, final long end) { + if ((frame >= start) && (frame <= end)) { + for (int i = this.keyFrames.length - 1; i > -1; i--) { + if (this.keyFrames[i] < start) { + out[0] = 0; - return i; - } - else if (this.keyFrames[i] <= frame) { - out[0] = 1; + return i; + } else if (this.keyFrames[i] <= frame) { + out[0] = 1; - return i; - } - } - } + return i; + } + } + } - out[0] = 0; + out[0] = 0; - return -1; - } + return -1; + } - @Override - public boolean ok() { - return this.ok; - } + @Override + public boolean ok() { + return this.ok; + } - @Override - public int getGeometryEmitterType() { - return this.geometryEmitterType; - } + @Override + public int getGeometryEmitterType() { + return this.geometryEmitterType; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java index b9e3b8f..093433a 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java @@ -26,756 +26,744 @@ import com.etheller.warsmash.viewer5.gl.DataTexture; import com.etheller.warsmash.viewer5.handlers.w3x.DynamicShadowManager; public class MdxComplexInstance extends ModelInstance { - private static final float[] visibilityHeap = new float[1]; - private static final float[] translationHeap = new float[3]; - private static final float[] rotationHeap = new float[4]; - private static final float[] scaleHeap = new float[3]; - private static final float[] colorHeap = new float[3]; - private static final float[] alphaHeap = new float[1]; - private static final long[] textureIdHeap = new long[1]; - - public List lights = new ArrayList<>(); - public List attachments = new ArrayList<>(); - public List particleEmitters = new ArrayList<>(); - public List particleEmitters2 = new ArrayList<>(); - public List ribbonEmitters = new ArrayList<>(); - public List> eventObjectEmitters = new ArrayList<>(); - public MdxNode[] nodes; - public SkeletalNode[] sortedNodes; - public int frame = 0; - public float floatingFrame = 0; - // Global sequences - public int counter = 0; - public int sequence = -1; - public SequenceLoopMode sequenceLoopMode = SequenceLoopMode.NEVER_LOOP; - public boolean sequenceEnded = false; - public float[] vertexColor = { 1, 1, 1, 1 }; - // Particles do not spawn when the sequence is -1, or when the sequence finished - // and it's not repeating - public boolean allowParticleSpawn = false; - // If forced is true, everything will update regardless of variancy. - // Any later non-forced update can then use variancy to skip updating things. - // It is set to true every time the sequence is set with setSequence(). - public boolean forced = true; - public float[][] geosetColors; - public float[] layerAlphas; - public int[] layerTextures; - public float[][] uvAnims; - public Matrix4[] worldMatrices; - public FloatBuffer worldMatricesCopyHeap; - public DataTexture boneTexture; - public Texture[] replaceableTextures = new Texture[WarsmashConstants.REPLACEABLE_TEXTURE_LIMIT]; - private float animationSpeed = 1.0f; - - public MdxComplexInstance(final MdxModel model) { - super(model); - } - - @Override - public void load() { - final MdxModel model = (MdxModel) this.model; - - this.geosetColors = new float[model.geosets.size()][]; - for (int i = 0, l = model.geosets.size(); i < l; i++) { - this.geosetColors[i] = new float[4]; - } - - this.layerAlphas = new float[model.layers.size()]; - this.layerTextures = new int[model.layers.size()]; - this.uvAnims = new float[model.layers.size()][]; - for (int i = 0, l = model.layers.size(); i < l; i++) { - this.layerAlphas[i] = 0; - this.layerTextures[i] = 0; - this.uvAnims[i] = new float[5]; - } - - // Create the needed amount of shared nodes. - final Object[] sharedNodeData = Node.createSkeletalNodes(model.genericObjects.size(), - MdxNodeDescriptor.INSTANCE); - final List nodes = (List) sharedNodeData[0]; - int nodeIndex = 0; - this.nodes = nodes.toArray(new MdxNode[nodes.size()]); - - // A shared typed array for all world matrices of the internal nodes. - this.worldMatrices = ((List) sharedNodeData[1]).toArray(new Matrix4[0]); - this.worldMatricesCopyHeap = ByteBuffer.allocateDirect(16 * this.worldMatrices.length * 4) - .order(ByteOrder.nativeOrder()).asFloatBuffer(); - - // And now initialize all of the nodes and objects - for (final Bone bone : model.bones) { - this.initNode(this.nodes, this.nodes[nodeIndex++], bone); - } - - for (final Light light : model.lights) { - final LightInstance lightInstance = new LightInstance(this, light); - this.lights.add(lightInstance); - this.initNode(this.nodes, this.nodes[nodeIndex++], light, lightInstance); - } - - for (final Helper helper : model.helpers) { - this.initNode(this.nodes, this.nodes[nodeIndex++], helper); - } - - for (final Attachment attachment : model.attachments) { - AttachmentInstance attachmentInstance = null; - - // Attachments may have game models attached to them, such as Undead and - // Nightelf building animations. - if (attachment.internalModel != null) { - attachmentInstance = new AttachmentInstance(this, attachment); - - this.attachments.add(attachmentInstance); - } - - this.initNode(this.nodes, this.nodes[nodeIndex++], attachment, attachmentInstance); - } - - for (final ParticleEmitterObject emitterObject : model.particleEmitters) { - final ParticleEmitter emitter = new ParticleEmitter(this, emitterObject); - - this.particleEmitters.add(emitter); - - this.initNode(this.nodes, this.nodes[nodeIndex++], emitterObject, emitter); - } - - for (final ParticleEmitter2Object emitterObject : model.particleEmitters2) { - final ParticleEmitter2 emitter = new ParticleEmitter2(this, emitterObject); - - this.particleEmitters2.add(emitter); - - this.initNode(this.nodes, this.nodes[nodeIndex++], emitterObject, emitter); - } - - for (final RibbonEmitterObject emitterObject : model.ribbonEmitters) { - final RibbonEmitter emitter = new RibbonEmitter(this, emitterObject); - - this.ribbonEmitters.add(emitter); - - this.initNode(this.nodes, this.nodes[nodeIndex++], emitterObject, emitter); - } - - for (final EventObjectEmitterObject emitterObject : model.eventObjects) { - final String type = emitterObject.type; - EventObjectEmitter emitter; - - if ("SPN".equals(type)) { - emitter = new EventObjectSpnEmitter(this, emitterObject); - } - else if ("SPL".equals(type)) { - emitter = new EventObjectSplEmitter(this, emitterObject); - } - else if ("UBR".equals(type)) { - emitter = new EventObjectUbrEmitter(this, emitterObject); - } - else { - emitter = new EventObjectSndEmitter(this, emitterObject); - } - - this.eventObjectEmitters.add(emitter); - - this.initNode(this.nodes, this.nodes[nodeIndex++], emitterObject, emitter); - } - - for (final CollisionShape collisionShape : model.collisionShapes) { - this.initNode(this.nodes, this.nodes[nodeIndex++], collisionShape); - } - - // Save a sorted array of all of the nodes, such that every child node comes - // after its parent. - // This allows for flat iteration when updating. - final List hierarchy = model.hierarchy; - - this.sortedNodes = new SkeletalNode[nodes.size()]; - for (int i = 0, l = nodes.size(); i < l; i++) { - this.sortedNodes[i] = this.nodes[hierarchy.get(i)]; - } - - // If the sequence was changed before the model was loaded, reset it now that - // the model loaded. - this.setSequence(this.sequence); - - if (model.bones.size() != 0) { - this.boneTexture = new DataTexture(model.viewer.gl, 4, model.bones.size() * 4, 1); - } - } - - /* - * Clear all of the emitted objects that belong to this instance. - */ - @Override - public void clearEmittedObjects() { - for (final ParticleEmitter emitter : this.particleEmitters) { - emitter.clear(); - } - - for (final ParticleEmitter2 emitter : this.particleEmitters2) { - emitter.clear(); - } - - for (final RibbonEmitter emitter : this.ribbonEmitters) { - emitter.clear(); - } - - for (final EventObjectEmitter emitter : this.eventObjectEmitters) { - emitter.clear(); - } - } - - private void initNode(final MdxNode[] nodes, final SkeletalNode node, final GenericObject genericObject) { - initNode(nodes, node, genericObject, null); - } - - /** - * Initialize a skeletal node. - */ - private void initNode(final MdxNode[] nodes, final SkeletalNode node, final GenericObject genericObject, - final UpdatableObject object) { - node.pivot.set(genericObject.pivot); - - if (genericObject.parentId == -1) { - node.parent = this; - } - else { - node.parent = nodes[genericObject.parentId]; - } - - /// TODO: single-axis billboarding - if (genericObject.billboarded != 0) { - node.billboarded = true; - } - else if (genericObject.billboardedX != 0) { - node.billboardedX = true; - } - else if (genericObject.billboardedY != 0) { - node.billboardedY = true; - } - else if (genericObject.billboardedZ != 0) { - node.billboardedZ = true; - } - - if (object != null) { - node.object = object; - } - - } - - /* - * Overriden to hide also attachment models. - */ - @Override - public void hide() { - super.hide(); - - for (final AttachmentInstance attachment : this.attachments) { - attachment.internalInstance.hide(); - } - } - - /** - * Updates all of this instance internal nodes and objects. Nodes that are - * determined to not be visible will not be updated, nor will any of their - * children down the hierarchy. - */ - public void updateNodes(final float dt, final boolean forced) { - if (!this.model.ok) { - return; - } - final int sequence = this.sequence; - final int frame = this.frame; - final int counter = this.counter; - final SkeletalNode[] sortedNodes = this.sortedNodes; - final MdxModel model = (MdxModel) this.model; - final List sortedGenericObjects = model.sortedGenericObjects; - final Scene scene = this.scene; - - // Update the nodes - for (int i = 0, l = sortedNodes.length; i < l; i++) { - final GenericObject genericObject = sortedGenericObjects.get(i); - final SkeletalNode node = sortedNodes[i]; - final GenericNode parent = node.parent; - - genericObject.getVisibility(visibilityHeap, sequence, frame, counter); - - final boolean objectVisible = visibilityHeap[0] > 0; - final boolean nodeVisible = forced || (parent.visible && objectVisible); - - node.visible = nodeVisible; - - // Every node only needs to be updated if this is a forced update, or if both - // the parent node and the generic object corresponding to this node are - // visible. - // Incoming messy code for optimizations! - if (nodeVisible) { - boolean wasDirty = false; - final GenericObject.Variants variants = genericObject.variants; - final Vector3 localLocation = node.localLocation; - final Quaternion localRotation = node.localRotation; - final Vector3 localScale = node.localScale; - - // Only update the local node data if there is a need to - if (forced || variants.generic[sequence]) { - wasDirty = true; - - // Translation - if (forced || variants.translation[sequence]) { - genericObject.getTranslation(translationHeap, sequence, frame, counter); - - localLocation.x = translationHeap[0]; - localLocation.y = translationHeap[1]; - localLocation.z = translationHeap[2]; - } - - // Rotation - if (forced || variants.rotation[sequence]) { - genericObject.getRotation(rotationHeap, sequence, frame, counter); - - localRotation.x = rotationHeap[0]; - localRotation.y = rotationHeap[1]; - localRotation.z = rotationHeap[2]; - localRotation.w = rotationHeap[3]; - } - - // Scale - if (forced || variants.scale[sequence]) { - genericObject.getScale(scaleHeap, sequence, frame, counter); - - localScale.x = scaleHeap[0]; - localScale.y = scaleHeap[1]; - localScale.z = scaleHeap[2]; - } - } - - final boolean wasReallyDirty = forced || wasDirty || parent.wasDirty || genericObject.anyBillboarding; - - node.wasDirty = wasReallyDirty; - - // If this is a forced update, or this node's local data was updated, or the - // parent node was updated, do a full world update. - if (wasReallyDirty) { - node.recalculateTransformation(scene); - } - - // If there is an instance object associated with this node, and the node is - // visible (which might not be the case for a forced update!), update the - // object. - // This includes attachments and emitters. - final UpdatableObject object = node.object; - - if (object != null) { - object.update(dt, objectVisible); - } - - // Update all of the node's non-skeletal children, which will update their - // children, and so on. - node.updateChildren(dt, scene); - } - } - } - - /** - * Update the batch data. - */ - public void updateBatches(final boolean forced) { - final int sequence = this.sequence; - final int frame = this.frame; - final int counter = this.counter; - final MdxModel model = (MdxModel) this.model; - if (!model.ok) { - return; - } - final List geosets = model.geosets; - final List layers = model.layers; - final float[][] geosetColors = this.geosetColors; - final float[] layerAlphas = this.layerAlphas; - final int[] layerTextures = this.layerTextures; - final float[][] uvAnims = this.uvAnims; - - // Geoset - for (int i = 0, l = geosets.size(); i < l; i++) { - final Geoset geoset = geosets.get(i); - final GeosetAnimation geosetAnimation = geoset.geosetAnimation; - final float[] geosetColor = geosetColors[i]; - - if (geosetAnimation != null) { - // Color - if (forced || (geosetAnimation.variants.get("color")[sequence] != 0)) { - geosetAnimation.getColor(colorHeap, sequence, frame, counter); - - geosetColor[0] = colorHeap[0]; - geosetColor[1] = colorHeap[1]; - geosetColor[2] = colorHeap[2]; - } - - // Alpha - if (forced || (geosetAnimation.variants.get("alpha")[sequence] != 0)) { - geosetAnimation.getAlpha(alphaHeap, sequence, frame, counter); - - geosetColor[3] = alphaHeap[0]; - } - } - else if (forced) { - geosetColor[0] = 1; - geosetColor[1] = 1; - geosetColor[2] = 1; - geosetColor[3] = 1; - } - } - - // Layers - for (int i = 0, l = layers.size(); i < l; i++) { - final Layer layer = layers.get(i); - final TextureAnimation textureAnimation = layer.textureAnimation; - final float[] uvAnim = uvAnims[i]; - - // Alpha - if (forced || (layer.variants.get("alpha")[sequence] != 0)) { - layer.getAlpha(alphaHeap, sequence, frame, counter); - - layerAlphas[i] = alphaHeap[0]; - } - - // Sprite animation - if (forced || (layer.variants.get("textureId")[sequence] != 0)) { - layer.getTextureId(textureIdHeap, sequence, frame, counter); - - layerTextures[i] = (int) textureIdHeap[0]; - } - - if (textureAnimation != null) { - // UV translation animation - if (forced || (textureAnimation.variants.get("translation")[sequence] != 0)) { - textureAnimation.getTranslation(translationHeap, sequence, frame, counter); - - uvAnim[0] = translationHeap[0]; - uvAnim[1] = translationHeap[1]; - } - - // UV rotation animation - if (forced || (textureAnimation.variants.get("rotation")[sequence] != 0)) { - textureAnimation.getRotation(rotationHeap, sequence, frame, counter); - - uvAnim[2] = rotationHeap[2]; - uvAnim[3] = rotationHeap[3]; - } - - // UV scale animation - if (forced || (textureAnimation.variants.get("scale")[sequence] != 0)) { - textureAnimation.getScale(scaleHeap, sequence, frame, counter); - - uvAnim[4] = scaleHeap[0]; - } - } - else if (forced) { - uvAnim[0] = 0; - uvAnim[1] = 0; - uvAnim[2] = 0; - uvAnim[3] = 1; - uvAnim[4] = 1; - } - } - } - - public void updateBoneTexture() { - if (this.boneTexture != null) { - this.worldMatricesCopyHeap.clear(); - for (int i = 0, l = this.worldMatrices.length; i < l; i++) { - final Matrix4 worldMatrix = this.worldMatrices[i]; - this.worldMatricesCopyHeap.put((i * 16) + 0, worldMatrix.val[Matrix4.M00]); - this.worldMatricesCopyHeap.put((i * 16) + 1, worldMatrix.val[Matrix4.M10]); - this.worldMatricesCopyHeap.put((i * 16) + 2, worldMatrix.val[Matrix4.M20]); - this.worldMatricesCopyHeap.put((i * 16) + 3, worldMatrix.val[Matrix4.M30]); - this.worldMatricesCopyHeap.put((i * 16) + 4, worldMatrix.val[Matrix4.M01]); - this.worldMatricesCopyHeap.put((i * 16) + 5, worldMatrix.val[Matrix4.M11]); - this.worldMatricesCopyHeap.put((i * 16) + 6, worldMatrix.val[Matrix4.M21]); - this.worldMatricesCopyHeap.put((i * 16) + 7, worldMatrix.val[Matrix4.M31]); - this.worldMatricesCopyHeap.put((i * 16) + 8, worldMatrix.val[Matrix4.M02]); - this.worldMatricesCopyHeap.put((i * 16) + 9, worldMatrix.val[Matrix4.M12]); - this.worldMatricesCopyHeap.put((i * 16) + 10, worldMatrix.val[Matrix4.M22]); - this.worldMatricesCopyHeap.put((i * 16) + 11, worldMatrix.val[Matrix4.M32]); - this.worldMatricesCopyHeap.put((i * 16) + 12, worldMatrix.val[Matrix4.M03]); - this.worldMatricesCopyHeap.put((i * 16) + 13, worldMatrix.val[Matrix4.M13]); - this.worldMatricesCopyHeap.put((i * 16) + 14, worldMatrix.val[Matrix4.M23]); - this.worldMatricesCopyHeap.put((i * 16) + 15, worldMatrix.val[Matrix4.M33]); - } - this.boneTexture.bindAndUpdate(this.worldMatricesCopyHeap); - } - } - - @Override - public void renderOpaque(final Matrix4 mvp) { - final MdxModel model = (MdxModel) this.model; - - for (final GenericGroup group : model.opaqueGroups) { - group.render(this, mvp); - } - } - - @Override - public void renderTranslucent() { - if (DynamicShadowManager.IS_SHADOW_MAPPING) { - return; - } - final MdxModel model = (MdxModel) this.model; - - for (final GenericGroup group : model.translucentGroups) { - group.render(this, this.scene.camera.viewProjectionMatrix); - } - } - - @Override - public void updateAnimations(final float dt) { - final MdxModel model = (MdxModel) this.model; - final int sequenceId = this.sequence; - - if (sequenceId != -1) { - final Sequence sequence = model.sequences.get(sequenceId); - final long[] interval = sequence.getInterval(); - final float frameTime = (dt * 1000 * this.animationSpeed); - - final int lastIntegerFrame = this.frame; - this.floatingFrame += frameTime; - this.frame = (int) this.floatingFrame; - final int integerFrameTime = this.frame - lastIntegerFrame; - this.counter += integerFrameTime; - this.allowParticleSpawn = true; - - if (this.floatingFrame >= interval[1]) { - if ((this.sequenceLoopMode == SequenceLoopMode.ALWAYS_LOOP) - || ((this.sequenceLoopMode == SequenceLoopMode.MODEL_LOOP) && (sequence.getFlags() == 0))) { - this.floatingFrame = this.frame = (int) interval[0]; // TODO not cast - - this.resetEventEmitters(); - } - else if (this.sequenceLoopMode == SequenceLoopMode.LOOP_TO_NEXT_ANIMATION) { // faux queued animation - // mode - final float framesPast = this.floatingFrame - interval[1]; - - final List sequences = model.sequences; - this.sequence = (this.sequence + 1) % sequences.size(); - this.floatingFrame = sequences.get(this.sequence).getInterval()[0] + framesPast; // TODO not cast - this.frame = (int) this.floatingFrame; - this.sequenceEnded = false; - this.resetEventEmitters(); - this.forced = true; - } - else { - this.floatingFrame = this.frame = (int) interval[1]; // TODO not cast - this.counter -= integerFrameTime; - this.allowParticleSpawn = false; - } - if (this.sequenceLoopMode == SequenceLoopMode.NEVER_LOOP_AND_HIDE_WHEN_DONE) { - hide(); - } - - this.sequenceEnded = true; - } - else { - this.sequenceEnded = false; - } - } - - final boolean forced = this.forced; - - if (sequenceId == -1) { - if (forced) { - // Update the nodes - this.updateNodes(dt, forced); - - this.updateBoneTexture(); - - // Update the batches - this.updateBatches(forced); - } - } - else { - // let variants = model.variants; - - // if (forced || variants.nodes[sequenceId]) { - // Update the nodes - this.updateNodes(dt, forced); - - this.updateBoneTexture(); - // } - - // if (forced || variants.batches[sequenceId]) { - // Update the batches - this.updateBatches(forced); - // } - } - - this.forced = false; - - } - - @Override - protected void updateLights(final Scene scene) { - for (final LightInstance light : this.lights) { - light.update(scene); - } - } - - @Override - protected void removeLights(final Scene scene2) { - for (final LightInstance light : this.lights) { - light.remove(this.scene); - } - } - - /** - * Set the team color of this instance. - */ - public MdxComplexInstance setTeamColor(final int id) { - this.replaceableTextures[1] = (Texture) this.model.viewer.load( - "ReplaceableTextures\\" + ReplaceableIds.getPathString(1) + ReplaceableIds.getIdString(id) + ".blp", - PathSolver.DEFAULT, null); - this.replaceableTextures[2] = (Texture) this.model.viewer.load( - "ReplaceableTextures\\" + ReplaceableIds.getPathString(2) + ReplaceableIds.getIdString(id) + ".blp", - PathSolver.DEFAULT, null); - return this; - } - - @Override - public void setReplaceableTexture(final int replaceableTextureId, final String replaceableTextureFile) { - this.replaceableTextures[replaceableTextureId] = (Texture) this.model.viewer.load(replaceableTextureFile, - PathSolver.DEFAULT, null); - } - - /** - * Set the vertex color of this instance. - */ - public MdxComplexInstance setVertexColor(final float[] color) { - System.arraycopy(color, 0, this.vertexColor, 0, color.length); - - return this; - } - - /** - * Set the sequence of this instance. - */ - public MdxComplexInstance setSequence(final int id) { - final MdxModel model = (MdxModel) this.model; - - this.sequence = id; - - if (model.ok) { - final List sequences = model.sequences; - - if ((id < 0) || (id > (sequences.size() - 1))) { - this.sequence = -1; - this.frame = 0; - this.floatingFrame = 0; - this.allowParticleSpawn = false; - } - else { - this.frame = (int) sequences.get(id).getInterval()[0]; // TODO not cast - this.floatingFrame = this.frame; - this.sequenceEnded = false; - } - - this.resetEventEmitters(); - - this.forced = true; - } - - return this; - } - - /** - * Set the seuqnece loop mode. 0 to never loop, 1 to loop based on the model, - * and 2 to always loop. 3 was added by Retera as "hide after done" for gameplay - * spawned effects - */ - public MdxComplexInstance setSequenceLoopMode(final SequenceLoopMode mode) { - this.sequenceLoopMode = mode; - - return this; - } - - /** - * Get an attachment node. - */ - public MdxNode getAttachment(final int id) { - final MdxModel model = (MdxModel) this.model; - final Attachment attachment = model.attachments.get(id); - - if (attachment != null) { - return this.nodes[attachment.index]; - } - - return null; - } - - /** - * Event emitters depend on keyframe index changes to emit, rather than only - * values. To work, they need to check what the last keyframe was, and only if - * it's a different one, do something. When changing sequences, these states - * need to be reset, so they can immediately emit things if needed. - */ - private void resetEventEmitters() { - /// TODO: Update this. Said Ghostwolf. - for (final EventObjectEmitter eventObjectEmitter : this.eventObjectEmitters) { - eventObjectEmitter.reset(); - } - } - - @Override - protected RenderBatch getBatch(final TextureMapper textureMapper2) { - throw new UnsupportedOperationException("NOT API"); - } - - public void intersectRayBounds(final Ray ray, final Vector3 intersection) { - this.model.bounds.intersectRay(ray, intersection); - } - - /** - * Intersects a world ray with the model's CollisionShapes. Only ever call this - * function on the Gdx thread because it uses static variables to hold state - * while processing. - * - * @param ray - */ - public boolean intersectRayWithCollision(final Ray ray, final Vector3 intersection, final boolean alwaysUseMesh) { - final MdxModel mdxModel = (MdxModel) this.model; - final List collisionShapes = mdxModel.collisionShapes; - for (final CollisionShape collisionShape : collisionShapes) { - final MdxNode mdxNode = this.nodes[collisionShape.index]; - if (collisionShape.checkIntersect(ray, mdxNode, intersection)) { - return true; - } - } - if (collisionShapes.isEmpty() || alwaysUseMesh) { - for (final Geoset geoset : mdxModel.geosets) { - if (!geoset.unselectable) { - geoset.getAlpha(alphaHeap, this.sequence, this.frame, this.counter); - if (alphaHeap[0] > 0) { - final com.etheller.warsmash.parsers.mdlx.Geoset mdlxGeoset = geoset.mdlxGeoset; - if (CollisionShape.intersectRayTriangles(ray, this, mdlxGeoset.getVertices(), - mdlxGeoset.getFaces(), 3, intersection)) { - return true; - } - } - } - } - } - return false; - } - - public void setAnimationSpeed(final float speedRatio) { - this.animationSpeed = speedRatio; - } - - public void setFrame(final int frame) { - this.frame = frame; - this.floatingFrame = frame; - } - - public void setFrameByRatio(final float ratioOfAnimationCompleted) { - if (this.sequence != -1) { - final Sequence currentlyPlayingSequence = ((MdxModel) this.model).sequences.get(this.sequence); - this.floatingFrame = currentlyPlayingSequence.getInterval()[0] - + ((currentlyPlayingSequence.getInterval()[1] - currentlyPlayingSequence.getInterval()[0]) - * ratioOfAnimationCompleted); - this.frame = (int) this.floatingFrame; - } - } + private static final float[] visibilityHeap = new float[1]; + private static final float[] translationHeap = new float[3]; + private static final float[] rotationHeap = new float[4]; + private static final float[] scaleHeap = new float[3]; + private static final float[] colorHeap = new float[3]; + private static final float[] alphaHeap = new float[1]; + private static final long[] textureIdHeap = new long[1]; + + public List lights = new ArrayList<>(); + public List attachments = new ArrayList<>(); + public List particleEmitters = new ArrayList<>(); + public List particleEmitters2 = new ArrayList<>(); + public List ribbonEmitters = new ArrayList<>(); + public List> eventObjectEmitters = new ArrayList<>(); + public MdxNode[] nodes; + public SkeletalNode[] sortedNodes; + public int frame = 0; + public float floatingFrame = 0; + // Global sequences + public int counter = 0; + public int sequence = -1; + public SequenceLoopMode sequenceLoopMode = SequenceLoopMode.NEVER_LOOP; + public boolean sequenceEnded = false; + public float[] vertexColor = {1, 1, 1, 1}; + // Particles do not spawn when the sequence is -1, or when the sequence finished + // and it's not repeating + public boolean allowParticleSpawn = false; + // If forced is true, everything will update regardless of variancy. + // Any later non-forced update can then use variancy to skip updating things. + // It is set to true every time the sequence is set with setSequence(). + public boolean forced = true; + public float[][] geosetColors; + public float[] layerAlphas; + public int[] layerTextures; + public float[][] uvAnims; + public Matrix4[] worldMatrices; + public FloatBuffer worldMatricesCopyHeap; + public DataTexture boneTexture; + public Texture[] replaceableTextures = new Texture[WarsmashConstants.REPLACEABLE_TEXTURE_LIMIT]; + private float animationSpeed = 1.0f; + + public MdxComplexInstance(final MdxModel model) { + super(model); + } + + @Override + public void load() { + final MdxModel model = (MdxModel) this.model; + + this.geosetColors = new float[model.geosets.size()][]; + for (int i = 0, l = model.geosets.size(); i < l; i++) { + this.geosetColors[i] = new float[4]; + } + + this.layerAlphas = new float[model.layers.size()]; + this.layerTextures = new int[model.layers.size()]; + this.uvAnims = new float[model.layers.size()][]; + for (int i = 0, l = model.layers.size(); i < l; i++) { + this.layerAlphas[i] = 0; + this.layerTextures[i] = 0; + this.uvAnims[i] = new float[5]; + } + + // Create the needed amount of shared nodes. + final Object[] sharedNodeData = Node.createSkeletalNodes(model.genericObjects.size(), + MdxNodeDescriptor.INSTANCE); + final List nodes = (List) sharedNodeData[0]; + int nodeIndex = 0; + this.nodes = nodes.toArray(new MdxNode[nodes.size()]); + + // A shared typed array for all world matrices of the internal nodes. + this.worldMatrices = ((List) sharedNodeData[1]).toArray(new Matrix4[0]); + this.worldMatricesCopyHeap = ByteBuffer.allocateDirect(16 * this.worldMatrices.length * 4) + .order(ByteOrder.nativeOrder()).asFloatBuffer(); + + // And now initialize all of the nodes and objects + for (final Bone bone : model.bones) { + this.initNode(this.nodes, this.nodes[nodeIndex++], bone); + } + + for (final Light light : model.lights) { + final LightInstance lightInstance = new LightInstance(this, light); + this.lights.add(lightInstance); + this.initNode(this.nodes, this.nodes[nodeIndex++], light, lightInstance); + } + + for (final Helper helper : model.helpers) { + this.initNode(this.nodes, this.nodes[nodeIndex++], helper); + } + + for (final Attachment attachment : model.attachments) { + AttachmentInstance attachmentInstance = null; + + // Attachments may have game models attached to them, such as Undead and + // Nightelf building animations. + if (attachment.internalModel != null) { + attachmentInstance = new AttachmentInstance(this, attachment); + + this.attachments.add(attachmentInstance); + } + + this.initNode(this.nodes, this.nodes[nodeIndex++], attachment, attachmentInstance); + } + + for (final ParticleEmitterObject emitterObject : model.particleEmitters) { + final ParticleEmitter emitter = new ParticleEmitter(this, emitterObject); + + this.particleEmitters.add(emitter); + + this.initNode(this.nodes, this.nodes[nodeIndex++], emitterObject, emitter); + } + + for (final ParticleEmitter2Object emitterObject : model.particleEmitters2) { + final ParticleEmitter2 emitter = new ParticleEmitter2(this, emitterObject); + + this.particleEmitters2.add(emitter); + + this.initNode(this.nodes, this.nodes[nodeIndex++], emitterObject, emitter); + } + + for (final RibbonEmitterObject emitterObject : model.ribbonEmitters) { + final RibbonEmitter emitter = new RibbonEmitter(this, emitterObject); + + this.ribbonEmitters.add(emitter); + + this.initNode(this.nodes, this.nodes[nodeIndex++], emitterObject, emitter); + } + + for (final EventObjectEmitterObject emitterObject : model.eventObjects) { + final String type = emitterObject.type; + EventObjectEmitter emitter; + + if ("SPN".equals(type)) { + emitter = new EventObjectSpnEmitter(this, emitterObject); + } else if ("SPL".equals(type)) { + emitter = new EventObjectSplEmitter(this, emitterObject); + } else if ("UBR".equals(type)) { + emitter = new EventObjectUbrEmitter(this, emitterObject); + } else { + emitter = new EventObjectSndEmitter(this, emitterObject); + } + + this.eventObjectEmitters.add(emitter); + + this.initNode(this.nodes, this.nodes[nodeIndex++], emitterObject, emitter); + } + + for (final CollisionShape collisionShape : model.collisionShapes) { + this.initNode(this.nodes, this.nodes[nodeIndex++], collisionShape); + } + + // Save a sorted array of all of the nodes, such that every child node comes + // after its parent. + // This allows for flat iteration when updating. + final List hierarchy = model.hierarchy; + + this.sortedNodes = new SkeletalNode[nodes.size()]; + for (int i = 0, l = nodes.size(); i < l; i++) { + this.sortedNodes[i] = this.nodes[hierarchy.get(i)]; + } + + // If the sequence was changed before the model was loaded, reset it now that + // the model loaded. + this.setSequence(this.sequence); + + if (model.bones.size() != 0) { + this.boneTexture = new DataTexture(model.viewer.gl, 4, model.bones.size() * 4, 1); + } + } + + /* + * Clear all of the emitted objects that belong to this instance. + */ + @Override + public void clearEmittedObjects() { + for (final ParticleEmitter emitter : this.particleEmitters) { + emitter.clear(); + } + + for (final ParticleEmitter2 emitter : this.particleEmitters2) { + emitter.clear(); + } + + for (final RibbonEmitter emitter : this.ribbonEmitters) { + emitter.clear(); + } + + for (final EventObjectEmitter emitter : this.eventObjectEmitters) { + emitter.clear(); + } + } + + private void initNode(final MdxNode[] nodes, final SkeletalNode node, final GenericObject genericObject) { + initNode(nodes, node, genericObject, null); + } + + /** + * Initialize a skeletal node. + */ + private void initNode(final MdxNode[] nodes, final SkeletalNode node, final GenericObject genericObject, + final UpdatableObject object) { + node.pivot.set(genericObject.pivot); + + if (genericObject.parentId == -1) { + node.parent = this; + } else { + node.parent = nodes[genericObject.parentId]; + } + + /// TODO: single-axis billboarding + if (genericObject.billboarded != 0) { + node.billboarded = true; + } else if (genericObject.billboardedX != 0) { + node.billboardedX = true; + } else if (genericObject.billboardedY != 0) { + node.billboardedY = true; + } else if (genericObject.billboardedZ != 0) { + node.billboardedZ = true; + } + + if (object != null) { + node.object = object; + } + + } + + /* + * Overriden to hide also attachment models. + */ + @Override + public void hide() { + super.hide(); + + for (final AttachmentInstance attachment : this.attachments) { + attachment.internalInstance.hide(); + } + } + + /** + * Updates all of this instance internal nodes and objects. Nodes that are + * determined to not be visible will not be updated, nor will any of their + * children down the hierarchy. + */ + public void updateNodes(final float dt, final boolean forced) { + if (!this.model.ok) { + return; + } + final int sequence = this.sequence; + final int frame = this.frame; + final int counter = this.counter; + final SkeletalNode[] sortedNodes = this.sortedNodes; + final MdxModel model = (MdxModel) this.model; + final List sortedGenericObjects = model.sortedGenericObjects; + final Scene scene = this.scene; + + // Update the nodes + for (int i = 0, l = sortedNodes.length; i < l; i++) { + final GenericObject genericObject = sortedGenericObjects.get(i); + final SkeletalNode node = sortedNodes[i]; + final GenericNode parent = node.parent; + + genericObject.getVisibility(visibilityHeap, sequence, frame, counter); + + final boolean objectVisible = visibilityHeap[0] > 0; + final boolean nodeVisible = forced || (parent.visible && objectVisible); + + node.visible = nodeVisible; + + // Every node only needs to be updated if this is a forced update, or if both + // the parent node and the generic object corresponding to this node are + // visible. + // Incoming messy code for optimizations! + if (nodeVisible) { + boolean wasDirty = false; + final GenericObject.Variants variants = genericObject.variants; + final Vector3 localLocation = node.localLocation; + final Quaternion localRotation = node.localRotation; + final Vector3 localScale = node.localScale; + + // Only update the local node data if there is a need to + if (forced || variants.generic[sequence]) { + wasDirty = true; + + // Translation + if (forced || variants.translation[sequence]) { + genericObject.getTranslation(translationHeap, sequence, frame, counter); + + localLocation.x = translationHeap[0]; + localLocation.y = translationHeap[1]; + localLocation.z = translationHeap[2]; + } + + // Rotation + if (forced || variants.rotation[sequence]) { + genericObject.getRotation(rotationHeap, sequence, frame, counter); + + localRotation.x = rotationHeap[0]; + localRotation.y = rotationHeap[1]; + localRotation.z = rotationHeap[2]; + localRotation.w = rotationHeap[3]; + } + + // Scale + if (forced || variants.scale[sequence]) { + genericObject.getScale(scaleHeap, sequence, frame, counter); + + localScale.x = scaleHeap[0]; + localScale.y = scaleHeap[1]; + localScale.z = scaleHeap[2]; + } + } + + final boolean wasReallyDirty = forced || wasDirty || parent.wasDirty || genericObject.anyBillboarding; + + node.wasDirty = wasReallyDirty; + + // If this is a forced update, or this node's local data was updated, or the + // parent node was updated, do a full world update. + if (wasReallyDirty) { + node.recalculateTransformation(scene); + } + + // If there is an instance object associated with this node, and the node is + // visible (which might not be the case for a forced update!), update the + // object. + // This includes attachments and emitters. + final UpdatableObject object = node.object; + + if (object != null) { + object.update(dt, objectVisible); + } + + // Update all of the node's non-skeletal children, which will update their + // children, and so on. + node.updateChildren(dt, scene); + } + } + } + + /** + * Update the batch data. + */ + public void updateBatches(final boolean forced) { + final int sequence = this.sequence; + final int frame = this.frame; + final int counter = this.counter; + final MdxModel model = (MdxModel) this.model; + if (!model.ok) { + return; + } + final List geosets = model.geosets; + final List layers = model.layers; + final float[][] geosetColors = this.geosetColors; + final float[] layerAlphas = this.layerAlphas; + final int[] layerTextures = this.layerTextures; + final float[][] uvAnims = this.uvAnims; + + // Geoset + for (int i = 0, l = geosets.size(); i < l; i++) { + final Geoset geoset = geosets.get(i); + final GeosetAnimation geosetAnimation = geoset.geosetAnimation; + final float[] geosetColor = geosetColors[i]; + + if (geosetAnimation != null) { + // Color + if (forced || (geosetAnimation.variants.get("color")[sequence] != 0)) { + geosetAnimation.getColor(colorHeap, sequence, frame, counter); + + geosetColor[0] = colorHeap[0]; + geosetColor[1] = colorHeap[1]; + geosetColor[2] = colorHeap[2]; + } + + // Alpha + if (forced || (geosetAnimation.variants.get("alpha")[sequence] != 0)) { + geosetAnimation.getAlpha(alphaHeap, sequence, frame, counter); + + geosetColor[3] = alphaHeap[0]; + } + } else if (forced) { + geosetColor[0] = 1; + geosetColor[1] = 1; + geosetColor[2] = 1; + geosetColor[3] = 1; + } + } + + // Layers + for (int i = 0, l = layers.size(); i < l; i++) { + final Layer layer = layers.get(i); + final TextureAnimation textureAnimation = layer.textureAnimation; + final float[] uvAnim = uvAnims[i]; + + // Alpha + if (forced || (layer.variants.get("alpha")[sequence] != 0)) { + layer.getAlpha(alphaHeap, sequence, frame, counter); + + layerAlphas[i] = alphaHeap[0]; + } + + // Sprite animation + if (forced || (layer.variants.get("textureId")[sequence] != 0)) { + layer.getTextureId(textureIdHeap, sequence, frame, counter); + + layerTextures[i] = (int) textureIdHeap[0]; + } + + if (textureAnimation != null) { + // UV translation animation + if (forced || (textureAnimation.variants.get("translation")[sequence] != 0)) { + textureAnimation.getTranslation(translationHeap, sequence, frame, counter); + + uvAnim[0] = translationHeap[0]; + uvAnim[1] = translationHeap[1]; + } + + // UV rotation animation + if (forced || (textureAnimation.variants.get("rotation")[sequence] != 0)) { + textureAnimation.getRotation(rotationHeap, sequence, frame, counter); + + uvAnim[2] = rotationHeap[2]; + uvAnim[3] = rotationHeap[3]; + } + + // UV scale animation + if (forced || (textureAnimation.variants.get("scale")[sequence] != 0)) { + textureAnimation.getScale(scaleHeap, sequence, frame, counter); + + uvAnim[4] = scaleHeap[0]; + } + } else if (forced) { + uvAnim[0] = 0; + uvAnim[1] = 0; + uvAnim[2] = 0; + uvAnim[3] = 1; + uvAnim[4] = 1; + } + } + } + + public void updateBoneTexture() { + if (this.boneTexture != null) { + this.worldMatricesCopyHeap.clear(); + for (int i = 0, l = this.worldMatrices.length; i < l; i++) { + final Matrix4 worldMatrix = this.worldMatrices[i]; + this.worldMatricesCopyHeap.put((i * 16) + 0, worldMatrix.val[Matrix4.M00]); + this.worldMatricesCopyHeap.put((i * 16) + 1, worldMatrix.val[Matrix4.M10]); + this.worldMatricesCopyHeap.put((i * 16) + 2, worldMatrix.val[Matrix4.M20]); + this.worldMatricesCopyHeap.put((i * 16) + 3, worldMatrix.val[Matrix4.M30]); + this.worldMatricesCopyHeap.put((i * 16) + 4, worldMatrix.val[Matrix4.M01]); + this.worldMatricesCopyHeap.put((i * 16) + 5, worldMatrix.val[Matrix4.M11]); + this.worldMatricesCopyHeap.put((i * 16) + 6, worldMatrix.val[Matrix4.M21]); + this.worldMatricesCopyHeap.put((i * 16) + 7, worldMatrix.val[Matrix4.M31]); + this.worldMatricesCopyHeap.put((i * 16) + 8, worldMatrix.val[Matrix4.M02]); + this.worldMatricesCopyHeap.put((i * 16) + 9, worldMatrix.val[Matrix4.M12]); + this.worldMatricesCopyHeap.put((i * 16) + 10, worldMatrix.val[Matrix4.M22]); + this.worldMatricesCopyHeap.put((i * 16) + 11, worldMatrix.val[Matrix4.M32]); + this.worldMatricesCopyHeap.put((i * 16) + 12, worldMatrix.val[Matrix4.M03]); + this.worldMatricesCopyHeap.put((i * 16) + 13, worldMatrix.val[Matrix4.M13]); + this.worldMatricesCopyHeap.put((i * 16) + 14, worldMatrix.val[Matrix4.M23]); + this.worldMatricesCopyHeap.put((i * 16) + 15, worldMatrix.val[Matrix4.M33]); + } + this.boneTexture.bindAndUpdate(this.worldMatricesCopyHeap); + } + } + + @Override + public void renderOpaque(final Matrix4 mvp) { + final MdxModel model = (MdxModel) this.model; + + for (final GenericGroup group : model.opaqueGroups) { + group.render(this, mvp); + } + } + + @Override + public void renderTranslucent() { + if (DynamicShadowManager.IS_SHADOW_MAPPING) { + return; + } + final MdxModel model = (MdxModel) this.model; + + for (final GenericGroup group : model.translucentGroups) { + group.render(this, this.scene.camera.viewProjectionMatrix); + } + } + + @Override + public void updateAnimations(final float dt) { + final MdxModel model = (MdxModel) this.model; + final int sequenceId = this.sequence; + + if (sequenceId != -1) { + final Sequence sequence = model.sequences.get(sequenceId); + final long[] interval = sequence.getInterval(); + final float frameTime = (dt * 1000 * this.animationSpeed); + + final int lastIntegerFrame = this.frame; + this.floatingFrame += frameTime; + this.frame = (int) this.floatingFrame; + final int integerFrameTime = this.frame - lastIntegerFrame; + this.counter += integerFrameTime; + this.allowParticleSpawn = true; + + if (this.floatingFrame >= interval[1]) { + if ((this.sequenceLoopMode == SequenceLoopMode.ALWAYS_LOOP) + || ((this.sequenceLoopMode == SequenceLoopMode.MODEL_LOOP) && (sequence.getFlags() == 0))) { + this.floatingFrame = this.frame = (int) interval[0]; // TODO not cast + + this.resetEventEmitters(); + } else if (this.sequenceLoopMode == SequenceLoopMode.LOOP_TO_NEXT_ANIMATION) { // faux queued animation + // mode + final float framesPast = this.floatingFrame - interval[1]; + + final List sequences = model.sequences; + this.sequence = (this.sequence + 1) % sequences.size(); + this.floatingFrame = sequences.get(this.sequence).getInterval()[0] + framesPast; // TODO not cast + this.frame = (int) this.floatingFrame; + this.sequenceEnded = false; + this.resetEventEmitters(); + this.forced = true; + } else { + this.floatingFrame = this.frame = (int) interval[1]; // TODO not cast + this.counter -= integerFrameTime; + this.allowParticleSpawn = false; + } + if (this.sequenceLoopMode == SequenceLoopMode.NEVER_LOOP_AND_HIDE_WHEN_DONE) { + hide(); + } + + this.sequenceEnded = true; + } else { + this.sequenceEnded = false; + } + } + + final boolean forced = this.forced; + + if (sequenceId == -1) { + if (forced) { + // Update the nodes + this.updateNodes(dt, forced); + + this.updateBoneTexture(); + + // Update the batches + this.updateBatches(forced); + } + } else { + // let variants = model.variants; + + // if (forced || variants.nodes[sequenceId]) { + // Update the nodes + this.updateNodes(dt, forced); + + this.updateBoneTexture(); + // } + + // if (forced || variants.batches[sequenceId]) { + // Update the batches + this.updateBatches(forced); + // } + } + + this.forced = false; + + } + + @Override + protected void updateLights(final Scene scene) { + for (final LightInstance light : this.lights) { + light.update(scene); + } + } + + @Override + protected void removeLights(final Scene scene2) { + for (final LightInstance light : this.lights) { + light.remove(this.scene); + } + } + + /** + * Set the team color of this instance. + */ + public MdxComplexInstance setTeamColor(final int id) { + this.replaceableTextures[1] = (Texture) this.model.viewer.load( + "ReplaceableTextures\\" + ReplaceableIds.getPathString(1) + ReplaceableIds.getIdString(id) + ".blp", + PathSolver.DEFAULT, null); + this.replaceableTextures[2] = (Texture) this.model.viewer.load( + "ReplaceableTextures\\" + ReplaceableIds.getPathString(2) + ReplaceableIds.getIdString(id) + ".blp", + PathSolver.DEFAULT, null); + return this; + } + + @Override + public void setReplaceableTexture(final int replaceableTextureId, final String replaceableTextureFile) { + this.replaceableTextures[replaceableTextureId] = (Texture) this.model.viewer.load(replaceableTextureFile, + PathSolver.DEFAULT, null); + } + + /** + * Set the vertex color of this instance. + */ + public MdxComplexInstance setVertexColor(final float[] color) { + System.arraycopy(color, 0, this.vertexColor, 0, color.length); + + return this; + } + + /** + * Set the sequence of this instance. + */ + public MdxComplexInstance setSequence(final int id) { + final MdxModel model = (MdxModel) this.model; + + this.sequence = id; + + if (model.ok) { + final List sequences = model.sequences; + + if ((id < 0) || (id > (sequences.size() - 1))) { + this.sequence = -1; + this.frame = 0; + this.floatingFrame = 0; + this.allowParticleSpawn = false; + } else { + this.frame = (int) sequences.get(id).getInterval()[0]; // TODO not cast + this.floatingFrame = this.frame; + this.sequenceEnded = false; + } + + this.resetEventEmitters(); + + this.forced = true; + } + + return this; + } + + /** + * Set the seuqnece loop mode. 0 to never loop, 1 to loop based on the model, + * and 2 to always loop. 3 was added by Retera as "hide after done" for gameplay + * spawned effects + */ + public MdxComplexInstance setSequenceLoopMode(final SequenceLoopMode mode) { + this.sequenceLoopMode = mode; + + return this; + } + + /** + * Get an attachment node. + */ + public MdxNode getAttachment(final int id) { + final MdxModel model = (MdxModel) this.model; + final Attachment attachment = model.attachments.get(id); + + if (attachment != null) { + return this.nodes[attachment.index]; + } + + return null; + } + + /** + * Event emitters depend on keyframe index changes to emit, rather than only + * values. To work, they need to check what the last keyframe was, and only if + * it's a different one, do something. When changing sequences, these states + * need to be reset, so they can immediately emit things if needed. + */ + private void resetEventEmitters() { + /// TODO: Update this. Said Ghostwolf. + for (final EventObjectEmitter eventObjectEmitter : this.eventObjectEmitters) { + eventObjectEmitter.reset(); + } + } + + @Override + protected RenderBatch getBatch(final TextureMapper textureMapper2) { + throw new UnsupportedOperationException("NOT API"); + } + + public void intersectRayBounds(final Ray ray, final Vector3 intersection) { + this.model.bounds.intersectRay(ray, intersection); + } + + /** + * Intersects a world ray with the model's CollisionShapes. Only ever call this + * function on the Gdx thread because it uses static variables to hold state + * while processing. + * + * @param ray + */ + public boolean intersectRayWithCollision(final Ray ray, final Vector3 intersection, final boolean alwaysUseMesh, final boolean onlyUseMesh) { + final MdxModel mdxModel = (MdxModel) this.model; + final List collisionShapes = mdxModel.collisionShapes; + if (!onlyUseMesh) { + for (final CollisionShape collisionShape : collisionShapes) { + final MdxNode mdxNode = this.nodes[collisionShape.index]; + if (collisionShape.checkIntersect(ray, mdxNode, intersection)) { + return true; + } + } + } + if (collisionShapes.isEmpty() || alwaysUseMesh) { + for (final Geoset geoset : mdxModel.geosets) { + if (!geoset.unselectable) { + geoset.getAlpha(alphaHeap, this.sequence, this.frame, this.counter); + if (alphaHeap[0] > 0) { + final com.etheller.warsmash.parsers.mdlx.Geoset mdlxGeoset = geoset.mdlxGeoset; + if (CollisionShape.intersectRayTriangles(ray, this, mdlxGeoset.getVertices(), + mdlxGeoset.getFaces(), 3, intersection)) { + return true; + } + } + } + } + } + return false; + } + + public void setAnimationSpeed(final float speedRatio) { + this.animationSpeed = speedRatio; + } + + public void setFrame(final int frame) { + this.frame = frame; + this.floatingFrame = frame; + } + + public void setFrameByRatio(final float ratioOfAnimationCompleted) { + if (this.sequence != -1) { + final Sequence currentlyPlayingSequence = ((MdxModel) this.model).sequences.get(this.sequence); + this.floatingFrame = currentlyPlayingSequence.getInterval()[0] + + ((currentlyPlayingSequence.getInterval()[1] - currentlyPlayingSequence.getInterval()[0]) + * ratioOfAnimationCompleted); + this.frame = (int) this.floatingFrame; + } + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxHandler.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxHandler.java index 921a24b..e2b7ee0 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxHandler.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxHandler.java @@ -32,14 +32,14 @@ public class MdxHandler extends ModelHandler { Shaders.extendedShadowMap = viewer.webGL.createShaderProgram( "#define EXTENDED_BONES\r\n" + MdxShaders.vsComplex, MdxShaders.fsComplexShadowMap); Shaders.particles = viewer.webGL.createShaderProgram(MdxShaders.vsParticles, MdxShaders.fsParticles); - Shaders.simple = viewer.webGL.createShaderProgram(MdxShaders.vsSimple, MdxShaders.fsSimple); + //Shaders.simple = viewer.webGL.createShaderProgram(MdxShaders.vsSimple, MdxShaders.fsSimple); // Shaders.hd = viewer.webGL.createShaderProgram(MdxShaders.vsHd, MdxShaders.fsHd); // TODO HD reforged // If a shader failed to compile, don't allow the handler to be registered, and // send an error instead. return Shaders.complex.isCompiled() && Shaders.extended.isCompiled() && Shaders.particles.isCompiled() - && Shaders.simple.isCompiled() /* && Shaders.hd.isCompiled() */; + /* && Shaders.simple.isCompiled() && Shaders.hd.isCompiled() */; } @Override diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SdSequence.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SdSequence.java index 58c91da..06517db 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SdSequence.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SdSequence.java @@ -152,6 +152,9 @@ public final class SdSequence { } private TYPE[] fixTimelineArray(final Timeline timeline, final TYPE[] values) { + if(values == null) { + return null; + } if (timeline.getName().equals(AnimationMap.KLAC.getWar3id()) || timeline.getName().equals(AnimationMap.KLBC.getWar3id())) { final float[][] flippedColorData = new float[values.length][3]; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/RenderDestructable.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/RenderDestructable.java index 5a55972..ed8b672 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/RenderDestructable.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/RenderDestructable.java @@ -2,25 +2,38 @@ package com.etheller.warsmash.viewer5.handlers.w3x; import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; import com.etheller.warsmash.units.manager.MutableObjectData.WorldEditorDataType; +import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; public class RenderDestructable extends RenderDoodad { + private static final War3ID TEX_FILE = War3ID.fromString("btxf"); + private static final War3ID TEX_ID = War3ID.fromString("btxi"); - private final float life; + private final float life; - public RenderDestructable(final War3MapViewer map, final MdxModel model, final MutableGameObject row, - final com.etheller.warsmash.parsers.w3x.doo.Doodad doodad, final WorldEditorDataType type, - final float maxPitch, final float maxRoll, final float life) { - super(map, model, row, doodad, type, maxPitch, maxRoll); - this.life = life; - } + public RenderDestructable(final War3MapViewer map, final MdxModel model, final MutableGameObject row, + final com.etheller.warsmash.parsers.w3x.doo.Doodad doodad, final WorldEditorDataType type, + final float maxPitch, final float maxRoll, final float life) { + super(map, model, row, doodad, type, maxPitch, maxRoll); + this.life = life; + String replaceableTextureFile = row.getFieldAsString(TEX_FILE, 0); + final int replaceableTextureId = row.getFieldAsInteger(TEX_ID, 0); + if ((replaceableTextureFile != null) && (replaceableTextureFile.length() > 1)) { + int dotIndex = replaceableTextureFile.lastIndexOf('.'); + if (dotIndex != -1) { + replaceableTextureFile = replaceableTextureFile.substring(0, dotIndex); + } + replaceableTextureFile += ".blp"; + instance.setReplaceableTexture(replaceableTextureId, replaceableTextureFile); + } + } - @Override - public PrimaryTag getAnimation() { - if (this.life <= 0) { - return PrimaryTag.DEATH; - } - return super.getAnimation(); - } + @Override + public PrimaryTag getAnimation() { + if (this.life <= 0) { + return PrimaryTag.DEATH; + } + return super.getAnimation(); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/RenderDoodad.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/RenderDoodad.java index 59a3e72..504e9fc 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/RenderDoodad.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/RenderDoodad.java @@ -12,86 +12,72 @@ import com.etheller.warsmash.viewer5.handlers.mdx.SequenceLoopMode; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; public class RenderDoodad { - private static final int SAMPLE_RADIUS = 4; - private static final War3ID TEX_FILE = War3ID.fromString("btxf"); - private static final War3ID TEX_ID = War3ID.fromString("btxi"); - public final ModelInstance instance; - private final MutableGameObject row; - private final float maxPitch; - private final float maxRoll; + private static final int SAMPLE_RADIUS = 4; + public final ModelInstance instance; + private final MutableGameObject row; + private final float maxPitch; + private final float maxRoll; - public RenderDoodad(final War3MapViewer map, final MdxModel model, final MutableGameObject row, - final com.etheller.warsmash.parsers.w3x.doo.Doodad doodad, final WorldEditorDataType type, - final float maxPitch, final float maxRoll) { - this.maxPitch = maxPitch; - this.maxRoll = maxRoll; - final boolean isSimple = row.readSLKTagBoolean("lightweight"); - ModelInstance instance; + public RenderDoodad(final War3MapViewer map, final MdxModel model, final MutableGameObject row, + final com.etheller.warsmash.parsers.w3x.doo.Doodad doodad, final WorldEditorDataType type, + final float maxPitch, final float maxRoll) { + this.maxPitch = maxPitch; + this.maxRoll = maxRoll; + final boolean isSimple = row.readSLKTagBoolean("lightweight"); + ModelInstance instance; - if (isSimple && false) { - instance = model.addInstance(1); - } - else { - instance = model.addInstance(); - ((MdxComplexInstance) instance).setSequenceLoopMode(SequenceLoopMode.NEVER_LOOP); - } + if (isSimple && false) { + instance = model.addInstance(1); + } else { + instance = model.addInstance(); + ((MdxComplexInstance) instance).setSequenceLoopMode(SequenceLoopMode.NEVER_LOOP); + } - instance.move(doodad.getLocation()); - // TODO: the following pitch/roll system is a heuristic, and we probably want to - // revisit it later. - // Specifically, I was pretty convinced that whichever is applied first - // (pitch/roll) should be used to do a projection onto the already-tilted plane - // to find the angle used for the other of the two - // (instead of measuring down from an imaginary flat ground plane, as we do - // currently). - final float facingRadians = doodad.getAngle(); - float pitch, roll; - final float x = doodad.getLocation()[0]; - final float y = doodad.getLocation()[1]; - final float pitchSampleForwardX = x + (SAMPLE_RADIUS * (float) Math.cos(facingRadians)); - final float pitchSampleForwardY = y + (SAMPLE_RADIUS * (float) Math.sin(facingRadians)); - final float pitchSampleBackwardX = x - (SAMPLE_RADIUS * (float) Math.cos(facingRadians)); - final float pitchSampleBackwardY = y - (SAMPLE_RADIUS * (float) Math.sin(facingRadians)); - final float pitchSampleGroundHeight1 = map.terrain.getGroundHeight(pitchSampleBackwardX, pitchSampleBackwardY); - final float pitchSampleGorundHeight2 = map.terrain.getGroundHeight(pitchSampleForwardX, pitchSampleForwardY); - pitch = Math.max(-maxPitch, Math.min(maxPitch, - (float) Math.atan2(pitchSampleGorundHeight2 - pitchSampleGroundHeight1, SAMPLE_RADIUS * 2))); - final double leftOfFacingAngle = facingRadians + (Math.PI / 2); - final float rollSampleForwardX = x + (SAMPLE_RADIUS * (float) Math.cos(leftOfFacingAngle)); - final float rollSampleForwardY = y + (SAMPLE_RADIUS * (float) Math.sin(leftOfFacingAngle)); - final float rollSampleBackwardX = x - (SAMPLE_RADIUS * (float) Math.cos(leftOfFacingAngle)); - final float rollSampleBackwardY = y - (SAMPLE_RADIUS * (float) Math.sin(leftOfFacingAngle)); - final float rollSampleGroundHeight1 = map.terrain.getGroundHeight(rollSampleBackwardX, rollSampleBackwardY); - final float rollSampleGroundHeight2 = map.terrain.getGroundHeight(rollSampleForwardX, rollSampleForwardY); - roll = Math.max(-maxRoll, Math.min(maxRoll, - (float) Math.atan2(rollSampleGroundHeight2 - rollSampleGroundHeight1, SAMPLE_RADIUS * 2))); - instance.rotate(new Quaternion().setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, facingRadians)); - instance.rotate(new Quaternion().setFromAxisRad(RenderMathUtils.VEC3_UNIT_Y, -pitch)); - instance.rotate(new Quaternion().setFromAxisRad(RenderMathUtils.VEC3_UNIT_X, roll)); + instance.move(doodad.getLocation()); + // TODO: the following pitch/roll system is a heuristic, and we probably want to + // revisit it later. + // Specifically, I was pretty convinced that whichever is applied first + // (pitch/roll) should be used to do a projection onto the already-tilted plane + // to find the angle used for the other of the two + // (instead of measuring down from an imaginary flat ground plane, as we do + // currently). + final float facingRadians = doodad.getAngle(); + float pitch, roll; + final float x = doodad.getLocation()[0]; + final float y = doodad.getLocation()[1]; + final float pitchSampleForwardX = x + (SAMPLE_RADIUS * (float) Math.cos(facingRadians)); + final float pitchSampleForwardY = y + (SAMPLE_RADIUS * (float) Math.sin(facingRadians)); + final float pitchSampleBackwardX = x - (SAMPLE_RADIUS * (float) Math.cos(facingRadians)); + final float pitchSampleBackwardY = y - (SAMPLE_RADIUS * (float) Math.sin(facingRadians)); + final float pitchSampleGroundHeight1 = map.terrain.getGroundHeight(pitchSampleBackwardX, pitchSampleBackwardY); + final float pitchSampleGorundHeight2 = map.terrain.getGroundHeight(pitchSampleForwardX, pitchSampleForwardY); + pitch = Math.max(-maxPitch, Math.min(maxPitch, + (float) Math.atan2(pitchSampleGorundHeight2 - pitchSampleGroundHeight1, SAMPLE_RADIUS * 2))); + final double leftOfFacingAngle = facingRadians + (Math.PI / 2); + final float rollSampleForwardX = x + (SAMPLE_RADIUS * (float) Math.cos(leftOfFacingAngle)); + final float rollSampleForwardY = y + (SAMPLE_RADIUS * (float) Math.sin(leftOfFacingAngle)); + final float rollSampleBackwardX = x - (SAMPLE_RADIUS * (float) Math.cos(leftOfFacingAngle)); + final float rollSampleBackwardY = y - (SAMPLE_RADIUS * (float) Math.sin(leftOfFacingAngle)); + final float rollSampleGroundHeight1 = map.terrain.getGroundHeight(rollSampleBackwardX, rollSampleBackwardY); + final float rollSampleGroundHeight2 = map.terrain.getGroundHeight(rollSampleForwardX, rollSampleForwardY); + roll = Math.max(-maxRoll, Math.min(maxRoll, + (float) Math.atan2(rollSampleGroundHeight2 - rollSampleGroundHeight1, SAMPLE_RADIUS * 2))); + instance.rotate(new Quaternion().setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, facingRadians)); + instance.rotate(new Quaternion().setFromAxisRad(RenderMathUtils.VEC3_UNIT_Y, -pitch)); + instance.rotate(new Quaternion().setFromAxisRad(RenderMathUtils.VEC3_UNIT_X, roll)); // instance.rotate(new Quaternion().setEulerAnglesRad(facingRadians, 0, 0)); - instance.scale(doodad.getScale()); - if (type == WorldEditorDataType.DOODADS) { - final float defScale = row.readSLKTagFloat("defScale"); - instance.uniformScale(defScale); - } - if (type == WorldEditorDataType.DESTRUCTIBLES) { - // TODO destructables need to be their own type, game simulation, etc - String replaceableTextureFile = row.getFieldAsString(TEX_FILE, 0); - final int replaceableTextureId = row.getFieldAsInteger(TEX_ID, 0); - if ((replaceableTextureFile != null) && (replaceableTextureFile.length() > 1)) { - if (!replaceableTextureFile.toLowerCase().endsWith(".blp")) { - replaceableTextureFile += ".blp"; - } - instance.setReplaceableTexture(replaceableTextureId, replaceableTextureFile); - } - } - instance.setScene(map.worldScene); + instance.scale(doodad.getScale()); + if (type == WorldEditorDataType.DOODADS) { + final float defScale = row.readSLKTagFloat("defScale"); + instance.uniformScale(defScale); + } + instance.setScene(map.worldScene); - this.instance = instance; - this.row = row; - } + this.instance = instance; + this.row = row; + } - public PrimaryTag getAnimation() { - return PrimaryTag.STAND; - } + public PrimaryTag getAnimation() { + return PrimaryTag.STAND; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index 1a797b9..4397358 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -19,6 +19,8 @@ import java.util.function.Consumer; import javax.imageio.ImageIO; +import com.badlogic.gdx.math.collision.BoundingBox; +import com.etheller.warsmash.util.*; import org.apache.commons.compress.utils.IOUtils; import org.apache.commons.compress.utils.SeekableInMemoryByteChannel; @@ -47,11 +49,6 @@ import com.etheller.warsmash.units.StandardObjectData; import com.etheller.warsmash.units.manager.MutableObjectData; import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; import com.etheller.warsmash.units.manager.MutableObjectData.WorldEditorDataType; -import com.etheller.warsmash.util.MappedData; -import com.etheller.warsmash.util.RenderMathUtils; -import com.etheller.warsmash.util.War3ID; -import com.etheller.warsmash.util.WarsmashConstants; -import com.etheller.warsmash.util.WorldEditStrings; import com.etheller.warsmash.viewer5.CanvasProvider; import com.etheller.warsmash.viewer5.GenericResource; import com.etheller.warsmash.viewer5.Grid; @@ -98,1282 +95,1349 @@ import mpq.MPQArchive; import mpq.MPQException; public class War3MapViewer extends ModelViewer { - private static final War3ID UNIT_FILE = War3ID.fromString("umdl"); - private static final War3ID UBER_SPLAT = War3ID.fromString("uubs"); - private static final War3ID UNIT_SHADOW = War3ID.fromString("ushu"); - private static final War3ID UNIT_SHADOW_X = War3ID.fromString("ushx"); - private static final War3ID UNIT_SHADOW_Y = War3ID.fromString("ushy"); - private static final War3ID UNIT_SHADOW_W = War3ID.fromString("ushw"); - private static final War3ID UNIT_SHADOW_H = War3ID.fromString("ushh"); - private static final War3ID BUILDING_SHADOW = War3ID.fromString("ushb"); - public static final War3ID UNIT_SELECT_SCALE = War3ID.fromString("ussc"); - private static final War3ID UNIT_SELECT_HEIGHT = War3ID.fromString("uslz"); - private static final War3ID UNIT_SOUNDSET = War3ID.fromString("usnd"); - private static final War3ID ITEM_FILE = War3ID.fromString("ifil"); - private static final War3ID UNIT_PATHING = War3ID.fromString("upat"); - private static final War3ID DESTRUCTABLE_PATHING = War3ID.fromString("bptx"); - private static final War3ID ELEVATION_SAMPLE_RADIUS = War3ID.fromString("uerd"); - private static final War3ID MAX_PITCH = War3ID.fromString("umxp"); - private static final War3ID MAX_ROLL = War3ID.fromString("umxr"); - private static final War3ID sloc = War3ID.fromString("sloc"); - private static final LoadGenericCallback stringDataCallback = new StringDataCallbackImplementation(); - private static final float[] rayHeap = new float[6]; - private static final Ray gdxRayHeap = new Ray(); - private static final Vector2 mousePosHeap = new Vector2(); - private static final Vector3 normalHeap = new Vector3(); - private static final Vector3 intersectionHeap = new Vector3(); - public static final StreamDataCallbackImplementation streamDataCallback = new StreamDataCallbackImplementation(); - - public PathSolver wc3PathSolver = PathSolver.DEFAULT; - public SolverParams solverParams = new SolverParams(); - public WorldScene worldScene; - public boolean anyReady; - public MappedData terrainData = new MappedData(); - public MappedData cliffTypesData = new MappedData(); - public MappedData waterData = new MappedData(); - public boolean terrainReady; - public boolean cliffsReady; - public boolean doodadsAndDestructiblesLoaded; - public MappedData doodadsData = new MappedData(); - public MappedData doodadMetaData = new MappedData(); - public MappedData destructableMetaData = new MappedData(); - public List doodads = new ArrayList<>(); - public List terrainDoodads = new ArrayList<>(); - public boolean doodadsReady; - public boolean unitsAndItemsLoaded; - public MappedData unitsData = new MappedData(); - public MappedData unitMetaData = new MappedData(); - public List units = new ArrayList<>(); - public List items = new ArrayList<>(); - public List projectiles = new ArrayList<>(); - public boolean unitsReady; - public War3Map mapMpq; - public PathSolver mapPathSolver = PathSolver.DEFAULT; - - private final DataSource gameDataSource; - - public Terrain terrain; - public int renderPathing = 0; - public int renderLighting = 1; - - public List selModels = new ArrayList<>(); - public List selected = new ArrayList<>(); - private DataTable unitAckSoundsTable; - private DataTable unitCombatSoundsTable; - public DataTable miscData; - private DataTable unitGlobalStrings; - private MdxComplexInstance confirmationInstance; - public MdxComplexInstance dncUnit; - public MdxComplexInstance dncUnitDay; - public MdxComplexInstance dncTerrain; - public MdxComplexInstance dncTarget; - public CSimulation simulation; - private float updateTime; - - // for World Editor, I think - public Vector2[] startLocations = new Vector2[WarsmashConstants.MAX_PLAYERS]; - - private final DynamicShadowManager dynamicShadowManager = new DynamicShadowManager(); - - private final Random seededRandom = new Random(1337L); - - private final Map filePathToPathingMap = new HashMap<>(); - - private final List selectionCircleSizes = new ArrayList<>(); - - private final Map unitToRenderPeer = new HashMap<>(); - private final Map unitIdToTypeData = new HashMap<>(); - private GameUI gameUI; - private Vector3 lightDirection; - - public War3MapViewer(final DataSource dataSource, final CanvasProvider canvas) { - super(dataSource, canvas); - this.gameDataSource = dataSource; - - final WebGL webGL = this.webGL; - - this.addHandler(new MdxHandler()); - - this.wc3PathSolver = PathSolver.DEFAULT; - - this.worldScene = this.addWorldScene(); - - if (!this.dynamicShadowManager.setup(webGL)) { - throw new IllegalStateException("FrameBuffer setup failed"); - } - } - - public void loadSLKs(final WorldEditStrings worldEditStrings) throws IOException { - final GenericResource terrain = this.loadMapGeneric("TerrainArt\\Terrain.slk", FetchDataTypeName.SLK, - stringDataCallback); - final GenericResource cliffTypes = this.loadMapGeneric("TerrainArt\\CliffTypes.slk", FetchDataTypeName.SLK, - stringDataCallback); - final GenericResource water = this.loadMapGeneric("TerrainArt\\Water.slk", FetchDataTypeName.SLK, - stringDataCallback); - - // == when loaded, which is always in our system == - this.terrainData.load(terrain.data.toString()); - this.cliffTypesData.load(cliffTypes.data.toString()); - this.waterData.load(water.data.toString()); - // emit terrain loaded?? - - final GenericResource doodads = this.loadMapGeneric("Doodads\\Doodads.slk", FetchDataTypeName.SLK, - stringDataCallback); - final GenericResource doodadMetaData = this.loadMapGeneric("Doodads\\DoodadMetaData.slk", FetchDataTypeName.SLK, - stringDataCallback); - final GenericResource destructableData = this.loadMapGeneric("Units\\DestructableData.slk", - FetchDataTypeName.SLK, stringDataCallback); - final GenericResource destructableMetaData = this.loadMapGeneric("Units\\DestructableMetaData.slk", - FetchDataTypeName.SLK, stringDataCallback); - - // == when loaded, which is always in our system == - this.doodadsAndDestructiblesLoaded = true; - this.doodadsData.load(doodads.data.toString()); - this.doodadMetaData.load(doodadMetaData.data.toString()); - this.doodadsData.load(destructableData.data.toString()); - this.destructableMetaData.load(destructableData.data.toString()); - // emit doodads loaded - - final GenericResource unitData = this.loadMapGeneric("Units\\UnitData.slk", FetchDataTypeName.SLK, - stringDataCallback); - final GenericResource unitUi = this.loadMapGeneric("Units\\unitUI.slk", FetchDataTypeName.SLK, - stringDataCallback); - final GenericResource itemData = this.loadMapGeneric("Units\\ItemData.slk", FetchDataTypeName.SLK, - stringDataCallback); - final GenericResource unitMetaData = this.loadMapGeneric("Units\\UnitMetaData.slk", FetchDataTypeName.SLK, - stringDataCallback); - - // == when loaded, which is always in our system == - this.unitsAndItemsLoaded = true; - this.unitsData.load(unitData.data.toString()); - this.unitsData.load(unitUi.data.toString()); - this.unitsData.load(itemData.data.toString()); - this.unitMetaData.load(unitMetaData.data.toString()); - // emit loaded - - this.unitAckSoundsTable = new DataTable(worldEditStrings); - try (InputStream terrainSlkStream = this.dataSource.getResourceAsStream("UI\\SoundInfo\\UnitAckSounds.slk")) { - this.unitAckSoundsTable.readSLK(terrainSlkStream); - } - this.unitCombatSoundsTable = new DataTable(worldEditStrings); - try (InputStream terrainSlkStream = this.dataSource - .getResourceAsStream("UI\\SoundInfo\\UnitCombatSounds.slk")) { - this.unitCombatSoundsTable.readSLK(terrainSlkStream); - } - this.miscData = new DataTable(worldEditStrings); - try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("UI\\MiscData.txt")) { - this.miscData.readTXT(miscDataTxtStream, true); - } - try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\MiscData.txt")) { - this.miscData.readTXT(miscDataTxtStream, true); - } - try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\MiscGame.txt")) { - this.miscData.readTXT(miscDataTxtStream, true); - } - try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("UI\\SoundInfo\\MiscData.txt")) { - this.miscData.readTXT(miscDataTxtStream, true); - } - if (this.dataSource.has("war3mapMisc.txt")) { - try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("war3mapMisc.txt")) { - this.miscData.readTXT(miscDataTxtStream, true); - } - } - final Element light = this.miscData.get("Light"); - final float lightX = light.getFieldFloatValue("Direction", 0); - final float lightY = light.getFieldFloatValue("Direction", 1); - final float lightZ = light.getFieldFloatValue("Direction", 2); - this.lightDirection = new Vector3(lightX, lightY, lightZ).nor(); - this.unitGlobalStrings = new DataTable(worldEditStrings); - try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\UnitGlobalStrings.txt")) { - this.unitGlobalStrings.readTXT(miscDataTxtStream, true); - } - final Element categories = this.unitGlobalStrings.get("Categories"); - for (final CUnitClassification unitClassification : CUnitClassification.values()) { - if (unitClassification.getLocaleKey() != null) { - final String displayName = categories.getField(unitClassification.getLocaleKey()); - unitClassification.setDisplayName(displayName); - } - } - this.selectionCircleSizes.clear(); - final Element selectionCircleData = this.miscData.get("SelectionCircle"); - final int selectionCircleNumSizes = selectionCircleData.getFieldValue("NumSizes"); - for (int i = 0; i < selectionCircleNumSizes; i++) { - final String indexString = i < 10 ? "0" + i : Integer.toString(i); - final float size = selectionCircleData.getFieldFloatValue("Size" + indexString); - final String texture = selectionCircleData.getField("Texture" + indexString); - final String textureDotted = selectionCircleData.getField("TextureDotted" + indexString); - this.selectionCircleSizes.add(new SelectionCircleSize(size, texture, textureDotted)); - } - this.selectionCircleScaleFactor = selectionCircleData.getFieldFloatValue("ScaleFactor"); - } - - public GenericResource loadMapGeneric(final String path, final FetchDataTypeName dataType, - final LoadGenericCallback callback) { - if (this.mapMpq == null) { - return loadGeneric(path, dataType, callback); - } - else { - return loadGeneric(path, dataType, callback, this.dataSource); - } - } - - public void loadMap(final String mapFilePath) throws IOException { - final War3Map war3Map = new War3Map(this.gameDataSource, mapFilePath); - - this.mapMpq = war3Map; - - final PathSolver wc3PathSolver = this.wc3PathSolver; - - char tileset = 'A'; - - final War3MapW3i w3iFile = this.mapMpq.readMapInformation(); - - tileset = w3iFile.getTileset(); - - DataSource tilesetSource; - try { - // Slightly complex. Here's the theory: - // 1.) Copy map into RAM - // 2.) Setup a Data Source that will read assets - // from either the map or the game, giving the map priority. - SeekableByteChannel sbc; - final CompoundDataSource compoundDataSource = war3Map.getCompoundDataSource(); - try (InputStream mapStream = compoundDataSource.getResourceAsStream(tileset + ".mpq")) { - if (mapStream == null) { - tilesetSource = new CompoundDataSource(Arrays.asList(compoundDataSource, - new SubdirDataSource(compoundDataSource, tileset + ".mpq/"), - new SubdirDataSource(compoundDataSource, "_tilesets/" + tileset + ".w3mod/"))); - } - else { - final byte[] mapData = IOUtils.toByteArray(mapStream); - sbc = new SeekableInMemoryByteChannel(mapData); - final DataSource internalMpqContentsDataSource = new MpqDataSource(new MPQArchive(sbc), sbc); - tilesetSource = new CompoundDataSource( - Arrays.asList(compoundDataSource, internalMpqContentsDataSource)); - } - } - catch (final IOException exc) { - tilesetSource = new CompoundDataSource( - Arrays.asList(compoundDataSource, new SubdirDataSource(compoundDataSource, tileset + ".mpq/"), - new SubdirDataSource(compoundDataSource, "_tilesets/" + tileset + ".w3mod/"))); - } - } - catch (final MPQException e) { - throw new RuntimeException(e); - } - setDataSource(tilesetSource); - this.worldEditStrings = new WorldEditStrings(this.dataSource); - loadSLKs(this.worldEditStrings); - - this.solverParams.tileset = Character.toLowerCase(tileset); - - final War3MapW3e terrainData = this.mapMpq.readEnvironment(); - - final War3MapWpm terrainPathing = this.mapMpq.readPathing(); - - final StandardObjectData standardObjectData = new StandardObjectData(this.dataSource); - this.worldEditData = standardObjectData.getWorldEditData(); - - this.terrain = new Terrain(terrainData, terrainPathing, w3iFile, this.webGL, this.dataSource, - this.worldEditStrings, this, this.worldEditData); - - final float[] centerOffset = terrainData.getCenterOffset(); - final int[] mapSize = terrainData.getMapSize(); - - this.terrainReady = true; - this.anyReady = true; - this.cliffsReady = true; - - // Override the grid based on the map. - this.worldScene.grid = new Grid(centerOffset[0], centerOffset[1], (mapSize[0] * 128) - 128, - (mapSize[1] * 128) - 128, 16 * 128, 16 * 128); - - final MdxModel confirmation = (MdxModel) load("UI\\Feedback\\Confirmation\\Confirmation.mdx", - PathSolver.DEFAULT, null); - this.confirmationInstance = (MdxComplexInstance) confirmation.addInstance(); - this.confirmationInstance.setSequenceLoopMode(SequenceLoopMode.NEVER_LOOP_AND_HIDE_WHEN_DONE); - this.confirmationInstance.setSequence(0); - this.confirmationInstance.setScene(this.worldScene); - - this.allObjectData = this.mapMpq.readModifications(); - this.simulation = new CSimulation(this.miscData, this.allObjectData.getUnits(), - this.allObjectData.getAbilities(), new SimulationRenderController() { - private final Map keyToCombatSound = new HashMap<>(); - - @Override - public CAttackProjectile createAttackProjectile(final CSimulation simulation, final float launchX, - final float launchY, final float launchFacing, final CUnit source, - final CUnitAttackMissile unitAttack, final CWidget target, final float damage, - final int bounceIndex) { - final War3ID typeId = source.getTypeId(); - final int projectileSpeed = unitAttack.getProjectileSpeed(); - final float projectileArc = unitAttack.getProjectileArc(); - String missileArt = unitAttack.getProjectileArt(); - final float projectileLaunchX = simulation.getUnitData().getProjectileLaunchX(typeId); - final float projectileLaunchY = simulation.getUnitData().getProjectileLaunchY(typeId); - final float projectileLaunchZ = simulation.getUnitData().getProjectileLaunchZ(typeId); - - missileArt = mdx(missileArt); - final float facing = launchFacing; - final float sinFacing = (float) Math.sin(facing); - final float cosFacing = (float) Math.cos(facing); - final float x = (launchX + (projectileLaunchY * cosFacing)) + (projectileLaunchX * sinFacing); - final float y = (launchY + (projectileLaunchY * sinFacing)) - (projectileLaunchX * cosFacing); - - final float height = War3MapViewer.this.terrain.getGroundHeight(x, y) + source.getFlyHeight() - + projectileLaunchZ; - final CAttackProjectile simulationAttackProjectile = new CAttackProjectile(x, y, - projectileSpeed, target, source, damage, unitAttack, bounceIndex); - - final MdxModel model = (MdxModel) load(missileArt, War3MapViewer.this.mapPathSolver, - War3MapViewer.this.solverParams); - final MdxComplexInstance modelInstance = (MdxComplexInstance) model.addInstance(); - modelInstance.setTeamColor(source.getPlayerIndex()); - modelInstance.setScene(War3MapViewer.this.worldScene); - if (bounceIndex == 0) { - SequenceUtils.randomBirthSequence(modelInstance); - } - else { - SequenceUtils.randomStandSequence(modelInstance); - } - modelInstance.setLocation(x, y, height); - final RenderAttackProjectile renderAttackProjectile = new RenderAttackProjectile( - simulationAttackProjectile, modelInstance, height, projectileArc, War3MapViewer.this); - - War3MapViewer.this.projectiles.add(renderAttackProjectile); - - return simulationAttackProjectile; - } - - @Override - public void createInstantAttackEffect(final CSimulation cSimulation, final CUnit source, - final CUnitAttackInstant unitAttack, final CWidget target) { - final War3ID typeId = source.getTypeId(); - - String missileArt = unitAttack.getProjectileArt(); - final float projectileLaunchX = War3MapViewer.this.simulation.getUnitData() - .getProjectileLaunchX(typeId); - final float projectileLaunchY = War3MapViewer.this.simulation.getUnitData() - .getProjectileLaunchY(typeId); - missileArt = mdx(missileArt); - final float facing = (float) Math.toRadians(source.getFacing()); - final float sinFacing = (float) Math.sin(facing); - final float cosFacing = (float) Math.cos(facing); - final float x = (source.getX() + (projectileLaunchY * cosFacing)) - + (projectileLaunchX * sinFacing); - final float y = (source.getY() + (projectileLaunchY * sinFacing)) - - (projectileLaunchX * cosFacing); - - final float targetX = target.getX(); - final float targetY = target.getY(); - final float angleToTarget = (float) Math.atan2(targetY - y, targetX - x); - - final float height = War3MapViewer.this.terrain.getGroundHeight(targetX, targetY) - + target.getFlyHeight() + target.getImpactZ(); - - final MdxModel model = (MdxModel) load(missileArt, War3MapViewer.this.mapPathSolver, - War3MapViewer.this.solverParams); - final MdxComplexInstance modelInstance = (MdxComplexInstance) model.addInstance(); - modelInstance.setTeamColor(source.getPlayerIndex()); - modelInstance.setScene(War3MapViewer.this.worldScene); - SequenceUtils.randomBirthSequence(modelInstance); - modelInstance.setLocation(targetX, targetY, height); - War3MapViewer.this.projectiles - .add(new RenderAttackInstant(modelInstance, War3MapViewer.this, angleToTarget)); - } - - @Override - public void spawnUnitDamageSound(final CUnit damagedUnit, final String weaponSound, - final String armorType) { - final String key = weaponSound + armorType; - UnitSound combatSound = this.keyToCombatSound.get(key); - if (combatSound == null) { - combatSound = UnitSound.create(War3MapViewer.this.dataSource, - War3MapViewer.this.unitCombatSoundsTable, weaponSound, armorType); - this.keyToCombatSound.put(key, combatSound); - } - combatSound.play(War3MapViewer.this.worldScene.audioContext, damagedUnit.getX(), - damagedUnit.getY()); - } - - @Override - public void removeUnit(final CUnit unit) { - final RenderUnit renderUnit = War3MapViewer.this.unitToRenderPeer.remove(unit); - War3MapViewer.this.units.remove(renderUnit); - War3MapViewer.this.worldScene.removeInstance(renderUnit.instance); - } - }, this.terrain.pathingGrid, - new Rectangle(centerOffset[0], centerOffset[1], (mapSize[0] * 128f) - 128, (mapSize[1] * 128f) - 128), - this.seededRandom, w3iFile.getPlayers()); - - if (this.doodadsAndDestructiblesLoaded) { - this.loadDoodadsAndDestructibles(this.allObjectData); - } - else { - throw new IllegalStateException("transcription of JS has not loaded a map and has no JS async promises"); - } - - this.terrain.createWaves(); - - loadLightsAndShading(tileset); - } - - /** - * Loads the map information that should be loaded after UI, such as units, who - * need to be able to setup their UI counterparts (icons, etc) for their - * abilities while loading. This allows the dynamic creation of units while the - * game is playing to better share code with the startup sequence's creation of - * units. - * - * @throws IOException - */ - public void loadAfterUI() throws IOException { - if (this.unitsAndItemsLoaded) { - this.loadUnitsAndItems(this.allObjectData); - } - else { - throw new IllegalStateException("transcription of JS has not loaded a map and has no JS async promises"); - } - - // After we finish loading units, we need to update & create the stored shadow - // information for all unit shadows - this.terrain.initShadows(); - } - - private void loadLightsAndShading(final char tileset) { - // TODO this should be set by the war3map.j actually, not by the tileset, so the - // call to set day night models is just for testing to make the test look pretty - final Element defaultTerrainLights = this.worldEditData.get("TerrainLights"); - final Element defaultUnitLights = this.worldEditData.get("UnitLights"); - setDayNightModels(defaultTerrainLights.getField(Character.toString(tileset)), - defaultUnitLights.getField(Character.toString(tileset))); - - } - - private void loadDoodadsAndDestructibles(final Warcraft3MapObjectData modifications) throws IOException { - this.applyModificationFile(this.doodadsData, this.doodadMetaData, modifications.getDoodads(), - WorldEditorDataType.DOODADS); - this.applyModificationFile(this.doodadsData, this.destructableMetaData, modifications.getDestructibles(), - WorldEditorDataType.DESTRUCTIBLES); - - final War3MapDoo doo = this.mapMpq.readDoodads(); - - for (final com.etheller.warsmash.parsers.w3x.doo.Doodad doodad : doo.getDoodads()) { - WorldEditorDataType type = WorldEditorDataType.DOODADS; - MutableGameObject row = modifications.getDoodads().get(doodad.getId()); - if (row == null) { - row = modifications.getDestructibles().get(doodad.getId()); - type = WorldEditorDataType.DESTRUCTIBLES; - } - if (row != null) { - String file = row.readSLKTag("file"); - final int numVar = row.readSLKTagInt("numVar"); - - if (file.endsWith(".mdx") || file.endsWith(".mdl")) { - file = file.substring(0, file.length() - 4); - } - - String fileVar = file; - - file += ".mdx"; - - if (numVar > 1) { - fileVar += Math.min(doodad.getVariation(), numVar - 1); - } - - fileVar += ".mdx"; - - final float maxPitch = row.readSLKTagFloat("maxPitch"); - final float maxRoll = row.readSLKTagFloat("maxRoll"); - if (type == WorldEditorDataType.DESTRUCTIBLES) { - final String shadowString = row.readSLKTag("shadow"); - if ((shadowString != null) && (shadowString.length() > 0) && !"_".equals(shadowString)) { - this.terrain.addShadow(shadowString, doodad.getLocation()[0], doodad.getLocation()[1]); - } - - final String pathingTexture = row.readSLKTag("pathTex"); - if ((pathingTexture != null) && (pathingTexture.length() > 0) && !"_".equals(pathingTexture)) { - - BufferedImage bufferedImage = this.filePathToPathingMap.get(pathingTexture.toLowerCase()); - if (bufferedImage == null) { - if (this.mapMpq.has(pathingTexture)) { - try { - bufferedImage = TgaFile.readTGA(pathingTexture, - this.mapMpq.getResourceAsStream(pathingTexture)); - this.filePathToPathingMap.put(pathingTexture.toLowerCase(), bufferedImage); - } - catch (final Exception exc) { - exc.printStackTrace(); - } - } - } - if (bufferedImage != null) { - this.terrain.pathingGrid.blitPathingOverlayTexture(doodad.getLocation()[0], - doodad.getLocation()[1], (int) Math.toDegrees(doodad.getAngle()), bufferedImage); - } - } - } - // First see if the model is local. - // Doodads referring to local models may have invalid variations, so if the - // variation doesn't exist, try without a variation. - - String path; - if (this.mapMpq.has(fileVar)) { - path = fileVar; - } - else { - path = file; - } - MdxModel model; - if (this.mapMpq.has(path)) { - model = (MdxModel) this.load(path, this.mapPathSolver, this.solverParams); - } - else { - model = (MdxModel) this.load(fileVar, this.mapPathSolver, this.solverParams); - } - - if (type == WorldEditorDataType.DESTRUCTIBLES) { - this.doodads.add(new RenderDestructable(this, model, row, doodad, type, maxPitch, maxRoll, - doodad.getLife())); - } - else { - this.doodads.add(new RenderDoodad(this, model, row, doodad, type, maxPitch, maxRoll)); - } - } - } - - // Cliff/Terrain doodads. - for (final com.etheller.warsmash.parsers.w3x.doo.TerrainDoodad doodad : doo.getTerrainDoodads()) { - final MutableGameObject row = modifications.getDoodads().get(doodad.getId()); - String file = row.readSLKTag("file");// - if ("".equals(file)) { - final String blaBla = row.readSLKTag("file"); - System.out.println("bla"); - } - if (file.toLowerCase().endsWith(".mdl")) { - file = file.substring(0, file.length() - 4); - } - if (!file.toLowerCase().endsWith(".mdx")) { - file += ".mdx"; - } - final MdxModel model = (MdxModel) this.load(file, this.mapPathSolver, this.solverParams); - - final String pathingTexture = row.readSLKTag("pathTex"); - BufferedImage pathingTextureImage; - if ((pathingTexture != null) && (pathingTexture.length() > 0) && !"_".equals(pathingTexture)) { - - pathingTextureImage = this.filePathToPathingMap.get(pathingTexture.toLowerCase()); - if (pathingTextureImage == null) { - if (this.mapMpq.has(pathingTexture)) { - try { - pathingTextureImage = TgaFile.readTGA(pathingTexture, - this.mapMpq.getResourceAsStream(pathingTexture)); - this.filePathToPathingMap.put(pathingTexture.toLowerCase(), pathingTextureImage); - } - catch (final Exception exc) { - exc.printStackTrace(); - } - } - } - } - else { - pathingTextureImage = null; - } - if (pathingTextureImage != null) { - // blit out terrain cells under this TerrainDoodad - final int textureWidth = pathingTextureImage.getWidth(); - final int textureHeight = pathingTextureImage.getHeight(); - final int textureWidthTerrainCells = textureWidth / 4; - final int textureHeightTerrainCells = textureHeight / 4; - final int minCellX = ((int) doodad.getLocation()[0]); - final int minCellY = ((int) doodad.getLocation()[1]); - final int maxCellX = (minCellX + textureWidthTerrainCells) - 1; - final int maxCellY = (minCellY + textureHeightTerrainCells) - 1; - for (int j = minCellY; j <= maxCellY; j++) { - for (int i = minCellX; i <= maxCellX; i++) { - this.terrain.removeTerrainCellWithoutFlush(i, j); - } - } - this.terrain.flushRemovedTerrainCells(); - } - - System.out.println("Loading terrain doodad: " + file); - this.terrainDoodads.add(new TerrainDoodad(this, model, row, doodad, pathingTextureImage)); - } - - this.doodadsReady = true; - this.anyReady = true; - } - - private void applyModificationFile(final MappedData doodadsData2, final MappedData doodadMetaData2, - final MutableObjectData destructibles, final WorldEditorDataType dataType) { - // TODO condense ported MappedData from Ghostwolf and MutableObjectData from - // Retera - - } - - private void loadUnitsAndItems(final Warcraft3MapObjectData modifications) throws IOException { - final War3Map mpq = this.mapMpq; - - if (this.dataSource.has("war3mapUnits.doo")) { - final War3MapUnitsDoo dooFile = mpq.readUnits(); - - final Map soundsetNameToSoundset = new HashMap<>(); - - // Collect the units and items data. - UnitSoundset soundset = null; - for (final com.etheller.warsmash.parsers.w3x.unitsdoo.Unit unit : dooFile.getUnits()) { - MutableGameObject row = null; - String path = null; - Splat unitShadowSplat = null; - BufferedImage buildingPathingPixelMap = null; - - // Hardcoded? - WorldEditorDataType type = null; - if (sloc.equals(unit.getId())) { + private static final War3ID UNIT_FILE = War3ID.fromString("umdl"); + private static final War3ID UBER_SPLAT = War3ID.fromString("uubs"); + private static final War3ID UNIT_SHADOW = War3ID.fromString("ushu"); + private static final War3ID UNIT_SHADOW_X = War3ID.fromString("ushx"); + private static final War3ID UNIT_SHADOW_Y = War3ID.fromString("ushy"); + private static final War3ID UNIT_SHADOW_W = War3ID.fromString("ushw"); + private static final War3ID UNIT_SHADOW_H = War3ID.fromString("ushh"); + private static final War3ID BUILDING_SHADOW = War3ID.fromString("ushb"); + public static final War3ID UNIT_SELECT_SCALE = War3ID.fromString("ussc"); + private static final War3ID UNIT_SELECT_HEIGHT = War3ID.fromString("uslz"); + private static final War3ID UNIT_SOUNDSET = War3ID.fromString("usnd"); + private static final War3ID ITEM_FILE = War3ID.fromString("ifil"); + private static final War3ID UNIT_PATHING = War3ID.fromString("upat"); + private static final War3ID DESTRUCTABLE_PATHING = War3ID.fromString("bptx"); + private static final War3ID ELEVATION_SAMPLE_RADIUS = War3ID.fromString("uerd"); + private static final War3ID MAX_PITCH = War3ID.fromString("umxp"); + private static final War3ID MAX_ROLL = War3ID.fromString("umxr"); + private static final War3ID sloc = War3ID.fromString("sloc"); + private static final LoadGenericCallback stringDataCallback = new StringDataCallbackImplementation(); + private static final float[] rayHeap = new float[6]; + public static final Ray gdxRayHeap = new Ray(); + private static final Vector2 mousePosHeap = new Vector2(); + private static final Vector3 normalHeap = new Vector3(); + public static final Vector3 intersectionHeap = new Vector3(); + private static final Rectangle rectangleHeap = new Rectangle(); + public static final StreamDataCallbackImplementation streamDataCallback = new StreamDataCallbackImplementation(); + + public PathSolver wc3PathSolver = PathSolver.DEFAULT; + public SolverParams solverParams = new SolverParams(); + public WorldScene worldScene; + public boolean anyReady; + public MappedData terrainData = new MappedData(); + public MappedData cliffTypesData = new MappedData(); + public MappedData waterData = new MappedData(); + public boolean terrainReady; + public boolean cliffsReady; + public boolean doodadsAndDestructiblesLoaded; + public MappedData doodadsData = new MappedData(); + public MappedData doodadMetaData = new MappedData(); + public MappedData destructableMetaData = new MappedData(); + public List doodads = new ArrayList<>(); + public List terrainDoodads = new ArrayList<>(); + public boolean doodadsReady; + public boolean unitsAndItemsLoaded; + public MappedData unitsData = new MappedData(); + public MappedData unitMetaData = new MappedData(); + public List units = new ArrayList<>(); + public List items = new ArrayList<>(); + public List projectiles = new ArrayList<>(); + public boolean unitsReady; + public War3Map mapMpq; + public PathSolver mapPathSolver = PathSolver.DEFAULT; + + private final DataSource gameDataSource; + + public Terrain terrain; + public int renderPathing = 0; + public int renderLighting = 1; + + public List selModels = new ArrayList<>(); + public List selected = new ArrayList<>(); + private DataTable unitAckSoundsTable; + private DataTable unitCombatSoundsTable; + public DataTable miscData; + private DataTable unitGlobalStrings; + private MdxComplexInstance confirmationInstance; + public MdxComplexInstance dncUnit; + public MdxComplexInstance dncUnitDay; + public MdxComplexInstance dncTerrain; + public MdxComplexInstance dncTarget; + public CSimulation simulation; + private float updateTime; + + // for World Editor, I think + public Vector2[] startLocations = new Vector2[WarsmashConstants.MAX_PLAYERS]; + + private final DynamicShadowManager dynamicShadowManager = new DynamicShadowManager(); + + private final Random seededRandom = new Random(1337L); + + private final Map filePathToPathingMap = new HashMap<>(); + + private final List selectionCircleSizes = new ArrayList<>(); + + private final Map unitToRenderPeer = new HashMap<>(); + private final Map unitIdToTypeData = new HashMap<>(); + private GameUI gameUI; + private Vector3 lightDirection; + + private Quadtree walkableObjectsTree; + private QuadtreeIntersectorFindsWalkableRenderHeight walkablesIntersector = new QuadtreeIntersectorFindsWalkableRenderHeight(); + private QuadtreeIntersectorFindsHitPoint walkablesIntersectionFinder = new QuadtreeIntersectorFindsHitPoint(); + private QuadtreeIntersectorFindsHighestWalkable intersectorFindsHighestWalkable = new QuadtreeIntersectorFindsHighestWalkable(); + + public War3MapViewer(final DataSource dataSource, final CanvasProvider canvas) { + super(dataSource, canvas); + this.gameDataSource = dataSource; + + final WebGL webGL = this.webGL; + + this.addHandler(new MdxHandler()); + + this.wc3PathSolver = PathSolver.DEFAULT; + + this.worldScene = this.addWorldScene(); + + if (!this.dynamicShadowManager.setup(webGL)) { + throw new IllegalStateException("FrameBuffer setup failed"); + } + } + + public void loadSLKs(final WorldEditStrings worldEditStrings) throws IOException { + final GenericResource terrain = this.loadMapGeneric("TerrainArt\\Terrain.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource cliffTypes = this.loadMapGeneric("TerrainArt\\CliffTypes.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource water = this.loadMapGeneric("TerrainArt\\Water.slk", FetchDataTypeName.SLK, + stringDataCallback); + + // == when loaded, which is always in our system == + this.terrainData.load(terrain.data.toString()); + this.cliffTypesData.load(cliffTypes.data.toString()); + this.waterData.load(water.data.toString()); + // emit terrain loaded?? + + final GenericResource doodads = this.loadMapGeneric("Doodads\\Doodads.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource doodadMetaData = this.loadMapGeneric("Doodads\\DoodadMetaData.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource destructableData = this.loadMapGeneric("Units\\DestructableData.slk", + FetchDataTypeName.SLK, stringDataCallback); + final GenericResource destructableMetaData = this.loadMapGeneric("Units\\DestructableMetaData.slk", + FetchDataTypeName.SLK, stringDataCallback); + + // == when loaded, which is always in our system == + this.doodadsAndDestructiblesLoaded = true; + this.doodadsData.load(doodads.data.toString()); + this.doodadMetaData.load(doodadMetaData.data.toString()); + this.doodadsData.load(destructableData.data.toString()); + this.destructableMetaData.load(destructableData.data.toString()); + // emit doodads loaded + + final GenericResource unitData = this.loadMapGeneric("Units\\UnitData.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource unitUi = this.loadMapGeneric("Units\\unitUI.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource itemData = this.loadMapGeneric("Units\\ItemData.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource unitMetaData = this.loadMapGeneric("Units\\UnitMetaData.slk", FetchDataTypeName.SLK, + stringDataCallback); + + // == when loaded, which is always in our system == + this.unitsAndItemsLoaded = true; + this.unitsData.load(unitData.data.toString()); + this.unitsData.load(unitUi.data.toString()); + this.unitsData.load(itemData.data.toString()); + this.unitMetaData.load(unitMetaData.data.toString()); + // emit loaded + + this.unitAckSoundsTable = new DataTable(worldEditStrings); + try (InputStream terrainSlkStream = this.dataSource.getResourceAsStream("UI\\SoundInfo\\UnitAckSounds.slk")) { + this.unitAckSoundsTable.readSLK(terrainSlkStream); + } + this.unitCombatSoundsTable = new DataTable(worldEditStrings); + try (InputStream terrainSlkStream = this.dataSource + .getResourceAsStream("UI\\SoundInfo\\UnitCombatSounds.slk")) { + this.unitCombatSoundsTable.readSLK(terrainSlkStream); + } + this.miscData = new DataTable(worldEditStrings); + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("UI\\MiscData.txt")) { + this.miscData.readTXT(miscDataTxtStream, true); + } + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\MiscData.txt")) { + this.miscData.readTXT(miscDataTxtStream, true); + } + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\MiscGame.txt")) { + this.miscData.readTXT(miscDataTxtStream, true); + } + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("UI\\SoundInfo\\MiscData.txt")) { + this.miscData.readTXT(miscDataTxtStream, true); + } + if (this.dataSource.has("war3mapMisc.txt")) { + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("war3mapMisc.txt")) { + this.miscData.readTXT(miscDataTxtStream, true); + } + } + final Element light = this.miscData.get("Light"); + final float lightX = light.getFieldFloatValue("Direction", 0); + final float lightY = light.getFieldFloatValue("Direction", 1); + final float lightZ = light.getFieldFloatValue("Direction", 2); + this.lightDirection = new Vector3(lightX, lightY, lightZ).nor(); + this.unitGlobalStrings = new DataTable(worldEditStrings); + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\UnitGlobalStrings.txt")) { + this.unitGlobalStrings.readTXT(miscDataTxtStream, true); + } + final Element categories = this.unitGlobalStrings.get("Categories"); + for (final CUnitClassification unitClassification : CUnitClassification.values()) { + if (unitClassification.getLocaleKey() != null) { + final String displayName = categories.getField(unitClassification.getLocaleKey()); + unitClassification.setDisplayName(displayName); + } + } + this.selectionCircleSizes.clear(); + final Element selectionCircleData = this.miscData.get("SelectionCircle"); + final int selectionCircleNumSizes = selectionCircleData.getFieldValue("NumSizes"); + for (int i = 0; i < selectionCircleNumSizes; i++) { + final String indexString = i < 10 ? "0" + i : Integer.toString(i); + final float size = selectionCircleData.getFieldFloatValue("Size" + indexString); + final String texture = selectionCircleData.getField("Texture" + indexString); + final String textureDotted = selectionCircleData.getField("TextureDotted" + indexString); + this.selectionCircleSizes.add(new SelectionCircleSize(size, texture, textureDotted)); + } + this.selectionCircleScaleFactor = selectionCircleData.getFieldFloatValue("ScaleFactor"); + } + + public GenericResource loadMapGeneric(final String path, final FetchDataTypeName dataType, + final LoadGenericCallback callback) { + if (this.mapMpq == null) { + return loadGeneric(path, dataType, callback); + } else { + return loadGeneric(path, dataType, callback, this.dataSource); + } + } + + public void loadMap(final String mapFilePath) throws IOException { + final War3Map war3Map = new War3Map(this.gameDataSource, mapFilePath); + + this.mapMpq = war3Map; + + final PathSolver wc3PathSolver = this.wc3PathSolver; + + char tileset = 'A'; + + final War3MapW3i w3iFile = this.mapMpq.readMapInformation(); + + tileset = w3iFile.getTileset(); + + DataSource tilesetSource; + try { + // Slightly complex. Here's the theory: + // 1.) Copy map into RAM + // 2.) Setup a Data Source that will read assets + // from either the map or the game, giving the map priority. + SeekableByteChannel sbc; + final CompoundDataSource compoundDataSource = war3Map.getCompoundDataSource(); + try (InputStream mapStream = compoundDataSource.getResourceAsStream(tileset + ".mpq")) { + if (mapStream == null) { + tilesetSource = new CompoundDataSource(Arrays.asList(compoundDataSource, + new SubdirDataSource(compoundDataSource, tileset + ".mpq/"), + new SubdirDataSource(compoundDataSource, "_tilesets/" + tileset + ".w3mod/"))); + } else { + final byte[] mapData = IOUtils.toByteArray(mapStream); + sbc = new SeekableInMemoryByteChannel(mapData); + final DataSource internalMpqContentsDataSource = new MpqDataSource(new MPQArchive(sbc), sbc); + tilesetSource = new CompoundDataSource( + Arrays.asList(compoundDataSource, internalMpqContentsDataSource)); + } + } catch (final IOException exc) { + tilesetSource = new CompoundDataSource( + Arrays.asList(compoundDataSource, new SubdirDataSource(compoundDataSource, tileset + ".mpq/"), + new SubdirDataSource(compoundDataSource, "_tilesets/" + tileset + ".w3mod/"))); + } + } catch (final MPQException e) { + throw new RuntimeException(e); + } + setDataSource(tilesetSource); + this.worldEditStrings = new WorldEditStrings(this.dataSource); + loadSLKs(this.worldEditStrings); + + this.solverParams.tileset = Character.toLowerCase(tileset); + + final War3MapW3e terrainData = this.mapMpq.readEnvironment(); + + final War3MapWpm terrainPathing = this.mapMpq.readPathing(); + + final StandardObjectData standardObjectData = new StandardObjectData(this.dataSource); + this.worldEditData = standardObjectData.getWorldEditData(); + + this.terrain = new Terrain(terrainData, terrainPathing, w3iFile, this.webGL, this.dataSource, + this.worldEditStrings, this, this.worldEditData); + + final float[] centerOffset = terrainData.getCenterOffset(); + final int[] mapSize = terrainData.getMapSize(); + + this.terrainReady = true; + this.anyReady = true; + this.cliffsReady = true; + + // Override the grid based on the map. + this.worldScene.grid = new Grid(centerOffset[0], centerOffset[1], (mapSize[0] * 128) - 128, + (mapSize[1] * 128) - 128, 16 * 128, 16 * 128); + + final MdxModel confirmation = (MdxModel) load("UI\\Feedback\\Confirmation\\Confirmation.mdx", + PathSolver.DEFAULT, null); + this.confirmationInstance = (MdxComplexInstance) confirmation.addInstance(); + this.confirmationInstance.setSequenceLoopMode(SequenceLoopMode.NEVER_LOOP_AND_HIDE_WHEN_DONE); + this.confirmationInstance.setSequence(0); + this.confirmationInstance.setScene(this.worldScene); + + this.allObjectData = this.mapMpq.readModifications(); + this.simulation = new CSimulation(this.miscData, this.allObjectData.getUnits(), + this.allObjectData.getAbilities(), new SimulationRenderController() { + private final Map keyToCombatSound = new HashMap<>(); + + @Override + public CAttackProjectile createAttackProjectile(final CSimulation simulation, final float launchX, + final float launchY, final float launchFacing, final CUnit source, + final CUnitAttackMissile unitAttack, final CWidget target, final float damage, + final int bounceIndex) { + final War3ID typeId = source.getTypeId(); + final int projectileSpeed = unitAttack.getProjectileSpeed(); + final float projectileArc = unitAttack.getProjectileArc(); + String missileArt = unitAttack.getProjectileArt(); + final float projectileLaunchX = simulation.getUnitData().getProjectileLaunchX(typeId); + final float projectileLaunchY = simulation.getUnitData().getProjectileLaunchY(typeId); + final float projectileLaunchZ = simulation.getUnitData().getProjectileLaunchZ(typeId); + + missileArt = mdx(missileArt); + final float facing = launchFacing; + final float sinFacing = (float) Math.sin(facing); + final float cosFacing = (float) Math.cos(facing); + final float x = (launchX + (projectileLaunchY * cosFacing)) + (projectileLaunchX * sinFacing); + final float y = (launchY + (projectileLaunchY * sinFacing)) - (projectileLaunchX * cosFacing); + + final float height = War3MapViewer.this.terrain.getGroundHeight(x, y) + source.getFlyHeight() + + projectileLaunchZ; + final CAttackProjectile simulationAttackProjectile = new CAttackProjectile(x, y, + projectileSpeed, target, source, damage, unitAttack, bounceIndex); + + final MdxModel model = (MdxModel) load(missileArt, War3MapViewer.this.mapPathSolver, + War3MapViewer.this.solverParams); + final MdxComplexInstance modelInstance = (MdxComplexInstance) model.addInstance(); + modelInstance.setTeamColor(source.getPlayerIndex()); + modelInstance.setScene(War3MapViewer.this.worldScene); + if (bounceIndex == 0) { + SequenceUtils.randomBirthSequence(modelInstance); + } else { + SequenceUtils.randomStandSequence(modelInstance); + } + modelInstance.setLocation(x, y, height); + final RenderAttackProjectile renderAttackProjectile = new RenderAttackProjectile( + simulationAttackProjectile, modelInstance, height, projectileArc, War3MapViewer.this); + + War3MapViewer.this.projectiles.add(renderAttackProjectile); + + return simulationAttackProjectile; + } + + @Override + public void createInstantAttackEffect(final CSimulation cSimulation, final CUnit source, + final CUnitAttackInstant unitAttack, final CWidget target) { + final War3ID typeId = source.getTypeId(); + + String missileArt = unitAttack.getProjectileArt(); + final float projectileLaunchX = War3MapViewer.this.simulation.getUnitData() + .getProjectileLaunchX(typeId); + final float projectileLaunchY = War3MapViewer.this.simulation.getUnitData() + .getProjectileLaunchY(typeId); + missileArt = mdx(missileArt); + final float facing = (float) Math.toRadians(source.getFacing()); + final float sinFacing = (float) Math.sin(facing); + final float cosFacing = (float) Math.cos(facing); + final float x = (source.getX() + (projectileLaunchY * cosFacing)) + + (projectileLaunchX * sinFacing); + final float y = (source.getY() + (projectileLaunchY * sinFacing)) + - (projectileLaunchX * cosFacing); + + final float targetX = target.getX(); + final float targetY = target.getY(); + final float angleToTarget = (float) Math.atan2(targetY - y, targetX - x); + + final float height = War3MapViewer.this.terrain.getGroundHeight(targetX, targetY) + + target.getFlyHeight() + target.getImpactZ(); + + final MdxModel model = (MdxModel) load(missileArt, War3MapViewer.this.mapPathSolver, + War3MapViewer.this.solverParams); + final MdxComplexInstance modelInstance = (MdxComplexInstance) model.addInstance(); + modelInstance.setTeamColor(source.getPlayerIndex()); + modelInstance.setScene(War3MapViewer.this.worldScene); + SequenceUtils.randomBirthSequence(modelInstance); + modelInstance.setLocation(targetX, targetY, height); + War3MapViewer.this.projectiles + .add(new RenderAttackInstant(modelInstance, War3MapViewer.this, angleToTarget)); + } + + @Override + public void spawnUnitDamageSound(final CUnit damagedUnit, final String weaponSound, + final String armorType) { + final String key = weaponSound + armorType; + UnitSound combatSound = this.keyToCombatSound.get(key); + if (combatSound == null) { + combatSound = UnitSound.create(War3MapViewer.this.dataSource, + War3MapViewer.this.unitCombatSoundsTable, weaponSound, armorType); + this.keyToCombatSound.put(key, combatSound); + } + combatSound.play(War3MapViewer.this.worldScene.audioContext, damagedUnit.getX(), + damagedUnit.getY()); + } + + @Override + public void removeUnit(final CUnit unit) { + final RenderUnit renderUnit = War3MapViewer.this.unitToRenderPeer.remove(unit); + War3MapViewer.this.units.remove(renderUnit); + War3MapViewer.this.worldScene.removeInstance(renderUnit.instance); + } + }, this.terrain.pathingGrid, + terrain.getEntireMap(), + this.seededRandom, w3iFile.getPlayers()); + + walkableObjectsTree = new Quadtree<>(terrain.getEntireMap()); + if (this.doodadsAndDestructiblesLoaded) { + this.loadDoodadsAndDestructibles(this.allObjectData); + } else { + throw new IllegalStateException("transcription of JS has not loaded a map and has no JS async promises"); + } + + this.terrain.createWaves(); + + loadLightsAndShading(tileset); + } + + /** + * Loads the map information that should be loaded after UI, such as units, who + * need to be able to setup their UI counterparts (icons, etc) for their + * abilities while loading. This allows the dynamic creation of units while the + * game is playing to better share code with the startup sequence's creation of + * units. + * + * @throws IOException + */ + public void loadAfterUI() throws IOException { + if (this.unitsAndItemsLoaded) { + this.loadUnitsAndItems(this.allObjectData); + } else { + throw new IllegalStateException("transcription of JS has not loaded a map and has no JS async promises"); + } + + // After we finish loading units, we need to update & create the stored shadow + // information for all unit shadows + this.terrain.initShadows(); + } + + private void loadLightsAndShading(final char tileset) { + // TODO this should be set by the war3map.j actually, not by the tileset, so the + // call to set day night models is just for testing to make the test look pretty + final Element defaultTerrainLights = this.worldEditData.get("TerrainLights"); + final Element defaultUnitLights = this.worldEditData.get("UnitLights"); + setDayNightModels(defaultTerrainLights.getField(Character.toString(tileset)), + defaultUnitLights.getField(Character.toString(tileset))); + + } + + private void loadDoodadsAndDestructibles(final Warcraft3MapObjectData modifications) throws IOException { + this.applyModificationFile(this.doodadsData, this.doodadMetaData, modifications.getDoodads(), + WorldEditorDataType.DOODADS); + this.applyModificationFile(this.doodadsData, this.destructableMetaData, modifications.getDestructibles(), + WorldEditorDataType.DESTRUCTIBLES); + + final War3MapDoo doo = this.mapMpq.readDoodads(); + + for (final com.etheller.warsmash.parsers.w3x.doo.Doodad doodad : doo.getDoodads()) { + WorldEditorDataType type = WorldEditorDataType.DOODADS; + MutableGameObject row = modifications.getDoodads().get(doodad.getId()); + if (row == null) { + row = modifications.getDestructibles().get(doodad.getId()); + type = WorldEditorDataType.DESTRUCTIBLES; + } + if (row != null) { + String file = row.readSLKTag("file"); + final int numVar = row.readSLKTagInt("numVar"); + + if (file.endsWith(".mdx") || file.endsWith(".mdl")) { + file = file.substring(0, file.length() - 4); + } + + String fileVar = file; + + file += ".mdx"; + + if (numVar > 1) { + fileVar += Math.min(doodad.getVariation(), numVar - 1); + } + + fileVar += ".mdx"; + + final float maxPitch = row.readSLKTagFloat("maxPitch"); + final float maxRoll = row.readSLKTagFloat("maxRoll"); + if (type == WorldEditorDataType.DESTRUCTIBLES) { + final String shadowString = row.readSLKTag("shadow"); + if ((shadowString != null) && (shadowString.length() > 0) && !"_".equals(shadowString)) { + this.terrain.addShadow(shadowString, doodad.getLocation()[0], doodad.getLocation()[1]); + } + + final String pathingTexture = row.readSLKTag("pathTex"); + if ((pathingTexture != null) && (pathingTexture.length() > 0) && !"_".equals(pathingTexture)) { + + BufferedImage bufferedImage = this.filePathToPathingMap.get(pathingTexture.toLowerCase()); + if (bufferedImage == null) { + if (this.mapMpq.has(pathingTexture)) { + try { + bufferedImage = TgaFile.readTGA(pathingTexture, + this.mapMpq.getResourceAsStream(pathingTexture)); + this.filePathToPathingMap.put(pathingTexture.toLowerCase(), bufferedImage); + } catch (final Exception exc) { + exc.printStackTrace(); + } + } + } + if (bufferedImage != null) { + this.terrain.pathingGrid.blitPathingOverlayTexture(doodad.getLocation()[0], + doodad.getLocation()[1], (int) Math.toDegrees(doodad.getAngle()), bufferedImage); + } + } + } + // First see if the model is local. + // Doodads referring to local models may have invalid variations, so if the + // variation doesn't exist, try without a variation. + + String path; + if (this.mapMpq.has(fileVar)) { + path = fileVar; + } else { + path = file; + } + MdxModel model; + if (this.mapMpq.has(path)) { + model = (MdxModel) this.load(path, this.mapPathSolver, this.solverParams); + } else { + model = (MdxModel) this.load(fileVar, this.mapPathSolver, this.solverParams); + } + + if (type == WorldEditorDataType.DESTRUCTIBLES) { + RenderDestructable renderDestructable = new RenderDestructable(this, model, row, doodad, type, maxPitch, maxRoll, + doodad.getLife()); + if (row.readSLKTagBoolean("walkable")) { + float x = doodad.getLocation()[0]; + float y = doodad.getLocation()[1]; + BoundingBox boundingBox = model.bounds.getBoundingBox(); + float minX = boundingBox.min.x + x; + float minY = boundingBox.min.y + y; + Rectangle renderDestructableBounds = new Rectangle(minX, minY, boundingBox.getWidth(), boundingBox.getHeight()); + walkableObjectsTree.add((MdxComplexInstance) renderDestructable.instance, renderDestructableBounds); + } + this.doodads.add(renderDestructable); + } else { + this.doodads.add(new RenderDoodad(this, model, row, doodad, type, maxPitch, maxRoll)); + } + } + } + + // Cliff/Terrain doodads. + for (final com.etheller.warsmash.parsers.w3x.doo.TerrainDoodad doodad : doo.getTerrainDoodads()) { + final MutableGameObject row = modifications.getDoodads().get(doodad.getId()); + String file = row.readSLKTag("file");// + if ("".equals(file)) { + final String blaBla = row.readSLKTag("file"); + System.out.println("bla"); + } + if (file.toLowerCase().endsWith(".mdl")) { + file = file.substring(0, file.length() - 4); + } + if (!file.toLowerCase().endsWith(".mdx")) { + file += ".mdx"; + } + final MdxModel model = (MdxModel) this.load(file, this.mapPathSolver, this.solverParams); + + final String pathingTexture = row.readSLKTag("pathTex"); + BufferedImage pathingTextureImage; + if ((pathingTexture != null) && (pathingTexture.length() > 0) && !"_".equals(pathingTexture)) { + + pathingTextureImage = this.filePathToPathingMap.get(pathingTexture.toLowerCase()); + if (pathingTextureImage == null) { + if (this.mapMpq.has(pathingTexture)) { + try { + pathingTextureImage = TgaFile.readTGA(pathingTexture, + this.mapMpq.getResourceAsStream(pathingTexture)); + this.filePathToPathingMap.put(pathingTexture.toLowerCase(), pathingTextureImage); + } catch (final Exception exc) { + exc.printStackTrace(); + } + } + } + } else { + pathingTextureImage = null; + } + if (pathingTextureImage != null) { + // blit out terrain cells under this TerrainDoodad + final int textureWidth = pathingTextureImage.getWidth(); + final int textureHeight = pathingTextureImage.getHeight(); + final int textureWidthTerrainCells = textureWidth / 4; + final int textureHeightTerrainCells = textureHeight / 4; + final int minCellX = ((int) doodad.getLocation()[0]); + final int minCellY = ((int) doodad.getLocation()[1]); + final int maxCellX = (minCellX + textureWidthTerrainCells) - 1; + final int maxCellY = (minCellY + textureHeightTerrainCells) - 1; + for (int j = minCellY; j <= maxCellY; j++) { + for (int i = minCellX; i <= maxCellX; i++) { + this.terrain.removeTerrainCellWithoutFlush(i, j); + } + } + this.terrain.flushRemovedTerrainCells(); + } + + System.out.println("Loading terrain doodad: " + file); + this.terrainDoodads.add(new TerrainDoodad(this, model, row, doodad, pathingTextureImage)); + } + + this.doodadsReady = true; + this.anyReady = true; + } + + private void applyModificationFile(final MappedData doodadsData2, final MappedData doodadMetaData2, + final MutableObjectData destructibles, final WorldEditorDataType dataType) { + // TODO condense ported MappedData from Ghostwolf and MutableObjectData from + // Retera + + } + + private void loadUnitsAndItems(final Warcraft3MapObjectData modifications) throws IOException { + final War3Map mpq = this.mapMpq; + + if (this.dataSource.has("war3mapUnits.doo")) { + final War3MapUnitsDoo dooFile = mpq.readUnits(); + + final Map soundsetNameToSoundset = new HashMap<>(); + + // Collect the units and items data. + UnitSoundset soundset = null; + for (final com.etheller.warsmash.parsers.w3x.unitsdoo.Unit unit : dooFile.getUnits()) { + MutableGameObject row = null; + String path = null; + Splat unitShadowSplat = null; + BufferedImage buildingPathingPixelMap = null; + + // Hardcoded? + WorldEditorDataType type = null; + if (sloc.equals(unit.getId())) { // path = "Objects\\StartLocation\\StartLocation.mdx"; - type = null; /// ?????? - this.startLocations[unit.getPlayer()] = new Vector2(unit.getLocation()[0], unit.getLocation()[1]); - } - else { - row = modifications.getUnits().get(unit.getId()); - if (row == null) { - row = modifications.getItems().get(unit.getId()); - if (row != null) { - type = WorldEditorDataType.ITEM; - path = row.getFieldAsString(ITEM_FILE, 0); + type = null; /// ?????? + this.startLocations[unit.getPlayer()] = new Vector2(unit.getLocation()[0], unit.getLocation()[1]); + } else { + row = modifications.getUnits().get(unit.getId()); + if (row == null) { + row = modifications.getItems().get(unit.getId()); + if (row != null) { + type = WorldEditorDataType.ITEM; + path = row.getFieldAsString(ITEM_FILE, 0); - if (path.toLowerCase().endsWith(".mdl") || path.toLowerCase().endsWith(".mdx")) { - path = path.substring(0, path.length() - 4); - } + if (path.toLowerCase().endsWith(".mdl") || path.toLowerCase().endsWith(".mdx")) { + path = path.substring(0, path.length() - 4); + } - final String unitShadow = "Shadow"; - if ((unitShadow != null) && !"_".equals(unitShadow)) { - final String texture = "ReplaceableTextures\\Shadows\\" + unitShadow + ".blp"; - final float shadowX = 50; - final float shadowY = 50; - final float shadowWidth = 128; - final float shadowHeight = 128; - if (!this.terrain.splats.containsKey(texture)) { - final Splat splat = new Splat(); - splat.opacity = 0.5f; - this.terrain.splats.put(texture, splat); - } - final float x = unit.getLocation()[0] - shadowX; - final float y = unit.getLocation()[1] - shadowY; - this.terrain.splats.get(texture).locations - .add(new float[] { x, y, x + shadowWidth, y + shadowHeight, 3 }); - unitShadowSplat = this.terrain.splats.get(texture); - } + final String unitShadow = "Shadow"; + if ((unitShadow != null) && !"_".equals(unitShadow)) { + final String texture = "ReplaceableTextures\\Shadows\\" + unitShadow + ".blp"; + final float shadowX = 50; + final float shadowY = 50; + final float shadowWidth = 128; + final float shadowHeight = 128; + if (!this.terrain.splats.containsKey(texture)) { + final Splat splat = new Splat(); + splat.opacity = 0.5f; + this.terrain.splats.put(texture, splat); + } + final float x = unit.getLocation()[0] - shadowX; + final float y = unit.getLocation()[1] - shadowY; + this.terrain.splats.get(texture).locations + .add(new float[]{x, y, x + shadowWidth, y + shadowHeight, 3}); + unitShadowSplat = this.terrain.splats.get(texture); + } - path += ".mdx"; - } - } - else { - type = WorldEditorDataType.UNITS; - path = row.getFieldAsString(UNIT_FILE, 0); + path += ".mdx"; + } + } else { + type = WorldEditorDataType.UNITS; + path = row.getFieldAsString(UNIT_FILE, 0); - if (path.toLowerCase().endsWith(".mdl") || path.toLowerCase().endsWith(".mdx")) { - path = path.substring(0, path.length() - 4); - } - if ((row.readSLKTagInt("fileVerFlags") == 2) && this.dataSource.has(path + "_V1.mdx")) { - path += "_V1"; - } + if (path.toLowerCase().endsWith(".mdl") || path.toLowerCase().endsWith(".mdx")) { + path = path.substring(0, path.length() - 4); + } + if ((row.readSLKTagInt("fileVerFlags") == 2) && this.dataSource.has(path + "_V1.mdx")) { + path += "_V1"; + } - path += ".mdx"; + path += ".mdx"; - final String uberSplat = row.getFieldAsString(UBER_SPLAT, 0); - if (uberSplat != null) { - final Element uberSplatInfo = this.terrain.uberSplatTable.get(uberSplat); - if (uberSplatInfo != null) { - final String texturePath = uberSplatInfo.getField("Dir") + "\\" - + uberSplatInfo.getField("file") + ".blp"; - if (!this.terrain.splats.containsKey(texturePath)) { - this.terrain.splats.put(texturePath, new Splat()); - } - final float x = unit.getLocation()[0]; - final float y = unit.getLocation()[1]; - final float s = uberSplatInfo.getFieldFloatValue("Scale"); - this.terrain.splats.get(texturePath).locations - .add(new float[] { x - s, y - s, x + s, y + s, 1 }); - } - } + final String uberSplat = row.getFieldAsString(UBER_SPLAT, 0); + if (uberSplat != null) { + final Element uberSplatInfo = this.terrain.uberSplatTable.get(uberSplat); + if (uberSplatInfo != null) { + final String texturePath = uberSplatInfo.getField("Dir") + "\\" + + uberSplatInfo.getField("file") + ".blp"; + if (!this.terrain.splats.containsKey(texturePath)) { + this.terrain.splats.put(texturePath, new Splat()); + } + final float x = unit.getLocation()[0]; + final float y = unit.getLocation()[1]; + final float s = uberSplatInfo.getFieldFloatValue("Scale"); + this.terrain.splats.get(texturePath).locations + .add(new float[]{x - s, y - s, x + s, y + s, 1}); + } + } - final String unitShadow = row.getFieldAsString(UNIT_SHADOW, 0); - if ((unitShadow != null) && !"_".equals(unitShadow)) { - final String texture = "ReplaceableTextures\\Shadows\\" + unitShadow + ".blp"; - final float shadowX = row.getFieldAsFloat(UNIT_SHADOW_X, 0); - final float shadowY = row.getFieldAsFloat(UNIT_SHADOW_Y, 0); - final float shadowWidth = row.getFieldAsFloat(UNIT_SHADOW_W, 0); - final float shadowHeight = row.getFieldAsFloat(UNIT_SHADOW_H, 0); - if (this.mapMpq.has(texture)) { - if (!this.terrain.splats.containsKey(texture)) { - final Splat splat = new Splat(); - splat.opacity = 0.5f; - this.terrain.splats.put(texture, splat); - } - final float x = unit.getLocation()[0] - shadowX; - final float y = unit.getLocation()[1] - shadowY; - this.terrain.splats.get(texture).locations - .add(new float[] { x, y, x + shadowWidth, y + shadowHeight, 3 }); - unitShadowSplat = this.terrain.splats.get(texture); - } - } + final String unitShadow = row.getFieldAsString(UNIT_SHADOW, 0); + if ((unitShadow != null) && !"_".equals(unitShadow)) { + final String texture = "ReplaceableTextures\\Shadows\\" + unitShadow + ".blp"; + final float shadowX = row.getFieldAsFloat(UNIT_SHADOW_X, 0); + final float shadowY = row.getFieldAsFloat(UNIT_SHADOW_Y, 0); + final float shadowWidth = row.getFieldAsFloat(UNIT_SHADOW_W, 0); + final float shadowHeight = row.getFieldAsFloat(UNIT_SHADOW_H, 0); + if (this.mapMpq.has(texture)) { + if (!this.terrain.splats.containsKey(texture)) { + final Splat splat = new Splat(); + splat.opacity = 0.5f; + this.terrain.splats.put(texture, splat); + } + final float x = unit.getLocation()[0] - shadowX; + final float y = unit.getLocation()[1] - shadowY; + this.terrain.splats.get(texture).locations + .add(new float[]{x, y, x + shadowWidth, y + shadowHeight, 3}); + unitShadowSplat = this.terrain.splats.get(texture); + } + } - final String buildingShadow = row.getFieldAsString(BUILDING_SHADOW, 0); - if ((buildingShadow != null) && !"_".equals(buildingShadow)) { - this.terrain.addShadow(buildingShadow, unit.getLocation()[0], unit.getLocation()[1]); - } - final String pathingTexture = row.getFieldAsString(UNIT_PATHING, 0); - if ((pathingTexture != null) && (pathingTexture.length() > 0) && !"_".equals(pathingTexture)) { - buildingPathingPixelMap = this.filePathToPathingMap.get(pathingTexture.toLowerCase()); - if (buildingPathingPixelMap == null) { - if (pathingTexture.toLowerCase().endsWith(".tga")) { - buildingPathingPixelMap = TgaFile.readTGA(pathingTexture, - this.mapMpq.getResourceAsStream(pathingTexture)); - } - else { - try (InputStream stream = this.mapMpq.getResourceAsStream(pathingTexture)) { - buildingPathingPixelMap = ImageIO.read(stream); - System.out.println("LOADING BLP PATHING: " + pathingTexture); - } - } - this.filePathToPathingMap.put(pathingTexture.toLowerCase(), buildingPathingPixelMap); - } - this.terrain.pathingGrid.blitPathingOverlayTexture(unit.getLocation()[0], - unit.getLocation()[1], (int) Math.toDegrees(unit.getAngle()), - buildingPathingPixelMap); - } + final String buildingShadow = row.getFieldAsString(BUILDING_SHADOW, 0); + if ((buildingShadow != null) && !"_".equals(buildingShadow)) { + this.terrain.addShadow(buildingShadow, unit.getLocation()[0], unit.getLocation()[1]); + } + final String pathingTexture = row.getFieldAsString(UNIT_PATHING, 0); + if ((pathingTexture != null) && (pathingTexture.length() > 0) && !"_".equals(pathingTexture)) { + buildingPathingPixelMap = this.filePathToPathingMap.get(pathingTexture.toLowerCase()); + if (buildingPathingPixelMap == null) { + if (pathingTexture.toLowerCase().endsWith(".tga")) { + buildingPathingPixelMap = TgaFile.readTGA(pathingTexture, + this.mapMpq.getResourceAsStream(pathingTexture)); + } else { + try (InputStream stream = this.mapMpq.getResourceAsStream(pathingTexture)) { + buildingPathingPixelMap = ImageIO.read(stream); + System.out.println("LOADING BLP PATHING: " + pathingTexture); + } + } + this.filePathToPathingMap.put(pathingTexture.toLowerCase(), buildingPathingPixelMap); + } + this.terrain.pathingGrid.blitPathingOverlayTexture(unit.getLocation()[0], + unit.getLocation()[1], (int) Math.toDegrees(unit.getAngle()), + buildingPathingPixelMap); + } - final String soundName = row.getFieldAsString(UNIT_SOUNDSET, 0); - UnitSoundset unitSoundset = soundsetNameToSoundset.get(soundName); - if (unitSoundset == null) { - unitSoundset = new UnitSoundset(this.dataSource, this.unitAckSoundsTable, soundName); - soundsetNameToSoundset.put(soundName, unitSoundset); - } - soundset = unitSoundset; - } - } + final String soundName = row.getFieldAsString(UNIT_SOUNDSET, 0); + UnitSoundset unitSoundset = soundsetNameToSoundset.get(soundName); + if (unitSoundset == null) { + unitSoundset = new UnitSoundset(this.dataSource, this.unitAckSoundsTable, soundName); + soundsetNameToSoundset.put(soundName, unitSoundset); + } + soundset = unitSoundset; + } + } - if (path != null) { - final MdxModel model = (MdxModel) this.load(path, this.mapPathSolver, this.solverParams); - MdxModel portraitModel; - final String portraitPath = path.substring(0, path.length() - 4) + "_portrait.mdx"; - if (this.dataSource.has(portraitPath)) { - portraitModel = (MdxModel) this.load(portraitPath, this.mapPathSolver, this.solverParams); - } - else { - portraitModel = model; - } - if (type == WorldEditorDataType.UNITS) { - final float angle = (float) Math.toDegrees(unit.getAngle()); - final CUnit simulationUnit = this.simulation.createUnit(row.getAlias(), unit.getPlayer(), - unit.getLocation()[0], unit.getLocation()[1], angle, buildingPathingPixelMap); - final RenderUnitTypeData typeData = getUnitTypeData(unit.getId(), row); - final RenderUnit renderUnit = new RenderUnit(this, model, row, unit, soundset, portraitModel, - simulationUnit, typeData); - this.unitToRenderPeer.put(simulationUnit, renderUnit); - this.units.add(renderUnit); - if (unitShadowSplat != null) { - unitShadowSplat.unitMapping.add(new Consumer() { - @Override - public void accept(final SplatMover t) { - renderUnit.shadow = t; - } - }); - } - } - else { - this.items.add(new RenderItem(this, model, row, unit, soundset, portraitModel)); // TODO store - // somewhere - if (unitShadowSplat != null) { - unitShadowSplat.unitMapping.add(new Consumer() { - @Override - public void accept(final SplatMover t) { + if (path != null) { + final MdxModel model = (MdxModel) this.load(path, this.mapPathSolver, this.solverParams); + MdxModel portraitModel; + final String portraitPath = path.substring(0, path.length() - 4) + "_portrait.mdx"; + if (this.dataSource.has(portraitPath)) { + portraitModel = (MdxModel) this.load(portraitPath, this.mapPathSolver, this.solverParams); + } else { + portraitModel = model; + } + if (type == WorldEditorDataType.UNITS) { + final float angle = (float) Math.toDegrees(unit.getAngle()); + final CUnit simulationUnit = this.simulation.createUnit(row.getAlias(), unit.getPlayer(), + unit.getLocation()[0], unit.getLocation()[1], angle, buildingPathingPixelMap); + final RenderUnitTypeData typeData = getUnitTypeData(unit.getId(), row); + final RenderUnit renderUnit = new RenderUnit(this, model, row, unit, soundset, portraitModel, + simulationUnit, typeData); + this.unitToRenderPeer.put(simulationUnit, renderUnit); + this.units.add(renderUnit); + if (unitShadowSplat != null) { + unitShadowSplat.unitMapping.add(new Consumer() { + @Override + public void accept(final SplatMover t) { + renderUnit.shadow = t; + } + }); + } + } else { + this.items.add(new RenderItem(this, model, row, unit, soundset, portraitModel)); // TODO store + // somewhere + if (unitShadowSplat != null) { + unitShadowSplat.unitMapping.add(new Consumer() { + @Override + public void accept(final SplatMover t) { - } - }); - } - } - } - else { - System.err.println("Unknown unit ID: " + unit.getId()); - } - } - } + } + }); + } + } + } else { + System.err.println("Unknown unit ID: " + unit.getId()); + } + } + } - this.terrain.loadSplats(); + this.terrain.loadSplats(); - this.unitsReady = true; - this.anyReady = true; - } + this.unitsReady = true; + this.anyReady = true; + } - private RenderUnitTypeData getUnitTypeData(final War3ID key, final MutableGameObject row) { - RenderUnitTypeData unitTypeData = this.unitIdToTypeData.get(key); - if (unitTypeData == null) { - unitTypeData = new RenderUnitTypeData(row.getFieldAsFloat(MAX_PITCH, 0), row.getFieldAsFloat(MAX_ROLL, 0), - row.getFieldAsFloat(ELEVATION_SAMPLE_RADIUS, 0)); - this.unitIdToTypeData.put(key, unitTypeData); - } - return unitTypeData; - } + private RenderUnitTypeData getUnitTypeData(final War3ID key, final MutableGameObject row) { + RenderUnitTypeData unitTypeData = this.unitIdToTypeData.get(key); + if (unitTypeData == null) { + unitTypeData = new RenderUnitTypeData(row.getFieldAsFloat(MAX_PITCH, 0), row.getFieldAsFloat(MAX_ROLL, 0), + row.getFieldAsFloat(ELEVATION_SAMPLE_RADIUS, 0)); + this.unitIdToTypeData.put(key, unitTypeData); + } + return unitTypeData; + } - @Override - public void update() { - if (this.anyReady) { - this.terrain.update(); + @Override + public void update() { + if (this.anyReady) { + this.terrain.update(); - super.update(); + super.update(); - for (final RenderUnit unit : this.units) { - unit.updateAnimations(this); - } - final Iterator projectileIterator = this.projectiles.iterator(); - while (projectileIterator.hasNext()) { - final RenderEffect projectile = projectileIterator.next(); - if (projectile.updateAnimations(this, Gdx.graphics.getDeltaTime())) { - projectileIterator.remove(); - } - } - for (final RenderItem item : this.items) { - final MdxComplexInstance instance = item.instance; - final MdxComplexInstance mdxComplexInstance = instance; - if (mdxComplexInstance.sequenceEnded || (mdxComplexInstance.sequence == -1)) { - SequenceUtils.randomStandSequence(mdxComplexInstance); - } - } - for (final RenderDoodad item : this.doodads) { - final ModelInstance instance = item.instance; - if (instance instanceof MdxComplexInstance) { - final MdxComplexInstance mdxComplexInstance = (MdxComplexInstance) instance; - if ((mdxComplexInstance.sequence == -1) - || (mdxComplexInstance.sequenceEnded/* - * && (((MdxModel) mdxComplexInstance.model).sequences - * .get(mdxComplexInstance.sequence).getFlags() == 0) - */)) { - SequenceUtils.randomSequence(mdxComplexInstance, item.getAnimation(), SequenceUtils.EMPTY, - true); - } - } - } + for (final RenderUnit unit : this.units) { + unit.updateAnimations(this); + } + final Iterator projectileIterator = this.projectiles.iterator(); + while (projectileIterator.hasNext()) { + final RenderEffect projectile = projectileIterator.next(); + if (projectile.updateAnimations(this, Gdx.graphics.getDeltaTime())) { + projectileIterator.remove(); + } + } + for (final RenderItem item : this.items) { + final MdxComplexInstance instance = item.instance; + final MdxComplexInstance mdxComplexInstance = instance; + if (mdxComplexInstance.sequenceEnded || (mdxComplexInstance.sequence == -1)) { + SequenceUtils.randomStandSequence(mdxComplexInstance); + } + } + for (final RenderDoodad item : this.doodads) { + final ModelInstance instance = item.instance; + if (instance instanceof MdxComplexInstance) { + final MdxComplexInstance mdxComplexInstance = (MdxComplexInstance) instance; + if ((mdxComplexInstance.sequence == -1) + || (mdxComplexInstance.sequenceEnded/* + * && (((MdxModel) mdxComplexInstance.model).sequences + * .get(mdxComplexInstance.sequence).getFlags() == 0) + */)) { + SequenceUtils.randomSequence(mdxComplexInstance, item.getAnimation(), SequenceUtils.EMPTY, + true); + } + } + } - final float rawDeltaTime = Gdx.graphics.getRawDeltaTime(); - this.updateTime += rawDeltaTime; - while (this.updateTime >= WarsmashConstants.SIMULATION_STEP_TIME) { - this.updateTime -= WarsmashConstants.SIMULATION_STEP_TIME; - this.simulation.update(); - } - this.dncTerrain.setFrameByRatio( - this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours()); - this.dncTerrain.update(rawDeltaTime, null); - this.dncUnit.setFrameByRatio( - this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours()); - this.dncUnit.update(rawDeltaTime, null); - this.dncUnitDay.setFrameByRatio(0.5f); - this.dncUnitDay.update(rawDeltaTime, null); - this.dncTarget.setFrameByRatio( - this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours()); - this.dncTarget.update(rawDeltaTime, null); - } - } + final float rawDeltaTime = Gdx.graphics.getRawDeltaTime(); + this.updateTime += rawDeltaTime; + while (this.updateTime >= WarsmashConstants.SIMULATION_STEP_TIME) { + this.updateTime -= WarsmashConstants.SIMULATION_STEP_TIME; + this.simulation.update(); + } + this.dncTerrain.setFrameByRatio( + this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours()); + this.dncTerrain.update(rawDeltaTime, null); + this.dncUnit.setFrameByRatio( + this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours()); + this.dncUnit.update(rawDeltaTime, null); + this.dncUnitDay.setFrameByRatio(0.5f); + this.dncUnitDay.update(rawDeltaTime, null); + this.dncTarget.setFrameByRatio( + this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours()); + this.dncTarget.update(rawDeltaTime, null); + } + } - @Override - public void render() { - if (this.anyReady) { - final Scene worldScene = this.worldScene; + @Override + public void render() { + if (this.anyReady) { + final Scene worldScene = this.worldScene; - startFrame(); - worldScene.startFrame(); - worldScene.renderOpaque(this.dynamicShadowManager, this.webGL); - this.terrain.renderGround(this.dynamicShadowManager); - this.terrain.renderCliffs(); - worldScene.renderOpaque(); - this.terrain.renderUberSplats(); - this.terrain.renderWater(); - worldScene.renderTranslucent(); + startFrame(); + worldScene.startFrame(); + worldScene.renderOpaque(this.dynamicShadowManager, this.webGL); + this.terrain.renderGround(this.dynamicShadowManager); + this.terrain.renderCliffs(); + worldScene.renderOpaque(); + this.terrain.renderUberSplats(); + this.terrain.renderWater(); + worldScene.renderTranslucent(); - final List scenes = this.scenes; - for (final Scene scene : scenes) { - if (scene != worldScene) { - scene.startFrame(); - scene.renderOpaque(); - scene.renderTranslucent(); - } - } - } - } + final List scenes = this.scenes; + for (final Scene scene : scenes) { + if (scene != worldScene) { + scene.startFrame(); + scene.renderOpaque(); + scene.renderTranslucent(); + } + } + } + } - public void deselect() { - if (!this.selModels.isEmpty()) { - for (final SplatModel model : this.selModels) { - this.terrain.removeSplatBatchModel(model); - } - this.selModels.clear(); - for (final RenderUnit unit : this.selected) { - unit.selectionCircle = null; - } - } - this.selected.clear(); - } + public void deselect() { + if (!this.selModels.isEmpty()) { + for (final SplatModel model : this.selModels) { + this.terrain.removeSplatBatchModel(model); + } + this.selModels.clear(); + for (final RenderUnit unit : this.selected) { + unit.selectionCircle = null; + } + } + this.selected.clear(); + } - public void doSelectUnit(final List units) { - deselect(); - if (units.isEmpty()) { - return; - } + public void doSelectUnit(final List units) { + deselect(); + if (units.isEmpty()) { + return; + } - final Map splats = new HashMap(); - for (final RenderUnit unit : units) { - if (unit.row != null) { - if (unit.selectionScale > 0) { - final float selectionSize = unit.selectionScale * this.selectionCircleScaleFactor; - String path = null; - for (int i = 0; i < this.selectionCircleSizes.size(); i++) { - final SelectionCircleSize selectionCircleSize = this.selectionCircleSizes.get(i); - if ((selectionSize < selectionCircleSize.size) - || (i == (this.selectionCircleSizes.size() - 1))) { - path = selectionCircleSize.texture; - break; - } - } - if (!path.toLowerCase().endsWith(".blp")) { - path += ".blp"; - } - if (!splats.containsKey(path)) { - splats.put(path, new Splat()); - } - final float x = unit.location[0]; - final float y = unit.location[1]; - System.out.println("Selecting a unit at " + x + "," + y); - final float z = unit.row.getFieldAsFloat(UNIT_SELECT_HEIGHT, 0); - splats.get(path).locations.add(new float[] { x - (selectionSize / 2), y - (selectionSize / 2), - x + (selectionSize / 2), y + (selectionSize / 2), z + 5 }); - splats.get(path).unitMapping.add(new Consumer() { - @Override - public void accept(final SplatMover t) { - unit.selectionCircle = t; - } - }); - } - this.selected.add(unit); - } - } - this.selModels.clear(); - for (final Map.Entry entry : splats.entrySet()) { - final String path = entry.getKey(); - final Splat locations = entry.getValue(); - final SplatModel model = new SplatModel(Gdx.gl30, (Texture) load(path, PathSolver.DEFAULT, null), - locations.locations, this.terrain.centerOffset, locations.unitMapping, true); - model.color[0] = 0; - model.color[1] = 1; - model.color[2] = 0; - model.color[3] = 1; - this.selModels.add(model); - this.terrain.addSplatBatchModel(model); - } - } + final Map splats = new HashMap(); + for (final RenderUnit unit : units) { + if (unit.row != null) { + if (unit.selectionScale > 0) { + final float selectionSize = unit.selectionScale * this.selectionCircleScaleFactor; + String path = null; + for (int i = 0; i < this.selectionCircleSizes.size(); i++) { + final SelectionCircleSize selectionCircleSize = this.selectionCircleSizes.get(i); + if ((selectionSize < selectionCircleSize.size) + || (i == (this.selectionCircleSizes.size() - 1))) { + path = selectionCircleSize.texture; + break; + } + } + if (!path.toLowerCase().endsWith(".blp")) { + path += ".blp"; + } + if (!splats.containsKey(path)) { + splats.put(path, new Splat()); + } + final float x = unit.location[0]; + final float y = unit.location[1]; + System.out.println("Selecting a unit at " + x + "," + y); + final float z = unit.row.getFieldAsFloat(UNIT_SELECT_HEIGHT, 0); + splats.get(path).locations.add(new float[]{x - (selectionSize / 2), y - (selectionSize / 2), + x + (selectionSize / 2), y + (selectionSize / 2), z + 5}); + splats.get(path).unitMapping.add(new Consumer() { + @Override + public void accept(final SplatMover t) { + unit.selectionCircle = t; + } + }); + } + this.selected.add(unit); + } + } + this.selModels.clear(); + for (final Map.Entry entry : splats.entrySet()) { + final String path = entry.getKey(); + final Splat locations = entry.getValue(); + final SplatModel model = new SplatModel(Gdx.gl30, (Texture) load(path, PathSolver.DEFAULT, null), + locations.locations, this.terrain.centerOffset, locations.unitMapping, true); + model.color[0] = 0; + model.color[1] = 1; + model.color[2] = 0; + model.color[3] = 1; + this.selModels.add(model); + this.terrain.addSplatBatchModel(model); + } + } - public void getClickLocation(final Vector3 out, final int screenX, final int screenY) { - final float[] ray = rayHeap; - mousePosHeap.set(screenX, screenY); - this.worldScene.camera.screenToWorldRay(ray, mousePosHeap); - gdxRayHeap.set(ray[0], ray[1], ray[2], ray[3] - ray[0], ray[4] - ray[1], ray[5] - ray[2]); - gdxRayHeap.direction.nor();// needed for libgdx - RenderMathUtils.intersectRayTriangles(gdxRayHeap, this.terrain.softwareGroundMesh.vertices, - this.terrain.softwareGroundMesh.indices, 3, out); - } + public void getClickLocation(final Vector3 out, final int screenX, final int screenY) { + final float[] ray = rayHeap; + mousePosHeap.set(screenX, screenY); + this.worldScene.camera.screenToWorldRay(ray, mousePosHeap); + gdxRayHeap.set(ray[0], ray[1], ray[2], ray[3] - ray[0], ray[4] - ray[1], ray[5] - ray[2]); + gdxRayHeap.direction.nor();// needed for libgdx + RenderMathUtils.intersectRayTriangles(gdxRayHeap, this.terrain.softwareGroundMesh.vertices, + this.terrain.softwareGroundMesh.indices, 3, out); + rectangleHeap.set(Math.min(out.x, gdxRayHeap.origin.x), Math.min(out.y, gdxRayHeap.origin.y), Math.abs(out.x - gdxRayHeap.origin.x), Math.abs(out.y - gdxRayHeap.origin.y)); + walkableObjectsTree.intersect(rectangleHeap, walkablesIntersectionFinder.reset(gdxRayHeap)); + if (walkablesIntersectionFinder.found) { + out.set(walkablesIntersectionFinder.intersection); + } else { + out.z = Math.max(getWalkableRenderHeight(out.x, out.y), terrain.getGroundHeight(out.x, out.y)); + } + } - public void showConfirmation(final Vector3 position, final float red, final float green, final float blue) { - this.confirmationInstance.show(); - this.confirmationInstance.setSequence(0); - this.confirmationInstance.setLocation(position); - this.worldScene.instanceMoved(this.confirmationInstance, position.x, position.y); - this.confirmationInstance.vertexColor[0] = red; - this.confirmationInstance.vertexColor[1] = green; - this.confirmationInstance.vertexColor[2] = blue; - } + public void showConfirmation(final Vector3 position, final float red, final float green, final float blue) { + this.confirmationInstance.show(); + this.confirmationInstance.setSequence(0); + this.confirmationInstance.setLocation(position); + this.worldScene.instanceMoved(this.confirmationInstance, position.x, position.y); + this.confirmationInstance.vertexColor[0] = red; + this.confirmationInstance.vertexColor[1] = green; + this.confirmationInstance.vertexColor[2] = blue; + } - public List selectUnit(final float x, final float y, final boolean toggle) { - System.out.println("world: " + x + "," + y); - final float[] ray = rayHeap; - mousePosHeap.set(x, y); - this.worldScene.camera.screenToWorldRay(ray, mousePosHeap); - gdxRayHeap.set(ray[0], ray[1], ray[2], ray[3] - ray[0], ray[4] - ray[1], ray[5] - ray[2]); - gdxRayHeap.direction.nor();// needed for libgdx + public List selectUnit(final float x, final float y, final boolean toggle) { + System.out.println("world: " + x + "," + y); + final float[] ray = rayHeap; + mousePosHeap.set(x, y); + this.worldScene.camera.screenToWorldRay(ray, mousePosHeap); + gdxRayHeap.set(ray[0], ray[1], ray[2], ray[3] - ray[0], ray[4] - ray[1], ray[5] - ray[2]); + gdxRayHeap.direction.nor();// needed for libgdx - RenderUnit entity = null; - for (final RenderUnit unit : this.units) { - final MdxComplexInstance instance = unit.instance; - if (instance.isVisible(this.worldScene.camera) - && instance.intersectRayWithCollision(gdxRayHeap, intersectionHeap, - unit.getSimulationUnit().getUnitType().isBuilding()) - && !unit.getSimulationUnit().isDead()) { - entity = unit; - } - } - List sel; - if (entity != null) { - if (toggle) { - sel = new ArrayList<>(this.selected); - final int idx = sel.indexOf(entity); - if (idx >= 0) { - sel.remove(idx); - } - else { - sel.add(entity); - } - } - else { - sel = Arrays.asList(entity); - } - } - else { - sel = Collections.emptyList(); - } - this.doSelectUnit(sel); - return sel; - } + RenderUnit entity = null; + for (final RenderUnit unit : this.units) { + final MdxComplexInstance instance = unit.instance; + if (instance.isVisible(this.worldScene.camera) + && instance.intersectRayWithCollision(gdxRayHeap, intersectionHeap, + unit.getSimulationUnit().getUnitType().isBuilding(), false) + && !unit.getSimulationUnit().isDead()) { + entity = unit; + } + } + List sel; + if (entity != null) { + if (toggle) { + sel = new ArrayList<>(this.selected); + final int idx = sel.indexOf(entity); + if (idx >= 0) { + sel.remove(idx); + } else { + sel.add(entity); + } + } else { + sel = Arrays.asList(entity); + } + } else { + sel = Collections.emptyList(); + } + this.doSelectUnit(sel); + return sel; + } - public RenderUnit rayPickUnit(final float x, final float y) { - final float[] ray = rayHeap; - mousePosHeap.set(x, y); - this.worldScene.camera.screenToWorldRay(ray, mousePosHeap); - gdxRayHeap.set(ray[0], ray[1], ray[2], ray[3] - ray[0], ray[4] - ray[1], ray[5] - ray[2]); - gdxRayHeap.direction.nor();// needed for libgdx + public RenderUnit rayPickUnit(final float x, final float y) { + final float[] ray = rayHeap; + mousePosHeap.set(x, y); + this.worldScene.camera.screenToWorldRay(ray, mousePosHeap); + gdxRayHeap.set(ray[0], ray[1], ray[2], ray[3] - ray[0], ray[4] - ray[1], ray[5] - ray[2]); + gdxRayHeap.direction.nor();// needed for libgdx - RenderUnit entity = null; - for (final RenderUnit unit : this.units) { - final MdxComplexInstance instance = unit.instance; - if (instance.isVisible(this.worldScene.camera) && instance.intersectRayWithCollision(gdxRayHeap, - intersectionHeap, unit.getSimulationUnit().getUnitType().isBuilding())) { - entity = unit; - } - } - return entity; - } + RenderUnit entity = null; + for (final RenderUnit unit : this.units) { + final MdxComplexInstance instance = unit.instance; + if (instance.isVisible(this.worldScene.camera) && instance.intersectRayWithCollision(gdxRayHeap, + intersectionHeap, unit.getSimulationUnit().getUnitType().isBuilding(), false)) { + entity = unit; + } + } + return entity; + } - private static final class MappedDataCallbackImplementation implements LoadGenericCallback { - @Override - public Object call(final InputStream data) { - final StringBuilder stringBuilder = new StringBuilder(); - try (BufferedReader reader = new BufferedReader(new InputStreamReader(data, "utf-8"))) { - String line; - while ((line = reader.readLine()) != null) { - stringBuilder.append(line); - stringBuilder.append("\n"); - } - } - catch (final UnsupportedEncodingException e) { - throw new RuntimeException(e); - } - catch (final IOException e) { - throw new RuntimeException(e); - } - return new MappedData(stringBuilder.toString()); - } - } + private static final class MappedDataCallbackImplementation implements LoadGenericCallback { + @Override + public Object call(final InputStream data) { + final StringBuilder stringBuilder = new StringBuilder(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(data, "utf-8"))) { + String line; + while ((line = reader.readLine()) != null) { + stringBuilder.append(line); + stringBuilder.append("\n"); + } + } catch (final UnsupportedEncodingException e) { + throw new RuntimeException(e); + } catch (final IOException e) { + throw new RuntimeException(e); + } + return new MappedData(stringBuilder.toString()); + } + } - private static final class StringDataCallbackImplementation implements LoadGenericCallback { - @Override - public Object call(final InputStream data) { - if (data == null) { - System.err.println("data null"); - } - final StringBuilder stringBuilder = new StringBuilder(); - try (BufferedReader reader = new BufferedReader(new InputStreamReader(data, "utf-8"))) { - String line; - while ((line = reader.readLine()) != null) { - stringBuilder.append(line); - stringBuilder.append("\n"); - } - } - catch (final UnsupportedEncodingException e) { - throw new RuntimeException(e); - } - catch (final IOException e) { - throw new RuntimeException(e); - } - return stringBuilder.toString(); - } - } + private static final class StringDataCallbackImplementation implements LoadGenericCallback { + @Override + public Object call(final InputStream data) { + if (data == null) { + System.err.println("data null"); + } + final StringBuilder stringBuilder = new StringBuilder(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(data, "utf-8"))) { + String line; + while ((line = reader.readLine()) != null) { + stringBuilder.append(line); + stringBuilder.append("\n"); + } + } catch (final UnsupportedEncodingException e) { + throw new RuntimeException(e); + } catch (final IOException e) { + throw new RuntimeException(e); + } + return stringBuilder.toString(); + } + } - private static final class StreamDataCallbackImplementation implements LoadGenericCallback { - @Override - public Object call(final InputStream data) { - return data; - } - } + private static final class StreamDataCallbackImplementation implements LoadGenericCallback { + @Override + public Object call(final InputStream data) { + return data; + } + } - public static final class SolverParams { - public char tileset; - public boolean reforged; - public boolean hd; - } + public static final class SolverParams { + public char tileset; + public boolean reforged; + public boolean hd; + } - public static final class CliffInfo { - public List locations = new ArrayList<>(); - public List textures = new ArrayList<>(); - } + public static final class CliffInfo { + public List locations = new ArrayList<>(); + public List textures = new ArrayList<>(); + } - private static final int MAXIMUM_ACCEPTED = 1 << 30; - private float selectionCircleScaleFactor; - private DataTable worldEditData; - private WorldEditStrings worldEditStrings; - private Warcraft3MapObjectData allObjectData; - private AbilityDataUI abilityDataUI; + private static final int MAXIMUM_ACCEPTED = 1 << 30; + private float selectionCircleScaleFactor; + private DataTable worldEditData; + private WorldEditStrings worldEditStrings; + private Warcraft3MapObjectData allObjectData; + private AbilityDataUI abilityDataUI; - /** - * Returns a power of two size for the given target capacity. - */ - private static final int pow2GreaterThan(final int capacity) { - int numElements = capacity - 1; - numElements |= numElements >>> 1; - numElements |= numElements >>> 2; - numElements |= numElements >>> 4; - numElements |= numElements >>> 8; - numElements |= numElements >>> 16; - return (numElements < 0) ? 1 : (numElements >= MAXIMUM_ACCEPTED) ? MAXIMUM_ACCEPTED : numElements + 1; - } + /** + * Returns a power of two size for the given target capacity. + */ + private static final int pow2GreaterThan(final int capacity) { + int numElements = capacity - 1; + numElements |= numElements >>> 1; + numElements |= numElements >>> 2; + numElements |= numElements >>> 4; + numElements |= numElements >>> 8; + numElements |= numElements >>> 16; + return (numElements < 0) ? 1 : (numElements >= MAXIMUM_ACCEPTED) ? MAXIMUM_ACCEPTED : numElements + 1; + } - public boolean orderSmart(final float x, final float y) { - mousePosHeap.x = x; - mousePosHeap.y = y; - boolean ordered = false; - for (final RenderUnit unit : this.selected) { - for (final CAbility ability : unit.getSimulationUnit().getAbilities()) { - if (ability instanceof CAbilityMove) { - ability.checkCanUse(this.simulation, unit.getSimulationUnit(), OrderIds.smart, - BooleanAbilityActivationReceiver.INSTANCE); - if (BooleanAbilityActivationReceiver.INSTANCE.isOk()) { - ability.checkCanTarget(this.simulation, unit.getSimulationUnit(), OrderIds.smart, mousePosHeap, - PointAbilityTargetCheckReceiver.INSTANCE); - final Vector2 target = PointAbilityTargetCheckReceiver.INSTANCE.getTarget(); - if (target != null) { - ability.onOrder(this.simulation, unit.getSimulationUnit(), OrderIds.smart, mousePosHeap, - false); - ordered = true; - } - else { - System.err.println("Target not valid."); - } - } - else { - System.err.println("Ability not ok to use."); - } - } - else { - System.err.println("Ability not move."); - } - } + public boolean orderSmart(final float x, final float y) { + mousePosHeap.x = x; + mousePosHeap.y = y; + boolean ordered = false; + for (final RenderUnit unit : this.selected) { + for (final CAbility ability : unit.getSimulationUnit().getAbilities()) { + if (ability instanceof CAbilityMove) { + ability.checkCanUse(this.simulation, unit.getSimulationUnit(), OrderIds.smart, + BooleanAbilityActivationReceiver.INSTANCE); + if (BooleanAbilityActivationReceiver.INSTANCE.isOk()) { + ability.checkCanTarget(this.simulation, unit.getSimulationUnit(), OrderIds.smart, mousePosHeap, + PointAbilityTargetCheckReceiver.INSTANCE); + final Vector2 target = PointAbilityTargetCheckReceiver.INSTANCE.getTarget(); + if (target != null) { + ability.onOrder(this.simulation, unit.getSimulationUnit(), OrderIds.smart, mousePosHeap, + false); + ordered = true; + } else { + System.err.println("Target not valid."); + } + } else { + System.err.println("Ability not ok to use."); + } + } else { + System.err.println("Ability not move."); + } + } - } - return ordered; - } + } + return ordered; + } - public boolean orderSmart(final RenderUnit target) { - boolean ordered = false; - for (final RenderUnit unit : this.selected) { - for (final CAbility ability : unit.getSimulationUnit().getAbilities()) { - if (ability instanceof CAbilityAttack) { - ability.checkCanUse(this.simulation, unit.getSimulationUnit(), OrderIds.smart, - BooleanAbilityActivationReceiver.INSTANCE); - if (BooleanAbilityActivationReceiver.INSTANCE.isOk()) { - ability.checkCanTarget(this.simulation, unit.getSimulationUnit(), OrderIds.smart, - target.getSimulationUnit(), CWidgetAbilityTargetCheckReceiver.INSTANCE); - final CWidget targetWidget = CWidgetAbilityTargetCheckReceiver.INSTANCE.getTarget(); - if (targetWidget != null) { - ability.onOrder(this.simulation, unit.getSimulationUnit(), OrderIds.smart, targetWidget, - false); - ordered = true; - } - else { - System.err.println("Target not valid."); - } - } - else { - System.err.println("Ability not ok to use."); - } - } - else { - System.err.println("Ability not move."); - } - } + public boolean orderSmart(final RenderUnit target) { + boolean ordered = false; + for (final RenderUnit unit : this.selected) { + for (final CAbility ability : unit.getSimulationUnit().getAbilities()) { + if (ability instanceof CAbilityAttack) { + ability.checkCanUse(this.simulation, unit.getSimulationUnit(), OrderIds.smart, + BooleanAbilityActivationReceiver.INSTANCE); + if (BooleanAbilityActivationReceiver.INSTANCE.isOk()) { + ability.checkCanTarget(this.simulation, unit.getSimulationUnit(), OrderIds.smart, + target.getSimulationUnit(), CWidgetAbilityTargetCheckReceiver.INSTANCE); + final CWidget targetWidget = CWidgetAbilityTargetCheckReceiver.INSTANCE.getTarget(); + if (targetWidget != null) { + ability.onOrder(this.simulation, unit.getSimulationUnit(), OrderIds.smart, targetWidget, + false); + ordered = true; + } else { + System.err.println("Target not valid."); + } + } else { + System.err.println("Ability not ok to use."); + } + } else { + System.err.println("Ability not move."); + } + } - } - return ordered; - } + } + return ordered; + } - public void standOnRepeat(final MdxComplexInstance instance) { - instance.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); - SequenceUtils.randomStandSequence(instance); - } + public void standOnRepeat(final MdxComplexInstance instance) { + instance.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + SequenceUtils.randomStandSequence(instance); + } - private static final class SelectionCircleSize { - private final float size; - private final String texture; - private final String textureDotted; + private static final class SelectionCircleSize { + private final float size; + private final String texture; + private final String textureDotted; - public SelectionCircleSize(final float size, final String texture, final String textureDotted) { - this.size = size; - this.texture = texture; - this.textureDotted = textureDotted; - } - } + public SelectionCircleSize(final float size, final String texture, final String textureDotted) { + this.size = size; + this.texture = texture; + this.textureDotted = textureDotted; + } + } - public void setDayNightModels(final String terrainDNCFile, final String unitDNCFile) { - final MdxModel terrainDNCModel = (MdxModel) load(mdx(terrainDNCFile), PathSolver.DEFAULT, null); - this.dncTerrain = (MdxComplexInstance) terrainDNCModel.addInstance(); - this.dncTerrain.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); - this.dncTerrain.setSequence(0); - final MdxModel unitDNCModel = (MdxModel) load(mdx(unitDNCFile), PathSolver.DEFAULT, null); - this.dncUnit = (MdxComplexInstance) unitDNCModel.addInstance(); - this.dncUnit.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); - this.dncUnit.setSequence(0); - this.dncUnitDay = (MdxComplexInstance) unitDNCModel.addInstance(); - this.dncUnitDay.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); - this.dncUnitDay.setSequence(0); - final MdxModel targetDNCModel = (MdxModel) load( - mdx("Environment\\DNC\\DNCLordaeron\\DNCLordaeronTarget\\DNCLordaeronTarget.mdl"), PathSolver.DEFAULT, - null); - this.dncTarget = (MdxComplexInstance) targetDNCModel.addInstance(); - this.dncTarget.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); - this.dncTarget.setSequence(0); - } + public void setDayNightModels(final String terrainDNCFile, final String unitDNCFile) { + final MdxModel terrainDNCModel = (MdxModel) load(mdx(terrainDNCFile), PathSolver.DEFAULT, null); + this.dncTerrain = (MdxComplexInstance) terrainDNCModel.addInstance(); + this.dncTerrain.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + this.dncTerrain.setSequence(0); + final MdxModel unitDNCModel = (MdxModel) load(mdx(unitDNCFile), PathSolver.DEFAULT, null); + this.dncUnit = (MdxComplexInstance) unitDNCModel.addInstance(); + this.dncUnit.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + this.dncUnit.setSequence(0); + this.dncUnitDay = (MdxComplexInstance) unitDNCModel.addInstance(); + this.dncUnitDay.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + this.dncUnitDay.setSequence(0); + final MdxModel targetDNCModel = (MdxModel) load( + mdx("Environment\\DNC\\DNCLordaeron\\DNCLordaeronTarget\\DNCLordaeronTarget.mdl"), PathSolver.DEFAULT, + null); + this.dncTarget = (MdxComplexInstance) targetDNCModel.addInstance(); + this.dncTarget.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + this.dncTarget.setSequence(0); + } - private static String mdx(String mdxPath) { - if (mdxPath.toLowerCase().endsWith(".mdl")) { - mdxPath = mdxPath.substring(0, mdxPath.length() - 4); - } - if (!mdxPath.toLowerCase().endsWith(".mdx")) { - mdxPath += ".mdx"; - } - return mdxPath; - } + private static String mdx(String mdxPath) { + if (mdxPath.toLowerCase().endsWith(".mdl")) { + mdxPath = mdxPath.substring(0, mdxPath.length() - 4); + } + if (!mdxPath.toLowerCase().endsWith(".mdx")) { + mdxPath += ".mdx"; + } + return mdxPath; + } - @Override - public SceneLightManager createLightManager(final boolean simple) { - if (simple) { - return new W3xScenePortraitLightManager(this, this.lightDirection); - } - else { - return new W3xSceneWorldLightManager(this); - } - } + @Override + public SceneLightManager createLightManager(final boolean simple) { + if (simple) { + return new W3xScenePortraitLightManager(this, this.lightDirection); + } else { + return new W3xSceneWorldLightManager(this); + } + } - public WorldEditStrings getWorldEditStrings() { - return this.worldEditStrings; - } + public WorldEditStrings getWorldEditStrings() { + return this.worldEditStrings; + } - public void setGameUI(final GameUI gameUI) { - this.gameUI = gameUI; - this.abilityDataUI = new AbilityDataUI(this.allObjectData.getAbilities(), gameUI); - } + public void setGameUI(final GameUI gameUI) { + this.gameUI = gameUI; + this.abilityDataUI = new AbilityDataUI(this.allObjectData.getAbilities(), gameUI); + } - public GameUI getGameUI() { - return this.gameUI; - } + public GameUI getGameUI() { + return this.gameUI; + } - public AbilityDataUI getAbilityDataUI() { - return this.abilityDataUI; - } + public AbilityDataUI getAbilityDataUI() { + return this.abilityDataUI; + } + + public float getWalkableRenderHeight(float x, float y) { + walkableObjectsTree.intersect(x, y, walkablesIntersector.reset(x, y)); + return walkablesIntersector.z; + } + + public MdxComplexInstance getHighestWalkableUnder(float x, float y) { + walkableObjectsTree.intersect(x, y, intersectorFindsHighestWalkable.reset(x, y)); + return intersectorFindsHighestWalkable.highestInstance; + } + + private static final class QuadtreeIntersectorFindsWalkableRenderHeight implements QuadtreeIntersector { + private float z; + private Ray ray = new Ray(); + private Vector3 intersection = new Vector3(); + + private QuadtreeIntersectorFindsWalkableRenderHeight reset(float x, float y) { + z = -Float.MAX_VALUE; + ray.set(x, y, 4096, 0, 0, -8192); + return this; + } + + @Override + public boolean onIntersect(MdxComplexInstance intersectingObject) { + if (intersectingObject.intersectRayWithCollision(ray, intersection, true, true)) { + z = Math.max(z, intersection.z); + } + return false; + } + } + + private static final class QuadtreeIntersectorFindsHighestWalkable implements QuadtreeIntersector { + private float z; + private Ray ray = new Ray(); + private Vector3 intersection = new Vector3(); + private MdxComplexInstance highestInstance; + + private QuadtreeIntersectorFindsHighestWalkable reset(float x, float y) { + z = -Float.MAX_VALUE; + ray.set(x, y, 4096, 0, 0, -8192); + highestInstance = null; + return this; + } + + @Override + public boolean onIntersect(MdxComplexInstance intersectingObject) { + if (intersectingObject.intersectRayWithCollision(ray, intersection, true, true)) { + if(intersection.z > z) { + z = intersection.z; + highestInstance = intersectingObject; + } + } + return false; + } + } + + private static final class QuadtreeIntersectorFindsHitPoint implements QuadtreeIntersector { + private Ray ray; + private Vector3 intersection = new Vector3(); + private boolean found; + + private QuadtreeIntersectorFindsHitPoint reset(Ray ray) { + this.ray = ray; + this.found = false; + return this; + } + + @Override + public boolean onIntersect(MdxComplexInstance intersectingObject) { + if (intersectingObject.intersectRayWithCollision(ray, intersection, true, true)) { + this.found = true; + return true; + } + return false; + } + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java index 08edfec..368a23d 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java @@ -411,6 +411,7 @@ public class Terrain { this.shaderMapBoundsRectangle = new Rectangle(this.shaderMapBounds[0], this.shaderMapBounds[1], this.shaderMapBounds[2] - this.shaderMapBounds[0], this.shaderMapBounds[3] - this.shaderMapBounds[1]); this.mapSize = w3eFile.getMapSize(); + this.entireMapRectangle = new Rectangle(centerOffset[0], centerOffset[1], (mapSize[0] * 128f) - 128, (mapSize[1] * 128f) - 128); this.softwareGroundMesh = new SoftwareGroundMesh(this.groundHeights, this.groundCornerHeights, this.centerOffset, width, height); @@ -1019,15 +1020,14 @@ public class Terrain { gl.glUniform1i(6, (int) this.waterIndex); gl.glUniform1f(this.waterShader.getUniformLocation("centerOffsetX"), this.centerOffset[0]); gl.glUniform1f(this.waterShader.getUniformLocation("centerOffsetY"), this.centerOffset[1]); - gl.glUniform4fv(9, 1, this.shaderMapBounds, 0); + gl.glUniform4fv(11, 1, this.shaderMapBounds, 0); final W3xSceneLightManager lightManager = (W3xSceneLightManager) this.viewer.worldScene.getLightManager(); - final DataTexture unitLightsTexture = lightManager.getTerrainLightsTexture(); + final DataTexture terrainLightsTexture = lightManager.getTerrainLightsTexture(); - unitLightsTexture.bind(21); - gl.glUniform1i(this.waterShader.getUniformLocation("lightTexture"), 21); - gl.glUniform1f(this.waterShader.getUniformLocation("lightCount"), lightManager.getTerrainLightCount()); - gl.glUniform1f(this.waterShader.getUniformLocation("lightCountHeight"), unitLightsTexture.getHeight()); + terrainLightsTexture.bind(3); + gl.glUniform1f(9, lightManager.getTerrainLightCount()); + gl.glUniform1f(10, terrainLightsTexture.getHeight()); gl.glActiveTexture(GL30.GL_TEXTURE0); gl.glBindTexture(GL30.GL_TEXTURE_2D, this.waterHeight); @@ -1035,7 +1035,7 @@ public class Terrain { gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeight); gl.glActiveTexture(GL30.GL_TEXTURE2); gl.glBindTexture(GL30.GL_TEXTURE_2D, this.waterExists); - gl.glActiveTexture(GL30.GL_TEXTURE3); + gl.glActiveTexture(GL30.GL_TEXTURE4); gl.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.waterTextureArray); gl.glBindBuffer(GL30.GL_ARRAY_BUFFER, Shapes.INSTANCE.vertexBuffer); @@ -1239,6 +1239,7 @@ public class Terrain { private final WaveBuilder waveBuilder; public PathingGrid pathingGrid; private final Rectangle shaderMapBoundsRectangle; + private final Rectangle entireMapRectangle; private static final class UnloadedTexture { private final int width; @@ -1399,4 +1400,8 @@ public class Terrain { public Rectangle getPlayableMapArea() { return this.shaderMapBoundsRectangle; } + + public Rectangle getEntireMap() { + return entireMapRectangle; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/TerrainShaders.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/TerrainShaders.java index 3771d07..4509e1e 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/TerrainShaders.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/TerrainShaders.java @@ -428,9 +428,9 @@ public class TerrainShaders { "layout (location = 3) uniform vec4 deep_color_min;\r\n" + // "layout (location = 4) uniform vec4 deep_color_max;\r\n" + // "layout (location = 5) uniform float water_offset;\r\n" + // - "layout (location = 10) uniform sampler2D lightTexture;\r\n" + // - "layout (location = 11) uniform float lightCount;\r\n" + // - "layout (location = 12) uniform float lightTextureHeight;\r\n" + // + "layout (binding = 3) uniform sampler2D lightTexture;\r\n" + // + "layout (location = 9) uniform float lightCount;\r\n" + // + "layout (location = 10) uniform float lightTextureHeight;\r\n" + // "\r\n" + // "out vec2 UV;\r\n" + // "out vec4 Color;\r\n" + // @@ -477,12 +477,12 @@ public class TerrainShaders { public static final String frag = "#version 450 core\r\n" + // "\r\n" + // - "layout (binding = 3) uniform sampler2DArray water_textures;\r\n" + // + "layout (binding = 4) uniform sampler2DArray water_textures;\r\n" + // "layout (binding = 2) uniform sampler2D water_exists_texture;\r\n" + // "\r\n" + // "\r\n" + // "layout (location = 6) uniform int current_texture;\r\n" + // - "layout (location = 9) uniform vec4 mapBounds;\r\n" + // + "layout (location = 11) uniform vec4 mapBounds;\r\n" + // "\r\n" + // "in vec2 UV;\r\n" + // "in vec4 Color;\r\n" + // diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java index 8fc6aed..0022736 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java @@ -29,374 +29,431 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitAnimationListe import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; public class RenderUnit { - private static final Quaternion tempQuat = new Quaternion(); - private static final War3ID RED = War3ID.fromString("uclr"); - private static final War3ID GREEN = War3ID.fromString("uclg"); - private static final War3ID BLUE = War3ID.fromString("uclb"); - private static final War3ID MODEL_SCALE = War3ID.fromString("usca"); - private static final War3ID MOVE_HEIGHT = War3ID.fromString("umvh"); - private static final War3ID ORIENTATION_INTERPOLATION = War3ID.fromString("uori"); - private static final War3ID ANIM_PROPS = War3ID.fromString("uani"); - private static final float[] heapZ = new float[3]; - public final MdxComplexInstance instance; - public final MutableGameObject row; - public final float[] location = new float[3]; - public float selectionScale; - public UnitSoundset soundset; - public final MdxModel portraitModel; - public int playerIndex; - private final CUnit simulationUnit; - public SplatMover shadow; - public SplatMover selectionCircle; + private static final Quaternion tempQuat = new Quaternion(); + private static final War3ID RED = War3ID.fromString("uclr"); + private static final War3ID GREEN = War3ID.fromString("uclg"); + private static final War3ID BLUE = War3ID.fromString("uclb"); + private static final War3ID MODEL_SCALE = War3ID.fromString("usca"); + private static final War3ID MOVE_HEIGHT = War3ID.fromString("umvh"); + private static final War3ID ORIENTATION_INTERPOLATION = War3ID.fromString("uori"); + private static final War3ID ANIM_PROPS = War3ID.fromString("uani"); + private static final float[] heapZ = new float[3]; + public final MdxComplexInstance instance; + public final MutableGameObject row; + public final float[] location = new float[3]; + public float selectionScale; + public UnitSoundset soundset; + public final MdxModel portraitModel; + public int playerIndex; + private final CUnit simulationUnit; + public SplatMover shadow; + public SplatMover selectionCircle; - private float x; - private float y; - private float facing; + private float x; + private float y; + private float facing; - private boolean swimming; + private boolean swimming; - private boolean dead = false; + private boolean dead = false; - private final UnitAnimationListenerImpl unitAnimationListenerImpl; - private OrientationInterpolation orientationInterpolation; - private float currentTurnVelocity = 0; - public long lastUnitResponseEndTimeMillis; - private boolean corpse; - private boolean boneCorpse; - private final RenderUnitTypeData typeData; + private final UnitAnimationListenerImpl unitAnimationListenerImpl; + private OrientationInterpolation orientationInterpolation; + private float currentTurnVelocity = 0; + public long lastUnitResponseEndTimeMillis; + private boolean corpse; + private boolean boneCorpse; + private final RenderUnitTypeData typeData; - public RenderUnit(final War3MapViewer map, final MdxModel model, final MutableGameObject row, - final com.etheller.warsmash.parsers.w3x.unitsdoo.Unit unit, final UnitSoundset soundset, - final MdxModel portraitModel, final CUnit simulationUnit, final RenderUnitTypeData typeData) { - this.portraitModel = portraitModel; - this.simulationUnit = simulationUnit; - this.typeData = typeData; - final MdxComplexInstance instance = (MdxComplexInstance) model.addInstance(); + public RenderUnit(final War3MapViewer map, final MdxModel model, final MutableGameObject row, + final com.etheller.warsmash.parsers.w3x.unitsdoo.Unit unit, final UnitSoundset soundset, + final MdxModel portraitModel, final CUnit simulationUnit, final RenderUnitTypeData typeData) { + this.portraitModel = portraitModel; + this.simulationUnit = simulationUnit; + this.typeData = typeData; + final MdxComplexInstance instance = (MdxComplexInstance) model.addInstance(); - final float[] location = unit.getLocation(); - System.arraycopy(location, 0, this.location, 0, 3); - instance.move(location); - this.facing = simulationUnit.getFacing(); - final float angle = (float) Math.toRadians(this.facing); + final float[] location = unit.getLocation(); + System.arraycopy(location, 0, this.location, 0, 3); + instance.move(location); + this.facing = simulationUnit.getFacing(); + final float angle = (float) Math.toRadians(this.facing); // instance.localRotation.setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, angle); - this.x = simulationUnit.getX(); - this.y = simulationUnit.getY(); - instance.rotate(tempQuat.setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, angle)); - instance.scale(unit.getScale()); - this.playerIndex = unit.getPlayer() & 0xFFFF; - instance.setTeamColor(this.playerIndex); - instance.setScene(map.worldScene); - this.unitAnimationListenerImpl = new UnitAnimationListenerImpl(instance); - simulationUnit.setUnitAnimationListener(this.unitAnimationListenerImpl); - final String requiredAnimationNames = row.getFieldAsString(ANIM_PROPS, 0); - TokenLoop: for (final String animationName : requiredAnimationNames.split(",")) { - final String upperCaseToken = animationName.toUpperCase(); - for (final SecondaryTag secondaryTag : SecondaryTag.values()) { - if (upperCaseToken.equals(secondaryTag.name())) { - this.unitAnimationListenerImpl.addSecondaryTag(secondaryTag); - continue TokenLoop; - } - } - } + this.x = simulationUnit.getX(); + this.y = simulationUnit.getY(); + instance.rotate(tempQuat.setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, angle)); + instance.scale(unit.getScale()); + this.playerIndex = unit.getPlayer() & 0xFFFF; + instance.setTeamColor(this.playerIndex); + instance.setScene(map.worldScene); + this.unitAnimationListenerImpl = new UnitAnimationListenerImpl(instance); + simulationUnit.setUnitAnimationListener(this.unitAnimationListenerImpl); + final String requiredAnimationNames = row.getFieldAsString(ANIM_PROPS, 0); + TokenLoop: + for (final String animationName : requiredAnimationNames.split(",")) { + final String upperCaseToken = animationName.toUpperCase(); + for (final SecondaryTag secondaryTag : SecondaryTag.values()) { + if (upperCaseToken.equals(secondaryTag.name())) { + this.unitAnimationListenerImpl.addSecondaryTag(secondaryTag); + continue TokenLoop; + } + } + } - if (row != null) { - heapZ[2] = simulationUnit.getFlyHeight(); - this.location[2] += heapZ[2]; + if (row != null) { + heapZ[2] = simulationUnit.getFlyHeight(); + this.location[2] += heapZ[2]; - instance.move(heapZ); - War3ID red; - War3ID green; - War3ID blue; - War3ID scale; - scale = MODEL_SCALE; - red = RED; - green = GREEN; - blue = BLUE; - instance.setVertexColor(new float[] { (row.getFieldAsInteger(red, 0)) / 255f, - (row.getFieldAsInteger(green, 0)) / 255f, (row.getFieldAsInteger(blue, 0)) / 255f }); - instance.uniformScale(row.getFieldAsFloat(scale, 0)); + instance.move(heapZ); + War3ID red; + War3ID green; + War3ID blue; + War3ID scale; + scale = MODEL_SCALE; + red = RED; + green = GREEN; + blue = BLUE; + instance.setVertexColor(new float[]{(row.getFieldAsInteger(red, 0)) / 255f, + (row.getFieldAsInteger(green, 0)) / 255f, (row.getFieldAsInteger(blue, 0)) / 255f}); + instance.uniformScale(row.getFieldAsFloat(scale, 0)); - this.selectionScale = row.getFieldAsFloat(War3MapViewer.UNIT_SELECT_SCALE, 0); - int orientationInterpolationOrdinal = row.getFieldAsInteger(ORIENTATION_INTERPOLATION, 0); - if ((orientationInterpolationOrdinal < 0) - || (orientationInterpolationOrdinal >= OrientationInterpolation.VALUES.length)) { - orientationInterpolationOrdinal = 0; - } - this.orientationInterpolation = OrientationInterpolation.VALUES[orientationInterpolationOrdinal]; - } + this.selectionScale = row.getFieldAsFloat(War3MapViewer.UNIT_SELECT_SCALE, 0); + int orientationInterpolationOrdinal = row.getFieldAsInteger(ORIENTATION_INTERPOLATION, 0); + if ((orientationInterpolationOrdinal < 0) + || (orientationInterpolationOrdinal >= OrientationInterpolation.VALUES.length)) { + orientationInterpolationOrdinal = 0; + } + this.orientationInterpolation = OrientationInterpolation.VALUES[orientationInterpolationOrdinal]; + } - this.instance = instance; - this.row = row; - this.soundset = soundset; + this.instance = instance; + this.row = row; + this.soundset = soundset; - } + } - public void populateCommandCard(final CommandButtonListener commandButtonListener, - final AbilityDataUI abilityDataUI) { - for (final CAbility ability : this.simulationUnit.getAbilities()) { - ability.visit(CommandCardPopulatingAbilityVisitor.INSTANCE.reset(commandButtonListener, abilityDataUI)); - } - } + public void populateCommandCard(final CommandButtonListener commandButtonListener, + final AbilityDataUI abilityDataUI) { + for (final CAbility ability : this.simulationUnit.getAbilities()) { + ability.visit(CommandCardPopulatingAbilityVisitor.INSTANCE.reset(commandButtonListener, abilityDataUI)); + } + } - public void updateAnimations(final War3MapViewer map) { - final float deltaTime = Gdx.graphics.getDeltaTime(); - final float simulationX = this.simulationUnit.getX(); - final float simulationY = this.simulationUnit.getY(); - final float simDx = simulationX - this.x; - final float simDy = simulationY - this.y; - final float distanceToSimulation = (float) Math.sqrt((simDx * simDx) + (simDy * simDy)); - final int speed = this.simulationUnit.getSpeed(); - final float speedDelta = speed * deltaTime; - if ((distanceToSimulation > speedDelta) && (deltaTime < 1.0)) { - // The 1.0 here says that after 1 second of lag, units just teleport to show - // where they actually are - this.x += (speedDelta * simDx) / distanceToSimulation; - this.y += (speedDelta * simDy) / distanceToSimulation; - } - else { - this.x = simulationX; - this.y = simulationY; - } - final float x = this.x; - final float dx = x - this.location[0]; - this.location[0] = x; - final float y = this.y; - final float dy = y - this.location[1]; - this.location[1] = y; - final float groundHeight; - final MovementType movementType = this.simulationUnit.getUnitType().getMovementType(); - final short terrainPathing = map.terrain.pathingGrid.getPathing(x, y); - final boolean swimming = (movementType == MovementType.AMPHIBIOUS) - && PathingGrid.isPathingFlag(terrainPathing, PathingGrid.PathingType.SWIMMABLE) - && !PathingGrid.isPathingFlag(terrainPathing, PathingGrid.PathingType.WALKABLE); - if ((swimming) || (movementType == MovementType.FLOAT) || (movementType == MovementType.FLY) - || (movementType == MovementType.HOVER)) { - groundHeight = Math.max(map.terrain.getGroundHeight(x, y), map.terrain.getWaterHeight(x, y)); - } - else { - groundHeight = map.terrain.getGroundHeight(x, y); - } - if (swimming && !this.swimming) { - this.unitAnimationListenerImpl.addSecondaryTag(AnimationTokens.SecondaryTag.SWIM); - } - else if (!swimming && this.swimming) { - this.unitAnimationListenerImpl.removeSecondaryTag(AnimationTokens.SecondaryTag.SWIM); - } - this.swimming = swimming; - final boolean dead = this.simulationUnit.isDead(); - final boolean corpse = this.simulationUnit.isCorpse(); - final boolean boneCorpse = this.simulationUnit.isBoneCorpse(); - if (dead && !this.dead) { - this.unitAnimationListenerImpl.playAnimation(true, PrimaryTag.DEATH, SequenceUtils.EMPTY, 1.0f, true); - if (this.shadow != null) { - this.shadow.destroy(Gdx.gl30, map.terrain.centerOffset); - this.shadow = null; - } - if (this.selectionCircle != null) { - this.selectionCircle.destroy(Gdx.gl30, map.terrain.centerOffset); - this.selectionCircle = null; - } - } - if (boneCorpse && !this.boneCorpse) { - this.unitAnimationListenerImpl.playAnimationWithDuration(true, PrimaryTag.DECAY, SequenceUtils.BONE, - this.simulationUnit.getEndingDecayTime(map.simulation), true); - } - else if (corpse && !this.corpse) { - this.unitAnimationListenerImpl.playAnimationWithDuration(true, PrimaryTag.DECAY, SequenceUtils.FLESH, - map.simulation.getGameplayConstants().getDecayTime(), true); - } - this.dead = dead; - this.corpse = corpse; - this.boneCorpse = boneCorpse; - this.location[2] = this.simulationUnit.getFlyHeight() + groundHeight; - this.instance.moveTo(this.location); - float simulationFacing = this.simulationUnit.getFacing(); - if (simulationFacing < 0) { - simulationFacing += 360; - } - float renderFacing = this.facing; - if (renderFacing < 0) { - renderFacing += 360; - } - float facingDelta = simulationFacing - renderFacing; - if (facingDelta < -180) { - facingDelta = 360 + facingDelta; - } - if (facingDelta > 180) { - facingDelta = -360 + facingDelta; - } - final float absoluteFacingDelta = Math.abs(facingDelta); - final float turningSign = Math.signum(facingDelta); + public void updateAnimations(final War3MapViewer map) { + final float deltaTime = Gdx.graphics.getDeltaTime(); + final float simulationX = this.simulationUnit.getX(); + final float simulationY = this.simulationUnit.getY(); + final float simDx = simulationX - this.x; + final float simDy = simulationY - this.y; + final float distanceToSimulation = (float) Math.sqrt((simDx * simDx) + (simDy * simDy)); + final int speed = this.simulationUnit.getSpeed(); + final float speedDelta = speed * deltaTime; + if ((distanceToSimulation > speedDelta) && (deltaTime < 1.0)) { + // The 1.0 here says that after 1 second of lag, units just teleport to show + // where they actually are + this.x += (speedDelta * simDx) / distanceToSimulation; + this.y += (speedDelta * simDy) / distanceToSimulation; + } else { + this.x = simulationX; + this.y = simulationY; + } + final float x = this.x; + final float dx = x - this.location[0]; + this.location[0] = x; + final float y = this.y; + final float dy = y - this.location[1]; + this.location[1] = y; + final float groundHeight; + final MovementType movementType = this.simulationUnit.getUnitType().getMovementType(); + final short terrainPathing = map.terrain.pathingGrid.getPathing(x, y); + boolean swimming = (movementType == MovementType.AMPHIBIOUS) + && PathingGrid.isPathingFlag(terrainPathing, PathingGrid.PathingType.SWIMMABLE) + && !PathingGrid.isPathingFlag(terrainPathing, PathingGrid.PathingType.WALKABLE); + float groundHeightTerrain = map.terrain.getGroundHeight(x, y); + float groundHeightTerrainAndWater; + MdxComplexInstance currentWalkableUnder; + boolean standingOnWater = (swimming) || (movementType == MovementType.FLOAT) || (movementType == MovementType.FLY) + || (movementType == MovementType.HOVER); + if (standingOnWater) { + groundHeightTerrainAndWater = Math.max(groundHeightTerrain, map.terrain.getWaterHeight(x, y)); + } else { + // land units will have their feet pass under the surface of the water + groundHeightTerrainAndWater = groundHeightTerrain; + } + if(movementType == MovementType.FLOAT) { + // boats cant go on bridges + groundHeight = groundHeightTerrainAndWater; + currentWalkableUnder = null; + } else { + currentWalkableUnder = map.getHighestWalkableUnder(x, y); + if(currentWalkableUnder != null) { + System.out.println("WALKABLE UNDER"); + } + War3MapViewer.gdxRayHeap.set(x, y, 4096, 0, 0, -8192); + if (currentWalkableUnder != null && currentWalkableUnder.intersectRayWithCollision(War3MapViewer.gdxRayHeap, War3MapViewer.intersectionHeap, true, true) + && War3MapViewer.intersectionHeap.z > groundHeightTerrainAndWater) { + groundHeight = War3MapViewer.intersectionHeap.z; + swimming = false; // Naga Royal Guard should slither across a bridge, not swim in rock + } else { + groundHeight = groundHeightTerrainAndWater; + currentWalkableUnder = null; + } + } + if (swimming && !this.swimming) { + this.unitAnimationListenerImpl.addSecondaryTag(AnimationTokens.SecondaryTag.SWIM); + } else if (!swimming && this.swimming) { + this.unitAnimationListenerImpl.removeSecondaryTag(AnimationTokens.SecondaryTag.SWIM); + } + this.swimming = swimming; + final boolean dead = this.simulationUnit.isDead(); + final boolean corpse = this.simulationUnit.isCorpse(); + final boolean boneCorpse = this.simulationUnit.isBoneCorpse(); + if (dead && !this.dead) { + this.unitAnimationListenerImpl.playAnimation(true, PrimaryTag.DEATH, SequenceUtils.EMPTY, 1.0f, true); + if (this.shadow != null) { + this.shadow.destroy(Gdx.gl30, map.terrain.centerOffset); + this.shadow = null; + } + if (this.selectionCircle != null) { + this.selectionCircle.destroy(Gdx.gl30, map.terrain.centerOffset); + this.selectionCircle = null; + } + } + if (boneCorpse && !this.boneCorpse) { + this.unitAnimationListenerImpl.playAnimationWithDuration(true, PrimaryTag.DECAY, SequenceUtils.BONE, + this.simulationUnit.getEndingDecayTime(map.simulation), true); + } else if (corpse && !this.corpse) { + this.unitAnimationListenerImpl.playAnimationWithDuration(true, PrimaryTag.DECAY, SequenceUtils.FLESH, + map.simulation.getGameplayConstants().getDecayTime(), true); + } + this.dead = dead; + this.corpse = corpse; + this.boneCorpse = boneCorpse; + this.location[2] = this.simulationUnit.getFlyHeight() + groundHeight; + this.instance.moveTo(this.location); + float simulationFacing = this.simulationUnit.getFacing(); + if (simulationFacing < 0) { + simulationFacing += 360; + } + float renderFacing = this.facing; + if (renderFacing < 0) { + renderFacing += 360; + } + float facingDelta = simulationFacing - renderFacing; + if (facingDelta < -180) { + facingDelta = 360 + facingDelta; + } + if (facingDelta > 180) { + facingDelta = -360 + facingDelta; + } + final float absoluteFacingDelta = Math.abs(facingDelta); + final float turningSign = Math.signum(facingDelta); - final float absoluteFacingDeltaRadians = (float) Math.toRadians(absoluteFacingDelta); - float acceleration; - final boolean endPhase = (absoluteFacingDeltaRadians <= this.orientationInterpolation.getEndingAccelCutoff()) - && ((this.currentTurnVelocity * turningSign) > 0); - if (endPhase) { - this.currentTurnVelocity = (1 - - ((this.orientationInterpolation.getEndingAccelCutoff() - absoluteFacingDeltaRadians) - / this.orientationInterpolation.getEndingAccelCutoff())) - * (this.orientationInterpolation.getMaxVelocity()) * turningSign; - } - else { - acceleration = this.orientationInterpolation.getStartingAcceleration() * turningSign; - this.currentTurnVelocity = this.currentTurnVelocity + acceleration; - } - if ((this.currentTurnVelocity * turningSign) > this.orientationInterpolation.getMaxVelocity()) { - this.currentTurnVelocity = this.orientationInterpolation.getMaxVelocity() * turningSign; - } - float angleToAdd = (float) ((Math.toDegrees(this.currentTurnVelocity) * deltaTime) / 0.03f); + final float absoluteFacingDeltaRadians = (float) Math.toRadians(absoluteFacingDelta); + float acceleration; + final boolean endPhase = (absoluteFacingDeltaRadians <= this.orientationInterpolation.getEndingAccelCutoff()) + && ((this.currentTurnVelocity * turningSign) > 0); + if (endPhase) { + this.currentTurnVelocity = (1 + - ((this.orientationInterpolation.getEndingAccelCutoff() - absoluteFacingDeltaRadians) + / this.orientationInterpolation.getEndingAccelCutoff())) + * (this.orientationInterpolation.getMaxVelocity()) * turningSign; + } else { + acceleration = this.orientationInterpolation.getStartingAcceleration() * turningSign; + this.currentTurnVelocity = this.currentTurnVelocity + acceleration; + } + if ((this.currentTurnVelocity * turningSign) > this.orientationInterpolation.getMaxVelocity()) { + this.currentTurnVelocity = this.orientationInterpolation.getMaxVelocity() * turningSign; + } + float angleToAdd = (float) ((Math.toDegrees(this.currentTurnVelocity) * deltaTime) / 0.03f); - if (absoluteFacingDelta < Math.abs(angleToAdd)) { - angleToAdd = facingDelta; - this.currentTurnVelocity = 0.0f; - } - this.facing = (((this.facing + angleToAdd) % 360) + 360) % 360; - this.instance.setLocalRotation(tempQuat.setFromAxis(RenderMathUtils.VEC3_UNIT_Z, this.facing)); + if (absoluteFacingDelta < Math.abs(angleToAdd)) { + angleToAdd = facingDelta; + this.currentTurnVelocity = 0.0f; + } + this.facing = (((this.facing + angleToAdd) % 360) + 360) % 360; + this.instance.setLocalRotation(tempQuat.setFromAxis(RenderMathUtils.VEC3_UNIT_Z, this.facing)); - final float facingRadians = (float) Math.toRadians(this.facing); - final float maxPitch = this.typeData.getMaxPitch(); - final float maxRoll = this.typeData.getMaxRoll(); - final float sampleRadius = this.typeData.getElevationSampleRadius(); - float pitch, roll; - final float pitchSampleForwardX = x + (sampleRadius * (float) Math.cos(facingRadians)); - final float pitchSampleForwardY = y + (sampleRadius * (float) Math.sin(facingRadians)); - final float pitchSampleBackwardX = x - (sampleRadius * (float) Math.cos(facingRadians)); - final float pitchSampleBackwardY = y - (sampleRadius * (float) Math.sin(facingRadians)); - final float pitchSampleGroundHeight1 = map.terrain.getGroundHeight(pitchSampleBackwardX, pitchSampleBackwardY); - final float pitchSampleGorundHeight2 = map.terrain.getGroundHeight(pitchSampleForwardX, pitchSampleForwardY); - pitch = Math.max(-maxPitch, Math.min(maxPitch, - (float) Math.atan2(pitchSampleGorundHeight2 - pitchSampleGroundHeight1, sampleRadius * 2))); - final double leftOfFacingAngle = facingRadians + (Math.PI / 2); - final float rollSampleForwardX = x + (sampleRadius * (float) Math.cos(leftOfFacingAngle)); - final float rollSampleForwardY = y + (sampleRadius * (float) Math.sin(leftOfFacingAngle)); - final float rollSampleBackwardX = x - (sampleRadius * (float) Math.cos(leftOfFacingAngle)); - final float rollSampleBackwardY = y - (sampleRadius * (float) Math.sin(leftOfFacingAngle)); - final float rollSampleGroundHeight1 = map.terrain.getGroundHeight(rollSampleBackwardX, rollSampleBackwardY); - final float rollSampleGroundHeight2 = map.terrain.getGroundHeight(rollSampleForwardX, rollSampleForwardY); - roll = Math.max(-maxRoll, Math.min(maxRoll, - (float) Math.atan2(rollSampleGroundHeight2 - rollSampleGroundHeight1, sampleRadius * 2))); - this.instance.rotate(tempQuat.setFromAxisRad(RenderMathUtils.VEC3_UNIT_Y, -pitch)); - this.instance.rotate(tempQuat.setFromAxisRad(RenderMathUtils.VEC3_UNIT_X, roll)); + final float facingRadians = (float) Math.toRadians(this.facing); + final float maxPitch = this.typeData.getMaxPitch(); + final float maxRoll = this.typeData.getMaxRoll(); + final float sampleRadius = this.typeData.getElevationSampleRadius(); + float pitch, roll; + final float pitchSampleForwardX = x + (sampleRadius * (float) Math.cos(facingRadians)); + final float pitchSampleForwardY = y + (sampleRadius * (float) Math.sin(facingRadians)); + final float pitchSampleBackwardX = x - (sampleRadius * (float) Math.cos(facingRadians)); + final float pitchSampleBackwardY = y - (sampleRadius * (float) Math.sin(facingRadians)); + final double leftOfFacingAngle = facingRadians + (Math.PI / 2); + final float rollSampleForwardX = x + (sampleRadius * (float) Math.cos(leftOfFacingAngle)); + final float rollSampleForwardY = y + (sampleRadius * (float) Math.sin(leftOfFacingAngle)); + final float rollSampleBackwardX = x - (sampleRadius * (float) Math.cos(leftOfFacingAngle)); + final float rollSampleBackwardY = y - (sampleRadius * (float) Math.sin(leftOfFacingAngle)); + final float pitchSampleGroundHeight1; + final float pitchSampleGroundHeight2; + final float rollSampleGroundHeight1; + final float rollSampleGroundHeight2; + if (currentWalkableUnder != null) { + pitchSampleGroundHeight1 = getGroundHeightSample(groundHeight, currentWalkableUnder, pitchSampleBackwardX, pitchSampleBackwardY); + pitchSampleGroundHeight2 = getGroundHeightSample(groundHeight, currentWalkableUnder, pitchSampleForwardX, pitchSampleForwardY); + rollSampleGroundHeight1 = getGroundHeightSample(groundHeight, currentWalkableUnder, rollSampleBackwardX, rollSampleBackwardY); + rollSampleGroundHeight2 = getGroundHeightSample(groundHeight, currentWalkableUnder, rollSampleForwardX, rollSampleForwardY); + } else { + float pitchGroundHeight1 = map.terrain.getGroundHeight(pitchSampleBackwardX, pitchSampleBackwardY); + float pitchGroundHeight2 = map.terrain.getGroundHeight(pitchSampleForwardX, pitchSampleForwardY); + float rollGroundHeight1 = map.terrain.getGroundHeight(rollSampleBackwardX, rollSampleBackwardY); + float rollGroundHeight2 = map.terrain.getGroundHeight(rollSampleForwardX, rollSampleForwardY); + if (standingOnWater) { + pitchSampleGroundHeight1 = Math.max(pitchGroundHeight1, + map.terrain.getWaterHeight(pitchSampleBackwardX, pitchSampleBackwardY)); + pitchSampleGroundHeight2 = Math.max(pitchGroundHeight2, + map.terrain.getWaterHeight(pitchSampleForwardX, pitchSampleForwardY)); + rollSampleGroundHeight1 = Math.max(rollGroundHeight1, + map.terrain.getWaterHeight(rollSampleBackwardX, rollSampleBackwardY)); + rollSampleGroundHeight2 = Math.max(rollGroundHeight2, + map.terrain.getWaterHeight(rollSampleForwardX, rollSampleForwardY)); + } else { + pitchSampleGroundHeight1 = pitchGroundHeight1; + pitchSampleGroundHeight2 = pitchGroundHeight2; + rollSampleGroundHeight1 = rollGroundHeight1; + rollSampleGroundHeight2 = rollGroundHeight2; + } + } + pitch = Math.max(-maxPitch, Math.min(maxPitch, + (float) Math.atan2(pitchSampleGroundHeight2 - pitchSampleGroundHeight1, sampleRadius * 2))); + roll = Math.max(-maxRoll, Math.min(maxRoll, + (float) Math.atan2(rollSampleGroundHeight2 - rollSampleGroundHeight1, sampleRadius * 2))); + this.instance.rotate(tempQuat.setFromAxisRad(RenderMathUtils.VEC3_UNIT_Y, -pitch)); + this.instance.rotate(tempQuat.setFromAxisRad(RenderMathUtils.VEC3_UNIT_X, roll)); - map.worldScene.instanceMoved(this.instance, this.location[0], this.location[1]); - if (this.shadow != null) { - this.shadow.move(dx, dy, map.terrain.centerOffset); - } - if (this.selectionCircle != null) { - this.selectionCircle.move(dx, dy, map.terrain.centerOffset); - } - this.unitAnimationListenerImpl.update(); - } + map.worldScene.instanceMoved(this.instance, this.location[0], this.location[1]); + if (this.shadow != null) { + this.shadow.move(dx, dy, map.terrain.centerOffset); + } + if (this.selectionCircle != null) { + this.selectionCircle.move(dx, dy, map.terrain.centerOffset); + } + this.unitAnimationListenerImpl.update(); + } - public CUnit getSimulationUnit() { - return this.simulationUnit; - } + private float getGroundHeightSample(float groundHeight, MdxComplexInstance currentWalkableUnder, float sampleX, float sampleY) { + final float sampleGroundHeight; + War3MapViewer.gdxRayHeap.origin.x = sampleX; + War3MapViewer.gdxRayHeap.origin.y = sampleY; + if (currentWalkableUnder.intersectRayWithCollision(War3MapViewer.gdxRayHeap, War3MapViewer.intersectionHeap, true, true)) { + sampleGroundHeight = War3MapViewer.intersectionHeap.z; + } else { + sampleGroundHeight = groundHeight; + } + return sampleGroundHeight; + } - private static final class UnitAnimationListenerImpl implements CUnitAnimationListener { - private final MdxComplexInstance instance; - private final EnumSet secondaryAnimationTags = EnumSet - .noneOf(AnimationTokens.SecondaryTag.class); - private final EnumSet recycleSet = EnumSet - .noneOf(AnimationTokens.SecondaryTag.class); - private PrimaryTag currentAnimation; - private EnumSet currentAnimationSecondaryTags; - private float currentSpeedRatio; - private boolean currentlyAllowingRarityVariations; - private final Queue animationQueue = new LinkedList<>(); + public CUnit getSimulationUnit() { + return this.simulationUnit; + } - public UnitAnimationListenerImpl(final MdxComplexInstance instance) { - this.instance = instance; - } + private static final class UnitAnimationListenerImpl implements CUnitAnimationListener { + private final MdxComplexInstance instance; + private final EnumSet secondaryAnimationTags = EnumSet + .noneOf(AnimationTokens.SecondaryTag.class); + private final EnumSet recycleSet = EnumSet + .noneOf(AnimationTokens.SecondaryTag.class); + private PrimaryTag currentAnimation; + private EnumSet currentAnimationSecondaryTags; + private float currentSpeedRatio; + private boolean currentlyAllowingRarityVariations; + private final Queue animationQueue = new LinkedList<>(); - public void addSecondaryTag(final AnimationTokens.SecondaryTag tag) { - this.secondaryAnimationTags.add(tag); - playAnimation(true, this.currentAnimation, this.currentAnimationSecondaryTags, this.currentSpeedRatio, - this.currentlyAllowingRarityVariations); - } + public UnitAnimationListenerImpl(final MdxComplexInstance instance) { + this.instance = instance; + } - public void removeSecondaryTag(final AnimationTokens.SecondaryTag tag) { - this.secondaryAnimationTags.remove(tag); - playAnimation(true, this.currentAnimation, this.currentAnimationSecondaryTags, this.currentSpeedRatio, - this.currentlyAllowingRarityVariations); - } + public void addSecondaryTag(final AnimationTokens.SecondaryTag tag) { + this.secondaryAnimationTags.add(tag); + playAnimation(true, this.currentAnimation, this.currentAnimationSecondaryTags, this.currentSpeedRatio, + this.currentlyAllowingRarityVariations); + } - @Override - public void playAnimation(final boolean force, final PrimaryTag animationName, - final EnumSet secondaryAnimationTags, final float speedRatio, - final boolean allowRarityVariations) { - this.animationQueue.clear(); - if (force || (animationName != this.currentAnimation)) { - this.currentAnimation = animationName; - this.currentAnimationSecondaryTags = secondaryAnimationTags; - this.currentSpeedRatio = speedRatio; - this.currentlyAllowingRarityVariations = allowRarityVariations; - this.recycleSet.clear(); - this.recycleSet.addAll(this.secondaryAnimationTags); - this.recycleSet.addAll(secondaryAnimationTags); - this.instance.setAnimationSpeed(speedRatio); - SequenceUtils.randomSequence(this.instance, animationName, this.recycleSet, allowRarityVariations); - } - } + public void removeSecondaryTag(final AnimationTokens.SecondaryTag tag) { + this.secondaryAnimationTags.remove(tag); + playAnimation(true, this.currentAnimation, this.currentAnimationSecondaryTags, this.currentSpeedRatio, + this.currentlyAllowingRarityVariations); + } - public void playAnimationWithDuration(final boolean force, final PrimaryTag animationName, - final EnumSet secondaryAnimationTags, final float duration, - final boolean allowRarityVariations) { - this.animationQueue.clear(); - if (force || (animationName != this.currentAnimation)) { - this.currentAnimation = animationName; - this.currentAnimationSecondaryTags = secondaryAnimationTags; - this.currentlyAllowingRarityVariations = allowRarityVariations; - this.recycleSet.clear(); - this.recycleSet.addAll(this.secondaryAnimationTags); - this.recycleSet.addAll(secondaryAnimationTags); - final Sequence sequence = SequenceUtils.randomSequence(this.instance, animationName, this.recycleSet, - allowRarityVariations); - if (sequence != null) { - this.currentSpeedRatio = ((sequence.getInterval()[1] - sequence.getInterval()[0]) / 1000.0f) - / duration; - this.instance.setAnimationSpeed(this.currentSpeedRatio); - } - } - } + @Override + public void playAnimation(final boolean force, final PrimaryTag animationName, + final EnumSet secondaryAnimationTags, final float speedRatio, + final boolean allowRarityVariations) { + this.animationQueue.clear(); + if (force || (animationName != this.currentAnimation)) { + this.currentAnimation = animationName; + this.currentAnimationSecondaryTags = secondaryAnimationTags; + this.currentSpeedRatio = speedRatio; + this.currentlyAllowingRarityVariations = allowRarityVariations; + this.recycleSet.clear(); + this.recycleSet.addAll(this.secondaryAnimationTags); + this.recycleSet.addAll(secondaryAnimationTags); + this.instance.setAnimationSpeed(speedRatio); + SequenceUtils.randomSequence(this.instance, animationName, this.recycleSet, allowRarityVariations); + } + } - @Override - public void queueAnimation(final PrimaryTag animationName, final EnumSet secondaryAnimationTags, - final boolean allowRarityVariations) { - this.animationQueue.add(new QueuedAnimation(animationName, secondaryAnimationTags, allowRarityVariations)); - } + public void playAnimationWithDuration(final boolean force, final PrimaryTag animationName, + final EnumSet secondaryAnimationTags, final float duration, + final boolean allowRarityVariations) { + this.animationQueue.clear(); + if (force || (animationName != this.currentAnimation)) { + this.currentAnimation = animationName; + this.currentAnimationSecondaryTags = secondaryAnimationTags; + this.currentlyAllowingRarityVariations = allowRarityVariations; + this.recycleSet.clear(); + this.recycleSet.addAll(this.secondaryAnimationTags); + this.recycleSet.addAll(secondaryAnimationTags); + final Sequence sequence = SequenceUtils.randomSequence(this.instance, animationName, this.recycleSet, + allowRarityVariations); + if (sequence != null) { + this.currentSpeedRatio = ((sequence.getInterval()[1] - sequence.getInterval()[0]) / 1000.0f) + / duration; + this.instance.setAnimationSpeed(this.currentSpeedRatio); + } + } + } - public void update() { - if (this.instance.sequenceEnded || (this.instance.sequence == -1)) { - // animation done - if ((this.instance.sequence != -1) && (((MdxModel) this.instance.model).getSequences() - .get(this.instance.sequence).getFlags() == 0)) { - // animation is a looping animation - playAnimation(true, this.currentAnimation, this.currentAnimationSecondaryTags, - this.currentSpeedRatio, this.currentlyAllowingRarityVariations); - } - else { - final QueuedAnimation nextAnimation = this.animationQueue.poll(); - if (nextAnimation != null) { - playAnimation(true, nextAnimation.animationName, nextAnimation.secondaryAnimationTags, 1.0f, - nextAnimation.allowRarityVariations); - } - } - } - } + @Override + public void queueAnimation(final PrimaryTag animationName, final EnumSet secondaryAnimationTags, + final boolean allowRarityVariations) { + this.animationQueue.add(new QueuedAnimation(animationName, secondaryAnimationTags, allowRarityVariations)); + } - } + public void update() { + if (this.instance.sequenceEnded || (this.instance.sequence == -1)) { + // animation done + if ((this.instance.sequence != -1) && (((MdxModel) this.instance.model).getSequences() + .get(this.instance.sequence).getFlags() == 0)) { + // animation is a looping animation + playAnimation(true, this.currentAnimation, this.currentAnimationSecondaryTags, + this.currentSpeedRatio, this.currentlyAllowingRarityVariations); + } else { + final QueuedAnimation nextAnimation = this.animationQueue.poll(); + if (nextAnimation != null) { + playAnimation(true, nextAnimation.animationName, nextAnimation.secondaryAnimationTags, 1.0f, + nextAnimation.allowRarityVariations); + } + } + } + } - private static final class QueuedAnimation { - private final PrimaryTag animationName; - private final EnumSet secondaryAnimationTags; - private final boolean allowRarityVariations; + } - public QueuedAnimation(final PrimaryTag animationName, final EnumSet secondaryAnimationTags, - final boolean allowRarityVariations) { - this.animationName = animationName; - this.secondaryAnimationTags = secondaryAnimationTags; - this.allowRarityVariations = allowRarityVariations; - } - } + private static final class QueuedAnimation { + private final PrimaryTag animationName; + private final EnumSet secondaryAnimationTags; + private final boolean allowRarityVariations; + + public QueuedAnimation(final PrimaryTag animationName, final EnumSet secondaryAnimationTags, + final boolean allowRarityVariations) { + this.animationName = animationName; + this.secondaryAnimationTags = secondaryAnimationTags; + this.allowRarityVariations = allowRarityVariations; + } + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java index a9ebc8d..c041354 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java @@ -32,388 +32,396 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUni import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.SimulationRenderController; public class CUnitData { - private static final War3ID MANA_INITIAL_AMOUNT = War3ID.fromString("umpi"); - private static final War3ID MANA_MAXIMUM = War3ID.fromString("umpm"); - private static final War3ID HIT_POINT_MAXIMUM = War3ID.fromString("uhpm"); - private static final War3ID MOVEMENT_SPEED_BASE = War3ID.fromString("umvs"); - private static final War3ID PROPULSION_WINDOW = War3ID.fromString("uprw"); - private static final War3ID TURN_RATE = War3ID.fromString("umvr"); - private static final War3ID IS_BLDG = War3ID.fromString("ubdg"); - private static final War3ID NAME = War3ID.fromString("unam"); - private static final War3ID PROJECTILE_LAUNCH_X = War3ID.fromString("ulpx"); - private static final War3ID PROJECTILE_LAUNCH_Y = War3ID.fromString("ulpy"); - private static final War3ID PROJECTILE_LAUNCH_Z = War3ID.fromString("ulpz"); - private static final War3ID ATTACKS_ENABLED = War3ID.fromString("uaen"); - private static final War3ID ATTACK1_BACKSWING_POINT = War3ID.fromString("ubs1"); - private static final War3ID ATTACK1_DAMAGE_POINT = War3ID.fromString("udp1"); - private static final War3ID ATTACK1_AREA_OF_EFFECT_FULL_DMG = War3ID.fromString("ua1f"); - private static final War3ID ATTACK1_AREA_OF_EFFECT_HALF_DMG = War3ID.fromString("ua1h"); - private static final War3ID ATTACK1_AREA_OF_EFFECT_QUARTER_DMG = War3ID.fromString("ua1q"); - private static final War3ID ATTACK1_AREA_OF_EFFECT_TARGETS = War3ID.fromString("ua1p"); - private static final War3ID ATTACK1_ATTACK_TYPE = War3ID.fromString("ua1t"); - private static final War3ID ATTACK1_COOLDOWN = War3ID.fromString("ua1c"); - private static final War3ID ATTACK1_DMG_BASE = War3ID.fromString("ua1b"); - private static final War3ID ATTACK1_DAMAGE_FACTOR_HALF = War3ID.fromString("uhd1"); - private static final War3ID ATTACK1_DAMAGE_FACTOR_QUARTER = War3ID.fromString("uqd1"); - private static final War3ID ATTACK1_DAMAGE_LOSS_FACTOR = War3ID.fromString("udl1"); - private static final War3ID ATTACK1_DMG_DICE = War3ID.fromString("ua1d"); - private static final War3ID ATTACK1_DMG_SIDES_PER_DIE = War3ID.fromString("ua1s"); - private static final War3ID ATTACK1_DMG_SPILL_DIST = War3ID.fromString("usd1"); - private static final War3ID ATTACK1_DMG_SPILL_RADIUS = War3ID.fromString("usr1"); - private static final War3ID ATTACK1_DMG_UPGRADE_AMT = War3ID.fromString("udu1"); - private static final War3ID ATTACK1_TARGET_COUNT = War3ID.fromString("utc1"); - private static final War3ID ATTACK1_PROJECTILE_ARC = War3ID.fromString("uma1"); - private static final War3ID ATTACK1_MISSILE_ART = War3ID.fromString("ua1m"); - private static final War3ID ATTACK1_PROJECTILE_HOMING_ENABLED = War3ID.fromString("umh1"); - private static final War3ID ATTACK1_PROJECTILE_SPEED = War3ID.fromString("ua1z"); - private static final War3ID ATTACK1_RANGE = War3ID.fromString("ua1r"); - private static final War3ID ATTACK1_RANGE_MOTION_BUFFER = War3ID.fromString("urb1"); - private static final War3ID ATTACK1_SHOW_UI = War3ID.fromString("uwu1"); - private static final War3ID ATTACK1_TARGETS_ALLOWED = War3ID.fromString("ua1g"); - private static final War3ID ATTACK1_WEAPON_SOUND = War3ID.fromString("ucs1"); - private static final War3ID ATTACK1_WEAPON_TYPE = War3ID.fromString("ua1w"); + private static final War3ID MANA_INITIAL_AMOUNT = War3ID.fromString("umpi"); + private static final War3ID MANA_MAXIMUM = War3ID.fromString("umpm"); + private static final War3ID HIT_POINT_MAXIMUM = War3ID.fromString("uhpm"); + private static final War3ID MOVEMENT_SPEED_BASE = War3ID.fromString("umvs"); + private static final War3ID PROPULSION_WINDOW = War3ID.fromString("uprw"); + private static final War3ID TURN_RATE = War3ID.fromString("umvr"); + private static final War3ID IS_BLDG = War3ID.fromString("ubdg"); + private static final War3ID NAME = War3ID.fromString("unam"); + private static final War3ID PROJECTILE_LAUNCH_X = War3ID.fromString("ulpx"); + private static final War3ID PROJECTILE_LAUNCH_Y = War3ID.fromString("ulpy"); + private static final War3ID PROJECTILE_LAUNCH_Z = War3ID.fromString("ulpz"); + private static final War3ID ATTACKS_ENABLED = War3ID.fromString("uaen"); + private static final War3ID ATTACK1_BACKSWING_POINT = War3ID.fromString("ubs1"); + private static final War3ID ATTACK1_DAMAGE_POINT = War3ID.fromString("udp1"); + private static final War3ID ATTACK1_AREA_OF_EFFECT_FULL_DMG = War3ID.fromString("ua1f"); + private static final War3ID ATTACK1_AREA_OF_EFFECT_HALF_DMG = War3ID.fromString("ua1h"); + private static final War3ID ATTACK1_AREA_OF_EFFECT_QUARTER_DMG = War3ID.fromString("ua1q"); + private static final War3ID ATTACK1_AREA_OF_EFFECT_TARGETS = War3ID.fromString("ua1p"); + private static final War3ID ATTACK1_ATTACK_TYPE = War3ID.fromString("ua1t"); + private static final War3ID ATTACK1_COOLDOWN = War3ID.fromString("ua1c"); + private static final War3ID ATTACK1_DMG_BASE = War3ID.fromString("ua1b"); + private static final War3ID ATTACK1_DAMAGE_FACTOR_HALF = War3ID.fromString("uhd1"); + private static final War3ID ATTACK1_DAMAGE_FACTOR_QUARTER = War3ID.fromString("uqd1"); + private static final War3ID ATTACK1_DAMAGE_LOSS_FACTOR = War3ID.fromString("udl1"); + private static final War3ID ATTACK1_DMG_DICE = War3ID.fromString("ua1d"); + private static final War3ID ATTACK1_DMG_SIDES_PER_DIE = War3ID.fromString("ua1s"); + private static final War3ID ATTACK1_DMG_SPILL_DIST = War3ID.fromString("usd1"); + private static final War3ID ATTACK1_DMG_SPILL_RADIUS = War3ID.fromString("usr1"); + private static final War3ID ATTACK1_DMG_UPGRADE_AMT = War3ID.fromString("udu1"); + private static final War3ID ATTACK1_TARGET_COUNT = War3ID.fromString("utc1"); + private static final War3ID ATTACK1_PROJECTILE_ARC = War3ID.fromString("uma1"); + private static final War3ID ATTACK1_MISSILE_ART = War3ID.fromString("ua1m"); + private static final War3ID ATTACK1_PROJECTILE_HOMING_ENABLED = War3ID.fromString("umh1"); + private static final War3ID ATTACK1_PROJECTILE_SPEED = War3ID.fromString("ua1z"); + private static final War3ID ATTACK1_RANGE = War3ID.fromString("ua1r"); + private static final War3ID ATTACK1_RANGE_MOTION_BUFFER = War3ID.fromString("urb1"); + private static final War3ID ATTACK1_SHOW_UI = War3ID.fromString("uwu1"); + private static final War3ID ATTACK1_TARGETS_ALLOWED = War3ID.fromString("ua1g"); + private static final War3ID ATTACK1_WEAPON_SOUND = War3ID.fromString("ucs1"); + private static final War3ID ATTACK1_WEAPON_TYPE = War3ID.fromString("ua1w"); - private static final War3ID ATTACK2_BACKSWING_POINT = War3ID.fromString("ubs2"); - private static final War3ID ATTACK2_DAMAGE_POINT = War3ID.fromString("udp2"); - private static final War3ID ATTACK2_AREA_OF_EFFECT_FULL_DMG = War3ID.fromString("ua2f"); - private static final War3ID ATTACK2_AREA_OF_EFFECT_HALF_DMG = War3ID.fromString("ua2h"); - private static final War3ID ATTACK2_AREA_OF_EFFECT_QUARTER_DMG = War3ID.fromString("ua2q"); - private static final War3ID ATTACK2_AREA_OF_EFFECT_TARGETS = War3ID.fromString("ua2p"); - private static final War3ID ATTACK2_ATTACK_TYPE = War3ID.fromString("ua2t"); - private static final War3ID ATTACK2_COOLDOWN = War3ID.fromString("ua2c"); - private static final War3ID ATTACK2_DMG_BASE = War3ID.fromString("ua2b"); - private static final War3ID ATTACK2_DAMAGE_FACTOR_HALF = War3ID.fromString("uhd2"); - private static final War3ID ATTACK2_DAMAGE_FACTOR_QUARTER = War3ID.fromString("uqd2"); - private static final War3ID ATTACK2_DAMAGE_LOSS_FACTOR = War3ID.fromString("udl2"); - private static final War3ID ATTACK2_DMG_DICE = War3ID.fromString("ua2d"); - private static final War3ID ATTACK2_DMG_SIDES_PER_DIE = War3ID.fromString("ua2s"); - private static final War3ID ATTACK2_DMG_SPILL_DIST = War3ID.fromString("usd2"); - private static final War3ID ATTACK2_DMG_SPILL_RADIUS = War3ID.fromString("usr2"); - private static final War3ID ATTACK2_DMG_UPGRADE_AMT = War3ID.fromString("udu2"); - private static final War3ID ATTACK2_TARGET_COUNT = War3ID.fromString("utc2"); - private static final War3ID ATTACK2_PROJECTILE_ARC = War3ID.fromString("uma2"); - private static final War3ID ATTACK2_MISSILE_ART = War3ID.fromString("ua2m"); - private static final War3ID ATTACK2_PROJECTILE_HOMING_ENABLED = War3ID.fromString("umh2"); - private static final War3ID ATTACK2_PROJECTILE_SPEED = War3ID.fromString("ua2z"); - private static final War3ID ATTACK2_RANGE = War3ID.fromString("ua2r"); - private static final War3ID ATTACK2_RANGE_MOTION_BUFFER = War3ID.fromString("urb2"); - private static final War3ID ATTACK2_SHOW_UI = War3ID.fromString("uwu2"); - private static final War3ID ATTACK2_TARGETS_ALLOWED = War3ID.fromString("ua2g"); - private static final War3ID ATTACK2_WEAPON_SOUND = War3ID.fromString("ucs2"); - private static final War3ID ATTACK2_WEAPON_TYPE = War3ID.fromString("ua2w"); + private static final War3ID ATTACK2_BACKSWING_POINT = War3ID.fromString("ubs2"); + private static final War3ID ATTACK2_DAMAGE_POINT = War3ID.fromString("udp2"); + private static final War3ID ATTACK2_AREA_OF_EFFECT_FULL_DMG = War3ID.fromString("ua2f"); + private static final War3ID ATTACK2_AREA_OF_EFFECT_HALF_DMG = War3ID.fromString("ua2h"); + private static final War3ID ATTACK2_AREA_OF_EFFECT_QUARTER_DMG = War3ID.fromString("ua2q"); + private static final War3ID ATTACK2_AREA_OF_EFFECT_TARGETS = War3ID.fromString("ua2p"); + private static final War3ID ATTACK2_ATTACK_TYPE = War3ID.fromString("ua2t"); + private static final War3ID ATTACK2_COOLDOWN = War3ID.fromString("ua2c"); + private static final War3ID ATTACK2_DMG_BASE = War3ID.fromString("ua2b"); + private static final War3ID ATTACK2_DAMAGE_FACTOR_HALF = War3ID.fromString("uhd2"); + private static final War3ID ATTACK2_DAMAGE_FACTOR_QUARTER = War3ID.fromString("uqd2"); + private static final War3ID ATTACK2_DAMAGE_LOSS_FACTOR = War3ID.fromString("udl2"); + private static final War3ID ATTACK2_DMG_DICE = War3ID.fromString("ua2d"); + private static final War3ID ATTACK2_DMG_SIDES_PER_DIE = War3ID.fromString("ua2s"); + private static final War3ID ATTACK2_DMG_SPILL_DIST = War3ID.fromString("usd2"); + private static final War3ID ATTACK2_DMG_SPILL_RADIUS = War3ID.fromString("usr2"); + private static final War3ID ATTACK2_DMG_UPGRADE_AMT = War3ID.fromString("udu2"); + private static final War3ID ATTACK2_TARGET_COUNT = War3ID.fromString("utc2"); + private static final War3ID ATTACK2_PROJECTILE_ARC = War3ID.fromString("uma2"); + private static final War3ID ATTACK2_MISSILE_ART = War3ID.fromString("ua2m"); + private static final War3ID ATTACK2_PROJECTILE_HOMING_ENABLED = War3ID.fromString("umh2"); + private static final War3ID ATTACK2_PROJECTILE_SPEED = War3ID.fromString("ua2z"); + private static final War3ID ATTACK2_RANGE = War3ID.fromString("ua2r"); + private static final War3ID ATTACK2_RANGE_MOTION_BUFFER = War3ID.fromString("urb2"); + private static final War3ID ATTACK2_SHOW_UI = War3ID.fromString("uwu2"); + private static final War3ID ATTACK2_TARGETS_ALLOWED = War3ID.fromString("ua2g"); + private static final War3ID ATTACK2_WEAPON_SOUND = War3ID.fromString("ucs2"); + private static final War3ID ATTACK2_WEAPON_TYPE = War3ID.fromString("ua2w"); - private static final War3ID ACQUISITION_RANGE = War3ID.fromString("uacq"); - private static final War3ID MINIMUM_ATTACK_RANGE = War3ID.fromString("uamn"); + private static final War3ID ACQUISITION_RANGE = War3ID.fromString("uacq"); + private static final War3ID MINIMUM_ATTACK_RANGE = War3ID.fromString("uamn"); - private static final War3ID PROJECTILE_IMPACT_Z = War3ID.fromString("uimz"); + private static final War3ID PROJECTILE_IMPACT_Z = War3ID.fromString("uimz"); - private static final War3ID DEATH_TYPE = War3ID.fromString("udea"); - private static final War3ID ARMOR_TYPE = War3ID.fromString("uarm"); + private static final War3ID DEATH_TYPE = War3ID.fromString("udea"); + private static final War3ID ARMOR_TYPE = War3ID.fromString("uarm"); - private static final War3ID DEFENSE = War3ID.fromString("udef"); - private static final War3ID DEFENSE_TYPE = War3ID.fromString("udty"); - private static final War3ID MOVE_HEIGHT = War3ID.fromString("umvh"); - private static final War3ID MOVE_TYPE = War3ID.fromString("umvt"); - private static final War3ID COLLISION_SIZE = War3ID.fromString("ucol"); - private static final War3ID CLASSIFICATION = War3ID.fromString("utyp"); - private static final War3ID DEATH_TIME = War3ID.fromString("udtm"); - private static final War3ID TARGETED_AS = War3ID.fromString("utar"); - private final MutableObjectData unitData; - private final Map unitIdToUnitType = new HashMap<>(); + private static final War3ID DEFENSE = War3ID.fromString("udef"); + private static final War3ID DEFENSE_TYPE = War3ID.fromString("udty"); + private static final War3ID MOVE_HEIGHT = War3ID.fromString("umvh"); + private static final War3ID MOVE_TYPE = War3ID.fromString("umvt"); + private static final War3ID COLLISION_SIZE = War3ID.fromString("ucol"); + private static final War3ID CLASSIFICATION = War3ID.fromString("utyp"); + private static final War3ID DEATH_TIME = War3ID.fromString("udtm"); + private static final War3ID TARGETED_AS = War3ID.fromString("utar"); + private final MutableObjectData unitData; + private final Map unitIdToUnitType = new HashMap<>(); - public CUnitData(final MutableObjectData unitData) { - this.unitData = unitData; - } + public CUnitData(final MutableObjectData unitData) { + this.unitData = unitData; + } - public CUnit create(final CSimulation simulation, final int playerIndex, final War3ID typeId, final float x, - final float y, final float facing, final BufferedImage buildingPathingPixelMap, - final SimulationRenderController simulationRenderController, final HandleIdAllocator handleIdAllocator) { - final MutableGameObject unitType = this.unitData.get(typeId); - final int handleId = handleIdAllocator.createId(); - final int life = unitType.getFieldAsInteger(HIT_POINT_MAXIMUM, 0); - final int manaInitial = unitType.getFieldAsInteger(MANA_INITIAL_AMOUNT, 0); - final int manaMaximum = unitType.getFieldAsInteger(MANA_MAXIMUM, 0); - final int speed = unitType.getFieldAsInteger(MOVEMENT_SPEED_BASE, 0); - final int defense = unitType.getFieldAsInteger(DEFENSE, 0); + public CUnit create(final CSimulation simulation, final int playerIndex, final War3ID typeId, final float x, + final float y, final float facing, final BufferedImage buildingPathingPixelMap, + final SimulationRenderController simulationRenderController, final HandleIdAllocator handleIdAllocator) { + final MutableGameObject unitType = this.unitData.get(typeId); + final int handleId = handleIdAllocator.createId(); + final int life = unitType.getFieldAsInteger(HIT_POINT_MAXIMUM, 0); + final int manaInitial = unitType.getFieldAsInteger(MANA_INITIAL_AMOUNT, 0); + final int manaMaximum = unitType.getFieldAsInteger(MANA_MAXIMUM, 0); + final int speed = unitType.getFieldAsInteger(MOVEMENT_SPEED_BASE, 0); + final int defense = unitType.getFieldAsInteger(DEFENSE, 0); - final CUnitType unitTypeInstance = getUnitTypeInstance(typeId, buildingPathingPixelMap, unitType); + final CUnitType unitTypeInstance = getUnitTypeInstance(typeId, buildingPathingPixelMap, unitType); - final CUnit unit = new CUnit(handleId, playerIndex, x, y, life, typeId, facing, manaInitial, life, manaMaximum, - speed, defense, unitTypeInstance); - if (speed > 0) { - unit.add(simulation, new CAbilityMove(handleIdAllocator.createId())); - } - if (!unitTypeInstance.getAttacks().isEmpty()) { - unit.add(simulation, new CAbilityAttack(handleIdAllocator.createId())); - } - return unit; - } + final CUnit unit = new CUnit(handleId, playerIndex, x, y, life, typeId, facing, manaInitial, life, manaMaximum, + speed, defense, unitTypeInstance); + if (speed > 0) { + unit.add(simulation, new CAbilityMove(handleIdAllocator.createId())); + } + if (!unitTypeInstance.getAttacks().isEmpty()) { + unit.add(simulation, new CAbilityAttack(handleIdAllocator.createId())); + } + return unit; + } - private CUnitType getUnitTypeInstance(final War3ID typeId, final BufferedImage buildingPathingPixelMap, - final MutableGameObject unitType) { - CUnitType unitTypeInstance = this.unitIdToUnitType.get(typeId); - if (unitTypeInstance == null) { - final float moveHeight = unitType.getFieldAsFloat(MOVE_HEIGHT, 0); - final String movetp = unitType.getFieldAsString(MOVE_TYPE, 0); - final float collisionSize = unitType.getFieldAsFloat(COLLISION_SIZE, 0); - final boolean isBldg = unitType.getFieldAsBoolean(IS_BLDG, 0); - final PathingGrid.MovementType movementType = PathingGrid.getMovementType(movetp); - final String unitName = unitType.getFieldAsString(NAME, 0); - final float acquisitionRange = unitType.getFieldAsFloat(ACQUISITION_RANGE, 0); - final float minimumAttackRange = unitType.getFieldAsFloat(MINIMUM_ATTACK_RANGE, 0); - final EnumSet targetedAs = CTargetType - .parseTargetTypeSet(unitType.getFieldAsString(TARGETED_AS, 0)); - final String classificationString = unitType.getFieldAsString(CLASSIFICATION, 0); - final EnumSet classifications = EnumSet.noneOf(CUnitClassification.class); - if (classificationString != null) { - final String[] classificationValues = classificationString.split(","); - for (final String unitEditorKey : classificationValues) { - final CUnitClassification unitClassification = CUnitClassification - .parseUnitClassification(unitEditorKey); - if (unitClassification != null) { - classifications.add(unitClassification); - } - } - } - final List attacks = new ArrayList<>(); - final int attacksEnabled = unitType.getFieldAsInteger(ATTACKS_ENABLED, 0); - if ((attacksEnabled & 0x1) != 0) { - // attack one - final float animationBackswingPoint = unitType.getFieldAsFloat(ATTACK1_BACKSWING_POINT, 0); - final float animationDamagePoint = unitType.getFieldAsFloat(ATTACK1_DAMAGE_POINT, 0); - final int areaOfEffectFullDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_FULL_DMG, 0); - final int areaOfEffectMediumDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_HALF_DMG, 0); - final int areaOfEffectSmallDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_QUARTER_DMG, 0); - final EnumSet areaOfEffectTargets = CTargetType - .parseTargetTypeSet(unitType.getFieldAsString(ATTACK1_AREA_OF_EFFECT_TARGETS, 0)); - final CAttackType attackType = CAttackType - .parseAttackType(unitType.getFieldAsString(ATTACK1_ATTACK_TYPE, 0)); - final float cooldownTime = unitType.getFieldAsFloat(ATTACK1_COOLDOWN, 0); - final int damageBase = unitType.getFieldAsInteger(ATTACK1_DMG_BASE, 0); - final float damageFactorMedium = unitType.getFieldAsFloat(ATTACK1_DAMAGE_FACTOR_HALF, 0); - final float damageFactorSmall = unitType.getFieldAsFloat(ATTACK1_DAMAGE_FACTOR_QUARTER, 0); - final float damageLossFactor = unitType.getFieldAsFloat(ATTACK1_DAMAGE_LOSS_FACTOR, 0); - final int damageDice = unitType.getFieldAsInteger(ATTACK1_DMG_DICE, 0); - final int damageSidesPerDie = unitType.getFieldAsInteger(ATTACK1_DMG_SIDES_PER_DIE, 0); - final float damageSpillDistance = unitType.getFieldAsFloat(ATTACK1_DMG_SPILL_DIST, 0); - final float damageSpillRadius = unitType.getFieldAsFloat(ATTACK1_DMG_SPILL_RADIUS, 0); - final int damageUpgradeAmount = unitType.getFieldAsInteger(ATTACK1_DMG_UPGRADE_AMT, 0); - final int maximumNumberOfTargets = unitType.getFieldAsInteger(ATTACK1_TARGET_COUNT, 0); - final float projectileArc = unitType.getFieldAsFloat(ATTACK1_PROJECTILE_ARC, 0); - final String projectileArt = unitType.getFieldAsString(ATTACK1_MISSILE_ART, 0); - final boolean projectileHomingEnabled = unitType.getFieldAsBoolean(ATTACK1_PROJECTILE_HOMING_ENABLED, - 0); - final int projectileSpeed = unitType.getFieldAsInteger(ATTACK1_PROJECTILE_SPEED, 0); - final int range = unitType.getFieldAsInteger(ATTACK1_RANGE, 0); - final float rangeMotionBuffer = unitType.getFieldAsFloat(ATTACK1_RANGE_MOTION_BUFFER, 0); - final boolean showUI = unitType.getFieldAsBoolean(ATTACK1_SHOW_UI, 0); - final EnumSet targetsAllowed = CTargetType - .parseTargetTypeSet(unitType.getFieldAsString(ATTACK1_TARGETS_ALLOWED, 0)); - final String weaponSound = unitType.getFieldAsString(ATTACK1_WEAPON_SOUND, 0); - final CWeaponType weaponType = CWeaponType - .parseWeaponType(unitType.getFieldAsString(ATTACK1_WEAPON_TYPE, 0)); - attacks.add(createAttack(animationBackswingPoint, animationDamagePoint, areaOfEffectFullDamage, - areaOfEffectMediumDamage, areaOfEffectSmallDamage, areaOfEffectTargets, attackType, - cooldownTime, damageBase, damageFactorMedium, damageFactorSmall, damageLossFactor, damageDice, - damageSidesPerDie, damageSpillDistance, damageSpillRadius, damageUpgradeAmount, - maximumNumberOfTargets, projectileArc, projectileArt, projectileHomingEnabled, projectileSpeed, - range, rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType)); - } - if ((attacksEnabled & 0x2) != 0) { - // attack two - final float animationBackswingPoint = unitType.getFieldAsFloat(ATTACK2_BACKSWING_POINT, 0); - final float animationDamagePoint = unitType.getFieldAsFloat(ATTACK2_DAMAGE_POINT, 0); - final int areaOfEffectFullDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_FULL_DMG, 0); - final int areaOfEffectMediumDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_HALF_DMG, 0); - final int areaOfEffectSmallDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_QUARTER_DMG, 0); - final EnumSet areaOfEffectTargets = CTargetType - .parseTargetTypeSet(unitType.getFieldAsString(ATTACK2_AREA_OF_EFFECT_TARGETS, 0)); - final CAttackType attackType = CAttackType - .parseAttackType(unitType.getFieldAsString(ATTACK2_ATTACK_TYPE, 0)); - final float cooldownTime = unitType.getFieldAsFloat(ATTACK2_COOLDOWN, 0); - final int damageBase = unitType.getFieldAsInteger(ATTACK2_DMG_BASE, 0); - final float damageFactorMedium = unitType.getFieldAsFloat(ATTACK2_DAMAGE_FACTOR_HALF, 0); - final float damageFactorSmall = unitType.getFieldAsFloat(ATTACK2_DAMAGE_FACTOR_QUARTER, 0); - final float damageLossFactor = unitType.getFieldAsFloat(ATTACK2_DAMAGE_LOSS_FACTOR, 0); - final int damageDice = unitType.getFieldAsInteger(ATTACK2_DMG_DICE, 0); - final int damageSidesPerDie = unitType.getFieldAsInteger(ATTACK2_DMG_SIDES_PER_DIE, 0); - final float damageSpillDistance = unitType.getFieldAsFloat(ATTACK2_DMG_SPILL_DIST, 0); - final float damageSpillRadius = unitType.getFieldAsFloat(ATTACK2_DMG_SPILL_RADIUS, 0); - final int damageUpgradeAmount = unitType.getFieldAsInteger(ATTACK2_DMG_UPGRADE_AMT, 0); - final int maximumNumberOfTargets = unitType.getFieldAsInteger(ATTACK2_TARGET_COUNT, 0); - final float projectileArc = unitType.getFieldAsFloat(ATTACK2_PROJECTILE_ARC, 0); - final String projectileArt = unitType.getFieldAsString(ATTACK2_MISSILE_ART, 0); - final boolean projectileHomingEnabled = unitType.getFieldAsBoolean(ATTACK2_PROJECTILE_HOMING_ENABLED, - 0); - final int projectileSpeed = unitType.getFieldAsInteger(ATTACK2_PROJECTILE_SPEED, 0); - final int range = unitType.getFieldAsInteger(ATTACK2_RANGE, 0); - final float rangeMotionBuffer = unitType.getFieldAsFloat(ATTACK2_RANGE_MOTION_BUFFER, 0); - final boolean showUI = unitType.getFieldAsBoolean(ATTACK2_SHOW_UI, 0); - final EnumSet targetsAllowed = CTargetType - .parseTargetTypeSet(unitType.getFieldAsString(ATTACK2_TARGETS_ALLOWED, 0)); - final String weaponSound = unitType.getFieldAsString(ATTACK2_WEAPON_SOUND, 0); - final CWeaponType weaponType = CWeaponType - .parseWeaponType(unitType.getFieldAsString(ATTACK2_WEAPON_TYPE, 0)); - attacks.add(createAttack(animationBackswingPoint, animationDamagePoint, areaOfEffectFullDamage, - areaOfEffectMediumDamage, areaOfEffectSmallDamage, areaOfEffectTargets, attackType, - cooldownTime, damageBase, damageFactorMedium, damageFactorSmall, damageLossFactor, damageDice, - damageSidesPerDie, damageSpillDistance, damageSpillRadius, damageUpgradeAmount, - maximumNumberOfTargets, projectileArc, projectileArt, projectileHomingEnabled, projectileSpeed, - range, rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType)); - } - final int deathType = unitType.getFieldAsInteger(DEATH_TYPE, 0); - final boolean raise = (deathType & 0x1) != 0; - final boolean decay = (deathType & 0x2) != 0; - final String armorType = unitType.getFieldAsString(ARMOR_TYPE, 0); - final float impactZ = unitType.getFieldAsFloat(PROJECTILE_IMPACT_Z, 0); - final CDefenseType defenseType = CDefenseType.parseDefenseType(unitType.getFieldAsString(DEFENSE_TYPE, 0)); - final float deathTime = unitType.getFieldAsFloat(DEATH_TIME, 0); - unitTypeInstance = new CUnitType(unitName, isBldg, movementType, moveHeight, collisionSize, classifications, - attacks, armorType, raise, decay, defenseType, impactZ, buildingPathingPixelMap, deathTime, - targetedAs, acquisitionRange, minimumAttackRange); - this.unitIdToUnitType.put(typeId, unitTypeInstance); - } - return unitTypeInstance; - } + private CUnitType getUnitTypeInstance(final War3ID typeId, final BufferedImage buildingPathingPixelMap, + final MutableGameObject unitType) { + CUnitType unitTypeInstance = this.unitIdToUnitType.get(typeId); + if (unitTypeInstance == null) { + final float moveHeight = unitType.getFieldAsFloat(MOVE_HEIGHT, 0); + final String movetp = unitType.getFieldAsString(MOVE_TYPE, 0); + final float collisionSize = unitType.getFieldAsFloat(COLLISION_SIZE, 0); + final boolean isBldg = unitType.getFieldAsBoolean(IS_BLDG, 0); + final PathingGrid.MovementType movementType = PathingGrid.getMovementType(movetp); + final String unitName = unitType.getFieldAsString(NAME, 0); + final float acquisitionRange = unitType.getFieldAsFloat(ACQUISITION_RANGE, 0); + final float minimumAttackRange = unitType.getFieldAsFloat(MINIMUM_ATTACK_RANGE, 0); + final EnumSet targetedAs = CTargetType + .parseTargetTypeSet(unitType.getFieldAsString(TARGETED_AS, 0)); + final String classificationString = unitType.getFieldAsString(CLASSIFICATION, 0); + final EnumSet classifications = EnumSet.noneOf(CUnitClassification.class); + if (classificationString != null) { + final String[] classificationValues = classificationString.split(","); + for (final String unitEditorKey : classificationValues) { + final CUnitClassification unitClassification = CUnitClassification + .parseUnitClassification(unitEditorKey); + if (unitClassification != null) { + classifications.add(unitClassification); + } + } + } + final List attacks = new ArrayList<>(); + final int attacksEnabled = unitType.getFieldAsInteger(ATTACKS_ENABLED, 0); + if ((attacksEnabled & 0x1) != 0) { + try { + // attack one + final float animationBackswingPoint = unitType.getFieldAsFloat(ATTACK1_BACKSWING_POINT, 0); + final float animationDamagePoint = unitType.getFieldAsFloat(ATTACK1_DAMAGE_POINT, 0); + final int areaOfEffectFullDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_FULL_DMG, 0); + final int areaOfEffectMediumDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_HALF_DMG, 0); + final int areaOfEffectSmallDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_QUARTER_DMG, 0); + final EnumSet areaOfEffectTargets = CTargetType + .parseTargetTypeSet(unitType.getFieldAsString(ATTACK1_AREA_OF_EFFECT_TARGETS, 0)); + final CAttackType attackType = CAttackType + .parseAttackType(unitType.getFieldAsString(ATTACK1_ATTACK_TYPE, 0)); + final float cooldownTime = unitType.getFieldAsFloat(ATTACK1_COOLDOWN, 0); + final int damageBase = unitType.getFieldAsInteger(ATTACK1_DMG_BASE, 0); + final float damageFactorMedium = unitType.getFieldAsFloat(ATTACK1_DAMAGE_FACTOR_HALF, 0); + final float damageFactorSmall = unitType.getFieldAsFloat(ATTACK1_DAMAGE_FACTOR_QUARTER, 0); + final float damageLossFactor = unitType.getFieldAsFloat(ATTACK1_DAMAGE_LOSS_FACTOR, 0); + final int damageDice = unitType.getFieldAsInteger(ATTACK1_DMG_DICE, 0); + final int damageSidesPerDie = unitType.getFieldAsInteger(ATTACK1_DMG_SIDES_PER_DIE, 0); + final float damageSpillDistance = unitType.getFieldAsFloat(ATTACK1_DMG_SPILL_DIST, 0); + final float damageSpillRadius = unitType.getFieldAsFloat(ATTACK1_DMG_SPILL_RADIUS, 0); + final int damageUpgradeAmount = unitType.getFieldAsInteger(ATTACK1_DMG_UPGRADE_AMT, 0); + final int maximumNumberOfTargets = unitType.getFieldAsInteger(ATTACK1_TARGET_COUNT, 0); + final float projectileArc = unitType.getFieldAsFloat(ATTACK1_PROJECTILE_ARC, 0); + final String projectileArt = unitType.getFieldAsString(ATTACK1_MISSILE_ART, 0); + final boolean projectileHomingEnabled = unitType.getFieldAsBoolean(ATTACK1_PROJECTILE_HOMING_ENABLED, + 0); + final int projectileSpeed = unitType.getFieldAsInteger(ATTACK1_PROJECTILE_SPEED, 0); + final int range = unitType.getFieldAsInteger(ATTACK1_RANGE, 0); + final float rangeMotionBuffer = unitType.getFieldAsFloat(ATTACK1_RANGE_MOTION_BUFFER, 0); + final boolean showUI = unitType.getFieldAsBoolean(ATTACK1_SHOW_UI, 0); + final EnumSet targetsAllowed = CTargetType + .parseTargetTypeSet(unitType.getFieldAsString(ATTACK1_TARGETS_ALLOWED, 0)); + final String weaponSound = unitType.getFieldAsString(ATTACK1_WEAPON_SOUND, 0); + final CWeaponType weaponType = CWeaponType + .parseWeaponType(unitType.getFieldAsString(ATTACK1_WEAPON_TYPE, 0)); + attacks.add(createAttack(animationBackswingPoint, animationDamagePoint, areaOfEffectFullDamage, + areaOfEffectMediumDamage, areaOfEffectSmallDamage, areaOfEffectTargets, attackType, + cooldownTime, damageBase, damageFactorMedium, damageFactorSmall, damageLossFactor, damageDice, + damageSidesPerDie, damageSpillDistance, damageSpillRadius, damageUpgradeAmount, + maximumNumberOfTargets, projectileArc, projectileArt, projectileHomingEnabled, projectileSpeed, + range, rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType)); + } catch (Exception exc) { + System.err.println("Attack 1 failed to parse with: " + exc.getClass() + ":" + exc.getMessage()); + } + } + if ((attacksEnabled & 0x2) != 0) { + try { + // attack two + final float animationBackswingPoint = unitType.getFieldAsFloat(ATTACK2_BACKSWING_POINT, 0); + final float animationDamagePoint = unitType.getFieldAsFloat(ATTACK2_DAMAGE_POINT, 0); + final int areaOfEffectFullDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_FULL_DMG, 0); + final int areaOfEffectMediumDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_HALF_DMG, 0); + final int areaOfEffectSmallDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_QUARTER_DMG, 0); + final EnumSet areaOfEffectTargets = CTargetType + .parseTargetTypeSet(unitType.getFieldAsString(ATTACK2_AREA_OF_EFFECT_TARGETS, 0)); + final CAttackType attackType = CAttackType + .parseAttackType(unitType.getFieldAsString(ATTACK2_ATTACK_TYPE, 0)); + final float cooldownTime = unitType.getFieldAsFloat(ATTACK2_COOLDOWN, 0); + final int damageBase = unitType.getFieldAsInteger(ATTACK2_DMG_BASE, 0); + final float damageFactorMedium = unitType.getFieldAsFloat(ATTACK2_DAMAGE_FACTOR_HALF, 0); + final float damageFactorSmall = unitType.getFieldAsFloat(ATTACK2_DAMAGE_FACTOR_QUARTER, 0); + final float damageLossFactor = unitType.getFieldAsFloat(ATTACK2_DAMAGE_LOSS_FACTOR, 0); + final int damageDice = unitType.getFieldAsInteger(ATTACK2_DMG_DICE, 0); + final int damageSidesPerDie = unitType.getFieldAsInteger(ATTACK2_DMG_SIDES_PER_DIE, 0); + final float damageSpillDistance = unitType.getFieldAsFloat(ATTACK2_DMG_SPILL_DIST, 0); + final float damageSpillRadius = unitType.getFieldAsFloat(ATTACK2_DMG_SPILL_RADIUS, 0); + final int damageUpgradeAmount = unitType.getFieldAsInteger(ATTACK2_DMG_UPGRADE_AMT, 0); + final int maximumNumberOfTargets = unitType.getFieldAsInteger(ATTACK2_TARGET_COUNT, 0); + final float projectileArc = unitType.getFieldAsFloat(ATTACK2_PROJECTILE_ARC, 0); + final String projectileArt = unitType.getFieldAsString(ATTACK2_MISSILE_ART, 0); + final boolean projectileHomingEnabled = unitType.getFieldAsBoolean(ATTACK2_PROJECTILE_HOMING_ENABLED, + 0); + final int projectileSpeed = unitType.getFieldAsInteger(ATTACK2_PROJECTILE_SPEED, 0); + final int range = unitType.getFieldAsInteger(ATTACK2_RANGE, 0); + final float rangeMotionBuffer = unitType.getFieldAsFloat(ATTACK2_RANGE_MOTION_BUFFER, 0); + final boolean showUI = unitType.getFieldAsBoolean(ATTACK2_SHOW_UI, 0); + final EnumSet targetsAllowed = CTargetType + .parseTargetTypeSet(unitType.getFieldAsString(ATTACK2_TARGETS_ALLOWED, 0)); + final String weaponSound = unitType.getFieldAsString(ATTACK2_WEAPON_SOUND, 0); + final CWeaponType weaponType = CWeaponType + .parseWeaponType(unitType.getFieldAsString(ATTACK2_WEAPON_TYPE, 0)); + attacks.add(createAttack(animationBackswingPoint, animationDamagePoint, areaOfEffectFullDamage, + areaOfEffectMediumDamage, areaOfEffectSmallDamage, areaOfEffectTargets, attackType, + cooldownTime, damageBase, damageFactorMedium, damageFactorSmall, damageLossFactor, damageDice, + damageSidesPerDie, damageSpillDistance, damageSpillRadius, damageUpgradeAmount, + maximumNumberOfTargets, projectileArc, projectileArt, projectileHomingEnabled, projectileSpeed, + range, rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType)); + } catch (Exception exc) { + System.err.println("Attack 2 failed to parse with: " + exc.getClass() + ":" + exc.getMessage()); + } + } + final int deathType = unitType.getFieldAsInteger(DEATH_TYPE, 0); + final boolean raise = (deathType & 0x1) != 0; + final boolean decay = (deathType & 0x2) != 0; + final String armorType = unitType.getFieldAsString(ARMOR_TYPE, 0); + final float impactZ = unitType.getFieldAsFloat(PROJECTILE_IMPACT_Z, 0); + final CDefenseType defenseType = CDefenseType.parseDefenseType(unitType.getFieldAsString(DEFENSE_TYPE, 0)); + final float deathTime = unitType.getFieldAsFloat(DEATH_TIME, 0); + unitTypeInstance = new CUnitType(unitName, isBldg, movementType, moveHeight, collisionSize, classifications, + attacks, armorType, raise, decay, defenseType, impactZ, buildingPathingPixelMap, deathTime, + targetedAs, acquisitionRange, minimumAttackRange); + this.unitIdToUnitType.put(typeId, unitTypeInstance); + } + return unitTypeInstance; + } - private CUnitAttack createAttack(final float animationBackswingPoint, final float animationDamagePoint, - final int areaOfEffectFullDamage, final int areaOfEffectMediumDamage, final int areaOfEffectSmallDamage, - final EnumSet areaOfEffectTargets, final CAttackType attackType, final float cooldownTime, - final int damageBase, final float damageFactorMedium, final float damageFactorSmall, - final float damageLossFactor, final int damageDice, final int damageSidesPerDie, - final float damageSpillDistance, final float damageSpillRadius, final int damageUpgradeAmount, - final int maximumNumberOfTargets, final float projectileArc, final String projectileArt, - final boolean projectileHomingEnabled, final int projectileSpeed, final int range, - final float rangeMotionBuffer, final boolean showUI, final EnumSet targetsAllowed, - final String weaponSound, final CWeaponType weaponType) { - final CUnitAttack attack; - switch (weaponType) { - case MISSILE: - attack = new CUnitAttackMissile(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, - damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, - targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, projectileHomingEnabled, - projectileSpeed); - break; - case MBOUNCE: - attack = new CUnitAttackMissileBounce(animationBackswingPoint, animationDamagePoint, attackType, - cooldownTime, damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, - rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, - projectileHomingEnabled, projectileSpeed, damageLossFactor, maximumNumberOfTargets, - areaOfEffectFullDamage, areaOfEffectTargets); - break; - case MSPLASH: - case ARTILLERY: - attack = new CUnitAttackMissileSplash(animationBackswingPoint, animationDamagePoint, attackType, - cooldownTime, damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, - rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, - projectileHomingEnabled, projectileSpeed, areaOfEffectFullDamage, areaOfEffectMediumDamage, - areaOfEffectSmallDamage, areaOfEffectTargets, damageFactorMedium, damageFactorSmall); - break; - case MLINE: - case ALINE: - attack = new CUnitAttackMissileLine(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, - damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, - targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, projectileHomingEnabled, - projectileSpeed, damageSpillDistance, damageSpillRadius); - break; - case INSTANT: - attack = new CUnitAttackInstant(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, - damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, - targetsAllowed, weaponSound, weaponType, projectileArt); - break; - default: - case NORMAL: - attack = new CUnitAttackNormal(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, - damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, - targetsAllowed, weaponSound, weaponType); - break; - } - return attack; - } + private CUnitAttack createAttack(final float animationBackswingPoint, final float animationDamagePoint, + final int areaOfEffectFullDamage, final int areaOfEffectMediumDamage, final int areaOfEffectSmallDamage, + final EnumSet areaOfEffectTargets, final CAttackType attackType, final float cooldownTime, + final int damageBase, final float damageFactorMedium, final float damageFactorSmall, + final float damageLossFactor, final int damageDice, final int damageSidesPerDie, + final float damageSpillDistance, final float damageSpillRadius, final int damageUpgradeAmount, + final int maximumNumberOfTargets, final float projectileArc, final String projectileArt, + final boolean projectileHomingEnabled, final int projectileSpeed, final int range, + final float rangeMotionBuffer, final boolean showUI, final EnumSet targetsAllowed, + final String weaponSound, final CWeaponType weaponType) { + final CUnitAttack attack; + switch (weaponType) { + case MISSILE: + attack = new CUnitAttackMissile(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, + damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, + targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, projectileHomingEnabled, + projectileSpeed); + break; + case MBOUNCE: + attack = new CUnitAttackMissileBounce(animationBackswingPoint, animationDamagePoint, attackType, + cooldownTime, damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, + rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, + projectileHomingEnabled, projectileSpeed, damageLossFactor, maximumNumberOfTargets, + areaOfEffectFullDamage, areaOfEffectTargets); + break; + case MSPLASH: + case ARTILLERY: + attack = new CUnitAttackMissileSplash(animationBackswingPoint, animationDamagePoint, attackType, + cooldownTime, damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, + rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, + projectileHomingEnabled, projectileSpeed, areaOfEffectFullDamage, areaOfEffectMediumDamage, + areaOfEffectSmallDamage, areaOfEffectTargets, damageFactorMedium, damageFactorSmall); + break; + case MLINE: + case ALINE: + attack = new CUnitAttackMissileLine(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, + damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, + targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, projectileHomingEnabled, + projectileSpeed, damageSpillDistance, damageSpillRadius); + break; + case INSTANT: + attack = new CUnitAttackInstant(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, + damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, + targetsAllowed, weaponSound, weaponType, projectileArt); + break; + default: + case NORMAL: + attack = new CUnitAttackNormal(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, + damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, + targetsAllowed, weaponSound, weaponType); + break; + } + return attack; + } - public float getPropulsionWindow(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(PROPULSION_WINDOW, 0); - } + public float getPropulsionWindow(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(PROPULSION_WINDOW, 0); + } - public float getTurnRate(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(TURN_RATE, 0); - } + public float getTurnRate(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(TURN_RATE, 0); + } - public boolean isBuilding(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsBoolean(IS_BLDG, 0); - } + public boolean isBuilding(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsBoolean(IS_BLDG, 0); + } - public String getName(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getName(); - } + public String getName(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getName(); + } - public int getA1MinDamage(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_BASE, 0) - + this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_DICE, 0); - } + public int getA1MinDamage(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_BASE, 0) + + this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_DICE, 0); + } - public int getA1MaxDamage(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_BASE, 0) - + (this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_DICE, 0) - * this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_SIDES_PER_DIE, 0)); - } + public int getA1MaxDamage(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_BASE, 0) + + (this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_DICE, 0) + * this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_SIDES_PER_DIE, 0)); + } - public int getA2MinDamage(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_BASE, 0) - + this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_DICE, 0); - } + public int getA2MinDamage(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_BASE, 0) + + this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_DICE, 0); + } - public int getA2MaxDamage(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_BASE, 0) - + (this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_DICE, 0) - * this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_SIDES_PER_DIE, 0)); - } + public int getA2MaxDamage(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_BASE, 0) + + (this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_DICE, 0) + * this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_SIDES_PER_DIE, 0)); + } - public int getDefense(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsInteger(DEFENSE, 0); - } + public int getDefense(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(DEFENSE, 0); + } - public int getA1ProjectileSpeed(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_PROJECTILE_SPEED, 0); - } + public int getA1ProjectileSpeed(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_PROJECTILE_SPEED, 0); + } - public float getA1ProjectileArc(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK1_PROJECTILE_ARC, 0); - } + public float getA1ProjectileArc(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK1_PROJECTILE_ARC, 0); + } - public int getA2ProjectileSpeed(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_PROJECTILE_SPEED, 0); - } + public int getA2ProjectileSpeed(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_PROJECTILE_SPEED, 0); + } - public float getA2ProjectileArc(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK2_PROJECTILE_ARC, 0); - } + public float getA2ProjectileArc(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK2_PROJECTILE_ARC, 0); + } - public String getA1MissileArt(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsString(ATTACK1_MISSILE_ART, 0); - } + public String getA1MissileArt(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsString(ATTACK1_MISSILE_ART, 0); + } - public String getA2MissileArt(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsString(ATTACK2_MISSILE_ART, 0); - } + public String getA2MissileArt(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsString(ATTACK2_MISSILE_ART, 0); + } - public float getA1Cooldown(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK1_COOLDOWN, 0); - } + public float getA1Cooldown(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK1_COOLDOWN, 0); + } - public float getA2Cooldown(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK2_COOLDOWN, 0); - } + public float getA2Cooldown(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK2_COOLDOWN, 0); + } - public float getProjectileLaunchX(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(PROJECTILE_LAUNCH_X, 0); - } + public float getProjectileLaunchX(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(PROJECTILE_LAUNCH_X, 0); + } - public float getProjectileLaunchY(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(PROJECTILE_LAUNCH_Y, 0); - } + public float getProjectileLaunchY(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(PROJECTILE_LAUNCH_Y, 0); + } - public float getProjectileLaunchZ(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(PROJECTILE_LAUNCH_Z, 0); - } + public float getProjectileLaunchZ(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(PROJECTILE_LAUNCH_Z, 0); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CAttackOrder.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CAttackOrder.java index 7bc942d..8d30b1f 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CAttackOrder.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CAttackOrder.java @@ -138,7 +138,6 @@ public class CAttackOrder implements COrder { else { damage = simulation.getSeededRandom().nextInt(maxDamage - minDamage) + minDamage; } - System.out.println(damage + " from " + minDamage + " to " + maxDamage); this.unitAttack.launch(simulation, this.unit, this.target, damage); this.damagePointLaunchTime = 0; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/pathing/CPathfindingProcessor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/pathing/CPathfindingProcessor.java index c9fe992..0af31d0 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/pathing/CPathfindingProcessor.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/pathing/CPathfindingProcessor.java @@ -177,7 +177,8 @@ public class CPathfindingProcessor { } } - while (!openSet.isEmpty()) { + int searchIterations = 0; + while (!openSet.isEmpty() && searchIterations < 150000) { Node current = openSet.poll(); if (isGoal(current)) { final LinkedList totalPath = new LinkedList<>(); @@ -259,6 +260,7 @@ public class CPathfindingProcessor { } } } + searchIterations++; } return Collections.emptyList(); } diff --git a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java index 1eaf29d..0675b3a 100644 --- a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java +++ b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java @@ -24,7 +24,7 @@ import com.etheller.warsmash.viewer5.gl.SoundLengthExtension; import com.etheller.warsmash.viewer5.gl.WireframeExtension; public class DesktopLauncher { - public static void main(final String[] arg) { + public static void main(String[] arg) { Extensions.angleInstancedArrays = new ANGLEInstancedArrays() { @Override public void glVertexAttribDivisorANGLE(final int index, final int divisor) { @@ -83,10 +83,11 @@ public class DesktopLauncher { config.useGL30 = true; config.gles30ContextMajorVersion = 3; config.gles30ContextMinorVersion = 3; - config.samples = 16; + //config.samples = 16; // config.vSyncEnabled = false; // config.foregroundFPS = 0; // config.backgroundFPS = 0; + arg = new String[]{"-windowed"}; if ((arg.length > 0) && "-windowed".equals(arg[0])) { config.fullscreen = false; } From 2173c993bcc8ef137a0d28e03b9510aaed5dcdbd Mon Sep 17 00:00:00 2001 From: Retera Date: Sat, 24 Oct 2020 01:10:37 -0400 Subject: [PATCH 054/116] Make UI icons clickable --- .../etheller/warsmash/WarsmashGdxMapGame.java | 95 +----- .../etheller/warsmash/parsers/fdf/GameUI.java | 13 + .../viewer5/handlers/w3x/War3MapViewer.java | 48 +-- .../commandbuttons/CommandButtonListener.java | 2 +- .../CommandCardPopulatingAbilityVisitor.java | 2 +- .../handlers/w3x/simulation/CSimulation.java | 21 ++ .../w3x/simulation/CUnitFilterFunction.java | 12 + .../simulation/players/CPlayerController.java | 9 - .../players/CPlayerUnitOrderExecutor.java | 101 ++++++ .../players/CPlayerUnitOrderListener.java | 12 + .../BooleanAbilityTargetCheckReceiver.java | 66 ++++ .../StringMsgAbilityActivationReceiver.java | 57 ++++ .../util/StringMsgTargetCheckReceiver.java | 96 ++++++ .../handlers/w3x/ui/CommandCardIcon.java | 19 +- .../viewer5/handlers/w3x/ui/MeleeUI.java | 311 ++++++++++++++++-- .../w3x/ui/command/ActiveCommand.java | 8 + .../command/CommandCardCommandListener.java | 7 + .../w3x/ui/command/CommandErrorListener.java | 5 + 18 files changed, 728 insertions(+), 156 deletions(-) create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitFilterFunction.java delete mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerController.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderExecutor.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderListener.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/BooleanAbilityTargetCheckReceiver.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/StringMsgAbilityActivationReceiver.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/StringMsgTargetCheckReceiver.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/ActiveCommand.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/CommandCardCommandListener.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/CommandErrorListener.java diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java index 71542ff..11872d9 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java @@ -10,7 +10,6 @@ import java.util.List; import com.badlogic.gdx.ApplicationAdapter; import com.badlogic.gdx.Gdx; -import com.badlogic.gdx.Input; import com.badlogic.gdx.InputProcessor; import com.badlogic.gdx.audio.Music; import com.badlogic.gdx.graphics.Color; @@ -26,8 +25,6 @@ import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator.FreeTypeFont import com.badlogic.gdx.graphics.glutils.ShapeRenderer; import com.badlogic.gdx.math.Matrix4; import com.badlogic.gdx.math.Rectangle; -import com.badlogic.gdx.math.Vector2; -import com.badlogic.gdx.math.Vector3; import com.badlogic.gdx.utils.viewport.ExtendViewport; import com.etheller.warsmash.datasources.CompoundDataSourceDescriptor; import com.etheller.warsmash.datasources.DataSource; @@ -50,17 +47,15 @@ import com.etheller.warsmash.viewer5.Scene; import com.etheller.warsmash.viewer5.TextureMapper; import com.etheller.warsmash.viewer5.handlers.ModelHandler; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; -import com.etheller.warsmash.viewer5.handlers.w3x.UnitSound; import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; import com.etheller.warsmash.viewer5.handlers.w3x.camera.CameraPreset; import com.etheller.warsmash.viewer5.handlers.w3x.camera.CameraRates; -import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayerUnitOrderExecutor; import com.etheller.warsmash.viewer5.handlers.w3x.ui.MeleeUI; +import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.CommandErrorListener; public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProvider, InputProcessor { private static final boolean ENABLE_MUSIC = false; - private static final Vector3 clickLocationTemp = new Vector3(); - private static final Vector2 clickLocationTemp2 = new Vector2(); private DataSource codebase; private War3MapViewer viewer; private final Rectangle tempRect = new Rectangle(); @@ -73,8 +68,6 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv private ExtendViewport uiViewport; private GlyphLayout glyphLayout; - private int selectedSoundCount = 0; - private Texture solidGreenTexture; private ShapeRenderer shapeRenderer; @@ -227,7 +220,12 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv music.play(); } } - }); + }, new CPlayerUnitOrderExecutor(this.viewer.simulation, new CommandErrorListener() { + @Override + public void showCommandError(final String message) { + WarsmashGdxMapGame.this.meleeUI.showCommandError(message); + } + })); final ModelInstance libgdxContentInstance = new LibGDXContentLayerModel(null, this.viewer, "", this.viewer.mapPathSolver, "").addInstance(); libgdxContentInstance.setScene(this.uiScene); @@ -361,85 +359,10 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv @Override public boolean touchDown(final int screenX, final int screenY, final int pointer, final int button) { final float worldScreenY = getHeight() - screenY; - System.out.println(screenX + "," + screenY); - clickLocationTemp2.x = screenX; - clickLocationTemp2.y = screenY; - this.uiViewport.unproject(clickLocationTemp2); - - if (this.meleeUI.touchDown(clickLocationTemp2.x, clickLocationTemp2.y, button)) { + if (this.meleeUI.touchDown(screenX, screenY, worldScreenY, button)) { return false; } - if (button == Input.Buttons.RIGHT) { - final RenderUnit rayPickUnit = this.viewer.rayPickUnit(screenX, worldScreenY); - if (this.meleeUI.getSelectedUnit() != null) { - if ((rayPickUnit != null) && (rayPickUnit.playerIndex != this.meleeUI.getSelectedUnit().playerIndex) - && !rayPickUnit.getSimulationUnit().isDead()) { - if (this.viewer.orderSmart(rayPickUnit)) { - if (this.meleeUI.getSelectedUnit().soundset.yesAttack.playUnitResponse( - this.viewer.worldScene.audioContext, this.meleeUI.getSelectedUnit())) { - this.meleeUI.portraitTalk(); - } - this.selectedSoundCount = 0; - } - } - else { - this.viewer.getClickLocation(clickLocationTemp, screenX, (int) worldScreenY); - System.out.println(clickLocationTemp); - this.viewer.showConfirmation(clickLocationTemp, 0, 1, 0); - final int x = (int) ((clickLocationTemp.x - this.viewer.terrain.centerOffset[0]) / 128); - final int y = (int) ((clickLocationTemp.y - this.viewer.terrain.centerOffset[1]) / 128); - System.out.println(x + "," + y); - this.viewer.terrain.logRomp(x, y); - if (this.viewer.orderSmart(clickLocationTemp.x, clickLocationTemp.y)) { - if (this.meleeUI.getSelectedUnit().soundset.yes.playUnitResponse( - this.viewer.worldScene.audioContext, this.meleeUI.getSelectedUnit())) { - this.meleeUI.portraitTalk(); - } - this.selectedSoundCount = 0; - } - } - } - } - else { - final List selectedUnits = this.viewer.selectUnit(screenX, worldScreenY, false); - if (!selectedUnits.isEmpty()) { - final RenderUnit unit = selectedUnits.get(0); - final boolean selectionChanged = this.meleeUI.getSelectedUnit() != unit; - boolean playedNewSound = false; - if (selectionChanged) { - this.selectedSoundCount = 0; - } - if (unit.soundset != null) { - UnitSound ackSoundToPlay = unit.soundset.what; - final int pissedSoundCount = unit.soundset.pissed.getSoundCount(); - int soundIndex; - if ((this.selectedSoundCount >= 3) && (pissedSoundCount > 0)) { - soundIndex = this.selectedSoundCount - 3; - ackSoundToPlay = unit.soundset.pissed; - } - else { - soundIndex = (int) (Math.random() * ackSoundToPlay.getSoundCount()); - } - if (ackSoundToPlay.playUnitResponse(this.viewer.worldScene.audioContext, unit, soundIndex)) { - this.selectedSoundCount++; - if ((this.selectedSoundCount - 3) >= pissedSoundCount) { - this.selectedSoundCount = 0; - } - playedNewSound = true; - } - } - if (selectionChanged) { - this.meleeUI.selectUnit(unit); - } - if (playedNewSound) { - this.meleeUI.portraitTalk(); - } - } - else { - this.meleeUI.selectUnit(null); - } - } return false; } diff --git a/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java index 1d0be0c..04a54bb 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java @@ -211,6 +211,19 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { return textureFrame; } + public StringFrame createStringFrame(final String name, final UIFrame parent, final Color color, + final TextJustify justifyH, final TextJustify justifyV, final float fdfFontSize) { + this.fontParam.size = (int) convertY(this.viewport, fdfFontSize); + if (this.fontParam.size == 0) { + this.fontParam.size = 24; + } + final BitmapFont frameFont = this.fontGenerator.generateFont(this.fontParam); + final StringFrame stringFrame = new StringFrame(name, parent, color, justifyH, justifyV, frameFont); + this.nameToFrame.put(name, stringFrame); + add(stringFrame); + return stringFrame; + } + public UIFrame inflate(final FrameDefinition frameDefinition, final UIFrame parent, final FrameDefinition parentDefinitionIfAvailable) { UIFrame inflatedFrame = null; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index 1a797b9..80984af 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -81,17 +81,16 @@ import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.AbilityDataU import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitClassification; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitFilterFunction; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityAttack; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityMove; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackInstant; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackMissile; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.projectile.CAttackProjectile; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.BooleanAbilityActivationReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.CWidgetAbilityTargetCheckReceiver; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.PointAbilityTargetCheckReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.SimulationRenderController; import mpq.MPQArchive; @@ -1135,6 +1134,10 @@ public class War3MapViewer extends ModelViewer { } public RenderUnit rayPickUnit(final float x, final float y) { + return rayPickUnit(x, y, CUnitFilterFunction.ACCEPT_ALL); + } + + public RenderUnit rayPickUnit(final float x, final float y, final CUnitFilterFunction filter) { final float[] ray = rayHeap; mousePosHeap.set(x, y); this.worldScene.camera.screenToWorldRay(ray, mousePosHeap); @@ -1146,7 +1149,11 @@ public class War3MapViewer extends ModelViewer { final MdxComplexInstance instance = unit.instance; if (instance.isVisible(this.worldScene.camera) && instance.intersectRayWithCollision(gdxRayHeap, intersectionHeap, unit.getSimulationUnit().getUnitType().isBuilding())) { - entity = unit; + if (filter.call(unit.getSimulationUnit())) { + if ((entity == null) || (entity.instance.depth > instance.depth)) { + entity = unit; + } + } } } return entity; @@ -1235,41 +1242,6 @@ public class War3MapViewer extends ModelViewer { return (numElements < 0) ? 1 : (numElements >= MAXIMUM_ACCEPTED) ? MAXIMUM_ACCEPTED : numElements + 1; } - public boolean orderSmart(final float x, final float y) { - mousePosHeap.x = x; - mousePosHeap.y = y; - boolean ordered = false; - for (final RenderUnit unit : this.selected) { - for (final CAbility ability : unit.getSimulationUnit().getAbilities()) { - if (ability instanceof CAbilityMove) { - ability.checkCanUse(this.simulation, unit.getSimulationUnit(), OrderIds.smart, - BooleanAbilityActivationReceiver.INSTANCE); - if (BooleanAbilityActivationReceiver.INSTANCE.isOk()) { - ability.checkCanTarget(this.simulation, unit.getSimulationUnit(), OrderIds.smart, mousePosHeap, - PointAbilityTargetCheckReceiver.INSTANCE); - final Vector2 target = PointAbilityTargetCheckReceiver.INSTANCE.getTarget(); - if (target != null) { - ability.onOrder(this.simulation, unit.getSimulationUnit(), OrderIds.smart, mousePosHeap, - false); - ordered = true; - } - else { - System.err.println("Target not valid."); - } - } - else { - System.err.println("Ability not ok to use."); - } - } - else { - System.err.println("Ability not move."); - } - } - - } - return ordered; - } - public boolean orderSmart(final RenderUnit target) { boolean ordered = false; for (final RenderUnit unit : this.selected) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandButtonListener.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandButtonListener.java index 04c2414..89a82a9 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandButtonListener.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandButtonListener.java @@ -34,5 +34,5 @@ public interface CommandButtonListener { // int getButtonPositionY(); // // int getOrderId(); - void commandButton(int buttonPositionX, int buttonPositionY, Texture icon, int orderId); + void commandButton(int buttonPositionX, int buttonPositionY, Texture icon, int abilityHandleId, int orderId); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java index a919955..c0aeb88 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java @@ -46,6 +46,6 @@ public class CommandCardPopulatingAbilityVisitor implements CAbilityVisitor handleIdToUnit = new HashMap<>(); + private final Map handleIdToAbility = new HashMap<>(); public CSimulation(final DataTable miscData, final MutableObjectData parsedUnitData, final MutableObjectData parsedAbilityData, final SimulationRenderController simulationRenderController, @@ -106,10 +111,22 @@ public class CSimulation { final CUnit unit = this.unitData.create(this, playerIndex, typeId, x, y, facing, buildingPathingPixelMap, this.simulationRenderController, this.handleIdAllocator); this.units.add(unit); + this.handleIdToUnit.put(unit.getHandleId(), unit); + for (final CAbility ability : unit.getAbilities()) { + this.handleIdToAbility.put(ability.getHandleId(), ability); + } this.worldCollision.addUnit(unit); return unit; } + public CUnit getUnit(final int handleId) { + return this.handleIdToUnit.get(handleId); + } + + public CAbility getAbility(final int handleId) { + return this.handleIdToAbility.get(handleId); + } + public CAttackProjectile createProjectile(final CUnit source, final float launchX, final float launchY, final float launchFacing, final CUnitAttackMissile attack, final CWidget target, final float damage, final int bounceIndex) { @@ -142,6 +159,10 @@ public class CSimulation { final CUnit unit = unitIterator.next(); if (unit.update(this)) { unitIterator.remove(); + for (final CAbility ability : unit.getAbilities()) { + this.handleIdToAbility.remove(ability.getHandleId()); + } + this.handleIdToUnit.remove(unit.getHandleId()); this.simulationRenderController.removeUnit(unit); } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitFilterFunction.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitFilterFunction.java new file mode 100644 index 0000000..6cbc073 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitFilterFunction.java @@ -0,0 +1,12 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation; + +public interface CUnitFilterFunction { + boolean call(CUnit unit); + + CUnitFilterFunction ACCEPT_ALL = new CUnitFilterFunction() { + @Override + public boolean call(final CUnit unit) { + return true; + } + }; +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerController.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerController.java deleted file mode 100644 index fc482c1..0000000 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerController.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.etheller.warsmash.viewer5.handlers.w3x.simulation.players; - -public interface CPlayerController { - boolean issueTargetOrder(int unitHandleId, int orderId, int targetHandleId); - - boolean issuePointOrder(int unitHandleId, int orderId, float x, float y); - - boolean issueImmediateOrder(int unitHandleId, int orderId); -} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderExecutor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderExecutor.java new file mode 100644 index 0000000..ea56443 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderExecutor.java @@ -0,0 +1,101 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.players; + +import com.badlogic.gdx.math.Vector2; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.StringMsgAbilityActivationReceiver; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.StringMsgTargetCheckReceiver; +import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.CommandErrorListener; + +public class CPlayerUnitOrderExecutor implements CPlayerUnitOrderListener { + private final CSimulation game; + private final CommandErrorListener errorListener; + private final StringMsgTargetCheckReceiver targetCheckReceiver = new StringMsgTargetCheckReceiver<>(); + private final StringMsgAbilityActivationReceiver abilityActivationReceiver = new StringMsgAbilityActivationReceiver(); + + private StringMsgTargetCheckReceiver targetCheckReceiver() { + return (StringMsgTargetCheckReceiver) this.targetCheckReceiver.reset(); + } + + public CPlayerUnitOrderExecutor(final CSimulation game, final CommandErrorListener errorListener) { + this.game = game; + this.errorListener = errorListener; + } + + @Override + public boolean issueTargetOrder(final int unitHandleId, final int abilityHandleId, final int orderId, + final int targetHandleId, final boolean queue) { + final CUnit unit = this.game.getUnit(unitHandleId); + final CAbility ability = this.game.getAbility(abilityHandleId); + ability.checkCanUse(this.game, unit, orderId, this.abilityActivationReceiver.reset()); + if (this.abilityActivationReceiver.isUseOk()) { + final CUnit target = this.game.getUnit(targetHandleId); + final StringMsgTargetCheckReceiver targetReceiver = this.targetCheckReceiver(); + ability.checkCanTarget(this.game, unit, orderId, target, targetReceiver); + if (targetReceiver.getTarget() != null) { + ability.onOrder(this.game, unit, orderId, target, queue); + return true; + } + else { + this.errorListener.showCommandError(targetReceiver.getMessage()); + return false; + } + } + else { + this.errorListener.showCommandError(this.abilityActivationReceiver.getMessage()); + return false; + } + } + + @Override + public boolean issuePointOrder(final int unitHandleId, final int abilityHandleId, final int orderId, final float x, + final float y, final boolean queue) { + final CUnit unit = this.game.getUnit(unitHandleId); + final CAbility ability = this.game.getAbility(abilityHandleId); + ability.checkCanUse(this.game, unit, orderId, this.abilityActivationReceiver.reset()); + if (this.abilityActivationReceiver.isUseOk()) { + final Vector2 target = new Vector2(x, y); + final StringMsgTargetCheckReceiver targetReceiver = this.targetCheckReceiver(); + ability.checkCanTarget(this.game, unit, orderId, target, targetReceiver); + if (targetReceiver.getTarget() != null) { + ability.onOrder(this.game, unit, orderId, target, queue); + return true; + } + else { + this.errorListener.showCommandError(targetReceiver.getMessage()); + return false; + } + } + else { + this.errorListener.showCommandError(this.abilityActivationReceiver.getMessage()); + return false; + } + } + + @Override + public boolean issueImmediateOrder(final int unitHandleId, final int abilityHandleId, final int orderId, + final boolean queue) { + final CUnit unit = this.game.getUnit(unitHandleId); + final CAbility ability = this.game.getAbility(abilityHandleId); + ability.checkCanUse(this.game, unit, orderId, this.abilityActivationReceiver.reset()); + if (this.abilityActivationReceiver.isUseOk()) { + final StringMsgTargetCheckReceiver targetReceiver = this.targetCheckReceiver(); + ability.checkCanTargetNoTarget(this.game, unit, orderId, targetReceiver); + if (targetReceiver.getTarget() != null) { + ability.onOrderNoTarget(this.game, unit, orderId, queue); + return true; + } + else { + this.errorListener.showCommandError(targetReceiver.getMessage()); + return false; + } + } + else { + this.errorListener.showCommandError(this.abilityActivationReceiver.getMessage()); + return false; + } + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderListener.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderListener.java new file mode 100644 index 0000000..a0d420a --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderListener.java @@ -0,0 +1,12 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.players; + +public interface CPlayerUnitOrderListener { + boolean issueTargetOrder(int unitHandleId, int abilityHandleId, int orderId, int targetHandleId, boolean queue); + + boolean issuePointOrder(int unitHandleId, int abilityHandleId, int orderId, float x, float y, boolean queue); + + // Below: used for "DROP ITEM AT POINT" ???? +// boolean issueTargetAndPointOrder(int unitHandleId, int orderId, int targetHandleId, float x, float y); + + boolean issueImmediateOrder(int unitHandleId, int abilityHandleId, int orderId, boolean queue); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/BooleanAbilityTargetCheckReceiver.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/BooleanAbilityTargetCheckReceiver.java new file mode 100644 index 0000000..ab1a986 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/BooleanAbilityTargetCheckReceiver.java @@ -0,0 +1,66 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.util; + +public final class BooleanAbilityTargetCheckReceiver implements AbilityTargetCheckReceiver { + private static final BooleanAbilityTargetCheckReceiver INSTANCE = new BooleanAbilityTargetCheckReceiver<>(); + + public static BooleanAbilityTargetCheckReceiver getInstance() { + return (BooleanAbilityTargetCheckReceiver) INSTANCE; + } + + private boolean targetable = false; + + public boolean isTargetable() { + return this.targetable; + } + + public BooleanAbilityTargetCheckReceiver reset() { + this.targetable = false; + return this; + } + + @Override + public void targetOk(final TARGET_TYPE target) { + this.targetable = true; + } + + @Override + public void mustTargetTeamType(final TeamType correctType) { + this.targetable = false; + } + + @Override + public void mustTargetType(final TargetType correctType) { + this.targetable = false; + } + + @Override + public void targetOutsideRange(final double howMuch) { + this.targetable = false; + } + + @Override + public void notAnActiveAbility() { + this.targetable = false; + } + + @Override + public void targetNotVisible() { + this.targetable = false; + } + + @Override + public void targetTooComplicated() { + this.targetable = false; + } + + @Override + public void targetNotInPlayableMap() { + this.targetable = false; + } + + @Override + public void orderIdNotAccepted() { + this.targetable = false; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/StringMsgAbilityActivationReceiver.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/StringMsgAbilityActivationReceiver.java new file mode 100644 index 0000000..d9d77bb --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/StringMsgAbilityActivationReceiver.java @@ -0,0 +1,57 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.util; + +public class StringMsgAbilityActivationReceiver implements AbilityActivationReceiver { + private static final StringMsgAbilityActivationReceiver INSTANCE = new StringMsgAbilityActivationReceiver(); + + public static StringMsgAbilityActivationReceiver getInstance() { + return INSTANCE; + } + + private String message; + private boolean useOk = false; + + public StringMsgAbilityActivationReceiver reset() { + this.message = null; + this.useOk = false; + return this; + } + + public String getMessage() { + return this.message; + } + + public boolean isUseOk() { + return this.useOk; + } + + @Override + public void useOk() { + this.useOk = true; + } + + @Override + public void notEnoughResources(final ResourceType resource, final int amount) { + this.message = "NOTEXTERN: Requires " + amount + " " + resource.name().toLowerCase() + "."; + } + + @Override + public void notAnActiveAbility() { + this.message = "NOTEXTERN: Not an active ability."; + } + + @Override + public void missingRequirement(final String name) { + this.message = "NOTEXTERN: Requires " + name; + } + + @Override + public void cargoCapacityUnavailable() { + this.message = "NOTEXTERN: Cargo capacity unavailable."; + } + + @Override + public void casterMovementDisabled() { + this.message = "NOTEXTERN: Caster movement disabled."; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/StringMsgTargetCheckReceiver.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/StringMsgTargetCheckReceiver.java new file mode 100644 index 0000000..1622261 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/StringMsgTargetCheckReceiver.java @@ -0,0 +1,96 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.util; + +public final class StringMsgTargetCheckReceiver implements AbilityTargetCheckReceiver { + private static final StringMsgTargetCheckReceiver INSTANCE = new StringMsgTargetCheckReceiver<>(); + + public static StringMsgTargetCheckReceiver getInstance() { + return (StringMsgTargetCheckReceiver) INSTANCE; + } + + private TARGET_TYPE target; + private String message; + + public TARGET_TYPE getTarget() { + return this.target; + } + + public String getMessage() { + return this.message; + } + + public StringMsgTargetCheckReceiver reset() { + this.target = null; + this.message = null; + return this; + } + + @Override + public void targetOk(final TARGET_TYPE target) { + this.target = target; + } + + @Override + public void mustTargetTeamType(final TeamType correctType) { + switch (correctType) { + case ALLIED: + this.message = "NOTEXTERN: Must target an allied unit."; + break; + case ENEMY: + this.message = "NOTEXTERN: Must target an enemy unit."; + break; + case PLAYER_UNITS: + this.message = "NOTEXTERN: Unable to target a unit you do not control."; + break; + default: + this.message = "NOTEXTERN: Must target team type: " + correctType; + } + } + + @Override + public void mustTargetType(final TargetType correctType) { + switch (correctType) { + case POINT: + this.message = "NOTEXTERN: Must target a point."; + break; + case UNIT: + this.message = "NOTEXTERN: Must target a unit."; + break; + case UNIT_OR_POINT: + this.message = "NOTEXTERN: Must target a unit or point."; + break; + default: + this.message = "NOTEXTERN: Must target type: " + correctType; + } + } + + @Override + public void targetOutsideRange(final double howMuch) { + this.message = "NOTEXTERN: Target is outside range."; + } + + @Override + public void notAnActiveAbility() { + this.message = "NOTEXTERN: Not an active ability."; + } + + @Override + public void targetNotVisible() { + this.message = "NOTEXTERN: Target is not visible."; + } + + @Override + public void targetTooComplicated() { + this.message = "NOTEXTERN: Target is too complicated."; + } + + @Override + public void targetNotInPlayableMap() { + this.message = "NOTEXTERN: Target is not within the designed combat area."; + } + + @Override + public void orderIdNotAccepted() { + this.message = "NOTEXTERN: OrderID not accepted."; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java index 2449cb6..fc7cbd3 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java @@ -1,5 +1,6 @@ package com.etheller.warsmash.viewer5.handlers.w3x.ui; +import com.badlogic.gdx.Input; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.GlyphLayout; @@ -11,6 +12,7 @@ import com.etheller.warsmash.parsers.fdf.frames.TextureFrame; import com.etheller.warsmash.parsers.fdf.frames.UIFrame; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.commandbuttons.CommandButton; +import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.CommandCardCommandListener; public class CommandCardIcon extends AbstractRenderableFrame { @@ -18,9 +20,14 @@ public class CommandCardIcon extends AbstractRenderableFrame { private SpriteFrame cooldownFrame; private SpriteFrame autocastFrame; private CommandButton commandButton; + private int abilityHandleId; + private int orderId; + private final CommandCardCommandListener commandCardCommandListener; - public CommandCardIcon(final String name, final UIFrame parent) { + public CommandCardIcon(final String name, final UIFrame parent, + final CommandCardCommandListener commandCardCommandListener) { super(name, parent); + this.commandCardCommandListener = commandCardCommandListener; } public void set(final TextureFrame iconFrame, final SpriteFrame cooldownFrame, final SpriteFrame autocastFrame) { @@ -57,11 +64,13 @@ public class CommandCardIcon extends AbstractRenderableFrame { } } - public void setCommandButtonData(final Texture texture, final int orderId) { + public void setCommandButtonData(final Texture texture, final int abilityHandleId, final int orderId) { this.iconFrame.setVisible(true); this.cooldownFrame.setVisible(false); this.autocastFrame.setVisible(false); this.iconFrame.setTexture(texture); + this.abilityHandleId = abilityHandleId; + this.orderId = orderId; } @Override @@ -81,6 +90,12 @@ public class CommandCardIcon extends AbstractRenderableFrame { @Override public UIFrame touchDown(final float screenX, final float screenY, final int button) { if (this.renderBounds.contains(screenX, screenY)) { + if (button == Input.Buttons.LEFT) { + this.commandCardCommandListener.startUsingAbility(this.abilityHandleId, this.orderId); + } + else if (button == Input.Buttons.RIGHT) { + this.commandCardCommandListener.toggleAutoCastAbility(this.abilityHandleId); + } return this; } return null; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java index 1c57b9a..8ae6024 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java @@ -1,10 +1,12 @@ package com.etheller.warsmash.viewer5.handlers.w3x.ui; import java.io.IOException; +import java.util.List; import javax.imageio.ImageIO; import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.Input; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.g2d.BitmapFont; @@ -13,11 +15,13 @@ import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.math.Vector3; import com.badlogic.gdx.utils.viewport.Viewport; import com.etheller.warsmash.datasources.DataSource; import com.etheller.warsmash.parsers.fdf.GameUI; import com.etheller.warsmash.parsers.fdf.datamodel.AnchorDefinition; import com.etheller.warsmash.parsers.fdf.datamodel.FramePoint; +import com.etheller.warsmash.parsers.fdf.datamodel.TextJustify; import com.etheller.warsmash.parsers.fdf.frames.SetPoint; import com.etheller.warsmash.parsers.fdf.frames.SpriteFrame; import com.etheller.warsmash.parsers.fdf.frames.StringFrame; @@ -34,6 +38,7 @@ import com.etheller.warsmash.viewer5.handlers.mdx.ReplaceableIds; import com.etheller.warsmash.viewer5.handlers.mdx.SequenceLoopMode; import com.etheller.warsmash.viewer5.handlers.tga.TgaFile; import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; +import com.etheller.warsmash.viewer5.handlers.w3x.UnitSound; import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; import com.etheller.warsmash.viewer5.handlers.w3x.camera.CameraPreset; import com.etheller.warsmash.viewer5.handlers.w3x.camera.CameraRates; @@ -41,18 +46,34 @@ import com.etheller.warsmash.viewer5.handlers.w3x.camera.GameCameraManager; import com.etheller.warsmash.viewer5.handlers.w3x.camera.PortraitCameraManager; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderUnit; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.commandbuttons.CommandButtonListener; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitClassification; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitFilterFunction; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitStateListener; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityAttack; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityView; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CDefenseType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CodeKeyType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayerUnitOrderListener; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.BooleanAbilityActivationReceiver; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.BooleanAbilityTargetCheckReceiver; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.CWidgetAbilityTargetCheckReceiver; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.PointAbilityTargetCheckReceiver; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.StringMsgAbilityActivationReceiver; +import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.CommandCardCommandListener; -public class MeleeUI implements CUnitStateListener, CommandButtonListener { +public class MeleeUI implements CUnitStateListener, CommandButtonListener, CommandCardCommandListener { private static final int COMMAND_CARD_WIDTH = 4; private static final int COMMAND_CARD_HEIGHT = 3; private static final Vector2 screenCoordsVector = new Vector2(); + private static final Vector3 clickLocationTemp = new Vector3(); + private static final Vector2 clickLocationTemp2 = new Vector2(); private final DataSource dataSource; private final Viewport uiViewport; private final FreeTypeFontGenerator fontGenerator; @@ -104,11 +125,20 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener { private UIFrame inventoryCover; private SpriteFrame cursorFrame; private MeleeUIMinimap meleeUIMinimap; + private final CPlayerUnitOrderListener unitOrderListener; + private StringFrame errorMessageFrame; + + private CAbilityView activeCommand; + private int activeCommandOrderId; + private RenderUnit activeCommandUnit; + + private int selectedSoundCount = 0; + private final ActiveCommandUnitTargetFilter activeCommandUnitTargetFilter; public MeleeUI(final DataSource dataSource, final Viewport uiViewport, final FreeTypeFontGenerator fontGenerator, final Scene uiScene, final Scene portraitScene, final CameraPreset[] cameraPresets, - final CameraRates cameraRates, final War3MapViewer war3MapViewer, - final RootFrameListener rootFrameListener) { + final CameraRates cameraRates, final War3MapViewer war3MapViewer, final RootFrameListener rootFrameListener, + final CPlayerUnitOrderListener unitOrderListener) { this.dataSource = dataSource; this.uiViewport = uiViewport; this.fontGenerator = fontGenerator; @@ -116,6 +146,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener { this.portraitScene = portraitScene; this.war3MapViewer = war3MapViewer; this.rootFrameListener = rootFrameListener; + this.unitOrderListener = unitOrderListener; this.cameraManager = new GameCameraManager(cameraPresets, cameraRates); @@ -127,6 +158,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener { this.activeButtonTexture = ImageUtils.getBLPTexture(war3MapViewer.mapMpq, "UI\\Widgets\\Console\\Human\\CommandButton\\human-activebutton.blp"); + this.activeCommandUnitTargetFilter = new ActiveCommandUnitTargetFilter(); } @@ -170,7 +202,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener { // ================================= // Load skins and templates // ================================= - this.rootFrame = new GameUI(this.dataSource, GameUI.loadSkin(this.dataSource, 3), this.uiViewport, + this.rootFrame = new GameUI(this.dataSource, GameUI.loadSkin(this.dataSource, 0), this.uiViewport, this.fontGenerator, this.uiScene, this.war3MapViewer); this.rootFrameListener.onCreate(this.rootFrame); try { @@ -259,11 +291,17 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener { this.inventoryCover = this.rootFrame.createSimpleFrame("SmashConsoleInventoryCover", this.rootFrame, 0); + this.errorMessageFrame = this.rootFrame.createStringFrame("SmashErrorMessageFrame", this.consoleUI, + new Color(0xFFFFCC00), TextJustify.LEFT, TextJustify.MIDDLE, 0.014f); + this.errorMessageFrame.addAnchor(new AnchorDefinition(FramePoint.BOTTOMLEFT, + GameUI.convertX(this.uiViewport, 0.275f), GameUI.convertY(this.uiViewport, 0.275f))); + this.errorMessageFrame.setWidth(GameUI.convertX(this.uiViewport, 0.25f)); + int commandButtonIndex = 0; for (int j = 0; j < COMMAND_CARD_HEIGHT; j++) { for (int i = 0; i < COMMAND_CARD_WIDTH; i++) { final CommandCardIcon commandCardIcon = new CommandCardIcon("SmashCommandButton_" + commandButtonIndex, - this.rootFrame); + this.rootFrame, this); this.rootFrame.add(commandCardIcon); final TextureFrame iconFrame = this.rootFrame.createTextureFrame( "SmashCommandButton_" + (commandButtonIndex) + "_Icon", this.rootFrame, false, null); @@ -271,17 +309,20 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener { "SmashCommandButton_" + (commandButtonIndex) + "_Cooldown", this.rootFrame, "", 0); final SpriteFrame autocastFrame = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", "SmashCommandButton_" + (commandButtonIndex) + "_Autocast", this.rootFrame, "", 0); - iconFrame.addAnchor(new AnchorDefinition(FramePoint.BOTTOMLEFT, + commandCardIcon.addAnchor(new AnchorDefinition(FramePoint.BOTTOMLEFT, GameUI.convertX(this.uiViewport, 0.6175f + (0.0434f * i)), GameUI.convertY(this.uiViewport, 0.095f - (0.044f * j)))); + commandCardIcon.setWidth(GameUI.convertX(this.uiViewport, 0.039f)); + commandCardIcon.setHeight(GameUI.convertY(this.uiViewport, 0.039f)); + iconFrame.addSetPoint(new SetPoint(FramePoint.CENTER, commandCardIcon, FramePoint.CENTER, 0, 0)); iconFrame.setWidth(GameUI.convertX(this.uiViewport, 0.039f)); iconFrame.setHeight(GameUI.convertY(this.uiViewport, 0.039f)); iconFrame.setTexture(ImageUtils.DEFAULT_ICON_PATH, this.rootFrame); - cooldownFrame.addSetPoint(new SetPoint(FramePoint.CENTER, iconFrame, FramePoint.CENTER, 0, 0)); + cooldownFrame.addSetPoint(new SetPoint(FramePoint.CENTER, commandCardIcon, FramePoint.CENTER, 0, 0)); this.rootFrame.setSpriteFrameModel(cooldownFrame, this.rootFrame.getSkinField("CommandButtonCooldown")); cooldownFrame.setWidth(GameUI.convertX(this.uiViewport, 0.039f)); cooldownFrame.setHeight(GameUI.convertY(this.uiViewport, 0.039f)); - autocastFrame.addSetPoint(new SetPoint(FramePoint.CENTER, iconFrame, FramePoint.CENTER, 0, 0)); + autocastFrame.addSetPoint(new SetPoint(FramePoint.CENTER, commandCardIcon, FramePoint.CENTER, 0, 0)); this.rootFrame.setSpriteFrameModel(autocastFrame, this.rootFrame.getSkinField("CommandButtonAutocast")); autocastFrame.setWidth(GameUI.convertX(this.uiViewport, 0.039f)); autocastFrame.setHeight(GameUI.convertY(this.uiViewport, 0.039f)); @@ -305,6 +346,51 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener { selectUnit(null); } + @Override + public void startUsingAbility(final int abilityHandleId, final int orderId) { + // TODO not O(N) + CAbilityView abilityToUse = null; + for (final CAbility ability : this.selectedUnit.getSimulationUnit().getAbilities()) { + if (ability.getHandleId() == abilityHandleId) { + abilityToUse = ability; + break; + } + } + if (abilityToUse != null) { + final StringMsgAbilityActivationReceiver stringMsgActivationReceiver = StringMsgAbilityActivationReceiver + .getInstance().reset(); + abilityToUse.checkCanUse(this.war3MapViewer.simulation, this.selectedUnit.getSimulationUnit(), orderId, + stringMsgActivationReceiver); + if (!stringMsgActivationReceiver.isUseOk()) { + showCommandError(stringMsgActivationReceiver.getMessage()); + } + else { + final BooleanAbilityTargetCheckReceiver noTargetReceiver = BooleanAbilityTargetCheckReceiver + .getInstance().reset(); + abilityToUse.checkCanTargetNoTarget(this.war3MapViewer.simulation, + this.selectedUnit.getSimulationUnit(), orderId, noTargetReceiver); + if (noTargetReceiver.isTargetable()) { + this.unitOrderListener.issueImmediateOrder(this.selectedUnit.getSimulationUnit().getHandleId(), + abilityHandleId, orderId, isShiftDown()); + } + else { + this.activeCommand = abilityToUse; + this.activeCommandOrderId = orderId; + this.activeCommandUnit = this.selectedUnit; + } + } + } + } + + public void showCommandError(final String message) { + this.errorMessageFrame.setText(message); + } + + @Override + public void toggleAutoCastAbility(final int abilityHandleId) { + + } + public void update(final float deltaTime) { this.portrait.update(); @@ -329,7 +415,10 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener { this.cursorFrame.setFramePointX(FramePoint.LEFT, screenCoordsVector.x); this.cursorFrame.setFramePointY(FramePoint.BOTTOM, screenCoordsVector.y); - if (down) { + if (this.activeCommand != null) { + this.cursorFrame.setSequence("Target"); + } + else if (down) { if (left) { this.cursorFrame.setSequence("Scroll Down Left"); } @@ -383,6 +472,17 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener { this.portrait.talk(); } + private final class ActiveCommandUnitTargetFilter implements CUnitFilterFunction { + @Override + public boolean call(final CUnit unit) { + final BooleanAbilityTargetCheckReceiver targetReceiver = BooleanAbilityTargetCheckReceiver + .getInstance(); + MeleeUI.this.activeCommand.checkCanTarget(MeleeUI.this.war3MapViewer.simulation, unit, + MeleeUI.this.activeCommandOrderId, unit, targetReceiver); + return targetReceiver.isTargetable(); + } + } + private static final class Portrait { private MdxComplexInstance modelInstance; private final PortraitCameraManager portraitCameraManager; @@ -539,11 +639,10 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener { @Override public void commandButton(final int buttonPositionX, final int buttonPositionY, final Texture icon, - final int orderId) { + final int abilityHandleId, final int orderId) { final int x = Math.max(0, Math.min(COMMAND_CARD_WIDTH - 1, buttonPositionX)); final int y = Math.max(0, Math.min(COMMAND_CARD_HEIGHT - 1, buttonPositionY)); - this.commandCard[y][x].setCommandButtonData(icon, orderId); - + this.commandCard[y][x].setCommandButtonData(icon, abilityHandleId, orderId); } public void resize(final Rectangle viewport) { @@ -649,17 +748,191 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener { this.cameraManager.scrolled(amount); } - public boolean touchDown(final float screenX, final float screenY, final int button) { - if (this.meleeUIMinimap.containsMouse(screenX, screenY)) { - final Vector2 worldPoint = this.meleeUIMinimap.getWorldPointFromScreen(screenX, screenY); + public boolean touchDown(final int screenX, final int screenY, final float worldScreenY, final int button) { + screenCoordsVector.set(screenX, screenY); + this.uiViewport.unproject(screenCoordsVector); + if (this.meleeUIMinimap.containsMouse(screenCoordsVector.x, screenCoordsVector.y)) { + final Vector2 worldPoint = this.meleeUIMinimap.getWorldPointFromScreen(screenCoordsVector.x, + screenCoordsVector.y); this.cameraManager.target.x = worldPoint.x; this.cameraManager.target.y = worldPoint.y; return true; } - screenCoordsVector.set(screenX, screenY); - this.uiViewport.unproject(screenCoordsVector); - this.rootFrame.touchDown(GameUI.unconvertX(this.uiViewport, screenCoordsVector.x), - GameUI.unconvertY(this.uiViewport, screenCoordsVector.y), button); + final UIFrame clickedUIFrame = this.rootFrame.touchDown(screenCoordsVector.x, screenCoordsVector.y, button); + if (clickedUIFrame == null) { + // try to interact with world + if (this.activeCommand != null) { + if (button == Input.Buttons.RIGHT) { + this.activeCommandUnit = null; + this.activeCommand = null; + this.activeCommandOrderId = -1; + } + else { + final RenderUnit rayPickUnit = this.war3MapViewer.rayPickUnit(screenX, worldScreenY, + this.activeCommandUnitTargetFilter); + final boolean shiftDown = isShiftDown(); + if (rayPickUnit != null) { + if (this.unitOrderListener.issueTargetOrder( + this.activeCommandUnit.getSimulationUnit().getHandleId(), + this.activeCommand.getHandleId(), this.activeCommandOrderId, + rayPickUnit.getSimulationUnit().getHandleId(), shiftDown)) { + if (getSelectedUnit().soundset.yesAttack + .playUnitResponse(this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { + portraitTalk(); + } + this.selectedSoundCount = 0; + if (!shiftDown) { + this.activeCommandUnit = null; + this.activeCommand = null; + this.activeCommandOrderId = -1; + } + } + } + else { + this.war3MapViewer.getClickLocation(clickLocationTemp, screenX, (int) worldScreenY); + this.activeCommand.checkCanTarget(this.war3MapViewer.simulation, + this.activeCommandUnit.getSimulationUnit(), this.activeCommandOrderId, + clickLocationTemp2, PointAbilityTargetCheckReceiver.INSTANCE); + final Vector2 target = PointAbilityTargetCheckReceiver.INSTANCE.getTarget(); + if (target != null) { + if (this.activeCommand instanceof CAbilityAttack) { + this.war3MapViewer.showConfirmation(clickLocationTemp, 1, 0, 0); + } + else { + this.war3MapViewer.showConfirmation(clickLocationTemp, 0, 1, 0); + } + if (this.unitOrderListener.issuePointOrder( + this.activeCommandUnit.getSimulationUnit().getHandleId(), + this.activeCommand.getHandleId(), this.activeCommandOrderId, clickLocationTemp2.x, + clickLocationTemp2.y, shiftDown)) { + if (getSelectedUnit().soundset.yes.playUnitResponse( + this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { + portraitTalk(); + } + this.selectedSoundCount = 0; + if (!shiftDown) { + this.activeCommandUnit = null; + this.activeCommand = null; + this.activeCommandOrderId = -1; + } + } + } + } + } + } + else { + if (button == Input.Buttons.RIGHT) { + final RenderUnit rayPickUnit = this.war3MapViewer.rayPickUnit(screenX, worldScreenY); + if (getSelectedUnit() != null) { + if ((rayPickUnit != null) && (rayPickUnit.playerIndex != getSelectedUnit().playerIndex) + && !rayPickUnit.getSimulationUnit().isDead()) { + boolean ordered = false; + for (final RenderUnit unit : this.war3MapViewer.selected) { + for (final CAbility ability : unit.getSimulationUnit().getAbilities()) { + ability.checkCanTarget(this.war3MapViewer.simulation, unit.getSimulationUnit(), + OrderIds.smart, rayPickUnit.getSimulationUnit(), + CWidgetAbilityTargetCheckReceiver.INSTANCE); + final CWidget targetWidget = CWidgetAbilityTargetCheckReceiver.INSTANCE.getTarget(); + if (targetWidget != null) { + this.unitOrderListener.issueTargetOrder(unit.getSimulationUnit().getHandleId(), + ability.getHandleId(), OrderIds.smart, targetWidget.getHandleId(), + isShiftDown()); + ordered = true; + } + } + + } + if (ordered) { + if (getSelectedUnit().soundset.yesAttack.playUnitResponse( + this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { + portraitTalk(); + } + this.selectedSoundCount = 0; + } + } + else { + this.war3MapViewer.getClickLocation(clickLocationTemp, screenX, (int) worldScreenY); + this.war3MapViewer.showConfirmation(clickLocationTemp, 0, 1, 0); + clickLocationTemp2.set(clickLocationTemp.x, clickLocationTemp.y); + + boolean ordered = false; + for (final RenderUnit unit : this.war3MapViewer.selected) { + for (final CAbility ability : unit.getSimulationUnit().getAbilities()) { + ability.checkCanUse(this.war3MapViewer.simulation, unit.getSimulationUnit(), + OrderIds.smart, BooleanAbilityActivationReceiver.INSTANCE); + if (BooleanAbilityActivationReceiver.INSTANCE.isOk()) { + ability.checkCanTarget(this.war3MapViewer.simulation, unit.getSimulationUnit(), + OrderIds.smart, clickLocationTemp2, + PointAbilityTargetCheckReceiver.INSTANCE); + final Vector2 target = PointAbilityTargetCheckReceiver.INSTANCE.getTarget(); + if (target != null) { + this.unitOrderListener.issuePointOrder( + unit.getSimulationUnit().getHandleId(), ability.getHandleId(), + OrderIds.smart, clickLocationTemp2.x, clickLocationTemp2.y, + isShiftDown()); + ordered = true; + } + } + } + + } + + if (ordered) { + if (getSelectedUnit().soundset.yes.playUnitResponse( + this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { + portraitTalk(); + } + this.selectedSoundCount = 0; + } + } + } + } + else { + final List selectedUnits = this.war3MapViewer.selectUnit(screenX, worldScreenY, false); + if (!selectedUnits.isEmpty()) { + final RenderUnit unit = selectedUnits.get(0); + final boolean selectionChanged = getSelectedUnit() != unit; + boolean playedNewSound = false; + if (selectionChanged) { + this.selectedSoundCount = 0; + } + if (unit.soundset != null) { + UnitSound ackSoundToPlay = unit.soundset.what; + final int pissedSoundCount = unit.soundset.pissed.getSoundCount(); + int soundIndex; + if ((this.selectedSoundCount >= 3) && (pissedSoundCount > 0)) { + soundIndex = this.selectedSoundCount - 3; + ackSoundToPlay = unit.soundset.pissed; + } + else { + soundIndex = (int) (Math.random() * ackSoundToPlay.getSoundCount()); + } + if (ackSoundToPlay.playUnitResponse(this.war3MapViewer.worldScene.audioContext, unit, + soundIndex)) { + this.selectedSoundCount++; + if ((this.selectedSoundCount - 3) >= pissedSoundCount) { + this.selectedSoundCount = 0; + } + playedNewSound = true; + } + } + if (selectionChanged) { + selectUnit(unit); + } + if (playedNewSound) { + portraitTalk(); + } + } + else { + selectUnit(null); + } + } + } + } return false; } + + private static boolean isShiftDown() { + return Gdx.input.isKeyPressed(Input.Keys.SHIFT_LEFT) || Gdx.input.isKeyPressed(Input.Keys.SHIFT_RIGHT); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/ActiveCommand.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/ActiveCommand.java new file mode 100644 index 0000000..269d9bd --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/ActiveCommand.java @@ -0,0 +1,8 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.ui.command; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; + +public interface ActiveCommand { + boolean finish(CSimulation simulation, CUnit selectedUnit, float mouseScreenX, float mouseScreenY); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/CommandCardCommandListener.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/CommandCardCommandListener.java new file mode 100644 index 0000000..2f0ea28 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/CommandCardCommandListener.java @@ -0,0 +1,7 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.ui.command; + +public interface CommandCardCommandListener { + void startUsingAbility(int abilityHandleId, int orderId); + + void toggleAutoCastAbility(int abilityHandleId); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/CommandErrorListener.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/CommandErrorListener.java new file mode 100644 index 0000000..0da222f --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/CommandErrorListener.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.ui.command; + +public interface CommandErrorListener { + void showCommandError(String message); +} From e21bb492c12a2a290901c6e0b03f9cf02915a6ad Mon Sep 17 00:00:00 2001 From: Retera Date: Sun, 25 Oct 2020 12:05:16 -0400 Subject: [PATCH 055/116] Remove click drag test system --- .../etheller/warsmash/WarsmashGdxMapGame.java | 20 ++++++--- .../fdf/frames/FilterModeTextureFrame.java | 33 +++++++++++++++ .../parsers/fdf/frames/TextureFrame.java | 3 -- .../simulation/orders/COrderTargetWidget.java | 5 +++ .../viewer5/handlers/w3x/ui/MeleeUI.java | 42 ++++--------------- .../handlers/w3x/ui/MeleeUIMinimap.java | 8 ---- .../warsmash/desktop/DesktopLauncher.java | 5 +-- 7 files changed, 62 insertions(+), 54 deletions(-) create mode 100644 core/src/com/etheller/warsmash/parsers/fdf/frames/FilterModeTextureFrame.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetWidget.java diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java index 5687bd8..64375dd 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java @@ -161,8 +161,8 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv // this.mainModel = (MdxModel) this.viewer.load("UI\\Glues\\MainMenu\\MainMenu3D_exp\\MainMenu3D_exp.mdx", // libGDX stuff - final float w = Gdx.graphics.getWidth(); - final float h = Gdx.graphics.getHeight(); + final int width = Gdx.graphics.getWidth(); + final int height = Gdx.graphics.getHeight(); final FreeTypeFontGenerator fontGenerator = new FreeTypeFontGenerator( new DataSourceFileHandle(this.viewer.dataSource, "fonts\\FRIZQT__.TTF")); @@ -177,8 +177,18 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv // height // Height is multiplied by aspect ratio. this.uiCamera = new OrthographicCamera(); - this.uiViewport = new ExtendViewport(1600, 1200, this.uiCamera); - this.uiViewport.update(Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); + int aspect3By4Width; + int aspect3By4Height; + if (width < ((height * 4) / 3)) { + aspect3By4Width = width; + aspect3By4Height = (width * 3) / 4; + } + else { + aspect3By4Width = (height * 4) / 3; + aspect3By4Height = height; + } + this.uiViewport = new ExtendViewport(aspect3By4Width, aspect3By4Height, this.uiCamera); + this.uiViewport.update(width, height); this.uiCamera.position.set(this.uiViewport.getMinWorldWidth() / 2, this.uiViewport.getMinWorldHeight() / 2, 0); this.uiCamera.update(); @@ -237,7 +247,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv updateUIScene(); - resize(Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); + resize(width, height); try { this.viewer.loadAfterUI(); diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/FilterModeTextureFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/FilterModeTextureFrame.java new file mode 100644 index 0000000..653c9df --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/FilterModeTextureFrame.java @@ -0,0 +1,33 @@ +package com.etheller.warsmash.parsers.fdf.frames; + +import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.g2d.GlyphLayout; +import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.etheller.warsmash.parsers.fdf.datamodel.Vector4Definition; +import com.etheller.warsmash.parsers.mdlx.Layer.FilterMode; + +public class FilterModeTextureFrame extends TextureFrame { + private int blendSrc; + private int blendDst; + + public FilterModeTextureFrame(final String name, final UIFrame parent, final boolean decorateFileNames, + final Vector4Definition texCoord) { + super(name, parent, decorateFileNames, texCoord); + } + + @Override + protected void internalRender(final SpriteBatch batch, final BitmapFont baseFont, final GlyphLayout glyphLayout) { + final int blendDstFunc = batch.getBlendDstFunc(); + final int blendSrcFunc = batch.getBlendSrcFunc(); + batch.setBlendFunction(this.blendSrc, this.blendDst); + super.internalRender(batch, baseFont, glyphLayout); + batch.setBlendFunction(blendSrcFunc, blendDstFunc); + } + + public void setFilterMode(final FilterMode filterMode) { + final int[] layerFilterMode = com.etheller.warsmash.viewer5.handlers.mdx.FilterMode.layerFilterMode(filterMode); + this.blendSrc = layerFilterMode[0]; + this.blendDst = layerFilterMode[1]; + } + +} diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/TextureFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/TextureFrame.java index 3e1ae18..203a167 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/TextureFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/TextureFrame.java @@ -66,9 +66,6 @@ public class TextureFrame extends AbstractRenderableFrame { @Override public UIFrame touchDown(final float screenX, final float screenY, final int button) { - if (this.renderBounds.contains(screenX, screenY)) { - return this; - } return super.touchDown(screenX, screenY, button); } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetWidget.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetWidget.java new file mode 100644 index 0000000..13ce91a --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetWidget.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders; + +public class COrderTargetWidget { + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java index 86643d5..0deff81 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java @@ -14,7 +14,6 @@ import com.badlogic.gdx.graphics.g2d.GlyphLayout; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator; import com.badlogic.gdx.graphics.glutils.ShapeRenderer; -import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.math.Vector3; @@ -138,8 +137,6 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma private int selectedSoundCount = 0; private final ActiveCommandUnitTargetFilter activeCommandUnitTargetFilter; - private UIFrame clickUI = null; - public MeleeUI(final DataSource dataSource, final Viewport uiViewport, final FreeTypeFontGenerator fontGenerator, final Scene uiScene, final Scene portraitScene, final CameraPreset[] cameraPresets, final CameraRates cameraRates, final War3MapViewer war3MapViewer, final RootFrameListener rootFrameListener, @@ -421,7 +418,9 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma mouseX = Math.max(minX, Math.min(maxX, mouseX)); mouseY = Math.max(minY, Math.min(maxY, mouseY)); -// Gdx.input.setCursorPosition(mouseX, mouseY); + if (Gdx.input.isCursorCatched()) { + Gdx.input.setCursorPosition(mouseX, mouseY); + } screenCoordsVector.set(mouseX, mouseY); this.uiViewport.unproject(screenCoordsVector); @@ -481,18 +480,6 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.meleeUIMinimap.render(batch, this.war3MapViewer.units); this.timeIndicator.setFrameByRatio(this.war3MapViewer.simulation.getGameTimeOfDay() / this.war3MapViewer.simulation.getGameplayConstants().getGameDayHours()); - - if (this.clickUI != null) { - batch.end(); - this.shapeRenderer.setProjectionMatrix(this.uiViewport.getCamera().combined); - this.shapeRenderer.begin(ShapeType.Line); - this.shapeRenderer.rect(this.clickUI.getFramePointX(FramePoint.LEFT), - this.clickUI.getFramePointY(FramePoint.BOTTOM), - this.clickUI.getFramePointX(FramePoint.RIGHT) - this.clickUI.getFramePointX(FramePoint.LEFT), - this.clickUI.getFramePointY(FramePoint.TOP) - this.clickUI.getFramePointY(FramePoint.BOTTOM)); - this.shapeRenderer.end(); - batch.begin(); - } } public void portraitTalk() { @@ -787,8 +774,6 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.cameraManager.scrolled(amount); } - private float lastX, lastY; - public boolean touchDown(final int screenX, final int screenY, final float worldScreenY, final int button) { screenCoordsVector.set(screenX, screenY); this.uiViewport.unproject(screenCoordsVector); @@ -797,8 +782,6 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma screenCoordsVector.y); this.cameraManager.target.x = worldPoint.x; this.cameraManager.target.y = worldPoint.y; - this.lastX = screenCoordsVector.x; - this.lastY = screenCoordsVector.y; return true; } final UIFrame clickedUIFrame = this.rootFrame.touchDown(screenCoordsVector.x, screenCoordsVector.y, button); @@ -973,11 +956,6 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } } } - else { - this.clickUI = clickedUIFrame; - this.lastX = screenCoordsVector.x; - this.lastY = screenCoordsVector.y; - } return false; } @@ -988,19 +966,13 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma public boolean touchDragged(final int screenX, final int screenY, final float worldScreenY, final int pointer) { screenCoordsVector.set(screenX, screenY); this.uiViewport.unproject(screenCoordsVector); - final float dx = screenCoordsVector.x - this.lastX; - final float dy = screenCoordsVector.y - this.lastY; if (this.meleeUIMinimap.containsMouse(screenCoordsVector.x, screenCoordsVector.y)) { - this.meleeUIMinimap.touchDragged(screenX, screenY, worldScreenY, pointer, dx, dy); + final Vector2 worldPoint = this.meleeUIMinimap.getWorldPointFromScreen(screenCoordsVector.x, + screenCoordsVector.y); + this.cameraManager.target.x = worldPoint.x; + this.cameraManager.target.y = worldPoint.y; } - else if (this.clickUI != null) { - this.clickUI.setFramePointX(FramePoint.LEFT, this.clickUI.getFramePointX(FramePoint.LEFT) + dx); - this.clickUI.setFramePointY(FramePoint.BOTTOM, this.clickUI.getFramePointY(FramePoint.BOTTOM) + dy); - } - - this.lastX = screenCoordsVector.x; - this.lastY = screenCoordsVector.y; return false; } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUIMinimap.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUIMinimap.java index ccd0ee9..fcaf00f 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUIMinimap.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUIMinimap.java @@ -59,12 +59,4 @@ public class MeleeUIMinimap { public boolean containsMouse(final float x, final float y) { return this.minimapFilledArea.contains(x, y); } - - public void touchDragged(final int screenX, final int screenY, final float worldScreenY, final int pointer, - final float dx, final float dy) { - this.minimapFilledArea.x += dx; - this.minimapFilledArea.y += dy; - this.minimap.x += dx; - this.minimap.y += dy; - } } diff --git a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java index 0675b3a..9caaca2 100644 --- a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java +++ b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java @@ -24,7 +24,7 @@ import com.etheller.warsmash.viewer5.gl.SoundLengthExtension; import com.etheller.warsmash.viewer5.gl.WireframeExtension; public class DesktopLauncher { - public static void main(String[] arg) { + public static void main(final String[] arg) { Extensions.angleInstancedArrays = new ANGLEInstancedArrays() { @Override public void glVertexAttribDivisorANGLE(final int index, final int divisor) { @@ -83,11 +83,10 @@ public class DesktopLauncher { config.useGL30 = true; config.gles30ContextMajorVersion = 3; config.gles30ContextMinorVersion = 3; - //config.samples = 16; + // config.samples = 16; // config.vSyncEnabled = false; // config.foregroundFPS = 0; // config.backgroundFPS = 0; - arg = new String[]{"-windowed"}; if ((arg.length > 0) && "-windowed".equals(arg[0])) { config.fullscreen = false; } From 0c0141cd71518884753f948342bb7faded7f15fc Mon Sep 17 00:00:00 2001 From: Retera Date: Mon, 26 Oct 2020 08:34:33 -0400 Subject: [PATCH 056/116] Update ui --- .../etheller/warsmash/WarsmashGdxMapGame.java | 3 +- .../warsmash/viewer5/SkeletalNode.java | 15 +- .../handlers/mdx/MdxComplexInstance.java | 1511 +++++++++-------- .../viewer5/handlers/w3x/War3MapViewer.java | 75 - .../handlers/w3x/rendersim/RenderUnit.java | 4 + .../commandbuttons/CommandButtonListener.java | 2 +- .../CommandCardPopulatingAbilityVisitor.java | 52 +- .../handlers/w3x/simulation/CSimulation.java | 17 +- .../handlers/w3x/simulation/CUnit.java | 149 +- .../w3x/simulation/CUnitStateListener.java | 6 +- .../w3x/simulation/abilities/CAbility.java | 7 +- .../simulation/abilities/CAbilityAttack.java | 29 +- .../abilities/CAbilityColdArrows.java | 117 ++ .../simulation/abilities/CAbilityGeneric.java | 84 + .../simulation/abilities/CAbilityMove.java | 32 +- .../simulation/abilities/CAbilityVisitor.java | 4 + .../behaviors/CAbstractRangedBehavior.java | 108 ++ .../w3x/simulation/behaviors/CBehavior.java | 13 + .../simulation/behaviors/CBehaviorAttack.java | 111 ++ .../simulation/behaviors/CBehaviorFollow.java | 38 + .../{orders => behaviors}/CBehaviorMove.java | 78 +- .../simulation/behaviors/CBehaviorPatrol.java | 37 + .../simulation/behaviors/CBehaviorStop.java | 22 + .../simulation/behaviors/CRangedBehavior.java | 7 + .../w3x/simulation/data/CAbilityData.java | 21 +- .../w3x/simulation/data/CUnitData.java | 736 ++++---- .../w3x/simulation/orders/CBehavior.java | 23 - .../simulation/orders/CBehaviorAttack.java | 177 -- .../simulation/orders/CBehaviorPatrol.java | 96 -- .../w3x/simulation/orders/CBehaviorStop.java | 22 - .../w3x/simulation/orders/COrder.java | 18 + .../w3x/simulation/orders/COrderNoTarget.java | 33 + .../simulation/orders/COrderTargetPoint.java | 57 + .../simulation/orders/COrderTargetWidget.java | 51 +- .../players/CPlayerUnitOrderExecutor.java | 78 +- .../players/CPlayerUnitOrderListener.java | 6 +- .../handlers/w3x/ui/CommandCardIcon.java | 16 +- .../viewer5/handlers/w3x/ui/MeleeUI.java | 97 +- .../command/CommandCardCommandListener.java | 2 - 39 files changed, 2194 insertions(+), 1760 deletions(-) create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityColdArrows.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityGeneric.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CAbstractRangedBehavior.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehavior.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorFollow.java rename core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/{orders => behaviors}/CBehaviorMove.java (87%) create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorPatrol.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorStop.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CRangedBehavior.java delete mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CBehavior.java delete mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CBehaviorAttack.java delete mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CBehaviorPatrol.java delete mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CBehaviorStop.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrder.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderNoTarget.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetPoint.java diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java index 64375dd..55e06f0 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java @@ -309,7 +309,8 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.font.setColor(Color.YELLOW); final String fpsString = "FPS: " + Gdx.graphics.getFramesPerSecond(); this.glyphLayout.setText(this.font, fpsString); - this.font.draw(this.batch, fpsString, (this.uiViewport.getMinWorldWidth() - this.glyphLayout.width) / 2, 1100); + this.font.draw(this.batch, fpsString, (this.uiViewport.getMinWorldWidth() - this.glyphLayout.width) / 2, + 1100 * this.meleeUI.getHeightRatioCorrection()); this.batch.end(); Gdx.gl30.glEnable(GL30.GL_SCISSOR_TEST); diff --git a/core/src/com/etheller/warsmash/viewer5/SkeletalNode.java b/core/src/com/etheller/warsmash/viewer5/SkeletalNode.java index bf6a231..134d393 100644 --- a/core/src/com/etheller/warsmash/viewer5/SkeletalNode.java +++ b/core/src/com/etheller/warsmash/viewer5/SkeletalNode.java @@ -21,6 +21,8 @@ public abstract class SkeletalNode extends GenericNode { public boolean billboardedY; public boolean billboardedZ; + public Matrix4 localBlendMatrix; + public SkeletalNode() { this.pivot = new Vector3(); this.localLocation = new Vector3(); @@ -33,6 +35,7 @@ public abstract class SkeletalNode extends GenericNode { this.inverseWorldRotation = new Quaternion(); this.inverseWorldScale = new Vector3(); this.localMatrix = new Matrix4(); + this.localBlendMatrix = new Matrix4(); this.worldMatrix = new Matrix4(); this.dontInheritTranslation = false; this.dontInheritRotation = false; @@ -66,7 +69,7 @@ public abstract class SkeletalNode extends GenericNode { this.billboardedZ = false; } - public void recalculateTransformation(final Scene scene) { + public void recalculateTransformation(final Scene scene, final float blendTimeRatio) { final Quaternion computedRotation; Vector3 computedScaling; @@ -135,6 +138,12 @@ public abstract class SkeletalNode extends GenericNode { RenderMathUtils.fromRotationTranslationScaleOrigin(computedRotation, this.localLocation, computedScaling, this.localMatrix, this.pivot); + if (!Float.isNaN(blendTimeRatio) && (blendTimeRatio > 0)) { + for (int i = 0; i < this.localMatrix.val.length; i++) { + this.localMatrix.val[i] = (this.localBlendMatrix.val[i] * blendTimeRatio) + + (this.localMatrix.val[i] * (1 - blendTimeRatio)); + } + } RenderMathUtils.mul(this.worldMatrix, this.parent.worldMatrix, this.localMatrix); @@ -168,6 +177,10 @@ public abstract class SkeletalNode extends GenericNode { this.inverseWorldLocation.z = -this.worldLocation.z; } + public void beginBlending() { + this.localBlendMatrix.set(this.localMatrix); + } + public void updateChildren(final float dt, final Scene scene) { for (int i = 0, l = this.children.size(); i < l; i++) { this.children.get(i).update(dt, scene); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java index 093433a..9b05474 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java @@ -26,744 +26,775 @@ import com.etheller.warsmash.viewer5.gl.DataTexture; import com.etheller.warsmash.viewer5.handlers.w3x.DynamicShadowManager; public class MdxComplexInstance extends ModelInstance { - private static final float[] visibilityHeap = new float[1]; - private static final float[] translationHeap = new float[3]; - private static final float[] rotationHeap = new float[4]; - private static final float[] scaleHeap = new float[3]; - private static final float[] colorHeap = new float[3]; - private static final float[] alphaHeap = new float[1]; - private static final long[] textureIdHeap = new long[1]; - - public List lights = new ArrayList<>(); - public List attachments = new ArrayList<>(); - public List particleEmitters = new ArrayList<>(); - public List particleEmitters2 = new ArrayList<>(); - public List ribbonEmitters = new ArrayList<>(); - public List> eventObjectEmitters = new ArrayList<>(); - public MdxNode[] nodes; - public SkeletalNode[] sortedNodes; - public int frame = 0; - public float floatingFrame = 0; - // Global sequences - public int counter = 0; - public int sequence = -1; - public SequenceLoopMode sequenceLoopMode = SequenceLoopMode.NEVER_LOOP; - public boolean sequenceEnded = false; - public float[] vertexColor = {1, 1, 1, 1}; - // Particles do not spawn when the sequence is -1, or when the sequence finished - // and it's not repeating - public boolean allowParticleSpawn = false; - // If forced is true, everything will update regardless of variancy. - // Any later non-forced update can then use variancy to skip updating things. - // It is set to true every time the sequence is set with setSequence(). - public boolean forced = true; - public float[][] geosetColors; - public float[] layerAlphas; - public int[] layerTextures; - public float[][] uvAnims; - public Matrix4[] worldMatrices; - public FloatBuffer worldMatricesCopyHeap; - public DataTexture boneTexture; - public Texture[] replaceableTextures = new Texture[WarsmashConstants.REPLACEABLE_TEXTURE_LIMIT]; - private float animationSpeed = 1.0f; - - public MdxComplexInstance(final MdxModel model) { - super(model); - } - - @Override - public void load() { - final MdxModel model = (MdxModel) this.model; - - this.geosetColors = new float[model.geosets.size()][]; - for (int i = 0, l = model.geosets.size(); i < l; i++) { - this.geosetColors[i] = new float[4]; - } - - this.layerAlphas = new float[model.layers.size()]; - this.layerTextures = new int[model.layers.size()]; - this.uvAnims = new float[model.layers.size()][]; - for (int i = 0, l = model.layers.size(); i < l; i++) { - this.layerAlphas[i] = 0; - this.layerTextures[i] = 0; - this.uvAnims[i] = new float[5]; - } - - // Create the needed amount of shared nodes. - final Object[] sharedNodeData = Node.createSkeletalNodes(model.genericObjects.size(), - MdxNodeDescriptor.INSTANCE); - final List nodes = (List) sharedNodeData[0]; - int nodeIndex = 0; - this.nodes = nodes.toArray(new MdxNode[nodes.size()]); - - // A shared typed array for all world matrices of the internal nodes. - this.worldMatrices = ((List) sharedNodeData[1]).toArray(new Matrix4[0]); - this.worldMatricesCopyHeap = ByteBuffer.allocateDirect(16 * this.worldMatrices.length * 4) - .order(ByteOrder.nativeOrder()).asFloatBuffer(); - - // And now initialize all of the nodes and objects - for (final Bone bone : model.bones) { - this.initNode(this.nodes, this.nodes[nodeIndex++], bone); - } - - for (final Light light : model.lights) { - final LightInstance lightInstance = new LightInstance(this, light); - this.lights.add(lightInstance); - this.initNode(this.nodes, this.nodes[nodeIndex++], light, lightInstance); - } - - for (final Helper helper : model.helpers) { - this.initNode(this.nodes, this.nodes[nodeIndex++], helper); - } - - for (final Attachment attachment : model.attachments) { - AttachmentInstance attachmentInstance = null; - - // Attachments may have game models attached to them, such as Undead and - // Nightelf building animations. - if (attachment.internalModel != null) { - attachmentInstance = new AttachmentInstance(this, attachment); - - this.attachments.add(attachmentInstance); - } - - this.initNode(this.nodes, this.nodes[nodeIndex++], attachment, attachmentInstance); - } - - for (final ParticleEmitterObject emitterObject : model.particleEmitters) { - final ParticleEmitter emitter = new ParticleEmitter(this, emitterObject); - - this.particleEmitters.add(emitter); - - this.initNode(this.nodes, this.nodes[nodeIndex++], emitterObject, emitter); - } - - for (final ParticleEmitter2Object emitterObject : model.particleEmitters2) { - final ParticleEmitter2 emitter = new ParticleEmitter2(this, emitterObject); - - this.particleEmitters2.add(emitter); - - this.initNode(this.nodes, this.nodes[nodeIndex++], emitterObject, emitter); - } - - for (final RibbonEmitterObject emitterObject : model.ribbonEmitters) { - final RibbonEmitter emitter = new RibbonEmitter(this, emitterObject); - - this.ribbonEmitters.add(emitter); - - this.initNode(this.nodes, this.nodes[nodeIndex++], emitterObject, emitter); - } - - for (final EventObjectEmitterObject emitterObject : model.eventObjects) { - final String type = emitterObject.type; - EventObjectEmitter emitter; - - if ("SPN".equals(type)) { - emitter = new EventObjectSpnEmitter(this, emitterObject); - } else if ("SPL".equals(type)) { - emitter = new EventObjectSplEmitter(this, emitterObject); - } else if ("UBR".equals(type)) { - emitter = new EventObjectUbrEmitter(this, emitterObject); - } else { - emitter = new EventObjectSndEmitter(this, emitterObject); - } - - this.eventObjectEmitters.add(emitter); - - this.initNode(this.nodes, this.nodes[nodeIndex++], emitterObject, emitter); - } - - for (final CollisionShape collisionShape : model.collisionShapes) { - this.initNode(this.nodes, this.nodes[nodeIndex++], collisionShape); - } - - // Save a sorted array of all of the nodes, such that every child node comes - // after its parent. - // This allows for flat iteration when updating. - final List hierarchy = model.hierarchy; - - this.sortedNodes = new SkeletalNode[nodes.size()]; - for (int i = 0, l = nodes.size(); i < l; i++) { - this.sortedNodes[i] = this.nodes[hierarchy.get(i)]; - } - - // If the sequence was changed before the model was loaded, reset it now that - // the model loaded. - this.setSequence(this.sequence); - - if (model.bones.size() != 0) { - this.boneTexture = new DataTexture(model.viewer.gl, 4, model.bones.size() * 4, 1); - } - } - - /* - * Clear all of the emitted objects that belong to this instance. - */ - @Override - public void clearEmittedObjects() { - for (final ParticleEmitter emitter : this.particleEmitters) { - emitter.clear(); - } - - for (final ParticleEmitter2 emitter : this.particleEmitters2) { - emitter.clear(); - } - - for (final RibbonEmitter emitter : this.ribbonEmitters) { - emitter.clear(); - } - - for (final EventObjectEmitter emitter : this.eventObjectEmitters) { - emitter.clear(); - } - } - - private void initNode(final MdxNode[] nodes, final SkeletalNode node, final GenericObject genericObject) { - initNode(nodes, node, genericObject, null); - } - - /** - * Initialize a skeletal node. - */ - private void initNode(final MdxNode[] nodes, final SkeletalNode node, final GenericObject genericObject, - final UpdatableObject object) { - node.pivot.set(genericObject.pivot); - - if (genericObject.parentId == -1) { - node.parent = this; - } else { - node.parent = nodes[genericObject.parentId]; - } - - /// TODO: single-axis billboarding - if (genericObject.billboarded != 0) { - node.billboarded = true; - } else if (genericObject.billboardedX != 0) { - node.billboardedX = true; - } else if (genericObject.billboardedY != 0) { - node.billboardedY = true; - } else if (genericObject.billboardedZ != 0) { - node.billboardedZ = true; - } - - if (object != null) { - node.object = object; - } - - } - - /* - * Overriden to hide also attachment models. - */ - @Override - public void hide() { - super.hide(); - - for (final AttachmentInstance attachment : this.attachments) { - attachment.internalInstance.hide(); - } - } - - /** - * Updates all of this instance internal nodes and objects. Nodes that are - * determined to not be visible will not be updated, nor will any of their - * children down the hierarchy. - */ - public void updateNodes(final float dt, final boolean forced) { - if (!this.model.ok) { - return; - } - final int sequence = this.sequence; - final int frame = this.frame; - final int counter = this.counter; - final SkeletalNode[] sortedNodes = this.sortedNodes; - final MdxModel model = (MdxModel) this.model; - final List sortedGenericObjects = model.sortedGenericObjects; - final Scene scene = this.scene; - - // Update the nodes - for (int i = 0, l = sortedNodes.length; i < l; i++) { - final GenericObject genericObject = sortedGenericObjects.get(i); - final SkeletalNode node = sortedNodes[i]; - final GenericNode parent = node.parent; - - genericObject.getVisibility(visibilityHeap, sequence, frame, counter); - - final boolean objectVisible = visibilityHeap[0] > 0; - final boolean nodeVisible = forced || (parent.visible && objectVisible); - - node.visible = nodeVisible; - - // Every node only needs to be updated if this is a forced update, or if both - // the parent node and the generic object corresponding to this node are - // visible. - // Incoming messy code for optimizations! - if (nodeVisible) { - boolean wasDirty = false; - final GenericObject.Variants variants = genericObject.variants; - final Vector3 localLocation = node.localLocation; - final Quaternion localRotation = node.localRotation; - final Vector3 localScale = node.localScale; - - // Only update the local node data if there is a need to - if (forced || variants.generic[sequence]) { - wasDirty = true; - - // Translation - if (forced || variants.translation[sequence]) { - genericObject.getTranslation(translationHeap, sequence, frame, counter); - - localLocation.x = translationHeap[0]; - localLocation.y = translationHeap[1]; - localLocation.z = translationHeap[2]; - } - - // Rotation - if (forced || variants.rotation[sequence]) { - genericObject.getRotation(rotationHeap, sequence, frame, counter); - - localRotation.x = rotationHeap[0]; - localRotation.y = rotationHeap[1]; - localRotation.z = rotationHeap[2]; - localRotation.w = rotationHeap[3]; - } - - // Scale - if (forced || variants.scale[sequence]) { - genericObject.getScale(scaleHeap, sequence, frame, counter); - - localScale.x = scaleHeap[0]; - localScale.y = scaleHeap[1]; - localScale.z = scaleHeap[2]; - } - } - - final boolean wasReallyDirty = forced || wasDirty || parent.wasDirty || genericObject.anyBillboarding; - - node.wasDirty = wasReallyDirty; - - // If this is a forced update, or this node's local data was updated, or the - // parent node was updated, do a full world update. - if (wasReallyDirty) { - node.recalculateTransformation(scene); - } - - // If there is an instance object associated with this node, and the node is - // visible (which might not be the case for a forced update!), update the - // object. - // This includes attachments and emitters. - final UpdatableObject object = node.object; - - if (object != null) { - object.update(dt, objectVisible); - } - - // Update all of the node's non-skeletal children, which will update their - // children, and so on. - node.updateChildren(dt, scene); - } - } - } - - /** - * Update the batch data. - */ - public void updateBatches(final boolean forced) { - final int sequence = this.sequence; - final int frame = this.frame; - final int counter = this.counter; - final MdxModel model = (MdxModel) this.model; - if (!model.ok) { - return; - } - final List geosets = model.geosets; - final List layers = model.layers; - final float[][] geosetColors = this.geosetColors; - final float[] layerAlphas = this.layerAlphas; - final int[] layerTextures = this.layerTextures; - final float[][] uvAnims = this.uvAnims; - - // Geoset - for (int i = 0, l = geosets.size(); i < l; i++) { - final Geoset geoset = geosets.get(i); - final GeosetAnimation geosetAnimation = geoset.geosetAnimation; - final float[] geosetColor = geosetColors[i]; - - if (geosetAnimation != null) { - // Color - if (forced || (geosetAnimation.variants.get("color")[sequence] != 0)) { - geosetAnimation.getColor(colorHeap, sequence, frame, counter); - - geosetColor[0] = colorHeap[0]; - geosetColor[1] = colorHeap[1]; - geosetColor[2] = colorHeap[2]; - } - - // Alpha - if (forced || (geosetAnimation.variants.get("alpha")[sequence] != 0)) { - geosetAnimation.getAlpha(alphaHeap, sequence, frame, counter); - - geosetColor[3] = alphaHeap[0]; - } - } else if (forced) { - geosetColor[0] = 1; - geosetColor[1] = 1; - geosetColor[2] = 1; - geosetColor[3] = 1; - } - } - - // Layers - for (int i = 0, l = layers.size(); i < l; i++) { - final Layer layer = layers.get(i); - final TextureAnimation textureAnimation = layer.textureAnimation; - final float[] uvAnim = uvAnims[i]; - - // Alpha - if (forced || (layer.variants.get("alpha")[sequence] != 0)) { - layer.getAlpha(alphaHeap, sequence, frame, counter); - - layerAlphas[i] = alphaHeap[0]; - } - - // Sprite animation - if (forced || (layer.variants.get("textureId")[sequence] != 0)) { - layer.getTextureId(textureIdHeap, sequence, frame, counter); - - layerTextures[i] = (int) textureIdHeap[0]; - } - - if (textureAnimation != null) { - // UV translation animation - if (forced || (textureAnimation.variants.get("translation")[sequence] != 0)) { - textureAnimation.getTranslation(translationHeap, sequence, frame, counter); - - uvAnim[0] = translationHeap[0]; - uvAnim[1] = translationHeap[1]; - } - - // UV rotation animation - if (forced || (textureAnimation.variants.get("rotation")[sequence] != 0)) { - textureAnimation.getRotation(rotationHeap, sequence, frame, counter); - - uvAnim[2] = rotationHeap[2]; - uvAnim[3] = rotationHeap[3]; - } - - // UV scale animation - if (forced || (textureAnimation.variants.get("scale")[sequence] != 0)) { - textureAnimation.getScale(scaleHeap, sequence, frame, counter); - - uvAnim[4] = scaleHeap[0]; - } - } else if (forced) { - uvAnim[0] = 0; - uvAnim[1] = 0; - uvAnim[2] = 0; - uvAnim[3] = 1; - uvAnim[4] = 1; - } - } - } - - public void updateBoneTexture() { - if (this.boneTexture != null) { - this.worldMatricesCopyHeap.clear(); - for (int i = 0, l = this.worldMatrices.length; i < l; i++) { - final Matrix4 worldMatrix = this.worldMatrices[i]; - this.worldMatricesCopyHeap.put((i * 16) + 0, worldMatrix.val[Matrix4.M00]); - this.worldMatricesCopyHeap.put((i * 16) + 1, worldMatrix.val[Matrix4.M10]); - this.worldMatricesCopyHeap.put((i * 16) + 2, worldMatrix.val[Matrix4.M20]); - this.worldMatricesCopyHeap.put((i * 16) + 3, worldMatrix.val[Matrix4.M30]); - this.worldMatricesCopyHeap.put((i * 16) + 4, worldMatrix.val[Matrix4.M01]); - this.worldMatricesCopyHeap.put((i * 16) + 5, worldMatrix.val[Matrix4.M11]); - this.worldMatricesCopyHeap.put((i * 16) + 6, worldMatrix.val[Matrix4.M21]); - this.worldMatricesCopyHeap.put((i * 16) + 7, worldMatrix.val[Matrix4.M31]); - this.worldMatricesCopyHeap.put((i * 16) + 8, worldMatrix.val[Matrix4.M02]); - this.worldMatricesCopyHeap.put((i * 16) + 9, worldMatrix.val[Matrix4.M12]); - this.worldMatricesCopyHeap.put((i * 16) + 10, worldMatrix.val[Matrix4.M22]); - this.worldMatricesCopyHeap.put((i * 16) + 11, worldMatrix.val[Matrix4.M32]); - this.worldMatricesCopyHeap.put((i * 16) + 12, worldMatrix.val[Matrix4.M03]); - this.worldMatricesCopyHeap.put((i * 16) + 13, worldMatrix.val[Matrix4.M13]); - this.worldMatricesCopyHeap.put((i * 16) + 14, worldMatrix.val[Matrix4.M23]); - this.worldMatricesCopyHeap.put((i * 16) + 15, worldMatrix.val[Matrix4.M33]); - } - this.boneTexture.bindAndUpdate(this.worldMatricesCopyHeap); - } - } - - @Override - public void renderOpaque(final Matrix4 mvp) { - final MdxModel model = (MdxModel) this.model; - - for (final GenericGroup group : model.opaqueGroups) { - group.render(this, mvp); - } - } - - @Override - public void renderTranslucent() { - if (DynamicShadowManager.IS_SHADOW_MAPPING) { - return; - } - final MdxModel model = (MdxModel) this.model; - - for (final GenericGroup group : model.translucentGroups) { - group.render(this, this.scene.camera.viewProjectionMatrix); - } - } - - @Override - public void updateAnimations(final float dt) { - final MdxModel model = (MdxModel) this.model; - final int sequenceId = this.sequence; - - if (sequenceId != -1) { - final Sequence sequence = model.sequences.get(sequenceId); - final long[] interval = sequence.getInterval(); - final float frameTime = (dt * 1000 * this.animationSpeed); - - final int lastIntegerFrame = this.frame; - this.floatingFrame += frameTime; - this.frame = (int) this.floatingFrame; - final int integerFrameTime = this.frame - lastIntegerFrame; - this.counter += integerFrameTime; - this.allowParticleSpawn = true; - - if (this.floatingFrame >= interval[1]) { - if ((this.sequenceLoopMode == SequenceLoopMode.ALWAYS_LOOP) - || ((this.sequenceLoopMode == SequenceLoopMode.MODEL_LOOP) && (sequence.getFlags() == 0))) { - this.floatingFrame = this.frame = (int) interval[0]; // TODO not cast - - this.resetEventEmitters(); - } else if (this.sequenceLoopMode == SequenceLoopMode.LOOP_TO_NEXT_ANIMATION) { // faux queued animation - // mode - final float framesPast = this.floatingFrame - interval[1]; - - final List sequences = model.sequences; - this.sequence = (this.sequence + 1) % sequences.size(); - this.floatingFrame = sequences.get(this.sequence).getInterval()[0] + framesPast; // TODO not cast - this.frame = (int) this.floatingFrame; - this.sequenceEnded = false; - this.resetEventEmitters(); - this.forced = true; - } else { - this.floatingFrame = this.frame = (int) interval[1]; // TODO not cast - this.counter -= integerFrameTime; - this.allowParticleSpawn = false; - } - if (this.sequenceLoopMode == SequenceLoopMode.NEVER_LOOP_AND_HIDE_WHEN_DONE) { - hide(); - } - - this.sequenceEnded = true; - } else { - this.sequenceEnded = false; - } - } - - final boolean forced = this.forced; - - if (sequenceId == -1) { - if (forced) { - // Update the nodes - this.updateNodes(dt, forced); - - this.updateBoneTexture(); - - // Update the batches - this.updateBatches(forced); - } - } else { - // let variants = model.variants; - - // if (forced || variants.nodes[sequenceId]) { - // Update the nodes - this.updateNodes(dt, forced); - - this.updateBoneTexture(); - // } - - // if (forced || variants.batches[sequenceId]) { - // Update the batches - this.updateBatches(forced); - // } - } - - this.forced = false; - - } - - @Override - protected void updateLights(final Scene scene) { - for (final LightInstance light : this.lights) { - light.update(scene); - } - } - - @Override - protected void removeLights(final Scene scene2) { - for (final LightInstance light : this.lights) { - light.remove(this.scene); - } - } - - /** - * Set the team color of this instance. - */ - public MdxComplexInstance setTeamColor(final int id) { - this.replaceableTextures[1] = (Texture) this.model.viewer.load( - "ReplaceableTextures\\" + ReplaceableIds.getPathString(1) + ReplaceableIds.getIdString(id) + ".blp", - PathSolver.DEFAULT, null); - this.replaceableTextures[2] = (Texture) this.model.viewer.load( - "ReplaceableTextures\\" + ReplaceableIds.getPathString(2) + ReplaceableIds.getIdString(id) + ".blp", - PathSolver.DEFAULT, null); - return this; - } - - @Override - public void setReplaceableTexture(final int replaceableTextureId, final String replaceableTextureFile) { - this.replaceableTextures[replaceableTextureId] = (Texture) this.model.viewer.load(replaceableTextureFile, - PathSolver.DEFAULT, null); - } - - /** - * Set the vertex color of this instance. - */ - public MdxComplexInstance setVertexColor(final float[] color) { - System.arraycopy(color, 0, this.vertexColor, 0, color.length); - - return this; - } - - /** - * Set the sequence of this instance. - */ - public MdxComplexInstance setSequence(final int id) { - final MdxModel model = (MdxModel) this.model; - - this.sequence = id; - - if (model.ok) { - final List sequences = model.sequences; - - if ((id < 0) || (id > (sequences.size() - 1))) { - this.sequence = -1; - this.frame = 0; - this.floatingFrame = 0; - this.allowParticleSpawn = false; - } else { - this.frame = (int) sequences.get(id).getInterval()[0]; // TODO not cast - this.floatingFrame = this.frame; - this.sequenceEnded = false; - } - - this.resetEventEmitters(); - - this.forced = true; - } - - return this; - } - - /** - * Set the seuqnece loop mode. 0 to never loop, 1 to loop based on the model, - * and 2 to always loop. 3 was added by Retera as "hide after done" for gameplay - * spawned effects - */ - public MdxComplexInstance setSequenceLoopMode(final SequenceLoopMode mode) { - this.sequenceLoopMode = mode; - - return this; - } - - /** - * Get an attachment node. - */ - public MdxNode getAttachment(final int id) { - final MdxModel model = (MdxModel) this.model; - final Attachment attachment = model.attachments.get(id); - - if (attachment != null) { - return this.nodes[attachment.index]; - } - - return null; - } - - /** - * Event emitters depend on keyframe index changes to emit, rather than only - * values. To work, they need to check what the last keyframe was, and only if - * it's a different one, do something. When changing sequences, these states - * need to be reset, so they can immediately emit things if needed. - */ - private void resetEventEmitters() { - /// TODO: Update this. Said Ghostwolf. - for (final EventObjectEmitter eventObjectEmitter : this.eventObjectEmitters) { - eventObjectEmitter.reset(); - } - } - - @Override - protected RenderBatch getBatch(final TextureMapper textureMapper2) { - throw new UnsupportedOperationException("NOT API"); - } - - public void intersectRayBounds(final Ray ray, final Vector3 intersection) { - this.model.bounds.intersectRay(ray, intersection); - } - - /** - * Intersects a world ray with the model's CollisionShapes. Only ever call this - * function on the Gdx thread because it uses static variables to hold state - * while processing. - * - * @param ray - */ - public boolean intersectRayWithCollision(final Ray ray, final Vector3 intersection, final boolean alwaysUseMesh, final boolean onlyUseMesh) { - final MdxModel mdxModel = (MdxModel) this.model; - final List collisionShapes = mdxModel.collisionShapes; - if (!onlyUseMesh) { - for (final CollisionShape collisionShape : collisionShapes) { - final MdxNode mdxNode = this.nodes[collisionShape.index]; - if (collisionShape.checkIntersect(ray, mdxNode, intersection)) { - return true; - } - } - } - if (collisionShapes.isEmpty() || alwaysUseMesh) { - for (final Geoset geoset : mdxModel.geosets) { - if (!geoset.unselectable) { - geoset.getAlpha(alphaHeap, this.sequence, this.frame, this.counter); - if (alphaHeap[0] > 0) { - final com.etheller.warsmash.parsers.mdlx.Geoset mdlxGeoset = geoset.mdlxGeoset; - if (CollisionShape.intersectRayTriangles(ray, this, mdlxGeoset.getVertices(), - mdlxGeoset.getFaces(), 3, intersection)) { - return true; - } - } - } - } - } - return false; - } - - public void setAnimationSpeed(final float speedRatio) { - this.animationSpeed = speedRatio; - } - - public void setFrame(final int frame) { - this.frame = frame; - this.floatingFrame = frame; - } - - public void setFrameByRatio(final float ratioOfAnimationCompleted) { - if (this.sequence != -1) { - final Sequence currentlyPlayingSequence = ((MdxModel) this.model).sequences.get(this.sequence); - this.floatingFrame = currentlyPlayingSequence.getInterval()[0] - + ((currentlyPlayingSequence.getInterval()[1] - currentlyPlayingSequence.getInterval()[0]) - * ratioOfAnimationCompleted); - this.frame = (int) this.floatingFrame; - } - } + private static final float[] visibilityHeap = new float[1]; + private static final float[] translationHeap = new float[3]; + private static final float[] rotationHeap = new float[4]; + private static final float[] scaleHeap = new float[3]; + private static final float[] colorHeap = new float[3]; + private static final float[] alphaHeap = new float[1]; + private static final long[] textureIdHeap = new long[1]; + + public List lights = new ArrayList<>(); + public List attachments = new ArrayList<>(); + public List particleEmitters = new ArrayList<>(); + public List particleEmitters2 = new ArrayList<>(); + public List ribbonEmitters = new ArrayList<>(); + public List> eventObjectEmitters = new ArrayList<>(); + public MdxNode[] nodes; + public SkeletalNode[] sortedNodes; + public int frame = 0; + public float floatingFrame = 0; + // Global sequences + public int counter = 0; + public int sequence = -1; + public SequenceLoopMode sequenceLoopMode = SequenceLoopMode.NEVER_LOOP; + public boolean sequenceEnded = false; + public float[] vertexColor = { 1, 1, 1, 1 }; + // Particles do not spawn when the sequence is -1, or when the sequence finished + // and it's not repeating + public boolean allowParticleSpawn = false; + // If forced is true, everything will update regardless of variancy. + // Any later non-forced update can then use variancy to skip updating things. + // It is set to true every time the sequence is set with setSequence(). + public boolean forced = true; + public float[][] geosetColors; + public float[] layerAlphas; + public int[] layerTextures; + public float[][] uvAnims; + public Matrix4[] worldMatrices; + public FloatBuffer worldMatricesCopyHeap; + public DataTexture boneTexture; + public Texture[] replaceableTextures = new Texture[WarsmashConstants.REPLACEABLE_TEXTURE_LIMIT]; + private float animationSpeed = 1.0f; + private float blendTime; + private float blendTimeRemaining; + + public MdxComplexInstance(final MdxModel model) { + super(model); + } + + @Override + public void load() { + final MdxModel model = (MdxModel) this.model; + + this.geosetColors = new float[model.geosets.size()][]; + for (int i = 0, l = model.geosets.size(); i < l; i++) { + this.geosetColors[i] = new float[4]; + } + + this.layerAlphas = new float[model.layers.size()]; + this.layerTextures = new int[model.layers.size()]; + this.uvAnims = new float[model.layers.size()][]; + for (int i = 0, l = model.layers.size(); i < l; i++) { + this.layerAlphas[i] = 0; + this.layerTextures[i] = 0; + this.uvAnims[i] = new float[5]; + } + + // Create the needed amount of shared nodes. + final Object[] sharedNodeData = Node.createSkeletalNodes(model.genericObjects.size(), + MdxNodeDescriptor.INSTANCE); + final List nodes = (List) sharedNodeData[0]; + int nodeIndex = 0; + this.nodes = nodes.toArray(new MdxNode[nodes.size()]); + + // A shared typed array for all world matrices of the internal nodes. + this.worldMatrices = ((List) sharedNodeData[1]).toArray(new Matrix4[0]); + this.worldMatricesCopyHeap = ByteBuffer.allocateDirect(16 * this.worldMatrices.length * 4) + .order(ByteOrder.nativeOrder()).asFloatBuffer(); + + // And now initialize all of the nodes and objects + for (final Bone bone : model.bones) { + this.initNode(this.nodes, this.nodes[nodeIndex++], bone); + } + + for (final Light light : model.lights) { + final LightInstance lightInstance = new LightInstance(this, light); + this.lights.add(lightInstance); + this.initNode(this.nodes, this.nodes[nodeIndex++], light, lightInstance); + } + + for (final Helper helper : model.helpers) { + this.initNode(this.nodes, this.nodes[nodeIndex++], helper); + } + + for (final Attachment attachment : model.attachments) { + AttachmentInstance attachmentInstance = null; + + // Attachments may have game models attached to them, such as Undead and + // Nightelf building animations. + if (attachment.internalModel != null) { + attachmentInstance = new AttachmentInstance(this, attachment); + + this.attachments.add(attachmentInstance); + } + + this.initNode(this.nodes, this.nodes[nodeIndex++], attachment, attachmentInstance); + } + + for (final ParticleEmitterObject emitterObject : model.particleEmitters) { + final ParticleEmitter emitter = new ParticleEmitter(this, emitterObject); + + this.particleEmitters.add(emitter); + + this.initNode(this.nodes, this.nodes[nodeIndex++], emitterObject, emitter); + } + + for (final ParticleEmitter2Object emitterObject : model.particleEmitters2) { + final ParticleEmitter2 emitter = new ParticleEmitter2(this, emitterObject); + + this.particleEmitters2.add(emitter); + + this.initNode(this.nodes, this.nodes[nodeIndex++], emitterObject, emitter); + } + + for (final RibbonEmitterObject emitterObject : model.ribbonEmitters) { + final RibbonEmitter emitter = new RibbonEmitter(this, emitterObject); + + this.ribbonEmitters.add(emitter); + + this.initNode(this.nodes, this.nodes[nodeIndex++], emitterObject, emitter); + } + + for (final EventObjectEmitterObject emitterObject : model.eventObjects) { + final String type = emitterObject.type; + EventObjectEmitter emitter; + + if ("SPN".equals(type)) { + emitter = new EventObjectSpnEmitter(this, emitterObject); + } + else if ("SPL".equals(type)) { + emitter = new EventObjectSplEmitter(this, emitterObject); + } + else if ("UBR".equals(type)) { + emitter = new EventObjectUbrEmitter(this, emitterObject); + } + else { + emitter = new EventObjectSndEmitter(this, emitterObject); + } + + this.eventObjectEmitters.add(emitter); + + this.initNode(this.nodes, this.nodes[nodeIndex++], emitterObject, emitter); + } + + for (final CollisionShape collisionShape : model.collisionShapes) { + this.initNode(this.nodes, this.nodes[nodeIndex++], collisionShape); + } + + // Save a sorted array of all of the nodes, such that every child node comes + // after its parent. + // This allows for flat iteration when updating. + final List hierarchy = model.hierarchy; + + this.sortedNodes = new SkeletalNode[nodes.size()]; + for (int i = 0, l = nodes.size(); i < l; i++) { + this.sortedNodes[i] = this.nodes[hierarchy.get(i)]; + } + + // If the sequence was changed before the model was loaded, reset it now that + // the model loaded. + this.setSequence(this.sequence); + + if (model.bones.size() != 0) { + this.boneTexture = new DataTexture(model.viewer.gl, 4, model.bones.size() * 4, 1); + } + } + + /* + * Clear all of the emitted objects that belong to this instance. + */ + @Override + public void clearEmittedObjects() { + for (final ParticleEmitter emitter : this.particleEmitters) { + emitter.clear(); + } + + for (final ParticleEmitter2 emitter : this.particleEmitters2) { + emitter.clear(); + } + + for (final RibbonEmitter emitter : this.ribbonEmitters) { + emitter.clear(); + } + + for (final EventObjectEmitter emitter : this.eventObjectEmitters) { + emitter.clear(); + } + } + + private void initNode(final MdxNode[] nodes, final SkeletalNode node, final GenericObject genericObject) { + initNode(nodes, node, genericObject, null); + } + + /** + * Initialize a skeletal node. + */ + private void initNode(final MdxNode[] nodes, final SkeletalNode node, final GenericObject genericObject, + final UpdatableObject object) { + node.pivot.set(genericObject.pivot); + + if (genericObject.parentId == -1) { + node.parent = this; + } + else { + node.parent = nodes[genericObject.parentId]; + } + + /// TODO: single-axis billboarding + if (genericObject.billboarded != 0) { + node.billboarded = true; + } + else if (genericObject.billboardedX != 0) { + node.billboardedX = true; + } + else if (genericObject.billboardedY != 0) { + node.billboardedY = true; + } + else if (genericObject.billboardedZ != 0) { + node.billboardedZ = true; + } + + if (object != null) { + node.object = object; + } + + } + + /* + * Overriden to hide also attachment models. + */ + @Override + public void hide() { + super.hide(); + + for (final AttachmentInstance attachment : this.attachments) { + attachment.internalInstance.hide(); + } + } + + /** + * Updates all of this instance internal nodes and objects. Nodes that are + * determined to not be visible will not be updated, nor will any of their + * children down the hierarchy. + */ + public void updateNodes(final float dt, final boolean forced) { + if (!this.model.ok) { + return; + } + final int sequence = this.sequence; + final int frame = this.frame; + final int counter = this.counter; + final SkeletalNode[] sortedNodes = this.sortedNodes; + final MdxModel model = (MdxModel) this.model; + final List sortedGenericObjects = model.sortedGenericObjects; + final Scene scene = this.scene; + + // Update the nodes + for (int i = 0, l = sortedNodes.length; i < l; i++) { + final GenericObject genericObject = sortedGenericObjects.get(i); + final SkeletalNode node = sortedNodes[i]; + final GenericNode parent = node.parent; + + genericObject.getVisibility(visibilityHeap, sequence, frame, counter); + + final boolean objectVisible = visibilityHeap[0] > 0; + final boolean nodeVisible = forced || (parent.visible && objectVisible); + + node.visible = nodeVisible; + + // Every node only needs to be updated if this is a forced update, or if both + // the parent node and the generic object corresponding to this node are + // visible. + // Incoming messy code for optimizations! + if (nodeVisible) { + boolean wasDirty = false; + final GenericObject.Variants variants = genericObject.variants; + final Vector3 localLocation = node.localLocation; + final Quaternion localRotation = node.localRotation; + final Vector3 localScale = node.localScale; + + // Only update the local node data if there is a need to + if (forced || variants.generic[sequence]) { + wasDirty = true; + + // Translation + if (forced || variants.translation[sequence]) { + genericObject.getTranslation(translationHeap, sequence, frame, counter); + + localLocation.x = translationHeap[0]; + localLocation.y = translationHeap[1]; + localLocation.z = translationHeap[2]; + } + + // Rotation + if (forced || variants.rotation[sequence]) { + genericObject.getRotation(rotationHeap, sequence, frame, counter); + + localRotation.x = rotationHeap[0]; + localRotation.y = rotationHeap[1]; + localRotation.z = rotationHeap[2]; + localRotation.w = rotationHeap[3]; + } + + // Scale + if (forced || variants.scale[sequence]) { + genericObject.getScale(scaleHeap, sequence, frame, counter); + + localScale.x = scaleHeap[0]; + localScale.y = scaleHeap[1]; + localScale.z = scaleHeap[2]; + } + } + + final boolean wasReallyDirty = forced || wasDirty || parent.wasDirty || genericObject.anyBillboarding; + + node.wasDirty = wasReallyDirty; + + // If this is a forced update, or this node's local data was updated, or the + // parent node was updated, do a full world update. + if (wasReallyDirty) { + node.recalculateTransformation(scene, this.blendTimeRemaining / this.blendTime); + } + + // If there is an instance object associated with this node, and the node is + // visible (which might not be the case for a forced update!), update the + // object. + // This includes attachments and emitters. + final UpdatableObject object = node.object; + + if (object != null) { + object.update(dt, objectVisible); + } + + // Update all of the node's non-skeletal children, which will update their + // children, and so on. + node.updateChildren(dt, scene); + } + } + } + + /** + * Update the batch data. + */ + public void updateBatches(final boolean forced) { + final int sequence = this.sequence; + final int frame = this.frame; + final int counter = this.counter; + final MdxModel model = (MdxModel) this.model; + if (!model.ok) { + return; + } + final List geosets = model.geosets; + final List layers = model.layers; + final float[][] geosetColors = this.geosetColors; + final float[] layerAlphas = this.layerAlphas; + final int[] layerTextures = this.layerTextures; + final float[][] uvAnims = this.uvAnims; + + // Geoset + for (int i = 0, l = geosets.size(); i < l; i++) { + final Geoset geoset = geosets.get(i); + final GeosetAnimation geosetAnimation = geoset.geosetAnimation; + final float[] geosetColor = geosetColors[i]; + + if (geosetAnimation != null) { + // Color + if (forced || (geosetAnimation.variants.get("color")[sequence] != 0)) { + geosetAnimation.getColor(colorHeap, sequence, frame, counter); + + geosetColor[0] = colorHeap[0]; + geosetColor[1] = colorHeap[1]; + geosetColor[2] = colorHeap[2]; + } + + // Alpha + if (forced || (geosetAnimation.variants.get("alpha")[sequence] != 0)) { + geosetAnimation.getAlpha(alphaHeap, sequence, frame, counter); + + geosetColor[3] = alphaHeap[0]; + } + } + else if (forced) { + geosetColor[0] = 1; + geosetColor[1] = 1; + geosetColor[2] = 1; + geosetColor[3] = 1; + } + } + + // Layers + for (int i = 0, l = layers.size(); i < l; i++) { + final Layer layer = layers.get(i); + final TextureAnimation textureAnimation = layer.textureAnimation; + final float[] uvAnim = uvAnims[i]; + + // Alpha + if (forced || (layer.variants.get("alpha")[sequence] != 0)) { + layer.getAlpha(alphaHeap, sequence, frame, counter); + + layerAlphas[i] = alphaHeap[0]; + } + + // Sprite animation + if (forced || (layer.variants.get("textureId")[sequence] != 0)) { + layer.getTextureId(textureIdHeap, sequence, frame, counter); + + layerTextures[i] = (int) textureIdHeap[0]; + } + + if (textureAnimation != null) { + // UV translation animation + if (forced || (textureAnimation.variants.get("translation")[sequence] != 0)) { + textureAnimation.getTranslation(translationHeap, sequence, frame, counter); + + uvAnim[0] = translationHeap[0]; + uvAnim[1] = translationHeap[1]; + } + + // UV rotation animation + if (forced || (textureAnimation.variants.get("rotation")[sequence] != 0)) { + textureAnimation.getRotation(rotationHeap, sequence, frame, counter); + + uvAnim[2] = rotationHeap[2]; + uvAnim[3] = rotationHeap[3]; + } + + // UV scale animation + if (forced || (textureAnimation.variants.get("scale")[sequence] != 0)) { + textureAnimation.getScale(scaleHeap, sequence, frame, counter); + + uvAnim[4] = scaleHeap[0]; + } + } + else if (forced) { + uvAnim[0] = 0; + uvAnim[1] = 0; + uvAnim[2] = 0; + uvAnim[3] = 1; + uvAnim[4] = 1; + } + } + } + + public void updateBoneTexture() { + if (this.boneTexture != null) { + this.worldMatricesCopyHeap.clear(); + for (int i = 0, l = this.worldMatrices.length; i < l; i++) { + final Matrix4 worldMatrix = this.worldMatrices[i]; + this.worldMatricesCopyHeap.put((i * 16) + 0, worldMatrix.val[Matrix4.M00]); + this.worldMatricesCopyHeap.put((i * 16) + 1, worldMatrix.val[Matrix4.M10]); + this.worldMatricesCopyHeap.put((i * 16) + 2, worldMatrix.val[Matrix4.M20]); + this.worldMatricesCopyHeap.put((i * 16) + 3, worldMatrix.val[Matrix4.M30]); + this.worldMatricesCopyHeap.put((i * 16) + 4, worldMatrix.val[Matrix4.M01]); + this.worldMatricesCopyHeap.put((i * 16) + 5, worldMatrix.val[Matrix4.M11]); + this.worldMatricesCopyHeap.put((i * 16) + 6, worldMatrix.val[Matrix4.M21]); + this.worldMatricesCopyHeap.put((i * 16) + 7, worldMatrix.val[Matrix4.M31]); + this.worldMatricesCopyHeap.put((i * 16) + 8, worldMatrix.val[Matrix4.M02]); + this.worldMatricesCopyHeap.put((i * 16) + 9, worldMatrix.val[Matrix4.M12]); + this.worldMatricesCopyHeap.put((i * 16) + 10, worldMatrix.val[Matrix4.M22]); + this.worldMatricesCopyHeap.put((i * 16) + 11, worldMatrix.val[Matrix4.M32]); + this.worldMatricesCopyHeap.put((i * 16) + 12, worldMatrix.val[Matrix4.M03]); + this.worldMatricesCopyHeap.put((i * 16) + 13, worldMatrix.val[Matrix4.M13]); + this.worldMatricesCopyHeap.put((i * 16) + 14, worldMatrix.val[Matrix4.M23]); + this.worldMatricesCopyHeap.put((i * 16) + 15, worldMatrix.val[Matrix4.M33]); + } + this.boneTexture.bindAndUpdate(this.worldMatricesCopyHeap); + } + } + + @Override + public void renderOpaque(final Matrix4 mvp) { + final MdxModel model = (MdxModel) this.model; + + for (final GenericGroup group : model.opaqueGroups) { + group.render(this, mvp); + } + } + + @Override + public void renderTranslucent() { + if (DynamicShadowManager.IS_SHADOW_MAPPING) { + return; + } + final MdxModel model = (MdxModel) this.model; + + for (final GenericGroup group : model.translucentGroups) { + group.render(this, this.scene.camera.viewProjectionMatrix); + } + } + + @Override + public void updateAnimations(final float dt) { + final MdxModel model = (MdxModel) this.model; + final int sequenceId = this.sequence; + + if (sequenceId != -1) { + final Sequence sequence = model.sequences.get(sequenceId); + final long[] interval = sequence.getInterval(); + final float frameTime = (dt * 1000 * this.animationSpeed); + + final int lastIntegerFrame = this.frame; + this.floatingFrame += frameTime; + this.blendTimeRemaining -= frameTime; + this.frame = (int) this.floatingFrame; + final int integerFrameTime = this.frame - lastIntegerFrame; + this.counter += integerFrameTime; + this.allowParticleSpawn = true; + + if (this.floatingFrame >= interval[1]) { + if ((this.sequenceLoopMode == SequenceLoopMode.ALWAYS_LOOP) + || ((this.sequenceLoopMode == SequenceLoopMode.MODEL_LOOP) && (sequence.getFlags() == 0))) { + this.floatingFrame = this.frame = (int) interval[0]; // TODO not cast + + this.resetEventEmitters(); + } + else if (this.sequenceLoopMode == SequenceLoopMode.LOOP_TO_NEXT_ANIMATION) { // faux queued animation + // mode + final float framesPast = this.floatingFrame - interval[1]; + + final List sequences = model.sequences; + this.sequence = (this.sequence + 1) % sequences.size(); + this.floatingFrame = sequences.get(this.sequence).getInterval()[0] + framesPast; // TODO not cast + this.frame = (int) this.floatingFrame; + this.sequenceEnded = false; + this.resetEventEmitters(); + this.forced = true; + } + else { + this.floatingFrame = this.frame = (int) interval[1]; // TODO not cast + this.counter -= integerFrameTime; + this.allowParticleSpawn = false; + } + if (this.sequenceLoopMode == SequenceLoopMode.NEVER_LOOP_AND_HIDE_WHEN_DONE) { + hide(); + } + + this.sequenceEnded = true; + } + else { + this.sequenceEnded = false; + } + } + + final boolean forced = this.forced; + + if (sequenceId == -1) { + if (forced) { + // Update the nodes + this.updateNodes(dt, forced); + + this.updateBoneTexture(); + + // Update the batches + this.updateBatches(forced); + } + } + else { + // let variants = model.variants; + + // if (forced || variants.nodes[sequenceId]) { + // Update the nodes + this.updateNodes(dt, forced); + + this.updateBoneTexture(); + // } + + // if (forced || variants.batches[sequenceId]) { + // Update the batches + this.updateBatches(forced); + // } + } + + this.forced = false; + + } + + @Override + protected void updateLights(final Scene scene) { + for (final LightInstance light : this.lights) { + light.update(scene); + } + } + + @Override + protected void removeLights(final Scene scene2) { + for (final LightInstance light : this.lights) { + light.remove(this.scene); + } + } + + /** + * Set the team color of this instance. + */ + public MdxComplexInstance setTeamColor(final int id) { + this.replaceableTextures[1] = (Texture) this.model.viewer.load( + "ReplaceableTextures\\" + ReplaceableIds.getPathString(1) + ReplaceableIds.getIdString(id) + ".blp", + PathSolver.DEFAULT, null); + this.replaceableTextures[2] = (Texture) this.model.viewer.load( + "ReplaceableTextures\\" + ReplaceableIds.getPathString(2) + ReplaceableIds.getIdString(id) + ".blp", + PathSolver.DEFAULT, null); + return this; + } + + @Override + public void setReplaceableTexture(final int replaceableTextureId, final String replaceableTextureFile) { + this.replaceableTextures[replaceableTextureId] = (Texture) this.model.viewer.load(replaceableTextureFile, + PathSolver.DEFAULT, null); + } + + /** + * Set the vertex color of this instance. + */ + public MdxComplexInstance setVertexColor(final float[] color) { + System.arraycopy(color, 0, this.vertexColor, 0, color.length); + + return this; + } + + /** + * Set the sequence of this instance. + */ + public MdxComplexInstance setSequence(final int id) { + final MdxModel model = (MdxModel) this.model; + + final int lastSequence = this.sequence; + this.sequence = id; + + if (model.ok) { + final List sequences = model.sequences; + + if ((id < 0) || (id > (sequences.size() - 1))) { + this.sequence = -1; + this.frame = 0; + this.floatingFrame = 0; + this.allowParticleSpawn = false; + } + else { + if ((this.blendTime > 0) && (lastSequence != this.sequence)) { + this.blendTimeRemaining = this.blendTime; + for (int i = 0, l = this.sortedNodes.length; i < l; i++) { + final SkeletalNode node = this.sortedNodes[i]; + node.beginBlending(); + } + } + + this.frame = (int) sequences.get(id).getInterval()[0]; // TODO not cast + this.floatingFrame = this.frame; + this.sequenceEnded = false; + } + + this.resetEventEmitters(); + + this.forced = true; + } + + return this; + } + + /** + * Set the seuqnece loop mode. 0 to never loop, 1 to loop based on the model, + * and 2 to always loop. 3 was added by Retera as "hide after done" for gameplay + * spawned effects + */ + public MdxComplexInstance setSequenceLoopMode(final SequenceLoopMode mode) { + this.sequenceLoopMode = mode; + + return this; + } + + /** + * Get an attachment node. + */ + public MdxNode getAttachment(final int id) { + final MdxModel model = (MdxModel) this.model; + final Attachment attachment = model.attachments.get(id); + + if (attachment != null) { + return this.nodes[attachment.index]; + } + + return null; + } + + /** + * Event emitters depend on keyframe index changes to emit, rather than only + * values. To work, they need to check what the last keyframe was, and only if + * it's a different one, do something. When changing sequences, these states + * need to be reset, so they can immediately emit things if needed. + */ + private void resetEventEmitters() { + /// TODO: Update this. Said Ghostwolf. + for (final EventObjectEmitter eventObjectEmitter : this.eventObjectEmitters) { + eventObjectEmitter.reset(); + } + } + + @Override + protected RenderBatch getBatch(final TextureMapper textureMapper2) { + throw new UnsupportedOperationException("NOT API"); + } + + public void intersectRayBounds(final Ray ray, final Vector3 intersection) { + this.model.bounds.intersectRay(ray, intersection); + } + + /** + * Intersects a world ray with the model's CollisionShapes. Only ever call this + * function on the Gdx thread because it uses static variables to hold state + * while processing. + * + * @param ray + */ + public boolean intersectRayWithCollision(final Ray ray, final Vector3 intersection, final boolean alwaysUseMesh, + final boolean onlyUseMesh) { + final MdxModel mdxModel = (MdxModel) this.model; + final List collisionShapes = mdxModel.collisionShapes; + if (!onlyUseMesh) { + for (final CollisionShape collisionShape : collisionShapes) { + final MdxNode mdxNode = this.nodes[collisionShape.index]; + if (collisionShape.checkIntersect(ray, mdxNode, intersection)) { + return true; + } + } + } + if (collisionShapes.isEmpty() || alwaysUseMesh) { + for (final Geoset geoset : mdxModel.geosets) { + if (!geoset.unselectable) { + geoset.getAlpha(alphaHeap, this.sequence, this.frame, this.counter); + if (alphaHeap[0] > 0) { + final com.etheller.warsmash.parsers.mdlx.Geoset mdlxGeoset = geoset.mdlxGeoset; + if (CollisionShape.intersectRayTriangles(ray, this, mdlxGeoset.getVertices(), + mdlxGeoset.getFaces(), 3, intersection)) { + return true; + } + } + } + } + } + return false; + } + + public void setAnimationSpeed(final float speedRatio) { + this.animationSpeed = speedRatio; + } + + public void setBlendTime(final float blendTime) { + this.blendTime = blendTime; + } + + public void setFrame(final int frame) { + this.frame = frame; + this.floatingFrame = frame; + } + + public void setFrameByRatio(final float ratioOfAnimationCompleted) { + if (this.sequence != -1) { + final Sequence currentlyPlayingSequence = ((MdxModel) this.model).sequences.get(this.sequence); + this.floatingFrame = currentlyPlayingSequence.getInterval()[0] + + ((currentlyPlayingSequence.getInterval()[1] - currentlyPlayingSequence.getInterval()[0]) + * ratioOfAnimationCompleted); + this.frame = (int) this.floatingFrame; + } + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index 7f8db03..82ab54f 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -86,16 +86,9 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitClassification; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitFilterFunction; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityAttack; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityMove; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackInstant; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackMissile; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.projectile.CAttackProjectile; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.BooleanAbilityActivationReceiver; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.CWidgetAbilityTargetCheckReceiver; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.PointAbilityTargetCheckReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.SimulationRenderController; import mpq.MPQArchive; @@ -1275,74 +1268,6 @@ public class War3MapViewer extends ModelViewer { return (numElements < 0) ? 1 : (numElements >= MAXIMUM_ACCEPTED) ? MAXIMUM_ACCEPTED : numElements + 1; } - public boolean orderSmart(final float x, final float y) { - mousePosHeap.x = x; - mousePosHeap.y = y; - boolean ordered = false; - for (final RenderUnit unit : this.selected) { - for (final CAbility ability : unit.getSimulationUnit().getAbilities()) { - if (ability instanceof CAbilityMove) { - ability.checkCanUse(this.simulation, unit.getSimulationUnit(), OrderIds.smart, - BooleanAbilityActivationReceiver.INSTANCE); - if (BooleanAbilityActivationReceiver.INSTANCE.isOk()) { - ability.checkCanTarget(this.simulation, unit.getSimulationUnit(), OrderIds.smart, mousePosHeap, - PointAbilityTargetCheckReceiver.INSTANCE); - final Vector2 target = PointAbilityTargetCheckReceiver.INSTANCE.getTarget(); - if (target != null) { - ability.onOrder(this.simulation, unit.getSimulationUnit(), OrderIds.smart, mousePosHeap, - false); - ordered = true; - } - else { - System.err.println("Target not valid."); - } - } - else { - System.err.println("Ability not ok to use."); - } - } - else { - System.err.println("Ability not move."); - } - } - - } - return ordered; - } - - public boolean orderSmart(final RenderUnit target) { - boolean ordered = false; - for (final RenderUnit unit : this.selected) { - for (final CAbility ability : unit.getSimulationUnit().getAbilities()) { - if (ability instanceof CAbilityAttack) { - ability.checkCanUse(this.simulation, unit.getSimulationUnit(), OrderIds.smart, - BooleanAbilityActivationReceiver.INSTANCE); - if (BooleanAbilityActivationReceiver.INSTANCE.isOk()) { - ability.checkCanTarget(this.simulation, unit.getSimulationUnit(), OrderIds.smart, - target.getSimulationUnit(), CWidgetAbilityTargetCheckReceiver.INSTANCE); - final CWidget targetWidget = CWidgetAbilityTargetCheckReceiver.INSTANCE.getTarget(); - if (targetWidget != null) { - ability.onOrder(this.simulation, unit.getSimulationUnit(), OrderIds.smart, targetWidget, - false); - ordered = true; - } - else { - System.err.println("Target not valid."); - } - } - else { - System.err.println("Ability not ok to use."); - } - } - else { - System.err.println("Ability not move."); - } - } - - } - return ordered; - } - public void standOnRepeat(final MdxComplexInstance instance) { instance.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); SequenceUtils.randomStandSequence(instance); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java index d0c12de..12704ea 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java @@ -38,6 +38,7 @@ public class RenderUnit { private static final War3ID MOVE_HEIGHT = War3ID.fromString("umvh"); private static final War3ID ORIENTATION_INTERPOLATION = War3ID.fromString("uori"); private static final War3ID ANIM_PROPS = War3ID.fromString("uani"); + private static final War3ID BLEND_TIME = War3ID.fromString("uble"); private static final float[] heapZ = new float[3]; public final MdxComplexInstance instance; public final MutableGameObject row; @@ -124,6 +125,9 @@ public class RenderUnit { orientationInterpolationOrdinal = 0; } this.orientationInterpolation = OrientationInterpolation.VALUES[orientationInterpolationOrdinal]; + + final float blendTime = row.getFieldAsFloat(BLEND_TIME, 0); + instance.setBlendTime(blendTime * 1000.0f); } this.instance = instance; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandButtonListener.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandButtonListener.java index 9971f88..d43f106 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandButtonListener.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandButtonListener.java @@ -35,5 +35,5 @@ public interface CommandButtonListener { // // int getOrderId(); void commandButton(int buttonPositionX, int buttonPositionY, Texture icon, int abilityHandleId, int orderId, - boolean active); + int autoCastOrderId, boolean active, boolean autoCastActive); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java index 014d80b..eac054d 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java @@ -6,10 +6,11 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityAttack; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityColdArrows; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityGeneric; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityMove; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.BooleanAbilityActivationReceiver; public class CommandCardPopulatingAbilityVisitor implements CAbilityVisitor { public static final CommandCardPopulatingAbilityVisitor INSTANCE = new CommandCardPopulatingAbilityVisitor(); @@ -33,41 +34,54 @@ public class CommandCardPopulatingAbilityVisitor implements CAbilityVisitor units; private final List players; private final List projectiles; @@ -47,6 +48,7 @@ public class CSimulation { private float currentGameDayTimeElapsed; private final Map handleIdToUnit = new HashMap<>(); private final Map handleIdToAbility = new HashMap<>(); + private transient CommandErrorListener commandErrorListener; public CSimulation(final DataTable miscData, final MutableObjectData parsedUnitData, final MutableObjectData parsedAbilityData, final SimulationRenderController simulationRenderController, @@ -55,8 +57,8 @@ public class CSimulation { this.gameplayConstants = new CGameplayConstants(miscData); this.simulationRenderController = simulationRenderController; this.pathingGrid = pathingGrid; - this.unitData = new CUnitData(parsedUnitData); this.abilityData = new CAbilityData(parsedAbilityData); + this.unitData = new CUnitData(parsedUnitData, this.abilityData); this.units = new ArrayList<>(); this.projectiles = new ArrayList<>(); this.newProjectiles = new ArrayList<>(); @@ -92,6 +94,13 @@ public class CSimulation { neutralPassive.setAlliance(cPlayer, CAllianceType.PASSIVE, true); } + this.commandErrorListener = new CommandErrorListener() { + @Override + public void showCommandError(final String message) { + throw new RuntimeException(message); + } + }; + } public CUnitData getUnitData() { @@ -208,4 +217,8 @@ public class CSimulation { public CPlayer getPlayer(final int index) { return this.players.get(index); } + + public CommandErrorListener getCommandErrorListener() { + return this.commandErrorListener; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java index 2efda2b..75de310 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java @@ -14,11 +14,16 @@ import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitStateListener.CUnitStateNotifier; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorAttack; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorFollow; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorMove; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorPatrol; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorStop; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.CBehaviorAttack; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.COrder; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CAllianceType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayer; @@ -38,8 +43,9 @@ public class CUnit extends CWidget { private final List abilities = new ArrayList<>(); - private CBehavior currentOrder; - private final Queue orderQueue = new LinkedList<>(); + private CBehavior currentBehavior; + private COrder currentOrder; + private final Queue orderQueue = new LinkedList<>(); private final CUnitType unitType; private Rectangle collisionRectangle; @@ -59,6 +65,12 @@ public class CUnit extends CWidget { private final float acquisitionRange; private transient static AutoAttackTargetFinderEnum autoAttackTargetFinderEnum = new AutoAttackTargetFinderEnum(); + private transient CBehaviorMove moveBehavior; + private transient CBehaviorAttack attackBehavior; + private transient CBehaviorFollow followBehavior; + private transient CBehaviorPatrol patrolBehavior; + private transient CBehaviorStop stopBehavior; + public CUnit(final int handleId, final int playerIndex, final float x, final float y, final float life, final War3ID typeId, final float facing, final float mana, final int maximumLife, final int maximumMana, final int speed, final int defense, final CUnitType unitType) { @@ -75,6 +87,8 @@ public class CUnit extends CWidget { this.unitType = unitType; this.classifications.addAll(unitType.getClassifications()); this.acquisitionRange = unitType.getDefaultAcquisitionRange(); + this.stopBehavior = new CBehaviorStop(this); + this.currentBehavior = this.stopBehavior; } public void setUnitAnimationListener(final CUnitAnimationListener unitAnimationListener) { @@ -185,38 +199,33 @@ public class CUnit extends CWidget { return true; } } - else if (this.currentOrder != null) { - if (this.currentOrder.update(game)) { - // remove current order, because it's completed, polling next - // item from order queue - this.currentOrder = this.orderQueue.poll(); - this.stateNotifier.ordersChanged(); - } - if (this.currentOrder == null) { - // maybe order "stop" here - this.unitAnimationListener.playAnimation(true, PrimaryTag.STAND, SequenceUtils.EMPTY, 1.0f, true); - } + else if (this.currentBehavior != null) { + this.currentBehavior = this.currentBehavior.update(game); } else { // check to auto acquire targets - if (!this.unitType.getAttacks().isEmpty()) { - if (this.collisionRectangle != null) { - tempRect.set(this.collisionRectangle); - } - else { - tempRect.set(this.getX(), this.getY(), 0, 0); - } - final float halfSize = this.acquisitionRange; - tempRect.x -= halfSize; - tempRect.y -= halfSize; - tempRect.width += halfSize * 2; - tempRect.height += halfSize * 2; - game.getWorldCollision().enumUnitsInRect(tempRect, autoAttackTargetFinderEnum.reset(game, this)); - } + autoAcquireAttackTargets(game); } return false; } + public void autoAcquireAttackTargets(final CSimulation game) { + if (!this.unitType.getAttacks().isEmpty()) { + if (this.collisionRectangle != null) { + tempRect.set(this.collisionRectangle); + } + else { + tempRect.set(this.getX(), this.getY(), 0, 0); + } + final float halfSize = this.acquisitionRange; + tempRect.x -= halfSize; + tempRect.y -= halfSize; + tempRect.width += halfSize * 2; + tempRect.height += halfSize * 2; + game.getWorldCollision().enumUnitsInRect(tempRect, autoAttackTargetFinderEnum.reset(game, this)); + } + } + public float getEndingDecayTime(final CSimulation game) { if (this.unitType.isBuilding()) { return game.getGameplayConstants().getStructureDecayTime(); @@ -224,7 +233,7 @@ public class CUnit extends CWidget { return game.getGameplayConstants().getBoneDecayTime(); } - public void order(final CBehavior order, final boolean queue) { + public void order(final CSimulation game, final COrder order, final boolean queue) { if (isDead()) { return; } @@ -232,14 +241,37 @@ public class CUnit extends CWidget { this.orderQueue.add(order); } else { - this.currentOrder = order; + this.currentBehavior = beginOrder(game, order); this.orderQueue.clear(); } - this.stateNotifier.ordersChanged(); } - public CBehavior getCurrentOrder() { - return this.currentOrder; + private CBehavior beginOrder(final CSimulation game, final COrder order) { + final boolean omitNotify = (this.currentOrder == null) && (order == null); + this.currentOrder = order; + if (!omitNotify) { + this.stateNotifier.ordersChanged(getCurrentAbilityHandleId(), getCurrentOrderId()); + } + CBehavior nextBehavior; + if (order != null) { + nextBehavior = order.begin(game, this); + } + else { + nextBehavior = this.stopBehavior; + } + return nextBehavior; + } + + public CBehavior getCurrentBehavior() { + return this.currentBehavior; + } + + public int getCurrentAbilityHandleId() { + return this.currentOrder == null ? 0 : this.currentOrder.getAbilityHandleId(); + } + + public int getCurrentOrderId() { + return this.currentOrder == null ? OrderIds.stop : this.currentOrder.getOrderId(); } public List getAbilities() { @@ -389,12 +421,12 @@ public class CUnit extends CWidget { } } else { - if (this.currentOrder == null) { + if (this.currentBehavior == null) { if (!simulation.getPlayer(getPlayerIndex()).hasAlliance(source.getPlayerIndex(), CAllianceType.PASSIVE)) { for (final CUnitAttack attack : this.unitType.getAttacks()) { if (source.canBeTargetedBy(simulation, this, attack.getTargetsAllowed())) { - this.order(new CBehaviorAttack(this, attack, OrderIds.attack, source), false); + this.currentBehavior = getAttackBehavior().reset(attack, source); break; } } @@ -404,7 +436,7 @@ public class CUnit extends CWidget { } private void kill(final CSimulation simulation) { - this.currentOrder = null; + this.currentBehavior = null; this.orderQueue.clear(); this.deathTurnTick = simulation.getGameTurnTick(); } @@ -558,7 +590,7 @@ public class CUnit extends CWidget { if (this.source.canReach(unit, this.source.acquisitionRange) && unit.canBeTargetedBy(this.game, this.source, attack.getTargetsAllowed()) && (this.source.distance(unit) >= this.source.getUnitType().getMinimumAttackRange())) { - this.source.order(new CBehaviorAttack(this.source, attack, OrderIds.attack, unit), false); + this.source.currentBehavior = this.source.getAttackBehavior().reset(attack, unit); return true; } } @@ -566,4 +598,45 @@ public class CUnit extends CWidget { return false; } } + + public CBehaviorMove getMoveBehavior() { + return this.moveBehavior; + } + + public void setMoveBehavior(final CBehaviorMove moveBehavior) { + this.moveBehavior = moveBehavior; + } + + public CBehaviorAttack getAttackBehavior() { + return this.attackBehavior; + } + + public void setAttackBehavior(final CBehaviorAttack attackBehavior) { + this.attackBehavior = attackBehavior; + } + + public CBehaviorStop getStopBehavior() { + return this.stopBehavior; + } + + public void setFollowBehavior(final CBehaviorFollow followBehavior) { + this.followBehavior = followBehavior; + } + + public void setPatrolBehavior(final CBehaviorPatrol patrolBehavior) { + this.patrolBehavior = patrolBehavior; + } + + public CBehaviorFollow getFollowBehavior() { + return this.followBehavior; + } + + public CBehaviorPatrol getPatrolBehavior() { + return this.patrolBehavior; + } + + public CBehavior pollNextOrderBehavior(final CSimulation game) { + final COrder order = this.orderQueue.poll(); + return beginOrder(game, order); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitStateListener.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitStateListener.java index c1d3f51..94b974c 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitStateListener.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitStateListener.java @@ -5,7 +5,7 @@ import com.etheller.warsmash.util.SubscriberSetNotifier; public interface CUnitStateListener { void lifeChanged(); // hp (current) changes - void ordersChanged(); + void ordersChanged(int abilityHandleId, int orderId); public static final class CUnitStateNotifier extends SubscriberSetNotifier implements CUnitStateListener { @@ -17,9 +17,9 @@ public interface CUnitStateListener { } @Override - public void ordersChanged() { + public void ordersChanged(final int abilityHandleId, final int orderId) { for (final CUnitStateListener listener : set) { - listener.ordersChanged(); + listener.ordersChanged(abilityHandleId, orderId); } } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbility.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbility.java index 743def9..7720194 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbility.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbility.java @@ -4,6 +4,7 @@ import com.badlogic.gdx.math.Vector2; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; public interface CAbility extends CAbilityView { /* should fire when ability added to unit */ @@ -12,10 +13,10 @@ public interface CAbility extends CAbilityView { /* should fire when ability removed from unit */ void onRemove(CSimulation game, CUnit unit); - void onOrder(CSimulation game, CUnit caster, int orderId, CWidget target, boolean queue); + CBehavior begin(CSimulation game, CUnit caster, int orderId, CWidget target); - void onOrder(CSimulation game, CUnit caster, int orderId, Vector2 point, boolean queue); + CBehavior begin(CSimulation game, CUnit caster, int orderId, Vector2 point); - void onOrderNoTarget(CSimulation game, CUnit caster, int orderId, boolean queue); + CBehavior beginNoTarget(CSimulation game, CUnit caster, int orderId); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java index c85cb66..f7e57d8 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java @@ -4,12 +4,10 @@ import com.badlogic.gdx.math.Vector2; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.StringsToExternalizeLater; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorAttack; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.CBehavior; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.CBehaviorAttack; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.CBehaviorMove; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CAllianceType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; @@ -99,6 +97,7 @@ public class CAbilityAttack implements CAbility { @Override public void onAdd(final CSimulation game, final CUnit unit) { + unit.setAttackBehavior(new CBehaviorAttack(unit)); } @Override @@ -106,30 +105,28 @@ public class CAbilityAttack implements CAbility { } @Override - public void onOrder(final CSimulation game, final CUnit caster, final int orderId, final CWidget target, - final boolean queue) { - CBehavior order = null; + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final CWidget target) { + CBehavior behavior = null; for (final CUnitAttack attack : caster.getUnitType().getAttacks()) { if (target.canBeTargetedBy(game, caster, attack.getTargetsAllowed())) { - order = new CBehaviorAttack(caster, attack, orderId, target); + behavior = caster.getAttackBehavior().reset(attack, target); break; } } - if (order == null) { - order = new CBehaviorMove(caster, orderId, target.getX(), target.getY()); + if (behavior == null) { + behavior = caster.getMoveBehavior().reset(target.getX(), target.getY()); } - caster.order(order, queue); + return behavior; } @Override - public void onOrder(final CSimulation game, final CUnit caster, final int orderId, final Vector2 target, - final boolean queue) { - throw new IllegalArgumentException(StringsToExternalizeLater.MUST_TARGET_WIDGET); + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final Vector2 point) { + return caster.pollNextOrderBehavior(game); } @Override - public void onOrderNoTarget(final CSimulation game, final CUnit caster, final int orderId, final boolean queue) { - throw new IllegalArgumentException(StringsToExternalizeLater.MUST_TARGET_WIDGET); + public CBehavior beginNoTarget(final CSimulation game, final CUnit caster, final int orderId) { + return caster.pollNextOrderBehavior(game); } @Override diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityColdArrows.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityColdArrows.java new file mode 100644 index 0000000..5efb110 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityColdArrows.java @@ -0,0 +1,117 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities; + +import com.badlogic.gdx.math.Vector2; +import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; + +/** + * Represents an ability from the object data + */ +public class CAbilityColdArrows implements CAbility { + private final War3ID rawcode; + private final int handleId; + private boolean autoCastActive; + + public CAbilityColdArrows(final War3ID rawcode, final int handleId) { + this.rawcode = rawcode; + this.handleId = handleId; + } + + @Override + public void checkCanUse(final CSimulation game, final CUnit unit, final int orderId, + final AbilityActivationReceiver receiver) { + receiver.useOk(); + } + + @Override + public void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, final CWidget target, + final AbilityTargetCheckReceiver receiver) { + switch (orderId) { + case OrderIds.coldarrowstarg: + receiver.targetOk(target); + break; + default: + receiver.orderIdNotAccepted(); + break; + } + } + + @Override + public void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, final Vector2 target, + final AbilityTargetCheckReceiver receiver) { + receiver.orderIdNotAccepted(); + } + + @Override + public void checkCanTargetNoTarget(final CSimulation game, final CUnit unit, final int orderId, + final AbilityTargetCheckReceiver receiver) { + switch (orderId) { + case OrderIds.coldarrows: + case OrderIds.uncoldarrows: + receiver.targetOk(null); + break; + default: + receiver.orderIdNotAccepted(); + break; + } + } + + public War3ID getRawcode() { + return this.rawcode; + } + + @Override + public int getHandleId() { + return this.handleId; + } + + public boolean isAutoCastActive() { + return this.autoCastActive; + } + + @Override + public T visit(final CAbilityVisitor visitor) { + return visitor.accept(this); + } + + @Override + public void onAdd(final CSimulation game, final CUnit unit) { + } + + @Override + public void onRemove(final CSimulation game, final CUnit unit) { + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final CWidget target) { + CBehavior behavior = null; + for (final CUnitAttack attack : caster.getUnitType().getAttacks()) { + if (target.canBeTargetedBy(game, caster, attack.getTargetsAllowed())) { + behavior = caster.getAttackBehavior().reset(attack, target); + break; + } + } + if (behavior != null) { + return behavior; + } + return caster.pollNextOrderBehavior(game); + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final Vector2 point) { + return caster.pollNextOrderBehavior(game); + } + + @Override + public CBehavior beginNoTarget(final CSimulation game, final CUnit caster, final int orderId) { + this.autoCastActive = !this.autoCastActive; + return caster.pollNextOrderBehavior(game); + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityGeneric.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityGeneric.java new file mode 100644 index 0000000..40ff01c --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityGeneric.java @@ -0,0 +1,84 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities; + +import com.badlogic.gdx.math.Vector2; +import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; + +/** + * Represents an ability from the object data + */ +public class CAbilityGeneric implements CAbility { + private final War3ID rawcode; + private final int handleId; + + public CAbilityGeneric(final War3ID rawcode, final int handleId) { + this.rawcode = rawcode; + this.handleId = handleId; + } + + public War3ID getRawcode() { + return this.rawcode; + } + + @Override + public void checkCanUse(final CSimulation game, final CUnit unit, final int orderId, + final AbilityActivationReceiver receiver) { + receiver.notAnActiveAbility(); + } + + @Override + public void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, final CWidget target, + final AbilityTargetCheckReceiver receiver) { + receiver.orderIdNotAccepted(); + } + + @Override + public void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, final Vector2 target, + final AbilityTargetCheckReceiver receiver) { + receiver.orderIdNotAccepted(); + } + + @Override + public void checkCanTargetNoTarget(final CSimulation game, final CUnit unit, final int orderId, + final AbilityTargetCheckReceiver receiver) { + receiver.orderIdNotAccepted(); + } + + @Override + public int getHandleId() { + return this.handleId; + } + + @Override + public T visit(final CAbilityVisitor visitor) { + return visitor.accept(this); + } + + @Override + public void onAdd(final CSimulation game, final CUnit unit) { + } + + @Override + public void onRemove(final CSimulation game, final CUnit unit) { + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final CWidget target) { + return caster.pollNextOrderBehavior(game); + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final Vector2 point) { + return caster.pollNextOrderBehavior(game); + } + + @Override + public CBehavior beginNoTarget(final CSimulation game, final CUnit caster, final int orderId) { + return caster.pollNextOrderBehavior(game); + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityMove.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityMove.java index 19d1765..8c34b8e 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityMove.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityMove.java @@ -4,9 +4,10 @@ import com.badlogic.gdx.math.Vector2; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.StringsToExternalizeLater; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.CBehaviorMove; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.CBehaviorPatrol; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorFollow; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorMove; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorPatrol; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; @@ -31,7 +32,7 @@ public class CAbilityMove implements CAbility { switch (orderId) { case OrderIds.smart: case OrderIds.patrol: - if (target instanceof CUnit) { + if ((target instanceof CUnit) && (target != unit)) { receiver.targetOk(target); } else { @@ -74,7 +75,9 @@ public class CAbilityMove implements CAbility { @Override public void onAdd(final CSimulation game, final CUnit unit) { - + unit.setMoveBehavior(new CBehaviorMove(unit)); + unit.setFollowBehavior(new CBehaviorFollow(unit)); + unit.setPatrolBehavior(new CBehaviorPatrol(unit)); } @Override @@ -83,20 +86,23 @@ public class CAbilityMove implements CAbility { } @Override - public void onOrder(final CSimulation game, final CUnit caster, final int orderId, final CWidget target, - final boolean queue) { - caster.order(new CBehaviorPatrol(caster, orderId, (CUnit) target), queue); + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final CWidget target) { + return caster.getFollowBehavior().reset((CUnit) target); } @Override - public void onOrder(final CSimulation game, final CUnit caster, final int orderId, final Vector2 target, - final boolean queue) { - caster.order(new CBehaviorMove(caster, orderId, target.x, target.y), queue); + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final Vector2 point) { + if (orderId == OrderIds.patrol) { + return caster.getPatrolBehavior().reset(point); + } + else { + return caster.getMoveBehavior().reset(point.x, point.y); + } } @Override - public void onOrderNoTarget(final CSimulation game, final CUnit caster, final int orderId, final boolean queue) { - throw new IllegalArgumentException(StringsToExternalizeLater.MUST_TARGET_POINT); + public CBehavior beginNoTarget(final CSimulation game, final CUnit caster, final int orderId) { + return caster.pollNextOrderBehavior(game); } @Override diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityVisitor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityVisitor.java index 626aec9..5b4d1fb 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityVisitor.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityVisitor.java @@ -12,4 +12,8 @@ public interface CAbilityVisitor { T accept(CAbilityAttack ability); T accept(CAbilityMove ability); + + T accept(CAbilityGeneric ability); + + T accept(CAbilityColdArrows ability); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CAbstractRangedBehavior.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CAbstractRangedBehavior.java new file mode 100644 index 0000000..809d097 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CAbstractRangedBehavior.java @@ -0,0 +1,108 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; + +public abstract class CAbstractRangedBehavior implements CRangedBehavior { + protected final CUnit unit; + + public CAbstractRangedBehavior(final CUnit unit) { + this.unit = unit; + } + + private boolean wasWithinPropWindow = false; + protected CWidget target; + private boolean wasInRange = false; + private CBehaviorMove moveBehavior; + + protected final CAbstractRangedBehavior innerReset(final CWidget target) { + this.wasWithinPropWindow = false; + this.target = target; + this.wasInRange = false; + if (!this.unit.isMovementDisabled()) { + if ((target instanceof CUnit) && !((CUnit) target).getUnitType().isBuilding()) { + this.moveBehavior = this.unit.getMoveBehavior().reset((CUnit) target, this); + } + else { + this.moveBehavior = this.unit.getMoveBehavior().reset(target.getX(), target.getY(), this); + } + } + else { + this.moveBehavior = null; + } + return this; + } + + protected abstract CBehavior update(CSimulation simulation, boolean withinRange); + + protected abstract boolean checkTargetStillValid(CSimulation simulation); + + protected abstract void resetBeforeMoving(CSimulation simulation); + + @Override + public final CBehavior update(final CSimulation simulation) { + if (!checkTargetStillValid(simulation)) { + return this.unit.pollNextOrderBehavior(simulation); + } + if (!isWithinRange(simulation)) { + if (this.moveBehavior == null) { + return this.unit.pollNextOrderBehavior(simulation); + } + this.wasInRange = false; + resetBeforeMoving(simulation); + ; + return this.unit.getMoveBehavior(); + } + this.wasInRange = true; + if (!this.unit.isMovementDisabled()) { + final float prevX = this.unit.getX(); + final float prevY = this.unit.getY(); + final float deltaY = this.target.getY() - prevY; + final float deltaX = this.target.getX() - prevX; + final double goalAngleRad = Math.atan2(deltaY, deltaX); + float goalAngle = (float) Math.toDegrees(goalAngleRad); + if (goalAngle < 0) { + goalAngle += 360; + } + float facing = this.unit.getFacing(); + float delta = goalAngle - facing; + final float propulsionWindow = simulation.getGameplayConstants().getAttackHalfAngle(); + final float turnRate = simulation.getUnitData().getTurnRate(this.unit.getTypeId()); + + if (delta < -180) { + delta = 360 + delta; + } + if (delta > 180) { + delta = -360 + delta; + } + final float absDelta = Math.abs(delta); + + if ((absDelta <= 1.0) && (absDelta != 0)) { + this.unit.setFacing(goalAngle); + } + else { + float angleToAdd = Math.signum(delta) * (float) Math.toDegrees(turnRate); + if (absDelta < Math.abs(angleToAdd)) { + angleToAdd = delta; + } + facing += angleToAdd; + this.unit.setFacing(facing); + } + if (absDelta < propulsionWindow) { + this.wasWithinPropWindow = true; + } + else { + // If this happens, the unit is facing the wrong way, and has to turn before + // moving. + this.wasWithinPropWindow = false; + } + } + else { + this.wasWithinPropWindow = true; + } + + return update(simulation, this.wasWithinPropWindow); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehavior.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehavior.java new file mode 100644 index 0000000..f848b67 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehavior.java @@ -0,0 +1,13 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; + +public interface CBehavior { + /** + * Executes one step of game simulation of the current order, and then returns + * the next behavior for the unit after the result of the update cycle. + * + * @return + */ + CBehavior update(CSimulation game); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java new file mode 100644 index 0000000..6004fd5 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java @@ -0,0 +1,111 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors; + +import com.etheller.warsmash.util.WarsmashConstants; +import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; +import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; + +public class CBehaviorAttack extends CAbstractRangedBehavior { + + public CBehaviorAttack(final CUnit unit) { + super(unit); + } + + private CUnitAttack unitAttack; + private int damagePointLaunchTime; + private int backSwingTime; + private int thisOrderCooldownEndTime; + + public CBehaviorAttack reset(final CUnitAttack unitAttack, final CWidget target) { + super.innerReset(target); + this.unitAttack = unitAttack; + this.damagePointLaunchTime = 0; + this.backSwingTime = 0; + this.thisOrderCooldownEndTime = 0; + return this; + } + + @Override + public boolean isWithinRange(final CSimulation simulation) { + float range = this.unitAttack.getRange(); + if ((this.target instanceof CUnit) && (((CUnit) this.target).getCurrentBehavior() instanceof CBehaviorMove) + && (this.damagePointLaunchTime != 0 /* + * only apply range motion buffer if they were already in range and + * attacked + */)) { + range += this.unitAttack.getRangeMotionBuffer(); + } + return this.unit.canReach(this.target, range) + && (this.unit.distance(this.target) >= this.unit.getUnitType().getMinimumAttackRange()); + } + + @Override + protected boolean checkTargetStillValid(final CSimulation simulation) { + return !this.target.isDead() + && this.target.canBeTargetedBy(simulation, this.unit, this.unitAttack.getTargetsAllowed()); + } + + @Override + protected void resetBeforeMoving(final CSimulation simulation) { + this.damagePointLaunchTime = 0; + this.thisOrderCooldownEndTime = 0; + } + + @Override + public CBehavior update(final CSimulation simulation, final boolean withinRange) { + final int cooldownEndTime = this.unit.getCooldownEndTime(); + final int currentTurnTick = simulation.getGameTurnTick(); + if (withinRange) { + if (this.damagePointLaunchTime != 0) { + if (currentTurnTick >= this.damagePointLaunchTime) { + int minDamage = this.unitAttack.getMinDamage(); + final int maxDamage = Math.max(0, this.unitAttack.getMaxDamage()); + if (minDamage > maxDamage) { + minDamage = maxDamage; + } + final int damage; + if (maxDamage == 0) { + damage = 0; + } + else if (minDamage == maxDamage) { + damage = minDamage; + } + else { + damage = simulation.getSeededRandom().nextInt(maxDamage - minDamage) + minDamage; + } + this.unitAttack.launch(simulation, this.unit, this.target, damage); + this.damagePointLaunchTime = 0; + } + } + else if (currentTurnTick >= cooldownEndTime) { + final float cooldownTime = this.unitAttack.getCooldownTime(); + final float animationBackswingPoint = this.unitAttack.getAnimationBackswingPoint(); + final int a1CooldownSteps = (int) (cooldownTime / WarsmashConstants.SIMULATION_STEP_TIME); + final int a1BackswingSteps = (int) (animationBackswingPoint / WarsmashConstants.SIMULATION_STEP_TIME); + final int a1DamagePointSteps = (int) (this.unitAttack.getAnimationDamagePoint() + / WarsmashConstants.SIMULATION_STEP_TIME); + this.unit.setCooldownEndTime(currentTurnTick + a1CooldownSteps); + this.thisOrderCooldownEndTime = currentTurnTick + a1CooldownSteps; + this.damagePointLaunchTime = currentTurnTick + a1DamagePointSteps; + this.backSwingTime = currentTurnTick + a1DamagePointSteps + a1BackswingSteps; + this.unit.getUnitAnimationListener().playAnimation(true, PrimaryTag.ATTACK, SequenceUtils.EMPTY, 1.0f, + true); + this.unit.getUnitAnimationListener().queueAnimation(PrimaryTag.STAND, SequenceUtils.READY, false); + } + else if ((currentTurnTick >= this.thisOrderCooldownEndTime)) { + this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.STAND, SequenceUtils.READY, 1.0f, + false); + } + } + else { + this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.STAND, SequenceUtils.READY, 1.0f, + false); + } + + return this; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorFollow.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorFollow.java new file mode 100644 index 0000000..ee80015 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorFollow.java @@ -0,0 +1,38 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors; + +import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; +import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; + +public class CBehaviorFollow extends CAbstractRangedBehavior { + + public CBehaviorFollow(final CUnit unit) { + super(unit); + } + + public CBehavior reset(final CUnit target) { + return innerReset(target); + } + + @Override + public boolean isWithinRange(final CSimulation simulation) { + return this.unit.canReach(this.target, this.unit.getAcquisitionRange()); + } + + @Override + protected CBehavior update(final CSimulation simulation, final boolean withinRange) { + this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.STAND, SequenceUtils.EMPTY, 1.0f, false); + return this; + } + + @Override + protected boolean checkTargetStillValid(final CSimulation simulation) { + return !this.target.isDead(); + } + + @Override + protected void resetBeforeMoving(final CSimulation simulation) { + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CBehaviorMove.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorMove.java similarity index 87% rename from core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CBehaviorMove.java rename to core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorMove.java index 2bca116..5171364 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CBehaviorMove.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorMove.java @@ -1,4 +1,4 @@ -package com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders; +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors; import java.awt.geom.Point2D; import java.awt.geom.Point2D.Float; @@ -18,35 +18,58 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.pathing.CPathfindin public class CBehaviorMove implements CBehavior { private static final Rectangle tempRect = new Rectangle(); private final CUnit unit; - private final int orderId; - private boolean wasWithinPropWindow = false; - private List path = null; - private final CPathfindingProcessor.GridMapping gridMapping; - private final Point2D.Float target; - private int searchCycles = 0; - private CUnit followUnit; - public CBehaviorMove(final CUnit unit, final int orderId, final float targetX, final float targetY) { + public CBehaviorMove(final CUnit unit) { this.unit = unit; - this.orderId = orderId; - this.gridMapping = CPathfindingProcessor.isCollisionSizeBetterSuitedForCorners( - unit.getUnitType().getCollisionSize()) ? CPathfindingProcessor.GridMapping.CORNERS - : CPathfindingProcessor.GridMapping.CELLS; - this.target = new Point2D.Float(targetX, targetY); } - public CBehaviorMove(final CUnit unit, final int orderId, final CUnit followUnit) { - this.unit = unit; - this.orderId = orderId; + private boolean wasWithinPropWindow = false; + private List path = null; + private CPathfindingProcessor.GridMapping gridMapping; + private Point2D.Float target; + private int searchCycles = 0; + private CUnit followUnit; + private CRangedBehavior rangedBehavior; + + public CBehaviorMove reset(final float targetX, final float targetY) { + return reset(targetX, targetY, null); + } + + public CBehaviorMove reset(final float targetX, final float targetY, final CRangedBehavior rangedBehavior) { + this.wasWithinPropWindow = false; this.gridMapping = CPathfindingProcessor.isCollisionSizeBetterSuitedForCorners( - unit.getUnitType().getCollisionSize()) ? CPathfindingProcessor.GridMapping.CORNERS + this.unit.getUnitType().getCollisionSize()) ? CPathfindingProcessor.GridMapping.CORNERS + : CPathfindingProcessor.GridMapping.CELLS; + this.target = new Point2D.Float(targetX, targetY); + this.path = null; + this.searchCycles = 0; + this.followUnit = null; + this.rangedBehavior = rangedBehavior; + return this; + } + + public CBehaviorMove reset(final CUnit followUnit) { + return reset(followUnit, null); + } + + public CBehaviorMove reset(final CUnit followUnit, final CRangedBehavior rangedBehavior) { + this.wasWithinPropWindow = false; + this.gridMapping = CPathfindingProcessor.isCollisionSizeBetterSuitedForCorners( + this.unit.getUnitType().getCollisionSize()) ? CPathfindingProcessor.GridMapping.CORNERS : CPathfindingProcessor.GridMapping.CELLS; this.target = new Point2D.Float(followUnit.getX(), followUnit.getY()); + this.path = null; + this.searchCycles = 0; this.followUnit = followUnit; + this.rangedBehavior = rangedBehavior; + return this; } @Override - public boolean update(final CSimulation simulation) { + public CBehavior update(final CSimulation simulation) { + if ((this.rangedBehavior != null) && this.rangedBehavior.isWithinRange(simulation)) { + return this.rangedBehavior; + } final float prevX = this.unit.getX(); final float prevY = this.unit.getY(); @@ -119,7 +142,7 @@ public class CBehaviorMove implements CBehavior { this.searchCycles < 4); System.out.println("new path (for target) " + this.path); if (this.path.isEmpty()) { - return true; + return this.unit.pollNextOrderBehavior(simulation); } } float currentTargetX; @@ -217,7 +240,7 @@ public class CBehaviorMove implements CBehavior { this.searchCycles = 0; } if (this.path.isEmpty()) { - return true; + return this.unit.pollNextOrderBehavior(simulation); } else { System.out.println(this.path); @@ -249,7 +272,7 @@ public class CBehaviorMove implements CBehavior { deltaY = currentTargetY - nextY; deltaX = currentTargetX - nextX; if ((deltaX == 0.000f) && (deltaY == 0.000f) && this.path.isEmpty()) { - return true; + return this.unit.pollNextOrderBehavior(simulation); } System.out.println("new target: " + currentTargetX + "," + currentTargetY); System.out.println("new delta: " + deltaX + "," + deltaY); @@ -274,7 +297,7 @@ public class CBehaviorMove implements CBehavior { SequenceUtils.EMPTY, 1.0f, true); } this.wasWithinPropWindow = false; - return false; + return this; } } } @@ -290,7 +313,7 @@ public class CBehaviorMove implements CBehavior { this.searchCycles++; System.out.println("new path " + this.path); if (this.path.isEmpty() || (this.searchCycles > 5)) { - return true; + return this.unit.pollNextOrderBehavior(simulation); } } this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.WALK, SequenceUtils.EMPTY, 1.0f, @@ -309,12 +332,7 @@ public class CBehaviorMove implements CBehavior { this.wasWithinPropWindow = false; } - return false; - } - - @Override - public int getOrderId() { - return this.orderId; + return this; } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorPatrol.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorPatrol.java new file mode 100644 index 0000000..947fbf3 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorPatrol.java @@ -0,0 +1,37 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors; + +import com.badlogic.gdx.math.Vector2; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; + +public class CBehaviorPatrol implements CRangedBehavior { + + private final CUnit unit; + private Vector2 target; + private Vector2 startPoint; + + public CBehaviorPatrol(final CUnit unit) { + this.unit = unit; + } + + public CBehavior reset(final Vector2 target) { + this.target = target; + this.startPoint = new Vector2(this.unit.getX(), this.unit.getY()); + return this; + } + + @Override + public boolean isWithinRange(final CSimulation simulation) { + return this.unit.distance(this.target.x, this.target.y) <= simulation.getGameplayConstants() + .getCloseEnoughRange(); // TODO this is not how it was meant to be used + } + + @Override + public CBehavior update(final CSimulation simulation) { + final Vector2 temp = this.target; + this.target = this.startPoint; + this.startPoint = temp; + return this.unit.getMoveBehavior().reset(this.target.x, this.target.y, this); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorStop.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorStop.java new file mode 100644 index 0000000..f884891 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorStop.java @@ -0,0 +1,22 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors; + +import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; +import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; + +public class CBehaviorStop implements CBehavior { + + private final CUnit unit; + + public CBehaviorStop(final CUnit unit) { + this.unit = unit; + } + + @Override + public CBehavior update(final CSimulation game) { + this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.STAND, SequenceUtils.EMPTY, 1.0f, true); + return this.unit.pollNextOrderBehavior(game); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CRangedBehavior.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CRangedBehavior.java new file mode 100644 index 0000000..5c1f8a1 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CRangedBehavior.java @@ -0,0 +1,7 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; + +public interface CRangedBehavior extends CBehavior { + boolean isWithinRange(final CSimulation simulation); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CAbilityData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CAbilityData.java index 7188d3c..987ca6c 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CAbilityData.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CAbilityData.java @@ -1,27 +1,24 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.data; import com.etheller.warsmash.units.manager.MutableObjectData; -import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; -import com.etheller.warsmash.util.ImageUtils; import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityColdArrows; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityGeneric; public class CAbilityData { - private static final War3ID ABILITY_ICON = War3ID.fromString("aart"); + private static final War3ID COLD_ARROWS = War3ID.fromString("ACcw"); private final MutableObjectData abilityData; public CAbilityData(final MutableObjectData abilityData) { this.abilityData = abilityData; } - public String getIconPath(final War3ID id, final int level) { - final MutableGameObject mutableGameObject = this.abilityData.get(id); - if (mutableGameObject == null) { - return ImageUtils.DEFAULT_ICON_PATH; + public CAbility createAbility(final String ability, final int handleId) { + final War3ID war3Id = War3ID.fromString(ability); + if (war3Id.equals(COLD_ARROWS)) { + return new CAbilityColdArrows(war3Id, handleId); } - final String iconPath = mutableGameObject.getFieldAsString(ABILITY_ICON, level); - if ((iconPath == null) || "".equals(iconPath)) { - return ImageUtils.DEFAULT_ICON_PATH; - } - return iconPath; + return new CAbilityGeneric(war3Id, handleId); } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java index c041354..37f184c 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java @@ -32,396 +32,410 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUni import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.SimulationRenderController; public class CUnitData { - private static final War3ID MANA_INITIAL_AMOUNT = War3ID.fromString("umpi"); - private static final War3ID MANA_MAXIMUM = War3ID.fromString("umpm"); - private static final War3ID HIT_POINT_MAXIMUM = War3ID.fromString("uhpm"); - private static final War3ID MOVEMENT_SPEED_BASE = War3ID.fromString("umvs"); - private static final War3ID PROPULSION_WINDOW = War3ID.fromString("uprw"); - private static final War3ID TURN_RATE = War3ID.fromString("umvr"); - private static final War3ID IS_BLDG = War3ID.fromString("ubdg"); - private static final War3ID NAME = War3ID.fromString("unam"); - private static final War3ID PROJECTILE_LAUNCH_X = War3ID.fromString("ulpx"); - private static final War3ID PROJECTILE_LAUNCH_Y = War3ID.fromString("ulpy"); - private static final War3ID PROJECTILE_LAUNCH_Z = War3ID.fromString("ulpz"); - private static final War3ID ATTACKS_ENABLED = War3ID.fromString("uaen"); - private static final War3ID ATTACK1_BACKSWING_POINT = War3ID.fromString("ubs1"); - private static final War3ID ATTACK1_DAMAGE_POINT = War3ID.fromString("udp1"); - private static final War3ID ATTACK1_AREA_OF_EFFECT_FULL_DMG = War3ID.fromString("ua1f"); - private static final War3ID ATTACK1_AREA_OF_EFFECT_HALF_DMG = War3ID.fromString("ua1h"); - private static final War3ID ATTACK1_AREA_OF_EFFECT_QUARTER_DMG = War3ID.fromString("ua1q"); - private static final War3ID ATTACK1_AREA_OF_EFFECT_TARGETS = War3ID.fromString("ua1p"); - private static final War3ID ATTACK1_ATTACK_TYPE = War3ID.fromString("ua1t"); - private static final War3ID ATTACK1_COOLDOWN = War3ID.fromString("ua1c"); - private static final War3ID ATTACK1_DMG_BASE = War3ID.fromString("ua1b"); - private static final War3ID ATTACK1_DAMAGE_FACTOR_HALF = War3ID.fromString("uhd1"); - private static final War3ID ATTACK1_DAMAGE_FACTOR_QUARTER = War3ID.fromString("uqd1"); - private static final War3ID ATTACK1_DAMAGE_LOSS_FACTOR = War3ID.fromString("udl1"); - private static final War3ID ATTACK1_DMG_DICE = War3ID.fromString("ua1d"); - private static final War3ID ATTACK1_DMG_SIDES_PER_DIE = War3ID.fromString("ua1s"); - private static final War3ID ATTACK1_DMG_SPILL_DIST = War3ID.fromString("usd1"); - private static final War3ID ATTACK1_DMG_SPILL_RADIUS = War3ID.fromString("usr1"); - private static final War3ID ATTACK1_DMG_UPGRADE_AMT = War3ID.fromString("udu1"); - private static final War3ID ATTACK1_TARGET_COUNT = War3ID.fromString("utc1"); - private static final War3ID ATTACK1_PROJECTILE_ARC = War3ID.fromString("uma1"); - private static final War3ID ATTACK1_MISSILE_ART = War3ID.fromString("ua1m"); - private static final War3ID ATTACK1_PROJECTILE_HOMING_ENABLED = War3ID.fromString("umh1"); - private static final War3ID ATTACK1_PROJECTILE_SPEED = War3ID.fromString("ua1z"); - private static final War3ID ATTACK1_RANGE = War3ID.fromString("ua1r"); - private static final War3ID ATTACK1_RANGE_MOTION_BUFFER = War3ID.fromString("urb1"); - private static final War3ID ATTACK1_SHOW_UI = War3ID.fromString("uwu1"); - private static final War3ID ATTACK1_TARGETS_ALLOWED = War3ID.fromString("ua1g"); - private static final War3ID ATTACK1_WEAPON_SOUND = War3ID.fromString("ucs1"); - private static final War3ID ATTACK1_WEAPON_TYPE = War3ID.fromString("ua1w"); + private static final War3ID MANA_INITIAL_AMOUNT = War3ID.fromString("umpi"); + private static final War3ID MANA_MAXIMUM = War3ID.fromString("umpm"); + private static final War3ID HIT_POINT_MAXIMUM = War3ID.fromString("uhpm"); + private static final War3ID MOVEMENT_SPEED_BASE = War3ID.fromString("umvs"); + private static final War3ID PROPULSION_WINDOW = War3ID.fromString("uprw"); + private static final War3ID TURN_RATE = War3ID.fromString("umvr"); + private static final War3ID IS_BLDG = War3ID.fromString("ubdg"); + private static final War3ID NAME = War3ID.fromString("unam"); + private static final War3ID PROJECTILE_LAUNCH_X = War3ID.fromString("ulpx"); + private static final War3ID PROJECTILE_LAUNCH_Y = War3ID.fromString("ulpy"); + private static final War3ID PROJECTILE_LAUNCH_Z = War3ID.fromString("ulpz"); + private static final War3ID ATTACKS_ENABLED = War3ID.fromString("uaen"); + private static final War3ID ATTACK1_BACKSWING_POINT = War3ID.fromString("ubs1"); + private static final War3ID ATTACK1_DAMAGE_POINT = War3ID.fromString("udp1"); + private static final War3ID ATTACK1_AREA_OF_EFFECT_FULL_DMG = War3ID.fromString("ua1f"); + private static final War3ID ATTACK1_AREA_OF_EFFECT_HALF_DMG = War3ID.fromString("ua1h"); + private static final War3ID ATTACK1_AREA_OF_EFFECT_QUARTER_DMG = War3ID.fromString("ua1q"); + private static final War3ID ATTACK1_AREA_OF_EFFECT_TARGETS = War3ID.fromString("ua1p"); + private static final War3ID ATTACK1_ATTACK_TYPE = War3ID.fromString("ua1t"); + private static final War3ID ATTACK1_COOLDOWN = War3ID.fromString("ua1c"); + private static final War3ID ATTACK1_DMG_BASE = War3ID.fromString("ua1b"); + private static final War3ID ATTACK1_DAMAGE_FACTOR_HALF = War3ID.fromString("uhd1"); + private static final War3ID ATTACK1_DAMAGE_FACTOR_QUARTER = War3ID.fromString("uqd1"); + private static final War3ID ATTACK1_DAMAGE_LOSS_FACTOR = War3ID.fromString("udl1"); + private static final War3ID ATTACK1_DMG_DICE = War3ID.fromString("ua1d"); + private static final War3ID ATTACK1_DMG_SIDES_PER_DIE = War3ID.fromString("ua1s"); + private static final War3ID ATTACK1_DMG_SPILL_DIST = War3ID.fromString("usd1"); + private static final War3ID ATTACK1_DMG_SPILL_RADIUS = War3ID.fromString("usr1"); + private static final War3ID ATTACK1_DMG_UPGRADE_AMT = War3ID.fromString("udu1"); + private static final War3ID ATTACK1_TARGET_COUNT = War3ID.fromString("utc1"); + private static final War3ID ATTACK1_PROJECTILE_ARC = War3ID.fromString("uma1"); + private static final War3ID ATTACK1_MISSILE_ART = War3ID.fromString("ua1m"); + private static final War3ID ATTACK1_PROJECTILE_HOMING_ENABLED = War3ID.fromString("umh1"); + private static final War3ID ATTACK1_PROJECTILE_SPEED = War3ID.fromString("ua1z"); + private static final War3ID ATTACK1_RANGE = War3ID.fromString("ua1r"); + private static final War3ID ATTACK1_RANGE_MOTION_BUFFER = War3ID.fromString("urb1"); + private static final War3ID ATTACK1_SHOW_UI = War3ID.fromString("uwu1"); + private static final War3ID ATTACK1_TARGETS_ALLOWED = War3ID.fromString("ua1g"); + private static final War3ID ATTACK1_WEAPON_SOUND = War3ID.fromString("ucs1"); + private static final War3ID ATTACK1_WEAPON_TYPE = War3ID.fromString("ua1w"); - private static final War3ID ATTACK2_BACKSWING_POINT = War3ID.fromString("ubs2"); - private static final War3ID ATTACK2_DAMAGE_POINT = War3ID.fromString("udp2"); - private static final War3ID ATTACK2_AREA_OF_EFFECT_FULL_DMG = War3ID.fromString("ua2f"); - private static final War3ID ATTACK2_AREA_OF_EFFECT_HALF_DMG = War3ID.fromString("ua2h"); - private static final War3ID ATTACK2_AREA_OF_EFFECT_QUARTER_DMG = War3ID.fromString("ua2q"); - private static final War3ID ATTACK2_AREA_OF_EFFECT_TARGETS = War3ID.fromString("ua2p"); - private static final War3ID ATTACK2_ATTACK_TYPE = War3ID.fromString("ua2t"); - private static final War3ID ATTACK2_COOLDOWN = War3ID.fromString("ua2c"); - private static final War3ID ATTACK2_DMG_BASE = War3ID.fromString("ua2b"); - private static final War3ID ATTACK2_DAMAGE_FACTOR_HALF = War3ID.fromString("uhd2"); - private static final War3ID ATTACK2_DAMAGE_FACTOR_QUARTER = War3ID.fromString("uqd2"); - private static final War3ID ATTACK2_DAMAGE_LOSS_FACTOR = War3ID.fromString("udl2"); - private static final War3ID ATTACK2_DMG_DICE = War3ID.fromString("ua2d"); - private static final War3ID ATTACK2_DMG_SIDES_PER_DIE = War3ID.fromString("ua2s"); - private static final War3ID ATTACK2_DMG_SPILL_DIST = War3ID.fromString("usd2"); - private static final War3ID ATTACK2_DMG_SPILL_RADIUS = War3ID.fromString("usr2"); - private static final War3ID ATTACK2_DMG_UPGRADE_AMT = War3ID.fromString("udu2"); - private static final War3ID ATTACK2_TARGET_COUNT = War3ID.fromString("utc2"); - private static final War3ID ATTACK2_PROJECTILE_ARC = War3ID.fromString("uma2"); - private static final War3ID ATTACK2_MISSILE_ART = War3ID.fromString("ua2m"); - private static final War3ID ATTACK2_PROJECTILE_HOMING_ENABLED = War3ID.fromString("umh2"); - private static final War3ID ATTACK2_PROJECTILE_SPEED = War3ID.fromString("ua2z"); - private static final War3ID ATTACK2_RANGE = War3ID.fromString("ua2r"); - private static final War3ID ATTACK2_RANGE_MOTION_BUFFER = War3ID.fromString("urb2"); - private static final War3ID ATTACK2_SHOW_UI = War3ID.fromString("uwu2"); - private static final War3ID ATTACK2_TARGETS_ALLOWED = War3ID.fromString("ua2g"); - private static final War3ID ATTACK2_WEAPON_SOUND = War3ID.fromString("ucs2"); - private static final War3ID ATTACK2_WEAPON_TYPE = War3ID.fromString("ua2w"); + private static final War3ID ATTACK2_BACKSWING_POINT = War3ID.fromString("ubs2"); + private static final War3ID ATTACK2_DAMAGE_POINT = War3ID.fromString("udp2"); + private static final War3ID ATTACK2_AREA_OF_EFFECT_FULL_DMG = War3ID.fromString("ua2f"); + private static final War3ID ATTACK2_AREA_OF_EFFECT_HALF_DMG = War3ID.fromString("ua2h"); + private static final War3ID ATTACK2_AREA_OF_EFFECT_QUARTER_DMG = War3ID.fromString("ua2q"); + private static final War3ID ATTACK2_AREA_OF_EFFECT_TARGETS = War3ID.fromString("ua2p"); + private static final War3ID ATTACK2_ATTACK_TYPE = War3ID.fromString("ua2t"); + private static final War3ID ATTACK2_COOLDOWN = War3ID.fromString("ua2c"); + private static final War3ID ATTACK2_DMG_BASE = War3ID.fromString("ua2b"); + private static final War3ID ATTACK2_DAMAGE_FACTOR_HALF = War3ID.fromString("uhd2"); + private static final War3ID ATTACK2_DAMAGE_FACTOR_QUARTER = War3ID.fromString("uqd2"); + private static final War3ID ATTACK2_DAMAGE_LOSS_FACTOR = War3ID.fromString("udl2"); + private static final War3ID ATTACK2_DMG_DICE = War3ID.fromString("ua2d"); + private static final War3ID ATTACK2_DMG_SIDES_PER_DIE = War3ID.fromString("ua2s"); + private static final War3ID ATTACK2_DMG_SPILL_DIST = War3ID.fromString("usd2"); + private static final War3ID ATTACK2_DMG_SPILL_RADIUS = War3ID.fromString("usr2"); + private static final War3ID ATTACK2_DMG_UPGRADE_AMT = War3ID.fromString("udu2"); + private static final War3ID ATTACK2_TARGET_COUNT = War3ID.fromString("utc2"); + private static final War3ID ATTACK2_PROJECTILE_ARC = War3ID.fromString("uma2"); + private static final War3ID ATTACK2_MISSILE_ART = War3ID.fromString("ua2m"); + private static final War3ID ATTACK2_PROJECTILE_HOMING_ENABLED = War3ID.fromString("umh2"); + private static final War3ID ATTACK2_PROJECTILE_SPEED = War3ID.fromString("ua2z"); + private static final War3ID ATTACK2_RANGE = War3ID.fromString("ua2r"); + private static final War3ID ATTACK2_RANGE_MOTION_BUFFER = War3ID.fromString("urb2"); + private static final War3ID ATTACK2_SHOW_UI = War3ID.fromString("uwu2"); + private static final War3ID ATTACK2_TARGETS_ALLOWED = War3ID.fromString("ua2g"); + private static final War3ID ATTACK2_WEAPON_SOUND = War3ID.fromString("ucs2"); + private static final War3ID ATTACK2_WEAPON_TYPE = War3ID.fromString("ua2w"); - private static final War3ID ACQUISITION_RANGE = War3ID.fromString("uacq"); - private static final War3ID MINIMUM_ATTACK_RANGE = War3ID.fromString("uamn"); + private static final War3ID ACQUISITION_RANGE = War3ID.fromString("uacq"); + private static final War3ID MINIMUM_ATTACK_RANGE = War3ID.fromString("uamn"); - private static final War3ID PROJECTILE_IMPACT_Z = War3ID.fromString("uimz"); + private static final War3ID PROJECTILE_IMPACT_Z = War3ID.fromString("uimz"); - private static final War3ID DEATH_TYPE = War3ID.fromString("udea"); - private static final War3ID ARMOR_TYPE = War3ID.fromString("uarm"); + private static final War3ID DEATH_TYPE = War3ID.fromString("udea"); + private static final War3ID ARMOR_TYPE = War3ID.fromString("uarm"); - private static final War3ID DEFENSE = War3ID.fromString("udef"); - private static final War3ID DEFENSE_TYPE = War3ID.fromString("udty"); - private static final War3ID MOVE_HEIGHT = War3ID.fromString("umvh"); - private static final War3ID MOVE_TYPE = War3ID.fromString("umvt"); - private static final War3ID COLLISION_SIZE = War3ID.fromString("ucol"); - private static final War3ID CLASSIFICATION = War3ID.fromString("utyp"); - private static final War3ID DEATH_TIME = War3ID.fromString("udtm"); - private static final War3ID TARGETED_AS = War3ID.fromString("utar"); - private final MutableObjectData unitData; - private final Map unitIdToUnitType = new HashMap<>(); + private static final War3ID DEFENSE = War3ID.fromString("udef"); + private static final War3ID DEFENSE_TYPE = War3ID.fromString("udty"); + private static final War3ID MOVE_HEIGHT = War3ID.fromString("umvh"); + private static final War3ID MOVE_TYPE = War3ID.fromString("umvt"); + private static final War3ID COLLISION_SIZE = War3ID.fromString("ucol"); + private static final War3ID CLASSIFICATION = War3ID.fromString("utyp"); + private static final War3ID DEATH_TIME = War3ID.fromString("udtm"); + private static final War3ID TARGETED_AS = War3ID.fromString("utar"); - public CUnitData(final MutableObjectData unitData) { - this.unitData = unitData; - } + private static final War3ID ABILITIES_NORMAL = War3ID.fromString("uabi"); + private final MutableObjectData unitData; + private final Map unitIdToUnitType = new HashMap<>(); + private final CAbilityData abilityData; - public CUnit create(final CSimulation simulation, final int playerIndex, final War3ID typeId, final float x, - final float y, final float facing, final BufferedImage buildingPathingPixelMap, - final SimulationRenderController simulationRenderController, final HandleIdAllocator handleIdAllocator) { - final MutableGameObject unitType = this.unitData.get(typeId); - final int handleId = handleIdAllocator.createId(); - final int life = unitType.getFieldAsInteger(HIT_POINT_MAXIMUM, 0); - final int manaInitial = unitType.getFieldAsInteger(MANA_INITIAL_AMOUNT, 0); - final int manaMaximum = unitType.getFieldAsInteger(MANA_MAXIMUM, 0); - final int speed = unitType.getFieldAsInteger(MOVEMENT_SPEED_BASE, 0); - final int defense = unitType.getFieldAsInteger(DEFENSE, 0); + public CUnitData(final MutableObjectData unitData, final CAbilityData abilityData) { + this.unitData = unitData; + this.abilityData = abilityData; + } - final CUnitType unitTypeInstance = getUnitTypeInstance(typeId, buildingPathingPixelMap, unitType); + public CUnit create(final CSimulation simulation, final int playerIndex, final War3ID typeId, final float x, + final float y, final float facing, final BufferedImage buildingPathingPixelMap, + final SimulationRenderController simulationRenderController, final HandleIdAllocator handleIdAllocator) { + final MutableGameObject unitType = this.unitData.get(typeId); + final int handleId = handleIdAllocator.createId(); + final int life = unitType.getFieldAsInteger(HIT_POINT_MAXIMUM, 0); + final int manaInitial = unitType.getFieldAsInteger(MANA_INITIAL_AMOUNT, 0); + final int manaMaximum = unitType.getFieldAsInteger(MANA_MAXIMUM, 0); + final int speed = unitType.getFieldAsInteger(MOVEMENT_SPEED_BASE, 0); + final int defense = unitType.getFieldAsInteger(DEFENSE, 0); + final String abilityList = unitType.getFieldAsString(ABILITIES_NORMAL, 0); - final CUnit unit = new CUnit(handleId, playerIndex, x, y, life, typeId, facing, manaInitial, life, manaMaximum, - speed, defense, unitTypeInstance); - if (speed > 0) { - unit.add(simulation, new CAbilityMove(handleIdAllocator.createId())); - } - if (!unitTypeInstance.getAttacks().isEmpty()) { - unit.add(simulation, new CAbilityAttack(handleIdAllocator.createId())); - } - return unit; - } + final CUnitType unitTypeInstance = getUnitTypeInstance(typeId, buildingPathingPixelMap, unitType); - private CUnitType getUnitTypeInstance(final War3ID typeId, final BufferedImage buildingPathingPixelMap, - final MutableGameObject unitType) { - CUnitType unitTypeInstance = this.unitIdToUnitType.get(typeId); - if (unitTypeInstance == null) { - final float moveHeight = unitType.getFieldAsFloat(MOVE_HEIGHT, 0); - final String movetp = unitType.getFieldAsString(MOVE_TYPE, 0); - final float collisionSize = unitType.getFieldAsFloat(COLLISION_SIZE, 0); - final boolean isBldg = unitType.getFieldAsBoolean(IS_BLDG, 0); - final PathingGrid.MovementType movementType = PathingGrid.getMovementType(movetp); - final String unitName = unitType.getFieldAsString(NAME, 0); - final float acquisitionRange = unitType.getFieldAsFloat(ACQUISITION_RANGE, 0); - final float minimumAttackRange = unitType.getFieldAsFloat(MINIMUM_ATTACK_RANGE, 0); - final EnumSet targetedAs = CTargetType - .parseTargetTypeSet(unitType.getFieldAsString(TARGETED_AS, 0)); - final String classificationString = unitType.getFieldAsString(CLASSIFICATION, 0); - final EnumSet classifications = EnumSet.noneOf(CUnitClassification.class); - if (classificationString != null) { - final String[] classificationValues = classificationString.split(","); - for (final String unitEditorKey : classificationValues) { - final CUnitClassification unitClassification = CUnitClassification - .parseUnitClassification(unitEditorKey); - if (unitClassification != null) { - classifications.add(unitClassification); - } - } - } - final List attacks = new ArrayList<>(); - final int attacksEnabled = unitType.getFieldAsInteger(ATTACKS_ENABLED, 0); - if ((attacksEnabled & 0x1) != 0) { - try { - // attack one - final float animationBackswingPoint = unitType.getFieldAsFloat(ATTACK1_BACKSWING_POINT, 0); - final float animationDamagePoint = unitType.getFieldAsFloat(ATTACK1_DAMAGE_POINT, 0); - final int areaOfEffectFullDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_FULL_DMG, 0); - final int areaOfEffectMediumDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_HALF_DMG, 0); - final int areaOfEffectSmallDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_QUARTER_DMG, 0); - final EnumSet areaOfEffectTargets = CTargetType - .parseTargetTypeSet(unitType.getFieldAsString(ATTACK1_AREA_OF_EFFECT_TARGETS, 0)); - final CAttackType attackType = CAttackType - .parseAttackType(unitType.getFieldAsString(ATTACK1_ATTACK_TYPE, 0)); - final float cooldownTime = unitType.getFieldAsFloat(ATTACK1_COOLDOWN, 0); - final int damageBase = unitType.getFieldAsInteger(ATTACK1_DMG_BASE, 0); - final float damageFactorMedium = unitType.getFieldAsFloat(ATTACK1_DAMAGE_FACTOR_HALF, 0); - final float damageFactorSmall = unitType.getFieldAsFloat(ATTACK1_DAMAGE_FACTOR_QUARTER, 0); - final float damageLossFactor = unitType.getFieldAsFloat(ATTACK1_DAMAGE_LOSS_FACTOR, 0); - final int damageDice = unitType.getFieldAsInteger(ATTACK1_DMG_DICE, 0); - final int damageSidesPerDie = unitType.getFieldAsInteger(ATTACK1_DMG_SIDES_PER_DIE, 0); - final float damageSpillDistance = unitType.getFieldAsFloat(ATTACK1_DMG_SPILL_DIST, 0); - final float damageSpillRadius = unitType.getFieldAsFloat(ATTACK1_DMG_SPILL_RADIUS, 0); - final int damageUpgradeAmount = unitType.getFieldAsInteger(ATTACK1_DMG_UPGRADE_AMT, 0); - final int maximumNumberOfTargets = unitType.getFieldAsInteger(ATTACK1_TARGET_COUNT, 0); - final float projectileArc = unitType.getFieldAsFloat(ATTACK1_PROJECTILE_ARC, 0); - final String projectileArt = unitType.getFieldAsString(ATTACK1_MISSILE_ART, 0); - final boolean projectileHomingEnabled = unitType.getFieldAsBoolean(ATTACK1_PROJECTILE_HOMING_ENABLED, - 0); - final int projectileSpeed = unitType.getFieldAsInteger(ATTACK1_PROJECTILE_SPEED, 0); - final int range = unitType.getFieldAsInteger(ATTACK1_RANGE, 0); - final float rangeMotionBuffer = unitType.getFieldAsFloat(ATTACK1_RANGE_MOTION_BUFFER, 0); - final boolean showUI = unitType.getFieldAsBoolean(ATTACK1_SHOW_UI, 0); - final EnumSet targetsAllowed = CTargetType - .parseTargetTypeSet(unitType.getFieldAsString(ATTACK1_TARGETS_ALLOWED, 0)); - final String weaponSound = unitType.getFieldAsString(ATTACK1_WEAPON_SOUND, 0); - final CWeaponType weaponType = CWeaponType - .parseWeaponType(unitType.getFieldAsString(ATTACK1_WEAPON_TYPE, 0)); - attacks.add(createAttack(animationBackswingPoint, animationDamagePoint, areaOfEffectFullDamage, - areaOfEffectMediumDamage, areaOfEffectSmallDamage, areaOfEffectTargets, attackType, - cooldownTime, damageBase, damageFactorMedium, damageFactorSmall, damageLossFactor, damageDice, - damageSidesPerDie, damageSpillDistance, damageSpillRadius, damageUpgradeAmount, - maximumNumberOfTargets, projectileArc, projectileArt, projectileHomingEnabled, projectileSpeed, - range, rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType)); - } catch (Exception exc) { - System.err.println("Attack 1 failed to parse with: " + exc.getClass() + ":" + exc.getMessage()); - } - } - if ((attacksEnabled & 0x2) != 0) { - try { - // attack two - final float animationBackswingPoint = unitType.getFieldAsFloat(ATTACK2_BACKSWING_POINT, 0); - final float animationDamagePoint = unitType.getFieldAsFloat(ATTACK2_DAMAGE_POINT, 0); - final int areaOfEffectFullDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_FULL_DMG, 0); - final int areaOfEffectMediumDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_HALF_DMG, 0); - final int areaOfEffectSmallDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_QUARTER_DMG, 0); - final EnumSet areaOfEffectTargets = CTargetType - .parseTargetTypeSet(unitType.getFieldAsString(ATTACK2_AREA_OF_EFFECT_TARGETS, 0)); - final CAttackType attackType = CAttackType - .parseAttackType(unitType.getFieldAsString(ATTACK2_ATTACK_TYPE, 0)); - final float cooldownTime = unitType.getFieldAsFloat(ATTACK2_COOLDOWN, 0); - final int damageBase = unitType.getFieldAsInteger(ATTACK2_DMG_BASE, 0); - final float damageFactorMedium = unitType.getFieldAsFloat(ATTACK2_DAMAGE_FACTOR_HALF, 0); - final float damageFactorSmall = unitType.getFieldAsFloat(ATTACK2_DAMAGE_FACTOR_QUARTER, 0); - final float damageLossFactor = unitType.getFieldAsFloat(ATTACK2_DAMAGE_LOSS_FACTOR, 0); - final int damageDice = unitType.getFieldAsInteger(ATTACK2_DMG_DICE, 0); - final int damageSidesPerDie = unitType.getFieldAsInteger(ATTACK2_DMG_SIDES_PER_DIE, 0); - final float damageSpillDistance = unitType.getFieldAsFloat(ATTACK2_DMG_SPILL_DIST, 0); - final float damageSpillRadius = unitType.getFieldAsFloat(ATTACK2_DMG_SPILL_RADIUS, 0); - final int damageUpgradeAmount = unitType.getFieldAsInteger(ATTACK2_DMG_UPGRADE_AMT, 0); - final int maximumNumberOfTargets = unitType.getFieldAsInteger(ATTACK2_TARGET_COUNT, 0); - final float projectileArc = unitType.getFieldAsFloat(ATTACK2_PROJECTILE_ARC, 0); - final String projectileArt = unitType.getFieldAsString(ATTACK2_MISSILE_ART, 0); - final boolean projectileHomingEnabled = unitType.getFieldAsBoolean(ATTACK2_PROJECTILE_HOMING_ENABLED, - 0); - final int projectileSpeed = unitType.getFieldAsInteger(ATTACK2_PROJECTILE_SPEED, 0); - final int range = unitType.getFieldAsInteger(ATTACK2_RANGE, 0); - final float rangeMotionBuffer = unitType.getFieldAsFloat(ATTACK2_RANGE_MOTION_BUFFER, 0); - final boolean showUI = unitType.getFieldAsBoolean(ATTACK2_SHOW_UI, 0); - final EnumSet targetsAllowed = CTargetType - .parseTargetTypeSet(unitType.getFieldAsString(ATTACK2_TARGETS_ALLOWED, 0)); - final String weaponSound = unitType.getFieldAsString(ATTACK2_WEAPON_SOUND, 0); - final CWeaponType weaponType = CWeaponType - .parseWeaponType(unitType.getFieldAsString(ATTACK2_WEAPON_TYPE, 0)); - attacks.add(createAttack(animationBackswingPoint, animationDamagePoint, areaOfEffectFullDamage, - areaOfEffectMediumDamage, areaOfEffectSmallDamage, areaOfEffectTargets, attackType, - cooldownTime, damageBase, damageFactorMedium, damageFactorSmall, damageLossFactor, damageDice, - damageSidesPerDie, damageSpillDistance, damageSpillRadius, damageUpgradeAmount, - maximumNumberOfTargets, projectileArc, projectileArt, projectileHomingEnabled, projectileSpeed, - range, rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType)); - } catch (Exception exc) { - System.err.println("Attack 2 failed to parse with: " + exc.getClass() + ":" + exc.getMessage()); - } - } - final int deathType = unitType.getFieldAsInteger(DEATH_TYPE, 0); - final boolean raise = (deathType & 0x1) != 0; - final boolean decay = (deathType & 0x2) != 0; - final String armorType = unitType.getFieldAsString(ARMOR_TYPE, 0); - final float impactZ = unitType.getFieldAsFloat(PROJECTILE_IMPACT_Z, 0); - final CDefenseType defenseType = CDefenseType.parseDefenseType(unitType.getFieldAsString(DEFENSE_TYPE, 0)); - final float deathTime = unitType.getFieldAsFloat(DEATH_TIME, 0); - unitTypeInstance = new CUnitType(unitName, isBldg, movementType, moveHeight, collisionSize, classifications, - attacks, armorType, raise, decay, defenseType, impactZ, buildingPathingPixelMap, deathTime, - targetedAs, acquisitionRange, minimumAttackRange); - this.unitIdToUnitType.put(typeId, unitTypeInstance); - } - return unitTypeInstance; - } + final CUnit unit = new CUnit(handleId, playerIndex, x, y, life, typeId, facing, manaInitial, life, manaMaximum, + speed, defense, unitTypeInstance); + if (speed > 0) { + unit.add(simulation, new CAbilityMove(handleIdAllocator.createId())); + } + if (!unitTypeInstance.getAttacks().isEmpty()) { + unit.add(simulation, new CAbilityAttack(handleIdAllocator.createId())); + } + for (final String ability : abilityList.split(",")) { + unit.add(simulation, this.abilityData.createAbility(ability, handleIdAllocator.createId())); + } + return unit; + } - private CUnitAttack createAttack(final float animationBackswingPoint, final float animationDamagePoint, - final int areaOfEffectFullDamage, final int areaOfEffectMediumDamage, final int areaOfEffectSmallDamage, - final EnumSet areaOfEffectTargets, final CAttackType attackType, final float cooldownTime, - final int damageBase, final float damageFactorMedium, final float damageFactorSmall, - final float damageLossFactor, final int damageDice, final int damageSidesPerDie, - final float damageSpillDistance, final float damageSpillRadius, final int damageUpgradeAmount, - final int maximumNumberOfTargets, final float projectileArc, final String projectileArt, - final boolean projectileHomingEnabled, final int projectileSpeed, final int range, - final float rangeMotionBuffer, final boolean showUI, final EnumSet targetsAllowed, - final String weaponSound, final CWeaponType weaponType) { - final CUnitAttack attack; - switch (weaponType) { - case MISSILE: - attack = new CUnitAttackMissile(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, - damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, - targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, projectileHomingEnabled, - projectileSpeed); - break; - case MBOUNCE: - attack = new CUnitAttackMissileBounce(animationBackswingPoint, animationDamagePoint, attackType, - cooldownTime, damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, - rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, - projectileHomingEnabled, projectileSpeed, damageLossFactor, maximumNumberOfTargets, - areaOfEffectFullDamage, areaOfEffectTargets); - break; - case MSPLASH: - case ARTILLERY: - attack = new CUnitAttackMissileSplash(animationBackswingPoint, animationDamagePoint, attackType, - cooldownTime, damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, - rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, - projectileHomingEnabled, projectileSpeed, areaOfEffectFullDamage, areaOfEffectMediumDamage, - areaOfEffectSmallDamage, areaOfEffectTargets, damageFactorMedium, damageFactorSmall); - break; - case MLINE: - case ALINE: - attack = new CUnitAttackMissileLine(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, - damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, - targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, projectileHomingEnabled, - projectileSpeed, damageSpillDistance, damageSpillRadius); - break; - case INSTANT: - attack = new CUnitAttackInstant(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, - damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, - targetsAllowed, weaponSound, weaponType, projectileArt); - break; - default: - case NORMAL: - attack = new CUnitAttackNormal(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, - damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, - targetsAllowed, weaponSound, weaponType); - break; - } - return attack; - } + private CUnitType getUnitTypeInstance(final War3ID typeId, final BufferedImage buildingPathingPixelMap, + final MutableGameObject unitType) { + CUnitType unitTypeInstance = this.unitIdToUnitType.get(typeId); + if (unitTypeInstance == null) { + final float moveHeight = unitType.getFieldAsFloat(MOVE_HEIGHT, 0); + final String movetp = unitType.getFieldAsString(MOVE_TYPE, 0); + final float collisionSize = unitType.getFieldAsFloat(COLLISION_SIZE, 0); + final boolean isBldg = unitType.getFieldAsBoolean(IS_BLDG, 0); + final PathingGrid.MovementType movementType = PathingGrid.getMovementType(movetp); + final String unitName = unitType.getFieldAsString(NAME, 0); + final float acquisitionRange = unitType.getFieldAsFloat(ACQUISITION_RANGE, 0); + final float minimumAttackRange = unitType.getFieldAsFloat(MINIMUM_ATTACK_RANGE, 0); + final EnumSet targetedAs = CTargetType + .parseTargetTypeSet(unitType.getFieldAsString(TARGETED_AS, 0)); + final String classificationString = unitType.getFieldAsString(CLASSIFICATION, 0); + final EnumSet classifications = EnumSet.noneOf(CUnitClassification.class); + if (classificationString != null) { + final String[] classificationValues = classificationString.split(","); + for (final String unitEditorKey : classificationValues) { + final CUnitClassification unitClassification = CUnitClassification + .parseUnitClassification(unitEditorKey); + if (unitClassification != null) { + classifications.add(unitClassification); + } + } + } + final List attacks = new ArrayList<>(); + final int attacksEnabled = unitType.getFieldAsInteger(ATTACKS_ENABLED, 0); + if ((attacksEnabled & 0x1) != 0) { + try { + // attack one + final float animationBackswingPoint = unitType.getFieldAsFloat(ATTACK1_BACKSWING_POINT, 0); + final float animationDamagePoint = unitType.getFieldAsFloat(ATTACK1_DAMAGE_POINT, 0); + final int areaOfEffectFullDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_FULL_DMG, 0); + final int areaOfEffectMediumDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_HALF_DMG, 0); + final int areaOfEffectSmallDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_QUARTER_DMG, + 0); + final EnumSet areaOfEffectTargets = CTargetType + .parseTargetTypeSet(unitType.getFieldAsString(ATTACK1_AREA_OF_EFFECT_TARGETS, 0)); + final CAttackType attackType = CAttackType + .parseAttackType(unitType.getFieldAsString(ATTACK1_ATTACK_TYPE, 0)); + final float cooldownTime = unitType.getFieldAsFloat(ATTACK1_COOLDOWN, 0); + final int damageBase = unitType.getFieldAsInteger(ATTACK1_DMG_BASE, 0); + final float damageFactorMedium = unitType.getFieldAsFloat(ATTACK1_DAMAGE_FACTOR_HALF, 0); + final float damageFactorSmall = unitType.getFieldAsFloat(ATTACK1_DAMAGE_FACTOR_QUARTER, 0); + final float damageLossFactor = unitType.getFieldAsFloat(ATTACK1_DAMAGE_LOSS_FACTOR, 0); + final int damageDice = unitType.getFieldAsInteger(ATTACK1_DMG_DICE, 0); + final int damageSidesPerDie = unitType.getFieldAsInteger(ATTACK1_DMG_SIDES_PER_DIE, 0); + final float damageSpillDistance = unitType.getFieldAsFloat(ATTACK1_DMG_SPILL_DIST, 0); + final float damageSpillRadius = unitType.getFieldAsFloat(ATTACK1_DMG_SPILL_RADIUS, 0); + final int damageUpgradeAmount = unitType.getFieldAsInteger(ATTACK1_DMG_UPGRADE_AMT, 0); + final int maximumNumberOfTargets = unitType.getFieldAsInteger(ATTACK1_TARGET_COUNT, 0); + final float projectileArc = unitType.getFieldAsFloat(ATTACK1_PROJECTILE_ARC, 0); + final String projectileArt = unitType.getFieldAsString(ATTACK1_MISSILE_ART, 0); + final boolean projectileHomingEnabled = unitType + .getFieldAsBoolean(ATTACK1_PROJECTILE_HOMING_ENABLED, 0); + final int projectileSpeed = unitType.getFieldAsInteger(ATTACK1_PROJECTILE_SPEED, 0); + final int range = unitType.getFieldAsInteger(ATTACK1_RANGE, 0); + final float rangeMotionBuffer = unitType.getFieldAsFloat(ATTACK1_RANGE_MOTION_BUFFER, 0); + final boolean showUI = unitType.getFieldAsBoolean(ATTACK1_SHOW_UI, 0); + final EnumSet targetsAllowed = CTargetType + .parseTargetTypeSet(unitType.getFieldAsString(ATTACK1_TARGETS_ALLOWED, 0)); + final String weaponSound = unitType.getFieldAsString(ATTACK1_WEAPON_SOUND, 0); + final CWeaponType weaponType = CWeaponType + .parseWeaponType(unitType.getFieldAsString(ATTACK1_WEAPON_TYPE, 0)); + attacks.add(createAttack(animationBackswingPoint, animationDamagePoint, areaOfEffectFullDamage, + areaOfEffectMediumDamage, areaOfEffectSmallDamage, areaOfEffectTargets, attackType, + cooldownTime, damageBase, damageFactorMedium, damageFactorSmall, damageLossFactor, + damageDice, damageSidesPerDie, damageSpillDistance, damageSpillRadius, damageUpgradeAmount, + maximumNumberOfTargets, projectileArc, projectileArt, projectileHomingEnabled, + projectileSpeed, range, rangeMotionBuffer, showUI, targetsAllowed, weaponSound, + weaponType)); + } + catch (final Exception exc) { + System.err.println("Attack 1 failed to parse with: " + exc.getClass() + ":" + exc.getMessage()); + } + } + if ((attacksEnabled & 0x2) != 0) { + try { + // attack two + final float animationBackswingPoint = unitType.getFieldAsFloat(ATTACK2_BACKSWING_POINT, 0); + final float animationDamagePoint = unitType.getFieldAsFloat(ATTACK2_DAMAGE_POINT, 0); + final int areaOfEffectFullDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_FULL_DMG, 0); + final int areaOfEffectMediumDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_HALF_DMG, 0); + final int areaOfEffectSmallDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_QUARTER_DMG, + 0); + final EnumSet areaOfEffectTargets = CTargetType + .parseTargetTypeSet(unitType.getFieldAsString(ATTACK2_AREA_OF_EFFECT_TARGETS, 0)); + final CAttackType attackType = CAttackType + .parseAttackType(unitType.getFieldAsString(ATTACK2_ATTACK_TYPE, 0)); + final float cooldownTime = unitType.getFieldAsFloat(ATTACK2_COOLDOWN, 0); + final int damageBase = unitType.getFieldAsInteger(ATTACK2_DMG_BASE, 0); + final float damageFactorMedium = unitType.getFieldAsFloat(ATTACK2_DAMAGE_FACTOR_HALF, 0); + final float damageFactorSmall = unitType.getFieldAsFloat(ATTACK2_DAMAGE_FACTOR_QUARTER, 0); + final float damageLossFactor = unitType.getFieldAsFloat(ATTACK2_DAMAGE_LOSS_FACTOR, 0); + final int damageDice = unitType.getFieldAsInteger(ATTACK2_DMG_DICE, 0); + final int damageSidesPerDie = unitType.getFieldAsInteger(ATTACK2_DMG_SIDES_PER_DIE, 0); + final float damageSpillDistance = unitType.getFieldAsFloat(ATTACK2_DMG_SPILL_DIST, 0); + final float damageSpillRadius = unitType.getFieldAsFloat(ATTACK2_DMG_SPILL_RADIUS, 0); + final int damageUpgradeAmount = unitType.getFieldAsInteger(ATTACK2_DMG_UPGRADE_AMT, 0); + final int maximumNumberOfTargets = unitType.getFieldAsInteger(ATTACK2_TARGET_COUNT, 0); + final float projectileArc = unitType.getFieldAsFloat(ATTACK2_PROJECTILE_ARC, 0); + final String projectileArt = unitType.getFieldAsString(ATTACK2_MISSILE_ART, 0); + final boolean projectileHomingEnabled = unitType + .getFieldAsBoolean(ATTACK2_PROJECTILE_HOMING_ENABLED, 0); + final int projectileSpeed = unitType.getFieldAsInteger(ATTACK2_PROJECTILE_SPEED, 0); + final int range = unitType.getFieldAsInteger(ATTACK2_RANGE, 0); + final float rangeMotionBuffer = unitType.getFieldAsFloat(ATTACK2_RANGE_MOTION_BUFFER, 0); + final boolean showUI = unitType.getFieldAsBoolean(ATTACK2_SHOW_UI, 0); + final EnumSet targetsAllowed = CTargetType + .parseTargetTypeSet(unitType.getFieldAsString(ATTACK2_TARGETS_ALLOWED, 0)); + final String weaponSound = unitType.getFieldAsString(ATTACK2_WEAPON_SOUND, 0); + final CWeaponType weaponType = CWeaponType + .parseWeaponType(unitType.getFieldAsString(ATTACK2_WEAPON_TYPE, 0)); + attacks.add(createAttack(animationBackswingPoint, animationDamagePoint, areaOfEffectFullDamage, + areaOfEffectMediumDamage, areaOfEffectSmallDamage, areaOfEffectTargets, attackType, + cooldownTime, damageBase, damageFactorMedium, damageFactorSmall, damageLossFactor, + damageDice, damageSidesPerDie, damageSpillDistance, damageSpillRadius, damageUpgradeAmount, + maximumNumberOfTargets, projectileArc, projectileArt, projectileHomingEnabled, + projectileSpeed, range, rangeMotionBuffer, showUI, targetsAllowed, weaponSound, + weaponType)); + } + catch (final Exception exc) { + System.err.println("Attack 2 failed to parse with: " + exc.getClass() + ":" + exc.getMessage()); + } + } + final int deathType = unitType.getFieldAsInteger(DEATH_TYPE, 0); + final boolean raise = (deathType & 0x1) != 0; + final boolean decay = (deathType & 0x2) != 0; + final String armorType = unitType.getFieldAsString(ARMOR_TYPE, 0); + final float impactZ = unitType.getFieldAsFloat(PROJECTILE_IMPACT_Z, 0); + final CDefenseType defenseType = CDefenseType.parseDefenseType(unitType.getFieldAsString(DEFENSE_TYPE, 0)); + final float deathTime = unitType.getFieldAsFloat(DEATH_TIME, 0); + unitTypeInstance = new CUnitType(unitName, isBldg, movementType, moveHeight, collisionSize, classifications, + attacks, armorType, raise, decay, defenseType, impactZ, buildingPathingPixelMap, deathTime, + targetedAs, acquisitionRange, minimumAttackRange); + this.unitIdToUnitType.put(typeId, unitTypeInstance); + } + return unitTypeInstance; + } - public float getPropulsionWindow(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(PROPULSION_WINDOW, 0); - } + private CUnitAttack createAttack(final float animationBackswingPoint, final float animationDamagePoint, + final int areaOfEffectFullDamage, final int areaOfEffectMediumDamage, final int areaOfEffectSmallDamage, + final EnumSet areaOfEffectTargets, final CAttackType attackType, final float cooldownTime, + final int damageBase, final float damageFactorMedium, final float damageFactorSmall, + final float damageLossFactor, final int damageDice, final int damageSidesPerDie, + final float damageSpillDistance, final float damageSpillRadius, final int damageUpgradeAmount, + final int maximumNumberOfTargets, final float projectileArc, final String projectileArt, + final boolean projectileHomingEnabled, final int projectileSpeed, final int range, + final float rangeMotionBuffer, final boolean showUI, final EnumSet targetsAllowed, + final String weaponSound, final CWeaponType weaponType) { + final CUnitAttack attack; + switch (weaponType) { + case MISSILE: + attack = new CUnitAttackMissile(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, + damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, + targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, projectileHomingEnabled, + projectileSpeed); + break; + case MBOUNCE: + attack = new CUnitAttackMissileBounce(animationBackswingPoint, animationDamagePoint, attackType, + cooldownTime, damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, + rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, + projectileHomingEnabled, projectileSpeed, damageLossFactor, maximumNumberOfTargets, + areaOfEffectFullDamage, areaOfEffectTargets); + break; + case MSPLASH: + case ARTILLERY: + attack = new CUnitAttackMissileSplash(animationBackswingPoint, animationDamagePoint, attackType, + cooldownTime, damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, + rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, + projectileHomingEnabled, projectileSpeed, areaOfEffectFullDamage, areaOfEffectMediumDamage, + areaOfEffectSmallDamage, areaOfEffectTargets, damageFactorMedium, damageFactorSmall); + break; + case MLINE: + case ALINE: + attack = new CUnitAttackMissileLine(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, + damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, + targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, projectileHomingEnabled, + projectileSpeed, damageSpillDistance, damageSpillRadius); + break; + case INSTANT: + attack = new CUnitAttackInstant(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, + damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, + targetsAllowed, weaponSound, weaponType, projectileArt); + break; + default: + case NORMAL: + attack = new CUnitAttackNormal(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, + damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, + targetsAllowed, weaponSound, weaponType); + break; + } + return attack; + } - public float getTurnRate(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(TURN_RATE, 0); - } + public float getPropulsionWindow(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(PROPULSION_WINDOW, 0); + } - public boolean isBuilding(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsBoolean(IS_BLDG, 0); - } + public float getTurnRate(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(TURN_RATE, 0); + } - public String getName(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getName(); - } + public boolean isBuilding(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsBoolean(IS_BLDG, 0); + } - public int getA1MinDamage(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_BASE, 0) - + this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_DICE, 0); - } + public String getName(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getName(); + } - public int getA1MaxDamage(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_BASE, 0) - + (this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_DICE, 0) - * this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_SIDES_PER_DIE, 0)); - } + public int getA1MinDamage(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_BASE, 0) + + this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_DICE, 0); + } - public int getA2MinDamage(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_BASE, 0) - + this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_DICE, 0); - } + public int getA1MaxDamage(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_BASE, 0) + + (this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_DICE, 0) + * this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_SIDES_PER_DIE, 0)); + } - public int getA2MaxDamage(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_BASE, 0) - + (this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_DICE, 0) - * this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_SIDES_PER_DIE, 0)); - } + public int getA2MinDamage(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_BASE, 0) + + this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_DICE, 0); + } - public int getDefense(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsInteger(DEFENSE, 0); - } + public int getA2MaxDamage(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_BASE, 0) + + (this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_DICE, 0) + * this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_SIDES_PER_DIE, 0)); + } - public int getA1ProjectileSpeed(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_PROJECTILE_SPEED, 0); - } + public int getDefense(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(DEFENSE, 0); + } - public float getA1ProjectileArc(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK1_PROJECTILE_ARC, 0); - } + public int getA1ProjectileSpeed(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_PROJECTILE_SPEED, 0); + } - public int getA2ProjectileSpeed(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_PROJECTILE_SPEED, 0); - } + public float getA1ProjectileArc(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK1_PROJECTILE_ARC, 0); + } - public float getA2ProjectileArc(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK2_PROJECTILE_ARC, 0); - } + public int getA2ProjectileSpeed(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_PROJECTILE_SPEED, 0); + } - public String getA1MissileArt(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsString(ATTACK1_MISSILE_ART, 0); - } + public float getA2ProjectileArc(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK2_PROJECTILE_ARC, 0); + } - public String getA2MissileArt(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsString(ATTACK2_MISSILE_ART, 0); - } + public String getA1MissileArt(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsString(ATTACK1_MISSILE_ART, 0); + } - public float getA1Cooldown(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK1_COOLDOWN, 0); - } + public String getA2MissileArt(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsString(ATTACK2_MISSILE_ART, 0); + } - public float getA2Cooldown(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK2_COOLDOWN, 0); - } + public float getA1Cooldown(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK1_COOLDOWN, 0); + } - public float getProjectileLaunchX(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(PROJECTILE_LAUNCH_X, 0); - } + public float getA2Cooldown(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK2_COOLDOWN, 0); + } - public float getProjectileLaunchY(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(PROJECTILE_LAUNCH_Y, 0); - } + public float getProjectileLaunchX(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(PROJECTILE_LAUNCH_X, 0); + } - public float getProjectileLaunchZ(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(PROJECTILE_LAUNCH_Z, 0); - } + public float getProjectileLaunchY(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(PROJECTILE_LAUNCH_Y, 0); + } + + public float getProjectileLaunchZ(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(PROJECTILE_LAUNCH_Z, 0); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CBehavior.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CBehavior.java deleted file mode 100644 index a5643e0..0000000 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CBehavior.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders; - -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; - -public interface CBehavior { - /** - * Executes one step of game simulation of the current order, returning true if - * the order has completed. Many orders may wrap the move order and spend a - * number of simulation steps moving to get within range of the target point - * before completing. - * - * @return - */ - boolean update(CSimulation game); - - /** - * Gets the Order ID of the order, useful for determining which icon to - * highlight on the unit's command card. - * - * @return - */ - int getOrderId(); -} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CBehaviorAttack.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CBehaviorAttack.java deleted file mode 100644 index c444bfb..0000000 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CBehaviorAttack.java +++ /dev/null @@ -1,177 +0,0 @@ -package com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders; - -import com.etheller.warsmash.util.WarsmashConstants; -import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; -import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; - -public class CBehaviorAttack implements CBehavior { - private final CUnit unit; - private final int orderId; - private boolean wasWithinPropWindow = false; - private final CUnitAttack unitAttack; - private final CWidget target; - private int damagePointLaunchTime; - private int backSwingTime; - private CBehavior moveOrder; - private int thisOrderCooldownEndTime; - private boolean wasInRange = false; - - public CBehaviorAttack(final CUnit unit, final CUnitAttack unitAttack, final int orderId, final CWidget target) { - this.unit = unit; - this.unitAttack = unitAttack; - this.orderId = orderId; - this.target = target; - createMoveOrder(unit, target); - } - - private void createMoveOrder(final CUnit unit, final CWidget target) { - if (!unit.isMovementDisabled()) { // TODO: Check mobility instead - if ((target instanceof CUnit) && !(((CUnit) target).getUnitType().isBuilding())) { - this.moveOrder = new CBehaviorMove(unit, this.orderId, (CUnit) target); - } - else { - this.moveOrder = new CBehaviorMove(unit, this.orderId, target.getX(), target.getY()); - } - } - else { - this.moveOrder = null; - } - } - - @Override - public boolean update(final CSimulation simulation) { - if (this.target.isDead() - || !this.target.canBeTargetedBy(simulation, this.unit, this.unitAttack.getTargetsAllowed())) { - return true; - } - float range = this.unitAttack.getRange(); - if ((this.target instanceof CUnit) && (((CUnit) this.target).getCurrentOrder() instanceof CBehaviorMove) - && (this.damagePointLaunchTime != 0 /* - * only apply range motion buffer if they were already in range and - * attacked - */)) { - range += this.unitAttack.getRangeMotionBuffer(); - } - if (!this.unit.canReach(this.target, range) - || (this.unit.distance(this.target) < this.unit.getUnitType().getMinimumAttackRange())) { - if (this.moveOrder == null) { - return true; - } - if (this.moveOrder.update(simulation)) { - return true; // we just cant reach them - } - this.wasInRange = false; - this.damagePointLaunchTime = 0; - this.thisOrderCooldownEndTime = 0; - return false; - } - this.wasInRange = true; - if (!this.unit.isMovementDisabled()) { - final float prevX = this.unit.getX(); - final float prevY = this.unit.getY(); - final float deltaY = this.target.getY() - prevY; - final float deltaX = this.target.getX() - prevX; - final double goalAngleRad = Math.atan2(deltaY, deltaX); - float goalAngle = (float) Math.toDegrees(goalAngleRad); - if (goalAngle < 0) { - goalAngle += 360; - } - float facing = this.unit.getFacing(); - float delta = goalAngle - facing; - final float propulsionWindow = simulation.getGameplayConstants().getAttackHalfAngle(); - final float turnRate = simulation.getUnitData().getTurnRate(this.unit.getTypeId()); - - if (delta < -180) { - delta = 360 + delta; - } - if (delta > 180) { - delta = -360 + delta; - } - final float absDelta = Math.abs(delta); - - if ((absDelta <= 1.0) && (absDelta != 0)) { - this.unit.setFacing(goalAngle); - } - else { - float angleToAdd = Math.signum(delta) * (float) Math.toDegrees(turnRate); - if (absDelta < Math.abs(angleToAdd)) { - angleToAdd = delta; - } - facing += angleToAdd; - this.unit.setFacing(facing); - } - if (absDelta < propulsionWindow) { - this.wasWithinPropWindow = true; - } - else { - // If this happens, the unit is facing the wrong way, and has to turn before - // moving. - this.wasWithinPropWindow = false; - } - } - else { - this.wasWithinPropWindow = true; - } - - final int cooldownEndTime = this.unit.getCooldownEndTime(); - final int currentTurnTick = simulation.getGameTurnTick(); - if (this.wasWithinPropWindow) { - if (this.damagePointLaunchTime != 0) { - if (currentTurnTick >= this.damagePointLaunchTime) { - int minDamage = this.unitAttack.getMinDamage(); - final int maxDamage = Math.max(0, this.unitAttack.getMaxDamage()); - if (minDamage > maxDamage) { - minDamage = maxDamage; - } - final int damage; - if (maxDamage == 0) { - damage = 0; - } - else if (minDamage == maxDamage) { - damage = minDamage; - } - else { - damage = simulation.getSeededRandom().nextInt(maxDamage - minDamage) + minDamage; - } - this.unitAttack.launch(simulation, this.unit, this.target, damage); - this.damagePointLaunchTime = 0; - } - } - else if (currentTurnTick >= cooldownEndTime) { - final float cooldownTime = this.unitAttack.getCooldownTime(); - final float animationBackswingPoint = this.unitAttack.getAnimationBackswingPoint(); - final int a1CooldownSteps = (int) (cooldownTime / WarsmashConstants.SIMULATION_STEP_TIME); - final int a1BackswingSteps = (int) (animationBackswingPoint / WarsmashConstants.SIMULATION_STEP_TIME); - final int a1DamagePointSteps = (int) (this.unitAttack.getAnimationDamagePoint() - / WarsmashConstants.SIMULATION_STEP_TIME); - this.unit.setCooldownEndTime(currentTurnTick + a1CooldownSteps); - this.thisOrderCooldownEndTime = currentTurnTick + a1CooldownSteps; - this.damagePointLaunchTime = currentTurnTick + a1DamagePointSteps; - this.backSwingTime = currentTurnTick + a1DamagePointSteps + a1BackswingSteps; - this.unit.getUnitAnimationListener().playAnimation(true, PrimaryTag.ATTACK, SequenceUtils.EMPTY, 1.0f, - true); - this.unit.getUnitAnimationListener().queueAnimation(PrimaryTag.STAND, SequenceUtils.READY, false); - } - else if ((currentTurnTick >= this.thisOrderCooldownEndTime)) { - this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.STAND, SequenceUtils.READY, 1.0f, - false); - } - } - else { - this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.STAND, SequenceUtils.READY, 1.0f, - false); - } - - return false; - } - - @Override - public int getOrderId() { - return this.orderId; - } - -} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CBehaviorPatrol.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CBehaviorPatrol.java deleted file mode 100644 index c1891d2..0000000 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CBehaviorPatrol.java +++ /dev/null @@ -1,96 +0,0 @@ -package com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders; - -import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; -import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; - -public class CBehaviorPatrol implements CBehavior { - private final CUnit unit; - private final int orderId; - private final CWidget target; - private CBehavior moveOrder; - - public CBehaviorPatrol(final CUnit unit, final int orderId, final CUnit target) { - this.unit = unit; - this.orderId = orderId; - this.target = target; - createMoveOrder(unit, target); - } - - private void createMoveOrder(final CUnit unit, final CUnit target) { - if (!unit.isMovementDisabled()) { // TODO: Check mobility instead - if ((target instanceof CUnit) && !(target.getUnitType().isBuilding())) { - this.moveOrder = new CBehaviorMove(unit, this.orderId, target); - } - else { - this.moveOrder = new CBehaviorMove(unit, this.orderId, target.getX(), target.getY()); - } - } - else { - this.moveOrder = null; - } - } - - @Override - public boolean update(final CSimulation simulation) { - if (this.target.isDead()) { - return true; - } - final float range = this.unit.getAcquisitionRange(); - if (!this.unit.canReach(this.target, range)) { - if (this.moveOrder == null) { - return true; - } - if (this.moveOrder.update(simulation)) { - return true; // we just cant reach them - } - return false; - } - if (!this.unit.isMovementDisabled()) { - final float prevX = this.unit.getX(); - final float prevY = this.unit.getY(); - final float deltaY = this.target.getY() - prevY; - final float deltaX = this.target.getX() - prevX; - final double goalAngleRad = Math.atan2(deltaY, deltaX); - float goalAngle = (float) Math.toDegrees(goalAngleRad); - if (goalAngle < 0) { - goalAngle += 360; - } - float facing = this.unit.getFacing(); - float delta = goalAngle - facing; - final float turnRate = simulation.getUnitData().getTurnRate(this.unit.getTypeId()); - - if (delta < -180) { - delta = 360 + delta; - } - if (delta > 180) { - delta = -360 + delta; - } - final float absDelta = Math.abs(delta); - - if ((absDelta <= 1.0) && (absDelta != 0)) { - this.unit.setFacing(goalAngle); - } - else { - float angleToAdd = Math.signum(delta) * (float) Math.toDegrees(turnRate); - if (absDelta < Math.abs(angleToAdd)) { - angleToAdd = delta; - } - facing += angleToAdd; - this.unit.setFacing(facing); - } - } - else { - } - this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.STAND, SequenceUtils.EMPTY, 1.0f, false); - return false; - } - - @Override - public int getOrderId() { - return this.orderId; - } - -} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CBehaviorStop.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CBehaviorStop.java deleted file mode 100644 index f850e63..0000000 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CBehaviorStop.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders; - -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; - -public class CBehaviorStop implements CBehavior { - private final int orderId; - - public CBehaviorStop(final int orderId) { - this.orderId = orderId; - } - - @Override - public boolean update(final CSimulation game) { - return true; - } - - @Override - public int getOrderId() { - return this.orderId; - } - -} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrder.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrder.java new file mode 100644 index 0000000..2755117 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrder.java @@ -0,0 +1,18 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.StringMsgAbilityActivationReceiver; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.StringMsgTargetCheckReceiver; + +public interface COrder { + int getAbilityHandleId(); + + int getOrderId(); + + CBehavior begin(final CSimulation game, CUnit caster); + + final StringMsgTargetCheckReceiver targetCheckReceiver = new StringMsgTargetCheckReceiver<>(); + final StringMsgAbilityActivationReceiver abilityActivationReceiver = new StringMsgAbilityActivationReceiver(); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderNoTarget.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderNoTarget.java new file mode 100644 index 0000000..299686f --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderNoTarget.java @@ -0,0 +1,33 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; + +public class COrderNoTarget implements COrder { + private final int abilityHandleId; + private final int orderId; + + public COrderNoTarget(final int abilityHandleId, final int orderId) { + this.abilityHandleId = abilityHandleId; + this.orderId = orderId; + } + + @Override + public int getAbilityHandleId() { + return this.abilityHandleId; + } + + @Override + public int getOrderId() { + return this.orderId; + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster) { + final CAbility ability = game.getAbility(this.abilityHandleId); + return ability.beginNoTarget(game, caster, this.orderId); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetPoint.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetPoint.java new file mode 100644 index 0000000..462a3aa --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetPoint.java @@ -0,0 +1,57 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders; + +import com.badlogic.gdx.math.Vector2; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.StringMsgTargetCheckReceiver; + +public class COrderTargetPoint implements COrder { + private final int abilityHandleId; + private final int orderId; + private final Vector2 target; + + public COrderTargetPoint(final int abilityHandleId, final int orderId, final Vector2 target) { + this.abilityHandleId = abilityHandleId; + this.orderId = orderId; + this.target = target; + } + + @Override + public int getAbilityHandleId() { + return this.abilityHandleId; + } + + @Override + public int getOrderId() { + return this.orderId; + } + + public Vector2 getTarget() { + return this.target; + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster) { + final CAbility ability = game.getAbility(this.abilityHandleId); + ability.checkCanUse(game, caster, this.orderId, this.abilityActivationReceiver.reset()); + if (this.abilityActivationReceiver.isUseOk()) { + final StringMsgTargetCheckReceiver targetReceiver = (StringMsgTargetCheckReceiver) targetCheckReceiver; + ability.checkCanTarget(game, caster, this.orderId, this.target, targetReceiver); + if (targetReceiver.getTarget() != null) { + return ability.begin(game, caster, this.orderId, this.target); + } + else { + game.getCommandErrorListener().showCommandError(targetReceiver.getMessage()); + return caster.pollNextOrderBehavior(game); + } + } + else { + game.getCommandErrorListener().showCommandError(this.abilityActivationReceiver.getMessage()); + return caster.pollNextOrderBehavior(game); + } + + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetWidget.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetWidget.java index 13ce91a..8b4a1d0 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetWidget.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetWidget.java @@ -1,5 +1,52 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders; -public class COrderTargetWidget { - +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.StringMsgTargetCheckReceiver; + +public class COrderTargetWidget implements COrder { + private final int abilityHandleId; + private final int orderId; + private final int targetHandleId; + + public COrderTargetWidget(final int abilityHandleId, final int orderId, final int targetHandleId) { + this.abilityHandleId = abilityHandleId; + this.orderId = orderId; + this.targetHandleId = targetHandleId; + } + + @Override + public int getAbilityHandleId() { + return this.abilityHandleId; + } + + @Override + public int getOrderId() { + return this.orderId; + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster) { + final CAbility ability = game.getAbility(this.abilityHandleId); + ability.checkCanUse(game, caster, this.orderId, abilityActivationReceiver.reset()); + if (abilityActivationReceiver.isUseOk()) { + final CUnit target = game.getUnit(this.targetHandleId); + final StringMsgTargetCheckReceiver targetReceiver = (StringMsgTargetCheckReceiver) targetCheckReceiver; + ability.checkCanTarget(game, caster, this.orderId, target, targetReceiver); + if (targetReceiver.getTarget() != null) { + return ability.begin(game, caster, this.orderId, targetReceiver.getTarget()); + } + else { + game.getCommandErrorListener().showCommandError(targetReceiver.getMessage()); + return caster.pollNextOrderBehavior(game); + } + } + else { + game.getCommandErrorListener().showCommandError(this.abilityActivationReceiver.getMessage()); + return caster.pollNextOrderBehavior(game); + } + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderExecutor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderExecutor.java index 0ba7d64..c51c6f3 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderExecutor.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderExecutor.java @@ -3,21 +3,14 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.players; import com.badlogic.gdx.math.Vector2; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.StringMsgAbilityActivationReceiver; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.StringMsgTargetCheckReceiver; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.COrderNoTarget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.COrderTargetPoint; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.COrderTargetWidget; import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.CommandErrorListener; public class CPlayerUnitOrderExecutor implements CPlayerUnitOrderListener { private final CSimulation game; private final CommandErrorListener errorListener; - private final StringMsgTargetCheckReceiver targetCheckReceiver = new StringMsgTargetCheckReceiver<>(); - private final StringMsgAbilityActivationReceiver abilityActivationReceiver = new StringMsgAbilityActivationReceiver(); - - private StringMsgTargetCheckReceiver targetCheckReceiver() { - return (StringMsgTargetCheckReceiver) this.targetCheckReceiver.reset(); - } public CPlayerUnitOrderExecutor(final CSimulation game, final CommandErrorListener errorListener) { this.game = game; @@ -25,77 +18,24 @@ public class CPlayerUnitOrderExecutor implements CPlayerUnitOrderListener { } @Override - public boolean issueTargetOrder(final int unitHandleId, final int abilityHandleId, final int orderId, + public void issueTargetOrder(final int unitHandleId, final int abilityHandleId, final int orderId, final int targetHandleId, final boolean queue) { final CUnit unit = this.game.getUnit(unitHandleId); - final CAbility ability = this.game.getAbility(abilityHandleId); - ability.checkCanUse(this.game, unit, orderId, this.abilityActivationReceiver.reset()); - if (this.abilityActivationReceiver.isUseOk()) { - final CUnit target = this.game.getUnit(targetHandleId); - final StringMsgTargetCheckReceiver targetReceiver = this.targetCheckReceiver(); - ability.checkCanTarget(this.game, unit, orderId, target, targetReceiver); - if (targetReceiver.getTarget() != null) { - ability.onOrder(this.game, unit, orderId, target, queue); - return true; - } - else { - this.errorListener.showCommandError(targetReceiver.getMessage()); - return false; - } - } - else { - this.errorListener.showCommandError(this.abilityActivationReceiver.getMessage()); - return false; - } + unit.order(this.game, new COrderTargetWidget(abilityHandleId, orderId, targetHandleId), queue); } @Override - public boolean issuePointOrder(final int unitHandleId, final int abilityHandleId, final int orderId, final float x, + public void issuePointOrder(final int unitHandleId, final int abilityHandleId, final int orderId, final float x, final float y, final boolean queue) { final CUnit unit = this.game.getUnit(unitHandleId); - final CAbility ability = this.game.getAbility(abilityHandleId); - ability.checkCanUse(this.game, unit, orderId, this.abilityActivationReceiver.reset()); - if (this.abilityActivationReceiver.isUseOk()) { - final Vector2 target = new Vector2(x, y); - final StringMsgTargetCheckReceiver targetReceiver = this.targetCheckReceiver(); - ability.checkCanTarget(this.game, unit, orderId, target, targetReceiver); - if (targetReceiver.getTarget() != null) { - ability.onOrder(this.game, unit, orderId, target, queue); - return true; - } - else { - this.errorListener.showCommandError(targetReceiver.getMessage()); - return false; - } - } - else { - this.errorListener.showCommandError(this.abilityActivationReceiver.getMessage()); - return false; - } + unit.order(this.game, new COrderTargetPoint(abilityHandleId, orderId, new Vector2(x, y)), queue); } @Override - public boolean issueImmediateOrder(final int unitHandleId, final int abilityHandleId, final int orderId, + public void issueImmediateOrder(final int unitHandleId, final int abilityHandleId, final int orderId, final boolean queue) { final CUnit unit = this.game.getUnit(unitHandleId); - final CAbility ability = this.game.getAbility(abilityHandleId); - ability.checkCanUse(this.game, unit, orderId, this.abilityActivationReceiver.reset()); - if (this.abilityActivationReceiver.isUseOk()) { - final StringMsgTargetCheckReceiver targetReceiver = this.targetCheckReceiver(); - ability.checkCanTargetNoTarget(this.game, unit, orderId, targetReceiver); - if (targetReceiver.getMessage() == null) { - ability.onOrderNoTarget(this.game, unit, orderId, queue); - return true; - } - else { - this.errorListener.showCommandError(targetReceiver.getMessage()); - return false; - } - } - else { - this.errorListener.showCommandError(this.abilityActivationReceiver.getMessage()); - return false; - } + unit.order(this.game, new COrderNoTarget(abilityHandleId, orderId), queue); } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderListener.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderListener.java index a0d420a..e9bf4cd 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderListener.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderListener.java @@ -1,12 +1,12 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.players; public interface CPlayerUnitOrderListener { - boolean issueTargetOrder(int unitHandleId, int abilityHandleId, int orderId, int targetHandleId, boolean queue); + void issueTargetOrder(int unitHandleId, int abilityHandleId, int orderId, int targetHandleId, boolean queue); - boolean issuePointOrder(int unitHandleId, int abilityHandleId, int orderId, float x, float y, boolean queue); + void issuePointOrder(int unitHandleId, int abilityHandleId, int orderId, float x, float y, boolean queue); // Below: used for "DROP ITEM AT POINT" ???? // boolean issueTargetAndPointOrder(int unitHandleId, int orderId, int targetHandleId, float x, float y); - boolean issueImmediateOrder(int unitHandleId, int abilityHandleId, int orderId, boolean queue); + void issueImmediateOrder(int unitHandleId, int abilityHandleId, int orderId, boolean queue); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java index e0f0fb5..0af1413 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java @@ -23,6 +23,7 @@ public class CommandCardIcon extends AbstractRenderableFrame { private CommandButton commandButton; private int abilityHandleId; private int orderId; + private int autoCastOrderId; private final CommandCardCommandListener commandCardCommandListener; public CommandCardIcon(final String name, final UIFrame parent, @@ -45,7 +46,7 @@ public class CommandCardIcon extends AbstractRenderableFrame { this.iconFrame.setVisible(false); this.activeHighlightFrame.setVisible(false); this.cooldownFrame.setVisible(false); - this.autocastFrame.setVisible(false); + this.autocastFrame.setSequence(PrimaryTag.DEATH); } else { if (commandButton.isEnabled()) { @@ -64,19 +65,24 @@ public class CommandCardIcon extends AbstractRenderableFrame { this.cooldownFrame .setFrameByRatio(1 - (commandButton.getCooldownRemaining() / commandButton.getCooldown())); } - this.autocastFrame.setVisible(commandButton.isAutoCastActive()); } } public void setCommandButtonData(final Texture texture, final int abilityHandleId, final int orderId, - final boolean active) { + final int autoCastOrderId, final boolean active, final boolean autoCastActive) { this.iconFrame.setVisible(true); this.activeHighlightFrame.setVisible(active); this.cooldownFrame.setVisible(false); - this.autocastFrame.setVisible(false); + if (autoCastActive) { + this.autocastFrame.setSequence(PrimaryTag.STAND); + } + else { + this.autocastFrame.setSequence(-1); + } this.iconFrame.setTexture(texture); this.abilityHandleId = abilityHandleId; this.orderId = orderId; + this.autoCastOrderId = autoCastOrderId; } @Override @@ -102,7 +108,7 @@ public class CommandCardIcon extends AbstractRenderableFrame { this.commandCardCommandListener.startUsingAbility(this.abilityHandleId, this.orderId); } else if (button == Input.Buttons.RIGHT) { - this.commandCardCommandListener.toggleAutoCastAbility(this.abilityHandleId); + this.commandCardCommandListener.startUsingAbility(this.abilityHandleId, this.autoCastOrderId); } return this; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java index 0deff81..5c812fa 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java @@ -13,11 +13,10 @@ import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.GlyphLayout; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator; -import com.badlogic.gdx.graphics.glutils.ShapeRenderer; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.math.Vector3; -import com.badlogic.gdx.utils.viewport.Viewport; +import com.badlogic.gdx.utils.viewport.ExtendViewport; import com.etheller.warsmash.datasources.DataSource; import com.etheller.warsmash.parsers.fdf.GameUI; import com.etheller.warsmash.parsers.fdf.datamodel.AnchorDefinition; @@ -77,7 +76,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma private static final Vector3 clickLocationTemp = new Vector3(); private static final Vector2 clickLocationTemp2 = new Vector2(); private final DataSource dataSource; - private final Viewport uiViewport; + private final ExtendViewport uiViewport; private final FreeTypeFontGenerator fontGenerator; private final Scene uiScene; private final Scene portraitScene; @@ -137,10 +136,15 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma private int selectedSoundCount = 0; private final ActiveCommandUnitTargetFilter activeCommandUnitTargetFilter; - public MeleeUI(final DataSource dataSource, final Viewport uiViewport, final FreeTypeFontGenerator fontGenerator, - final Scene uiScene, final Scene portraitScene, final CameraPreset[] cameraPresets, - final CameraRates cameraRates, final War3MapViewer war3MapViewer, final RootFrameListener rootFrameListener, - final CPlayerUnitOrderListener unitOrderListener) { + // TODO these corrections are used for old hardcoded UI stuff, we should + // probably remove them later + private final float widthRatioCorrection; + private final float heightRatioCorrection; + + public MeleeUI(final DataSource dataSource, final ExtendViewport uiViewport, + final FreeTypeFontGenerator fontGenerator, final Scene uiScene, final Scene portraitScene, + final CameraPreset[] cameraPresets, final CameraRates cameraRates, final War3MapViewer war3MapViewer, + final RootFrameListener rootFrameListener, final CPlayerUnitOrderListener unitOrderListener) { this.dataSource = dataSource; this.uiViewport = uiViewport; this.fontGenerator = fontGenerator; @@ -161,11 +165,15 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.activeButtonTexture = ImageUtils.getBLPTexture(war3MapViewer.mapMpq, "UI\\Widgets\\Console\\Human\\CommandButton\\human-activebutton.blp"); this.activeCommandUnitTargetFilter = new ActiveCommandUnitTargetFilter(); + this.widthRatioCorrection = this.uiViewport.getMinWorldWidth() / 1600f; + this.heightRatioCorrection = this.uiViewport.getMinWorldHeight() / 1200f; } private MeleeUIMinimap createMinimap(final War3MapViewer war3MapViewer) { - final Rectangle minimapDisplayArea = new Rectangle(18.75f, 13.75f, 278.75f, 276.25f); + final Rectangle minimapDisplayArea = new Rectangle(18.75f * this.widthRatioCorrection, + 13.75f * this.heightRatioCorrection, 278.75f * this.widthRatioCorrection, + 276.25f * this.heightRatioCorrection); Texture minimapTexture = null; if (war3MapViewer.dataSource.has("war3mapMap.tga")) { try { @@ -396,11 +404,6 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.errorMessageFrame.setText(message); } - @Override - public void toggleAutoCastAbility(final int abilityHandleId) { - - } - public void update(final float deltaTime) { this.portrait.update(); @@ -468,8 +471,6 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.cameraManager.updateCamera(); } - private final ShapeRenderer shapeRenderer = new ShapeRenderer(); - public void render(final SpriteBatch batch, final BitmapFont font20, final GlyphLayout glyphLayout) { this.rootFrame.render(batch, font20, glyphLayout); if (this.selectedUnit != null) { @@ -658,10 +659,11 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma @Override public void commandButton(final int buttonPositionX, final int buttonPositionY, final Texture icon, - final int abilityHandleId, final int orderId, final boolean active) { + final int abilityHandleId, final int orderId, final int autoCastId, final boolean active, + final boolean autoCastActive) { final int x = Math.max(0, Math.min(COMMAND_CARD_WIDTH - 1, buttonPositionX)); final int y = Math.max(0, Math.min(COMMAND_CARD_HEIGHT - 1, buttonPositionY)); - this.commandCard[y][x].setCommandButtonData(icon, abilityHandleId, orderId, active); + this.commandCard[y][x].setCommandButtonData(icon, abilityHandleId, orderId, autoCastId, active, autoCastActive); } public void resize(final Rectangle viewport) { @@ -670,10 +672,10 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } public void positionPortrait() { - this.projectionTemp1.x = 422; - this.projectionTemp1.y = 57; - this.projectionTemp2.x = 422 + 167; - this.projectionTemp2.y = 57 + 170; + this.projectionTemp1.x = 422 * this.widthRatioCorrection; + this.projectionTemp1.y = 57 * this.heightRatioCorrection; + this.projectionTemp2.x = (422 + 167) * this.widthRatioCorrection; + this.projectionTemp2.y = (57 + 170) * this.heightRatioCorrection; this.uiViewport.project(this.projectionTemp1); this.uiViewport.project(this.projectionTemp2); @@ -752,7 +754,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } @Override - public void ordersChanged() { + public void ordersChanged(final int abilityHandleId, final int orderId) { clearCommandCard(); this.selectedUnit.populateCommandCard(this.war3MapViewer.simulation, this, this.war3MapViewer.getAbilityDataUI()); @@ -798,20 +800,19 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.activeCommandUnitTargetFilter); final boolean shiftDown = isShiftDown(); if (rayPickUnit != null) { - if (this.unitOrderListener.issueTargetOrder( + this.unitOrderListener.issueTargetOrder( this.activeCommandUnit.getSimulationUnit().getHandleId(), this.activeCommand.getHandleId(), this.activeCommandOrderId, - rayPickUnit.getSimulationUnit().getHandleId(), shiftDown)) { - if (getSelectedUnit().soundset.yesAttack - .playUnitResponse(this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { - portraitTalk(); - } - this.selectedSoundCount = 0; - if (!shiftDown) { - this.activeCommandUnit = null; - this.activeCommand = null; - this.activeCommandOrderId = -1; - } + rayPickUnit.getSimulationUnit().getHandleId(), shiftDown); + if (getSelectedUnit().soundset.yesAttack + .playUnitResponse(this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { + portraitTalk(); + } + this.selectedSoundCount = 0; + if (!shiftDown) { + this.activeCommandUnit = null; + this.activeCommand = null; + this.activeCommandOrderId = -1; } } else { @@ -829,21 +830,21 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma else { this.war3MapViewer.showConfirmation(clickLocationTemp, 0, 1, 0); } - if (this.unitOrderListener.issuePointOrder( + this.unitOrderListener.issuePointOrder( this.activeCommandUnit.getSimulationUnit().getHandleId(), this.activeCommand.getHandleId(), this.activeCommandOrderId, clickLocationTemp2.x, - clickLocationTemp2.y, shiftDown)) { - if (getSelectedUnit().soundset.yes.playUnitResponse( - this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { - portraitTalk(); - } - this.selectedSoundCount = 0; - if (!shiftDown) { - this.activeCommandUnit = null; - this.activeCommand = null; - this.activeCommandOrderId = -1; - } + clickLocationTemp2.y, shiftDown); + if (getSelectedUnit().soundset.yes + .playUnitResponse(this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { + portraitTalk(); } + this.selectedSoundCount = 0; + if (!shiftDown) { + this.activeCommandUnit = null; + this.activeCommand = null; + this.activeCommandOrderId = -1; + } + } } } @@ -975,4 +976,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } return false; } + + public float getHeightRatioCorrection() { + return this.heightRatioCorrection; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/CommandCardCommandListener.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/CommandCardCommandListener.java index 2f0ea28..35591b1 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/CommandCardCommandListener.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/CommandCardCommandListener.java @@ -2,6 +2,4 @@ package com.etheller.warsmash.viewer5.handlers.w3x.ui.command; public interface CommandCardCommandListener { void startUsingAbility(int abilityHandleId, int orderId); - - void toggleAutoCastAbility(int abilityHandleId); } From fa94450d541ea8381503a04375b9e6ebf9719ddf Mon Sep 17 00:00:00 2001 From: Retera Date: Thu, 29 Oct 2020 08:39:13 -0400 Subject: [PATCH 057/116] Update autocast art --- .../handlers/w3x/ui/CommandCardIcon.java | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java index 0af1413..4de1c7a 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java @@ -24,6 +24,7 @@ public class CommandCardIcon extends AbstractRenderableFrame { private int abilityHandleId; private int orderId; private int autoCastOrderId; + private boolean autoCastActive; private final CommandCardCommandListener commandCardCommandListener; public CommandCardIcon(final String name, final UIFrame parent, @@ -46,7 +47,7 @@ public class CommandCardIcon extends AbstractRenderableFrame { this.iconFrame.setVisible(false); this.activeHighlightFrame.setVisible(false); this.cooldownFrame.setVisible(false); - this.autocastFrame.setSequence(PrimaryTag.DEATH); + this.autocastFrame.setVisible(false); } else { if (commandButton.isEnabled()) { @@ -73,11 +74,17 @@ public class CommandCardIcon extends AbstractRenderableFrame { this.iconFrame.setVisible(true); this.activeHighlightFrame.setVisible(active); this.cooldownFrame.setVisible(false); - if (autoCastActive) { - this.autocastFrame.setSequence(PrimaryTag.STAND); - } - else { - this.autocastFrame.setSequence(-1); + this.autocastFrame.setVisible(autoCastOrderId != 0); + if (autoCastOrderId != 0) { + if (this.autoCastActive != autoCastActive) { + if (autoCastActive) { + this.autocastFrame.setSequence(PrimaryTag.STAND); + } + else { + this.autocastFrame.setSequence(-1); + } + } + this.autoCastActive = autoCastActive; } this.iconFrame.setTexture(texture); this.abilityHandleId = abilityHandleId; From 238015f21df9be052d1e8a928ed2946eb962352c Mon Sep 17 00:00:00 2001 From: Retera Date: Sat, 31 Oct 2020 15:08:00 -0400 Subject: [PATCH 058/116] Highlight proper movement icons, improve sequence filter --- .../w3x/SecondaryTagSequenceComparator.java | 41 + .../viewer5/handlers/w3x/SequenceUtils.java | 388 +-- .../viewer5/handlers/w3x/War3MapViewer.java | 2501 ++++++++--------- .../w3x/environment/TerrainShaders.java | 2 +- .../handlers/w3x/rendersim/RenderUnit.java | 3 - .../CommandCardPopulatingAbilityVisitor.java | 124 +- .../handlers/w3x/simulation/CUnit.java | 17 +- .../simulation/abilities/CAbilityAttack.java | 4 +- .../abilities/CAbilityColdArrows.java | 2 +- .../simulation/abilities/CAbilityMove.java | 4 +- .../w3x/simulation/behaviors/CBehavior.java | 2 + .../simulation/behaviors/CBehaviorAttack.java | 10 +- .../simulation/behaviors/CBehaviorFollow.java | 10 +- .../simulation/behaviors/CBehaviorMove.java | 39 +- .../simulation/behaviors/CBehaviorPatrol.java | 6 + .../simulation/behaviors/CBehaviorStop.java | 7 + .../w3x/simulation/data/CUnitData.java | 748 ++--- 17 files changed, 1998 insertions(+), 1910 deletions(-) create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/SecondaryTagSequenceComparator.java diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SecondaryTagSequenceComparator.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SecondaryTagSequenceComparator.java new file mode 100644 index 0000000..1a5ab6b --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SecondaryTagSequenceComparator.java @@ -0,0 +1,41 @@ +package com.etheller.warsmash.viewer5.handlers.w3x; + +import java.util.Comparator; +import java.util.EnumSet; + +public class SecondaryTagSequenceComparator implements Comparator { + private final StandSequenceComparator standSequenceComparator; + private EnumSet ignoredTags; + + public SecondaryTagSequenceComparator(StandSequenceComparator standSequenceComparator) { + this.standSequenceComparator = standSequenceComparator; + } + + public SecondaryTagSequenceComparator reset(EnumSet ignoredTags) { + this.ignoredTags = ignoredTags; + return this; + } + + @Override + public int compare(final IndexedSequence a, final IndexedSequence b) { + EnumSet secondaryTagsA = a.sequence.getSecondaryTags(); + EnumSet secondaryTagsB = b.sequence.getSecondaryTags(); + int secondaryTagsAOrdinal = getTagsOrdinal(secondaryTagsA, ignoredTags); + int secondaryTagsBOrdinal = getTagsOrdinal(secondaryTagsB, ignoredTags); + if (secondaryTagsAOrdinal != secondaryTagsBOrdinal) { + return secondaryTagsBOrdinal - secondaryTagsAOrdinal; + } + return standSequenceComparator.compare(a, b); + } + + public static int getTagsOrdinal(EnumSet secondaryTagsA, EnumSet ignoredTags) { + int secondaryTagsBOrdinal = Integer.MAX_VALUE; + for (AnimationTokens.SecondaryTag secondaryTag : secondaryTagsA) { + if (secondaryTag.ordinal() < secondaryTagsBOrdinal && !ignoredTags.contains(secondaryTag)) { + secondaryTagsBOrdinal = secondaryTag.ordinal(); + } + } + return secondaryTagsBOrdinal; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SequenceUtils.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SequenceUtils.java index eb727b8..cfd9e99 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SequenceUtils.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SequenceUtils.java @@ -1,6 +1,7 @@ package com.etheller.warsmash.viewer5.handlers.w3x; import java.util.ArrayList; +import java.util.Comparator; import java.util.EnumSet; import java.util.List; @@ -11,229 +12,244 @@ import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.SecondaryTag; public class SequenceUtils { - public static final EnumSet EMPTY = EnumSet.noneOf(SecondaryTag.class); - public static final EnumSet READY = EnumSet.of(SecondaryTag.READY); - public static final EnumSet FLESH = EnumSet.of(SecondaryTag.FLESH); - public static final EnumSet BONE = EnumSet.of(SecondaryTag.BONE); + public static final EnumSet EMPTY = EnumSet.noneOf(SecondaryTag.class); + public static final EnumSet READY = EnumSet.of(SecondaryTag.READY); + public static final EnumSet FLESH = EnumSet.of(SecondaryTag.FLESH); + public static final EnumSet BONE = EnumSet.of(SecondaryTag.BONE); - private static final StandSequenceComparator STAND_SEQUENCE_COMPARATOR = new StandSequenceComparator(); + private static final StandSequenceComparator STAND_SEQUENCE_COMPARATOR = new StandSequenceComparator(); + private static final SecondaryTagSequenceComparator SECONDARY_TAG_SEQUENCE_COMPARATOR = new SecondaryTagSequenceComparator(STAND_SEQUENCE_COMPARATOR); - public static List filterSequences(final String type, final List sequences) { - final List filtered = new ArrayList<>(); + public static List filterSequences(final String type, final List sequences) { + final List filtered = new ArrayList<>(); - for (int i = 0, l = sequences.size(); i < l; i++) { - final Sequence sequence = sequences.get(i); - final String name = sequence.getName().split("-")[0].trim().toLowerCase(); + for (int i = 0, l = sequences.size(); i < l; i++) { + final Sequence sequence = sequences.get(i); + final String name = sequence.getName().split("-")[0].trim().toLowerCase(); - if (name.equals(type)) { - filtered.add(new IndexedSequence(sequence, i)); - } - } + if (name.equals(type)) { + filtered.add(new IndexedSequence(sequence, i)); + } + } - return filtered; - } + return filtered; + } - private static List filterSequences(final PrimaryTag type, final EnumSet tags, - final List sequences) { - final List filtered = new ArrayList<>(); + private static List filterSequences(final PrimaryTag type, final EnumSet tags, + final List sequences) { + final List filtered = new ArrayList<>(); - for (int i = 0, l = sequences.size(); i < l; i++) { - final Sequence sequence = sequences.get(i); - if (sequence.getPrimaryTags().contains(type) && sequence.getSecondaryTags().containsAll(tags) - && tags.containsAll(sequence.getSecondaryTags())) { - for (final AnimationTokens.SecondaryTag secondaryTag : sequence.getSecondaryTags()) { - if (tags.contains(secondaryTag)) { - filtered.add(new IndexedSequence(sequence, i)); - } - } - } - } - if (filtered.isEmpty()) { - for (int i = 0, l = sequences.size(); i < l; i++) { - final Sequence sequence = sequences.get(i); - if (sequence.getPrimaryTags().contains(type) && sequence.getSecondaryTags().containsAll(tags) - && tags.containsAll(sequence.getSecondaryTags())) { - filtered.add(new IndexedSequence(sequence, i)); - } - } - } + for (int i = 0, l = sequences.size(); i < l; i++) { + final Sequence sequence = sequences.get(i); + if (sequence.getPrimaryTags().contains(type) && (sequence.getSecondaryTags().containsAll(tags) + && tags.containsAll(sequence.getSecondaryTags()))) { + filtered.add(new IndexedSequence(sequence, i)); + } + } - return filtered; - } + return filtered; + } - public static IndexedSequence selectSequence(final String type, final List sequences) { - final List filtered = filterSequences(type, sequences); + public static IndexedSequence selectSequence(final String type, final List sequences) { + final List filtered = filterSequences(type, sequences); - filtered.sort(STAND_SEQUENCE_COMPARATOR); + filtered.sort(STAND_SEQUENCE_COMPARATOR); - int i = 0; - final double randomRoll = Math.random() * 100; - for (final int l = filtered.size(); i < l; i++) { - final Sequence sequence = filtered.get(i).sequence; - final float rarity = sequence.getRarity(); + int i = 0; + final double randomRoll = Math.random() * 100; + for (final int l = filtered.size(); i < l; i++) { + final Sequence sequence = filtered.get(i).sequence; + final float rarity = sequence.getRarity(); - if (rarity == 0) { - break; - } + if (rarity == 0) { + break; + } - if (randomRoll < (10 - rarity)) { - return filtered.get(i); - } - } + if (randomRoll < (10 - rarity)) { + return filtered.get(i); + } + } - final int sequencesLeft = filtered.size() - i; - final int random = (int) (i + Math.floor(Math.random() * sequencesLeft)); - if (sequencesLeft <= 0) { - return null; // new IndexedSequence(null, 0); - } - final IndexedSequence sequence = filtered.get(random); + final int sequencesLeft = filtered.size() - i; + final int random = (int) (i + Math.floor(Math.random() * sequencesLeft)); + if (sequencesLeft <= 0) { + return null; // new IndexedSequence(null, 0); + } + final IndexedSequence sequence = filtered.get(random); - return sequence; - } + return sequence; + } - public static IndexedSequence selectSequence(final AnimationTokens.PrimaryTag type, - final EnumSet tags, final List sequences, - final boolean allowRarityVariations) { - final List filtered = filterSequences(type, tags, sequences); + public static IndexedSequence selectSequence(final AnimationTokens.PrimaryTag type, + final EnumSet tags, final List sequences, + final boolean allowRarityVariations) { + List filtered = filterSequences(type, tags, sequences); + Comparator sequenceComparator = STAND_SEQUENCE_COMPARATOR; - filtered.sort(STAND_SEQUENCE_COMPARATOR); + if (filtered.isEmpty() && !tags.isEmpty()) { + filtered = filterSequences(type, EMPTY, sequences); + } + if (filtered.isEmpty()) { + // find tags + EnumSet fallbackTags = null; + for (int i = 0, l = sequences.size(); i < l; i++) { + final Sequence sequence = sequences.get(i); + if (sequence.getPrimaryTags().contains(type)) { + if (fallbackTags == null || sequence.getSecondaryTags().size() < fallbackTags.size() + || (sequence.getSecondaryTags().size() == fallbackTags.size() && SecondaryTagSequenceComparator.getTagsOrdinal(sequence.getSecondaryTags(), tags) + > SecondaryTagSequenceComparator.getTagsOrdinal(fallbackTags, tags)) + ) { + fallbackTags = sequence.getSecondaryTags(); + } + } + } + if (fallbackTags != null) { + filtered = filterSequences(type, fallbackTags, sequences); + } + } - int i = 0; - final double randomRoll = Math.random() * 100; - for (final int l = filtered.size(); i < l; i++) { - final Sequence sequence = filtered.get(i).sequence; - final float rarity = sequence.getRarity(); + filtered.sort(sequenceComparator); - if (rarity == 0) { - break; - } + int i = 0; + final double randomRoll = Math.random() * 100; + for (final int l = filtered.size(); i < l; i++) { + final Sequence sequence = filtered.get(i).sequence; + final float rarity = sequence.getRarity(); - if ((randomRoll < (10 - rarity)) && allowRarityVariations) { - return filtered.get(i); - } - } + if (rarity == 0) { + break; + } - final int sequencesLeft = filtered.size() - i; - final int random = (int) (i + Math.floor(Math.random() * sequencesLeft)); - if (sequencesLeft <= 0) { - return null; // new IndexedSequence(null, 0); - } - final IndexedSequence sequence = filtered.get(random); + if ((randomRoll < (10 - rarity)) && allowRarityVariations) { + return filtered.get(i); + } + } - return sequence; - } + final int sequencesLeft = filtered.size() - i; + final int random = (int) (i + Math.floor(Math.random() * sequencesLeft)); + if (sequencesLeft <= 0) { + return null; // new IndexedSequence(null, 0); + } + final IndexedSequence sequence = filtered.get(random); - public static void randomStandSequence(final MdxComplexInstance target) { - final MdxModel model = (MdxModel) target.model; - final List sequences = model.getSequences(); - final IndexedSequence sequence = selectSequence("stand", sequences); + return sequence; + } - if (sequence != null) { - target.setSequence(sequence.index); - } - else { - target.setSequence(0); - } - } + public static void randomStandSequence(final MdxComplexInstance target) { + final MdxModel model = (MdxModel) target.model; + final List sequences = model.getSequences(); + final IndexedSequence sequence = selectSequence("stand", sequences); - public static void randomDeathSequence(final MdxComplexInstance target) { - final MdxModel model = (MdxModel) target.model; - final List sequences = model.getSequences(); - final IndexedSequence sequence = selectSequence("death", sequences); + if (sequence != null) { + target.setSequence(sequence.index); + } else { + target.setSequence(0); + } + } - if (sequence != null) { - target.setSequence(sequence.index); - } - else { - target.setSequence(0); - } - } + public static void randomDeathSequence(final MdxComplexInstance target) { + final MdxModel model = (MdxModel) target.model; + final List sequences = model.getSequences(); + final IndexedSequence sequence = selectSequence("death", sequences); - public static void randomWalkSequence(final MdxComplexInstance target) { - final MdxModel model = (MdxModel) target.model; - final List sequences = model.getSequences(); - final IndexedSequence sequence = selectSequence("walk", sequences); + if (sequence != null) { + target.setSequence(sequence.index); + } else { + target.setSequence(0); + } + } - if (sequence != null) { - target.setSequence(sequence.index); - } - else { - randomStandSequence(target); - } - } + public static void randomWalkSequence(final MdxComplexInstance target) { + final MdxModel model = (MdxModel) target.model; + final List sequences = model.getSequences(); + final IndexedSequence sequence = selectSequence("walk", sequences); - public static void randomBirthSequence(final MdxComplexInstance target) { - final MdxModel model = (MdxModel) target.model; - final List sequences = model.getSequences(); - final IndexedSequence sequence = selectSequence("birth", sequences); + if (sequence != null) { + target.setSequence(sequence.index); + } else { + randomStandSequence(target); + } + } - if (sequence != null) { - target.setSequence(sequence.index); - } - else { - randomStandSequence(target); - } - } + public static void randomBirthSequence(final MdxComplexInstance target) { + final MdxModel model = (MdxModel) target.model; + final List sequences = model.getSequences(); + final IndexedSequence sequence = selectSequence("birth", sequences); - public static void randomPortraitSequence(final MdxComplexInstance target) { - final MdxModel model = (MdxModel) target.model; - final List sequences = model.getSequences(); - final IndexedSequence sequence = selectSequence("portrait", sequences); + if (sequence != null) { + target.setSequence(sequence.index); + } else { + randomStandSequence(target); + } + } - if (sequence != null) { - target.setSequence(sequence.index); - } - else { - randomStandSequence(target); - } - } + public static void randomPortraitSequence(final MdxComplexInstance target) { + final MdxModel model = (MdxModel) target.model; + final List sequences = model.getSequences(); + final IndexedSequence sequence = selectSequence("portrait", sequences); - public static void randomPortraitTalkSequence(final MdxComplexInstance target) { - final MdxModel model = (MdxModel) target.model; - final List sequences = model.getSequences(); - final IndexedSequence sequence = selectSequence("portrait talk", sequences); + if (sequence != null) { + target.setSequence(sequence.index); + } else { + randomStandSequence(target); + } + } - if (sequence != null) { - target.setSequence(sequence.index); - } - else { - randomPortraitSequence(target); - } - } + public static void randomPortraitTalkSequence(final MdxComplexInstance target) { + final MdxModel model = (MdxModel) target.model; + final List sequences = model.getSequences(); + final IndexedSequence sequence = selectSequence("portrait talk", sequences); - public static void randomSequence(final MdxComplexInstance target, final String sequenceName) { - final MdxModel model = (MdxModel) target.model; - final List sequences = model.getSequences(); - final IndexedSequence sequence = selectSequence(sequenceName, sequences); + if (sequence != null) { + target.setSequence(sequence.index); + } else { + randomPortraitSequence(target); + } + } - if (sequence != null) { - target.setSequence(sequence.index); - } - else { - randomStandSequence(target); - } - } + public static void randomSequence(final MdxComplexInstance target, final String sequenceName) { + final MdxModel model = (MdxModel) target.model; + final List sequences = model.getSequences(); + final IndexedSequence sequence = selectSequence(sequenceName, sequences); - public static Sequence randomSequence(final MdxComplexInstance target, final PrimaryTag animationName, - final EnumSet secondaryAnimationTags, final boolean allowRarityVariations) { - final MdxModel model = (MdxModel) target.model; - final List sequences = model.getSequences(); - final IndexedSequence sequence = selectSequence(animationName, secondaryAnimationTags, sequences, - allowRarityVariations); + if (sequence != null) { + target.setSequence(sequence.index); + } else { + randomStandSequence(target); + } + } - if (sequence != null) { - target.setSequence(sequence.index); - return sequence.sequence; - } - else { - if (!secondaryAnimationTags.isEmpty()) { - return randomSequence(target, animationName, EMPTY, allowRarityVariations); - } - return null; - } - } + public static Sequence randomSequence(final MdxComplexInstance target, final PrimaryTag animationName, + final boolean allowRarityVariations) { + final MdxModel model = (MdxModel) target.model; + final List sequences = model.getSequences(); + final IndexedSequence sequence = selectSequence(animationName, null, sequences, + allowRarityVariations); - public static Sequence randomSequence(final MdxComplexInstance target, final PrimaryTag animationName) { - return randomSequence(target, animationName, EMPTY, false); - } + if (sequence != null) { + target.setSequence(sequence.index); + return sequence.sequence; + } else { + return null; + } + } + + public static Sequence randomSequence(final MdxComplexInstance target, final PrimaryTag animationName, + final EnumSet secondaryAnimationTags, final boolean allowRarityVariations) { + final MdxModel model = (MdxModel) target.model; + final List sequences = model.getSequences(); + final IndexedSequence sequence = selectSequence(animationName, secondaryAnimationTags, sequences, + allowRarityVariations); + + if (sequence != null) { + target.setSequence(sequence.index); + return sequence.sequence; + } else { + return null; + } + } + + public static Sequence randomSequence(final MdxComplexInstance target, final PrimaryTag animationName) { + return randomSequence(target, animationName, EMPTY, false); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index 82ab54f..0376097 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -95,1328 +95,1301 @@ import mpq.MPQArchive; import mpq.MPQException; public class War3MapViewer extends ModelViewer { - private static final War3ID UNIT_FILE = War3ID.fromString("umdl"); - private static final War3ID UBER_SPLAT = War3ID.fromString("uubs"); - private static final War3ID UNIT_SHADOW = War3ID.fromString("ushu"); - private static final War3ID UNIT_SHADOW_X = War3ID.fromString("ushx"); - private static final War3ID UNIT_SHADOW_Y = War3ID.fromString("ushy"); - private static final War3ID UNIT_SHADOW_W = War3ID.fromString("ushw"); - private static final War3ID UNIT_SHADOW_H = War3ID.fromString("ushh"); - private static final War3ID BUILDING_SHADOW = War3ID.fromString("ushb"); - public static final War3ID UNIT_SELECT_SCALE = War3ID.fromString("ussc"); - private static final War3ID UNIT_SELECT_HEIGHT = War3ID.fromString("uslz"); - private static final War3ID UNIT_SOUNDSET = War3ID.fromString("usnd"); - private static final War3ID ITEM_FILE = War3ID.fromString("ifil"); - private static final War3ID UNIT_PATHING = War3ID.fromString("upat"); - private static final War3ID DESTRUCTABLE_PATHING = War3ID.fromString("bptx"); - private static final War3ID ELEVATION_SAMPLE_RADIUS = War3ID.fromString("uerd"); - private static final War3ID MAX_PITCH = War3ID.fromString("umxp"); - private static final War3ID MAX_ROLL = War3ID.fromString("umxr"); - private static final War3ID sloc = War3ID.fromString("sloc"); - private static final LoadGenericCallback stringDataCallback = new StringDataCallbackImplementation(); - private static final float[] rayHeap = new float[6]; - public static final Ray gdxRayHeap = new Ray(); - private static final Vector2 mousePosHeap = new Vector2(); - private static final Vector3 normalHeap = new Vector3(); - public static final Vector3 intersectionHeap = new Vector3(); - private static final Rectangle rectangleHeap = new Rectangle(); - public static final StreamDataCallbackImplementation streamDataCallback = new StreamDataCallbackImplementation(); - - public PathSolver wc3PathSolver = PathSolver.DEFAULT; - public SolverParams solverParams = new SolverParams(); - public WorldScene worldScene; - public boolean anyReady; - public MappedData terrainData = new MappedData(); - public MappedData cliffTypesData = new MappedData(); - public MappedData waterData = new MappedData(); - public boolean terrainReady; - public boolean cliffsReady; - public boolean doodadsAndDestructiblesLoaded; - public MappedData doodadsData = new MappedData(); - public MappedData doodadMetaData = new MappedData(); - public MappedData destructableMetaData = new MappedData(); - public List doodads = new ArrayList<>(); - public List terrainDoodads = new ArrayList<>(); - public boolean doodadsReady; - public boolean unitsAndItemsLoaded; - public MappedData unitsData = new MappedData(); - public MappedData unitMetaData = new MappedData(); - public List units = new ArrayList<>(); - public List items = new ArrayList<>(); - public List projectiles = new ArrayList<>(); - public boolean unitsReady; - public War3Map mapMpq; - public PathSolver mapPathSolver = PathSolver.DEFAULT; - - private final DataSource gameDataSource; - - public Terrain terrain; - public int renderPathing = 0; - public int renderLighting = 1; - - public List selModels = new ArrayList<>(); - public List selected = new ArrayList<>(); - private DataTable unitAckSoundsTable; - private DataTable unitCombatSoundsTable; - public DataTable miscData; - private DataTable unitGlobalStrings; - private MdxComplexInstance confirmationInstance; - public MdxComplexInstance dncUnit; - public MdxComplexInstance dncUnitDay; - public MdxComplexInstance dncTerrain; - public MdxComplexInstance dncTarget; - public CSimulation simulation; - private float updateTime; - - // for World Editor, I think - public Vector2[] startLocations = new Vector2[WarsmashConstants.MAX_PLAYERS]; - - private final DynamicShadowManager dynamicShadowManager = new DynamicShadowManager(); - - private final Random seededRandom = new Random(1337L); - - private final Map filePathToPathingMap = new HashMap<>(); - - private final List selectionCircleSizes = new ArrayList<>(); - - private final Map unitToRenderPeer = new HashMap<>(); - private final Map unitIdToTypeData = new HashMap<>(); - private GameUI gameUI; - private Vector3 lightDirection; - - private Quadtree walkableObjectsTree; - private final QuadtreeIntersectorFindsWalkableRenderHeight walkablesIntersector = new QuadtreeIntersectorFindsWalkableRenderHeight(); - private final QuadtreeIntersectorFindsHitPoint walkablesIntersectionFinder = new QuadtreeIntersectorFindsHitPoint(); - private final QuadtreeIntersectorFindsHighestWalkable intersectorFindsHighestWalkable = new QuadtreeIntersectorFindsHighestWalkable(); - - public War3MapViewer(final DataSource dataSource, final CanvasProvider canvas) { - super(dataSource, canvas); - this.gameDataSource = dataSource; - - final WebGL webGL = this.webGL; - - this.addHandler(new MdxHandler()); - - this.wc3PathSolver = PathSolver.DEFAULT; - - this.worldScene = this.addWorldScene(); - - if (!this.dynamicShadowManager.setup(webGL)) { - throw new IllegalStateException("FrameBuffer setup failed"); - } - } - - public void loadSLKs(final WorldEditStrings worldEditStrings) throws IOException { - final GenericResource terrain = this.loadMapGeneric("TerrainArt\\Terrain.slk", FetchDataTypeName.SLK, - stringDataCallback); - final GenericResource cliffTypes = this.loadMapGeneric("TerrainArt\\CliffTypes.slk", FetchDataTypeName.SLK, - stringDataCallback); - final GenericResource water = this.loadMapGeneric("TerrainArt\\Water.slk", FetchDataTypeName.SLK, - stringDataCallback); - - // == when loaded, which is always in our system == - this.terrainData.load(terrain.data.toString()); - this.cliffTypesData.load(cliffTypes.data.toString()); - this.waterData.load(water.data.toString()); - // emit terrain loaded?? - - final GenericResource doodads = this.loadMapGeneric("Doodads\\Doodads.slk", FetchDataTypeName.SLK, - stringDataCallback); - final GenericResource doodadMetaData = this.loadMapGeneric("Doodads\\DoodadMetaData.slk", FetchDataTypeName.SLK, - stringDataCallback); - final GenericResource destructableData = this.loadMapGeneric("Units\\DestructableData.slk", - FetchDataTypeName.SLK, stringDataCallback); - final GenericResource destructableMetaData = this.loadMapGeneric("Units\\DestructableMetaData.slk", - FetchDataTypeName.SLK, stringDataCallback); - - // == when loaded, which is always in our system == - this.doodadsAndDestructiblesLoaded = true; - this.doodadsData.load(doodads.data.toString()); - this.doodadMetaData.load(doodadMetaData.data.toString()); - this.doodadsData.load(destructableData.data.toString()); - this.destructableMetaData.load(destructableData.data.toString()); - // emit doodads loaded - - final GenericResource unitData = this.loadMapGeneric("Units\\UnitData.slk", FetchDataTypeName.SLK, - stringDataCallback); - final GenericResource unitUi = this.loadMapGeneric("Units\\unitUI.slk", FetchDataTypeName.SLK, - stringDataCallback); - final GenericResource itemData = this.loadMapGeneric("Units\\ItemData.slk", FetchDataTypeName.SLK, - stringDataCallback); - final GenericResource unitMetaData = this.loadMapGeneric("Units\\UnitMetaData.slk", FetchDataTypeName.SLK, - stringDataCallback); - - // == when loaded, which is always in our system == - this.unitsAndItemsLoaded = true; - this.unitsData.load(unitData.data.toString()); - this.unitsData.load(unitUi.data.toString()); - this.unitsData.load(itemData.data.toString()); - this.unitMetaData.load(unitMetaData.data.toString()); - // emit loaded - - this.unitAckSoundsTable = new DataTable(worldEditStrings); - try (InputStream terrainSlkStream = this.dataSource.getResourceAsStream("UI\\SoundInfo\\UnitAckSounds.slk")) { - this.unitAckSoundsTable.readSLK(terrainSlkStream); - } - this.unitCombatSoundsTable = new DataTable(worldEditStrings); - try (InputStream terrainSlkStream = this.dataSource - .getResourceAsStream("UI\\SoundInfo\\UnitCombatSounds.slk")) { - this.unitCombatSoundsTable.readSLK(terrainSlkStream); - } - this.miscData = new DataTable(worldEditStrings); - try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("UI\\MiscData.txt")) { - this.miscData.readTXT(miscDataTxtStream, true); - } - try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\MiscData.txt")) { - this.miscData.readTXT(miscDataTxtStream, true); - } - try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\MiscGame.txt")) { - this.miscData.readTXT(miscDataTxtStream, true); - } - try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("UI\\SoundInfo\\MiscData.txt")) { - this.miscData.readTXT(miscDataTxtStream, true); - } - if (this.dataSource.has("war3mapMisc.txt")) { - try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("war3mapMisc.txt")) { - this.miscData.readTXT(miscDataTxtStream, true); - } - } - final Element light = this.miscData.get("Light"); - final float lightX = light.getFieldFloatValue("Direction", 0); - final float lightY = light.getFieldFloatValue("Direction", 1); - final float lightZ = light.getFieldFloatValue("Direction", 2); - this.lightDirection = new Vector3(lightX, lightY, lightZ).nor(); - this.unitGlobalStrings = new DataTable(worldEditStrings); - try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\UnitGlobalStrings.txt")) { - this.unitGlobalStrings.readTXT(miscDataTxtStream, true); - } - final Element categories = this.unitGlobalStrings.get("Categories"); - for (final CUnitClassification unitClassification : CUnitClassification.values()) { - if (unitClassification.getLocaleKey() != null) { - final String displayName = categories.getField(unitClassification.getLocaleKey()); - unitClassification.setDisplayName(displayName); - } - } - this.selectionCircleSizes.clear(); - final Element selectionCircleData = this.miscData.get("SelectionCircle"); - final int selectionCircleNumSizes = selectionCircleData.getFieldValue("NumSizes"); - for (int i = 0; i < selectionCircleNumSizes; i++) { - final String indexString = i < 10 ? "0" + i : Integer.toString(i); - final float size = selectionCircleData.getFieldFloatValue("Size" + indexString); - final String texture = selectionCircleData.getField("Texture" + indexString); - final String textureDotted = selectionCircleData.getField("TextureDotted" + indexString); - this.selectionCircleSizes.add(new SelectionCircleSize(size, texture, textureDotted)); - } - this.selectionCircleScaleFactor = selectionCircleData.getFieldFloatValue("ScaleFactor"); - } - - public GenericResource loadMapGeneric(final String path, final FetchDataTypeName dataType, - final LoadGenericCallback callback) { - if (this.mapMpq == null) { - return loadGeneric(path, dataType, callback); - } - else { - return loadGeneric(path, dataType, callback, this.dataSource); - } - } - - public void loadMap(final String mapFilePath) throws IOException { - final War3Map war3Map = new War3Map(this.gameDataSource, mapFilePath); - - this.mapMpq = war3Map; - - final PathSolver wc3PathSolver = this.wc3PathSolver; - - char tileset = 'A'; - - final War3MapW3i w3iFile = this.mapMpq.readMapInformation(); - - tileset = w3iFile.getTileset(); - - DataSource tilesetSource; - try { - // Slightly complex. Here's the theory: - // 1.) Copy map into RAM - // 2.) Setup a Data Source that will read assets - // from either the map or the game, giving the map priority. - SeekableByteChannel sbc; - final CompoundDataSource compoundDataSource = war3Map.getCompoundDataSource(); - try (InputStream mapStream = compoundDataSource.getResourceAsStream(tileset + ".mpq")) { - if (mapStream == null) { - tilesetSource = new CompoundDataSource(Arrays.asList(compoundDataSource, - new SubdirDataSource(compoundDataSource, tileset + ".mpq/"), - new SubdirDataSource(compoundDataSource, "_tilesets/" + tileset + ".w3mod/"))); - } - else { - final byte[] mapData = IOUtils.toByteArray(mapStream); - sbc = new SeekableInMemoryByteChannel(mapData); - final DataSource internalMpqContentsDataSource = new MpqDataSource(new MPQArchive(sbc), sbc); - tilesetSource = new CompoundDataSource( - Arrays.asList(compoundDataSource, internalMpqContentsDataSource)); - } - } - catch (final IOException exc) { - tilesetSource = new CompoundDataSource( - Arrays.asList(compoundDataSource, new SubdirDataSource(compoundDataSource, tileset + ".mpq/"), - new SubdirDataSource(compoundDataSource, "_tilesets/" + tileset + ".w3mod/"))); - } - } - catch (final MPQException e) { - throw new RuntimeException(e); - } - setDataSource(tilesetSource); - this.worldEditStrings = new WorldEditStrings(this.dataSource); - loadSLKs(this.worldEditStrings); - - this.solverParams.tileset = Character.toLowerCase(tileset); - - final War3MapW3e terrainData = this.mapMpq.readEnvironment(); - - final War3MapWpm terrainPathing = this.mapMpq.readPathing(); - - final StandardObjectData standardObjectData = new StandardObjectData(this.dataSource); - this.worldEditData = standardObjectData.getWorldEditData(); - - this.terrain = new Terrain(terrainData, terrainPathing, w3iFile, this.webGL, this.dataSource, - this.worldEditStrings, this, this.worldEditData); - - final float[] centerOffset = terrainData.getCenterOffset(); - final int[] mapSize = terrainData.getMapSize(); - - this.terrainReady = true; - this.anyReady = true; - this.cliffsReady = true; - - // Override the grid based on the map. - this.worldScene.grid = new Grid(centerOffset[0], centerOffset[1], (mapSize[0] * 128) - 128, - (mapSize[1] * 128) - 128, 16 * 128, 16 * 128); - - final MdxModel confirmation = (MdxModel) load("UI\\Feedback\\Confirmation\\Confirmation.mdx", - PathSolver.DEFAULT, null); - this.confirmationInstance = (MdxComplexInstance) confirmation.addInstance(); - this.confirmationInstance.setSequenceLoopMode(SequenceLoopMode.NEVER_LOOP_AND_HIDE_WHEN_DONE); - this.confirmationInstance.setSequence(0); - this.confirmationInstance.setScene(this.worldScene); - - this.allObjectData = this.mapMpq.readModifications(); - this.simulation = new CSimulation(this.miscData, this.allObjectData.getUnits(), - this.allObjectData.getAbilities(), new SimulationRenderController() { - private final Map keyToCombatSound = new HashMap<>(); - - @Override - public CAttackProjectile createAttackProjectile(final CSimulation simulation, final float launchX, - final float launchY, final float launchFacing, final CUnit source, - final CUnitAttackMissile unitAttack, final CWidget target, final float damage, - final int bounceIndex) { - final War3ID typeId = source.getTypeId(); - final int projectileSpeed = unitAttack.getProjectileSpeed(); - final float projectileArc = unitAttack.getProjectileArc(); - String missileArt = unitAttack.getProjectileArt(); - final float projectileLaunchX = simulation.getUnitData().getProjectileLaunchX(typeId); - final float projectileLaunchY = simulation.getUnitData().getProjectileLaunchY(typeId); - final float projectileLaunchZ = simulation.getUnitData().getProjectileLaunchZ(typeId); - - missileArt = mdx(missileArt); - final float facing = launchFacing; - final float sinFacing = (float) Math.sin(facing); - final float cosFacing = (float) Math.cos(facing); - final float x = (launchX + (projectileLaunchY * cosFacing)) + (projectileLaunchX * sinFacing); - final float y = (launchY + (projectileLaunchY * sinFacing)) - (projectileLaunchX * cosFacing); - - final float height = War3MapViewer.this.terrain.getGroundHeight(x, y) + source.getFlyHeight() - + projectileLaunchZ; - final CAttackProjectile simulationAttackProjectile = new CAttackProjectile(x, y, - projectileSpeed, target, source, damage, unitAttack, bounceIndex); - - final MdxModel model = (MdxModel) load(missileArt, War3MapViewer.this.mapPathSolver, - War3MapViewer.this.solverParams); - final MdxComplexInstance modelInstance = (MdxComplexInstance) model.addInstance(); - modelInstance.setTeamColor(source.getPlayerIndex()); - modelInstance.setScene(War3MapViewer.this.worldScene); - if (bounceIndex == 0) { - SequenceUtils.randomBirthSequence(modelInstance); - } - else { - SequenceUtils.randomStandSequence(modelInstance); - } - modelInstance.setLocation(x, y, height); - final RenderAttackProjectile renderAttackProjectile = new RenderAttackProjectile( - simulationAttackProjectile, modelInstance, height, projectileArc, War3MapViewer.this); - - War3MapViewer.this.projectiles.add(renderAttackProjectile); - - return simulationAttackProjectile; - } - - @Override - public void createInstantAttackEffect(final CSimulation cSimulation, final CUnit source, - final CUnitAttackInstant unitAttack, final CWidget target) { - final War3ID typeId = source.getTypeId(); - - String missileArt = unitAttack.getProjectileArt(); - final float projectileLaunchX = War3MapViewer.this.simulation.getUnitData() - .getProjectileLaunchX(typeId); - final float projectileLaunchY = War3MapViewer.this.simulation.getUnitData() - .getProjectileLaunchY(typeId); - missileArt = mdx(missileArt); - final float facing = (float) Math.toRadians(source.getFacing()); - final float sinFacing = (float) Math.sin(facing); - final float cosFacing = (float) Math.cos(facing); - final float x = (source.getX() + (projectileLaunchY * cosFacing)) - + (projectileLaunchX * sinFacing); - final float y = (source.getY() + (projectileLaunchY * sinFacing)) - - (projectileLaunchX * cosFacing); - - final float targetX = target.getX(); - final float targetY = target.getY(); - final float angleToTarget = (float) Math.atan2(targetY - y, targetX - x); - - final float height = War3MapViewer.this.terrain.getGroundHeight(targetX, targetY) - + target.getFlyHeight() + target.getImpactZ(); - - final MdxModel model = (MdxModel) load(missileArt, War3MapViewer.this.mapPathSolver, - War3MapViewer.this.solverParams); - final MdxComplexInstance modelInstance = (MdxComplexInstance) model.addInstance(); - modelInstance.setTeamColor(source.getPlayerIndex()); - modelInstance.setScene(War3MapViewer.this.worldScene); - SequenceUtils.randomBirthSequence(modelInstance); - modelInstance.setLocation(targetX, targetY, height); - War3MapViewer.this.projectiles - .add(new RenderAttackInstant(modelInstance, War3MapViewer.this, angleToTarget)); - } - - @Override - public void spawnUnitDamageSound(final CUnit damagedUnit, final String weaponSound, - final String armorType) { - final String key = weaponSound + armorType; - UnitSound combatSound = this.keyToCombatSound.get(key); - if (combatSound == null) { - combatSound = UnitSound.create(War3MapViewer.this.dataSource, - War3MapViewer.this.unitCombatSoundsTable, weaponSound, armorType); - this.keyToCombatSound.put(key, combatSound); - } - combatSound.play(War3MapViewer.this.worldScene.audioContext, damagedUnit.getX(), - damagedUnit.getY()); - } - - @Override - public void removeUnit(final CUnit unit) { - final RenderUnit renderUnit = War3MapViewer.this.unitToRenderPeer.remove(unit); - War3MapViewer.this.units.remove(renderUnit); - War3MapViewer.this.worldScene.removeInstance(renderUnit.instance); - } - }, this.terrain.pathingGrid, this.terrain.getEntireMap(), this.seededRandom, w3iFile.getPlayers()); - - this.walkableObjectsTree = new Quadtree<>(this.terrain.getEntireMap()); - if (this.doodadsAndDestructiblesLoaded) { - this.loadDoodadsAndDestructibles(this.allObjectData); - } - else { - throw new IllegalStateException("transcription of JS has not loaded a map and has no JS async promises"); - } - - this.terrain.createWaves(); - - loadLightsAndShading(tileset); - } - - /** - * Loads the map information that should be loaded after UI, such as units, who - * need to be able to setup their UI counterparts (icons, etc) for their - * abilities while loading. This allows the dynamic creation of units while the - * game is playing to better share code with the startup sequence's creation of - * units. - * - * @throws IOException - */ - public void loadAfterUI() throws IOException { - if (this.unitsAndItemsLoaded) { - this.loadUnitsAndItems(this.allObjectData); - } - else { - throw new IllegalStateException("transcription of JS has not loaded a map and has no JS async promises"); - } - - // After we finish loading units, we need to update & create the stored shadow - // information for all unit shadows - this.terrain.initShadows(); - } - - private void loadLightsAndShading(final char tileset) { - // TODO this should be set by the war3map.j actually, not by the tileset, so the - // call to set day night models is just for testing to make the test look pretty - final Element defaultTerrainLights = this.worldEditData.get("TerrainLights"); - final Element defaultUnitLights = this.worldEditData.get("UnitLights"); - setDayNightModels(defaultTerrainLights.getField(Character.toString(tileset)), - defaultUnitLights.getField(Character.toString(tileset))); - - } - - private void loadDoodadsAndDestructibles(final Warcraft3MapObjectData modifications) throws IOException { - this.applyModificationFile(this.doodadsData, this.doodadMetaData, modifications.getDoodads(), - WorldEditorDataType.DOODADS); - this.applyModificationFile(this.doodadsData, this.destructableMetaData, modifications.getDestructibles(), - WorldEditorDataType.DESTRUCTIBLES); - - final War3MapDoo doo = this.mapMpq.readDoodads(); - - for (final com.etheller.warsmash.parsers.w3x.doo.Doodad doodad : doo.getDoodads()) { - WorldEditorDataType type = WorldEditorDataType.DOODADS; - MutableGameObject row = modifications.getDoodads().get(doodad.getId()); - if (row == null) { - row = modifications.getDestructibles().get(doodad.getId()); - type = WorldEditorDataType.DESTRUCTIBLES; - } - if (row != null) { - String file = row.readSLKTag("file"); - final int numVar = row.readSLKTagInt("numVar"); - - if (file.endsWith(".mdx") || file.endsWith(".mdl")) { - file = file.substring(0, file.length() - 4); - } - - String fileVar = file; - - file += ".mdx"; - - if (numVar > 1) { - fileVar += Math.min(doodad.getVariation(), numVar - 1); - } - - fileVar += ".mdx"; - - final float maxPitch = row.readSLKTagFloat("maxPitch"); - final float maxRoll = row.readSLKTagFloat("maxRoll"); - if (type == WorldEditorDataType.DESTRUCTIBLES) { - final String shadowString = row.readSLKTag("shadow"); - if ((shadowString != null) && (shadowString.length() > 0) && !"_".equals(shadowString)) { - this.terrain.addShadow(shadowString, doodad.getLocation()[0], doodad.getLocation()[1]); - } - - final String pathingTexture = row.readSLKTag("pathTex"); - if ((pathingTexture != null) && (pathingTexture.length() > 0) && !"_".equals(pathingTexture)) { - - BufferedImage bufferedImage = this.filePathToPathingMap.get(pathingTexture.toLowerCase()); - if (bufferedImage == null) { - if (this.mapMpq.has(pathingTexture)) { - try { - bufferedImage = TgaFile.readTGA(pathingTexture, - this.mapMpq.getResourceAsStream(pathingTexture)); - this.filePathToPathingMap.put(pathingTexture.toLowerCase(), bufferedImage); - } - catch (final Exception exc) { - exc.printStackTrace(); - } - } - } - if (bufferedImage != null) { - this.terrain.pathingGrid.blitPathingOverlayTexture(doodad.getLocation()[0], - doodad.getLocation()[1], (int) Math.toDegrees(doodad.getAngle()), bufferedImage); - } - } - } - // First see if the model is local. - // Doodads referring to local models may have invalid variations, so if the - // variation doesn't exist, try without a variation. - - String path; - if (this.mapMpq.has(fileVar)) { - path = fileVar; - } - else { - path = file; - } - MdxModel model; - if (this.mapMpq.has(path)) { - model = (MdxModel) this.load(path, this.mapPathSolver, this.solverParams); - } - else { - model = (MdxModel) this.load(fileVar, this.mapPathSolver, this.solverParams); - } - - if (type == WorldEditorDataType.DESTRUCTIBLES) { - final RenderDestructable renderDestructable = new RenderDestructable(this, model, row, doodad, type, - maxPitch, maxRoll, doodad.getLife()); - if (row.readSLKTagBoolean("walkable")) { - final float x = doodad.getLocation()[0]; - final float y = doodad.getLocation()[1]; - final BoundingBox boundingBox = model.bounds.getBoundingBox(); - final float minX = boundingBox.min.x + x; - final float minY = boundingBox.min.y + y; - final Rectangle renderDestructableBounds = new Rectangle(minX, minY, boundingBox.getWidth(), - boundingBox.getHeight()); - this.walkableObjectsTree.add((MdxComplexInstance) renderDestructable.instance, - renderDestructableBounds); - } - this.doodads.add(renderDestructable); - } - else { - this.doodads.add(new RenderDoodad(this, model, row, doodad, type, maxPitch, maxRoll)); - } - } - } - - // Cliff/Terrain doodads. - for (final com.etheller.warsmash.parsers.w3x.doo.TerrainDoodad doodad : doo.getTerrainDoodads()) { - final MutableGameObject row = modifications.getDoodads().get(doodad.getId()); - String file = row.readSLKTag("file");// - if ("".equals(file)) { - final String blaBla = row.readSLKTag("file"); - System.out.println("bla"); - } - if (file.toLowerCase().endsWith(".mdl")) { - file = file.substring(0, file.length() - 4); - } - if (!file.toLowerCase().endsWith(".mdx")) { - file += ".mdx"; - } - final MdxModel model = (MdxModel) this.load(file, this.mapPathSolver, this.solverParams); - - final String pathingTexture = row.readSLKTag("pathTex"); - BufferedImage pathingTextureImage; - if ((pathingTexture != null) && (pathingTexture.length() > 0) && !"_".equals(pathingTexture)) { - - pathingTextureImage = this.filePathToPathingMap.get(pathingTexture.toLowerCase()); - if (pathingTextureImage == null) { - if (this.mapMpq.has(pathingTexture)) { - try { - pathingTextureImage = TgaFile.readTGA(pathingTexture, - this.mapMpq.getResourceAsStream(pathingTexture)); - this.filePathToPathingMap.put(pathingTexture.toLowerCase(), pathingTextureImage); - } - catch (final Exception exc) { - exc.printStackTrace(); - } - } - } - } - else { - pathingTextureImage = null; - } - if (pathingTextureImage != null) { - // blit out terrain cells under this TerrainDoodad - final int textureWidth = pathingTextureImage.getWidth(); - final int textureHeight = pathingTextureImage.getHeight(); - final int textureWidthTerrainCells = textureWidth / 4; - final int textureHeightTerrainCells = textureHeight / 4; - final int minCellX = ((int) doodad.getLocation()[0]); - final int minCellY = ((int) doodad.getLocation()[1]); - final int maxCellX = (minCellX + textureWidthTerrainCells) - 1; - final int maxCellY = (minCellY + textureHeightTerrainCells) - 1; - for (int j = minCellY; j <= maxCellY; j++) { - for (int i = minCellX; i <= maxCellX; i++) { - this.terrain.removeTerrainCellWithoutFlush(i, j); - } - } - this.terrain.flushRemovedTerrainCells(); - } - - System.out.println("Loading terrain doodad: " + file); - this.terrainDoodads.add(new TerrainDoodad(this, model, row, doodad, pathingTextureImage)); - } - - this.doodadsReady = true; - this.anyReady = true; - } - - private void applyModificationFile(final MappedData doodadsData2, final MappedData doodadMetaData2, - final MutableObjectData destructibles, final WorldEditorDataType dataType) { - // TODO condense ported MappedData from Ghostwolf and MutableObjectData from - // Retera - - } - - private void loadUnitsAndItems(final Warcraft3MapObjectData modifications) throws IOException { - final War3Map mpq = this.mapMpq; - - if (this.dataSource.has("war3mapUnits.doo")) { - final War3MapUnitsDoo dooFile = mpq.readUnits(); - - final Map soundsetNameToSoundset = new HashMap<>(); - - // Collect the units and items data. - UnitSoundset soundset = null; - for (final com.etheller.warsmash.parsers.w3x.unitsdoo.Unit unit : dooFile.getUnits()) { - MutableGameObject row = null; - String path = null; - Splat unitShadowSplat = null; - BufferedImage buildingPathingPixelMap = null; - - // Hardcoded? - WorldEditorDataType type = null; - if (sloc.equals(unit.getId())) { + private static final War3ID UNIT_FILE = War3ID.fromString("umdl"); + private static final War3ID UBER_SPLAT = War3ID.fromString("uubs"); + private static final War3ID UNIT_SHADOW = War3ID.fromString("ushu"); + private static final War3ID UNIT_SHADOW_X = War3ID.fromString("ushx"); + private static final War3ID UNIT_SHADOW_Y = War3ID.fromString("ushy"); + private static final War3ID UNIT_SHADOW_W = War3ID.fromString("ushw"); + private static final War3ID UNIT_SHADOW_H = War3ID.fromString("ushh"); + private static final War3ID BUILDING_SHADOW = War3ID.fromString("ushb"); + public static final War3ID UNIT_SELECT_SCALE = War3ID.fromString("ussc"); + private static final War3ID UNIT_SELECT_HEIGHT = War3ID.fromString("uslz"); + private static final War3ID UNIT_SOUNDSET = War3ID.fromString("usnd"); + private static final War3ID ITEM_FILE = War3ID.fromString("ifil"); + private static final War3ID UNIT_PATHING = War3ID.fromString("upat"); + private static final War3ID DESTRUCTABLE_PATHING = War3ID.fromString("bptx"); + private static final War3ID ELEVATION_SAMPLE_RADIUS = War3ID.fromString("uerd"); + private static final War3ID MAX_PITCH = War3ID.fromString("umxp"); + private static final War3ID MAX_ROLL = War3ID.fromString("umxr"); + private static final War3ID sloc = War3ID.fromString("sloc"); + private static final LoadGenericCallback stringDataCallback = new StringDataCallbackImplementation(); + private static final float[] rayHeap = new float[6]; + public static final Ray gdxRayHeap = new Ray(); + private static final Vector2 mousePosHeap = new Vector2(); + private static final Vector3 normalHeap = new Vector3(); + public static final Vector3 intersectionHeap = new Vector3(); + private static final Rectangle rectangleHeap = new Rectangle(); + public static final StreamDataCallbackImplementation streamDataCallback = new StreamDataCallbackImplementation(); + + public PathSolver wc3PathSolver = PathSolver.DEFAULT; + public SolverParams solverParams = new SolverParams(); + public WorldScene worldScene; + public boolean anyReady; + public MappedData terrainData = new MappedData(); + public MappedData cliffTypesData = new MappedData(); + public MappedData waterData = new MappedData(); + public boolean terrainReady; + public boolean cliffsReady; + public boolean doodadsAndDestructiblesLoaded; + public MappedData doodadsData = new MappedData(); + public MappedData doodadMetaData = new MappedData(); + public MappedData destructableMetaData = new MappedData(); + public List doodads = new ArrayList<>(); + public List terrainDoodads = new ArrayList<>(); + public boolean doodadsReady; + public boolean unitsAndItemsLoaded; + public MappedData unitsData = new MappedData(); + public MappedData unitMetaData = new MappedData(); + public List units = new ArrayList<>(); + public List items = new ArrayList<>(); + public List projectiles = new ArrayList<>(); + public boolean unitsReady; + public War3Map mapMpq; + public PathSolver mapPathSolver = PathSolver.DEFAULT; + + private final DataSource gameDataSource; + + public Terrain terrain; + public int renderPathing = 0; + public int renderLighting = 1; + + public List selModels = new ArrayList<>(); + public List selected = new ArrayList<>(); + private DataTable unitAckSoundsTable; + private DataTable unitCombatSoundsTable; + public DataTable miscData; + private DataTable unitGlobalStrings; + private MdxComplexInstance confirmationInstance; + public MdxComplexInstance dncUnit; + public MdxComplexInstance dncUnitDay; + public MdxComplexInstance dncTerrain; + public MdxComplexInstance dncTarget; + public CSimulation simulation; + private float updateTime; + + // for World Editor, I think + public Vector2[] startLocations = new Vector2[WarsmashConstants.MAX_PLAYERS]; + + private final DynamicShadowManager dynamicShadowManager = new DynamicShadowManager(); + + private final Random seededRandom = new Random(1337L); + + private final Map filePathToPathingMap = new HashMap<>(); + + private final List selectionCircleSizes = new ArrayList<>(); + + private final Map unitToRenderPeer = new HashMap<>(); + private final Map unitIdToTypeData = new HashMap<>(); + private GameUI gameUI; + private Vector3 lightDirection; + + private Quadtree walkableObjectsTree; + private final QuadtreeIntersectorFindsWalkableRenderHeight walkablesIntersector = new QuadtreeIntersectorFindsWalkableRenderHeight(); + private final QuadtreeIntersectorFindsHitPoint walkablesIntersectionFinder = new QuadtreeIntersectorFindsHitPoint(); + private final QuadtreeIntersectorFindsHighestWalkable intersectorFindsHighestWalkable = new QuadtreeIntersectorFindsHighestWalkable(); + + public War3MapViewer(final DataSource dataSource, final CanvasProvider canvas) { + super(dataSource, canvas); + this.gameDataSource = dataSource; + + final WebGL webGL = this.webGL; + + this.addHandler(new MdxHandler()); + + this.wc3PathSolver = PathSolver.DEFAULT; + + this.worldScene = this.addWorldScene(); + + if (!this.dynamicShadowManager.setup(webGL)) { + throw new IllegalStateException("FrameBuffer setup failed"); + } + } + + public void loadSLKs(final WorldEditStrings worldEditStrings) throws IOException { + final GenericResource terrain = this.loadMapGeneric("TerrainArt\\Terrain.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource cliffTypes = this.loadMapGeneric("TerrainArt\\CliffTypes.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource water = this.loadMapGeneric("TerrainArt\\Water.slk", FetchDataTypeName.SLK, + stringDataCallback); + + // == when loaded, which is always in our system == + this.terrainData.load(terrain.data.toString()); + this.cliffTypesData.load(cliffTypes.data.toString()); + this.waterData.load(water.data.toString()); + // emit terrain loaded?? + + final GenericResource doodads = this.loadMapGeneric("Doodads\\Doodads.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource doodadMetaData = this.loadMapGeneric("Doodads\\DoodadMetaData.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource destructableData = this.loadMapGeneric("Units\\DestructableData.slk", + FetchDataTypeName.SLK, stringDataCallback); + final GenericResource destructableMetaData = this.loadMapGeneric("Units\\DestructableMetaData.slk", + FetchDataTypeName.SLK, stringDataCallback); + + // == when loaded, which is always in our system == + this.doodadsAndDestructiblesLoaded = true; + this.doodadsData.load(doodads.data.toString()); + this.doodadMetaData.load(doodadMetaData.data.toString()); + this.doodadsData.load(destructableData.data.toString()); + this.destructableMetaData.load(destructableData.data.toString()); + // emit doodads loaded + + final GenericResource unitData = this.loadMapGeneric("Units\\UnitData.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource unitUi = this.loadMapGeneric("Units\\unitUI.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource itemData = this.loadMapGeneric("Units\\ItemData.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource unitMetaData = this.loadMapGeneric("Units\\UnitMetaData.slk", FetchDataTypeName.SLK, + stringDataCallback); + + // == when loaded, which is always in our system == + this.unitsAndItemsLoaded = true; + this.unitsData.load(unitData.data.toString()); + this.unitsData.load(unitUi.data.toString()); + this.unitsData.load(itemData.data.toString()); + this.unitMetaData.load(unitMetaData.data.toString()); + // emit loaded + + this.unitAckSoundsTable = new DataTable(worldEditStrings); + try (InputStream terrainSlkStream = this.dataSource.getResourceAsStream("UI\\SoundInfo\\UnitAckSounds.slk")) { + this.unitAckSoundsTable.readSLK(terrainSlkStream); + } + this.unitCombatSoundsTable = new DataTable(worldEditStrings); + try (InputStream terrainSlkStream = this.dataSource + .getResourceAsStream("UI\\SoundInfo\\UnitCombatSounds.slk")) { + this.unitCombatSoundsTable.readSLK(terrainSlkStream); + } + this.miscData = new DataTable(worldEditStrings); + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("UI\\MiscData.txt")) { + this.miscData.readTXT(miscDataTxtStream, true); + } + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\MiscData.txt")) { + this.miscData.readTXT(miscDataTxtStream, true); + } + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\MiscGame.txt")) { + this.miscData.readTXT(miscDataTxtStream, true); + } + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("UI\\SoundInfo\\MiscData.txt")) { + this.miscData.readTXT(miscDataTxtStream, true); + } + if (this.dataSource.has("war3mapMisc.txt")) { + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("war3mapMisc.txt")) { + this.miscData.readTXT(miscDataTxtStream, true); + } + } + final Element light = this.miscData.get("Light"); + final float lightX = light.getFieldFloatValue("Direction", 0); + final float lightY = light.getFieldFloatValue("Direction", 1); + final float lightZ = light.getFieldFloatValue("Direction", 2); + this.lightDirection = new Vector3(lightX, lightY, lightZ).nor(); + this.unitGlobalStrings = new DataTable(worldEditStrings); + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\UnitGlobalStrings.txt")) { + this.unitGlobalStrings.readTXT(miscDataTxtStream, true); + } + final Element categories = this.unitGlobalStrings.get("Categories"); + for (final CUnitClassification unitClassification : CUnitClassification.values()) { + if (unitClassification.getLocaleKey() != null) { + final String displayName = categories.getField(unitClassification.getLocaleKey()); + unitClassification.setDisplayName(displayName); + } + } + this.selectionCircleSizes.clear(); + final Element selectionCircleData = this.miscData.get("SelectionCircle"); + final int selectionCircleNumSizes = selectionCircleData.getFieldValue("NumSizes"); + for (int i = 0; i < selectionCircleNumSizes; i++) { + final String indexString = i < 10 ? "0" + i : Integer.toString(i); + final float size = selectionCircleData.getFieldFloatValue("Size" + indexString); + final String texture = selectionCircleData.getField("Texture" + indexString); + final String textureDotted = selectionCircleData.getField("TextureDotted" + indexString); + this.selectionCircleSizes.add(new SelectionCircleSize(size, texture, textureDotted)); + } + this.selectionCircleScaleFactor = selectionCircleData.getFieldFloatValue("ScaleFactor"); + } + + public GenericResource loadMapGeneric(final String path, final FetchDataTypeName dataType, + final LoadGenericCallback callback) { + if (this.mapMpq == null) { + return loadGeneric(path, dataType, callback); + } else { + return loadGeneric(path, dataType, callback, this.dataSource); + } + } + + public void loadMap(final String mapFilePath) throws IOException { + final War3Map war3Map = new War3Map(this.gameDataSource, mapFilePath); + + this.mapMpq = war3Map; + + final PathSolver wc3PathSolver = this.wc3PathSolver; + + char tileset = 'A'; + + final War3MapW3i w3iFile = this.mapMpq.readMapInformation(); + + tileset = w3iFile.getTileset(); + + DataSource tilesetSource; + try { + // Slightly complex. Here's the theory: + // 1.) Copy map into RAM + // 2.) Setup a Data Source that will read assets + // from either the map or the game, giving the map priority. + SeekableByteChannel sbc; + final CompoundDataSource compoundDataSource = war3Map.getCompoundDataSource(); + try (InputStream mapStream = compoundDataSource.getResourceAsStream(tileset + ".mpq")) { + if (mapStream == null) { + tilesetSource = new CompoundDataSource(Arrays.asList(compoundDataSource, + new SubdirDataSource(compoundDataSource, tileset + ".mpq/"), + new SubdirDataSource(compoundDataSource, "_tilesets/" + tileset + ".w3mod/"))); + } else { + final byte[] mapData = IOUtils.toByteArray(mapStream); + sbc = new SeekableInMemoryByteChannel(mapData); + final DataSource internalMpqContentsDataSource = new MpqDataSource(new MPQArchive(sbc), sbc); + tilesetSource = new CompoundDataSource( + Arrays.asList(compoundDataSource, internalMpqContentsDataSource)); + } + } catch (final IOException exc) { + tilesetSource = new CompoundDataSource( + Arrays.asList(compoundDataSource, new SubdirDataSource(compoundDataSource, tileset + ".mpq/"), + new SubdirDataSource(compoundDataSource, "_tilesets/" + tileset + ".w3mod/"))); + } + } catch (final MPQException e) { + throw new RuntimeException(e); + } + setDataSource(tilesetSource); + this.worldEditStrings = new WorldEditStrings(this.dataSource); + loadSLKs(this.worldEditStrings); + + this.solverParams.tileset = Character.toLowerCase(tileset); + + final War3MapW3e terrainData = this.mapMpq.readEnvironment(); + + final War3MapWpm terrainPathing = this.mapMpq.readPathing(); + + final StandardObjectData standardObjectData = new StandardObjectData(this.dataSource); + this.worldEditData = standardObjectData.getWorldEditData(); + + this.terrain = new Terrain(terrainData, terrainPathing, w3iFile, this.webGL, this.dataSource, + this.worldEditStrings, this, this.worldEditData); + + final float[] centerOffset = terrainData.getCenterOffset(); + final int[] mapSize = terrainData.getMapSize(); + + this.terrainReady = true; + this.anyReady = true; + this.cliffsReady = true; + + // Override the grid based on the map. + this.worldScene.grid = new Grid(centerOffset[0], centerOffset[1], (mapSize[0] * 128) - 128, + (mapSize[1] * 128) - 128, 16 * 128, 16 * 128); + + final MdxModel confirmation = (MdxModel) load("UI\\Feedback\\Confirmation\\Confirmation.mdx", + PathSolver.DEFAULT, null); + this.confirmationInstance = (MdxComplexInstance) confirmation.addInstance(); + this.confirmationInstance.setSequenceLoopMode(SequenceLoopMode.NEVER_LOOP_AND_HIDE_WHEN_DONE); + this.confirmationInstance.setSequence(0); + this.confirmationInstance.setScene(this.worldScene); + + this.allObjectData = this.mapMpq.readModifications(); + this.simulation = new CSimulation(this.miscData, this.allObjectData.getUnits(), + this.allObjectData.getAbilities(), new SimulationRenderController() { + private final Map keyToCombatSound = new HashMap<>(); + + @Override + public CAttackProjectile createAttackProjectile(final CSimulation simulation, final float launchX, + final float launchY, final float launchFacing, final CUnit source, + final CUnitAttackMissile unitAttack, final CWidget target, final float damage, + final int bounceIndex) { + final War3ID typeId = source.getTypeId(); + final int projectileSpeed = unitAttack.getProjectileSpeed(); + final float projectileArc = unitAttack.getProjectileArc(); + String missileArt = unitAttack.getProjectileArt(); + final float projectileLaunchX = simulation.getUnitData().getProjectileLaunchX(typeId); + final float projectileLaunchY = simulation.getUnitData().getProjectileLaunchY(typeId); + final float projectileLaunchZ = simulation.getUnitData().getProjectileLaunchZ(typeId); + + missileArt = mdx(missileArt); + final float facing = launchFacing; + final float sinFacing = (float) Math.sin(facing); + final float cosFacing = (float) Math.cos(facing); + final float x = (launchX + (projectileLaunchY * cosFacing)) + (projectileLaunchX * sinFacing); + final float y = (launchY + (projectileLaunchY * sinFacing)) - (projectileLaunchX * cosFacing); + + final float height = War3MapViewer.this.terrain.getGroundHeight(x, y) + source.getFlyHeight() + + projectileLaunchZ; + final CAttackProjectile simulationAttackProjectile = new CAttackProjectile(x, y, + projectileSpeed, target, source, damage, unitAttack, bounceIndex); + + final MdxModel model = (MdxModel) load(missileArt, War3MapViewer.this.mapPathSolver, + War3MapViewer.this.solverParams); + final MdxComplexInstance modelInstance = (MdxComplexInstance) model.addInstance(); + modelInstance.setTeamColor(source.getPlayerIndex()); + modelInstance.setScene(War3MapViewer.this.worldScene); + if (bounceIndex == 0) { + SequenceUtils.randomBirthSequence(modelInstance); + } else { + SequenceUtils.randomStandSequence(modelInstance); + } + modelInstance.setLocation(x, y, height); + final RenderAttackProjectile renderAttackProjectile = new RenderAttackProjectile( + simulationAttackProjectile, modelInstance, height, projectileArc, War3MapViewer.this); + + War3MapViewer.this.projectiles.add(renderAttackProjectile); + + return simulationAttackProjectile; + } + + @Override + public void createInstantAttackEffect(final CSimulation cSimulation, final CUnit source, + final CUnitAttackInstant unitAttack, final CWidget target) { + final War3ID typeId = source.getTypeId(); + + String missileArt = unitAttack.getProjectileArt(); + final float projectileLaunchX = War3MapViewer.this.simulation.getUnitData() + .getProjectileLaunchX(typeId); + final float projectileLaunchY = War3MapViewer.this.simulation.getUnitData() + .getProjectileLaunchY(typeId); + missileArt = mdx(missileArt); + final float facing = (float) Math.toRadians(source.getFacing()); + final float sinFacing = (float) Math.sin(facing); + final float cosFacing = (float) Math.cos(facing); + final float x = (source.getX() + (projectileLaunchY * cosFacing)) + + (projectileLaunchX * sinFacing); + final float y = (source.getY() + (projectileLaunchY * sinFacing)) + - (projectileLaunchX * cosFacing); + + final float targetX = target.getX(); + final float targetY = target.getY(); + final float angleToTarget = (float) Math.atan2(targetY - y, targetX - x); + + final float height = War3MapViewer.this.terrain.getGroundHeight(targetX, targetY) + + target.getFlyHeight() + target.getImpactZ(); + + final MdxModel model = (MdxModel) load(missileArt, War3MapViewer.this.mapPathSolver, + War3MapViewer.this.solverParams); + final MdxComplexInstance modelInstance = (MdxComplexInstance) model.addInstance(); + modelInstance.setTeamColor(source.getPlayerIndex()); + modelInstance.setScene(War3MapViewer.this.worldScene); + SequenceUtils.randomBirthSequence(modelInstance); + modelInstance.setLocation(targetX, targetY, height); + War3MapViewer.this.projectiles + .add(new RenderAttackInstant(modelInstance, War3MapViewer.this, angleToTarget)); + } + + @Override + public void spawnUnitDamageSound(final CUnit damagedUnit, final String weaponSound, + final String armorType) { + final String key = weaponSound + armorType; + UnitSound combatSound = this.keyToCombatSound.get(key); + if (combatSound == null) { + combatSound = UnitSound.create(War3MapViewer.this.dataSource, + War3MapViewer.this.unitCombatSoundsTable, weaponSound, armorType); + this.keyToCombatSound.put(key, combatSound); + } + combatSound.play(War3MapViewer.this.worldScene.audioContext, damagedUnit.getX(), + damagedUnit.getY()); + } + + @Override + public void removeUnit(final CUnit unit) { + final RenderUnit renderUnit = War3MapViewer.this.unitToRenderPeer.remove(unit); + War3MapViewer.this.units.remove(renderUnit); + War3MapViewer.this.worldScene.removeInstance(renderUnit.instance); + } + }, this.terrain.pathingGrid, this.terrain.getEntireMap(), this.seededRandom, w3iFile.getPlayers()); + + this.walkableObjectsTree = new Quadtree<>(this.terrain.getEntireMap()); + if (this.doodadsAndDestructiblesLoaded) { + this.loadDoodadsAndDestructibles(this.allObjectData); + } else { + throw new IllegalStateException("transcription of JS has not loaded a map and has no JS async promises"); + } + + this.terrain.createWaves(); + + loadLightsAndShading(tileset); + } + + /** + * Loads the map information that should be loaded after UI, such as units, who + * need to be able to setup their UI counterparts (icons, etc) for their + * abilities while loading. This allows the dynamic creation of units while the + * game is playing to better share code with the startup sequence's creation of + * units. + * + * @throws IOException + */ + public void loadAfterUI() throws IOException { + if (this.unitsAndItemsLoaded) { + this.loadUnitsAndItems(this.allObjectData); + } else { + throw new IllegalStateException("transcription of JS has not loaded a map and has no JS async promises"); + } + + // After we finish loading units, we need to update & create the stored shadow + // information for all unit shadows + this.terrain.initShadows(); + } + + private void loadLightsAndShading(final char tileset) { + // TODO this should be set by the war3map.j actually, not by the tileset, so the + // call to set day night models is just for testing to make the test look pretty + final Element defaultTerrainLights = this.worldEditData.get("TerrainLights"); + final Element defaultUnitLights = this.worldEditData.get("UnitLights"); + setDayNightModels(defaultTerrainLights.getField(Character.toString(tileset)), + defaultUnitLights.getField(Character.toString(tileset))); + + } + + private void loadDoodadsAndDestructibles(final Warcraft3MapObjectData modifications) throws IOException { + this.applyModificationFile(this.doodadsData, this.doodadMetaData, modifications.getDoodads(), + WorldEditorDataType.DOODADS); + this.applyModificationFile(this.doodadsData, this.destructableMetaData, modifications.getDestructibles(), + WorldEditorDataType.DESTRUCTIBLES); + + final War3MapDoo doo = this.mapMpq.readDoodads(); + + for (final com.etheller.warsmash.parsers.w3x.doo.Doodad doodad : doo.getDoodads()) { + WorldEditorDataType type = WorldEditorDataType.DOODADS; + MutableGameObject row = modifications.getDoodads().get(doodad.getId()); + if (row == null) { + row = modifications.getDestructibles().get(doodad.getId()); + type = WorldEditorDataType.DESTRUCTIBLES; + } + if (row != null) { + String file = row.readSLKTag("file"); + final int numVar = row.readSLKTagInt("numVar"); + + if (file.endsWith(".mdx") || file.endsWith(".mdl")) { + file = file.substring(0, file.length() - 4); + } + + String fileVar = file; + + file += ".mdx"; + + if (numVar > 1) { + fileVar += Math.min(doodad.getVariation(), numVar - 1); + } + + fileVar += ".mdx"; + + final float maxPitch = row.readSLKTagFloat("maxPitch"); + final float maxRoll = row.readSLKTagFloat("maxRoll"); + if (type == WorldEditorDataType.DESTRUCTIBLES) { + final String shadowString = row.readSLKTag("shadow"); + if ((shadowString != null) && (shadowString.length() > 0) && !"_".equals(shadowString)) { + this.terrain.addShadow(shadowString, doodad.getLocation()[0], doodad.getLocation()[1]); + } + + final String pathingTexture = row.readSLKTag("pathTex"); + if ((pathingTexture != null) && (pathingTexture.length() > 0) && !"_".equals(pathingTexture)) { + + BufferedImage bufferedImage = this.filePathToPathingMap.get(pathingTexture.toLowerCase()); + if (bufferedImage == null) { + if (this.mapMpq.has(pathingTexture)) { + try { + bufferedImage = TgaFile.readTGA(pathingTexture, + this.mapMpq.getResourceAsStream(pathingTexture)); + this.filePathToPathingMap.put(pathingTexture.toLowerCase(), bufferedImage); + } catch (final Exception exc) { + exc.printStackTrace(); + } + } + } + if (bufferedImage != null) { + this.terrain.pathingGrid.blitPathingOverlayTexture(doodad.getLocation()[0], + doodad.getLocation()[1], (int) Math.toDegrees(doodad.getAngle()), bufferedImage); + } + } + } + // First see if the model is local. + // Doodads referring to local models may have invalid variations, so if the + // variation doesn't exist, try without a variation. + + String path; + if (this.mapMpq.has(fileVar)) { + path = fileVar; + } else { + path = file; + } + MdxModel model; + if (this.mapMpq.has(path)) { + model = (MdxModel) this.load(path, this.mapPathSolver, this.solverParams); + } else { + model = (MdxModel) this.load(fileVar, this.mapPathSolver, this.solverParams); + } + + if (type == WorldEditorDataType.DESTRUCTIBLES) { + final RenderDestructable renderDestructable = new RenderDestructable(this, model, row, doodad, type, + maxPitch, maxRoll, doodad.getLife()); + if (row.readSLKTagBoolean("walkable")) { + final float x = doodad.getLocation()[0]; + final float y = doodad.getLocation()[1]; + final BoundingBox boundingBox = model.bounds.getBoundingBox(); + final float minX = boundingBox.min.x + x; + final float minY = boundingBox.min.y + y; + final Rectangle renderDestructableBounds = new Rectangle(minX, minY, boundingBox.getWidth(), + boundingBox.getHeight()); + this.walkableObjectsTree.add((MdxComplexInstance) renderDestructable.instance, + renderDestructableBounds); + } + this.doodads.add(renderDestructable); + } else { + this.doodads.add(new RenderDoodad(this, model, row, doodad, type, maxPitch, maxRoll)); + } + } + } + + // Cliff/Terrain doodads. + for (final com.etheller.warsmash.parsers.w3x.doo.TerrainDoodad doodad : doo.getTerrainDoodads()) { + final MutableGameObject row = modifications.getDoodads().get(doodad.getId()); + String file = row.readSLKTag("file");// + if ("".equals(file)) { + final String blaBla = row.readSLKTag("file"); + System.out.println("bla"); + } + if (file.toLowerCase().endsWith(".mdl")) { + file = file.substring(0, file.length() - 4); + } + if (!file.toLowerCase().endsWith(".mdx")) { + file += ".mdx"; + } + final MdxModel model = (MdxModel) this.load(file, this.mapPathSolver, this.solverParams); + + final String pathingTexture = row.readSLKTag("pathTex"); + BufferedImage pathingTextureImage; + if ((pathingTexture != null) && (pathingTexture.length() > 0) && !"_".equals(pathingTexture)) { + + pathingTextureImage = this.filePathToPathingMap.get(pathingTexture.toLowerCase()); + if (pathingTextureImage == null) { + if (this.mapMpq.has(pathingTexture)) { + try { + pathingTextureImage = TgaFile.readTGA(pathingTexture, + this.mapMpq.getResourceAsStream(pathingTexture)); + this.filePathToPathingMap.put(pathingTexture.toLowerCase(), pathingTextureImage); + } catch (final Exception exc) { + exc.printStackTrace(); + } + } + } + } else { + pathingTextureImage = null; + } + if (pathingTextureImage != null) { + // blit out terrain cells under this TerrainDoodad + final int textureWidth = pathingTextureImage.getWidth(); + final int textureHeight = pathingTextureImage.getHeight(); + final int textureWidthTerrainCells = textureWidth / 4; + final int textureHeightTerrainCells = textureHeight / 4; + final int minCellX = ((int) doodad.getLocation()[0]); + final int minCellY = ((int) doodad.getLocation()[1]); + final int maxCellX = (minCellX + textureWidthTerrainCells) - 1; + final int maxCellY = (minCellY + textureHeightTerrainCells) - 1; + for (int j = minCellY; j <= maxCellY; j++) { + for (int i = minCellX; i <= maxCellX; i++) { + this.terrain.removeTerrainCellWithoutFlush(i, j); + } + } + this.terrain.flushRemovedTerrainCells(); + } + + System.out.println("Loading terrain doodad: " + file); + this.terrainDoodads.add(new TerrainDoodad(this, model, row, doodad, pathingTextureImage)); + } + + this.doodadsReady = true; + this.anyReady = true; + } + + private void applyModificationFile(final MappedData doodadsData2, final MappedData doodadMetaData2, + final MutableObjectData destructibles, final WorldEditorDataType dataType) { + // TODO condense ported MappedData from Ghostwolf and MutableObjectData from + // Retera + + } + + private void loadUnitsAndItems(final Warcraft3MapObjectData modifications) throws IOException { + final War3Map mpq = this.mapMpq; + + if (this.dataSource.has("war3mapUnits.doo")) { + final War3MapUnitsDoo dooFile = mpq.readUnits(); + + final Map soundsetNameToSoundset = new HashMap<>(); + + // Collect the units and items data. + UnitSoundset soundset = null; + for (final com.etheller.warsmash.parsers.w3x.unitsdoo.Unit unit : dooFile.getUnits()) { + MutableGameObject row = null; + String path = null; + Splat unitShadowSplat = null; + BufferedImage buildingPathingPixelMap = null; + + // Hardcoded? + WorldEditorDataType type = null; + if (sloc.equals(unit.getId())) { // path = "Objects\\StartLocation\\StartLocation.mdx"; - type = null; /// ?????? - this.startLocations[unit.getPlayer()] = new Vector2(unit.getLocation()[0], unit.getLocation()[1]); - } - else { - row = modifications.getUnits().get(unit.getId()); - if (row == null) { - row = modifications.getItems().get(unit.getId()); - if (row != null) { - type = WorldEditorDataType.ITEM; - path = row.getFieldAsString(ITEM_FILE, 0); + type = null; /// ?????? + this.startLocations[unit.getPlayer()] = new Vector2(unit.getLocation()[0], unit.getLocation()[1]); + } else { + row = modifications.getUnits().get(unit.getId()); + if (row == null) { + row = modifications.getItems().get(unit.getId()); + if (row != null) { + type = WorldEditorDataType.ITEM; + path = row.getFieldAsString(ITEM_FILE, 0); - if (path.toLowerCase().endsWith(".mdl") || path.toLowerCase().endsWith(".mdx")) { - path = path.substring(0, path.length() - 4); - } + if (path.toLowerCase().endsWith(".mdl") || path.toLowerCase().endsWith(".mdx")) { + path = path.substring(0, path.length() - 4); + } - final String unitShadow = "Shadow"; - if ((unitShadow != null) && !"_".equals(unitShadow)) { - final String texture = "ReplaceableTextures\\Shadows\\" + unitShadow + ".blp"; - final float shadowX = 50; - final float shadowY = 50; - final float shadowWidth = 128; - final float shadowHeight = 128; - if (!this.terrain.splats.containsKey(texture)) { - final Splat splat = new Splat(); - splat.opacity = 0.5f; - this.terrain.splats.put(texture, splat); - } - final float x = unit.getLocation()[0] - shadowX; - final float y = unit.getLocation()[1] - shadowY; - this.terrain.splats.get(texture).locations - .add(new float[] { x, y, x + shadowWidth, y + shadowHeight, 3 }); - unitShadowSplat = this.terrain.splats.get(texture); - } + final String unitShadow = "Shadow"; + if ((unitShadow != null) && !"_".equals(unitShadow)) { + final String texture = "ReplaceableTextures\\Shadows\\" + unitShadow + ".blp"; + final float shadowX = 50; + final float shadowY = 50; + final float shadowWidth = 128; + final float shadowHeight = 128; + if (!this.terrain.splats.containsKey(texture)) { + final Splat splat = new Splat(); + splat.opacity = 0.5f; + this.terrain.splats.put(texture, splat); + } + final float x = unit.getLocation()[0] - shadowX; + final float y = unit.getLocation()[1] - shadowY; + this.terrain.splats.get(texture).locations + .add(new float[]{x, y, x + shadowWidth, y + shadowHeight, 3}); + unitShadowSplat = this.terrain.splats.get(texture); + } - path += ".mdx"; - } - } - else { - type = WorldEditorDataType.UNITS; - path = row.getFieldAsString(UNIT_FILE, 0); + path += ".mdx"; + } + } else { + type = WorldEditorDataType.UNITS; + path = row.getFieldAsString(UNIT_FILE, 0); - if (path.toLowerCase().endsWith(".mdl") || path.toLowerCase().endsWith(".mdx")) { - path = path.substring(0, path.length() - 4); - } - if ((row.readSLKTagInt("fileVerFlags") == 2) && this.dataSource.has(path + "_V1.mdx")) { - path += "_V1"; - } + if (path.toLowerCase().endsWith(".mdl") || path.toLowerCase().endsWith(".mdx")) { + path = path.substring(0, path.length() - 4); + } + if ((row.readSLKTagInt("fileVerFlags") == 2) && this.dataSource.has(path + "_V1.mdx")) { + path += "_V1"; + } - path += ".mdx"; + path += ".mdx"; - final String uberSplat = row.getFieldAsString(UBER_SPLAT, 0); - if (uberSplat != null) { - final Element uberSplatInfo = this.terrain.uberSplatTable.get(uberSplat); - if (uberSplatInfo != null) { - final String texturePath = uberSplatInfo.getField("Dir") + "\\" - + uberSplatInfo.getField("file") + ".blp"; - if (!this.terrain.splats.containsKey(texturePath)) { - this.terrain.splats.put(texturePath, new Splat()); - } - final float x = unit.getLocation()[0]; - final float y = unit.getLocation()[1]; - final float s = uberSplatInfo.getFieldFloatValue("Scale"); - this.terrain.splats.get(texturePath).locations - .add(new float[] { x - s, y - s, x + s, y + s, 1 }); - } - } + final String uberSplat = row.getFieldAsString(UBER_SPLAT, 0); + if (uberSplat != null) { + final Element uberSplatInfo = this.terrain.uberSplatTable.get(uberSplat); + if (uberSplatInfo != null) { + final String texturePath = uberSplatInfo.getField("Dir") + "\\" + + uberSplatInfo.getField("file") + ".blp"; + if (!this.terrain.splats.containsKey(texturePath)) { + this.terrain.splats.put(texturePath, new Splat()); + } + final float x = unit.getLocation()[0]; + final float y = unit.getLocation()[1]; + final float s = uberSplatInfo.getFieldFloatValue("Scale"); + this.terrain.splats.get(texturePath).locations + .add(new float[]{x - s, y - s, x + s, y + s, 1}); + } + } - final String unitShadow = row.getFieldAsString(UNIT_SHADOW, 0); - if ((unitShadow != null) && !"_".equals(unitShadow)) { - final String texture = "ReplaceableTextures\\Shadows\\" + unitShadow + ".blp"; - final float shadowX = row.getFieldAsFloat(UNIT_SHADOW_X, 0); - final float shadowY = row.getFieldAsFloat(UNIT_SHADOW_Y, 0); - final float shadowWidth = row.getFieldAsFloat(UNIT_SHADOW_W, 0); - final float shadowHeight = row.getFieldAsFloat(UNIT_SHADOW_H, 0); - if (this.mapMpq.has(texture)) { - if (!this.terrain.splats.containsKey(texture)) { - final Splat splat = new Splat(); - splat.opacity = 0.5f; - this.terrain.splats.put(texture, splat); - } - final float x = unit.getLocation()[0] - shadowX; - final float y = unit.getLocation()[1] - shadowY; - this.terrain.splats.get(texture).locations - .add(new float[] { x, y, x + shadowWidth, y + shadowHeight, 3 }); - unitShadowSplat = this.terrain.splats.get(texture); - } - } + final String unitShadow = row.getFieldAsString(UNIT_SHADOW, 0); + if ((unitShadow != null) && !"_".equals(unitShadow)) { + final String texture = "ReplaceableTextures\\Shadows\\" + unitShadow + ".blp"; + final float shadowX = row.getFieldAsFloat(UNIT_SHADOW_X, 0); + final float shadowY = row.getFieldAsFloat(UNIT_SHADOW_Y, 0); + final float shadowWidth = row.getFieldAsFloat(UNIT_SHADOW_W, 0); + final float shadowHeight = row.getFieldAsFloat(UNIT_SHADOW_H, 0); + if (this.mapMpq.has(texture)) { + if (!this.terrain.splats.containsKey(texture)) { + final Splat splat = new Splat(); + splat.opacity = 0.5f; + this.terrain.splats.put(texture, splat); + } + final float x = unit.getLocation()[0] - shadowX; + final float y = unit.getLocation()[1] - shadowY; + this.terrain.splats.get(texture).locations + .add(new float[]{x, y, x + shadowWidth, y + shadowHeight, 3}); + unitShadowSplat = this.terrain.splats.get(texture); + } + } - final String buildingShadow = row.getFieldAsString(BUILDING_SHADOW, 0); - if ((buildingShadow != null) && !"_".equals(buildingShadow)) { - this.terrain.addShadow(buildingShadow, unit.getLocation()[0], unit.getLocation()[1]); - } - final String pathingTexture = row.getFieldAsString(UNIT_PATHING, 0); - if ((pathingTexture != null) && (pathingTexture.length() > 0) && !"_".equals(pathingTexture)) { - buildingPathingPixelMap = this.filePathToPathingMap.get(pathingTexture.toLowerCase()); - if (buildingPathingPixelMap == null) { - if (pathingTexture.toLowerCase().endsWith(".tga")) { - buildingPathingPixelMap = TgaFile.readTGA(pathingTexture, - this.mapMpq.getResourceAsStream(pathingTexture)); - } - else { - try (InputStream stream = this.mapMpq.getResourceAsStream(pathingTexture)) { - buildingPathingPixelMap = ImageIO.read(stream); - System.out.println("LOADING BLP PATHING: " + pathingTexture); - } - } - this.filePathToPathingMap.put(pathingTexture.toLowerCase(), buildingPathingPixelMap); - } - this.terrain.pathingGrid.blitPathingOverlayTexture(unit.getLocation()[0], - unit.getLocation()[1], (int) Math.toDegrees(unit.getAngle()), - buildingPathingPixelMap); - } + final String buildingShadow = row.getFieldAsString(BUILDING_SHADOW, 0); + if ((buildingShadow != null) && !"_".equals(buildingShadow)) { + this.terrain.addShadow(buildingShadow, unit.getLocation()[0], unit.getLocation()[1]); + } + final String pathingTexture = row.getFieldAsString(UNIT_PATHING, 0); + if ((pathingTexture != null) && (pathingTexture.length() > 0) && !"_".equals(pathingTexture)) { + buildingPathingPixelMap = this.filePathToPathingMap.get(pathingTexture.toLowerCase()); + if (buildingPathingPixelMap == null) { + if (pathingTexture.toLowerCase().endsWith(".tga")) { + buildingPathingPixelMap = TgaFile.readTGA(pathingTexture, + this.mapMpq.getResourceAsStream(pathingTexture)); + } else { + try (InputStream stream = this.mapMpq.getResourceAsStream(pathingTexture)) { + buildingPathingPixelMap = ImageIO.read(stream); + System.out.println("LOADING BLP PATHING: " + pathingTexture); + } + } + this.filePathToPathingMap.put(pathingTexture.toLowerCase(), buildingPathingPixelMap); + } + this.terrain.pathingGrid.blitPathingOverlayTexture(unit.getLocation()[0], + unit.getLocation()[1], (int) Math.toDegrees(unit.getAngle()), + buildingPathingPixelMap); + } - final String soundName = row.getFieldAsString(UNIT_SOUNDSET, 0); - UnitSoundset unitSoundset = soundsetNameToSoundset.get(soundName); - if (unitSoundset == null) { - unitSoundset = new UnitSoundset(this.dataSource, this.unitAckSoundsTable, soundName); - soundsetNameToSoundset.put(soundName, unitSoundset); - } - soundset = unitSoundset; - } - } + final String soundName = row.getFieldAsString(UNIT_SOUNDSET, 0); + UnitSoundset unitSoundset = soundsetNameToSoundset.get(soundName); + if (unitSoundset == null) { + unitSoundset = new UnitSoundset(this.dataSource, this.unitAckSoundsTable, soundName); + soundsetNameToSoundset.put(soundName, unitSoundset); + } + soundset = unitSoundset; + } + } - if (path != null) { - final MdxModel model = (MdxModel) this.load(path, this.mapPathSolver, this.solverParams); - MdxModel portraitModel; - final String portraitPath = path.substring(0, path.length() - 4) + "_portrait.mdx"; - if (this.dataSource.has(portraitPath)) { - portraitModel = (MdxModel) this.load(portraitPath, this.mapPathSolver, this.solverParams); - } - else { - portraitModel = model; - } - if (type == WorldEditorDataType.UNITS) { - final float angle = (float) Math.toDegrees(unit.getAngle()); - final CUnit simulationUnit = this.simulation.createUnit(row.getAlias(), unit.getPlayer(), - unit.getLocation()[0], unit.getLocation()[1], angle, buildingPathingPixelMap); - final RenderUnitTypeData typeData = getUnitTypeData(unit.getId(), row); - final RenderUnit renderUnit = new RenderUnit(this, model, row, unit, soundset, portraitModel, - simulationUnit, typeData); - this.unitToRenderPeer.put(simulationUnit, renderUnit); - this.units.add(renderUnit); - if (unitShadowSplat != null) { - unitShadowSplat.unitMapping.add(new Consumer() { - @Override - public void accept(final SplatMover t) { - renderUnit.shadow = t; - } - }); - } - } - else { - this.items.add(new RenderItem(this, model, row, unit, soundset, portraitModel)); // TODO store - // somewhere - if (unitShadowSplat != null) { - unitShadowSplat.unitMapping.add(new Consumer() { - @Override - public void accept(final SplatMover t) { + if (path != null) { + final MdxModel model = (MdxModel) this.load(path, this.mapPathSolver, this.solverParams); + MdxModel portraitModel; + final String portraitPath = path.substring(0, path.length() - 4) + "_portrait.mdx"; + if (this.dataSource.has(portraitPath)) { + portraitModel = (MdxModel) this.load(portraitPath, this.mapPathSolver, this.solverParams); + } else { + portraitModel = model; + } + if (type == WorldEditorDataType.UNITS) { + final float angle = (float) Math.toDegrees(unit.getAngle()); + final CUnit simulationUnit = this.simulation.createUnit(row.getAlias(), unit.getPlayer(), + unit.getLocation()[0], unit.getLocation()[1], angle, buildingPathingPixelMap); + final RenderUnitTypeData typeData = getUnitTypeData(unit.getId(), row); + final RenderUnit renderUnit = new RenderUnit(this, model, row, unit, soundset, portraitModel, + simulationUnit, typeData); + this.unitToRenderPeer.put(simulationUnit, renderUnit); + this.units.add(renderUnit); + if (unitShadowSplat != null) { + unitShadowSplat.unitMapping.add(new Consumer() { + @Override + public void accept(final SplatMover t) { + renderUnit.shadow = t; + } + }); + } + } else { + this.items.add(new RenderItem(this, model, row, unit, soundset, portraitModel)); // TODO store + // somewhere + if (unitShadowSplat != null) { + unitShadowSplat.unitMapping.add(new Consumer() { + @Override + public void accept(final SplatMover t) { - } - }); - } - } - } - else { - System.err.println("Unknown unit ID: " + unit.getId()); - } - } - } + } + }); + } + } + } else { + System.err.println("Unknown unit ID: " + unit.getId()); + } + } + } - this.terrain.loadSplats(); + this.terrain.loadSplats(); - this.unitsReady = true; - this.anyReady = true; - } + this.unitsReady = true; + this.anyReady = true; + } - private RenderUnitTypeData getUnitTypeData(final War3ID key, final MutableGameObject row) { - RenderUnitTypeData unitTypeData = this.unitIdToTypeData.get(key); - if (unitTypeData == null) { - unitTypeData = new RenderUnitTypeData(row.getFieldAsFloat(MAX_PITCH, 0), row.getFieldAsFloat(MAX_ROLL, 0), - row.getFieldAsFloat(ELEVATION_SAMPLE_RADIUS, 0)); - this.unitIdToTypeData.put(key, unitTypeData); - } - return unitTypeData; - } + private RenderUnitTypeData getUnitTypeData(final War3ID key, final MutableGameObject row) { + RenderUnitTypeData unitTypeData = this.unitIdToTypeData.get(key); + if (unitTypeData == null) { + unitTypeData = new RenderUnitTypeData(row.getFieldAsFloat(MAX_PITCH, 0), row.getFieldAsFloat(MAX_ROLL, 0), + row.getFieldAsFloat(ELEVATION_SAMPLE_RADIUS, 0)); + this.unitIdToTypeData.put(key, unitTypeData); + } + return unitTypeData; + } - @Override - public void update() { - if (this.anyReady) { - this.terrain.update(); + @Override + public void update() { + if (this.anyReady) { + this.terrain.update(); - super.update(); + super.update(); - for (final RenderUnit unit : this.units) { - unit.updateAnimations(this); - } - final Iterator projectileIterator = this.projectiles.iterator(); - while (projectileIterator.hasNext()) { - final RenderEffect projectile = projectileIterator.next(); - if (projectile.updateAnimations(this, Gdx.graphics.getDeltaTime())) { - projectileIterator.remove(); - } - } - for (final RenderItem item : this.items) { - final MdxComplexInstance instance = item.instance; - final MdxComplexInstance mdxComplexInstance = instance; - if (mdxComplexInstance.sequenceEnded || (mdxComplexInstance.sequence == -1)) { - SequenceUtils.randomStandSequence(mdxComplexInstance); - } - } - for (final RenderDoodad item : this.doodads) { - final ModelInstance instance = item.instance; - if (instance instanceof MdxComplexInstance) { - final MdxComplexInstance mdxComplexInstance = (MdxComplexInstance) instance; - if ((mdxComplexInstance.sequence == -1) - || (mdxComplexInstance.sequenceEnded/* - * && (((MdxModel) mdxComplexInstance.model).sequences - * .get(mdxComplexInstance.sequence).getFlags() == 0) - */)) { - SequenceUtils.randomSequence(mdxComplexInstance, item.getAnimation(), SequenceUtils.EMPTY, - true); - } - } - } + for (final RenderUnit unit : this.units) { + unit.updateAnimations(this); + } + final Iterator projectileIterator = this.projectiles.iterator(); + while (projectileIterator.hasNext()) { + final RenderEffect projectile = projectileIterator.next(); + if (projectile.updateAnimations(this, Gdx.graphics.getDeltaTime())) { + projectileIterator.remove(); + } + } + for (final RenderItem item : this.items) { + final MdxComplexInstance instance = item.instance; + final MdxComplexInstance mdxComplexInstance = instance; + if (mdxComplexInstance.sequenceEnded || (mdxComplexInstance.sequence == -1)) { + SequenceUtils.randomStandSequence(mdxComplexInstance); + } + } + for (final RenderDoodad item : this.doodads) { + final ModelInstance instance = item.instance; + if (instance instanceof MdxComplexInstance) { + final MdxComplexInstance mdxComplexInstance = (MdxComplexInstance) instance; + if (mdxComplexInstance.sequence == -1 || + (mdxComplexInstance.sequenceEnded && (item.getAnimation() != AnimationTokens.PrimaryTag.DEATH || + (((MdxModel) mdxComplexInstance.model).sequences + .get(mdxComplexInstance.sequence).getFlags() == 0) + ))) { + SequenceUtils.randomSequence(mdxComplexInstance, item.getAnimation(), SequenceUtils.EMPTY, + true); - final float rawDeltaTime = Gdx.graphics.getRawDeltaTime(); - this.updateTime += rawDeltaTime; - while (this.updateTime >= WarsmashConstants.SIMULATION_STEP_TIME) { - this.updateTime -= WarsmashConstants.SIMULATION_STEP_TIME; - this.simulation.update(); - } - this.dncTerrain.setFrameByRatio( - this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours()); - this.dncTerrain.update(rawDeltaTime, null); - this.dncUnit.setFrameByRatio( - this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours()); - this.dncUnit.update(rawDeltaTime, null); - this.dncUnitDay.setFrameByRatio(0.5f); - this.dncUnitDay.update(rawDeltaTime, null); - this.dncTarget.setFrameByRatio( - this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours()); - this.dncTarget.update(rawDeltaTime, null); - } - } + } + } + } - @Override - public void render() { - if (this.anyReady) { - final Scene worldScene = this.worldScene; + final float rawDeltaTime = Gdx.graphics.getRawDeltaTime(); + this.updateTime += rawDeltaTime; + while (this.updateTime >= WarsmashConstants.SIMULATION_STEP_TIME) { + this.updateTime -= WarsmashConstants.SIMULATION_STEP_TIME; + this.simulation.update(); + } + this.dncTerrain.setFrameByRatio( + this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours()); + this.dncTerrain.update(rawDeltaTime, null); + this.dncUnit.setFrameByRatio( + this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours()); + this.dncUnit.update(rawDeltaTime, null); + this.dncUnitDay.setFrameByRatio(0.5f); + this.dncUnitDay.update(rawDeltaTime, null); + this.dncTarget.setFrameByRatio( + this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours()); + this.dncTarget.update(rawDeltaTime, null); + } + } - startFrame(); - worldScene.startFrame(); - worldScene.renderOpaque(this.dynamicShadowManager, this.webGL); - this.terrain.renderGround(this.dynamicShadowManager); - this.terrain.renderCliffs(); - worldScene.renderOpaque(); - this.terrain.renderUberSplats(); - this.terrain.renderWater(); - worldScene.renderTranslucent(); + @Override + public void render() { + if (this.anyReady) { + final Scene worldScene = this.worldScene; - final List scenes = this.scenes; - for (final Scene scene : scenes) { - if (scene != worldScene) { - scene.startFrame(); - scene.renderOpaque(); - scene.renderTranslucent(); - } - } - } - } + startFrame(); + worldScene.startFrame(); + worldScene.renderOpaque(this.dynamicShadowManager, this.webGL); + this.terrain.renderGround(this.dynamicShadowManager); + this.terrain.renderCliffs(); + worldScene.renderOpaque(); + this.terrain.renderUberSplats(); + this.terrain.renderWater(); + worldScene.renderTranslucent(); - public void deselect() { - if (!this.selModels.isEmpty()) { - for (final SplatModel model : this.selModels) { - this.terrain.removeSplatBatchModel(model); - } - this.selModels.clear(); - for (final RenderUnit unit : this.selected) { - unit.selectionCircle = null; - } - } - this.selected.clear(); - } + final List scenes = this.scenes; + for (final Scene scene : scenes) { + if (scene != worldScene) { + scene.startFrame(); + scene.renderOpaque(); + scene.renderTranslucent(); + } + } + } + } - public void doSelectUnit(final List units) { - deselect(); - if (units.isEmpty()) { - return; - } + public void deselect() { + if (!this.selModels.isEmpty()) { + for (final SplatModel model : this.selModels) { + this.terrain.removeSplatBatchModel(model); + } + this.selModels.clear(); + for (final RenderUnit unit : this.selected) { + unit.selectionCircle = null; + } + } + this.selected.clear(); + } - final Map splats = new HashMap(); - for (final RenderUnit unit : units) { - if (unit.row != null) { - if (unit.selectionScale > 0) { - final float selectionSize = unit.selectionScale * this.selectionCircleScaleFactor; - String path = null; - for (int i = 0; i < this.selectionCircleSizes.size(); i++) { - final SelectionCircleSize selectionCircleSize = this.selectionCircleSizes.get(i); - if ((selectionSize < selectionCircleSize.size) - || (i == (this.selectionCircleSizes.size() - 1))) { - path = selectionCircleSize.texture; - break; - } - } - if (!path.toLowerCase().endsWith(".blp")) { - path += ".blp"; - } - if (!splats.containsKey(path)) { - splats.put(path, new Splat()); - } - final float x = unit.location[0]; - final float y = unit.location[1]; - System.out.println("Selecting a unit at " + x + "," + y); - final float z = unit.row.getFieldAsFloat(UNIT_SELECT_HEIGHT, 0); - splats.get(path).locations.add(new float[] { x - (selectionSize / 2), y - (selectionSize / 2), - x + (selectionSize / 2), y + (selectionSize / 2), z + 5 }); - splats.get(path).unitMapping.add(new Consumer() { - @Override - public void accept(final SplatMover t) { - unit.selectionCircle = t; - } - }); - } - this.selected.add(unit); - } - } - this.selModels.clear(); - for (final Map.Entry entry : splats.entrySet()) { - final String path = entry.getKey(); - final Splat locations = entry.getValue(); - final SplatModel model = new SplatModel(Gdx.gl30, (Texture) load(path, PathSolver.DEFAULT, null), - locations.locations, this.terrain.centerOffset, locations.unitMapping, true); - model.color[0] = 0; - model.color[1] = 1; - model.color[2] = 0; - model.color[3] = 1; - this.selModels.add(model); - this.terrain.addSplatBatchModel(model); - } - } + public void doSelectUnit(final List units) { + deselect(); + if (units.isEmpty()) { + return; + } - public void getClickLocation(final Vector3 out, final int screenX, final int screenY) { - final float[] ray = rayHeap; - mousePosHeap.set(screenX, screenY); - this.worldScene.camera.screenToWorldRay(ray, mousePosHeap); - gdxRayHeap.set(ray[0], ray[1], ray[2], ray[3] - ray[0], ray[4] - ray[1], ray[5] - ray[2]); - gdxRayHeap.direction.nor();// needed for libgdx - RenderMathUtils.intersectRayTriangles(gdxRayHeap, this.terrain.softwareGroundMesh.vertices, - this.terrain.softwareGroundMesh.indices, 3, out); - rectangleHeap.set(Math.min(out.x, gdxRayHeap.origin.x), Math.min(out.y, gdxRayHeap.origin.y), - Math.abs(out.x - gdxRayHeap.origin.x), Math.abs(out.y - gdxRayHeap.origin.y)); - this.walkableObjectsTree.intersect(rectangleHeap, this.walkablesIntersectionFinder.reset(gdxRayHeap)); - if (this.walkablesIntersectionFinder.found) { - out.set(this.walkablesIntersectionFinder.intersection); - } - else { - out.z = Math.max(getWalkableRenderHeight(out.x, out.y), this.terrain.getGroundHeight(out.x, out.y)); - } - } + final Map splats = new HashMap(); + for (final RenderUnit unit : units) { + if (unit.row != null) { + if (unit.selectionScale > 0) { + final float selectionSize = unit.selectionScale * this.selectionCircleScaleFactor; + String path = null; + for (int i = 0; i < this.selectionCircleSizes.size(); i++) { + final SelectionCircleSize selectionCircleSize = this.selectionCircleSizes.get(i); + if ((selectionSize < selectionCircleSize.size) + || (i == (this.selectionCircleSizes.size() - 1))) { + path = selectionCircleSize.texture; + break; + } + } + if (!path.toLowerCase().endsWith(".blp")) { + path += ".blp"; + } + if (!splats.containsKey(path)) { + splats.put(path, new Splat()); + } + final float x = unit.location[0]; + final float y = unit.location[1]; + System.out.println("Selecting a unit at " + x + "," + y); + final float z = unit.row.getFieldAsFloat(UNIT_SELECT_HEIGHT, 0); + splats.get(path).locations.add(new float[]{x - (selectionSize / 2), y - (selectionSize / 2), + x + (selectionSize / 2), y + (selectionSize / 2), z + 5}); + splats.get(path).unitMapping.add(new Consumer() { + @Override + public void accept(final SplatMover t) { + unit.selectionCircle = t; + } + }); + } + this.selected.add(unit); + } + } + this.selModels.clear(); + for (final Map.Entry entry : splats.entrySet()) { + final String path = entry.getKey(); + final Splat locations = entry.getValue(); + final SplatModel model = new SplatModel(Gdx.gl30, (Texture) load(path, PathSolver.DEFAULT, null), + locations.locations, this.terrain.centerOffset, locations.unitMapping, true); + model.color[0] = 0; + model.color[1] = 1; + model.color[2] = 0; + model.color[3] = 1; + this.selModels.add(model); + this.terrain.addSplatBatchModel(model); + } + } - public void showConfirmation(final Vector3 position, final float red, final float green, final float blue) { - this.confirmationInstance.show(); - this.confirmationInstance.setSequence(0); - this.confirmationInstance.setLocation(position); - this.worldScene.instanceMoved(this.confirmationInstance, position.x, position.y); - this.confirmationInstance.vertexColor[0] = red; - this.confirmationInstance.vertexColor[1] = green; - this.confirmationInstance.vertexColor[2] = blue; - } + public void getClickLocation(final Vector3 out, final int screenX, final int screenY) { + final float[] ray = rayHeap; + mousePosHeap.set(screenX, screenY); + this.worldScene.camera.screenToWorldRay(ray, mousePosHeap); + gdxRayHeap.set(ray[0], ray[1], ray[2], ray[3] - ray[0], ray[4] - ray[1], ray[5] - ray[2]); + gdxRayHeap.direction.nor();// needed for libgdx + RenderMathUtils.intersectRayTriangles(gdxRayHeap, this.terrain.softwareGroundMesh.vertices, + this.terrain.softwareGroundMesh.indices, 3, out); + rectangleHeap.set(Math.min(out.x, gdxRayHeap.origin.x), Math.min(out.y, gdxRayHeap.origin.y), + Math.abs(out.x - gdxRayHeap.origin.x), Math.abs(out.y - gdxRayHeap.origin.y)); + this.walkableObjectsTree.intersect(rectangleHeap, this.walkablesIntersectionFinder.reset(gdxRayHeap)); + if (this.walkablesIntersectionFinder.found) { + out.set(this.walkablesIntersectionFinder.intersection); + } else { + out.z = Math.max(getWalkableRenderHeight(out.x, out.y), this.terrain.getGroundHeight(out.x, out.y)); + } + } - public List selectUnit(final float x, final float y, final boolean toggle) { - System.out.println("world: " + x + "," + y); - final float[] ray = rayHeap; - mousePosHeap.set(x, y); - this.worldScene.camera.screenToWorldRay(ray, mousePosHeap); - gdxRayHeap.set(ray[0], ray[1], ray[2], ray[3] - ray[0], ray[4] - ray[1], ray[5] - ray[2]); - gdxRayHeap.direction.nor();// needed for libgdx + public void showConfirmation(final Vector3 position, final float red, final float green, final float blue) { + this.confirmationInstance.show(); + this.confirmationInstance.setSequence(0); + this.confirmationInstance.setLocation(position); + this.worldScene.instanceMoved(this.confirmationInstance, position.x, position.y); + this.confirmationInstance.vertexColor[0] = red; + this.confirmationInstance.vertexColor[1] = green; + this.confirmationInstance.vertexColor[2] = blue; + } - RenderUnit entity = null; - for (final RenderUnit unit : this.units) { - final MdxComplexInstance instance = unit.instance; - if (instance.isVisible(this.worldScene.camera) - && instance.intersectRayWithCollision(gdxRayHeap, intersectionHeap, - unit.getSimulationUnit().getUnitType().isBuilding(), false) - && !unit.getSimulationUnit().isDead()) { - if ((entity == null) || (entity.instance.depth > instance.depth)) { - entity = unit; - } - } - } - List sel; - if (entity != null) { - if (toggle) { - sel = new ArrayList<>(this.selected); - final int idx = sel.indexOf(entity); - if (idx >= 0) { - sel.remove(idx); - } - else { - sel.add(entity); - } - } - else { - sel = Arrays.asList(entity); - } - } - else { - sel = Collections.emptyList(); - } - this.doSelectUnit(sel); - return sel; - } + public List selectUnit(final float x, final float y, final boolean toggle) { + System.out.println("world: " + x + "," + y); + final float[] ray = rayHeap; + mousePosHeap.set(x, y); + this.worldScene.camera.screenToWorldRay(ray, mousePosHeap); + gdxRayHeap.set(ray[0], ray[1], ray[2], ray[3] - ray[0], ray[4] - ray[1], ray[5] - ray[2]); + gdxRayHeap.direction.nor();// needed for libgdx - public RenderUnit rayPickUnit(final float x, final float y) { - return rayPickUnit(x, y, CUnitFilterFunction.ACCEPT_ALL); - } + RenderUnit entity = null; + for (final RenderUnit unit : this.units) { + final MdxComplexInstance instance = unit.instance; + if (instance.isVisible(this.worldScene.camera) + && instance.intersectRayWithCollision(gdxRayHeap, intersectionHeap, + unit.getSimulationUnit().getUnitType().isBuilding(), false) + && !unit.getSimulationUnit().isDead()) { + if ((entity == null) || (entity.instance.depth > instance.depth)) { + entity = unit; + } + } + } + List sel; + if (entity != null) { + if (toggle) { + sel = new ArrayList<>(this.selected); + final int idx = sel.indexOf(entity); + if (idx >= 0) { + sel.remove(idx); + } else { + sel.add(entity); + } + } else { + sel = Arrays.asList(entity); + } + } else { + sel = Collections.emptyList(); + } + this.doSelectUnit(sel); + return sel; + } - public RenderUnit rayPickUnit(final float x, final float y, final CUnitFilterFunction filter) { - final float[] ray = rayHeap; - mousePosHeap.set(x, y); - this.worldScene.camera.screenToWorldRay(ray, mousePosHeap); - gdxRayHeap.set(ray[0], ray[1], ray[2], ray[3] - ray[0], ray[4] - ray[1], ray[5] - ray[2]); - gdxRayHeap.direction.nor();// needed for libgdx + public RenderUnit rayPickUnit(final float x, final float y) { + return rayPickUnit(x, y, CUnitFilterFunction.ACCEPT_ALL); + } - RenderUnit entity = null; - for (final RenderUnit unit : this.units) { - final MdxComplexInstance instance = unit.instance; - if (instance.isVisible(this.worldScene.camera) && instance.intersectRayWithCollision(gdxRayHeap, - intersectionHeap, unit.getSimulationUnit().getUnitType().isBuilding(), false)) { - if (filter.call(unit.getSimulationUnit())) { - if ((entity == null) || (entity.instance.depth > instance.depth)) { - entity = unit; - } - } - } - } - return entity; - } + public RenderUnit rayPickUnit(final float x, final float y, final CUnitFilterFunction filter) { + final float[] ray = rayHeap; + mousePosHeap.set(x, y); + this.worldScene.camera.screenToWorldRay(ray, mousePosHeap); + gdxRayHeap.set(ray[0], ray[1], ray[2], ray[3] - ray[0], ray[4] - ray[1], ray[5] - ray[2]); + gdxRayHeap.direction.nor();// needed for libgdx - private static final class MappedDataCallbackImplementation implements LoadGenericCallback { - @Override - public Object call(final InputStream data) { - final StringBuilder stringBuilder = new StringBuilder(); - try (BufferedReader reader = new BufferedReader(new InputStreamReader(data, "utf-8"))) { - String line; - while ((line = reader.readLine()) != null) { - stringBuilder.append(line); - stringBuilder.append("\n"); - } - } - catch (final UnsupportedEncodingException e) { - throw new RuntimeException(e); - } - catch (final IOException e) { - throw new RuntimeException(e); - } - return new MappedData(stringBuilder.toString()); - } - } + RenderUnit entity = null; + for (final RenderUnit unit : this.units) { + final MdxComplexInstance instance = unit.instance; + if (instance.isVisible(this.worldScene.camera) && instance.intersectRayWithCollision(gdxRayHeap, + intersectionHeap, unit.getSimulationUnit().getUnitType().isBuilding(), false)) { + if (filter.call(unit.getSimulationUnit())) { + if ((entity == null) || (entity.instance.depth > instance.depth)) { + entity = unit; + } + } + } + } + return entity; + } - private static final class StringDataCallbackImplementation implements LoadGenericCallback { - @Override - public Object call(final InputStream data) { - if (data == null) { - System.err.println("data null"); - } - final StringBuilder stringBuilder = new StringBuilder(); - try (BufferedReader reader = new BufferedReader(new InputStreamReader(data, "utf-8"))) { - String line; - while ((line = reader.readLine()) != null) { - stringBuilder.append(line); - stringBuilder.append("\n"); - } - } - catch (final UnsupportedEncodingException e) { - throw new RuntimeException(e); - } - catch (final IOException e) { - throw new RuntimeException(e); - } - return stringBuilder.toString(); - } - } + private static final class MappedDataCallbackImplementation implements LoadGenericCallback { + @Override + public Object call(final InputStream data) { + final StringBuilder stringBuilder = new StringBuilder(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(data, "utf-8"))) { + String line; + while ((line = reader.readLine()) != null) { + stringBuilder.append(line); + stringBuilder.append("\n"); + } + } catch (final UnsupportedEncodingException e) { + throw new RuntimeException(e); + } catch (final IOException e) { + throw new RuntimeException(e); + } + return new MappedData(stringBuilder.toString()); + } + } - private static final class StreamDataCallbackImplementation implements LoadGenericCallback { - @Override - public Object call(final InputStream data) { - return data; - } - } + private static final class StringDataCallbackImplementation implements LoadGenericCallback { + @Override + public Object call(final InputStream data) { + if (data == null) { + System.err.println("data null"); + } + final StringBuilder stringBuilder = new StringBuilder(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(data, "utf-8"))) { + String line; + while ((line = reader.readLine()) != null) { + stringBuilder.append(line); + stringBuilder.append("\n"); + } + } catch (final UnsupportedEncodingException e) { + throw new RuntimeException(e); + } catch (final IOException e) { + throw new RuntimeException(e); + } + return stringBuilder.toString(); + } + } - public static final class SolverParams { - public char tileset; - public boolean reforged; - public boolean hd; - } + private static final class StreamDataCallbackImplementation implements LoadGenericCallback { + @Override + public Object call(final InputStream data) { + return data; + } + } - public static final class CliffInfo { - public List locations = new ArrayList<>(); - public List textures = new ArrayList<>(); - } + public static final class SolverParams { + public char tileset; + public boolean reforged; + public boolean hd; + } - private static final int MAXIMUM_ACCEPTED = 1 << 30; - private float selectionCircleScaleFactor; - private DataTable worldEditData; - private WorldEditStrings worldEditStrings; - private Warcraft3MapObjectData allObjectData; - private AbilityDataUI abilityDataUI; + public static final class CliffInfo { + public List locations = new ArrayList<>(); + public List textures = new ArrayList<>(); + } - /** - * Returns a power of two size for the given target capacity. - */ - private static final int pow2GreaterThan(final int capacity) { - int numElements = capacity - 1; - numElements |= numElements >>> 1; - numElements |= numElements >>> 2; - numElements |= numElements >>> 4; - numElements |= numElements >>> 8; - numElements |= numElements >>> 16; - return (numElements < 0) ? 1 : (numElements >= MAXIMUM_ACCEPTED) ? MAXIMUM_ACCEPTED : numElements + 1; - } + private static final int MAXIMUM_ACCEPTED = 1 << 30; + private float selectionCircleScaleFactor; + private DataTable worldEditData; + private WorldEditStrings worldEditStrings; + private Warcraft3MapObjectData allObjectData; + private AbilityDataUI abilityDataUI; - public void standOnRepeat(final MdxComplexInstance instance) { - instance.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); - SequenceUtils.randomStandSequence(instance); - } + /** + * Returns a power of two size for the given target capacity. + */ + private static final int pow2GreaterThan(final int capacity) { + int numElements = capacity - 1; + numElements |= numElements >>> 1; + numElements |= numElements >>> 2; + numElements |= numElements >>> 4; + numElements |= numElements >>> 8; + numElements |= numElements >>> 16; + return (numElements < 0) ? 1 : (numElements >= MAXIMUM_ACCEPTED) ? MAXIMUM_ACCEPTED : numElements + 1; + } - private static final class SelectionCircleSize { - private final float size; - private final String texture; - private final String textureDotted; + public void standOnRepeat(final MdxComplexInstance instance) { + instance.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + SequenceUtils.randomStandSequence(instance); + } - public SelectionCircleSize(final float size, final String texture, final String textureDotted) { - this.size = size; - this.texture = texture; - this.textureDotted = textureDotted; - } - } + private static final class SelectionCircleSize { + private final float size; + private final String texture; + private final String textureDotted; - public void setDayNightModels(final String terrainDNCFile, final String unitDNCFile) { - final MdxModel terrainDNCModel = (MdxModel) load(mdx(terrainDNCFile), PathSolver.DEFAULT, null); - this.dncTerrain = (MdxComplexInstance) terrainDNCModel.addInstance(); - this.dncTerrain.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); - this.dncTerrain.setSequence(0); - final MdxModel unitDNCModel = (MdxModel) load(mdx(unitDNCFile), PathSolver.DEFAULT, null); - this.dncUnit = (MdxComplexInstance) unitDNCModel.addInstance(); - this.dncUnit.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); - this.dncUnit.setSequence(0); - this.dncUnitDay = (MdxComplexInstance) unitDNCModel.addInstance(); - this.dncUnitDay.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); - this.dncUnitDay.setSequence(0); - final MdxModel targetDNCModel = (MdxModel) load( - mdx("Environment\\DNC\\DNCLordaeron\\DNCLordaeronTarget\\DNCLordaeronTarget.mdl"), PathSolver.DEFAULT, - null); - this.dncTarget = (MdxComplexInstance) targetDNCModel.addInstance(); - this.dncTarget.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); - this.dncTarget.setSequence(0); - } + public SelectionCircleSize(final float size, final String texture, final String textureDotted) { + this.size = size; + this.texture = texture; + this.textureDotted = textureDotted; + } + } - private static String mdx(String mdxPath) { - if (mdxPath.toLowerCase().endsWith(".mdl")) { - mdxPath = mdxPath.substring(0, mdxPath.length() - 4); - } - if (!mdxPath.toLowerCase().endsWith(".mdx")) { - mdxPath += ".mdx"; - } - return mdxPath; - } + public void setDayNightModels(final String terrainDNCFile, final String unitDNCFile) { + final MdxModel terrainDNCModel = (MdxModel) load(mdx(terrainDNCFile), PathSolver.DEFAULT, null); + this.dncTerrain = (MdxComplexInstance) terrainDNCModel.addInstance(); + this.dncTerrain.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + this.dncTerrain.setSequence(0); + final MdxModel unitDNCModel = (MdxModel) load(mdx(unitDNCFile), PathSolver.DEFAULT, null); + this.dncUnit = (MdxComplexInstance) unitDNCModel.addInstance(); + this.dncUnit.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + this.dncUnit.setSequence(0); + this.dncUnitDay = (MdxComplexInstance) unitDNCModel.addInstance(); + this.dncUnitDay.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + this.dncUnitDay.setSequence(0); + final MdxModel targetDNCModel = (MdxModel) load( + mdx("Environment\\DNC\\DNCLordaeron\\DNCLordaeronTarget\\DNCLordaeronTarget.mdl"), PathSolver.DEFAULT, + null); + this.dncTarget = (MdxComplexInstance) targetDNCModel.addInstance(); + this.dncTarget.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + this.dncTarget.setSequence(0); + } - @Override - public SceneLightManager createLightManager(final boolean simple) { - if (simple) { - return new W3xScenePortraitLightManager(this, this.lightDirection); - } - else { - return new W3xSceneWorldLightManager(this); - } - } + private static String mdx(String mdxPath) { + if (mdxPath.toLowerCase().endsWith(".mdl")) { + mdxPath = mdxPath.substring(0, mdxPath.length() - 4); + } + if (!mdxPath.toLowerCase().endsWith(".mdx")) { + mdxPath += ".mdx"; + } + return mdxPath; + } - public WorldEditStrings getWorldEditStrings() { - return this.worldEditStrings; - } + @Override + public SceneLightManager createLightManager(final boolean simple) { + if (simple) { + return new W3xScenePortraitLightManager(this, this.lightDirection); + } else { + return new W3xSceneWorldLightManager(this); + } + } - public void setGameUI(final GameUI gameUI) { - this.gameUI = gameUI; - this.abilityDataUI = new AbilityDataUI(this.allObjectData.getAbilities(), gameUI); - } + public WorldEditStrings getWorldEditStrings() { + return this.worldEditStrings; + } - public GameUI getGameUI() { - return this.gameUI; - } + public void setGameUI(final GameUI gameUI) { + this.gameUI = gameUI; + this.abilityDataUI = new AbilityDataUI(this.allObjectData.getAbilities(), gameUI); + } - public AbilityDataUI getAbilityDataUI() { - return this.abilityDataUI; - } + public GameUI getGameUI() { + return this.gameUI; + } - public float getWalkableRenderHeight(final float x, final float y) { - this.walkableObjectsTree.intersect(x, y, this.walkablesIntersector.reset(x, y)); - return this.walkablesIntersector.z; - } + public AbilityDataUI getAbilityDataUI() { + return this.abilityDataUI; + } - public MdxComplexInstance getHighestWalkableUnder(final float x, final float y) { - this.walkableObjectsTree.intersect(x, y, this.intersectorFindsHighestWalkable.reset(x, y)); - return this.intersectorFindsHighestWalkable.highestInstance; - } + public float getWalkableRenderHeight(final float x, final float y) { + this.walkableObjectsTree.intersect(x, y, this.walkablesIntersector.reset(x, y)); + return this.walkablesIntersector.z; + } - private static final class QuadtreeIntersectorFindsWalkableRenderHeight - implements QuadtreeIntersector { - private float z; - private final Ray ray = new Ray(); - private final Vector3 intersection = new Vector3(); + public MdxComplexInstance getHighestWalkableUnder(final float x, final float y) { + this.walkableObjectsTree.intersect(x, y, this.intersectorFindsHighestWalkable.reset(x, y)); + return this.intersectorFindsHighestWalkable.highestInstance; + } - private QuadtreeIntersectorFindsWalkableRenderHeight reset(final float x, final float y) { - this.z = -Float.MAX_VALUE; - this.ray.set(x, y, 4096, 0, 0, -8192); - return this; - } + private static final class QuadtreeIntersectorFindsWalkableRenderHeight + implements QuadtreeIntersector { + private float z; + private final Ray ray = new Ray(); + private final Vector3 intersection = new Vector3(); - @Override - public boolean onIntersect(final MdxComplexInstance intersectingObject) { - if (intersectingObject.intersectRayWithCollision(this.ray, this.intersection, true, true)) { - this.z = Math.max(this.z, this.intersection.z); - } - return false; - } - } + private QuadtreeIntersectorFindsWalkableRenderHeight reset(final float x, final float y) { + this.z = -Float.MAX_VALUE; + this.ray.set(x, y, 4096, 0, 0, -8192); + return this; + } - private static final class QuadtreeIntersectorFindsHighestWalkable - implements QuadtreeIntersector { - private float z; - private final Ray ray = new Ray(); - private final Vector3 intersection = new Vector3(); - private MdxComplexInstance highestInstance; + @Override + public boolean onIntersect(final MdxComplexInstance intersectingObject) { + if (intersectingObject.intersectRayWithCollision(this.ray, this.intersection, true, true)) { + this.z = Math.max(this.z, this.intersection.z); + } + return false; + } + } - private QuadtreeIntersectorFindsHighestWalkable reset(final float x, final float y) { - this.z = -Float.MAX_VALUE; - this.ray.set(x, y, 4096, 0, 0, -8192); - this.highestInstance = null; - return this; - } + private static final class QuadtreeIntersectorFindsHighestWalkable + implements QuadtreeIntersector { + private float z; + private final Ray ray = new Ray(); + private final Vector3 intersection = new Vector3(); + private MdxComplexInstance highestInstance; - @Override - public boolean onIntersect(final MdxComplexInstance intersectingObject) { - if (intersectingObject.intersectRayWithCollision(this.ray, this.intersection, true, true)) { - if (this.intersection.z > this.z) { - this.z = this.intersection.z; - this.highestInstance = intersectingObject; - } - } - return false; - } - } + private QuadtreeIntersectorFindsHighestWalkable reset(final float x, final float y) { + this.z = -Float.MAX_VALUE; + this.ray.set(x, y, 4096, 0, 0, -8192); + this.highestInstance = null; + return this; + } - private static final class QuadtreeIntersectorFindsHitPoint implements QuadtreeIntersector { - private Ray ray; - private final Vector3 intersection = new Vector3(); - private boolean found; + @Override + public boolean onIntersect(final MdxComplexInstance intersectingObject) { + if (intersectingObject.intersectRayWithCollision(this.ray, this.intersection, true, true)) { + if (this.intersection.z > this.z) { + this.z = this.intersection.z; + this.highestInstance = intersectingObject; + } + } + return false; + } + } - private QuadtreeIntersectorFindsHitPoint reset(final Ray ray) { - this.ray = ray; - this.found = false; - return this; - } + private static final class QuadtreeIntersectorFindsHitPoint implements QuadtreeIntersector { + private Ray ray; + private final Vector3 intersection = new Vector3(); + private boolean found; - @Override - public boolean onIntersect(final MdxComplexInstance intersectingObject) { - if (intersectingObject.intersectRayWithCollision(this.ray, this.intersection, true, true)) { - this.found = true; - return true; - } - return false; - } - } + private QuadtreeIntersectorFindsHitPoint reset(final Ray ray) { + this.ray = ray; + this.found = false; + return this; + } + + @Override + public boolean onIntersect(final MdxComplexInstance intersectingObject) { + if (intersectingObject.intersectRayWithCollision(this.ray, this.intersection, true, true)) { + this.found = true; + return true; + } + return false; + } + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/TerrainShaders.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/TerrainShaders.java index 4509e1e..7164836 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/TerrainShaders.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/TerrainShaders.java @@ -61,7 +61,7 @@ public class TerrainShaders { " vec3 terrain_normal = normalize(vNormal);//vec3(hL - hR, hD - hU, 2.0)+vNormal);\r\n" + // "\r\n" + // " Normal = terrain_normal;\r\n" + // - Shaders.lightSystem("Normal", "myposition.xyz", "lightTexture", "lightTextureHeight", "lightCount", + Shaders.lightSystem("terrain_normal", "myposition.xyz", "lightTexture", "lightTextureHeight", "lightCount", true) + "\r\n" + // " shadeColor = clamp(lightFactor, 0.0, 1.0);\r\n" + // diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java index 12704ea..1afb33c 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java @@ -194,9 +194,6 @@ public class RenderUnit { } else { currentWalkableUnder = map.getHighestWalkableUnder(x, y); - if (currentWalkableUnder != null) { - System.out.println("WALKABLE UNDER"); - } War3MapViewer.gdxRayHeap.set(x, y, 4096, 0, 0, -8192); if ((currentWalkableUnder != null) && currentWalkableUnder.intersectRayWithCollision(War3MapViewer.gdxRayHeap, diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java index eac054d..e4544bc 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java @@ -13,75 +13,73 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityV import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; public class CommandCardPopulatingAbilityVisitor implements CAbilityVisitor { - public static final CommandCardPopulatingAbilityVisitor INSTANCE = new CommandCardPopulatingAbilityVisitor(); + public static final CommandCardPopulatingAbilityVisitor INSTANCE = new CommandCardPopulatingAbilityVisitor(); - private CSimulation game; - private CUnit unit; + private CSimulation game; + private CUnit unit; - private CommandButtonListener commandButtonListener; - private AbilityDataUI abilityDataUI; - private boolean hasStop; + private CommandButtonListener commandButtonListener; + private AbilityDataUI abilityDataUI; + private boolean hasStop; - public CommandCardPopulatingAbilityVisitor reset(final CSimulation game, final CUnit unit, - final CommandButtonListener commandButtonListener, final AbilityDataUI abilityDataUI) { - this.game = game; - this.unit = unit; - this.commandButtonListener = commandButtonListener; - this.abilityDataUI = abilityDataUI; - this.hasStop = false; - return this; - } + public CommandCardPopulatingAbilityVisitor reset(final CSimulation game, final CUnit unit, + final CommandButtonListener commandButtonListener, final AbilityDataUI abilityDataUI) { + this.game = game; + this.unit = unit; + this.commandButtonListener = commandButtonListener; + this.abilityDataUI = abilityDataUI; + this.hasStop = false; + return this; + } - @Override - public Void accept(final CAbilityAttack ability) { - addCommandButton(ability, this.abilityDataUI.getAttackUI(), ability.getHandleId(), OrderIds.attack, 0, false); - if (!this.hasStop) { - this.hasStop = true; - addCommandButton(null, this.abilityDataUI.getStopUI(), 0, OrderIds.stop, 0, false); - } - return null; - } + @Override + public Void accept(final CAbilityAttack ability) { + addCommandButton(ability, this.abilityDataUI.getAttackUI(), ability.getHandleId(), OrderIds.attack, 0, false); + if (!this.hasStop) { + this.hasStop = true; + addCommandButton(null, this.abilityDataUI.getStopUI(), 0, OrderIds.stop, 0, false); + } + return null; + } - @Override - public Void accept(final CAbilityMove ability) { - addCommandButton(ability, this.abilityDataUI.getMoveUI(), ability.getHandleId(), OrderIds.move, 0, false); - addCommandButton(ability, this.abilityDataUI.getHoldPosUI(), ability.getHandleId(), OrderIds.holdposition, 0, - false); - addCommandButton(ability, this.abilityDataUI.getPatrolUI(), ability.getHandleId(), OrderIds.patrol, 0, false); - if (!this.hasStop) { - this.hasStop = true; - addCommandButton(null, this.abilityDataUI.getStopUI(), 0, OrderIds.stop, 0, false); - } - return null; - } + @Override + public Void accept(final CAbilityMove ability) { + addCommandButton(ability, this.abilityDataUI.getMoveUI(), ability.getHandleId(), OrderIds.move, 0, false); + addCommandButton(ability, this.abilityDataUI.getHoldPosUI(), ability.getHandleId(), OrderIds.holdposition, 0, + false); + addCommandButton(ability, this.abilityDataUI.getPatrolUI(), ability.getHandleId(), OrderIds.patrol, 0, false); + if (!this.hasStop) { + this.hasStop = true; + addCommandButton(null, this.abilityDataUI.getStopUI(), 0, OrderIds.stop, 0, false); + } + return null; + } - @Override - public Void accept(final CAbilityGeneric ability) { - addCommandButton(ability, this.abilityDataUI.getUI(ability.getRawcode()).getOnIconUI(), ability.getHandleId(), - OrderIds.channel, 0, false); - return null; - } + @Override + public Void accept(final CAbilityGeneric ability) { + addCommandButton(ability, this.abilityDataUI.getUI(ability.getRawcode()).getOnIconUI(), ability.getHandleId(), + OrderIds.channel, 0, false); + return null; + } - @Override - public Void accept(final CAbilityColdArrows ability) { - final boolean autoCastActive = ability.isAutoCastActive(); - int autoCastId; - if (autoCastActive) { - autoCastId = OrderIds.coldarrows; - } - else { - autoCastId = OrderIds.uncoldarrows; - } - addCommandButton(ability, this.abilityDataUI.getUI(ability.getRawcode()).getOnIconUI(), ability.getHandleId(), - OrderIds.coldarrowstarg, autoCastId, autoCastActive); - return null; - } + @Override + public Void accept(final CAbilityColdArrows ability) { + final boolean autoCastActive = ability.isAutoCastActive(); + int autoCastId; + if (autoCastActive) { + autoCastId = OrderIds.coldarrows; + } else { + autoCastId = OrderIds.uncoldarrows; + } + addCommandButton(ability, this.abilityDataUI.getUI(ability.getRawcode()).getOnIconUI(), ability.getHandleId(), + OrderIds.coldarrowstarg, autoCastId, autoCastActive); + return null; + } - private void addCommandButton(final CAbility ability, final IconUI iconUI, final int handleId, final int orderId, - final int autoCastOrderId, final boolean autoCastActive) { - final boolean active = ((handleId == this.unit.getCurrentAbilityHandleId()) - && (orderId == this.unit.getCurrentOrderId())); - this.commandButtonListener.commandButton(iconUI.getButtonPositionX(), iconUI.getButtonPositionY(), - iconUI.getIcon(), handleId, orderId, autoCastOrderId, active, autoCastActive); - } + private void addCommandButton(final CAbility ability, final IconUI iconUI, final int handleId, final int orderId, + final int autoCastOrderId, final boolean autoCastActive) { + final boolean active = (this.unit.getCurrentBehavior() != null && orderId == this.unit.getCurrentBehavior().getHighlightOrderId()); + this.commandButtonListener.commandButton(iconUI.getButtonPositionX(), iconUI.getButtonPositionY(), + iconUI.getIcon(), handleId, orderId, autoCastOrderId, active, autoCastActive); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java index 75de310..3392af1 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java @@ -6,6 +6,7 @@ import java.util.EnumSet; import java.util.LinkedList; import java.util.List; import java.util.Queue; +import java.util.concurrent.CyclicBarrier; import com.badlogic.gdx.math.Rectangle; import com.etheller.warsmash.util.War3ID; @@ -200,7 +201,11 @@ public class CUnit extends CWidget { } } else if (this.currentBehavior != null) { + CBehavior lastBehavior = this.currentBehavior; this.currentBehavior = this.currentBehavior.update(game); + if(this.currentBehavior.getHighlightOrderId() != lastBehavior.getHighlightOrderId()) { + this.stateNotifier.ordersChanged(getCurrentAbilityHandleId(), getCurrentOrderId()); + } } else { // check to auto acquire targets @@ -243,15 +248,15 @@ public class CUnit extends CWidget { else { this.currentBehavior = beginOrder(game, order); this.orderQueue.clear(); + final boolean omitNotify = (this.currentOrder == null) && (order == null); + if (!omitNotify) { + this.stateNotifier.ordersChanged(getCurrentAbilityHandleId(), getCurrentOrderId()); + } } } private CBehavior beginOrder(final CSimulation game, final COrder order) { - final boolean omitNotify = (this.currentOrder == null) && (order == null); this.currentOrder = order; - if (!omitNotify) { - this.stateNotifier.ordersChanged(getCurrentAbilityHandleId(), getCurrentOrderId()); - } CBehavior nextBehavior; if (order != null) { nextBehavior = order.begin(game, this); @@ -426,7 +431,7 @@ public class CUnit extends CWidget { CAllianceType.PASSIVE)) { for (final CUnitAttack attack : this.unitType.getAttacks()) { if (source.canBeTargetedBy(simulation, this, attack.getTargetsAllowed())) { - this.currentBehavior = getAttackBehavior().reset(attack, source); + this.currentBehavior = getAttackBehavior().reset(OrderIds.attack, attack, source); break; } } @@ -590,7 +595,7 @@ public class CUnit extends CWidget { if (this.source.canReach(unit, this.source.acquisitionRange) && unit.canBeTargetedBy(this.game, this.source, attack.getTargetsAllowed()) && (this.source.distance(unit) >= this.source.getUnitType().getMinimumAttackRange())) { - this.source.currentBehavior = this.source.getAttackBehavior().reset(attack, unit); + this.source.currentBehavior = this.source.getAttackBehavior().reset(OrderIds.attack, attack, unit); return true; } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java index f7e57d8..a58c30f 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java @@ -109,12 +109,12 @@ public class CAbilityAttack implements CAbility { CBehavior behavior = null; for (final CUnitAttack attack : caster.getUnitType().getAttacks()) { if (target.canBeTargetedBy(game, caster, attack.getTargetsAllowed())) { - behavior = caster.getAttackBehavior().reset(attack, target); + behavior = caster.getAttackBehavior().reset(OrderIds.attack, attack, target); break; } } if (behavior == null) { - behavior = caster.getMoveBehavior().reset(target.getX(), target.getY()); + behavior = caster.getMoveBehavior().reset(OrderIds.attack, target.getX(), target.getY()); } return behavior; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityColdArrows.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityColdArrows.java index 5efb110..61152bf 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityColdArrows.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityColdArrows.java @@ -94,7 +94,7 @@ public class CAbilityColdArrows implements CAbility { CBehavior behavior = null; for (final CUnitAttack attack : caster.getUnitType().getAttacks()) { if (target.canBeTargetedBy(game, caster, attack.getTargetsAllowed())) { - behavior = caster.getAttackBehavior().reset(attack, target); + behavior = caster.getAttackBehavior().reset(OrderIds.coldarrowstarg, attack, target); break; } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityMove.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityMove.java index 8c34b8e..fb1fc05 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityMove.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityMove.java @@ -87,7 +87,7 @@ public class CAbilityMove implements CAbility { @Override public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final CWidget target) { - return caster.getFollowBehavior().reset((CUnit) target); + return caster.getFollowBehavior().reset(OrderIds.move, (CUnit) target); } @Override @@ -96,7 +96,7 @@ public class CAbilityMove implements CAbility { return caster.getPatrolBehavior().reset(point); } else { - return caster.getMoveBehavior().reset(point.x, point.y); + return caster.getMoveBehavior().reset(OrderIds.move, point.x, point.y); } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehavior.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehavior.java index f848b67..91f12ed 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehavior.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehavior.java @@ -10,4 +10,6 @@ public interface CBehavior { * @return */ CBehavior update(CSimulation game); + + int getHighlightOrderId(); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java index 6004fd5..8837938 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java @@ -10,6 +10,8 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUni public class CBehaviorAttack extends CAbstractRangedBehavior { + private int highlightOrderId; + public CBehaviorAttack(final CUnit unit) { super(unit); } @@ -19,7 +21,8 @@ public class CBehaviorAttack extends CAbstractRangedBehavior { private int backSwingTime; private int thisOrderCooldownEndTime; - public CBehaviorAttack reset(final CUnitAttack unitAttack, final CWidget target) { + public CBehaviorAttack reset(int highlightOrderId, final CUnitAttack unitAttack, final CWidget target) { + this.highlightOrderId = highlightOrderId; super.innerReset(target); this.unitAttack = unitAttack; this.damagePointLaunchTime = 0; @@ -28,6 +31,11 @@ public class CBehaviorAttack extends CAbstractRangedBehavior { return this; } + @Override + public int getHighlightOrderId() { + return highlightOrderId; + } + @Override public boolean isWithinRange(final CSimulation simulation) { float range = this.unitAttack.getRange(); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorFollow.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorFollow.java index ee80015..785195e 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorFollow.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorFollow.java @@ -7,14 +7,22 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; public class CBehaviorFollow extends CAbstractRangedBehavior { + private int higlightOrderId; + public CBehaviorFollow(final CUnit unit) { super(unit); } - public CBehavior reset(final CUnit target) { + public CBehavior reset(int higlightOrderId, final CUnit target) { + this.higlightOrderId = higlightOrderId; return innerReset(target); } + @Override + public int getHighlightOrderId() { + return higlightOrderId; + } + @Override public boolean isWithinRange(final CSimulation simulation) { return this.unit.canReach(this.target, this.unit.getAcquisitionRange()); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorMove.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorMove.java index 5171364..b9e322f 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorMove.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorMove.java @@ -18,6 +18,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.pathing.CPathfindin public class CBehaviorMove implements CBehavior { private static final Rectangle tempRect = new Rectangle(); private final CUnit unit; + private int highlightOrderId; public CBehaviorMove(final CUnit unit) { this.unit = unit; @@ -31,25 +32,34 @@ public class CBehaviorMove implements CBehavior { private CUnit followUnit; private CRangedBehavior rangedBehavior; - public CBehaviorMove reset(final float targetX, final float targetY) { - return reset(targetX, targetY, null); + public CBehaviorMove reset(int highlightOrderId, final float targetX, final float targetY) { + internalResetMove(highlightOrderId, targetX, targetY); + this.rangedBehavior = null; + return this; } - public CBehaviorMove reset(final float targetX, final float targetY, final CRangedBehavior rangedBehavior) { + private void internalResetMove(int highlightOrderId, final float targetX, final float targetY) { + this.highlightOrderId = highlightOrderId; this.wasWithinPropWindow = false; this.gridMapping = CPathfindingProcessor.isCollisionSizeBetterSuitedForCorners( this.unit.getUnitType().getCollisionSize()) ? CPathfindingProcessor.GridMapping.CORNERS - : CPathfindingProcessor.GridMapping.CELLS; + : CPathfindingProcessor.GridMapping.CELLS; this.target = new Point2D.Float(targetX, targetY); this.path = null; this.searchCycles = 0; this.followUnit = null; + } + + public CBehaviorMove reset(final float targetX, final float targetY, final CRangedBehavior rangedBehavior) { + internalResetMove(rangedBehavior.getHighlightOrderId(), targetX, targetY); this.rangedBehavior = rangedBehavior; return this; } - public CBehaviorMove reset(final CUnit followUnit) { - return reset(followUnit, null); + public CBehaviorMove reset(int highlightOrderId, final CUnit followUnit) { + internalResetMove(highlightOrderId, followUnit); + this.rangedBehavior = null; + return this; } public CBehaviorMove reset(final CUnit followUnit, final CRangedBehavior rangedBehavior) { @@ -65,6 +75,23 @@ public class CBehaviorMove implements CBehavior { return this; } + private void internalResetMove(int highlightOrderId, CUnit followUnit) { + this.highlightOrderId = highlightOrderId; + this.wasWithinPropWindow = false; + this.gridMapping = CPathfindingProcessor.isCollisionSizeBetterSuitedForCorners( + this.unit.getUnitType().getCollisionSize()) ? CPathfindingProcessor.GridMapping.CORNERS + : CPathfindingProcessor.GridMapping.CELLS; + this.target = new Float(followUnit.getX(), followUnit.getY()); + this.path = null; + this.searchCycles = 0; + this.followUnit = followUnit; + } + + @Override + public int getHighlightOrderId() { + return highlightOrderId; + } + @Override public CBehavior update(final CSimulation simulation) { if ((this.rangedBehavior != null) && this.rangedBehavior.isWithinRange(simulation)) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorPatrol.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorPatrol.java index 947fbf3..f2d6c09 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorPatrol.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorPatrol.java @@ -3,6 +3,7 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors; import com.badlogic.gdx.math.Vector2; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; public class CBehaviorPatrol implements CRangedBehavior { @@ -20,6 +21,11 @@ public class CBehaviorPatrol implements CRangedBehavior { return this; } + @Override + public int getHighlightOrderId() { + return OrderIds.patrol; + } + @Override public boolean isWithinRange(final CSimulation simulation) { return this.unit.distance(this.target.x, this.target.y) <= simulation.getGameplayConstants() diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorStop.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorStop.java index f884891..7845bfb 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorStop.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorStop.java @@ -4,6 +4,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; public class CBehaviorStop implements CBehavior { @@ -13,8 +14,14 @@ public class CBehaviorStop implements CBehavior { this.unit = unit; } + @Override + public int getHighlightOrderId() { + return OrderIds.stop; + } + @Override public CBehavior update(final CSimulation game) { + this.unit.autoAcquireAttackTargets(game); this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.STAND, SequenceUtils.EMPTY, 1.0f, true); return this.unit.pollNextOrderBehavior(game); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java index 37f184c..2baa1ab 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java @@ -32,410 +32,410 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUni import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.SimulationRenderController; public class CUnitData { - private static final War3ID MANA_INITIAL_AMOUNT = War3ID.fromString("umpi"); - private static final War3ID MANA_MAXIMUM = War3ID.fromString("umpm"); - private static final War3ID HIT_POINT_MAXIMUM = War3ID.fromString("uhpm"); - private static final War3ID MOVEMENT_SPEED_BASE = War3ID.fromString("umvs"); - private static final War3ID PROPULSION_WINDOW = War3ID.fromString("uprw"); - private static final War3ID TURN_RATE = War3ID.fromString("umvr"); - private static final War3ID IS_BLDG = War3ID.fromString("ubdg"); - private static final War3ID NAME = War3ID.fromString("unam"); - private static final War3ID PROJECTILE_LAUNCH_X = War3ID.fromString("ulpx"); - private static final War3ID PROJECTILE_LAUNCH_Y = War3ID.fromString("ulpy"); - private static final War3ID PROJECTILE_LAUNCH_Z = War3ID.fromString("ulpz"); - private static final War3ID ATTACKS_ENABLED = War3ID.fromString("uaen"); - private static final War3ID ATTACK1_BACKSWING_POINT = War3ID.fromString("ubs1"); - private static final War3ID ATTACK1_DAMAGE_POINT = War3ID.fromString("udp1"); - private static final War3ID ATTACK1_AREA_OF_EFFECT_FULL_DMG = War3ID.fromString("ua1f"); - private static final War3ID ATTACK1_AREA_OF_EFFECT_HALF_DMG = War3ID.fromString("ua1h"); - private static final War3ID ATTACK1_AREA_OF_EFFECT_QUARTER_DMG = War3ID.fromString("ua1q"); - private static final War3ID ATTACK1_AREA_OF_EFFECT_TARGETS = War3ID.fromString("ua1p"); - private static final War3ID ATTACK1_ATTACK_TYPE = War3ID.fromString("ua1t"); - private static final War3ID ATTACK1_COOLDOWN = War3ID.fromString("ua1c"); - private static final War3ID ATTACK1_DMG_BASE = War3ID.fromString("ua1b"); - private static final War3ID ATTACK1_DAMAGE_FACTOR_HALF = War3ID.fromString("uhd1"); - private static final War3ID ATTACK1_DAMAGE_FACTOR_QUARTER = War3ID.fromString("uqd1"); - private static final War3ID ATTACK1_DAMAGE_LOSS_FACTOR = War3ID.fromString("udl1"); - private static final War3ID ATTACK1_DMG_DICE = War3ID.fromString("ua1d"); - private static final War3ID ATTACK1_DMG_SIDES_PER_DIE = War3ID.fromString("ua1s"); - private static final War3ID ATTACK1_DMG_SPILL_DIST = War3ID.fromString("usd1"); - private static final War3ID ATTACK1_DMG_SPILL_RADIUS = War3ID.fromString("usr1"); - private static final War3ID ATTACK1_DMG_UPGRADE_AMT = War3ID.fromString("udu1"); - private static final War3ID ATTACK1_TARGET_COUNT = War3ID.fromString("utc1"); - private static final War3ID ATTACK1_PROJECTILE_ARC = War3ID.fromString("uma1"); - private static final War3ID ATTACK1_MISSILE_ART = War3ID.fromString("ua1m"); - private static final War3ID ATTACK1_PROJECTILE_HOMING_ENABLED = War3ID.fromString("umh1"); - private static final War3ID ATTACK1_PROJECTILE_SPEED = War3ID.fromString("ua1z"); - private static final War3ID ATTACK1_RANGE = War3ID.fromString("ua1r"); - private static final War3ID ATTACK1_RANGE_MOTION_BUFFER = War3ID.fromString("urb1"); - private static final War3ID ATTACK1_SHOW_UI = War3ID.fromString("uwu1"); - private static final War3ID ATTACK1_TARGETS_ALLOWED = War3ID.fromString("ua1g"); - private static final War3ID ATTACK1_WEAPON_SOUND = War3ID.fromString("ucs1"); - private static final War3ID ATTACK1_WEAPON_TYPE = War3ID.fromString("ua1w"); + private static final War3ID MANA_INITIAL_AMOUNT = War3ID.fromString("umpi"); + private static final War3ID MANA_MAXIMUM = War3ID.fromString("umpm"); + private static final War3ID HIT_POINT_MAXIMUM = War3ID.fromString("uhpm"); + private static final War3ID MOVEMENT_SPEED_BASE = War3ID.fromString("umvs"); + private static final War3ID PROPULSION_WINDOW = War3ID.fromString("uprw"); + private static final War3ID TURN_RATE = War3ID.fromString("umvr"); + private static final War3ID IS_BLDG = War3ID.fromString("ubdg"); + private static final War3ID NAME = War3ID.fromString("unam"); + private static final War3ID PROJECTILE_LAUNCH_X = War3ID.fromString("ulpx"); + private static final War3ID PROJECTILE_LAUNCH_Y = War3ID.fromString("ulpy"); + private static final War3ID PROJECTILE_LAUNCH_Z = War3ID.fromString("ulpz"); + private static final War3ID ATTACKS_ENABLED = War3ID.fromString("uaen"); + private static final War3ID ATTACK1_BACKSWING_POINT = War3ID.fromString("ubs1"); + private static final War3ID ATTACK1_DAMAGE_POINT = War3ID.fromString("udp1"); + private static final War3ID ATTACK1_AREA_OF_EFFECT_FULL_DMG = War3ID.fromString("ua1f"); + private static final War3ID ATTACK1_AREA_OF_EFFECT_HALF_DMG = War3ID.fromString("ua1h"); + private static final War3ID ATTACK1_AREA_OF_EFFECT_QUARTER_DMG = War3ID.fromString("ua1q"); + private static final War3ID ATTACK1_AREA_OF_EFFECT_TARGETS = War3ID.fromString("ua1p"); + private static final War3ID ATTACK1_ATTACK_TYPE = War3ID.fromString("ua1t"); + private static final War3ID ATTACK1_COOLDOWN = War3ID.fromString("ua1c"); + private static final War3ID ATTACK1_DMG_BASE = War3ID.fromString("ua1b"); + private static final War3ID ATTACK1_DAMAGE_FACTOR_HALF = War3ID.fromString("uhd1"); + private static final War3ID ATTACK1_DAMAGE_FACTOR_QUARTER = War3ID.fromString("uqd1"); + private static final War3ID ATTACK1_DAMAGE_LOSS_FACTOR = War3ID.fromString("udl1"); + private static final War3ID ATTACK1_DMG_DICE = War3ID.fromString("ua1d"); + private static final War3ID ATTACK1_DMG_SIDES_PER_DIE = War3ID.fromString("ua1s"); + private static final War3ID ATTACK1_DMG_SPILL_DIST = War3ID.fromString("usd1"); + private static final War3ID ATTACK1_DMG_SPILL_RADIUS = War3ID.fromString("usr1"); + private static final War3ID ATTACK1_DMG_UPGRADE_AMT = War3ID.fromString("udu1"); + private static final War3ID ATTACK1_TARGET_COUNT = War3ID.fromString("utc1"); + private static final War3ID ATTACK1_PROJECTILE_ARC = War3ID.fromString("uma1"); + private static final War3ID ATTACK1_MISSILE_ART = War3ID.fromString("ua1m"); + private static final War3ID ATTACK1_PROJECTILE_HOMING_ENABLED = War3ID.fromString("umh1"); + private static final War3ID ATTACK1_PROJECTILE_SPEED = War3ID.fromString("ua1z"); + private static final War3ID ATTACK1_RANGE = War3ID.fromString("ua1r"); + private static final War3ID ATTACK1_RANGE_MOTION_BUFFER = War3ID.fromString("urb1"); + private static final War3ID ATTACK1_SHOW_UI = War3ID.fromString("uwu1"); + private static final War3ID ATTACK1_TARGETS_ALLOWED = War3ID.fromString("ua1g"); + private static final War3ID ATTACK1_WEAPON_SOUND = War3ID.fromString("ucs1"); + private static final War3ID ATTACK1_WEAPON_TYPE = War3ID.fromString("ua1w"); - private static final War3ID ATTACK2_BACKSWING_POINT = War3ID.fromString("ubs2"); - private static final War3ID ATTACK2_DAMAGE_POINT = War3ID.fromString("udp2"); - private static final War3ID ATTACK2_AREA_OF_EFFECT_FULL_DMG = War3ID.fromString("ua2f"); - private static final War3ID ATTACK2_AREA_OF_EFFECT_HALF_DMG = War3ID.fromString("ua2h"); - private static final War3ID ATTACK2_AREA_OF_EFFECT_QUARTER_DMG = War3ID.fromString("ua2q"); - private static final War3ID ATTACK2_AREA_OF_EFFECT_TARGETS = War3ID.fromString("ua2p"); - private static final War3ID ATTACK2_ATTACK_TYPE = War3ID.fromString("ua2t"); - private static final War3ID ATTACK2_COOLDOWN = War3ID.fromString("ua2c"); - private static final War3ID ATTACK2_DMG_BASE = War3ID.fromString("ua2b"); - private static final War3ID ATTACK2_DAMAGE_FACTOR_HALF = War3ID.fromString("uhd2"); - private static final War3ID ATTACK2_DAMAGE_FACTOR_QUARTER = War3ID.fromString("uqd2"); - private static final War3ID ATTACK2_DAMAGE_LOSS_FACTOR = War3ID.fromString("udl2"); - private static final War3ID ATTACK2_DMG_DICE = War3ID.fromString("ua2d"); - private static final War3ID ATTACK2_DMG_SIDES_PER_DIE = War3ID.fromString("ua2s"); - private static final War3ID ATTACK2_DMG_SPILL_DIST = War3ID.fromString("usd2"); - private static final War3ID ATTACK2_DMG_SPILL_RADIUS = War3ID.fromString("usr2"); - private static final War3ID ATTACK2_DMG_UPGRADE_AMT = War3ID.fromString("udu2"); - private static final War3ID ATTACK2_TARGET_COUNT = War3ID.fromString("utc2"); - private static final War3ID ATTACK2_PROJECTILE_ARC = War3ID.fromString("uma2"); - private static final War3ID ATTACK2_MISSILE_ART = War3ID.fromString("ua2m"); - private static final War3ID ATTACK2_PROJECTILE_HOMING_ENABLED = War3ID.fromString("umh2"); - private static final War3ID ATTACK2_PROJECTILE_SPEED = War3ID.fromString("ua2z"); - private static final War3ID ATTACK2_RANGE = War3ID.fromString("ua2r"); - private static final War3ID ATTACK2_RANGE_MOTION_BUFFER = War3ID.fromString("urb2"); - private static final War3ID ATTACK2_SHOW_UI = War3ID.fromString("uwu2"); - private static final War3ID ATTACK2_TARGETS_ALLOWED = War3ID.fromString("ua2g"); - private static final War3ID ATTACK2_WEAPON_SOUND = War3ID.fromString("ucs2"); - private static final War3ID ATTACK2_WEAPON_TYPE = War3ID.fromString("ua2w"); + private static final War3ID ATTACK2_BACKSWING_POINT = War3ID.fromString("ubs2"); + private static final War3ID ATTACK2_DAMAGE_POINT = War3ID.fromString("udp2"); + private static final War3ID ATTACK2_AREA_OF_EFFECT_FULL_DMG = War3ID.fromString("ua2f"); + private static final War3ID ATTACK2_AREA_OF_EFFECT_HALF_DMG = War3ID.fromString("ua2h"); + private static final War3ID ATTACK2_AREA_OF_EFFECT_QUARTER_DMG = War3ID.fromString("ua2q"); + private static final War3ID ATTACK2_AREA_OF_EFFECT_TARGETS = War3ID.fromString("ua2p"); + private static final War3ID ATTACK2_ATTACK_TYPE = War3ID.fromString("ua2t"); + private static final War3ID ATTACK2_COOLDOWN = War3ID.fromString("ua2c"); + private static final War3ID ATTACK2_DMG_BASE = War3ID.fromString("ua2b"); + private static final War3ID ATTACK2_DAMAGE_FACTOR_HALF = War3ID.fromString("uhd2"); + private static final War3ID ATTACK2_DAMAGE_FACTOR_QUARTER = War3ID.fromString("uqd2"); + private static final War3ID ATTACK2_DAMAGE_LOSS_FACTOR = War3ID.fromString("udl2"); + private static final War3ID ATTACK2_DMG_DICE = War3ID.fromString("ua2d"); + private static final War3ID ATTACK2_DMG_SIDES_PER_DIE = War3ID.fromString("ua2s"); + private static final War3ID ATTACK2_DMG_SPILL_DIST = War3ID.fromString("usd2"); + private static final War3ID ATTACK2_DMG_SPILL_RADIUS = War3ID.fromString("usr2"); + private static final War3ID ATTACK2_DMG_UPGRADE_AMT = War3ID.fromString("udu2"); + private static final War3ID ATTACK2_TARGET_COUNT = War3ID.fromString("utc2"); + private static final War3ID ATTACK2_PROJECTILE_ARC = War3ID.fromString("uma2"); + private static final War3ID ATTACK2_MISSILE_ART = War3ID.fromString("ua2m"); + private static final War3ID ATTACK2_PROJECTILE_HOMING_ENABLED = War3ID.fromString("umh2"); + private static final War3ID ATTACK2_PROJECTILE_SPEED = War3ID.fromString("ua2z"); + private static final War3ID ATTACK2_RANGE = War3ID.fromString("ua2r"); + private static final War3ID ATTACK2_RANGE_MOTION_BUFFER = War3ID.fromString("urb2"); + private static final War3ID ATTACK2_SHOW_UI = War3ID.fromString("uwu2"); + private static final War3ID ATTACK2_TARGETS_ALLOWED = War3ID.fromString("ua2g"); + private static final War3ID ATTACK2_WEAPON_SOUND = War3ID.fromString("ucs2"); + private static final War3ID ATTACK2_WEAPON_TYPE = War3ID.fromString("ua2w"); - private static final War3ID ACQUISITION_RANGE = War3ID.fromString("uacq"); - private static final War3ID MINIMUM_ATTACK_RANGE = War3ID.fromString("uamn"); + private static final War3ID ACQUISITION_RANGE = War3ID.fromString("uacq"); + private static final War3ID MINIMUM_ATTACK_RANGE = War3ID.fromString("uamn"); - private static final War3ID PROJECTILE_IMPACT_Z = War3ID.fromString("uimz"); + private static final War3ID PROJECTILE_IMPACT_Z = War3ID.fromString("uimz"); - private static final War3ID DEATH_TYPE = War3ID.fromString("udea"); - private static final War3ID ARMOR_TYPE = War3ID.fromString("uarm"); + private static final War3ID DEATH_TYPE = War3ID.fromString("udea"); + private static final War3ID ARMOR_TYPE = War3ID.fromString("uarm"); - private static final War3ID DEFENSE = War3ID.fromString("udef"); - private static final War3ID DEFENSE_TYPE = War3ID.fromString("udty"); - private static final War3ID MOVE_HEIGHT = War3ID.fromString("umvh"); - private static final War3ID MOVE_TYPE = War3ID.fromString("umvt"); - private static final War3ID COLLISION_SIZE = War3ID.fromString("ucol"); - private static final War3ID CLASSIFICATION = War3ID.fromString("utyp"); - private static final War3ID DEATH_TIME = War3ID.fromString("udtm"); - private static final War3ID TARGETED_AS = War3ID.fromString("utar"); + private static final War3ID DEFENSE = War3ID.fromString("udef"); + private static final War3ID DEFENSE_TYPE = War3ID.fromString("udty"); + private static final War3ID MOVE_HEIGHT = War3ID.fromString("umvh"); + private static final War3ID MOVE_TYPE = War3ID.fromString("umvt"); + private static final War3ID COLLISION_SIZE = War3ID.fromString("ucol"); + private static final War3ID CLASSIFICATION = War3ID.fromString("utyp"); + private static final War3ID DEATH_TIME = War3ID.fromString("udtm"); + private static final War3ID TARGETED_AS = War3ID.fromString("utar"); - private static final War3ID ABILITIES_NORMAL = War3ID.fromString("uabi"); - private final MutableObjectData unitData; - private final Map unitIdToUnitType = new HashMap<>(); - private final CAbilityData abilityData; + private static final War3ID ABILITIES_NORMAL = War3ID.fromString("uabi"); + private final MutableObjectData unitData; + private final Map unitIdToUnitType = new HashMap<>(); + private final CAbilityData abilityData; - public CUnitData(final MutableObjectData unitData, final CAbilityData abilityData) { - this.unitData = unitData; - this.abilityData = abilityData; - } + public CUnitData(final MutableObjectData unitData, final CAbilityData abilityData) { + this.unitData = unitData; + this.abilityData = abilityData; + } - public CUnit create(final CSimulation simulation, final int playerIndex, final War3ID typeId, final float x, - final float y, final float facing, final BufferedImage buildingPathingPixelMap, - final SimulationRenderController simulationRenderController, final HandleIdAllocator handleIdAllocator) { - final MutableGameObject unitType = this.unitData.get(typeId); - final int handleId = handleIdAllocator.createId(); - final int life = unitType.getFieldAsInteger(HIT_POINT_MAXIMUM, 0); - final int manaInitial = unitType.getFieldAsInteger(MANA_INITIAL_AMOUNT, 0); - final int manaMaximum = unitType.getFieldAsInteger(MANA_MAXIMUM, 0); - final int speed = unitType.getFieldAsInteger(MOVEMENT_SPEED_BASE, 0); - final int defense = unitType.getFieldAsInteger(DEFENSE, 0); - final String abilityList = unitType.getFieldAsString(ABILITIES_NORMAL, 0); + public CUnit create(final CSimulation simulation, final int playerIndex, final War3ID typeId, final float x, + final float y, final float facing, final BufferedImage buildingPathingPixelMap, + final SimulationRenderController simulationRenderController, final HandleIdAllocator handleIdAllocator) { + final MutableGameObject unitType = this.unitData.get(typeId); + final int handleId = handleIdAllocator.createId(); + final int life = unitType.getFieldAsInteger(HIT_POINT_MAXIMUM, 0); + final int manaInitial = unitType.getFieldAsInteger(MANA_INITIAL_AMOUNT, 0); + final int manaMaximum = unitType.getFieldAsInteger(MANA_MAXIMUM, 0); + final int speed = unitType.getFieldAsInteger(MOVEMENT_SPEED_BASE, 0); + final int defense = unitType.getFieldAsInteger(DEFENSE, 0); + final String abilityList = unitType.getFieldAsString(ABILITIES_NORMAL, 0); - final CUnitType unitTypeInstance = getUnitTypeInstance(typeId, buildingPathingPixelMap, unitType); + final CUnitType unitTypeInstance = getUnitTypeInstance(typeId, buildingPathingPixelMap, unitType); - final CUnit unit = new CUnit(handleId, playerIndex, x, y, life, typeId, facing, manaInitial, life, manaMaximum, - speed, defense, unitTypeInstance); - if (speed > 0) { - unit.add(simulation, new CAbilityMove(handleIdAllocator.createId())); - } - if (!unitTypeInstance.getAttacks().isEmpty()) { - unit.add(simulation, new CAbilityAttack(handleIdAllocator.createId())); - } - for (final String ability : abilityList.split(",")) { - unit.add(simulation, this.abilityData.createAbility(ability, handleIdAllocator.createId())); - } - return unit; - } + final CUnit unit = new CUnit(handleId, playerIndex, x, y, life, typeId, facing, manaInitial, life, manaMaximum, + speed, defense, unitTypeInstance); + if (speed > 0) { + unit.add(simulation, new CAbilityMove(handleIdAllocator.createId())); + } + if (!unitTypeInstance.getAttacks().isEmpty()) { + unit.add(simulation, new CAbilityAttack(handleIdAllocator.createId())); + } + for (final String ability : abilityList.split(",")) { + if (ability.length() > 0 && !"_".equals(ability)) { + unit.add(simulation, this.abilityData.createAbility(ability, handleIdAllocator.createId())); + } + } + return unit; + } - private CUnitType getUnitTypeInstance(final War3ID typeId, final BufferedImage buildingPathingPixelMap, - final MutableGameObject unitType) { - CUnitType unitTypeInstance = this.unitIdToUnitType.get(typeId); - if (unitTypeInstance == null) { - final float moveHeight = unitType.getFieldAsFloat(MOVE_HEIGHT, 0); - final String movetp = unitType.getFieldAsString(MOVE_TYPE, 0); - final float collisionSize = unitType.getFieldAsFloat(COLLISION_SIZE, 0); - final boolean isBldg = unitType.getFieldAsBoolean(IS_BLDG, 0); - final PathingGrid.MovementType movementType = PathingGrid.getMovementType(movetp); - final String unitName = unitType.getFieldAsString(NAME, 0); - final float acquisitionRange = unitType.getFieldAsFloat(ACQUISITION_RANGE, 0); - final float minimumAttackRange = unitType.getFieldAsFloat(MINIMUM_ATTACK_RANGE, 0); - final EnumSet targetedAs = CTargetType - .parseTargetTypeSet(unitType.getFieldAsString(TARGETED_AS, 0)); - final String classificationString = unitType.getFieldAsString(CLASSIFICATION, 0); - final EnumSet classifications = EnumSet.noneOf(CUnitClassification.class); - if (classificationString != null) { - final String[] classificationValues = classificationString.split(","); - for (final String unitEditorKey : classificationValues) { - final CUnitClassification unitClassification = CUnitClassification - .parseUnitClassification(unitEditorKey); - if (unitClassification != null) { - classifications.add(unitClassification); - } - } - } - final List attacks = new ArrayList<>(); - final int attacksEnabled = unitType.getFieldAsInteger(ATTACKS_ENABLED, 0); - if ((attacksEnabled & 0x1) != 0) { - try { - // attack one - final float animationBackswingPoint = unitType.getFieldAsFloat(ATTACK1_BACKSWING_POINT, 0); - final float animationDamagePoint = unitType.getFieldAsFloat(ATTACK1_DAMAGE_POINT, 0); - final int areaOfEffectFullDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_FULL_DMG, 0); - final int areaOfEffectMediumDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_HALF_DMG, 0); - final int areaOfEffectSmallDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_QUARTER_DMG, - 0); - final EnumSet areaOfEffectTargets = CTargetType - .parseTargetTypeSet(unitType.getFieldAsString(ATTACK1_AREA_OF_EFFECT_TARGETS, 0)); - final CAttackType attackType = CAttackType - .parseAttackType(unitType.getFieldAsString(ATTACK1_ATTACK_TYPE, 0)); - final float cooldownTime = unitType.getFieldAsFloat(ATTACK1_COOLDOWN, 0); - final int damageBase = unitType.getFieldAsInteger(ATTACK1_DMG_BASE, 0); - final float damageFactorMedium = unitType.getFieldAsFloat(ATTACK1_DAMAGE_FACTOR_HALF, 0); - final float damageFactorSmall = unitType.getFieldAsFloat(ATTACK1_DAMAGE_FACTOR_QUARTER, 0); - final float damageLossFactor = unitType.getFieldAsFloat(ATTACK1_DAMAGE_LOSS_FACTOR, 0); - final int damageDice = unitType.getFieldAsInteger(ATTACK1_DMG_DICE, 0); - final int damageSidesPerDie = unitType.getFieldAsInteger(ATTACK1_DMG_SIDES_PER_DIE, 0); - final float damageSpillDistance = unitType.getFieldAsFloat(ATTACK1_DMG_SPILL_DIST, 0); - final float damageSpillRadius = unitType.getFieldAsFloat(ATTACK1_DMG_SPILL_RADIUS, 0); - final int damageUpgradeAmount = unitType.getFieldAsInteger(ATTACK1_DMG_UPGRADE_AMT, 0); - final int maximumNumberOfTargets = unitType.getFieldAsInteger(ATTACK1_TARGET_COUNT, 0); - final float projectileArc = unitType.getFieldAsFloat(ATTACK1_PROJECTILE_ARC, 0); - final String projectileArt = unitType.getFieldAsString(ATTACK1_MISSILE_ART, 0); - final boolean projectileHomingEnabled = unitType - .getFieldAsBoolean(ATTACK1_PROJECTILE_HOMING_ENABLED, 0); - final int projectileSpeed = unitType.getFieldAsInteger(ATTACK1_PROJECTILE_SPEED, 0); - final int range = unitType.getFieldAsInteger(ATTACK1_RANGE, 0); - final float rangeMotionBuffer = unitType.getFieldAsFloat(ATTACK1_RANGE_MOTION_BUFFER, 0); - final boolean showUI = unitType.getFieldAsBoolean(ATTACK1_SHOW_UI, 0); - final EnumSet targetsAllowed = CTargetType - .parseTargetTypeSet(unitType.getFieldAsString(ATTACK1_TARGETS_ALLOWED, 0)); - final String weaponSound = unitType.getFieldAsString(ATTACK1_WEAPON_SOUND, 0); - final CWeaponType weaponType = CWeaponType - .parseWeaponType(unitType.getFieldAsString(ATTACK1_WEAPON_TYPE, 0)); - attacks.add(createAttack(animationBackswingPoint, animationDamagePoint, areaOfEffectFullDamage, - areaOfEffectMediumDamage, areaOfEffectSmallDamage, areaOfEffectTargets, attackType, - cooldownTime, damageBase, damageFactorMedium, damageFactorSmall, damageLossFactor, - damageDice, damageSidesPerDie, damageSpillDistance, damageSpillRadius, damageUpgradeAmount, - maximumNumberOfTargets, projectileArc, projectileArt, projectileHomingEnabled, - projectileSpeed, range, rangeMotionBuffer, showUI, targetsAllowed, weaponSound, - weaponType)); - } - catch (final Exception exc) { - System.err.println("Attack 1 failed to parse with: " + exc.getClass() + ":" + exc.getMessage()); - } - } - if ((attacksEnabled & 0x2) != 0) { - try { - // attack two - final float animationBackswingPoint = unitType.getFieldAsFloat(ATTACK2_BACKSWING_POINT, 0); - final float animationDamagePoint = unitType.getFieldAsFloat(ATTACK2_DAMAGE_POINT, 0); - final int areaOfEffectFullDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_FULL_DMG, 0); - final int areaOfEffectMediumDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_HALF_DMG, 0); - final int areaOfEffectSmallDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_QUARTER_DMG, - 0); - final EnumSet areaOfEffectTargets = CTargetType - .parseTargetTypeSet(unitType.getFieldAsString(ATTACK2_AREA_OF_EFFECT_TARGETS, 0)); - final CAttackType attackType = CAttackType - .parseAttackType(unitType.getFieldAsString(ATTACK2_ATTACK_TYPE, 0)); - final float cooldownTime = unitType.getFieldAsFloat(ATTACK2_COOLDOWN, 0); - final int damageBase = unitType.getFieldAsInteger(ATTACK2_DMG_BASE, 0); - final float damageFactorMedium = unitType.getFieldAsFloat(ATTACK2_DAMAGE_FACTOR_HALF, 0); - final float damageFactorSmall = unitType.getFieldAsFloat(ATTACK2_DAMAGE_FACTOR_QUARTER, 0); - final float damageLossFactor = unitType.getFieldAsFloat(ATTACK2_DAMAGE_LOSS_FACTOR, 0); - final int damageDice = unitType.getFieldAsInteger(ATTACK2_DMG_DICE, 0); - final int damageSidesPerDie = unitType.getFieldAsInteger(ATTACK2_DMG_SIDES_PER_DIE, 0); - final float damageSpillDistance = unitType.getFieldAsFloat(ATTACK2_DMG_SPILL_DIST, 0); - final float damageSpillRadius = unitType.getFieldAsFloat(ATTACK2_DMG_SPILL_RADIUS, 0); - final int damageUpgradeAmount = unitType.getFieldAsInteger(ATTACK2_DMG_UPGRADE_AMT, 0); - final int maximumNumberOfTargets = unitType.getFieldAsInteger(ATTACK2_TARGET_COUNT, 0); - final float projectileArc = unitType.getFieldAsFloat(ATTACK2_PROJECTILE_ARC, 0); - final String projectileArt = unitType.getFieldAsString(ATTACK2_MISSILE_ART, 0); - final boolean projectileHomingEnabled = unitType - .getFieldAsBoolean(ATTACK2_PROJECTILE_HOMING_ENABLED, 0); - final int projectileSpeed = unitType.getFieldAsInteger(ATTACK2_PROJECTILE_SPEED, 0); - final int range = unitType.getFieldAsInteger(ATTACK2_RANGE, 0); - final float rangeMotionBuffer = unitType.getFieldAsFloat(ATTACK2_RANGE_MOTION_BUFFER, 0); - final boolean showUI = unitType.getFieldAsBoolean(ATTACK2_SHOW_UI, 0); - final EnumSet targetsAllowed = CTargetType - .parseTargetTypeSet(unitType.getFieldAsString(ATTACK2_TARGETS_ALLOWED, 0)); - final String weaponSound = unitType.getFieldAsString(ATTACK2_WEAPON_SOUND, 0); - final CWeaponType weaponType = CWeaponType - .parseWeaponType(unitType.getFieldAsString(ATTACK2_WEAPON_TYPE, 0)); - attacks.add(createAttack(animationBackswingPoint, animationDamagePoint, areaOfEffectFullDamage, - areaOfEffectMediumDamage, areaOfEffectSmallDamage, areaOfEffectTargets, attackType, - cooldownTime, damageBase, damageFactorMedium, damageFactorSmall, damageLossFactor, - damageDice, damageSidesPerDie, damageSpillDistance, damageSpillRadius, damageUpgradeAmount, - maximumNumberOfTargets, projectileArc, projectileArt, projectileHomingEnabled, - projectileSpeed, range, rangeMotionBuffer, showUI, targetsAllowed, weaponSound, - weaponType)); - } - catch (final Exception exc) { - System.err.println("Attack 2 failed to parse with: " + exc.getClass() + ":" + exc.getMessage()); - } - } - final int deathType = unitType.getFieldAsInteger(DEATH_TYPE, 0); - final boolean raise = (deathType & 0x1) != 0; - final boolean decay = (deathType & 0x2) != 0; - final String armorType = unitType.getFieldAsString(ARMOR_TYPE, 0); - final float impactZ = unitType.getFieldAsFloat(PROJECTILE_IMPACT_Z, 0); - final CDefenseType defenseType = CDefenseType.parseDefenseType(unitType.getFieldAsString(DEFENSE_TYPE, 0)); - final float deathTime = unitType.getFieldAsFloat(DEATH_TIME, 0); - unitTypeInstance = new CUnitType(unitName, isBldg, movementType, moveHeight, collisionSize, classifications, - attacks, armorType, raise, decay, defenseType, impactZ, buildingPathingPixelMap, deathTime, - targetedAs, acquisitionRange, minimumAttackRange); - this.unitIdToUnitType.put(typeId, unitTypeInstance); - } - return unitTypeInstance; - } + private CUnitType getUnitTypeInstance(final War3ID typeId, final BufferedImage buildingPathingPixelMap, + final MutableGameObject unitType) { + CUnitType unitTypeInstance = this.unitIdToUnitType.get(typeId); + if (unitTypeInstance == null) { + final float moveHeight = unitType.getFieldAsFloat(MOVE_HEIGHT, 0); + final String movetp = unitType.getFieldAsString(MOVE_TYPE, 0); + final float collisionSize = unitType.getFieldAsFloat(COLLISION_SIZE, 0); + final boolean isBldg = unitType.getFieldAsBoolean(IS_BLDG, 0); + final PathingGrid.MovementType movementType = PathingGrid.getMovementType(movetp); + final String unitName = unitType.getFieldAsString(NAME, 0); + final float acquisitionRange = unitType.getFieldAsFloat(ACQUISITION_RANGE, 0); + final float minimumAttackRange = unitType.getFieldAsFloat(MINIMUM_ATTACK_RANGE, 0); + final EnumSet targetedAs = CTargetType + .parseTargetTypeSet(unitType.getFieldAsString(TARGETED_AS, 0)); + final String classificationString = unitType.getFieldAsString(CLASSIFICATION, 0); + final EnumSet classifications = EnumSet.noneOf(CUnitClassification.class); + if (classificationString != null) { + final String[] classificationValues = classificationString.split(","); + for (final String unitEditorKey : classificationValues) { + final CUnitClassification unitClassification = CUnitClassification + .parseUnitClassification(unitEditorKey); + if (unitClassification != null) { + classifications.add(unitClassification); + } + } + } + final List attacks = new ArrayList<>(); + final int attacksEnabled = unitType.getFieldAsInteger(ATTACKS_ENABLED, 0); + if ((attacksEnabled & 0x1) != 0) { + try { + // attack one + final float animationBackswingPoint = unitType.getFieldAsFloat(ATTACK1_BACKSWING_POINT, 0); + final float animationDamagePoint = unitType.getFieldAsFloat(ATTACK1_DAMAGE_POINT, 0); + final int areaOfEffectFullDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_FULL_DMG, 0); + final int areaOfEffectMediumDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_HALF_DMG, 0); + final int areaOfEffectSmallDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_QUARTER_DMG, + 0); + final EnumSet areaOfEffectTargets = CTargetType + .parseTargetTypeSet(unitType.getFieldAsString(ATTACK1_AREA_OF_EFFECT_TARGETS, 0)); + final CAttackType attackType = CAttackType + .parseAttackType(unitType.getFieldAsString(ATTACK1_ATTACK_TYPE, 0)); + final float cooldownTime = unitType.getFieldAsFloat(ATTACK1_COOLDOWN, 0); + final int damageBase = unitType.getFieldAsInteger(ATTACK1_DMG_BASE, 0); + final float damageFactorMedium = unitType.getFieldAsFloat(ATTACK1_DAMAGE_FACTOR_HALF, 0); + final float damageFactorSmall = unitType.getFieldAsFloat(ATTACK1_DAMAGE_FACTOR_QUARTER, 0); + final float damageLossFactor = unitType.getFieldAsFloat(ATTACK1_DAMAGE_LOSS_FACTOR, 0); + final int damageDice = unitType.getFieldAsInteger(ATTACK1_DMG_DICE, 0); + final int damageSidesPerDie = unitType.getFieldAsInteger(ATTACK1_DMG_SIDES_PER_DIE, 0); + final float damageSpillDistance = unitType.getFieldAsFloat(ATTACK1_DMG_SPILL_DIST, 0); + final float damageSpillRadius = unitType.getFieldAsFloat(ATTACK1_DMG_SPILL_RADIUS, 0); + final int damageUpgradeAmount = unitType.getFieldAsInteger(ATTACK1_DMG_UPGRADE_AMT, 0); + final int maximumNumberOfTargets = unitType.getFieldAsInteger(ATTACK1_TARGET_COUNT, 0); + final float projectileArc = unitType.getFieldAsFloat(ATTACK1_PROJECTILE_ARC, 0); + final String projectileArt = unitType.getFieldAsString(ATTACK1_MISSILE_ART, 0); + final boolean projectileHomingEnabled = unitType + .getFieldAsBoolean(ATTACK1_PROJECTILE_HOMING_ENABLED, 0); + final int projectileSpeed = unitType.getFieldAsInteger(ATTACK1_PROJECTILE_SPEED, 0); + final int range = unitType.getFieldAsInteger(ATTACK1_RANGE, 0); + final float rangeMotionBuffer = unitType.getFieldAsFloat(ATTACK1_RANGE_MOTION_BUFFER, 0); + final boolean showUI = unitType.getFieldAsBoolean(ATTACK1_SHOW_UI, 0); + final EnumSet targetsAllowed = CTargetType + .parseTargetTypeSet(unitType.getFieldAsString(ATTACK1_TARGETS_ALLOWED, 0)); + final String weaponSound = unitType.getFieldAsString(ATTACK1_WEAPON_SOUND, 0); + final CWeaponType weaponType = CWeaponType + .parseWeaponType(unitType.getFieldAsString(ATTACK1_WEAPON_TYPE, 0)); + attacks.add(createAttack(animationBackswingPoint, animationDamagePoint, areaOfEffectFullDamage, + areaOfEffectMediumDamage, areaOfEffectSmallDamage, areaOfEffectTargets, attackType, + cooldownTime, damageBase, damageFactorMedium, damageFactorSmall, damageLossFactor, + damageDice, damageSidesPerDie, damageSpillDistance, damageSpillRadius, damageUpgradeAmount, + maximumNumberOfTargets, projectileArc, projectileArt, projectileHomingEnabled, + projectileSpeed, range, rangeMotionBuffer, showUI, targetsAllowed, weaponSound, + weaponType)); + } catch (final Exception exc) { + System.err.println("Attack 1 failed to parse with: " + exc.getClass() + ":" + exc.getMessage()); + } + } + if ((attacksEnabled & 0x2) != 0) { + try { + // attack two + final float animationBackswingPoint = unitType.getFieldAsFloat(ATTACK2_BACKSWING_POINT, 0); + final float animationDamagePoint = unitType.getFieldAsFloat(ATTACK2_DAMAGE_POINT, 0); + final int areaOfEffectFullDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_FULL_DMG, 0); + final int areaOfEffectMediumDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_HALF_DMG, 0); + final int areaOfEffectSmallDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_QUARTER_DMG, + 0); + final EnumSet areaOfEffectTargets = CTargetType + .parseTargetTypeSet(unitType.getFieldAsString(ATTACK2_AREA_OF_EFFECT_TARGETS, 0)); + final CAttackType attackType = CAttackType + .parseAttackType(unitType.getFieldAsString(ATTACK2_ATTACK_TYPE, 0)); + final float cooldownTime = unitType.getFieldAsFloat(ATTACK2_COOLDOWN, 0); + final int damageBase = unitType.getFieldAsInteger(ATTACK2_DMG_BASE, 0); + final float damageFactorMedium = unitType.getFieldAsFloat(ATTACK2_DAMAGE_FACTOR_HALF, 0); + final float damageFactorSmall = unitType.getFieldAsFloat(ATTACK2_DAMAGE_FACTOR_QUARTER, 0); + final float damageLossFactor = unitType.getFieldAsFloat(ATTACK2_DAMAGE_LOSS_FACTOR, 0); + final int damageDice = unitType.getFieldAsInteger(ATTACK2_DMG_DICE, 0); + final int damageSidesPerDie = unitType.getFieldAsInteger(ATTACK2_DMG_SIDES_PER_DIE, 0); + final float damageSpillDistance = unitType.getFieldAsFloat(ATTACK2_DMG_SPILL_DIST, 0); + final float damageSpillRadius = unitType.getFieldAsFloat(ATTACK2_DMG_SPILL_RADIUS, 0); + final int damageUpgradeAmount = unitType.getFieldAsInteger(ATTACK2_DMG_UPGRADE_AMT, 0); + final int maximumNumberOfTargets = unitType.getFieldAsInteger(ATTACK2_TARGET_COUNT, 0); + final float projectileArc = unitType.getFieldAsFloat(ATTACK2_PROJECTILE_ARC, 0); + final String projectileArt = unitType.getFieldAsString(ATTACK2_MISSILE_ART, 0); + final boolean projectileHomingEnabled = unitType + .getFieldAsBoolean(ATTACK2_PROJECTILE_HOMING_ENABLED, 0); + final int projectileSpeed = unitType.getFieldAsInteger(ATTACK2_PROJECTILE_SPEED, 0); + final int range = unitType.getFieldAsInteger(ATTACK2_RANGE, 0); + final float rangeMotionBuffer = unitType.getFieldAsFloat(ATTACK2_RANGE_MOTION_BUFFER, 0); + final boolean showUI = unitType.getFieldAsBoolean(ATTACK2_SHOW_UI, 0); + final EnumSet targetsAllowed = CTargetType + .parseTargetTypeSet(unitType.getFieldAsString(ATTACK2_TARGETS_ALLOWED, 0)); + final String weaponSound = unitType.getFieldAsString(ATTACK2_WEAPON_SOUND, 0); + final CWeaponType weaponType = CWeaponType + .parseWeaponType(unitType.getFieldAsString(ATTACK2_WEAPON_TYPE, 0)); + attacks.add(createAttack(animationBackswingPoint, animationDamagePoint, areaOfEffectFullDamage, + areaOfEffectMediumDamage, areaOfEffectSmallDamage, areaOfEffectTargets, attackType, + cooldownTime, damageBase, damageFactorMedium, damageFactorSmall, damageLossFactor, + damageDice, damageSidesPerDie, damageSpillDistance, damageSpillRadius, damageUpgradeAmount, + maximumNumberOfTargets, projectileArc, projectileArt, projectileHomingEnabled, + projectileSpeed, range, rangeMotionBuffer, showUI, targetsAllowed, weaponSound, + weaponType)); + } catch (final Exception exc) { + System.err.println("Attack 2 failed to parse with: " + exc.getClass() + ":" + exc.getMessage()); + } + } + final int deathType = unitType.getFieldAsInteger(DEATH_TYPE, 0); + final boolean raise = (deathType & 0x1) != 0; + final boolean decay = (deathType & 0x2) != 0; + final String armorType = unitType.getFieldAsString(ARMOR_TYPE, 0); + final float impactZ = unitType.getFieldAsFloat(PROJECTILE_IMPACT_Z, 0); + final CDefenseType defenseType = CDefenseType.parseDefenseType(unitType.getFieldAsString(DEFENSE_TYPE, 0)); + final float deathTime = unitType.getFieldAsFloat(DEATH_TIME, 0); + unitTypeInstance = new CUnitType(unitName, isBldg, movementType, moveHeight, collisionSize, classifications, + attacks, armorType, raise, decay, defenseType, impactZ, buildingPathingPixelMap, deathTime, + targetedAs, acquisitionRange, minimumAttackRange); + this.unitIdToUnitType.put(typeId, unitTypeInstance); + } + return unitTypeInstance; + } - private CUnitAttack createAttack(final float animationBackswingPoint, final float animationDamagePoint, - final int areaOfEffectFullDamage, final int areaOfEffectMediumDamage, final int areaOfEffectSmallDamage, - final EnumSet areaOfEffectTargets, final CAttackType attackType, final float cooldownTime, - final int damageBase, final float damageFactorMedium, final float damageFactorSmall, - final float damageLossFactor, final int damageDice, final int damageSidesPerDie, - final float damageSpillDistance, final float damageSpillRadius, final int damageUpgradeAmount, - final int maximumNumberOfTargets, final float projectileArc, final String projectileArt, - final boolean projectileHomingEnabled, final int projectileSpeed, final int range, - final float rangeMotionBuffer, final boolean showUI, final EnumSet targetsAllowed, - final String weaponSound, final CWeaponType weaponType) { - final CUnitAttack attack; - switch (weaponType) { - case MISSILE: - attack = new CUnitAttackMissile(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, - damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, - targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, projectileHomingEnabled, - projectileSpeed); - break; - case MBOUNCE: - attack = new CUnitAttackMissileBounce(animationBackswingPoint, animationDamagePoint, attackType, - cooldownTime, damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, - rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, - projectileHomingEnabled, projectileSpeed, damageLossFactor, maximumNumberOfTargets, - areaOfEffectFullDamage, areaOfEffectTargets); - break; - case MSPLASH: - case ARTILLERY: - attack = new CUnitAttackMissileSplash(animationBackswingPoint, animationDamagePoint, attackType, - cooldownTime, damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, - rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, - projectileHomingEnabled, projectileSpeed, areaOfEffectFullDamage, areaOfEffectMediumDamage, - areaOfEffectSmallDamage, areaOfEffectTargets, damageFactorMedium, damageFactorSmall); - break; - case MLINE: - case ALINE: - attack = new CUnitAttackMissileLine(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, - damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, - targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, projectileHomingEnabled, - projectileSpeed, damageSpillDistance, damageSpillRadius); - break; - case INSTANT: - attack = new CUnitAttackInstant(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, - damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, - targetsAllowed, weaponSound, weaponType, projectileArt); - break; - default: - case NORMAL: - attack = new CUnitAttackNormal(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, - damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, - targetsAllowed, weaponSound, weaponType); - break; - } - return attack; - } + private CUnitAttack createAttack(final float animationBackswingPoint, final float animationDamagePoint, + final int areaOfEffectFullDamage, final int areaOfEffectMediumDamage, final int areaOfEffectSmallDamage, + final EnumSet areaOfEffectTargets, final CAttackType attackType, final float cooldownTime, + final int damageBase, final float damageFactorMedium, final float damageFactorSmall, + final float damageLossFactor, final int damageDice, final int damageSidesPerDie, + final float damageSpillDistance, final float damageSpillRadius, final int damageUpgradeAmount, + final int maximumNumberOfTargets, final float projectileArc, final String projectileArt, + final boolean projectileHomingEnabled, final int projectileSpeed, final int range, + final float rangeMotionBuffer, final boolean showUI, final EnumSet targetsAllowed, + final String weaponSound, final CWeaponType weaponType) { + final CUnitAttack attack; + switch (weaponType) { + case MISSILE: + attack = new CUnitAttackMissile(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, + damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, + targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, projectileHomingEnabled, + projectileSpeed); + break; + case MBOUNCE: + attack = new CUnitAttackMissileBounce(animationBackswingPoint, animationDamagePoint, attackType, + cooldownTime, damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, + rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, + projectileHomingEnabled, projectileSpeed, damageLossFactor, maximumNumberOfTargets, + areaOfEffectFullDamage, areaOfEffectTargets); + break; + case MSPLASH: + case ARTILLERY: + attack = new CUnitAttackMissileSplash(animationBackswingPoint, animationDamagePoint, attackType, + cooldownTime, damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, + rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, + projectileHomingEnabled, projectileSpeed, areaOfEffectFullDamage, areaOfEffectMediumDamage, + areaOfEffectSmallDamage, areaOfEffectTargets, damageFactorMedium, damageFactorSmall); + break; + case MLINE: + case ALINE: + attack = new CUnitAttackMissileLine(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, + damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, + targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, projectileHomingEnabled, + projectileSpeed, damageSpillDistance, damageSpillRadius); + break; + case INSTANT: + attack = new CUnitAttackInstant(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, + damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, + targetsAllowed, weaponSound, weaponType, projectileArt); + break; + default: + case NORMAL: + attack = new CUnitAttackNormal(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, + damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, + targetsAllowed, weaponSound, weaponType); + break; + } + return attack; + } - public float getPropulsionWindow(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(PROPULSION_WINDOW, 0); - } + public float getPropulsionWindow(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(PROPULSION_WINDOW, 0); + } - public float getTurnRate(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(TURN_RATE, 0); - } + public float getTurnRate(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(TURN_RATE, 0); + } - public boolean isBuilding(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsBoolean(IS_BLDG, 0); - } + public boolean isBuilding(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsBoolean(IS_BLDG, 0); + } - public String getName(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getName(); - } + public String getName(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getName(); + } - public int getA1MinDamage(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_BASE, 0) - + this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_DICE, 0); - } + public int getA1MinDamage(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_BASE, 0) + + this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_DICE, 0); + } - public int getA1MaxDamage(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_BASE, 0) - + (this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_DICE, 0) - * this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_SIDES_PER_DIE, 0)); - } + public int getA1MaxDamage(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_BASE, 0) + + (this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_DICE, 0) + * this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_SIDES_PER_DIE, 0)); + } - public int getA2MinDamage(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_BASE, 0) - + this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_DICE, 0); - } + public int getA2MinDamage(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_BASE, 0) + + this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_DICE, 0); + } - public int getA2MaxDamage(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_BASE, 0) - + (this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_DICE, 0) - * this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_SIDES_PER_DIE, 0)); - } + public int getA2MaxDamage(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_BASE, 0) + + (this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_DICE, 0) + * this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_SIDES_PER_DIE, 0)); + } - public int getDefense(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsInteger(DEFENSE, 0); - } + public int getDefense(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(DEFENSE, 0); + } - public int getA1ProjectileSpeed(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_PROJECTILE_SPEED, 0); - } + public int getA1ProjectileSpeed(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_PROJECTILE_SPEED, 0); + } - public float getA1ProjectileArc(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK1_PROJECTILE_ARC, 0); - } + public float getA1ProjectileArc(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK1_PROJECTILE_ARC, 0); + } - public int getA2ProjectileSpeed(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_PROJECTILE_SPEED, 0); - } + public int getA2ProjectileSpeed(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_PROJECTILE_SPEED, 0); + } - public float getA2ProjectileArc(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK2_PROJECTILE_ARC, 0); - } + public float getA2ProjectileArc(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK2_PROJECTILE_ARC, 0); + } - public String getA1MissileArt(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsString(ATTACK1_MISSILE_ART, 0); - } + public String getA1MissileArt(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsString(ATTACK1_MISSILE_ART, 0); + } - public String getA2MissileArt(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsString(ATTACK2_MISSILE_ART, 0); - } + public String getA2MissileArt(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsString(ATTACK2_MISSILE_ART, 0); + } - public float getA1Cooldown(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK1_COOLDOWN, 0); - } + public float getA1Cooldown(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK1_COOLDOWN, 0); + } - public float getA2Cooldown(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK2_COOLDOWN, 0); - } + public float getA2Cooldown(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK2_COOLDOWN, 0); + } - public float getProjectileLaunchX(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(PROJECTILE_LAUNCH_X, 0); - } + public float getProjectileLaunchX(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(PROJECTILE_LAUNCH_X, 0); + } - public float getProjectileLaunchY(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(PROJECTILE_LAUNCH_Y, 0); - } + public float getProjectileLaunchY(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(PROJECTILE_LAUNCH_Y, 0); + } - public float getProjectileLaunchZ(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(PROJECTILE_LAUNCH_Z, 0); - } + public float getProjectileLaunchZ(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(PROJECTILE_LAUNCH_Z, 0); + } } From bec43bcb3e4a14a031e1e3004d243ea12a8348d0 Mon Sep 17 00:00:00 2001 From: Retera Date: Sun, 1 Nov 2020 13:05:38 -0500 Subject: [PATCH 059/116] Fix mouse intersect underground, fix anim blend, icon click sound --- .../etheller/warsmash/WarsmashGdxMapGame.java | 6 + .../fdf/frames/AbstractRenderableFrame.java | 5 + .../parsers/fdf/frames/AbstractUIFrame.java | 11 + .../parsers/fdf/frames/TextureFrame.java | 5 - .../warsmash/parsers/fdf/frames/UIFrame.java | 2 + .../warsmash/viewer5/SkeletalNode.java | 51 +- .../viewer5/handlers/w3x/SequenceUtils.java | 407 +-- .../viewer5/handlers/w3x/War3MapViewer.java | 2539 +++++++++-------- .../handlers/w3x/environment/Terrain.java | 7 +- .../handlers/w3x/rendersim/RenderUnit.java | 20 +- .../w3x/rendersim/ability/AbilityDataUI.java | 62 +- .../commandbuttons/CommandButtonListener.java | 2 +- .../CommandCardPopulatingAbilityVisitor.java | 197 +- .../handlers/w3x/simulation/CUnitType.java | 35 +- .../abilities/AbstractCAbility.java | 14 + .../simulation/abilities/CAbilityVisitor.java | 20 + .../build/AbstractCAbilityBuild.java | 78 + .../abilities/build/CAbilityHumanBuild.java | 61 + .../abilities/build/CAbilityNagaBuild.java | 53 + .../abilities/build/CAbilityNeutralBuild.java | 53 + .../build/CAbilityNightElfBuild.java | 61 + .../abilities/build/CAbilityOrcBuild.java | 53 + .../abilities/build/CAbilityUndeadBuild.java | 61 + .../{ => combat}/CAbilityColdArrows.java | 4 +- .../abilities/menu/CAbilityMenu.java | 8 + .../simulation/behaviors/CBehaviorMove.java | 21 +- .../w3x/simulation/data/CAbilityData.java | 2 +- .../w3x/simulation/data/CUnitData.java | 823 +++--- .../w3x/simulation/data/CUnitRace.java | 32 + .../w3x/simulation/orders/COrderNoTarget.java | 4 + .../util/AbilityActivationReceiver.java | 2 +- .../BooleanAbilityActivationReceiver.java | 2 +- .../util/SimulationRenderController.java | 5 + .../StringMsgAbilityActivationReceiver.java | 4 +- .../handlers/w3x/ui/CommandCardIcon.java | 51 +- .../viewer5/handlers/w3x/ui/MeleeUI.java | 107 +- .../command/CommandCardCommandListener.java | 2 + .../handlers/w3x/ui/sound/KeyedSounds.java | 29 + 38 files changed, 2950 insertions(+), 1949 deletions(-) create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/AbstractCAbility.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityHumanBuild.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityNagaBuild.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityNeutralBuild.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityNightElfBuild.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityOrcBuild.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityUndeadBuild.java rename core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/{ => combat}/CAbilityColdArrows.java (95%) create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/menu/CAbilityMenu.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitRace.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/sound/KeyedSounds.java diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java index 55e06f0..df38da2 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java @@ -157,6 +157,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv final Scene portraitScene = this.viewer.addSimpleScene(); this.uiScene = this.viewer.addSimpleScene(); this.uiScene.alpha = true; + this.uiScene.enableAudio(); // this.mainModel = (MdxModel) this.viewer.load("UI\\Glues\\MainMenu\\MainMenu3D_exp\\MainMenu3D_exp.mdx", @@ -385,6 +386,11 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv @Override public boolean touchUp(final int screenX, final int screenY, final int pointer, final int button) { + final float worldScreenY = getHeight() - screenY; + + if (this.meleeUI.touchUp(screenX, screenY, worldScreenY, button)) { + return false; + } return false; } diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java index 893401f..f6d0894 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java @@ -346,6 +346,11 @@ public abstract class AbstractRenderableFrame implements UIFrame { return null; } + @Override + public UIFrame touchUp(final float screenX, final float screenY, final int button) { + return null; + } + @Override public String getName() { return this.name; diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractUIFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractUIFrame.java index 8a7bbb7..91e12d0 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractUIFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractUIFrame.java @@ -46,4 +46,15 @@ public abstract class AbstractUIFrame extends AbstractRenderableFrame implements } return super.touchDown(screenX, screenY, button); } + + @Override + public UIFrame touchUp(final float screenX, final float screenY, final int button) { + for (final UIFrame childFrame : this.childFrames) { + final UIFrame clickedChild = childFrame.touchUp(screenX, screenY, button); + if (clickedChild != null) { + return clickedChild; + } + } + return super.touchUp(screenX, screenY, button); + } } diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/TextureFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/TextureFrame.java index 203a167..c3f5190 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/TextureFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/TextureFrame.java @@ -63,9 +63,4 @@ public class TextureFrame extends AbstractRenderableFrame { public void setTexture(final TextureRegion texture) { this.texture = texture; } - - @Override - public UIFrame touchDown(final float screenX, final float screenY, final int button) { - return super.touchDown(screenX, screenY, button); - } } diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/UIFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/UIFrame.java index b6ceca7..cb819c0 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/UIFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/UIFrame.java @@ -34,5 +34,7 @@ public interface UIFrame { UIFrame touchDown(float screenX, float screenY, int button); + UIFrame touchUp(float screenX, float screenY, int button); + String getName(); } diff --git a/core/src/com/etheller/warsmash/viewer5/SkeletalNode.java b/core/src/com/etheller/warsmash/viewer5/SkeletalNode.java index 134d393..0c6359b 100644 --- a/core/src/com/etheller/warsmash/viewer5/SkeletalNode.java +++ b/core/src/com/etheller/warsmash/viewer5/SkeletalNode.java @@ -13,6 +13,9 @@ public abstract class SkeletalNode extends GenericNode { protected static final Quaternion rotationHeap = new Quaternion(); protected static final Quaternion rotationHeap2 = new Quaternion(); protected static final Vector3 scalingHeap = new Vector3(); + protected static final Vector3 blendLocationHeap = new Vector3(); + protected static final Vector3 blendHeap = new Vector3(); + protected static final Vector3 blendScaleHeap = new Vector3(); public UpdatableObject object; @@ -21,7 +24,9 @@ public abstract class SkeletalNode extends GenericNode { public boolean billboardedY; public boolean billboardedZ; - public Matrix4 localBlendMatrix; + public Vector3 localBlendLocation; + public Quaternion localBlendRotation; + public Vector3 localBlendScale; public SkeletalNode() { this.pivot = new Vector3(); @@ -35,7 +40,9 @@ public abstract class SkeletalNode extends GenericNode { this.inverseWorldRotation = new Quaternion(); this.inverseWorldScale = new Vector3(); this.localMatrix = new Matrix4(); - this.localBlendMatrix = new Matrix4(); + this.localBlendLocation = new Vector3(); + this.localBlendRotation = new Quaternion(0, 0, 0, 1); + this.localBlendScale = new Vector3(1, 1, 1); this.worldMatrix = new Matrix4(); this.dontInheritTranslation = false; this.dontInheritRotation = false; @@ -70,8 +77,10 @@ public abstract class SkeletalNode extends GenericNode { } public void recalculateTransformation(final Scene scene, final float blendTimeRatio) { + final float inverseBlendRatio = 1 - blendTimeRatio; final Quaternion computedRotation; Vector3 computedScaling; + Vector3 computedLocation; if (this.dontInheritScaling) { computedScaling = scalingHeap; @@ -86,7 +95,14 @@ public abstract class SkeletalNode extends GenericNode { this.worldScale.z = this.localScale.z; } else { - computedScaling = this.localScale; + if (!Float.isNaN(blendTimeRatio) && (blendTimeRatio > 0)) { + blendScaleHeap.set(this.localScale).scl(inverseBlendRatio) + .add(blendHeap.set(this.localBlendScale).scl(blendTimeRatio)); + computedScaling = blendScaleHeap; + } + else { + computedScaling = this.localScale; + } final Vector3 parentScale = this.parent.worldScale; this.worldScale.x = parentScale.x * this.localScale.x; @@ -133,18 +149,25 @@ public abstract class SkeletalNode extends GenericNode { computedRotation.setFromAxisRad(billboardAxisHeap, angle); } else { - computedRotation = this.localRotation; - } - - RenderMathUtils.fromRotationTranslationScaleOrigin(computedRotation, this.localLocation, computedScaling, - this.localMatrix, this.pivot); - if (!Float.isNaN(blendTimeRatio) && (blendTimeRatio > 0)) { - for (int i = 0; i < this.localMatrix.val.length; i++) { - this.localMatrix.val[i] = (this.localBlendMatrix.val[i] * blendTimeRatio) - + (this.localMatrix.val[i] * (1 - blendTimeRatio)); + if (!Float.isNaN(blendTimeRatio) && (blendTimeRatio > 0)) { + rotationHeap.set(this.localRotation).slerp(this.localBlendRotation, blendTimeRatio); + computedRotation = rotationHeap; + } + else { + computedRotation = this.localRotation; } } + if (!Float.isNaN(blendTimeRatio) && (blendTimeRatio > 0)) { + computedLocation = blendLocationHeap.set(this.localLocation).scl(inverseBlendRatio) + .add(blendHeap.set(this.localBlendLocation).scl(blendTimeRatio)); + } + else { + computedLocation = this.localLocation; + } + RenderMathUtils.fromRotationTranslationScaleOrigin(computedRotation, computedLocation, computedScaling, + this.localMatrix, this.pivot); + RenderMathUtils.mul(this.worldMatrix, this.parent.worldMatrix, this.localMatrix); RenderMathUtils.mul(this.worldRotation, this.parent.worldRotation, computedRotation); @@ -178,7 +201,9 @@ public abstract class SkeletalNode extends GenericNode { } public void beginBlending() { - this.localBlendMatrix.set(this.localMatrix); + this.localBlendLocation.set(this.localLocation); + this.localBlendRotation.set(this.localRotation); + this.localBlendScale.set(this.localScale); } public void updateChildren(final float dt, final Scene scene) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SequenceUtils.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SequenceUtils.java index cfd9e99..d513062 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SequenceUtils.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SequenceUtils.java @@ -12,244 +12,255 @@ import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.SecondaryTag; public class SequenceUtils { - public static final EnumSet EMPTY = EnumSet.noneOf(SecondaryTag.class); - public static final EnumSet READY = EnumSet.of(SecondaryTag.READY); - public static final EnumSet FLESH = EnumSet.of(SecondaryTag.FLESH); - public static final EnumSet BONE = EnumSet.of(SecondaryTag.BONE); + public static final EnumSet EMPTY = EnumSet.noneOf(SecondaryTag.class); + public static final EnumSet READY = EnumSet.of(SecondaryTag.READY); + public static final EnumSet FLESH = EnumSet.of(SecondaryTag.FLESH); + public static final EnumSet TALK = EnumSet.of(SecondaryTag.TALK); + public static final EnumSet BONE = EnumSet.of(SecondaryTag.BONE); - private static final StandSequenceComparator STAND_SEQUENCE_COMPARATOR = new StandSequenceComparator(); - private static final SecondaryTagSequenceComparator SECONDARY_TAG_SEQUENCE_COMPARATOR = new SecondaryTagSequenceComparator(STAND_SEQUENCE_COMPARATOR); + private static final StandSequenceComparator STAND_SEQUENCE_COMPARATOR = new StandSequenceComparator(); + private static final SecondaryTagSequenceComparator SECONDARY_TAG_SEQUENCE_COMPARATOR = new SecondaryTagSequenceComparator( + STAND_SEQUENCE_COMPARATOR); - public static List filterSequences(final String type, final List sequences) { - final List filtered = new ArrayList<>(); + public static List filterSequences(final String type, final List sequences) { + final List filtered = new ArrayList<>(); - for (int i = 0, l = sequences.size(); i < l; i++) { - final Sequence sequence = sequences.get(i); - final String name = sequence.getName().split("-")[0].trim().toLowerCase(); + for (int i = 0, l = sequences.size(); i < l; i++) { + final Sequence sequence = sequences.get(i); + final String name = sequence.getName().split("-")[0].trim().toLowerCase(); - if (name.equals(type)) { - filtered.add(new IndexedSequence(sequence, i)); - } - } + if (name.equals(type)) { + filtered.add(new IndexedSequence(sequence, i)); + } + } - return filtered; - } + return filtered; + } - private static List filterSequences(final PrimaryTag type, final EnumSet tags, - final List sequences) { - final List filtered = new ArrayList<>(); + private static List filterSequences(final PrimaryTag type, final EnumSet tags, + final List sequences) { + final List filtered = new ArrayList<>(); - for (int i = 0, l = sequences.size(); i < l; i++) { - final Sequence sequence = sequences.get(i); - if (sequence.getPrimaryTags().contains(type) && (sequence.getSecondaryTags().containsAll(tags) - && tags.containsAll(sequence.getSecondaryTags()))) { - filtered.add(new IndexedSequence(sequence, i)); - } - } + for (int i = 0, l = sequences.size(); i < l; i++) { + final Sequence sequence = sequences.get(i); + if (sequence.getPrimaryTags().contains(type) && (sequence.getSecondaryTags().containsAll(tags) + && tags.containsAll(sequence.getSecondaryTags()))) { + filtered.add(new IndexedSequence(sequence, i)); + } + } - return filtered; - } + return filtered; + } - public static IndexedSequence selectSequence(final String type, final List sequences) { - final List filtered = filterSequences(type, sequences); + public static IndexedSequence selectSequence(final String type, final List sequences) { + final List filtered = filterSequences(type, sequences); - filtered.sort(STAND_SEQUENCE_COMPARATOR); + filtered.sort(STAND_SEQUENCE_COMPARATOR); - int i = 0; - final double randomRoll = Math.random() * 100; - for (final int l = filtered.size(); i < l; i++) { - final Sequence sequence = filtered.get(i).sequence; - final float rarity = sequence.getRarity(); + int i = 0; + final double randomRoll = Math.random() * 100; + for (final int l = filtered.size(); i < l; i++) { + final Sequence sequence = filtered.get(i).sequence; + final float rarity = sequence.getRarity(); - if (rarity == 0) { - break; - } + if (rarity == 0) { + break; + } - if (randomRoll < (10 - rarity)) { - return filtered.get(i); - } - } + if (randomRoll < (10 - rarity)) { + return filtered.get(i); + } + } - final int sequencesLeft = filtered.size() - i; - final int random = (int) (i + Math.floor(Math.random() * sequencesLeft)); - if (sequencesLeft <= 0) { - return null; // new IndexedSequence(null, 0); - } - final IndexedSequence sequence = filtered.get(random); + final int sequencesLeft = filtered.size() - i; + final int random = (int) (i + Math.floor(Math.random() * sequencesLeft)); + if (sequencesLeft <= 0) { + return null; // new IndexedSequence(null, 0); + } + final IndexedSequence sequence = filtered.get(random); - return sequence; - } + return sequence; + } - public static IndexedSequence selectSequence(final AnimationTokens.PrimaryTag type, - final EnumSet tags, final List sequences, - final boolean allowRarityVariations) { - List filtered = filterSequences(type, tags, sequences); - Comparator sequenceComparator = STAND_SEQUENCE_COMPARATOR; + public static IndexedSequence selectSequence(final AnimationTokens.PrimaryTag type, + final EnumSet tags, final List sequences, + final boolean allowRarityVariations) { + List filtered = filterSequences(type, tags, sequences); + final Comparator sequenceComparator = STAND_SEQUENCE_COMPARATOR; - if (filtered.isEmpty() && !tags.isEmpty()) { - filtered = filterSequences(type, EMPTY, sequences); - } - if (filtered.isEmpty()) { - // find tags - EnumSet fallbackTags = null; - for (int i = 0, l = sequences.size(); i < l; i++) { - final Sequence sequence = sequences.get(i); - if (sequence.getPrimaryTags().contains(type)) { - if (fallbackTags == null || sequence.getSecondaryTags().size() < fallbackTags.size() - || (sequence.getSecondaryTags().size() == fallbackTags.size() && SecondaryTagSequenceComparator.getTagsOrdinal(sequence.getSecondaryTags(), tags) - > SecondaryTagSequenceComparator.getTagsOrdinal(fallbackTags, tags)) - ) { - fallbackTags = sequence.getSecondaryTags(); - } - } - } - if (fallbackTags != null) { - filtered = filterSequences(type, fallbackTags, sequences); - } - } + if (filtered.isEmpty() && !tags.isEmpty()) { + filtered = filterSequences(type, EMPTY, sequences); + } + if (filtered.isEmpty()) { + // find tags + EnumSet fallbackTags = null; + for (int i = 0, l = sequences.size(); i < l; i++) { + final Sequence sequence = sequences.get(i); + if (sequence.getPrimaryTags().contains(type)) { + if ((fallbackTags == null) || (sequence.getSecondaryTags().size() < fallbackTags.size()) + || ((sequence.getSecondaryTags().size() == fallbackTags.size()) + && (SecondaryTagSequenceComparator.getTagsOrdinal(sequence.getSecondaryTags(), + tags) > SecondaryTagSequenceComparator.getTagsOrdinal(fallbackTags, + tags)))) { + fallbackTags = sequence.getSecondaryTags(); + } + } + } + if (fallbackTags != null) { + filtered = filterSequences(type, fallbackTags, sequences); + } + } - filtered.sort(sequenceComparator); + filtered.sort(sequenceComparator); - int i = 0; - final double randomRoll = Math.random() * 100; - for (final int l = filtered.size(); i < l; i++) { - final Sequence sequence = filtered.get(i).sequence; - final float rarity = sequence.getRarity(); + int i = 0; + final double randomRoll = Math.random() * 100; + for (final int l = filtered.size(); i < l; i++) { + final Sequence sequence = filtered.get(i).sequence; + final float rarity = sequence.getRarity(); - if (rarity == 0) { - break; - } + if (rarity == 0) { + break; + } - if ((randomRoll < (10 - rarity)) && allowRarityVariations) { - return filtered.get(i); - } - } + if ((randomRoll < (10 - rarity)) && allowRarityVariations) { + return filtered.get(i); + } + } - final int sequencesLeft = filtered.size() - i; - final int random = (int) (i + Math.floor(Math.random() * sequencesLeft)); - if (sequencesLeft <= 0) { - return null; // new IndexedSequence(null, 0); - } - final IndexedSequence sequence = filtered.get(random); + final int sequencesLeft = filtered.size() - i; + final int random = (int) (i + Math.floor(Math.random() * sequencesLeft)); + if (sequencesLeft <= 0) { + return null; // new IndexedSequence(null, 0); + } + final IndexedSequence sequence = filtered.get(random); - return sequence; - } + return sequence; + } - public static void randomStandSequence(final MdxComplexInstance target) { - final MdxModel model = (MdxModel) target.model; - final List sequences = model.getSequences(); - final IndexedSequence sequence = selectSequence("stand", sequences); + public static void randomStandSequence(final MdxComplexInstance target) { + final MdxModel model = (MdxModel) target.model; + final List sequences = model.getSequences(); + final IndexedSequence sequence = selectSequence("stand", sequences); - if (sequence != null) { - target.setSequence(sequence.index); - } else { - target.setSequence(0); - } - } + if (sequence != null) { + target.setSequence(sequence.index); + } + else { + target.setSequence(0); + } + } - public static void randomDeathSequence(final MdxComplexInstance target) { - final MdxModel model = (MdxModel) target.model; - final List sequences = model.getSequences(); - final IndexedSequence sequence = selectSequence("death", sequences); + public static void randomDeathSequence(final MdxComplexInstance target) { + final MdxModel model = (MdxModel) target.model; + final List sequences = model.getSequences(); + final IndexedSequence sequence = selectSequence("death", sequences); - if (sequence != null) { - target.setSequence(sequence.index); - } else { - target.setSequence(0); - } - } + if (sequence != null) { + target.setSequence(sequence.index); + } + else { + target.setSequence(0); + } + } - public static void randomWalkSequence(final MdxComplexInstance target) { - final MdxModel model = (MdxModel) target.model; - final List sequences = model.getSequences(); - final IndexedSequence sequence = selectSequence("walk", sequences); + public static void randomWalkSequence(final MdxComplexInstance target) { + final MdxModel model = (MdxModel) target.model; + final List sequences = model.getSequences(); + final IndexedSequence sequence = selectSequence("walk", sequences); - if (sequence != null) { - target.setSequence(sequence.index); - } else { - randomStandSequence(target); - } - } + if (sequence != null) { + target.setSequence(sequence.index); + } + else { + randomStandSequence(target); + } + } - public static void randomBirthSequence(final MdxComplexInstance target) { - final MdxModel model = (MdxModel) target.model; - final List sequences = model.getSequences(); - final IndexedSequence sequence = selectSequence("birth", sequences); + public static void randomBirthSequence(final MdxComplexInstance target) { + final MdxModel model = (MdxModel) target.model; + final List sequences = model.getSequences(); + final IndexedSequence sequence = selectSequence("birth", sequences); - if (sequence != null) { - target.setSequence(sequence.index); - } else { - randomStandSequence(target); - } - } + if (sequence != null) { + target.setSequence(sequence.index); + } + else { + randomStandSequence(target); + } + } - public static void randomPortraitSequence(final MdxComplexInstance target) { - final MdxModel model = (MdxModel) target.model; - final List sequences = model.getSequences(); - final IndexedSequence sequence = selectSequence("portrait", sequences); + public static void randomPortraitSequence(final MdxComplexInstance target) { + final MdxModel model = (MdxModel) target.model; + final List sequences = model.getSequences(); + final IndexedSequence sequence = selectSequence("portrait", sequences); - if (sequence != null) { - target.setSequence(sequence.index); - } else { - randomStandSequence(target); - } - } + if (sequence != null) { + target.setSequence(sequence.index); + } + else { + randomStandSequence(target); + } + } - public static void randomPortraitTalkSequence(final MdxComplexInstance target) { - final MdxModel model = (MdxModel) target.model; - final List sequences = model.getSequences(); - final IndexedSequence sequence = selectSequence("portrait talk", sequences); + public static void randomPortraitTalkSequence(final MdxComplexInstance target) { + final MdxModel model = (MdxModel) target.model; + final List sequences = model.getSequences(); + final IndexedSequence sequence = selectSequence("portrait talk", sequences); - if (sequence != null) { - target.setSequence(sequence.index); - } else { - randomPortraitSequence(target); - } - } + if (sequence != null) { + target.setSequence(sequence.index); + } + else { + randomPortraitSequence(target); + } + } - public static void randomSequence(final MdxComplexInstance target, final String sequenceName) { - final MdxModel model = (MdxModel) target.model; - final List sequences = model.getSequences(); - final IndexedSequence sequence = selectSequence(sequenceName, sequences); + public static void randomSequence(final MdxComplexInstance target, final String sequenceName) { + final MdxModel model = (MdxModel) target.model; + final List sequences = model.getSequences(); + final IndexedSequence sequence = selectSequence(sequenceName, sequences); - if (sequence != null) { - target.setSequence(sequence.index); - } else { - randomStandSequence(target); - } - } + if (sequence != null) { + target.setSequence(sequence.index); + } + else { + randomStandSequence(target); + } + } - public static Sequence randomSequence(final MdxComplexInstance target, final PrimaryTag animationName, - final boolean allowRarityVariations) { - final MdxModel model = (MdxModel) target.model; - final List sequences = model.getSequences(); - final IndexedSequence sequence = selectSequence(animationName, null, sequences, - allowRarityVariations); + public static Sequence randomSequence(final MdxComplexInstance target, final PrimaryTag animationName, + final boolean allowRarityVariations) { + final MdxModel model = (MdxModel) target.model; + final List sequences = model.getSequences(); + final IndexedSequence sequence = selectSequence(animationName, null, sequences, allowRarityVariations); - if (sequence != null) { - target.setSequence(sequence.index); - return sequence.sequence; - } else { - return null; - } - } + if (sequence != null) { + target.setSequence(sequence.index); + return sequence.sequence; + } + else { + return null; + } + } - public static Sequence randomSequence(final MdxComplexInstance target, final PrimaryTag animationName, - final EnumSet secondaryAnimationTags, final boolean allowRarityVariations) { - final MdxModel model = (MdxModel) target.model; - final List sequences = model.getSequences(); - final IndexedSequence sequence = selectSequence(animationName, secondaryAnimationTags, sequences, - allowRarityVariations); + public static Sequence randomSequence(final MdxComplexInstance target, final PrimaryTag animationName, + final EnumSet secondaryAnimationTags, final boolean allowRarityVariations) { + final MdxModel model = (MdxModel) target.model; + final List sequences = model.getSequences(); + final IndexedSequence sequence = selectSequence(animationName, secondaryAnimationTags, sequences, + allowRarityVariations); - if (sequence != null) { - target.setSequence(sequence.index); - return sequence.sequence; - } else { - return null; - } - } + if (sequence != null) { + target.setSequence(sequence.index); + return sequence.sequence; + } + else { + return null; + } + } - public static Sequence randomSequence(final MdxComplexInstance target, final PrimaryTag animationName) { - return randomSequence(target, animationName, EMPTY, false); - } + public static Sequence randomSequence(final MdxComplexInstance target, final PrimaryTag animationName) { + return randomSequence(target, animationName, EMPTY, false); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index 0376097..d46b562 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -90,1306 +90,1373 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUni import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackMissile; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.projectile.CAttackProjectile; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.SimulationRenderController; +import com.etheller.warsmash.viewer5.handlers.w3x.ui.sound.KeyedSounds; import mpq.MPQArchive; import mpq.MPQException; public class War3MapViewer extends ModelViewer { - private static final War3ID UNIT_FILE = War3ID.fromString("umdl"); - private static final War3ID UBER_SPLAT = War3ID.fromString("uubs"); - private static final War3ID UNIT_SHADOW = War3ID.fromString("ushu"); - private static final War3ID UNIT_SHADOW_X = War3ID.fromString("ushx"); - private static final War3ID UNIT_SHADOW_Y = War3ID.fromString("ushy"); - private static final War3ID UNIT_SHADOW_W = War3ID.fromString("ushw"); - private static final War3ID UNIT_SHADOW_H = War3ID.fromString("ushh"); - private static final War3ID BUILDING_SHADOW = War3ID.fromString("ushb"); - public static final War3ID UNIT_SELECT_SCALE = War3ID.fromString("ussc"); - private static final War3ID UNIT_SELECT_HEIGHT = War3ID.fromString("uslz"); - private static final War3ID UNIT_SOUNDSET = War3ID.fromString("usnd"); - private static final War3ID ITEM_FILE = War3ID.fromString("ifil"); - private static final War3ID UNIT_PATHING = War3ID.fromString("upat"); - private static final War3ID DESTRUCTABLE_PATHING = War3ID.fromString("bptx"); - private static final War3ID ELEVATION_SAMPLE_RADIUS = War3ID.fromString("uerd"); - private static final War3ID MAX_PITCH = War3ID.fromString("umxp"); - private static final War3ID MAX_ROLL = War3ID.fromString("umxr"); - private static final War3ID sloc = War3ID.fromString("sloc"); - private static final LoadGenericCallback stringDataCallback = new StringDataCallbackImplementation(); - private static final float[] rayHeap = new float[6]; - public static final Ray gdxRayHeap = new Ray(); - private static final Vector2 mousePosHeap = new Vector2(); - private static final Vector3 normalHeap = new Vector3(); - public static final Vector3 intersectionHeap = new Vector3(); - private static final Rectangle rectangleHeap = new Rectangle(); - public static final StreamDataCallbackImplementation streamDataCallback = new StreamDataCallbackImplementation(); - - public PathSolver wc3PathSolver = PathSolver.DEFAULT; - public SolverParams solverParams = new SolverParams(); - public WorldScene worldScene; - public boolean anyReady; - public MappedData terrainData = new MappedData(); - public MappedData cliffTypesData = new MappedData(); - public MappedData waterData = new MappedData(); - public boolean terrainReady; - public boolean cliffsReady; - public boolean doodadsAndDestructiblesLoaded; - public MappedData doodadsData = new MappedData(); - public MappedData doodadMetaData = new MappedData(); - public MappedData destructableMetaData = new MappedData(); - public List doodads = new ArrayList<>(); - public List terrainDoodads = new ArrayList<>(); - public boolean doodadsReady; - public boolean unitsAndItemsLoaded; - public MappedData unitsData = new MappedData(); - public MappedData unitMetaData = new MappedData(); - public List units = new ArrayList<>(); - public List items = new ArrayList<>(); - public List projectiles = new ArrayList<>(); - public boolean unitsReady; - public War3Map mapMpq; - public PathSolver mapPathSolver = PathSolver.DEFAULT; - - private final DataSource gameDataSource; - - public Terrain terrain; - public int renderPathing = 0; - public int renderLighting = 1; - - public List selModels = new ArrayList<>(); - public List selected = new ArrayList<>(); - private DataTable unitAckSoundsTable; - private DataTable unitCombatSoundsTable; - public DataTable miscData; - private DataTable unitGlobalStrings; - private MdxComplexInstance confirmationInstance; - public MdxComplexInstance dncUnit; - public MdxComplexInstance dncUnitDay; - public MdxComplexInstance dncTerrain; - public MdxComplexInstance dncTarget; - public CSimulation simulation; - private float updateTime; - - // for World Editor, I think - public Vector2[] startLocations = new Vector2[WarsmashConstants.MAX_PLAYERS]; - - private final DynamicShadowManager dynamicShadowManager = new DynamicShadowManager(); - - private final Random seededRandom = new Random(1337L); - - private final Map filePathToPathingMap = new HashMap<>(); - - private final List selectionCircleSizes = new ArrayList<>(); - - private final Map unitToRenderPeer = new HashMap<>(); - private final Map unitIdToTypeData = new HashMap<>(); - private GameUI gameUI; - private Vector3 lightDirection; - - private Quadtree walkableObjectsTree; - private final QuadtreeIntersectorFindsWalkableRenderHeight walkablesIntersector = new QuadtreeIntersectorFindsWalkableRenderHeight(); - private final QuadtreeIntersectorFindsHitPoint walkablesIntersectionFinder = new QuadtreeIntersectorFindsHitPoint(); - private final QuadtreeIntersectorFindsHighestWalkable intersectorFindsHighestWalkable = new QuadtreeIntersectorFindsHighestWalkable(); - - public War3MapViewer(final DataSource dataSource, final CanvasProvider canvas) { - super(dataSource, canvas); - this.gameDataSource = dataSource; - - final WebGL webGL = this.webGL; - - this.addHandler(new MdxHandler()); - - this.wc3PathSolver = PathSolver.DEFAULT; - - this.worldScene = this.addWorldScene(); - - if (!this.dynamicShadowManager.setup(webGL)) { - throw new IllegalStateException("FrameBuffer setup failed"); - } - } - - public void loadSLKs(final WorldEditStrings worldEditStrings) throws IOException { - final GenericResource terrain = this.loadMapGeneric("TerrainArt\\Terrain.slk", FetchDataTypeName.SLK, - stringDataCallback); - final GenericResource cliffTypes = this.loadMapGeneric("TerrainArt\\CliffTypes.slk", FetchDataTypeName.SLK, - stringDataCallback); - final GenericResource water = this.loadMapGeneric("TerrainArt\\Water.slk", FetchDataTypeName.SLK, - stringDataCallback); - - // == when loaded, which is always in our system == - this.terrainData.load(terrain.data.toString()); - this.cliffTypesData.load(cliffTypes.data.toString()); - this.waterData.load(water.data.toString()); - // emit terrain loaded?? - - final GenericResource doodads = this.loadMapGeneric("Doodads\\Doodads.slk", FetchDataTypeName.SLK, - stringDataCallback); - final GenericResource doodadMetaData = this.loadMapGeneric("Doodads\\DoodadMetaData.slk", FetchDataTypeName.SLK, - stringDataCallback); - final GenericResource destructableData = this.loadMapGeneric("Units\\DestructableData.slk", - FetchDataTypeName.SLK, stringDataCallback); - final GenericResource destructableMetaData = this.loadMapGeneric("Units\\DestructableMetaData.slk", - FetchDataTypeName.SLK, stringDataCallback); - - // == when loaded, which is always in our system == - this.doodadsAndDestructiblesLoaded = true; - this.doodadsData.load(doodads.data.toString()); - this.doodadMetaData.load(doodadMetaData.data.toString()); - this.doodadsData.load(destructableData.data.toString()); - this.destructableMetaData.load(destructableData.data.toString()); - // emit doodads loaded - - final GenericResource unitData = this.loadMapGeneric("Units\\UnitData.slk", FetchDataTypeName.SLK, - stringDataCallback); - final GenericResource unitUi = this.loadMapGeneric("Units\\unitUI.slk", FetchDataTypeName.SLK, - stringDataCallback); - final GenericResource itemData = this.loadMapGeneric("Units\\ItemData.slk", FetchDataTypeName.SLK, - stringDataCallback); - final GenericResource unitMetaData = this.loadMapGeneric("Units\\UnitMetaData.slk", FetchDataTypeName.SLK, - stringDataCallback); - - // == when loaded, which is always in our system == - this.unitsAndItemsLoaded = true; - this.unitsData.load(unitData.data.toString()); - this.unitsData.load(unitUi.data.toString()); - this.unitsData.load(itemData.data.toString()); - this.unitMetaData.load(unitMetaData.data.toString()); - // emit loaded - - this.unitAckSoundsTable = new DataTable(worldEditStrings); - try (InputStream terrainSlkStream = this.dataSource.getResourceAsStream("UI\\SoundInfo\\UnitAckSounds.slk")) { - this.unitAckSoundsTable.readSLK(terrainSlkStream); - } - this.unitCombatSoundsTable = new DataTable(worldEditStrings); - try (InputStream terrainSlkStream = this.dataSource - .getResourceAsStream("UI\\SoundInfo\\UnitCombatSounds.slk")) { - this.unitCombatSoundsTable.readSLK(terrainSlkStream); - } - this.miscData = new DataTable(worldEditStrings); - try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("UI\\MiscData.txt")) { - this.miscData.readTXT(miscDataTxtStream, true); - } - try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\MiscData.txt")) { - this.miscData.readTXT(miscDataTxtStream, true); - } - try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\MiscGame.txt")) { - this.miscData.readTXT(miscDataTxtStream, true); - } - try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("UI\\SoundInfo\\MiscData.txt")) { - this.miscData.readTXT(miscDataTxtStream, true); - } - if (this.dataSource.has("war3mapMisc.txt")) { - try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("war3mapMisc.txt")) { - this.miscData.readTXT(miscDataTxtStream, true); - } - } - final Element light = this.miscData.get("Light"); - final float lightX = light.getFieldFloatValue("Direction", 0); - final float lightY = light.getFieldFloatValue("Direction", 1); - final float lightZ = light.getFieldFloatValue("Direction", 2); - this.lightDirection = new Vector3(lightX, lightY, lightZ).nor(); - this.unitGlobalStrings = new DataTable(worldEditStrings); - try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\UnitGlobalStrings.txt")) { - this.unitGlobalStrings.readTXT(miscDataTxtStream, true); - } - final Element categories = this.unitGlobalStrings.get("Categories"); - for (final CUnitClassification unitClassification : CUnitClassification.values()) { - if (unitClassification.getLocaleKey() != null) { - final String displayName = categories.getField(unitClassification.getLocaleKey()); - unitClassification.setDisplayName(displayName); - } - } - this.selectionCircleSizes.clear(); - final Element selectionCircleData = this.miscData.get("SelectionCircle"); - final int selectionCircleNumSizes = selectionCircleData.getFieldValue("NumSizes"); - for (int i = 0; i < selectionCircleNumSizes; i++) { - final String indexString = i < 10 ? "0" + i : Integer.toString(i); - final float size = selectionCircleData.getFieldFloatValue("Size" + indexString); - final String texture = selectionCircleData.getField("Texture" + indexString); - final String textureDotted = selectionCircleData.getField("TextureDotted" + indexString); - this.selectionCircleSizes.add(new SelectionCircleSize(size, texture, textureDotted)); - } - this.selectionCircleScaleFactor = selectionCircleData.getFieldFloatValue("ScaleFactor"); - } - - public GenericResource loadMapGeneric(final String path, final FetchDataTypeName dataType, - final LoadGenericCallback callback) { - if (this.mapMpq == null) { - return loadGeneric(path, dataType, callback); - } else { - return loadGeneric(path, dataType, callback, this.dataSource); - } - } - - public void loadMap(final String mapFilePath) throws IOException { - final War3Map war3Map = new War3Map(this.gameDataSource, mapFilePath); - - this.mapMpq = war3Map; - - final PathSolver wc3PathSolver = this.wc3PathSolver; - - char tileset = 'A'; - - final War3MapW3i w3iFile = this.mapMpq.readMapInformation(); - - tileset = w3iFile.getTileset(); - - DataSource tilesetSource; - try { - // Slightly complex. Here's the theory: - // 1.) Copy map into RAM - // 2.) Setup a Data Source that will read assets - // from either the map or the game, giving the map priority. - SeekableByteChannel sbc; - final CompoundDataSource compoundDataSource = war3Map.getCompoundDataSource(); - try (InputStream mapStream = compoundDataSource.getResourceAsStream(tileset + ".mpq")) { - if (mapStream == null) { - tilesetSource = new CompoundDataSource(Arrays.asList(compoundDataSource, - new SubdirDataSource(compoundDataSource, tileset + ".mpq/"), - new SubdirDataSource(compoundDataSource, "_tilesets/" + tileset + ".w3mod/"))); - } else { - final byte[] mapData = IOUtils.toByteArray(mapStream); - sbc = new SeekableInMemoryByteChannel(mapData); - final DataSource internalMpqContentsDataSource = new MpqDataSource(new MPQArchive(sbc), sbc); - tilesetSource = new CompoundDataSource( - Arrays.asList(compoundDataSource, internalMpqContentsDataSource)); - } - } catch (final IOException exc) { - tilesetSource = new CompoundDataSource( - Arrays.asList(compoundDataSource, new SubdirDataSource(compoundDataSource, tileset + ".mpq/"), - new SubdirDataSource(compoundDataSource, "_tilesets/" + tileset + ".w3mod/"))); - } - } catch (final MPQException e) { - throw new RuntimeException(e); - } - setDataSource(tilesetSource); - this.worldEditStrings = new WorldEditStrings(this.dataSource); - loadSLKs(this.worldEditStrings); - - this.solverParams.tileset = Character.toLowerCase(tileset); - - final War3MapW3e terrainData = this.mapMpq.readEnvironment(); - - final War3MapWpm terrainPathing = this.mapMpq.readPathing(); - - final StandardObjectData standardObjectData = new StandardObjectData(this.dataSource); - this.worldEditData = standardObjectData.getWorldEditData(); - - this.terrain = new Terrain(terrainData, terrainPathing, w3iFile, this.webGL, this.dataSource, - this.worldEditStrings, this, this.worldEditData); - - final float[] centerOffset = terrainData.getCenterOffset(); - final int[] mapSize = terrainData.getMapSize(); - - this.terrainReady = true; - this.anyReady = true; - this.cliffsReady = true; - - // Override the grid based on the map. - this.worldScene.grid = new Grid(centerOffset[0], centerOffset[1], (mapSize[0] * 128) - 128, - (mapSize[1] * 128) - 128, 16 * 128, 16 * 128); - - final MdxModel confirmation = (MdxModel) load("UI\\Feedback\\Confirmation\\Confirmation.mdx", - PathSolver.DEFAULT, null); - this.confirmationInstance = (MdxComplexInstance) confirmation.addInstance(); - this.confirmationInstance.setSequenceLoopMode(SequenceLoopMode.NEVER_LOOP_AND_HIDE_WHEN_DONE); - this.confirmationInstance.setSequence(0); - this.confirmationInstance.setScene(this.worldScene); - - this.allObjectData = this.mapMpq.readModifications(); - this.simulation = new CSimulation(this.miscData, this.allObjectData.getUnits(), - this.allObjectData.getAbilities(), new SimulationRenderController() { - private final Map keyToCombatSound = new HashMap<>(); - - @Override - public CAttackProjectile createAttackProjectile(final CSimulation simulation, final float launchX, - final float launchY, final float launchFacing, final CUnit source, - final CUnitAttackMissile unitAttack, final CWidget target, final float damage, - final int bounceIndex) { - final War3ID typeId = source.getTypeId(); - final int projectileSpeed = unitAttack.getProjectileSpeed(); - final float projectileArc = unitAttack.getProjectileArc(); - String missileArt = unitAttack.getProjectileArt(); - final float projectileLaunchX = simulation.getUnitData().getProjectileLaunchX(typeId); - final float projectileLaunchY = simulation.getUnitData().getProjectileLaunchY(typeId); - final float projectileLaunchZ = simulation.getUnitData().getProjectileLaunchZ(typeId); - - missileArt = mdx(missileArt); - final float facing = launchFacing; - final float sinFacing = (float) Math.sin(facing); - final float cosFacing = (float) Math.cos(facing); - final float x = (launchX + (projectileLaunchY * cosFacing)) + (projectileLaunchX * sinFacing); - final float y = (launchY + (projectileLaunchY * sinFacing)) - (projectileLaunchX * cosFacing); - - final float height = War3MapViewer.this.terrain.getGroundHeight(x, y) + source.getFlyHeight() - + projectileLaunchZ; - final CAttackProjectile simulationAttackProjectile = new CAttackProjectile(x, y, - projectileSpeed, target, source, damage, unitAttack, bounceIndex); - - final MdxModel model = (MdxModel) load(missileArt, War3MapViewer.this.mapPathSolver, - War3MapViewer.this.solverParams); - final MdxComplexInstance modelInstance = (MdxComplexInstance) model.addInstance(); - modelInstance.setTeamColor(source.getPlayerIndex()); - modelInstance.setScene(War3MapViewer.this.worldScene); - if (bounceIndex == 0) { - SequenceUtils.randomBirthSequence(modelInstance); - } else { - SequenceUtils.randomStandSequence(modelInstance); - } - modelInstance.setLocation(x, y, height); - final RenderAttackProjectile renderAttackProjectile = new RenderAttackProjectile( - simulationAttackProjectile, modelInstance, height, projectileArc, War3MapViewer.this); - - War3MapViewer.this.projectiles.add(renderAttackProjectile); - - return simulationAttackProjectile; - } - - @Override - public void createInstantAttackEffect(final CSimulation cSimulation, final CUnit source, - final CUnitAttackInstant unitAttack, final CWidget target) { - final War3ID typeId = source.getTypeId(); - - String missileArt = unitAttack.getProjectileArt(); - final float projectileLaunchX = War3MapViewer.this.simulation.getUnitData() - .getProjectileLaunchX(typeId); - final float projectileLaunchY = War3MapViewer.this.simulation.getUnitData() - .getProjectileLaunchY(typeId); - missileArt = mdx(missileArt); - final float facing = (float) Math.toRadians(source.getFacing()); - final float sinFacing = (float) Math.sin(facing); - final float cosFacing = (float) Math.cos(facing); - final float x = (source.getX() + (projectileLaunchY * cosFacing)) - + (projectileLaunchX * sinFacing); - final float y = (source.getY() + (projectileLaunchY * sinFacing)) - - (projectileLaunchX * cosFacing); - - final float targetX = target.getX(); - final float targetY = target.getY(); - final float angleToTarget = (float) Math.atan2(targetY - y, targetX - x); - - final float height = War3MapViewer.this.terrain.getGroundHeight(targetX, targetY) - + target.getFlyHeight() + target.getImpactZ(); - - final MdxModel model = (MdxModel) load(missileArt, War3MapViewer.this.mapPathSolver, - War3MapViewer.this.solverParams); - final MdxComplexInstance modelInstance = (MdxComplexInstance) model.addInstance(); - modelInstance.setTeamColor(source.getPlayerIndex()); - modelInstance.setScene(War3MapViewer.this.worldScene); - SequenceUtils.randomBirthSequence(modelInstance); - modelInstance.setLocation(targetX, targetY, height); - War3MapViewer.this.projectiles - .add(new RenderAttackInstant(modelInstance, War3MapViewer.this, angleToTarget)); - } - - @Override - public void spawnUnitDamageSound(final CUnit damagedUnit, final String weaponSound, - final String armorType) { - final String key = weaponSound + armorType; - UnitSound combatSound = this.keyToCombatSound.get(key); - if (combatSound == null) { - combatSound = UnitSound.create(War3MapViewer.this.dataSource, - War3MapViewer.this.unitCombatSoundsTable, weaponSound, armorType); - this.keyToCombatSound.put(key, combatSound); - } - combatSound.play(War3MapViewer.this.worldScene.audioContext, damagedUnit.getX(), - damagedUnit.getY()); - } - - @Override - public void removeUnit(final CUnit unit) { - final RenderUnit renderUnit = War3MapViewer.this.unitToRenderPeer.remove(unit); - War3MapViewer.this.units.remove(renderUnit); - War3MapViewer.this.worldScene.removeInstance(renderUnit.instance); - } - }, this.terrain.pathingGrid, this.terrain.getEntireMap(), this.seededRandom, w3iFile.getPlayers()); - - this.walkableObjectsTree = new Quadtree<>(this.terrain.getEntireMap()); - if (this.doodadsAndDestructiblesLoaded) { - this.loadDoodadsAndDestructibles(this.allObjectData); - } else { - throw new IllegalStateException("transcription of JS has not loaded a map and has no JS async promises"); - } - - this.terrain.createWaves(); - - loadLightsAndShading(tileset); - } - - /** - * Loads the map information that should be loaded after UI, such as units, who - * need to be able to setup their UI counterparts (icons, etc) for their - * abilities while loading. This allows the dynamic creation of units while the - * game is playing to better share code with the startup sequence's creation of - * units. - * - * @throws IOException - */ - public void loadAfterUI() throws IOException { - if (this.unitsAndItemsLoaded) { - this.loadUnitsAndItems(this.allObjectData); - } else { - throw new IllegalStateException("transcription of JS has not loaded a map and has no JS async promises"); - } - - // After we finish loading units, we need to update & create the stored shadow - // information for all unit shadows - this.terrain.initShadows(); - } - - private void loadLightsAndShading(final char tileset) { - // TODO this should be set by the war3map.j actually, not by the tileset, so the - // call to set day night models is just for testing to make the test look pretty - final Element defaultTerrainLights = this.worldEditData.get("TerrainLights"); - final Element defaultUnitLights = this.worldEditData.get("UnitLights"); - setDayNightModels(defaultTerrainLights.getField(Character.toString(tileset)), - defaultUnitLights.getField(Character.toString(tileset))); - - } - - private void loadDoodadsAndDestructibles(final Warcraft3MapObjectData modifications) throws IOException { - this.applyModificationFile(this.doodadsData, this.doodadMetaData, modifications.getDoodads(), - WorldEditorDataType.DOODADS); - this.applyModificationFile(this.doodadsData, this.destructableMetaData, modifications.getDestructibles(), - WorldEditorDataType.DESTRUCTIBLES); - - final War3MapDoo doo = this.mapMpq.readDoodads(); - - for (final com.etheller.warsmash.parsers.w3x.doo.Doodad doodad : doo.getDoodads()) { - WorldEditorDataType type = WorldEditorDataType.DOODADS; - MutableGameObject row = modifications.getDoodads().get(doodad.getId()); - if (row == null) { - row = modifications.getDestructibles().get(doodad.getId()); - type = WorldEditorDataType.DESTRUCTIBLES; - } - if (row != null) { - String file = row.readSLKTag("file"); - final int numVar = row.readSLKTagInt("numVar"); - - if (file.endsWith(".mdx") || file.endsWith(".mdl")) { - file = file.substring(0, file.length() - 4); - } - - String fileVar = file; - - file += ".mdx"; - - if (numVar > 1) { - fileVar += Math.min(doodad.getVariation(), numVar - 1); - } - - fileVar += ".mdx"; - - final float maxPitch = row.readSLKTagFloat("maxPitch"); - final float maxRoll = row.readSLKTagFloat("maxRoll"); - if (type == WorldEditorDataType.DESTRUCTIBLES) { - final String shadowString = row.readSLKTag("shadow"); - if ((shadowString != null) && (shadowString.length() > 0) && !"_".equals(shadowString)) { - this.terrain.addShadow(shadowString, doodad.getLocation()[0], doodad.getLocation()[1]); - } - - final String pathingTexture = row.readSLKTag("pathTex"); - if ((pathingTexture != null) && (pathingTexture.length() > 0) && !"_".equals(pathingTexture)) { - - BufferedImage bufferedImage = this.filePathToPathingMap.get(pathingTexture.toLowerCase()); - if (bufferedImage == null) { - if (this.mapMpq.has(pathingTexture)) { - try { - bufferedImage = TgaFile.readTGA(pathingTexture, - this.mapMpq.getResourceAsStream(pathingTexture)); - this.filePathToPathingMap.put(pathingTexture.toLowerCase(), bufferedImage); - } catch (final Exception exc) { - exc.printStackTrace(); - } - } - } - if (bufferedImage != null) { - this.terrain.pathingGrid.blitPathingOverlayTexture(doodad.getLocation()[0], - doodad.getLocation()[1], (int) Math.toDegrees(doodad.getAngle()), bufferedImage); - } - } - } - // First see if the model is local. - // Doodads referring to local models may have invalid variations, so if the - // variation doesn't exist, try without a variation. - - String path; - if (this.mapMpq.has(fileVar)) { - path = fileVar; - } else { - path = file; - } - MdxModel model; - if (this.mapMpq.has(path)) { - model = (MdxModel) this.load(path, this.mapPathSolver, this.solverParams); - } else { - model = (MdxModel) this.load(fileVar, this.mapPathSolver, this.solverParams); - } - - if (type == WorldEditorDataType.DESTRUCTIBLES) { - final RenderDestructable renderDestructable = new RenderDestructable(this, model, row, doodad, type, - maxPitch, maxRoll, doodad.getLife()); - if (row.readSLKTagBoolean("walkable")) { - final float x = doodad.getLocation()[0]; - final float y = doodad.getLocation()[1]; - final BoundingBox boundingBox = model.bounds.getBoundingBox(); - final float minX = boundingBox.min.x + x; - final float minY = boundingBox.min.y + y; - final Rectangle renderDestructableBounds = new Rectangle(minX, minY, boundingBox.getWidth(), - boundingBox.getHeight()); - this.walkableObjectsTree.add((MdxComplexInstance) renderDestructable.instance, - renderDestructableBounds); - } - this.doodads.add(renderDestructable); - } else { - this.doodads.add(new RenderDoodad(this, model, row, doodad, type, maxPitch, maxRoll)); - } - } - } - - // Cliff/Terrain doodads. - for (final com.etheller.warsmash.parsers.w3x.doo.TerrainDoodad doodad : doo.getTerrainDoodads()) { - final MutableGameObject row = modifications.getDoodads().get(doodad.getId()); - String file = row.readSLKTag("file");// - if ("".equals(file)) { - final String blaBla = row.readSLKTag("file"); - System.out.println("bla"); - } - if (file.toLowerCase().endsWith(".mdl")) { - file = file.substring(0, file.length() - 4); - } - if (!file.toLowerCase().endsWith(".mdx")) { - file += ".mdx"; - } - final MdxModel model = (MdxModel) this.load(file, this.mapPathSolver, this.solverParams); - - final String pathingTexture = row.readSLKTag("pathTex"); - BufferedImage pathingTextureImage; - if ((pathingTexture != null) && (pathingTexture.length() > 0) && !"_".equals(pathingTexture)) { - - pathingTextureImage = this.filePathToPathingMap.get(pathingTexture.toLowerCase()); - if (pathingTextureImage == null) { - if (this.mapMpq.has(pathingTexture)) { - try { - pathingTextureImage = TgaFile.readTGA(pathingTexture, - this.mapMpq.getResourceAsStream(pathingTexture)); - this.filePathToPathingMap.put(pathingTexture.toLowerCase(), pathingTextureImage); - } catch (final Exception exc) { - exc.printStackTrace(); - } - } - } - } else { - pathingTextureImage = null; - } - if (pathingTextureImage != null) { - // blit out terrain cells under this TerrainDoodad - final int textureWidth = pathingTextureImage.getWidth(); - final int textureHeight = pathingTextureImage.getHeight(); - final int textureWidthTerrainCells = textureWidth / 4; - final int textureHeightTerrainCells = textureHeight / 4; - final int minCellX = ((int) doodad.getLocation()[0]); - final int minCellY = ((int) doodad.getLocation()[1]); - final int maxCellX = (minCellX + textureWidthTerrainCells) - 1; - final int maxCellY = (minCellY + textureHeightTerrainCells) - 1; - for (int j = minCellY; j <= maxCellY; j++) { - for (int i = minCellX; i <= maxCellX; i++) { - this.terrain.removeTerrainCellWithoutFlush(i, j); - } - } - this.terrain.flushRemovedTerrainCells(); - } - - System.out.println("Loading terrain doodad: " + file); - this.terrainDoodads.add(new TerrainDoodad(this, model, row, doodad, pathingTextureImage)); - } - - this.doodadsReady = true; - this.anyReady = true; - } - - private void applyModificationFile(final MappedData doodadsData2, final MappedData doodadMetaData2, - final MutableObjectData destructibles, final WorldEditorDataType dataType) { - // TODO condense ported MappedData from Ghostwolf and MutableObjectData from - // Retera - - } - - private void loadUnitsAndItems(final Warcraft3MapObjectData modifications) throws IOException { - final War3Map mpq = this.mapMpq; - - if (this.dataSource.has("war3mapUnits.doo")) { - final War3MapUnitsDoo dooFile = mpq.readUnits(); - - final Map soundsetNameToSoundset = new HashMap<>(); - - // Collect the units and items data. - UnitSoundset soundset = null; - for (final com.etheller.warsmash.parsers.w3x.unitsdoo.Unit unit : dooFile.getUnits()) { - MutableGameObject row = null; - String path = null; - Splat unitShadowSplat = null; - BufferedImage buildingPathingPixelMap = null; - - // Hardcoded? - WorldEditorDataType type = null; - if (sloc.equals(unit.getId())) { + private static final War3ID UNIT_FILE = War3ID.fromString("umdl"); + private static final War3ID UBER_SPLAT = War3ID.fromString("uubs"); + private static final War3ID UNIT_SHADOW = War3ID.fromString("ushu"); + private static final War3ID UNIT_SHADOW_X = War3ID.fromString("ushx"); + private static final War3ID UNIT_SHADOW_Y = War3ID.fromString("ushy"); + private static final War3ID UNIT_SHADOW_W = War3ID.fromString("ushw"); + private static final War3ID UNIT_SHADOW_H = War3ID.fromString("ushh"); + private static final War3ID BUILDING_SHADOW = War3ID.fromString("ushb"); + public static final War3ID UNIT_SELECT_SCALE = War3ID.fromString("ussc"); + private static final War3ID UNIT_SELECT_HEIGHT = War3ID.fromString("uslz"); + private static final War3ID UNIT_SOUNDSET = War3ID.fromString("usnd"); + private static final War3ID ITEM_FILE = War3ID.fromString("ifil"); + private static final War3ID UNIT_PATHING = War3ID.fromString("upat"); + private static final War3ID DESTRUCTABLE_PATHING = War3ID.fromString("bptx"); + private static final War3ID ELEVATION_SAMPLE_RADIUS = War3ID.fromString("uerd"); + private static final War3ID MAX_PITCH = War3ID.fromString("umxp"); + private static final War3ID MAX_ROLL = War3ID.fromString("umxr"); + private static final War3ID sloc = War3ID.fromString("sloc"); + private static final LoadGenericCallback stringDataCallback = new StringDataCallbackImplementation(); + private static final float[] rayHeap = new float[6]; + public static final Ray gdxRayHeap = new Ray(); + private static final Vector2 mousePosHeap = new Vector2(); + private static final Vector3 normalHeap = new Vector3(); + public static final Vector3 intersectionHeap = new Vector3(); + private static final Rectangle rectangleHeap = new Rectangle(); + public static final StreamDataCallbackImplementation streamDataCallback = new StreamDataCallbackImplementation(); + + public PathSolver wc3PathSolver = PathSolver.DEFAULT; + public SolverParams solverParams = new SolverParams(); + public WorldScene worldScene; + public boolean anyReady; + public MappedData terrainData = new MappedData(); + public MappedData cliffTypesData = new MappedData(); + public MappedData waterData = new MappedData(); + public boolean terrainReady; + public boolean cliffsReady; + public boolean doodadsAndDestructiblesLoaded; + public MappedData doodadsData = new MappedData(); + public MappedData doodadMetaData = new MappedData(); + public MappedData destructableMetaData = new MappedData(); + public List doodads = new ArrayList<>(); + public List terrainDoodads = new ArrayList<>(); + public boolean doodadsReady; + public boolean unitsAndItemsLoaded; + public MappedData unitsData = new MappedData(); + public MappedData unitMetaData = new MappedData(); + public List units = new ArrayList<>(); + public List items = new ArrayList<>(); + public List projectiles = new ArrayList<>(); + public boolean unitsReady; + public War3Map mapMpq; + public PathSolver mapPathSolver = PathSolver.DEFAULT; + + private final DataSource gameDataSource; + + public Terrain terrain; + public int renderPathing = 0; + public int renderLighting = 1; + + public List selModels = new ArrayList<>(); + public List selected = new ArrayList<>(); + private DataTable unitAckSoundsTable; + private DataTable unitCombatSoundsTable; + public DataTable miscData; + private DataTable unitGlobalStrings; + public DataTable uiSoundsTable; + private MdxComplexInstance confirmationInstance; + public MdxComplexInstance dncUnit; + public MdxComplexInstance dncUnitDay; + public MdxComplexInstance dncTerrain; + public MdxComplexInstance dncTarget; + public CSimulation simulation; + private float updateTime; + + // for World Editor, I think + public Vector2[] startLocations = new Vector2[WarsmashConstants.MAX_PLAYERS]; + + private final DynamicShadowManager dynamicShadowManager = new DynamicShadowManager(); + + private final Random seededRandom = new Random(1337L); + + private final Map filePathToPathingMap = new HashMap<>(); + + private final List selectionCircleSizes = new ArrayList<>(); + + private final Map unitToRenderPeer = new HashMap<>(); + private final Map unitIdToTypeData = new HashMap<>(); + private GameUI gameUI; + private Vector3 lightDirection; + + private Quadtree walkableObjectsTree; + private final QuadtreeIntersectorFindsWalkableRenderHeight walkablesIntersector = new QuadtreeIntersectorFindsWalkableRenderHeight(); + private final QuadtreeIntersectorFindsHitPoint walkablesIntersectionFinder = new QuadtreeIntersectorFindsHitPoint(); + private final QuadtreeIntersectorFindsHighestWalkable intersectorFindsHighestWalkable = new QuadtreeIntersectorFindsHighestWalkable(); + + private KeyedSounds uiSounds; + + public War3MapViewer(final DataSource dataSource, final CanvasProvider canvas) { + super(dataSource, canvas); + this.gameDataSource = dataSource; + + final WebGL webGL = this.webGL; + + this.addHandler(new MdxHandler()); + + this.wc3PathSolver = PathSolver.DEFAULT; + + this.worldScene = this.addWorldScene(); + + if (!this.dynamicShadowManager.setup(webGL)) { + throw new IllegalStateException("FrameBuffer setup failed"); + } + } + + public void loadSLKs(final WorldEditStrings worldEditStrings) throws IOException { + final GenericResource terrain = this.loadMapGeneric("TerrainArt\\Terrain.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource cliffTypes = this.loadMapGeneric("TerrainArt\\CliffTypes.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource water = this.loadMapGeneric("TerrainArt\\Water.slk", FetchDataTypeName.SLK, + stringDataCallback); + + // == when loaded, which is always in our system == + this.terrainData.load(terrain.data.toString()); + this.cliffTypesData.load(cliffTypes.data.toString()); + this.waterData.load(water.data.toString()); + // emit terrain loaded?? + + final GenericResource doodads = this.loadMapGeneric("Doodads\\Doodads.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource doodadMetaData = this.loadMapGeneric("Doodads\\DoodadMetaData.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource destructableData = this.loadMapGeneric("Units\\DestructableData.slk", + FetchDataTypeName.SLK, stringDataCallback); + final GenericResource destructableMetaData = this.loadMapGeneric("Units\\DestructableMetaData.slk", + FetchDataTypeName.SLK, stringDataCallback); + + // == when loaded, which is always in our system == + this.doodadsAndDestructiblesLoaded = true; + this.doodadsData.load(doodads.data.toString()); + this.doodadMetaData.load(doodadMetaData.data.toString()); + this.doodadsData.load(destructableData.data.toString()); + this.destructableMetaData.load(destructableData.data.toString()); + // emit doodads loaded + + final GenericResource unitData = this.loadMapGeneric("Units\\UnitData.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource unitUi = this.loadMapGeneric("Units\\unitUI.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource itemData = this.loadMapGeneric("Units\\ItemData.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource unitMetaData = this.loadMapGeneric("Units\\UnitMetaData.slk", FetchDataTypeName.SLK, + stringDataCallback); + + // == when loaded, which is always in our system == + this.unitsAndItemsLoaded = true; + this.unitsData.load(unitData.data.toString()); + this.unitsData.load(unitUi.data.toString()); + this.unitsData.load(itemData.data.toString()); + this.unitMetaData.load(unitMetaData.data.toString()); + // emit loaded + + this.unitAckSoundsTable = new DataTable(worldEditStrings); + try (InputStream terrainSlkStream = this.dataSource.getResourceAsStream("UI\\SoundInfo\\UnitAckSounds.slk")) { + this.unitAckSoundsTable.readSLK(terrainSlkStream); + } + this.unitCombatSoundsTable = new DataTable(worldEditStrings); + try (InputStream terrainSlkStream = this.dataSource + .getResourceAsStream("UI\\SoundInfo\\UnitCombatSounds.slk")) { + this.unitCombatSoundsTable.readSLK(terrainSlkStream); + } + this.miscData = new DataTable(worldEditStrings); + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("UI\\MiscData.txt")) { + this.miscData.readTXT(miscDataTxtStream, true); + } + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\MiscData.txt")) { + this.miscData.readTXT(miscDataTxtStream, true); + } + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\MiscGame.txt")) { + this.miscData.readTXT(miscDataTxtStream, true); + } + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("UI\\SoundInfo\\MiscData.txt")) { + this.miscData.readTXT(miscDataTxtStream, true); + } + if (this.dataSource.has("war3mapMisc.txt")) { + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("war3mapMisc.txt")) { + this.miscData.readTXT(miscDataTxtStream, true); + } + } + final Element light = this.miscData.get("Light"); + final float lightX = light.getFieldFloatValue("Direction", 0); + final float lightY = light.getFieldFloatValue("Direction", 1); + final float lightZ = light.getFieldFloatValue("Direction", 2); + this.lightDirection = new Vector3(lightX, lightY, lightZ).nor(); + this.unitGlobalStrings = new DataTable(worldEditStrings); + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\UnitGlobalStrings.txt")) { + this.unitGlobalStrings.readTXT(miscDataTxtStream, true); + } + final Element categories = this.unitGlobalStrings.get("Categories"); + for (final CUnitClassification unitClassification : CUnitClassification.values()) { + if (unitClassification.getLocaleKey() != null) { + final String displayName = categories.getField(unitClassification.getLocaleKey()); + unitClassification.setDisplayName(displayName); + } + } + this.selectionCircleSizes.clear(); + final Element selectionCircleData = this.miscData.get("SelectionCircle"); + final int selectionCircleNumSizes = selectionCircleData.getFieldValue("NumSizes"); + for (int i = 0; i < selectionCircleNumSizes; i++) { + final String indexString = i < 10 ? "0" + i : Integer.toString(i); + final float size = selectionCircleData.getFieldFloatValue("Size" + indexString); + final String texture = selectionCircleData.getField("Texture" + indexString); + final String textureDotted = selectionCircleData.getField("TextureDotted" + indexString); + this.selectionCircleSizes.add(new SelectionCircleSize(size, texture, textureDotted)); + } + this.selectionCircleScaleFactor = selectionCircleData.getFieldFloatValue("ScaleFactor"); + + this.uiSoundsTable = new DataTable(worldEditStrings); + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("UI\\SoundInfo\\UISounds.slk")) { + this.uiSoundsTable.readSLK(miscDataTxtStream); + } + } + + public GenericResource loadMapGeneric(final String path, final FetchDataTypeName dataType, + final LoadGenericCallback callback) { + if (this.mapMpq == null) { + return loadGeneric(path, dataType, callback); + } + else { + return loadGeneric(path, dataType, callback, this.dataSource); + } + } + + public void loadMap(final String mapFilePath) throws IOException { + final War3Map war3Map = new War3Map(this.gameDataSource, mapFilePath); + + this.mapMpq = war3Map; + + final PathSolver wc3PathSolver = this.wc3PathSolver; + + char tileset = 'A'; + + final War3MapW3i w3iFile = this.mapMpq.readMapInformation(); + + tileset = w3iFile.getTileset(); + + DataSource tilesetSource; + try { + // Slightly complex. Here's the theory: + // 1.) Copy map into RAM + // 2.) Setup a Data Source that will read assets + // from either the map or the game, giving the map priority. + SeekableByteChannel sbc; + final CompoundDataSource compoundDataSource = war3Map.getCompoundDataSource(); + try (InputStream mapStream = compoundDataSource.getResourceAsStream(tileset + ".mpq")) { + if (mapStream == null) { + tilesetSource = new CompoundDataSource(Arrays.asList(compoundDataSource, + new SubdirDataSource(compoundDataSource, tileset + ".mpq/"), + new SubdirDataSource(compoundDataSource, "_tilesets/" + tileset + ".w3mod/"))); + } + else { + final byte[] mapData = IOUtils.toByteArray(mapStream); + sbc = new SeekableInMemoryByteChannel(mapData); + final DataSource internalMpqContentsDataSource = new MpqDataSource(new MPQArchive(sbc), sbc); + tilesetSource = new CompoundDataSource( + Arrays.asList(compoundDataSource, internalMpqContentsDataSource)); + } + } + catch (final IOException exc) { + tilesetSource = new CompoundDataSource( + Arrays.asList(compoundDataSource, new SubdirDataSource(compoundDataSource, tileset + ".mpq/"), + new SubdirDataSource(compoundDataSource, "_tilesets/" + tileset + ".w3mod/"))); + } + } + catch (final MPQException e) { + throw new RuntimeException(e); + } + setDataSource(tilesetSource); + this.worldEditStrings = new WorldEditStrings(this.dataSource); + loadSLKs(this.worldEditStrings); + + this.solverParams.tileset = Character.toLowerCase(tileset); + + final War3MapW3e terrainData = this.mapMpq.readEnvironment(); + + final War3MapWpm terrainPathing = this.mapMpq.readPathing(); + + final StandardObjectData standardObjectData = new StandardObjectData(this.dataSource); + this.worldEditData = standardObjectData.getWorldEditData(); + + this.terrain = new Terrain(terrainData, terrainPathing, w3iFile, this.webGL, this.dataSource, + this.worldEditStrings, this, this.worldEditData); + + final float[] centerOffset = terrainData.getCenterOffset(); + final int[] mapSize = terrainData.getMapSize(); + + this.terrainReady = true; + this.anyReady = true; + this.cliffsReady = true; + + // Override the grid based on the map. + this.worldScene.grid = new Grid(centerOffset[0], centerOffset[1], (mapSize[0] * 128) - 128, + (mapSize[1] * 128) - 128, 16 * 128, 16 * 128); + + final MdxModel confirmation = (MdxModel) load("UI\\Feedback\\Confirmation\\Confirmation.mdx", + PathSolver.DEFAULT, null); + this.confirmationInstance = (MdxComplexInstance) confirmation.addInstance(); + this.confirmationInstance.setSequenceLoopMode(SequenceLoopMode.NEVER_LOOP_AND_HIDE_WHEN_DONE); + this.confirmationInstance.setSequence(0); + this.confirmationInstance.setScene(this.worldScene); + + this.allObjectData = this.mapMpq.readModifications(); + this.simulation = new CSimulation(this.miscData, this.allObjectData.getUnits(), + this.allObjectData.getAbilities(), new SimulationRenderController() { + private final Map keyToCombatSound = new HashMap<>(); + + @Override + public CAttackProjectile createAttackProjectile(final CSimulation simulation, final float launchX, + final float launchY, final float launchFacing, final CUnit source, + final CUnitAttackMissile unitAttack, final CWidget target, final float damage, + final int bounceIndex) { + final War3ID typeId = source.getTypeId(); + final int projectileSpeed = unitAttack.getProjectileSpeed(); + final float projectileArc = unitAttack.getProjectileArc(); + String missileArt = unitAttack.getProjectileArt(); + final float projectileLaunchX = simulation.getUnitData().getProjectileLaunchX(typeId); + final float projectileLaunchY = simulation.getUnitData().getProjectileLaunchY(typeId); + final float projectileLaunchZ = simulation.getUnitData().getProjectileLaunchZ(typeId); + + missileArt = mdx(missileArt); + final float facing = launchFacing; + final float sinFacing = (float) Math.sin(facing); + final float cosFacing = (float) Math.cos(facing); + final float x = (launchX + (projectileLaunchY * cosFacing)) + (projectileLaunchX * sinFacing); + final float y = (launchY + (projectileLaunchY * sinFacing)) - (projectileLaunchX * cosFacing); + + final float height = War3MapViewer.this.terrain.getGroundHeight(x, y) + source.getFlyHeight() + + projectileLaunchZ; + final CAttackProjectile simulationAttackProjectile = new CAttackProjectile(x, y, + projectileSpeed, target, source, damage, unitAttack, bounceIndex); + + final MdxModel model = (MdxModel) load(missileArt, War3MapViewer.this.mapPathSolver, + War3MapViewer.this.solverParams); + final MdxComplexInstance modelInstance = (MdxComplexInstance) model.addInstance(); + modelInstance.setTeamColor(source.getPlayerIndex()); + modelInstance.setScene(War3MapViewer.this.worldScene); + if (bounceIndex == 0) { + SequenceUtils.randomBirthSequence(modelInstance); + } + else { + SequenceUtils.randomStandSequence(modelInstance); + } + modelInstance.setLocation(x, y, height); + final RenderAttackProjectile renderAttackProjectile = new RenderAttackProjectile( + simulationAttackProjectile, modelInstance, height, projectileArc, War3MapViewer.this); + + War3MapViewer.this.projectiles.add(renderAttackProjectile); + + return simulationAttackProjectile; + } + + @Override + public void createInstantAttackEffect(final CSimulation cSimulation, final CUnit source, + final CUnitAttackInstant unitAttack, final CWidget target) { + final War3ID typeId = source.getTypeId(); + + String missileArt = unitAttack.getProjectileArt(); + final float projectileLaunchX = War3MapViewer.this.simulation.getUnitData() + .getProjectileLaunchX(typeId); + final float projectileLaunchY = War3MapViewer.this.simulation.getUnitData() + .getProjectileLaunchY(typeId); + missileArt = mdx(missileArt); + final float facing = (float) Math.toRadians(source.getFacing()); + final float sinFacing = (float) Math.sin(facing); + final float cosFacing = (float) Math.cos(facing); + final float x = (source.getX() + (projectileLaunchY * cosFacing)) + + (projectileLaunchX * sinFacing); + final float y = (source.getY() + (projectileLaunchY * sinFacing)) + - (projectileLaunchX * cosFacing); + + final float targetX = target.getX(); + final float targetY = target.getY(); + final float angleToTarget = (float) Math.atan2(targetY - y, targetX - x); + + final float height = War3MapViewer.this.terrain.getGroundHeight(targetX, targetY) + + target.getFlyHeight() + target.getImpactZ(); + + final MdxModel model = (MdxModel) load(missileArt, War3MapViewer.this.mapPathSolver, + War3MapViewer.this.solverParams); + final MdxComplexInstance modelInstance = (MdxComplexInstance) model.addInstance(); + modelInstance.setTeamColor(source.getPlayerIndex()); + modelInstance.setScene(War3MapViewer.this.worldScene); + SequenceUtils.randomBirthSequence(modelInstance); + modelInstance.setLocation(targetX, targetY, height); + War3MapViewer.this.projectiles + .add(new RenderAttackInstant(modelInstance, War3MapViewer.this, angleToTarget)); + } + + @Override + public void spawnUnitDamageSound(final CUnit damagedUnit, final String weaponSound, + final String armorType) { + final String key = weaponSound + armorType; + UnitSound combatSound = this.keyToCombatSound.get(key); + if (combatSound == null) { + combatSound = UnitSound.create(War3MapViewer.this.dataSource, + War3MapViewer.this.unitCombatSoundsTable, weaponSound, armorType); + this.keyToCombatSound.put(key, combatSound); + } + combatSound.play(War3MapViewer.this.worldScene.audioContext, damagedUnit.getX(), + damagedUnit.getY()); + } + + @Override + public void removeUnit(final CUnit unit) { + final RenderUnit renderUnit = War3MapViewer.this.unitToRenderPeer.remove(unit); + War3MapViewer.this.units.remove(renderUnit); + War3MapViewer.this.worldScene.removeInstance(renderUnit.instance); + } + + @Override + public BufferedImage getBuildingPathingPixelMap(final War3ID rawcode) { + return War3MapViewer.this + .getBuildingPathingPixelMap(War3MapViewer.this.allObjectData.getUnits().get(rawcode)); + } + }, this.terrain.pathingGrid, this.terrain.getEntireMap(), this.seededRandom, w3iFile.getPlayers()); + + this.walkableObjectsTree = new Quadtree<>(this.terrain.getEntireMap()); + if (this.doodadsAndDestructiblesLoaded) { + this.loadDoodadsAndDestructibles(this.allObjectData); + } + else { + throw new IllegalStateException("transcription of JS has not loaded a map and has no JS async promises"); + } + + loadSounds(); + + this.terrain.createWaves(); + + loadLightsAndShading(tileset); + } + + private void loadSounds() { + this.uiSounds = new KeyedSounds(this.uiSoundsTable, this.mapMpq); + } + + /** + * Loads the map information that should be loaded after UI, such as units, who + * need to be able to setup their UI counterparts (icons, etc) for their + * abilities while loading. This allows the dynamic creation of units while the + * game is playing to better share code with the startup sequence's creation of + * units. + * + * @throws IOException + */ + public void loadAfterUI() throws IOException { + if (this.unitsAndItemsLoaded) { + this.loadUnitsAndItems(this.allObjectData); + } + else { + throw new IllegalStateException("transcription of JS has not loaded a map and has no JS async promises"); + } + + // After we finish loading units, we need to update & create the stored shadow + // information for all unit shadows + this.terrain.initShadows(); + } + + private void loadLightsAndShading(final char tileset) { + // TODO this should be set by the war3map.j actually, not by the tileset, so the + // call to set day night models is just for testing to make the test look pretty + final Element defaultTerrainLights = this.worldEditData.get("TerrainLights"); + final Element defaultUnitLights = this.worldEditData.get("UnitLights"); + setDayNightModels(defaultTerrainLights.getField(Character.toString(tileset)), + defaultUnitLights.getField(Character.toString(tileset))); + + } + + private void loadDoodadsAndDestructibles(final Warcraft3MapObjectData modifications) throws IOException { + this.applyModificationFile(this.doodadsData, this.doodadMetaData, modifications.getDoodads(), + WorldEditorDataType.DOODADS); + this.applyModificationFile(this.doodadsData, this.destructableMetaData, modifications.getDestructibles(), + WorldEditorDataType.DESTRUCTIBLES); + + final War3MapDoo doo = this.mapMpq.readDoodads(); + + for (final com.etheller.warsmash.parsers.w3x.doo.Doodad doodad : doo.getDoodads()) { + WorldEditorDataType type = WorldEditorDataType.DOODADS; + MutableGameObject row = modifications.getDoodads().get(doodad.getId()); + if (row == null) { + row = modifications.getDestructibles().get(doodad.getId()); + type = WorldEditorDataType.DESTRUCTIBLES; + } + if (row != null) { + String file = row.readSLKTag("file"); + final int numVar = row.readSLKTagInt("numVar"); + + if (file.endsWith(".mdx") || file.endsWith(".mdl")) { + file = file.substring(0, file.length() - 4); + } + + String fileVar = file; + + file += ".mdx"; + + if (numVar > 1) { + fileVar += Math.min(doodad.getVariation(), numVar - 1); + } + + fileVar += ".mdx"; + + final float maxPitch = row.readSLKTagFloat("maxPitch"); + final float maxRoll = row.readSLKTagFloat("maxRoll"); + if (type == WorldEditorDataType.DESTRUCTIBLES) { + final String shadowString = row.readSLKTag("shadow"); + if ((shadowString != null) && (shadowString.length() > 0) && !"_".equals(shadowString)) { + this.terrain.addShadow(shadowString, doodad.getLocation()[0], doodad.getLocation()[1]); + } + + final String pathingTexture = row.readSLKTag("pathTex"); + if ((pathingTexture != null) && (pathingTexture.length() > 0) && !"_".equals(pathingTexture)) { + + BufferedImage bufferedImage = this.filePathToPathingMap.get(pathingTexture.toLowerCase()); + if (bufferedImage == null) { + if (this.mapMpq.has(pathingTexture)) { + try { + bufferedImage = TgaFile.readTGA(pathingTexture, + this.mapMpq.getResourceAsStream(pathingTexture)); + this.filePathToPathingMap.put(pathingTexture.toLowerCase(), bufferedImage); + } + catch (final Exception exc) { + exc.printStackTrace(); + } + } + } + if (bufferedImage != null) { + this.terrain.pathingGrid.blitPathingOverlayTexture(doodad.getLocation()[0], + doodad.getLocation()[1], (int) Math.toDegrees(doodad.getAngle()), bufferedImage); + } + } + } + // First see if the model is local. + // Doodads referring to local models may have invalid variations, so if the + // variation doesn't exist, try without a variation. + + String path; + if (this.mapMpq.has(fileVar)) { + path = fileVar; + } + else { + path = file; + } + MdxModel model; + if (this.mapMpq.has(path)) { + model = (MdxModel) this.load(path, this.mapPathSolver, this.solverParams); + } + else { + model = (MdxModel) this.load(fileVar, this.mapPathSolver, this.solverParams); + } + + if (type == WorldEditorDataType.DESTRUCTIBLES) { + final RenderDestructable renderDestructable = new RenderDestructable(this, model, row, doodad, type, + maxPitch, maxRoll, doodad.getLife()); + if (row.readSLKTagBoolean("walkable")) { + final float x = doodad.getLocation()[0]; + final float y = doodad.getLocation()[1]; + final BoundingBox boundingBox = model.bounds.getBoundingBox(); + final float minX = boundingBox.min.x + x; + final float minY = boundingBox.min.y + y; + final Rectangle renderDestructableBounds = new Rectangle(minX, minY, boundingBox.getWidth(), + boundingBox.getHeight()); + this.walkableObjectsTree.add((MdxComplexInstance) renderDestructable.instance, + renderDestructableBounds); + } + this.doodads.add(renderDestructable); + } + else { + this.doodads.add(new RenderDoodad(this, model, row, doodad, type, maxPitch, maxRoll)); + } + } + } + + // Cliff/Terrain doodads. + for (final com.etheller.warsmash.parsers.w3x.doo.TerrainDoodad doodad : doo.getTerrainDoodads()) { + final MutableGameObject row = modifications.getDoodads().get(doodad.getId()); + String file = row.readSLKTag("file");// + if ("".equals(file)) { + final String blaBla = row.readSLKTag("file"); + System.out.println("bla"); + } + if (file.toLowerCase().endsWith(".mdl")) { + file = file.substring(0, file.length() - 4); + } + if (!file.toLowerCase().endsWith(".mdx")) { + file += ".mdx"; + } + final MdxModel model = (MdxModel) this.load(file, this.mapPathSolver, this.solverParams); + + final String pathingTexture = row.readSLKTag("pathTex"); + BufferedImage pathingTextureImage; + if ((pathingTexture != null) && (pathingTexture.length() > 0) && !"_".equals(pathingTexture)) { + + pathingTextureImage = this.filePathToPathingMap.get(pathingTexture.toLowerCase()); + if (pathingTextureImage == null) { + if (this.mapMpq.has(pathingTexture)) { + try { + pathingTextureImage = TgaFile.readTGA(pathingTexture, + this.mapMpq.getResourceAsStream(pathingTexture)); + this.filePathToPathingMap.put(pathingTexture.toLowerCase(), pathingTextureImage); + } + catch (final Exception exc) { + exc.printStackTrace(); + } + } + } + } + else { + pathingTextureImage = null; + } + if (pathingTextureImage != null) { + // blit out terrain cells under this TerrainDoodad + final int textureWidth = pathingTextureImage.getWidth(); + final int textureHeight = pathingTextureImage.getHeight(); + final int textureWidthTerrainCells = textureWidth / 4; + final int textureHeightTerrainCells = textureHeight / 4; + final int minCellX = ((int) doodad.getLocation()[0]); + final int minCellY = ((int) doodad.getLocation()[1]); + final int maxCellX = (minCellX + textureWidthTerrainCells) - 1; + final int maxCellY = (minCellY + textureHeightTerrainCells) - 1; + for (int j = minCellY; j <= maxCellY; j++) { + for (int i = minCellX; i <= maxCellX; i++) { + this.terrain.removeTerrainCellWithoutFlush(i, j); + } + } + this.terrain.flushRemovedTerrainCells(); + } + + System.out.println("Loading terrain doodad: " + file); + this.terrainDoodads.add(new TerrainDoodad(this, model, row, doodad, pathingTextureImage)); + } + + this.doodadsReady = true; + this.anyReady = true; + } + + private void applyModificationFile(final MappedData doodadsData2, final MappedData doodadMetaData2, + final MutableObjectData destructibles, final WorldEditorDataType dataType) { + // TODO condense ported MappedData from Ghostwolf and MutableObjectData from + // Retera + + } + + private void loadUnitsAndItems(final Warcraft3MapObjectData modifications) throws IOException { + final War3Map mpq = this.mapMpq; + + if (this.dataSource.has("war3mapUnits.doo")) { + final War3MapUnitsDoo dooFile = mpq.readUnits(); + + final Map soundsetNameToSoundset = new HashMap<>(); + + // Collect the units and items data. + UnitSoundset soundset = null; + for (final com.etheller.warsmash.parsers.w3x.unitsdoo.Unit unit : dooFile.getUnits()) { + MutableGameObject row = null; + String path = null; + Splat unitShadowSplat = null; + BufferedImage buildingPathingPixelMap = null; + + // Hardcoded? + WorldEditorDataType type = null; + if (sloc.equals(unit.getId())) { // path = "Objects\\StartLocation\\StartLocation.mdx"; - type = null; /// ?????? - this.startLocations[unit.getPlayer()] = new Vector2(unit.getLocation()[0], unit.getLocation()[1]); - } else { - row = modifications.getUnits().get(unit.getId()); - if (row == null) { - row = modifications.getItems().get(unit.getId()); - if (row != null) { - type = WorldEditorDataType.ITEM; - path = row.getFieldAsString(ITEM_FILE, 0); + type = null; /// ?????? + this.startLocations[unit.getPlayer()] = new Vector2(unit.getLocation()[0], unit.getLocation()[1]); + } + else { + row = modifications.getUnits().get(unit.getId()); + if (row == null) { + row = modifications.getItems().get(unit.getId()); + if (row != null) { + type = WorldEditorDataType.ITEM; + path = row.getFieldAsString(ITEM_FILE, 0); - if (path.toLowerCase().endsWith(".mdl") || path.toLowerCase().endsWith(".mdx")) { - path = path.substring(0, path.length() - 4); - } + if (path.toLowerCase().endsWith(".mdl") || path.toLowerCase().endsWith(".mdx")) { + path = path.substring(0, path.length() - 4); + } - final String unitShadow = "Shadow"; - if ((unitShadow != null) && !"_".equals(unitShadow)) { - final String texture = "ReplaceableTextures\\Shadows\\" + unitShadow + ".blp"; - final float shadowX = 50; - final float shadowY = 50; - final float shadowWidth = 128; - final float shadowHeight = 128; - if (!this.terrain.splats.containsKey(texture)) { - final Splat splat = new Splat(); - splat.opacity = 0.5f; - this.terrain.splats.put(texture, splat); - } - final float x = unit.getLocation()[0] - shadowX; - final float y = unit.getLocation()[1] - shadowY; - this.terrain.splats.get(texture).locations - .add(new float[]{x, y, x + shadowWidth, y + shadowHeight, 3}); - unitShadowSplat = this.terrain.splats.get(texture); - } + final String unitShadow = "Shadow"; + if ((unitShadow != null) && !"_".equals(unitShadow)) { + final String texture = "ReplaceableTextures\\Shadows\\" + unitShadow + ".blp"; + final float shadowX = 50; + final float shadowY = 50; + final float shadowWidth = 128; + final float shadowHeight = 128; + if (!this.terrain.splats.containsKey(texture)) { + final Splat splat = new Splat(); + splat.opacity = 0.5f; + this.terrain.splats.put(texture, splat); + } + final float x = unit.getLocation()[0] - shadowX; + final float y = unit.getLocation()[1] - shadowY; + this.terrain.splats.get(texture).locations + .add(new float[] { x, y, x + shadowWidth, y + shadowHeight, 3 }); + unitShadowSplat = this.terrain.splats.get(texture); + } - path += ".mdx"; - } - } else { - type = WorldEditorDataType.UNITS; - path = row.getFieldAsString(UNIT_FILE, 0); + path += ".mdx"; + } + } + else { + type = WorldEditorDataType.UNITS; + path = row.getFieldAsString(UNIT_FILE, 0); - if (path.toLowerCase().endsWith(".mdl") || path.toLowerCase().endsWith(".mdx")) { - path = path.substring(0, path.length() - 4); - } - if ((row.readSLKTagInt("fileVerFlags") == 2) && this.dataSource.has(path + "_V1.mdx")) { - path += "_V1"; - } + if (path.toLowerCase().endsWith(".mdl") || path.toLowerCase().endsWith(".mdx")) { + path = path.substring(0, path.length() - 4); + } + if ((row.readSLKTagInt("fileVerFlags") == 2) && this.dataSource.has(path + "_V1.mdx")) { + path += "_V1"; + } - path += ".mdx"; + path += ".mdx"; - final String uberSplat = row.getFieldAsString(UBER_SPLAT, 0); - if (uberSplat != null) { - final Element uberSplatInfo = this.terrain.uberSplatTable.get(uberSplat); - if (uberSplatInfo != null) { - final String texturePath = uberSplatInfo.getField("Dir") + "\\" - + uberSplatInfo.getField("file") + ".blp"; - if (!this.terrain.splats.containsKey(texturePath)) { - this.terrain.splats.put(texturePath, new Splat()); - } - final float x = unit.getLocation()[0]; - final float y = unit.getLocation()[1]; - final float s = uberSplatInfo.getFieldFloatValue("Scale"); - this.terrain.splats.get(texturePath).locations - .add(new float[]{x - s, y - s, x + s, y + s, 1}); - } - } + final String uberSplat = row.getFieldAsString(UBER_SPLAT, 0); + if (uberSplat != null) { + final Element uberSplatInfo = this.terrain.uberSplatTable.get(uberSplat); + if (uberSplatInfo != null) { + final String texturePath = uberSplatInfo.getField("Dir") + "\\" + + uberSplatInfo.getField("file") + ".blp"; + if (!this.terrain.splats.containsKey(texturePath)) { + this.terrain.splats.put(texturePath, new Splat()); + } + final float x = unit.getLocation()[0]; + final float y = unit.getLocation()[1]; + final float s = uberSplatInfo.getFieldFloatValue("Scale"); + this.terrain.splats.get(texturePath).locations + .add(new float[] { x - s, y - s, x + s, y + s, 1 }); + } + } - final String unitShadow = row.getFieldAsString(UNIT_SHADOW, 0); - if ((unitShadow != null) && !"_".equals(unitShadow)) { - final String texture = "ReplaceableTextures\\Shadows\\" + unitShadow + ".blp"; - final float shadowX = row.getFieldAsFloat(UNIT_SHADOW_X, 0); - final float shadowY = row.getFieldAsFloat(UNIT_SHADOW_Y, 0); - final float shadowWidth = row.getFieldAsFloat(UNIT_SHADOW_W, 0); - final float shadowHeight = row.getFieldAsFloat(UNIT_SHADOW_H, 0); - if (this.mapMpq.has(texture)) { - if (!this.terrain.splats.containsKey(texture)) { - final Splat splat = new Splat(); - splat.opacity = 0.5f; - this.terrain.splats.put(texture, splat); - } - final float x = unit.getLocation()[0] - shadowX; - final float y = unit.getLocation()[1] - shadowY; - this.terrain.splats.get(texture).locations - .add(new float[]{x, y, x + shadowWidth, y + shadowHeight, 3}); - unitShadowSplat = this.terrain.splats.get(texture); - } - } + final String unitShadow = row.getFieldAsString(UNIT_SHADOW, 0); + if ((unitShadow != null) && !"_".equals(unitShadow)) { + final String texture = "ReplaceableTextures\\Shadows\\" + unitShadow + ".blp"; + final float shadowX = row.getFieldAsFloat(UNIT_SHADOW_X, 0); + final float shadowY = row.getFieldAsFloat(UNIT_SHADOW_Y, 0); + final float shadowWidth = row.getFieldAsFloat(UNIT_SHADOW_W, 0); + final float shadowHeight = row.getFieldAsFloat(UNIT_SHADOW_H, 0); + if (this.mapMpq.has(texture)) { + if (!this.terrain.splats.containsKey(texture)) { + final Splat splat = new Splat(); + splat.opacity = 0.5f; + this.terrain.splats.put(texture, splat); + } + final float x = unit.getLocation()[0] - shadowX; + final float y = unit.getLocation()[1] - shadowY; + this.terrain.splats.get(texture).locations + .add(new float[] { x, y, x + shadowWidth, y + shadowHeight, 3 }); + unitShadowSplat = this.terrain.splats.get(texture); + } + } - final String buildingShadow = row.getFieldAsString(BUILDING_SHADOW, 0); - if ((buildingShadow != null) && !"_".equals(buildingShadow)) { - this.terrain.addShadow(buildingShadow, unit.getLocation()[0], unit.getLocation()[1]); - } - final String pathingTexture = row.getFieldAsString(UNIT_PATHING, 0); - if ((pathingTexture != null) && (pathingTexture.length() > 0) && !"_".equals(pathingTexture)) { - buildingPathingPixelMap = this.filePathToPathingMap.get(pathingTexture.toLowerCase()); - if (buildingPathingPixelMap == null) { - if (pathingTexture.toLowerCase().endsWith(".tga")) { - buildingPathingPixelMap = TgaFile.readTGA(pathingTexture, - this.mapMpq.getResourceAsStream(pathingTexture)); - } else { - try (InputStream stream = this.mapMpq.getResourceAsStream(pathingTexture)) { - buildingPathingPixelMap = ImageIO.read(stream); - System.out.println("LOADING BLP PATHING: " + pathingTexture); - } - } - this.filePathToPathingMap.put(pathingTexture.toLowerCase(), buildingPathingPixelMap); - } - this.terrain.pathingGrid.blitPathingOverlayTexture(unit.getLocation()[0], - unit.getLocation()[1], (int) Math.toDegrees(unit.getAngle()), - buildingPathingPixelMap); - } + final String buildingShadow = row.getFieldAsString(BUILDING_SHADOW, 0); + if ((buildingShadow != null) && !"_".equals(buildingShadow)) { + this.terrain.addShadow(buildingShadow, unit.getLocation()[0], unit.getLocation()[1]); + } + buildingPathingPixelMap = getBuildingPathingPixelMap(row); + if (buildingPathingPixelMap != null) { + this.terrain.pathingGrid.blitPathingOverlayTexture(unit.getLocation()[0], + unit.getLocation()[1], (int) Math.toDegrees(unit.getAngle()), + buildingPathingPixelMap); + } - final String soundName = row.getFieldAsString(UNIT_SOUNDSET, 0); - UnitSoundset unitSoundset = soundsetNameToSoundset.get(soundName); - if (unitSoundset == null) { - unitSoundset = new UnitSoundset(this.dataSource, this.unitAckSoundsTable, soundName); - soundsetNameToSoundset.put(soundName, unitSoundset); - } - soundset = unitSoundset; - } - } + final String soundName = row.getFieldAsString(UNIT_SOUNDSET, 0); + UnitSoundset unitSoundset = soundsetNameToSoundset.get(soundName); + if (unitSoundset == null) { + unitSoundset = new UnitSoundset(this.dataSource, this.unitAckSoundsTable, soundName); + soundsetNameToSoundset.put(soundName, unitSoundset); + } + soundset = unitSoundset; + } + } - if (path != null) { - final MdxModel model = (MdxModel) this.load(path, this.mapPathSolver, this.solverParams); - MdxModel portraitModel; - final String portraitPath = path.substring(0, path.length() - 4) + "_portrait.mdx"; - if (this.dataSource.has(portraitPath)) { - portraitModel = (MdxModel) this.load(portraitPath, this.mapPathSolver, this.solverParams); - } else { - portraitModel = model; - } - if (type == WorldEditorDataType.UNITS) { - final float angle = (float) Math.toDegrees(unit.getAngle()); - final CUnit simulationUnit = this.simulation.createUnit(row.getAlias(), unit.getPlayer(), - unit.getLocation()[0], unit.getLocation()[1], angle, buildingPathingPixelMap); - final RenderUnitTypeData typeData = getUnitTypeData(unit.getId(), row); - final RenderUnit renderUnit = new RenderUnit(this, model, row, unit, soundset, portraitModel, - simulationUnit, typeData); - this.unitToRenderPeer.put(simulationUnit, renderUnit); - this.units.add(renderUnit); - if (unitShadowSplat != null) { - unitShadowSplat.unitMapping.add(new Consumer() { - @Override - public void accept(final SplatMover t) { - renderUnit.shadow = t; - } - }); - } - } else { - this.items.add(new RenderItem(this, model, row, unit, soundset, portraitModel)); // TODO store - // somewhere - if (unitShadowSplat != null) { - unitShadowSplat.unitMapping.add(new Consumer() { - @Override - public void accept(final SplatMover t) { + if (path != null) { + final MdxModel model = (MdxModel) this.load(path, this.mapPathSolver, this.solverParams); + MdxModel portraitModel; + final String portraitPath = path.substring(0, path.length() - 4) + "_portrait.mdx"; + if (this.dataSource.has(portraitPath)) { + portraitModel = (MdxModel) this.load(portraitPath, this.mapPathSolver, this.solverParams); + } + else { + portraitModel = model; + } + if (type == WorldEditorDataType.UNITS) { + final float angle = (float) Math.toDegrees(unit.getAngle()); + final CUnit simulationUnit = this.simulation.createUnit(row.getAlias(), unit.getPlayer(), + unit.getLocation()[0], unit.getLocation()[1], angle, buildingPathingPixelMap); + final RenderUnitTypeData typeData = getUnitTypeData(unit.getId(), row); + final RenderUnit renderUnit = new RenderUnit(this, model, row, unit, soundset, portraitModel, + simulationUnit, typeData); + this.unitToRenderPeer.put(simulationUnit, renderUnit); + this.units.add(renderUnit); + if (unitShadowSplat != null) { + unitShadowSplat.unitMapping.add(new Consumer() { + @Override + public void accept(final SplatMover t) { + renderUnit.shadow = t; + } + }); + } + } + else { + this.items.add(new RenderItem(this, model, row, unit, soundset, portraitModel)); // TODO store + // somewhere + if (unitShadowSplat != null) { + unitShadowSplat.unitMapping.add(new Consumer() { + @Override + public void accept(final SplatMover t) { - } - }); - } - } - } else { - System.err.println("Unknown unit ID: " + unit.getId()); - } - } - } + } + }); + } + } + } + else { + System.err.println("Unknown unit ID: " + unit.getId()); + } + } + } - this.terrain.loadSplats(); + this.terrain.loadSplats(); - this.unitsReady = true; - this.anyReady = true; - } + this.unitsReady = true; + this.anyReady = true; + } - private RenderUnitTypeData getUnitTypeData(final War3ID key, final MutableGameObject row) { - RenderUnitTypeData unitTypeData = this.unitIdToTypeData.get(key); - if (unitTypeData == null) { - unitTypeData = new RenderUnitTypeData(row.getFieldAsFloat(MAX_PITCH, 0), row.getFieldAsFloat(MAX_ROLL, 0), - row.getFieldAsFloat(ELEVATION_SAMPLE_RADIUS, 0)); - this.unitIdToTypeData.put(key, unitTypeData); - } - return unitTypeData; - } + private BufferedImage getBuildingPathingPixelMap(final MutableGameObject row) { + BufferedImage buildingPathingPixelMap = null; + final String pathingTexture = row.getFieldAsString(UNIT_PATHING, 0); + if ((pathingTexture != null) && (pathingTexture.length() > 0) && !"_".equals(pathingTexture)) { + buildingPathingPixelMap = this.filePathToPathingMap.get(pathingTexture.toLowerCase()); + if (buildingPathingPixelMap == null) { + try { + if (pathingTexture.toLowerCase().endsWith(".tga")) { + buildingPathingPixelMap = TgaFile.readTGA(pathingTexture, + this.mapMpq.getResourceAsStream(pathingTexture)); + } + else { + try (InputStream stream = this.mapMpq.getResourceAsStream(pathingTexture)) { + buildingPathingPixelMap = ImageIO.read(stream); + System.out.println("LOADING BLP PATHING: " + pathingTexture); + } + } + this.filePathToPathingMap.put(pathingTexture.toLowerCase(), buildingPathingPixelMap); + } + catch (final IOException exc) { + System.err.println("Failure to get pathing: " + exc.getClass() + ":" + exc.getMessage()); + } + } + } + return buildingPathingPixelMap; + } - @Override - public void update() { - if (this.anyReady) { - this.terrain.update(); + private RenderUnitTypeData getUnitTypeData(final War3ID key, final MutableGameObject row) { + RenderUnitTypeData unitTypeData = this.unitIdToTypeData.get(key); + if (unitTypeData == null) { + unitTypeData = new RenderUnitTypeData(row.getFieldAsFloat(MAX_PITCH, 0), row.getFieldAsFloat(MAX_ROLL, 0), + row.getFieldAsFloat(ELEVATION_SAMPLE_RADIUS, 0)); + this.unitIdToTypeData.put(key, unitTypeData); + } + return unitTypeData; + } - super.update(); + @Override + public void update() { + if (this.anyReady) { + this.terrain.update(); - for (final RenderUnit unit : this.units) { - unit.updateAnimations(this); - } - final Iterator projectileIterator = this.projectiles.iterator(); - while (projectileIterator.hasNext()) { - final RenderEffect projectile = projectileIterator.next(); - if (projectile.updateAnimations(this, Gdx.graphics.getDeltaTime())) { - projectileIterator.remove(); - } - } - for (final RenderItem item : this.items) { - final MdxComplexInstance instance = item.instance; - final MdxComplexInstance mdxComplexInstance = instance; - if (mdxComplexInstance.sequenceEnded || (mdxComplexInstance.sequence == -1)) { - SequenceUtils.randomStandSequence(mdxComplexInstance); - } - } - for (final RenderDoodad item : this.doodads) { - final ModelInstance instance = item.instance; - if (instance instanceof MdxComplexInstance) { - final MdxComplexInstance mdxComplexInstance = (MdxComplexInstance) instance; - if (mdxComplexInstance.sequence == -1 || - (mdxComplexInstance.sequenceEnded && (item.getAnimation() != AnimationTokens.PrimaryTag.DEATH || - (((MdxModel) mdxComplexInstance.model).sequences - .get(mdxComplexInstance.sequence).getFlags() == 0) - ))) { - SequenceUtils.randomSequence(mdxComplexInstance, item.getAnimation(), SequenceUtils.EMPTY, - true); + super.update(); - } - } - } + for (final RenderUnit unit : this.units) { + unit.updateAnimations(this); + } + final Iterator projectileIterator = this.projectiles.iterator(); + while (projectileIterator.hasNext()) { + final RenderEffect projectile = projectileIterator.next(); + if (projectile.updateAnimations(this, Gdx.graphics.getDeltaTime())) { + projectileIterator.remove(); + } + } + for (final RenderItem item : this.items) { + final MdxComplexInstance instance = item.instance; + final MdxComplexInstance mdxComplexInstance = instance; + if (mdxComplexInstance.sequenceEnded || (mdxComplexInstance.sequence == -1)) { + SequenceUtils.randomStandSequence(mdxComplexInstance); + } + } + for (final RenderDoodad item : this.doodads) { + final ModelInstance instance = item.instance; + if (instance instanceof MdxComplexInstance) { + final MdxComplexInstance mdxComplexInstance = (MdxComplexInstance) instance; + if ((mdxComplexInstance.sequence == -1) || (mdxComplexInstance.sequenceEnded + && ((item.getAnimation() != AnimationTokens.PrimaryTag.DEATH) + || (((MdxModel) mdxComplexInstance.model).sequences.get(mdxComplexInstance.sequence) + .getFlags() == 0)))) { + SequenceUtils.randomSequence(mdxComplexInstance, item.getAnimation(), SequenceUtils.EMPTY, + true); - final float rawDeltaTime = Gdx.graphics.getRawDeltaTime(); - this.updateTime += rawDeltaTime; - while (this.updateTime >= WarsmashConstants.SIMULATION_STEP_TIME) { - this.updateTime -= WarsmashConstants.SIMULATION_STEP_TIME; - this.simulation.update(); - } - this.dncTerrain.setFrameByRatio( - this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours()); - this.dncTerrain.update(rawDeltaTime, null); - this.dncUnit.setFrameByRatio( - this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours()); - this.dncUnit.update(rawDeltaTime, null); - this.dncUnitDay.setFrameByRatio(0.5f); - this.dncUnitDay.update(rawDeltaTime, null); - this.dncTarget.setFrameByRatio( - this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours()); - this.dncTarget.update(rawDeltaTime, null); - } - } + } + } + } - @Override - public void render() { - if (this.anyReady) { - final Scene worldScene = this.worldScene; + final float rawDeltaTime = Gdx.graphics.getRawDeltaTime(); + this.updateTime += rawDeltaTime; + while (this.updateTime >= WarsmashConstants.SIMULATION_STEP_TIME) { + this.updateTime -= WarsmashConstants.SIMULATION_STEP_TIME; + this.simulation.update(); + } + this.dncTerrain.setFrameByRatio( + this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours()); + this.dncTerrain.update(rawDeltaTime, null); + this.dncUnit.setFrameByRatio( + this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours()); + this.dncUnit.update(rawDeltaTime, null); + this.dncUnitDay.setFrameByRatio(0.5f); + this.dncUnitDay.update(rawDeltaTime, null); + this.dncTarget.setFrameByRatio( + this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours()); + this.dncTarget.update(rawDeltaTime, null); + } + } - startFrame(); - worldScene.startFrame(); - worldScene.renderOpaque(this.dynamicShadowManager, this.webGL); - this.terrain.renderGround(this.dynamicShadowManager); - this.terrain.renderCliffs(); - worldScene.renderOpaque(); - this.terrain.renderUberSplats(); - this.terrain.renderWater(); - worldScene.renderTranslucent(); + @Override + public void render() { + if (this.anyReady) { + final Scene worldScene = this.worldScene; - final List scenes = this.scenes; - for (final Scene scene : scenes) { - if (scene != worldScene) { - scene.startFrame(); - scene.renderOpaque(); - scene.renderTranslucent(); - } - } - } - } + startFrame(); + worldScene.startFrame(); + worldScene.renderOpaque(this.dynamicShadowManager, this.webGL); + this.terrain.renderGround(this.dynamicShadowManager); + this.terrain.renderCliffs(); + worldScene.renderOpaque(); + this.terrain.renderUberSplats(); + this.terrain.renderWater(); + worldScene.renderTranslucent(); - public void deselect() { - if (!this.selModels.isEmpty()) { - for (final SplatModel model : this.selModels) { - this.terrain.removeSplatBatchModel(model); - } - this.selModels.clear(); - for (final RenderUnit unit : this.selected) { - unit.selectionCircle = null; - } - } - this.selected.clear(); - } + final List scenes = this.scenes; + for (final Scene scene : scenes) { + if (scene != worldScene) { + scene.startFrame(); + scene.renderOpaque(); + scene.renderTranslucent(); + } + } + } + } - public void doSelectUnit(final List units) { - deselect(); - if (units.isEmpty()) { - return; - } + public void deselect() { + if (!this.selModels.isEmpty()) { + for (final SplatModel model : this.selModels) { + this.terrain.removeSplatBatchModel(model); + } + this.selModels.clear(); + for (final RenderUnit unit : this.selected) { + unit.selectionCircle = null; + } + } + this.selected.clear(); + } - final Map splats = new HashMap(); - for (final RenderUnit unit : units) { - if (unit.row != null) { - if (unit.selectionScale > 0) { - final float selectionSize = unit.selectionScale * this.selectionCircleScaleFactor; - String path = null; - for (int i = 0; i < this.selectionCircleSizes.size(); i++) { - final SelectionCircleSize selectionCircleSize = this.selectionCircleSizes.get(i); - if ((selectionSize < selectionCircleSize.size) - || (i == (this.selectionCircleSizes.size() - 1))) { - path = selectionCircleSize.texture; - break; - } - } - if (!path.toLowerCase().endsWith(".blp")) { - path += ".blp"; - } - if (!splats.containsKey(path)) { - splats.put(path, new Splat()); - } - final float x = unit.location[0]; - final float y = unit.location[1]; - System.out.println("Selecting a unit at " + x + "," + y); - final float z = unit.row.getFieldAsFloat(UNIT_SELECT_HEIGHT, 0); - splats.get(path).locations.add(new float[]{x - (selectionSize / 2), y - (selectionSize / 2), - x + (selectionSize / 2), y + (selectionSize / 2), z + 5}); - splats.get(path).unitMapping.add(new Consumer() { - @Override - public void accept(final SplatMover t) { - unit.selectionCircle = t; - } - }); - } - this.selected.add(unit); - } - } - this.selModels.clear(); - for (final Map.Entry entry : splats.entrySet()) { - final String path = entry.getKey(); - final Splat locations = entry.getValue(); - final SplatModel model = new SplatModel(Gdx.gl30, (Texture) load(path, PathSolver.DEFAULT, null), - locations.locations, this.terrain.centerOffset, locations.unitMapping, true); - model.color[0] = 0; - model.color[1] = 1; - model.color[2] = 0; - model.color[3] = 1; - this.selModels.add(model); - this.terrain.addSplatBatchModel(model); - } - } + public void doSelectUnit(final List units) { + deselect(); + if (units.isEmpty()) { + return; + } - public void getClickLocation(final Vector3 out, final int screenX, final int screenY) { - final float[] ray = rayHeap; - mousePosHeap.set(screenX, screenY); - this.worldScene.camera.screenToWorldRay(ray, mousePosHeap); - gdxRayHeap.set(ray[0], ray[1], ray[2], ray[3] - ray[0], ray[4] - ray[1], ray[5] - ray[2]); - gdxRayHeap.direction.nor();// needed for libgdx - RenderMathUtils.intersectRayTriangles(gdxRayHeap, this.terrain.softwareGroundMesh.vertices, - this.terrain.softwareGroundMesh.indices, 3, out); - rectangleHeap.set(Math.min(out.x, gdxRayHeap.origin.x), Math.min(out.y, gdxRayHeap.origin.y), - Math.abs(out.x - gdxRayHeap.origin.x), Math.abs(out.y - gdxRayHeap.origin.y)); - this.walkableObjectsTree.intersect(rectangleHeap, this.walkablesIntersectionFinder.reset(gdxRayHeap)); - if (this.walkablesIntersectionFinder.found) { - out.set(this.walkablesIntersectionFinder.intersection); - } else { - out.z = Math.max(getWalkableRenderHeight(out.x, out.y), this.terrain.getGroundHeight(out.x, out.y)); - } - } + final Map splats = new HashMap(); + for (final RenderUnit unit : units) { + if (unit.row != null) { + if (unit.selectionScale > 0) { + final float selectionSize = unit.selectionScale * this.selectionCircleScaleFactor; + String path = null; + for (int i = 0; i < this.selectionCircleSizes.size(); i++) { + final SelectionCircleSize selectionCircleSize = this.selectionCircleSizes.get(i); + if ((selectionSize < selectionCircleSize.size) + || (i == (this.selectionCircleSizes.size() - 1))) { + path = selectionCircleSize.texture; + break; + } + } + if (!path.toLowerCase().endsWith(".blp")) { + path += ".blp"; + } + if (!splats.containsKey(path)) { + splats.put(path, new Splat()); + } + final float x = unit.location[0]; + final float y = unit.location[1]; + System.out.println("Selecting a unit at " + x + "," + y); + final float z = unit.row.getFieldAsFloat(UNIT_SELECT_HEIGHT, 0); + splats.get(path).locations.add(new float[] { x - (selectionSize / 2), y - (selectionSize / 2), + x + (selectionSize / 2), y + (selectionSize / 2), z + 5 }); + splats.get(path).unitMapping.add(new Consumer() { + @Override + public void accept(final SplatMover t) { + unit.selectionCircle = t; + } + }); + } + this.selected.add(unit); + } + } + this.selModels.clear(); + for (final Map.Entry entry : splats.entrySet()) { + final String path = entry.getKey(); + final Splat locations = entry.getValue(); + final SplatModel model = new SplatModel(Gdx.gl30, (Texture) load(path, PathSolver.DEFAULT, null), + locations.locations, this.terrain.centerOffset, locations.unitMapping, true); + model.color[0] = 0; + model.color[1] = 1; + model.color[2] = 0; + model.color[3] = 1; + this.selModels.add(model); + this.terrain.addSplatBatchModel(model); + } + } - public void showConfirmation(final Vector3 position, final float red, final float green, final float blue) { - this.confirmationInstance.show(); - this.confirmationInstance.setSequence(0); - this.confirmationInstance.setLocation(position); - this.worldScene.instanceMoved(this.confirmationInstance, position.x, position.y); - this.confirmationInstance.vertexColor[0] = red; - this.confirmationInstance.vertexColor[1] = green; - this.confirmationInstance.vertexColor[2] = blue; - } + public void getClickLocation(final Vector3 out, final int screenX, final int screenY) { + final float[] ray = rayHeap; + mousePosHeap.set(screenX, screenY); + this.worldScene.camera.screenToWorldRay(ray, mousePosHeap); + gdxRayHeap.set(ray[0], ray[1], ray[2], ray[3] - ray[0], ray[4] - ray[1], ray[5] - ray[2]); + gdxRayHeap.direction.nor();// needed for libgdx + RenderMathUtils.intersectRayTriangles(gdxRayHeap, this.terrain.softwareGroundMesh.vertices, + this.terrain.softwareGroundMesh.indices, 3, out); + rectangleHeap.set(Math.min(out.x, gdxRayHeap.origin.x), Math.min(out.y, gdxRayHeap.origin.y), + Math.abs(out.x - gdxRayHeap.origin.x), Math.abs(out.y - gdxRayHeap.origin.y)); + this.walkableObjectsTree.intersect(rectangleHeap, this.walkablesIntersectionFinder.reset(gdxRayHeap)); + if (this.walkablesIntersectionFinder.found) { + out.set(this.walkablesIntersectionFinder.intersection); + } + else { + out.z = Math.max(getWalkableRenderHeight(out.x, out.y), this.terrain.getGroundHeight(out.x, out.y)); + } + } - public List selectUnit(final float x, final float y, final boolean toggle) { - System.out.println("world: " + x + "," + y); - final float[] ray = rayHeap; - mousePosHeap.set(x, y); - this.worldScene.camera.screenToWorldRay(ray, mousePosHeap); - gdxRayHeap.set(ray[0], ray[1], ray[2], ray[3] - ray[0], ray[4] - ray[1], ray[5] - ray[2]); - gdxRayHeap.direction.nor();// needed for libgdx + public void showConfirmation(final Vector3 position, final float red, final float green, final float blue) { + this.confirmationInstance.show(); + this.confirmationInstance.setSequence(0); + this.confirmationInstance.setLocation(position); + this.worldScene.instanceMoved(this.confirmationInstance, position.x, position.y); + this.confirmationInstance.vertexColor[0] = red; + this.confirmationInstance.vertexColor[1] = green; + this.confirmationInstance.vertexColor[2] = blue; + } - RenderUnit entity = null; - for (final RenderUnit unit : this.units) { - final MdxComplexInstance instance = unit.instance; - if (instance.isVisible(this.worldScene.camera) - && instance.intersectRayWithCollision(gdxRayHeap, intersectionHeap, - unit.getSimulationUnit().getUnitType().isBuilding(), false) - && !unit.getSimulationUnit().isDead()) { - if ((entity == null) || (entity.instance.depth > instance.depth)) { - entity = unit; - } - } - } - List sel; - if (entity != null) { - if (toggle) { - sel = new ArrayList<>(this.selected); - final int idx = sel.indexOf(entity); - if (idx >= 0) { - sel.remove(idx); - } else { - sel.add(entity); - } - } else { - sel = Arrays.asList(entity); - } - } else { - sel = Collections.emptyList(); - } - this.doSelectUnit(sel); - return sel; - } + public List selectUnit(final float x, final float y, final boolean toggle) { + System.out.println("world: " + x + "," + y); + final float[] ray = rayHeap; + mousePosHeap.set(x, y); + this.worldScene.camera.screenToWorldRay(ray, mousePosHeap); + gdxRayHeap.set(ray[0], ray[1], ray[2], ray[3] - ray[0], ray[4] - ray[1], ray[5] - ray[2]); + gdxRayHeap.direction.nor();// needed for libgdx - public RenderUnit rayPickUnit(final float x, final float y) { - return rayPickUnit(x, y, CUnitFilterFunction.ACCEPT_ALL); - } + RenderUnit entity = null; + for (final RenderUnit unit : this.units) { + final MdxComplexInstance instance = unit.instance; + if (instance.isVisible(this.worldScene.camera) + && instance.intersectRayWithCollision(gdxRayHeap, intersectionHeap, + unit.getSimulationUnit().getUnitType().isBuilding(), false) + && !unit.getSimulationUnit().isDead()) { + if ((entity == null) || (entity.instance.depth > instance.depth)) { + entity = unit; + } + } + } + List sel; + if (entity != null) { + if (toggle) { + sel = new ArrayList<>(this.selected); + final int idx = sel.indexOf(entity); + if (idx >= 0) { + sel.remove(idx); + } + else { + sel.add(entity); + } + } + else { + sel = Arrays.asList(entity); + } + } + else { + sel = Collections.emptyList(); + } + this.doSelectUnit(sel); + return sel; + } - public RenderUnit rayPickUnit(final float x, final float y, final CUnitFilterFunction filter) { - final float[] ray = rayHeap; - mousePosHeap.set(x, y); - this.worldScene.camera.screenToWorldRay(ray, mousePosHeap); - gdxRayHeap.set(ray[0], ray[1], ray[2], ray[3] - ray[0], ray[4] - ray[1], ray[5] - ray[2]); - gdxRayHeap.direction.nor();// needed for libgdx + public RenderUnit rayPickUnit(final float x, final float y) { + return rayPickUnit(x, y, CUnitFilterFunction.ACCEPT_ALL); + } - RenderUnit entity = null; - for (final RenderUnit unit : this.units) { - final MdxComplexInstance instance = unit.instance; - if (instance.isVisible(this.worldScene.camera) && instance.intersectRayWithCollision(gdxRayHeap, - intersectionHeap, unit.getSimulationUnit().getUnitType().isBuilding(), false)) { - if (filter.call(unit.getSimulationUnit())) { - if ((entity == null) || (entity.instance.depth > instance.depth)) { - entity = unit; - } - } - } - } - return entity; - } + public RenderUnit rayPickUnit(final float x, final float y, final CUnitFilterFunction filter) { + final float[] ray = rayHeap; + mousePosHeap.set(x, y); + this.worldScene.camera.screenToWorldRay(ray, mousePosHeap); + gdxRayHeap.set(ray[0], ray[1], ray[2], ray[3] - ray[0], ray[4] - ray[1], ray[5] - ray[2]); + gdxRayHeap.direction.nor();// needed for libgdx - private static final class MappedDataCallbackImplementation implements LoadGenericCallback { - @Override - public Object call(final InputStream data) { - final StringBuilder stringBuilder = new StringBuilder(); - try (BufferedReader reader = new BufferedReader(new InputStreamReader(data, "utf-8"))) { - String line; - while ((line = reader.readLine()) != null) { - stringBuilder.append(line); - stringBuilder.append("\n"); - } - } catch (final UnsupportedEncodingException e) { - throw new RuntimeException(e); - } catch (final IOException e) { - throw new RuntimeException(e); - } - return new MappedData(stringBuilder.toString()); - } - } + RenderUnit entity = null; + for (final RenderUnit unit : this.units) { + final MdxComplexInstance instance = unit.instance; + if (instance.isVisible(this.worldScene.camera) && instance.intersectRayWithCollision(gdxRayHeap, + intersectionHeap, unit.getSimulationUnit().getUnitType().isBuilding(), false)) { + if (filter.call(unit.getSimulationUnit()) && (intersectionHeap.z > this.terrain + .getGroundHeight(intersectionHeap.x, intersectionHeap.y))) { + if ((entity == null) || (entity.instance.depth > instance.depth)) { + entity = unit; + } + } + } + } + return entity; + } - private static final class StringDataCallbackImplementation implements LoadGenericCallback { - @Override - public Object call(final InputStream data) { - if (data == null) { - System.err.println("data null"); - } - final StringBuilder stringBuilder = new StringBuilder(); - try (BufferedReader reader = new BufferedReader(new InputStreamReader(data, "utf-8"))) { - String line; - while ((line = reader.readLine()) != null) { - stringBuilder.append(line); - stringBuilder.append("\n"); - } - } catch (final UnsupportedEncodingException e) { - throw new RuntimeException(e); - } catch (final IOException e) { - throw new RuntimeException(e); - } - return stringBuilder.toString(); - } - } + private static final class MappedDataCallbackImplementation implements LoadGenericCallback { + @Override + public Object call(final InputStream data) { + final StringBuilder stringBuilder = new StringBuilder(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(data, "utf-8"))) { + String line; + while ((line = reader.readLine()) != null) { + stringBuilder.append(line); + stringBuilder.append("\n"); + } + } + catch (final UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + return new MappedData(stringBuilder.toString()); + } + } - private static final class StreamDataCallbackImplementation implements LoadGenericCallback { - @Override - public Object call(final InputStream data) { - return data; - } - } + private static final class StringDataCallbackImplementation implements LoadGenericCallback { + @Override + public Object call(final InputStream data) { + if (data == null) { + System.err.println("data null"); + } + final StringBuilder stringBuilder = new StringBuilder(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(data, "utf-8"))) { + String line; + while ((line = reader.readLine()) != null) { + stringBuilder.append(line); + stringBuilder.append("\n"); + } + } + catch (final UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + return stringBuilder.toString(); + } + } - public static final class SolverParams { - public char tileset; - public boolean reforged; - public boolean hd; - } + private static final class StreamDataCallbackImplementation implements LoadGenericCallback { + @Override + public Object call(final InputStream data) { + return data; + } + } - public static final class CliffInfo { - public List locations = new ArrayList<>(); - public List textures = new ArrayList<>(); - } + public static final class SolverParams { + public char tileset; + public boolean reforged; + public boolean hd; + } - private static final int MAXIMUM_ACCEPTED = 1 << 30; - private float selectionCircleScaleFactor; - private DataTable worldEditData; - private WorldEditStrings worldEditStrings; - private Warcraft3MapObjectData allObjectData; - private AbilityDataUI abilityDataUI; + public static final class CliffInfo { + public List locations = new ArrayList<>(); + public List textures = new ArrayList<>(); + } - /** - * Returns a power of two size for the given target capacity. - */ - private static final int pow2GreaterThan(final int capacity) { - int numElements = capacity - 1; - numElements |= numElements >>> 1; - numElements |= numElements >>> 2; - numElements |= numElements >>> 4; - numElements |= numElements >>> 8; - numElements |= numElements >>> 16; - return (numElements < 0) ? 1 : (numElements >= MAXIMUM_ACCEPTED) ? MAXIMUM_ACCEPTED : numElements + 1; - } + private static final int MAXIMUM_ACCEPTED = 1 << 30; + private float selectionCircleScaleFactor; + private DataTable worldEditData; + private WorldEditStrings worldEditStrings; + private Warcraft3MapObjectData allObjectData; + private AbilityDataUI abilityDataUI; - public void standOnRepeat(final MdxComplexInstance instance) { - instance.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); - SequenceUtils.randomStandSequence(instance); - } + /** + * Returns a power of two size for the given target capacity. + */ + private static final int pow2GreaterThan(final int capacity) { + int numElements = capacity - 1; + numElements |= numElements >>> 1; + numElements |= numElements >>> 2; + numElements |= numElements >>> 4; + numElements |= numElements >>> 8; + numElements |= numElements >>> 16; + return (numElements < 0) ? 1 : (numElements >= MAXIMUM_ACCEPTED) ? MAXIMUM_ACCEPTED : numElements + 1; + } - private static final class SelectionCircleSize { - private final float size; - private final String texture; - private final String textureDotted; + public void standOnRepeat(final MdxComplexInstance instance) { + instance.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + SequenceUtils.randomStandSequence(instance); + } - public SelectionCircleSize(final float size, final String texture, final String textureDotted) { - this.size = size; - this.texture = texture; - this.textureDotted = textureDotted; - } - } + private static final class SelectionCircleSize { + private final float size; + private final String texture; + private final String textureDotted; - public void setDayNightModels(final String terrainDNCFile, final String unitDNCFile) { - final MdxModel terrainDNCModel = (MdxModel) load(mdx(terrainDNCFile), PathSolver.DEFAULT, null); - this.dncTerrain = (MdxComplexInstance) terrainDNCModel.addInstance(); - this.dncTerrain.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); - this.dncTerrain.setSequence(0); - final MdxModel unitDNCModel = (MdxModel) load(mdx(unitDNCFile), PathSolver.DEFAULT, null); - this.dncUnit = (MdxComplexInstance) unitDNCModel.addInstance(); - this.dncUnit.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); - this.dncUnit.setSequence(0); - this.dncUnitDay = (MdxComplexInstance) unitDNCModel.addInstance(); - this.dncUnitDay.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); - this.dncUnitDay.setSequence(0); - final MdxModel targetDNCModel = (MdxModel) load( - mdx("Environment\\DNC\\DNCLordaeron\\DNCLordaeronTarget\\DNCLordaeronTarget.mdl"), PathSolver.DEFAULT, - null); - this.dncTarget = (MdxComplexInstance) targetDNCModel.addInstance(); - this.dncTarget.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); - this.dncTarget.setSequence(0); - } + public SelectionCircleSize(final float size, final String texture, final String textureDotted) { + this.size = size; + this.texture = texture; + this.textureDotted = textureDotted; + } + } - private static String mdx(String mdxPath) { - if (mdxPath.toLowerCase().endsWith(".mdl")) { - mdxPath = mdxPath.substring(0, mdxPath.length() - 4); - } - if (!mdxPath.toLowerCase().endsWith(".mdx")) { - mdxPath += ".mdx"; - } - return mdxPath; - } + public void setDayNightModels(final String terrainDNCFile, final String unitDNCFile) { + final MdxModel terrainDNCModel = (MdxModel) load(mdx(terrainDNCFile), PathSolver.DEFAULT, null); + this.dncTerrain = (MdxComplexInstance) terrainDNCModel.addInstance(); + this.dncTerrain.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + this.dncTerrain.setSequence(0); + final MdxModel unitDNCModel = (MdxModel) load(mdx(unitDNCFile), PathSolver.DEFAULT, null); + this.dncUnit = (MdxComplexInstance) unitDNCModel.addInstance(); + this.dncUnit.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + this.dncUnit.setSequence(0); + this.dncUnitDay = (MdxComplexInstance) unitDNCModel.addInstance(); + this.dncUnitDay.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + this.dncUnitDay.setSequence(0); + final MdxModel targetDNCModel = (MdxModel) load( + mdx("Environment\\DNC\\DNCLordaeron\\DNCLordaeronTarget\\DNCLordaeronTarget.mdl"), PathSolver.DEFAULT, + null); + this.dncTarget = (MdxComplexInstance) targetDNCModel.addInstance(); + this.dncTarget.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + this.dncTarget.setSequence(0); + } - @Override - public SceneLightManager createLightManager(final boolean simple) { - if (simple) { - return new W3xScenePortraitLightManager(this, this.lightDirection); - } else { - return new W3xSceneWorldLightManager(this); - } - } + private static String mdx(String mdxPath) { + if (mdxPath.toLowerCase().endsWith(".mdl")) { + mdxPath = mdxPath.substring(0, mdxPath.length() - 4); + } + if (!mdxPath.toLowerCase().endsWith(".mdx")) { + mdxPath += ".mdx"; + } + return mdxPath; + } - public WorldEditStrings getWorldEditStrings() { - return this.worldEditStrings; - } + @Override + public SceneLightManager createLightManager(final boolean simple) { + if (simple) { + return new W3xScenePortraitLightManager(this, this.lightDirection); + } + else { + return new W3xSceneWorldLightManager(this); + } + } - public void setGameUI(final GameUI gameUI) { - this.gameUI = gameUI; - this.abilityDataUI = new AbilityDataUI(this.allObjectData.getAbilities(), gameUI); - } + public WorldEditStrings getWorldEditStrings() { + return this.worldEditStrings; + } - public GameUI getGameUI() { - return this.gameUI; - } + public void setGameUI(final GameUI gameUI) { + this.gameUI = gameUI; + this.abilityDataUI = new AbilityDataUI(this.allObjectData.getAbilities(), this.allObjectData.getUnits(), + gameUI); + } - public AbilityDataUI getAbilityDataUI() { - return this.abilityDataUI; - } + public GameUI getGameUI() { + return this.gameUI; + } - public float getWalkableRenderHeight(final float x, final float y) { - this.walkableObjectsTree.intersect(x, y, this.walkablesIntersector.reset(x, y)); - return this.walkablesIntersector.z; - } + public AbilityDataUI getAbilityDataUI() { + return this.abilityDataUI; + } - public MdxComplexInstance getHighestWalkableUnder(final float x, final float y) { - this.walkableObjectsTree.intersect(x, y, this.intersectorFindsHighestWalkable.reset(x, y)); - return this.intersectorFindsHighestWalkable.highestInstance; - } + public KeyedSounds getUiSounds() { + return this.uiSounds; + } - private static final class QuadtreeIntersectorFindsWalkableRenderHeight - implements QuadtreeIntersector { - private float z; - private final Ray ray = new Ray(); - private final Vector3 intersection = new Vector3(); + public float getWalkableRenderHeight(final float x, final float y) { + this.walkableObjectsTree.intersect(x, y, this.walkablesIntersector.reset(x, y)); + return this.walkablesIntersector.z; + } - private QuadtreeIntersectorFindsWalkableRenderHeight reset(final float x, final float y) { - this.z = -Float.MAX_VALUE; - this.ray.set(x, y, 4096, 0, 0, -8192); - return this; - } + public MdxComplexInstance getHighestWalkableUnder(final float x, final float y) { + this.walkableObjectsTree.intersect(x, y, this.intersectorFindsHighestWalkable.reset(x, y)); + return this.intersectorFindsHighestWalkable.highestInstance; + } - @Override - public boolean onIntersect(final MdxComplexInstance intersectingObject) { - if (intersectingObject.intersectRayWithCollision(this.ray, this.intersection, true, true)) { - this.z = Math.max(this.z, this.intersection.z); - } - return false; - } - } + private static final class QuadtreeIntersectorFindsWalkableRenderHeight + implements QuadtreeIntersector { + private float z; + private final Ray ray = new Ray(); + private final Vector3 intersection = new Vector3(); - private static final class QuadtreeIntersectorFindsHighestWalkable - implements QuadtreeIntersector { - private float z; - private final Ray ray = new Ray(); - private final Vector3 intersection = new Vector3(); - private MdxComplexInstance highestInstance; + private QuadtreeIntersectorFindsWalkableRenderHeight reset(final float x, final float y) { + this.z = -Float.MAX_VALUE; + this.ray.set(x, y, 4096, 0, 0, -8192); + return this; + } - private QuadtreeIntersectorFindsHighestWalkable reset(final float x, final float y) { - this.z = -Float.MAX_VALUE; - this.ray.set(x, y, 4096, 0, 0, -8192); - this.highestInstance = null; - return this; - } + @Override + public boolean onIntersect(final MdxComplexInstance intersectingObject) { + if (intersectingObject.intersectRayWithCollision(this.ray, this.intersection, true, true)) { + this.z = Math.max(this.z, this.intersection.z); + } + return false; + } + } - @Override - public boolean onIntersect(final MdxComplexInstance intersectingObject) { - if (intersectingObject.intersectRayWithCollision(this.ray, this.intersection, true, true)) { - if (this.intersection.z > this.z) { - this.z = this.intersection.z; - this.highestInstance = intersectingObject; - } - } - return false; - } - } + private static final class QuadtreeIntersectorFindsHighestWalkable + implements QuadtreeIntersector { + private float z; + private final Ray ray = new Ray(); + private final Vector3 intersection = new Vector3(); + private MdxComplexInstance highestInstance; - private static final class QuadtreeIntersectorFindsHitPoint implements QuadtreeIntersector { - private Ray ray; - private final Vector3 intersection = new Vector3(); - private boolean found; + private QuadtreeIntersectorFindsHighestWalkable reset(final float x, final float y) { + this.z = -Float.MAX_VALUE; + this.ray.set(x, y, 4096, 0, 0, -8192); + this.highestInstance = null; + return this; + } - private QuadtreeIntersectorFindsHitPoint reset(final Ray ray) { - this.ray = ray; - this.found = false; - return this; - } + @Override + public boolean onIntersect(final MdxComplexInstance intersectingObject) { + if (intersectingObject.intersectRayWithCollision(this.ray, this.intersection, true, true)) { + if (this.intersection.z > this.z) { + this.z = this.intersection.z; + this.highestInstance = intersectingObject; + } + } + return false; + } + } - @Override - public boolean onIntersect(final MdxComplexInstance intersectingObject) { - if (intersectingObject.intersectRayWithCollision(this.ray, this.intersection, true, true)) { - this.found = true; - return true; - } - return false; - } - } + private static final class QuadtreeIntersectorFindsHitPoint implements QuadtreeIntersector { + private Ray ray; + private final Vector3 intersection = new Vector3(); + private boolean found; + + private QuadtreeIntersectorFindsHitPoint reset(final Ray ray) { + this.ray = ray; + this.found = false; + return this; + } + + @Override + public boolean onIntersect(final MdxComplexInstance intersectingObject) { + if (intersectingObject.intersectRayWithCollision(this.ray, this.intersection, true, true)) { + this.found = true; + return true; + } + return false; + } + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java index 368a23d..50d5562 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java @@ -60,7 +60,7 @@ public class Terrain { private static final Vector3 normalHeap2 = new Vector3(); private static final float[] fourComponentHeap = new float[4]; private static final Matrix4 tempMatrix = new Matrix4(); - private static final boolean WIREFRAME_TERRAIN = false; + private static final boolean WIREFRAME_TERRAIN = true; public ShaderProgram groundShader; public ShaderProgram waterShader; @@ -411,7 +411,8 @@ public class Terrain { this.shaderMapBoundsRectangle = new Rectangle(this.shaderMapBounds[0], this.shaderMapBounds[1], this.shaderMapBounds[2] - this.shaderMapBounds[0], this.shaderMapBounds[3] - this.shaderMapBounds[1]); this.mapSize = w3eFile.getMapSize(); - this.entireMapRectangle = new Rectangle(centerOffset[0], centerOffset[1], (mapSize[0] * 128f) - 128, (mapSize[1] * 128f) - 128); + this.entireMapRectangle = new Rectangle(this.centerOffset[0], this.centerOffset[1], + (this.mapSize[0] * 128f) - 128, (this.mapSize[1] * 128f) - 128); this.softwareGroundMesh = new SoftwareGroundMesh(this.groundHeights, this.groundCornerHeights, this.centerOffset, width, height); @@ -1402,6 +1403,6 @@ public class Terrain { } public Rectangle getEntireMap() { - return entireMapRectangle; + return this.entireMapRectangle; } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java index 1afb33c..05143df 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java @@ -137,10 +137,10 @@ public class RenderUnit { } public void populateCommandCard(final CSimulation game, final CommandButtonListener commandButtonListener, - final AbilityDataUI abilityDataUI) { + final AbilityDataUI abilityDataUI, final int subMenuOrderId) { for (final CAbility ability : this.simulationUnit.getAbilities()) { ability.visit(CommandCardPopulatingAbilityVisitor.INSTANCE.reset(game, this.simulationUnit, - commandButtonListener, abilityDataUI)); + commandButtonListener, abilityDataUI, subMenuOrderId)); } } @@ -405,15 +405,17 @@ public class RenderUnit { final boolean allowRarityVariations) { this.animationQueue.clear(); if (force || (animationName != this.currentAnimation)) { - this.currentAnimation = animationName; - this.currentAnimationSecondaryTags = secondaryAnimationTags; this.currentSpeedRatio = speedRatio; - this.currentlyAllowingRarityVariations = allowRarityVariations; this.recycleSet.clear(); this.recycleSet.addAll(this.secondaryAnimationTags); this.recycleSet.addAll(secondaryAnimationTags); this.instance.setAnimationSpeed(speedRatio); - SequenceUtils.randomSequence(this.instance, animationName, this.recycleSet, allowRarityVariations); + if (SequenceUtils.randomSequence(this.instance, animationName, this.recycleSet, + allowRarityVariations) != null) { + this.currentAnimation = animationName; + this.currentAnimationSecondaryTags = secondaryAnimationTags; + this.currentlyAllowingRarityVariations = allowRarityVariations; + } } } @@ -422,15 +424,15 @@ public class RenderUnit { final boolean allowRarityVariations) { this.animationQueue.clear(); if (force || (animationName != this.currentAnimation)) { - this.currentAnimation = animationName; - this.currentAnimationSecondaryTags = secondaryAnimationTags; - this.currentlyAllowingRarityVariations = allowRarityVariations; this.recycleSet.clear(); this.recycleSet.addAll(this.secondaryAnimationTags); this.recycleSet.addAll(secondaryAnimationTags); final Sequence sequence = SequenceUtils.randomSequence(this.instance, animationName, this.recycleSet, allowRarityVariations); if (sequence != null) { + this.currentAnimation = animationName; + this.currentAnimationSecondaryTags = secondaryAnimationTags; + this.currentlyAllowingRarityVariations = allowRarityVariations; this.currentSpeedRatio = ((sequence.getInterval()[1] - sequence.getInterval()[0]) / 1000.0f) / duration; this.instance.setAnimationSpeed(this.currentSpeedRatio); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityDataUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityDataUI.java index 9871acf..a9b97a7 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityDataUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityDataUI.java @@ -22,15 +22,27 @@ public class AbilityDataUI { private static final War3ID ICON_RESEARCH_X = War3ID.fromString("arpx"); private static final War3ID ICON_RESEARCH_Y = War3ID.fromString("arpy"); + private static final War3ID UNIT_ICON_NORMAL_X = War3ID.fromString("ubpx"); + private static final War3ID UNIT_ICON_NORMAL_Y = War3ID.fromString("ubpy"); + private static final War3ID UNIT_ICON_NORMAL = War3ID.fromString("uico"); + private final Map rawcodeToUI = new HashMap<>(); + private final Map rawcodeToUnitUI = new HashMap<>(); private final IconUI moveUI; private final IconUI stopUI; private final IconUI holdPosUI; private final IconUI patrolUI; private final IconUI attackUI; private final IconUI attackGroundUI; + private final IconUI buildHumanUI; + private final IconUI buildOrcUI; + private final IconUI buildNightElfUI; + private final IconUI buildUndeadUI; + private final IconUI buildNeutralUI; + private final IconUI buildNagaUI; + private final IconUI cancelUI; - public AbilityDataUI(final MutableObjectData abilityData, final GameUI gameUI) { + public AbilityDataUI(final MutableObjectData abilityData, final MutableObjectData unitData, final GameUI gameUI) { final String disabledPrefix = gameUI.getSkinField("CommandButtonDisabledArtPath"); for (final War3ID alias : abilityData.keySet()) { final MutableGameObject abilityTypeData = abilityData.get(alias); @@ -54,12 +66,28 @@ public class AbilityDataUI { new IconUI(iconNormal, iconNormalDisabled, iconNormalX, iconNormalY), new IconUI(iconTurnOff, iconTurnOffDisabled, iconTurnOffX, iconTurnOffY))); } + for (final War3ID alias : unitData.keySet()) { + final MutableGameObject abilityTypeData = unitData.get(alias); + final String iconNormalPath = gameUI.trySkinField(abilityTypeData.getFieldAsString(UNIT_ICON_NORMAL, 0)); + final int iconNormalX = abilityTypeData.getFieldAsInteger(UNIT_ICON_NORMAL_X, 0); + final int iconNormalY = abilityTypeData.getFieldAsInteger(UNIT_ICON_NORMAL_Y, 0); + final Texture iconNormal = gameUI.loadTexture(iconNormalPath); + final Texture iconNormalDisabled = gameUI.loadTexture(disable(iconNormalPath, disabledPrefix)); + this.rawcodeToUnitUI.put(alias, new IconUI(iconNormal, iconNormalDisabled, iconNormalX, iconNormalY)); + } this.moveUI = createBuiltInIconUI(gameUI, "CmdMove", disabledPrefix); this.stopUI = createBuiltInIconUI(gameUI, "CmdStop", disabledPrefix); this.holdPosUI = createBuiltInIconUI(gameUI, "CmdHoldPos", disabledPrefix); this.patrolUI = createBuiltInIconUI(gameUI, "CmdPatrol", disabledPrefix); this.attackUI = createBuiltInIconUI(gameUI, "CmdAttack", disabledPrefix); + this.buildHumanUI = createBuiltInIconUI(gameUI, "CmdBuildHuman", disabledPrefix); + this.buildOrcUI = createBuiltInIconUI(gameUI, "CmdBuildOrc", disabledPrefix); + this.buildNightElfUI = createBuiltInIconUI(gameUI, "CmdBuildNightElf", disabledPrefix); + this.buildUndeadUI = createBuiltInIconUI(gameUI, "CmdBuildUndead", disabledPrefix); + this.buildNagaUI = createBuiltInIconUI(gameUI, "CmdBuildNaga", disabledPrefix); + this.buildNeutralUI = createBuiltInIconUI(gameUI, "CmdBuild", disabledPrefix); this.attackGroundUI = createBuiltInIconUI(gameUI, "CmdAttackGround", disabledPrefix); + this.cancelUI = createBuiltInIconUI(gameUI, "CmdCancel", disabledPrefix); } private IconUI createBuiltInIconUI(final GameUI gameUI, final String key, final String disabledPrefix) { @@ -76,6 +104,10 @@ public class AbilityDataUI { return this.rawcodeToUI.get(rawcode); } + public IconUI getUnitUI(final War3ID rawcode) { + return this.rawcodeToUnitUI.get(rawcode); + } + private static String disable(final String path, final String disabledPrefix) { final int slashIndex = path.lastIndexOf('\\'); String name = path; @@ -109,4 +141,32 @@ public class AbilityDataUI { return this.attackGroundUI; } + public IconUI getBuildHumanUI() { + return this.buildHumanUI; + } + + public IconUI getBuildNightElfUI() { + return this.buildNightElfUI; + } + + public IconUI getBuildOrcUI() { + return this.buildOrcUI; + } + + public IconUI getBuildUndeadUI() { + return this.buildUndeadUI; + } + + public IconUI getBuildNagaUI() { + return this.buildNagaUI; + } + + public IconUI getBuildNeutralUI() { + return this.buildNeutralUI; + } + + public IconUI getCancelUI() { + return this.cancelUI; + } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandButtonListener.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandButtonListener.java index d43f106..0840520 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandButtonListener.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandButtonListener.java @@ -35,5 +35,5 @@ public interface CommandButtonListener { // // int getOrderId(); void commandButton(int buttonPositionX, int buttonPositionY, Texture icon, int abilityHandleId, int orderId, - int autoCastOrderId, boolean active, boolean autoCastActive); + int autoCastOrderId, boolean active, boolean autoCastActive, boolean menuButton); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java index e4544bc..6b350d8 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java @@ -1,85 +1,158 @@ package com.etheller.warsmash.viewer5.handlers.w3x.rendersim.commandbuttons; +import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.AbilityDataUI; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.IconUI; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityAttack; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityColdArrows; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityGeneric; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityMove; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.AbstractCAbilityBuild; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityHumanBuild; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityNagaBuild; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityNeutralBuild; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityNightElfBuild; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityOrcBuild; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityUndeadBuild; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.combat.CAbilityColdArrows; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; public class CommandCardPopulatingAbilityVisitor implements CAbilityVisitor { - public static final CommandCardPopulatingAbilityVisitor INSTANCE = new CommandCardPopulatingAbilityVisitor(); + public static final CommandCardPopulatingAbilityVisitor INSTANCE = new CommandCardPopulatingAbilityVisitor(); + private CSimulation game; + private CUnit unit; - private CSimulation game; - private CUnit unit; + private CommandButtonListener commandButtonListener; + private AbilityDataUI abilityDataUI; + private int menuBaseOrderId; + private boolean hasStop; - private CommandButtonListener commandButtonListener; - private AbilityDataUI abilityDataUI; - private boolean hasStop; + public CommandCardPopulatingAbilityVisitor reset(final CSimulation game, final CUnit unit, + final CommandButtonListener commandButtonListener, final AbilityDataUI abilityDataUI, + final int menuBaseOrderId) { + this.game = game; + this.unit = unit; + this.commandButtonListener = commandButtonListener; + this.abilityDataUI = abilityDataUI; + this.menuBaseOrderId = menuBaseOrderId; + this.hasStop = false; + return this; + } - public CommandCardPopulatingAbilityVisitor reset(final CSimulation game, final CUnit unit, - final CommandButtonListener commandButtonListener, final AbilityDataUI abilityDataUI) { - this.game = game; - this.unit = unit; - this.commandButtonListener = commandButtonListener; - this.abilityDataUI = abilityDataUI; - this.hasStop = false; - return this; - } + @Override + public Void accept(final CAbilityAttack ability) { + if (this.menuBaseOrderId == 0) { + addCommandButton(ability, this.abilityDataUI.getAttackUI(), ability.getHandleId(), OrderIds.attack, 0, + false, false); + if (!this.hasStop) { + this.hasStop = true; + addCommandButton(null, this.abilityDataUI.getStopUI(), 0, OrderIds.stop, 0, false, false); + } + } + return null; + } - @Override - public Void accept(final CAbilityAttack ability) { - addCommandButton(ability, this.abilityDataUI.getAttackUI(), ability.getHandleId(), OrderIds.attack, 0, false); - if (!this.hasStop) { - this.hasStop = true; - addCommandButton(null, this.abilityDataUI.getStopUI(), 0, OrderIds.stop, 0, false); - } - return null; - } + @Override + public Void accept(final CAbilityMove ability) { + if (this.menuBaseOrderId == 0) { + addCommandButton(ability, this.abilityDataUI.getMoveUI(), ability.getHandleId(), OrderIds.move, 0, false, + false); + addCommandButton(ability, this.abilityDataUI.getHoldPosUI(), ability.getHandleId(), OrderIds.holdposition, + 0, false, false); + addCommandButton(ability, this.abilityDataUI.getPatrolUI(), ability.getHandleId(), OrderIds.patrol, 0, + false, false); + if (!this.hasStop) { + this.hasStop = true; + addCommandButton(null, this.abilityDataUI.getStopUI(), 0, OrderIds.stop, 0, false, false); + } + } + return null; + } - @Override - public Void accept(final CAbilityMove ability) { - addCommandButton(ability, this.abilityDataUI.getMoveUI(), ability.getHandleId(), OrderIds.move, 0, false); - addCommandButton(ability, this.abilityDataUI.getHoldPosUI(), ability.getHandleId(), OrderIds.holdposition, 0, - false); - addCommandButton(ability, this.abilityDataUI.getPatrolUI(), ability.getHandleId(), OrderIds.patrol, 0, false); - if (!this.hasStop) { - this.hasStop = true; - addCommandButton(null, this.abilityDataUI.getStopUI(), 0, OrderIds.stop, 0, false); - } - return null; - } + @Override + public Void accept(final CAbilityGeneric ability) { + if (this.menuBaseOrderId == 0) { + addCommandButton(ability, this.abilityDataUI.getUI(ability.getRawcode()).getOnIconUI(), + ability.getHandleId(), 0, 0, false, false); + } + return null; + } - @Override - public Void accept(final CAbilityGeneric ability) { - addCommandButton(ability, this.abilityDataUI.getUI(ability.getRawcode()).getOnIconUI(), ability.getHandleId(), - OrderIds.channel, 0, false); - return null; - } + @Override + public Void accept(final CAbilityColdArrows ability) { + if (this.menuBaseOrderId == 0) { + final boolean autoCastActive = ability.isAutoCastActive(); + int autoCastId; + if (autoCastActive) { + autoCastId = OrderIds.coldarrows; + } + else { + autoCastId = OrderIds.uncoldarrows; + } + addCommandButton(ability, this.abilityDataUI.getUI(ability.getRawcode()).getOnIconUI(), + ability.getHandleId(), OrderIds.coldarrowstarg, autoCastId, autoCastActive, false); + } + return null; + } - @Override - public Void accept(final CAbilityColdArrows ability) { - final boolean autoCastActive = ability.isAutoCastActive(); - int autoCastId; - if (autoCastActive) { - autoCastId = OrderIds.coldarrows; - } else { - autoCastId = OrderIds.uncoldarrows; - } - addCommandButton(ability, this.abilityDataUI.getUI(ability.getRawcode()).getOnIconUI(), ability.getHandleId(), - OrderIds.coldarrowstarg, autoCastId, autoCastActive); - return null; - } + @Override + public Void accept(final CAbilityOrcBuild ability) { + handleBuildMenu(ability, this.abilityDataUI.getBuildOrcUI()); + return null; + } - private void addCommandButton(final CAbility ability, final IconUI iconUI, final int handleId, final int orderId, - final int autoCastOrderId, final boolean autoCastActive) { - final boolean active = (this.unit.getCurrentBehavior() != null && orderId == this.unit.getCurrentBehavior().getHighlightOrderId()); - this.commandButtonListener.commandButton(iconUI.getButtonPositionX(), iconUI.getButtonPositionY(), - iconUI.getIcon(), handleId, orderId, autoCastOrderId, active, autoCastActive); - } + @Override + public Void accept(final CAbilityHumanBuild ability) { + handleBuildMenu(ability, this.abilityDataUI.getBuildHumanUI()); + return null; + } + + @Override + public Void accept(final CAbilityNightElfBuild ability) { + handleBuildMenu(ability, this.abilityDataUI.getBuildNightElfUI()); + return null; + } + + @Override + public Void accept(final CAbilityUndeadBuild ability) { + handleBuildMenu(ability, this.abilityDataUI.getBuildUndeadUI()); + return null; + } + + @Override + public Void accept(final CAbilityNagaBuild ability) { + handleBuildMenu(ability, this.abilityDataUI.getBuildNagaUI()); + return null; + } + + @Override + public Void accept(final CAbilityNeutralBuild ability) { + handleBuildMenu(ability, this.abilityDataUI.getBuildNeutralUI()); + return null; + } + + private void handleBuildMenu(final AbstractCAbilityBuild ability, final IconUI buildUI) { + if (this.menuBaseOrderId == ability.getBaseOrderId()) { + for (final War3ID unitType : ability.getStructuresBuilt()) { + final IconUI unitUI = this.abilityDataUI.getUnitUI(unitType); + if (unitUI != null) { + addCommandButton(ability, unitUI, ability.getHandleId(), unitType.getValue(), 0, false, false); + } + } + } + else { + addCommandButton(ability, buildUI, ability.getHandleId(), ability.getBaseOrderId(), 0, false, true); + } + } + + private void addCommandButton(final CAbility ability, final IconUI iconUI, final int handleId, final int orderId, + final int autoCastOrderId, final boolean autoCastActive, final boolean menuButton) { + final boolean active = ((this.unit.getCurrentBehavior() != null) + && (orderId == this.unit.getCurrentBehavior().getHighlightOrderId())); + this.commandButtonListener.commandButton(iconUI.getButtonPositionX(), iconUI.getButtonPositionY(), + iconUI.getIcon(), handleId, orderId, autoCastOrderId, active, autoCastActive, menuButton); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java index 1a95891..5606115 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java @@ -4,11 +4,13 @@ import java.awt.image.BufferedImage; import java.util.EnumSet; import java.util.List; +import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.MovementType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CDefenseType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.data.CUnitRace; /** * The quick (symbol table instead of map) lookup for unit type values that we @@ -35,13 +37,19 @@ public class CUnitType { private final EnumSet targetedAs; private final float defaultAcquisitionRange; private final float minimumAttackRange; + private final List structuresBuilt; + private final CUnitRace unitRace; + private final int goldCost; + private final int lumberCost; + private final int buildTime; public CUnitType(final String name, final boolean isBldg, final MovementType movementType, final float defaultFlyingHeight, final float collisionSize, final EnumSet classifications, final List attacks, final String armorType, final boolean raise, final boolean decay, final CDefenseType defenseType, final float impactZ, final BufferedImage buildingPathingPixelMap, final float deathTime, final EnumSet targetedAs, - final float defaultAcquisitionRange, final float minimumAttackRange) { + final float defaultAcquisitionRange, final float minimumAttackRange, final List structuresBuilt, + final CUnitRace unitRace, final int goldCost, final int lumberCost, final int buildTime) { this.name = name; this.building = isBldg; this.movementType = movementType; @@ -59,6 +67,11 @@ public class CUnitType { this.targetedAs = targetedAs; this.defaultAcquisitionRange = defaultAcquisitionRange; this.minimumAttackRange = minimumAttackRange; + this.structuresBuilt = structuresBuilt; + this.unitRace = unitRace; + this.goldCost = goldCost; + this.lumberCost = lumberCost; + this.buildTime = buildTime; } public String getName() { @@ -128,4 +141,24 @@ public class CUnitType { public float getMinimumAttackRange() { return this.minimumAttackRange; } + + public List getStructuresBuilt() { + return this.structuresBuilt; + } + + public CUnitRace getRace() { + return this.unitRace; + } + + public int getGoldCost() { + return this.goldCost; + } + + public int getLumberCost() { + return this.lumberCost; + } + + public int getBuildTime() { + return this.buildTime; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/AbstractCAbility.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/AbstractCAbility.java new file mode 100644 index 0000000..c6fdb06 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/AbstractCAbility.java @@ -0,0 +1,14 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities; + +public abstract class AbstractCAbility implements CAbility { + private final int handleId; + + public AbstractCAbility(final int handleId) { + this.handleId = handleId; + } + + @Override + public final int getHandleId() { + return this.handleId; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityVisitor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityVisitor.java index 5b4d1fb..5d3a7c5 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityVisitor.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityVisitor.java @@ -1,5 +1,13 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityHumanBuild; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityNagaBuild; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityNeutralBuild; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityNightElfBuild; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityOrcBuild; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityUndeadBuild; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.combat.CAbilityColdArrows; + /** * A visitor for the lowest level inherent types of an ability. It's a bit of a * design clash to have the notion of an ability visitor pattern while also @@ -13,7 +21,19 @@ public interface CAbilityVisitor { T accept(CAbilityMove ability); + T accept(CAbilityOrcBuild ability); + + T accept(CAbilityHumanBuild ability); + + T accept(CAbilityUndeadBuild ability); + + T accept(CAbilityNightElfBuild ability); + T accept(CAbilityGeneric ability); T accept(CAbilityColdArrows ability); + + T accept(CAbilityNagaBuild ability); + + T accept(CAbilityNeutralBuild ability); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java new file mode 100644 index 0000000..387499c --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java @@ -0,0 +1,78 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build; + +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import com.badlogic.gdx.math.Vector2; +import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.AbstractCAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.menu.CAbilityMenu; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayer; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.ResourceType; + +public abstract class AbstractCAbilityBuild extends AbstractCAbility implements CAbilityMenu { + private final Set structuresBuilt; + + public AbstractCAbilityBuild(final int handleId, final List structuresBuilt) { + super(handleId); + this.structuresBuilt = new LinkedHashSet<>(structuresBuilt); + } + + public Collection getStructuresBuilt() { + return this.structuresBuilt; + } + + @Override + public void checkCanUse(final CSimulation game, final CUnit unit, final int orderId, + final AbilityActivationReceiver receiver) { + final CUnitType unitType = game.getUnitData().getUnitType(new War3ID(orderId)); + if (unitType != null) { + final CPlayer player = game.getPlayer(unit.getPlayerIndex()); + if (player.getGold() >= unitType.getGoldCost()) { + if (player.getLumber() >= unitType.getLumberCost()) { + receiver.useOk(); + } + else { + receiver.notEnoughResources(ResourceType.LUMBER); + } + } + else { + receiver.notEnoughResources(ResourceType.GOLD); + } + } + else { + receiver.useOk(); + } + } + + @Override + public final void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, final CWidget target, + final AbilityTargetCheckReceiver receiver) { + receiver.orderIdNotAccepted(); + } + + @Override + public final void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, final Vector2 target, + final AbilityTargetCheckReceiver receiver) { + if (this.structuresBuilt.contains(new War3ID(orderId))) { + receiver.targetOk(target); + } + else { + receiver.orderIdNotAccepted(); + } + } + + @Override + public final void checkCanTargetNoTarget(final CSimulation game, final CUnit unit, final int orderId, + final AbilityTargetCheckReceiver receiver) { + receiver.orderIdNotAccepted(); + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityHumanBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityHumanBuild.java new file mode 100644 index 0000000..9eb3c64 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityHumanBuild.java @@ -0,0 +1,61 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build; + +import java.util.List; + +import com.badlogic.gdx.math.Vector2; +import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; + +public class CAbilityHumanBuild extends AbstractCAbilityBuild { + + public CAbilityHumanBuild(final int handleId, final List structuresBuilt) { + super(handleId, structuresBuilt); + // TODO Auto-generated constructor stub + } + + @Override + public int getBaseOrderId() { + return OrderIds.humanbuild; + } + + @Override + public void onAdd(final CSimulation game, final CUnit unit) { + // TODO Auto-generated method stub + + } + + @Override + public void onRemove(final CSimulation game, final CUnit unit) { + // TODO Auto-generated method stub + + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final CWidget target) { + // TODO Auto-generated method stub + return null; + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final Vector2 point) { + // TODO Auto-generated method stub + return null; + } + + @Override + public CBehavior beginNoTarget(final CSimulation game, final CUnit caster, final int orderId) { + // TODO Auto-generated method stub + return null; + } + + @Override + public T visit(final CAbilityVisitor visitor) { + return visitor.accept(this); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityNagaBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityNagaBuild.java new file mode 100644 index 0000000..28a0af0 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityNagaBuild.java @@ -0,0 +1,53 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build; + +import java.util.List; + +import com.badlogic.gdx.math.Vector2; +import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; + +public class CAbilityNagaBuild extends AbstractCAbilityBuild { + + public CAbilityNagaBuild(final int handleId, final List structuresBuilt) { + super(handleId, structuresBuilt); + } + + @Override + public T visit(final CAbilityVisitor visitor) { + return visitor.accept(this); + } + + @Override + public void onAdd(final CSimulation game, final CUnit unit) { + } + + @Override + public void onRemove(final CSimulation game, final CUnit unit) { + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final CWidget target) { + return caster.pollNextOrderBehavior(game); + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final Vector2 point) { + // TODO Auto-generated method stub + return null; + } + + @Override + public CBehavior beginNoTarget(final CSimulation game, final CUnit caster, final int orderId) { + return caster.pollNextOrderBehavior(game); + } + + @Override + public int getBaseOrderId() { + return OrderIds.nagabuild; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityNeutralBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityNeutralBuild.java new file mode 100644 index 0000000..f9517cf --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityNeutralBuild.java @@ -0,0 +1,53 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build; + +import java.util.List; + +import com.badlogic.gdx.math.Vector2; +import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; + +public class CAbilityNeutralBuild extends AbstractCAbilityBuild { + + public CAbilityNeutralBuild(final int handleId, final List structuresBuilt) { + super(handleId, structuresBuilt); + } + + @Override + public T visit(final CAbilityVisitor visitor) { + return visitor.accept(this); + } + + @Override + public void onAdd(final CSimulation game, final CUnit unit) { + } + + @Override + public void onRemove(final CSimulation game, final CUnit unit) { + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final CWidget target) { + return caster.pollNextOrderBehavior(game); + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final Vector2 point) { + // TODO Auto-generated method stub + return null; + } + + @Override + public CBehavior beginNoTarget(final CSimulation game, final CUnit caster, final int orderId) { + return caster.pollNextOrderBehavior(game); + } + + @Override + public int getBaseOrderId() { + return OrderIds.buildmenu; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityNightElfBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityNightElfBuild.java new file mode 100644 index 0000000..a69a4e0 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityNightElfBuild.java @@ -0,0 +1,61 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build; + +import java.util.List; + +import com.badlogic.gdx.math.Vector2; +import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; + +public class CAbilityNightElfBuild extends AbstractCAbilityBuild { + + public CAbilityNightElfBuild(final int handleId, final List structuresBuilt) { + super(handleId, structuresBuilt); + // TODO Auto-generated constructor stub + } + + @Override + public int getBaseOrderId() { + return OrderIds.nightelfbuild; + } + + @Override + public void onAdd(final CSimulation game, final CUnit unit) { + // TODO Auto-generated method stub + + } + + @Override + public void onRemove(final CSimulation game, final CUnit unit) { + // TODO Auto-generated method stub + + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final CWidget target) { + // TODO Auto-generated method stub + return null; + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final Vector2 point) { + // TODO Auto-generated method stub + return null; + } + + @Override + public CBehavior beginNoTarget(final CSimulation game, final CUnit caster, final int orderId) { + // TODO Auto-generated method stub + return null; + } + + @Override + public T visit(final CAbilityVisitor visitor) { + return visitor.accept(this); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityOrcBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityOrcBuild.java new file mode 100644 index 0000000..79678f3 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityOrcBuild.java @@ -0,0 +1,53 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build; + +import java.util.List; + +import com.badlogic.gdx.math.Vector2; +import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; + +public class CAbilityOrcBuild extends AbstractCAbilityBuild { + + public CAbilityOrcBuild(final int handleId, final List structuresBuilt) { + super(handleId, structuresBuilt); + } + + @Override + public T visit(final CAbilityVisitor visitor) { + return visitor.accept(this); + } + + @Override + public void onAdd(final CSimulation game, final CUnit unit) { + } + + @Override + public void onRemove(final CSimulation game, final CUnit unit) { + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final CWidget target) { + return caster.pollNextOrderBehavior(game); + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final Vector2 point) { + // TODO Auto-generated method stub + return null; + } + + @Override + public CBehavior beginNoTarget(final CSimulation game, final CUnit caster, final int orderId) { + return caster.pollNextOrderBehavior(game); + } + + @Override + public int getBaseOrderId() { + return OrderIds.orcbuild; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityUndeadBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityUndeadBuild.java new file mode 100644 index 0000000..cd0b388 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityUndeadBuild.java @@ -0,0 +1,61 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build; + +import java.util.List; + +import com.badlogic.gdx.math.Vector2; +import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; + +public class CAbilityUndeadBuild extends AbstractCAbilityBuild { + + public CAbilityUndeadBuild(final int handleId, final List structuresBuilt) { + super(handleId, structuresBuilt); + // TODO Auto-generated constructor stub + } + + @Override + public int getBaseOrderId() { + return OrderIds.undeadbuild; + } + + @Override + public void onAdd(final CSimulation game, final CUnit unit) { + // TODO Auto-generated method stub + + } + + @Override + public void onRemove(final CSimulation game, final CUnit unit) { + // TODO Auto-generated method stub + + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final CWidget target) { + // TODO Auto-generated method stub + return null; + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final Vector2 point) { + // TODO Auto-generated method stub + return null; + } + + @Override + public CBehavior beginNoTarget(final CSimulation game, final CUnit caster, final int orderId) { + // TODO Auto-generated method stub + return null; + } + + @Override + public T visit(final CAbilityVisitor visitor) { + return visitor.accept(this); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityColdArrows.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/combat/CAbilityColdArrows.java similarity index 95% rename from core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityColdArrows.java rename to core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/combat/CAbilityColdArrows.java index 61152bf..3a11ac4 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityColdArrows.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/combat/CAbilityColdArrows.java @@ -1,10 +1,12 @@ -package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities; +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.combat; import com.badlogic.gdx.math.Vector2; import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/menu/CAbilityMenu.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/menu/CAbilityMenu.java new file mode 100644 index 0000000..7344084 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/menu/CAbilityMenu.java @@ -0,0 +1,8 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.menu; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; + +public interface CAbilityMenu extends CAbility { + // the base order ID of the menu + int getBaseOrderId(); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorMove.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorMove.java index b9e322f..1f3efc3 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorMove.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorMove.java @@ -32,18 +32,18 @@ public class CBehaviorMove implements CBehavior { private CUnit followUnit; private CRangedBehavior rangedBehavior; - public CBehaviorMove reset(int highlightOrderId, final float targetX, final float targetY) { + public CBehaviorMove reset(final int highlightOrderId, final float targetX, final float targetY) { internalResetMove(highlightOrderId, targetX, targetY); this.rangedBehavior = null; return this; } - private void internalResetMove(int highlightOrderId, final float targetX, final float targetY) { + private void internalResetMove(final int highlightOrderId, final float targetX, final float targetY) { this.highlightOrderId = highlightOrderId; this.wasWithinPropWindow = false; this.gridMapping = CPathfindingProcessor.isCollisionSizeBetterSuitedForCorners( this.unit.getUnitType().getCollisionSize()) ? CPathfindingProcessor.GridMapping.CORNERS - : CPathfindingProcessor.GridMapping.CELLS; + : CPathfindingProcessor.GridMapping.CELLS; this.target = new Point2D.Float(targetX, targetY); this.path = null; this.searchCycles = 0; @@ -56,26 +56,19 @@ public class CBehaviorMove implements CBehavior { return this; } - public CBehaviorMove reset(int highlightOrderId, final CUnit followUnit) { + public CBehaviorMove reset(final int highlightOrderId, final CUnit followUnit) { internalResetMove(highlightOrderId, followUnit); this.rangedBehavior = null; return this; } public CBehaviorMove reset(final CUnit followUnit, final CRangedBehavior rangedBehavior) { - this.wasWithinPropWindow = false; - this.gridMapping = CPathfindingProcessor.isCollisionSizeBetterSuitedForCorners( - this.unit.getUnitType().getCollisionSize()) ? CPathfindingProcessor.GridMapping.CORNERS - : CPathfindingProcessor.GridMapping.CELLS; - this.target = new Point2D.Float(followUnit.getX(), followUnit.getY()); - this.path = null; - this.searchCycles = 0; - this.followUnit = followUnit; + internalResetMove(rangedBehavior.getHighlightOrderId(), followUnit); this.rangedBehavior = rangedBehavior; return this; } - private void internalResetMove(int highlightOrderId, CUnit followUnit) { + private void internalResetMove(final int highlightOrderId, final CUnit followUnit) { this.highlightOrderId = highlightOrderId; this.wasWithinPropWindow = false; this.gridMapping = CPathfindingProcessor.isCollisionSizeBetterSuitedForCorners( @@ -89,7 +82,7 @@ public class CBehaviorMove implements CBehavior { @Override public int getHighlightOrderId() { - return highlightOrderId; + return this.highlightOrderId; } @Override diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CAbilityData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CAbilityData.java index 987ca6c..6d75cfe 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CAbilityData.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CAbilityData.java @@ -3,8 +3,8 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.data; import com.etheller.warsmash.units.manager.MutableObjectData; import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityColdArrows; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityGeneric; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.combat.CAbilityColdArrows; public class CAbilityData { private static final War3ID COLD_ARROWS = War3ID.fromString("ACcw"); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java index 2baa1ab..037deec 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java @@ -18,6 +18,12 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.HandleIdAllocator; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityAttack; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityMove; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityHumanBuild; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityNagaBuild; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityNeutralBuild; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityNightElfBuild; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityOrcBuild; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityUndeadBuild; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CDefenseType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; @@ -32,410 +38,479 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUni import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.SimulationRenderController; public class CUnitData { - private static final War3ID MANA_INITIAL_AMOUNT = War3ID.fromString("umpi"); - private static final War3ID MANA_MAXIMUM = War3ID.fromString("umpm"); - private static final War3ID HIT_POINT_MAXIMUM = War3ID.fromString("uhpm"); - private static final War3ID MOVEMENT_SPEED_BASE = War3ID.fromString("umvs"); - private static final War3ID PROPULSION_WINDOW = War3ID.fromString("uprw"); - private static final War3ID TURN_RATE = War3ID.fromString("umvr"); - private static final War3ID IS_BLDG = War3ID.fromString("ubdg"); - private static final War3ID NAME = War3ID.fromString("unam"); - private static final War3ID PROJECTILE_LAUNCH_X = War3ID.fromString("ulpx"); - private static final War3ID PROJECTILE_LAUNCH_Y = War3ID.fromString("ulpy"); - private static final War3ID PROJECTILE_LAUNCH_Z = War3ID.fromString("ulpz"); - private static final War3ID ATTACKS_ENABLED = War3ID.fromString("uaen"); - private static final War3ID ATTACK1_BACKSWING_POINT = War3ID.fromString("ubs1"); - private static final War3ID ATTACK1_DAMAGE_POINT = War3ID.fromString("udp1"); - private static final War3ID ATTACK1_AREA_OF_EFFECT_FULL_DMG = War3ID.fromString("ua1f"); - private static final War3ID ATTACK1_AREA_OF_EFFECT_HALF_DMG = War3ID.fromString("ua1h"); - private static final War3ID ATTACK1_AREA_OF_EFFECT_QUARTER_DMG = War3ID.fromString("ua1q"); - private static final War3ID ATTACK1_AREA_OF_EFFECT_TARGETS = War3ID.fromString("ua1p"); - private static final War3ID ATTACK1_ATTACK_TYPE = War3ID.fromString("ua1t"); - private static final War3ID ATTACK1_COOLDOWN = War3ID.fromString("ua1c"); - private static final War3ID ATTACK1_DMG_BASE = War3ID.fromString("ua1b"); - private static final War3ID ATTACK1_DAMAGE_FACTOR_HALF = War3ID.fromString("uhd1"); - private static final War3ID ATTACK1_DAMAGE_FACTOR_QUARTER = War3ID.fromString("uqd1"); - private static final War3ID ATTACK1_DAMAGE_LOSS_FACTOR = War3ID.fromString("udl1"); - private static final War3ID ATTACK1_DMG_DICE = War3ID.fromString("ua1d"); - private static final War3ID ATTACK1_DMG_SIDES_PER_DIE = War3ID.fromString("ua1s"); - private static final War3ID ATTACK1_DMG_SPILL_DIST = War3ID.fromString("usd1"); - private static final War3ID ATTACK1_DMG_SPILL_RADIUS = War3ID.fromString("usr1"); - private static final War3ID ATTACK1_DMG_UPGRADE_AMT = War3ID.fromString("udu1"); - private static final War3ID ATTACK1_TARGET_COUNT = War3ID.fromString("utc1"); - private static final War3ID ATTACK1_PROJECTILE_ARC = War3ID.fromString("uma1"); - private static final War3ID ATTACK1_MISSILE_ART = War3ID.fromString("ua1m"); - private static final War3ID ATTACK1_PROJECTILE_HOMING_ENABLED = War3ID.fromString("umh1"); - private static final War3ID ATTACK1_PROJECTILE_SPEED = War3ID.fromString("ua1z"); - private static final War3ID ATTACK1_RANGE = War3ID.fromString("ua1r"); - private static final War3ID ATTACK1_RANGE_MOTION_BUFFER = War3ID.fromString("urb1"); - private static final War3ID ATTACK1_SHOW_UI = War3ID.fromString("uwu1"); - private static final War3ID ATTACK1_TARGETS_ALLOWED = War3ID.fromString("ua1g"); - private static final War3ID ATTACK1_WEAPON_SOUND = War3ID.fromString("ucs1"); - private static final War3ID ATTACK1_WEAPON_TYPE = War3ID.fromString("ua1w"); + private static final War3ID MANA_INITIAL_AMOUNT = War3ID.fromString("umpi"); + private static final War3ID MANA_MAXIMUM = War3ID.fromString("umpm"); + private static final War3ID HIT_POINT_MAXIMUM = War3ID.fromString("uhpm"); + private static final War3ID MOVEMENT_SPEED_BASE = War3ID.fromString("umvs"); + private static final War3ID PROPULSION_WINDOW = War3ID.fromString("uprw"); + private static final War3ID TURN_RATE = War3ID.fromString("umvr"); + private static final War3ID IS_BLDG = War3ID.fromString("ubdg"); + private static final War3ID NAME = War3ID.fromString("unam"); + private static final War3ID PROJECTILE_LAUNCH_X = War3ID.fromString("ulpx"); + private static final War3ID PROJECTILE_LAUNCH_Y = War3ID.fromString("ulpy"); + private static final War3ID PROJECTILE_LAUNCH_Z = War3ID.fromString("ulpz"); + private static final War3ID ATTACKS_ENABLED = War3ID.fromString("uaen"); + private static final War3ID ATTACK1_BACKSWING_POINT = War3ID.fromString("ubs1"); + private static final War3ID ATTACK1_DAMAGE_POINT = War3ID.fromString("udp1"); + private static final War3ID ATTACK1_AREA_OF_EFFECT_FULL_DMG = War3ID.fromString("ua1f"); + private static final War3ID ATTACK1_AREA_OF_EFFECT_HALF_DMG = War3ID.fromString("ua1h"); + private static final War3ID ATTACK1_AREA_OF_EFFECT_QUARTER_DMG = War3ID.fromString("ua1q"); + private static final War3ID ATTACK1_AREA_OF_EFFECT_TARGETS = War3ID.fromString("ua1p"); + private static final War3ID ATTACK1_ATTACK_TYPE = War3ID.fromString("ua1t"); + private static final War3ID ATTACK1_COOLDOWN = War3ID.fromString("ua1c"); + private static final War3ID ATTACK1_DMG_BASE = War3ID.fromString("ua1b"); + private static final War3ID ATTACK1_DAMAGE_FACTOR_HALF = War3ID.fromString("uhd1"); + private static final War3ID ATTACK1_DAMAGE_FACTOR_QUARTER = War3ID.fromString("uqd1"); + private static final War3ID ATTACK1_DAMAGE_LOSS_FACTOR = War3ID.fromString("udl1"); + private static final War3ID ATTACK1_DMG_DICE = War3ID.fromString("ua1d"); + private static final War3ID ATTACK1_DMG_SIDES_PER_DIE = War3ID.fromString("ua1s"); + private static final War3ID ATTACK1_DMG_SPILL_DIST = War3ID.fromString("usd1"); + private static final War3ID ATTACK1_DMG_SPILL_RADIUS = War3ID.fromString("usr1"); + private static final War3ID ATTACK1_DMG_UPGRADE_AMT = War3ID.fromString("udu1"); + private static final War3ID ATTACK1_TARGET_COUNT = War3ID.fromString("utc1"); + private static final War3ID ATTACK1_PROJECTILE_ARC = War3ID.fromString("uma1"); + private static final War3ID ATTACK1_MISSILE_ART = War3ID.fromString("ua1m"); + private static final War3ID ATTACK1_PROJECTILE_HOMING_ENABLED = War3ID.fromString("umh1"); + private static final War3ID ATTACK1_PROJECTILE_SPEED = War3ID.fromString("ua1z"); + private static final War3ID ATTACK1_RANGE = War3ID.fromString("ua1r"); + private static final War3ID ATTACK1_RANGE_MOTION_BUFFER = War3ID.fromString("urb1"); + private static final War3ID ATTACK1_SHOW_UI = War3ID.fromString("uwu1"); + private static final War3ID ATTACK1_TARGETS_ALLOWED = War3ID.fromString("ua1g"); + private static final War3ID ATTACK1_WEAPON_SOUND = War3ID.fromString("ucs1"); + private static final War3ID ATTACK1_WEAPON_TYPE = War3ID.fromString("ua1w"); - private static final War3ID ATTACK2_BACKSWING_POINT = War3ID.fromString("ubs2"); - private static final War3ID ATTACK2_DAMAGE_POINT = War3ID.fromString("udp2"); - private static final War3ID ATTACK2_AREA_OF_EFFECT_FULL_DMG = War3ID.fromString("ua2f"); - private static final War3ID ATTACK2_AREA_OF_EFFECT_HALF_DMG = War3ID.fromString("ua2h"); - private static final War3ID ATTACK2_AREA_OF_EFFECT_QUARTER_DMG = War3ID.fromString("ua2q"); - private static final War3ID ATTACK2_AREA_OF_EFFECT_TARGETS = War3ID.fromString("ua2p"); - private static final War3ID ATTACK2_ATTACK_TYPE = War3ID.fromString("ua2t"); - private static final War3ID ATTACK2_COOLDOWN = War3ID.fromString("ua2c"); - private static final War3ID ATTACK2_DMG_BASE = War3ID.fromString("ua2b"); - private static final War3ID ATTACK2_DAMAGE_FACTOR_HALF = War3ID.fromString("uhd2"); - private static final War3ID ATTACK2_DAMAGE_FACTOR_QUARTER = War3ID.fromString("uqd2"); - private static final War3ID ATTACK2_DAMAGE_LOSS_FACTOR = War3ID.fromString("udl2"); - private static final War3ID ATTACK2_DMG_DICE = War3ID.fromString("ua2d"); - private static final War3ID ATTACK2_DMG_SIDES_PER_DIE = War3ID.fromString("ua2s"); - private static final War3ID ATTACK2_DMG_SPILL_DIST = War3ID.fromString("usd2"); - private static final War3ID ATTACK2_DMG_SPILL_RADIUS = War3ID.fromString("usr2"); - private static final War3ID ATTACK2_DMG_UPGRADE_AMT = War3ID.fromString("udu2"); - private static final War3ID ATTACK2_TARGET_COUNT = War3ID.fromString("utc2"); - private static final War3ID ATTACK2_PROJECTILE_ARC = War3ID.fromString("uma2"); - private static final War3ID ATTACK2_MISSILE_ART = War3ID.fromString("ua2m"); - private static final War3ID ATTACK2_PROJECTILE_HOMING_ENABLED = War3ID.fromString("umh2"); - private static final War3ID ATTACK2_PROJECTILE_SPEED = War3ID.fromString("ua2z"); - private static final War3ID ATTACK2_RANGE = War3ID.fromString("ua2r"); - private static final War3ID ATTACK2_RANGE_MOTION_BUFFER = War3ID.fromString("urb2"); - private static final War3ID ATTACK2_SHOW_UI = War3ID.fromString("uwu2"); - private static final War3ID ATTACK2_TARGETS_ALLOWED = War3ID.fromString("ua2g"); - private static final War3ID ATTACK2_WEAPON_SOUND = War3ID.fromString("ucs2"); - private static final War3ID ATTACK2_WEAPON_TYPE = War3ID.fromString("ua2w"); + private static final War3ID ATTACK2_BACKSWING_POINT = War3ID.fromString("ubs2"); + private static final War3ID ATTACK2_DAMAGE_POINT = War3ID.fromString("udp2"); + private static final War3ID ATTACK2_AREA_OF_EFFECT_FULL_DMG = War3ID.fromString("ua2f"); + private static final War3ID ATTACK2_AREA_OF_EFFECT_HALF_DMG = War3ID.fromString("ua2h"); + private static final War3ID ATTACK2_AREA_OF_EFFECT_QUARTER_DMG = War3ID.fromString("ua2q"); + private static final War3ID ATTACK2_AREA_OF_EFFECT_TARGETS = War3ID.fromString("ua2p"); + private static final War3ID ATTACK2_ATTACK_TYPE = War3ID.fromString("ua2t"); + private static final War3ID ATTACK2_COOLDOWN = War3ID.fromString("ua2c"); + private static final War3ID ATTACK2_DMG_BASE = War3ID.fromString("ua2b"); + private static final War3ID ATTACK2_DAMAGE_FACTOR_HALF = War3ID.fromString("uhd2"); + private static final War3ID ATTACK2_DAMAGE_FACTOR_QUARTER = War3ID.fromString("uqd2"); + private static final War3ID ATTACK2_DAMAGE_LOSS_FACTOR = War3ID.fromString("udl2"); + private static final War3ID ATTACK2_DMG_DICE = War3ID.fromString("ua2d"); + private static final War3ID ATTACK2_DMG_SIDES_PER_DIE = War3ID.fromString("ua2s"); + private static final War3ID ATTACK2_DMG_SPILL_DIST = War3ID.fromString("usd2"); + private static final War3ID ATTACK2_DMG_SPILL_RADIUS = War3ID.fromString("usr2"); + private static final War3ID ATTACK2_DMG_UPGRADE_AMT = War3ID.fromString("udu2"); + private static final War3ID ATTACK2_TARGET_COUNT = War3ID.fromString("utc2"); + private static final War3ID ATTACK2_PROJECTILE_ARC = War3ID.fromString("uma2"); + private static final War3ID ATTACK2_MISSILE_ART = War3ID.fromString("ua2m"); + private static final War3ID ATTACK2_PROJECTILE_HOMING_ENABLED = War3ID.fromString("umh2"); + private static final War3ID ATTACK2_PROJECTILE_SPEED = War3ID.fromString("ua2z"); + private static final War3ID ATTACK2_RANGE = War3ID.fromString("ua2r"); + private static final War3ID ATTACK2_RANGE_MOTION_BUFFER = War3ID.fromString("urb2"); + private static final War3ID ATTACK2_SHOW_UI = War3ID.fromString("uwu2"); + private static final War3ID ATTACK2_TARGETS_ALLOWED = War3ID.fromString("ua2g"); + private static final War3ID ATTACK2_WEAPON_SOUND = War3ID.fromString("ucs2"); + private static final War3ID ATTACK2_WEAPON_TYPE = War3ID.fromString("ua2w"); - private static final War3ID ACQUISITION_RANGE = War3ID.fromString("uacq"); - private static final War3ID MINIMUM_ATTACK_RANGE = War3ID.fromString("uamn"); + private static final War3ID ACQUISITION_RANGE = War3ID.fromString("uacq"); + private static final War3ID MINIMUM_ATTACK_RANGE = War3ID.fromString("uamn"); - private static final War3ID PROJECTILE_IMPACT_Z = War3ID.fromString("uimz"); + private static final War3ID PROJECTILE_IMPACT_Z = War3ID.fromString("uimz"); - private static final War3ID DEATH_TYPE = War3ID.fromString("udea"); - private static final War3ID ARMOR_TYPE = War3ID.fromString("uarm"); + private static final War3ID DEATH_TYPE = War3ID.fromString("udea"); + private static final War3ID ARMOR_TYPE = War3ID.fromString("uarm"); - private static final War3ID DEFENSE = War3ID.fromString("udef"); - private static final War3ID DEFENSE_TYPE = War3ID.fromString("udty"); - private static final War3ID MOVE_HEIGHT = War3ID.fromString("umvh"); - private static final War3ID MOVE_TYPE = War3ID.fromString("umvt"); - private static final War3ID COLLISION_SIZE = War3ID.fromString("ucol"); - private static final War3ID CLASSIFICATION = War3ID.fromString("utyp"); - private static final War3ID DEATH_TIME = War3ID.fromString("udtm"); - private static final War3ID TARGETED_AS = War3ID.fromString("utar"); + private static final War3ID DEFENSE = War3ID.fromString("udef"); + private static final War3ID DEFENSE_TYPE = War3ID.fromString("udty"); + private static final War3ID MOVE_HEIGHT = War3ID.fromString("umvh"); + private static final War3ID MOVE_TYPE = War3ID.fromString("umvt"); + private static final War3ID COLLISION_SIZE = War3ID.fromString("ucol"); + private static final War3ID CLASSIFICATION = War3ID.fromString("utyp"); + private static final War3ID DEATH_TIME = War3ID.fromString("udtm"); + private static final War3ID TARGETED_AS = War3ID.fromString("utar"); - private static final War3ID ABILITIES_NORMAL = War3ID.fromString("uabi"); - private final MutableObjectData unitData; - private final Map unitIdToUnitType = new HashMap<>(); - private final CAbilityData abilityData; + private static final War3ID ABILITIES_NORMAL = War3ID.fromString("uabi"); - public CUnitData(final MutableObjectData unitData, final CAbilityData abilityData) { - this.unitData = unitData; - this.abilityData = abilityData; - } + private static final War3ID STRUCTURES_BUILT = War3ID.fromString("ubui"); + private static final War3ID UNIT_RACE = War3ID.fromString("urac"); - public CUnit create(final CSimulation simulation, final int playerIndex, final War3ID typeId, final float x, - final float y, final float facing, final BufferedImage buildingPathingPixelMap, - final SimulationRenderController simulationRenderController, final HandleIdAllocator handleIdAllocator) { - final MutableGameObject unitType = this.unitData.get(typeId); - final int handleId = handleIdAllocator.createId(); - final int life = unitType.getFieldAsInteger(HIT_POINT_MAXIMUM, 0); - final int manaInitial = unitType.getFieldAsInteger(MANA_INITIAL_AMOUNT, 0); - final int manaMaximum = unitType.getFieldAsInteger(MANA_MAXIMUM, 0); - final int speed = unitType.getFieldAsInteger(MOVEMENT_SPEED_BASE, 0); - final int defense = unitType.getFieldAsInteger(DEFENSE, 0); - final String abilityList = unitType.getFieldAsString(ABILITIES_NORMAL, 0); + private static final War3ID GOLD_COST = War3ID.fromString("ugol"); + private static final War3ID LUMBER_COST = War3ID.fromString("ulum"); + private static final War3ID BUILD_TIME = War3ID.fromString("ubld"); - final CUnitType unitTypeInstance = getUnitTypeInstance(typeId, buildingPathingPixelMap, unitType); + private final MutableObjectData unitData; + private final Map unitIdToUnitType = new HashMap<>(); + private final CAbilityData abilityData; + private SimulationRenderController simulationRenderController; - final CUnit unit = new CUnit(handleId, playerIndex, x, y, life, typeId, facing, manaInitial, life, manaMaximum, - speed, defense, unitTypeInstance); - if (speed > 0) { - unit.add(simulation, new CAbilityMove(handleIdAllocator.createId())); - } - if (!unitTypeInstance.getAttacks().isEmpty()) { - unit.add(simulation, new CAbilityAttack(handleIdAllocator.createId())); - } - for (final String ability : abilityList.split(",")) { - if (ability.length() > 0 && !"_".equals(ability)) { - unit.add(simulation, this.abilityData.createAbility(ability, handleIdAllocator.createId())); - } - } - return unit; - } + public CUnitData(final MutableObjectData unitData, final CAbilityData abilityData) { + this.unitData = unitData; + this.abilityData = abilityData; + } - private CUnitType getUnitTypeInstance(final War3ID typeId, final BufferedImage buildingPathingPixelMap, - final MutableGameObject unitType) { - CUnitType unitTypeInstance = this.unitIdToUnitType.get(typeId); - if (unitTypeInstance == null) { - final float moveHeight = unitType.getFieldAsFloat(MOVE_HEIGHT, 0); - final String movetp = unitType.getFieldAsString(MOVE_TYPE, 0); - final float collisionSize = unitType.getFieldAsFloat(COLLISION_SIZE, 0); - final boolean isBldg = unitType.getFieldAsBoolean(IS_BLDG, 0); - final PathingGrid.MovementType movementType = PathingGrid.getMovementType(movetp); - final String unitName = unitType.getFieldAsString(NAME, 0); - final float acquisitionRange = unitType.getFieldAsFloat(ACQUISITION_RANGE, 0); - final float minimumAttackRange = unitType.getFieldAsFloat(MINIMUM_ATTACK_RANGE, 0); - final EnumSet targetedAs = CTargetType - .parseTargetTypeSet(unitType.getFieldAsString(TARGETED_AS, 0)); - final String classificationString = unitType.getFieldAsString(CLASSIFICATION, 0); - final EnumSet classifications = EnumSet.noneOf(CUnitClassification.class); - if (classificationString != null) { - final String[] classificationValues = classificationString.split(","); - for (final String unitEditorKey : classificationValues) { - final CUnitClassification unitClassification = CUnitClassification - .parseUnitClassification(unitEditorKey); - if (unitClassification != null) { - classifications.add(unitClassification); - } - } - } - final List attacks = new ArrayList<>(); - final int attacksEnabled = unitType.getFieldAsInteger(ATTACKS_ENABLED, 0); - if ((attacksEnabled & 0x1) != 0) { - try { - // attack one - final float animationBackswingPoint = unitType.getFieldAsFloat(ATTACK1_BACKSWING_POINT, 0); - final float animationDamagePoint = unitType.getFieldAsFloat(ATTACK1_DAMAGE_POINT, 0); - final int areaOfEffectFullDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_FULL_DMG, 0); - final int areaOfEffectMediumDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_HALF_DMG, 0); - final int areaOfEffectSmallDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_QUARTER_DMG, - 0); - final EnumSet areaOfEffectTargets = CTargetType - .parseTargetTypeSet(unitType.getFieldAsString(ATTACK1_AREA_OF_EFFECT_TARGETS, 0)); - final CAttackType attackType = CAttackType - .parseAttackType(unitType.getFieldAsString(ATTACK1_ATTACK_TYPE, 0)); - final float cooldownTime = unitType.getFieldAsFloat(ATTACK1_COOLDOWN, 0); - final int damageBase = unitType.getFieldAsInteger(ATTACK1_DMG_BASE, 0); - final float damageFactorMedium = unitType.getFieldAsFloat(ATTACK1_DAMAGE_FACTOR_HALF, 0); - final float damageFactorSmall = unitType.getFieldAsFloat(ATTACK1_DAMAGE_FACTOR_QUARTER, 0); - final float damageLossFactor = unitType.getFieldAsFloat(ATTACK1_DAMAGE_LOSS_FACTOR, 0); - final int damageDice = unitType.getFieldAsInteger(ATTACK1_DMG_DICE, 0); - final int damageSidesPerDie = unitType.getFieldAsInteger(ATTACK1_DMG_SIDES_PER_DIE, 0); - final float damageSpillDistance = unitType.getFieldAsFloat(ATTACK1_DMG_SPILL_DIST, 0); - final float damageSpillRadius = unitType.getFieldAsFloat(ATTACK1_DMG_SPILL_RADIUS, 0); - final int damageUpgradeAmount = unitType.getFieldAsInteger(ATTACK1_DMG_UPGRADE_AMT, 0); - final int maximumNumberOfTargets = unitType.getFieldAsInteger(ATTACK1_TARGET_COUNT, 0); - final float projectileArc = unitType.getFieldAsFloat(ATTACK1_PROJECTILE_ARC, 0); - final String projectileArt = unitType.getFieldAsString(ATTACK1_MISSILE_ART, 0); - final boolean projectileHomingEnabled = unitType - .getFieldAsBoolean(ATTACK1_PROJECTILE_HOMING_ENABLED, 0); - final int projectileSpeed = unitType.getFieldAsInteger(ATTACK1_PROJECTILE_SPEED, 0); - final int range = unitType.getFieldAsInteger(ATTACK1_RANGE, 0); - final float rangeMotionBuffer = unitType.getFieldAsFloat(ATTACK1_RANGE_MOTION_BUFFER, 0); - final boolean showUI = unitType.getFieldAsBoolean(ATTACK1_SHOW_UI, 0); - final EnumSet targetsAllowed = CTargetType - .parseTargetTypeSet(unitType.getFieldAsString(ATTACK1_TARGETS_ALLOWED, 0)); - final String weaponSound = unitType.getFieldAsString(ATTACK1_WEAPON_SOUND, 0); - final CWeaponType weaponType = CWeaponType - .parseWeaponType(unitType.getFieldAsString(ATTACK1_WEAPON_TYPE, 0)); - attacks.add(createAttack(animationBackswingPoint, animationDamagePoint, areaOfEffectFullDamage, - areaOfEffectMediumDamage, areaOfEffectSmallDamage, areaOfEffectTargets, attackType, - cooldownTime, damageBase, damageFactorMedium, damageFactorSmall, damageLossFactor, - damageDice, damageSidesPerDie, damageSpillDistance, damageSpillRadius, damageUpgradeAmount, - maximumNumberOfTargets, projectileArc, projectileArt, projectileHomingEnabled, - projectileSpeed, range, rangeMotionBuffer, showUI, targetsAllowed, weaponSound, - weaponType)); - } catch (final Exception exc) { - System.err.println("Attack 1 failed to parse with: " + exc.getClass() + ":" + exc.getMessage()); - } - } - if ((attacksEnabled & 0x2) != 0) { - try { - // attack two - final float animationBackswingPoint = unitType.getFieldAsFloat(ATTACK2_BACKSWING_POINT, 0); - final float animationDamagePoint = unitType.getFieldAsFloat(ATTACK2_DAMAGE_POINT, 0); - final int areaOfEffectFullDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_FULL_DMG, 0); - final int areaOfEffectMediumDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_HALF_DMG, 0); - final int areaOfEffectSmallDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_QUARTER_DMG, - 0); - final EnumSet areaOfEffectTargets = CTargetType - .parseTargetTypeSet(unitType.getFieldAsString(ATTACK2_AREA_OF_EFFECT_TARGETS, 0)); - final CAttackType attackType = CAttackType - .parseAttackType(unitType.getFieldAsString(ATTACK2_ATTACK_TYPE, 0)); - final float cooldownTime = unitType.getFieldAsFloat(ATTACK2_COOLDOWN, 0); - final int damageBase = unitType.getFieldAsInteger(ATTACK2_DMG_BASE, 0); - final float damageFactorMedium = unitType.getFieldAsFloat(ATTACK2_DAMAGE_FACTOR_HALF, 0); - final float damageFactorSmall = unitType.getFieldAsFloat(ATTACK2_DAMAGE_FACTOR_QUARTER, 0); - final float damageLossFactor = unitType.getFieldAsFloat(ATTACK2_DAMAGE_LOSS_FACTOR, 0); - final int damageDice = unitType.getFieldAsInteger(ATTACK2_DMG_DICE, 0); - final int damageSidesPerDie = unitType.getFieldAsInteger(ATTACK2_DMG_SIDES_PER_DIE, 0); - final float damageSpillDistance = unitType.getFieldAsFloat(ATTACK2_DMG_SPILL_DIST, 0); - final float damageSpillRadius = unitType.getFieldAsFloat(ATTACK2_DMG_SPILL_RADIUS, 0); - final int damageUpgradeAmount = unitType.getFieldAsInteger(ATTACK2_DMG_UPGRADE_AMT, 0); - final int maximumNumberOfTargets = unitType.getFieldAsInteger(ATTACK2_TARGET_COUNT, 0); - final float projectileArc = unitType.getFieldAsFloat(ATTACK2_PROJECTILE_ARC, 0); - final String projectileArt = unitType.getFieldAsString(ATTACK2_MISSILE_ART, 0); - final boolean projectileHomingEnabled = unitType - .getFieldAsBoolean(ATTACK2_PROJECTILE_HOMING_ENABLED, 0); - final int projectileSpeed = unitType.getFieldAsInteger(ATTACK2_PROJECTILE_SPEED, 0); - final int range = unitType.getFieldAsInteger(ATTACK2_RANGE, 0); - final float rangeMotionBuffer = unitType.getFieldAsFloat(ATTACK2_RANGE_MOTION_BUFFER, 0); - final boolean showUI = unitType.getFieldAsBoolean(ATTACK2_SHOW_UI, 0); - final EnumSet targetsAllowed = CTargetType - .parseTargetTypeSet(unitType.getFieldAsString(ATTACK2_TARGETS_ALLOWED, 0)); - final String weaponSound = unitType.getFieldAsString(ATTACK2_WEAPON_SOUND, 0); - final CWeaponType weaponType = CWeaponType - .parseWeaponType(unitType.getFieldAsString(ATTACK2_WEAPON_TYPE, 0)); - attacks.add(createAttack(animationBackswingPoint, animationDamagePoint, areaOfEffectFullDamage, - areaOfEffectMediumDamage, areaOfEffectSmallDamage, areaOfEffectTargets, attackType, - cooldownTime, damageBase, damageFactorMedium, damageFactorSmall, damageLossFactor, - damageDice, damageSidesPerDie, damageSpillDistance, damageSpillRadius, damageUpgradeAmount, - maximumNumberOfTargets, projectileArc, projectileArt, projectileHomingEnabled, - projectileSpeed, range, rangeMotionBuffer, showUI, targetsAllowed, weaponSound, - weaponType)); - } catch (final Exception exc) { - System.err.println("Attack 2 failed to parse with: " + exc.getClass() + ":" + exc.getMessage()); - } - } - final int deathType = unitType.getFieldAsInteger(DEATH_TYPE, 0); - final boolean raise = (deathType & 0x1) != 0; - final boolean decay = (deathType & 0x2) != 0; - final String armorType = unitType.getFieldAsString(ARMOR_TYPE, 0); - final float impactZ = unitType.getFieldAsFloat(PROJECTILE_IMPACT_Z, 0); - final CDefenseType defenseType = CDefenseType.parseDefenseType(unitType.getFieldAsString(DEFENSE_TYPE, 0)); - final float deathTime = unitType.getFieldAsFloat(DEATH_TIME, 0); - unitTypeInstance = new CUnitType(unitName, isBldg, movementType, moveHeight, collisionSize, classifications, - attacks, armorType, raise, decay, defenseType, impactZ, buildingPathingPixelMap, deathTime, - targetedAs, acquisitionRange, minimumAttackRange); - this.unitIdToUnitType.put(typeId, unitTypeInstance); - } - return unitTypeInstance; - } + public CUnit create(final CSimulation simulation, final int playerIndex, final War3ID typeId, final float x, + final float y, final float facing, final BufferedImage buildingPathingPixelMap, + final SimulationRenderController simulationRenderController, final HandleIdAllocator handleIdAllocator) { + this.simulationRenderController = simulationRenderController; + final MutableGameObject unitType = this.unitData.get(typeId); + final int handleId = handleIdAllocator.createId(); + final int life = unitType.getFieldAsInteger(HIT_POINT_MAXIMUM, 0); + final int manaInitial = unitType.getFieldAsInteger(MANA_INITIAL_AMOUNT, 0); + final int manaMaximum = unitType.getFieldAsInteger(MANA_MAXIMUM, 0); + final int speed = unitType.getFieldAsInteger(MOVEMENT_SPEED_BASE, 0); + final int defense = unitType.getFieldAsInteger(DEFENSE, 0); + final String abilityList = unitType.getFieldAsString(ABILITIES_NORMAL, 0); - private CUnitAttack createAttack(final float animationBackswingPoint, final float animationDamagePoint, - final int areaOfEffectFullDamage, final int areaOfEffectMediumDamage, final int areaOfEffectSmallDamage, - final EnumSet areaOfEffectTargets, final CAttackType attackType, final float cooldownTime, - final int damageBase, final float damageFactorMedium, final float damageFactorSmall, - final float damageLossFactor, final int damageDice, final int damageSidesPerDie, - final float damageSpillDistance, final float damageSpillRadius, final int damageUpgradeAmount, - final int maximumNumberOfTargets, final float projectileArc, final String projectileArt, - final boolean projectileHomingEnabled, final int projectileSpeed, final int range, - final float rangeMotionBuffer, final boolean showUI, final EnumSet targetsAllowed, - final String weaponSound, final CWeaponType weaponType) { - final CUnitAttack attack; - switch (weaponType) { - case MISSILE: - attack = new CUnitAttackMissile(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, - damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, - targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, projectileHomingEnabled, - projectileSpeed); - break; - case MBOUNCE: - attack = new CUnitAttackMissileBounce(animationBackswingPoint, animationDamagePoint, attackType, - cooldownTime, damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, - rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, - projectileHomingEnabled, projectileSpeed, damageLossFactor, maximumNumberOfTargets, - areaOfEffectFullDamage, areaOfEffectTargets); - break; - case MSPLASH: - case ARTILLERY: - attack = new CUnitAttackMissileSplash(animationBackswingPoint, animationDamagePoint, attackType, - cooldownTime, damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, - rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, - projectileHomingEnabled, projectileSpeed, areaOfEffectFullDamage, areaOfEffectMediumDamage, - areaOfEffectSmallDamage, areaOfEffectTargets, damageFactorMedium, damageFactorSmall); - break; - case MLINE: - case ALINE: - attack = new CUnitAttackMissileLine(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, - damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, - targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, projectileHomingEnabled, - projectileSpeed, damageSpillDistance, damageSpillRadius); - break; - case INSTANT: - attack = new CUnitAttackInstant(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, - damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, - targetsAllowed, weaponSound, weaponType, projectileArt); - break; - default: - case NORMAL: - attack = new CUnitAttackNormal(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, - damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, - targetsAllowed, weaponSound, weaponType); - break; - } - return attack; - } + final CUnitType unitTypeInstance = getUnitTypeInstance(typeId, buildingPathingPixelMap, unitType); - public float getPropulsionWindow(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(PROPULSION_WINDOW, 0); - } + final CUnit unit = new CUnit(handleId, playerIndex, x, y, life, typeId, facing, manaInitial, life, manaMaximum, + speed, defense, unitTypeInstance); + if (speed > 0) { + unit.add(simulation, new CAbilityMove(handleIdAllocator.createId())); + } + if (!unitTypeInstance.getAttacks().isEmpty()) { + unit.add(simulation, new CAbilityAttack(handleIdAllocator.createId())); + } + final List structuresBuilt = unitTypeInstance.getStructuresBuilt(); + if (!structuresBuilt.isEmpty()) { + switch (unitTypeInstance.getRace()) { + case ORC: + unit.add(simulation, new CAbilityOrcBuild(handleIdAllocator.createId(), structuresBuilt)); + break; + case HUMAN: + unit.add(simulation, new CAbilityHumanBuild(handleIdAllocator.createId(), structuresBuilt)); + break; + case UNDEAD: + unit.add(simulation, new CAbilityUndeadBuild(handleIdAllocator.createId(), structuresBuilt)); + break; + case NIGHTELF: + unit.add(simulation, new CAbilityNightElfBuild(handleIdAllocator.createId(), structuresBuilt)); + break; + case NAGA: + unit.add(simulation, new CAbilityNagaBuild(handleIdAllocator.createId(), structuresBuilt)); + break; + case CREEPS: + case CRITTERS: + case DEMON: + case OTHER: + unit.add(simulation, new CAbilityNeutralBuild(handleIdAllocator.createId(), structuresBuilt)); + break; + } + } + for (final String ability : abilityList.split(",")) { + if ((ability.length() > 0) && !"_".equals(ability)) { + unit.add(simulation, this.abilityData.createAbility(ability, handleIdAllocator.createId())); + } + } + return unit; + } - public float getTurnRate(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(TURN_RATE, 0); - } + private CUnitType getUnitTypeInstance(final War3ID typeId, final BufferedImage buildingPathingPixelMap, + final MutableGameObject unitType) { + CUnitType unitTypeInstance = this.unitIdToUnitType.get(typeId); + if (unitTypeInstance == null) { + final float moveHeight = unitType.getFieldAsFloat(MOVE_HEIGHT, 0); + final String movetp = unitType.getFieldAsString(MOVE_TYPE, 0); + final float collisionSize = unitType.getFieldAsFloat(COLLISION_SIZE, 0); + final boolean isBldg = unitType.getFieldAsBoolean(IS_BLDG, 0); + final PathingGrid.MovementType movementType = PathingGrid.getMovementType(movetp); + final String unitName = unitType.getFieldAsString(NAME, 0); + final float acquisitionRange = unitType.getFieldAsFloat(ACQUISITION_RANGE, 0); + final float minimumAttackRange = unitType.getFieldAsFloat(MINIMUM_ATTACK_RANGE, 0); + final EnumSet targetedAs = CTargetType + .parseTargetTypeSet(unitType.getFieldAsString(TARGETED_AS, 0)); + final String classificationString = unitType.getFieldAsString(CLASSIFICATION, 0); + final EnumSet classifications = EnumSet.noneOf(CUnitClassification.class); + if (classificationString != null) { + final String[] classificationValues = classificationString.split(","); + for (final String unitEditorKey : classificationValues) { + final CUnitClassification unitClassification = CUnitClassification + .parseUnitClassification(unitEditorKey); + if (unitClassification != null) { + classifications.add(unitClassification); + } + } + } + final List attacks = new ArrayList<>(); + final int attacksEnabled = unitType.getFieldAsInteger(ATTACKS_ENABLED, 0); + if ((attacksEnabled & 0x1) != 0) { + try { + // attack one + final float animationBackswingPoint = unitType.getFieldAsFloat(ATTACK1_BACKSWING_POINT, 0); + final float animationDamagePoint = unitType.getFieldAsFloat(ATTACK1_DAMAGE_POINT, 0); + final int areaOfEffectFullDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_FULL_DMG, 0); + final int areaOfEffectMediumDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_HALF_DMG, 0); + final int areaOfEffectSmallDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_QUARTER_DMG, + 0); + final EnumSet areaOfEffectTargets = CTargetType + .parseTargetTypeSet(unitType.getFieldAsString(ATTACK1_AREA_OF_EFFECT_TARGETS, 0)); + final CAttackType attackType = CAttackType + .parseAttackType(unitType.getFieldAsString(ATTACK1_ATTACK_TYPE, 0)); + final float cooldownTime = unitType.getFieldAsFloat(ATTACK1_COOLDOWN, 0); + final int damageBase = unitType.getFieldAsInteger(ATTACK1_DMG_BASE, 0); + final float damageFactorMedium = unitType.getFieldAsFloat(ATTACK1_DAMAGE_FACTOR_HALF, 0); + final float damageFactorSmall = unitType.getFieldAsFloat(ATTACK1_DAMAGE_FACTOR_QUARTER, 0); + final float damageLossFactor = unitType.getFieldAsFloat(ATTACK1_DAMAGE_LOSS_FACTOR, 0); + final int damageDice = unitType.getFieldAsInteger(ATTACK1_DMG_DICE, 0); + final int damageSidesPerDie = unitType.getFieldAsInteger(ATTACK1_DMG_SIDES_PER_DIE, 0); + final float damageSpillDistance = unitType.getFieldAsFloat(ATTACK1_DMG_SPILL_DIST, 0); + final float damageSpillRadius = unitType.getFieldAsFloat(ATTACK1_DMG_SPILL_RADIUS, 0); + final int damageUpgradeAmount = unitType.getFieldAsInteger(ATTACK1_DMG_UPGRADE_AMT, 0); + final int maximumNumberOfTargets = unitType.getFieldAsInteger(ATTACK1_TARGET_COUNT, 0); + final float projectileArc = unitType.getFieldAsFloat(ATTACK1_PROJECTILE_ARC, 0); + final String projectileArt = unitType.getFieldAsString(ATTACK1_MISSILE_ART, 0); + final boolean projectileHomingEnabled = unitType + .getFieldAsBoolean(ATTACK1_PROJECTILE_HOMING_ENABLED, 0); + final int projectileSpeed = unitType.getFieldAsInteger(ATTACK1_PROJECTILE_SPEED, 0); + final int range = unitType.getFieldAsInteger(ATTACK1_RANGE, 0); + final float rangeMotionBuffer = unitType.getFieldAsFloat(ATTACK1_RANGE_MOTION_BUFFER, 0); + final boolean showUI = unitType.getFieldAsBoolean(ATTACK1_SHOW_UI, 0); + final EnumSet targetsAllowed = CTargetType + .parseTargetTypeSet(unitType.getFieldAsString(ATTACK1_TARGETS_ALLOWED, 0)); + final String weaponSound = unitType.getFieldAsString(ATTACK1_WEAPON_SOUND, 0); + final CWeaponType weaponType = CWeaponType + .parseWeaponType(unitType.getFieldAsString(ATTACK1_WEAPON_TYPE, 0)); + attacks.add(createAttack(animationBackswingPoint, animationDamagePoint, areaOfEffectFullDamage, + areaOfEffectMediumDamage, areaOfEffectSmallDamage, areaOfEffectTargets, attackType, + cooldownTime, damageBase, damageFactorMedium, damageFactorSmall, damageLossFactor, + damageDice, damageSidesPerDie, damageSpillDistance, damageSpillRadius, damageUpgradeAmount, + maximumNumberOfTargets, projectileArc, projectileArt, projectileHomingEnabled, + projectileSpeed, range, rangeMotionBuffer, showUI, targetsAllowed, weaponSound, + weaponType)); + } + catch (final Exception exc) { + System.err.println("Attack 1 failed to parse with: " + exc.getClass() + ":" + exc.getMessage()); + } + } + if ((attacksEnabled & 0x2) != 0) { + try { + // attack two + final float animationBackswingPoint = unitType.getFieldAsFloat(ATTACK2_BACKSWING_POINT, 0); + final float animationDamagePoint = unitType.getFieldAsFloat(ATTACK2_DAMAGE_POINT, 0); + final int areaOfEffectFullDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_FULL_DMG, 0); + final int areaOfEffectMediumDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_HALF_DMG, 0); + final int areaOfEffectSmallDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_QUARTER_DMG, + 0); + final EnumSet areaOfEffectTargets = CTargetType + .parseTargetTypeSet(unitType.getFieldAsString(ATTACK2_AREA_OF_EFFECT_TARGETS, 0)); + final CAttackType attackType = CAttackType + .parseAttackType(unitType.getFieldAsString(ATTACK2_ATTACK_TYPE, 0)); + final float cooldownTime = unitType.getFieldAsFloat(ATTACK2_COOLDOWN, 0); + final int damageBase = unitType.getFieldAsInteger(ATTACK2_DMG_BASE, 0); + final float damageFactorMedium = unitType.getFieldAsFloat(ATTACK2_DAMAGE_FACTOR_HALF, 0); + final float damageFactorSmall = unitType.getFieldAsFloat(ATTACK2_DAMAGE_FACTOR_QUARTER, 0); + final float damageLossFactor = unitType.getFieldAsFloat(ATTACK2_DAMAGE_LOSS_FACTOR, 0); + final int damageDice = unitType.getFieldAsInteger(ATTACK2_DMG_DICE, 0); + final int damageSidesPerDie = unitType.getFieldAsInteger(ATTACK2_DMG_SIDES_PER_DIE, 0); + final float damageSpillDistance = unitType.getFieldAsFloat(ATTACK2_DMG_SPILL_DIST, 0); + final float damageSpillRadius = unitType.getFieldAsFloat(ATTACK2_DMG_SPILL_RADIUS, 0); + final int damageUpgradeAmount = unitType.getFieldAsInteger(ATTACK2_DMG_UPGRADE_AMT, 0); + final int maximumNumberOfTargets = unitType.getFieldAsInteger(ATTACK2_TARGET_COUNT, 0); + final float projectileArc = unitType.getFieldAsFloat(ATTACK2_PROJECTILE_ARC, 0); + final String projectileArt = unitType.getFieldAsString(ATTACK2_MISSILE_ART, 0); + final boolean projectileHomingEnabled = unitType + .getFieldAsBoolean(ATTACK2_PROJECTILE_HOMING_ENABLED, 0); + final int projectileSpeed = unitType.getFieldAsInteger(ATTACK2_PROJECTILE_SPEED, 0); + final int range = unitType.getFieldAsInteger(ATTACK2_RANGE, 0); + final float rangeMotionBuffer = unitType.getFieldAsFloat(ATTACK2_RANGE_MOTION_BUFFER, 0); + final boolean showUI = unitType.getFieldAsBoolean(ATTACK2_SHOW_UI, 0); + final EnumSet targetsAllowed = CTargetType + .parseTargetTypeSet(unitType.getFieldAsString(ATTACK2_TARGETS_ALLOWED, 0)); + final String weaponSound = unitType.getFieldAsString(ATTACK2_WEAPON_SOUND, 0); + final CWeaponType weaponType = CWeaponType + .parseWeaponType(unitType.getFieldAsString(ATTACK2_WEAPON_TYPE, 0)); + attacks.add(createAttack(animationBackswingPoint, animationDamagePoint, areaOfEffectFullDamage, + areaOfEffectMediumDamage, areaOfEffectSmallDamage, areaOfEffectTargets, attackType, + cooldownTime, damageBase, damageFactorMedium, damageFactorSmall, damageLossFactor, + damageDice, damageSidesPerDie, damageSpillDistance, damageSpillRadius, damageUpgradeAmount, + maximumNumberOfTargets, projectileArc, projectileArt, projectileHomingEnabled, + projectileSpeed, range, rangeMotionBuffer, showUI, targetsAllowed, weaponSound, + weaponType)); + } + catch (final Exception exc) { + System.err.println("Attack 2 failed to parse with: " + exc.getClass() + ":" + exc.getMessage()); + } + } + final int deathType = unitType.getFieldAsInteger(DEATH_TYPE, 0); + final boolean raise = (deathType & 0x1) != 0; + final boolean decay = (deathType & 0x2) != 0; + final String armorType = unitType.getFieldAsString(ARMOR_TYPE, 0); + final float impactZ = unitType.getFieldAsFloat(PROJECTILE_IMPACT_Z, 0); + final CDefenseType defenseType = CDefenseType.parseDefenseType(unitType.getFieldAsString(DEFENSE_TYPE, 0)); + final float deathTime = unitType.getFieldAsFloat(DEATH_TIME, 0); + final int goldCost = unitType.getFieldAsInteger(GOLD_COST, 0); + final int lumberCost = unitType.getFieldAsInteger(LUMBER_COST, 0); + final int buildTime = unitType.getFieldAsInteger(BUILD_TIME, 0); - public boolean isBuilding(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsBoolean(IS_BLDG, 0); - } + final String structuresBuiltString = unitType.getFieldAsString(STRUCTURES_BUILT, 0); + final String[] structuresBuiltStringItems = structuresBuiltString.split(","); + final List structuresBuilt = new ArrayList<>(); + for (final String structuresBuiltStringItem : structuresBuiltStringItems) { + if (structuresBuiltStringItem.length() == 4) { + structuresBuilt.add(War3ID.fromString(structuresBuiltStringItem)); + } + } - public String getName(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getName(); - } + final String raceString = unitType.getFieldAsString(UNIT_RACE, 0); + final CUnitRace unitRace = CUnitRace.parseRace(raceString); - public int getA1MinDamage(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_BASE, 0) - + this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_DICE, 0); - } + unitTypeInstance = new CUnitType(unitName, isBldg, movementType, moveHeight, collisionSize, classifications, + attacks, armorType, raise, decay, defenseType, impactZ, buildingPathingPixelMap, deathTime, + targetedAs, acquisitionRange, minimumAttackRange, structuresBuilt, unitRace, goldCost, lumberCost, + buildTime); + this.unitIdToUnitType.put(typeId, unitTypeInstance); + } + return unitTypeInstance; + } - public int getA1MaxDamage(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_BASE, 0) - + (this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_DICE, 0) - * this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_SIDES_PER_DIE, 0)); - } + private CUnitAttack createAttack(final float animationBackswingPoint, final float animationDamagePoint, + final int areaOfEffectFullDamage, final int areaOfEffectMediumDamage, final int areaOfEffectSmallDamage, + final EnumSet areaOfEffectTargets, final CAttackType attackType, final float cooldownTime, + final int damageBase, final float damageFactorMedium, final float damageFactorSmall, + final float damageLossFactor, final int damageDice, final int damageSidesPerDie, + final float damageSpillDistance, final float damageSpillRadius, final int damageUpgradeAmount, + final int maximumNumberOfTargets, final float projectileArc, final String projectileArt, + final boolean projectileHomingEnabled, final int projectileSpeed, final int range, + final float rangeMotionBuffer, final boolean showUI, final EnumSet targetsAllowed, + final String weaponSound, final CWeaponType weaponType) { + final CUnitAttack attack; + switch (weaponType) { + case MISSILE: + attack = new CUnitAttackMissile(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, + damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, + targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, projectileHomingEnabled, + projectileSpeed); + break; + case MBOUNCE: + attack = new CUnitAttackMissileBounce(animationBackswingPoint, animationDamagePoint, attackType, + cooldownTime, damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, + rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, + projectileHomingEnabled, projectileSpeed, damageLossFactor, maximumNumberOfTargets, + areaOfEffectFullDamage, areaOfEffectTargets); + break; + case MSPLASH: + case ARTILLERY: + attack = new CUnitAttackMissileSplash(animationBackswingPoint, animationDamagePoint, attackType, + cooldownTime, damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, + rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, + projectileHomingEnabled, projectileSpeed, areaOfEffectFullDamage, areaOfEffectMediumDamage, + areaOfEffectSmallDamage, areaOfEffectTargets, damageFactorMedium, damageFactorSmall); + break; + case MLINE: + case ALINE: + attack = new CUnitAttackMissileLine(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, + damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, + targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, projectileHomingEnabled, + projectileSpeed, damageSpillDistance, damageSpillRadius); + break; + case INSTANT: + attack = new CUnitAttackInstant(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, + damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, + targetsAllowed, weaponSound, weaponType, projectileArt); + break; + default: + case NORMAL: + attack = new CUnitAttackNormal(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, + damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, + targetsAllowed, weaponSound, weaponType); + break; + } + return attack; + } - public int getA2MinDamage(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_BASE, 0) - + this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_DICE, 0); - } + public float getPropulsionWindow(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(PROPULSION_WINDOW, 0); + } - public int getA2MaxDamage(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_BASE, 0) - + (this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_DICE, 0) - * this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_SIDES_PER_DIE, 0)); - } + public float getTurnRate(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(TURN_RATE, 0); + } - public int getDefense(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsInteger(DEFENSE, 0); - } + public boolean isBuilding(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsBoolean(IS_BLDG, 0); + } - public int getA1ProjectileSpeed(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_PROJECTILE_SPEED, 0); - } + public String getName(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getName(); + } - public float getA1ProjectileArc(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK1_PROJECTILE_ARC, 0); - } + public int getA1MinDamage(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_BASE, 0) + + this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_DICE, 0); + } - public int getA2ProjectileSpeed(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_PROJECTILE_SPEED, 0); - } + public int getA1MaxDamage(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_BASE, 0) + + (this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_DICE, 0) + * this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_SIDES_PER_DIE, 0)); + } - public float getA2ProjectileArc(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK2_PROJECTILE_ARC, 0); - } + public int getA2MinDamage(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_BASE, 0) + + this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_DICE, 0); + } - public String getA1MissileArt(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsString(ATTACK1_MISSILE_ART, 0); - } + public int getA2MaxDamage(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_BASE, 0) + + (this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_DICE, 0) + * this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_SIDES_PER_DIE, 0)); + } - public String getA2MissileArt(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsString(ATTACK2_MISSILE_ART, 0); - } + public int getDefense(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(DEFENSE, 0); + } - public float getA1Cooldown(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK1_COOLDOWN, 0); - } + public int getA1ProjectileSpeed(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_PROJECTILE_SPEED, 0); + } - public float getA2Cooldown(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK2_COOLDOWN, 0); - } + public float getA1ProjectileArc(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK1_PROJECTILE_ARC, 0); + } - public float getProjectileLaunchX(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(PROJECTILE_LAUNCH_X, 0); - } + public int getA2ProjectileSpeed(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_PROJECTILE_SPEED, 0); + } - public float getProjectileLaunchY(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(PROJECTILE_LAUNCH_Y, 0); - } + public float getA2ProjectileArc(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK2_PROJECTILE_ARC, 0); + } - public float getProjectileLaunchZ(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(PROJECTILE_LAUNCH_Z, 0); - } + public String getA1MissileArt(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsString(ATTACK1_MISSILE_ART, 0); + } + + public String getA2MissileArt(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsString(ATTACK2_MISSILE_ART, 0); + } + + public float getA1Cooldown(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK1_COOLDOWN, 0); + } + + public float getA2Cooldown(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK2_COOLDOWN, 0); + } + + public float getProjectileLaunchX(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(PROJECTILE_LAUNCH_X, 0); + } + + public float getProjectileLaunchY(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(PROJECTILE_LAUNCH_Y, 0); + } + + public float getProjectileLaunchZ(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(PROJECTILE_LAUNCH_Z, 0); + } + + public CUnitType getUnitType(final War3ID rawcode) { + final CUnitType unitTypeInstance = this.unitIdToUnitType.get(rawcode); + if (unitTypeInstance != null) { + return unitTypeInstance; + } + final MutableGameObject unitType = this.unitData.get(rawcode); + if (unitType == null) { + return null; + } + final BufferedImage buildingPathingPixelMap = this.simulationRenderController + .getBuildingPathingPixelMap(rawcode); + return getUnitTypeInstance(rawcode, buildingPathingPixelMap, unitType); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitRace.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitRace.java new file mode 100644 index 0000000..57f77f8 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitRace.java @@ -0,0 +1,32 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.data; + +import java.util.HashMap; +import java.util.Map; + +public enum CUnitRace { + HUMAN, + ORC, + UNDEAD, + NIGHTELF, + NAGA, + CREEPS, + DEMON, + CRITTERS, + OTHER; + + private static Map keyToRace = new HashMap<>(); + + static { + for (final CUnitRace race : CUnitRace.values()) { + keyToRace.put(race.name(), race); + } + } + + public static CUnitRace parseRace(final String raceString) { + final CUnitRace race = keyToRace.get(raceString.toUpperCase()); + if (race == null) { + return OTHER; + } + return race; + } +} \ No newline at end of file diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderNoTarget.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderNoTarget.java index 299686f..9f3687d 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderNoTarget.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderNoTarget.java @@ -27,6 +27,10 @@ public class COrderNoTarget implements COrder { @Override public CBehavior begin(final CSimulation game, final CUnit caster) { final CAbility ability = game.getAbility(this.abilityHandleId); + if ((ability == null) && (this.orderId == OrderIds.stop)) { + // stop + return caster.getStopBehavior(); + } return ability.beginNoTarget(game, caster, this.orderId); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/AbilityActivationReceiver.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/AbilityActivationReceiver.java index 242d59a..d248b69 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/AbilityActivationReceiver.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/AbilityActivationReceiver.java @@ -3,7 +3,7 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.util; public interface AbilityActivationReceiver { void useOk(); - void notEnoughResources(ResourceType resource, int amount); + void notEnoughResources(ResourceType resource); void notAnActiveAbility(); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/BooleanAbilityActivationReceiver.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/BooleanAbilityActivationReceiver.java index 8bca4f9..bfafcc3 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/BooleanAbilityActivationReceiver.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/BooleanAbilityActivationReceiver.java @@ -10,7 +10,7 @@ public class BooleanAbilityActivationReceiver implements AbilityActivationReceiv } @Override - public void notEnoughResources(final ResourceType resource, final int amount) { + public void notEnoughResources(final ResourceType resource) { this.ok = false; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java index 43f0993..ea07743 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java @@ -1,5 +1,8 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.util; +import java.awt.image.BufferedImage; + +import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; @@ -16,4 +19,6 @@ public interface SimulationRenderController { void spawnUnitDamageSound(CUnit damagedUnit, final String weaponSound, String armorType); void removeUnit(CUnit unit); + + BufferedImage getBuildingPathingPixelMap(War3ID rawcode); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/StringMsgAbilityActivationReceiver.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/StringMsgAbilityActivationReceiver.java index d9d77bb..90b2208 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/StringMsgAbilityActivationReceiver.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/StringMsgAbilityActivationReceiver.java @@ -30,8 +30,8 @@ public class StringMsgAbilityActivationReceiver implements AbilityActivationRece } @Override - public void notEnoughResources(final ResourceType resource, final int amount) { - this.message = "NOTEXTERN: Requires " + amount + " " + resource.name().toLowerCase() + "."; + public void notEnoughResources(final ResourceType resource) { + this.message = "NOTEXTERN: Requires more " + resource.name().toLowerCase() + "."; } @Override diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java index 4de1c7a..6d2f033 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java @@ -6,6 +6,7 @@ import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.GlyphLayout; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.utils.viewport.Viewport; +import com.etheller.warsmash.parsers.fdf.GameUI; import com.etheller.warsmash.parsers.fdf.frames.AbstractRenderableFrame; import com.etheller.warsmash.parsers.fdf.frames.SpriteFrame; import com.etheller.warsmash.parsers.fdf.frames.TextureFrame; @@ -26,6 +27,7 @@ public class CommandCardIcon extends AbstractRenderableFrame { private int autoCastOrderId; private boolean autoCastActive; private final CommandCardCommandListener commandCardCommandListener; + private boolean menuButton; public CommandCardIcon(final String name, final UIFrame parent, final CommandCardCommandListener commandCardCommandListener) { @@ -70,7 +72,8 @@ public class CommandCardIcon extends AbstractRenderableFrame { } public void setCommandButtonData(final Texture texture, final int abilityHandleId, final int orderId, - final int autoCastOrderId, final boolean active, final boolean autoCastActive) { + final int autoCastOrderId, final boolean active, final boolean autoCastActive, final boolean menuButton) { + this.menuButton = menuButton; this.iconFrame.setVisible(true); this.activeHighlightFrame.setVisible(active); this.cooldownFrame.setVisible(false); @@ -111,14 +114,46 @@ public class CommandCardIcon extends AbstractRenderableFrame { @Override public UIFrame touchDown(final float screenX, final float screenY, final int button) { if (this.renderBounds.contains(screenX, screenY)) { - if (button == Input.Buttons.LEFT) { - this.commandCardCommandListener.startUsingAbility(this.abilityHandleId, this.orderId); - } - else if (button == Input.Buttons.RIGHT) { - this.commandCardCommandListener.startUsingAbility(this.abilityHandleId, this.autoCastOrderId); - } return this; } - return null; + return super.touchDown(screenX, screenY, button); + } + + @Override + public UIFrame touchUp(final float screenX, final float screenY, final int button) { + if (this.renderBounds.contains(screenX, screenY)) { + return this; + } + return super.touchUp(screenX, screenY, button); + } + + public boolean isMenuButton() { + return this.menuButton; + } + + public void onClick(final int button) { + if (button == Input.Buttons.LEFT) { + if (this.menuButton) { + this.commandCardCommandListener.openMenu(this.orderId); + } + else { + this.commandCardCommandListener.startUsingAbility(this.abilityHandleId, this.orderId); + } + } + else if (button == Input.Buttons.RIGHT) { + this.commandCardCommandListener.startUsingAbility(this.abilityHandleId, this.autoCastOrderId); + } + } + + public void mouseDown(final Viewport uiViewport) { + this.iconFrame.setWidth(GameUI.convertX(uiViewport, MeleeUI.DEFAULT_COMMAND_CARD_ICON_PRESSED_WIDTH)); + this.iconFrame.setHeight(GameUI.convertY(uiViewport, MeleeUI.DEFAULT_COMMAND_CARD_ICON_PRESSED_WIDTH)); + positionBounds(uiViewport); + } + + public void mouseUp(final Viewport uiViewport) { + this.iconFrame.setWidth(GameUI.convertX(uiViewport, MeleeUI.DEFAULT_COMMAND_CARD_ICON_WIDTH)); + this.iconFrame.setHeight(GameUI.convertY(uiViewport, MeleeUI.DEFAULT_COMMAND_CARD_ICON_WIDTH)); + positionBounds(uiViewport); } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java index 5c812fa..43cb979 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java @@ -1,6 +1,7 @@ package com.etheller.warsmash.viewer5.handlers.w3x.ui; import java.io.IOException; +import java.util.ArrayList; import java.util.List; import javax.imageio.ImageIO; @@ -38,6 +39,7 @@ import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; import com.etheller.warsmash.viewer5.handlers.mdx.ReplaceableIds; import com.etheller.warsmash.viewer5.handlers.mdx.SequenceLoopMode; import com.etheller.warsmash.viewer5.handlers.tga.TgaFile; +import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; import com.etheller.warsmash.viewer5.handlers.w3x.UnitSound; import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; @@ -46,6 +48,8 @@ import com.etheller.warsmash.viewer5.handlers.w3x.camera.CameraRates; import com.etheller.warsmash.viewer5.handlers.w3x.camera.GameCameraManager; import com.etheller.warsmash.viewer5.handlers.w3x.camera.PortraitCameraManager; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.AbilityDataUI; +import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.IconUI; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.commandbuttons.CommandButtonListener; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitClassification; @@ -69,6 +73,8 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.StringMsgAbili import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.CommandCardCommandListener; public class MeleeUI implements CUnitStateListener, CommandButtonListener, CommandCardCommandListener { + public static final float DEFAULT_COMMAND_CARD_ICON_WIDTH = 0.039f; + public static final float DEFAULT_COMMAND_CARD_ICON_PRESSED_WIDTH = 0.037f; private static final int COMMAND_CARD_WIDTH = 4; private static final int COMMAND_CARD_HEIGHT = 3; @@ -120,6 +126,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma private final CommandCardIcon[][] commandCard = new CommandCardIcon[COMMAND_CARD_HEIGHT][COMMAND_CARD_WIDTH]; private RenderUnit selectedUnit; + private final List subMenuOrderIdStack = new ArrayList<>(); // TODO remove this & replace with FDF private final Texture activeButtonTexture; @@ -140,6 +147,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma // probably remove them later private final float widthRatioCorrection; private final float heightRatioCorrection; + private CommandCardIcon mouseDownUIFrame; public MeleeUI(final DataSource dataSource, final ExtendViewport uiViewport, final FreeTypeFontGenerator fontGenerator, final Scene uiScene, final Scene portraitScene, @@ -325,25 +333,25 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma commandCardIcon.addAnchor(new AnchorDefinition(FramePoint.BOTTOMLEFT, GameUI.convertX(this.uiViewport, 0.6175f + (0.0434f * i)), GameUI.convertY(this.uiViewport, 0.095f - (0.044f * j)))); - commandCardIcon.setWidth(GameUI.convertX(this.uiViewport, 0.039f)); - commandCardIcon.setHeight(GameUI.convertY(this.uiViewport, 0.039f)); + commandCardIcon.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); + commandCardIcon.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); iconFrame.addSetPoint(new SetPoint(FramePoint.CENTER, commandCardIcon, FramePoint.CENTER, 0, 0)); - iconFrame.setWidth(GameUI.convertX(this.uiViewport, 0.039f)); - iconFrame.setHeight(GameUI.convertY(this.uiViewport, 0.039f)); + iconFrame.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); + iconFrame.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); iconFrame.setTexture(ImageUtils.DEFAULT_ICON_PATH, this.rootFrame); activeHighlightFrame .addSetPoint(new SetPoint(FramePoint.CENTER, commandCardIcon, FramePoint.CENTER, 0, 0)); - activeHighlightFrame.setWidth(GameUI.convertX(this.uiViewport, 0.039f)); - activeHighlightFrame.setHeight(GameUI.convertY(this.uiViewport, 0.039f)); + activeHighlightFrame.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); + activeHighlightFrame.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); activeHighlightFrame.setTexture("CommandButtonActiveHighlight", this.rootFrame); cooldownFrame.addSetPoint(new SetPoint(FramePoint.CENTER, commandCardIcon, FramePoint.CENTER, 0, 0)); this.rootFrame.setSpriteFrameModel(cooldownFrame, this.rootFrame.getSkinField("CommandButtonCooldown")); - cooldownFrame.setWidth(GameUI.convertX(this.uiViewport, 0.039f)); - cooldownFrame.setHeight(GameUI.convertY(this.uiViewport, 0.039f)); + cooldownFrame.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); + cooldownFrame.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); autocastFrame.addSetPoint(new SetPoint(FramePoint.CENTER, commandCardIcon, FramePoint.CENTER, 0, 0)); this.rootFrame.setSpriteFrameModel(autocastFrame, this.rootFrame.getSkinField("CommandButtonAutocast")); - autocastFrame.setWidth(GameUI.convertX(this.uiViewport, 0.039f)); - autocastFrame.setHeight(GameUI.convertY(this.uiViewport, 0.039f)); + autocastFrame.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); + autocastFrame.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); commandCardIcon.set(iconFrame, activeHighlightFrame, cooldownFrame, autocastFrame); this.commandCard[j][i] = commandCardIcon; commandCardIcon.setCommandButton(null); @@ -395,9 +403,20 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.activeCommand = abilityToUse; this.activeCommandOrderId = orderId; this.activeCommandUnit = this.selectedUnit; + clearAndRepopulateCommandCard(); } } } + else { + this.unitOrderListener.issueImmediateOrder(this.selectedUnit.getSimulationUnit().getHandleId(), + abilityHandleId, orderId, isShiftDown()); + } + } + + @Override + public void openMenu(final int orderId) { + this.subMenuOrderIdStack.add(orderId); + clearAndRepopulateCommandCard(); } public void showCommandError(final String message) { @@ -515,12 +534,12 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.portraitCameraManager.updateCamera(); if ((this.modelInstance != null) && (this.modelInstance.sequenceEnded || (this.modelInstance.sequence == -1))) { - SequenceUtils.randomPortraitSequence(this.modelInstance); + SequenceUtils.randomSequence(this.modelInstance, PrimaryTag.PORTRAIT, SequenceUtils.EMPTY, true); } } public void talk() { - SequenceUtils.randomPortraitTalkSequence(this.modelInstance); + SequenceUtils.randomSequence(this.modelInstance, PrimaryTag.PORTRAIT, SequenceUtils.TALK, true); } public void setSelectedUnit(final RenderUnit unit) { @@ -539,7 +558,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } this.modelInstance = (MdxComplexInstance) portraitModel.addInstance(); this.portraitCameraManager.setModelInstance(this.modelInstance, portraitModel); - this.modelInstance.setSequenceLoopMode(SequenceLoopMode.MODEL_LOOP); + this.modelInstance.setSequenceLoopMode(SequenceLoopMode.NEVER_LOOP); this.modelInstance.setScene(this.portraitScene); this.modelInstance.setVertexColor(unit.instance.vertexColor); this.modelInstance.setTeamColor(unit.playerIndex); @@ -549,6 +568,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } public void selectUnit(RenderUnit unit) { + this.subMenuOrderIdStack.clear(); if ((unit != null) && unit.getSimulationUnit().isDead()) { unit = null; } @@ -645,7 +665,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } localArmorIconBackdrop.setTexture(defenseTexture); localArmorInfoPanelIconValue.setText(Integer.toString(unit.getSimulationUnit().getDefense())); - unit.populateCommandCard(this.war3MapViewer.simulation, this, this.war3MapViewer.getAbilityDataUI()); + unit.populateCommandCard(this.war3MapViewer.simulation, this, this.war3MapViewer.getAbilityDataUI(), + getSubMenuOrderId()); } } @@ -660,10 +681,11 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma @Override public void commandButton(final int buttonPositionX, final int buttonPositionY, final Texture icon, final int abilityHandleId, final int orderId, final int autoCastId, final boolean active, - final boolean autoCastActive) { + final boolean autoCastActive, final boolean menuButton) { final int x = Math.max(0, Math.min(COMMAND_CARD_WIDTH - 1, buttonPositionX)); final int y = Math.max(0, Math.min(COMMAND_CARD_HEIGHT - 1, buttonPositionY)); - this.commandCard[y][x].setCommandButtonData(icon, abilityHandleId, orderId, autoCastId, active, autoCastActive); + this.commandCard[y][x].setCommandButtonData(icon, abilityHandleId, orderId, autoCastId, active, autoCastActive, + menuButton); } public void resize(final Rectangle viewport) { @@ -755,9 +777,34 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma @Override public void ordersChanged(final int abilityHandleId, final int orderId) { + clearAndRepopulateCommandCard(); + } + + private void clearAndRepopulateCommandCard() { clearCommandCard(); - this.selectedUnit.populateCommandCard(this.war3MapViewer.simulation, this, - this.war3MapViewer.getAbilityDataUI()); + final AbilityDataUI abilityDataUI = this.war3MapViewer.getAbilityDataUI(); + final int menuOrderId = getSubMenuOrderId(); + if (this.activeCommand != null) { + final IconUI cancelUI = abilityDataUI.getCancelUI(); + this.commandButton(cancelUI.getButtonPositionX(), cancelUI.getButtonPositionY(), cancelUI.getIcon(), 0, + menuOrderId, 0, false, false, true); + } + else { + if (menuOrderId != 0) { + final int exitOrderId = this.subMenuOrderIdStack.size() > 1 + ? this.subMenuOrderIdStack.get(this.subMenuOrderIdStack.size() - 2) + : 0; + final IconUI cancelUI = abilityDataUI.getCancelUI(); + this.commandButton(cancelUI.getButtonPositionX(), cancelUI.getButtonPositionY(), cancelUI.getIcon(), 0, + exitOrderId, 0, false, false, true); + } + this.selectedUnit.populateCommandCard(this.war3MapViewer.simulation, this, abilityDataUI, menuOrderId); + } + } + + private int getSubMenuOrderId() { + return this.subMenuOrderIdStack.isEmpty() ? 0 + : this.subMenuOrderIdStack.get(this.subMenuOrderIdStack.size() - 1); } public RenderUnit getSelectedUnit() { @@ -794,6 +841,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.activeCommandUnit = null; this.activeCommand = null; this.activeCommandOrderId = -1; + clearAndRepopulateCommandCard(); } else { final RenderUnit rayPickUnit = this.war3MapViewer.rayPickUnit(screenX, worldScreenY, @@ -813,6 +861,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.activeCommandUnit = null; this.activeCommand = null; this.activeCommandOrderId = -1; + clearAndRepopulateCommandCard(); } } else { @@ -843,6 +892,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.activeCommandUnit = null; this.activeCommand = null; this.activeCommandOrderId = -1; + clearAndRepopulateCommandCard(); } } @@ -957,6 +1007,27 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } } } + else { + if (clickedUIFrame instanceof CommandCardIcon) { + this.mouseDownUIFrame = (CommandCardIcon) clickedUIFrame; + this.mouseDownUIFrame.mouseDown(this.uiViewport); + } + } + return false; + } + + public boolean touchUp(final int screenX, final int screenY, final float worldScreenY, final int button) { + screenCoordsVector.set(screenX, screenY); + this.uiViewport.unproject(screenCoordsVector); + final UIFrame clickedUIFrame = this.rootFrame.touchUp(screenCoordsVector.x, screenCoordsVector.y, button); + if (this.mouseDownUIFrame != null) { + if (clickedUIFrame == this.mouseDownUIFrame) { + this.mouseDownUIFrame.onClick(button); + this.war3MapViewer.getUiSounds().getSound("InterfaceClick").play(this.uiScene.audioContext, 0, 0); + } + this.mouseDownUIFrame.mouseUp(this.uiViewport); + } + this.mouseDownUIFrame = null; return false; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/CommandCardCommandListener.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/CommandCardCommandListener.java index 35591b1..02ef4a4 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/CommandCardCommandListener.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/CommandCardCommandListener.java @@ -2,4 +2,6 @@ package com.etheller.warsmash.viewer5.handlers.w3x.ui.command; public interface CommandCardCommandListener { void startUsingAbility(int abilityHandleId, int orderId); + + void openMenu(int orderId); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/sound/KeyedSounds.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/sound/KeyedSounds.java new file mode 100644 index 0000000..ee8fab3 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/sound/KeyedSounds.java @@ -0,0 +1,29 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.ui.sound; + +import java.util.HashMap; +import java.util.Map; + +import com.etheller.warsmash.datasources.DataSource; +import com.etheller.warsmash.units.DataTable; +import com.etheller.warsmash.viewer5.handlers.w3x.UnitSound; + +public class KeyedSounds { + private final DataTable uiSoundsTable; + private final DataSource dataSource; + private final Map keyToSound; + + public KeyedSounds(final DataTable uiSoundsTable, final DataSource dataSource) { + this.uiSoundsTable = uiSoundsTable; + this.dataSource = dataSource; + this.keyToSound = new HashMap<>(); + } + + public UnitSound getSound(final String key) { + UnitSound sound = this.keyToSound.get(key); + if (sound == null) { + sound = UnitSound.create(this.dataSource, this.uiSoundsTable, key, ""); + this.keyToSound.put(key, sound); + } + return sound; + } +} From b5bb731b293f2e5bda4f43ed267eca2f9789eeaa Mon Sep 17 00:00:00 2001 From: Retera Date: Sun, 1 Nov 2020 20:28:22 -0500 Subject: [PATCH 060/116] Fix UI exit function --- .../handlers/w3x/environment/Terrain.java | 2 +- .../viewer5/handlers/w3x/ui/MeleeUI.java | 1928 ++++++++--------- 2 files changed, 951 insertions(+), 979 deletions(-) diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java index 50d5562..c5b6b40 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java @@ -60,7 +60,7 @@ public class Terrain { private static final Vector3 normalHeap2 = new Vector3(); private static final float[] fourComponentHeap = new float[4]; private static final Matrix4 tempMatrix = new Matrix4(); - private static final boolean WIREFRAME_TERRAIN = true; + private static final boolean WIREFRAME_TERRAIN = false; public ShaderProgram groundShader; public ShaderProgram waterShader; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java index 43cb979..9c7280f 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java @@ -73,982 +73,954 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.StringMsgAbili import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.CommandCardCommandListener; public class MeleeUI implements CUnitStateListener, CommandButtonListener, CommandCardCommandListener { - public static final float DEFAULT_COMMAND_CARD_ICON_WIDTH = 0.039f; - public static final float DEFAULT_COMMAND_CARD_ICON_PRESSED_WIDTH = 0.037f; - private static final int COMMAND_CARD_WIDTH = 4; - private static final int COMMAND_CARD_HEIGHT = 3; - - private static final Vector2 screenCoordsVector = new Vector2(); - private static final Vector3 clickLocationTemp = new Vector3(); - private static final Vector2 clickLocationTemp2 = new Vector2(); - private final DataSource dataSource; - private final ExtendViewport uiViewport; - private final FreeTypeFontGenerator fontGenerator; - private final Scene uiScene; - private final Scene portraitScene; - private final GameCameraManager cameraManager; - private final War3MapViewer war3MapViewer; - private final RootFrameListener rootFrameListener; - private GameUI rootFrame; - private UIFrame consoleUI; - private UIFrame resourceBar; - private StringFrame resourceBarGoldText; - private StringFrame resourceBarLumberText; - private StringFrame resourceBarSupplyText; - private StringFrame resourceBarUpkeepText; - private SpriteFrame timeIndicator; - private UIFrame unitPortrait; - private StringFrame unitLifeText; - private StringFrame unitManaText; - private Portrait portrait; - private final Rectangle tempRect = new Rectangle(); - private final Vector2 projectionTemp1 = new Vector2(); - private final Vector2 projectionTemp2 = new Vector2(); - private UIFrame simpleInfoPanelUnitDetail; - private StringFrame simpleNameValue; - private StringFrame simpleClassValue; - private StringFrame simpleBuildingActionLabel; - private UIFrame attack1Icon; - private TextureFrame attack1IconBackdrop; - private StringFrame attack1InfoPanelIconValue; - private StringFrame attack1InfoPanelIconLevel; - private UIFrame attack2Icon; - private TextureFrame attack2IconBackdrop; - private StringFrame attack2InfoPanelIconValue; - private StringFrame attack2InfoPanelIconLevel; - private UIFrame armorIcon; - private TextureFrame armorIconBackdrop; - private StringFrame armorInfoPanelIconValue; - private StringFrame armorInfoPanelIconLevel; - private InfoPanelIconBackdrops damageBackdrops; - private InfoPanelIconBackdrops defenseBackdrops; - - private final CommandCardIcon[][] commandCard = new CommandCardIcon[COMMAND_CARD_HEIGHT][COMMAND_CARD_WIDTH]; - - private RenderUnit selectedUnit; - private final List subMenuOrderIdStack = new ArrayList<>(); - - // TODO remove this & replace with FDF - private final Texture activeButtonTexture; - private UIFrame inventoryCover; - private SpriteFrame cursorFrame; - private MeleeUIMinimap meleeUIMinimap; - private final CPlayerUnitOrderListener unitOrderListener; - private StringFrame errorMessageFrame; - - private CAbilityView activeCommand; - private int activeCommandOrderId; - private RenderUnit activeCommandUnit; - - private int selectedSoundCount = 0; - private final ActiveCommandUnitTargetFilter activeCommandUnitTargetFilter; - - // TODO these corrections are used for old hardcoded UI stuff, we should - // probably remove them later - private final float widthRatioCorrection; - private final float heightRatioCorrection; - private CommandCardIcon mouseDownUIFrame; - - public MeleeUI(final DataSource dataSource, final ExtendViewport uiViewport, - final FreeTypeFontGenerator fontGenerator, final Scene uiScene, final Scene portraitScene, - final CameraPreset[] cameraPresets, final CameraRates cameraRates, final War3MapViewer war3MapViewer, - final RootFrameListener rootFrameListener, final CPlayerUnitOrderListener unitOrderListener) { - this.dataSource = dataSource; - this.uiViewport = uiViewport; - this.fontGenerator = fontGenerator; - this.uiScene = uiScene; - this.portraitScene = portraitScene; - this.war3MapViewer = war3MapViewer; - this.rootFrameListener = rootFrameListener; - this.unitOrderListener = unitOrderListener; - - this.cameraManager = new GameCameraManager(cameraPresets, cameraRates); - - this.cameraManager.setupCamera(war3MapViewer.worldScene); - if (this.war3MapViewer.startLocations[0] != null) { - this.cameraManager.target.x = this.war3MapViewer.startLocations[0].x; - this.cameraManager.target.y = this.war3MapViewer.startLocations[0].y; - } - - this.activeButtonTexture = ImageUtils.getBLPTexture(war3MapViewer.mapMpq, - "UI\\Widgets\\Console\\Human\\CommandButton\\human-activebutton.blp"); - this.activeCommandUnitTargetFilter = new ActiveCommandUnitTargetFilter(); - this.widthRatioCorrection = this.uiViewport.getMinWorldWidth() / 1600f; - this.heightRatioCorrection = this.uiViewport.getMinWorldHeight() / 1200f; - - } - - private MeleeUIMinimap createMinimap(final War3MapViewer war3MapViewer) { - final Rectangle minimapDisplayArea = new Rectangle(18.75f * this.widthRatioCorrection, - 13.75f * this.heightRatioCorrection, 278.75f * this.widthRatioCorrection, - 276.25f * this.heightRatioCorrection); - Texture minimapTexture = null; - if (war3MapViewer.dataSource.has("war3mapMap.tga")) { - try { - minimapTexture = ImageUtils.getTextureNoColorCorrection(TgaFile.readTGA("war3mapMap.tga", - war3MapViewer.dataSource.getResourceAsStream("war3mapMap.tga"))); - } - catch (final IOException e) { - System.err.println("Could not load minimap TGA file"); - e.printStackTrace(); - } - } - else if (war3MapViewer.dataSource.has("war3mapMap.blp")) { - try { - minimapTexture = ImageUtils - .getTexture(ImageIO.read(war3MapViewer.dataSource.getResourceAsStream("war3mapMap.blp"))); - } - catch (final IOException e) { - System.err.println("Could not load minimap BLP file"); - e.printStackTrace(); - } - } - final Texture[] teamColors = new Texture[WarsmashConstants.MAX_PLAYERS]; - for (int i = 0; i < teamColors.length; i++) { - teamColors[i] = ImageUtils.getBLPTexture(war3MapViewer.dataSource, - "ReplaceableTextures\\" + ReplaceableIds.getPathString(1) + ReplaceableIds.getIdString(i) + ".blp"); - } - final Rectangle playableMapArea = war3MapViewer.terrain.getPlayableMapArea(); - return new MeleeUIMinimap(minimapDisplayArea, playableMapArea, minimapTexture, teamColors); - } - - /** - * Called "main" because this was originally written in JASS so that maps could - * override it, and I may convert it back to the JASS at some point. - */ - public void main() { - // ================================= - // Load skins and templates - // ================================= - this.rootFrame = new GameUI(this.dataSource, GameUI.loadSkin(this.dataSource, 0), this.uiViewport, - this.fontGenerator, this.uiScene, this.war3MapViewer); - this.rootFrameListener.onCreate(this.rootFrame); - try { - this.rootFrame.loadTOCFile("UI\\FrameDef\\FrameDef.toc"); - } - catch (final IOException exc) { - throw new IllegalStateException("Unable to load FrameDef.toc", exc); - } - try { - this.rootFrame.loadTOCFile("UI\\FrameDef\\SmashFrameDef.toc"); - } - catch (final IOException exc) { - throw new IllegalStateException("Unable to load SmashFrameDef.toc", exc); - } - this.damageBackdrops = new InfoPanelIconBackdrops(CAttackType.values(), this.rootFrame, "Damage", "Neutral"); - this.defenseBackdrops = new InfoPanelIconBackdrops(CDefenseType.values(), this.rootFrame, "Armor", "Neutral"); - - // ================================= - // Load major UI components - // ================================= - // Console UI is the background with the racial theme - this.consoleUI = this.rootFrame.createSimpleFrame("ConsoleUI", this.rootFrame, 0); - this.consoleUI.setSetAllPoints(true); - - // Resource bar is a 3 part bar with Gold, Lumber, and Food. - // Its template does not specify where to put it, so we must - // put it in the "TOPRIGHT" corner. - this.resourceBar = this.rootFrame.createSimpleFrame("ResourceBarFrame", this.consoleUI, 0); - this.resourceBar.addSetPoint(new SetPoint(FramePoint.TOPRIGHT, this.consoleUI, FramePoint.TOPRIGHT, 0, 0)); - this.resourceBarGoldText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarGoldText", 0); - this.resourceBarGoldText.setText("500"); - this.resourceBarLumberText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarLumberText", 0); - this.resourceBarLumberText.setText("150"); - this.resourceBarSupplyText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarSupplyText", 0); - this.resourceBarSupplyText.setText("12/100"); - this.resourceBarUpkeepText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarUpkeepText", 0); - this.resourceBarUpkeepText.setText("No Upkeep"); - this.resourceBarUpkeepText.setColor(Color.GREEN); - - // Create the Time Indicator (clock) - this.timeIndicator = (SpriteFrame) this.rootFrame.createFrame("TimeOfDayIndicator", this.rootFrame, 0, 0); - this.timeIndicator.setSequence(0); // play the stand - this.timeIndicator.setAnimationSpeed(0.0f); // do not advance automatically - - // Create the unit portrait stuff - this.portrait = new Portrait(this.war3MapViewer, this.portraitScene); - positionPortrait(); - this.unitPortrait = this.rootFrame.createSimpleFrame("UnitPortrait", this.consoleUI, 0); - this.unitLifeText = (StringFrame) this.rootFrame.getFrameByName("UnitPortraitHitPointText", 0); - this.unitManaText = (StringFrame) this.rootFrame.getFrameByName("UnitPortraitManaPointText", 0); - - this.simpleInfoPanelUnitDetail = this.rootFrame.createSimpleFrame("SimpleInfoPanelUnitDetail", this.consoleUI, - 0); - this.simpleInfoPanelUnitDetail - .addAnchor(new AnchorDefinition(FramePoint.BOTTOM, 0, GameUI.convertY(this.uiViewport, 0.0f))); - this.simpleInfoPanelUnitDetail.setWidth(GameUI.convertY(this.uiViewport, 0.180f)); - this.simpleInfoPanelUnitDetail.setHeight(GameUI.convertY(this.uiViewport, 0.105f)); - this.simpleNameValue = (StringFrame) this.rootFrame.getFrameByName("SimpleNameValue", 0); - this.simpleClassValue = (StringFrame) this.rootFrame.getFrameByName("SimpleClassValue", 0); - this.simpleBuildingActionLabel = (StringFrame) this.rootFrame.getFrameByName("SimpleBuildingActionLabel", 0); - - this.attack1Icon = this.rootFrame.createSimpleFrame("SimpleInfoPanelIconDamage", this.simpleInfoPanelUnitDetail, - 0); - this.attack1Icon.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, - FramePoint.TOPLEFT, 0, GameUI.convertY(this.uiViewport, -0.030125f))); - this.attack1IconBackdrop = (TextureFrame) this.rootFrame.getFrameByName("InfoPanelIconBackdrop", 0); - this.attack1InfoPanelIconValue = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconValue", 0); - this.attack1InfoPanelIconLevel = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconLevel", 0); - - this.attack2Icon = this.rootFrame.createSimpleFrame("SimpleInfoPanelIconDamage", this.simpleInfoPanelUnitDetail, - 1); - this.attack2Icon - .addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, FramePoint.TOPLEFT, - GameUI.convertX(this.uiViewport, 0.1f), GameUI.convertY(this.uiViewport, -0.030125f))); - this.attack2IconBackdrop = (TextureFrame) this.rootFrame.getFrameByName("InfoPanelIconBackdrop", 1); - this.attack2InfoPanelIconValue = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconValue", 1); - this.attack2InfoPanelIconLevel = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconLevel", 1); - - this.armorIcon = this.rootFrame.createSimpleFrame("SimpleInfoPanelIconArmor", this.simpleInfoPanelUnitDetail, - 1); - this.armorIcon.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, FramePoint.TOPLEFT, - GameUI.convertX(this.uiViewport, 0f), GameUI.convertY(this.uiViewport, -0.06025f))); - this.armorIconBackdrop = (TextureFrame) this.rootFrame.getFrameByName("InfoPanelIconBackdrop", 0); - this.armorInfoPanelIconValue = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconValue", 0); - this.armorInfoPanelIconLevel = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconLevel", 0); - - this.inventoryCover = this.rootFrame.createSimpleFrame("SmashConsoleInventoryCover", this.rootFrame, 0); - - this.errorMessageFrame = this.rootFrame.createStringFrame("SmashErrorMessageFrame", this.consoleUI, - new Color(0xFFFFCC00), TextJustify.LEFT, TextJustify.MIDDLE, 0.014f); - this.errorMessageFrame.addAnchor(new AnchorDefinition(FramePoint.BOTTOMLEFT, - GameUI.convertX(this.uiViewport, 0.275f), GameUI.convertY(this.uiViewport, 0.275f))); - this.errorMessageFrame.setWidth(GameUI.convertX(this.uiViewport, 0.25f)); - - int commandButtonIndex = 0; - for (int j = 0; j < COMMAND_CARD_HEIGHT; j++) { - for (int i = 0; i < COMMAND_CARD_WIDTH; i++) { - final CommandCardIcon commandCardIcon = new CommandCardIcon("SmashCommandButton_" + commandButtonIndex, - this.rootFrame, this); - this.rootFrame.add(commandCardIcon); - final TextureFrame iconFrame = this.rootFrame.createTextureFrame( - "SmashCommandButton_" + (commandButtonIndex) + "_Icon", this.rootFrame, false, null); - final TextureFrame activeHighlightFrame = this.rootFrame.createTextureFrame( - "SmashCommandButton_" + (commandButtonIndex) + "_ActiveHighlight", this.rootFrame, true, null, - FilterMode.ADDALPHA); - final SpriteFrame cooldownFrame = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", - "SmashCommandButton_" + (commandButtonIndex) + "_Cooldown", this.rootFrame, "", 0); - final SpriteFrame autocastFrame = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", - "SmashCommandButton_" + (commandButtonIndex) + "_Autocast", this.rootFrame, "", 0); - commandCardIcon.addAnchor(new AnchorDefinition(FramePoint.BOTTOMLEFT, - GameUI.convertX(this.uiViewport, 0.6175f + (0.0434f * i)), - GameUI.convertY(this.uiViewport, 0.095f - (0.044f * j)))); - commandCardIcon.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); - commandCardIcon.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); - iconFrame.addSetPoint(new SetPoint(FramePoint.CENTER, commandCardIcon, FramePoint.CENTER, 0, 0)); - iconFrame.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); - iconFrame.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); - iconFrame.setTexture(ImageUtils.DEFAULT_ICON_PATH, this.rootFrame); - activeHighlightFrame - .addSetPoint(new SetPoint(FramePoint.CENTER, commandCardIcon, FramePoint.CENTER, 0, 0)); - activeHighlightFrame.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); - activeHighlightFrame.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); - activeHighlightFrame.setTexture("CommandButtonActiveHighlight", this.rootFrame); - cooldownFrame.addSetPoint(new SetPoint(FramePoint.CENTER, commandCardIcon, FramePoint.CENTER, 0, 0)); - this.rootFrame.setSpriteFrameModel(cooldownFrame, this.rootFrame.getSkinField("CommandButtonCooldown")); - cooldownFrame.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); - cooldownFrame.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); - autocastFrame.addSetPoint(new SetPoint(FramePoint.CENTER, commandCardIcon, FramePoint.CENTER, 0, 0)); - this.rootFrame.setSpriteFrameModel(autocastFrame, this.rootFrame.getSkinField("CommandButtonAutocast")); - autocastFrame.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); - autocastFrame.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); - commandCardIcon.set(iconFrame, activeHighlightFrame, cooldownFrame, autocastFrame); - this.commandCard[j][i] = commandCardIcon; - commandCardIcon.setCommandButton(null); - commandButtonIndex++; - } - } - - this.cursorFrame = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", "SmashCursorFrame", this.rootFrame, - "", 0); - this.rootFrame.setSpriteFrameModel(this.cursorFrame, this.rootFrame.getSkinField("Cursor")); - this.cursorFrame.setSequence("Normal"); - this.cursorFrame.setZDepth(1.0f); - Gdx.input.setCursorCatched(true); - - this.meleeUIMinimap = createMinimap(this.war3MapViewer); - - this.rootFrame.positionBounds(this.uiViewport); - selectUnit(null); - } - - @Override - public void startUsingAbility(final int abilityHandleId, final int orderId) { - // TODO not O(N) - CAbilityView abilityToUse = null; - for (final CAbility ability : this.selectedUnit.getSimulationUnit().getAbilities()) { - if (ability.getHandleId() == abilityHandleId) { - abilityToUse = ability; - break; - } - } - if (abilityToUse != null) { - final StringMsgAbilityActivationReceiver stringMsgActivationReceiver = StringMsgAbilityActivationReceiver - .getInstance().reset(); - abilityToUse.checkCanUse(this.war3MapViewer.simulation, this.selectedUnit.getSimulationUnit(), orderId, - stringMsgActivationReceiver); - if (!stringMsgActivationReceiver.isUseOk()) { - showCommandError(stringMsgActivationReceiver.getMessage()); - } - else { - final BooleanAbilityTargetCheckReceiver noTargetReceiver = BooleanAbilityTargetCheckReceiver - .getInstance().reset(); - abilityToUse.checkCanTargetNoTarget(this.war3MapViewer.simulation, - this.selectedUnit.getSimulationUnit(), orderId, noTargetReceiver); - if (noTargetReceiver.isTargetable()) { - this.unitOrderListener.issueImmediateOrder(this.selectedUnit.getSimulationUnit().getHandleId(), - abilityHandleId, orderId, isShiftDown()); - } - else { - this.activeCommand = abilityToUse; - this.activeCommandOrderId = orderId; - this.activeCommandUnit = this.selectedUnit; - clearAndRepopulateCommandCard(); - } - } - } - else { - this.unitOrderListener.issueImmediateOrder(this.selectedUnit.getSimulationUnit().getHandleId(), - abilityHandleId, orderId, isShiftDown()); - } - } - - @Override - public void openMenu(final int orderId) { - this.subMenuOrderIdStack.add(orderId); - clearAndRepopulateCommandCard(); - } - - public void showCommandError(final String message) { - this.errorMessageFrame.setText(message); - } - - public void update(final float deltaTime) { - this.portrait.update(); - - int mouseX = Gdx.input.getX(); - int mouseY = Gdx.input.getY(); - final int minX = this.uiViewport.getScreenX(); - final int maxX = minX + this.uiViewport.getScreenWidth(); - final int minY = this.uiViewport.getScreenY(); - final int maxY = minY + this.uiViewport.getScreenHeight(); - final boolean left = mouseX <= (minX + 3); - final boolean right = mouseX >= (maxX - 3); - final boolean up = mouseY <= (minY + 3); - final boolean down = mouseY >= (maxY - 3); - this.cameraManager.applyVelocity(deltaTime, up, down, left, right); - - mouseX = Math.max(minX, Math.min(maxX, mouseX)); - mouseY = Math.max(minY, Math.min(maxY, mouseY)); - if (Gdx.input.isCursorCatched()) { - Gdx.input.setCursorPosition(mouseX, mouseY); - } - - screenCoordsVector.set(mouseX, mouseY); - this.uiViewport.unproject(screenCoordsVector); - this.cursorFrame.setFramePointX(FramePoint.LEFT, screenCoordsVector.x); - this.cursorFrame.setFramePointY(FramePoint.BOTTOM, screenCoordsVector.y); - - if (this.activeCommand != null) { - this.cursorFrame.setSequence("Target"); - } - else if (down) { - if (left) { - this.cursorFrame.setSequence("Scroll Down Left"); - } - else if (right) { - this.cursorFrame.setSequence("Scroll Down Right"); - } - else { - this.cursorFrame.setSequence("Scroll Down"); - } - } - else if (up) { - if (left) { - this.cursorFrame.setSequence("Scroll Up Left"); - } - else if (right) { - this.cursorFrame.setSequence("Scroll Up Right"); - } - else { - this.cursorFrame.setSequence("Scroll Up"); - } - } - else if (left) { - this.cursorFrame.setSequence("Scroll Left"); - } - else if (right) { - this.cursorFrame.setSequence("Scroll Right"); - } - else { - this.cursorFrame.setSequence("Normal"); - } - final float groundHeight = Math.max( - this.war3MapViewer.terrain.getGroundHeight(this.cameraManager.target.x, this.cameraManager.target.y), - this.war3MapViewer.terrain.getWaterHeight(this.cameraManager.target.x, this.cameraManager.target.y)); - this.cameraManager.updateTargetZ(groundHeight); - this.cameraManager.updateCamera(); - } - - public void render(final SpriteBatch batch, final BitmapFont font20, final GlyphLayout glyphLayout) { - this.rootFrame.render(batch, font20, glyphLayout); - if (this.selectedUnit != null) { - font20.setColor(Color.WHITE); - - } - - this.meleeUIMinimap.render(batch, this.war3MapViewer.units); - this.timeIndicator.setFrameByRatio(this.war3MapViewer.simulation.getGameTimeOfDay() - / this.war3MapViewer.simulation.getGameplayConstants().getGameDayHours()); - } - - public void portraitTalk() { - this.portrait.talk(); - } - - private final class ActiveCommandUnitTargetFilter implements CUnitFilterFunction { - @Override - public boolean call(final CUnit unit) { - final BooleanAbilityTargetCheckReceiver targetReceiver = BooleanAbilityTargetCheckReceiver - .getInstance(); - MeleeUI.this.activeCommand.checkCanTarget(MeleeUI.this.war3MapViewer.simulation, - MeleeUI.this.activeCommandUnit.getSimulationUnit(), MeleeUI.this.activeCommandOrderId, unit, - targetReceiver); - return targetReceiver.isTargetable(); - } - } - - private static final class Portrait { - private MdxComplexInstance modelInstance; - private final PortraitCameraManager portraitCameraManager; - private final Scene portraitScene; - - public Portrait(final War3MapViewer war3MapViewer, final Scene portraitScene) { - this.portraitScene = portraitScene; - this.portraitCameraManager = new PortraitCameraManager(); - this.portraitCameraManager.setupCamera(this.portraitScene); - this.portraitScene.camera.viewport(new Rectangle(100, 0, 6400, 48)); - } - - public void update() { - this.portraitCameraManager.updateCamera(); - if ((this.modelInstance != null) - && (this.modelInstance.sequenceEnded || (this.modelInstance.sequence == -1))) { - SequenceUtils.randomSequence(this.modelInstance, PrimaryTag.PORTRAIT, SequenceUtils.EMPTY, true); - } - } - - public void talk() { - SequenceUtils.randomSequence(this.modelInstance, PrimaryTag.PORTRAIT, SequenceUtils.TALK, true); - } - - public void setSelectedUnit(final RenderUnit unit) { - if (unit == null) { - if (this.modelInstance != null) { - this.portraitScene.removeInstance(this.modelInstance); - } - this.modelInstance = null; - this.portraitCameraManager.setModelInstance(null, null); - } - else { - final MdxModel portraitModel = unit.portraitModel; - if (portraitModel != null) { - if (this.modelInstance != null) { - this.portraitScene.removeInstance(this.modelInstance); - } - this.modelInstance = (MdxComplexInstance) portraitModel.addInstance(); - this.portraitCameraManager.setModelInstance(this.modelInstance, portraitModel); - this.modelInstance.setSequenceLoopMode(SequenceLoopMode.NEVER_LOOP); - this.modelInstance.setScene(this.portraitScene); - this.modelInstance.setVertexColor(unit.instance.vertexColor); - this.modelInstance.setTeamColor(unit.playerIndex); - } - } - } - } - - public void selectUnit(RenderUnit unit) { - this.subMenuOrderIdStack.clear(); - if ((unit != null) && unit.getSimulationUnit().isDead()) { - unit = null; - } - if (this.selectedUnit != null) { - this.selectedUnit.getSimulationUnit().removeStateListener(this); - } - this.portrait.setSelectedUnit(unit); - this.selectedUnit = unit; - clearCommandCard(); - if (unit == null) { - this.simpleNameValue.setText(""); - this.unitLifeText.setText(""); - this.unitManaText.setText(""); - this.simpleClassValue.setText(""); - this.simpleBuildingActionLabel.setText(""); - this.attack1Icon.setVisible(false); - this.attack2Icon.setVisible(false); - this.attack1InfoPanelIconLevel.setText(""); - this.attack2InfoPanelIconLevel.setText(""); - this.armorIcon.setVisible(false); - this.armorInfoPanelIconLevel.setText(""); - } - else { - unit.getSimulationUnit().addStateListener(this); - this.simpleNameValue.setText(unit.getSimulationUnit().getUnitType().getName()); - String classText = null; - for (final CUnitClassification classification : unit.getSimulationUnit().getClassifications()) { - if ((classification == CUnitClassification.MECHANICAL) - && unit.getSimulationUnit().getUnitType().isBuilding()) { - // buildings dont display MECHANICAL - continue; - } - if (classification.getDisplayName() != null) { - classText = classification.getDisplayName(); - } - } - if (classText != null) { - this.simpleClassValue.setText(classText); - } - else { - this.simpleClassValue.setText(""); - } - this.unitLifeText.setText(FastNumberFormat.formatWholeNumber(unit.getSimulationUnit().getLife()) + " / " - + unit.getSimulationUnit().getMaximumLife()); - final int maximumMana = unit.getSimulationUnit().getMaximumMana(); - if (maximumMana > 0) { - this.unitManaText.setText( - FastNumberFormat.formatWholeNumber(unit.getSimulationUnit().getMana()) + " / " + maximumMana); - } - else { - this.unitManaText.setText(""); - } - this.simpleBuildingActionLabel.setText(""); - - final boolean anyAttacks = unit.getSimulationUnit().getUnitType().getAttacks().size() > 0; - final UIFrame localArmorIcon = this.armorIcon; - final TextureFrame localArmorIconBackdrop = this.armorIconBackdrop; - final StringFrame localArmorInfoPanelIconValue = this.armorInfoPanelIconValue; - if (anyAttacks) { - final CUnitAttack attackOne = unit.getSimulationUnit().getUnitType().getAttacks().get(0); - this.attack1Icon.setVisible(attackOne.isShowUI()); - this.attack1IconBackdrop.setTexture(this.damageBackdrops.getTexture(attackOne.getAttackType())); - this.attack1InfoPanelIconValue.setText(attackOne.getMinDamage() + " - " + attackOne.getMaxDamage()); - if (unit.getSimulationUnit().getUnitType().getAttacks().size() > 1) { - final CUnitAttack attackTwo = unit.getSimulationUnit().getUnitType().getAttacks().get(1); - this.attack2Icon.setVisible(attackTwo.isShowUI()); - this.attack2IconBackdrop.setTexture(this.damageBackdrops.getTexture(attackTwo.getAttackType())); - this.attack2InfoPanelIconValue.setText(attackTwo.getMinDamage() + " - " + attackTwo.getMaxDamage()); - } - else { - this.attack2Icon.setVisible(false); - } - - this.armorIcon.addSetPoint( - new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, FramePoint.TOPLEFT, - GameUI.convertX(this.uiViewport, 0f), GameUI.convertY(this.uiViewport, -0.06025f))); - this.armorIcon.positionBounds(this.uiViewport); - } - else { - this.attack1Icon.setVisible(false); - this.attack2Icon.setVisible(false); - - this.armorIcon.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, - FramePoint.TOPLEFT, 0, GameUI.convertY(this.uiViewport, -0.030125f))); - this.armorIcon.positionBounds(this.uiViewport); - } - - localArmorIcon.setVisible(true); - final Texture defenseTexture = this.defenseBackdrops - .getTexture(unit.getSimulationUnit().getUnitType().getDefenseType()); - if (defenseTexture == null) { - throw new RuntimeException( - unit.getSimulationUnit().getUnitType().getDefenseType() + " can't find texture!"); - } - localArmorIconBackdrop.setTexture(defenseTexture); - localArmorInfoPanelIconValue.setText(Integer.toString(unit.getSimulationUnit().getDefense())); - unit.populateCommandCard(this.war3MapViewer.simulation, this, this.war3MapViewer.getAbilityDataUI(), - getSubMenuOrderId()); - } - } - - private void clearCommandCard() { - for (int j = 0; j < COMMAND_CARD_HEIGHT; j++) { - for (int i = 0; i < COMMAND_CARD_WIDTH; i++) { - this.commandCard[j][i].setCommandButton(null); - } - } - } - - @Override - public void commandButton(final int buttonPositionX, final int buttonPositionY, final Texture icon, - final int abilityHandleId, final int orderId, final int autoCastId, final boolean active, - final boolean autoCastActive, final boolean menuButton) { - final int x = Math.max(0, Math.min(COMMAND_CARD_WIDTH - 1, buttonPositionX)); - final int y = Math.max(0, Math.min(COMMAND_CARD_HEIGHT - 1, buttonPositionY)); - this.commandCard[y][x].setCommandButtonData(icon, abilityHandleId, orderId, autoCastId, active, autoCastActive, - menuButton); - } - - public void resize(final Rectangle viewport) { - this.cameraManager.resize(viewport); - positionPortrait(); - } - - public void positionPortrait() { - this.projectionTemp1.x = 422 * this.widthRatioCorrection; - this.projectionTemp1.y = 57 * this.heightRatioCorrection; - this.projectionTemp2.x = (422 + 167) * this.widthRatioCorrection; - this.projectionTemp2.y = (57 + 170) * this.heightRatioCorrection; - this.uiViewport.project(this.projectionTemp1); - this.uiViewport.project(this.projectionTemp2); - - this.tempRect.x = this.projectionTemp1.x + this.uiViewport.getScreenX(); - this.tempRect.y = this.projectionTemp1.y + this.uiViewport.getScreenY(); - this.tempRect.width = this.projectionTemp2.x - this.projectionTemp1.x; - this.tempRect.height = this.projectionTemp2.y - this.projectionTemp1.y; - this.portrait.portraitScene.camera.viewport(this.tempRect); - } - - private static final class InfoPanelIconBackdrops { - private final Texture[] damageBackdropTextures; - - public InfoPanelIconBackdrops(final CodeKeyType[] attackTypes, final GameUI gameUI, final String prefix, - final String suffix) { - this.damageBackdropTextures = new Texture[attackTypes.length]; - for (int index = 0; index < attackTypes.length; index++) { - final CodeKeyType attackType = attackTypes[index]; - String skinLookupKey = "InfoPanelIcon" + prefix + attackType.getCodeKey() + suffix; - final Texture suffixTexture = gameUI.loadTexture(gameUI.getSkinField(skinLookupKey)); - if (suffixTexture != null) { - this.damageBackdropTextures[index] = suffixTexture; - } - else { - skinLookupKey = "InfoPanelIcon" + prefix + attackType.getCodeKey(); - this.damageBackdropTextures[index] = gameUI.loadTexture(gameUI.getSkinField(skinLookupKey)); - } - } - } - - public Texture getTexture(final CodeKeyType attackType) { - if (attackType != null) { - final int ordinal = attackType.ordinal(); - if ((ordinal >= 0) && (ordinal < this.damageBackdropTextures.length)) { - return this.damageBackdropTextures[ordinal]; - } - } - return this.damageBackdropTextures[0]; - } - - private static String getSuffix(final CAttackType attackType) { - switch (attackType) { - case CHAOS: - return "Chaos"; - case HERO: - return "Hero"; - case MAGIC: - return "Magic"; - case NORMAL: - return "Normal"; - case PIERCE: - return "Pierce"; - case SIEGE: - return "Siege"; - case SPELLS: - return "Magic"; - case UNKNOWN: - return "Unknown"; - default: - throw new IllegalArgumentException("Unknown attack type: " + attackType); - } - - } - } - - @Override - public void lifeChanged() { - if (this.selectedUnit.getSimulationUnit().isDead()) { - selectUnit(null); - } - else { - this.unitLifeText - .setText(FastNumberFormat.formatWholeNumber(this.selectedUnit.getSimulationUnit().getLife()) + " / " - + this.selectedUnit.getSimulationUnit().getMaximumLife()); - } - } - - @Override - public void ordersChanged(final int abilityHandleId, final int orderId) { - clearAndRepopulateCommandCard(); - } - - private void clearAndRepopulateCommandCard() { - clearCommandCard(); - final AbilityDataUI abilityDataUI = this.war3MapViewer.getAbilityDataUI(); - final int menuOrderId = getSubMenuOrderId(); - if (this.activeCommand != null) { - final IconUI cancelUI = abilityDataUI.getCancelUI(); - this.commandButton(cancelUI.getButtonPositionX(), cancelUI.getButtonPositionY(), cancelUI.getIcon(), 0, - menuOrderId, 0, false, false, true); - } - else { - if (menuOrderId != 0) { - final int exitOrderId = this.subMenuOrderIdStack.size() > 1 - ? this.subMenuOrderIdStack.get(this.subMenuOrderIdStack.size() - 2) - : 0; - final IconUI cancelUI = abilityDataUI.getCancelUI(); - this.commandButton(cancelUI.getButtonPositionX(), cancelUI.getButtonPositionY(), cancelUI.getIcon(), 0, - exitOrderId, 0, false, false, true); - } - this.selectedUnit.populateCommandCard(this.war3MapViewer.simulation, this, abilityDataUI, menuOrderId); - } - } - - private int getSubMenuOrderId() { - return this.subMenuOrderIdStack.isEmpty() ? 0 - : this.subMenuOrderIdStack.get(this.subMenuOrderIdStack.size() - 1); - } - - public RenderUnit getSelectedUnit() { - return this.selectedUnit; - } - - public boolean keyDown(final int keycode) { - return this.cameraManager.keyDown(keycode); - } - - public boolean keyUp(final int keycode) { - return this.cameraManager.keyUp(keycode); - } - - public void scrolled(final int amount) { - this.cameraManager.scrolled(amount); - } - - public boolean touchDown(final int screenX, final int screenY, final float worldScreenY, final int button) { - screenCoordsVector.set(screenX, screenY); - this.uiViewport.unproject(screenCoordsVector); - if (this.meleeUIMinimap.containsMouse(screenCoordsVector.x, screenCoordsVector.y)) { - final Vector2 worldPoint = this.meleeUIMinimap.getWorldPointFromScreen(screenCoordsVector.x, - screenCoordsVector.y); - this.cameraManager.target.x = worldPoint.x; - this.cameraManager.target.y = worldPoint.y; - return true; - } - final UIFrame clickedUIFrame = this.rootFrame.touchDown(screenCoordsVector.x, screenCoordsVector.y, button); - if (clickedUIFrame == null) { - // try to interact with world - if (this.activeCommand != null) { - if (button == Input.Buttons.RIGHT) { - this.activeCommandUnit = null; - this.activeCommand = null; - this.activeCommandOrderId = -1; - clearAndRepopulateCommandCard(); - } - else { - final RenderUnit rayPickUnit = this.war3MapViewer.rayPickUnit(screenX, worldScreenY, - this.activeCommandUnitTargetFilter); - final boolean shiftDown = isShiftDown(); - if (rayPickUnit != null) { - this.unitOrderListener.issueTargetOrder( - this.activeCommandUnit.getSimulationUnit().getHandleId(), - this.activeCommand.getHandleId(), this.activeCommandOrderId, - rayPickUnit.getSimulationUnit().getHandleId(), shiftDown); - if (getSelectedUnit().soundset.yesAttack - .playUnitResponse(this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { - portraitTalk(); - } - this.selectedSoundCount = 0; - if (!shiftDown) { - this.activeCommandUnit = null; - this.activeCommand = null; - this.activeCommandOrderId = -1; - clearAndRepopulateCommandCard(); - } - } - else { - this.war3MapViewer.getClickLocation(clickLocationTemp, screenX, (int) worldScreenY); - clickLocationTemp2.set(clickLocationTemp.x, clickLocationTemp.y); - - this.activeCommand.checkCanTarget(this.war3MapViewer.simulation, - this.activeCommandUnit.getSimulationUnit(), this.activeCommandOrderId, - clickLocationTemp2, PointAbilityTargetCheckReceiver.INSTANCE); - final Vector2 target = PointAbilityTargetCheckReceiver.INSTANCE.getTarget(); - if (target != null) { - if (this.activeCommand instanceof CAbilityAttack) { - this.war3MapViewer.showConfirmation(clickLocationTemp, 1, 0, 0); - } - else { - this.war3MapViewer.showConfirmation(clickLocationTemp, 0, 1, 0); - } - this.unitOrderListener.issuePointOrder( - this.activeCommandUnit.getSimulationUnit().getHandleId(), - this.activeCommand.getHandleId(), this.activeCommandOrderId, clickLocationTemp2.x, - clickLocationTemp2.y, shiftDown); - if (getSelectedUnit().soundset.yes - .playUnitResponse(this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { - portraitTalk(); - } - this.selectedSoundCount = 0; - if (!shiftDown) { - this.activeCommandUnit = null; - this.activeCommand = null; - this.activeCommandOrderId = -1; - clearAndRepopulateCommandCard(); - } - - } - } - } - } - else { - if (button == Input.Buttons.RIGHT) { - if (getSelectedUnit() != null) { - final RenderUnit rayPickUnit = this.war3MapViewer.rayPickUnit(screenX, worldScreenY); - if ((rayPickUnit != null) && !rayPickUnit.getSimulationUnit().isDead()) { - boolean ordered = false; - for (final RenderUnit unit : this.war3MapViewer.selected) { - for (final CAbility ability : unit.getSimulationUnit().getAbilities()) { - ability.checkCanTarget(this.war3MapViewer.simulation, unit.getSimulationUnit(), - OrderIds.smart, rayPickUnit.getSimulationUnit(), - CWidgetAbilityTargetCheckReceiver.INSTANCE); - final CWidget targetWidget = CWidgetAbilityTargetCheckReceiver.INSTANCE.getTarget(); - if (targetWidget != null) { - this.unitOrderListener.issueTargetOrder(unit.getSimulationUnit().getHandleId(), - ability.getHandleId(), OrderIds.smart, targetWidget.getHandleId(), - isShiftDown()); - ordered = true; - } - } - - } - if (ordered) { - if (getSelectedUnit().soundset.yesAttack.playUnitResponse( - this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { - portraitTalk(); - } - this.selectedSoundCount = 0; - } - } - else { - this.war3MapViewer.getClickLocation(clickLocationTemp, screenX, (int) worldScreenY); - this.war3MapViewer.showConfirmation(clickLocationTemp, 0, 1, 0); - clickLocationTemp2.set(clickLocationTemp.x, clickLocationTemp.y); - - boolean ordered = false; - for (final RenderUnit unit : this.war3MapViewer.selected) { - for (final CAbility ability : unit.getSimulationUnit().getAbilities()) { - ability.checkCanUse(this.war3MapViewer.simulation, unit.getSimulationUnit(), - OrderIds.smart, BooleanAbilityActivationReceiver.INSTANCE); - if (BooleanAbilityActivationReceiver.INSTANCE.isOk()) { - ability.checkCanTarget(this.war3MapViewer.simulation, unit.getSimulationUnit(), - OrderIds.smart, clickLocationTemp2, - PointAbilityTargetCheckReceiver.INSTANCE); - final Vector2 target = PointAbilityTargetCheckReceiver.INSTANCE.getTarget(); - if (target != null) { - this.unitOrderListener.issuePointOrder( - unit.getSimulationUnit().getHandleId(), ability.getHandleId(), - OrderIds.smart, clickLocationTemp2.x, clickLocationTemp2.y, - isShiftDown()); - ordered = true; - } - } - } - - } - - if (ordered) { - if (getSelectedUnit().soundset.yes.playUnitResponse( - this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { - portraitTalk(); - } - this.selectedSoundCount = 0; - } - } - } - } - else { - final List selectedUnits = this.war3MapViewer.selectUnit(screenX, worldScreenY, false); - if (!selectedUnits.isEmpty()) { - final RenderUnit unit = selectedUnits.get(0); - final boolean selectionChanged = getSelectedUnit() != unit; - boolean playedNewSound = false; - if (selectionChanged) { - this.selectedSoundCount = 0; - } - if (unit.soundset != null) { - UnitSound ackSoundToPlay = unit.soundset.what; - final int pissedSoundCount = unit.soundset.pissed.getSoundCount(); - int soundIndex; - if ((this.selectedSoundCount >= 3) && (pissedSoundCount > 0)) { - soundIndex = this.selectedSoundCount - 3; - ackSoundToPlay = unit.soundset.pissed; - } - else { - soundIndex = (int) (Math.random() * ackSoundToPlay.getSoundCount()); - } - if (ackSoundToPlay.playUnitResponse(this.war3MapViewer.worldScene.audioContext, unit, - soundIndex)) { - this.selectedSoundCount++; - if ((this.selectedSoundCount - 3) >= pissedSoundCount) { - this.selectedSoundCount = 0; - } - playedNewSound = true; - } - } - if (selectionChanged) { - selectUnit(unit); - } - if (playedNewSound) { - portraitTalk(); - } - } - else { - selectUnit(null); - } - } - } - } - else { - if (clickedUIFrame instanceof CommandCardIcon) { - this.mouseDownUIFrame = (CommandCardIcon) clickedUIFrame; - this.mouseDownUIFrame.mouseDown(this.uiViewport); - } - } - return false; - } - - public boolean touchUp(final int screenX, final int screenY, final float worldScreenY, final int button) { - screenCoordsVector.set(screenX, screenY); - this.uiViewport.unproject(screenCoordsVector); - final UIFrame clickedUIFrame = this.rootFrame.touchUp(screenCoordsVector.x, screenCoordsVector.y, button); - if (this.mouseDownUIFrame != null) { - if (clickedUIFrame == this.mouseDownUIFrame) { - this.mouseDownUIFrame.onClick(button); - this.war3MapViewer.getUiSounds().getSound("InterfaceClick").play(this.uiScene.audioContext, 0, 0); - } - this.mouseDownUIFrame.mouseUp(this.uiViewport); - } - this.mouseDownUIFrame = null; - return false; - } - - private static boolean isShiftDown() { - return Gdx.input.isKeyPressed(Input.Keys.SHIFT_LEFT) || Gdx.input.isKeyPressed(Input.Keys.SHIFT_RIGHT); - } - - public boolean touchDragged(final int screenX, final int screenY, final float worldScreenY, final int pointer) { - screenCoordsVector.set(screenX, screenY); - this.uiViewport.unproject(screenCoordsVector); - - if (this.meleeUIMinimap.containsMouse(screenCoordsVector.x, screenCoordsVector.y)) { - final Vector2 worldPoint = this.meleeUIMinimap.getWorldPointFromScreen(screenCoordsVector.x, - screenCoordsVector.y); - this.cameraManager.target.x = worldPoint.x; - this.cameraManager.target.y = worldPoint.y; - } - return false; - } - - public float getHeightRatioCorrection() { - return this.heightRatioCorrection; - } + public static final float DEFAULT_COMMAND_CARD_ICON_WIDTH = 0.039f; + public static final float DEFAULT_COMMAND_CARD_ICON_PRESSED_WIDTH = 0.037f; + private static final int COMMAND_CARD_WIDTH = 4; + private static final int COMMAND_CARD_HEIGHT = 3; + + private static final Vector2 screenCoordsVector = new Vector2(); + private static final Vector3 clickLocationTemp = new Vector3(); + private static final Vector2 clickLocationTemp2 = new Vector2(); + private final DataSource dataSource; + private final ExtendViewport uiViewport; + private final FreeTypeFontGenerator fontGenerator; + private final Scene uiScene; + private final Scene portraitScene; + private final GameCameraManager cameraManager; + private final War3MapViewer war3MapViewer; + private final RootFrameListener rootFrameListener; + private GameUI rootFrame; + private UIFrame consoleUI; + private UIFrame resourceBar; + private StringFrame resourceBarGoldText; + private StringFrame resourceBarLumberText; + private StringFrame resourceBarSupplyText; + private StringFrame resourceBarUpkeepText; + private SpriteFrame timeIndicator; + private UIFrame unitPortrait; + private StringFrame unitLifeText; + private StringFrame unitManaText; + private Portrait portrait; + private final Rectangle tempRect = new Rectangle(); + private final Vector2 projectionTemp1 = new Vector2(); + private final Vector2 projectionTemp2 = new Vector2(); + private UIFrame simpleInfoPanelUnitDetail; + private StringFrame simpleNameValue; + private StringFrame simpleClassValue; + private StringFrame simpleBuildingActionLabel; + private UIFrame attack1Icon; + private TextureFrame attack1IconBackdrop; + private StringFrame attack1InfoPanelIconValue; + private StringFrame attack1InfoPanelIconLevel; + private UIFrame attack2Icon; + private TextureFrame attack2IconBackdrop; + private StringFrame attack2InfoPanelIconValue; + private StringFrame attack2InfoPanelIconLevel; + private UIFrame armorIcon; + private TextureFrame armorIconBackdrop; + private StringFrame armorInfoPanelIconValue; + private StringFrame armorInfoPanelIconLevel; + private InfoPanelIconBackdrops damageBackdrops; + private InfoPanelIconBackdrops defenseBackdrops; + + private final CommandCardIcon[][] commandCard = new CommandCardIcon[COMMAND_CARD_HEIGHT][COMMAND_CARD_WIDTH]; + + private RenderUnit selectedUnit; + private final List subMenuOrderIdStack = new ArrayList<>(); + + // TODO remove this & replace with FDF + private final Texture activeButtonTexture; + private UIFrame inventoryCover; + private SpriteFrame cursorFrame; + private MeleeUIMinimap meleeUIMinimap; + private final CPlayerUnitOrderListener unitOrderListener; + private StringFrame errorMessageFrame; + + private CAbilityView activeCommand; + private int activeCommandOrderId; + private RenderUnit activeCommandUnit; + + private int selectedSoundCount = 0; + private final ActiveCommandUnitTargetFilter activeCommandUnitTargetFilter; + + // TODO these corrections are used for old hardcoded UI stuff, we should + // probably remove them later + private final float widthRatioCorrection; + private final float heightRatioCorrection; + private CommandCardIcon mouseDownUIFrame; + + public MeleeUI(final DataSource dataSource, final ExtendViewport uiViewport, + final FreeTypeFontGenerator fontGenerator, final Scene uiScene, final Scene portraitScene, + final CameraPreset[] cameraPresets, final CameraRates cameraRates, final War3MapViewer war3MapViewer, + final RootFrameListener rootFrameListener, final CPlayerUnitOrderListener unitOrderListener) { + this.dataSource = dataSource; + this.uiViewport = uiViewport; + this.fontGenerator = fontGenerator; + this.uiScene = uiScene; + this.portraitScene = portraitScene; + this.war3MapViewer = war3MapViewer; + this.rootFrameListener = rootFrameListener; + this.unitOrderListener = unitOrderListener; + + this.cameraManager = new GameCameraManager(cameraPresets, cameraRates); + + this.cameraManager.setupCamera(war3MapViewer.worldScene); + if (this.war3MapViewer.startLocations[0] != null) { + this.cameraManager.target.x = this.war3MapViewer.startLocations[0].x; + this.cameraManager.target.y = this.war3MapViewer.startLocations[0].y; + } + + this.activeButtonTexture = ImageUtils.getBLPTexture(war3MapViewer.mapMpq, + "UI\\Widgets\\Console\\Human\\CommandButton\\human-activebutton.blp"); + this.activeCommandUnitTargetFilter = new ActiveCommandUnitTargetFilter(); + this.widthRatioCorrection = this.uiViewport.getMinWorldWidth() / 1600f; + this.heightRatioCorrection = this.uiViewport.getMinWorldHeight() / 1200f; + + } + + private MeleeUIMinimap createMinimap(final War3MapViewer war3MapViewer) { + final Rectangle minimapDisplayArea = new Rectangle(18.75f * this.widthRatioCorrection, + 13.75f * this.heightRatioCorrection, 278.75f * this.widthRatioCorrection, + 276.25f * this.heightRatioCorrection); + Texture minimapTexture = null; + if (war3MapViewer.dataSource.has("war3mapMap.tga")) { + try { + minimapTexture = ImageUtils.getTextureNoColorCorrection(TgaFile.readTGA("war3mapMap.tga", + war3MapViewer.dataSource.getResourceAsStream("war3mapMap.tga"))); + } catch (final IOException e) { + System.err.println("Could not load minimap TGA file"); + e.printStackTrace(); + } + } else if (war3MapViewer.dataSource.has("war3mapMap.blp")) { + try { + minimapTexture = ImageUtils + .getTexture(ImageIO.read(war3MapViewer.dataSource.getResourceAsStream("war3mapMap.blp"))); + } catch (final IOException e) { + System.err.println("Could not load minimap BLP file"); + e.printStackTrace(); + } + } + final Texture[] teamColors = new Texture[WarsmashConstants.MAX_PLAYERS]; + for (int i = 0; i < teamColors.length; i++) { + teamColors[i] = ImageUtils.getBLPTexture(war3MapViewer.dataSource, + "ReplaceableTextures\\" + ReplaceableIds.getPathString(1) + ReplaceableIds.getIdString(i) + ".blp"); + } + final Rectangle playableMapArea = war3MapViewer.terrain.getPlayableMapArea(); + return new MeleeUIMinimap(minimapDisplayArea, playableMapArea, minimapTexture, teamColors); + } + + /** + * Called "main" because this was originally written in JASS so that maps could + * override it, and I may convert it back to the JASS at some point. + */ + public void main() { + // ================================= + // Load skins and templates + // ================================= + this.rootFrame = new GameUI(this.dataSource, GameUI.loadSkin(this.dataSource, 0), this.uiViewport, + this.fontGenerator, this.uiScene, this.war3MapViewer); + this.rootFrameListener.onCreate(this.rootFrame); + try { + this.rootFrame.loadTOCFile("UI\\FrameDef\\FrameDef.toc"); + } catch (final IOException exc) { + throw new IllegalStateException("Unable to load FrameDef.toc", exc); + } + try { + this.rootFrame.loadTOCFile("UI\\FrameDef\\SmashFrameDef.toc"); + } catch (final IOException exc) { + throw new IllegalStateException("Unable to load SmashFrameDef.toc", exc); + } + this.damageBackdrops = new InfoPanelIconBackdrops(CAttackType.values(), this.rootFrame, "Damage", "Neutral"); + this.defenseBackdrops = new InfoPanelIconBackdrops(CDefenseType.values(), this.rootFrame, "Armor", "Neutral"); + + // ================================= + // Load major UI components + // ================================= + // Console UI is the background with the racial theme + this.consoleUI = this.rootFrame.createSimpleFrame("ConsoleUI", this.rootFrame, 0); + this.consoleUI.setSetAllPoints(true); + + // Resource bar is a 3 part bar with Gold, Lumber, and Food. + // Its template does not specify where to put it, so we must + // put it in the "TOPRIGHT" corner. + this.resourceBar = this.rootFrame.createSimpleFrame("ResourceBarFrame", this.consoleUI, 0); + this.resourceBar.addSetPoint(new SetPoint(FramePoint.TOPRIGHT, this.consoleUI, FramePoint.TOPRIGHT, 0, 0)); + this.resourceBarGoldText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarGoldText", 0); + this.resourceBarGoldText.setText("500"); + this.resourceBarLumberText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarLumberText", 0); + this.resourceBarLumberText.setText("150"); + this.resourceBarSupplyText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarSupplyText", 0); + this.resourceBarSupplyText.setText("12/100"); + this.resourceBarUpkeepText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarUpkeepText", 0); + this.resourceBarUpkeepText.setText("No Upkeep"); + this.resourceBarUpkeepText.setColor(Color.GREEN); + + // Create the Time Indicator (clock) + this.timeIndicator = (SpriteFrame) this.rootFrame.createFrame("TimeOfDayIndicator", this.rootFrame, 0, 0); + this.timeIndicator.setSequence(0); // play the stand + this.timeIndicator.setAnimationSpeed(0.0f); // do not advance automatically + + // Create the unit portrait stuff + this.portrait = new Portrait(this.war3MapViewer, this.portraitScene); + positionPortrait(); + this.unitPortrait = this.rootFrame.createSimpleFrame("UnitPortrait", this.consoleUI, 0); + this.unitLifeText = (StringFrame) this.rootFrame.getFrameByName("UnitPortraitHitPointText", 0); + this.unitManaText = (StringFrame) this.rootFrame.getFrameByName("UnitPortraitManaPointText", 0); + + this.simpleInfoPanelUnitDetail = this.rootFrame.createSimpleFrame("SimpleInfoPanelUnitDetail", this.consoleUI, + 0); + this.simpleInfoPanelUnitDetail + .addAnchor(new AnchorDefinition(FramePoint.BOTTOM, 0, GameUI.convertY(this.uiViewport, 0.0f))); + this.simpleInfoPanelUnitDetail.setWidth(GameUI.convertY(this.uiViewport, 0.180f)); + this.simpleInfoPanelUnitDetail.setHeight(GameUI.convertY(this.uiViewport, 0.105f)); + this.simpleNameValue = (StringFrame) this.rootFrame.getFrameByName("SimpleNameValue", 0); + this.simpleClassValue = (StringFrame) this.rootFrame.getFrameByName("SimpleClassValue", 0); + this.simpleBuildingActionLabel = (StringFrame) this.rootFrame.getFrameByName("SimpleBuildingActionLabel", 0); + + this.attack1Icon = this.rootFrame.createSimpleFrame("SimpleInfoPanelIconDamage", this.simpleInfoPanelUnitDetail, + 0); + this.attack1Icon.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, + FramePoint.TOPLEFT, 0, GameUI.convertY(this.uiViewport, -0.030125f))); + this.attack1IconBackdrop = (TextureFrame) this.rootFrame.getFrameByName("InfoPanelIconBackdrop", 0); + this.attack1InfoPanelIconValue = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconValue", 0); + this.attack1InfoPanelIconLevel = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconLevel", 0); + + this.attack2Icon = this.rootFrame.createSimpleFrame("SimpleInfoPanelIconDamage", this.simpleInfoPanelUnitDetail, + 1); + this.attack2Icon + .addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, FramePoint.TOPLEFT, + GameUI.convertX(this.uiViewport, 0.1f), GameUI.convertY(this.uiViewport, -0.030125f))); + this.attack2IconBackdrop = (TextureFrame) this.rootFrame.getFrameByName("InfoPanelIconBackdrop", 1); + this.attack2InfoPanelIconValue = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconValue", 1); + this.attack2InfoPanelIconLevel = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconLevel", 1); + + this.armorIcon = this.rootFrame.createSimpleFrame("SimpleInfoPanelIconArmor", this.simpleInfoPanelUnitDetail, + 1); + this.armorIcon.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, FramePoint.TOPLEFT, + GameUI.convertX(this.uiViewport, 0f), GameUI.convertY(this.uiViewport, -0.06025f))); + this.armorIconBackdrop = (TextureFrame) this.rootFrame.getFrameByName("InfoPanelIconBackdrop", 0); + this.armorInfoPanelIconValue = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconValue", 0); + this.armorInfoPanelIconLevel = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconLevel", 0); + + this.inventoryCover = this.rootFrame.createSimpleFrame("SmashConsoleInventoryCover", this.rootFrame, 0); + + this.errorMessageFrame = this.rootFrame.createStringFrame("SmashErrorMessageFrame", this.consoleUI, + new Color(0xFFFFCC00), TextJustify.LEFT, TextJustify.MIDDLE, 0.014f); + this.errorMessageFrame.addAnchor(new AnchorDefinition(FramePoint.BOTTOMLEFT, + GameUI.convertX(this.uiViewport, 0.275f), GameUI.convertY(this.uiViewport, 0.275f))); + this.errorMessageFrame.setWidth(GameUI.convertX(this.uiViewport, 0.25f)); + + int commandButtonIndex = 0; + for (int j = 0; j < COMMAND_CARD_HEIGHT; j++) { + for (int i = 0; i < COMMAND_CARD_WIDTH; i++) { + final CommandCardIcon commandCardIcon = new CommandCardIcon("SmashCommandButton_" + commandButtonIndex, + this.rootFrame, this); + this.rootFrame.add(commandCardIcon); + final TextureFrame iconFrame = this.rootFrame.createTextureFrame( + "SmashCommandButton_" + (commandButtonIndex) + "_Icon", this.rootFrame, false, null); + final TextureFrame activeHighlightFrame = this.rootFrame.createTextureFrame( + "SmashCommandButton_" + (commandButtonIndex) + "_ActiveHighlight", this.rootFrame, true, null, + FilterMode.ADDALPHA); + final SpriteFrame cooldownFrame = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", + "SmashCommandButton_" + (commandButtonIndex) + "_Cooldown", this.rootFrame, "", 0); + final SpriteFrame autocastFrame = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", + "SmashCommandButton_" + (commandButtonIndex) + "_Autocast", this.rootFrame, "", 0); + commandCardIcon.addAnchor(new AnchorDefinition(FramePoint.BOTTOMLEFT, + GameUI.convertX(this.uiViewport, 0.6175f + (0.0434f * i)), + GameUI.convertY(this.uiViewport, 0.095f - (0.044f * j)))); + commandCardIcon.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); + commandCardIcon.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); + iconFrame.addSetPoint(new SetPoint(FramePoint.CENTER, commandCardIcon, FramePoint.CENTER, 0, 0)); + iconFrame.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); + iconFrame.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); + iconFrame.setTexture(ImageUtils.DEFAULT_ICON_PATH, this.rootFrame); + activeHighlightFrame + .addSetPoint(new SetPoint(FramePoint.CENTER, commandCardIcon, FramePoint.CENTER, 0, 0)); + activeHighlightFrame.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); + activeHighlightFrame.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); + activeHighlightFrame.setTexture("CommandButtonActiveHighlight", this.rootFrame); + cooldownFrame.addSetPoint(new SetPoint(FramePoint.CENTER, commandCardIcon, FramePoint.CENTER, 0, 0)); + this.rootFrame.setSpriteFrameModel(cooldownFrame, this.rootFrame.getSkinField("CommandButtonCooldown")); + cooldownFrame.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); + cooldownFrame.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); + autocastFrame.addSetPoint(new SetPoint(FramePoint.CENTER, commandCardIcon, FramePoint.CENTER, 0, 0)); + this.rootFrame.setSpriteFrameModel(autocastFrame, this.rootFrame.getSkinField("CommandButtonAutocast")); + autocastFrame.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); + autocastFrame.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); + commandCardIcon.set(iconFrame, activeHighlightFrame, cooldownFrame, autocastFrame); + this.commandCard[j][i] = commandCardIcon; + commandCardIcon.setCommandButton(null); + commandButtonIndex++; + } + } + + this.cursorFrame = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", "SmashCursorFrame", this.rootFrame, + "", 0); + this.rootFrame.setSpriteFrameModel(this.cursorFrame, this.rootFrame.getSkinField("Cursor")); + this.cursorFrame.setSequence("Normal"); + this.cursorFrame.setZDepth(1.0f); + Gdx.input.setCursorCatched(true); + + this.meleeUIMinimap = createMinimap(this.war3MapViewer); + + this.rootFrame.positionBounds(this.uiViewport); + selectUnit(null); + } + + @Override + public void startUsingAbility(final int abilityHandleId, final int orderId) { + // TODO not O(N) + CAbilityView abilityToUse = null; + for (final CAbility ability : this.selectedUnit.getSimulationUnit().getAbilities()) { + if (ability.getHandleId() == abilityHandleId) { + abilityToUse = ability; + break; + } + } + if (abilityToUse != null) { + final StringMsgAbilityActivationReceiver stringMsgActivationReceiver = StringMsgAbilityActivationReceiver + .getInstance().reset(); + abilityToUse.checkCanUse(this.war3MapViewer.simulation, this.selectedUnit.getSimulationUnit(), orderId, + stringMsgActivationReceiver); + if (!stringMsgActivationReceiver.isUseOk()) { + showCommandError(stringMsgActivationReceiver.getMessage()); + } else { + final BooleanAbilityTargetCheckReceiver noTargetReceiver = BooleanAbilityTargetCheckReceiver + .getInstance().reset(); + abilityToUse.checkCanTargetNoTarget(this.war3MapViewer.simulation, + this.selectedUnit.getSimulationUnit(), orderId, noTargetReceiver); + if (noTargetReceiver.isTargetable()) { + this.unitOrderListener.issueImmediateOrder(this.selectedUnit.getSimulationUnit().getHandleId(), + abilityHandleId, orderId, isShiftDown()); + } else { + this.activeCommand = abilityToUse; + this.activeCommandOrderId = orderId; + this.activeCommandUnit = this.selectedUnit; + clearAndRepopulateCommandCard(); + } + } + } else { + this.unitOrderListener.issueImmediateOrder(this.selectedUnit.getSimulationUnit().getHandleId(), + abilityHandleId, orderId, isShiftDown()); + } + } + + @Override + public void openMenu(final int orderId) { + if (orderId == 0) { + subMenuOrderIdStack.clear(); + this.activeCommandUnit = null; + this.activeCommand = null; + this.activeCommandOrderId = -1; + } else { + this.subMenuOrderIdStack.add(orderId); + } + clearAndRepopulateCommandCard(); + } + + public void showCommandError(final String message) { + this.errorMessageFrame.setText(message); + } + + public void update(final float deltaTime) { + this.portrait.update(); + + int mouseX = Gdx.input.getX(); + int mouseY = Gdx.input.getY(); + final int minX = this.uiViewport.getScreenX(); + final int maxX = minX + this.uiViewport.getScreenWidth(); + final int minY = this.uiViewport.getScreenY(); + final int maxY = minY + this.uiViewport.getScreenHeight(); + final boolean left = mouseX <= (minX + 3); + final boolean right = mouseX >= (maxX - 3); + final boolean up = mouseY <= (minY + 3); + final boolean down = mouseY >= (maxY - 3); + this.cameraManager.applyVelocity(deltaTime, up, down, left, right); + + mouseX = Math.max(minX, Math.min(maxX, mouseX)); + mouseY = Math.max(minY, Math.min(maxY, mouseY)); + if (Gdx.input.isCursorCatched()) { + Gdx.input.setCursorPosition(mouseX, mouseY); + } + + screenCoordsVector.set(mouseX, mouseY); + this.uiViewport.unproject(screenCoordsVector); + this.cursorFrame.setFramePointX(FramePoint.LEFT, screenCoordsVector.x); + this.cursorFrame.setFramePointY(FramePoint.BOTTOM, screenCoordsVector.y); + + if (this.activeCommand != null) { + this.cursorFrame.setSequence("Target"); + } else if (down) { + if (left) { + this.cursorFrame.setSequence("Scroll Down Left"); + } else if (right) { + this.cursorFrame.setSequence("Scroll Down Right"); + } else { + this.cursorFrame.setSequence("Scroll Down"); + } + } else if (up) { + if (left) { + this.cursorFrame.setSequence("Scroll Up Left"); + } else if (right) { + this.cursorFrame.setSequence("Scroll Up Right"); + } else { + this.cursorFrame.setSequence("Scroll Up"); + } + } else if (left) { + this.cursorFrame.setSequence("Scroll Left"); + } else if (right) { + this.cursorFrame.setSequence("Scroll Right"); + } else { + this.cursorFrame.setSequence("Normal"); + } + final float groundHeight = Math.max( + this.war3MapViewer.terrain.getGroundHeight(this.cameraManager.target.x, this.cameraManager.target.y), + this.war3MapViewer.terrain.getWaterHeight(this.cameraManager.target.x, this.cameraManager.target.y)); + this.cameraManager.updateTargetZ(groundHeight); + this.cameraManager.updateCamera(); + } + + public void render(final SpriteBatch batch, final BitmapFont font20, final GlyphLayout glyphLayout) { + this.rootFrame.render(batch, font20, glyphLayout); + if (this.selectedUnit != null) { + font20.setColor(Color.WHITE); + + } + + this.meleeUIMinimap.render(batch, this.war3MapViewer.units); + this.timeIndicator.setFrameByRatio(this.war3MapViewer.simulation.getGameTimeOfDay() + / this.war3MapViewer.simulation.getGameplayConstants().getGameDayHours()); + } + + public void portraitTalk() { + this.portrait.talk(); + } + + private final class ActiveCommandUnitTargetFilter implements CUnitFilterFunction { + @Override + public boolean call(final CUnit unit) { + final BooleanAbilityTargetCheckReceiver targetReceiver = BooleanAbilityTargetCheckReceiver + .getInstance(); + MeleeUI.this.activeCommand.checkCanTarget(MeleeUI.this.war3MapViewer.simulation, + MeleeUI.this.activeCommandUnit.getSimulationUnit(), MeleeUI.this.activeCommandOrderId, unit, + targetReceiver); + return targetReceiver.isTargetable(); + } + } + + private static final class Portrait { + private MdxComplexInstance modelInstance; + private final PortraitCameraManager portraitCameraManager; + private final Scene portraitScene; + + public Portrait(final War3MapViewer war3MapViewer, final Scene portraitScene) { + this.portraitScene = portraitScene; + this.portraitCameraManager = new PortraitCameraManager(); + this.portraitCameraManager.setupCamera(this.portraitScene); + this.portraitScene.camera.viewport(new Rectangle(100, 0, 6400, 48)); + } + + public void update() { + this.portraitCameraManager.updateCamera(); + if ((this.modelInstance != null) + && (this.modelInstance.sequenceEnded || (this.modelInstance.sequence == -1))) { + SequenceUtils.randomSequence(this.modelInstance, PrimaryTag.PORTRAIT, SequenceUtils.EMPTY, true); + } + } + + public void talk() { + SequenceUtils.randomSequence(this.modelInstance, PrimaryTag.PORTRAIT, SequenceUtils.TALK, true); + } + + public void setSelectedUnit(final RenderUnit unit) { + if (unit == null) { + if (this.modelInstance != null) { + this.portraitScene.removeInstance(this.modelInstance); + } + this.modelInstance = null; + this.portraitCameraManager.setModelInstance(null, null); + } else { + final MdxModel portraitModel = unit.portraitModel; + if (portraitModel != null) { + if (this.modelInstance != null) { + this.portraitScene.removeInstance(this.modelInstance); + } + this.modelInstance = (MdxComplexInstance) portraitModel.addInstance(); + this.portraitCameraManager.setModelInstance(this.modelInstance, portraitModel); + this.modelInstance.setSequenceLoopMode(SequenceLoopMode.NEVER_LOOP); + this.modelInstance.setScene(this.portraitScene); + this.modelInstance.setVertexColor(unit.instance.vertexColor); + this.modelInstance.setTeamColor(unit.playerIndex); + } + } + } + } + + public void selectUnit(RenderUnit unit) { + this.subMenuOrderIdStack.clear(); + if ((unit != null) && unit.getSimulationUnit().isDead()) { + unit = null; + } + if (this.selectedUnit != null) { + this.selectedUnit.getSimulationUnit().removeStateListener(this); + } + this.portrait.setSelectedUnit(unit); + this.selectedUnit = unit; + clearCommandCard(); + if (unit == null) { + this.simpleNameValue.setText(""); + this.unitLifeText.setText(""); + this.unitManaText.setText(""); + this.simpleClassValue.setText(""); + this.simpleBuildingActionLabel.setText(""); + this.attack1Icon.setVisible(false); + this.attack2Icon.setVisible(false); + this.attack1InfoPanelIconLevel.setText(""); + this.attack2InfoPanelIconLevel.setText(""); + this.armorIcon.setVisible(false); + this.armorInfoPanelIconLevel.setText(""); + } else { + unit.getSimulationUnit().addStateListener(this); + this.simpleNameValue.setText(unit.getSimulationUnit().getUnitType().getName()); + String classText = null; + for (final CUnitClassification classification : unit.getSimulationUnit().getClassifications()) { + if ((classification == CUnitClassification.MECHANICAL) + && unit.getSimulationUnit().getUnitType().isBuilding()) { + // buildings dont display MECHANICAL + continue; + } + if (classification.getDisplayName() != null) { + classText = classification.getDisplayName(); + } + } + if (classText != null) { + this.simpleClassValue.setText(classText); + } else { + this.simpleClassValue.setText(""); + } + this.unitLifeText.setText(FastNumberFormat.formatWholeNumber(unit.getSimulationUnit().getLife()) + " / " + + unit.getSimulationUnit().getMaximumLife()); + final int maximumMana = unit.getSimulationUnit().getMaximumMana(); + if (maximumMana > 0) { + this.unitManaText.setText( + FastNumberFormat.formatWholeNumber(unit.getSimulationUnit().getMana()) + " / " + maximumMana); + } else { + this.unitManaText.setText(""); + } + this.simpleBuildingActionLabel.setText(""); + + final boolean anyAttacks = unit.getSimulationUnit().getUnitType().getAttacks().size() > 0; + final UIFrame localArmorIcon = this.armorIcon; + final TextureFrame localArmorIconBackdrop = this.armorIconBackdrop; + final StringFrame localArmorInfoPanelIconValue = this.armorInfoPanelIconValue; + if (anyAttacks) { + final CUnitAttack attackOne = unit.getSimulationUnit().getUnitType().getAttacks().get(0); + this.attack1Icon.setVisible(attackOne.isShowUI()); + this.attack1IconBackdrop.setTexture(this.damageBackdrops.getTexture(attackOne.getAttackType())); + this.attack1InfoPanelIconValue.setText(attackOne.getMinDamage() + " - " + attackOne.getMaxDamage()); + if (unit.getSimulationUnit().getUnitType().getAttacks().size() > 1) { + final CUnitAttack attackTwo = unit.getSimulationUnit().getUnitType().getAttacks().get(1); + this.attack2Icon.setVisible(attackTwo.isShowUI()); + this.attack2IconBackdrop.setTexture(this.damageBackdrops.getTexture(attackTwo.getAttackType())); + this.attack2InfoPanelIconValue.setText(attackTwo.getMinDamage() + " - " + attackTwo.getMaxDamage()); + } else { + this.attack2Icon.setVisible(false); + } + + this.armorIcon.addSetPoint( + new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, FramePoint.TOPLEFT, + GameUI.convertX(this.uiViewport, 0f), GameUI.convertY(this.uiViewport, -0.06025f))); + this.armorIcon.positionBounds(this.uiViewport); + } else { + this.attack1Icon.setVisible(false); + this.attack2Icon.setVisible(false); + + this.armorIcon.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, + FramePoint.TOPLEFT, 0, GameUI.convertY(this.uiViewport, -0.030125f))); + this.armorIcon.positionBounds(this.uiViewport); + } + + localArmorIcon.setVisible(true); + final Texture defenseTexture = this.defenseBackdrops + .getTexture(unit.getSimulationUnit().getUnitType().getDefenseType()); + if (defenseTexture == null) { + throw new RuntimeException( + unit.getSimulationUnit().getUnitType().getDefenseType() + " can't find texture!"); + } + localArmorIconBackdrop.setTexture(defenseTexture); + localArmorInfoPanelIconValue.setText(Integer.toString(unit.getSimulationUnit().getDefense())); + unit.populateCommandCard(this.war3MapViewer.simulation, this, this.war3MapViewer.getAbilityDataUI(), + getSubMenuOrderId()); + } + } + + private void clearCommandCard() { + for (int j = 0; j < COMMAND_CARD_HEIGHT; j++) { + for (int i = 0; i < COMMAND_CARD_WIDTH; i++) { + this.commandCard[j][i].setCommandButton(null); + } + } + } + + @Override + public void commandButton(final int buttonPositionX, final int buttonPositionY, final Texture icon, + final int abilityHandleId, final int orderId, final int autoCastId, final boolean active, + final boolean autoCastActive, final boolean menuButton) { + final int x = Math.max(0, Math.min(COMMAND_CARD_WIDTH - 1, buttonPositionX)); + final int y = Math.max(0, Math.min(COMMAND_CARD_HEIGHT - 1, buttonPositionY)); + this.commandCard[y][x].setCommandButtonData(icon, abilityHandleId, orderId, autoCastId, active, autoCastActive, + menuButton); + } + + public void resize(final Rectangle viewport) { + this.cameraManager.resize(viewport); + positionPortrait(); + } + + public void positionPortrait() { + this.projectionTemp1.x = 422 * this.widthRatioCorrection; + this.projectionTemp1.y = 57 * this.heightRatioCorrection; + this.projectionTemp2.x = (422 + 167) * this.widthRatioCorrection; + this.projectionTemp2.y = (57 + 170) * this.heightRatioCorrection; + this.uiViewport.project(this.projectionTemp1); + this.uiViewport.project(this.projectionTemp2); + + this.tempRect.x = this.projectionTemp1.x + this.uiViewport.getScreenX(); + this.tempRect.y = this.projectionTemp1.y + this.uiViewport.getScreenY(); + this.tempRect.width = this.projectionTemp2.x - this.projectionTemp1.x; + this.tempRect.height = this.projectionTemp2.y - this.projectionTemp1.y; + this.portrait.portraitScene.camera.viewport(this.tempRect); + } + + private static final class InfoPanelIconBackdrops { + private final Texture[] damageBackdropTextures; + + public InfoPanelIconBackdrops(final CodeKeyType[] attackTypes, final GameUI gameUI, final String prefix, + final String suffix) { + this.damageBackdropTextures = new Texture[attackTypes.length]; + for (int index = 0; index < attackTypes.length; index++) { + final CodeKeyType attackType = attackTypes[index]; + String skinLookupKey = "InfoPanelIcon" + prefix + attackType.getCodeKey() + suffix; + final Texture suffixTexture = gameUI.loadTexture(gameUI.getSkinField(skinLookupKey)); + if (suffixTexture != null) { + this.damageBackdropTextures[index] = suffixTexture; + } else { + skinLookupKey = "InfoPanelIcon" + prefix + attackType.getCodeKey(); + this.damageBackdropTextures[index] = gameUI.loadTexture(gameUI.getSkinField(skinLookupKey)); + } + } + } + + public Texture getTexture(final CodeKeyType attackType) { + if (attackType != null) { + final int ordinal = attackType.ordinal(); + if ((ordinal >= 0) && (ordinal < this.damageBackdropTextures.length)) { + return this.damageBackdropTextures[ordinal]; + } + } + return this.damageBackdropTextures[0]; + } + + private static String getSuffix(final CAttackType attackType) { + switch (attackType) { + case CHAOS: + return "Chaos"; + case HERO: + return "Hero"; + case MAGIC: + return "Magic"; + case NORMAL: + return "Normal"; + case PIERCE: + return "Pierce"; + case SIEGE: + return "Siege"; + case SPELLS: + return "Magic"; + case UNKNOWN: + return "Unknown"; + default: + throw new IllegalArgumentException("Unknown attack type: " + attackType); + } + + } + } + + @Override + public void lifeChanged() { + if (this.selectedUnit.getSimulationUnit().isDead()) { + selectUnit(null); + } else { + this.unitLifeText + .setText(FastNumberFormat.formatWholeNumber(this.selectedUnit.getSimulationUnit().getLife()) + " / " + + this.selectedUnit.getSimulationUnit().getMaximumLife()); + } + } + + @Override + public void ordersChanged(final int abilityHandleId, final int orderId) { + clearAndRepopulateCommandCard(); + } + + private void clearAndRepopulateCommandCard() { + clearCommandCard(); + final AbilityDataUI abilityDataUI = this.war3MapViewer.getAbilityDataUI(); + final int menuOrderId = getSubMenuOrderId(); + if (this.activeCommand != null) { + final IconUI cancelUI = abilityDataUI.getCancelUI(); + this.commandButton(cancelUI.getButtonPositionX(), cancelUI.getButtonPositionY(), cancelUI.getIcon(), 0, + menuOrderId, 0, false, false, true); + } else { + if (menuOrderId != 0) { + final int exitOrderId = this.subMenuOrderIdStack.size() > 1 + ? this.subMenuOrderIdStack.get(this.subMenuOrderIdStack.size() - 2) + : 0; + final IconUI cancelUI = abilityDataUI.getCancelUI(); + this.commandButton(cancelUI.getButtonPositionX(), cancelUI.getButtonPositionY(), cancelUI.getIcon(), 0, + exitOrderId, 0, false, false, true); + } + this.selectedUnit.populateCommandCard(this.war3MapViewer.simulation, this, abilityDataUI, menuOrderId); + } + } + + private int getSubMenuOrderId() { + return this.subMenuOrderIdStack.isEmpty() ? 0 + : this.subMenuOrderIdStack.get(this.subMenuOrderIdStack.size() - 1); + } + + public RenderUnit getSelectedUnit() { + return this.selectedUnit; + } + + public boolean keyDown(final int keycode) { + return this.cameraManager.keyDown(keycode); + } + + public boolean keyUp(final int keycode) { + return this.cameraManager.keyUp(keycode); + } + + public void scrolled(final int amount) { + this.cameraManager.scrolled(amount); + } + + public boolean touchDown(final int screenX, final int screenY, final float worldScreenY, final int button) { + screenCoordsVector.set(screenX, screenY); + this.uiViewport.unproject(screenCoordsVector); + if (this.meleeUIMinimap.containsMouse(screenCoordsVector.x, screenCoordsVector.y)) { + final Vector2 worldPoint = this.meleeUIMinimap.getWorldPointFromScreen(screenCoordsVector.x, + screenCoordsVector.y); + this.cameraManager.target.x = worldPoint.x; + this.cameraManager.target.y = worldPoint.y; + return true; + } + final UIFrame clickedUIFrame = this.rootFrame.touchDown(screenCoordsVector.x, screenCoordsVector.y, button); + if (clickedUIFrame == null) { + // try to interact with world + if (this.activeCommand != null) { + if (button == Input.Buttons.RIGHT) { + this.activeCommandUnit = null; + this.activeCommand = null; + this.activeCommandOrderId = -1; + clearAndRepopulateCommandCard(); + } else { + final RenderUnit rayPickUnit = this.war3MapViewer.rayPickUnit(screenX, worldScreenY, + this.activeCommandUnitTargetFilter); + final boolean shiftDown = isShiftDown(); + if (rayPickUnit != null) { + this.unitOrderListener.issueTargetOrder( + this.activeCommandUnit.getSimulationUnit().getHandleId(), + this.activeCommand.getHandleId(), this.activeCommandOrderId, + rayPickUnit.getSimulationUnit().getHandleId(), shiftDown); + if (getSelectedUnit().soundset.yesAttack + .playUnitResponse(this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { + portraitTalk(); + } + this.selectedSoundCount = 0; + if (!shiftDown) { + this.activeCommandUnit = null; + this.activeCommand = null; + this.activeCommandOrderId = -1; + clearAndRepopulateCommandCard(); + } + } else { + this.war3MapViewer.getClickLocation(clickLocationTemp, screenX, (int) worldScreenY); + clickLocationTemp2.set(clickLocationTemp.x, clickLocationTemp.y); + + this.activeCommand.checkCanTarget(this.war3MapViewer.simulation, + this.activeCommandUnit.getSimulationUnit(), this.activeCommandOrderId, + clickLocationTemp2, PointAbilityTargetCheckReceiver.INSTANCE); + final Vector2 target = PointAbilityTargetCheckReceiver.INSTANCE.getTarget(); + if (target != null) { + if (this.activeCommand instanceof CAbilityAttack) { + this.war3MapViewer.showConfirmation(clickLocationTemp, 1, 0, 0); + } else { + this.war3MapViewer.showConfirmation(clickLocationTemp, 0, 1, 0); + } + this.unitOrderListener.issuePointOrder( + this.activeCommandUnit.getSimulationUnit().getHandleId(), + this.activeCommand.getHandleId(), this.activeCommandOrderId, clickLocationTemp2.x, + clickLocationTemp2.y, shiftDown); + if (getSelectedUnit().soundset.yes + .playUnitResponse(this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { + portraitTalk(); + } + this.selectedSoundCount = 0; + if (!shiftDown) { + this.activeCommandUnit = null; + this.activeCommand = null; + this.activeCommandOrderId = -1; + clearAndRepopulateCommandCard(); + } + + } + } + } + } else { + if (button == Input.Buttons.RIGHT) { + if (getSelectedUnit() != null) { + final RenderUnit rayPickUnit = this.war3MapViewer.rayPickUnit(screenX, worldScreenY); + if ((rayPickUnit != null) && !rayPickUnit.getSimulationUnit().isDead()) { + boolean ordered = false; + for (final RenderUnit unit : this.war3MapViewer.selected) { + for (final CAbility ability : unit.getSimulationUnit().getAbilities()) { + ability.checkCanTarget(this.war3MapViewer.simulation, unit.getSimulationUnit(), + OrderIds.smart, rayPickUnit.getSimulationUnit(), + CWidgetAbilityTargetCheckReceiver.INSTANCE); + final CWidget targetWidget = CWidgetAbilityTargetCheckReceiver.INSTANCE.getTarget(); + if (targetWidget != null) { + this.unitOrderListener.issueTargetOrder(unit.getSimulationUnit().getHandleId(), + ability.getHandleId(), OrderIds.smart, targetWidget.getHandleId(), + isShiftDown()); + ordered = true; + } + } + + } + if (ordered) { + if (getSelectedUnit().soundset.yesAttack.playUnitResponse( + this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { + portraitTalk(); + } + this.selectedSoundCount = 0; + } + } else { + this.war3MapViewer.getClickLocation(clickLocationTemp, screenX, (int) worldScreenY); + this.war3MapViewer.showConfirmation(clickLocationTemp, 0, 1, 0); + clickLocationTemp2.set(clickLocationTemp.x, clickLocationTemp.y); + + boolean ordered = false; + for (final RenderUnit unit : this.war3MapViewer.selected) { + for (final CAbility ability : unit.getSimulationUnit().getAbilities()) { + ability.checkCanUse(this.war3MapViewer.simulation, unit.getSimulationUnit(), + OrderIds.smart, BooleanAbilityActivationReceiver.INSTANCE); + if (BooleanAbilityActivationReceiver.INSTANCE.isOk()) { + ability.checkCanTarget(this.war3MapViewer.simulation, unit.getSimulationUnit(), + OrderIds.smart, clickLocationTemp2, + PointAbilityTargetCheckReceiver.INSTANCE); + final Vector2 target = PointAbilityTargetCheckReceiver.INSTANCE.getTarget(); + if (target != null) { + this.unitOrderListener.issuePointOrder( + unit.getSimulationUnit().getHandleId(), ability.getHandleId(), + OrderIds.smart, clickLocationTemp2.x, clickLocationTemp2.y, + isShiftDown()); + ordered = true; + } + } + } + + } + + if (ordered) { + if (getSelectedUnit().soundset.yes.playUnitResponse( + this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { + portraitTalk(); + } + this.selectedSoundCount = 0; + } + } + } + } else { + final List selectedUnits = this.war3MapViewer.selectUnit(screenX, worldScreenY, false); + if (!selectedUnits.isEmpty()) { + final RenderUnit unit = selectedUnits.get(0); + final boolean selectionChanged = getSelectedUnit() != unit; + boolean playedNewSound = false; + if (selectionChanged) { + this.selectedSoundCount = 0; + } + if (unit.soundset != null) { + UnitSound ackSoundToPlay = unit.soundset.what; + final int pissedSoundCount = unit.soundset.pissed.getSoundCount(); + int soundIndex; + if ((this.selectedSoundCount >= 3) && (pissedSoundCount > 0)) { + soundIndex = this.selectedSoundCount - 3; + ackSoundToPlay = unit.soundset.pissed; + } else { + soundIndex = (int) (Math.random() * ackSoundToPlay.getSoundCount()); + } + if (ackSoundToPlay.playUnitResponse(this.war3MapViewer.worldScene.audioContext, unit, + soundIndex)) { + this.selectedSoundCount++; + if ((this.selectedSoundCount - 3) >= pissedSoundCount) { + this.selectedSoundCount = 0; + } + playedNewSound = true; + } + } + if (selectionChanged) { + selectUnit(unit); + } + if (playedNewSound) { + portraitTalk(); + } + } else { + selectUnit(null); + } + } + } + } else { + if (clickedUIFrame instanceof CommandCardIcon) { + this.mouseDownUIFrame = (CommandCardIcon) clickedUIFrame; + this.mouseDownUIFrame.mouseDown(this.uiViewport); + } + } + return false; + } + + public boolean touchUp(final int screenX, final int screenY, final float worldScreenY, final int button) { + screenCoordsVector.set(screenX, screenY); + this.uiViewport.unproject(screenCoordsVector); + final UIFrame clickedUIFrame = this.rootFrame.touchUp(screenCoordsVector.x, screenCoordsVector.y, button); + if (this.mouseDownUIFrame != null) { + if (clickedUIFrame == this.mouseDownUIFrame) { + this.mouseDownUIFrame.onClick(button); + this.war3MapViewer.getUiSounds().getSound("InterfaceClick").play(this.uiScene.audioContext, 0, 0); + } + this.mouseDownUIFrame.mouseUp(this.uiViewport); + } + this.mouseDownUIFrame = null; + return false; + } + + private static boolean isShiftDown() { + return Gdx.input.isKeyPressed(Input.Keys.SHIFT_LEFT) || Gdx.input.isKeyPressed(Input.Keys.SHIFT_RIGHT); + } + + public boolean touchDragged(final int screenX, final int screenY, final float worldScreenY, final int pointer) { + screenCoordsVector.set(screenX, screenY); + this.uiViewport.unproject(screenCoordsVector); + + if (this.meleeUIMinimap.containsMouse(screenCoordsVector.x, screenCoordsVector.y)) { + final Vector2 worldPoint = this.meleeUIMinimap.getWorldPointFromScreen(screenCoordsVector.x, + screenCoordsVector.y); + this.cameraManager.target.x = worldPoint.x; + this.cameraManager.target.y = worldPoint.y; + } + return false; + } + + public float getHeightRatioCorrection() { + return this.heightRatioCorrection; + } } From b74b2f6c91889654152c1ffd9070c2604a6040d9 Mon Sep 17 00:00:00 2001 From: Retera Date: Sat, 7 Nov 2020 14:55:09 -0500 Subject: [PATCH 061/116] Improve some edge cases when sequences are missing start and end keyframes --- .../viewer5/handlers/mdx/SdSequence.java | 53 ++++++++++--------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SdSequence.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SdSequence.java index 06517db..04780d0 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SdSequence.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SdSequence.java @@ -6,9 +6,9 @@ import java.util.Arrays; import com.etheller.warsmash.parsers.mdlx.AnimationMap; import com.etheller.warsmash.parsers.mdlx.timeline.Timeline; import com.etheller.warsmash.util.ParseUtils; -import com.etheller.warsmash.util.RenderMathUtils; public final class SdSequence { + private static boolean INJECT_FRAMES_GHOSTWOLF_STYLE = false; private final Sd sd; public final long start; // UInt32 @@ -99,7 +99,7 @@ public final class SdSequence { } this.constant = allFramesMatch; - if (!this.constant) { + if (!this.constant && INJECT_FRAMES_GHOSTWOLF_STYLE) { // If there is no opening keyframe for this sequence, inject one // with the default value. final boolean hasStart = framesBuilder.get(0) == start; @@ -152,7 +152,7 @@ public final class SdSequence { } private TYPE[] fixTimelineArray(final Timeline timeline, final TYPE[] values) { - if(values == null) { + if (values == null) { return null; } if (timeline.getName().equals(AnimationMap.KLAC.getWar3id()) @@ -166,10 +166,6 @@ public final class SdSequence { return values; } -// private TYPE[] makeArray(final int size) { -// return (TYPE[]) new Object[size]; -// } - public int getValue(final TYPE out, final long frame) { final int l = this.frames.length; @@ -178,26 +174,35 @@ public final class SdSequence { return -1; } - else if (frame >= this.end) { - this.sd.copy(out, this.values[l - 1]); - - return l - 1; - } else { - for (int i = 1; i < l; i++) { - if (this.frames[i] > frame) { - final long start = this.frames[i - 1]; - final long end = this.frames[i]; - final float t = RenderMathUtils - .clamp(((end - start) == 0 ? 0 : ((frame - start) / (float) (end - start))), 0, 1); - - this.sd.interpolate(out, this.values, this.inTans, this.outTans, i - 1, i, t); - - return i; + int startFrame = -1; + int endFrame = -1; + final int l1 = l - 1; + if ((frame < this.frames[0]) || (frame >= this.frames[l1])) { + startFrame = l1; + endFrame = 0; + } + else { + for (int i = 1; i < l; i++) { + if (this.frames[i] > frame) { + startFrame = i - 1; + endFrame = i; + break; + } } } - - return -1; + long start = this.frames[startFrame]; + final long end = this.frames[endFrame]; + long timeBetweenFrames = end - start; + if (timeBetweenFrames < 0) { + timeBetweenFrames += (this.end - this.start); + if (frame < start) { + start = end; + } + } + final float t = ((timeBetweenFrames) == 0 ? 0 : ((frame - start) / (float) (timeBetweenFrames))); + this.sd.interpolate(out, this.values, this.inTans, this.outTans, startFrame, endFrame, t); + return startFrame; } } From cf9beb992371affb8998b4a7dadedd40eea04b18 Mon Sep 17 00:00:00 2001 From: Retera Date: Sun, 8 Nov 2020 18:16:32 -0500 Subject: [PATCH 062/116] Working with behaviors --- .../etheller/warsmash/WarsmashGdxMapGame.java | 11 +- .../viewer5/handlers/mdx/EventObjectSpn.java | 3 + .../viewer5/handlers/w3x/UnitSound.java | 3 + .../viewer5/handlers/w3x/War3MapViewer.java | 6 + .../handlers/w3x/simulation/CUnit.java | 32 +- .../w3x/simulation/abilities/CAbility.java | 3 + .../simulation/abilities/CAbilityAttack.java | 8 +- .../simulation/abilities/CAbilityGeneric.java | 5 + .../simulation/abilities/CAbilityMove.java | 5 + .../build/AbstractCAbilityBuild.java | 5 + .../abilities/build/CAbilityHumanBuild.java | 2 +- .../abilities/combat/CAbilityColdArrows.java | 13 +- .../behaviors/CAbstractRangedBehavior.java | 27 +- .../CAbstractRangedPointTargetBehavior.java | 35 + .../CAbstractRangedWidgetTargetBehavior.java | 39 + .../simulation/behaviors/CBehaviorAttack.java | 18 +- .../simulation/behaviors/CBehaviorFollow.java | 6 +- .../behaviors/build/CBehaviorBuild.java | 45 + .../w3x/simulation/players/CPlayer.java | 4 +- .../w3x/simulation/test/BaseBehavior.java | 5 + .../w3x/simulation/test/BaseState.java | 5 + .../w3x/simulation/test/IAbility.java | 28 + .../w3x/simulation/test/IBehavior.java | 5 + .../handlers/w3x/simulation/test/IState.java | 5 + .../test/ability/AttackAbility.java | 28 + .../simulation/test/ability/MoveAbility.java | 22 + .../test/behavior/AttackTarget.java | 22 + .../simulation/test/behavior/MoveToPoint.java | 45 + .../w3x/simulation/test/state/MoveState.java | 28 + .../util/SimulationRenderController.java | 3 + .../viewer5/handlers/w3x/ui/MeleeUI.java | 1936 +++++++++-------- .../warsmash/desktop/DesktopLauncher.java | 3 + 32 files changed, 1417 insertions(+), 988 deletions(-) create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CAbstractRangedPointTargetBehavior.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CAbstractRangedWidgetTargetBehavior.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorBuild.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/BaseBehavior.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/BaseState.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/IAbility.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/IBehavior.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/IState.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/ability/AttackAbility.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/ability/MoveAbility.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/behavior/AttackTarget.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/behavior/MoveToPoint.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/state/MoveState.java diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java index df38da2..0284e4e 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java @@ -55,6 +55,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.ui.MeleeUI; import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.CommandErrorListener; public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProvider, InputProcessor { + private static final boolean ENABLE_AUDIO = false; private static final boolean ENABLE_MUSIC = false; private DataSource codebase; private War3MapViewer viewer; @@ -124,8 +125,10 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.codebase = new CompoundDataSourceDescriptor(dataSourcesList).createDataSource(); this.viewer = new War3MapViewer(this.codebase, this); - this.viewer.worldScene.enableAudio(); - this.viewer.enableAudio(); + if (ENABLE_AUDIO) { + this.viewer.worldScene.enableAudio(); + this.viewer.enableAudio(); + } try { this.viewer.loadMap(this.warsmashIni.get("Map").getField("FilePath")); } @@ -157,7 +160,9 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv final Scene portraitScene = this.viewer.addSimpleScene(); this.uiScene = this.viewer.addSimpleScene(); this.uiScene.alpha = true; - this.uiScene.enableAudio(); + if (ENABLE_AUDIO) { + this.uiScene.enableAudio(); + } // this.mainModel = (MdxModel) this.viewer.load("UI\\Glues\\MainMenu\\MainMenu3D_exp\\MainMenu3D_exp.mdx", diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectSpn.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectSpn.java index d59b650..9c58ef8 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectSpn.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectSpn.java @@ -38,6 +38,9 @@ public class EventObjectSpn extends EmittedObject= model.getSequences().get(0).getInterval()[1]) { this.health = 0; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitSound.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitSound.java index 68a991e..f0f0585 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitSound.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitSound.java @@ -95,6 +95,9 @@ public final class UnitSound { return false; } + if (audioContext == null) { + return true; + } final AudioPanner panner = audioContext.createPanner(); final AudioBufferSource source = audioContext.createBufferSource(); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index d46b562..8aab527 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -520,6 +520,12 @@ public class War3MapViewer extends ModelViewer { return War3MapViewer.this .getBuildingPathingPixelMap(War3MapViewer.this.allObjectData.getUnits().get(rawcode)); } + + @Override + public CUnit createUnit(final CSimulation simulation, final War3ID typeId, final int playerIndex, + final float x, final float y, final float facing) { + return null; + } }, this.terrain.pathingGrid, this.terrain.getEntireMap(), this.seededRandom, w3iFile.getPlayers()); this.walkableObjectsTree = new Quadtree<>(this.terrain.getEntireMap()); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java index 3392af1..a9e9d98 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java @@ -6,7 +6,6 @@ import java.util.EnumSet; import java.util.LinkedList; import java.util.List; import java.util.Queue; -import java.util.concurrent.CyclicBarrier; import com.badlogic.gdx.math.Rectangle; import com.etheller.warsmash.util.War3ID; @@ -28,6 +27,8 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.COrder; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CAllianceType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayer; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.test.IBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.test.IState; public class CUnit extends CWidget { private static final Rectangle tempRect = new Rectangle(); @@ -71,6 +72,8 @@ public class CUnit extends CWidget { private transient CBehaviorFollow followBehavior; private transient CBehaviorPatrol patrolBehavior; private transient CBehaviorStop stopBehavior; + private IBehavior behavior; + private IState state; public CUnit(final int handleId, final int playerIndex, final float x, final float y, final float life, final War3ID typeId, final float facing, final float mana, final int maximumLife, final int maximumMana, @@ -201,9 +204,9 @@ public class CUnit extends CWidget { } } else if (this.currentBehavior != null) { - CBehavior lastBehavior = this.currentBehavior; + final CBehavior lastBehavior = this.currentBehavior; this.currentBehavior = this.currentBehavior.update(game); - if(this.currentBehavior.getHighlightOrderId() != lastBehavior.getHighlightOrderId()) { + if (this.currentBehavior.getHighlightOrderId() != lastBehavior.getHighlightOrderId()) { this.stateNotifier.ordersChanged(getCurrentAbilityHandleId(), getCurrentOrderId()); } } @@ -242,6 +245,17 @@ public class CUnit extends CWidget { if (isDead()) { return; } + + final CAbility ability = game.getAbility(order.getAbilityHandleId()); + if (ability != null) { + // Allow the ability to response to the order without actually placing itself in + // the queue, nor modifying (interrupting) the queue. + if (!ability.checkBeforeQueue(game, this, order.getOrderId())) { + this.stateNotifier.ordersChanged(getCurrentAbilityHandleId(), getCurrentOrderId()); + return; + } + } + if (queue && (this.currentOrder != null)) { this.orderQueue.add(order); } @@ -595,7 +609,8 @@ public class CUnit extends CWidget { if (this.source.canReach(unit, this.source.acquisitionRange) && unit.canBeTargetedBy(this.game, this.source, attack.getTargetsAllowed()) && (this.source.distance(unit) >= this.source.getUnitType().getMinimumAttackRange())) { - this.source.currentBehavior = this.source.getAttackBehavior().reset(OrderIds.attack, attack, unit); + this.source.currentBehavior = this.source.getAttackBehavior().reset(OrderIds.attack, attack, + unit); return true; } } @@ -644,4 +659,13 @@ public class CUnit extends CWidget { final COrder order = this.orderQueue.poll(); return beginOrder(game, order); } + + public boolean isMoving() { + return getCurrentBehavior() instanceof CBehaviorMove; + } + + public void setBehavior(final IBehavior behavior) { + this.behavior = behavior; + this.state = behavior.resolveNext(); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbility.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbility.java index 7720194..3de15cc 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbility.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbility.java @@ -13,6 +13,9 @@ public interface CAbility extends CAbilityView { /* should fire when ability removed from unit */ void onRemove(CSimulation game, CUnit unit); + /* return false to not do anything, such as for toggling autocast */ + boolean checkBeforeQueue(CSimulation game, CUnit caster, int orderId); + CBehavior begin(CSimulation game, CUnit caster, int orderId, CWidget target); CBehavior begin(CSimulation game, CUnit caster, int orderId, Vector2 point); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java index a58c30f..e6d9e0f 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java @@ -10,11 +10,12 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CAllianceType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.test.IAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver.TargetType; -public class CAbilityAttack implements CAbility { +public class CAbilityAttack implements CAbility, IAbility { private final int handleId; public CAbilityAttack(final int handleId) { @@ -60,6 +61,11 @@ public class CAbilityAttack implements CAbility { } } + @Override + public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId) { + return true; + } + @Override public void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, final Vector2 target, final AbilityTargetCheckReceiver receiver) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityGeneric.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityGeneric.java index 40ff01c..52946c3 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityGeneric.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityGeneric.java @@ -67,6 +67,11 @@ public class CAbilityGeneric implements CAbility { public void onRemove(final CSimulation game, final CUnit unit) { } + @Override + public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId) { + return false; + } + @Override public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final CWidget target) { return caster.pollNextOrderBehavior(game); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityMove.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityMove.java index fb1fc05..64b1988 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityMove.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityMove.java @@ -85,6 +85,11 @@ public class CAbilityMove implements CAbility { } + @Override + public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId) { + return true; + } + @Override public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final CWidget target) { return caster.getFollowBehavior().reset(OrderIds.move, (CUnit) target); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java index 387499c..a946d90 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java @@ -75,4 +75,9 @@ public abstract class AbstractCAbilityBuild extends AbstractCAbility implements final AbilityTargetCheckReceiver receiver) { receiver.orderIdNotAccepted(); } + + @Override + public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId) { + return false; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityHumanBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityHumanBuild.java index 9eb3c64..d8764c9 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityHumanBuild.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityHumanBuild.java @@ -43,7 +43,7 @@ public class CAbilityHumanBuild extends AbstractCAbilityBuild { @Override public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final Vector2 point) { - // TODO Auto-generated method stub +// caster.getMoveBehavior().reset(point.x, point.y, ) return null; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/combat/CAbilityColdArrows.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/combat/CAbilityColdArrows.java index 3a11ac4..742e0ae 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/combat/CAbilityColdArrows.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/combat/CAbilityColdArrows.java @@ -91,6 +91,18 @@ public class CAbilityColdArrows implements CAbility { public void onRemove(final CSimulation game, final CUnit unit) { } + @Override + public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId) { + switch (orderId) { + case OrderIds.coldarrows: + case OrderIds.uncoldarrows: + this.autoCastActive = !this.autoCastActive; + return false; + default: + return true; + } + } + @Override public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final CWidget target) { CBehavior behavior = null; @@ -113,7 +125,6 @@ public class CAbilityColdArrows implements CAbility { @Override public CBehavior beginNoTarget(final CSimulation game, final CUnit caster, final int orderId) { - this.autoCastActive = !this.autoCastActive; return caster.pollNextOrderBehavior(game); } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CAbstractRangedBehavior.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CAbstractRangedBehavior.java index 809d097..bea9639 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CAbstractRangedBehavior.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CAbstractRangedBehavior.java @@ -2,7 +2,6 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; public abstract class CAbstractRangedBehavior implements CRangedBehavior { protected final CUnit unit; @@ -12,28 +11,29 @@ public abstract class CAbstractRangedBehavior implements CRangedBehavior { } private boolean wasWithinPropWindow = false; - protected CWidget target; private boolean wasInRange = false; private CBehaviorMove moveBehavior; - protected final CAbstractRangedBehavior innerReset(final CWidget target) { + protected final CAbstractRangedBehavior innerReset() { this.wasWithinPropWindow = false; - this.target = target; this.wasInRange = false; + CBehaviorMove moveBehavior; if (!this.unit.isMovementDisabled()) { - if ((target instanceof CUnit) && !((CUnit) target).getUnitType().isBuilding()) { - this.moveBehavior = this.unit.getMoveBehavior().reset((CUnit) target, this); - } - else { - this.moveBehavior = this.unit.getMoveBehavior().reset(target.getX(), target.getY(), this); - } + moveBehavior = setupMoveBehavior(); } else { - this.moveBehavior = null; + moveBehavior = null; } + this.moveBehavior = moveBehavior; return this; } + protected abstract CBehaviorMove setupMoveBehavior(); + + protected abstract float getTargetX(); + + protected abstract float getTargetY(); + protected abstract CBehavior update(CSimulation simulation, boolean withinRange); protected abstract boolean checkTargetStillValid(CSimulation simulation); @@ -51,15 +51,14 @@ public abstract class CAbstractRangedBehavior implements CRangedBehavior { } this.wasInRange = false; resetBeforeMoving(simulation); - ; return this.unit.getMoveBehavior(); } this.wasInRange = true; if (!this.unit.isMovementDisabled()) { final float prevX = this.unit.getX(); final float prevY = this.unit.getY(); - final float deltaY = this.target.getY() - prevY; - final float deltaX = this.target.getX() - prevX; + final float deltaY = getTargetY() - prevY; + final float deltaX = getTargetX() - prevX; final double goalAngleRad = Math.atan2(deltaY, deltaX); float goalAngle = (float) Math.toDegrees(goalAngleRad); if (goalAngle < 0) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CAbstractRangedPointTargetBehavior.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CAbstractRangedPointTargetBehavior.java new file mode 100644 index 0000000..1064290 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CAbstractRangedPointTargetBehavior.java @@ -0,0 +1,35 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; + +public abstract class CAbstractRangedPointTargetBehavior extends CAbstractRangedBehavior { + + protected float targetX; + protected float targetY; + + public CAbstractRangedPointTargetBehavior(final CUnit unit) { + super(unit); + } + + protected final CAbstractRangedBehavior innerReset(final float targetX, final float targetY) { + this.targetX = targetX; + this.targetY = targetY; + return innerReset(); + } + + @Override + protected float getTargetX() { + return this.targetX; + } + + @Override + protected float getTargetY() { + return this.targetY; + } + + @Override + protected CBehaviorMove setupMoveBehavior() { + return this.unit.getMoveBehavior().reset(this.targetX, this.targetY, this); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CAbstractRangedWidgetTargetBehavior.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CAbstractRangedWidgetTargetBehavior.java new file mode 100644 index 0000000..3b1ff9c --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CAbstractRangedWidgetTargetBehavior.java @@ -0,0 +1,39 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; + +public abstract class CAbstractRangedWidgetTargetBehavior extends CAbstractRangedBehavior { + + protected CWidget target; + + public CAbstractRangedWidgetTargetBehavior(final CUnit unit) { + super(unit); + } + + protected final CAbstractRangedBehavior innerReset(final CWidget target) { + this.target = target; + return innerReset(); + } + + @Override + protected float getTargetX() { + return this.target.getX(); + } + + @Override + protected float getTargetY() { + return this.target.getY(); + } + + @Override + protected CBehaviorMove setupMoveBehavior() { + if ((this.target instanceof CUnit) && !((CUnit) this.target).getUnitType().isBuilding()) { + return this.unit.getMoveBehavior().reset((CUnit) this.target, this); + } + else { + return this.unit.getMoveBehavior().reset(this.target.getX(), this.target.getY(), this); + } + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java index 8837938..bde7654 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java @@ -8,7 +8,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; -public class CBehaviorAttack extends CAbstractRangedBehavior { +public class CBehaviorAttack extends CAbstractRangedWidgetTargetBehavior { private int highlightOrderId; @@ -21,7 +21,7 @@ public class CBehaviorAttack extends CAbstractRangedBehavior { private int backSwingTime; private int thisOrderCooldownEndTime; - public CBehaviorAttack reset(int highlightOrderId, final CUnitAttack unitAttack, final CWidget target) { + public CBehaviorAttack reset(final int highlightOrderId, final CUnitAttack unitAttack, final CWidget target) { this.highlightOrderId = highlightOrderId; super.innerReset(target); this.unitAttack = unitAttack; @@ -33,18 +33,18 @@ public class CBehaviorAttack extends CAbstractRangedBehavior { @Override public int getHighlightOrderId() { - return highlightOrderId; + return this.highlightOrderId; } @Override public boolean isWithinRange(final CSimulation simulation) { float range = this.unitAttack.getRange(); - if ((this.target instanceof CUnit) && (((CUnit) this.target).getCurrentBehavior() instanceof CBehaviorMove) - && (this.damagePointLaunchTime != 0 /* - * only apply range motion buffer if they were already in range and - * attacked - */)) { - range += this.unitAttack.getRangeMotionBuffer(); + if ((this.target instanceof CUnit) && (((CUnit) this.target).isMoving()) && (simulation + .getGameTurnTick() < this.unit.getCooldownEndTime() /* + * only apply range motion buffer if they were + * already in range and attacked + */)) { + range += this.unitAttack.getRangeMotionBuffer() + 1000; } return this.unit.canReach(this.target, range) && (this.unit.distance(this.target) >= this.unit.getUnitType().getMinimumAttackRange()); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorFollow.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorFollow.java index 785195e..02c98dd 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorFollow.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorFollow.java @@ -5,7 +5,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; -public class CBehaviorFollow extends CAbstractRangedBehavior { +public class CBehaviorFollow extends CAbstractRangedWidgetTargetBehavior { private int higlightOrderId; @@ -13,14 +13,14 @@ public class CBehaviorFollow extends CAbstractRangedBehavior { super(unit); } - public CBehavior reset(int higlightOrderId, final CUnit target) { + public CBehavior reset(final int higlightOrderId, final CUnit target) { this.higlightOrderId = higlightOrderId; return innerReset(target); } @Override public int getHighlightOrderId() { - return higlightOrderId; + return this.higlightOrderId; } @Override diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorBuild.java new file mode 100644 index 0000000..f407ad5 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorBuild.java @@ -0,0 +1,45 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.build; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CAbstractRangedPointTargetBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; + +public class CBehaviorBuild extends CAbstractRangedPointTargetBehavior { + private int orderId; + + public CBehaviorBuild(final CUnit unit) { + super(unit); + } + + public CBehavior reset(final float targetX, final float targetY, final int orderId) { + this.orderId = orderId; + return innerReset(targetX, targetY); + } + + @Override + public boolean isWithinRange(final CSimulation simulation) { + return this.unit.distance(this.targetX, this.targetY) <= 23525; + } + + @Override + public int getHighlightOrderId() { + return this.orderId; + } + + @Override + protected CBehavior update(final CSimulation simulation, final boolean withinRange) { + return this; + } + + @Override + protected boolean checkTargetStillValid(final CSimulation simulation) { + return true; + } + + @Override + protected void resetBeforeMoving(final CSimulation simulation) { + + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayer.java index cc3f424..93425ef 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayer.java @@ -12,8 +12,8 @@ public class CPlayer { private final CRace race; private final float[] startLocation; private final EnumSet racePrefs; - private int gold; - private int lumber; + private int gold = 5000; + private int lumber = 5000; private final EnumSet[] alliances = new EnumSet[WarsmashConstants.MAX_PLAYERS]; public CPlayer(final int id, final CMapControl controlType, final String name, final CRace race, diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/BaseBehavior.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/BaseBehavior.java new file mode 100644 index 0000000..8db5d76 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/BaseBehavior.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.test; + +public abstract class BaseBehavior { + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/BaseState.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/BaseState.java new file mode 100644 index 0000000..3ea24f6 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/BaseState.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.test; + +public abstract class BaseState implements IState { + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/IAbility.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/IAbility.java new file mode 100644 index 0000000..c8c82e6 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/IAbility.java @@ -0,0 +1,28 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.test; + +import java.awt.Point; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; + +/* + * IAbility + Execute(unit caster, int orderId, unit targetUnit, point targetPoint); + +IBehavior + ResolveNext(); + +IState + Execute(); + +abstract BaseState + ctor(unit unit, IBehavior behavior) + abstract Execute(); + +abstract BaseBehavior + ctor(unit unit) + + */ +public interface IAbility { + void execute(CUnit caster, int orderId, CWidget targetUnit, Point targetPoint); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/IBehavior.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/IBehavior.java new file mode 100644 index 0000000..ff9e07e --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/IBehavior.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.test; + +public interface IBehavior { + IState resolveNext(); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/IState.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/IState.java new file mode 100644 index 0000000..7c351ca --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/IState.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.test; + +public interface IState { + void execute(); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/ability/AttackAbility.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/ability/AttackAbility.java new file mode 100644 index 0000000..83d2ba1 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/ability/AttackAbility.java @@ -0,0 +1,28 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.test.ability; + +import java.awt.Point; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.test.IAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.test.behavior.AttackTarget; + +public class AttackAbility implements IAbility { + + @Override + public void execute(final CUnit caster, final int orderId, final CWidget target, final Point targetPoint) { + if (target != null) { + new AttackTarget(caster, target); + } + else if (targetPoint != null) { + if (orderId == OrderIds.attackground) { + // TODO some stuff + } + else if (orderId == OrderIds.attack) { + + } + } + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/ability/MoveAbility.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/ability/MoveAbility.java new file mode 100644 index 0000000..e12eb86 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/ability/MoveAbility.java @@ -0,0 +1,22 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.test.ability; + +import java.awt.Point; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.test.IAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.test.behavior.MoveToPoint; + +public class MoveAbility implements IAbility { + + @Override + public void execute(final CUnit caster, final int orderId, final CWidget targetUnit, final Point targetPoint) { + if (targetUnit == null) { + caster.setBehavior(new MoveToPoint(caster, targetPoint)); + } + else { + + } + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/behavior/AttackTarget.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/behavior/AttackTarget.java new file mode 100644 index 0000000..639f53e --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/behavior/AttackTarget.java @@ -0,0 +1,22 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.test.behavior; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.test.IBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.test.IState; + +public class AttackTarget implements IBehavior { + private final CUnit attackingUnit; + private final CWidget targetUnit; + + public AttackTarget(final CUnit attackingUnit, final CWidget targetUnit) { + this.attackingUnit = attackingUnit; + this.targetUnit = targetUnit; + } + + @Override + public IState resolveNext() { + return null; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/behavior/MoveToPoint.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/behavior/MoveToPoint.java new file mode 100644 index 0000000..5e2cfa9 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/behavior/MoveToPoint.java @@ -0,0 +1,45 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.test.behavior; + +import java.awt.Point; +import java.awt.geom.Point2D; +import java.awt.geom.Point2D.Float; +import java.util.List; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.test.IBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.test.state.MoveState; + +public class MoveToPoint implements IBehavior { + + private final CSimulation simulation; + private final CUnit unit; + private final Point point; + private final MoveState moveState; + private final List waypointList; + + public MoveToPoint(final CSimulation simulation, final CUnit unit, final Point point) { + this.simulation = simulation; + this.unit = unit; + this.point = point; + this.waypointList = this.simulation.findNaiveSlowPath(unit, null, unit.getX(), unit.getY(), + new Point2D.Float(point.x, point.y), unit.getUnitType().getMovementType(), + unit.getUnitType().getCollisionSize(), true); + this.moveState = new MoveState(); + this.unit.setBehavior(this); + this.unit.setState(this.moveState); + resolveNext(); + } + + @Override + public void resolveNext() { + if (this.waypointList.isEmpty()) { + this.unit.setState(state); + } + else { + final Float firstWaypoint = this.waypointList.remove(0); + this.unit.setState(this.moveState.reset(this, this.unit, firstWaypoint.x, firstWaypoint.y)); + } + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/state/MoveState.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/state/MoveState.java new file mode 100644 index 0000000..b1758db --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/state/MoveState.java @@ -0,0 +1,28 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.test.state; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.test.IBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.test.IState; + +public class MoveState implements IState { + public IBehavior behavior; + public CUnit unit; + public float targetX; + public float targetY; + + public MoveState reset(final IBehavior behavior, final CUnit unit, final float targetX, final float targetY) { + this.behavior = behavior; + this.unit = unit; + this.targetX = targetX; + this.targetY = targetY; + return this; + } + + @Override + public void execute() { + final float dx = this.targetX - this.unit.getX(); + final float dy = this.targetY - this.unit.getY(); + this.unit.setX(this.unit.getX(), collision); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java index ea07743..0052226 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java @@ -14,6 +14,9 @@ public interface SimulationRenderController { CAttackProjectile createAttackProjectile(CSimulation simulation, float launchX, float launchY, float launchFacing, CUnit source, CUnitAttackMissile attack, CWidget target, float damage, int bounceIndex); + CUnit createUnit(CSimulation simulation, final War3ID typeId, final int playerIndex, final float x, final float y, + final float facing); + void createInstantAttackEffect(CSimulation cSimulation, CUnit source, CUnitAttackInstant attack, CWidget target); void spawnUnitDamageSound(CUnit damagedUnit, final String weaponSound, String armorType); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java index 9c7280f..433fd21 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java @@ -73,954 +73,990 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.StringMsgAbili import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.CommandCardCommandListener; public class MeleeUI implements CUnitStateListener, CommandButtonListener, CommandCardCommandListener { - public static final float DEFAULT_COMMAND_CARD_ICON_WIDTH = 0.039f; - public static final float DEFAULT_COMMAND_CARD_ICON_PRESSED_WIDTH = 0.037f; - private static final int COMMAND_CARD_WIDTH = 4; - private static final int COMMAND_CARD_HEIGHT = 3; - - private static final Vector2 screenCoordsVector = new Vector2(); - private static final Vector3 clickLocationTemp = new Vector3(); - private static final Vector2 clickLocationTemp2 = new Vector2(); - private final DataSource dataSource; - private final ExtendViewport uiViewport; - private final FreeTypeFontGenerator fontGenerator; - private final Scene uiScene; - private final Scene portraitScene; - private final GameCameraManager cameraManager; - private final War3MapViewer war3MapViewer; - private final RootFrameListener rootFrameListener; - private GameUI rootFrame; - private UIFrame consoleUI; - private UIFrame resourceBar; - private StringFrame resourceBarGoldText; - private StringFrame resourceBarLumberText; - private StringFrame resourceBarSupplyText; - private StringFrame resourceBarUpkeepText; - private SpriteFrame timeIndicator; - private UIFrame unitPortrait; - private StringFrame unitLifeText; - private StringFrame unitManaText; - private Portrait portrait; - private final Rectangle tempRect = new Rectangle(); - private final Vector2 projectionTemp1 = new Vector2(); - private final Vector2 projectionTemp2 = new Vector2(); - private UIFrame simpleInfoPanelUnitDetail; - private StringFrame simpleNameValue; - private StringFrame simpleClassValue; - private StringFrame simpleBuildingActionLabel; - private UIFrame attack1Icon; - private TextureFrame attack1IconBackdrop; - private StringFrame attack1InfoPanelIconValue; - private StringFrame attack1InfoPanelIconLevel; - private UIFrame attack2Icon; - private TextureFrame attack2IconBackdrop; - private StringFrame attack2InfoPanelIconValue; - private StringFrame attack2InfoPanelIconLevel; - private UIFrame armorIcon; - private TextureFrame armorIconBackdrop; - private StringFrame armorInfoPanelIconValue; - private StringFrame armorInfoPanelIconLevel; - private InfoPanelIconBackdrops damageBackdrops; - private InfoPanelIconBackdrops defenseBackdrops; - - private final CommandCardIcon[][] commandCard = new CommandCardIcon[COMMAND_CARD_HEIGHT][COMMAND_CARD_WIDTH]; - - private RenderUnit selectedUnit; - private final List subMenuOrderIdStack = new ArrayList<>(); - - // TODO remove this & replace with FDF - private final Texture activeButtonTexture; - private UIFrame inventoryCover; - private SpriteFrame cursorFrame; - private MeleeUIMinimap meleeUIMinimap; - private final CPlayerUnitOrderListener unitOrderListener; - private StringFrame errorMessageFrame; - - private CAbilityView activeCommand; - private int activeCommandOrderId; - private RenderUnit activeCommandUnit; - - private int selectedSoundCount = 0; - private final ActiveCommandUnitTargetFilter activeCommandUnitTargetFilter; - - // TODO these corrections are used for old hardcoded UI stuff, we should - // probably remove them later - private final float widthRatioCorrection; - private final float heightRatioCorrection; - private CommandCardIcon mouseDownUIFrame; - - public MeleeUI(final DataSource dataSource, final ExtendViewport uiViewport, - final FreeTypeFontGenerator fontGenerator, final Scene uiScene, final Scene portraitScene, - final CameraPreset[] cameraPresets, final CameraRates cameraRates, final War3MapViewer war3MapViewer, - final RootFrameListener rootFrameListener, final CPlayerUnitOrderListener unitOrderListener) { - this.dataSource = dataSource; - this.uiViewport = uiViewport; - this.fontGenerator = fontGenerator; - this.uiScene = uiScene; - this.portraitScene = portraitScene; - this.war3MapViewer = war3MapViewer; - this.rootFrameListener = rootFrameListener; - this.unitOrderListener = unitOrderListener; - - this.cameraManager = new GameCameraManager(cameraPresets, cameraRates); - - this.cameraManager.setupCamera(war3MapViewer.worldScene); - if (this.war3MapViewer.startLocations[0] != null) { - this.cameraManager.target.x = this.war3MapViewer.startLocations[0].x; - this.cameraManager.target.y = this.war3MapViewer.startLocations[0].y; - } - - this.activeButtonTexture = ImageUtils.getBLPTexture(war3MapViewer.mapMpq, - "UI\\Widgets\\Console\\Human\\CommandButton\\human-activebutton.blp"); - this.activeCommandUnitTargetFilter = new ActiveCommandUnitTargetFilter(); - this.widthRatioCorrection = this.uiViewport.getMinWorldWidth() / 1600f; - this.heightRatioCorrection = this.uiViewport.getMinWorldHeight() / 1200f; - - } - - private MeleeUIMinimap createMinimap(final War3MapViewer war3MapViewer) { - final Rectangle minimapDisplayArea = new Rectangle(18.75f * this.widthRatioCorrection, - 13.75f * this.heightRatioCorrection, 278.75f * this.widthRatioCorrection, - 276.25f * this.heightRatioCorrection); - Texture minimapTexture = null; - if (war3MapViewer.dataSource.has("war3mapMap.tga")) { - try { - minimapTexture = ImageUtils.getTextureNoColorCorrection(TgaFile.readTGA("war3mapMap.tga", - war3MapViewer.dataSource.getResourceAsStream("war3mapMap.tga"))); - } catch (final IOException e) { - System.err.println("Could not load minimap TGA file"); - e.printStackTrace(); - } - } else if (war3MapViewer.dataSource.has("war3mapMap.blp")) { - try { - minimapTexture = ImageUtils - .getTexture(ImageIO.read(war3MapViewer.dataSource.getResourceAsStream("war3mapMap.blp"))); - } catch (final IOException e) { - System.err.println("Could not load minimap BLP file"); - e.printStackTrace(); - } - } - final Texture[] teamColors = new Texture[WarsmashConstants.MAX_PLAYERS]; - for (int i = 0; i < teamColors.length; i++) { - teamColors[i] = ImageUtils.getBLPTexture(war3MapViewer.dataSource, - "ReplaceableTextures\\" + ReplaceableIds.getPathString(1) + ReplaceableIds.getIdString(i) + ".blp"); - } - final Rectangle playableMapArea = war3MapViewer.terrain.getPlayableMapArea(); - return new MeleeUIMinimap(minimapDisplayArea, playableMapArea, minimapTexture, teamColors); - } - - /** - * Called "main" because this was originally written in JASS so that maps could - * override it, and I may convert it back to the JASS at some point. - */ - public void main() { - // ================================= - // Load skins and templates - // ================================= - this.rootFrame = new GameUI(this.dataSource, GameUI.loadSkin(this.dataSource, 0), this.uiViewport, - this.fontGenerator, this.uiScene, this.war3MapViewer); - this.rootFrameListener.onCreate(this.rootFrame); - try { - this.rootFrame.loadTOCFile("UI\\FrameDef\\FrameDef.toc"); - } catch (final IOException exc) { - throw new IllegalStateException("Unable to load FrameDef.toc", exc); - } - try { - this.rootFrame.loadTOCFile("UI\\FrameDef\\SmashFrameDef.toc"); - } catch (final IOException exc) { - throw new IllegalStateException("Unable to load SmashFrameDef.toc", exc); - } - this.damageBackdrops = new InfoPanelIconBackdrops(CAttackType.values(), this.rootFrame, "Damage", "Neutral"); - this.defenseBackdrops = new InfoPanelIconBackdrops(CDefenseType.values(), this.rootFrame, "Armor", "Neutral"); - - // ================================= - // Load major UI components - // ================================= - // Console UI is the background with the racial theme - this.consoleUI = this.rootFrame.createSimpleFrame("ConsoleUI", this.rootFrame, 0); - this.consoleUI.setSetAllPoints(true); - - // Resource bar is a 3 part bar with Gold, Lumber, and Food. - // Its template does not specify where to put it, so we must - // put it in the "TOPRIGHT" corner. - this.resourceBar = this.rootFrame.createSimpleFrame("ResourceBarFrame", this.consoleUI, 0); - this.resourceBar.addSetPoint(new SetPoint(FramePoint.TOPRIGHT, this.consoleUI, FramePoint.TOPRIGHT, 0, 0)); - this.resourceBarGoldText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarGoldText", 0); - this.resourceBarGoldText.setText("500"); - this.resourceBarLumberText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarLumberText", 0); - this.resourceBarLumberText.setText("150"); - this.resourceBarSupplyText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarSupplyText", 0); - this.resourceBarSupplyText.setText("12/100"); - this.resourceBarUpkeepText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarUpkeepText", 0); - this.resourceBarUpkeepText.setText("No Upkeep"); - this.resourceBarUpkeepText.setColor(Color.GREEN); - - // Create the Time Indicator (clock) - this.timeIndicator = (SpriteFrame) this.rootFrame.createFrame("TimeOfDayIndicator", this.rootFrame, 0, 0); - this.timeIndicator.setSequence(0); // play the stand - this.timeIndicator.setAnimationSpeed(0.0f); // do not advance automatically - - // Create the unit portrait stuff - this.portrait = new Portrait(this.war3MapViewer, this.portraitScene); - positionPortrait(); - this.unitPortrait = this.rootFrame.createSimpleFrame("UnitPortrait", this.consoleUI, 0); - this.unitLifeText = (StringFrame) this.rootFrame.getFrameByName("UnitPortraitHitPointText", 0); - this.unitManaText = (StringFrame) this.rootFrame.getFrameByName("UnitPortraitManaPointText", 0); - - this.simpleInfoPanelUnitDetail = this.rootFrame.createSimpleFrame("SimpleInfoPanelUnitDetail", this.consoleUI, - 0); - this.simpleInfoPanelUnitDetail - .addAnchor(new AnchorDefinition(FramePoint.BOTTOM, 0, GameUI.convertY(this.uiViewport, 0.0f))); - this.simpleInfoPanelUnitDetail.setWidth(GameUI.convertY(this.uiViewport, 0.180f)); - this.simpleInfoPanelUnitDetail.setHeight(GameUI.convertY(this.uiViewport, 0.105f)); - this.simpleNameValue = (StringFrame) this.rootFrame.getFrameByName("SimpleNameValue", 0); - this.simpleClassValue = (StringFrame) this.rootFrame.getFrameByName("SimpleClassValue", 0); - this.simpleBuildingActionLabel = (StringFrame) this.rootFrame.getFrameByName("SimpleBuildingActionLabel", 0); - - this.attack1Icon = this.rootFrame.createSimpleFrame("SimpleInfoPanelIconDamage", this.simpleInfoPanelUnitDetail, - 0); - this.attack1Icon.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, - FramePoint.TOPLEFT, 0, GameUI.convertY(this.uiViewport, -0.030125f))); - this.attack1IconBackdrop = (TextureFrame) this.rootFrame.getFrameByName("InfoPanelIconBackdrop", 0); - this.attack1InfoPanelIconValue = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconValue", 0); - this.attack1InfoPanelIconLevel = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconLevel", 0); - - this.attack2Icon = this.rootFrame.createSimpleFrame("SimpleInfoPanelIconDamage", this.simpleInfoPanelUnitDetail, - 1); - this.attack2Icon - .addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, FramePoint.TOPLEFT, - GameUI.convertX(this.uiViewport, 0.1f), GameUI.convertY(this.uiViewport, -0.030125f))); - this.attack2IconBackdrop = (TextureFrame) this.rootFrame.getFrameByName("InfoPanelIconBackdrop", 1); - this.attack2InfoPanelIconValue = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconValue", 1); - this.attack2InfoPanelIconLevel = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconLevel", 1); - - this.armorIcon = this.rootFrame.createSimpleFrame("SimpleInfoPanelIconArmor", this.simpleInfoPanelUnitDetail, - 1); - this.armorIcon.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, FramePoint.TOPLEFT, - GameUI.convertX(this.uiViewport, 0f), GameUI.convertY(this.uiViewport, -0.06025f))); - this.armorIconBackdrop = (TextureFrame) this.rootFrame.getFrameByName("InfoPanelIconBackdrop", 0); - this.armorInfoPanelIconValue = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconValue", 0); - this.armorInfoPanelIconLevel = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconLevel", 0); - - this.inventoryCover = this.rootFrame.createSimpleFrame("SmashConsoleInventoryCover", this.rootFrame, 0); - - this.errorMessageFrame = this.rootFrame.createStringFrame("SmashErrorMessageFrame", this.consoleUI, - new Color(0xFFFFCC00), TextJustify.LEFT, TextJustify.MIDDLE, 0.014f); - this.errorMessageFrame.addAnchor(new AnchorDefinition(FramePoint.BOTTOMLEFT, - GameUI.convertX(this.uiViewport, 0.275f), GameUI.convertY(this.uiViewport, 0.275f))); - this.errorMessageFrame.setWidth(GameUI.convertX(this.uiViewport, 0.25f)); - - int commandButtonIndex = 0; - for (int j = 0; j < COMMAND_CARD_HEIGHT; j++) { - for (int i = 0; i < COMMAND_CARD_WIDTH; i++) { - final CommandCardIcon commandCardIcon = new CommandCardIcon("SmashCommandButton_" + commandButtonIndex, - this.rootFrame, this); - this.rootFrame.add(commandCardIcon); - final TextureFrame iconFrame = this.rootFrame.createTextureFrame( - "SmashCommandButton_" + (commandButtonIndex) + "_Icon", this.rootFrame, false, null); - final TextureFrame activeHighlightFrame = this.rootFrame.createTextureFrame( - "SmashCommandButton_" + (commandButtonIndex) + "_ActiveHighlight", this.rootFrame, true, null, - FilterMode.ADDALPHA); - final SpriteFrame cooldownFrame = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", - "SmashCommandButton_" + (commandButtonIndex) + "_Cooldown", this.rootFrame, "", 0); - final SpriteFrame autocastFrame = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", - "SmashCommandButton_" + (commandButtonIndex) + "_Autocast", this.rootFrame, "", 0); - commandCardIcon.addAnchor(new AnchorDefinition(FramePoint.BOTTOMLEFT, - GameUI.convertX(this.uiViewport, 0.6175f + (0.0434f * i)), - GameUI.convertY(this.uiViewport, 0.095f - (0.044f * j)))); - commandCardIcon.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); - commandCardIcon.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); - iconFrame.addSetPoint(new SetPoint(FramePoint.CENTER, commandCardIcon, FramePoint.CENTER, 0, 0)); - iconFrame.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); - iconFrame.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); - iconFrame.setTexture(ImageUtils.DEFAULT_ICON_PATH, this.rootFrame); - activeHighlightFrame - .addSetPoint(new SetPoint(FramePoint.CENTER, commandCardIcon, FramePoint.CENTER, 0, 0)); - activeHighlightFrame.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); - activeHighlightFrame.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); - activeHighlightFrame.setTexture("CommandButtonActiveHighlight", this.rootFrame); - cooldownFrame.addSetPoint(new SetPoint(FramePoint.CENTER, commandCardIcon, FramePoint.CENTER, 0, 0)); - this.rootFrame.setSpriteFrameModel(cooldownFrame, this.rootFrame.getSkinField("CommandButtonCooldown")); - cooldownFrame.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); - cooldownFrame.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); - autocastFrame.addSetPoint(new SetPoint(FramePoint.CENTER, commandCardIcon, FramePoint.CENTER, 0, 0)); - this.rootFrame.setSpriteFrameModel(autocastFrame, this.rootFrame.getSkinField("CommandButtonAutocast")); - autocastFrame.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); - autocastFrame.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); - commandCardIcon.set(iconFrame, activeHighlightFrame, cooldownFrame, autocastFrame); - this.commandCard[j][i] = commandCardIcon; - commandCardIcon.setCommandButton(null); - commandButtonIndex++; - } - } - - this.cursorFrame = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", "SmashCursorFrame", this.rootFrame, - "", 0); - this.rootFrame.setSpriteFrameModel(this.cursorFrame, this.rootFrame.getSkinField("Cursor")); - this.cursorFrame.setSequence("Normal"); - this.cursorFrame.setZDepth(1.0f); - Gdx.input.setCursorCatched(true); - - this.meleeUIMinimap = createMinimap(this.war3MapViewer); - - this.rootFrame.positionBounds(this.uiViewport); - selectUnit(null); - } - - @Override - public void startUsingAbility(final int abilityHandleId, final int orderId) { - // TODO not O(N) - CAbilityView abilityToUse = null; - for (final CAbility ability : this.selectedUnit.getSimulationUnit().getAbilities()) { - if (ability.getHandleId() == abilityHandleId) { - abilityToUse = ability; - break; - } - } - if (abilityToUse != null) { - final StringMsgAbilityActivationReceiver stringMsgActivationReceiver = StringMsgAbilityActivationReceiver - .getInstance().reset(); - abilityToUse.checkCanUse(this.war3MapViewer.simulation, this.selectedUnit.getSimulationUnit(), orderId, - stringMsgActivationReceiver); - if (!stringMsgActivationReceiver.isUseOk()) { - showCommandError(stringMsgActivationReceiver.getMessage()); - } else { - final BooleanAbilityTargetCheckReceiver noTargetReceiver = BooleanAbilityTargetCheckReceiver - .getInstance().reset(); - abilityToUse.checkCanTargetNoTarget(this.war3MapViewer.simulation, - this.selectedUnit.getSimulationUnit(), orderId, noTargetReceiver); - if (noTargetReceiver.isTargetable()) { - this.unitOrderListener.issueImmediateOrder(this.selectedUnit.getSimulationUnit().getHandleId(), - abilityHandleId, orderId, isShiftDown()); - } else { - this.activeCommand = abilityToUse; - this.activeCommandOrderId = orderId; - this.activeCommandUnit = this.selectedUnit; - clearAndRepopulateCommandCard(); - } - } - } else { - this.unitOrderListener.issueImmediateOrder(this.selectedUnit.getSimulationUnit().getHandleId(), - abilityHandleId, orderId, isShiftDown()); - } - } - - @Override - public void openMenu(final int orderId) { - if (orderId == 0) { - subMenuOrderIdStack.clear(); - this.activeCommandUnit = null; - this.activeCommand = null; - this.activeCommandOrderId = -1; - } else { - this.subMenuOrderIdStack.add(orderId); - } - clearAndRepopulateCommandCard(); - } - - public void showCommandError(final String message) { - this.errorMessageFrame.setText(message); - } - - public void update(final float deltaTime) { - this.portrait.update(); - - int mouseX = Gdx.input.getX(); - int mouseY = Gdx.input.getY(); - final int minX = this.uiViewport.getScreenX(); - final int maxX = minX + this.uiViewport.getScreenWidth(); - final int minY = this.uiViewport.getScreenY(); - final int maxY = minY + this.uiViewport.getScreenHeight(); - final boolean left = mouseX <= (minX + 3); - final boolean right = mouseX >= (maxX - 3); - final boolean up = mouseY <= (minY + 3); - final boolean down = mouseY >= (maxY - 3); - this.cameraManager.applyVelocity(deltaTime, up, down, left, right); - - mouseX = Math.max(minX, Math.min(maxX, mouseX)); - mouseY = Math.max(minY, Math.min(maxY, mouseY)); - if (Gdx.input.isCursorCatched()) { - Gdx.input.setCursorPosition(mouseX, mouseY); - } - - screenCoordsVector.set(mouseX, mouseY); - this.uiViewport.unproject(screenCoordsVector); - this.cursorFrame.setFramePointX(FramePoint.LEFT, screenCoordsVector.x); - this.cursorFrame.setFramePointY(FramePoint.BOTTOM, screenCoordsVector.y); - - if (this.activeCommand != null) { - this.cursorFrame.setSequence("Target"); - } else if (down) { - if (left) { - this.cursorFrame.setSequence("Scroll Down Left"); - } else if (right) { - this.cursorFrame.setSequence("Scroll Down Right"); - } else { - this.cursorFrame.setSequence("Scroll Down"); - } - } else if (up) { - if (left) { - this.cursorFrame.setSequence("Scroll Up Left"); - } else if (right) { - this.cursorFrame.setSequence("Scroll Up Right"); - } else { - this.cursorFrame.setSequence("Scroll Up"); - } - } else if (left) { - this.cursorFrame.setSequence("Scroll Left"); - } else if (right) { - this.cursorFrame.setSequence("Scroll Right"); - } else { - this.cursorFrame.setSequence("Normal"); - } - final float groundHeight = Math.max( - this.war3MapViewer.terrain.getGroundHeight(this.cameraManager.target.x, this.cameraManager.target.y), - this.war3MapViewer.terrain.getWaterHeight(this.cameraManager.target.x, this.cameraManager.target.y)); - this.cameraManager.updateTargetZ(groundHeight); - this.cameraManager.updateCamera(); - } - - public void render(final SpriteBatch batch, final BitmapFont font20, final GlyphLayout glyphLayout) { - this.rootFrame.render(batch, font20, glyphLayout); - if (this.selectedUnit != null) { - font20.setColor(Color.WHITE); - - } - - this.meleeUIMinimap.render(batch, this.war3MapViewer.units); - this.timeIndicator.setFrameByRatio(this.war3MapViewer.simulation.getGameTimeOfDay() - / this.war3MapViewer.simulation.getGameplayConstants().getGameDayHours()); - } - - public void portraitTalk() { - this.portrait.talk(); - } - - private final class ActiveCommandUnitTargetFilter implements CUnitFilterFunction { - @Override - public boolean call(final CUnit unit) { - final BooleanAbilityTargetCheckReceiver targetReceiver = BooleanAbilityTargetCheckReceiver - .getInstance(); - MeleeUI.this.activeCommand.checkCanTarget(MeleeUI.this.war3MapViewer.simulation, - MeleeUI.this.activeCommandUnit.getSimulationUnit(), MeleeUI.this.activeCommandOrderId, unit, - targetReceiver); - return targetReceiver.isTargetable(); - } - } - - private static final class Portrait { - private MdxComplexInstance modelInstance; - private final PortraitCameraManager portraitCameraManager; - private final Scene portraitScene; - - public Portrait(final War3MapViewer war3MapViewer, final Scene portraitScene) { - this.portraitScene = portraitScene; - this.portraitCameraManager = new PortraitCameraManager(); - this.portraitCameraManager.setupCamera(this.portraitScene); - this.portraitScene.camera.viewport(new Rectangle(100, 0, 6400, 48)); - } - - public void update() { - this.portraitCameraManager.updateCamera(); - if ((this.modelInstance != null) - && (this.modelInstance.sequenceEnded || (this.modelInstance.sequence == -1))) { - SequenceUtils.randomSequence(this.modelInstance, PrimaryTag.PORTRAIT, SequenceUtils.EMPTY, true); - } - } - - public void talk() { - SequenceUtils.randomSequence(this.modelInstance, PrimaryTag.PORTRAIT, SequenceUtils.TALK, true); - } - - public void setSelectedUnit(final RenderUnit unit) { - if (unit == null) { - if (this.modelInstance != null) { - this.portraitScene.removeInstance(this.modelInstance); - } - this.modelInstance = null; - this.portraitCameraManager.setModelInstance(null, null); - } else { - final MdxModel portraitModel = unit.portraitModel; - if (portraitModel != null) { - if (this.modelInstance != null) { - this.portraitScene.removeInstance(this.modelInstance); - } - this.modelInstance = (MdxComplexInstance) portraitModel.addInstance(); - this.portraitCameraManager.setModelInstance(this.modelInstance, portraitModel); - this.modelInstance.setSequenceLoopMode(SequenceLoopMode.NEVER_LOOP); - this.modelInstance.setScene(this.portraitScene); - this.modelInstance.setVertexColor(unit.instance.vertexColor); - this.modelInstance.setTeamColor(unit.playerIndex); - } - } - } - } - - public void selectUnit(RenderUnit unit) { - this.subMenuOrderIdStack.clear(); - if ((unit != null) && unit.getSimulationUnit().isDead()) { - unit = null; - } - if (this.selectedUnit != null) { - this.selectedUnit.getSimulationUnit().removeStateListener(this); - } - this.portrait.setSelectedUnit(unit); - this.selectedUnit = unit; - clearCommandCard(); - if (unit == null) { - this.simpleNameValue.setText(""); - this.unitLifeText.setText(""); - this.unitManaText.setText(""); - this.simpleClassValue.setText(""); - this.simpleBuildingActionLabel.setText(""); - this.attack1Icon.setVisible(false); - this.attack2Icon.setVisible(false); - this.attack1InfoPanelIconLevel.setText(""); - this.attack2InfoPanelIconLevel.setText(""); - this.armorIcon.setVisible(false); - this.armorInfoPanelIconLevel.setText(""); - } else { - unit.getSimulationUnit().addStateListener(this); - this.simpleNameValue.setText(unit.getSimulationUnit().getUnitType().getName()); - String classText = null; - for (final CUnitClassification classification : unit.getSimulationUnit().getClassifications()) { - if ((classification == CUnitClassification.MECHANICAL) - && unit.getSimulationUnit().getUnitType().isBuilding()) { - // buildings dont display MECHANICAL - continue; - } - if (classification.getDisplayName() != null) { - classText = classification.getDisplayName(); - } - } - if (classText != null) { - this.simpleClassValue.setText(classText); - } else { - this.simpleClassValue.setText(""); - } - this.unitLifeText.setText(FastNumberFormat.formatWholeNumber(unit.getSimulationUnit().getLife()) + " / " - + unit.getSimulationUnit().getMaximumLife()); - final int maximumMana = unit.getSimulationUnit().getMaximumMana(); - if (maximumMana > 0) { - this.unitManaText.setText( - FastNumberFormat.formatWholeNumber(unit.getSimulationUnit().getMana()) + " / " + maximumMana); - } else { - this.unitManaText.setText(""); - } - this.simpleBuildingActionLabel.setText(""); - - final boolean anyAttacks = unit.getSimulationUnit().getUnitType().getAttacks().size() > 0; - final UIFrame localArmorIcon = this.armorIcon; - final TextureFrame localArmorIconBackdrop = this.armorIconBackdrop; - final StringFrame localArmorInfoPanelIconValue = this.armorInfoPanelIconValue; - if (anyAttacks) { - final CUnitAttack attackOne = unit.getSimulationUnit().getUnitType().getAttacks().get(0); - this.attack1Icon.setVisible(attackOne.isShowUI()); - this.attack1IconBackdrop.setTexture(this.damageBackdrops.getTexture(attackOne.getAttackType())); - this.attack1InfoPanelIconValue.setText(attackOne.getMinDamage() + " - " + attackOne.getMaxDamage()); - if (unit.getSimulationUnit().getUnitType().getAttacks().size() > 1) { - final CUnitAttack attackTwo = unit.getSimulationUnit().getUnitType().getAttacks().get(1); - this.attack2Icon.setVisible(attackTwo.isShowUI()); - this.attack2IconBackdrop.setTexture(this.damageBackdrops.getTexture(attackTwo.getAttackType())); - this.attack2InfoPanelIconValue.setText(attackTwo.getMinDamage() + " - " + attackTwo.getMaxDamage()); - } else { - this.attack2Icon.setVisible(false); - } - - this.armorIcon.addSetPoint( - new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, FramePoint.TOPLEFT, - GameUI.convertX(this.uiViewport, 0f), GameUI.convertY(this.uiViewport, -0.06025f))); - this.armorIcon.positionBounds(this.uiViewport); - } else { - this.attack1Icon.setVisible(false); - this.attack2Icon.setVisible(false); - - this.armorIcon.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, - FramePoint.TOPLEFT, 0, GameUI.convertY(this.uiViewport, -0.030125f))); - this.armorIcon.positionBounds(this.uiViewport); - } - - localArmorIcon.setVisible(true); - final Texture defenseTexture = this.defenseBackdrops - .getTexture(unit.getSimulationUnit().getUnitType().getDefenseType()); - if (defenseTexture == null) { - throw new RuntimeException( - unit.getSimulationUnit().getUnitType().getDefenseType() + " can't find texture!"); - } - localArmorIconBackdrop.setTexture(defenseTexture); - localArmorInfoPanelIconValue.setText(Integer.toString(unit.getSimulationUnit().getDefense())); - unit.populateCommandCard(this.war3MapViewer.simulation, this, this.war3MapViewer.getAbilityDataUI(), - getSubMenuOrderId()); - } - } - - private void clearCommandCard() { - for (int j = 0; j < COMMAND_CARD_HEIGHT; j++) { - for (int i = 0; i < COMMAND_CARD_WIDTH; i++) { - this.commandCard[j][i].setCommandButton(null); - } - } - } - - @Override - public void commandButton(final int buttonPositionX, final int buttonPositionY, final Texture icon, - final int abilityHandleId, final int orderId, final int autoCastId, final boolean active, - final boolean autoCastActive, final boolean menuButton) { - final int x = Math.max(0, Math.min(COMMAND_CARD_WIDTH - 1, buttonPositionX)); - final int y = Math.max(0, Math.min(COMMAND_CARD_HEIGHT - 1, buttonPositionY)); - this.commandCard[y][x].setCommandButtonData(icon, abilityHandleId, orderId, autoCastId, active, autoCastActive, - menuButton); - } - - public void resize(final Rectangle viewport) { - this.cameraManager.resize(viewport); - positionPortrait(); - } - - public void positionPortrait() { - this.projectionTemp1.x = 422 * this.widthRatioCorrection; - this.projectionTemp1.y = 57 * this.heightRatioCorrection; - this.projectionTemp2.x = (422 + 167) * this.widthRatioCorrection; - this.projectionTemp2.y = (57 + 170) * this.heightRatioCorrection; - this.uiViewport.project(this.projectionTemp1); - this.uiViewport.project(this.projectionTemp2); - - this.tempRect.x = this.projectionTemp1.x + this.uiViewport.getScreenX(); - this.tempRect.y = this.projectionTemp1.y + this.uiViewport.getScreenY(); - this.tempRect.width = this.projectionTemp2.x - this.projectionTemp1.x; - this.tempRect.height = this.projectionTemp2.y - this.projectionTemp1.y; - this.portrait.portraitScene.camera.viewport(this.tempRect); - } - - private static final class InfoPanelIconBackdrops { - private final Texture[] damageBackdropTextures; - - public InfoPanelIconBackdrops(final CodeKeyType[] attackTypes, final GameUI gameUI, final String prefix, - final String suffix) { - this.damageBackdropTextures = new Texture[attackTypes.length]; - for (int index = 0; index < attackTypes.length; index++) { - final CodeKeyType attackType = attackTypes[index]; - String skinLookupKey = "InfoPanelIcon" + prefix + attackType.getCodeKey() + suffix; - final Texture suffixTexture = gameUI.loadTexture(gameUI.getSkinField(skinLookupKey)); - if (suffixTexture != null) { - this.damageBackdropTextures[index] = suffixTexture; - } else { - skinLookupKey = "InfoPanelIcon" + prefix + attackType.getCodeKey(); - this.damageBackdropTextures[index] = gameUI.loadTexture(gameUI.getSkinField(skinLookupKey)); - } - } - } - - public Texture getTexture(final CodeKeyType attackType) { - if (attackType != null) { - final int ordinal = attackType.ordinal(); - if ((ordinal >= 0) && (ordinal < this.damageBackdropTextures.length)) { - return this.damageBackdropTextures[ordinal]; - } - } - return this.damageBackdropTextures[0]; - } - - private static String getSuffix(final CAttackType attackType) { - switch (attackType) { - case CHAOS: - return "Chaos"; - case HERO: - return "Hero"; - case MAGIC: - return "Magic"; - case NORMAL: - return "Normal"; - case PIERCE: - return "Pierce"; - case SIEGE: - return "Siege"; - case SPELLS: - return "Magic"; - case UNKNOWN: - return "Unknown"; - default: - throw new IllegalArgumentException("Unknown attack type: " + attackType); - } - - } - } - - @Override - public void lifeChanged() { - if (this.selectedUnit.getSimulationUnit().isDead()) { - selectUnit(null); - } else { - this.unitLifeText - .setText(FastNumberFormat.formatWholeNumber(this.selectedUnit.getSimulationUnit().getLife()) + " / " - + this.selectedUnit.getSimulationUnit().getMaximumLife()); - } - } - - @Override - public void ordersChanged(final int abilityHandleId, final int orderId) { - clearAndRepopulateCommandCard(); - } - - private void clearAndRepopulateCommandCard() { - clearCommandCard(); - final AbilityDataUI abilityDataUI = this.war3MapViewer.getAbilityDataUI(); - final int menuOrderId = getSubMenuOrderId(); - if (this.activeCommand != null) { - final IconUI cancelUI = abilityDataUI.getCancelUI(); - this.commandButton(cancelUI.getButtonPositionX(), cancelUI.getButtonPositionY(), cancelUI.getIcon(), 0, - menuOrderId, 0, false, false, true); - } else { - if (menuOrderId != 0) { - final int exitOrderId = this.subMenuOrderIdStack.size() > 1 - ? this.subMenuOrderIdStack.get(this.subMenuOrderIdStack.size() - 2) - : 0; - final IconUI cancelUI = abilityDataUI.getCancelUI(); - this.commandButton(cancelUI.getButtonPositionX(), cancelUI.getButtonPositionY(), cancelUI.getIcon(), 0, - exitOrderId, 0, false, false, true); - } - this.selectedUnit.populateCommandCard(this.war3MapViewer.simulation, this, abilityDataUI, menuOrderId); - } - } - - private int getSubMenuOrderId() { - return this.subMenuOrderIdStack.isEmpty() ? 0 - : this.subMenuOrderIdStack.get(this.subMenuOrderIdStack.size() - 1); - } - - public RenderUnit getSelectedUnit() { - return this.selectedUnit; - } - - public boolean keyDown(final int keycode) { - return this.cameraManager.keyDown(keycode); - } - - public boolean keyUp(final int keycode) { - return this.cameraManager.keyUp(keycode); - } - - public void scrolled(final int amount) { - this.cameraManager.scrolled(amount); - } - - public boolean touchDown(final int screenX, final int screenY, final float worldScreenY, final int button) { - screenCoordsVector.set(screenX, screenY); - this.uiViewport.unproject(screenCoordsVector); - if (this.meleeUIMinimap.containsMouse(screenCoordsVector.x, screenCoordsVector.y)) { - final Vector2 worldPoint = this.meleeUIMinimap.getWorldPointFromScreen(screenCoordsVector.x, - screenCoordsVector.y); - this.cameraManager.target.x = worldPoint.x; - this.cameraManager.target.y = worldPoint.y; - return true; - } - final UIFrame clickedUIFrame = this.rootFrame.touchDown(screenCoordsVector.x, screenCoordsVector.y, button); - if (clickedUIFrame == null) { - // try to interact with world - if (this.activeCommand != null) { - if (button == Input.Buttons.RIGHT) { - this.activeCommandUnit = null; - this.activeCommand = null; - this.activeCommandOrderId = -1; - clearAndRepopulateCommandCard(); - } else { - final RenderUnit rayPickUnit = this.war3MapViewer.rayPickUnit(screenX, worldScreenY, - this.activeCommandUnitTargetFilter); - final boolean shiftDown = isShiftDown(); - if (rayPickUnit != null) { - this.unitOrderListener.issueTargetOrder( - this.activeCommandUnit.getSimulationUnit().getHandleId(), - this.activeCommand.getHandleId(), this.activeCommandOrderId, - rayPickUnit.getSimulationUnit().getHandleId(), shiftDown); - if (getSelectedUnit().soundset.yesAttack - .playUnitResponse(this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { - portraitTalk(); - } - this.selectedSoundCount = 0; - if (!shiftDown) { - this.activeCommandUnit = null; - this.activeCommand = null; - this.activeCommandOrderId = -1; - clearAndRepopulateCommandCard(); - } - } else { - this.war3MapViewer.getClickLocation(clickLocationTemp, screenX, (int) worldScreenY); - clickLocationTemp2.set(clickLocationTemp.x, clickLocationTemp.y); - - this.activeCommand.checkCanTarget(this.war3MapViewer.simulation, - this.activeCommandUnit.getSimulationUnit(), this.activeCommandOrderId, - clickLocationTemp2, PointAbilityTargetCheckReceiver.INSTANCE); - final Vector2 target = PointAbilityTargetCheckReceiver.INSTANCE.getTarget(); - if (target != null) { - if (this.activeCommand instanceof CAbilityAttack) { - this.war3MapViewer.showConfirmation(clickLocationTemp, 1, 0, 0); - } else { - this.war3MapViewer.showConfirmation(clickLocationTemp, 0, 1, 0); - } - this.unitOrderListener.issuePointOrder( - this.activeCommandUnit.getSimulationUnit().getHandleId(), - this.activeCommand.getHandleId(), this.activeCommandOrderId, clickLocationTemp2.x, - clickLocationTemp2.y, shiftDown); - if (getSelectedUnit().soundset.yes - .playUnitResponse(this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { - portraitTalk(); - } - this.selectedSoundCount = 0; - if (!shiftDown) { - this.activeCommandUnit = null; - this.activeCommand = null; - this.activeCommandOrderId = -1; - clearAndRepopulateCommandCard(); - } - - } - } - } - } else { - if (button == Input.Buttons.RIGHT) { - if (getSelectedUnit() != null) { - final RenderUnit rayPickUnit = this.war3MapViewer.rayPickUnit(screenX, worldScreenY); - if ((rayPickUnit != null) && !rayPickUnit.getSimulationUnit().isDead()) { - boolean ordered = false; - for (final RenderUnit unit : this.war3MapViewer.selected) { - for (final CAbility ability : unit.getSimulationUnit().getAbilities()) { - ability.checkCanTarget(this.war3MapViewer.simulation, unit.getSimulationUnit(), - OrderIds.smart, rayPickUnit.getSimulationUnit(), - CWidgetAbilityTargetCheckReceiver.INSTANCE); - final CWidget targetWidget = CWidgetAbilityTargetCheckReceiver.INSTANCE.getTarget(); - if (targetWidget != null) { - this.unitOrderListener.issueTargetOrder(unit.getSimulationUnit().getHandleId(), - ability.getHandleId(), OrderIds.smart, targetWidget.getHandleId(), - isShiftDown()); - ordered = true; - } - } - - } - if (ordered) { - if (getSelectedUnit().soundset.yesAttack.playUnitResponse( - this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { - portraitTalk(); - } - this.selectedSoundCount = 0; - } - } else { - this.war3MapViewer.getClickLocation(clickLocationTemp, screenX, (int) worldScreenY); - this.war3MapViewer.showConfirmation(clickLocationTemp, 0, 1, 0); - clickLocationTemp2.set(clickLocationTemp.x, clickLocationTemp.y); - - boolean ordered = false; - for (final RenderUnit unit : this.war3MapViewer.selected) { - for (final CAbility ability : unit.getSimulationUnit().getAbilities()) { - ability.checkCanUse(this.war3MapViewer.simulation, unit.getSimulationUnit(), - OrderIds.smart, BooleanAbilityActivationReceiver.INSTANCE); - if (BooleanAbilityActivationReceiver.INSTANCE.isOk()) { - ability.checkCanTarget(this.war3MapViewer.simulation, unit.getSimulationUnit(), - OrderIds.smart, clickLocationTemp2, - PointAbilityTargetCheckReceiver.INSTANCE); - final Vector2 target = PointAbilityTargetCheckReceiver.INSTANCE.getTarget(); - if (target != null) { - this.unitOrderListener.issuePointOrder( - unit.getSimulationUnit().getHandleId(), ability.getHandleId(), - OrderIds.smart, clickLocationTemp2.x, clickLocationTemp2.y, - isShiftDown()); - ordered = true; - } - } - } - - } - - if (ordered) { - if (getSelectedUnit().soundset.yes.playUnitResponse( - this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { - portraitTalk(); - } - this.selectedSoundCount = 0; - } - } - } - } else { - final List selectedUnits = this.war3MapViewer.selectUnit(screenX, worldScreenY, false); - if (!selectedUnits.isEmpty()) { - final RenderUnit unit = selectedUnits.get(0); - final boolean selectionChanged = getSelectedUnit() != unit; - boolean playedNewSound = false; - if (selectionChanged) { - this.selectedSoundCount = 0; - } - if (unit.soundset != null) { - UnitSound ackSoundToPlay = unit.soundset.what; - final int pissedSoundCount = unit.soundset.pissed.getSoundCount(); - int soundIndex; - if ((this.selectedSoundCount >= 3) && (pissedSoundCount > 0)) { - soundIndex = this.selectedSoundCount - 3; - ackSoundToPlay = unit.soundset.pissed; - } else { - soundIndex = (int) (Math.random() * ackSoundToPlay.getSoundCount()); - } - if (ackSoundToPlay.playUnitResponse(this.war3MapViewer.worldScene.audioContext, unit, - soundIndex)) { - this.selectedSoundCount++; - if ((this.selectedSoundCount - 3) >= pissedSoundCount) { - this.selectedSoundCount = 0; - } - playedNewSound = true; - } - } - if (selectionChanged) { - selectUnit(unit); - } - if (playedNewSound) { - portraitTalk(); - } - } else { - selectUnit(null); - } - } - } - } else { - if (clickedUIFrame instanceof CommandCardIcon) { - this.mouseDownUIFrame = (CommandCardIcon) clickedUIFrame; - this.mouseDownUIFrame.mouseDown(this.uiViewport); - } - } - return false; - } - - public boolean touchUp(final int screenX, final int screenY, final float worldScreenY, final int button) { - screenCoordsVector.set(screenX, screenY); - this.uiViewport.unproject(screenCoordsVector); - final UIFrame clickedUIFrame = this.rootFrame.touchUp(screenCoordsVector.x, screenCoordsVector.y, button); - if (this.mouseDownUIFrame != null) { - if (clickedUIFrame == this.mouseDownUIFrame) { - this.mouseDownUIFrame.onClick(button); - this.war3MapViewer.getUiSounds().getSound("InterfaceClick").play(this.uiScene.audioContext, 0, 0); - } - this.mouseDownUIFrame.mouseUp(this.uiViewport); - } - this.mouseDownUIFrame = null; - return false; - } - - private static boolean isShiftDown() { - return Gdx.input.isKeyPressed(Input.Keys.SHIFT_LEFT) || Gdx.input.isKeyPressed(Input.Keys.SHIFT_RIGHT); - } - - public boolean touchDragged(final int screenX, final int screenY, final float worldScreenY, final int pointer) { - screenCoordsVector.set(screenX, screenY); - this.uiViewport.unproject(screenCoordsVector); - - if (this.meleeUIMinimap.containsMouse(screenCoordsVector.x, screenCoordsVector.y)) { - final Vector2 worldPoint = this.meleeUIMinimap.getWorldPointFromScreen(screenCoordsVector.x, - screenCoordsVector.y); - this.cameraManager.target.x = worldPoint.x; - this.cameraManager.target.y = worldPoint.y; - } - return false; - } - - public float getHeightRatioCorrection() { - return this.heightRatioCorrection; - } + public static final float DEFAULT_COMMAND_CARD_ICON_WIDTH = 0.039f; + public static final float DEFAULT_COMMAND_CARD_ICON_PRESSED_WIDTH = 0.037f; + private static final int COMMAND_CARD_WIDTH = 4; + private static final int COMMAND_CARD_HEIGHT = 3; + + private static final Vector2 screenCoordsVector = new Vector2(); + private static final Vector3 clickLocationTemp = new Vector3(); + private static final Vector2 clickLocationTemp2 = new Vector2(); + private final DataSource dataSource; + private final ExtendViewport uiViewport; + private final FreeTypeFontGenerator fontGenerator; + private final Scene uiScene; + private final Scene portraitScene; + private final GameCameraManager cameraManager; + private final War3MapViewer war3MapViewer; + private final RootFrameListener rootFrameListener; + private GameUI rootFrame; + private UIFrame consoleUI; + private UIFrame resourceBar; + private StringFrame resourceBarGoldText; + private StringFrame resourceBarLumberText; + private StringFrame resourceBarSupplyText; + private StringFrame resourceBarUpkeepText; + private SpriteFrame timeIndicator; + private UIFrame unitPortrait; + private StringFrame unitLifeText; + private StringFrame unitManaText; + private Portrait portrait; + private final Rectangle tempRect = new Rectangle(); + private final Vector2 projectionTemp1 = new Vector2(); + private final Vector2 projectionTemp2 = new Vector2(); + private UIFrame simpleInfoPanelUnitDetail; + private StringFrame simpleNameValue; + private StringFrame simpleClassValue; + private StringFrame simpleBuildingActionLabel; + private UIFrame attack1Icon; + private TextureFrame attack1IconBackdrop; + private StringFrame attack1InfoPanelIconValue; + private StringFrame attack1InfoPanelIconLevel; + private UIFrame attack2Icon; + private TextureFrame attack2IconBackdrop; + private StringFrame attack2InfoPanelIconValue; + private StringFrame attack2InfoPanelIconLevel; + private UIFrame armorIcon; + private TextureFrame armorIconBackdrop; + private StringFrame armorInfoPanelIconValue; + private StringFrame armorInfoPanelIconLevel; + private InfoPanelIconBackdrops damageBackdrops; + private InfoPanelIconBackdrops defenseBackdrops; + + private final CommandCardIcon[][] commandCard = new CommandCardIcon[COMMAND_CARD_HEIGHT][COMMAND_CARD_WIDTH]; + + private RenderUnit selectedUnit; + private final List subMenuOrderIdStack = new ArrayList<>(); + + // TODO remove this & replace with FDF + private final Texture activeButtonTexture; + private UIFrame inventoryCover; + private SpriteFrame cursorFrame; + private MeleeUIMinimap meleeUIMinimap; + private final CPlayerUnitOrderListener unitOrderListener; + private StringFrame errorMessageFrame; + + private CAbilityView activeCommand; + private int activeCommandOrderId; + private RenderUnit activeCommandUnit; + + private int selectedSoundCount = 0; + private final ActiveCommandUnitTargetFilter activeCommandUnitTargetFilter; + + // TODO these corrections are used for old hardcoded UI stuff, we should + // probably remove them later + private final float widthRatioCorrection; + private final float heightRatioCorrection; + private CommandCardIcon mouseDownUIFrame; + + public MeleeUI(final DataSource dataSource, final ExtendViewport uiViewport, + final FreeTypeFontGenerator fontGenerator, final Scene uiScene, final Scene portraitScene, + final CameraPreset[] cameraPresets, final CameraRates cameraRates, final War3MapViewer war3MapViewer, + final RootFrameListener rootFrameListener, final CPlayerUnitOrderListener unitOrderListener) { + this.dataSource = dataSource; + this.uiViewport = uiViewport; + this.fontGenerator = fontGenerator; + this.uiScene = uiScene; + this.portraitScene = portraitScene; + this.war3MapViewer = war3MapViewer; + this.rootFrameListener = rootFrameListener; + this.unitOrderListener = unitOrderListener; + + this.cameraManager = new GameCameraManager(cameraPresets, cameraRates); + + this.cameraManager.setupCamera(war3MapViewer.worldScene); + if (this.war3MapViewer.startLocations[0] != null) { + this.cameraManager.target.x = this.war3MapViewer.startLocations[0].x; + this.cameraManager.target.y = this.war3MapViewer.startLocations[0].y; + } + + this.activeButtonTexture = ImageUtils.getBLPTexture(war3MapViewer.mapMpq, + "UI\\Widgets\\Console\\Human\\CommandButton\\human-activebutton.blp"); + this.activeCommandUnitTargetFilter = new ActiveCommandUnitTargetFilter(); + this.widthRatioCorrection = this.uiViewport.getMinWorldWidth() / 1600f; + this.heightRatioCorrection = this.uiViewport.getMinWorldHeight() / 1200f; + + } + + private MeleeUIMinimap createMinimap(final War3MapViewer war3MapViewer) { + final Rectangle minimapDisplayArea = new Rectangle(18.75f * this.widthRatioCorrection, + 13.75f * this.heightRatioCorrection, 278.75f * this.widthRatioCorrection, + 276.25f * this.heightRatioCorrection); + Texture minimapTexture = null; + if (war3MapViewer.dataSource.has("war3mapMap.tga")) { + try { + minimapTexture = ImageUtils.getTextureNoColorCorrection(TgaFile.readTGA("war3mapMap.tga", + war3MapViewer.dataSource.getResourceAsStream("war3mapMap.tga"))); + } + catch (final IOException e) { + System.err.println("Could not load minimap TGA file"); + e.printStackTrace(); + } + } + else if (war3MapViewer.dataSource.has("war3mapMap.blp")) { + try { + minimapTexture = ImageUtils + .getTexture(ImageIO.read(war3MapViewer.dataSource.getResourceAsStream("war3mapMap.blp"))); + } + catch (final IOException e) { + System.err.println("Could not load minimap BLP file"); + e.printStackTrace(); + } + } + final Texture[] teamColors = new Texture[WarsmashConstants.MAX_PLAYERS]; + for (int i = 0; i < teamColors.length; i++) { + teamColors[i] = ImageUtils.getBLPTexture(war3MapViewer.dataSource, + "ReplaceableTextures\\" + ReplaceableIds.getPathString(1) + ReplaceableIds.getIdString(i) + ".blp"); + } + final Rectangle playableMapArea = war3MapViewer.terrain.getPlayableMapArea(); + return new MeleeUIMinimap(minimapDisplayArea, playableMapArea, minimapTexture, teamColors); + } + + /** + * Called "main" because this was originally written in JASS so that maps could + * override it, and I may convert it back to the JASS at some point. + */ + public void main() { + // ================================= + // Load skins and templates + // ================================= + this.rootFrame = new GameUI(this.dataSource, GameUI.loadSkin(this.dataSource, 0), this.uiViewport, + this.fontGenerator, this.uiScene, this.war3MapViewer); + this.rootFrameListener.onCreate(this.rootFrame); + try { + this.rootFrame.loadTOCFile("UI\\FrameDef\\FrameDef.toc"); + } + catch (final IOException exc) { + throw new IllegalStateException("Unable to load FrameDef.toc", exc); + } + try { + this.rootFrame.loadTOCFile("UI\\FrameDef\\SmashFrameDef.toc"); + } + catch (final IOException exc) { + throw new IllegalStateException("Unable to load SmashFrameDef.toc", exc); + } + this.damageBackdrops = new InfoPanelIconBackdrops(CAttackType.values(), this.rootFrame, "Damage", "Neutral"); + this.defenseBackdrops = new InfoPanelIconBackdrops(CDefenseType.values(), this.rootFrame, "Armor", "Neutral"); + + // ================================= + // Load major UI components + // ================================= + // Console UI is the background with the racial theme + this.consoleUI = this.rootFrame.createSimpleFrame("ConsoleUI", this.rootFrame, 0); + this.consoleUI.setSetAllPoints(true); + + // Resource bar is a 3 part bar with Gold, Lumber, and Food. + // Its template does not specify where to put it, so we must + // put it in the "TOPRIGHT" corner. + this.resourceBar = this.rootFrame.createSimpleFrame("ResourceBarFrame", this.consoleUI, 0); + this.resourceBar.addSetPoint(new SetPoint(FramePoint.TOPRIGHT, this.consoleUI, FramePoint.TOPRIGHT, 0, 0)); + this.resourceBarGoldText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarGoldText", 0); + this.resourceBarGoldText.setText("500"); + this.resourceBarLumberText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarLumberText", 0); + this.resourceBarLumberText.setText("150"); + this.resourceBarSupplyText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarSupplyText", 0); + this.resourceBarSupplyText.setText("12/100"); + this.resourceBarUpkeepText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarUpkeepText", 0); + this.resourceBarUpkeepText.setText("No Upkeep"); + this.resourceBarUpkeepText.setColor(Color.GREEN); + + // Create the Time Indicator (clock) + this.timeIndicator = (SpriteFrame) this.rootFrame.createFrame("TimeOfDayIndicator", this.rootFrame, 0, 0); + this.timeIndicator.setSequence(0); // play the stand + this.timeIndicator.setAnimationSpeed(0.0f); // do not advance automatically + + // Create the unit portrait stuff + this.portrait = new Portrait(this.war3MapViewer, this.portraitScene); + positionPortrait(); + this.unitPortrait = this.rootFrame.createSimpleFrame("UnitPortrait", this.consoleUI, 0); + this.unitLifeText = (StringFrame) this.rootFrame.getFrameByName("UnitPortraitHitPointText", 0); + this.unitManaText = (StringFrame) this.rootFrame.getFrameByName("UnitPortraitManaPointText", 0); + + this.simpleInfoPanelUnitDetail = this.rootFrame.createSimpleFrame("SimpleInfoPanelUnitDetail", this.consoleUI, + 0); + this.simpleInfoPanelUnitDetail + .addAnchor(new AnchorDefinition(FramePoint.BOTTOM, 0, GameUI.convertY(this.uiViewport, 0.0f))); + this.simpleInfoPanelUnitDetail.setWidth(GameUI.convertY(this.uiViewport, 0.180f)); + this.simpleInfoPanelUnitDetail.setHeight(GameUI.convertY(this.uiViewport, 0.105f)); + this.simpleNameValue = (StringFrame) this.rootFrame.getFrameByName("SimpleNameValue", 0); + this.simpleClassValue = (StringFrame) this.rootFrame.getFrameByName("SimpleClassValue", 0); + this.simpleBuildingActionLabel = (StringFrame) this.rootFrame.getFrameByName("SimpleBuildingActionLabel", 0); + + this.attack1Icon = this.rootFrame.createSimpleFrame("SimpleInfoPanelIconDamage", this.simpleInfoPanelUnitDetail, + 0); + this.attack1Icon.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, + FramePoint.TOPLEFT, 0, GameUI.convertY(this.uiViewport, -0.030125f))); + this.attack1IconBackdrop = (TextureFrame) this.rootFrame.getFrameByName("InfoPanelIconBackdrop", 0); + this.attack1InfoPanelIconValue = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconValue", 0); + this.attack1InfoPanelIconLevel = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconLevel", 0); + + this.attack2Icon = this.rootFrame.createSimpleFrame("SimpleInfoPanelIconDamage", this.simpleInfoPanelUnitDetail, + 1); + this.attack2Icon + .addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, FramePoint.TOPLEFT, + GameUI.convertX(this.uiViewport, 0.1f), GameUI.convertY(this.uiViewport, -0.030125f))); + this.attack2IconBackdrop = (TextureFrame) this.rootFrame.getFrameByName("InfoPanelIconBackdrop", 1); + this.attack2InfoPanelIconValue = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconValue", 1); + this.attack2InfoPanelIconLevel = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconLevel", 1); + + this.armorIcon = this.rootFrame.createSimpleFrame("SimpleInfoPanelIconArmor", this.simpleInfoPanelUnitDetail, + 1); + this.armorIcon.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, FramePoint.TOPLEFT, + GameUI.convertX(this.uiViewport, 0f), GameUI.convertY(this.uiViewport, -0.06025f))); + this.armorIconBackdrop = (TextureFrame) this.rootFrame.getFrameByName("InfoPanelIconBackdrop", 0); + this.armorInfoPanelIconValue = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconValue", 0); + this.armorInfoPanelIconLevel = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconLevel", 0); + + this.inventoryCover = this.rootFrame.createSimpleFrame("SmashConsoleInventoryCover", this.rootFrame, 0); + + this.errorMessageFrame = this.rootFrame.createStringFrame("SmashErrorMessageFrame", this.consoleUI, + new Color(0xFFFFCC00), TextJustify.LEFT, TextJustify.MIDDLE, 0.014f); + this.errorMessageFrame.addAnchor(new AnchorDefinition(FramePoint.BOTTOMLEFT, + GameUI.convertX(this.uiViewport, 0.275f), GameUI.convertY(this.uiViewport, 0.275f))); + this.errorMessageFrame.setWidth(GameUI.convertX(this.uiViewport, 0.25f)); + + int commandButtonIndex = 0; + for (int j = 0; j < COMMAND_CARD_HEIGHT; j++) { + for (int i = 0; i < COMMAND_CARD_WIDTH; i++) { + final CommandCardIcon commandCardIcon = new CommandCardIcon("SmashCommandButton_" + commandButtonIndex, + this.rootFrame, this); + this.rootFrame.add(commandCardIcon); + final TextureFrame iconFrame = this.rootFrame.createTextureFrame( + "SmashCommandButton_" + (commandButtonIndex) + "_Icon", this.rootFrame, false, null); + final TextureFrame activeHighlightFrame = this.rootFrame.createTextureFrame( + "SmashCommandButton_" + (commandButtonIndex) + "_ActiveHighlight", this.rootFrame, true, null, + FilterMode.ADDALPHA); + final SpriteFrame cooldownFrame = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", + "SmashCommandButton_" + (commandButtonIndex) + "_Cooldown", this.rootFrame, "", 0); + final SpriteFrame autocastFrame = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", + "SmashCommandButton_" + (commandButtonIndex) + "_Autocast", this.rootFrame, "", 0); + commandCardIcon.addAnchor(new AnchorDefinition(FramePoint.BOTTOMLEFT, + GameUI.convertX(this.uiViewport, 0.6175f + (0.0434f * i)), + GameUI.convertY(this.uiViewport, 0.095f - (0.044f * j)))); + commandCardIcon.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); + commandCardIcon.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); + iconFrame.addSetPoint(new SetPoint(FramePoint.CENTER, commandCardIcon, FramePoint.CENTER, 0, 0)); + iconFrame.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); + iconFrame.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); + iconFrame.setTexture(ImageUtils.DEFAULT_ICON_PATH, this.rootFrame); + activeHighlightFrame + .addSetPoint(new SetPoint(FramePoint.CENTER, commandCardIcon, FramePoint.CENTER, 0, 0)); + activeHighlightFrame.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); + activeHighlightFrame.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); + activeHighlightFrame.setTexture("CommandButtonActiveHighlight", this.rootFrame); + cooldownFrame.addSetPoint(new SetPoint(FramePoint.CENTER, commandCardIcon, FramePoint.CENTER, 0, 0)); + this.rootFrame.setSpriteFrameModel(cooldownFrame, this.rootFrame.getSkinField("CommandButtonCooldown")); + cooldownFrame.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); + cooldownFrame.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); + autocastFrame.addSetPoint(new SetPoint(FramePoint.CENTER, commandCardIcon, FramePoint.CENTER, 0, 0)); + this.rootFrame.setSpriteFrameModel(autocastFrame, this.rootFrame.getSkinField("CommandButtonAutocast")); + autocastFrame.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); + autocastFrame.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); + commandCardIcon.set(iconFrame, activeHighlightFrame, cooldownFrame, autocastFrame); + this.commandCard[j][i] = commandCardIcon; + commandCardIcon.setCommandButton(null); + commandButtonIndex++; + } + } + + this.cursorFrame = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", "SmashCursorFrame", this.rootFrame, + "", 0); + this.rootFrame.setSpriteFrameModel(this.cursorFrame, this.rootFrame.getSkinField("Cursor")); + this.cursorFrame.setSequence("Normal"); + this.cursorFrame.setZDepth(1.0f); + Gdx.input.setCursorCatched(true); + + this.meleeUIMinimap = createMinimap(this.war3MapViewer); + + this.rootFrame.positionBounds(this.uiViewport); + selectUnit(null); + } + + @Override + public void startUsingAbility(final int abilityHandleId, final int orderId) { + // TODO not O(N) + CAbilityView abilityToUse = null; + for (final CAbility ability : this.selectedUnit.getSimulationUnit().getAbilities()) { + if (ability.getHandleId() == abilityHandleId) { + abilityToUse = ability; + break; + } + } + if (abilityToUse != null) { + final StringMsgAbilityActivationReceiver stringMsgActivationReceiver = StringMsgAbilityActivationReceiver + .getInstance().reset(); + abilityToUse.checkCanUse(this.war3MapViewer.simulation, this.selectedUnit.getSimulationUnit(), orderId, + stringMsgActivationReceiver); + if (!stringMsgActivationReceiver.isUseOk()) { + showCommandError(stringMsgActivationReceiver.getMessage()); + } + else { + final BooleanAbilityTargetCheckReceiver noTargetReceiver = BooleanAbilityTargetCheckReceiver + .getInstance().reset(); + abilityToUse.checkCanTargetNoTarget(this.war3MapViewer.simulation, + this.selectedUnit.getSimulationUnit(), orderId, noTargetReceiver); + if (noTargetReceiver.isTargetable()) { + this.unitOrderListener.issueImmediateOrder(this.selectedUnit.getSimulationUnit().getHandleId(), + abilityHandleId, orderId, isShiftDown()); + } + else { + this.activeCommand = abilityToUse; + this.activeCommandOrderId = orderId; + this.activeCommandUnit = this.selectedUnit; + clearAndRepopulateCommandCard(); + } + } + } + else { + this.unitOrderListener.issueImmediateOrder(this.selectedUnit.getSimulationUnit().getHandleId(), + abilityHandleId, orderId, isShiftDown()); + } + } + + @Override + public void openMenu(final int orderId) { + if (orderId == 0) { + this.subMenuOrderIdStack.clear(); + this.activeCommandUnit = null; + this.activeCommand = null; + this.activeCommandOrderId = -1; + } + else { + this.subMenuOrderIdStack.add(orderId); + } + clearAndRepopulateCommandCard(); + } + + public void showCommandError(final String message) { + this.errorMessageFrame.setText(message); + } + + public void update(final float deltaTime) { + this.portrait.update(); + + int mouseX = Gdx.input.getX(); + int mouseY = Gdx.input.getY(); + final int minX = this.uiViewport.getScreenX(); + final int maxX = minX + this.uiViewport.getScreenWidth(); + final int minY = this.uiViewport.getScreenY(); + final int maxY = minY + this.uiViewport.getScreenHeight(); + final boolean left = mouseX <= (minX + 3); + final boolean right = mouseX >= (maxX - 3); + final boolean up = mouseY <= (minY + 3); + final boolean down = mouseY >= (maxY - 3); + this.cameraManager.applyVelocity(deltaTime, up, down, left, right); + + mouseX = Math.max(minX, Math.min(maxX, mouseX)); + mouseY = Math.max(minY, Math.min(maxY, mouseY)); + if (Gdx.input.isCursorCatched()) { + Gdx.input.setCursorPosition(mouseX, mouseY); + } + + screenCoordsVector.set(mouseX, mouseY); + this.uiViewport.unproject(screenCoordsVector); + this.cursorFrame.setFramePointX(FramePoint.LEFT, screenCoordsVector.x); + this.cursorFrame.setFramePointY(FramePoint.BOTTOM, screenCoordsVector.y); + + if (this.activeCommand != null) { + this.cursorFrame.setSequence("Target"); + } + else if (down) { + if (left) { + this.cursorFrame.setSequence("Scroll Down Left"); + } + else if (right) { + this.cursorFrame.setSequence("Scroll Down Right"); + } + else { + this.cursorFrame.setSequence("Scroll Down"); + } + } + else if (up) { + if (left) { + this.cursorFrame.setSequence("Scroll Up Left"); + } + else if (right) { + this.cursorFrame.setSequence("Scroll Up Right"); + } + else { + this.cursorFrame.setSequence("Scroll Up"); + } + } + else if (left) { + this.cursorFrame.setSequence("Scroll Left"); + } + else if (right) { + this.cursorFrame.setSequence("Scroll Right"); + } + else { + this.cursorFrame.setSequence("Normal"); + } + final float groundHeight = Math.max( + this.war3MapViewer.terrain.getGroundHeight(this.cameraManager.target.x, this.cameraManager.target.y), + this.war3MapViewer.terrain.getWaterHeight(this.cameraManager.target.x, this.cameraManager.target.y)); + this.cameraManager.updateTargetZ(groundHeight); + this.cameraManager.updateCamera(); + } + + public void render(final SpriteBatch batch, final BitmapFont font20, final GlyphLayout glyphLayout) { + this.rootFrame.render(batch, font20, glyphLayout); + if (this.selectedUnit != null) { + font20.setColor(Color.WHITE); + + } + + this.meleeUIMinimap.render(batch, this.war3MapViewer.units); + this.timeIndicator.setFrameByRatio(this.war3MapViewer.simulation.getGameTimeOfDay() + / this.war3MapViewer.simulation.getGameplayConstants().getGameDayHours()); + } + + public void portraitTalk() { + this.portrait.talk(); + } + + private final class ActiveCommandUnitTargetFilter implements CUnitFilterFunction { + @Override + public boolean call(final CUnit unit) { + final BooleanAbilityTargetCheckReceiver targetReceiver = BooleanAbilityTargetCheckReceiver + .getInstance(); + MeleeUI.this.activeCommand.checkCanTarget(MeleeUI.this.war3MapViewer.simulation, + MeleeUI.this.activeCommandUnit.getSimulationUnit(), MeleeUI.this.activeCommandOrderId, unit, + targetReceiver); + return targetReceiver.isTargetable(); + } + } + + private static final class Portrait { + private MdxComplexInstance modelInstance; + private final PortraitCameraManager portraitCameraManager; + private final Scene portraitScene; + + public Portrait(final War3MapViewer war3MapViewer, final Scene portraitScene) { + this.portraitScene = portraitScene; + this.portraitCameraManager = new PortraitCameraManager(); + this.portraitCameraManager.setupCamera(this.portraitScene); + this.portraitScene.camera.viewport(new Rectangle(100, 0, 6400, 48)); + } + + public void update() { + this.portraitCameraManager.updateCamera(); + if ((this.modelInstance != null) + && (this.modelInstance.sequenceEnded || (this.modelInstance.sequence == -1))) { + SequenceUtils.randomSequence(this.modelInstance, PrimaryTag.PORTRAIT, SequenceUtils.EMPTY, true); + } + } + + public void talk() { + SequenceUtils.randomSequence(this.modelInstance, PrimaryTag.PORTRAIT, SequenceUtils.TALK, true); + } + + public void setSelectedUnit(final RenderUnit unit) { + if (unit == null) { + if (this.modelInstance != null) { + this.portraitScene.removeInstance(this.modelInstance); + } + this.modelInstance = null; + this.portraitCameraManager.setModelInstance(null, null); + } + else { + final MdxModel portraitModel = unit.portraitModel; + if (portraitModel != null) { + if (this.modelInstance != null) { + this.portraitScene.removeInstance(this.modelInstance); + } + this.modelInstance = (MdxComplexInstance) portraitModel.addInstance(); + this.portraitCameraManager.setModelInstance(this.modelInstance, portraitModel); + this.modelInstance.setSequenceLoopMode(SequenceLoopMode.NEVER_LOOP); + this.modelInstance.setScene(this.portraitScene); + this.modelInstance.setVertexColor(unit.instance.vertexColor); + this.modelInstance.setTeamColor(unit.playerIndex); + } + } + } + } + + public void selectUnit(RenderUnit unit) { + this.subMenuOrderIdStack.clear(); + if ((unit != null) && unit.getSimulationUnit().isDead()) { + unit = null; + } + if (this.selectedUnit != null) { + this.selectedUnit.getSimulationUnit().removeStateListener(this); + } + this.portrait.setSelectedUnit(unit); + this.selectedUnit = unit; + clearCommandCard(); + if (unit == null) { + this.simpleNameValue.setText(""); + this.unitLifeText.setText(""); + this.unitManaText.setText(""); + this.simpleClassValue.setText(""); + this.simpleBuildingActionLabel.setText(""); + this.attack1Icon.setVisible(false); + this.attack2Icon.setVisible(false); + this.attack1InfoPanelIconLevel.setText(""); + this.attack2InfoPanelIconLevel.setText(""); + this.armorIcon.setVisible(false); + this.armorInfoPanelIconLevel.setText(""); + } + else { + unit.getSimulationUnit().addStateListener(this); + this.simpleNameValue.setText(unit.getSimulationUnit().getUnitType().getName()); + String classText = null; + for (final CUnitClassification classification : unit.getSimulationUnit().getClassifications()) { + if ((classification == CUnitClassification.MECHANICAL) + && unit.getSimulationUnit().getUnitType().isBuilding()) { + // buildings dont display MECHANICAL + continue; + } + if (classification.getDisplayName() != null) { + classText = classification.getDisplayName(); + } + } + if (classText != null) { + this.simpleClassValue.setText(classText); + } + else { + this.simpleClassValue.setText(""); + } + this.unitLifeText.setText(FastNumberFormat.formatWholeNumber(unit.getSimulationUnit().getLife()) + " / " + + unit.getSimulationUnit().getMaximumLife()); + final int maximumMana = unit.getSimulationUnit().getMaximumMana(); + if (maximumMana > 0) { + this.unitManaText.setText( + FastNumberFormat.formatWholeNumber(unit.getSimulationUnit().getMana()) + " / " + maximumMana); + } + else { + this.unitManaText.setText(""); + } + this.simpleBuildingActionLabel.setText(""); + + final boolean anyAttacks = unit.getSimulationUnit().getUnitType().getAttacks().size() > 0; + final UIFrame localArmorIcon = this.armorIcon; + final TextureFrame localArmorIconBackdrop = this.armorIconBackdrop; + final StringFrame localArmorInfoPanelIconValue = this.armorInfoPanelIconValue; + if (anyAttacks) { + final CUnitAttack attackOne = unit.getSimulationUnit().getUnitType().getAttacks().get(0); + this.attack1Icon.setVisible(attackOne.isShowUI()); + this.attack1IconBackdrop.setTexture(this.damageBackdrops.getTexture(attackOne.getAttackType())); + this.attack1InfoPanelIconValue.setText(attackOne.getMinDamage() + " - " + attackOne.getMaxDamage()); + if (unit.getSimulationUnit().getUnitType().getAttacks().size() > 1) { + final CUnitAttack attackTwo = unit.getSimulationUnit().getUnitType().getAttacks().get(1); + this.attack2Icon.setVisible(attackTwo.isShowUI()); + this.attack2IconBackdrop.setTexture(this.damageBackdrops.getTexture(attackTwo.getAttackType())); + this.attack2InfoPanelIconValue.setText(attackTwo.getMinDamage() + " - " + attackTwo.getMaxDamage()); + } + else { + this.attack2Icon.setVisible(false); + } + + this.armorIcon.addSetPoint( + new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, FramePoint.TOPLEFT, + GameUI.convertX(this.uiViewport, 0f), GameUI.convertY(this.uiViewport, -0.06025f))); + this.armorIcon.positionBounds(this.uiViewport); + } + else { + this.attack1Icon.setVisible(false); + this.attack2Icon.setVisible(false); + + this.armorIcon.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, + FramePoint.TOPLEFT, 0, GameUI.convertY(this.uiViewport, -0.030125f))); + this.armorIcon.positionBounds(this.uiViewport); + } + + localArmorIcon.setVisible(true); + final Texture defenseTexture = this.defenseBackdrops + .getTexture(unit.getSimulationUnit().getUnitType().getDefenseType()); + if (defenseTexture == null) { + throw new RuntimeException( + unit.getSimulationUnit().getUnitType().getDefenseType() + " can't find texture!"); + } + localArmorIconBackdrop.setTexture(defenseTexture); + localArmorInfoPanelIconValue.setText(Integer.toString(unit.getSimulationUnit().getDefense())); + unit.populateCommandCard(this.war3MapViewer.simulation, this, this.war3MapViewer.getAbilityDataUI(), + getSubMenuOrderId()); + } + } + + private void clearCommandCard() { + for (int j = 0; j < COMMAND_CARD_HEIGHT; j++) { + for (int i = 0; i < COMMAND_CARD_WIDTH; i++) { + this.commandCard[j][i].setCommandButton(null); + } + } + } + + @Override + public void commandButton(final int buttonPositionX, final int buttonPositionY, final Texture icon, + final int abilityHandleId, final int orderId, final int autoCastId, final boolean active, + final boolean autoCastActive, final boolean menuButton) { + final int x = Math.max(0, Math.min(COMMAND_CARD_WIDTH - 1, buttonPositionX)); + final int y = Math.max(0, Math.min(COMMAND_CARD_HEIGHT - 1, buttonPositionY)); + this.commandCard[y][x].setCommandButtonData(icon, abilityHandleId, orderId, autoCastId, active, autoCastActive, + menuButton); + } + + public void resize(final Rectangle viewport) { + this.cameraManager.resize(viewport); + positionPortrait(); + } + + public void positionPortrait() { + this.projectionTemp1.x = 422 * this.widthRatioCorrection; + this.projectionTemp1.y = 57 * this.heightRatioCorrection; + this.projectionTemp2.x = (422 + 167) * this.widthRatioCorrection; + this.projectionTemp2.y = (57 + 170) * this.heightRatioCorrection; + this.uiViewport.project(this.projectionTemp1); + this.uiViewport.project(this.projectionTemp2); + + this.tempRect.x = this.projectionTemp1.x + this.uiViewport.getScreenX(); + this.tempRect.y = this.projectionTemp1.y + this.uiViewport.getScreenY(); + this.tempRect.width = this.projectionTemp2.x - this.projectionTemp1.x; + this.tempRect.height = this.projectionTemp2.y - this.projectionTemp1.y; + this.portrait.portraitScene.camera.viewport(this.tempRect); + } + + private static final class InfoPanelIconBackdrops { + private final Texture[] damageBackdropTextures; + + public InfoPanelIconBackdrops(final CodeKeyType[] attackTypes, final GameUI gameUI, final String prefix, + final String suffix) { + this.damageBackdropTextures = new Texture[attackTypes.length]; + for (int index = 0; index < attackTypes.length; index++) { + final CodeKeyType attackType = attackTypes[index]; + String skinLookupKey = "InfoPanelIcon" + prefix + attackType.getCodeKey() + suffix; + final Texture suffixTexture = gameUI.loadTexture(gameUI.getSkinField(skinLookupKey)); + if (suffixTexture != null) { + this.damageBackdropTextures[index] = suffixTexture; + } + else { + skinLookupKey = "InfoPanelIcon" + prefix + attackType.getCodeKey(); + this.damageBackdropTextures[index] = gameUI.loadTexture(gameUI.getSkinField(skinLookupKey)); + } + } + } + + public Texture getTexture(final CodeKeyType attackType) { + if (attackType != null) { + final int ordinal = attackType.ordinal(); + if ((ordinal >= 0) && (ordinal < this.damageBackdropTextures.length)) { + return this.damageBackdropTextures[ordinal]; + } + } + return this.damageBackdropTextures[0]; + } + + private static String getSuffix(final CAttackType attackType) { + switch (attackType) { + case CHAOS: + return "Chaos"; + case HERO: + return "Hero"; + case MAGIC: + return "Magic"; + case NORMAL: + return "Normal"; + case PIERCE: + return "Pierce"; + case SIEGE: + return "Siege"; + case SPELLS: + return "Magic"; + case UNKNOWN: + return "Unknown"; + default: + throw new IllegalArgumentException("Unknown attack type: " + attackType); + } + + } + } + + @Override + public void lifeChanged() { + if (this.selectedUnit.getSimulationUnit().isDead()) { + selectUnit(null); + } + else { + this.unitLifeText + .setText(FastNumberFormat.formatWholeNumber(this.selectedUnit.getSimulationUnit().getLife()) + " / " + + this.selectedUnit.getSimulationUnit().getMaximumLife()); + } + } + + @Override + public void ordersChanged(final int abilityHandleId, final int orderId) { + clearAndRepopulateCommandCard(); + } + + private void clearAndRepopulateCommandCard() { + clearCommandCard(); + final AbilityDataUI abilityDataUI = this.war3MapViewer.getAbilityDataUI(); + final int menuOrderId = getSubMenuOrderId(); + if (this.activeCommand != null) { + final IconUI cancelUI = abilityDataUI.getCancelUI(); + this.commandButton(cancelUI.getButtonPositionX(), cancelUI.getButtonPositionY(), cancelUI.getIcon(), 0, + menuOrderId, 0, false, false, true); + } + else { + if (menuOrderId != 0) { + final int exitOrderId = this.subMenuOrderIdStack.size() > 1 + ? this.subMenuOrderIdStack.get(this.subMenuOrderIdStack.size() - 2) + : 0; + final IconUI cancelUI = abilityDataUI.getCancelUI(); + this.commandButton(cancelUI.getButtonPositionX(), cancelUI.getButtonPositionY(), cancelUI.getIcon(), 0, + exitOrderId, 0, false, false, true); + } + this.selectedUnit.populateCommandCard(this.war3MapViewer.simulation, this, abilityDataUI, menuOrderId); + } + } + + private int getSubMenuOrderId() { + return this.subMenuOrderIdStack.isEmpty() ? 0 + : this.subMenuOrderIdStack.get(this.subMenuOrderIdStack.size() - 1); + } + + public RenderUnit getSelectedUnit() { + return this.selectedUnit; + } + + public boolean keyDown(final int keycode) { + return this.cameraManager.keyDown(keycode); + } + + public boolean keyUp(final int keycode) { + return this.cameraManager.keyUp(keycode); + } + + public void scrolled(final int amount) { + this.cameraManager.scrolled(amount); + } + + public boolean touchDown(final int screenX, final int screenY, final float worldScreenY, final int button) { + screenCoordsVector.set(screenX, screenY); + this.uiViewport.unproject(screenCoordsVector); + if (this.meleeUIMinimap.containsMouse(screenCoordsVector.x, screenCoordsVector.y)) { + final Vector2 worldPoint = this.meleeUIMinimap.getWorldPointFromScreen(screenCoordsVector.x, + screenCoordsVector.y); + this.cameraManager.target.x = worldPoint.x; + this.cameraManager.target.y = worldPoint.y; + return true; + } + final UIFrame clickedUIFrame = this.rootFrame.touchDown(screenCoordsVector.x, screenCoordsVector.y, button); + if (clickedUIFrame == null) { + // try to interact with world + if (this.activeCommand != null) { + if (button == Input.Buttons.RIGHT) { + this.activeCommandUnit = null; + this.activeCommand = null; + this.activeCommandOrderId = -1; + clearAndRepopulateCommandCard(); + } + else { + final RenderUnit rayPickUnit = this.war3MapViewer.rayPickUnit(screenX, worldScreenY, + this.activeCommandUnitTargetFilter); + final boolean shiftDown = isShiftDown(); + if (rayPickUnit != null) { + this.unitOrderListener.issueTargetOrder( + this.activeCommandUnit.getSimulationUnit().getHandleId(), + this.activeCommand.getHandleId(), this.activeCommandOrderId, + rayPickUnit.getSimulationUnit().getHandleId(), shiftDown); + if (getSelectedUnit().soundset.yesAttack + .playUnitResponse(this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { + portraitTalk(); + } + this.selectedSoundCount = 0; + if (!shiftDown) { + this.activeCommandUnit = null; + this.activeCommand = null; + this.activeCommandOrderId = -1; + clearAndRepopulateCommandCard(); + } + } + else { + this.war3MapViewer.getClickLocation(clickLocationTemp, screenX, (int) worldScreenY); + clickLocationTemp2.set(clickLocationTemp.x, clickLocationTemp.y); + + this.activeCommand.checkCanTarget(this.war3MapViewer.simulation, + this.activeCommandUnit.getSimulationUnit(), this.activeCommandOrderId, + clickLocationTemp2, PointAbilityTargetCheckReceiver.INSTANCE); + final Vector2 target = PointAbilityTargetCheckReceiver.INSTANCE.getTarget(); + if (target != null) { + if (this.activeCommand instanceof CAbilityAttack) { + this.war3MapViewer.showConfirmation(clickLocationTemp, 1, 0, 0); + } + else { + this.war3MapViewer.showConfirmation(clickLocationTemp, 0, 1, 0); + } + this.unitOrderListener.issuePointOrder( + this.activeCommandUnit.getSimulationUnit().getHandleId(), + this.activeCommand.getHandleId(), this.activeCommandOrderId, clickLocationTemp2.x, + clickLocationTemp2.y, shiftDown); + if (getSelectedUnit().soundset.yes + .playUnitResponse(this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { + portraitTalk(); + } + this.selectedSoundCount = 0; + if (!shiftDown) { + this.activeCommandUnit = null; + this.activeCommand = null; + this.activeCommandOrderId = -1; + clearAndRepopulateCommandCard(); + } + + } + } + } + } + else { + if (button == Input.Buttons.RIGHT) { + if (getSelectedUnit() != null) { + final RenderUnit rayPickUnit = this.war3MapViewer.rayPickUnit(screenX, worldScreenY); + if ((rayPickUnit != null) && !rayPickUnit.getSimulationUnit().isDead()) { + boolean ordered = false; + for (final RenderUnit unit : this.war3MapViewer.selected) { + for (final CAbility ability : unit.getSimulationUnit().getAbilities()) { + ability.checkCanTarget(this.war3MapViewer.simulation, unit.getSimulationUnit(), + OrderIds.smart, rayPickUnit.getSimulationUnit(), + CWidgetAbilityTargetCheckReceiver.INSTANCE); + final CWidget targetWidget = CWidgetAbilityTargetCheckReceiver.INSTANCE.getTarget(); + if (targetWidget != null) { + this.unitOrderListener.issueTargetOrder(unit.getSimulationUnit().getHandleId(), + ability.getHandleId(), OrderIds.smart, targetWidget.getHandleId(), + isShiftDown()); + ordered = true; + } + } + + } + if (ordered) { + if (getSelectedUnit().soundset.yesAttack.playUnitResponse( + this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { + portraitTalk(); + } + this.selectedSoundCount = 0; + } + } + else { + this.war3MapViewer.getClickLocation(clickLocationTemp, screenX, (int) worldScreenY); + this.war3MapViewer.showConfirmation(clickLocationTemp, 0, 1, 0); + clickLocationTemp2.set(clickLocationTemp.x, clickLocationTemp.y); + + boolean ordered = false; + for (final RenderUnit unit : this.war3MapViewer.selected) { + for (final CAbility ability : unit.getSimulationUnit().getAbilities()) { + ability.checkCanUse(this.war3MapViewer.simulation, unit.getSimulationUnit(), + OrderIds.smart, BooleanAbilityActivationReceiver.INSTANCE); + if (BooleanAbilityActivationReceiver.INSTANCE.isOk()) { + ability.checkCanTarget(this.war3MapViewer.simulation, unit.getSimulationUnit(), + OrderIds.smart, clickLocationTemp2, + PointAbilityTargetCheckReceiver.INSTANCE); + final Vector2 target = PointAbilityTargetCheckReceiver.INSTANCE.getTarget(); + if (target != null) { + this.unitOrderListener.issuePointOrder( + unit.getSimulationUnit().getHandleId(), ability.getHandleId(), + OrderIds.smart, clickLocationTemp2.x, clickLocationTemp2.y, + isShiftDown()); + ordered = true; + } + } + } + + } + + if (ordered) { + if (getSelectedUnit().soundset.yes.playUnitResponse( + this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { + portraitTalk(); + } + this.selectedSoundCount = 0; + } + } + } + } + else { + final List selectedUnits = this.war3MapViewer.selectUnit(screenX, worldScreenY, false); + if (!selectedUnits.isEmpty()) { + final RenderUnit unit = selectedUnits.get(0); + final boolean selectionChanged = getSelectedUnit() != unit; + boolean playedNewSound = false; + if (selectionChanged) { + this.selectedSoundCount = 0; + } + if (unit.soundset != null) { + UnitSound ackSoundToPlay = unit.soundset.what; + final int pissedSoundCount = unit.soundset.pissed.getSoundCount(); + int soundIndex; + if ((this.selectedSoundCount >= 3) && (pissedSoundCount > 0)) { + soundIndex = this.selectedSoundCount - 3; + ackSoundToPlay = unit.soundset.pissed; + } + else { + soundIndex = (int) (Math.random() * ackSoundToPlay.getSoundCount()); + } + if (ackSoundToPlay.playUnitResponse(this.war3MapViewer.worldScene.audioContext, unit, + soundIndex)) { + this.selectedSoundCount++; + if ((this.selectedSoundCount - 3) >= pissedSoundCount) { + this.selectedSoundCount = 0; + } + playedNewSound = true; + } + } + if (selectionChanged) { + selectUnit(unit); + } + if (playedNewSound) { + portraitTalk(); + } + } + else { + selectUnit(null); + } + } + } + } + else { + if (clickedUIFrame instanceof CommandCardIcon) { + this.mouseDownUIFrame = (CommandCardIcon) clickedUIFrame; + this.mouseDownUIFrame.mouseDown(this.uiViewport); + } + } + return false; + } + + public boolean touchUp(final int screenX, final int screenY, final float worldScreenY, final int button) { + screenCoordsVector.set(screenX, screenY); + this.uiViewport.unproject(screenCoordsVector); + final UIFrame clickedUIFrame = this.rootFrame.touchUp(screenCoordsVector.x, screenCoordsVector.y, button); + if (this.mouseDownUIFrame != null) { + if (clickedUIFrame == this.mouseDownUIFrame) { + this.mouseDownUIFrame.onClick(button); + this.war3MapViewer.getUiSounds().getSound("InterfaceClick").play(this.uiScene.audioContext, 0, 0); + } + this.mouseDownUIFrame.mouseUp(this.uiViewport); + } + this.mouseDownUIFrame = null; + return false; + } + + private static boolean isShiftDown() { + return Gdx.input.isKeyPressed(Input.Keys.SHIFT_LEFT) || Gdx.input.isKeyPressed(Input.Keys.SHIFT_RIGHT); + } + + public boolean touchDragged(final int screenX, final int screenY, final float worldScreenY, final int pointer) { + screenCoordsVector.set(screenX, screenY); + this.uiViewport.unproject(screenCoordsVector); + + if (this.meleeUIMinimap.containsMouse(screenCoordsVector.x, screenCoordsVector.y)) { + final Vector2 worldPoint = this.meleeUIMinimap.getWorldPointFromScreen(screenCoordsVector.x, + screenCoordsVector.y); + this.cameraManager.target.x = worldPoint.x; + this.cameraManager.target.y = worldPoint.y; + } + return false; + } + + public float getHeightRatioCorrection() { + return this.heightRatioCorrection; + } } diff --git a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java index 9caaca2..c53e166 100644 --- a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java +++ b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java @@ -64,6 +64,9 @@ public class DesktopLauncher { Extensions.soundLengthExtension = new SoundLengthExtension() { @Override public float getDuration(final Sound sound) { + if (sound == null) { + return 1; + } return ((OpenALSound) sound).duration(); } }; From bc9f7d37a092b7a510dafec5c97a9b39709704cf Mon Sep 17 00:00:00 2001 From: Retera Date: Sun, 8 Nov 2020 19:08:01 -0500 Subject: [PATCH 063/116] Remove code from behaviortest branch --- .../handlers/w3x/simulation/CUnit.java | 8 ---- .../w3x/simulation/test/BaseBehavior.java | 5 --- .../w3x/simulation/test/BaseState.java | 5 --- .../w3x/simulation/test/IAbility.java | 28 ------------ .../w3x/simulation/test/IBehavior.java | 5 --- .../handlers/w3x/simulation/test/IState.java | 5 --- .../test/ability/AttackAbility.java | 28 ------------ .../simulation/test/ability/MoveAbility.java | 22 --------- .../test/behavior/AttackTarget.java | 22 --------- .../simulation/test/behavior/MoveToPoint.java | 45 ------------------- .../w3x/simulation/test/state/MoveState.java | 28 ------------ 11 files changed, 201 deletions(-) delete mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/BaseBehavior.java delete mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/BaseState.java delete mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/IAbility.java delete mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/IBehavior.java delete mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/IState.java delete mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/ability/AttackAbility.java delete mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/ability/MoveAbility.java delete mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/behavior/AttackTarget.java delete mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/behavior/MoveToPoint.java delete mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/state/MoveState.java diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java index a9e9d98..0baebe2 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java @@ -27,8 +27,6 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.COrder; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CAllianceType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayer; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.test.IBehavior; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.test.IState; public class CUnit extends CWidget { private static final Rectangle tempRect = new Rectangle(); @@ -72,8 +70,6 @@ public class CUnit extends CWidget { private transient CBehaviorFollow followBehavior; private transient CBehaviorPatrol patrolBehavior; private transient CBehaviorStop stopBehavior; - private IBehavior behavior; - private IState state; public CUnit(final int handleId, final int playerIndex, final float x, final float y, final float life, final War3ID typeId, final float facing, final float mana, final int maximumLife, final int maximumMana, @@ -664,8 +660,4 @@ public class CUnit extends CWidget { return getCurrentBehavior() instanceof CBehaviorMove; } - public void setBehavior(final IBehavior behavior) { - this.behavior = behavior; - this.state = behavior.resolveNext(); - } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/BaseBehavior.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/BaseBehavior.java deleted file mode 100644 index 8db5d76..0000000 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/BaseBehavior.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.etheller.warsmash.viewer5.handlers.w3x.simulation.test; - -public abstract class BaseBehavior { - -} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/BaseState.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/BaseState.java deleted file mode 100644 index 3ea24f6..0000000 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/BaseState.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.etheller.warsmash.viewer5.handlers.w3x.simulation.test; - -public abstract class BaseState implements IState { - -} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/IAbility.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/IAbility.java deleted file mode 100644 index c8c82e6..0000000 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/IAbility.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.etheller.warsmash.viewer5.handlers.w3x.simulation.test; - -import java.awt.Point; - -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; - -/* - * IAbility - Execute(unit caster, int orderId, unit targetUnit, point targetPoint); - -IBehavior - ResolveNext(); - -IState - Execute(); - -abstract BaseState - ctor(unit unit, IBehavior behavior) - abstract Execute(); - -abstract BaseBehavior - ctor(unit unit) - - */ -public interface IAbility { - void execute(CUnit caster, int orderId, CWidget targetUnit, Point targetPoint); -} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/IBehavior.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/IBehavior.java deleted file mode 100644 index ff9e07e..0000000 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/IBehavior.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.etheller.warsmash.viewer5.handlers.w3x.simulation.test; - -public interface IBehavior { - IState resolveNext(); -} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/IState.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/IState.java deleted file mode 100644 index 7c351ca..0000000 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/IState.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.etheller.warsmash.viewer5.handlers.w3x.simulation.test; - -public interface IState { - void execute(); -} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/ability/AttackAbility.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/ability/AttackAbility.java deleted file mode 100644 index 83d2ba1..0000000 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/ability/AttackAbility.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.etheller.warsmash.viewer5.handlers.w3x.simulation.test.ability; - -import java.awt.Point; - -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.test.IAbility; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.test.behavior.AttackTarget; - -public class AttackAbility implements IAbility { - - @Override - public void execute(final CUnit caster, final int orderId, final CWidget target, final Point targetPoint) { - if (target != null) { - new AttackTarget(caster, target); - } - else if (targetPoint != null) { - if (orderId == OrderIds.attackground) { - // TODO some stuff - } - else if (orderId == OrderIds.attack) { - - } - } - } - -} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/ability/MoveAbility.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/ability/MoveAbility.java deleted file mode 100644 index e12eb86..0000000 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/ability/MoveAbility.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.etheller.warsmash.viewer5.handlers.w3x.simulation.test.ability; - -import java.awt.Point; - -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.test.IAbility; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.test.behavior.MoveToPoint; - -public class MoveAbility implements IAbility { - - @Override - public void execute(final CUnit caster, final int orderId, final CWidget targetUnit, final Point targetPoint) { - if (targetUnit == null) { - caster.setBehavior(new MoveToPoint(caster, targetPoint)); - } - else { - - } - } - -} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/behavior/AttackTarget.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/behavior/AttackTarget.java deleted file mode 100644 index 639f53e..0000000 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/behavior/AttackTarget.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.etheller.warsmash.viewer5.handlers.w3x.simulation.test.behavior; - -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.test.IBehavior; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.test.IState; - -public class AttackTarget implements IBehavior { - private final CUnit attackingUnit; - private final CWidget targetUnit; - - public AttackTarget(final CUnit attackingUnit, final CWidget targetUnit) { - this.attackingUnit = attackingUnit; - this.targetUnit = targetUnit; - } - - @Override - public IState resolveNext() { - return null; - } - -} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/behavior/MoveToPoint.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/behavior/MoveToPoint.java deleted file mode 100644 index 5e2cfa9..0000000 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/behavior/MoveToPoint.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.etheller.warsmash.viewer5.handlers.w3x.simulation.test.behavior; - -import java.awt.Point; -import java.awt.geom.Point2D; -import java.awt.geom.Point2D.Float; -import java.util.List; - -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.test.IBehavior; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.test.state.MoveState; - -public class MoveToPoint implements IBehavior { - - private final CSimulation simulation; - private final CUnit unit; - private final Point point; - private final MoveState moveState; - private final List waypointList; - - public MoveToPoint(final CSimulation simulation, final CUnit unit, final Point point) { - this.simulation = simulation; - this.unit = unit; - this.point = point; - this.waypointList = this.simulation.findNaiveSlowPath(unit, null, unit.getX(), unit.getY(), - new Point2D.Float(point.x, point.y), unit.getUnitType().getMovementType(), - unit.getUnitType().getCollisionSize(), true); - this.moveState = new MoveState(); - this.unit.setBehavior(this); - this.unit.setState(this.moveState); - resolveNext(); - } - - @Override - public void resolveNext() { - if (this.waypointList.isEmpty()) { - this.unit.setState(state); - } - else { - final Float firstWaypoint = this.waypointList.remove(0); - this.unit.setState(this.moveState.reset(this, this.unit, firstWaypoint.x, firstWaypoint.y)); - } - } - -} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/state/MoveState.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/state/MoveState.java deleted file mode 100644 index b1758db..0000000 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/test/state/MoveState.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.etheller.warsmash.viewer5.handlers.w3x.simulation.test.state; - -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.test.IBehavior; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.test.IState; - -public class MoveState implements IState { - public IBehavior behavior; - public CUnit unit; - public float targetX; - public float targetY; - - public MoveState reset(final IBehavior behavior, final CUnit unit, final float targetX, final float targetY) { - this.behavior = behavior; - this.unit = unit; - this.targetX = targetX; - this.targetY = targetY; - return this; - } - - @Override - public void execute() { - final float dx = this.targetX - this.unit.getX(); - final float dy = this.targetY - this.unit.getY(); - this.unit.setX(this.unit.getX(), collision); - } - -} From 4fbb7adeaf47b884f16f9b93dfe9a15383116473 Mon Sep 17 00:00:00 2001 From: Retera Date: Tue, 10 Nov 2020 22:44:22 -0500 Subject: [PATCH 064/116] Build behavior first draft --- .../etheller/warsmash/WarsmashGdxMapGame.java | 4 +- .../viewer5/handlers/w3x/War3MapViewer.java | 364 ++++++++++-------- .../handlers/w3x/rendersim/RenderItem.java | 16 +- .../handlers/w3x/rendersim/RenderUnit.java | 16 +- .../w3x/simulation/CGameplayConstants.java | 13 + .../handlers/w3x/simulation/CSimulation.java | 11 +- .../handlers/w3x/simulation/CUnit.java | 90 +++-- .../simulation/abilities/CAbilityAttack.java | 3 +- .../build/AbstractCAbilityBuild.java | 2 +- .../abilities/build/CAbilityOrcBuild.java | 6 +- .../simulation/behaviors/CBehaviorAttack.java | 8 +- .../simulation/behaviors/CBehaviorMove.java | 2 +- ...aviorBuild.java => CBehaviorOrcBuild.java} | 24 +- .../handlers/w3x/ui/CommandCardIcon.java | 4 +- .../viewer5/handlers/w3x/ui/MeleeUI.java | 126 ++++-- .../command/CommandCardCommandListener.java | 2 +- 16 files changed, 411 insertions(+), 280 deletions(-) rename core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/{CBehaviorBuild.java => CBehaviorOrcBuild.java} (51%) diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java index 0284e4e..ac61fe3 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java @@ -55,8 +55,8 @@ import com.etheller.warsmash.viewer5.handlers.w3x.ui.MeleeUI; import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.CommandErrorListener; public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProvider, InputProcessor { - private static final boolean ENABLE_AUDIO = false; - private static final boolean ENABLE_MUSIC = false; + private static final boolean ENABLE_AUDIO = true; + private static final boolean ENABLE_MUSIC = true; private DataSource codebase; private War3MapViewer viewer; private final Rectangle tempRect = new Rectangle(); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index 8aab527..abca5c8 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -524,7 +524,8 @@ public class War3MapViewer extends ModelViewer { @Override public CUnit createUnit(final CSimulation simulation, final War3ID typeId, final int playerIndex, final float x, final float y, final float facing) { - return null; + return createNewUnit(War3MapViewer.this.allObjectData, typeId, x, y, 0f, playerIndex, + (float) Math.toRadians(facing)); } }, this.terrain.pathingGrid, this.terrain.getEntireMap(), this.seededRandom, w3iFile.getPlayers()); @@ -756,176 +757,21 @@ public class War3MapViewer extends ModelViewer { private void loadUnitsAndItems(final Warcraft3MapObjectData modifications) throws IOException { final War3Map mpq = this.mapMpq; + this.soundsetNameToSoundset = new HashMap<>(); + if (this.dataSource.has("war3mapUnits.doo")) { final War3MapUnitsDoo dooFile = mpq.readUnits(); - final Map soundsetNameToSoundset = new HashMap<>(); - // Collect the units and items data. - UnitSoundset soundset = null; for (final com.etheller.warsmash.parsers.w3x.unitsdoo.Unit unit : dooFile.getUnits()) { - MutableGameObject row = null; - String path = null; - Splat unitShadowSplat = null; - BufferedImage buildingPathingPixelMap = null; + final War3ID unitId = unit.getId(); + final float unitX = unit.getLocation()[0]; + final float unitY = unit.getLocation()[1]; + final float unitZ = unit.getLocation()[2]; + final int playerIndex = unit.getPlayer(); + final float unitAngle = unit.getAngle(); - // Hardcoded? - WorldEditorDataType type = null; - if (sloc.equals(unit.getId())) { -// path = "Objects\\StartLocation\\StartLocation.mdx"; - type = null; /// ?????? - this.startLocations[unit.getPlayer()] = new Vector2(unit.getLocation()[0], unit.getLocation()[1]); - } - else { - row = modifications.getUnits().get(unit.getId()); - if (row == null) { - row = modifications.getItems().get(unit.getId()); - if (row != null) { - type = WorldEditorDataType.ITEM; - path = row.getFieldAsString(ITEM_FILE, 0); - - if (path.toLowerCase().endsWith(".mdl") || path.toLowerCase().endsWith(".mdx")) { - path = path.substring(0, path.length() - 4); - } - - final String unitShadow = "Shadow"; - if ((unitShadow != null) && !"_".equals(unitShadow)) { - final String texture = "ReplaceableTextures\\Shadows\\" + unitShadow + ".blp"; - final float shadowX = 50; - final float shadowY = 50; - final float shadowWidth = 128; - final float shadowHeight = 128; - if (!this.terrain.splats.containsKey(texture)) { - final Splat splat = new Splat(); - splat.opacity = 0.5f; - this.terrain.splats.put(texture, splat); - } - final float x = unit.getLocation()[0] - shadowX; - final float y = unit.getLocation()[1] - shadowY; - this.terrain.splats.get(texture).locations - .add(new float[] { x, y, x + shadowWidth, y + shadowHeight, 3 }); - unitShadowSplat = this.terrain.splats.get(texture); - } - - path += ".mdx"; - } - } - else { - type = WorldEditorDataType.UNITS; - path = row.getFieldAsString(UNIT_FILE, 0); - - if (path.toLowerCase().endsWith(".mdl") || path.toLowerCase().endsWith(".mdx")) { - path = path.substring(0, path.length() - 4); - } - if ((row.readSLKTagInt("fileVerFlags") == 2) && this.dataSource.has(path + "_V1.mdx")) { - path += "_V1"; - } - - path += ".mdx"; - - final String uberSplat = row.getFieldAsString(UBER_SPLAT, 0); - if (uberSplat != null) { - final Element uberSplatInfo = this.terrain.uberSplatTable.get(uberSplat); - if (uberSplatInfo != null) { - final String texturePath = uberSplatInfo.getField("Dir") + "\\" - + uberSplatInfo.getField("file") + ".blp"; - if (!this.terrain.splats.containsKey(texturePath)) { - this.terrain.splats.put(texturePath, new Splat()); - } - final float x = unit.getLocation()[0]; - final float y = unit.getLocation()[1]; - final float s = uberSplatInfo.getFieldFloatValue("Scale"); - this.terrain.splats.get(texturePath).locations - .add(new float[] { x - s, y - s, x + s, y + s, 1 }); - } - } - - final String unitShadow = row.getFieldAsString(UNIT_SHADOW, 0); - if ((unitShadow != null) && !"_".equals(unitShadow)) { - final String texture = "ReplaceableTextures\\Shadows\\" + unitShadow + ".blp"; - final float shadowX = row.getFieldAsFloat(UNIT_SHADOW_X, 0); - final float shadowY = row.getFieldAsFloat(UNIT_SHADOW_Y, 0); - final float shadowWidth = row.getFieldAsFloat(UNIT_SHADOW_W, 0); - final float shadowHeight = row.getFieldAsFloat(UNIT_SHADOW_H, 0); - if (this.mapMpq.has(texture)) { - if (!this.terrain.splats.containsKey(texture)) { - final Splat splat = new Splat(); - splat.opacity = 0.5f; - this.terrain.splats.put(texture, splat); - } - final float x = unit.getLocation()[0] - shadowX; - final float y = unit.getLocation()[1] - shadowY; - this.terrain.splats.get(texture).locations - .add(new float[] { x, y, x + shadowWidth, y + shadowHeight, 3 }); - unitShadowSplat = this.terrain.splats.get(texture); - } - } - - final String buildingShadow = row.getFieldAsString(BUILDING_SHADOW, 0); - if ((buildingShadow != null) && !"_".equals(buildingShadow)) { - this.terrain.addShadow(buildingShadow, unit.getLocation()[0], unit.getLocation()[1]); - } - buildingPathingPixelMap = getBuildingPathingPixelMap(row); - if (buildingPathingPixelMap != null) { - this.terrain.pathingGrid.blitPathingOverlayTexture(unit.getLocation()[0], - unit.getLocation()[1], (int) Math.toDegrees(unit.getAngle()), - buildingPathingPixelMap); - } - - final String soundName = row.getFieldAsString(UNIT_SOUNDSET, 0); - UnitSoundset unitSoundset = soundsetNameToSoundset.get(soundName); - if (unitSoundset == null) { - unitSoundset = new UnitSoundset(this.dataSource, this.unitAckSoundsTable, soundName); - soundsetNameToSoundset.put(soundName, unitSoundset); - } - soundset = unitSoundset; - } - } - - if (path != null) { - final MdxModel model = (MdxModel) this.load(path, this.mapPathSolver, this.solverParams); - MdxModel portraitModel; - final String portraitPath = path.substring(0, path.length() - 4) + "_portrait.mdx"; - if (this.dataSource.has(portraitPath)) { - portraitModel = (MdxModel) this.load(portraitPath, this.mapPathSolver, this.solverParams); - } - else { - portraitModel = model; - } - if (type == WorldEditorDataType.UNITS) { - final float angle = (float) Math.toDegrees(unit.getAngle()); - final CUnit simulationUnit = this.simulation.createUnit(row.getAlias(), unit.getPlayer(), - unit.getLocation()[0], unit.getLocation()[1], angle, buildingPathingPixelMap); - final RenderUnitTypeData typeData = getUnitTypeData(unit.getId(), row); - final RenderUnit renderUnit = new RenderUnit(this, model, row, unit, soundset, portraitModel, - simulationUnit, typeData); - this.unitToRenderPeer.put(simulationUnit, renderUnit); - this.units.add(renderUnit); - if (unitShadowSplat != null) { - unitShadowSplat.unitMapping.add(new Consumer() { - @Override - public void accept(final SplatMover t) { - renderUnit.shadow = t; - } - }); - } - } - else { - this.items.add(new RenderItem(this, model, row, unit, soundset, portraitModel)); // TODO store - // somewhere - if (unitShadowSplat != null) { - unitShadowSplat.unitMapping.add(new Consumer() { - @Override - public void accept(final SplatMover t) { - - } - }); - } - } - } - else { - System.err.println("Unknown unit ID: " + unit.getId()); - } + createNewUnit(modifications, unitId, unitX, unitY, unitZ, playerIndex, unitAngle); } } @@ -935,6 +781,187 @@ public class War3MapViewer extends ModelViewer { this.anyReady = true; } + private CUnit createNewUnit(final Warcraft3MapObjectData modifications, final War3ID unitId, float unitX, + float unitY, final float unitZ, final int playerIndex, final float unitAngle) { + UnitSoundset soundset = null; + MutableGameObject row = null; + String path = null; + Splat unitShadowSplat = null; + BufferedImage buildingPathingPixelMap = null; + final float unitVertexScale = 1.0f; + + // Hardcoded? + WorldEditorDataType type = null; + if (sloc.equals(unitId)) { +// path = "Objects\\StartLocation\\StartLocation.mdx"; + type = null; /// ?????? + this.startLocations[playerIndex] = new Vector2(unitX, unitY); + } + else { + row = modifications.getUnits().get(unitId); + if (row == null) { + row = modifications.getItems().get(unitId); + if (row != null) { + type = WorldEditorDataType.ITEM; + path = row.getFieldAsString(ITEM_FILE, 0); + + if (path.toLowerCase().endsWith(".mdl") || path.toLowerCase().endsWith(".mdx")) { + path = path.substring(0, path.length() - 4); + } + + final String unitShadow = "Shadow"; + if ((unitShadow != null) && !"_".equals(unitShadow)) { + final String texture = "ReplaceableTextures\\Shadows\\" + unitShadow + ".blp"; + final float shadowX = 50; + final float shadowY = 50; + final float shadowWidth = 128; + final float shadowHeight = 128; + if (!this.terrain.splats.containsKey(texture)) { + final Splat splat = new Splat(); + splat.opacity = 0.5f; + this.terrain.splats.put(texture, splat); + } + final float x = unitX - shadowX; + final float y = unitY - shadowY; + this.terrain.splats.get(texture).locations + .add(new float[] { x, y, x + shadowWidth, y + shadowHeight, 3 }); + unitShadowSplat = this.terrain.splats.get(texture); + } + + path += ".mdx"; + } + } + else { + type = WorldEditorDataType.UNITS; + path = getUnitModelPath(row); + + buildingPathingPixelMap = getBuildingPathingPixelMap(row); + if (buildingPathingPixelMap != null) { + unitX = Math.round(unitX / 64f) * 64f; + unitY = Math.round(unitY / 64f) * 64f; + this.terrain.pathingGrid.blitPathingOverlayTexture(unitX, unitY, (int) Math.toDegrees(unitAngle), + buildingPathingPixelMap); + } + + final String uberSplat = row.getFieldAsString(UBER_SPLAT, 0); + if (uberSplat != null) { + final Element uberSplatInfo = this.terrain.uberSplatTable.get(uberSplat); + if (uberSplatInfo != null) { + final String texturePath = uberSplatInfo.getField("Dir") + "\\" + uberSplatInfo.getField("file") + + ".blp"; + if (!this.terrain.splats.containsKey(texturePath)) { + this.terrain.splats.put(texturePath, new Splat()); + } + final float x = unitX; + final float y = unitY; + final float s = uberSplatInfo.getFieldFloatValue("Scale"); + this.terrain.splats.get(texturePath).locations + .add(new float[] { x - s, y - s, x + s, y + s, 1 }); + } + } + + final String unitShadow = row.getFieldAsString(UNIT_SHADOW, 0); + if ((unitShadow != null) && !"_".equals(unitShadow)) { + final String texture = "ReplaceableTextures\\Shadows\\" + unitShadow + ".blp"; + final float shadowX = row.getFieldAsFloat(UNIT_SHADOW_X, 0); + final float shadowY = row.getFieldAsFloat(UNIT_SHADOW_Y, 0); + final float shadowWidth = row.getFieldAsFloat(UNIT_SHADOW_W, 0); + final float shadowHeight = row.getFieldAsFloat(UNIT_SHADOW_H, 0); + if (this.mapMpq.has(texture)) { + if (!this.terrain.splats.containsKey(texture)) { + final Splat splat = new Splat(); + splat.opacity = 0.5f; + this.terrain.splats.put(texture, splat); + } + final float x = unitX - shadowX; + final float y = unitY - shadowY; + this.terrain.splats.get(texture).locations + .add(new float[] { x, y, x + shadowWidth, y + shadowHeight, 3 }); + unitShadowSplat = this.terrain.splats.get(texture); + } + } + + final String buildingShadow = row.getFieldAsString(BUILDING_SHADOW, 0); + if ((buildingShadow != null) && !"_".equals(buildingShadow)) { + this.terrain.addShadow(buildingShadow, unitX, unitY); + } + + final String soundName = row.getFieldAsString(UNIT_SOUNDSET, 0); + UnitSoundset unitSoundset = this.soundsetNameToSoundset.get(soundName); + if (unitSoundset == null) { + unitSoundset = new UnitSoundset(this.dataSource, this.unitAckSoundsTable, soundName); + this.soundsetNameToSoundset.put(soundName, unitSoundset); + } + soundset = unitSoundset; + + } + } + + if (path != null) { + final MdxModel model = (MdxModel) this.load(path, this.mapPathSolver, this.solverParams); + MdxModel portraitModel; + final String portraitPath = path.substring(0, path.length() - 4) + "_portrait.mdx"; + if (this.dataSource.has(portraitPath)) { + portraitModel = (MdxModel) this.load(portraitPath, this.mapPathSolver, this.solverParams); + } + else { + portraitModel = model; + } + if (type == WorldEditorDataType.UNITS) { + final float angle = (float) Math.toDegrees(unitAngle); + final CUnit simulationUnit = this.simulation.createUnit(row.getAlias(), playerIndex, unitX, unitY, + angle, buildingPathingPixelMap); + final RenderUnitTypeData typeData = getUnitTypeData(unitId, row); + final RenderUnit renderUnit = new RenderUnit(this, model, row, unitX, unitY, unitZ, playerIndex, + soundset, portraitModel, simulationUnit, typeData); + this.unitToRenderPeer.put(simulationUnit, renderUnit); + this.units.add(renderUnit); + if (unitShadowSplat != null) { + unitShadowSplat.unitMapping.add(new Consumer() { + @Override + public void accept(final SplatMover t) { + renderUnit.shadow = t; + } + }); + } + return simulationUnit; + } + else { + this.items + .add(new RenderItem(this, model, row, unitX, unitY, unitZ, unitAngle, soundset, portraitModel)); // TODO + // store + // somewhere + if (unitShadowSplat != null) { + unitShadowSplat.unitMapping.add(new Consumer() { + @Override + public void accept(final SplatMover t) { + + } + }); + } + } + } + else { + System.err.println("Unknown unit ID: " + unitId); + } + return null; + } + + public String getUnitModelPath(final MutableGameObject row) { + String path; + path = row.getFieldAsString(UNIT_FILE, 0); + + if (path.toLowerCase().endsWith(".mdl") || path.toLowerCase().endsWith(".mdx")) { + path = path.substring(0, path.length() - 4); + } + if ((row.readSLKTagInt("fileVerFlags") == 2) && this.dataSource.has(path + "_V1.mdx")) { + path += "_V1"; + } + + path += ".mdx"; + return path; + } + private BufferedImage getBuildingPathingPixelMap(final MutableGameObject row) { BufferedImage buildingPathingPixelMap = null; final String pathingTexture = row.getFieldAsString(UNIT_PATHING, 0); @@ -962,7 +989,7 @@ public class War3MapViewer extends ModelViewer { return buildingPathingPixelMap; } - private RenderUnitTypeData getUnitTypeData(final War3ID key, final MutableGameObject row) { + public RenderUnitTypeData getUnitTypeData(final War3ID key, final MutableGameObject row) { RenderUnitTypeData unitTypeData = this.unitIdToTypeData.get(key); if (unitTypeData == null) { unitTypeData = new RenderUnitTypeData(row.getFieldAsFloat(MAX_PITCH, 0), row.getFieldAsFloat(MAX_ROLL, 0), @@ -1295,6 +1322,7 @@ public class War3MapViewer extends ModelViewer { private WorldEditStrings worldEditStrings; private Warcraft3MapObjectData allObjectData; private AbilityDataUI abilityDataUI; + private Map soundsetNameToSoundset; /** * Returns a power of two size for the given target capacity. @@ -1388,6 +1416,10 @@ public class War3MapViewer extends ModelViewer { return this.uiSounds; } + public Warcraft3MapObjectData getAllObjectData() { + return this.allObjectData; + } + public float getWalkableRenderHeight(final float x, final float y) { this.walkableObjectsTree.intersect(x, y, this.walkablesIntersector.reset(x, y)); return this.walkablesIntersector.z; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderItem.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderItem.java index f90d0e1..ac7169c 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderItem.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderItem.java @@ -20,23 +20,19 @@ public class RenderItem { public float radius; public UnitSoundset soundset; public final MdxModel portraitModel; - public int playerIndex; - public RenderItem(final War3MapViewer map, final MdxModel model, final MutableGameObject row, - final com.etheller.warsmash.parsers.w3x.unitsdoo.Unit unit, final UnitSoundset soundset, + public RenderItem(final War3MapViewer map, final MdxModel model, final MutableGameObject row, final float x, + final float y, final float z, final float angle, final UnitSoundset soundset, final MdxModel portraitModel) { this.portraitModel = portraitModel; final MdxComplexInstance instance = (MdxComplexInstance) model.addInstance(); - final float[] location = unit.getLocation(); - System.arraycopy(location, 0, this.location, 0, 3); - instance.move(location); - final float angle = unit.getAngle(); + this.location[0] = x; + this.location[1] = y; + this.location[2] = z; + instance.move(this.location); // instance.localRotation.setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, angle); instance.rotate(new Quaternion().setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, angle)); - instance.scale(unit.getScale()); - this.playerIndex = unit.getPlayer(); - instance.setTeamColor(this.playerIndex); instance.setScene(map.worldScene); if (row != null) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java index 05143df..e6368ba 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java @@ -30,7 +30,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitAnimationListe import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; public class RenderUnit { - private static final Quaternion tempQuat = new Quaternion(); + public static final Quaternion tempQuat = new Quaternion(); private static final War3ID RED = War3ID.fromString("uclr"); private static final War3ID GREEN = War3ID.fromString("uclg"); private static final War3ID BLUE = War3ID.fromString("uclb"); @@ -67,25 +67,25 @@ public class RenderUnit { private boolean boneCorpse; private final RenderUnitTypeData typeData; - public RenderUnit(final War3MapViewer map, final MdxModel model, final MutableGameObject row, - final com.etheller.warsmash.parsers.w3x.unitsdoo.Unit unit, final UnitSoundset soundset, + public RenderUnit(final War3MapViewer map, final MdxModel model, final MutableGameObject row, final float x, + final float y, final float z, final int playerIndex, final UnitSoundset soundset, final MdxModel portraitModel, final CUnit simulationUnit, final RenderUnitTypeData typeData) { this.portraitModel = portraitModel; this.simulationUnit = simulationUnit; this.typeData = typeData; final MdxComplexInstance instance = (MdxComplexInstance) model.addInstance(); - final float[] location = unit.getLocation(); - System.arraycopy(location, 0, this.location, 0, 3); - instance.move(location); + this.location[0] = x; + this.location[1] = y; + this.location[2] = z; + instance.move(this.location); this.facing = simulationUnit.getFacing(); final float angle = (float) Math.toRadians(this.facing); // instance.localRotation.setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, angle); this.x = simulationUnit.getX(); this.y = simulationUnit.getY(); instance.rotate(tempQuat.setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, angle)); - instance.scale(unit.getScale()); - this.playerIndex = unit.getPlayer() & 0xFFFF; + this.playerIndex = playerIndex & 0xFFFF; instance.setTeamColor(this.playerIndex); instance.setScene(map.worldScene); this.unitAnimationListenerImpl = new UnitAnimationListenerImpl(instance); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CGameplayConstants.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CGameplayConstants.java index 9b70d09..4d18759 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CGameplayConstants.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CGameplayConstants.java @@ -24,6 +24,8 @@ public class CGameplayConstants { private final float gameDayHours; private final float gameDayLength; private final float structureDecayTime; + private final float buildingAngle; + private final float rootAngle; public CGameplayConstants(final DataTable parsedDataTable) { final Element miscData = parsedDataTable.get("Misc"); @@ -41,6 +43,9 @@ public class CGameplayConstants { this.gameDayHours = miscData.getFieldFloatValue("DayHours"); this.gameDayLength = miscData.getFieldFloatValue("DayLength"); + this.buildingAngle = miscData.getFieldFloatValue("BuildingAngle"); + this.rootAngle = miscData.getFieldFloatValue("RootAngle"); + final CDefenseType[] defenseTypeOrder = { CDefenseType.SMALL, CDefenseType.MEDIUM, CDefenseType.LARGE, CDefenseType.FORT, CDefenseType.NORMAL, CDefenseType.HERO, CDefenseType.DIVINE, CDefenseType.NONE, }; this.damageBonusTable = new float[CAttackType.values().length][defenseTypeOrder.length]; @@ -111,4 +116,12 @@ public class CGameplayConstants { public float getStructureDecayTime() { return this.structureDecayTime; } + + public float getBuildingAngle() { + return this.buildingAngle; + } + + public float getRootAngle() { + return this.rootAngle; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java index c035c9c..2e29414 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java @@ -34,6 +34,7 @@ public class CSimulation { private final CAbilityData abilityData; private final CUnitData unitData; private final List units; + private final List newUnits; private final List players; private final List projectiles; private final List newProjectiles; @@ -60,6 +61,7 @@ public class CSimulation { this.abilityData = new CAbilityData(parsedAbilityData); this.unitData = new CUnitData(parsedUnitData, this.abilityData); this.units = new ArrayList<>(); + this.newUnits = new ArrayList<>(); this.projectiles = new ArrayList<>(); this.newProjectiles = new ArrayList<>(); this.handleIdAllocator = new HandleIdAllocator(); @@ -119,7 +121,7 @@ public class CSimulation { final float facing, final BufferedImage buildingPathingPixelMap) { final CUnit unit = this.unitData.create(this, playerIndex, typeId, x, y, facing, buildingPathingPixelMap, this.simulationRenderController, this.handleIdAllocator); - this.units.add(unit); + this.newUnits.add(unit); this.handleIdToUnit.put(unit.getHandleId(), unit); for (final CAbility ability : unit.getAbilities()) { this.handleIdToAbility.put(ability.getHandleId(), ability); @@ -128,6 +130,11 @@ public class CSimulation { return unit; } + public CUnit createUnit(final War3ID typeId, final int playerIndex, final float x, final float y, + final float facing) { + return this.simulationRenderController.createUnit(this, typeId, playerIndex, x, y, facing); + } + public CUnit getUnit(final int handleId) { return this.handleIdToUnit.get(handleId); } @@ -175,6 +182,8 @@ public class CSimulation { this.simulationRenderController.removeUnit(unit); } } + this.units.addAll(this.newUnits); + this.newUnits.clear(); final Iterator projectileIterator = this.projectiles.iterator(); while (projectileIterator.hasNext()) { final CAttackProjectile projectile = projectileIterator.next(); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java index 0baebe2..8eb6864 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java @@ -462,53 +462,63 @@ public class CUnit extends CWidget { final CUnit targetUnit = (CUnit) target; final CUnitType targetUnitType = targetUnit.getUnitType(); if (targetUnitType.isBuilding() && (targetUnitType.getBuildingPathingPixelMap() != null)) { - final float relativeOffsetX = getX() - target.getX(); - final float relativeOffsetY = getY() - target.getY(); - final int rotation = ((int) targetUnit.getFacing() + 450) % 360; final BufferedImage buildingPathingPixelMap = targetUnitType.getBuildingPathingPixelMap(); - final int gridWidth = ((rotation % 180) != 0) ? buildingPathingPixelMap.getHeight() - : buildingPathingPixelMap.getWidth(); - final int gridHeight = ((rotation % 180) != 0) ? buildingPathingPixelMap.getWidth() - : buildingPathingPixelMap.getHeight(); - final int relativeGridX = (int) Math.floor(relativeOffsetX / 32f) + (gridWidth / 2); - final int relativeGridY = (int) Math.floor(relativeOffsetY / 32f) + (gridHeight / 2); - final int rangeInCells = (int) Math.floor(range / 32f); - final int rangeInCellsSquare = rangeInCells * rangeInCells; - int minCheckX = relativeGridX - rangeInCells; - int minCheckY = relativeGridY - rangeInCells; - int maxCheckX = relativeGridX + rangeInCells; - int maxCheckY = relativeGridY + rangeInCells; - if ((minCheckX < gridWidth) && (maxCheckX >= 0)) { - if ((minCheckY < gridHeight) && (maxCheckY >= 0)) { - if (minCheckX < 0) { - minCheckX = 0; - } - if (minCheckY < 0) { - minCheckY = 0; - } - if (maxCheckX > (gridWidth - 1)) { - maxCheckX = gridWidth - 1; - } - if (maxCheckY > (gridHeight - 1)) { - maxCheckY = gridHeight - 1; - } - for (int checkX = minCheckX; checkX <= maxCheckX; checkX++) { - for (int checkY = minCheckY; checkY <= maxCheckY; checkY++) { - final int dx = relativeGridX - checkX; - final int dy = relativeGridY - checkY; - if (((dx * dx) + (dy * dy)) <= rangeInCellsSquare) { - if (((getRGBFromPixelData(buildingPathingPixelMap, checkX, checkY, rotation) - & 0xFF0000) >>> 16) > 127) { - return true; - } - } + final float targetX = target.getX(); + final float targetY = target.getY(); + if (canReachToPathing(range, targetUnit.getFacing(), buildingPathingPixelMap, targetX, targetY)) { + return true; + } + } + } + return distance <= range; + } + + public boolean canReachToPathing(final float range, final float rotationForPathing, + final BufferedImage buildingPathingPixelMap, final float targetX, final float targetY) { + final int rotation = ((int) rotationForPathing + 450) % 360; + final float relativeOffsetX = getX() - targetX; + final float relativeOffsetY = getY() - targetY; + final int gridWidth = ((rotation % 180) != 0) ? buildingPathingPixelMap.getHeight() + : buildingPathingPixelMap.getWidth(); + final int gridHeight = ((rotation % 180) != 0) ? buildingPathingPixelMap.getWidth() + : buildingPathingPixelMap.getHeight(); + final int relativeGridX = (int) Math.floor(relativeOffsetX / 32f) + (gridWidth / 2); + final int relativeGridY = (int) Math.floor(relativeOffsetY / 32f) + (gridHeight / 2); + final int rangeInCells = (int) Math.floor(range / 32f); + final int rangeInCellsSquare = rangeInCells * rangeInCells; + int minCheckX = relativeGridX - rangeInCells; + int minCheckY = relativeGridY - rangeInCells; + int maxCheckX = relativeGridX + rangeInCells; + int maxCheckY = relativeGridY + rangeInCells; + if ((minCheckX < gridWidth) && (maxCheckX >= 0)) { + if ((minCheckY < gridHeight) && (maxCheckY >= 0)) { + if (minCheckX < 0) { + minCheckX = 0; + } + if (minCheckY < 0) { + minCheckY = 0; + } + if (maxCheckX > (gridWidth - 1)) { + maxCheckX = gridWidth - 1; + } + if (maxCheckY > (gridHeight - 1)) { + maxCheckY = gridHeight - 1; + } + for (int checkX = minCheckX; checkX <= maxCheckX; checkX++) { + for (int checkY = minCheckY; checkY <= maxCheckY; checkY++) { + final int dx = relativeGridX - checkX; + final int dy = relativeGridY - checkY; + if (((dx * dx) + (dy * dy)) <= rangeInCellsSquare) { + if (((getRGBFromPixelData(buildingPathingPixelMap, checkX, checkY, rotation) + & 0xFF0000) >>> 16) > 127) { + return true; } } } } } } - return distance <= range; + return false; } private int getRGBFromPixelData(final BufferedImage buildingPathingPixelMap, final int checkX, final int checkY, diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java index e6d9e0f..cbe2994 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java @@ -10,12 +10,11 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CAllianceType; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.test.IAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver.TargetType; -public class CAbilityAttack implements CAbility, IAbility { +public class CAbilityAttack implements CAbility { private final int handleId; public CAbilityAttack(final int handleId) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java index a946d90..487a78f 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java @@ -78,6 +78,6 @@ public abstract class AbstractCAbilityBuild extends AbstractCAbility implements @Override public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId) { - return false; + return true; } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityOrcBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityOrcBuild.java index 79678f3..c10fa33 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityOrcBuild.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityOrcBuild.java @@ -9,9 +9,11 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.build.CBehaviorOrcBuild; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; public class CAbilityOrcBuild extends AbstractCAbilityBuild { + private CBehaviorOrcBuild buildBehavior; public CAbilityOrcBuild(final int handleId, final List structuresBuilt) { super(handleId, structuresBuilt); @@ -24,6 +26,7 @@ public class CAbilityOrcBuild extends AbstractCAbilityBuild { @Override public void onAdd(final CSimulation game, final CUnit unit) { + this.buildBehavior = new CBehaviorOrcBuild(unit); } @Override @@ -37,8 +40,7 @@ public class CAbilityOrcBuild extends AbstractCAbilityBuild { @Override public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final Vector2 point) { - // TODO Auto-generated method stub - return null; + return this.buildBehavior.reset(point.x, point.y, orderId, getBaseOrderId()); } @Override diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java index bde7654..15b59ca 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java @@ -39,12 +39,8 @@ public class CBehaviorAttack extends CAbstractRangedWidgetTargetBehavior { @Override public boolean isWithinRange(final CSimulation simulation) { float range = this.unitAttack.getRange(); - if ((this.target instanceof CUnit) && (((CUnit) this.target).isMoving()) && (simulation - .getGameTurnTick() < this.unit.getCooldownEndTime() /* - * only apply range motion buffer if they were - * already in range and attacked - */)) { - range += this.unitAttack.getRangeMotionBuffer() + 1000; + if (simulation.getGameTurnTick() < this.unit.getCooldownEndTime()) { + range += this.unitAttack.getRangeMotionBuffer(); } return this.unit.canReach(this.target, range) && (this.unit.distance(this.target) >= this.unit.getUnitType().getMinimumAttackRange()); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorMove.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorMove.java index 1f3efc3..6e1e995 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorMove.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorMove.java @@ -88,7 +88,7 @@ public class CBehaviorMove implements CBehavior { @Override public CBehavior update(final CSimulation simulation) { if ((this.rangedBehavior != null) && this.rangedBehavior.isWithinRange(simulation)) { - return this.rangedBehavior; + return this.rangedBehavior.update(simulation); } final float prevX = this.unit.getX(); final float prevY = this.unit.getY(); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java similarity index 51% rename from core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorBuild.java rename to core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java index f407ad5..38dd56d 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorBuild.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java @@ -1,35 +1,43 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.build; +import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CAbstractRangedPointTargetBehavior; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; -public class CBehaviorBuild extends CAbstractRangedPointTargetBehavior { - private int orderId; +public class CBehaviorOrcBuild extends CAbstractRangedPointTargetBehavior { + private int highlightOrderId; + private War3ID orderId; - public CBehaviorBuild(final CUnit unit) { + public CBehaviorOrcBuild(final CUnit unit) { super(unit); } - public CBehavior reset(final float targetX, final float targetY, final int orderId) { - this.orderId = orderId; + public CBehavior reset(final float targetX, final float targetY, final int orderId, final int highlightOrderId) { + this.highlightOrderId = highlightOrderId; + this.orderId = new War3ID(orderId); return innerReset(targetX, targetY); } @Override public boolean isWithinRange(final CSimulation simulation) { - return this.unit.distance(this.targetX, this.targetY) <= 23525; + final CUnitType unitType = simulation.getUnitData().getUnitType(this.orderId); + return this.unit.canReachToPathing(0, simulation.getGameplayConstants().getBuildingAngle(), + unitType.getBuildingPathingPixelMap(), this.targetX, this.targetY); } @Override public int getHighlightOrderId() { - return this.orderId; + return this.highlightOrderId; } @Override protected CBehavior update(final CSimulation simulation, final boolean withinRange) { - return this; + simulation.createUnit(this.orderId, this.unit.getPlayerIndex(), this.targetX, this.targetY, + simulation.getGameplayConstants().getBuildingAngle()); + return this.unit.pollNextOrderBehavior(simulation); } @Override diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java index 6d2f033..12cc1d8 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java @@ -137,11 +137,11 @@ public class CommandCardIcon extends AbstractRenderableFrame { this.commandCardCommandListener.openMenu(this.orderId); } else { - this.commandCardCommandListener.startUsingAbility(this.abilityHandleId, this.orderId); + this.commandCardCommandListener.startUsingAbility(this.abilityHandleId, this.orderId, false); } } else if (button == Input.Buttons.RIGHT) { - this.commandCardCommandListener.startUsingAbility(this.abilityHandleId, this.autoCastOrderId); + this.commandCardCommandListener.startUsingAbility(this.abilityHandleId, this.autoCastOrderId, true); } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java index 433fd21..0f72e55 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java @@ -1,5 +1,6 @@ package com.etheller.warsmash.viewer5.handlers.w3x.ui; +import java.awt.image.BufferedImage; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -30,8 +31,11 @@ import com.etheller.warsmash.parsers.fdf.frames.TextureFrame; import com.etheller.warsmash.parsers.fdf.frames.UIFrame; import com.etheller.warsmash.parsers.jass.Jass2.RootFrameListener; import com.etheller.warsmash.parsers.mdlx.Layer.FilterMode; +import com.etheller.warsmash.units.manager.MutableObjectData; import com.etheller.warsmash.util.FastNumberFormat; import com.etheller.warsmash.util.ImageUtils; +import com.etheller.warsmash.util.RenderMathUtils; +import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.util.WarsmashConstants; import com.etheller.warsmash.viewer5.Scene; import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; @@ -55,10 +59,12 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitClassification; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitFilterFunction; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitStateListener; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityAttack; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityView; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.AbstractCAbilityBuild; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CDefenseType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CodeKeyType; @@ -139,6 +145,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma private CAbilityView activeCommand; private int activeCommandOrderId; private RenderUnit activeCommandUnit; + private MdxComplexInstance cursorModelInstance = null; + private BufferedImage cursorModelPathing; private int selectedSoundCount = 0; private final ActiveCommandUnitTargetFilter activeCommandUnitTargetFilter; @@ -373,7 +381,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } @Override - public void startUsingAbility(final int abilityHandleId, final int orderId) { + public void startUsingAbility(final int abilityHandleId, final int orderId, final boolean rightClick) { // TODO not O(N) CAbilityView abilityToUse = null; for (final CAbility ability : this.selectedUnit.getSimulationUnit().getAbilities()) { @@ -411,6 +419,9 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.unitOrderListener.issueImmediateOrder(this.selectedUnit.getSimulationUnit().getHandleId(), abilityHandleId, orderId, isShiftDown()); } + if (rightClick) { + this.war3MapViewer.getUiSounds().getSound("AutoCastButtonClick").play(this.uiScene.audioContext, 0, 0); + } } @Override @@ -434,8 +445,10 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma public void update(final float deltaTime) { this.portrait.update(); - int mouseX = Gdx.input.getX(); - int mouseY = Gdx.input.getY(); + final int baseMouseX = Gdx.input.getX(); + int mouseX = baseMouseX; + final int baseMouseY = Gdx.input.getY(); + int mouseY = baseMouseY; final int minX = this.uiViewport.getScreenX(); final int maxX = minX + this.uiViewport.getScreenWidth(); final int minY = this.uiViewport.getScreenY(); @@ -458,38 +471,85 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.cursorFrame.setFramePointY(FramePoint.BOTTOM, screenCoordsVector.y); if (this.activeCommand != null) { - this.cursorFrame.setSequence("Target"); - } - else if (down) { - if (left) { - this.cursorFrame.setSequence("Scroll Down Left"); - } - else if (right) { - this.cursorFrame.setSequence("Scroll Down Right"); + if (this.activeCommand instanceof AbstractCAbilityBuild) { + boolean justLoaded = false; + if (this.cursorModelInstance == null) { + final MutableObjectData unitData = this.war3MapViewer.getAllObjectData().getUnits(); + final War3ID buildingTypeId = new War3ID(this.activeCommandOrderId); + final String unitModelPath = this.war3MapViewer.getUnitModelPath(unitData.get(buildingTypeId)); + final MdxModel model = (MdxModel) this.war3MapViewer.load(unitModelPath, + this.war3MapViewer.mapPathSolver, this.war3MapViewer.solverParams); + this.cursorModelInstance = (MdxComplexInstance) model.addInstance(); + this.cursorModelInstance.setVertexColor(new float[] { 1, 1, 1, 0.5f }); + this.cursorModelInstance.rotate(RenderUnit.tempQuat.setFromAxis(RenderMathUtils.VEC3_UNIT_Z, + this.war3MapViewer.simulation.getGameplayConstants().getBuildingAngle())); + this.cursorModelInstance.setAnimationSpeed(0f); + justLoaded = true; + final CUnitType buildingUnitType = this.war3MapViewer.simulation.getUnitData() + .getUnitType(buildingTypeId); + this.cursorModelPathing = buildingUnitType.getBuildingPathingPixelMap(); + } + this.war3MapViewer.getClickLocation(clickLocationTemp, baseMouseX, + Gdx.graphics.getHeight() - baseMouseY); + if (this.cursorModelPathing != null) { + clickLocationTemp.x = Math.round(clickLocationTemp.x / 64f) * 64f; + clickLocationTemp.y = Math.round(clickLocationTemp.y / 64f) * 64f; + clickLocationTemp.z = this.war3MapViewer.terrain.getGroundHeight(clickLocationTemp.x, + clickLocationTemp.y); + } + this.cursorModelInstance.setLocation(clickLocationTemp); + SequenceUtils.randomSequence(this.cursorModelInstance, PrimaryTag.STAND); + this.cursorFrame.setVisible(false); + if (justLoaded) { + this.cursorModelInstance.setScene(this.war3MapViewer.worldScene); + } } else { - this.cursorFrame.setSequence("Scroll Down"); + if (this.cursorModelInstance != null) { + this.cursorModelInstance.detach(); + this.cursorModelInstance = null; + this.cursorFrame.setVisible(true); + } + this.cursorFrame.setSequence("Target"); } } - else if (up) { - if (left) { - this.cursorFrame.setSequence("Scroll Up Left"); - } - else if (right) { - this.cursorFrame.setSequence("Scroll Up Right"); - } - else { - this.cursorFrame.setSequence("Scroll Up"); - } - } - else if (left) { - this.cursorFrame.setSequence("Scroll Left"); - } - else if (right) { - this.cursorFrame.setSequence("Scroll Right"); - } else { - this.cursorFrame.setSequence("Normal"); + if (this.cursorModelInstance != null) { + this.cursorModelInstance.detach(); + this.cursorModelInstance = null; + this.cursorFrame.setVisible(true); + } + if (down) { + if (left) { + this.cursorFrame.setSequence("Scroll Down Left"); + } + else if (right) { + this.cursorFrame.setSequence("Scroll Down Right"); + } + else { + this.cursorFrame.setSequence("Scroll Down"); + } + } + else if (up) { + if (left) { + this.cursorFrame.setSequence("Scroll Up Left"); + } + else if (right) { + this.cursorFrame.setSequence("Scroll Up Right"); + } + else { + this.cursorFrame.setSequence("Scroll Up"); + } + } + else if (left) { + this.cursorFrame.setSequence("Scroll Left"); + } + else if (right) { + this.cursorFrame.setSequence("Scroll Right"); + } + else { + this.cursorFrame.setSequence("Normal"); + } } final float groundHeight = Math.max( this.war3MapViewer.terrain.getGroundHeight(this.cameraManager.target.x, this.cameraManager.target.y), @@ -866,6 +926,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } this.selectedSoundCount = 0; if (!shiftDown) { + this.subMenuOrderIdStack.clear(); this.activeCommandUnit = null; this.activeCommand = null; this.activeCommandOrderId = -1; @@ -896,7 +957,12 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma portraitTalk(); } this.selectedSoundCount = 0; + if (this.activeCommand instanceof AbstractCAbilityBuild) { + this.war3MapViewer.getUiSounds().getSound("PlaceBuildingDefault") + .play(this.uiScene.audioContext, 0, 0); + } if (!shiftDown) { + this.subMenuOrderIdStack.clear(); this.activeCommandUnit = null; this.activeCommand = null; this.activeCommandOrderId = -1; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/CommandCardCommandListener.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/CommandCardCommandListener.java index 02ef4a4..36dc718 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/CommandCardCommandListener.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/CommandCardCommandListener.java @@ -1,7 +1,7 @@ package com.etheller.warsmash.viewer5.handlers.w3x.ui.command; public interface CommandCardCommandListener { - void startUsingAbility(int abilityHandleId, int orderId); + void startUsingAbility(int abilityHandleId, int orderId, boolean rightClick); void openMenu(int orderId); } From 632576323c3ae9ad2282f296878f555cae3aae49 Mon Sep 17 00:00:00 2001 From: Retera Date: Fri, 13 Nov 2020 20:00:33 -0500 Subject: [PATCH 065/116] Add build sounds and rough draft of build progress bar --- .../etheller/warsmash/parsers/fdf/GameUI.java | 4 + .../viewer5/handlers/w3x/SplatModel.java | 5 + .../viewer5/handlers/w3x/War3MapViewer.java | 2669 ++++++++--------- .../handlers/w3x/environment/Terrain.java | 2411 +++++++-------- .../handlers/w3x/rendersim/RenderUnit.java | 11 +- .../handlers/w3x/simulation/CSimulation.java | 8 + .../handlers/w3x/simulation/CUnit.java | 28 + .../behaviors/build/CBehaviorOrcBuild.java | 4 +- .../util/SimulationRenderController.java | 4 + .../viewer5/handlers/w3x/ui/MeleeUI.java | 2079 +++++++------ 10 files changed, 3641 insertions(+), 3582 deletions(-) diff --git a/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java index c296465..2bdc4dd 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java @@ -446,4 +446,8 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { super.add(childFrame); this.nameToFrame.put(childFrame.getName(), childFrame); } + + public Scene getUiScene() { + return uiScene; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SplatModel.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SplatModel.java index fa89ac6..fa37dfb 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SplatModel.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SplatModel.java @@ -225,6 +225,11 @@ public class SplatModel { } + public void add(float x, float y, float z, float scale, float[] centerOffset) { + locations.add(new float[]{x - scale, y - scale, x + scale, y + scale, z}); + compact(Gdx.gl30, centerOffset); + } + private static final class Batch { private final int uvsOffset; private final int vertexBuffer; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index abca5c8..c148071 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -96,1405 +96,1402 @@ import mpq.MPQArchive; import mpq.MPQException; public class War3MapViewer extends ModelViewer { - private static final War3ID UNIT_FILE = War3ID.fromString("umdl"); - private static final War3ID UBER_SPLAT = War3ID.fromString("uubs"); - private static final War3ID UNIT_SHADOW = War3ID.fromString("ushu"); - private static final War3ID UNIT_SHADOW_X = War3ID.fromString("ushx"); - private static final War3ID UNIT_SHADOW_Y = War3ID.fromString("ushy"); - private static final War3ID UNIT_SHADOW_W = War3ID.fromString("ushw"); - private static final War3ID UNIT_SHADOW_H = War3ID.fromString("ushh"); - private static final War3ID BUILDING_SHADOW = War3ID.fromString("ushb"); - public static final War3ID UNIT_SELECT_SCALE = War3ID.fromString("ussc"); - private static final War3ID UNIT_SELECT_HEIGHT = War3ID.fromString("uslz"); - private static final War3ID UNIT_SOUNDSET = War3ID.fromString("usnd"); - private static final War3ID ITEM_FILE = War3ID.fromString("ifil"); - private static final War3ID UNIT_PATHING = War3ID.fromString("upat"); - private static final War3ID DESTRUCTABLE_PATHING = War3ID.fromString("bptx"); - private static final War3ID ELEVATION_SAMPLE_RADIUS = War3ID.fromString("uerd"); - private static final War3ID MAX_PITCH = War3ID.fromString("umxp"); - private static final War3ID MAX_ROLL = War3ID.fromString("umxr"); - private static final War3ID sloc = War3ID.fromString("sloc"); - private static final LoadGenericCallback stringDataCallback = new StringDataCallbackImplementation(); - private static final float[] rayHeap = new float[6]; - public static final Ray gdxRayHeap = new Ray(); - private static final Vector2 mousePosHeap = new Vector2(); - private static final Vector3 normalHeap = new Vector3(); - public static final Vector3 intersectionHeap = new Vector3(); - private static final Rectangle rectangleHeap = new Rectangle(); - public static final StreamDataCallbackImplementation streamDataCallback = new StreamDataCallbackImplementation(); - - public PathSolver wc3PathSolver = PathSolver.DEFAULT; - public SolverParams solverParams = new SolverParams(); - public WorldScene worldScene; - public boolean anyReady; - public MappedData terrainData = new MappedData(); - public MappedData cliffTypesData = new MappedData(); - public MappedData waterData = new MappedData(); - public boolean terrainReady; - public boolean cliffsReady; - public boolean doodadsAndDestructiblesLoaded; - public MappedData doodadsData = new MappedData(); - public MappedData doodadMetaData = new MappedData(); - public MappedData destructableMetaData = new MappedData(); - public List doodads = new ArrayList<>(); - public List terrainDoodads = new ArrayList<>(); - public boolean doodadsReady; - public boolean unitsAndItemsLoaded; - public MappedData unitsData = new MappedData(); - public MappedData unitMetaData = new MappedData(); - public List units = new ArrayList<>(); - public List items = new ArrayList<>(); - public List projectiles = new ArrayList<>(); - public boolean unitsReady; - public War3Map mapMpq; - public PathSolver mapPathSolver = PathSolver.DEFAULT; - - private final DataSource gameDataSource; - - public Terrain terrain; - public int renderPathing = 0; - public int renderLighting = 1; - - public List selModels = new ArrayList<>(); - public List selected = new ArrayList<>(); - private DataTable unitAckSoundsTable; - private DataTable unitCombatSoundsTable; - public DataTable miscData; - private DataTable unitGlobalStrings; - public DataTable uiSoundsTable; - private MdxComplexInstance confirmationInstance; - public MdxComplexInstance dncUnit; - public MdxComplexInstance dncUnitDay; - public MdxComplexInstance dncTerrain; - public MdxComplexInstance dncTarget; - public CSimulation simulation; - private float updateTime; - - // for World Editor, I think - public Vector2[] startLocations = new Vector2[WarsmashConstants.MAX_PLAYERS]; - - private final DynamicShadowManager dynamicShadowManager = new DynamicShadowManager(); - - private final Random seededRandom = new Random(1337L); - - private final Map filePathToPathingMap = new HashMap<>(); - - private final List selectionCircleSizes = new ArrayList<>(); - - private final Map unitToRenderPeer = new HashMap<>(); - private final Map unitIdToTypeData = new HashMap<>(); - private GameUI gameUI; - private Vector3 lightDirection; - - private Quadtree walkableObjectsTree; - private final QuadtreeIntersectorFindsWalkableRenderHeight walkablesIntersector = new QuadtreeIntersectorFindsWalkableRenderHeight(); - private final QuadtreeIntersectorFindsHitPoint walkablesIntersectionFinder = new QuadtreeIntersectorFindsHitPoint(); - private final QuadtreeIntersectorFindsHighestWalkable intersectorFindsHighestWalkable = new QuadtreeIntersectorFindsHighestWalkable(); - - private KeyedSounds uiSounds; - - public War3MapViewer(final DataSource dataSource, final CanvasProvider canvas) { - super(dataSource, canvas); - this.gameDataSource = dataSource; - - final WebGL webGL = this.webGL; - - this.addHandler(new MdxHandler()); - - this.wc3PathSolver = PathSolver.DEFAULT; - - this.worldScene = this.addWorldScene(); - - if (!this.dynamicShadowManager.setup(webGL)) { - throw new IllegalStateException("FrameBuffer setup failed"); - } - } - - public void loadSLKs(final WorldEditStrings worldEditStrings) throws IOException { - final GenericResource terrain = this.loadMapGeneric("TerrainArt\\Terrain.slk", FetchDataTypeName.SLK, - stringDataCallback); - final GenericResource cliffTypes = this.loadMapGeneric("TerrainArt\\CliffTypes.slk", FetchDataTypeName.SLK, - stringDataCallback); - final GenericResource water = this.loadMapGeneric("TerrainArt\\Water.slk", FetchDataTypeName.SLK, - stringDataCallback); - - // == when loaded, which is always in our system == - this.terrainData.load(terrain.data.toString()); - this.cliffTypesData.load(cliffTypes.data.toString()); - this.waterData.load(water.data.toString()); - // emit terrain loaded?? - - final GenericResource doodads = this.loadMapGeneric("Doodads\\Doodads.slk", FetchDataTypeName.SLK, - stringDataCallback); - final GenericResource doodadMetaData = this.loadMapGeneric("Doodads\\DoodadMetaData.slk", FetchDataTypeName.SLK, - stringDataCallback); - final GenericResource destructableData = this.loadMapGeneric("Units\\DestructableData.slk", - FetchDataTypeName.SLK, stringDataCallback); - final GenericResource destructableMetaData = this.loadMapGeneric("Units\\DestructableMetaData.slk", - FetchDataTypeName.SLK, stringDataCallback); - - // == when loaded, which is always in our system == - this.doodadsAndDestructiblesLoaded = true; - this.doodadsData.load(doodads.data.toString()); - this.doodadMetaData.load(doodadMetaData.data.toString()); - this.doodadsData.load(destructableData.data.toString()); - this.destructableMetaData.load(destructableData.data.toString()); - // emit doodads loaded - - final GenericResource unitData = this.loadMapGeneric("Units\\UnitData.slk", FetchDataTypeName.SLK, - stringDataCallback); - final GenericResource unitUi = this.loadMapGeneric("Units\\unitUI.slk", FetchDataTypeName.SLK, - stringDataCallback); - final GenericResource itemData = this.loadMapGeneric("Units\\ItemData.slk", FetchDataTypeName.SLK, - stringDataCallback); - final GenericResource unitMetaData = this.loadMapGeneric("Units\\UnitMetaData.slk", FetchDataTypeName.SLK, - stringDataCallback); - - // == when loaded, which is always in our system == - this.unitsAndItemsLoaded = true; - this.unitsData.load(unitData.data.toString()); - this.unitsData.load(unitUi.data.toString()); - this.unitsData.load(itemData.data.toString()); - this.unitMetaData.load(unitMetaData.data.toString()); - // emit loaded - - this.unitAckSoundsTable = new DataTable(worldEditStrings); - try (InputStream terrainSlkStream = this.dataSource.getResourceAsStream("UI\\SoundInfo\\UnitAckSounds.slk")) { - this.unitAckSoundsTable.readSLK(terrainSlkStream); - } - this.unitCombatSoundsTable = new DataTable(worldEditStrings); - try (InputStream terrainSlkStream = this.dataSource - .getResourceAsStream("UI\\SoundInfo\\UnitCombatSounds.slk")) { - this.unitCombatSoundsTable.readSLK(terrainSlkStream); - } - this.miscData = new DataTable(worldEditStrings); - try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("UI\\MiscData.txt")) { - this.miscData.readTXT(miscDataTxtStream, true); - } - try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\MiscData.txt")) { - this.miscData.readTXT(miscDataTxtStream, true); - } - try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\MiscGame.txt")) { - this.miscData.readTXT(miscDataTxtStream, true); - } - try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("UI\\SoundInfo\\MiscData.txt")) { - this.miscData.readTXT(miscDataTxtStream, true); - } - if (this.dataSource.has("war3mapMisc.txt")) { - try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("war3mapMisc.txt")) { - this.miscData.readTXT(miscDataTxtStream, true); - } - } - final Element light = this.miscData.get("Light"); - final float lightX = light.getFieldFloatValue("Direction", 0); - final float lightY = light.getFieldFloatValue("Direction", 1); - final float lightZ = light.getFieldFloatValue("Direction", 2); - this.lightDirection = new Vector3(lightX, lightY, lightZ).nor(); - this.unitGlobalStrings = new DataTable(worldEditStrings); - try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\UnitGlobalStrings.txt")) { - this.unitGlobalStrings.readTXT(miscDataTxtStream, true); - } - final Element categories = this.unitGlobalStrings.get("Categories"); - for (final CUnitClassification unitClassification : CUnitClassification.values()) { - if (unitClassification.getLocaleKey() != null) { - final String displayName = categories.getField(unitClassification.getLocaleKey()); - unitClassification.setDisplayName(displayName); - } - } - this.selectionCircleSizes.clear(); - final Element selectionCircleData = this.miscData.get("SelectionCircle"); - final int selectionCircleNumSizes = selectionCircleData.getFieldValue("NumSizes"); - for (int i = 0; i < selectionCircleNumSizes; i++) { - final String indexString = i < 10 ? "0" + i : Integer.toString(i); - final float size = selectionCircleData.getFieldFloatValue("Size" + indexString); - final String texture = selectionCircleData.getField("Texture" + indexString); - final String textureDotted = selectionCircleData.getField("TextureDotted" + indexString); - this.selectionCircleSizes.add(new SelectionCircleSize(size, texture, textureDotted)); - } - this.selectionCircleScaleFactor = selectionCircleData.getFieldFloatValue("ScaleFactor"); - - this.uiSoundsTable = new DataTable(worldEditStrings); - try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("UI\\SoundInfo\\UISounds.slk")) { - this.uiSoundsTable.readSLK(miscDataTxtStream); - } - } - - public GenericResource loadMapGeneric(final String path, final FetchDataTypeName dataType, - final LoadGenericCallback callback) { - if (this.mapMpq == null) { - return loadGeneric(path, dataType, callback); - } - else { - return loadGeneric(path, dataType, callback, this.dataSource); - } - } - - public void loadMap(final String mapFilePath) throws IOException { - final War3Map war3Map = new War3Map(this.gameDataSource, mapFilePath); - - this.mapMpq = war3Map; - - final PathSolver wc3PathSolver = this.wc3PathSolver; - - char tileset = 'A'; - - final War3MapW3i w3iFile = this.mapMpq.readMapInformation(); - - tileset = w3iFile.getTileset(); - - DataSource tilesetSource; - try { - // Slightly complex. Here's the theory: - // 1.) Copy map into RAM - // 2.) Setup a Data Source that will read assets - // from either the map or the game, giving the map priority. - SeekableByteChannel sbc; - final CompoundDataSource compoundDataSource = war3Map.getCompoundDataSource(); - try (InputStream mapStream = compoundDataSource.getResourceAsStream(tileset + ".mpq")) { - if (mapStream == null) { - tilesetSource = new CompoundDataSource(Arrays.asList(compoundDataSource, - new SubdirDataSource(compoundDataSource, tileset + ".mpq/"), - new SubdirDataSource(compoundDataSource, "_tilesets/" + tileset + ".w3mod/"))); - } - else { - final byte[] mapData = IOUtils.toByteArray(mapStream); - sbc = new SeekableInMemoryByteChannel(mapData); - final DataSource internalMpqContentsDataSource = new MpqDataSource(new MPQArchive(sbc), sbc); - tilesetSource = new CompoundDataSource( - Arrays.asList(compoundDataSource, internalMpqContentsDataSource)); - } - } - catch (final IOException exc) { - tilesetSource = new CompoundDataSource( - Arrays.asList(compoundDataSource, new SubdirDataSource(compoundDataSource, tileset + ".mpq/"), - new SubdirDataSource(compoundDataSource, "_tilesets/" + tileset + ".w3mod/"))); - } - } - catch (final MPQException e) { - throw new RuntimeException(e); - } - setDataSource(tilesetSource); - this.worldEditStrings = new WorldEditStrings(this.dataSource); - loadSLKs(this.worldEditStrings); - - this.solverParams.tileset = Character.toLowerCase(tileset); - - final War3MapW3e terrainData = this.mapMpq.readEnvironment(); - - final War3MapWpm terrainPathing = this.mapMpq.readPathing(); - - final StandardObjectData standardObjectData = new StandardObjectData(this.dataSource); - this.worldEditData = standardObjectData.getWorldEditData(); - - this.terrain = new Terrain(terrainData, terrainPathing, w3iFile, this.webGL, this.dataSource, - this.worldEditStrings, this, this.worldEditData); - - final float[] centerOffset = terrainData.getCenterOffset(); - final int[] mapSize = terrainData.getMapSize(); - - this.terrainReady = true; - this.anyReady = true; - this.cliffsReady = true; - - // Override the grid based on the map. - this.worldScene.grid = new Grid(centerOffset[0], centerOffset[1], (mapSize[0] * 128) - 128, - (mapSize[1] * 128) - 128, 16 * 128, 16 * 128); - - final MdxModel confirmation = (MdxModel) load("UI\\Feedback\\Confirmation\\Confirmation.mdx", - PathSolver.DEFAULT, null); - this.confirmationInstance = (MdxComplexInstance) confirmation.addInstance(); - this.confirmationInstance.setSequenceLoopMode(SequenceLoopMode.NEVER_LOOP_AND_HIDE_WHEN_DONE); - this.confirmationInstance.setSequence(0); - this.confirmationInstance.setScene(this.worldScene); - - this.allObjectData = this.mapMpq.readModifications(); - this.simulation = new CSimulation(this.miscData, this.allObjectData.getUnits(), - this.allObjectData.getAbilities(), new SimulationRenderController() { - private final Map keyToCombatSound = new HashMap<>(); - - @Override - public CAttackProjectile createAttackProjectile(final CSimulation simulation, final float launchX, - final float launchY, final float launchFacing, final CUnit source, - final CUnitAttackMissile unitAttack, final CWidget target, final float damage, - final int bounceIndex) { - final War3ID typeId = source.getTypeId(); - final int projectileSpeed = unitAttack.getProjectileSpeed(); - final float projectileArc = unitAttack.getProjectileArc(); - String missileArt = unitAttack.getProjectileArt(); - final float projectileLaunchX = simulation.getUnitData().getProjectileLaunchX(typeId); - final float projectileLaunchY = simulation.getUnitData().getProjectileLaunchY(typeId); - final float projectileLaunchZ = simulation.getUnitData().getProjectileLaunchZ(typeId); - - missileArt = mdx(missileArt); - final float facing = launchFacing; - final float sinFacing = (float) Math.sin(facing); - final float cosFacing = (float) Math.cos(facing); - final float x = (launchX + (projectileLaunchY * cosFacing)) + (projectileLaunchX * sinFacing); - final float y = (launchY + (projectileLaunchY * sinFacing)) - (projectileLaunchX * cosFacing); - - final float height = War3MapViewer.this.terrain.getGroundHeight(x, y) + source.getFlyHeight() - + projectileLaunchZ; - final CAttackProjectile simulationAttackProjectile = new CAttackProjectile(x, y, - projectileSpeed, target, source, damage, unitAttack, bounceIndex); - - final MdxModel model = (MdxModel) load(missileArt, War3MapViewer.this.mapPathSolver, - War3MapViewer.this.solverParams); - final MdxComplexInstance modelInstance = (MdxComplexInstance) model.addInstance(); - modelInstance.setTeamColor(source.getPlayerIndex()); - modelInstance.setScene(War3MapViewer.this.worldScene); - if (bounceIndex == 0) { - SequenceUtils.randomBirthSequence(modelInstance); - } - else { - SequenceUtils.randomStandSequence(modelInstance); - } - modelInstance.setLocation(x, y, height); - final RenderAttackProjectile renderAttackProjectile = new RenderAttackProjectile( - simulationAttackProjectile, modelInstance, height, projectileArc, War3MapViewer.this); - - War3MapViewer.this.projectiles.add(renderAttackProjectile); - - return simulationAttackProjectile; - } - - @Override - public void createInstantAttackEffect(final CSimulation cSimulation, final CUnit source, - final CUnitAttackInstant unitAttack, final CWidget target) { - final War3ID typeId = source.getTypeId(); - - String missileArt = unitAttack.getProjectileArt(); - final float projectileLaunchX = War3MapViewer.this.simulation.getUnitData() - .getProjectileLaunchX(typeId); - final float projectileLaunchY = War3MapViewer.this.simulation.getUnitData() - .getProjectileLaunchY(typeId); - missileArt = mdx(missileArt); - final float facing = (float) Math.toRadians(source.getFacing()); - final float sinFacing = (float) Math.sin(facing); - final float cosFacing = (float) Math.cos(facing); - final float x = (source.getX() + (projectileLaunchY * cosFacing)) - + (projectileLaunchX * sinFacing); - final float y = (source.getY() + (projectileLaunchY * sinFacing)) - - (projectileLaunchX * cosFacing); - - final float targetX = target.getX(); - final float targetY = target.getY(); - final float angleToTarget = (float) Math.atan2(targetY - y, targetX - x); - - final float height = War3MapViewer.this.terrain.getGroundHeight(targetX, targetY) - + target.getFlyHeight() + target.getImpactZ(); - - final MdxModel model = (MdxModel) load(missileArt, War3MapViewer.this.mapPathSolver, - War3MapViewer.this.solverParams); - final MdxComplexInstance modelInstance = (MdxComplexInstance) model.addInstance(); - modelInstance.setTeamColor(source.getPlayerIndex()); - modelInstance.setScene(War3MapViewer.this.worldScene); - SequenceUtils.randomBirthSequence(modelInstance); - modelInstance.setLocation(targetX, targetY, height); - War3MapViewer.this.projectiles - .add(new RenderAttackInstant(modelInstance, War3MapViewer.this, angleToTarget)); - } - - @Override - public void spawnUnitDamageSound(final CUnit damagedUnit, final String weaponSound, - final String armorType) { - final String key = weaponSound + armorType; - UnitSound combatSound = this.keyToCombatSound.get(key); - if (combatSound == null) { - combatSound = UnitSound.create(War3MapViewer.this.dataSource, - War3MapViewer.this.unitCombatSoundsTable, weaponSound, armorType); - this.keyToCombatSound.put(key, combatSound); - } - combatSound.play(War3MapViewer.this.worldScene.audioContext, damagedUnit.getX(), - damagedUnit.getY()); - } - - @Override - public void removeUnit(final CUnit unit) { - final RenderUnit renderUnit = War3MapViewer.this.unitToRenderPeer.remove(unit); - War3MapViewer.this.units.remove(renderUnit); - War3MapViewer.this.worldScene.removeInstance(renderUnit.instance); - } - - @Override - public BufferedImage getBuildingPathingPixelMap(final War3ID rawcode) { - return War3MapViewer.this - .getBuildingPathingPixelMap(War3MapViewer.this.allObjectData.getUnits().get(rawcode)); - } - - @Override - public CUnit createUnit(final CSimulation simulation, final War3ID typeId, final int playerIndex, - final float x, final float y, final float facing) { - return createNewUnit(War3MapViewer.this.allObjectData, typeId, x, y, 0f, playerIndex, - (float) Math.toRadians(facing)); - } - }, this.terrain.pathingGrid, this.terrain.getEntireMap(), this.seededRandom, w3iFile.getPlayers()); - - this.walkableObjectsTree = new Quadtree<>(this.terrain.getEntireMap()); - if (this.doodadsAndDestructiblesLoaded) { - this.loadDoodadsAndDestructibles(this.allObjectData); - } - else { - throw new IllegalStateException("transcription of JS has not loaded a map and has no JS async promises"); - } - - loadSounds(); - - this.terrain.createWaves(); - - loadLightsAndShading(tileset); - } - - private void loadSounds() { - this.uiSounds = new KeyedSounds(this.uiSoundsTable, this.mapMpq); - } - - /** - * Loads the map information that should be loaded after UI, such as units, who - * need to be able to setup their UI counterparts (icons, etc) for their - * abilities while loading. This allows the dynamic creation of units while the - * game is playing to better share code with the startup sequence's creation of - * units. - * - * @throws IOException - */ - public void loadAfterUI() throws IOException { - if (this.unitsAndItemsLoaded) { - this.loadUnitsAndItems(this.allObjectData); - } - else { - throw new IllegalStateException("transcription of JS has not loaded a map and has no JS async promises"); - } - - // After we finish loading units, we need to update & create the stored shadow - // information for all unit shadows - this.terrain.initShadows(); - } - - private void loadLightsAndShading(final char tileset) { - // TODO this should be set by the war3map.j actually, not by the tileset, so the - // call to set day night models is just for testing to make the test look pretty - final Element defaultTerrainLights = this.worldEditData.get("TerrainLights"); - final Element defaultUnitLights = this.worldEditData.get("UnitLights"); - setDayNightModels(defaultTerrainLights.getField(Character.toString(tileset)), - defaultUnitLights.getField(Character.toString(tileset))); - - } - - private void loadDoodadsAndDestructibles(final Warcraft3MapObjectData modifications) throws IOException { - this.applyModificationFile(this.doodadsData, this.doodadMetaData, modifications.getDoodads(), - WorldEditorDataType.DOODADS); - this.applyModificationFile(this.doodadsData, this.destructableMetaData, modifications.getDestructibles(), - WorldEditorDataType.DESTRUCTIBLES); - - final War3MapDoo doo = this.mapMpq.readDoodads(); - - for (final com.etheller.warsmash.parsers.w3x.doo.Doodad doodad : doo.getDoodads()) { - WorldEditorDataType type = WorldEditorDataType.DOODADS; - MutableGameObject row = modifications.getDoodads().get(doodad.getId()); - if (row == null) { - row = modifications.getDestructibles().get(doodad.getId()); - type = WorldEditorDataType.DESTRUCTIBLES; - } - if (row != null) { - String file = row.readSLKTag("file"); - final int numVar = row.readSLKTagInt("numVar"); - - if (file.endsWith(".mdx") || file.endsWith(".mdl")) { - file = file.substring(0, file.length() - 4); - } - - String fileVar = file; - - file += ".mdx"; - - if (numVar > 1) { - fileVar += Math.min(doodad.getVariation(), numVar - 1); - } - - fileVar += ".mdx"; - - final float maxPitch = row.readSLKTagFloat("maxPitch"); - final float maxRoll = row.readSLKTagFloat("maxRoll"); - if (type == WorldEditorDataType.DESTRUCTIBLES) { - final String shadowString = row.readSLKTag("shadow"); - if ((shadowString != null) && (shadowString.length() > 0) && !"_".equals(shadowString)) { - this.terrain.addShadow(shadowString, doodad.getLocation()[0], doodad.getLocation()[1]); - } - - final String pathingTexture = row.readSLKTag("pathTex"); - if ((pathingTexture != null) && (pathingTexture.length() > 0) && !"_".equals(pathingTexture)) { - - BufferedImage bufferedImage = this.filePathToPathingMap.get(pathingTexture.toLowerCase()); - if (bufferedImage == null) { - if (this.mapMpq.has(pathingTexture)) { - try { - bufferedImage = TgaFile.readTGA(pathingTexture, - this.mapMpq.getResourceAsStream(pathingTexture)); - this.filePathToPathingMap.put(pathingTexture.toLowerCase(), bufferedImage); - } - catch (final Exception exc) { - exc.printStackTrace(); - } - } - } - if (bufferedImage != null) { - this.terrain.pathingGrid.blitPathingOverlayTexture(doodad.getLocation()[0], - doodad.getLocation()[1], (int) Math.toDegrees(doodad.getAngle()), bufferedImage); - } - } - } - // First see if the model is local. - // Doodads referring to local models may have invalid variations, so if the - // variation doesn't exist, try without a variation. - - String path; - if (this.mapMpq.has(fileVar)) { - path = fileVar; - } - else { - path = file; - } - MdxModel model; - if (this.mapMpq.has(path)) { - model = (MdxModel) this.load(path, this.mapPathSolver, this.solverParams); - } - else { - model = (MdxModel) this.load(fileVar, this.mapPathSolver, this.solverParams); - } - - if (type == WorldEditorDataType.DESTRUCTIBLES) { - final RenderDestructable renderDestructable = new RenderDestructable(this, model, row, doodad, type, - maxPitch, maxRoll, doodad.getLife()); - if (row.readSLKTagBoolean("walkable")) { - final float x = doodad.getLocation()[0]; - final float y = doodad.getLocation()[1]; - final BoundingBox boundingBox = model.bounds.getBoundingBox(); - final float minX = boundingBox.min.x + x; - final float minY = boundingBox.min.y + y; - final Rectangle renderDestructableBounds = new Rectangle(minX, minY, boundingBox.getWidth(), - boundingBox.getHeight()); - this.walkableObjectsTree.add((MdxComplexInstance) renderDestructable.instance, - renderDestructableBounds); - } - this.doodads.add(renderDestructable); - } - else { - this.doodads.add(new RenderDoodad(this, model, row, doodad, type, maxPitch, maxRoll)); - } - } - } - - // Cliff/Terrain doodads. - for (final com.etheller.warsmash.parsers.w3x.doo.TerrainDoodad doodad : doo.getTerrainDoodads()) { - final MutableGameObject row = modifications.getDoodads().get(doodad.getId()); - String file = row.readSLKTag("file");// - if ("".equals(file)) { - final String blaBla = row.readSLKTag("file"); - System.out.println("bla"); - } - if (file.toLowerCase().endsWith(".mdl")) { - file = file.substring(0, file.length() - 4); - } - if (!file.toLowerCase().endsWith(".mdx")) { - file += ".mdx"; - } - final MdxModel model = (MdxModel) this.load(file, this.mapPathSolver, this.solverParams); - - final String pathingTexture = row.readSLKTag("pathTex"); - BufferedImage pathingTextureImage; - if ((pathingTexture != null) && (pathingTexture.length() > 0) && !"_".equals(pathingTexture)) { - - pathingTextureImage = this.filePathToPathingMap.get(pathingTexture.toLowerCase()); - if (pathingTextureImage == null) { - if (this.mapMpq.has(pathingTexture)) { - try { - pathingTextureImage = TgaFile.readTGA(pathingTexture, - this.mapMpq.getResourceAsStream(pathingTexture)); - this.filePathToPathingMap.put(pathingTexture.toLowerCase(), pathingTextureImage); - } - catch (final Exception exc) { - exc.printStackTrace(); - } - } - } - } - else { - pathingTextureImage = null; - } - if (pathingTextureImage != null) { - // blit out terrain cells under this TerrainDoodad - final int textureWidth = pathingTextureImage.getWidth(); - final int textureHeight = pathingTextureImage.getHeight(); - final int textureWidthTerrainCells = textureWidth / 4; - final int textureHeightTerrainCells = textureHeight / 4; - final int minCellX = ((int) doodad.getLocation()[0]); - final int minCellY = ((int) doodad.getLocation()[1]); - final int maxCellX = (minCellX + textureWidthTerrainCells) - 1; - final int maxCellY = (minCellY + textureHeightTerrainCells) - 1; - for (int j = minCellY; j <= maxCellY; j++) { - for (int i = minCellX; i <= maxCellX; i++) { - this.terrain.removeTerrainCellWithoutFlush(i, j); - } - } - this.terrain.flushRemovedTerrainCells(); - } - - System.out.println("Loading terrain doodad: " + file); - this.terrainDoodads.add(new TerrainDoodad(this, model, row, doodad, pathingTextureImage)); - } - - this.doodadsReady = true; - this.anyReady = true; - } - - private void applyModificationFile(final MappedData doodadsData2, final MappedData doodadMetaData2, - final MutableObjectData destructibles, final WorldEditorDataType dataType) { - // TODO condense ported MappedData from Ghostwolf and MutableObjectData from - // Retera - - } - - private void loadUnitsAndItems(final Warcraft3MapObjectData modifications) throws IOException { - final War3Map mpq = this.mapMpq; - - this.soundsetNameToSoundset = new HashMap<>(); - - if (this.dataSource.has("war3mapUnits.doo")) { - final War3MapUnitsDoo dooFile = mpq.readUnits(); - - // Collect the units and items data. - for (final com.etheller.warsmash.parsers.w3x.unitsdoo.Unit unit : dooFile.getUnits()) { - final War3ID unitId = unit.getId(); - final float unitX = unit.getLocation()[0]; - final float unitY = unit.getLocation()[1]; - final float unitZ = unit.getLocation()[2]; - final int playerIndex = unit.getPlayer(); - final float unitAngle = unit.getAngle(); - - createNewUnit(modifications, unitId, unitX, unitY, unitZ, playerIndex, unitAngle); - } - } - - this.terrain.loadSplats(); - - this.unitsReady = true; - this.anyReady = true; - } - - private CUnit createNewUnit(final Warcraft3MapObjectData modifications, final War3ID unitId, float unitX, - float unitY, final float unitZ, final int playerIndex, final float unitAngle) { - UnitSoundset soundset = null; - MutableGameObject row = null; - String path = null; - Splat unitShadowSplat = null; - BufferedImage buildingPathingPixelMap = null; - final float unitVertexScale = 1.0f; - - // Hardcoded? - WorldEditorDataType type = null; - if (sloc.equals(unitId)) { + private static final War3ID UNIT_FILE = War3ID.fromString("umdl"); + private static final War3ID UBER_SPLAT = War3ID.fromString("uubs"); + private static final War3ID UNIT_SHADOW = War3ID.fromString("ushu"); + private static final War3ID UNIT_SHADOW_X = War3ID.fromString("ushx"); + private static final War3ID UNIT_SHADOW_Y = War3ID.fromString("ushy"); + private static final War3ID UNIT_SHADOW_W = War3ID.fromString("ushw"); + private static final War3ID UNIT_SHADOW_H = War3ID.fromString("ushh"); + private static final War3ID BUILDING_SHADOW = War3ID.fromString("ushb"); + public static final War3ID UNIT_SELECT_SCALE = War3ID.fromString("ussc"); + private static final War3ID UNIT_SELECT_HEIGHT = War3ID.fromString("uslz"); + private static final War3ID UNIT_SOUNDSET = War3ID.fromString("usnd"); + private static final War3ID ITEM_FILE = War3ID.fromString("ifil"); + private static final War3ID UNIT_PATHING = War3ID.fromString("upat"); + private static final War3ID DESTRUCTABLE_PATHING = War3ID.fromString("bptx"); + private static final War3ID ELEVATION_SAMPLE_RADIUS = War3ID.fromString("uerd"); + private static final War3ID MAX_PITCH = War3ID.fromString("umxp"); + private static final War3ID MAX_ROLL = War3ID.fromString("umxr"); + private static final War3ID sloc = War3ID.fromString("sloc"); + private static final LoadGenericCallback stringDataCallback = new StringDataCallbackImplementation(); + private static final float[] rayHeap = new float[6]; + public static final Ray gdxRayHeap = new Ray(); + private static final Vector2 mousePosHeap = new Vector2(); + private static final Vector3 normalHeap = new Vector3(); + public static final Vector3 intersectionHeap = new Vector3(); + private static final Rectangle rectangleHeap = new Rectangle(); + public static final StreamDataCallbackImplementation streamDataCallback = new StreamDataCallbackImplementation(); + + public PathSolver wc3PathSolver = PathSolver.DEFAULT; + public SolverParams solverParams = new SolverParams(); + public WorldScene worldScene; + public boolean anyReady; + public MappedData terrainData = new MappedData(); + public MappedData cliffTypesData = new MappedData(); + public MappedData waterData = new MappedData(); + public boolean terrainReady; + public boolean cliffsReady; + public boolean doodadsAndDestructiblesLoaded; + public MappedData doodadsData = new MappedData(); + public MappedData doodadMetaData = new MappedData(); + public MappedData destructableMetaData = new MappedData(); + public List doodads = new ArrayList<>(); + public List terrainDoodads = new ArrayList<>(); + public boolean doodadsReady; + public boolean unitsAndItemsLoaded; + public MappedData unitsData = new MappedData(); + public MappedData unitMetaData = new MappedData(); + public List units = new ArrayList<>(); + public List items = new ArrayList<>(); + public List projectiles = new ArrayList<>(); + public boolean unitsReady; + public War3Map mapMpq; + public PathSolver mapPathSolver = PathSolver.DEFAULT; + + private final DataSource gameDataSource; + + public Terrain terrain; + public int renderPathing = 0; + public int renderLighting = 1; + + public List selModels = new ArrayList<>(); + public List selected = new ArrayList<>(); + private DataTable unitAckSoundsTable; + private DataTable unitCombatSoundsTable; + public DataTable miscData; + private DataTable unitGlobalStrings; + public DataTable uiSoundsTable; + private MdxComplexInstance confirmationInstance; + public MdxComplexInstance dncUnit; + public MdxComplexInstance dncUnitDay; + public MdxComplexInstance dncTerrain; + public MdxComplexInstance dncTarget; + public CSimulation simulation; + private float updateTime; + + // for World Editor, I think + public Vector2[] startLocations = new Vector2[WarsmashConstants.MAX_PLAYERS]; + + private final DynamicShadowManager dynamicShadowManager = new DynamicShadowManager(); + + private final Random seededRandom = new Random(1337L); + + private final Map filePathToPathingMap = new HashMap<>(); + + private final List selectionCircleSizes = new ArrayList<>(); + + private final Map unitToRenderPeer = new HashMap<>(); + private final Map unitIdToTypeData = new HashMap<>(); + private GameUI gameUI; + private Vector3 lightDirection; + + private Quadtree walkableObjectsTree; + private final QuadtreeIntersectorFindsWalkableRenderHeight walkablesIntersector = new QuadtreeIntersectorFindsWalkableRenderHeight(); + private final QuadtreeIntersectorFindsHitPoint walkablesIntersectionFinder = new QuadtreeIntersectorFindsHitPoint(); + private final QuadtreeIntersectorFindsHighestWalkable intersectorFindsHighestWalkable = new QuadtreeIntersectorFindsHighestWalkable(); + + private KeyedSounds uiSounds; + + public War3MapViewer(final DataSource dataSource, final CanvasProvider canvas) { + super(dataSource, canvas); + this.gameDataSource = dataSource; + + final WebGL webGL = this.webGL; + + this.addHandler(new MdxHandler()); + + this.wc3PathSolver = PathSolver.DEFAULT; + + this.worldScene = this.addWorldScene(); + + if (!this.dynamicShadowManager.setup(webGL)) { + throw new IllegalStateException("FrameBuffer setup failed"); + } + } + + public void loadSLKs(final WorldEditStrings worldEditStrings) throws IOException { + final GenericResource terrain = this.loadMapGeneric("TerrainArt\\Terrain.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource cliffTypes = this.loadMapGeneric("TerrainArt\\CliffTypes.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource water = this.loadMapGeneric("TerrainArt\\Water.slk", FetchDataTypeName.SLK, + stringDataCallback); + + // == when loaded, which is always in our system == + this.terrainData.load(terrain.data.toString()); + this.cliffTypesData.load(cliffTypes.data.toString()); + this.waterData.load(water.data.toString()); + // emit terrain loaded?? + + final GenericResource doodads = this.loadMapGeneric("Doodads\\Doodads.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource doodadMetaData = this.loadMapGeneric("Doodads\\DoodadMetaData.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource destructableData = this.loadMapGeneric("Units\\DestructableData.slk", + FetchDataTypeName.SLK, stringDataCallback); + final GenericResource destructableMetaData = this.loadMapGeneric("Units\\DestructableMetaData.slk", + FetchDataTypeName.SLK, stringDataCallback); + + // == when loaded, which is always in our system == + this.doodadsAndDestructiblesLoaded = true; + this.doodadsData.load(doodads.data.toString()); + this.doodadMetaData.load(doodadMetaData.data.toString()); + this.doodadsData.load(destructableData.data.toString()); + this.destructableMetaData.load(destructableData.data.toString()); + // emit doodads loaded + + final GenericResource unitData = this.loadMapGeneric("Units\\UnitData.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource unitUi = this.loadMapGeneric("Units\\unitUI.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource itemData = this.loadMapGeneric("Units\\ItemData.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource unitMetaData = this.loadMapGeneric("Units\\UnitMetaData.slk", FetchDataTypeName.SLK, + stringDataCallback); + + // == when loaded, which is always in our system == + this.unitsAndItemsLoaded = true; + this.unitsData.load(unitData.data.toString()); + this.unitsData.load(unitUi.data.toString()); + this.unitsData.load(itemData.data.toString()); + this.unitMetaData.load(unitMetaData.data.toString()); + // emit loaded + + this.unitAckSoundsTable = new DataTable(worldEditStrings); + try (InputStream terrainSlkStream = this.dataSource.getResourceAsStream("UI\\SoundInfo\\UnitAckSounds.slk")) { + this.unitAckSoundsTable.readSLK(terrainSlkStream); + } + this.unitCombatSoundsTable = new DataTable(worldEditStrings); + try (InputStream terrainSlkStream = this.dataSource + .getResourceAsStream("UI\\SoundInfo\\UnitCombatSounds.slk")) { + this.unitCombatSoundsTable.readSLK(terrainSlkStream); + } + this.miscData = new DataTable(worldEditStrings); + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("UI\\MiscData.txt")) { + this.miscData.readTXT(miscDataTxtStream, true); + } + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\MiscData.txt")) { + this.miscData.readTXT(miscDataTxtStream, true); + } + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\MiscGame.txt")) { + this.miscData.readTXT(miscDataTxtStream, true); + } + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("UI\\SoundInfo\\MiscData.txt")) { + this.miscData.readTXT(miscDataTxtStream, true); + } + if (this.dataSource.has("war3mapMisc.txt")) { + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("war3mapMisc.txt")) { + this.miscData.readTXT(miscDataTxtStream, true); + } + } + final Element light = this.miscData.get("Light"); + final float lightX = light.getFieldFloatValue("Direction", 0); + final float lightY = light.getFieldFloatValue("Direction", 1); + final float lightZ = light.getFieldFloatValue("Direction", 2); + this.lightDirection = new Vector3(lightX, lightY, lightZ).nor(); + this.unitGlobalStrings = new DataTable(worldEditStrings); + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\UnitGlobalStrings.txt")) { + this.unitGlobalStrings.readTXT(miscDataTxtStream, true); + } + final Element categories = this.unitGlobalStrings.get("Categories"); + for (final CUnitClassification unitClassification : CUnitClassification.values()) { + if (unitClassification.getLocaleKey() != null) { + final String displayName = categories.getField(unitClassification.getLocaleKey()); + unitClassification.setDisplayName(displayName); + } + } + this.selectionCircleSizes.clear(); + final Element selectionCircleData = this.miscData.get("SelectionCircle"); + final int selectionCircleNumSizes = selectionCircleData.getFieldValue("NumSizes"); + for (int i = 0; i < selectionCircleNumSizes; i++) { + final String indexString = i < 10 ? "0" + i : Integer.toString(i); + final float size = selectionCircleData.getFieldFloatValue("Size" + indexString); + final String texture = selectionCircleData.getField("Texture" + indexString); + final String textureDotted = selectionCircleData.getField("TextureDotted" + indexString); + this.selectionCircleSizes.add(new SelectionCircleSize(size, texture, textureDotted)); + } + this.selectionCircleScaleFactor = selectionCircleData.getFieldFloatValue("ScaleFactor"); + + this.uiSoundsTable = new DataTable(worldEditStrings); + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("UI\\SoundInfo\\UISounds.slk")) { + this.uiSoundsTable.readSLK(miscDataTxtStream); + } + } + + public GenericResource loadMapGeneric(final String path, final FetchDataTypeName dataType, + final LoadGenericCallback callback) { + if (this.mapMpq == null) { + return loadGeneric(path, dataType, callback); + } else { + return loadGeneric(path, dataType, callback, this.dataSource); + } + } + + public void loadMap(final String mapFilePath) throws IOException { + final War3Map war3Map = new War3Map(this.gameDataSource, mapFilePath); + + this.mapMpq = war3Map; + + final PathSolver wc3PathSolver = this.wc3PathSolver; + + char tileset = 'A'; + + final War3MapW3i w3iFile = this.mapMpq.readMapInformation(); + + tileset = w3iFile.getTileset(); + + DataSource tilesetSource; + try { + // Slightly complex. Here's the theory: + // 1.) Copy map into RAM + // 2.) Setup a Data Source that will read assets + // from either the map or the game, giving the map priority. + SeekableByteChannel sbc; + final CompoundDataSource compoundDataSource = war3Map.getCompoundDataSource(); + try (InputStream mapStream = compoundDataSource.getResourceAsStream(tileset + ".mpq")) { + if (mapStream == null) { + tilesetSource = new CompoundDataSource(Arrays.asList(compoundDataSource, + new SubdirDataSource(compoundDataSource, tileset + ".mpq/"), + new SubdirDataSource(compoundDataSource, "_tilesets/" + tileset + ".w3mod/"))); + } else { + final byte[] mapData = IOUtils.toByteArray(mapStream); + sbc = new SeekableInMemoryByteChannel(mapData); + final DataSource internalMpqContentsDataSource = new MpqDataSource(new MPQArchive(sbc), sbc); + tilesetSource = new CompoundDataSource( + Arrays.asList(compoundDataSource, internalMpqContentsDataSource)); + } + } catch (final IOException exc) { + tilesetSource = new CompoundDataSource( + Arrays.asList(compoundDataSource, new SubdirDataSource(compoundDataSource, tileset + ".mpq/"), + new SubdirDataSource(compoundDataSource, "_tilesets/" + tileset + ".w3mod/"))); + } + } catch (final MPQException e) { + throw new RuntimeException(e); + } + setDataSource(tilesetSource); + this.worldEditStrings = new WorldEditStrings(this.dataSource); + loadSLKs(this.worldEditStrings); + + this.solverParams.tileset = Character.toLowerCase(tileset); + + final War3MapW3e terrainData = this.mapMpq.readEnvironment(); + + final War3MapWpm terrainPathing = this.mapMpq.readPathing(); + + final StandardObjectData standardObjectData = new StandardObjectData(this.dataSource); + this.worldEditData = standardObjectData.getWorldEditData(); + + this.terrain = new Terrain(terrainData, terrainPathing, w3iFile, this.webGL, this.dataSource, + this.worldEditStrings, this, this.worldEditData); + + final float[] centerOffset = terrainData.getCenterOffset(); + final int[] mapSize = terrainData.getMapSize(); + + this.terrainReady = true; + this.anyReady = true; + this.cliffsReady = true; + + // Override the grid based on the map. + this.worldScene.grid = new Grid(centerOffset[0], centerOffset[1], (mapSize[0] * 128) - 128, + (mapSize[1] * 128) - 128, 16 * 128, 16 * 128); + + final MdxModel confirmation = (MdxModel) load("UI\\Feedback\\Confirmation\\Confirmation.mdx", + PathSolver.DEFAULT, null); + this.confirmationInstance = (MdxComplexInstance) confirmation.addInstance(); + this.confirmationInstance.setSequenceLoopMode(SequenceLoopMode.NEVER_LOOP_AND_HIDE_WHEN_DONE); + this.confirmationInstance.setSequence(0); + this.confirmationInstance.setScene(this.worldScene); + + this.allObjectData = this.mapMpq.readModifications(); + this.simulation = new CSimulation(this.miscData, this.allObjectData.getUnits(), + this.allObjectData.getAbilities(), new SimulationRenderController() { + private final Map keyToCombatSound = new HashMap<>(); + + @Override + public CAttackProjectile createAttackProjectile(final CSimulation simulation, final float launchX, + final float launchY, final float launchFacing, final CUnit source, + final CUnitAttackMissile unitAttack, final CWidget target, final float damage, + final int bounceIndex) { + final War3ID typeId = source.getTypeId(); + final int projectileSpeed = unitAttack.getProjectileSpeed(); + final float projectileArc = unitAttack.getProjectileArc(); + String missileArt = unitAttack.getProjectileArt(); + final float projectileLaunchX = simulation.getUnitData().getProjectileLaunchX(typeId); + final float projectileLaunchY = simulation.getUnitData().getProjectileLaunchY(typeId); + final float projectileLaunchZ = simulation.getUnitData().getProjectileLaunchZ(typeId); + + missileArt = mdx(missileArt); + final float facing = launchFacing; + final float sinFacing = (float) Math.sin(facing); + final float cosFacing = (float) Math.cos(facing); + final float x = (launchX + (projectileLaunchY * cosFacing)) + (projectileLaunchX * sinFacing); + final float y = (launchY + (projectileLaunchY * sinFacing)) - (projectileLaunchX * cosFacing); + + final float height = War3MapViewer.this.terrain.getGroundHeight(x, y) + source.getFlyHeight() + + projectileLaunchZ; + final CAttackProjectile simulationAttackProjectile = new CAttackProjectile(x, y, + projectileSpeed, target, source, damage, unitAttack, bounceIndex); + + final MdxModel model = (MdxModel) load(missileArt, War3MapViewer.this.mapPathSolver, + War3MapViewer.this.solverParams); + final MdxComplexInstance modelInstance = (MdxComplexInstance) model.addInstance(); + modelInstance.setTeamColor(source.getPlayerIndex()); + modelInstance.setScene(War3MapViewer.this.worldScene); + if (bounceIndex == 0) { + SequenceUtils.randomBirthSequence(modelInstance); + } else { + SequenceUtils.randomStandSequence(modelInstance); + } + modelInstance.setLocation(x, y, height); + final RenderAttackProjectile renderAttackProjectile = new RenderAttackProjectile( + simulationAttackProjectile, modelInstance, height, projectileArc, War3MapViewer.this); + + War3MapViewer.this.projectiles.add(renderAttackProjectile); + + return simulationAttackProjectile; + } + + @Override + public void createInstantAttackEffect(final CSimulation cSimulation, final CUnit source, + final CUnitAttackInstant unitAttack, final CWidget target) { + final War3ID typeId = source.getTypeId(); + + String missileArt = unitAttack.getProjectileArt(); + final float projectileLaunchX = War3MapViewer.this.simulation.getUnitData() + .getProjectileLaunchX(typeId); + final float projectileLaunchY = War3MapViewer.this.simulation.getUnitData() + .getProjectileLaunchY(typeId); + missileArt = mdx(missileArt); + final float facing = (float) Math.toRadians(source.getFacing()); + final float sinFacing = (float) Math.sin(facing); + final float cosFacing = (float) Math.cos(facing); + final float x = (source.getX() + (projectileLaunchY * cosFacing)) + + (projectileLaunchX * sinFacing); + final float y = (source.getY() + (projectileLaunchY * sinFacing)) + - (projectileLaunchX * cosFacing); + + final float targetX = target.getX(); + final float targetY = target.getY(); + final float angleToTarget = (float) Math.atan2(targetY - y, targetX - x); + + final float height = War3MapViewer.this.terrain.getGroundHeight(targetX, targetY) + + target.getFlyHeight() + target.getImpactZ(); + + final MdxModel model = (MdxModel) load(missileArt, War3MapViewer.this.mapPathSolver, + War3MapViewer.this.solverParams); + final MdxComplexInstance modelInstance = (MdxComplexInstance) model.addInstance(); + modelInstance.setTeamColor(source.getPlayerIndex()); + modelInstance.setScene(War3MapViewer.this.worldScene); + SequenceUtils.randomBirthSequence(modelInstance); + modelInstance.setLocation(targetX, targetY, height); + War3MapViewer.this.projectiles + .add(new RenderAttackInstant(modelInstance, War3MapViewer.this, angleToTarget)); + } + + @Override + public void spawnUnitDamageSound(final CUnit damagedUnit, final String weaponSound, + final String armorType) { + final String key = weaponSound + armorType; + UnitSound combatSound = this.keyToCombatSound.get(key); + if (combatSound == null) { + combatSound = UnitSound.create(War3MapViewer.this.dataSource, + War3MapViewer.this.unitCombatSoundsTable, weaponSound, armorType); + this.keyToCombatSound.put(key, combatSound); + } + combatSound.play(War3MapViewer.this.worldScene.audioContext, damagedUnit.getX(), + damagedUnit.getY()); + } + + @Override + public void spawnUnitConstructionSound(CUnit constructingUnit, CUnit constructedStructure) { + UnitSound constructingBuilding = uiSounds.getSound(gameUI.getSkinField("ConstructingBuilding")); + if (constructingBuilding != null) { + constructingBuilding.play(worldScene.audioContext, constructedStructure.getX(), constructedStructure.getY()); + } + } + + @Override + public void removeUnit(final CUnit unit) { + final RenderUnit renderUnit = War3MapViewer.this.unitToRenderPeer.remove(unit); + War3MapViewer.this.units.remove(renderUnit); + War3MapViewer.this.worldScene.removeInstance(renderUnit.instance); + } + + @Override + public BufferedImage getBuildingPathingPixelMap(final War3ID rawcode) { + return War3MapViewer.this + .getBuildingPathingPixelMap(War3MapViewer.this.allObjectData.getUnits().get(rawcode)); + } + + @Override + public void spawnUnitConstructionFinishSound(CUnit constructedStructure) { + UnitSound constructingBuilding = uiSounds.getSound(gameUI.getSkinField("JobDoneSound")); + if (constructingBuilding != null) { + constructingBuilding.play(worldScene.audioContext, constructedStructure.getX(), constructedStructure.getY()); + } + } + + @Override + public CUnit createUnit(final CSimulation simulation, final War3ID typeId, final int playerIndex, + final float x, final float y, final float facing) { + return createNewUnit(War3MapViewer.this.allObjectData, typeId, x, y, 0f, playerIndex, + (float) Math.toRadians(facing)); + } + }, this.terrain.pathingGrid, this.terrain.getEntireMap(), this.seededRandom, w3iFile.getPlayers()); + + this.walkableObjectsTree = new Quadtree<>(this.terrain.getEntireMap()); + if (this.doodadsAndDestructiblesLoaded) { + this.loadDoodadsAndDestructibles(this.allObjectData); + } else { + throw new IllegalStateException("transcription of JS has not loaded a map and has no JS async promises"); + } + + loadSounds(); + + this.terrain.createWaves(); + + loadLightsAndShading(tileset); + } + + private void loadSounds() { + this.uiSounds = new KeyedSounds(this.uiSoundsTable, this.mapMpq); + } + + /** + * Loads the map information that should be loaded after UI, such as units, who + * need to be able to setup their UI counterparts (icons, etc) for their + * abilities while loading. This allows the dynamic creation of units while the + * game is playing to better share code with the startup sequence's creation of + * units. + * + * @throws IOException + */ + public void loadAfterUI() throws IOException { + if (this.unitsAndItemsLoaded) { + this.loadUnitsAndItems(this.allObjectData); + } else { + throw new IllegalStateException("transcription of JS has not loaded a map and has no JS async promises"); + } + + // After we finish loading units, we need to update & create the stored shadow + // information for all unit shadows + this.terrain.initShadows(); + } + + private void loadLightsAndShading(final char tileset) { + // TODO this should be set by the war3map.j actually, not by the tileset, so the + // call to set day night models is just for testing to make the test look pretty + final Element defaultTerrainLights = this.worldEditData.get("TerrainLights"); + final Element defaultUnitLights = this.worldEditData.get("UnitLights"); + setDayNightModels(defaultTerrainLights.getField(Character.toString(tileset)), + defaultUnitLights.getField(Character.toString(tileset))); + + } + + private void loadDoodadsAndDestructibles(final Warcraft3MapObjectData modifications) throws IOException { + this.applyModificationFile(this.doodadsData, this.doodadMetaData, modifications.getDoodads(), + WorldEditorDataType.DOODADS); + this.applyModificationFile(this.doodadsData, this.destructableMetaData, modifications.getDestructibles(), + WorldEditorDataType.DESTRUCTIBLES); + + final War3MapDoo doo = this.mapMpq.readDoodads(); + + for (final com.etheller.warsmash.parsers.w3x.doo.Doodad doodad : doo.getDoodads()) { + WorldEditorDataType type = WorldEditorDataType.DOODADS; + MutableGameObject row = modifications.getDoodads().get(doodad.getId()); + if (row == null) { + row = modifications.getDestructibles().get(doodad.getId()); + type = WorldEditorDataType.DESTRUCTIBLES; + } + if (row != null) { + String file = row.readSLKTag("file"); + final int numVar = row.readSLKTagInt("numVar"); + + if (file.endsWith(".mdx") || file.endsWith(".mdl")) { + file = file.substring(0, file.length() - 4); + } + + String fileVar = file; + + file += ".mdx"; + + if (numVar > 1) { + fileVar += Math.min(doodad.getVariation(), numVar - 1); + } + + fileVar += ".mdx"; + + final float maxPitch = row.readSLKTagFloat("maxPitch"); + final float maxRoll = row.readSLKTagFloat("maxRoll"); + if (type == WorldEditorDataType.DESTRUCTIBLES) { + final String shadowString = row.readSLKTag("shadow"); + if ((shadowString != null) && (shadowString.length() > 0) && !"_".equals(shadowString)) { + this.terrain.addShadow(shadowString, doodad.getLocation()[0], doodad.getLocation()[1]); + } + + final String pathingTexture = row.readSLKTag("pathTex"); + if ((pathingTexture != null) && (pathingTexture.length() > 0) && !"_".equals(pathingTexture)) { + + BufferedImage bufferedImage = this.filePathToPathingMap.get(pathingTexture.toLowerCase()); + if (bufferedImage == null) { + if (this.mapMpq.has(pathingTexture)) { + try { + bufferedImage = TgaFile.readTGA(pathingTexture, + this.mapMpq.getResourceAsStream(pathingTexture)); + this.filePathToPathingMap.put(pathingTexture.toLowerCase(), bufferedImage); + } catch (final Exception exc) { + exc.printStackTrace(); + } + } + } + if (bufferedImage != null) { + this.terrain.pathingGrid.blitPathingOverlayTexture(doodad.getLocation()[0], + doodad.getLocation()[1], (int) Math.toDegrees(doodad.getAngle()), bufferedImage); + } + } + } + // First see if the model is local. + // Doodads referring to local models may have invalid variations, so if the + // variation doesn't exist, try without a variation. + + String path; + if (this.mapMpq.has(fileVar)) { + path = fileVar; + } else { + path = file; + } + MdxModel model; + if (this.mapMpq.has(path)) { + model = (MdxModel) this.load(path, this.mapPathSolver, this.solverParams); + } else { + model = (MdxModel) this.load(fileVar, this.mapPathSolver, this.solverParams); + } + + if (type == WorldEditorDataType.DESTRUCTIBLES) { + final RenderDestructable renderDestructable = new RenderDestructable(this, model, row, doodad, type, + maxPitch, maxRoll, doodad.getLife()); + if (row.readSLKTagBoolean("walkable")) { + final float x = doodad.getLocation()[0]; + final float y = doodad.getLocation()[1]; + final BoundingBox boundingBox = model.bounds.getBoundingBox(); + final float minX = boundingBox.min.x + x; + final float minY = boundingBox.min.y + y; + final Rectangle renderDestructableBounds = new Rectangle(minX, minY, boundingBox.getWidth(), + boundingBox.getHeight()); + this.walkableObjectsTree.add((MdxComplexInstance) renderDestructable.instance, + renderDestructableBounds); + } + this.doodads.add(renderDestructable); + } else { + this.doodads.add(new RenderDoodad(this, model, row, doodad, type, maxPitch, maxRoll)); + } + } + } + + // Cliff/Terrain doodads. + for (final com.etheller.warsmash.parsers.w3x.doo.TerrainDoodad doodad : doo.getTerrainDoodads()) { + final MutableGameObject row = modifications.getDoodads().get(doodad.getId()); + String file = row.readSLKTag("file");// + if ("".equals(file)) { + final String blaBla = row.readSLKTag("file"); + System.out.println("bla"); + } + if (file.toLowerCase().endsWith(".mdl")) { + file = file.substring(0, file.length() - 4); + } + if (!file.toLowerCase().endsWith(".mdx")) { + file += ".mdx"; + } + final MdxModel model = (MdxModel) this.load(file, this.mapPathSolver, this.solverParams); + + final String pathingTexture = row.readSLKTag("pathTex"); + BufferedImage pathingTextureImage; + if ((pathingTexture != null) && (pathingTexture.length() > 0) && !"_".equals(pathingTexture)) { + + pathingTextureImage = this.filePathToPathingMap.get(pathingTexture.toLowerCase()); + if (pathingTextureImage == null) { + if (this.mapMpq.has(pathingTexture)) { + try { + pathingTextureImage = TgaFile.readTGA(pathingTexture, + this.mapMpq.getResourceAsStream(pathingTexture)); + this.filePathToPathingMap.put(pathingTexture.toLowerCase(), pathingTextureImage); + } catch (final Exception exc) { + exc.printStackTrace(); + } + } + } + } else { + pathingTextureImage = null; + } + if (pathingTextureImage != null) { + // blit out terrain cells under this TerrainDoodad + final int textureWidth = pathingTextureImage.getWidth(); + final int textureHeight = pathingTextureImage.getHeight(); + final int textureWidthTerrainCells = textureWidth / 4; + final int textureHeightTerrainCells = textureHeight / 4; + final int minCellX = ((int) doodad.getLocation()[0]); + final int minCellY = ((int) doodad.getLocation()[1]); + final int maxCellX = (minCellX + textureWidthTerrainCells) - 1; + final int maxCellY = (minCellY + textureHeightTerrainCells) - 1; + for (int j = minCellY; j <= maxCellY; j++) { + for (int i = minCellX; i <= maxCellX; i++) { + this.terrain.removeTerrainCellWithoutFlush(i, j); + } + } + this.terrain.flushRemovedTerrainCells(); + } + + System.out.println("Loading terrain doodad: " + file); + this.terrainDoodads.add(new TerrainDoodad(this, model, row, doodad, pathingTextureImage)); + } + + this.doodadsReady = true; + this.anyReady = true; + } + + private void applyModificationFile(final MappedData doodadsData2, final MappedData doodadMetaData2, + final MutableObjectData destructibles, final WorldEditorDataType dataType) { + // TODO condense ported MappedData from Ghostwolf and MutableObjectData from + // Retera + + } + + private void loadUnitsAndItems(final Warcraft3MapObjectData modifications) throws IOException { + final War3Map mpq = this.mapMpq; + this.unitsReady = false; + + this.soundsetNameToSoundset = new HashMap<>(); + + if (this.dataSource.has("war3mapUnits.doo")) { + final War3MapUnitsDoo dooFile = mpq.readUnits(); + + // Collect the units and items data. + for (final com.etheller.warsmash.parsers.w3x.unitsdoo.Unit unit : dooFile.getUnits()) { + final War3ID unitId = unit.getId(); + final float unitX = unit.getLocation()[0]; + final float unitY = unit.getLocation()[1]; + final float unitZ = unit.getLocation()[2]; + final int playerIndex = unit.getPlayer(); + final float unitAngle = unit.getAngle(); + + createNewUnit(modifications, unitId, unitX, unitY, unitZ, playerIndex, unitAngle); + } + } + + this.terrain.loadSplats(); + + this.unitsReady = true; + this.anyReady = true; + } + + private CUnit createNewUnit(final Warcraft3MapObjectData modifications, final War3ID unitId, float unitX, + float unitY, final float unitZ, final int playerIndex, final float unitAngle) { + UnitSoundset soundset = null; + MutableGameObject row = null; + String path = null; + Splat unitShadowSplat = null; + BufferedImage buildingPathingPixelMap = null; + final float unitVertexScale = 1.0f; + + // Hardcoded? + WorldEditorDataType type = null; + if (sloc.equals(unitId)) { // path = "Objects\\StartLocation\\StartLocation.mdx"; - type = null; /// ?????? - this.startLocations[playerIndex] = new Vector2(unitX, unitY); - } - else { - row = modifications.getUnits().get(unitId); - if (row == null) { - row = modifications.getItems().get(unitId); - if (row != null) { - type = WorldEditorDataType.ITEM; - path = row.getFieldAsString(ITEM_FILE, 0); + type = null; /// ?????? + this.startLocations[playerIndex] = new Vector2(unitX, unitY); + } else { + row = modifications.getUnits().get(unitId); + if (row == null) { + row = modifications.getItems().get(unitId); + if (row != null) { + type = WorldEditorDataType.ITEM; + path = row.getFieldAsString(ITEM_FILE, 0); - if (path.toLowerCase().endsWith(".mdl") || path.toLowerCase().endsWith(".mdx")) { - path = path.substring(0, path.length() - 4); - } + if (path.toLowerCase().endsWith(".mdl") || path.toLowerCase().endsWith(".mdx")) { + path = path.substring(0, path.length() - 4); + } - final String unitShadow = "Shadow"; - if ((unitShadow != null) && !"_".equals(unitShadow)) { - final String texture = "ReplaceableTextures\\Shadows\\" + unitShadow + ".blp"; - final float shadowX = 50; - final float shadowY = 50; - final float shadowWidth = 128; - final float shadowHeight = 128; - if (!this.terrain.splats.containsKey(texture)) { - final Splat splat = new Splat(); - splat.opacity = 0.5f; - this.terrain.splats.put(texture, splat); - } - final float x = unitX - shadowX; - final float y = unitY - shadowY; - this.terrain.splats.get(texture).locations - .add(new float[] { x, y, x + shadowWidth, y + shadowHeight, 3 }); - unitShadowSplat = this.terrain.splats.get(texture); - } + Element misc = miscData.get("Misc"); + String itemShadowFile = misc.getField("ItemShadowFile"); + int itemShadowWidth = misc.getFieldValue("ItemShadowSize", 0); + int itemShadowHeight = misc.getFieldValue("ItemShadowSize", 1); + int itemShadowX = misc.getFieldValue("ItemShadowOffset", 0); + int itemShadowY = misc.getFieldValue("ItemShadowOffset", 1); + if ((itemShadowFile != null) && !"_".equals(itemShadowFile)) { + final String texture = "ReplaceableTextures\\Shadows\\" + itemShadowFile + ".blp"; + final float shadowX = itemShadowX; + final float shadowY = itemShadowY; + final float shadowWidth = itemShadowWidth; + final float shadowHeight = itemShadowHeight; + if (!this.terrain.splats.containsKey(texture)) { + final Splat splat = new Splat(); + splat.opacity = 0.5f; + this.terrain.splats.put(texture, splat); + } + final float x = unitX - shadowX; + final float y = unitY - shadowY; + this.terrain.splats.get(texture).locations + .add(new float[]{x, y, x + shadowWidth, y + shadowHeight, 3}); + unitShadowSplat = this.terrain.splats.get(texture); + } - path += ".mdx"; - } - } - else { - type = WorldEditorDataType.UNITS; - path = getUnitModelPath(row); + path += ".mdx"; + } + } else { + type = WorldEditorDataType.UNITS; + path = getUnitModelPath(row); - buildingPathingPixelMap = getBuildingPathingPixelMap(row); - if (buildingPathingPixelMap != null) { - unitX = Math.round(unitX / 64f) * 64f; - unitY = Math.round(unitY / 64f) * 64f; - this.terrain.pathingGrid.blitPathingOverlayTexture(unitX, unitY, (int) Math.toDegrees(unitAngle), - buildingPathingPixelMap); - } + buildingPathingPixelMap = getBuildingPathingPixelMap(row); + if (buildingPathingPixelMap != null) { + unitX = Math.round(unitX / 64f) * 64f; + unitY = Math.round(unitY / 64f) * 64f; + this.terrain.pathingGrid.blitPathingOverlayTexture(unitX, unitY, (int) Math.toDegrees(unitAngle), + buildingPathingPixelMap); + } - final String uberSplat = row.getFieldAsString(UBER_SPLAT, 0); - if (uberSplat != null) { - final Element uberSplatInfo = this.terrain.uberSplatTable.get(uberSplat); - if (uberSplatInfo != null) { - final String texturePath = uberSplatInfo.getField("Dir") + "\\" + uberSplatInfo.getField("file") - + ".blp"; - if (!this.terrain.splats.containsKey(texturePath)) { - this.terrain.splats.put(texturePath, new Splat()); - } - final float x = unitX; - final float y = unitY; - final float s = uberSplatInfo.getFieldFloatValue("Scale"); - this.terrain.splats.get(texturePath).locations - .add(new float[] { x - s, y - s, x + s, y + s, 1 }); - } - } + final String uberSplat = row.getFieldAsString(UBER_SPLAT, 0); + if (uberSplat != null) { + final Element uberSplatInfo = this.terrain.uberSplatTable.get(uberSplat); + if (uberSplatInfo != null) { + final String texturePath = uberSplatInfo.getField("Dir") + "\\" + uberSplatInfo.getField("file") + + ".blp"; + final float s = uberSplatInfo.getFieldFloatValue("Scale"); + if (unitsReady) { + this.terrain.addUberSplat(texturePath, unitX, unitY, 1, s); + } else { + if (!this.terrain.splats.containsKey(texturePath)) { + this.terrain.splats.put(texturePath, new Splat()); + } + final float x = unitX; + final float y = unitY; + this.terrain.splats.get(texturePath).locations + .add(new float[]{x - s, y - s, x + s, y + s, 1}); + } + } + } - final String unitShadow = row.getFieldAsString(UNIT_SHADOW, 0); - if ((unitShadow != null) && !"_".equals(unitShadow)) { - final String texture = "ReplaceableTextures\\Shadows\\" + unitShadow + ".blp"; - final float shadowX = row.getFieldAsFloat(UNIT_SHADOW_X, 0); - final float shadowY = row.getFieldAsFloat(UNIT_SHADOW_Y, 0); - final float shadowWidth = row.getFieldAsFloat(UNIT_SHADOW_W, 0); - final float shadowHeight = row.getFieldAsFloat(UNIT_SHADOW_H, 0); - if (this.mapMpq.has(texture)) { - if (!this.terrain.splats.containsKey(texture)) { - final Splat splat = new Splat(); - splat.opacity = 0.5f; - this.terrain.splats.put(texture, splat); - } - final float x = unitX - shadowX; - final float y = unitY - shadowY; - this.terrain.splats.get(texture).locations - .add(new float[] { x, y, x + shadowWidth, y + shadowHeight, 3 }); - unitShadowSplat = this.terrain.splats.get(texture); - } - } + final String unitShadow = row.getFieldAsString(UNIT_SHADOW, 0); + if ((unitShadow != null) && !"_".equals(unitShadow)) { + final String texture = "ReplaceableTextures\\Shadows\\" + unitShadow + ".blp"; + final float shadowX = row.getFieldAsFloat(UNIT_SHADOW_X, 0); + final float shadowY = row.getFieldAsFloat(UNIT_SHADOW_Y, 0); + final float shadowWidth = row.getFieldAsFloat(UNIT_SHADOW_W, 0); + final float shadowHeight = row.getFieldAsFloat(UNIT_SHADOW_H, 0); + if (this.mapMpq.has(texture)) { + if (!this.terrain.splats.containsKey(texture)) { + final Splat splat = new Splat(); + splat.opacity = 0.5f; + this.terrain.splats.put(texture, splat); + } + final float x = unitX - shadowX; + final float y = unitY - shadowY; + this.terrain.splats.get(texture).locations + .add(new float[]{x, y, x + shadowWidth, y + shadowHeight, 3}); + unitShadowSplat = this.terrain.splats.get(texture); + } + } - final String buildingShadow = row.getFieldAsString(BUILDING_SHADOW, 0); - if ((buildingShadow != null) && !"_".equals(buildingShadow)) { - this.terrain.addShadow(buildingShadow, unitX, unitY); - } + final String buildingShadow = row.getFieldAsString(BUILDING_SHADOW, 0); + if ((buildingShadow != null) && !"_".equals(buildingShadow)) { + this.terrain.addShadow(buildingShadow, unitX, unitY); + } - final String soundName = row.getFieldAsString(UNIT_SOUNDSET, 0); - UnitSoundset unitSoundset = this.soundsetNameToSoundset.get(soundName); - if (unitSoundset == null) { - unitSoundset = new UnitSoundset(this.dataSource, this.unitAckSoundsTable, soundName); - this.soundsetNameToSoundset.put(soundName, unitSoundset); - } - soundset = unitSoundset; + final String soundName = row.getFieldAsString(UNIT_SOUNDSET, 0); + UnitSoundset unitSoundset = this.soundsetNameToSoundset.get(soundName); + if (unitSoundset == null) { + unitSoundset = new UnitSoundset(this.dataSource, this.unitAckSoundsTable, soundName); + this.soundsetNameToSoundset.put(soundName, unitSoundset); + } + soundset = unitSoundset; - } - } + } + } - if (path != null) { - final MdxModel model = (MdxModel) this.load(path, this.mapPathSolver, this.solverParams); - MdxModel portraitModel; - final String portraitPath = path.substring(0, path.length() - 4) + "_portrait.mdx"; - if (this.dataSource.has(portraitPath)) { - portraitModel = (MdxModel) this.load(portraitPath, this.mapPathSolver, this.solverParams); - } - else { - portraitModel = model; - } - if (type == WorldEditorDataType.UNITS) { - final float angle = (float) Math.toDegrees(unitAngle); - final CUnit simulationUnit = this.simulation.createUnit(row.getAlias(), playerIndex, unitX, unitY, - angle, buildingPathingPixelMap); - final RenderUnitTypeData typeData = getUnitTypeData(unitId, row); - final RenderUnit renderUnit = new RenderUnit(this, model, row, unitX, unitY, unitZ, playerIndex, - soundset, portraitModel, simulationUnit, typeData); - this.unitToRenderPeer.put(simulationUnit, renderUnit); - this.units.add(renderUnit); - if (unitShadowSplat != null) { - unitShadowSplat.unitMapping.add(new Consumer() { - @Override - public void accept(final SplatMover t) { - renderUnit.shadow = t; - } - }); - } - return simulationUnit; - } - else { - this.items - .add(new RenderItem(this, model, row, unitX, unitY, unitZ, unitAngle, soundset, portraitModel)); // TODO - // store - // somewhere - if (unitShadowSplat != null) { - unitShadowSplat.unitMapping.add(new Consumer() { - @Override - public void accept(final SplatMover t) { + if (path != null) { + final MdxModel model = (MdxModel) this.load(path, this.mapPathSolver, this.solverParams); + MdxModel portraitModel; + final String portraitPath = path.substring(0, path.length() - 4) + "_portrait.mdx"; + if (this.dataSource.has(portraitPath)) { + portraitModel = (MdxModel) this.load(portraitPath, this.mapPathSolver, this.solverParams); + } else { + portraitModel = model; + } + if (type == WorldEditorDataType.UNITS) { + final float angle = (float) Math.toDegrees(unitAngle); + final CUnit simulationUnit = this.simulation.createUnit(row.getAlias(), playerIndex, unitX, unitY, + angle, buildingPathingPixelMap); + final RenderUnitTypeData typeData = getUnitTypeData(unitId, row); + final RenderUnit renderUnit = new RenderUnit(this, model, row, unitX, unitY, unitZ, playerIndex, + soundset, portraitModel, simulationUnit, typeData); + this.unitToRenderPeer.put(simulationUnit, renderUnit); + this.units.add(renderUnit); + if (unitShadowSplat != null) { + unitShadowSplat.unitMapping.add(new Consumer() { + @Override + public void accept(final SplatMover t) { + renderUnit.shadow = t; + } + }); + } + return simulationUnit; + } else { + this.items + .add(new RenderItem(this, model, row, unitX, unitY, unitZ, unitAngle, soundset, portraitModel)); // TODO + // store + // somewhere + if (unitShadowSplat != null) { + unitShadowSplat.unitMapping.add(new Consumer() { + @Override + public void accept(final SplatMover t) { - } - }); - } - } - } - else { - System.err.println("Unknown unit ID: " + unitId); - } - return null; - } + } + }); + } + } + } else { + System.err.println("Unknown unit ID: " + unitId); + } + return null; + } - public String getUnitModelPath(final MutableGameObject row) { - String path; - path = row.getFieldAsString(UNIT_FILE, 0); + public String getUnitModelPath(final MutableGameObject row) { + String path; + path = row.getFieldAsString(UNIT_FILE, 0); - if (path.toLowerCase().endsWith(".mdl") || path.toLowerCase().endsWith(".mdx")) { - path = path.substring(0, path.length() - 4); - } - if ((row.readSLKTagInt("fileVerFlags") == 2) && this.dataSource.has(path + "_V1.mdx")) { - path += "_V1"; - } + if (path.toLowerCase().endsWith(".mdl") || path.toLowerCase().endsWith(".mdx")) { + path = path.substring(0, path.length() - 4); + } + if ((row.readSLKTagInt("fileVerFlags") == 2) && this.dataSource.has(path + "_V1.mdx")) { + path += "_V1"; + } - path += ".mdx"; - return path; - } + path += ".mdx"; + return path; + } - private BufferedImage getBuildingPathingPixelMap(final MutableGameObject row) { - BufferedImage buildingPathingPixelMap = null; - final String pathingTexture = row.getFieldAsString(UNIT_PATHING, 0); - if ((pathingTexture != null) && (pathingTexture.length() > 0) && !"_".equals(pathingTexture)) { - buildingPathingPixelMap = this.filePathToPathingMap.get(pathingTexture.toLowerCase()); - if (buildingPathingPixelMap == null) { - try { - if (pathingTexture.toLowerCase().endsWith(".tga")) { - buildingPathingPixelMap = TgaFile.readTGA(pathingTexture, - this.mapMpq.getResourceAsStream(pathingTexture)); - } - else { - try (InputStream stream = this.mapMpq.getResourceAsStream(pathingTexture)) { - buildingPathingPixelMap = ImageIO.read(stream); - System.out.println("LOADING BLP PATHING: " + pathingTexture); - } - } - this.filePathToPathingMap.put(pathingTexture.toLowerCase(), buildingPathingPixelMap); - } - catch (final IOException exc) { - System.err.println("Failure to get pathing: " + exc.getClass() + ":" + exc.getMessage()); - } - } - } - return buildingPathingPixelMap; - } + private BufferedImage getBuildingPathingPixelMap(final MutableGameObject row) { + BufferedImage buildingPathingPixelMap = null; + final String pathingTexture = row.getFieldAsString(UNIT_PATHING, 0); + if ((pathingTexture != null) && (pathingTexture.length() > 0) && !"_".equals(pathingTexture)) { + buildingPathingPixelMap = this.filePathToPathingMap.get(pathingTexture.toLowerCase()); + if (buildingPathingPixelMap == null) { + try { + if (pathingTexture.toLowerCase().endsWith(".tga")) { + buildingPathingPixelMap = TgaFile.readTGA(pathingTexture, + this.mapMpq.getResourceAsStream(pathingTexture)); + } else { + try (InputStream stream = this.mapMpq.getResourceAsStream(pathingTexture)) { + buildingPathingPixelMap = ImageIO.read(stream); + System.out.println("LOADING BLP PATHING: " + pathingTexture); + } + } + this.filePathToPathingMap.put(pathingTexture.toLowerCase(), buildingPathingPixelMap); + } catch (final IOException exc) { + System.err.println("Failure to get pathing: " + exc.getClass() + ":" + exc.getMessage()); + } + } + } + return buildingPathingPixelMap; + } - public RenderUnitTypeData getUnitTypeData(final War3ID key, final MutableGameObject row) { - RenderUnitTypeData unitTypeData = this.unitIdToTypeData.get(key); - if (unitTypeData == null) { - unitTypeData = new RenderUnitTypeData(row.getFieldAsFloat(MAX_PITCH, 0), row.getFieldAsFloat(MAX_ROLL, 0), - row.getFieldAsFloat(ELEVATION_SAMPLE_RADIUS, 0)); - this.unitIdToTypeData.put(key, unitTypeData); - } - return unitTypeData; - } + public RenderUnitTypeData getUnitTypeData(final War3ID key, final MutableGameObject row) { + RenderUnitTypeData unitTypeData = this.unitIdToTypeData.get(key); + if (unitTypeData == null) { + unitTypeData = new RenderUnitTypeData(row.getFieldAsFloat(MAX_PITCH, 0), row.getFieldAsFloat(MAX_ROLL, 0), + row.getFieldAsFloat(ELEVATION_SAMPLE_RADIUS, 0)); + this.unitIdToTypeData.put(key, unitTypeData); + } + return unitTypeData; + } - @Override - public void update() { - if (this.anyReady) { - this.terrain.update(); + @Override + public void update() { + if (this.anyReady) { + this.terrain.update(); - super.update(); + super.update(); - for (final RenderUnit unit : this.units) { - unit.updateAnimations(this); - } - final Iterator projectileIterator = this.projectiles.iterator(); - while (projectileIterator.hasNext()) { - final RenderEffect projectile = projectileIterator.next(); - if (projectile.updateAnimations(this, Gdx.graphics.getDeltaTime())) { - projectileIterator.remove(); - } - } - for (final RenderItem item : this.items) { - final MdxComplexInstance instance = item.instance; - final MdxComplexInstance mdxComplexInstance = instance; - if (mdxComplexInstance.sequenceEnded || (mdxComplexInstance.sequence == -1)) { - SequenceUtils.randomStandSequence(mdxComplexInstance); - } - } - for (final RenderDoodad item : this.doodads) { - final ModelInstance instance = item.instance; - if (instance instanceof MdxComplexInstance) { - final MdxComplexInstance mdxComplexInstance = (MdxComplexInstance) instance; - if ((mdxComplexInstance.sequence == -1) || (mdxComplexInstance.sequenceEnded - && ((item.getAnimation() != AnimationTokens.PrimaryTag.DEATH) - || (((MdxModel) mdxComplexInstance.model).sequences.get(mdxComplexInstance.sequence) - .getFlags() == 0)))) { - SequenceUtils.randomSequence(mdxComplexInstance, item.getAnimation(), SequenceUtils.EMPTY, - true); + for (final RenderUnit unit : this.units) { + unit.updateAnimations(this); + } + final Iterator projectileIterator = this.projectiles.iterator(); + while (projectileIterator.hasNext()) { + final RenderEffect projectile = projectileIterator.next(); + if (projectile.updateAnimations(this, Gdx.graphics.getDeltaTime())) { + projectileIterator.remove(); + } + } + for (final RenderItem item : this.items) { + final MdxComplexInstance instance = item.instance; + final MdxComplexInstance mdxComplexInstance = instance; + if (mdxComplexInstance.sequenceEnded || (mdxComplexInstance.sequence == -1)) { + SequenceUtils.randomStandSequence(mdxComplexInstance); + } + } + for (final RenderDoodad item : this.doodads) { + final ModelInstance instance = item.instance; + if (instance instanceof MdxComplexInstance) { + final MdxComplexInstance mdxComplexInstance = (MdxComplexInstance) instance; + if ((mdxComplexInstance.sequence == -1) || (mdxComplexInstance.sequenceEnded + && ((item.getAnimation() != AnimationTokens.PrimaryTag.DEATH) + || (((MdxModel) mdxComplexInstance.model).sequences.get(mdxComplexInstance.sequence) + .getFlags() == 0)))) { + SequenceUtils.randomSequence(mdxComplexInstance, item.getAnimation(), SequenceUtils.EMPTY, + true); - } - } - } + } + } + } - final float rawDeltaTime = Gdx.graphics.getRawDeltaTime(); - this.updateTime += rawDeltaTime; - while (this.updateTime >= WarsmashConstants.SIMULATION_STEP_TIME) { - this.updateTime -= WarsmashConstants.SIMULATION_STEP_TIME; - this.simulation.update(); - } - this.dncTerrain.setFrameByRatio( - this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours()); - this.dncTerrain.update(rawDeltaTime, null); - this.dncUnit.setFrameByRatio( - this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours()); - this.dncUnit.update(rawDeltaTime, null); - this.dncUnitDay.setFrameByRatio(0.5f); - this.dncUnitDay.update(rawDeltaTime, null); - this.dncTarget.setFrameByRatio( - this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours()); - this.dncTarget.update(rawDeltaTime, null); - } - } + final float rawDeltaTime = Gdx.graphics.getRawDeltaTime(); + this.updateTime += rawDeltaTime; + while (this.updateTime >= WarsmashConstants.SIMULATION_STEP_TIME) { + this.updateTime -= WarsmashConstants.SIMULATION_STEP_TIME; + this.simulation.update(); + } + this.dncTerrain.setFrameByRatio( + this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours()); + this.dncTerrain.update(rawDeltaTime, null); + this.dncUnit.setFrameByRatio( + this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours()); + this.dncUnit.update(rawDeltaTime, null); + this.dncUnitDay.setFrameByRatio(0.5f); + this.dncUnitDay.update(rawDeltaTime, null); + this.dncTarget.setFrameByRatio( + this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours()); + this.dncTarget.update(rawDeltaTime, null); + } + } - @Override - public void render() { - if (this.anyReady) { - final Scene worldScene = this.worldScene; + @Override + public void render() { + if (this.anyReady) { + final Scene worldScene = this.worldScene; - startFrame(); - worldScene.startFrame(); - worldScene.renderOpaque(this.dynamicShadowManager, this.webGL); - this.terrain.renderGround(this.dynamicShadowManager); - this.terrain.renderCliffs(); - worldScene.renderOpaque(); - this.terrain.renderUberSplats(); - this.terrain.renderWater(); - worldScene.renderTranslucent(); + startFrame(); + worldScene.startFrame(); + worldScene.renderOpaque(this.dynamicShadowManager, this.webGL); + this.terrain.renderGround(this.dynamicShadowManager); + this.terrain.renderCliffs(); + worldScene.renderOpaque(); + this.terrain.renderUberSplats(); + this.terrain.renderWater(); + worldScene.renderTranslucent(); - final List scenes = this.scenes; - for (final Scene scene : scenes) { - if (scene != worldScene) { - scene.startFrame(); - scene.renderOpaque(); - scene.renderTranslucent(); - } - } - } - } + final List scenes = this.scenes; + for (final Scene scene : scenes) { + if (scene != worldScene) { + scene.startFrame(); + scene.renderOpaque(); + scene.renderTranslucent(); + } + } + } + } - public void deselect() { - if (!this.selModels.isEmpty()) { - for (final SplatModel model : this.selModels) { - this.terrain.removeSplatBatchModel(model); - } - this.selModels.clear(); - for (final RenderUnit unit : this.selected) { - unit.selectionCircle = null; - } - } - this.selected.clear(); - } + public void deselect() { + if (!this.selModels.isEmpty()) { + for (final SplatModel model : this.selModels) { + this.terrain.removeSplatBatchModel("selection"); + } + this.selModels.clear(); + for (final RenderUnit unit : this.selected) { + unit.selectionCircle = null; + } + } + this.selected.clear(); + } - public void doSelectUnit(final List units) { - deselect(); - if (units.isEmpty()) { - return; - } + public void doSelectUnit(final List units) { + deselect(); + if (units.isEmpty()) { + return; + } - final Map splats = new HashMap(); - for (final RenderUnit unit : units) { - if (unit.row != null) { - if (unit.selectionScale > 0) { - final float selectionSize = unit.selectionScale * this.selectionCircleScaleFactor; - String path = null; - for (int i = 0; i < this.selectionCircleSizes.size(); i++) { - final SelectionCircleSize selectionCircleSize = this.selectionCircleSizes.get(i); - if ((selectionSize < selectionCircleSize.size) - || (i == (this.selectionCircleSizes.size() - 1))) { - path = selectionCircleSize.texture; - break; - } - } - if (!path.toLowerCase().endsWith(".blp")) { - path += ".blp"; - } - if (!splats.containsKey(path)) { - splats.put(path, new Splat()); - } - final float x = unit.location[0]; - final float y = unit.location[1]; - System.out.println("Selecting a unit at " + x + "," + y); - final float z = unit.row.getFieldAsFloat(UNIT_SELECT_HEIGHT, 0); - splats.get(path).locations.add(new float[] { x - (selectionSize / 2), y - (selectionSize / 2), - x + (selectionSize / 2), y + (selectionSize / 2), z + 5 }); - splats.get(path).unitMapping.add(new Consumer() { - @Override - public void accept(final SplatMover t) { - unit.selectionCircle = t; - } - }); - } - this.selected.add(unit); - } - } - this.selModels.clear(); - for (final Map.Entry entry : splats.entrySet()) { - final String path = entry.getKey(); - final Splat locations = entry.getValue(); - final SplatModel model = new SplatModel(Gdx.gl30, (Texture) load(path, PathSolver.DEFAULT, null), - locations.locations, this.terrain.centerOffset, locations.unitMapping, true); - model.color[0] = 0; - model.color[1] = 1; - model.color[2] = 0; - model.color[3] = 1; - this.selModels.add(model); - this.terrain.addSplatBatchModel(model); - } - } + final Map splats = new HashMap(); + for (final RenderUnit unit : units) { + if (unit.row != null) { + if (unit.selectionScale > 0) { + final float selectionSize = unit.selectionScale * this.selectionCircleScaleFactor; + String path = null; + for (int i = 0; i < this.selectionCircleSizes.size(); i++) { + final SelectionCircleSize selectionCircleSize = this.selectionCircleSizes.get(i); + if ((selectionSize < selectionCircleSize.size) + || (i == (this.selectionCircleSizes.size() - 1))) { + path = selectionCircleSize.texture; + break; + } + } + if (!path.toLowerCase().endsWith(".blp")) { + path += ".blp"; + } + if (!splats.containsKey(path)) { + splats.put(path, new Splat()); + } + final float x = unit.location[0]; + final float y = unit.location[1]; + System.out.println("Selecting a unit at " + x + "," + y); + final float z = unit.row.getFieldAsFloat(UNIT_SELECT_HEIGHT, 0); + splats.get(path).locations.add(new float[]{x - (selectionSize / 2), y - (selectionSize / 2), + x + (selectionSize / 2), y + (selectionSize / 2), z + 5}); + splats.get(path).unitMapping.add(new Consumer() { + @Override + public void accept(final SplatMover t) { + unit.selectionCircle = t; + } + }); + } + this.selected.add(unit); + } + } + this.selModels.clear(); + for (final Map.Entry entry : splats.entrySet()) { + final String path = entry.getKey(); + final Splat locations = entry.getValue(); + final SplatModel model = new SplatModel(Gdx.gl30, (Texture) load(path, PathSolver.DEFAULT, null), + locations.locations, this.terrain.centerOffset, locations.unitMapping, true); + model.color[0] = 0; + model.color[1] = 1; + model.color[2] = 0; + model.color[3] = 1; + this.selModels.add(model); + this.terrain.addSplatBatchModel("selection", model); + } + } - public void getClickLocation(final Vector3 out, final int screenX, final int screenY) { - final float[] ray = rayHeap; - mousePosHeap.set(screenX, screenY); - this.worldScene.camera.screenToWorldRay(ray, mousePosHeap); - gdxRayHeap.set(ray[0], ray[1], ray[2], ray[3] - ray[0], ray[4] - ray[1], ray[5] - ray[2]); - gdxRayHeap.direction.nor();// needed for libgdx - RenderMathUtils.intersectRayTriangles(gdxRayHeap, this.terrain.softwareGroundMesh.vertices, - this.terrain.softwareGroundMesh.indices, 3, out); - rectangleHeap.set(Math.min(out.x, gdxRayHeap.origin.x), Math.min(out.y, gdxRayHeap.origin.y), - Math.abs(out.x - gdxRayHeap.origin.x), Math.abs(out.y - gdxRayHeap.origin.y)); - this.walkableObjectsTree.intersect(rectangleHeap, this.walkablesIntersectionFinder.reset(gdxRayHeap)); - if (this.walkablesIntersectionFinder.found) { - out.set(this.walkablesIntersectionFinder.intersection); - } - else { - out.z = Math.max(getWalkableRenderHeight(out.x, out.y), this.terrain.getGroundHeight(out.x, out.y)); - } - } + public void getClickLocation(final Vector3 out, final int screenX, final int screenY) { + final float[] ray = rayHeap; + mousePosHeap.set(screenX, screenY); + this.worldScene.camera.screenToWorldRay(ray, mousePosHeap); + gdxRayHeap.set(ray[0], ray[1], ray[2], ray[3] - ray[0], ray[4] - ray[1], ray[5] - ray[2]); + gdxRayHeap.direction.nor();// needed for libgdx + RenderMathUtils.intersectRayTriangles(gdxRayHeap, this.terrain.softwareGroundMesh.vertices, + this.terrain.softwareGroundMesh.indices, 3, out); + rectangleHeap.set(Math.min(out.x, gdxRayHeap.origin.x), Math.min(out.y, gdxRayHeap.origin.y), + Math.abs(out.x - gdxRayHeap.origin.x), Math.abs(out.y - gdxRayHeap.origin.y)); + this.walkableObjectsTree.intersect(rectangleHeap, this.walkablesIntersectionFinder.reset(gdxRayHeap)); + if (this.walkablesIntersectionFinder.found) { + out.set(this.walkablesIntersectionFinder.intersection); + } else { + out.z = Math.max(getWalkableRenderHeight(out.x, out.y), this.terrain.getGroundHeight(out.x, out.y)); + } + } - public void showConfirmation(final Vector3 position, final float red, final float green, final float blue) { - this.confirmationInstance.show(); - this.confirmationInstance.setSequence(0); - this.confirmationInstance.setLocation(position); - this.worldScene.instanceMoved(this.confirmationInstance, position.x, position.y); - this.confirmationInstance.vertexColor[0] = red; - this.confirmationInstance.vertexColor[1] = green; - this.confirmationInstance.vertexColor[2] = blue; - } + public void showConfirmation(final Vector3 position, final float red, final float green, final float blue) { + this.confirmationInstance.show(); + this.confirmationInstance.setSequence(0); + this.confirmationInstance.setLocation(position); + this.worldScene.instanceMoved(this.confirmationInstance, position.x, position.y); + this.confirmationInstance.vertexColor[0] = red; + this.confirmationInstance.vertexColor[1] = green; + this.confirmationInstance.vertexColor[2] = blue; + } - public List selectUnit(final float x, final float y, final boolean toggle) { - System.out.println("world: " + x + "," + y); - final float[] ray = rayHeap; - mousePosHeap.set(x, y); - this.worldScene.camera.screenToWorldRay(ray, mousePosHeap); - gdxRayHeap.set(ray[0], ray[1], ray[2], ray[3] - ray[0], ray[4] - ray[1], ray[5] - ray[2]); - gdxRayHeap.direction.nor();// needed for libgdx + public List selectUnit(final float x, final float y, final boolean toggle) { + System.out.println("world: " + x + "," + y); + final float[] ray = rayHeap; + mousePosHeap.set(x, y); + this.worldScene.camera.screenToWorldRay(ray, mousePosHeap); + gdxRayHeap.set(ray[0], ray[1], ray[2], ray[3] - ray[0], ray[4] - ray[1], ray[5] - ray[2]); + gdxRayHeap.direction.nor();// needed for libgdx - RenderUnit entity = null; - for (final RenderUnit unit : this.units) { - final MdxComplexInstance instance = unit.instance; - if (instance.isVisible(this.worldScene.camera) - && instance.intersectRayWithCollision(gdxRayHeap, intersectionHeap, - unit.getSimulationUnit().getUnitType().isBuilding(), false) - && !unit.getSimulationUnit().isDead()) { - if ((entity == null) || (entity.instance.depth > instance.depth)) { - entity = unit; - } - } - } - List sel; - if (entity != null) { - if (toggle) { - sel = new ArrayList<>(this.selected); - final int idx = sel.indexOf(entity); - if (idx >= 0) { - sel.remove(idx); - } - else { - sel.add(entity); - } - } - else { - sel = Arrays.asList(entity); - } - } - else { - sel = Collections.emptyList(); - } - this.doSelectUnit(sel); - return sel; - } + RenderUnit entity = null; + for (final RenderUnit unit : this.units) { + final MdxComplexInstance instance = unit.instance; + if (instance.isVisible(this.worldScene.camera) + && instance.intersectRayWithCollision(gdxRayHeap, intersectionHeap, + unit.getSimulationUnit().getUnitType().isBuilding(), false) + && !unit.getSimulationUnit().isDead()) { + if ((entity == null) || (entity.instance.depth > instance.depth)) { + entity = unit; + } + } + } + List sel; + if (entity != null) { + if (toggle) { + sel = new ArrayList<>(this.selected); + final int idx = sel.indexOf(entity); + if (idx >= 0) { + sel.remove(idx); + } else { + sel.add(entity); + } + } else { + sel = Arrays.asList(entity); + } + } else { + sel = Collections.emptyList(); + } + this.doSelectUnit(sel); + return sel; + } - public RenderUnit rayPickUnit(final float x, final float y) { - return rayPickUnit(x, y, CUnitFilterFunction.ACCEPT_ALL); - } + public RenderUnit rayPickUnit(final float x, final float y) { + return rayPickUnit(x, y, CUnitFilterFunction.ACCEPT_ALL); + } - public RenderUnit rayPickUnit(final float x, final float y, final CUnitFilterFunction filter) { - final float[] ray = rayHeap; - mousePosHeap.set(x, y); - this.worldScene.camera.screenToWorldRay(ray, mousePosHeap); - gdxRayHeap.set(ray[0], ray[1], ray[2], ray[3] - ray[0], ray[4] - ray[1], ray[5] - ray[2]); - gdxRayHeap.direction.nor();// needed for libgdx + public RenderUnit rayPickUnit(final float x, final float y, final CUnitFilterFunction filter) { + final float[] ray = rayHeap; + mousePosHeap.set(x, y); + this.worldScene.camera.screenToWorldRay(ray, mousePosHeap); + gdxRayHeap.set(ray[0], ray[1], ray[2], ray[3] - ray[0], ray[4] - ray[1], ray[5] - ray[2]); + gdxRayHeap.direction.nor();// needed for libgdx - RenderUnit entity = null; - for (final RenderUnit unit : this.units) { - final MdxComplexInstance instance = unit.instance; - if (instance.isVisible(this.worldScene.camera) && instance.intersectRayWithCollision(gdxRayHeap, - intersectionHeap, unit.getSimulationUnit().getUnitType().isBuilding(), false)) { - if (filter.call(unit.getSimulationUnit()) && (intersectionHeap.z > this.terrain - .getGroundHeight(intersectionHeap.x, intersectionHeap.y))) { - if ((entity == null) || (entity.instance.depth > instance.depth)) { - entity = unit; - } - } - } - } - return entity; - } + RenderUnit entity = null; + for (final RenderUnit unit : this.units) { + final MdxComplexInstance instance = unit.instance; + if (instance.isVisible(this.worldScene.camera) && instance.intersectRayWithCollision(gdxRayHeap, + intersectionHeap, unit.getSimulationUnit().getUnitType().isBuilding(), false)) { + if (filter.call(unit.getSimulationUnit()) && (intersectionHeap.z > this.terrain + .getGroundHeight(intersectionHeap.x, intersectionHeap.y))) { + if ((entity == null) || (entity.instance.depth > instance.depth)) { + entity = unit; + } + } + } + } + return entity; + } - private static final class MappedDataCallbackImplementation implements LoadGenericCallback { - @Override - public Object call(final InputStream data) { - final StringBuilder stringBuilder = new StringBuilder(); - try (BufferedReader reader = new BufferedReader(new InputStreamReader(data, "utf-8"))) { - String line; - while ((line = reader.readLine()) != null) { - stringBuilder.append(line); - stringBuilder.append("\n"); - } - } - catch (final UnsupportedEncodingException e) { - throw new RuntimeException(e); - } - catch (final IOException e) { - throw new RuntimeException(e); - } - return new MappedData(stringBuilder.toString()); - } - } + private static final class MappedDataCallbackImplementation implements LoadGenericCallback { + @Override + public Object call(final InputStream data) { + final StringBuilder stringBuilder = new StringBuilder(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(data, "utf-8"))) { + String line; + while ((line = reader.readLine()) != null) { + stringBuilder.append(line); + stringBuilder.append("\n"); + } + } catch (final UnsupportedEncodingException e) { + throw new RuntimeException(e); + } catch (final IOException e) { + throw new RuntimeException(e); + } + return new MappedData(stringBuilder.toString()); + } + } - private static final class StringDataCallbackImplementation implements LoadGenericCallback { - @Override - public Object call(final InputStream data) { - if (data == null) { - System.err.println("data null"); - } - final StringBuilder stringBuilder = new StringBuilder(); - try (BufferedReader reader = new BufferedReader(new InputStreamReader(data, "utf-8"))) { - String line; - while ((line = reader.readLine()) != null) { - stringBuilder.append(line); - stringBuilder.append("\n"); - } - } - catch (final UnsupportedEncodingException e) { - throw new RuntimeException(e); - } - catch (final IOException e) { - throw new RuntimeException(e); - } - return stringBuilder.toString(); - } - } + private static final class StringDataCallbackImplementation implements LoadGenericCallback { + @Override + public Object call(final InputStream data) { + if (data == null) { + System.err.println("data null"); + } + final StringBuilder stringBuilder = new StringBuilder(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(data, "utf-8"))) { + String line; + while ((line = reader.readLine()) != null) { + stringBuilder.append(line); + stringBuilder.append("\n"); + } + } catch (final UnsupportedEncodingException e) { + throw new RuntimeException(e); + } catch (final IOException e) { + throw new RuntimeException(e); + } + return stringBuilder.toString(); + } + } - private static final class StreamDataCallbackImplementation implements LoadGenericCallback { - @Override - public Object call(final InputStream data) { - return data; - } - } + private static final class StreamDataCallbackImplementation implements LoadGenericCallback { + @Override + public Object call(final InputStream data) { + return data; + } + } - public static final class SolverParams { - public char tileset; - public boolean reforged; - public boolean hd; - } + public static final class SolverParams { + public char tileset; + public boolean reforged; + public boolean hd; + } - public static final class CliffInfo { - public List locations = new ArrayList<>(); - public List textures = new ArrayList<>(); - } + public static final class CliffInfo { + public List locations = new ArrayList<>(); + public List textures = new ArrayList<>(); + } - private static final int MAXIMUM_ACCEPTED = 1 << 30; - private float selectionCircleScaleFactor; - private DataTable worldEditData; - private WorldEditStrings worldEditStrings; - private Warcraft3MapObjectData allObjectData; - private AbilityDataUI abilityDataUI; - private Map soundsetNameToSoundset; + private static final int MAXIMUM_ACCEPTED = 1 << 30; + private float selectionCircleScaleFactor; + private DataTable worldEditData; + private WorldEditStrings worldEditStrings; + private Warcraft3MapObjectData allObjectData; + private AbilityDataUI abilityDataUI; + private Map soundsetNameToSoundset; - /** - * Returns a power of two size for the given target capacity. - */ - private static final int pow2GreaterThan(final int capacity) { - int numElements = capacity - 1; - numElements |= numElements >>> 1; - numElements |= numElements >>> 2; - numElements |= numElements >>> 4; - numElements |= numElements >>> 8; - numElements |= numElements >>> 16; - return (numElements < 0) ? 1 : (numElements >= MAXIMUM_ACCEPTED) ? MAXIMUM_ACCEPTED : numElements + 1; - } + /** + * Returns a power of two size for the given target capacity. + */ + private static final int pow2GreaterThan(final int capacity) { + int numElements = capacity - 1; + numElements |= numElements >>> 1; + numElements |= numElements >>> 2; + numElements |= numElements >>> 4; + numElements |= numElements >>> 8; + numElements |= numElements >>> 16; + return (numElements < 0) ? 1 : (numElements >= MAXIMUM_ACCEPTED) ? MAXIMUM_ACCEPTED : numElements + 1; + } - public void standOnRepeat(final MdxComplexInstance instance) { - instance.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); - SequenceUtils.randomStandSequence(instance); - } + public void standOnRepeat(final MdxComplexInstance instance) { + instance.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + SequenceUtils.randomStandSequence(instance); + } - private static final class SelectionCircleSize { - private final float size; - private final String texture; - private final String textureDotted; + private static final class SelectionCircleSize { + private final float size; + private final String texture; + private final String textureDotted; - public SelectionCircleSize(final float size, final String texture, final String textureDotted) { - this.size = size; - this.texture = texture; - this.textureDotted = textureDotted; - } - } + public SelectionCircleSize(final float size, final String texture, final String textureDotted) { + this.size = size; + this.texture = texture; + this.textureDotted = textureDotted; + } + } - public void setDayNightModels(final String terrainDNCFile, final String unitDNCFile) { - final MdxModel terrainDNCModel = (MdxModel) load(mdx(terrainDNCFile), PathSolver.DEFAULT, null); - this.dncTerrain = (MdxComplexInstance) terrainDNCModel.addInstance(); - this.dncTerrain.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); - this.dncTerrain.setSequence(0); - final MdxModel unitDNCModel = (MdxModel) load(mdx(unitDNCFile), PathSolver.DEFAULT, null); - this.dncUnit = (MdxComplexInstance) unitDNCModel.addInstance(); - this.dncUnit.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); - this.dncUnit.setSequence(0); - this.dncUnitDay = (MdxComplexInstance) unitDNCModel.addInstance(); - this.dncUnitDay.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); - this.dncUnitDay.setSequence(0); - final MdxModel targetDNCModel = (MdxModel) load( - mdx("Environment\\DNC\\DNCLordaeron\\DNCLordaeronTarget\\DNCLordaeronTarget.mdl"), PathSolver.DEFAULT, - null); - this.dncTarget = (MdxComplexInstance) targetDNCModel.addInstance(); - this.dncTarget.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); - this.dncTarget.setSequence(0); - } + public void setDayNightModels(final String terrainDNCFile, final String unitDNCFile) { + final MdxModel terrainDNCModel = (MdxModel) load(mdx(terrainDNCFile), PathSolver.DEFAULT, null); + this.dncTerrain = (MdxComplexInstance) terrainDNCModel.addInstance(); + this.dncTerrain.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + this.dncTerrain.setSequence(0); + final MdxModel unitDNCModel = (MdxModel) load(mdx(unitDNCFile), PathSolver.DEFAULT, null); + this.dncUnit = (MdxComplexInstance) unitDNCModel.addInstance(); + this.dncUnit.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + this.dncUnit.setSequence(0); + this.dncUnitDay = (MdxComplexInstance) unitDNCModel.addInstance(); + this.dncUnitDay.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + this.dncUnitDay.setSequence(0); + final MdxModel targetDNCModel = (MdxModel) load( + mdx("Environment\\DNC\\DNCLordaeron\\DNCLordaeronTarget\\DNCLordaeronTarget.mdl"), PathSolver.DEFAULT, + null); + this.dncTarget = (MdxComplexInstance) targetDNCModel.addInstance(); + this.dncTarget.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + this.dncTarget.setSequence(0); + } - private static String mdx(String mdxPath) { - if (mdxPath.toLowerCase().endsWith(".mdl")) { - mdxPath = mdxPath.substring(0, mdxPath.length() - 4); - } - if (!mdxPath.toLowerCase().endsWith(".mdx")) { - mdxPath += ".mdx"; - } - return mdxPath; - } + private static String mdx(String mdxPath) { + if (mdxPath.toLowerCase().endsWith(".mdl")) { + mdxPath = mdxPath.substring(0, mdxPath.length() - 4); + } + if (!mdxPath.toLowerCase().endsWith(".mdx")) { + mdxPath += ".mdx"; + } + return mdxPath; + } - @Override - public SceneLightManager createLightManager(final boolean simple) { - if (simple) { - return new W3xScenePortraitLightManager(this, this.lightDirection); - } - else { - return new W3xSceneWorldLightManager(this); - } - } + @Override + public SceneLightManager createLightManager(final boolean simple) { + if (simple) { + return new W3xScenePortraitLightManager(this, this.lightDirection); + } else { + return new W3xSceneWorldLightManager(this); + } + } - public WorldEditStrings getWorldEditStrings() { - return this.worldEditStrings; - } + public WorldEditStrings getWorldEditStrings() { + return this.worldEditStrings; + } - public void setGameUI(final GameUI gameUI) { - this.gameUI = gameUI; - this.abilityDataUI = new AbilityDataUI(this.allObjectData.getAbilities(), this.allObjectData.getUnits(), - gameUI); - } + public void setGameUI(final GameUI gameUI) { + this.gameUI = gameUI; + this.abilityDataUI = new AbilityDataUI(this.allObjectData.getAbilities(), this.allObjectData.getUnits(), + gameUI); + } - public GameUI getGameUI() { - return this.gameUI; - } + public GameUI getGameUI() { + return this.gameUI; + } - public AbilityDataUI getAbilityDataUI() { - return this.abilityDataUI; - } + public AbilityDataUI getAbilityDataUI() { + return this.abilityDataUI; + } - public KeyedSounds getUiSounds() { - return this.uiSounds; - } + public KeyedSounds getUiSounds() { + return this.uiSounds; + } - public Warcraft3MapObjectData getAllObjectData() { - return this.allObjectData; - } + public Warcraft3MapObjectData getAllObjectData() { + return this.allObjectData; + } - public float getWalkableRenderHeight(final float x, final float y) { - this.walkableObjectsTree.intersect(x, y, this.walkablesIntersector.reset(x, y)); - return this.walkablesIntersector.z; - } + public float getWalkableRenderHeight(final float x, final float y) { + this.walkableObjectsTree.intersect(x, y, this.walkablesIntersector.reset(x, y)); + return this.walkablesIntersector.z; + } - public MdxComplexInstance getHighestWalkableUnder(final float x, final float y) { - this.walkableObjectsTree.intersect(x, y, this.intersectorFindsHighestWalkable.reset(x, y)); - return this.intersectorFindsHighestWalkable.highestInstance; - } + public MdxComplexInstance getHighestWalkableUnder(final float x, final float y) { + this.walkableObjectsTree.intersect(x, y, this.intersectorFindsHighestWalkable.reset(x, y)); + return this.intersectorFindsHighestWalkable.highestInstance; + } - private static final class QuadtreeIntersectorFindsWalkableRenderHeight - implements QuadtreeIntersector { - private float z; - private final Ray ray = new Ray(); - private final Vector3 intersection = new Vector3(); + private static final class QuadtreeIntersectorFindsWalkableRenderHeight + implements QuadtreeIntersector { + private float z; + private final Ray ray = new Ray(); + private final Vector3 intersection = new Vector3(); - private QuadtreeIntersectorFindsWalkableRenderHeight reset(final float x, final float y) { - this.z = -Float.MAX_VALUE; - this.ray.set(x, y, 4096, 0, 0, -8192); - return this; - } + private QuadtreeIntersectorFindsWalkableRenderHeight reset(final float x, final float y) { + this.z = -Float.MAX_VALUE; + this.ray.set(x, y, 4096, 0, 0, -8192); + return this; + } - @Override - public boolean onIntersect(final MdxComplexInstance intersectingObject) { - if (intersectingObject.intersectRayWithCollision(this.ray, this.intersection, true, true)) { - this.z = Math.max(this.z, this.intersection.z); - } - return false; - } - } + @Override + public boolean onIntersect(final MdxComplexInstance intersectingObject) { + if (intersectingObject.intersectRayWithCollision(this.ray, this.intersection, true, true)) { + this.z = Math.max(this.z, this.intersection.z); + } + return false; + } + } - private static final class QuadtreeIntersectorFindsHighestWalkable - implements QuadtreeIntersector { - private float z; - private final Ray ray = new Ray(); - private final Vector3 intersection = new Vector3(); - private MdxComplexInstance highestInstance; + private static final class QuadtreeIntersectorFindsHighestWalkable + implements QuadtreeIntersector { + private float z; + private final Ray ray = new Ray(); + private final Vector3 intersection = new Vector3(); + private MdxComplexInstance highestInstance; - private QuadtreeIntersectorFindsHighestWalkable reset(final float x, final float y) { - this.z = -Float.MAX_VALUE; - this.ray.set(x, y, 4096, 0, 0, -8192); - this.highestInstance = null; - return this; - } + private QuadtreeIntersectorFindsHighestWalkable reset(final float x, final float y) { + this.z = -Float.MAX_VALUE; + this.ray.set(x, y, 4096, 0, 0, -8192); + this.highestInstance = null; + return this; + } - @Override - public boolean onIntersect(final MdxComplexInstance intersectingObject) { - if (intersectingObject.intersectRayWithCollision(this.ray, this.intersection, true, true)) { - if (this.intersection.z > this.z) { - this.z = this.intersection.z; - this.highestInstance = intersectingObject; - } - } - return false; - } - } + @Override + public boolean onIntersect(final MdxComplexInstance intersectingObject) { + if (intersectingObject.intersectRayWithCollision(this.ray, this.intersection, true, true)) { + if (this.intersection.z > this.z) { + this.z = this.intersection.z; + this.highestInstance = intersectingObject; + } + } + return false; + } + } - private static final class QuadtreeIntersectorFindsHitPoint implements QuadtreeIntersector { - private Ray ray; - private final Vector3 intersection = new Vector3(); - private boolean found; + private static final class QuadtreeIntersectorFindsHitPoint implements QuadtreeIntersector { + private Ray ray; + private final Vector3 intersection = new Vector3(); + private boolean found; - private QuadtreeIntersectorFindsHitPoint reset(final Ray ray) { - this.ray = ray; - this.found = false; - return this; - } + private QuadtreeIntersectorFindsHitPoint reset(final Ray ray) { + this.ray = ray; + this.found = false; + return this; + } - @Override - public boolean onIntersect(final MdxComplexInstance intersectingObject) { - if (intersectingObject.intersectRayWithCollision(this.ray, this.intersection, true, true)) { - this.found = true; - return true; - } - return false; - } - } + @Override + public boolean onIntersect(final MdxComplexInstance intersectingObject) { + if (intersectingObject.intersectRayWithCollision(this.ray, this.intersection, true, true)) { + this.found = true; + return true; + } + return false; + } + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java index c5b6b40..471801b 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java @@ -5,13 +5,7 @@ import java.io.IOException; import java.io.InputStream; import java.nio.Buffer; import java.nio.FloatBuffer; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.TreeSet; +import java.util.*; import java.util.function.Consumer; import javax.imageio.ImageIO; @@ -54,1148 +48,1167 @@ import com.etheller.warsmash.viewer5.handlers.w3x.W3xShaders; import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; public class Terrain { - private static final String[] colorTags = { "R", "G", "B", "A" }; - private static final float[] sizeHeap = new float[2]; - private static final Vector3 normalHeap1 = new Vector3(); - private static final Vector3 normalHeap2 = new Vector3(); - private static final float[] fourComponentHeap = new float[4]; - private static final Matrix4 tempMatrix = new Matrix4(); - private static final boolean WIREFRAME_TERRAIN = false; + private static final String[] colorTags = {"R", "G", "B", "A"}; + private static final float[] sizeHeap = new float[2]; + private static final Vector3 normalHeap1 = new Vector3(); + private static final Vector3 normalHeap2 = new Vector3(); + private static final float[] fourComponentHeap = new float[4]; + private static final Matrix4 tempMatrix = new Matrix4(); + private static final boolean WIREFRAME_TERRAIN = false; - public ShaderProgram groundShader; - public ShaderProgram waterShader; - public ShaderProgram cliffShader; - public ShaderProgram testShader; - public float waterIndex; - public float waterIncreasePerFrame; - public float waterHeightOffset; + public ShaderProgram groundShader; + public ShaderProgram waterShader; + public ShaderProgram cliffShader; + public ShaderProgram testShader; + public float waterIndex; + public float waterIncreasePerFrame; + public float waterHeightOffset; -// - public List groundTextures = new ArrayList<>(); - public List cliffTextures = new ArrayList<>(); - public RenderCorner[][] corners; - public int columns; - public int rows; - public int blightTextureIndex = -1; - public float[] maxDeepColor = new float[4]; - public float[] minDeepColor = new float[4]; - public float[] maxShallowColor = new float[4]; - public float[] minShallowColor = new float[4]; + // + public List groundTextures = new ArrayList<>(); + public List cliffTextures = new ArrayList<>(); + public RenderCorner[][] corners; + public int columns; + public int rows; + public int blightTextureIndex = -1; + public float[] maxDeepColor = new float[4]; + public float[] minDeepColor = new float[4]; + public float[] maxShallowColor = new float[4]; + public float[] minShallowColor = new float[4]; - private final DataTable terrainTable; - private final DataTable cliffTable; - private final DataTable waterTable; - private final int waterTextureCount; - private int cliffTexturesSize; - private final List cliffMeshes = new ArrayList<>(); - private final Map pathToCliff = new HashMap<>(); - private final Map groundTextureToId = new HashMap<>(); - private final List cliffToGroundTexture = new ArrayList<>(); - private final List cliffs = new ArrayList<>(); - private final DataSource dataSource; - private final float[] groundHeights; - private final float[] groundCornerHeights; - private final short[] groundTextureList; - private final float[] waterHeights; - private final byte[] waterExistsData; + private final DataTable terrainTable; + private final DataTable cliffTable; + private final DataTable waterTable; + private final int waterTextureCount; + private int cliffTexturesSize; + private final List cliffMeshes = new ArrayList<>(); + private final Map pathToCliff = new HashMap<>(); + private final Map groundTextureToId = new HashMap<>(); + private final List cliffToGroundTexture = new ArrayList<>(); + private final List cliffs = new ArrayList<>(); + private final DataSource dataSource; + private final float[] groundHeights; + private final float[] groundCornerHeights; + private final short[] groundTextureList; + private final float[] waterHeights; + private final byte[] waterExistsData; - private int groundTextureData = -1; - private final int groundHeight; - private final int groundCornerHeight; - private final int groundCornerHeightLinear; - private final int cliffTextureArray; - private final int waterHeight; - private final int waterExists; - private final int waterTextureArray; - private final Camera camera; - private final War3MapViewer viewer; - public float[] centerOffset; - private final WebGL webGL; - private final ShaderProgram uberSplatShader; - public final DataTable uberSplatTable; + private int groundTextureData = -1; + private final int groundHeight; + private final int groundCornerHeight; + private final int groundCornerHeightLinear; + private final int cliffTextureArray; + private final int waterHeight; + private final int waterExists; + private final int waterTextureArray; + private final Camera camera; + private final War3MapViewer viewer; + public float[] centerOffset; + private final WebGL webGL; + private final ShaderProgram uberSplatShader; + public final DataTable uberSplatTable; - private final List uberSplatModels; - private int shadowMap; - public final Map splats = new HashMap<>(); - public final Map> shadows = new HashMap<>(); - public final Map shadowTextures = new HashMap<>(); - private final int[] mapBounds; - private final float[] shaderMapBounds; - private final int[] mapSize; - public final SoftwareGroundMesh softwareGroundMesh; - private final int testArrayBuffer; - private final int testElementBuffer; + private final Map uberSplatModels; + private int shadowMap; + public final Map splats = new HashMap<>(); + public final Map> shadows = new HashMap<>(); + public final Map shadowTextures = new HashMap<>(); + private final int[] mapBounds; + private final float[] shaderMapBounds; + private final int[] mapSize; + public final SoftwareGroundMesh softwareGroundMesh; + private final int testArrayBuffer; + private final int testElementBuffer; + private boolean initShadowsFinished = false; + private byte[] shadowData; - public Terrain(final War3MapW3e w3eFile, final War3MapWpm terrainPathing, final War3MapW3i w3iFile, - final WebGL webGL, final DataSource dataSource, final WorldEditStrings worldEditStrings, - final War3MapViewer viewer, final DataTable worldEditData) throws IOException { - this.webGL = webGL; - this.viewer = viewer; - this.camera = viewer.worldScene.camera; - this.dataSource = dataSource; - final String texturesExt = ".blp"; - final Corner[][] corners = w3eFile.getCorners(); - this.corners = new RenderCorner[corners[0].length][corners.length]; - for (int i = 0; i < corners.length; i++) { - for (int j = 0; j < corners[i].length; j++) { - this.corners[j][i] = new RenderCorner(corners[i][j]); - } - } - final int width = w3eFile.getMapSize()[0]; - final int height = w3eFile.getMapSize()[1]; - this.columns = width; - this.rows = height; - for (int i = 0; i < (width - 1); i++) { - for (int j = 0; j < (height - 1); j++) { - final RenderCorner bottomLeft = this.corners[i][j]; - final RenderCorner bottomRight = this.corners[i + 1][j]; - final RenderCorner topLeft = this.corners[i][j + 1]; - final RenderCorner topRight = this.corners[i + 1][j + 1]; + public Terrain(final War3MapW3e w3eFile, final War3MapWpm terrainPathing, final War3MapW3i w3iFile, + final WebGL webGL, final DataSource dataSource, final WorldEditStrings worldEditStrings, + final War3MapViewer viewer, final DataTable worldEditData) throws IOException { + this.webGL = webGL; + this.viewer = viewer; + this.camera = viewer.worldScene.camera; + this.dataSource = dataSource; + final String texturesExt = ".blp"; + final Corner[][] corners = w3eFile.getCorners(); + this.corners = new RenderCorner[corners[0].length][corners.length]; + for (int i = 0; i < corners.length; i++) { + for (int j = 0; j < corners[i].length; j++) { + this.corners[j][i] = new RenderCorner(corners[i][j]); + } + } + final int width = w3eFile.getMapSize()[0]; + final int height = w3eFile.getMapSize()[1]; + this.columns = width; + this.rows = height; + for (int i = 0; i < (width - 1); i++) { + for (int j = 0; j < (height - 1); j++) { + final RenderCorner bottomLeft = this.corners[i][j]; + final RenderCorner bottomRight = this.corners[i + 1][j]; + final RenderCorner topLeft = this.corners[i][j + 1]; + final RenderCorner topRight = this.corners[i + 1][j + 1]; - bottomLeft.cliff = (bottomLeft.getLayerHeight() != bottomRight.getLayerHeight()) - || (bottomLeft.getLayerHeight() != topLeft.getLayerHeight()) - || (bottomLeft.getLayerHeight() != topRight.getLayerHeight()); - } - } + bottomLeft.cliff = (bottomLeft.getLayerHeight() != bottomRight.getLayerHeight()) + || (bottomLeft.getLayerHeight() != topLeft.getLayerHeight()) + || (bottomLeft.getLayerHeight() != topRight.getLayerHeight()); + } + } - this.terrainTable = new DataTable(worldEditStrings); - try (InputStream terrainSlkStream = dataSource.getResourceAsStream("TerrainArt\\Terrain.slk")) { - this.terrainTable.readSLK(terrainSlkStream); - } - this.cliffTable = new DataTable(worldEditStrings); - try (InputStream cliffSlkStream = dataSource.getResourceAsStream("TerrainArt\\CliffTypes.slk")) { - this.cliffTable.readSLK(cliffSlkStream); - } - this.waterTable = new DataTable(worldEditStrings); - try (InputStream waterSlkStream = dataSource.getResourceAsStream("TerrainArt\\Water.slk")) { - this.waterTable.readSLK(waterSlkStream); - } - this.uberSplatTable = new DataTable(worldEditStrings); - try (InputStream uberSlkStream = dataSource.getResourceAsStream("Splats\\UberSplatData.slk")) { - this.uberSplatTable.readSLK(uberSlkStream); - } + this.terrainTable = new DataTable(worldEditStrings); + try (InputStream terrainSlkStream = dataSource.getResourceAsStream("TerrainArt\\Terrain.slk")) { + this.terrainTable.readSLK(terrainSlkStream); + } + this.cliffTable = new DataTable(worldEditStrings); + try (InputStream cliffSlkStream = dataSource.getResourceAsStream("TerrainArt\\CliffTypes.slk")) { + this.cliffTable.readSLK(cliffSlkStream); + } + this.waterTable = new DataTable(worldEditStrings); + try (InputStream waterSlkStream = dataSource.getResourceAsStream("TerrainArt\\Water.slk")) { + this.waterTable.readSLK(waterSlkStream); + } + this.uberSplatTable = new DataTable(worldEditStrings); + try (InputStream uberSlkStream = dataSource.getResourceAsStream("Splats\\UberSplatData.slk")) { + this.uberSplatTable.readSLK(uberSlkStream); + } - final char tileset = w3eFile.getTileset(); - final Element waterInfo = this.waterTable.get(tileset + "Sha"); - if (waterInfo != null) { - this.waterHeightOffset = waterInfo.getFieldFloatValue("height"); - this.waterTextureCount = waterInfo.getFieldValue("numTex"); - this.waterIncreasePerFrame = waterInfo.getFieldValue("texRate") / 60f; - } - else { - this.waterHeightOffset = 0; - this.waterTextureCount = 0; - this.waterIncreasePerFrame = 0; - } + final char tileset = w3eFile.getTileset(); + final Element waterInfo = this.waterTable.get(tileset + "Sha"); + if (waterInfo != null) { + this.waterHeightOffset = waterInfo.getFieldFloatValue("height"); + this.waterTextureCount = waterInfo.getFieldValue("numTex"); + this.waterIncreasePerFrame = waterInfo.getFieldValue("texRate") / 60f; + } else { + this.waterHeightOffset = 0; + this.waterTextureCount = 0; + this.waterIncreasePerFrame = 0; + } - loadWaterColor(this.minShallowColor, "Smin", waterInfo); - loadWaterColor(this.maxShallowColor, "Smax", waterInfo); - loadWaterColor(this.minDeepColor, "Dmin", waterInfo); - loadWaterColor(this.maxDeepColor, "Dmax", waterInfo); - for (int i = 0; i < 3; i++) { - if (this.minDeepColor[i] > this.maxDeepColor[i]) { - this.maxDeepColor[i] = this.minDeepColor[i]; - } - } + loadWaterColor(this.minShallowColor, "Smin", waterInfo); + loadWaterColor(this.maxShallowColor, "Smax", waterInfo); + loadWaterColor(this.minDeepColor, "Dmin", waterInfo); + loadWaterColor(this.maxDeepColor, "Dmax", waterInfo); + for (int i = 0; i < 3; i++) { + if (this.minDeepColor[i] > this.maxDeepColor[i]) { + this.maxDeepColor[i] = this.minDeepColor[i]; + } + } - // Cliff Meshes + // Cliff Meshes - Map cliffVars = Variations.CLIFF_VARS; - for (final Map.Entry cliffVar : cliffVars.entrySet()) { - final Integer maxVariations = cliffVar.getValue(); - for (int variation = 0; variation <= maxVariations; variation++) { - final String fileName = "Doodads\\Terrain\\Cliffs\\Cliffs" + cliffVar.getKey() + variation + ".mdx"; - this.cliffMeshes.add(new CliffMesh(fileName, dataSource, Gdx.gl30)); - this.pathToCliff.put("Cliffs" + cliffVar.getKey() + variation, this.cliffMeshes.size() - 1); - } - } - cliffVars = Variations.CITY_CLIFF_VARS; - for (final Map.Entry cliffVar : cliffVars.entrySet()) { - final Integer maxVariations = cliffVar.getValue(); - for (int variation = 0; variation <= maxVariations; variation++) { - final String fileName = "Doodads\\Terrain\\CityCliffs\\CityCliffs" + cliffVar.getKey() + variation - + ".mdx"; - this.cliffMeshes.add(new CliffMesh(fileName, dataSource, Gdx.gl30)); - this.pathToCliff.put("CityCliffs" + cliffVar.getKey() + variation, this.cliffMeshes.size() - 1); - } - } + Map cliffVars = Variations.CLIFF_VARS; + for (final Map.Entry cliffVar : cliffVars.entrySet()) { + final Integer maxVariations = cliffVar.getValue(); + for (int variation = 0; variation <= maxVariations; variation++) { + final String fileName = "Doodads\\Terrain\\Cliffs\\Cliffs" + cliffVar.getKey() + variation + ".mdx"; + this.cliffMeshes.add(new CliffMesh(fileName, dataSource, Gdx.gl30)); + this.pathToCliff.put("Cliffs" + cliffVar.getKey() + variation, this.cliffMeshes.size() - 1); + } + } + cliffVars = Variations.CITY_CLIFF_VARS; + for (final Map.Entry cliffVar : cliffVars.entrySet()) { + final Integer maxVariations = cliffVar.getValue(); + for (int variation = 0; variation <= maxVariations; variation++) { + final String fileName = "Doodads\\Terrain\\CityCliffs\\CityCliffs" + cliffVar.getKey() + variation + + ".mdx"; + this.cliffMeshes.add(new CliffMesh(fileName, dataSource, Gdx.gl30)); + this.pathToCliff.put("CityCliffs" + cliffVar.getKey() + variation, this.cliffMeshes.size() - 1); + } + } - // Ground textures - for (final War3ID groundTile : w3eFile.getGroundTiles()) { - final Element terrainTileInfo = this.terrainTable.get(groundTile.asStringValue()); - if (terrainTileInfo == null) { - throw new RuntimeException("No terrain info for: " + groundTile.asStringValue()); - } - final String dir = terrainTileInfo.getField("dir"); - final String file = terrainTileInfo.getField("file"); - this.groundTextures.add(new GroundTexture(dir + "\\" + file + texturesExt, dataSource, Gdx.gl30)); - this.groundTextureToId.put(groundTile.asStringValue(), this.groundTextures.size() - 1); - } + // Ground textures + for (final War3ID groundTile : w3eFile.getGroundTiles()) { + final Element terrainTileInfo = this.terrainTable.get(groundTile.asStringValue()); + if (terrainTileInfo == null) { + throw new RuntimeException("No terrain info for: " + groundTile.asStringValue()); + } + final String dir = terrainTileInfo.getField("dir"); + final String file = terrainTileInfo.getField("file"); + this.groundTextures.add(new GroundTexture(dir + "\\" + file + texturesExt, dataSource, Gdx.gl30)); + this.groundTextureToId.put(groundTile.asStringValue(), this.groundTextures.size() - 1); + } - final Element tilesets = worldEditData.get("TileSets"); + final Element tilesets = worldEditData.get("TileSets"); - this.blightTextureIndex = this.groundTextures.size(); - this.groundTextures.add(new GroundTexture( - tilesets.getField(Character.toString(tileset)).split(",")[1] + texturesExt, dataSource, Gdx.gl30)); + this.blightTextureIndex = this.groundTextures.size(); + this.groundTextures.add(new GroundTexture( + tilesets.getField(Character.toString(tileset)).split(",")[1] + texturesExt, dataSource, Gdx.gl30)); - // Cliff Textures - for (final War3ID cliffTile : w3eFile.getCliffTiles()) { - final Element cliffInfo = this.cliffTable.get(cliffTile.asStringValue()); - final String texDir = cliffInfo.getField("texDir"); - final String texFile = cliffInfo.getField("texFile"); - try (InputStream imageStream = dataSource.getResourceAsStream(texDir + "\\" + texFile + texturesExt)) { - final BufferedImage image; - if (imageStream == null) { - final String tgaPath = texDir + "\\" + texFile + ".tga"; - try (final InputStream tgaStream = dataSource.getResourceAsStream(tgaPath)) { - if (tgaStream != null) { - image = TgaFile.readTGA(tgaPath, tgaStream); - } - else { - throw new IllegalStateException( - "Missing cliff texture: " + texDir + "\\" + texFile + texturesExt); - } - } - } - else { - image = ImageIO.read(imageStream); - if (image == null) { - throw new IllegalStateException( - "Missing cliff texture: " + texDir + "\\" + texFile + texturesExt); - } - } - this.cliffTextures.add(new UnloadedTexture(image.getWidth(), image.getHeight(), - ImageUtils.getTextureBuffer(ImageUtils.forceBufferedImagesRGB(image)), - cliffInfo.getField("cliffModelDir"), cliffInfo.getField("rampModelDir"))); - } - this.cliffTexturesSize = Math.max(this.cliffTexturesSize, - this.cliffTextures.get(this.cliffTextures.size() - 1).width); - this.cliffToGroundTexture.add(this.groundTextureToId.get(cliffInfo.getField("groundTile"))); - } + // Cliff Textures + for (final War3ID cliffTile : w3eFile.getCliffTiles()) { + final Element cliffInfo = this.cliffTable.get(cliffTile.asStringValue()); + final String texDir = cliffInfo.getField("texDir"); + final String texFile = cliffInfo.getField("texFile"); + try (InputStream imageStream = dataSource.getResourceAsStream(texDir + "\\" + texFile + texturesExt)) { + final BufferedImage image; + if (imageStream == null) { + final String tgaPath = texDir + "\\" + texFile + ".tga"; + try (final InputStream tgaStream = dataSource.getResourceAsStream(tgaPath)) { + if (tgaStream != null) { + image = TgaFile.readTGA(tgaPath, tgaStream); + } else { + throw new IllegalStateException( + "Missing cliff texture: " + texDir + "\\" + texFile + texturesExt); + } + } + } else { + image = ImageIO.read(imageStream); + if (image == null) { + throw new IllegalStateException( + "Missing cliff texture: " + texDir + "\\" + texFile + texturesExt); + } + } + this.cliffTextures.add(new UnloadedTexture(image.getWidth(), image.getHeight(), + ImageUtils.getTextureBuffer(ImageUtils.forceBufferedImagesRGB(image)), + cliffInfo.getField("cliffModelDir"), cliffInfo.getField("rampModelDir"))); + } + this.cliffTexturesSize = Math.max(this.cliffTexturesSize, + this.cliffTextures.get(this.cliffTextures.size() - 1).width); + this.cliffToGroundTexture.add(this.groundTextureToId.get(cliffInfo.getField("groundTile"))); + } - updateCliffMeshes(new Rectangle(0, 0, width - 1, height - 1)); + updateCliffMeshes(new Rectangle(0, 0, width - 1, height - 1)); - // prepare GPU data - this.groundHeights = new float[width * height]; - this.groundCornerHeights = new float[width * height]; - this.groundTextureList = new short[(width - 1) * (height - 1) * 4]; - this.waterHeights = new float[width * height]; - this.waterExistsData = new byte[width * height]; + // prepare GPU data + this.groundHeights = new float[width * height]; + this.groundCornerHeights = new float[width * height]; + this.groundTextureList = new short[(width - 1) * (height - 1) * 4]; + this.waterHeights = new float[width * height]; + this.waterExistsData = new byte[width * height]; - updateGroundTextures(new Rectangle(0, 0, width - 1, height - 1)); - for (int i = 0; i < width; i++) { - for (int j = 0; j < height; j++) { - this.groundCornerHeights[(j * width) + i] = this.corners[i][j].computeFinalGroundHeight(); - this.waterExistsData[(j * width) + i] = (byte) this.corners[i][j].getWater(); - this.groundHeights[(j * width) + i] = this.corners[i][j].getGroundHeight(); - this.waterHeights[(j * width) + i] = this.corners[i][j].getWaterHeight(); - } - } + updateGroundTextures(new Rectangle(0, 0, width - 1, height - 1)); + for (int i = 0; i < width; i++) { + for (int j = 0; j < height; j++) { + this.groundCornerHeights[(j * width) + i] = this.corners[i][j].computeFinalGroundHeight(); + this.waterExistsData[(j * width) + i] = (byte) this.corners[i][j].getWater(); + this.groundHeights[(j * width) + i] = this.corners[i][j].getGroundHeight(); + this.waterHeights[(j * width) + i] = this.corners[i][j].getWaterHeight(); + } + } - final GL30 gl = Gdx.gl30; - // Ground - this.groundTextureData = gl.glGenTexture(); - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundTextureData); - gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_RGBA16UI, width - 1, height - 1, 0, GL30.GL_RGBA_INTEGER, - GL30.GL_UNSIGNED_SHORT, RenderMathUtils.wrapShort(this.groundTextureList)); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_NEAREST); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_NEAREST); + final GL30 gl = Gdx.gl30; + // Ground + this.groundTextureData = gl.glGenTexture(); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundTextureData); + gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_RGBA16UI, width - 1, height - 1, 0, GL30.GL_RGBA_INTEGER, + GL30.GL_UNSIGNED_SHORT, RenderMathUtils.wrapShort(this.groundTextureList)); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_NEAREST); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_NEAREST); - this.groundHeight = gl.glGenTexture(); - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundHeight); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_LINEAR); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_LINEAR); + this.groundHeight = gl.glGenTexture(); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundHeight); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_LINEAR); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_LINEAR); - gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_R16F, width, height, 0, GL30.GL_RED, GL30.GL_FLOAT, - RenderMathUtils.wrap(this.groundHeights)); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_S, GL30.GL_CLAMP_TO_EDGE); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_T, GL30.GL_CLAMP_TO_EDGE); + gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_R16F, width, height, 0, GL30.GL_RED, GL30.GL_FLOAT, + RenderMathUtils.wrap(this.groundHeights)); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_S, GL30.GL_CLAMP_TO_EDGE); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_T, GL30.GL_CLAMP_TO_EDGE); - this.groundCornerHeight = gl.glGenTexture(); - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeight); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_NEAREST); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_NEAREST); + this.groundCornerHeight = gl.glGenTexture(); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeight); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_NEAREST); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_NEAREST); - gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_R16F, width, height, 0, GL30.GL_RED, GL30.GL_FLOAT, - RenderMathUtils.wrap(this.groundCornerHeights)); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_S, GL30.GL_CLAMP_TO_EDGE); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_T, GL30.GL_CLAMP_TO_EDGE); + gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_R16F, width, height, 0, GL30.GL_RED, GL30.GL_FLOAT, + RenderMathUtils.wrap(this.groundCornerHeights)); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_S, GL30.GL_CLAMP_TO_EDGE); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_T, GL30.GL_CLAMP_TO_EDGE); - this.groundCornerHeightLinear = gl.glGenTexture(); - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeightLinear); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_LINEAR); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_LINEAR); + this.groundCornerHeightLinear = gl.glGenTexture(); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeightLinear); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_LINEAR); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_LINEAR); - gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_R16F, width, height, 0, GL30.GL_RED, GL30.GL_FLOAT, - RenderMathUtils.wrap(this.groundCornerHeights)); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_S, GL30.GL_CLAMP_TO_EDGE); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_T, GL30.GL_CLAMP_TO_EDGE); + gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_R16F, width, height, 0, GL30.GL_RED, GL30.GL_FLOAT, + RenderMathUtils.wrap(this.groundCornerHeights)); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_S, GL30.GL_CLAMP_TO_EDGE); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_T, GL30.GL_CLAMP_TO_EDGE); - // Cliff - this.cliffTextureArray = gl.glGenTexture(); - gl.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.cliffTextureArray); - gl.glTexImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, GL30.GL_RGBA8, this.cliffTexturesSize, this.cliffTexturesSize, - this.cliffTextures.size(), 0, GL30.GL_RGBA, GL30.GL_UNSIGNED_BYTE, null); - gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_LINEAR_MIPMAP_LINEAR); - gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_BASE_LEVEL, 0); + // Cliff + this.cliffTextureArray = gl.glGenTexture(); + gl.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.cliffTextureArray); + gl.glTexImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, GL30.GL_RGBA8, this.cliffTexturesSize, this.cliffTexturesSize, + this.cliffTextures.size(), 0, GL30.GL_RGBA, GL30.GL_UNSIGNED_BYTE, null); + gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_LINEAR_MIPMAP_LINEAR); + gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_BASE_LEVEL, 0); - int sub = 0; - for (final UnloadedTexture i : this.cliffTextures) { - gl.glTexSubImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, 0, 0, sub, i.width, i.height, 1, GL30.GL_RGBA, - GL30.GL_UNSIGNED_BYTE, i.data); - sub += 1; - } - gl.glGenerateMipmap(GL30.GL_TEXTURE_2D_ARRAY); + int sub = 0; + for (final UnloadedTexture i : this.cliffTextures) { + gl.glTexSubImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, 0, 0, sub, i.width, i.height, 1, GL30.GL_RGBA, + GL30.GL_UNSIGNED_BYTE, i.data); + sub += 1; + } + gl.glGenerateMipmap(GL30.GL_TEXTURE_2D_ARRAY); - // Water - this.waterHeight = gl.glGenTexture(); - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.waterHeight); - gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_R16F, width, height, 0, GL30.GL_RED, GL30.GL_FLOAT, - RenderMathUtils.wrap(this.waterHeights)); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_NEAREST); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_NEAREST); + // Water + this.waterHeight = gl.glGenTexture(); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.waterHeight); + gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_R16F, width, height, 0, GL30.GL_RED, GL30.GL_FLOAT, + RenderMathUtils.wrap(this.waterHeights)); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_NEAREST); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_NEAREST); - this.waterExists = gl.glGenTexture(); - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.waterExists); - gl.glPixelStorei(GL30.GL_UNPACK_ALIGNMENT, 1); - gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_R8, width, height, 0, GL30.GL_RED, GL30.GL_UNSIGNED_BYTE, - RenderMathUtils.wrap(this.waterExistsData)); - gl.glPixelStorei(GL30.GL_UNPACK_ALIGNMENT, 4); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_NEAREST); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_NEAREST); + this.waterExists = gl.glGenTexture(); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.waterExists); + gl.glPixelStorei(GL30.GL_UNPACK_ALIGNMENT, 1); + gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_R8, width, height, 0, GL30.GL_RED, GL30.GL_UNSIGNED_BYTE, + RenderMathUtils.wrap(this.waterExistsData)); + gl.glPixelStorei(GL30.GL_UNPACK_ALIGNMENT, 4); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_NEAREST); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_NEAREST); - // Water textures - this.waterTextureArray = gl.glGenTexture(); - gl.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.waterTextureArray); - gl.glTexImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, GL30.GL_SRGB8_ALPHA8, 128, 128, this.waterTextureCount, 0, - GL30.GL_RGBA, GL30.GL_UNSIGNED_BYTE, null); - gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_WRAP_S, GL30.GL_CLAMP_TO_EDGE); - gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_WRAP_T, GL30.GL_CLAMP_TO_EDGE); - gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_BASE_LEVEL, 0); + // Water textures + this.waterTextureArray = gl.glGenTexture(); + gl.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.waterTextureArray); + gl.glTexImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, GL30.GL_SRGB8_ALPHA8, 128, 128, this.waterTextureCount, 0, + GL30.GL_RGBA, GL30.GL_UNSIGNED_BYTE, null); + gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_WRAP_S, GL30.GL_CLAMP_TO_EDGE); + gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_WRAP_T, GL30.GL_CLAMP_TO_EDGE); + gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_BASE_LEVEL, 0); - final String fileName = waterInfo.getField("texFile"); - for (int i = 0; i < this.waterTextureCount; i++) { + final String fileName = waterInfo.getField("texFile"); + for (int i = 0; i < this.waterTextureCount; i++) { - try (InputStream imageStream = dataSource - .getResourceAsStream(fileName + (i < 10 ? "0" : "") + Integer.toString(i) + texturesExt)) { - final BufferedImage image = ImageIO.read(imageStream); - if ((image.getWidth() != 128) || (image.getHeight() != 128)) { - System.err.println( - "Odd water texture size detected of " + image.getWidth() + " x " + image.getHeight()); - } + try (InputStream imageStream = dataSource + .getResourceAsStream(fileName + (i < 10 ? "0" : "") + Integer.toString(i) + texturesExt)) { + final BufferedImage image = ImageIO.read(imageStream); + if ((image.getWidth() != 128) || (image.getHeight() != 128)) { + System.err.println( + "Odd water texture size detected of " + image.getWidth() + " x " + image.getHeight()); + } - gl.glTexSubImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, 0, 0, i, image.getWidth(), image.getHeight(), 1, - GL30.GL_RGBA, GL30.GL_UNSIGNED_BYTE, ImageUtils.getTextureBuffer(image)); - } - } - gl.glGenerateMipmap(GL30.GL_TEXTURE_2D_ARRAY); + gl.glTexSubImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, 0, 0, i, image.getWidth(), image.getHeight(), 1, + GL30.GL_RGBA, GL30.GL_UNSIGNED_BYTE, ImageUtils.getTextureBuffer(image)); + } + } + gl.glGenerateMipmap(GL30.GL_TEXTURE_2D_ARRAY); - updateGroundHeights(new Rectangle(0, 0, width - 1, height - 1)); + updateGroundHeights(new Rectangle(0, 0, width - 1, height - 1)); - this.groundShader = webGL.createShaderProgram(TerrainShaders.Terrain.vert, TerrainShaders.Terrain.frag); - this.cliffShader = webGL.createShaderProgram(TerrainShaders.Cliffs.vert, TerrainShaders.Cliffs.frag); - this.waterShader = webGL.createShaderProgram(TerrainShaders.Water.vert, TerrainShaders.Water.frag); - this.testShader = webGL.createShaderProgram(TerrainShaders.Test.vert, TerrainShaders.Test.frag); + this.groundShader = webGL.createShaderProgram(TerrainShaders.Terrain.vert, TerrainShaders.Terrain.frag); + this.cliffShader = webGL.createShaderProgram(TerrainShaders.Cliffs.vert, TerrainShaders.Cliffs.frag); + this.waterShader = webGL.createShaderProgram(TerrainShaders.Water.vert, TerrainShaders.Water.frag); + this.testShader = webGL.createShaderProgram(TerrainShaders.Test.vert, TerrainShaders.Test.frag); - this.uberSplatShader = webGL.createShaderProgram(W3xShaders.UberSplat.vert, W3xShaders.UberSplat.frag); + this.uberSplatShader = webGL.createShaderProgram(W3xShaders.UberSplat.vert, W3xShaders.UberSplat.frag); - // TODO collision bodies (?) + // TODO collision bodies (?) - this.centerOffset = w3eFile.getCenterOffset(); - this.uberSplatModels = new ArrayList<>(); - this.mapBounds = w3iFile.getCameraBoundsComplements(); - this.shaderMapBounds = new float[] { (this.mapBounds[0] * 128.0f) + this.centerOffset[0], - (this.mapBounds[2] * 128.0f) + this.centerOffset[1], - ((this.columns - this.mapBounds[1] - 1) * 128.0f) + this.centerOffset[0], - ((this.rows - this.mapBounds[3] - 1) * 128.0f) + this.centerOffset[1] }; - this.shaderMapBoundsRectangle = new Rectangle(this.shaderMapBounds[0], this.shaderMapBounds[1], - this.shaderMapBounds[2] - this.shaderMapBounds[0], this.shaderMapBounds[3] - this.shaderMapBounds[1]); - this.mapSize = w3eFile.getMapSize(); - this.entireMapRectangle = new Rectangle(this.centerOffset[0], this.centerOffset[1], - (this.mapSize[0] * 128f) - 128, (this.mapSize[1] * 128f) - 128); - this.softwareGroundMesh = new SoftwareGroundMesh(this.groundHeights, this.groundCornerHeights, - this.centerOffset, width, height); + this.centerOffset = w3eFile.getCenterOffset(); + this.uberSplatModels = new LinkedHashMap<>(); + this.mapBounds = w3iFile.getCameraBoundsComplements(); + this.shaderMapBounds = new float[]{(this.mapBounds[0] * 128.0f) + this.centerOffset[0], + (this.mapBounds[2] * 128.0f) + this.centerOffset[1], + ((this.columns - this.mapBounds[1] - 1) * 128.0f) + this.centerOffset[0], + ((this.rows - this.mapBounds[3] - 1) * 128.0f) + this.centerOffset[1]}; + this.shaderMapBoundsRectangle = new Rectangle(this.shaderMapBounds[0], this.shaderMapBounds[1], + this.shaderMapBounds[2] - this.shaderMapBounds[0], this.shaderMapBounds[3] - this.shaderMapBounds[1]); + this.mapSize = w3eFile.getMapSize(); + this.entireMapRectangle = new Rectangle(this.centerOffset[0], this.centerOffset[1], + (this.mapSize[0] * 128f) - 128, (this.mapSize[1] * 128f) - 128); + this.softwareGroundMesh = new SoftwareGroundMesh(this.groundHeights, this.groundCornerHeights, + this.centerOffset, width, height); - this.testArrayBuffer = gl.glGenBuffer(); - gl.glBindBuffer(GL30.GL_ARRAY_BUFFER, this.testArrayBuffer); - gl.glBufferData(GL30.GL_ARRAY_BUFFER, this.softwareGroundMesh.vertices.length, - RenderMathUtils.wrap(this.softwareGroundMesh.vertices), GL30.GL_STATIC_DRAW); + this.testArrayBuffer = gl.glGenBuffer(); + gl.glBindBuffer(GL30.GL_ARRAY_BUFFER, this.testArrayBuffer); + gl.glBufferData(GL30.GL_ARRAY_BUFFER, this.softwareGroundMesh.vertices.length, + RenderMathUtils.wrap(this.softwareGroundMesh.vertices), GL30.GL_STATIC_DRAW); - this.testElementBuffer = gl.glGenBuffer(); + this.testElementBuffer = gl.glGenBuffer(); // gl.glBindBuffer(GL30.GL_ELEMENT_ARRAY_BUFFER, this.testElementBuffer); // gl.glBufferData(GL30.GL_ELEMENT_ARRAY_BUFFER, this.softwareGroundMesh.indices.length, // RenderMathUtils.wrap(this.softwareGroundMesh.indices), GL30.GL_STATIC_DRAW); - this.waveBuilder = new WaveBuilder(this.mapSize, this.waterTable, viewer, this.corners, this.centerOffset, - this.waterHeightOffset, w3eFile, w3iFile); - this.pathingGrid = new PathingGrid(terrainPathing, this.centerOffset); - } - - public void createWaves() { - this.waveBuilder.createWaves(this); - } - - private void updateGroundHeights(final Rectangle area) { - for (int j = (int) area.y; j < (area.y + area.height); j++) { - for (int i = (int) area.x; i < (area.x + area.width); i++) { - this.groundHeights[(j * this.columns) + i] = this.corners[i][j].getGroundHeight(); - - float rampHeight = 0f; - // Check if in one of the configurations the bottom_left is a ramp - XLoop: for (int xOffset = -1; xOffset <= 0; xOffset++) { - for (int yOffset = -1; yOffset <= 0; yOffset++) { - if (((i + xOffset) >= 0) && ((i + xOffset) < (this.columns - 1)) && ((j + yOffset) >= 0) - && ((j + yOffset) < (this.rows - 1))) { - final RenderCorner bottomLeft = this.corners[i + xOffset][j + yOffset]; - final RenderCorner bottomRight = this.corners[i + 1 + xOffset][j + yOffset]; - final RenderCorner topLeft = this.corners[i + xOffset][j + 1 + yOffset]; - final RenderCorner topRight = this.corners[i + 1 + xOffset][j + 1 + yOffset]; - - final int base = Math.min( - Math.min(bottomLeft.getLayerHeight(), bottomRight.getLayerHeight()), - Math.min(topLeft.getLayerHeight(), topRight.getLayerHeight())); - if (this.corners[i][j].getLayerHeight() != base) { - continue; - } - - if (isCornerRampEntrance(i + xOffset, j + yOffset)) { - rampHeight = 0.5f; - break XLoop; - } - } - } - } - - final RenderCorner corner = this.corners[i][j]; - final float newGroundCornerHeight = corner.computeFinalGroundHeight() + rampHeight; - this.groundCornerHeights[(j * this.columns) + i] = newGroundCornerHeight; - corner.depth = (corner.getWater() != 0) - ? (this.waterHeightOffset + corner.getWaterHeight()) - newGroundCornerHeight - : 0; - } - } - updateGroundHeights(); - updateCornerHeights(); - } - - private void updateGroundHeights() { - Gdx.gl30.glBindTexture(GL30.GL_TEXTURE_2D, this.groundHeight); - Gdx.gl30.glTexSubImage2D(GL30.GL_TEXTURE_2D, 0, 0, 0, this.columns, this.rows, GL30.GL_RED, GL30.GL_FLOAT, - RenderMathUtils.wrap(this.groundHeights)); - } - - private void updateCornerHeights() { - final FloatBuffer groundCornerHeightsWrapped = RenderMathUtils.wrap(this.groundCornerHeights); - Gdx.gl30.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeight); - Gdx.gl30.glTexSubImage2D(GL30.GL_TEXTURE_2D, 0, 0, 0, this.columns, this.rows, GL30.GL_RED, GL30.GL_FLOAT, - groundCornerHeightsWrapped); - Gdx.gl30.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeightLinear); - Gdx.gl30.glTexSubImage2D(GL30.GL_TEXTURE_2D, 0, 0, 0, this.columns, this.rows, GL30.GL_RED, GL30.GL_FLOAT, - groundCornerHeightsWrapped); - } - - /** - * calculateRamps() is copied from Riv whereas a lot of the rest of the terrain - * was copied from HiveWE - */ - private void calculateRamps() { - final int columns = this.mapSize[0]; - final int rows = this.mapSize[1]; - - final String[] ramps = { "AAHL", "AALH", "ABHL", "AHLA", "ALHA", "ALHB", "BALH", "BHLA", "HAAL", "HBAL", "HLAA", - "HLAB", "LAAH", "LABH", "LHAA", "LHBA" }; - - // Adjust terrain height inside ramps (set rampAdjust) - for (int y = 1; y < (rows - 1); ++y) { - for (int x = 1; x < (columns - 1); ++x) { - final RenderCorner o = this.corners[x][y]; - if (!o.isRamp()) { - continue; - } - final RenderCorner a = this.corners[x - 1][y - 1]; - final RenderCorner b = this.corners[x - 1][y]; - final RenderCorner c = this.corners[x - 1][y + 1]; - final RenderCorner d = this.corners[x][y + 1]; - final RenderCorner e = this.corners[x + 1][y + 1]; - final RenderCorner f = this.corners[x + 1][y]; - final RenderCorner g = this.corners[x + 1][y - 1]; - final RenderCorner h = this.corners[x][y - 1]; - final int base = o.getLayerHeight(); - if ((b.isRamp() && f.isRamp()) || (d.isRamp() && h.isRamp())) { - float adjust = 0; - if (b.isRamp() && f.isRamp()) { - adjust = Math.max(adjust, ((b.getLayerHeight() + f.getLayerHeight()) / 2) - base); - } - if (d.isRamp() && h.isRamp()) { - adjust = Math.max(adjust, ((d.getLayerHeight() + h.getLayerHeight()) / 2) - base); - } - if (a.isRamp() && e.isRamp()) { - adjust = Math.max(adjust, (((a.getLayerHeight() + e.getLayerHeight()) / 2) - base) / 2); - } - if (c.isRamp() && g.isRamp()) { - adjust = Math.max(adjust, (((c.getLayerHeight() + g.getLayerHeight()) / 2) - base) / 2); - } - o.rampAdjust = adjust; - } - } - } - } - - /// TODO clean - /// Function is a bit of a mess - /// Updates the cliff and ramp meshes for an area - private void updateCliffMeshes(final Rectangle area) throws IOException { - // Remove all existing cliff meshes in area - for (int i = this.cliffs.size(); i-- > 0;) { - final IVec3 pos = this.cliffs.get(i); - if (area.contains(pos.x, pos.y)) { - this.cliffs.remove(i); - } - } - - for (int i = (int) area.getX(); i < (int) (area.getX() + area.getWidth()); i++) { - for (int j = (int) area.getY(); j < (int) (area.getY() + area.getHeight()); j++) { - this.corners[i][j].romp = false; - } - } - - final Rectangle adjusted = new Rectangle(area.x - 2, area.y - 2, area.width + 4, area.height + 4); - final Rectangle rampArea = new Rectangle(); - Intersector.intersectRectangles(new Rectangle(0, 0, this.columns, this.rows), adjusted, rampArea); - - // Add new cliff meshes - final int xLimit = (int) ((rampArea.getX() + rampArea.getWidth()) - 1); - for (int i = (int) rampArea.getX(); i < xLimit; i++) { - final int yLimit = (int) ((rampArea.getY() + rampArea.getHeight()) - 1); - for (int j = (int) rampArea.getY(); j < yLimit; j++) { - final RenderCorner bottomLeft = this.corners[i][j]; - final RenderCorner bottomRight = this.corners[i + 1][j]; - final RenderCorner topLeft = this.corners[i][j + 1]; - final RenderCorner topRight = this.corners[i + 1][j + 1]; - - if (bottomLeft.cliff && !bottomLeft.hideCliff) { - final int base = Math.min(Math.min(bottomLeft.getLayerHeight(), bottomRight.getLayerHeight()), - Math.min(topLeft.getLayerHeight(), topRight.getLayerHeight())); - - final boolean facingDown = (topLeft.getLayerHeight() >= bottomLeft.getLayerHeight()) - && (topRight.getLayerHeight() >= bottomRight.getLayerHeight()); - final boolean facingLeft = (bottomRight.getLayerHeight() >= bottomLeft.getLayerHeight()) - && (topRight.getLayerHeight() >= topLeft.getLayerHeight()); - - int bottomLeftCliffTex = bottomLeft.getCliffTexture(); - if (bottomLeftCliffTex == 15) { - bottomLeftCliffTex -= 14; - } - if (!(facingDown && (j == 0)) && !(!facingDown && (j >= (this.rows - 2))) - && !(facingLeft && (i == 0)) && !(!facingLeft && (i >= (this.columns - 2)))) { - final boolean br = ((bottomLeft.getRamp() != 0) != (bottomRight.getRamp() != 0)) - && ((topLeft.getRamp() != 0) != (topRight.getRamp() != 0)) - && !this.corners[i + (bottomRight.getRamp() != 0 ? 1 : 0)][j - + (facingDown ? -1 : 1)].cliff; - - final boolean bo = ((bottomLeft.getRamp() != 0) != (topLeft.getRamp() != 0)) - && ((bottomRight.getRamp() != 0) != (topRight.getRamp() != 0)) - && !this.corners[i + (facingLeft ? -1 : 1)][j + (topLeft.getRamp() != 0 ? 1 : 0)].cliff; - - if (br || bo) { - String fileName = "" + (char) ((bottomLeft.getRamp() != 0 ? 'L' : 'A') - + ((bottomLeft.getLayerHeight() - base) * (bottomLeft.getRamp() != 0 ? -4 : 1))) - + (char) ((topLeft.getRamp() != 0 ? 'L' : 'A') - + ((topLeft.getLayerHeight() - base) * (topLeft.getRamp() != 0 ? -4 : 1))) - + (char) ((topRight.getRamp() != 0 ? 'L' : 'A') - + ((topRight.getLayerHeight() - base) * (topRight.getRamp() != 0 ? -4 : 1))) - + (char) ((bottomRight.getRamp() != 0 ? 'L' : 'A') - + ((bottomRight.getLayerHeight() - base) - * (bottomRight.getRamp() != 0 ? -4 : 1))); - - final String rampModelDir = this.cliffTextures.get(bottomLeftCliffTex).rampModelDir; - fileName = "Doodads\\Terrain\\" + rampModelDir + "\\" + rampModelDir + fileName + "0.mdx"; - - if (this.dataSource.has(fileName)) { - if (!this.pathToCliff.containsKey(fileName)) { - this.cliffMeshes.add(new CliffMesh(fileName, this.dataSource, Gdx.gl30)); - this.pathToCliff.put(fileName, this.cliffMeshes.size() - 1); - } - - for (int ji = this.cliffs.size(); ji-- > 0;) { - final IVec3 pos = this.cliffs.get(ji); - if ((pos.x == (i + ((bo ? 1 : 0) * (facingLeft ? 0 : 1)))) - && (pos.y == (j - ((br ? 1 : 0) * (facingDown ? 1 : 0))))) { - this.cliffs.remove(ji); - break; - } - } - - this.cliffs.add(new IVec3((i + ((bo ? 1 : 0) * (facingLeft ? 0 : 1))), - (j - ((br ? 1 : 0) * (facingDown ? 1 : 0))), this.pathToCliff.get(fileName))); - bottomLeft.romp = true; - - this.corners[i + ((facingLeft ? -1 : 1) * (bo ? 1 : 0))][j - + ((facingDown ? -1 : 1) * (br ? 1 : 0))].romp = true; - - continue; - } - } - } - - if (isCornerRampEntrance(i, j)) { - continue; - } - - // Ramps move 1 right/down in some cases and thus their area is one bigger to - // the top and left. - if (!area.contains(i, j)) { - continue; - } - - // Cliff model path - - String fileName = "" + (char) (('A' + bottomLeft.getLayerHeight()) - base) - + (char) (('A' + topLeft.getLayerHeight()) - base) - + (char) (('A' + topRight.getLayerHeight()) - base) - + (char) (('A' + bottomRight.getLayerHeight()) - base); - - if ("AAAA".equals(fileName)) { - continue; - } - - // Clamp to within max variations - - fileName = this.cliffTextures.get(bottomLeftCliffTex).cliffModelDir + fileName - + Variations.getCliffVariation(this.cliffTextures.get(bottomLeftCliffTex).cliffModelDir, - fileName, bottomLeft.getCliffVariation()); - if (!this.pathToCliff.containsKey(fileName)) { - throw new IllegalArgumentException("No such pathToCliff entry: " + fileName); - } - this.cliffs.add(new IVec3(i, j, this.pathToCliff.get(fileName))); - } - } - } - - } - - public void logRomp(final int x, final int y) { - System.out.println("romp: " + this.corners[x][y].romp); - System.out.println("ramp: " + this.corners[x][y].isRamp()); - System.out.println("cliff: " + this.corners[x][y].cliff); - } - - private void updateGroundTextures(final Rectangle area) { - final Rectangle adjusted = new Rectangle(area.x - 1, area.y - 1, area.width + 2, area.height + 2); - final Rectangle updateArea = new Rectangle(); - Intersector.intersectRectangles(new Rectangle(0, 0, this.columns - 1, this.rows - 1), adjusted, updateArea); - - for (int j = (int) (updateArea.getY()); j <= (int) ((updateArea.getY() + updateArea.getHeight()) - 1); j++) { - for (int i = (int) (updateArea.getX()); i <= (int) ((updateArea.getX() + updateArea.getWidth()) - 1); i++) { - getTextureVariations(i, j, this.groundTextureList, ((j * (this.columns - 1)) + i) * 4); - - if (this.corners[i][j].cliff || this.corners[i][j].romp) { - if (isCornerRampEntrance(i, j)) { - continue; - } - this.groundTextureList[(((j * (this.columns - 1)) + i) * 4) + 3] |= 0b1000000000000000; - } - } - } - - uploadGroundTexture(); - } - - public void removeTerrainCell(final int i, final int j) { - this.groundTextureList[(((j * (this.columns - 1)) + i) * 4) + 3] |= 0b1000000000000000; - this.corners[i][j].hideCliff = true; - uploadGroundTexture(); - try { - updateCliffMeshes(new Rectangle(i - 1, j - 1, 1, 1)); // TODO does this work? - } - catch (final IOException e) { - throw new RuntimeException(e); - } - } - - public void removeTerrainCellWithoutFlush(final int i, final int j) { - this.groundTextureList[(((j * (this.columns - 1)) + i) * 4) + 3] |= 0b1000000000000000; - this.corners[i][j].hideCliff = true; - } - - public void flushRemovedTerrainCells() { - uploadGroundTexture(); - try { - updateCliffMeshes(new Rectangle(0, 0, this.columns - 1, this.rows - 1)); - } - catch (final IOException e) { - throw new RuntimeException(e); - } - } - - private void uploadGroundTexture() { - if (this.groundTextureData != -1) { - Gdx.gl30.glBindTexture(GL30.GL_TEXTURE_2D, this.groundTextureData); - Gdx.gl30.glTexSubImage2D(GL30.GL_TEXTURE_2D, 0, 0, 0, this.columns - 1, this.rows - 1, GL30.GL_RGBA_INTEGER, - GL30.GL_UNSIGNED_SHORT, RenderMathUtils.wrapShort(this.groundTextureList)); - } - } - - /// The 4 ground textures of the tilepoint. First 5 bits are which texture array - /// to use and the next 5 bits are which subtexture to use - private void getTextureVariations(final int x, final int y, final short[] out, final int outStartOffset) { - final int bottomLeft = realTileTexture(x, y); - final int bottomRight = realTileTexture(x + 1, y); - final int topLeft = realTileTexture(x, y + 1); - final int topRight = realTileTexture(x + 1, y + 1); - - final TreeSet set = new TreeSet<>(); - set.add(bottomLeft); - set.add(bottomRight); - set.add(topLeft); - set.add(topRight); - Arrays.fill(out, outStartOffset, outStartOffset + 4, (short) 17); - int component = outStartOffset + 1; - - final Iterator iterator = set.iterator(); - iterator.hasNext(); - final short firstValue = iterator.next().shortValue(); - out[outStartOffset] = (short) (firstValue - + (getVariation(firstValue, this.corners[x][y].getGroundVariation()) << 5)); - - int index; - while (iterator.hasNext()) { - index = 0; - final int texture = iterator.next().intValue(); - index |= (bottomRight == texture ? 1 : 0) << 0; - index |= (bottomLeft == texture ? 1 : 0) << 1; - index |= (topRight == texture ? 1 : 0) << 2; - index |= (topLeft == texture ? 1 : 0) << 3; - - out[component++] = (short) (texture + (index << 5)); - } - } - - private int realTileTexture(final int x, final int y) { - ILoop: for (int i = -1; i < 1; i++) { - for (int j = -1; j < 1; j++) { - if (((x + i) >= 0) && ((x + i) < this.columns) && ((y + j) >= 0) && ((y + j) < this.rows)) { - if (this.corners[x + i][y + j].cliff) { - if (((x + i) < (this.columns - 1)) && ((y + j) < (this.rows - 1))) { - final RenderCorner bottomLeft = this.corners[x + i][y + j]; - final RenderCorner bottomRight = this.corners[x + i + 1][y + j]; - final RenderCorner topLeft = this.corners[x + i][y + j + 1]; - final RenderCorner topRight = this.corners[x + i + 1][y + j + 1]; - - if ((bottomLeft.getRamp() != 0) && (topLeft.getRamp() != 0) && (bottomRight.getRamp() != 0) - && (topRight.getRamp() != 0) && (!bottomLeft.romp) && (!bottomRight.romp) - && (!topLeft.romp) && (!topRight.romp)) { - break ILoop; - } - } - } - if (this.corners[x + i][y + j].romp || this.corners[x + i][y + j].cliff) { - int texture = this.corners[x + i][y + j].getCliffTexture(); - // Number 15 seems to be something - if (texture == 15) { - texture -= 14; - } - - return this.cliffToGroundTexture.get(texture); - } - } - } - } - - if (this.corners[x][y].getBlight() != 0) { - return this.blightTextureIndex; - } - - return this.corners[x][y].getGroundTexture(); - } - - private boolean isCornerRampEntrance(final int x, final int y) { - if ((x == this.columns) || (y == this.rows)) { - return false; - } - - final RenderCorner bottomLeft = this.corners[x][y]; - final RenderCorner bottomRight = this.corners[x + 1][y]; - final RenderCorner topLeft = this.corners[x][y + 1]; - final RenderCorner topRight = this.corners[x + 1][y + 1]; - - return (bottomLeft.getRamp() != 0) && (topLeft.getRamp() != 0) && (bottomRight.getRamp() != 0) - && (topRight.getRamp() != 0) && !((bottomLeft.getLayerHeight() == topRight.getLayerHeight()) - && (topLeft.getLayerHeight() == bottomRight.getLayerHeight())); - } - - private static void loadWaterColor(final float[] out, final String prefix, final Element waterInfo) { - for (int i = 0; i < colorTags.length; i++) { - final String colorTag = colorTags[i]; - out[i] = waterInfo == null ? 0.0f : waterInfo.getFieldFloatValue(prefix + "_" + colorTag) / 255f; - } - } - - public short getVariation(final int groundTexture, final int variation) { - final GroundTexture texture = this.groundTextures.get(groundTexture); - - // Extended ? - if (texture.extended) { - if (variation <= 15) { - return (short) (16 + variation); - } - else if (variation == 16) { - return 15; - } - else { - return 0; - } - } - else { - if (variation == 0) { - return 0; - } - else { - return 15; - } - } - } - - public void update() { - this.waterIndex += this.waterIncreasePerFrame; - - if (this.waterIndex >= this.waterTextureCount) { - this.waterIndex = 0; - } - } - - public void renderGround(final DynamicShadowManager dynamicShadowManager) { - // Render tiles - - this.webGL.useShaderProgram(this.groundShader); - - final GL30 gl = Gdx.gl30; - gl.glEnable(GL20.GL_CULL_FACE); - gl.glDisable(GL30.GL_BLEND); - gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA); - gl.glEnable(GL20.GL_DEPTH_TEST); - gl.glDepthMask(true); - - gl.glUniformMatrix4fv(this.groundShader.getUniformLocation("MVP"), 1, false, - this.camera.viewProjectionMatrix.val, 0); - gl.glUniform1i(this.groundShader.getUniformLocation("show_pathing_map"), this.viewer.renderPathing); - gl.glUniform1i(this.groundShader.getUniformLocation("show_lighting"), this.viewer.renderLighting); - gl.glUniform1i(this.groundShader.getUniformLocation("height_texture"), 0); - gl.glUniform1i(this.groundShader.getUniformLocation("height_cliff_texture"), 1); - gl.glUniform1i(this.groundShader.getUniformLocation("terrain_texture_list"), 2); - gl.glUniform1i(this.groundShader.getUniformLocation("shadowMap"), 20); - gl.glUniform1f(this.groundShader.getUniformLocation("centerOffsetX"), this.centerOffset[0]); - gl.glUniform1f(this.groundShader.getUniformLocation("centerOffsetY"), this.centerOffset[1]); - - final W3xSceneLightManager lightManager = (W3xSceneLightManager) this.viewer.worldScene.getLightManager(); - final DataTexture unitLightsTexture = lightManager.getTerrainLightsTexture(); - - unitLightsTexture.bind(21); - gl.glUniform1i(this.groundShader.getUniformLocation("lightTexture"), 21); - gl.glUniform1f(this.groundShader.getUniformLocation("lightCount"), lightManager.getTerrainLightCount()); - gl.glUniform1f(this.groundShader.getUniformLocation("lightTextureHeight"), unitLightsTexture.getHeight()); - - gl.glUniformMatrix4fv(this.groundShader.getUniformLocation("DepthBiasMVP"), 1, false, - dynamicShadowManager.getDepthBiasMVP().val, 0); - - gl.glActiveTexture(GL30.GL_TEXTURE0); - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundHeight); - - gl.glActiveTexture(GL30.GL_TEXTURE1); - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeight); - - gl.glActiveTexture(GL30.GL_TEXTURE2); - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundTextureData); - - for (int i = 0; i < this.groundTextures.size(); i++) { - gl.glActiveTexture(GL30.GL_TEXTURE3 + i); - gl.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.groundTextures.get(i).id); - } + this.waveBuilder = new WaveBuilder(this.mapSize, this.waterTable, viewer, this.corners, this.centerOffset, + this.waterHeightOffset, w3eFile, w3iFile); + this.pathingGrid = new PathingGrid(terrainPathing, this.centerOffset); + } + + public void createWaves() { + this.waveBuilder.createWaves(this); + } + + private void updateGroundHeights(final Rectangle area) { + for (int j = (int) area.y; j < (area.y + area.height); j++) { + for (int i = (int) area.x; i < (area.x + area.width); i++) { + this.groundHeights[(j * this.columns) + i] = this.corners[i][j].getGroundHeight(); + + float rampHeight = 0f; + // Check if in one of the configurations the bottom_left is a ramp + XLoop: + for (int xOffset = -1; xOffset <= 0; xOffset++) { + for (int yOffset = -1; yOffset <= 0; yOffset++) { + if (((i + xOffset) >= 0) && ((i + xOffset) < (this.columns - 1)) && ((j + yOffset) >= 0) + && ((j + yOffset) < (this.rows - 1))) { + final RenderCorner bottomLeft = this.corners[i + xOffset][j + yOffset]; + final RenderCorner bottomRight = this.corners[i + 1 + xOffset][j + yOffset]; + final RenderCorner topLeft = this.corners[i + xOffset][j + 1 + yOffset]; + final RenderCorner topRight = this.corners[i + 1 + xOffset][j + 1 + yOffset]; + + final int base = Math.min( + Math.min(bottomLeft.getLayerHeight(), bottomRight.getLayerHeight()), + Math.min(topLeft.getLayerHeight(), topRight.getLayerHeight())); + if (this.corners[i][j].getLayerHeight() != base) { + continue; + } + + if (isCornerRampEntrance(i + xOffset, j + yOffset)) { + rampHeight = 0.5f; + break XLoop; + } + } + } + } + + final RenderCorner corner = this.corners[i][j]; + final float newGroundCornerHeight = corner.computeFinalGroundHeight() + rampHeight; + this.groundCornerHeights[(j * this.columns) + i] = newGroundCornerHeight; + corner.depth = (corner.getWater() != 0) + ? (this.waterHeightOffset + corner.getWaterHeight()) - newGroundCornerHeight + : 0; + } + } + updateGroundHeights(); + updateCornerHeights(); + } + + private void updateGroundHeights() { + Gdx.gl30.glBindTexture(GL30.GL_TEXTURE_2D, this.groundHeight); + Gdx.gl30.glTexSubImage2D(GL30.GL_TEXTURE_2D, 0, 0, 0, this.columns, this.rows, GL30.GL_RED, GL30.GL_FLOAT, + RenderMathUtils.wrap(this.groundHeights)); + } + + private void updateCornerHeights() { + final FloatBuffer groundCornerHeightsWrapped = RenderMathUtils.wrap(this.groundCornerHeights); + Gdx.gl30.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeight); + Gdx.gl30.glTexSubImage2D(GL30.GL_TEXTURE_2D, 0, 0, 0, this.columns, this.rows, GL30.GL_RED, GL30.GL_FLOAT, + groundCornerHeightsWrapped); + Gdx.gl30.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeightLinear); + Gdx.gl30.glTexSubImage2D(GL30.GL_TEXTURE_2D, 0, 0, 0, this.columns, this.rows, GL30.GL_RED, GL30.GL_FLOAT, + groundCornerHeightsWrapped); + } + + /** + * calculateRamps() is copied from Riv whereas a lot of the rest of the terrain + * was copied from HiveWE + */ + private void calculateRamps() { + final int columns = this.mapSize[0]; + final int rows = this.mapSize[1]; + + final String[] ramps = {"AAHL", "AALH", "ABHL", "AHLA", "ALHA", "ALHB", "BALH", "BHLA", "HAAL", "HBAL", "HLAA", + "HLAB", "LAAH", "LABH", "LHAA", "LHBA"}; + + // Adjust terrain height inside ramps (set rampAdjust) + for (int y = 1; y < (rows - 1); ++y) { + for (int x = 1; x < (columns - 1); ++x) { + final RenderCorner o = this.corners[x][y]; + if (!o.isRamp()) { + continue; + } + final RenderCorner a = this.corners[x - 1][y - 1]; + final RenderCorner b = this.corners[x - 1][y]; + final RenderCorner c = this.corners[x - 1][y + 1]; + final RenderCorner d = this.corners[x][y + 1]; + final RenderCorner e = this.corners[x + 1][y + 1]; + final RenderCorner f = this.corners[x + 1][y]; + final RenderCorner g = this.corners[x + 1][y - 1]; + final RenderCorner h = this.corners[x][y - 1]; + final int base = o.getLayerHeight(); + if ((b.isRamp() && f.isRamp()) || (d.isRamp() && h.isRamp())) { + float adjust = 0; + if (b.isRamp() && f.isRamp()) { + adjust = Math.max(adjust, ((b.getLayerHeight() + f.getLayerHeight()) / 2) - base); + } + if (d.isRamp() && h.isRamp()) { + adjust = Math.max(adjust, ((d.getLayerHeight() + h.getLayerHeight()) / 2) - base); + } + if (a.isRamp() && e.isRamp()) { + adjust = Math.max(adjust, (((a.getLayerHeight() + e.getLayerHeight()) / 2) - base) / 2); + } + if (c.isRamp() && g.isRamp()) { + adjust = Math.max(adjust, (((c.getLayerHeight() + g.getLayerHeight()) / 2) - base) / 2); + } + o.rampAdjust = adjust; + } + } + } + } + + /// TODO clean + /// Function is a bit of a mess + /// Updates the cliff and ramp meshes for an area + private void updateCliffMeshes(final Rectangle area) throws IOException { + // Remove all existing cliff meshes in area + for (int i = this.cliffs.size(); i-- > 0; ) { + final IVec3 pos = this.cliffs.get(i); + if (area.contains(pos.x, pos.y)) { + this.cliffs.remove(i); + } + } + + for (int i = (int) area.getX(); i < (int) (area.getX() + area.getWidth()); i++) { + for (int j = (int) area.getY(); j < (int) (area.getY() + area.getHeight()); j++) { + this.corners[i][j].romp = false; + } + } + + final Rectangle adjusted = new Rectangle(area.x - 2, area.y - 2, area.width + 4, area.height + 4); + final Rectangle rampArea = new Rectangle(); + Intersector.intersectRectangles(new Rectangle(0, 0, this.columns, this.rows), adjusted, rampArea); + + // Add new cliff meshes + final int xLimit = (int) ((rampArea.getX() + rampArea.getWidth()) - 1); + for (int i = (int) rampArea.getX(); i < xLimit; i++) { + final int yLimit = (int) ((rampArea.getY() + rampArea.getHeight()) - 1); + for (int j = (int) rampArea.getY(); j < yLimit; j++) { + final RenderCorner bottomLeft = this.corners[i][j]; + final RenderCorner bottomRight = this.corners[i + 1][j]; + final RenderCorner topLeft = this.corners[i][j + 1]; + final RenderCorner topRight = this.corners[i + 1][j + 1]; + + if (bottomLeft.cliff && !bottomLeft.hideCliff) { + final int base = Math.min(Math.min(bottomLeft.getLayerHeight(), bottomRight.getLayerHeight()), + Math.min(topLeft.getLayerHeight(), topRight.getLayerHeight())); + + final boolean facingDown = (topLeft.getLayerHeight() >= bottomLeft.getLayerHeight()) + && (topRight.getLayerHeight() >= bottomRight.getLayerHeight()); + final boolean facingLeft = (bottomRight.getLayerHeight() >= bottomLeft.getLayerHeight()) + && (topRight.getLayerHeight() >= topLeft.getLayerHeight()); + + int bottomLeftCliffTex = bottomLeft.getCliffTexture(); + if (bottomLeftCliffTex == 15) { + bottomLeftCliffTex -= 14; + } + if (!(facingDown && (j == 0)) && !(!facingDown && (j >= (this.rows - 2))) + && !(facingLeft && (i == 0)) && !(!facingLeft && (i >= (this.columns - 2)))) { + final boolean br = ((bottomLeft.getRamp() != 0) != (bottomRight.getRamp() != 0)) + && ((topLeft.getRamp() != 0) != (topRight.getRamp() != 0)) + && !this.corners[i + (bottomRight.getRamp() != 0 ? 1 : 0)][j + + (facingDown ? -1 : 1)].cliff; + + final boolean bo = ((bottomLeft.getRamp() != 0) != (topLeft.getRamp() != 0)) + && ((bottomRight.getRamp() != 0) != (topRight.getRamp() != 0)) + && !this.corners[i + (facingLeft ? -1 : 1)][j + (topLeft.getRamp() != 0 ? 1 : 0)].cliff; + + if (br || bo) { + String fileName = "" + (char) ((bottomLeft.getRamp() != 0 ? 'L' : 'A') + + ((bottomLeft.getLayerHeight() - base) * (bottomLeft.getRamp() != 0 ? -4 : 1))) + + (char) ((topLeft.getRamp() != 0 ? 'L' : 'A') + + ((topLeft.getLayerHeight() - base) * (topLeft.getRamp() != 0 ? -4 : 1))) + + (char) ((topRight.getRamp() != 0 ? 'L' : 'A') + + ((topRight.getLayerHeight() - base) * (topRight.getRamp() != 0 ? -4 : 1))) + + (char) ((bottomRight.getRamp() != 0 ? 'L' : 'A') + + ((bottomRight.getLayerHeight() - base) + * (bottomRight.getRamp() != 0 ? -4 : 1))); + + final String rampModelDir = this.cliffTextures.get(bottomLeftCliffTex).rampModelDir; + fileName = "Doodads\\Terrain\\" + rampModelDir + "\\" + rampModelDir + fileName + "0.mdx"; + + if (this.dataSource.has(fileName)) { + if (!this.pathToCliff.containsKey(fileName)) { + this.cliffMeshes.add(new CliffMesh(fileName, this.dataSource, Gdx.gl30)); + this.pathToCliff.put(fileName, this.cliffMeshes.size() - 1); + } + + for (int ji = this.cliffs.size(); ji-- > 0; ) { + final IVec3 pos = this.cliffs.get(ji); + if ((pos.x == (i + ((bo ? 1 : 0) * (facingLeft ? 0 : 1)))) + && (pos.y == (j - ((br ? 1 : 0) * (facingDown ? 1 : 0))))) { + this.cliffs.remove(ji); + break; + } + } + + this.cliffs.add(new IVec3((i + ((bo ? 1 : 0) * (facingLeft ? 0 : 1))), + (j - ((br ? 1 : 0) * (facingDown ? 1 : 0))), this.pathToCliff.get(fileName))); + bottomLeft.romp = true; + + this.corners[i + ((facingLeft ? -1 : 1) * (bo ? 1 : 0))][j + + ((facingDown ? -1 : 1) * (br ? 1 : 0))].romp = true; + + continue; + } + } + } + + if (isCornerRampEntrance(i, j)) { + continue; + } + + // Ramps move 1 right/down in some cases and thus their area is one bigger to + // the top and left. + if (!area.contains(i, j)) { + continue; + } + + // Cliff model path + + String fileName = "" + (char) (('A' + bottomLeft.getLayerHeight()) - base) + + (char) (('A' + topLeft.getLayerHeight()) - base) + + (char) (('A' + topRight.getLayerHeight()) - base) + + (char) (('A' + bottomRight.getLayerHeight()) - base); + + if ("AAAA".equals(fileName)) { + continue; + } + + // Clamp to within max variations + + fileName = this.cliffTextures.get(bottomLeftCliffTex).cliffModelDir + fileName + + Variations.getCliffVariation(this.cliffTextures.get(bottomLeftCliffTex).cliffModelDir, + fileName, bottomLeft.getCliffVariation()); + if (!this.pathToCliff.containsKey(fileName)) { + throw new IllegalArgumentException("No such pathToCliff entry: " + fileName); + } + this.cliffs.add(new IVec3(i, j, this.pathToCliff.get(fileName))); + } + } + } + + } + + public void logRomp(final int x, final int y) { + System.out.println("romp: " + this.corners[x][y].romp); + System.out.println("ramp: " + this.corners[x][y].isRamp()); + System.out.println("cliff: " + this.corners[x][y].cliff); + } + + private void updateGroundTextures(final Rectangle area) { + final Rectangle adjusted = new Rectangle(area.x - 1, area.y - 1, area.width + 2, area.height + 2); + final Rectangle updateArea = new Rectangle(); + Intersector.intersectRectangles(new Rectangle(0, 0, this.columns - 1, this.rows - 1), adjusted, updateArea); + + for (int j = (int) (updateArea.getY()); j <= (int) ((updateArea.getY() + updateArea.getHeight()) - 1); j++) { + for (int i = (int) (updateArea.getX()); i <= (int) ((updateArea.getX() + updateArea.getWidth()) - 1); i++) { + getTextureVariations(i, j, this.groundTextureList, ((j * (this.columns - 1)) + i) * 4); + + if (this.corners[i][j].cliff || this.corners[i][j].romp) { + if (isCornerRampEntrance(i, j)) { + continue; + } + this.groundTextureList[(((j * (this.columns - 1)) + i) * 4) + 3] |= 0b1000000000000000; + } + } + } + + uploadGroundTexture(); + } + + public void removeTerrainCell(final int i, final int j) { + this.groundTextureList[(((j * (this.columns - 1)) + i) * 4) + 3] |= 0b1000000000000000; + this.corners[i][j].hideCliff = true; + uploadGroundTexture(); + try { + updateCliffMeshes(new Rectangle(i - 1, j - 1, 1, 1)); // TODO does this work? + } catch (final IOException e) { + throw new RuntimeException(e); + } + } + + public void removeTerrainCellWithoutFlush(final int i, final int j) { + this.groundTextureList[(((j * (this.columns - 1)) + i) * 4) + 3] |= 0b1000000000000000; + this.corners[i][j].hideCliff = true; + } + + public void flushRemovedTerrainCells() { + uploadGroundTexture(); + try { + updateCliffMeshes(new Rectangle(0, 0, this.columns - 1, this.rows - 1)); + } catch (final IOException e) { + throw new RuntimeException(e); + } + } + + private void uploadGroundTexture() { + if (this.groundTextureData != -1) { + Gdx.gl30.glBindTexture(GL30.GL_TEXTURE_2D, this.groundTextureData); + Gdx.gl30.glTexSubImage2D(GL30.GL_TEXTURE_2D, 0, 0, 0, this.columns - 1, this.rows - 1, GL30.GL_RGBA_INTEGER, + GL30.GL_UNSIGNED_SHORT, RenderMathUtils.wrapShort(this.groundTextureList)); + } + } + + /// The 4 ground textures of the tilepoint. First 5 bits are which texture array + /// to use and the next 5 bits are which subtexture to use + private void getTextureVariations(final int x, final int y, final short[] out, final int outStartOffset) { + final int bottomLeft = realTileTexture(x, y); + final int bottomRight = realTileTexture(x + 1, y); + final int topLeft = realTileTexture(x, y + 1); + final int topRight = realTileTexture(x + 1, y + 1); + + final TreeSet set = new TreeSet<>(); + set.add(bottomLeft); + set.add(bottomRight); + set.add(topLeft); + set.add(topRight); + Arrays.fill(out, outStartOffset, outStartOffset + 4, (short) 17); + int component = outStartOffset + 1; + + final Iterator iterator = set.iterator(); + iterator.hasNext(); + final short firstValue = iterator.next().shortValue(); + out[outStartOffset] = (short) (firstValue + + (getVariation(firstValue, this.corners[x][y].getGroundVariation()) << 5)); + + int index; + while (iterator.hasNext()) { + index = 0; + final int texture = iterator.next().intValue(); + index |= (bottomRight == texture ? 1 : 0) << 0; + index |= (bottomLeft == texture ? 1 : 0) << 1; + index |= (topRight == texture ? 1 : 0) << 2; + index |= (topLeft == texture ? 1 : 0) << 3; + + out[component++] = (short) (texture + (index << 5)); + } + } + + private int realTileTexture(final int x, final int y) { + ILoop: + for (int i = -1; i < 1; i++) { + for (int j = -1; j < 1; j++) { + if (((x + i) >= 0) && ((x + i) < this.columns) && ((y + j) >= 0) && ((y + j) < this.rows)) { + if (this.corners[x + i][y + j].cliff) { + if (((x + i) < (this.columns - 1)) && ((y + j) < (this.rows - 1))) { + final RenderCorner bottomLeft = this.corners[x + i][y + j]; + final RenderCorner bottomRight = this.corners[x + i + 1][y + j]; + final RenderCorner topLeft = this.corners[x + i][y + j + 1]; + final RenderCorner topRight = this.corners[x + i + 1][y + j + 1]; + + if ((bottomLeft.getRamp() != 0) && (topLeft.getRamp() != 0) && (bottomRight.getRamp() != 0) + && (topRight.getRamp() != 0) && (!bottomLeft.romp) && (!bottomRight.romp) + && (!topLeft.romp) && (!topRight.romp)) { + break ILoop; + } + } + } + if (this.corners[x + i][y + j].romp || this.corners[x + i][y + j].cliff) { + int texture = this.corners[x + i][y + j].getCliffTexture(); + // Number 15 seems to be something + if (texture == 15) { + texture -= 14; + } + + return this.cliffToGroundTexture.get(texture); + } + } + } + } + + if (this.corners[x][y].getBlight() != 0) { + return this.blightTextureIndex; + } + + return this.corners[x][y].getGroundTexture(); + } + + private boolean isCornerRampEntrance(final int x, final int y) { + if ((x == this.columns) || (y == this.rows)) { + return false; + } + + final RenderCorner bottomLeft = this.corners[x][y]; + final RenderCorner bottomRight = this.corners[x + 1][y]; + final RenderCorner topLeft = this.corners[x][y + 1]; + final RenderCorner topRight = this.corners[x + 1][y + 1]; + + return (bottomLeft.getRamp() != 0) && (topLeft.getRamp() != 0) && (bottomRight.getRamp() != 0) + && (topRight.getRamp() != 0) && !((bottomLeft.getLayerHeight() == topRight.getLayerHeight()) + && (topLeft.getLayerHeight() == bottomRight.getLayerHeight())); + } + + private static void loadWaterColor(final float[] out, final String prefix, final Element waterInfo) { + for (int i = 0; i < colorTags.length; i++) { + final String colorTag = colorTags[i]; + out[i] = waterInfo == null ? 0.0f : waterInfo.getFieldFloatValue(prefix + "_" + colorTag) / 255f; + } + } + + public short getVariation(final int groundTexture, final int variation) { + final GroundTexture texture = this.groundTextures.get(groundTexture); + + // Extended ? + if (texture.extended) { + if (variation <= 15) { + return (short) (16 + variation); + } else if (variation == 16) { + return 15; + } else { + return 0; + } + } else { + if (variation == 0) { + return 0; + } else { + return 15; + } + } + } + + public void update() { + this.waterIndex += this.waterIncreasePerFrame; + + if (this.waterIndex >= this.waterTextureCount) { + this.waterIndex = 0; + } + } + + public void renderGround(final DynamicShadowManager dynamicShadowManager) { + // Render tiles + + this.webGL.useShaderProgram(this.groundShader); + + final GL30 gl = Gdx.gl30; + gl.glEnable(GL20.GL_CULL_FACE); + gl.glDisable(GL30.GL_BLEND); + gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA); + gl.glEnable(GL20.GL_DEPTH_TEST); + gl.glDepthMask(true); + + gl.glUniformMatrix4fv(this.groundShader.getUniformLocation("MVP"), 1, false, + this.camera.viewProjectionMatrix.val, 0); + gl.glUniform1i(this.groundShader.getUniformLocation("show_pathing_map"), this.viewer.renderPathing); + gl.glUniform1i(this.groundShader.getUniformLocation("show_lighting"), this.viewer.renderLighting); + gl.glUniform1i(this.groundShader.getUniformLocation("height_texture"), 0); + gl.glUniform1i(this.groundShader.getUniformLocation("height_cliff_texture"), 1); + gl.glUniform1i(this.groundShader.getUniformLocation("terrain_texture_list"), 2); + gl.glUniform1i(this.groundShader.getUniformLocation("shadowMap"), 20); + gl.glUniform1f(this.groundShader.getUniformLocation("centerOffsetX"), this.centerOffset[0]); + gl.glUniform1f(this.groundShader.getUniformLocation("centerOffsetY"), this.centerOffset[1]); + + final W3xSceneLightManager lightManager = (W3xSceneLightManager) this.viewer.worldScene.getLightManager(); + final DataTexture unitLightsTexture = lightManager.getTerrainLightsTexture(); + + unitLightsTexture.bind(21); + gl.glUniform1i(this.groundShader.getUniformLocation("lightTexture"), 21); + gl.glUniform1f(this.groundShader.getUniformLocation("lightCount"), lightManager.getTerrainLightCount()); + gl.glUniform1f(this.groundShader.getUniformLocation("lightTextureHeight"), unitLightsTexture.getHeight()); + + gl.glUniformMatrix4fv(this.groundShader.getUniformLocation("DepthBiasMVP"), 1, false, + dynamicShadowManager.getDepthBiasMVP().val, 0); + + gl.glActiveTexture(GL30.GL_TEXTURE0); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundHeight); + + gl.glActiveTexture(GL30.GL_TEXTURE1); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeight); + + gl.glActiveTexture(GL30.GL_TEXTURE2); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundTextureData); + + for (int i = 0; i < this.groundTextures.size(); i++) { + gl.glActiveTexture(GL30.GL_TEXTURE3 + i); + gl.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.groundTextures.get(i).id); + } // gl.glActiveTexture(GL30.GL_TEXTURE20, /*pathingMap.getTextureStatic()*/); // gl.glActiveTexture(GL30.GL_TEXTURE21, /*pathingMap.getTextureDynamic()*/); - gl.glActiveTexture(GL30.GL_TEXTURE20); - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.shadowMap); + gl.glActiveTexture(GL30.GL_TEXTURE20); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.shadowMap); // gl.glEnableVertexAttribArray(0); - gl.glBindBuffer(GL30.GL_ARRAY_BUFFER, Shapes.INSTANCE.vertexBuffer); - gl.glVertexAttribPointer(0, 2, GL30.GL_FLOAT, false, 0, 0); + gl.glBindBuffer(GL30.GL_ARRAY_BUFFER, Shapes.INSTANCE.vertexBuffer); + gl.glVertexAttribPointer(0, 2, GL30.GL_FLOAT, false, 0, 0); - gl.glBindBuffer(GL30.GL_ELEMENT_ARRAY_BUFFER, Shapes.INSTANCE.indexBuffer); - if (WIREFRAME_TERRAIN) { - Extensions.wireframeExtension.glPolygonMode(GL20.GL_FRONT_AND_BACK, Extensions.GL_LINE); - } - gl.glDrawElementsInstanced(GL30.GL_TRIANGLES, Shapes.INSTANCE.quadIndices.length * 3, GL30.GL_UNSIGNED_INT, 0, - (this.columns - 1) * (this.rows - 1)); - if (WIREFRAME_TERRAIN) { - Extensions.wireframeExtension.glPolygonMode(GL20.GL_FRONT_AND_BACK, Extensions.GL_FILL); - } + gl.glBindBuffer(GL30.GL_ELEMENT_ARRAY_BUFFER, Shapes.INSTANCE.indexBuffer); + if (WIREFRAME_TERRAIN) { + Extensions.wireframeExtension.glPolygonMode(GL20.GL_FRONT_AND_BACK, Extensions.GL_LINE); + } + gl.glDrawElementsInstanced(GL30.GL_TRIANGLES, Shapes.INSTANCE.quadIndices.length * 3, GL30.GL_UNSIGNED_INT, 0, + (this.columns - 1) * (this.rows - 1)); + if (WIREFRAME_TERRAIN) { + Extensions.wireframeExtension.glPolygonMode(GL20.GL_FRONT_AND_BACK, Extensions.GL_FILL); + } // gl.glDisableVertexAttribArray(0); - gl.glEnable(GL30.GL_BLEND); + gl.glEnable(GL30.GL_BLEND); - } + } - private GL30 renderGroundIntersectionMesh() { - if (true) { - throw new UnsupportedOperationException("No longer supported"); - } - this.webGL.useShaderProgram(this.testShader); + private GL30 renderGroundIntersectionMesh() { + if (true) { + throw new UnsupportedOperationException("No longer supported"); + } + this.webGL.useShaderProgram(this.testShader); - final GL30 gl = Gdx.gl30; - gl.glDisable(GL30.GL_CULL_FACE); - gl.glDisable(GL30.GL_BLEND); - this.testShader.setUniformMatrix("MVP", this.camera.viewProjectionMatrix); - gl.glBindBuffer(GL30.GL_ARRAY_BUFFER, this.testArrayBuffer); - this.testShader.setVertexAttribute("vPosition", 3, GL30.GL_FLOAT, false, 12, 0); + final GL30 gl = Gdx.gl30; + gl.glDisable(GL30.GL_CULL_FACE); + gl.glDisable(GL30.GL_BLEND); + this.testShader.setUniformMatrix("MVP", this.camera.viewProjectionMatrix); + gl.glBindBuffer(GL30.GL_ARRAY_BUFFER, this.testArrayBuffer); + this.testShader.setVertexAttribute("vPosition", 3, GL30.GL_FLOAT, false, 12, 0); - gl.glBindBuffer(GL30.GL_ELEMENT_ARRAY_BUFFER, this.testElementBuffer); - gl.glDrawElements(GL30.GL_LINES, this.softwareGroundMesh.indices.length, GL30.GL_UNSIGNED_SHORT, 0);// ); + gl.glBindBuffer(GL30.GL_ELEMENT_ARRAY_BUFFER, this.testElementBuffer); + gl.glDrawElements(GL30.GL_LINES, this.softwareGroundMesh.indices.length, GL30.GL_UNSIGNED_SHORT, 0);// ); - gl.glEnable(GL30.GL_BLEND); - return gl; - } + gl.glEnable(GL30.GL_BLEND); + return gl; + } - public void renderUberSplats() { - final GL30 gl = Gdx.gl30; - final WebGL webGL = this.webGL; - final ShaderProgram shader = this.uberSplatShader; + public void renderUberSplats() { + final GL30 gl = Gdx.gl30; + final WebGL webGL = this.webGL; + final ShaderProgram shader = this.uberSplatShader; - gl.glDepthMask(false); - gl.glEnable(GL30.GL_BLEND); - gl.glBlendFunc(GL30.GL_SRC_ALPHA, GL30.GL_ONE_MINUS_SRC_ALPHA); - gl.glBlendEquation(GL30.GL_FUNC_ADD); + gl.glDepthMask(false); + gl.glEnable(GL30.GL_BLEND); + gl.glBlendFunc(GL30.GL_SRC_ALPHA, GL30.GL_ONE_MINUS_SRC_ALPHA); + gl.glBlendEquation(GL30.GL_FUNC_ADD); - webGL.useShaderProgram(this.uberSplatShader); + webGL.useShaderProgram(this.uberSplatShader); - shader.setUniformMatrix("u_mvp", this.camera.viewProjectionMatrix); - shader.setUniformi("u_heightMap", 0); - sizeHeap[0] = this.columns - 1; - sizeHeap[1] = this.rows - 1; - shader.setUniform2fv("u_size", sizeHeap, 0, 2); - sizeHeap[0] = 1 / (float) this.columns; - sizeHeap[1] = 1 / (float) this.rows; - shader.setUniform2fv("u_pixel", sizeHeap, 0, 2); - shader.setUniform2fv("u_centerOffset", this.centerOffset, 0, 2); - shader.setUniformi("u_texture", 1); - shader.setUniformi("u_shadowMap", 2); + shader.setUniformMatrix("u_mvp", this.camera.viewProjectionMatrix); + shader.setUniformi("u_heightMap", 0); + sizeHeap[0] = this.columns - 1; + sizeHeap[1] = this.rows - 1; + shader.setUniform2fv("u_size", sizeHeap, 0, 2); + sizeHeap[0] = 1 / (float) this.columns; + sizeHeap[1] = 1 / (float) this.rows; + shader.setUniform2fv("u_pixel", sizeHeap, 0, 2); + shader.setUniform2fv("u_centerOffset", this.centerOffset, 0, 2); + shader.setUniformi("u_texture", 1); + shader.setUniformi("u_shadowMap", 2); - gl.glActiveTexture(GL30.GL_TEXTURE0); - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeightLinear); + gl.glActiveTexture(GL30.GL_TEXTURE0); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeightLinear); - gl.glActiveTexture(GL30.GL_TEXTURE2); - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.shadowMap); + gl.glActiveTexture(GL30.GL_TEXTURE2); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.shadowMap); - final W3xSceneLightManager lightManager = (W3xSceneLightManager) this.viewer.worldScene.getLightManager(); - final DataTexture terrainLightsTexture = lightManager.getTerrainLightsTexture(); + final W3xSceneLightManager lightManager = (W3xSceneLightManager) this.viewer.worldScene.getLightManager(); + final DataTexture terrainLightsTexture = lightManager.getTerrainLightsTexture(); - terrainLightsTexture.bind(21); - gl.glUniform1i(shader.getUniformLocation("u_lightTexture"), 21); - gl.glUniform1f(shader.getUniformLocation("u_lightCount"), lightManager.getTerrainLightCount()); - gl.glUniform1f(shader.getUniformLocation("u_lightTextureHeight"), terrainLightsTexture.getHeight()); + terrainLightsTexture.bind(21); + gl.glUniform1i(shader.getUniformLocation("u_lightTexture"), 21); + gl.glUniform1f(shader.getUniformLocation("u_lightCount"), lightManager.getTerrainLightCount()); + gl.glUniform1f(shader.getUniformLocation("u_lightTextureHeight"), terrainLightsTexture.getHeight()); - // Render the cliffs - for (final SplatModel splat : this.uberSplatModels) { - splat.render(gl, shader); - } - } + // Render the cliffs + for (final SplatModel splat : this.uberSplatModels.values()) { + splat.render(gl, shader); + } + } - public void renderWater() { - // Render water - this.webGL.useShaderProgram(this.waterShader); + public void renderWater() { + // Render water + this.webGL.useShaderProgram(this.waterShader); - final GL30 gl = Gdx.gl30; - gl.glDepthMask(false); - gl.glDisable(GL30.GL_CULL_FACE); - gl.glEnable(GL30.GL_BLEND); - gl.glBlendFunc(GL30.GL_SRC_ALPHA, GL30.GL_ONE_MINUS_SRC_ALPHA); + final GL30 gl = Gdx.gl30; + gl.glDepthMask(false); + gl.glDisable(GL30.GL_CULL_FACE); + gl.glEnable(GL30.GL_BLEND); + gl.glBlendFunc(GL30.GL_SRC_ALPHA, GL30.GL_ONE_MINUS_SRC_ALPHA); - gl.glUniformMatrix4fv(0, 1, false, this.camera.viewProjectionMatrix.val, 0); - gl.glUniform4fv(1, 1, this.minShallowColor, 0); - gl.glUniform4fv(2, 1, this.maxShallowColor, 0); - gl.glUniform4fv(3, 1, this.minDeepColor, 0); - gl.glUniform4fv(4, 1, this.maxDeepColor, 0); - gl.glUniform1f(5, this.waterHeightOffset); - gl.glUniform1i(6, (int) this.waterIndex); - gl.glUniform1f(this.waterShader.getUniformLocation("centerOffsetX"), this.centerOffset[0]); - gl.glUniform1f(this.waterShader.getUniformLocation("centerOffsetY"), this.centerOffset[1]); - gl.glUniform4fv(11, 1, this.shaderMapBounds, 0); + gl.glUniformMatrix4fv(0, 1, false, this.camera.viewProjectionMatrix.val, 0); + gl.glUniform4fv(1, 1, this.minShallowColor, 0); + gl.glUniform4fv(2, 1, this.maxShallowColor, 0); + gl.glUniform4fv(3, 1, this.minDeepColor, 0); + gl.glUniform4fv(4, 1, this.maxDeepColor, 0); + gl.glUniform1f(5, this.waterHeightOffset); + gl.glUniform1i(6, (int) this.waterIndex); + gl.glUniform1f(this.waterShader.getUniformLocation("centerOffsetX"), this.centerOffset[0]); + gl.glUniform1f(this.waterShader.getUniformLocation("centerOffsetY"), this.centerOffset[1]); + gl.glUniform4fv(11, 1, this.shaderMapBounds, 0); - final W3xSceneLightManager lightManager = (W3xSceneLightManager) this.viewer.worldScene.getLightManager(); - final DataTexture terrainLightsTexture = lightManager.getTerrainLightsTexture(); + final W3xSceneLightManager lightManager = (W3xSceneLightManager) this.viewer.worldScene.getLightManager(); + final DataTexture terrainLightsTexture = lightManager.getTerrainLightsTexture(); - terrainLightsTexture.bind(3); - gl.glUniform1f(9, lightManager.getTerrainLightCount()); - gl.glUniform1f(10, terrainLightsTexture.getHeight()); + terrainLightsTexture.bind(3); + gl.glUniform1f(9, lightManager.getTerrainLightCount()); + gl.glUniform1f(10, terrainLightsTexture.getHeight()); - gl.glActiveTexture(GL30.GL_TEXTURE0); - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.waterHeight); - gl.glActiveTexture(GL30.GL_TEXTURE1); - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeight); - gl.glActiveTexture(GL30.GL_TEXTURE2); - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.waterExists); - gl.glActiveTexture(GL30.GL_TEXTURE4); - gl.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.waterTextureArray); + gl.glActiveTexture(GL30.GL_TEXTURE0); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.waterHeight); + gl.glActiveTexture(GL30.GL_TEXTURE1); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeight); + gl.glActiveTexture(GL30.GL_TEXTURE2); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.waterExists); + gl.glActiveTexture(GL30.GL_TEXTURE4); + gl.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.waterTextureArray); - gl.glBindBuffer(GL30.GL_ARRAY_BUFFER, Shapes.INSTANCE.vertexBuffer); - gl.glVertexAttribPointer(0, 2, GL30.GL_FLOAT, false, 0, 0); + gl.glBindBuffer(GL30.GL_ARRAY_BUFFER, Shapes.INSTANCE.vertexBuffer); + gl.glVertexAttribPointer(0, 2, GL30.GL_FLOAT, false, 0, 0); - gl.glBindBuffer(GL30.GL_ELEMENT_ARRAY_BUFFER, Shapes.INSTANCE.indexBuffer); - gl.glDrawElementsInstanced(GL30.GL_TRIANGLES, Shapes.INSTANCE.quadIndices.length * 3, GL30.GL_UNSIGNED_INT, 0, - (this.columns - 1) * (this.rows - 1)); + gl.glBindBuffer(GL30.GL_ELEMENT_ARRAY_BUFFER, Shapes.INSTANCE.indexBuffer); + gl.glDrawElementsInstanced(GL30.GL_TRIANGLES, Shapes.INSTANCE.quadIndices.length * 3, GL30.GL_UNSIGNED_INT, 0, + (this.columns - 1) * (this.rows - 1)); - gl.glEnable(GL30.GL_BLEND); - } + gl.glEnable(GL30.GL_BLEND); + } - public void renderCliffs() { + public void renderCliffs() { - // Render cliffs - for (final IVec3 i : this.cliffs) { - final RenderCorner bottomLeft = this.corners[i.x][i.y]; - final RenderCorner bottomRight = this.corners[i.x + 1][i.y]; - final RenderCorner topLeft = this.corners[i.x][i.y + 1]; - final RenderCorner topRight = this.corners[i.x + 1][i.y + 1]; + // Render cliffs + for (final IVec3 i : this.cliffs) { + final RenderCorner bottomLeft = this.corners[i.x][i.y]; + final RenderCorner bottomRight = this.corners[i.x + 1][i.y]; + final RenderCorner topLeft = this.corners[i.x][i.y + 1]; + final RenderCorner topRight = this.corners[i.x + 1][i.y + 1]; - final float min = Math.min(Math.min(bottomLeft.getLayerHeight() - 2, bottomRight.getLayerHeight() - 2), - Math.min(topLeft.getLayerHeight() - 2, topRight.getLayerHeight() - 2)); + final float min = Math.min(Math.min(bottomLeft.getLayerHeight() - 2, bottomRight.getLayerHeight() - 2), + Math.min(topLeft.getLayerHeight() - 2, topRight.getLayerHeight() - 2)); - fourComponentHeap[0] = i.x; - fourComponentHeap[1] = i.y; - fourComponentHeap[2] = min; - fourComponentHeap[3] = bottomLeft.getCliffTexture(); - this.cliffMeshes.get(i.z).renderQueue(fourComponentHeap); - } + fourComponentHeap[0] = i.x; + fourComponentHeap[1] = i.y; + fourComponentHeap[2] = min; + fourComponentHeap[3] = bottomLeft.getCliffTexture(); + this.cliffMeshes.get(i.z).renderQueue(fourComponentHeap); + } - this.webGL.useShaderProgram(this.cliffShader); + this.webGL.useShaderProgram(this.cliffShader); - final GL30 gl = Gdx.gl30; - gl.glDisable(GL30.GL_BLEND); + final GL30 gl = Gdx.gl30; + gl.glDisable(GL30.GL_BLEND); - // WC3 models are 128x too large - tempMatrix.set(this.camera.viewProjectionMatrix); - gl.glUniformMatrix4fv(0, 1, false, tempMatrix.val, 0); - gl.glUniform1i(1, this.viewer.renderPathing); - gl.glUniform1i(2, this.viewer.renderLighting); + // WC3 models are 128x too large + tempMatrix.set(this.camera.viewProjectionMatrix); + gl.glUniformMatrix4fv(0, 1, false, tempMatrix.val, 0); + gl.glUniform1i(1, this.viewer.renderPathing); + gl.glUniform1i(2, this.viewer.renderLighting); - final W3xSceneLightManager lightManager = (W3xSceneLightManager) this.viewer.worldScene.getLightManager(); - final DataTexture unitLightsTexture = lightManager.getTerrainLightsTexture(); + final W3xSceneLightManager lightManager = (W3xSceneLightManager) this.viewer.worldScene.getLightManager(); + final DataTexture unitLightsTexture = lightManager.getTerrainLightsTexture(); - unitLightsTexture.bind(21); - gl.glUniform1i(this.cliffShader.getUniformLocation("lightTexture"), 21); - gl.glUniform1f(this.cliffShader.getUniformLocation("lightCount"), lightManager.getTerrainLightCount()); - gl.glUniform1f(this.cliffShader.getUniformLocation("lightTextureHeight"), unitLightsTexture.getHeight()); + unitLightsTexture.bind(21); + gl.glUniform1i(this.cliffShader.getUniformLocation("lightTexture"), 21); + gl.glUniform1f(this.cliffShader.getUniformLocation("lightCount"), lightManager.getTerrainLightCount()); + gl.glUniform1f(this.cliffShader.getUniformLocation("lightTextureHeight"), unitLightsTexture.getHeight()); - this.cliffShader.setUniformi("shadowMap", 2); - gl.glActiveTexture(GL30.GL_TEXTURE2); - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.shadowMap); + this.cliffShader.setUniformi("shadowMap", 2); + gl.glActiveTexture(GL30.GL_TEXTURE2); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.shadowMap); - gl.glActiveTexture(GL30.GL_TEXTURE0); - gl.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.cliffTextureArray); - gl.glActiveTexture(GL30.GL_TEXTURE1); - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundHeight); + gl.glActiveTexture(GL30.GL_TEXTURE0); + gl.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.cliffTextureArray); + gl.glActiveTexture(GL30.GL_TEXTURE1); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundHeight); // gl.glActiveTexture(GL30.GL_TEXTURE2); - for (final CliffMesh i : this.cliffMeshes) { - gl.glUniform1f(this.cliffShader.getUniformLocation("centerOffsetX"), this.centerOffset[0]); - gl.glUniform1f(this.cliffShader.getUniformLocation("centerOffsetY"), this.centerOffset[1]); - i.render(); - } - } + for (final CliffMesh i : this.cliffMeshes) { + gl.glUniform1f(this.cliffShader.getUniformLocation("centerOffsetX"), this.centerOffset[0]); + gl.glUniform1f(this.cliffShader.getUniformLocation("centerOffsetY"), this.centerOffset[1]); + i.render(); + } + } - public void addShadow(final String file, final float x, final float y) { - if (!this.shadows.containsKey(file)) { - final String path = "ReplaceableTextures\\Shadows\\" + file + ".blp"; - this.shadows.put(file, new ArrayList<>()); - this.shadowTextures.put(file, (Texture) this.viewer.load(path, PathSolver.DEFAULT, null)); - } - this.shadows.get(file).add(new float[] { x, y }); - } + public void addShadow(final String file, final float shadowX, final float shadowY) { + if (!this.shadows.containsKey(file)) { + final String path = "ReplaceableTextures\\Shadows\\" + file + ".blp"; + this.shadows.put(file, new ArrayList<>()); + this.shadowTextures.put(file, (Texture) this.viewer.load(path, PathSolver.DEFAULT, null)); + } + this.shadows.get(file).add(new float[]{shadowX, shadowY}); + if (initShadowsFinished) { + final Texture texture = shadowTextures.get(file); - public void initShadows() throws IOException { - final GL30 gl = Gdx.gl30; - final float[] centerOffset = this.centerOffset; - final int columns = (this.columns - 1) * 4; - final int rows = (this.rows - 1) * 4; + final int columns = (this.columns - 1) * 4; + final int rows = (this.rows - 1) * 4; + blitShadowData(columns, rows, shadowX, shadowY, texture); + GL30 gl = Gdx.gl30; + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.shadowMap); + gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_R8, columns, rows, 0, GL30.GL_RED, GL30.GL_UNSIGNED_BYTE, + RenderMathUtils.wrap(shadowData)); + } + } - final int shadowSize = columns * rows; - final byte[] shadowData = new byte[columns * rows]; - if (this.viewer.mapMpq.has("war3map.shd")) { - final byte[] buffer; + public void blitShadowData(int columns, int rows, float shadowX, float shadowY, Texture texture) { + final int width = texture.getWidth(); + final int height = texture.getHeight(); + final int ox = (int) Math.round(width * 0.3); + final int oy = (int) Math.round(height * 0.7); + blitShadowDataLocation(columns, rows, (RawOpenGLTextureResource) texture, width, height, ox, oy, centerOffset, shadowX, shadowY); + } - try (final InputStream shadowSource = this.viewer.mapMpq.getResourceAsStream("war3map.shd")) { - buffer = IOUtils.toByteArray(shadowSource); - } + public void initShadows() throws IOException { + final GL30 gl = Gdx.gl30; + final float[] centerOffset = this.centerOffset; + final int columns = (this.columns - 1) * 4; + final int rows = (this.rows - 1) * 4; - for (int i = 0; i < shadowSize; i++) { - shadowData[i] = (byte) ((buffer[i] & 0xFF) / 2f); - } - } + final int shadowSize = columns * rows; + shadowData = new byte[columns * rows]; + if (this.viewer.mapMpq.has("war3map.shd")) { + final byte[] buffer; - for (final Map.Entry fileAndTexture : this.shadowTextures.entrySet()) { - final String file = fileAndTexture.getKey(); - final Texture texture = fileAndTexture.getValue(); + try (final InputStream shadowSource = this.viewer.mapMpq.getResourceAsStream("war3map.shd")) { + buffer = IOUtils.toByteArray(shadowSource); + } - final int width = texture.getWidth(); - final int height = texture.getHeight(); - final int ox = (int) Math.round(width * 0.3); - final int oy = (int) Math.round(height * 0.7); - for (final float[] location : this.shadows.get(file)) { - final int x0 = (int) Math.floor((location[0] - centerOffset[0]) / 32.0) - ox; - final int y0 = (int) Math.floor((location[1] - centerOffset[1]) / 32.0) + oy; - for (int y = 0; y < height; ++y) { - if (((y0 - y) < 0) || ((y0 - y) >= rows)) { - continue; - } - for (int x = 0; x < width; ++x) { - if (((x0 + x) < 0) || ((x0 + x) >= columns)) { - continue; - } - if (((RawOpenGLTextureResource) texture).getData().get((((y * width) + x) * 4) + 3) != 0) { - shadowData[((y0 - y) * columns) + x0 + x] = (byte) 128; - } - } - } - } - } + for (int i = 0; i < shadowSize; i++) { + shadowData[i] = (byte) ((buffer[i] & 0xFF) / 2f); + } + } - final byte outsideArea = (byte) 204; - final int x0 = this.mapBounds[0] * 4, x1 = (this.mapSize[0] - this.mapBounds[1] - 1) * 4, - y0 = this.mapBounds[2] * 4, y1 = (this.mapSize[1] - this.mapBounds[3] - 1) * 4; - for (int y = y0; y < y1; ++y) { - for (int x = x0; x < x1; ++x) { - final RenderCorner c = this.corners[x >> 2][y >> 2]; - if (c.getBoundary() != 0) { - shadowData[(y * columns) + x] = outsideArea; - } - } - } - for (int y = 0; y < rows; ++y) { - for (int x = 0; x < x0; ++x) { - shadowData[(y * columns) + x] = outsideArea; - } - for (int x = x1; x < columns; ++x) { - shadowData[(y * columns) + x] = outsideArea; - } - } - for (int x = x0; x < x1; ++x) { - for (int y = 0; y < y0; ++y) { - shadowData[(y * columns) + x] = outsideArea; - } - for (int y = y1; y < rows; ++y) { - shadowData[(y * columns) + x] = outsideArea; - } - } + for (final Map.Entry fileAndTexture : this.shadowTextures.entrySet()) { + final String file = fileAndTexture.getKey(); + final Texture texture = fileAndTexture.getValue(); - this.shadowMap = gl.glGenTexture(); - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.shadowMap); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_LINEAR); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_LINEAR); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_S, GL30.GL_CLAMP_TO_EDGE); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_T, GL30.GL_CLAMP_TO_EDGE); - gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_R8, columns, rows, 0, GL30.GL_RED, GL30.GL_UNSIGNED_BYTE, - RenderMathUtils.wrap(shadowData)); - } + final int width = texture.getWidth(); + final int height = texture.getHeight(); + final int ox = (int) Math.round(width * 0.3); + final int oy = (int) Math.round(height * 0.7); + for (final float[] location : this.shadows.get(file)) { + blitShadowDataLocation(columns, rows, (RawOpenGLTextureResource) texture, width, height, ox, oy, centerOffset, location[0], location[1]); + } + } + + final byte outsideArea = (byte) 204; + final int x0 = this.mapBounds[0] * 4, x1 = (this.mapSize[0] - this.mapBounds[1] - 1) * 4, + y0 = this.mapBounds[2] * 4, y1 = (this.mapSize[1] - this.mapBounds[3] - 1) * 4; + for (int y = y0; y < y1; ++y) { + for (int x = x0; x < x1; ++x) { + final RenderCorner c = this.corners[x >> 2][y >> 2]; + if (c.getBoundary() != 0) { + shadowData[(y * columns) + x] = outsideArea; + } + } + } + for (int y = 0; y < rows; ++y) { + for (int x = 0; x < x0; ++x) { + shadowData[(y * columns) + x] = outsideArea; + } + for (int x = x1; x < columns; ++x) { + shadowData[(y * columns) + x] = outsideArea; + } + } + for (int x = x0; x < x1; ++x) { + for (int y = 0; y < y0; ++y) { + shadowData[(y * columns) + x] = outsideArea; + } + for (int y = y1; y < rows; ++y) { + shadowData[(y * columns) + x] = outsideArea; + } + } + + this.shadowMap = gl.glGenTexture(); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.shadowMap); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_LINEAR); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_LINEAR); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_S, GL30.GL_CLAMP_TO_EDGE); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_T, GL30.GL_CLAMP_TO_EDGE); + gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_R8, columns, rows, 0, GL30.GL_RED, GL30.GL_UNSIGNED_BYTE, + RenderMathUtils.wrap(shadowData)); + this.initShadowsFinished = true; + } + + public void blitShadowDataLocation(int columns, int rows, RawOpenGLTextureResource texture, int width, int height, int x01, int y01, float[] centerOffset, float v, float v2) { + final int x0 = (int) Math.floor((v - centerOffset[0]) / 32.0) - x01; + final int y0 = (int) Math.floor((v2 - centerOffset[1]) / 32.0) + y01; + for (int y = 0; y < height; ++y) { + if (((y0 - y) < 0) || ((y0 - y) >= rows)) { + continue; + } + for (int x = 0; x < width; ++x) { + if (((x0 + x) < 0) || ((x0 + x) >= columns)) { + continue; + } + if (texture.getData().get((((y * width) + x) * 4) + 3) != 0) { + shadowData[((y0 - y) * columns) + x0 + x] = (byte) 128; + } + } + } + } // public Vector3 groundNormal(final Vector3 out, int x, int y) { // final float[] centerOffset = this.centerOffset; @@ -1237,172 +1250,180 @@ public class Terrain { // return out; // } - private final WaveBuilder waveBuilder; - public PathingGrid pathingGrid; - private final Rectangle shaderMapBoundsRectangle; - private final Rectangle entireMapRectangle; + private final WaveBuilder waveBuilder; + public PathingGrid pathingGrid; + private final Rectangle shaderMapBoundsRectangle; + private final Rectangle entireMapRectangle; - private static final class UnloadedTexture { - private final int width; - private final int height; - private final Buffer data; - private final String cliffModelDir; - private final String rampModelDir; + private static final class UnloadedTexture { + private final int width; + private final int height; + private final Buffer data; + private final String cliffModelDir; + private final String rampModelDir; - public UnloadedTexture(final int width, final int height, final Buffer data, final String cliffModelDir, - final String rampModelDir) { - this.width = width; - this.height = height; - this.data = data; - this.cliffModelDir = cliffModelDir; - this.rampModelDir = rampModelDir; - } + public UnloadedTexture(final int width, final int height, final Buffer data, final String cliffModelDir, + final String rampModelDir) { + this.width = width; + this.height = height; + this.data = data; + this.cliffModelDir = cliffModelDir; + this.rampModelDir = rampModelDir; + } - } + } - public float getGroundHeight(final float x, final float y) { - final float userCellSpaceX = (x - this.centerOffset[0]) / 128.0f; - final float userCellSpaceY = (y - this.centerOffset[1]) / 128.0f; - final int cellX = (int) userCellSpaceX; - final int cellY = (int) userCellSpaceY; + public float getGroundHeight(final float x, final float y) { + final float userCellSpaceX = (x - this.centerOffset[0]) / 128.0f; + final float userCellSpaceY = (y - this.centerOffset[1]) / 128.0f; + final int cellX = (int) userCellSpaceX; + final int cellY = (int) userCellSpaceY; - if ((cellX >= 0) && (cellX < (this.mapSize[0] - 1)) && (cellY >= 0) && (cellY < (this.mapSize[1] - 1))) { - final float bottomLeft = this.groundCornerHeights[(cellY * this.columns) + cellX]; - final float bottomRight = this.groundCornerHeights[(cellY * this.columns) + cellX + 1]; - final float topLeft = this.groundCornerHeights[((cellY + 1) * this.columns) + cellX]; - final float topRight = this.groundCornerHeights[((cellY + 1) * this.columns) + cellX + 1]; - final float sqX = userCellSpaceX - cellX; - final float sqY = userCellSpaceY - cellY; - float height; + if ((cellX >= 0) && (cellX < (this.mapSize[0] - 1)) && (cellY >= 0) && (cellY < (this.mapSize[1] - 1))) { + final float bottomLeft = this.groundCornerHeights[(cellY * this.columns) + cellX]; + final float bottomRight = this.groundCornerHeights[(cellY * this.columns) + cellX + 1]; + final float topLeft = this.groundCornerHeights[((cellY + 1) * this.columns) + cellX]; + final float topRight = this.groundCornerHeights[((cellY + 1) * this.columns) + cellX + 1]; + final float sqX = userCellSpaceX - cellX; + final float sqY = userCellSpaceY - cellY; + float height; - if ((sqX + sqY) < 1) { - height = bottomLeft + ((bottomRight - bottomLeft) * sqX) + ((topLeft - bottomLeft) * sqY); - } - else { - height = topRight + ((bottomRight - topRight) * (1 - sqY)) + ((topLeft - topRight) * (1 - sqX)); - } + if ((sqX + sqY) < 1) { + height = bottomLeft + ((bottomRight - bottomLeft) * sqX) + ((topLeft - bottomLeft) * sqY); + } else { + height = topRight + ((bottomRight - topRight) * (1 - sqY)) + ((topLeft - topRight) * (1 - sqX)); + } - return height * 128.0f; - } + return height * 128.0f; + } - return 0; - } + return 0; + } - public float getWaterHeight(final float x, final float y) { - final float userCellSpaceX = (x - this.centerOffset[0]) / 128.0f; - final float userCellSpaceY = (y - this.centerOffset[1]) / 128.0f; - final int cellX = (int) userCellSpaceX; - final int cellY = (int) userCellSpaceY; + public float getWaterHeight(final float x, final float y) { + final float userCellSpaceX = (x - this.centerOffset[0]) / 128.0f; + final float userCellSpaceY = (y - this.centerOffset[1]) / 128.0f; + final int cellX = (int) userCellSpaceX; + final int cellY = (int) userCellSpaceY; - if ((cellX >= 0) && (cellX < (this.mapSize[0] - 1)) && (cellY >= 0) && (cellY < (this.mapSize[1] - 1))) { - final float bottomLeft = this.waterHeights[(cellY * this.columns) + cellX]; - final float bottomRight = this.waterHeights[(cellY * this.columns) + cellX + 1]; - final float topLeft = this.waterHeights[((cellY + 1) * this.columns) + cellX]; - final float topRight = this.waterHeights[((cellY + 1) * this.columns) + cellX + 1]; - final float sqX = userCellSpaceX - cellX; - final float sqY = userCellSpaceY - cellY; - float height; + if ((cellX >= 0) && (cellX < (this.mapSize[0] - 1)) && (cellY >= 0) && (cellY < (this.mapSize[1] - 1))) { + final float bottomLeft = this.waterHeights[(cellY * this.columns) + cellX]; + final float bottomRight = this.waterHeights[(cellY * this.columns) + cellX + 1]; + final float topLeft = this.waterHeights[((cellY + 1) * this.columns) + cellX]; + final float topRight = this.waterHeights[((cellY + 1) * this.columns) + cellX + 1]; + final float sqX = userCellSpaceX - cellX; + final float sqY = userCellSpaceY - cellY; + float height; - if ((sqX + sqY) < 1) { - height = bottomLeft + ((bottomRight - bottomLeft) * sqX) + ((topLeft - bottomLeft) * sqY); - } - else { - height = topRight + ((bottomRight - topRight) * (1 - sqY)) + ((topLeft - topRight) * (1 - sqX)); - } + if ((sqX + sqY) < 1) { + height = bottomLeft + ((bottomRight - bottomLeft) * sqX) + ((topLeft - bottomLeft) * sqY); + } else { + height = topRight + ((bottomRight - topRight) * (1 - sqY)) + ((topLeft - topRight) * (1 - sqX)); + } - return ((height + this.waterHeightOffset) * 128.0f); - } + return ((height + this.waterHeightOffset) * 128.0f); + } - return this.waterHeightOffset * 128.0f; - } + return this.waterHeightOffset * 128.0f; + } - public static final class Splat { - public List locations = new ArrayList<>(); - public List> unitMapping = new ArrayList<>(); - public float opacity = 1; - } + public static final class Splat { + public List locations = new ArrayList<>(); + public List> unitMapping = new ArrayList<>(); + public float opacity = 1; + } - public void loadSplats() throws IOException { - for (final Map.Entry entry : this.splats.entrySet()) { - final String path = entry.getKey(); - final Splat splat = entry.getValue(); + public void loadSplats() throws IOException { + for (final Map.Entry entry : this.splats.entrySet()) { + final String path = entry.getKey(); + final Splat splat = entry.getValue(); - final SplatModel splatModel = new SplatModel(Gdx.gl30, - (Texture) this.viewer.load(path, PathSolver.DEFAULT, null), splat.locations, this.centerOffset, - splat.unitMapping, false); - splatModel.color[3] = splat.opacity; - this.uberSplatModels.add(splatModel); - } - } + final SplatModel splatModel = new SplatModel(Gdx.gl30, + (Texture) this.viewer.load(path, PathSolver.DEFAULT, null), splat.locations, this.centerOffset, + splat.unitMapping, false); + splatModel.color[3] = splat.opacity; + this.uberSplatModels.put(path, splatModel); + } + } - public void removeSplatBatchModel(final SplatModel model) { - this.uberSplatModels.remove(model); - } + public void removeSplatBatchModel(String path) { + this.uberSplatModels.remove(path); + } - public void addSplatBatchModel(final SplatModel model) { - this.uberSplatModels.add(model); - } + public void addSplatBatchModel(String path, final SplatModel model) { + this.uberSplatModels.put(path, model); + } - public static final class SoftwareGroundMesh { - public final float[] vertices; - public final int[] indices; + public void addUberSplat(String path, float x, float y, float z, float scale) { + SplatModel splatModel = this.uberSplatModels.get(path); + if (splatModel == null) { + splatModel = new SplatModel(Gdx.gl30, (Texture) this.viewer.load(path, PathSolver.DEFAULT, null), + new ArrayList<>(), centerOffset, null, false); + this.uberSplatModels.put(path, splatModel); + } + splatModel.add(x, y, z, scale, centerOffset); + } - private SoftwareGroundMesh(final float[] groundHeights, final float[] groundCornerHeights, - final float[] centerOffset, final int columns, final int rows) { - this.vertices = new float[(columns - 1) * (rows - 1) * Shapes.INSTANCE.quadVertices.length * 3]; - this.indices = new int[(columns - 1) * (rows - 1) * Shapes.INSTANCE.quadIndices.length * 3]; - for (int y = 0; y < (rows - 1); y++) { - for (int x = 0; x < (columns - 1); x++) { - final int instanceId = (y * (columns - 1)) + x; - for (int vertexId = 0; vertexId < Shapes.INSTANCE.quadVertices.length; vertexId++) { - final float vPositionX = Shapes.INSTANCE.quadVertices[vertexId][0]; - final float vPositionY = Shapes.INSTANCE.quadVertices[vertexId][1]; - final int groundCornerHeightIndex = (int) (((vPositionY + y) * (columns)) + (vPositionX + x)); - final float height = groundCornerHeights[groundCornerHeightIndex]; - this.vertices[(instanceId * 4 * 3) + (vertexId * 3)] = ((vPositionX + x) * 128f) - + centerOffset[0]; - this.vertices[(instanceId * 4 * 3) + (vertexId * 3) + 1] = ((vPositionY + y) * 128f) - + centerOffset[1]; - this.vertices[(instanceId * 4 * 3) + (vertexId * 3) + 2] = height * 128f; - } - for (int triangle = 0; triangle < Shapes.INSTANCE.quadIndices.length; triangle++) { - for (int vertexId = 0; vertexId < Shapes.INSTANCE.quadIndices[triangle].length; vertexId++) { - final int vertexIndex = Shapes.INSTANCE.quadIndices[triangle][vertexId]; - final int indexValue = (vertexIndex + (instanceId * 4)); - if ((indexValue * 3) >= this.vertices.length) { - throw new IllegalStateException(); - } - this.indices[(instanceId * 2 * 3) + (triangle * 3) + vertexId] = indexValue; - } - } - } - } - } - } + public static final class SoftwareGroundMesh { + public final float[] vertices; + public final int[] indices; - public boolean inPlayableArea(float x, float y) { - x = (x - this.centerOffset[0]) / 128.0f; - y = (y - this.centerOffset[1]) / 128.0f; - if (x < this.mapBounds[0]) { - return false; - } - if (x >= (this.mapSize[0] - this.mapBounds[1] - 1)) { - return false; - } - if (y < this.mapBounds[2]) { - return false; - } - if (y >= (this.mapSize[1] - this.mapBounds[3] - 1)) { - return false; - } // TODO why do we use floor if we can use int cast? - return this.corners[(int) Math.floor(x)][(int) Math.floor(y)].getBoundary() == 0; - } + private SoftwareGroundMesh(final float[] groundHeights, final float[] groundCornerHeights, + final float[] centerOffset, final int columns, final int rows) { + this.vertices = new float[(columns - 1) * (rows - 1) * Shapes.INSTANCE.quadVertices.length * 3]; + this.indices = new int[(columns - 1) * (rows - 1) * Shapes.INSTANCE.quadIndices.length * 3]; + for (int y = 0; y < (rows - 1); y++) { + for (int x = 0; x < (columns - 1); x++) { + final int instanceId = (y * (columns - 1)) + x; + for (int vertexId = 0; vertexId < Shapes.INSTANCE.quadVertices.length; vertexId++) { + final float vPositionX = Shapes.INSTANCE.quadVertices[vertexId][0]; + final float vPositionY = Shapes.INSTANCE.quadVertices[vertexId][1]; + final int groundCornerHeightIndex = (int) (((vPositionY + y) * (columns)) + (vPositionX + x)); + final float height = groundCornerHeights[groundCornerHeightIndex]; + this.vertices[(instanceId * 4 * 3) + (vertexId * 3)] = ((vPositionX + x) * 128f) + + centerOffset[0]; + this.vertices[(instanceId * 4 * 3) + (vertexId * 3) + 1] = ((vPositionY + y) * 128f) + + centerOffset[1]; + this.vertices[(instanceId * 4 * 3) + (vertexId * 3) + 2] = height * 128f; + } + for (int triangle = 0; triangle < Shapes.INSTANCE.quadIndices.length; triangle++) { + for (int vertexId = 0; vertexId < Shapes.INSTANCE.quadIndices[triangle].length; vertexId++) { + final int vertexIndex = Shapes.INSTANCE.quadIndices[triangle][vertexId]; + final int indexValue = (vertexIndex + (instanceId * 4)); + if ((indexValue * 3) >= this.vertices.length) { + throw new IllegalStateException(); + } + this.indices[(instanceId * 2 * 3) + (triangle * 3) + vertexId] = indexValue; + } + } + } + } + } + } - public Rectangle getPlayableMapArea() { - return this.shaderMapBoundsRectangle; - } + public boolean inPlayableArea(float x, float y) { + x = (x - this.centerOffset[0]) / 128.0f; + y = (y - this.centerOffset[1]) / 128.0f; + if (x < this.mapBounds[0]) { + return false; + } + if (x >= (this.mapSize[0] - this.mapBounds[1] - 1)) { + return false; + } + if (y < this.mapBounds[2]) { + return false; + } + if (y >= (this.mapSize[1] - this.mapBounds[3] - 1)) { + return false; + } // TODO why do we use floor if we can use int cast? + return this.corners[(int) Math.floor(x)][(int) Math.floor(y)].getBoundary() == 0; + } - public Rectangle getEntireMap() { - return this.entireMapRectangle; - } + public Rectangle getPlayableMapArea() { + return this.shaderMapBoundsRectangle; + } + + public Rectangle getEntireMap() { + return this.entireMapRectangle; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java index e6368ba..8affe9f 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java @@ -12,13 +12,10 @@ import com.etheller.warsmash.util.RenderMathUtils; import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; -import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens; +import com.etheller.warsmash.viewer5.handlers.w3x.*; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.SecondaryTag; -import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; import com.etheller.warsmash.viewer5.handlers.w3x.SplatModel.SplatMover; -import com.etheller.warsmash.viewer5.handlers.w3x.UnitSoundset; -import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.MovementType; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.AbilityDataUI; @@ -39,6 +36,7 @@ public class RenderUnit { private static final War3ID ORIENTATION_INTERPOLATION = War3ID.fromString("uori"); private static final War3ID ANIM_PROPS = War3ID.fromString("uani"); private static final War3ID BLEND_TIME = War3ID.fromString("uble"); + private static final War3ID BUILD_SOUND_LABEL = War3ID.fromString("ubsl"); private static final float[] heapZ = new float[3]; public final MdxComplexInstance instance; public final MutableGameObject row; @@ -66,6 +64,7 @@ public class RenderUnit { private boolean corpse; private boolean boneCorpse; private final RenderUnitTypeData typeData; + public UnitSound buildSound; public RenderUnit(final War3MapViewer map, final MdxModel model, final MutableGameObject row, final float x, final float y, final float z, final int playerIndex, final UnitSoundset soundset, @@ -128,6 +127,7 @@ public class RenderUnit { final float blendTime = row.getFieldAsFloat(BLEND_TIME, 0); instance.setBlendTime(blendTime * 1000.0f); + buildSound = map.getUiSounds().getSound(row.getFieldAsString(BUILD_SOUND_LABEL, 0)); } this.instance = instance; @@ -350,6 +350,9 @@ public class RenderUnit { this.selectionCircle.move(dx, dy, map.terrain.centerOffset); } this.unitAnimationListenerImpl.update(); + if(!dead && simulationUnit.isConstructing()) { + instance.setFrameByRatio(simulationUnit.getConstructionProgress() / simulationUnit.getUnitType().getBuildTime()); + } } private float getGroundHeightSample(final float groundHeight, final MdxComplexInstance currentWalkableUnder, diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java index 2e29414..bbd3378 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java @@ -223,6 +223,10 @@ public class CSimulation { this.simulationRenderController.spawnUnitDamageSound(damagedUnit, weaponSound, armorType); } + public void unitConstructedEvent(CUnit constructingUnit, CUnit constructedStructure) { + this.simulationRenderController.spawnUnitConstructionSound(constructingUnit, constructedStructure); + } + public CPlayer getPlayer(final int index) { return this.players.get(index); } @@ -230,4 +234,8 @@ public class CSimulation { public CommandErrorListener getCommandErrorListener() { return this.commandErrorListener; } + + public void unitConstructFinishEvent(CUnit constructedStructure) { + this.simulationRenderController.spawnUnitConstructionFinishSound(constructedStructure); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java index 8eb6864..e69812b 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java @@ -70,6 +70,8 @@ public class CUnit extends CWidget { private transient CBehaviorFollow followBehavior; private transient CBehaviorPatrol patrolBehavior; private transient CBehaviorStop stopBehavior; + private boolean constructing = false; + private float constructionProgress; public CUnit(final int handleId, final int playerIndex, final float x, final float y, final float life, final War3ID typeId, final float facing, final float mana, final int maximumLife, final int maximumMana, @@ -199,6 +201,14 @@ public class CUnit extends CWidget { return true; } } + else if (constructing) { + constructionProgress += WarsmashConstants.SIMULATION_STEP_TIME; + if (constructionProgress >= unitType.getBuildTime()) { + constructing = false; + game.unitConstructFinishEvent(this); + this.stateNotifier.ordersChanged(getCurrentAbilityHandleId(), getCurrentOrderId()); + } + } else if (this.currentBehavior != null) { final CBehavior lastBehavior = this.currentBehavior; this.currentBehavior = this.currentBehavior.update(game); @@ -670,4 +680,22 @@ public class CUnit extends CWidget { return getCurrentBehavior() instanceof CBehaviorMove; } + public void setConstructing(boolean constructing) { + this.constructing = constructing; + if (constructing) { + unitAnimationListener.playAnimation(true, PrimaryTag.BIRTH, SequenceUtils.EMPTY, 0.0f, true); + } + } + + public void setConstructionProgress(float constructionProgress) { + this.constructionProgress = constructionProgress; + } + + public boolean isConstructing() { + return constructing; + } + + public float getConstructionProgress() { + return constructionProgress; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java index 38dd56d..83e7b3d 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java @@ -35,8 +35,10 @@ public class CBehaviorOrcBuild extends CAbstractRangedPointTargetBehavior { @Override protected CBehavior update(final CSimulation simulation, final boolean withinRange) { - simulation.createUnit(this.orderId, this.unit.getPlayerIndex(), this.targetX, this.targetY, + CUnit constructedStructure = simulation.createUnit(this.orderId, this.unit.getPlayerIndex(), this.targetX, this.targetY, simulation.getGameplayConstants().getBuildingAngle()); + constructedStructure.setConstructing(true); + simulation.unitConstructedEvent(this.unit, constructedStructure); return this.unit.pollNextOrderBehavior(simulation); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java index 0052226..eeb1f73 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java @@ -21,7 +21,11 @@ public interface SimulationRenderController { void spawnUnitDamageSound(CUnit damagedUnit, final String weaponSound, String armorType); + void spawnUnitConstructionSound(CUnit constructingUnit, CUnit constructedStructure); + void removeUnit(CUnit unit); BufferedImage getBuildingPathingPixelMap(War3ID rawcode); + + void spawnUnitConstructionFinishSound(CUnit constructedStructure); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java index 0f72e55..0b0a887 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java @@ -79,1050 +79,1037 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.StringMsgAbili import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.CommandCardCommandListener; public class MeleeUI implements CUnitStateListener, CommandButtonListener, CommandCardCommandListener { - public static final float DEFAULT_COMMAND_CARD_ICON_WIDTH = 0.039f; - public static final float DEFAULT_COMMAND_CARD_ICON_PRESSED_WIDTH = 0.037f; - private static final int COMMAND_CARD_WIDTH = 4; - private static final int COMMAND_CARD_HEIGHT = 3; - - private static final Vector2 screenCoordsVector = new Vector2(); - private static final Vector3 clickLocationTemp = new Vector3(); - private static final Vector2 clickLocationTemp2 = new Vector2(); - private final DataSource dataSource; - private final ExtendViewport uiViewport; - private final FreeTypeFontGenerator fontGenerator; - private final Scene uiScene; - private final Scene portraitScene; - private final GameCameraManager cameraManager; - private final War3MapViewer war3MapViewer; - private final RootFrameListener rootFrameListener; - private GameUI rootFrame; - private UIFrame consoleUI; - private UIFrame resourceBar; - private StringFrame resourceBarGoldText; - private StringFrame resourceBarLumberText; - private StringFrame resourceBarSupplyText; - private StringFrame resourceBarUpkeepText; - private SpriteFrame timeIndicator; - private UIFrame unitPortrait; - private StringFrame unitLifeText; - private StringFrame unitManaText; - private Portrait portrait; - private final Rectangle tempRect = new Rectangle(); - private final Vector2 projectionTemp1 = new Vector2(); - private final Vector2 projectionTemp2 = new Vector2(); - private UIFrame simpleInfoPanelUnitDetail; - private StringFrame simpleNameValue; - private StringFrame simpleClassValue; - private StringFrame simpleBuildingActionLabel; - private UIFrame attack1Icon; - private TextureFrame attack1IconBackdrop; - private StringFrame attack1InfoPanelIconValue; - private StringFrame attack1InfoPanelIconLevel; - private UIFrame attack2Icon; - private TextureFrame attack2IconBackdrop; - private StringFrame attack2InfoPanelIconValue; - private StringFrame attack2InfoPanelIconLevel; - private UIFrame armorIcon; - private TextureFrame armorIconBackdrop; - private StringFrame armorInfoPanelIconValue; - private StringFrame armorInfoPanelIconLevel; - private InfoPanelIconBackdrops damageBackdrops; - private InfoPanelIconBackdrops defenseBackdrops; - - private final CommandCardIcon[][] commandCard = new CommandCardIcon[COMMAND_CARD_HEIGHT][COMMAND_CARD_WIDTH]; - - private RenderUnit selectedUnit; - private final List subMenuOrderIdStack = new ArrayList<>(); - - // TODO remove this & replace with FDF - private final Texture activeButtonTexture; - private UIFrame inventoryCover; - private SpriteFrame cursorFrame; - private MeleeUIMinimap meleeUIMinimap; - private final CPlayerUnitOrderListener unitOrderListener; - private StringFrame errorMessageFrame; - - private CAbilityView activeCommand; - private int activeCommandOrderId; - private RenderUnit activeCommandUnit; - private MdxComplexInstance cursorModelInstance = null; - private BufferedImage cursorModelPathing; - - private int selectedSoundCount = 0; - private final ActiveCommandUnitTargetFilter activeCommandUnitTargetFilter; - - // TODO these corrections are used for old hardcoded UI stuff, we should - // probably remove them later - private final float widthRatioCorrection; - private final float heightRatioCorrection; - private CommandCardIcon mouseDownUIFrame; - - public MeleeUI(final DataSource dataSource, final ExtendViewport uiViewport, - final FreeTypeFontGenerator fontGenerator, final Scene uiScene, final Scene portraitScene, - final CameraPreset[] cameraPresets, final CameraRates cameraRates, final War3MapViewer war3MapViewer, - final RootFrameListener rootFrameListener, final CPlayerUnitOrderListener unitOrderListener) { - this.dataSource = dataSource; - this.uiViewport = uiViewport; - this.fontGenerator = fontGenerator; - this.uiScene = uiScene; - this.portraitScene = portraitScene; - this.war3MapViewer = war3MapViewer; - this.rootFrameListener = rootFrameListener; - this.unitOrderListener = unitOrderListener; - - this.cameraManager = new GameCameraManager(cameraPresets, cameraRates); - - this.cameraManager.setupCamera(war3MapViewer.worldScene); - if (this.war3MapViewer.startLocations[0] != null) { - this.cameraManager.target.x = this.war3MapViewer.startLocations[0].x; - this.cameraManager.target.y = this.war3MapViewer.startLocations[0].y; - } - - this.activeButtonTexture = ImageUtils.getBLPTexture(war3MapViewer.mapMpq, - "UI\\Widgets\\Console\\Human\\CommandButton\\human-activebutton.blp"); - this.activeCommandUnitTargetFilter = new ActiveCommandUnitTargetFilter(); - this.widthRatioCorrection = this.uiViewport.getMinWorldWidth() / 1600f; - this.heightRatioCorrection = this.uiViewport.getMinWorldHeight() / 1200f; - - } - - private MeleeUIMinimap createMinimap(final War3MapViewer war3MapViewer) { - final Rectangle minimapDisplayArea = new Rectangle(18.75f * this.widthRatioCorrection, - 13.75f * this.heightRatioCorrection, 278.75f * this.widthRatioCorrection, - 276.25f * this.heightRatioCorrection); - Texture minimapTexture = null; - if (war3MapViewer.dataSource.has("war3mapMap.tga")) { - try { - minimapTexture = ImageUtils.getTextureNoColorCorrection(TgaFile.readTGA("war3mapMap.tga", - war3MapViewer.dataSource.getResourceAsStream("war3mapMap.tga"))); - } - catch (final IOException e) { - System.err.println("Could not load minimap TGA file"); - e.printStackTrace(); - } - } - else if (war3MapViewer.dataSource.has("war3mapMap.blp")) { - try { - minimapTexture = ImageUtils - .getTexture(ImageIO.read(war3MapViewer.dataSource.getResourceAsStream("war3mapMap.blp"))); - } - catch (final IOException e) { - System.err.println("Could not load minimap BLP file"); - e.printStackTrace(); - } - } - final Texture[] teamColors = new Texture[WarsmashConstants.MAX_PLAYERS]; - for (int i = 0; i < teamColors.length; i++) { - teamColors[i] = ImageUtils.getBLPTexture(war3MapViewer.dataSource, - "ReplaceableTextures\\" + ReplaceableIds.getPathString(1) + ReplaceableIds.getIdString(i) + ".blp"); - } - final Rectangle playableMapArea = war3MapViewer.terrain.getPlayableMapArea(); - return new MeleeUIMinimap(minimapDisplayArea, playableMapArea, minimapTexture, teamColors); - } - - /** - * Called "main" because this was originally written in JASS so that maps could - * override it, and I may convert it back to the JASS at some point. - */ - public void main() { - // ================================= - // Load skins and templates - // ================================= - this.rootFrame = new GameUI(this.dataSource, GameUI.loadSkin(this.dataSource, 0), this.uiViewport, - this.fontGenerator, this.uiScene, this.war3MapViewer); - this.rootFrameListener.onCreate(this.rootFrame); - try { - this.rootFrame.loadTOCFile("UI\\FrameDef\\FrameDef.toc"); - } - catch (final IOException exc) { - throw new IllegalStateException("Unable to load FrameDef.toc", exc); - } - try { - this.rootFrame.loadTOCFile("UI\\FrameDef\\SmashFrameDef.toc"); - } - catch (final IOException exc) { - throw new IllegalStateException("Unable to load SmashFrameDef.toc", exc); - } - this.damageBackdrops = new InfoPanelIconBackdrops(CAttackType.values(), this.rootFrame, "Damage", "Neutral"); - this.defenseBackdrops = new InfoPanelIconBackdrops(CDefenseType.values(), this.rootFrame, "Armor", "Neutral"); - - // ================================= - // Load major UI components - // ================================= - // Console UI is the background with the racial theme - this.consoleUI = this.rootFrame.createSimpleFrame("ConsoleUI", this.rootFrame, 0); - this.consoleUI.setSetAllPoints(true); - - // Resource bar is a 3 part bar with Gold, Lumber, and Food. - // Its template does not specify where to put it, so we must - // put it in the "TOPRIGHT" corner. - this.resourceBar = this.rootFrame.createSimpleFrame("ResourceBarFrame", this.consoleUI, 0); - this.resourceBar.addSetPoint(new SetPoint(FramePoint.TOPRIGHT, this.consoleUI, FramePoint.TOPRIGHT, 0, 0)); - this.resourceBarGoldText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarGoldText", 0); - this.resourceBarGoldText.setText("500"); - this.resourceBarLumberText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarLumberText", 0); - this.resourceBarLumberText.setText("150"); - this.resourceBarSupplyText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarSupplyText", 0); - this.resourceBarSupplyText.setText("12/100"); - this.resourceBarUpkeepText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarUpkeepText", 0); - this.resourceBarUpkeepText.setText("No Upkeep"); - this.resourceBarUpkeepText.setColor(Color.GREEN); - - // Create the Time Indicator (clock) - this.timeIndicator = (SpriteFrame) this.rootFrame.createFrame("TimeOfDayIndicator", this.rootFrame, 0, 0); - this.timeIndicator.setSequence(0); // play the stand - this.timeIndicator.setAnimationSpeed(0.0f); // do not advance automatically - - // Create the unit portrait stuff - this.portrait = new Portrait(this.war3MapViewer, this.portraitScene); - positionPortrait(); - this.unitPortrait = this.rootFrame.createSimpleFrame("UnitPortrait", this.consoleUI, 0); - this.unitLifeText = (StringFrame) this.rootFrame.getFrameByName("UnitPortraitHitPointText", 0); - this.unitManaText = (StringFrame) this.rootFrame.getFrameByName("UnitPortraitManaPointText", 0); - - this.simpleInfoPanelUnitDetail = this.rootFrame.createSimpleFrame("SimpleInfoPanelUnitDetail", this.consoleUI, - 0); - this.simpleInfoPanelUnitDetail - .addAnchor(new AnchorDefinition(FramePoint.BOTTOM, 0, GameUI.convertY(this.uiViewport, 0.0f))); - this.simpleInfoPanelUnitDetail.setWidth(GameUI.convertY(this.uiViewport, 0.180f)); - this.simpleInfoPanelUnitDetail.setHeight(GameUI.convertY(this.uiViewport, 0.105f)); - this.simpleNameValue = (StringFrame) this.rootFrame.getFrameByName("SimpleNameValue", 0); - this.simpleClassValue = (StringFrame) this.rootFrame.getFrameByName("SimpleClassValue", 0); - this.simpleBuildingActionLabel = (StringFrame) this.rootFrame.getFrameByName("SimpleBuildingActionLabel", 0); - - this.attack1Icon = this.rootFrame.createSimpleFrame("SimpleInfoPanelIconDamage", this.simpleInfoPanelUnitDetail, - 0); - this.attack1Icon.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, - FramePoint.TOPLEFT, 0, GameUI.convertY(this.uiViewport, -0.030125f))); - this.attack1IconBackdrop = (TextureFrame) this.rootFrame.getFrameByName("InfoPanelIconBackdrop", 0); - this.attack1InfoPanelIconValue = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconValue", 0); - this.attack1InfoPanelIconLevel = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconLevel", 0); - - this.attack2Icon = this.rootFrame.createSimpleFrame("SimpleInfoPanelIconDamage", this.simpleInfoPanelUnitDetail, - 1); - this.attack2Icon - .addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, FramePoint.TOPLEFT, - GameUI.convertX(this.uiViewport, 0.1f), GameUI.convertY(this.uiViewport, -0.030125f))); - this.attack2IconBackdrop = (TextureFrame) this.rootFrame.getFrameByName("InfoPanelIconBackdrop", 1); - this.attack2InfoPanelIconValue = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconValue", 1); - this.attack2InfoPanelIconLevel = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconLevel", 1); - - this.armorIcon = this.rootFrame.createSimpleFrame("SimpleInfoPanelIconArmor", this.simpleInfoPanelUnitDetail, - 1); - this.armorIcon.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, FramePoint.TOPLEFT, - GameUI.convertX(this.uiViewport, 0f), GameUI.convertY(this.uiViewport, -0.06025f))); - this.armorIconBackdrop = (TextureFrame) this.rootFrame.getFrameByName("InfoPanelIconBackdrop", 0); - this.armorInfoPanelIconValue = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconValue", 0); - this.armorInfoPanelIconLevel = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconLevel", 0); - - this.inventoryCover = this.rootFrame.createSimpleFrame("SmashConsoleInventoryCover", this.rootFrame, 0); - - this.errorMessageFrame = this.rootFrame.createStringFrame("SmashErrorMessageFrame", this.consoleUI, - new Color(0xFFFFCC00), TextJustify.LEFT, TextJustify.MIDDLE, 0.014f); - this.errorMessageFrame.addAnchor(new AnchorDefinition(FramePoint.BOTTOMLEFT, - GameUI.convertX(this.uiViewport, 0.275f), GameUI.convertY(this.uiViewport, 0.275f))); - this.errorMessageFrame.setWidth(GameUI.convertX(this.uiViewport, 0.25f)); - - int commandButtonIndex = 0; - for (int j = 0; j < COMMAND_CARD_HEIGHT; j++) { - for (int i = 0; i < COMMAND_CARD_WIDTH; i++) { - final CommandCardIcon commandCardIcon = new CommandCardIcon("SmashCommandButton_" + commandButtonIndex, - this.rootFrame, this); - this.rootFrame.add(commandCardIcon); - final TextureFrame iconFrame = this.rootFrame.createTextureFrame( - "SmashCommandButton_" + (commandButtonIndex) + "_Icon", this.rootFrame, false, null); - final TextureFrame activeHighlightFrame = this.rootFrame.createTextureFrame( - "SmashCommandButton_" + (commandButtonIndex) + "_ActiveHighlight", this.rootFrame, true, null, - FilterMode.ADDALPHA); - final SpriteFrame cooldownFrame = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", - "SmashCommandButton_" + (commandButtonIndex) + "_Cooldown", this.rootFrame, "", 0); - final SpriteFrame autocastFrame = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", - "SmashCommandButton_" + (commandButtonIndex) + "_Autocast", this.rootFrame, "", 0); - commandCardIcon.addAnchor(new AnchorDefinition(FramePoint.BOTTOMLEFT, - GameUI.convertX(this.uiViewport, 0.6175f + (0.0434f * i)), - GameUI.convertY(this.uiViewport, 0.095f - (0.044f * j)))); - commandCardIcon.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); - commandCardIcon.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); - iconFrame.addSetPoint(new SetPoint(FramePoint.CENTER, commandCardIcon, FramePoint.CENTER, 0, 0)); - iconFrame.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); - iconFrame.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); - iconFrame.setTexture(ImageUtils.DEFAULT_ICON_PATH, this.rootFrame); - activeHighlightFrame - .addSetPoint(new SetPoint(FramePoint.CENTER, commandCardIcon, FramePoint.CENTER, 0, 0)); - activeHighlightFrame.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); - activeHighlightFrame.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); - activeHighlightFrame.setTexture("CommandButtonActiveHighlight", this.rootFrame); - cooldownFrame.addSetPoint(new SetPoint(FramePoint.CENTER, commandCardIcon, FramePoint.CENTER, 0, 0)); - this.rootFrame.setSpriteFrameModel(cooldownFrame, this.rootFrame.getSkinField("CommandButtonCooldown")); - cooldownFrame.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); - cooldownFrame.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); - autocastFrame.addSetPoint(new SetPoint(FramePoint.CENTER, commandCardIcon, FramePoint.CENTER, 0, 0)); - this.rootFrame.setSpriteFrameModel(autocastFrame, this.rootFrame.getSkinField("CommandButtonAutocast")); - autocastFrame.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); - autocastFrame.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); - commandCardIcon.set(iconFrame, activeHighlightFrame, cooldownFrame, autocastFrame); - this.commandCard[j][i] = commandCardIcon; - commandCardIcon.setCommandButton(null); - commandButtonIndex++; - } - } - - this.cursorFrame = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", "SmashCursorFrame", this.rootFrame, - "", 0); - this.rootFrame.setSpriteFrameModel(this.cursorFrame, this.rootFrame.getSkinField("Cursor")); - this.cursorFrame.setSequence("Normal"); - this.cursorFrame.setZDepth(1.0f); - Gdx.input.setCursorCatched(true); - - this.meleeUIMinimap = createMinimap(this.war3MapViewer); - - this.rootFrame.positionBounds(this.uiViewport); - selectUnit(null); - } - - @Override - public void startUsingAbility(final int abilityHandleId, final int orderId, final boolean rightClick) { - // TODO not O(N) - CAbilityView abilityToUse = null; - for (final CAbility ability : this.selectedUnit.getSimulationUnit().getAbilities()) { - if (ability.getHandleId() == abilityHandleId) { - abilityToUse = ability; - break; - } - } - if (abilityToUse != null) { - final StringMsgAbilityActivationReceiver stringMsgActivationReceiver = StringMsgAbilityActivationReceiver - .getInstance().reset(); - abilityToUse.checkCanUse(this.war3MapViewer.simulation, this.selectedUnit.getSimulationUnit(), orderId, - stringMsgActivationReceiver); - if (!stringMsgActivationReceiver.isUseOk()) { - showCommandError(stringMsgActivationReceiver.getMessage()); - } - else { - final BooleanAbilityTargetCheckReceiver noTargetReceiver = BooleanAbilityTargetCheckReceiver - .getInstance().reset(); - abilityToUse.checkCanTargetNoTarget(this.war3MapViewer.simulation, - this.selectedUnit.getSimulationUnit(), orderId, noTargetReceiver); - if (noTargetReceiver.isTargetable()) { - this.unitOrderListener.issueImmediateOrder(this.selectedUnit.getSimulationUnit().getHandleId(), - abilityHandleId, orderId, isShiftDown()); - } - else { - this.activeCommand = abilityToUse; - this.activeCommandOrderId = orderId; - this.activeCommandUnit = this.selectedUnit; - clearAndRepopulateCommandCard(); - } - } - } - else { - this.unitOrderListener.issueImmediateOrder(this.selectedUnit.getSimulationUnit().getHandleId(), - abilityHandleId, orderId, isShiftDown()); - } - if (rightClick) { - this.war3MapViewer.getUiSounds().getSound("AutoCastButtonClick").play(this.uiScene.audioContext, 0, 0); - } - } - - @Override - public void openMenu(final int orderId) { - if (orderId == 0) { - this.subMenuOrderIdStack.clear(); - this.activeCommandUnit = null; - this.activeCommand = null; - this.activeCommandOrderId = -1; - } - else { - this.subMenuOrderIdStack.add(orderId); - } - clearAndRepopulateCommandCard(); - } - - public void showCommandError(final String message) { - this.errorMessageFrame.setText(message); - } - - public void update(final float deltaTime) { - this.portrait.update(); - - final int baseMouseX = Gdx.input.getX(); - int mouseX = baseMouseX; - final int baseMouseY = Gdx.input.getY(); - int mouseY = baseMouseY; - final int minX = this.uiViewport.getScreenX(); - final int maxX = minX + this.uiViewport.getScreenWidth(); - final int minY = this.uiViewport.getScreenY(); - final int maxY = minY + this.uiViewport.getScreenHeight(); - final boolean left = mouseX <= (minX + 3); - final boolean right = mouseX >= (maxX - 3); - final boolean up = mouseY <= (minY + 3); - final boolean down = mouseY >= (maxY - 3); - this.cameraManager.applyVelocity(deltaTime, up, down, left, right); - - mouseX = Math.max(minX, Math.min(maxX, mouseX)); - mouseY = Math.max(minY, Math.min(maxY, mouseY)); - if (Gdx.input.isCursorCatched()) { - Gdx.input.setCursorPosition(mouseX, mouseY); - } - - screenCoordsVector.set(mouseX, mouseY); - this.uiViewport.unproject(screenCoordsVector); - this.cursorFrame.setFramePointX(FramePoint.LEFT, screenCoordsVector.x); - this.cursorFrame.setFramePointY(FramePoint.BOTTOM, screenCoordsVector.y); - - if (this.activeCommand != null) { - if (this.activeCommand instanceof AbstractCAbilityBuild) { - boolean justLoaded = false; - if (this.cursorModelInstance == null) { - final MutableObjectData unitData = this.war3MapViewer.getAllObjectData().getUnits(); - final War3ID buildingTypeId = new War3ID(this.activeCommandOrderId); - final String unitModelPath = this.war3MapViewer.getUnitModelPath(unitData.get(buildingTypeId)); - final MdxModel model = (MdxModel) this.war3MapViewer.load(unitModelPath, - this.war3MapViewer.mapPathSolver, this.war3MapViewer.solverParams); - this.cursorModelInstance = (MdxComplexInstance) model.addInstance(); - this.cursorModelInstance.setVertexColor(new float[] { 1, 1, 1, 0.5f }); - this.cursorModelInstance.rotate(RenderUnit.tempQuat.setFromAxis(RenderMathUtils.VEC3_UNIT_Z, - this.war3MapViewer.simulation.getGameplayConstants().getBuildingAngle())); - this.cursorModelInstance.setAnimationSpeed(0f); - justLoaded = true; - final CUnitType buildingUnitType = this.war3MapViewer.simulation.getUnitData() - .getUnitType(buildingTypeId); - this.cursorModelPathing = buildingUnitType.getBuildingPathingPixelMap(); - } - this.war3MapViewer.getClickLocation(clickLocationTemp, baseMouseX, - Gdx.graphics.getHeight() - baseMouseY); - if (this.cursorModelPathing != null) { - clickLocationTemp.x = Math.round(clickLocationTemp.x / 64f) * 64f; - clickLocationTemp.y = Math.round(clickLocationTemp.y / 64f) * 64f; - clickLocationTemp.z = this.war3MapViewer.terrain.getGroundHeight(clickLocationTemp.x, - clickLocationTemp.y); - } - this.cursorModelInstance.setLocation(clickLocationTemp); - SequenceUtils.randomSequence(this.cursorModelInstance, PrimaryTag.STAND); - this.cursorFrame.setVisible(false); - if (justLoaded) { - this.cursorModelInstance.setScene(this.war3MapViewer.worldScene); - } - } - else { - if (this.cursorModelInstance != null) { - this.cursorModelInstance.detach(); - this.cursorModelInstance = null; - this.cursorFrame.setVisible(true); - } - this.cursorFrame.setSequence("Target"); - } - } - else { - if (this.cursorModelInstance != null) { - this.cursorModelInstance.detach(); - this.cursorModelInstance = null; - this.cursorFrame.setVisible(true); - } - if (down) { - if (left) { - this.cursorFrame.setSequence("Scroll Down Left"); - } - else if (right) { - this.cursorFrame.setSequence("Scroll Down Right"); - } - else { - this.cursorFrame.setSequence("Scroll Down"); - } - } - else if (up) { - if (left) { - this.cursorFrame.setSequence("Scroll Up Left"); - } - else if (right) { - this.cursorFrame.setSequence("Scroll Up Right"); - } - else { - this.cursorFrame.setSequence("Scroll Up"); - } - } - else if (left) { - this.cursorFrame.setSequence("Scroll Left"); - } - else if (right) { - this.cursorFrame.setSequence("Scroll Right"); - } - else { - this.cursorFrame.setSequence("Normal"); - } - } - final float groundHeight = Math.max( - this.war3MapViewer.terrain.getGroundHeight(this.cameraManager.target.x, this.cameraManager.target.y), - this.war3MapViewer.terrain.getWaterHeight(this.cameraManager.target.x, this.cameraManager.target.y)); - this.cameraManager.updateTargetZ(groundHeight); - this.cameraManager.updateCamera(); - } - - public void render(final SpriteBatch batch, final BitmapFont font20, final GlyphLayout glyphLayout) { - this.rootFrame.render(batch, font20, glyphLayout); - if (this.selectedUnit != null) { - font20.setColor(Color.WHITE); - - } - - this.meleeUIMinimap.render(batch, this.war3MapViewer.units); - this.timeIndicator.setFrameByRatio(this.war3MapViewer.simulation.getGameTimeOfDay() - / this.war3MapViewer.simulation.getGameplayConstants().getGameDayHours()); - } - - public void portraitTalk() { - this.portrait.talk(); - } - - private final class ActiveCommandUnitTargetFilter implements CUnitFilterFunction { - @Override - public boolean call(final CUnit unit) { - final BooleanAbilityTargetCheckReceiver targetReceiver = BooleanAbilityTargetCheckReceiver - .getInstance(); - MeleeUI.this.activeCommand.checkCanTarget(MeleeUI.this.war3MapViewer.simulation, - MeleeUI.this.activeCommandUnit.getSimulationUnit(), MeleeUI.this.activeCommandOrderId, unit, - targetReceiver); - return targetReceiver.isTargetable(); - } - } - - private static final class Portrait { - private MdxComplexInstance modelInstance; - private final PortraitCameraManager portraitCameraManager; - private final Scene portraitScene; - - public Portrait(final War3MapViewer war3MapViewer, final Scene portraitScene) { - this.portraitScene = portraitScene; - this.portraitCameraManager = new PortraitCameraManager(); - this.portraitCameraManager.setupCamera(this.portraitScene); - this.portraitScene.camera.viewport(new Rectangle(100, 0, 6400, 48)); - } - - public void update() { - this.portraitCameraManager.updateCamera(); - if ((this.modelInstance != null) - && (this.modelInstance.sequenceEnded || (this.modelInstance.sequence == -1))) { - SequenceUtils.randomSequence(this.modelInstance, PrimaryTag.PORTRAIT, SequenceUtils.EMPTY, true); - } - } - - public void talk() { - SequenceUtils.randomSequence(this.modelInstance, PrimaryTag.PORTRAIT, SequenceUtils.TALK, true); - } - - public void setSelectedUnit(final RenderUnit unit) { - if (unit == null) { - if (this.modelInstance != null) { - this.portraitScene.removeInstance(this.modelInstance); - } - this.modelInstance = null; - this.portraitCameraManager.setModelInstance(null, null); - } - else { - final MdxModel portraitModel = unit.portraitModel; - if (portraitModel != null) { - if (this.modelInstance != null) { - this.portraitScene.removeInstance(this.modelInstance); - } - this.modelInstance = (MdxComplexInstance) portraitModel.addInstance(); - this.portraitCameraManager.setModelInstance(this.modelInstance, portraitModel); - this.modelInstance.setSequenceLoopMode(SequenceLoopMode.NEVER_LOOP); - this.modelInstance.setScene(this.portraitScene); - this.modelInstance.setVertexColor(unit.instance.vertexColor); - this.modelInstance.setTeamColor(unit.playerIndex); - } - } - } - } - - public void selectUnit(RenderUnit unit) { - this.subMenuOrderIdStack.clear(); - if ((unit != null) && unit.getSimulationUnit().isDead()) { - unit = null; - } - if (this.selectedUnit != null) { - this.selectedUnit.getSimulationUnit().removeStateListener(this); - } - this.portrait.setSelectedUnit(unit); - this.selectedUnit = unit; - clearCommandCard(); - if (unit == null) { - this.simpleNameValue.setText(""); - this.unitLifeText.setText(""); - this.unitManaText.setText(""); - this.simpleClassValue.setText(""); - this.simpleBuildingActionLabel.setText(""); - this.attack1Icon.setVisible(false); - this.attack2Icon.setVisible(false); - this.attack1InfoPanelIconLevel.setText(""); - this.attack2InfoPanelIconLevel.setText(""); - this.armorIcon.setVisible(false); - this.armorInfoPanelIconLevel.setText(""); - } - else { - unit.getSimulationUnit().addStateListener(this); - this.simpleNameValue.setText(unit.getSimulationUnit().getUnitType().getName()); - String classText = null; - for (final CUnitClassification classification : unit.getSimulationUnit().getClassifications()) { - if ((classification == CUnitClassification.MECHANICAL) - && unit.getSimulationUnit().getUnitType().isBuilding()) { - // buildings dont display MECHANICAL - continue; - } - if (classification.getDisplayName() != null) { - classText = classification.getDisplayName(); - } - } - if (classText != null) { - this.simpleClassValue.setText(classText); - } - else { - this.simpleClassValue.setText(""); - } - this.unitLifeText.setText(FastNumberFormat.formatWholeNumber(unit.getSimulationUnit().getLife()) + " / " - + unit.getSimulationUnit().getMaximumLife()); - final int maximumMana = unit.getSimulationUnit().getMaximumMana(); - if (maximumMana > 0) { - this.unitManaText.setText( - FastNumberFormat.formatWholeNumber(unit.getSimulationUnit().getMana()) + " / " + maximumMana); - } - else { - this.unitManaText.setText(""); - } - this.simpleBuildingActionLabel.setText(""); - - final boolean anyAttacks = unit.getSimulationUnit().getUnitType().getAttacks().size() > 0; - final UIFrame localArmorIcon = this.armorIcon; - final TextureFrame localArmorIconBackdrop = this.armorIconBackdrop; - final StringFrame localArmorInfoPanelIconValue = this.armorInfoPanelIconValue; - if (anyAttacks) { - final CUnitAttack attackOne = unit.getSimulationUnit().getUnitType().getAttacks().get(0); - this.attack1Icon.setVisible(attackOne.isShowUI()); - this.attack1IconBackdrop.setTexture(this.damageBackdrops.getTexture(attackOne.getAttackType())); - this.attack1InfoPanelIconValue.setText(attackOne.getMinDamage() + " - " + attackOne.getMaxDamage()); - if (unit.getSimulationUnit().getUnitType().getAttacks().size() > 1) { - final CUnitAttack attackTwo = unit.getSimulationUnit().getUnitType().getAttacks().get(1); - this.attack2Icon.setVisible(attackTwo.isShowUI()); - this.attack2IconBackdrop.setTexture(this.damageBackdrops.getTexture(attackTwo.getAttackType())); - this.attack2InfoPanelIconValue.setText(attackTwo.getMinDamage() + " - " + attackTwo.getMaxDamage()); - } - else { - this.attack2Icon.setVisible(false); - } - - this.armorIcon.addSetPoint( - new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, FramePoint.TOPLEFT, - GameUI.convertX(this.uiViewport, 0f), GameUI.convertY(this.uiViewport, -0.06025f))); - this.armorIcon.positionBounds(this.uiViewport); - } - else { - this.attack1Icon.setVisible(false); - this.attack2Icon.setVisible(false); - - this.armorIcon.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, - FramePoint.TOPLEFT, 0, GameUI.convertY(this.uiViewport, -0.030125f))); - this.armorIcon.positionBounds(this.uiViewport); - } - - localArmorIcon.setVisible(true); - final Texture defenseTexture = this.defenseBackdrops - .getTexture(unit.getSimulationUnit().getUnitType().getDefenseType()); - if (defenseTexture == null) { - throw new RuntimeException( - unit.getSimulationUnit().getUnitType().getDefenseType() + " can't find texture!"); - } - localArmorIconBackdrop.setTexture(defenseTexture); - localArmorInfoPanelIconValue.setText(Integer.toString(unit.getSimulationUnit().getDefense())); - unit.populateCommandCard(this.war3MapViewer.simulation, this, this.war3MapViewer.getAbilityDataUI(), - getSubMenuOrderId()); - } - } - - private void clearCommandCard() { - for (int j = 0; j < COMMAND_CARD_HEIGHT; j++) { - for (int i = 0; i < COMMAND_CARD_WIDTH; i++) { - this.commandCard[j][i].setCommandButton(null); - } - } - } - - @Override - public void commandButton(final int buttonPositionX, final int buttonPositionY, final Texture icon, - final int abilityHandleId, final int orderId, final int autoCastId, final boolean active, - final boolean autoCastActive, final boolean menuButton) { - final int x = Math.max(0, Math.min(COMMAND_CARD_WIDTH - 1, buttonPositionX)); - final int y = Math.max(0, Math.min(COMMAND_CARD_HEIGHT - 1, buttonPositionY)); - this.commandCard[y][x].setCommandButtonData(icon, abilityHandleId, orderId, autoCastId, active, autoCastActive, - menuButton); - } - - public void resize(final Rectangle viewport) { - this.cameraManager.resize(viewport); - positionPortrait(); - } - - public void positionPortrait() { - this.projectionTemp1.x = 422 * this.widthRatioCorrection; - this.projectionTemp1.y = 57 * this.heightRatioCorrection; - this.projectionTemp2.x = (422 + 167) * this.widthRatioCorrection; - this.projectionTemp2.y = (57 + 170) * this.heightRatioCorrection; - this.uiViewport.project(this.projectionTemp1); - this.uiViewport.project(this.projectionTemp2); - - this.tempRect.x = this.projectionTemp1.x + this.uiViewport.getScreenX(); - this.tempRect.y = this.projectionTemp1.y + this.uiViewport.getScreenY(); - this.tempRect.width = this.projectionTemp2.x - this.projectionTemp1.x; - this.tempRect.height = this.projectionTemp2.y - this.projectionTemp1.y; - this.portrait.portraitScene.camera.viewport(this.tempRect); - } - - private static final class InfoPanelIconBackdrops { - private final Texture[] damageBackdropTextures; - - public InfoPanelIconBackdrops(final CodeKeyType[] attackTypes, final GameUI gameUI, final String prefix, - final String suffix) { - this.damageBackdropTextures = new Texture[attackTypes.length]; - for (int index = 0; index < attackTypes.length; index++) { - final CodeKeyType attackType = attackTypes[index]; - String skinLookupKey = "InfoPanelIcon" + prefix + attackType.getCodeKey() + suffix; - final Texture suffixTexture = gameUI.loadTexture(gameUI.getSkinField(skinLookupKey)); - if (suffixTexture != null) { - this.damageBackdropTextures[index] = suffixTexture; - } - else { - skinLookupKey = "InfoPanelIcon" + prefix + attackType.getCodeKey(); - this.damageBackdropTextures[index] = gameUI.loadTexture(gameUI.getSkinField(skinLookupKey)); - } - } - } - - public Texture getTexture(final CodeKeyType attackType) { - if (attackType != null) { - final int ordinal = attackType.ordinal(); - if ((ordinal >= 0) && (ordinal < this.damageBackdropTextures.length)) { - return this.damageBackdropTextures[ordinal]; - } - } - return this.damageBackdropTextures[0]; - } - - private static String getSuffix(final CAttackType attackType) { - switch (attackType) { - case CHAOS: - return "Chaos"; - case HERO: - return "Hero"; - case MAGIC: - return "Magic"; - case NORMAL: - return "Normal"; - case PIERCE: - return "Pierce"; - case SIEGE: - return "Siege"; - case SPELLS: - return "Magic"; - case UNKNOWN: - return "Unknown"; - default: - throw new IllegalArgumentException("Unknown attack type: " + attackType); - } - - } - } - - @Override - public void lifeChanged() { - if (this.selectedUnit.getSimulationUnit().isDead()) { - selectUnit(null); - } - else { - this.unitLifeText - .setText(FastNumberFormat.formatWholeNumber(this.selectedUnit.getSimulationUnit().getLife()) + " / " - + this.selectedUnit.getSimulationUnit().getMaximumLife()); - } - } - - @Override - public void ordersChanged(final int abilityHandleId, final int orderId) { - clearAndRepopulateCommandCard(); - } - - private void clearAndRepopulateCommandCard() { - clearCommandCard(); - final AbilityDataUI abilityDataUI = this.war3MapViewer.getAbilityDataUI(); - final int menuOrderId = getSubMenuOrderId(); - if (this.activeCommand != null) { - final IconUI cancelUI = abilityDataUI.getCancelUI(); - this.commandButton(cancelUI.getButtonPositionX(), cancelUI.getButtonPositionY(), cancelUI.getIcon(), 0, - menuOrderId, 0, false, false, true); - } - else { - if (menuOrderId != 0) { - final int exitOrderId = this.subMenuOrderIdStack.size() > 1 - ? this.subMenuOrderIdStack.get(this.subMenuOrderIdStack.size() - 2) - : 0; - final IconUI cancelUI = abilityDataUI.getCancelUI(); - this.commandButton(cancelUI.getButtonPositionX(), cancelUI.getButtonPositionY(), cancelUI.getIcon(), 0, - exitOrderId, 0, false, false, true); - } - this.selectedUnit.populateCommandCard(this.war3MapViewer.simulation, this, abilityDataUI, menuOrderId); - } - } - - private int getSubMenuOrderId() { - return this.subMenuOrderIdStack.isEmpty() ? 0 - : this.subMenuOrderIdStack.get(this.subMenuOrderIdStack.size() - 1); - } - - public RenderUnit getSelectedUnit() { - return this.selectedUnit; - } - - public boolean keyDown(final int keycode) { - return this.cameraManager.keyDown(keycode); - } - - public boolean keyUp(final int keycode) { - return this.cameraManager.keyUp(keycode); - } - - public void scrolled(final int amount) { - this.cameraManager.scrolled(amount); - } - - public boolean touchDown(final int screenX, final int screenY, final float worldScreenY, final int button) { - screenCoordsVector.set(screenX, screenY); - this.uiViewport.unproject(screenCoordsVector); - if (this.meleeUIMinimap.containsMouse(screenCoordsVector.x, screenCoordsVector.y)) { - final Vector2 worldPoint = this.meleeUIMinimap.getWorldPointFromScreen(screenCoordsVector.x, - screenCoordsVector.y); - this.cameraManager.target.x = worldPoint.x; - this.cameraManager.target.y = worldPoint.y; - return true; - } - final UIFrame clickedUIFrame = this.rootFrame.touchDown(screenCoordsVector.x, screenCoordsVector.y, button); - if (clickedUIFrame == null) { - // try to interact with world - if (this.activeCommand != null) { - if (button == Input.Buttons.RIGHT) { - this.activeCommandUnit = null; - this.activeCommand = null; - this.activeCommandOrderId = -1; - clearAndRepopulateCommandCard(); - } - else { - final RenderUnit rayPickUnit = this.war3MapViewer.rayPickUnit(screenX, worldScreenY, - this.activeCommandUnitTargetFilter); - final boolean shiftDown = isShiftDown(); - if (rayPickUnit != null) { - this.unitOrderListener.issueTargetOrder( - this.activeCommandUnit.getSimulationUnit().getHandleId(), - this.activeCommand.getHandleId(), this.activeCommandOrderId, - rayPickUnit.getSimulationUnit().getHandleId(), shiftDown); - if (getSelectedUnit().soundset.yesAttack - .playUnitResponse(this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { - portraitTalk(); - } - this.selectedSoundCount = 0; - if (!shiftDown) { - this.subMenuOrderIdStack.clear(); - this.activeCommandUnit = null; - this.activeCommand = null; - this.activeCommandOrderId = -1; - clearAndRepopulateCommandCard(); - } - } - else { - this.war3MapViewer.getClickLocation(clickLocationTemp, screenX, (int) worldScreenY); - clickLocationTemp2.set(clickLocationTemp.x, clickLocationTemp.y); - - this.activeCommand.checkCanTarget(this.war3MapViewer.simulation, - this.activeCommandUnit.getSimulationUnit(), this.activeCommandOrderId, - clickLocationTemp2, PointAbilityTargetCheckReceiver.INSTANCE); - final Vector2 target = PointAbilityTargetCheckReceiver.INSTANCE.getTarget(); - if (target != null) { - if (this.activeCommand instanceof CAbilityAttack) { - this.war3MapViewer.showConfirmation(clickLocationTemp, 1, 0, 0); - } - else { - this.war3MapViewer.showConfirmation(clickLocationTemp, 0, 1, 0); - } - this.unitOrderListener.issuePointOrder( - this.activeCommandUnit.getSimulationUnit().getHandleId(), - this.activeCommand.getHandleId(), this.activeCommandOrderId, clickLocationTemp2.x, - clickLocationTemp2.y, shiftDown); - if (getSelectedUnit().soundset.yes - .playUnitResponse(this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { - portraitTalk(); - } - this.selectedSoundCount = 0; - if (this.activeCommand instanceof AbstractCAbilityBuild) { - this.war3MapViewer.getUiSounds().getSound("PlaceBuildingDefault") - .play(this.uiScene.audioContext, 0, 0); - } - if (!shiftDown) { - this.subMenuOrderIdStack.clear(); - this.activeCommandUnit = null; - this.activeCommand = null; - this.activeCommandOrderId = -1; - clearAndRepopulateCommandCard(); - } - - } - } - } - } - else { - if (button == Input.Buttons.RIGHT) { - if (getSelectedUnit() != null) { - final RenderUnit rayPickUnit = this.war3MapViewer.rayPickUnit(screenX, worldScreenY); - if ((rayPickUnit != null) && !rayPickUnit.getSimulationUnit().isDead()) { - boolean ordered = false; - for (final RenderUnit unit : this.war3MapViewer.selected) { - for (final CAbility ability : unit.getSimulationUnit().getAbilities()) { - ability.checkCanTarget(this.war3MapViewer.simulation, unit.getSimulationUnit(), - OrderIds.smart, rayPickUnit.getSimulationUnit(), - CWidgetAbilityTargetCheckReceiver.INSTANCE); - final CWidget targetWidget = CWidgetAbilityTargetCheckReceiver.INSTANCE.getTarget(); - if (targetWidget != null) { - this.unitOrderListener.issueTargetOrder(unit.getSimulationUnit().getHandleId(), - ability.getHandleId(), OrderIds.smart, targetWidget.getHandleId(), - isShiftDown()); - ordered = true; - } - } - - } - if (ordered) { - if (getSelectedUnit().soundset.yesAttack.playUnitResponse( - this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { - portraitTalk(); - } - this.selectedSoundCount = 0; - } - } - else { - this.war3MapViewer.getClickLocation(clickLocationTemp, screenX, (int) worldScreenY); - this.war3MapViewer.showConfirmation(clickLocationTemp, 0, 1, 0); - clickLocationTemp2.set(clickLocationTemp.x, clickLocationTemp.y); - - boolean ordered = false; - for (final RenderUnit unit : this.war3MapViewer.selected) { - for (final CAbility ability : unit.getSimulationUnit().getAbilities()) { - ability.checkCanUse(this.war3MapViewer.simulation, unit.getSimulationUnit(), - OrderIds.smart, BooleanAbilityActivationReceiver.INSTANCE); - if (BooleanAbilityActivationReceiver.INSTANCE.isOk()) { - ability.checkCanTarget(this.war3MapViewer.simulation, unit.getSimulationUnit(), - OrderIds.smart, clickLocationTemp2, - PointAbilityTargetCheckReceiver.INSTANCE); - final Vector2 target = PointAbilityTargetCheckReceiver.INSTANCE.getTarget(); - if (target != null) { - this.unitOrderListener.issuePointOrder( - unit.getSimulationUnit().getHandleId(), ability.getHandleId(), - OrderIds.smart, clickLocationTemp2.x, clickLocationTemp2.y, - isShiftDown()); - ordered = true; - } - } - } - - } - - if (ordered) { - if (getSelectedUnit().soundset.yes.playUnitResponse( - this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { - portraitTalk(); - } - this.selectedSoundCount = 0; - } - } - } - } - else { - final List selectedUnits = this.war3MapViewer.selectUnit(screenX, worldScreenY, false); - if (!selectedUnits.isEmpty()) { - final RenderUnit unit = selectedUnits.get(0); - final boolean selectionChanged = getSelectedUnit() != unit; - boolean playedNewSound = false; - if (selectionChanged) { - this.selectedSoundCount = 0; - } - if (unit.soundset != null) { - UnitSound ackSoundToPlay = unit.soundset.what; - final int pissedSoundCount = unit.soundset.pissed.getSoundCount(); - int soundIndex; - if ((this.selectedSoundCount >= 3) && (pissedSoundCount > 0)) { - soundIndex = this.selectedSoundCount - 3; - ackSoundToPlay = unit.soundset.pissed; - } - else { - soundIndex = (int) (Math.random() * ackSoundToPlay.getSoundCount()); - } - if (ackSoundToPlay.playUnitResponse(this.war3MapViewer.worldScene.audioContext, unit, - soundIndex)) { - this.selectedSoundCount++; - if ((this.selectedSoundCount - 3) >= pissedSoundCount) { - this.selectedSoundCount = 0; - } - playedNewSound = true; - } - } - if (selectionChanged) { - selectUnit(unit); - } - if (playedNewSound) { - portraitTalk(); - } - } - else { - selectUnit(null); - } - } - } - } - else { - if (clickedUIFrame instanceof CommandCardIcon) { - this.mouseDownUIFrame = (CommandCardIcon) clickedUIFrame; - this.mouseDownUIFrame.mouseDown(this.uiViewport); - } - } - return false; - } - - public boolean touchUp(final int screenX, final int screenY, final float worldScreenY, final int button) { - screenCoordsVector.set(screenX, screenY); - this.uiViewport.unproject(screenCoordsVector); - final UIFrame clickedUIFrame = this.rootFrame.touchUp(screenCoordsVector.x, screenCoordsVector.y, button); - if (this.mouseDownUIFrame != null) { - if (clickedUIFrame == this.mouseDownUIFrame) { - this.mouseDownUIFrame.onClick(button); - this.war3MapViewer.getUiSounds().getSound("InterfaceClick").play(this.uiScene.audioContext, 0, 0); - } - this.mouseDownUIFrame.mouseUp(this.uiViewport); - } - this.mouseDownUIFrame = null; - return false; - } - - private static boolean isShiftDown() { - return Gdx.input.isKeyPressed(Input.Keys.SHIFT_LEFT) || Gdx.input.isKeyPressed(Input.Keys.SHIFT_RIGHT); - } - - public boolean touchDragged(final int screenX, final int screenY, final float worldScreenY, final int pointer) { - screenCoordsVector.set(screenX, screenY); - this.uiViewport.unproject(screenCoordsVector); - - if (this.meleeUIMinimap.containsMouse(screenCoordsVector.x, screenCoordsVector.y)) { - final Vector2 worldPoint = this.meleeUIMinimap.getWorldPointFromScreen(screenCoordsVector.x, - screenCoordsVector.y); - this.cameraManager.target.x = worldPoint.x; - this.cameraManager.target.y = worldPoint.y; - } - return false; - } - - public float getHeightRatioCorrection() { - return this.heightRatioCorrection; - } + public static final float DEFAULT_COMMAND_CARD_ICON_WIDTH = 0.039f; + public static final float DEFAULT_COMMAND_CARD_ICON_PRESSED_WIDTH = 0.037f; + private static final int COMMAND_CARD_WIDTH = 4; + private static final int COMMAND_CARD_HEIGHT = 3; + + private static final Vector2 screenCoordsVector = new Vector2(); + private static final Vector3 clickLocationTemp = new Vector3(); + private static final Vector2 clickLocationTemp2 = new Vector2(); + private final DataSource dataSource; + private final ExtendViewport uiViewport; + private final FreeTypeFontGenerator fontGenerator; + private final Scene uiScene; + private final Scene portraitScene; + private final GameCameraManager cameraManager; + private final War3MapViewer war3MapViewer; + private final RootFrameListener rootFrameListener; + private GameUI rootFrame; + private UIFrame consoleUI; + private UIFrame resourceBar; + private StringFrame resourceBarGoldText; + private StringFrame resourceBarLumberText; + private StringFrame resourceBarSupplyText; + private StringFrame resourceBarUpkeepText; + private SpriteFrame timeIndicator; + private UIFrame unitPortrait; + private StringFrame unitLifeText; + private StringFrame unitManaText; + private Portrait portrait; + private final Rectangle tempRect = new Rectangle(); + private final Vector2 projectionTemp1 = new Vector2(); + private final Vector2 projectionTemp2 = new Vector2(); + private UIFrame simpleInfoPanelUnitDetail; + private StringFrame simpleNameValue; + private StringFrame simpleClassValue; + private StringFrame simpleBuildingActionLabel; + private UIFrame attack1Icon; + private TextureFrame attack1IconBackdrop; + private StringFrame attack1InfoPanelIconValue; + private StringFrame attack1InfoPanelIconLevel; + private UIFrame attack2Icon; + private TextureFrame attack2IconBackdrop; + private StringFrame attack2InfoPanelIconValue; + private StringFrame attack2InfoPanelIconLevel; + private UIFrame armorIcon; + private TextureFrame armorIconBackdrop; + private StringFrame armorInfoPanelIconValue; + private StringFrame armorInfoPanelIconLevel; + private InfoPanelIconBackdrops damageBackdrops; + private InfoPanelIconBackdrops defenseBackdrops; + private SpriteFrame buildTimeIndicator; + + private final CommandCardIcon[][] commandCard = new CommandCardIcon[COMMAND_CARD_HEIGHT][COMMAND_CARD_WIDTH]; + + private RenderUnit selectedUnit; + private final List subMenuOrderIdStack = new ArrayList<>(); + + // TODO remove this & replace with FDF + private final Texture activeButtonTexture; + private UIFrame inventoryCover; + private SpriteFrame cursorFrame; + private MeleeUIMinimap meleeUIMinimap; + private final CPlayerUnitOrderListener unitOrderListener; + private StringFrame errorMessageFrame; + + private CAbilityView activeCommand; + private int activeCommandOrderId; + private RenderUnit activeCommandUnit; + private MdxComplexInstance cursorModelInstance = null; + private BufferedImage cursorModelPathing; + + private int selectedSoundCount = 0; + private final ActiveCommandUnitTargetFilter activeCommandUnitTargetFilter; + + // TODO these corrections are used for old hardcoded UI stuff, we should + // probably remove them later + private final float widthRatioCorrection; + private final float heightRatioCorrection; + private CommandCardIcon mouseDownUIFrame; + + public MeleeUI(final DataSource dataSource, final ExtendViewport uiViewport, + final FreeTypeFontGenerator fontGenerator, final Scene uiScene, final Scene portraitScene, + final CameraPreset[] cameraPresets, final CameraRates cameraRates, final War3MapViewer war3MapViewer, + final RootFrameListener rootFrameListener, final CPlayerUnitOrderListener unitOrderListener) { + this.dataSource = dataSource; + this.uiViewport = uiViewport; + this.fontGenerator = fontGenerator; + this.uiScene = uiScene; + this.portraitScene = portraitScene; + this.war3MapViewer = war3MapViewer; + this.rootFrameListener = rootFrameListener; + this.unitOrderListener = unitOrderListener; + + this.cameraManager = new GameCameraManager(cameraPresets, cameraRates); + + this.cameraManager.setupCamera(war3MapViewer.worldScene); + if (this.war3MapViewer.startLocations[0] != null) { + this.cameraManager.target.x = this.war3MapViewer.startLocations[0].x; + this.cameraManager.target.y = this.war3MapViewer.startLocations[0].y; + } + + this.activeButtonTexture = ImageUtils.getBLPTexture(war3MapViewer.mapMpq, + "UI\\Widgets\\Console\\Human\\CommandButton\\human-activebutton.blp"); + this.activeCommandUnitTargetFilter = new ActiveCommandUnitTargetFilter(); + this.widthRatioCorrection = this.uiViewport.getMinWorldWidth() / 1600f; + this.heightRatioCorrection = this.uiViewport.getMinWorldHeight() / 1200f; + + } + + private MeleeUIMinimap createMinimap(final War3MapViewer war3MapViewer) { + final Rectangle minimapDisplayArea = new Rectangle(18.75f * this.widthRatioCorrection, + 13.75f * this.heightRatioCorrection, 278.75f * this.widthRatioCorrection, + 276.25f * this.heightRatioCorrection); + Texture minimapTexture = null; + if (war3MapViewer.dataSource.has("war3mapMap.tga")) { + try { + minimapTexture = ImageUtils.getTextureNoColorCorrection(TgaFile.readTGA("war3mapMap.tga", + war3MapViewer.dataSource.getResourceAsStream("war3mapMap.tga"))); + } catch (final IOException e) { + System.err.println("Could not load minimap TGA file"); + e.printStackTrace(); + } + } else if (war3MapViewer.dataSource.has("war3mapMap.blp")) { + try { + minimapTexture = ImageUtils + .getTexture(ImageIO.read(war3MapViewer.dataSource.getResourceAsStream("war3mapMap.blp"))); + } catch (final IOException e) { + System.err.println("Could not load minimap BLP file"); + e.printStackTrace(); + } + } + final Texture[] teamColors = new Texture[WarsmashConstants.MAX_PLAYERS]; + for (int i = 0; i < teamColors.length; i++) { + teamColors[i] = ImageUtils.getBLPTexture(war3MapViewer.dataSource, + "ReplaceableTextures\\" + ReplaceableIds.getPathString(1) + ReplaceableIds.getIdString(i) + ".blp"); + } + final Rectangle playableMapArea = war3MapViewer.terrain.getPlayableMapArea(); + return new MeleeUIMinimap(minimapDisplayArea, playableMapArea, minimapTexture, teamColors); + } + + /** + * Called "main" because this was originally written in JASS so that maps could + * override it, and I may convert it back to the JASS at some point. + */ + public void main() { + // ================================= + // Load skins and templates + // ================================= + this.rootFrame = new GameUI(this.dataSource, GameUI.loadSkin(this.dataSource, 0), this.uiViewport, + this.fontGenerator, this.uiScene, this.war3MapViewer); + this.rootFrameListener.onCreate(this.rootFrame); + try { + this.rootFrame.loadTOCFile("UI\\FrameDef\\FrameDef.toc"); + } catch (final IOException exc) { + throw new IllegalStateException("Unable to load FrameDef.toc", exc); + } + try { + this.rootFrame.loadTOCFile("UI\\FrameDef\\SmashFrameDef.toc"); + } catch (final IOException exc) { + throw new IllegalStateException("Unable to load SmashFrameDef.toc", exc); + } + this.damageBackdrops = new InfoPanelIconBackdrops(CAttackType.values(), this.rootFrame, "Damage", "Neutral"); + this.defenseBackdrops = new InfoPanelIconBackdrops(CDefenseType.values(), this.rootFrame, "Armor", "Neutral"); + + // ================================= + // Load major UI components + // ================================= + // Console UI is the background with the racial theme + this.consoleUI = this.rootFrame.createSimpleFrame("ConsoleUI", this.rootFrame, 0); + this.consoleUI.setSetAllPoints(true); + + // Resource bar is a 3 part bar with Gold, Lumber, and Food. + // Its template does not specify where to put it, so we must + // put it in the "TOPRIGHT" corner. + this.resourceBar = this.rootFrame.createSimpleFrame("ResourceBarFrame", this.consoleUI, 0); + this.resourceBar.addSetPoint(new SetPoint(FramePoint.TOPRIGHT, this.consoleUI, FramePoint.TOPRIGHT, 0, 0)); + this.resourceBarGoldText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarGoldText", 0); + this.resourceBarGoldText.setText("500"); + this.resourceBarLumberText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarLumberText", 0); + this.resourceBarLumberText.setText("150"); + this.resourceBarSupplyText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarSupplyText", 0); + this.resourceBarSupplyText.setText("12/100"); + this.resourceBarUpkeepText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarUpkeepText", 0); + this.resourceBarUpkeepText.setText("No Upkeep"); + this.resourceBarUpkeepText.setColor(Color.GREEN); + + // Create the Time Indicator (clock) + this.timeIndicator = (SpriteFrame) this.rootFrame.createFrame("TimeOfDayIndicator", this.rootFrame, 0, 0); + this.timeIndicator.setSequence(0); // play the stand + this.timeIndicator.setAnimationSpeed(0.0f); // do not advance automatically + + // Create the unit portrait stuff + this.portrait = new Portrait(this.war3MapViewer, this.portraitScene); + positionPortrait(); + this.unitPortrait = this.rootFrame.createSimpleFrame("UnitPortrait", this.consoleUI, 0); + this.unitLifeText = (StringFrame) this.rootFrame.getFrameByName("UnitPortraitHitPointText", 0); + this.unitManaText = (StringFrame) this.rootFrame.getFrameByName("UnitPortraitManaPointText", 0); + + this.simpleInfoPanelUnitDetail = this.rootFrame.createSimpleFrame("SimpleInfoPanelUnitDetail", this.consoleUI, + 0); + this.simpleInfoPanelUnitDetail + .addAnchor(new AnchorDefinition(FramePoint.BOTTOM, 0, GameUI.convertY(this.uiViewport, 0.0f))); + this.simpleInfoPanelUnitDetail.setWidth(GameUI.convertY(this.uiViewport, 0.180f)); + this.simpleInfoPanelUnitDetail.setHeight(GameUI.convertY(this.uiViewport, 0.105f)); + this.simpleNameValue = (StringFrame) this.rootFrame.getFrameByName("SimpleNameValue", 0); + this.simpleClassValue = (StringFrame) this.rootFrame.getFrameByName("SimpleClassValue", 0); + this.simpleBuildingActionLabel = (StringFrame) this.rootFrame.getFrameByName("SimpleBuildingActionLabel", 0); + + this.attack1Icon = this.rootFrame.createSimpleFrame("SimpleInfoPanelIconDamage", this.simpleInfoPanelUnitDetail, + 0); + this.attack1Icon.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, + FramePoint.TOPLEFT, 0, GameUI.convertY(this.uiViewport, -0.030125f))); + this.attack1IconBackdrop = (TextureFrame) this.rootFrame.getFrameByName("InfoPanelIconBackdrop", 0); + this.attack1InfoPanelIconValue = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconValue", 0); + this.attack1InfoPanelIconLevel = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconLevel", 0); + + this.attack2Icon = this.rootFrame.createSimpleFrame("SimpleInfoPanelIconDamage", this.simpleInfoPanelUnitDetail, + 1); + this.attack2Icon + .addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, FramePoint.TOPLEFT, + GameUI.convertX(this.uiViewport, 0.1f), GameUI.convertY(this.uiViewport, -0.030125f))); + this.attack2IconBackdrop = (TextureFrame) this.rootFrame.getFrameByName("InfoPanelIconBackdrop", 1); + this.attack2InfoPanelIconValue = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconValue", 1); + this.attack2InfoPanelIconLevel = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconLevel", 1); + + this.armorIcon = this.rootFrame.createSimpleFrame("SimpleInfoPanelIconArmor", this.simpleInfoPanelUnitDetail, + 1); + this.armorIcon.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, FramePoint.TOPLEFT, + GameUI.convertX(this.uiViewport, 0f), GameUI.convertY(this.uiViewport, -0.06025f))); + this.armorIconBackdrop = (TextureFrame) this.rootFrame.getFrameByName("InfoPanelIconBackdrop", 0); + this.armorInfoPanelIconValue = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconValue", 0); + this.armorInfoPanelIconLevel = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconLevel", 0); + + this.inventoryCover = this.rootFrame.createSimpleFrame("SmashConsoleInventoryCover", this.rootFrame, 0); + + this.errorMessageFrame = this.rootFrame.createStringFrame("SmashErrorMessageFrame", this.consoleUI, + new Color(0xFFFFCC00), TextJustify.LEFT, TextJustify.MIDDLE, 0.014f); + this.errorMessageFrame.addAnchor(new AnchorDefinition(FramePoint.BOTTOMLEFT, + GameUI.convertX(this.uiViewport, 0.275f), GameUI.convertY(this.uiViewport, 0.275f))); + this.errorMessageFrame.setWidth(GameUI.convertX(this.uiViewport, 0.25f)); + + int commandButtonIndex = 0; + for (int j = 0; j < COMMAND_CARD_HEIGHT; j++) { + for (int i = 0; i < COMMAND_CARD_WIDTH; i++) { + final CommandCardIcon commandCardIcon = new CommandCardIcon("SmashCommandButton_" + commandButtonIndex, + this.rootFrame, this); + this.rootFrame.add(commandCardIcon); + final TextureFrame iconFrame = this.rootFrame.createTextureFrame( + "SmashCommandButton_" + (commandButtonIndex) + "_Icon", this.rootFrame, false, null); + final TextureFrame activeHighlightFrame = this.rootFrame.createTextureFrame( + "SmashCommandButton_" + (commandButtonIndex) + "_ActiveHighlight", this.rootFrame, true, null, + FilterMode.ADDALPHA); + final SpriteFrame cooldownFrame = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", + "SmashCommandButton_" + (commandButtonIndex) + "_Cooldown", this.rootFrame, "", 0); + final SpriteFrame autocastFrame = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", + "SmashCommandButton_" + (commandButtonIndex) + "_Autocast", this.rootFrame, "", 0); + commandCardIcon.addAnchor(new AnchorDefinition(FramePoint.BOTTOMLEFT, + GameUI.convertX(this.uiViewport, 0.6175f + (0.0434f * i)), + GameUI.convertY(this.uiViewport, 0.095f - (0.044f * j)))); + commandCardIcon.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); + commandCardIcon.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); + iconFrame.addSetPoint(new SetPoint(FramePoint.CENTER, commandCardIcon, FramePoint.CENTER, 0, 0)); + iconFrame.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); + iconFrame.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); + iconFrame.setTexture(ImageUtils.DEFAULT_ICON_PATH, this.rootFrame); + activeHighlightFrame + .addSetPoint(new SetPoint(FramePoint.CENTER, commandCardIcon, FramePoint.CENTER, 0, 0)); + activeHighlightFrame.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); + activeHighlightFrame.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); + activeHighlightFrame.setTexture("CommandButtonActiveHighlight", this.rootFrame); + cooldownFrame.addSetPoint(new SetPoint(FramePoint.CENTER, commandCardIcon, FramePoint.CENTER, 0, 0)); + this.rootFrame.setSpriteFrameModel(cooldownFrame, this.rootFrame.getSkinField("CommandButtonCooldown")); + cooldownFrame.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); + cooldownFrame.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); + autocastFrame.addSetPoint(new SetPoint(FramePoint.CENTER, commandCardIcon, FramePoint.CENTER, 0, 0)); + this.rootFrame.setSpriteFrameModel(autocastFrame, this.rootFrame.getSkinField("CommandButtonAutocast")); + autocastFrame.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); + autocastFrame.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); + commandCardIcon.set(iconFrame, activeHighlightFrame, cooldownFrame, autocastFrame); + this.commandCard[j][i] = commandCardIcon; + commandCardIcon.setCommandButton(null); + commandButtonIndex++; + } + } + buildTimeIndicator = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", + "SmashBuildProgressBar", this.rootFrame, "", 0); + buildTimeIndicator.addSetPoint(new SetPoint(FramePoint.CENTER, simpleClassValue, FramePoint.CENTER, + GameUI.convertX(this.uiViewport, -0.04f), + GameUI.convertY(this.uiViewport, -0.025f))); + this.rootFrame.setSpriteFrameModel(buildTimeIndicator, this.rootFrame.getSkinField("BuildTimeIndicator")); + buildTimeIndicator.setAnimationSpeed(0); + + this.cursorFrame = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", "SmashCursorFrame", this.rootFrame, + "", 0); + this.rootFrame.setSpriteFrameModel(this.cursorFrame, this.rootFrame.getSkinField("Cursor")); + this.cursorFrame.setSequence("Normal"); + this.cursorFrame.setZDepth(1.0f); + Gdx.input.setCursorCatched(true); + + this.meleeUIMinimap = createMinimap(this.war3MapViewer); + + this.rootFrame.positionBounds(this.uiViewport); + selectUnit(null); + } + + @Override + public void startUsingAbility(final int abilityHandleId, final int orderId, final boolean rightClick) { + // TODO not O(N) + CAbilityView abilityToUse = null; + for (final CAbility ability : this.selectedUnit.getSimulationUnit().getAbilities()) { + if (ability.getHandleId() == abilityHandleId) { + abilityToUse = ability; + break; + } + } + if (abilityToUse != null) { + final StringMsgAbilityActivationReceiver stringMsgActivationReceiver = StringMsgAbilityActivationReceiver + .getInstance().reset(); + abilityToUse.checkCanUse(this.war3MapViewer.simulation, this.selectedUnit.getSimulationUnit(), orderId, + stringMsgActivationReceiver); + if (!stringMsgActivationReceiver.isUseOk()) { + showCommandError(stringMsgActivationReceiver.getMessage()); + } else { + final BooleanAbilityTargetCheckReceiver noTargetReceiver = BooleanAbilityTargetCheckReceiver + .getInstance().reset(); + abilityToUse.checkCanTargetNoTarget(this.war3MapViewer.simulation, + this.selectedUnit.getSimulationUnit(), orderId, noTargetReceiver); + if (noTargetReceiver.isTargetable()) { + this.unitOrderListener.issueImmediateOrder(this.selectedUnit.getSimulationUnit().getHandleId(), + abilityHandleId, orderId, isShiftDown()); + } else { + this.activeCommand = abilityToUse; + this.activeCommandOrderId = orderId; + this.activeCommandUnit = this.selectedUnit; + clearAndRepopulateCommandCard(); + } + } + } else { + this.unitOrderListener.issueImmediateOrder(this.selectedUnit.getSimulationUnit().getHandleId(), + abilityHandleId, orderId, isShiftDown()); + } + if (rightClick) { + this.war3MapViewer.getUiSounds().getSound("AutoCastButtonClick").play(this.uiScene.audioContext, 0, 0); + } + } + + @Override + public void openMenu(final int orderId) { + if (orderId == 0) { + this.subMenuOrderIdStack.clear(); + this.activeCommandUnit = null; + this.activeCommand = null; + this.activeCommandOrderId = -1; + } else { + this.subMenuOrderIdStack.add(orderId); + } + clearAndRepopulateCommandCard(); + } + + public void showCommandError(final String message) { + this.errorMessageFrame.setText(message); + } + + public void update(final float deltaTime) { + this.portrait.update(); + + final int baseMouseX = Gdx.input.getX(); + int mouseX = baseMouseX; + final int baseMouseY = Gdx.input.getY(); + int mouseY = baseMouseY; + final int minX = this.uiViewport.getScreenX(); + final int maxX = minX + this.uiViewport.getScreenWidth(); + final int minY = this.uiViewport.getScreenY(); + final int maxY = minY + this.uiViewport.getScreenHeight(); + final boolean left = mouseX <= (minX + 3); + final boolean right = mouseX >= (maxX - 3); + final boolean up = mouseY <= (minY + 3); + final boolean down = mouseY >= (maxY - 3); + this.cameraManager.applyVelocity(deltaTime, up, down, left, right); + + mouseX = Math.max(minX, Math.min(maxX, mouseX)); + mouseY = Math.max(minY, Math.min(maxY, mouseY)); + if (Gdx.input.isCursorCatched()) { + Gdx.input.setCursorPosition(mouseX, mouseY); + } + + screenCoordsVector.set(mouseX, mouseY); + this.uiViewport.unproject(screenCoordsVector); + this.cursorFrame.setFramePointX(FramePoint.LEFT, screenCoordsVector.x); + this.cursorFrame.setFramePointY(FramePoint.BOTTOM, screenCoordsVector.y); + + if (this.activeCommand != null) { + if (this.activeCommand instanceof AbstractCAbilityBuild) { + boolean justLoaded = false; + if (this.cursorModelInstance == null) { + final MutableObjectData unitData = this.war3MapViewer.getAllObjectData().getUnits(); + final War3ID buildingTypeId = new War3ID(this.activeCommandOrderId); + final String unitModelPath = this.war3MapViewer.getUnitModelPath(unitData.get(buildingTypeId)); + final MdxModel model = (MdxModel) this.war3MapViewer.load(unitModelPath, + this.war3MapViewer.mapPathSolver, this.war3MapViewer.solverParams); + this.cursorModelInstance = (MdxComplexInstance) model.addInstance(); + this.cursorModelInstance.setVertexColor(new float[]{1, 1, 1, 0.5f}); + this.cursorModelInstance.rotate(RenderUnit.tempQuat.setFromAxis(RenderMathUtils.VEC3_UNIT_Z, + this.war3MapViewer.simulation.getGameplayConstants().getBuildingAngle())); + this.cursorModelInstance.setAnimationSpeed(0f); + justLoaded = true; + final CUnitType buildingUnitType = this.war3MapViewer.simulation.getUnitData() + .getUnitType(buildingTypeId); + this.cursorModelPathing = buildingUnitType.getBuildingPathingPixelMap(); + } + this.war3MapViewer.getClickLocation(clickLocationTemp, baseMouseX, + Gdx.graphics.getHeight() - baseMouseY); + if (this.cursorModelPathing != null) { + clickLocationTemp.x = Math.round(clickLocationTemp.x / 64f) * 64f; + clickLocationTemp.y = Math.round(clickLocationTemp.y / 64f) * 64f; + clickLocationTemp.z = this.war3MapViewer.terrain.getGroundHeight(clickLocationTemp.x, + clickLocationTemp.y); + } + this.cursorModelInstance.setLocation(clickLocationTemp); + SequenceUtils.randomSequence(this.cursorModelInstance, PrimaryTag.STAND); + this.cursorFrame.setVisible(false); + if (justLoaded) { + this.cursorModelInstance.setScene(this.war3MapViewer.worldScene); + } + } else { + if (this.cursorModelInstance != null) { + this.cursorModelInstance.detach(); + this.cursorModelInstance = null; + this.cursorFrame.setVisible(true); + } + this.cursorFrame.setSequence("Target"); + } + } else { + if (this.cursorModelInstance != null) { + this.cursorModelInstance.detach(); + this.cursorModelInstance = null; + this.cursorFrame.setVisible(true); + } + if (down) { + if (left) { + this.cursorFrame.setSequence("Scroll Down Left"); + } else if (right) { + this.cursorFrame.setSequence("Scroll Down Right"); + } else { + this.cursorFrame.setSequence("Scroll Down"); + } + } else if (up) { + if (left) { + this.cursorFrame.setSequence("Scroll Up Left"); + } else if (right) { + this.cursorFrame.setSequence("Scroll Up Right"); + } else { + this.cursorFrame.setSequence("Scroll Up"); + } + } else if (left) { + this.cursorFrame.setSequence("Scroll Left"); + } else if (right) { + this.cursorFrame.setSequence("Scroll Right"); + } else { + this.cursorFrame.setSequence("Normal"); + } + } + if (this.buildTimeIndicator.isVisible() && this.selectedUnit != null) { + buildTimeIndicator.setFrameByRatio(Math.min(this.selectedUnit.getSimulationUnit().getConstructionProgress() / + this.selectedUnit.getSimulationUnit().getUnitType().getBuildTime(), 0.99f)); + } + final float groundHeight = Math.max( + this.war3MapViewer.terrain.getGroundHeight(this.cameraManager.target.x, this.cameraManager.target.y), + this.war3MapViewer.terrain.getWaterHeight(this.cameraManager.target.x, this.cameraManager.target.y)); + this.cameraManager.updateTargetZ(groundHeight); + this.cameraManager.updateCamera(); + } + + public void render(final SpriteBatch batch, final BitmapFont font20, final GlyphLayout glyphLayout) { + this.rootFrame.render(batch, font20, glyphLayout); + if (this.selectedUnit != null) { + font20.setColor(Color.WHITE); + + } + + this.meleeUIMinimap.render(batch, this.war3MapViewer.units); + this.timeIndicator.setFrameByRatio(this.war3MapViewer.simulation.getGameTimeOfDay() + / this.war3MapViewer.simulation.getGameplayConstants().getGameDayHours()); + } + + public void portraitTalk() { + this.portrait.talk(); + } + + private final class ActiveCommandUnitTargetFilter implements CUnitFilterFunction { + @Override + public boolean call(final CUnit unit) { + final BooleanAbilityTargetCheckReceiver targetReceiver = BooleanAbilityTargetCheckReceiver + .getInstance(); + MeleeUI.this.activeCommand.checkCanTarget(MeleeUI.this.war3MapViewer.simulation, + MeleeUI.this.activeCommandUnit.getSimulationUnit(), MeleeUI.this.activeCommandOrderId, unit, + targetReceiver); + return targetReceiver.isTargetable(); + } + } + + private static final class Portrait { + private MdxComplexInstance modelInstance; + private final PortraitCameraManager portraitCameraManager; + private final Scene portraitScene; + + public Portrait(final War3MapViewer war3MapViewer, final Scene portraitScene) { + this.portraitScene = portraitScene; + this.portraitCameraManager = new PortraitCameraManager(); + this.portraitCameraManager.setupCamera(this.portraitScene); + this.portraitScene.camera.viewport(new Rectangle(100, 0, 6400, 48)); + } + + public void update() { + this.portraitCameraManager.updateCamera(); + if ((this.modelInstance != null) + && (this.modelInstance.sequenceEnded || (this.modelInstance.sequence == -1))) { + SequenceUtils.randomSequence(this.modelInstance, PrimaryTag.PORTRAIT, SequenceUtils.EMPTY, true); + } + } + + public void talk() { + SequenceUtils.randomSequence(this.modelInstance, PrimaryTag.PORTRAIT, SequenceUtils.TALK, true); + } + + public void setSelectedUnit(final RenderUnit unit) { + if (unit == null) { + if (this.modelInstance != null) { + this.portraitScene.removeInstance(this.modelInstance); + } + this.modelInstance = null; + this.portraitCameraManager.setModelInstance(null, null); + } else { + final MdxModel portraitModel = unit.portraitModel; + if (portraitModel != null) { + if (this.modelInstance != null) { + this.portraitScene.removeInstance(this.modelInstance); + } + this.modelInstance = (MdxComplexInstance) portraitModel.addInstance(); + this.portraitCameraManager.setModelInstance(this.modelInstance, portraitModel); + this.modelInstance.setSequenceLoopMode(SequenceLoopMode.NEVER_LOOP); + this.modelInstance.setScene(this.portraitScene); + this.modelInstance.setVertexColor(unit.instance.vertexColor); + this.modelInstance.setTeamColor(unit.playerIndex); + } + } + } + } + + public void selectUnit(RenderUnit unit) { + this.subMenuOrderIdStack.clear(); + if ((unit != null) && unit.getSimulationUnit().isDead()) { + unit = null; + } + if (this.selectedUnit != null) { + this.selectedUnit.getSimulationUnit().removeStateListener(this); + } + this.portrait.setSelectedUnit(unit); + this.selectedUnit = unit; + if (unit == null) { + clearCommandCard(); + this.simpleNameValue.setText(""); + this.unitLifeText.setText(""); + this.unitManaText.setText(""); + this.simpleClassValue.setText(""); + this.simpleBuildingActionLabel.setText(""); + this.attack1Icon.setVisible(false); + this.attack2Icon.setVisible(false); + this.attack1InfoPanelIconLevel.setText(""); + this.attack2InfoPanelIconLevel.setText(""); + this.armorIcon.setVisible(false); + this.armorInfoPanelIconLevel.setText(""); + this.buildTimeIndicator.setVisible(false); + } else { + unit.getSimulationUnit().addStateListener(this); + this.simpleNameValue.setText(unit.getSimulationUnit().getUnitType().getName()); + String classText = null; + for (final CUnitClassification classification : unit.getSimulationUnit().getClassifications()) { + if ((classification == CUnitClassification.MECHANICAL) + && unit.getSimulationUnit().getUnitType().isBuilding()) { + // buildings dont display MECHANICAL + continue; + } + if (classification.getDisplayName() != null) { + classText = classification.getDisplayName(); + } + } + if (classText != null) { + this.simpleClassValue.setText(classText); + } else { + this.simpleClassValue.setText(""); + } + this.unitLifeText.setText(FastNumberFormat.formatWholeNumber(unit.getSimulationUnit().getLife()) + " / " + + unit.getSimulationUnit().getMaximumLife()); + final int maximumMana = unit.getSimulationUnit().getMaximumMana(); + if (maximumMana > 0) { + this.unitManaText.setText( + FastNumberFormat.formatWholeNumber(unit.getSimulationUnit().getMana()) + " / " + maximumMana); + } else { + this.unitManaText.setText(""); + } + this.simpleBuildingActionLabel.setText(""); + + final boolean anyAttacks = unit.getSimulationUnit().getUnitType().getAttacks().size() > 0; + boolean constructing = unit.getSimulationUnit().isConstructing(); + final UIFrame localArmorIcon = this.armorIcon; + final TextureFrame localArmorIconBackdrop = this.armorIconBackdrop; + final StringFrame localArmorInfoPanelIconValue = this.armorInfoPanelIconValue; + if (anyAttacks && !constructing) { + final CUnitAttack attackOne = unit.getSimulationUnit().getUnitType().getAttacks().get(0); + this.attack1Icon.setVisible(attackOne.isShowUI()); + this.attack1IconBackdrop.setTexture(this.damageBackdrops.getTexture(attackOne.getAttackType())); + this.attack1InfoPanelIconValue.setText(attackOne.getMinDamage() + " - " + attackOne.getMaxDamage()); + if (unit.getSimulationUnit().getUnitType().getAttacks().size() > 1) { + final CUnitAttack attackTwo = unit.getSimulationUnit().getUnitType().getAttacks().get(1); + this.attack2Icon.setVisible(attackTwo.isShowUI()); + this.attack2IconBackdrop.setTexture(this.damageBackdrops.getTexture(attackTwo.getAttackType())); + this.attack2InfoPanelIconValue.setText(attackTwo.getMinDamage() + " - " + attackTwo.getMaxDamage()); + } else { + this.attack2Icon.setVisible(false); + } + + this.armorIcon.addSetPoint( + new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, FramePoint.TOPLEFT, + GameUI.convertX(this.uiViewport, 0f), GameUI.convertY(this.uiViewport, -0.06025f))); + this.armorIcon.positionBounds(this.uiViewport); + } else { + this.attack1Icon.setVisible(false); + this.attack2Icon.setVisible(false); + + this.armorIcon.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, + FramePoint.TOPLEFT, 0, GameUI.convertY(this.uiViewport, -0.030125f))); + this.armorIcon.positionBounds(this.uiViewport); + } + + localArmorIcon.setVisible(!constructing); + buildTimeIndicator.setVisible(constructing); + if (constructing) { + buildTimeIndicator.setSequence(0); + } + final Texture defenseTexture = this.defenseBackdrops + .getTexture(unit.getSimulationUnit().getUnitType().getDefenseType()); + if (defenseTexture == null) { + throw new RuntimeException( + unit.getSimulationUnit().getUnitType().getDefenseType() + " can't find texture!"); + } + localArmorIconBackdrop.setTexture(defenseTexture); + localArmorInfoPanelIconValue.setText(Integer.toString(unit.getSimulationUnit().getDefense())); + clearAndRepopulateCommandCard(); + } + } + + private void clearCommandCard() { + for (int j = 0; j < COMMAND_CARD_HEIGHT; j++) { + for (int i = 0; i < COMMAND_CARD_WIDTH; i++) { + this.commandCard[j][i].setCommandButton(null); + } + } + } + + @Override + public void commandButton(final int buttonPositionX, final int buttonPositionY, final Texture icon, + final int abilityHandleId, final int orderId, final int autoCastId, final boolean active, + final boolean autoCastActive, final boolean menuButton) { + final int x = Math.max(0, Math.min(COMMAND_CARD_WIDTH - 1, buttonPositionX)); + final int y = Math.max(0, Math.min(COMMAND_CARD_HEIGHT - 1, buttonPositionY)); + this.commandCard[y][x].setCommandButtonData(icon, abilityHandleId, orderId, autoCastId, active, autoCastActive, + menuButton); + } + + public void resize(final Rectangle viewport) { + this.cameraManager.resize(viewport); + positionPortrait(); + } + + public void positionPortrait() { + this.projectionTemp1.x = 422 * this.widthRatioCorrection; + this.projectionTemp1.y = 57 * this.heightRatioCorrection; + this.projectionTemp2.x = (422 + 167) * this.widthRatioCorrection; + this.projectionTemp2.y = (57 + 170) * this.heightRatioCorrection; + this.uiViewport.project(this.projectionTemp1); + this.uiViewport.project(this.projectionTemp2); + + this.tempRect.x = this.projectionTemp1.x + this.uiViewport.getScreenX(); + this.tempRect.y = this.projectionTemp1.y + this.uiViewport.getScreenY(); + this.tempRect.width = this.projectionTemp2.x - this.projectionTemp1.x; + this.tempRect.height = this.projectionTemp2.y - this.projectionTemp1.y; + this.portrait.portraitScene.camera.viewport(this.tempRect); + } + + private static final class InfoPanelIconBackdrops { + private final Texture[] damageBackdropTextures; + + public InfoPanelIconBackdrops(final CodeKeyType[] attackTypes, final GameUI gameUI, final String prefix, + final String suffix) { + this.damageBackdropTextures = new Texture[attackTypes.length]; + for (int index = 0; index < attackTypes.length; index++) { + final CodeKeyType attackType = attackTypes[index]; + String skinLookupKey = "InfoPanelIcon" + prefix + attackType.getCodeKey() + suffix; + final Texture suffixTexture = gameUI.loadTexture(gameUI.getSkinField(skinLookupKey)); + if (suffixTexture != null) { + this.damageBackdropTextures[index] = suffixTexture; + } else { + skinLookupKey = "InfoPanelIcon" + prefix + attackType.getCodeKey(); + this.damageBackdropTextures[index] = gameUI.loadTexture(gameUI.getSkinField(skinLookupKey)); + } + } + } + + public Texture getTexture(final CodeKeyType attackType) { + if (attackType != null) { + final int ordinal = attackType.ordinal(); + if ((ordinal >= 0) && (ordinal < this.damageBackdropTextures.length)) { + return this.damageBackdropTextures[ordinal]; + } + } + return this.damageBackdropTextures[0]; + } + + private static String getSuffix(final CAttackType attackType) { + switch (attackType) { + case CHAOS: + return "Chaos"; + case HERO: + return "Hero"; + case MAGIC: + return "Magic"; + case NORMAL: + return "Normal"; + case PIERCE: + return "Pierce"; + case SIEGE: + return "Siege"; + case SPELLS: + return "Magic"; + case UNKNOWN: + return "Unknown"; + default: + throw new IllegalArgumentException("Unknown attack type: " + attackType); + } + + } + } + + @Override + public void lifeChanged() { + if (this.selectedUnit.getSimulationUnit().isDead()) { + selectUnit(null); + } else { + this.unitLifeText + .setText(FastNumberFormat.formatWholeNumber(this.selectedUnit.getSimulationUnit().getLife()) + " / " + + this.selectedUnit.getSimulationUnit().getMaximumLife()); + } + } + + @Override + public void ordersChanged(final int abilityHandleId, final int orderId) { + selectUnit(selectedUnit); + } + + private void clearAndRepopulateCommandCard() { + clearCommandCard(); + final AbilityDataUI abilityDataUI = this.war3MapViewer.getAbilityDataUI(); + final int menuOrderId = getSubMenuOrderId(); + if (this.activeCommand != null) { + final IconUI cancelUI = abilityDataUI.getCancelUI(); + this.commandButton(cancelUI.getButtonPositionX(), cancelUI.getButtonPositionY(), cancelUI.getIcon(), 0, + menuOrderId, 0, false, false, true); + } else if (this.selectedUnit.getSimulationUnit().isConstructing()) { + + } else { + if (menuOrderId != 0) { + final int exitOrderId = this.subMenuOrderIdStack.size() > 1 + ? this.subMenuOrderIdStack.get(this.subMenuOrderIdStack.size() - 2) + : 0; + final IconUI cancelUI = abilityDataUI.getCancelUI(); + this.commandButton(cancelUI.getButtonPositionX(), cancelUI.getButtonPositionY(), cancelUI.getIcon(), 0, + exitOrderId, 0, false, false, true); + } + this.selectedUnit.populateCommandCard(this.war3MapViewer.simulation, this, abilityDataUI, menuOrderId); + } + } + + private int getSubMenuOrderId() { + return this.subMenuOrderIdStack.isEmpty() ? 0 + : this.subMenuOrderIdStack.get(this.subMenuOrderIdStack.size() - 1); + } + + public RenderUnit getSelectedUnit() { + return this.selectedUnit; + } + + public boolean keyDown(final int keycode) { + return this.cameraManager.keyDown(keycode); + } + + public boolean keyUp(final int keycode) { + return this.cameraManager.keyUp(keycode); + } + + public void scrolled(final int amount) { + this.cameraManager.scrolled(amount); + } + + public boolean touchDown(final int screenX, final int screenY, final float worldScreenY, final int button) { + screenCoordsVector.set(screenX, screenY); + this.uiViewport.unproject(screenCoordsVector); + if (this.meleeUIMinimap.containsMouse(screenCoordsVector.x, screenCoordsVector.y)) { + final Vector2 worldPoint = this.meleeUIMinimap.getWorldPointFromScreen(screenCoordsVector.x, + screenCoordsVector.y); + this.cameraManager.target.x = worldPoint.x; + this.cameraManager.target.y = worldPoint.y; + return true; + } + final UIFrame clickedUIFrame = this.rootFrame.touchDown(screenCoordsVector.x, screenCoordsVector.y, button); + if (clickedUIFrame == null) { + // try to interact with world + if (this.activeCommand != null) { + if (button == Input.Buttons.RIGHT) { + this.activeCommandUnit = null; + this.activeCommand = null; + this.activeCommandOrderId = -1; + clearAndRepopulateCommandCard(); + } else { + final RenderUnit rayPickUnit = this.war3MapViewer.rayPickUnit(screenX, worldScreenY, + this.activeCommandUnitTargetFilter); + final boolean shiftDown = isShiftDown(); + if (rayPickUnit != null) { + this.unitOrderListener.issueTargetOrder( + this.activeCommandUnit.getSimulationUnit().getHandleId(), + this.activeCommand.getHandleId(), this.activeCommandOrderId, + rayPickUnit.getSimulationUnit().getHandleId(), shiftDown); + if (getSelectedUnit().soundset.yesAttack + .playUnitResponse(this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { + portraitTalk(); + } + this.selectedSoundCount = 0; + if (!shiftDown) { + this.subMenuOrderIdStack.clear(); + this.activeCommandUnit = null; + this.activeCommand = null; + this.activeCommandOrderId = -1; + clearAndRepopulateCommandCard(); + } + } else { + this.war3MapViewer.getClickLocation(clickLocationTemp, screenX, (int) worldScreenY); + clickLocationTemp2.set(clickLocationTemp.x, clickLocationTemp.y); + + this.activeCommand.checkCanTarget(this.war3MapViewer.simulation, + this.activeCommandUnit.getSimulationUnit(), this.activeCommandOrderId, + clickLocationTemp2, PointAbilityTargetCheckReceiver.INSTANCE); + final Vector2 target = PointAbilityTargetCheckReceiver.INSTANCE.getTarget(); + if (target != null) { + if (this.activeCommand instanceof CAbilityAttack) { + this.war3MapViewer.showConfirmation(clickLocationTemp, 1, 0, 0); + } else { + this.war3MapViewer.showConfirmation(clickLocationTemp, 0, 1, 0); + } + this.unitOrderListener.issuePointOrder( + this.activeCommandUnit.getSimulationUnit().getHandleId(), + this.activeCommand.getHandleId(), this.activeCommandOrderId, clickLocationTemp2.x, + clickLocationTemp2.y, shiftDown); + if (getSelectedUnit().soundset.yes + .playUnitResponse(this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { + portraitTalk(); + } + this.selectedSoundCount = 0; + if (this.activeCommand instanceof AbstractCAbilityBuild) { + this.war3MapViewer.getUiSounds().getSound("PlaceBuildingDefault") + .play(this.uiScene.audioContext, 0, 0); + } + if (!shiftDown) { + this.subMenuOrderIdStack.clear(); + this.activeCommandUnit = null; + this.activeCommand = null; + this.activeCommandOrderId = -1; + clearAndRepopulateCommandCard(); + } + + } + } + } + } else { + if (button == Input.Buttons.RIGHT) { + if (getSelectedUnit() != null) { + final RenderUnit rayPickUnit = this.war3MapViewer.rayPickUnit(screenX, worldScreenY); + if ((rayPickUnit != null) && !rayPickUnit.getSimulationUnit().isDead()) { + boolean ordered = false; + for (final RenderUnit unit : this.war3MapViewer.selected) { + for (final CAbility ability : unit.getSimulationUnit().getAbilities()) { + ability.checkCanTarget(this.war3MapViewer.simulation, unit.getSimulationUnit(), + OrderIds.smart, rayPickUnit.getSimulationUnit(), + CWidgetAbilityTargetCheckReceiver.INSTANCE); + final CWidget targetWidget = CWidgetAbilityTargetCheckReceiver.INSTANCE.getTarget(); + if (targetWidget != null) { + this.unitOrderListener.issueTargetOrder(unit.getSimulationUnit().getHandleId(), + ability.getHandleId(), OrderIds.smart, targetWidget.getHandleId(), + isShiftDown()); + ordered = true; + } + } + + } + if (ordered) { + if (getSelectedUnit().soundset.yesAttack.playUnitResponse( + this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { + portraitTalk(); + } + this.selectedSoundCount = 0; + } + } else { + this.war3MapViewer.getClickLocation(clickLocationTemp, screenX, (int) worldScreenY); + this.war3MapViewer.showConfirmation(clickLocationTemp, 0, 1, 0); + clickLocationTemp2.set(clickLocationTemp.x, clickLocationTemp.y); + + boolean ordered = false; + for (final RenderUnit unit : this.war3MapViewer.selected) { + for (final CAbility ability : unit.getSimulationUnit().getAbilities()) { + ability.checkCanUse(this.war3MapViewer.simulation, unit.getSimulationUnit(), + OrderIds.smart, BooleanAbilityActivationReceiver.INSTANCE); + if (BooleanAbilityActivationReceiver.INSTANCE.isOk()) { + ability.checkCanTarget(this.war3MapViewer.simulation, unit.getSimulationUnit(), + OrderIds.smart, clickLocationTemp2, + PointAbilityTargetCheckReceiver.INSTANCE); + final Vector2 target = PointAbilityTargetCheckReceiver.INSTANCE.getTarget(); + if (target != null) { + this.unitOrderListener.issuePointOrder( + unit.getSimulationUnit().getHandleId(), ability.getHandleId(), + OrderIds.smart, clickLocationTemp2.x, clickLocationTemp2.y, + isShiftDown()); + ordered = true; + } + } + } + + } + + if (ordered) { + if (getSelectedUnit().soundset.yes.playUnitResponse( + this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { + portraitTalk(); + } + this.selectedSoundCount = 0; + } + } + } + } else { + final List selectedUnits = this.war3MapViewer.selectUnit(screenX, worldScreenY, false); + if (!selectedUnits.isEmpty()) { + final RenderUnit unit = selectedUnits.get(0); + final boolean selectionChanged = getSelectedUnit() != unit; + boolean playedNewSound = false; + if (selectionChanged) { + this.selectedSoundCount = 0; + } + if (unit.soundset != null) { + UnitSound ackSoundToPlay = unit.soundset.what; + int soundIndex; + final int pissedSoundCount = unit.soundset.pissed.getSoundCount(); + if (unit.getSimulationUnit().isConstructing()) { + ackSoundToPlay = unit.buildSound; + soundIndex = 0; + } else { + if ((this.selectedSoundCount >= 3) && (pissedSoundCount > 0)) { + soundIndex = this.selectedSoundCount - 3; + ackSoundToPlay = unit.soundset.pissed; + } else { + soundIndex = (int) (Math.random() * ackSoundToPlay.getSoundCount()); + } + } + if (ackSoundToPlay != null && ackSoundToPlay.playUnitResponse(this.war3MapViewer.worldScene.audioContext, unit, + soundIndex)) { + this.selectedSoundCount++; + if ((this.selectedSoundCount - 3) >= pissedSoundCount) { + this.selectedSoundCount = 0; + } + playedNewSound = true; + } + } + if (selectionChanged) { + selectUnit(unit); + } + if (playedNewSound) { + portraitTalk(); + } + } else { + selectUnit(null); + } + } + } + } else { + if (clickedUIFrame instanceof CommandCardIcon) { + this.mouseDownUIFrame = (CommandCardIcon) clickedUIFrame; + this.mouseDownUIFrame.mouseDown(this.uiViewport); + } + } + return false; + } + + public boolean touchUp(final int screenX, final int screenY, final float worldScreenY, final int button) { + screenCoordsVector.set(screenX, screenY); + this.uiViewport.unproject(screenCoordsVector); + final UIFrame clickedUIFrame = this.rootFrame.touchUp(screenCoordsVector.x, screenCoordsVector.y, button); + if (this.mouseDownUIFrame != null) { + if (clickedUIFrame == this.mouseDownUIFrame) { + this.mouseDownUIFrame.onClick(button); + this.war3MapViewer.getUiSounds().getSound("InterfaceClick").play(this.uiScene.audioContext, 0, 0); + } + this.mouseDownUIFrame.mouseUp(this.uiViewport); + } + this.mouseDownUIFrame = null; + return false; + } + + private static boolean isShiftDown() { + return Gdx.input.isKeyPressed(Input.Keys.SHIFT_LEFT) || Gdx.input.isKeyPressed(Input.Keys.SHIFT_RIGHT); + } + + public boolean touchDragged(final int screenX, final int screenY, final float worldScreenY, final int pointer) { + screenCoordsVector.set(screenX, screenY); + this.uiViewport.unproject(screenCoordsVector); + + if (this.meleeUIMinimap.containsMouse(screenCoordsVector.x, screenCoordsVector.y)) { + final Vector2 worldPoint = this.meleeUIMinimap.getWorldPointFromScreen(screenCoordsVector.x, + screenCoordsVector.y); + this.cameraManager.target.x = worldPoint.x; + this.cameraManager.target.y = worldPoint.y; + } + return false; + } + + public float getHeightRatioCorrection() { + return this.heightRatioCorrection; + } } From 2b3e5bdf4ab615c6d0dd48b713ee4fcd64f60038 Mon Sep 17 00:00:00 2001 From: Retera Date: Fri, 13 Nov 2020 23:12:39 -0500 Subject: [PATCH 066/116] Update with simple status bar --- .../etheller/warsmash/WarsmashGdxMapGame.java | 4 +- .../etheller/warsmash/parsers/fdf/GameUI.java | 48 +- .../fdf/frames/SimpleStatusBarFrame.java | 39 + .../warsmash/viewer5/AudioBufferSource.java | 4 +- .../mdx/EventObjectEmitterObject.java | 580 ++-- .../viewer5/handlers/mdx/EventObjectSnd.java | 4 +- .../viewer5/handlers/w3x/UnitAckSound.java | 21 +- .../viewer5/handlers/w3x/UnitSound.java | 16 +- .../viewer5/handlers/w3x/War3MapViewer.java | 2710 +++++++++-------- .../handlers/w3x/rendersim/RenderUnit.java | 12 +- .../handlers/w3x/ui/CommandCardIcon.java | 6 +- .../viewer5/handlers/w3x/ui/MeleeUI.java | 2117 ++++++------- 12 files changed, 2888 insertions(+), 2673 deletions(-) create mode 100644 core/src/com/etheller/warsmash/parsers/fdf/frames/SimpleStatusBarFrame.java diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java index ac61fe3..7944cc3 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java @@ -130,7 +130,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.viewer.enableAudio(); } try { - this.viewer.loadMap(this.warsmashIni.get("Map").getField("FilePath")); + this.viewer.loadMap(this.warsmashIni.get("Map").getField("FilePath"), 0); } catch (final IOException e) { throw new RuntimeException(e); @@ -222,7 +222,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv cameraRatesElement.getFieldFloatValue("FOV"), cameraRatesElement.getFieldFloatValue("Rotation"), cameraRatesElement.getFieldFloatValue("Distance"), cameraRatesElement.getFieldFloatValue("Forward"), cameraRatesElement.getFieldFloatValue("Strafe")); - this.meleeUI = new MeleeUI(this.codebase, this.uiViewport, fontGenerator, this.uiScene, portraitScene, + this.meleeUI = new MeleeUI(this.viewer.mapMpq, this.uiViewport, fontGenerator, this.uiScene, portraitScene, cameraPresets, cameraRates, this.viewer, new RootFrameListener() { @Override public void onCreate(final GameUI rootFrame) { diff --git a/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java index 2bdc4dd..24edd47 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java @@ -30,6 +30,7 @@ import com.etheller.warsmash.parsers.fdf.frames.AbstractUIFrame; import com.etheller.warsmash.parsers.fdf.frames.FilterModeTextureFrame; import com.etheller.warsmash.parsers.fdf.frames.SetPoint; import com.etheller.warsmash.parsers.fdf.frames.SimpleFrame; +import com.etheller.warsmash.parsers.fdf.frames.SimpleStatusBarFrame; import com.etheller.warsmash.parsers.fdf.frames.SpriteFrame; import com.etheller.warsmash.parsers.fdf.frames.StringFrame; import com.etheller.warsmash.parsers.fdf.frames.TextureFrame; @@ -104,16 +105,31 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { catch (final IOException e) { throw new RuntimeException(e); } + // TODO eliminate duplicate read of skin TXT!! + if (dataSource.has("war3mapSkin.txt")) { + try (InputStream miscDataTxtStream = dataSource.getResourceAsStream("war3mapSkin.txt")) { + skinsTable.readTXT(miscDataTxtStream, true); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + } // final Element main = skinsTable.get("Main"); // final String skinsField = main.getField("Skins"); // final String[] skins = skinsField.split(","); final Element defaultSkin = skinsTable.get("Default"); final Element userSkin = skinsTable.get(skin); + final Element customSkin = skinsTable.get("CustomSkin"); for (final String key : defaultSkin.keySet()) { if (!userSkin.hasField(key)) { userSkin.setField(key, defaultSkin.getField(key)); } } + if (customSkin != null) { + for (final String key : customSkin.keySet()) { + userSkin.setField(key, customSkin.getField(key)); + } + } return userSkin; } @@ -125,16 +141,31 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { catch (final IOException e) { throw new RuntimeException(e); } + // TODO eliminate duplicate read of skin TXT!! + if (dataSource.has("war3mapSkin.txt")) { + try (InputStream miscDataTxtStream = dataSource.getResourceAsStream("war3mapSkin.txt")) { + skinsTable.readTXT(miscDataTxtStream, true); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + } final Element main = skinsTable.get("Main"); final String skinsField = main.getField("Skins"); final String[] skins = skinsField.split(","); final Element defaultSkin = skinsTable.get("Default"); final Element userSkin = skinsTable.get(skins[skinIndex]); + final Element customSkin = skinsTable.get("CustomSkin"); for (final String key : defaultSkin.keySet()) { if (!userSkin.hasField(key)) { userSkin.setField(key, defaultSkin.getField(key)); } } + if (customSkin != null) { + for (final String key : customSkin.keySet()) { + userSkin.setField(key, customSkin.getField(key)); + } + } return userSkin; } @@ -252,6 +283,17 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { } inflatedFrame = simpleFrame; } + else if ("SIMPLESTATUSBAR".equals(frameDefinition.getFrameType())) { + final boolean decorateFileNames = frameDefinition.has("DecorateFileNames") + || ((parentDefinitionIfAvailable != null) + && parentDefinitionIfAvailable.has("DecorateFileNames")); + final SimpleStatusBarFrame simpleStatusBarFrame = new SimpleStatusBarFrame(frameDefinition.getName(), + parent, decorateFileNames); + for (final FrameDefinition childDefinition : frameDefinition.getInnerFrames()) { + simpleStatusBarFrame.add(inflate(childDefinition, simpleStatusBarFrame, frameDefinition)); + } + inflatedFrame = simpleStatusBarFrame; + } else if ("SPRITE".equals(frameDefinition.getFrameType())) { final SpriteFrame spriteFrame = new SpriteFrame(frameDefinition.getName(), parent, this.uiScene, viewport2); @@ -448,6 +490,10 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { } public Scene getUiScene() { - return uiScene; + return this.uiScene; + } + + public FrameTemplateEnvironment getTemplates() { + return this.templates; } } diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/SimpleStatusBarFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/SimpleStatusBarFrame.java new file mode 100644 index 0000000..6fcef72 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/SimpleStatusBarFrame.java @@ -0,0 +1,39 @@ +package com.etheller.warsmash.parsers.fdf.frames; + +import com.etheller.warsmash.parsers.fdf.datamodel.FramePoint; +import com.etheller.warsmash.parsers.fdf.datamodel.Vector4Definition; + +public class SimpleStatusBarFrame extends AbstractUIFrame { + private final boolean decorateFileNames; + private final TextureFrame barFrame; + private final TextureFrame borderFrame; + + public SimpleStatusBarFrame(final String name, final UIFrame parent, final boolean decorateFileNames) { + super(name, parent); + this.decorateFileNames = decorateFileNames; + this.barFrame = new TextureFrame(name + "Bar", this, decorateFileNames, new Vector4Definition(0, 1, 0, 1)); + this.borderFrame = new TextureFrame(name + "Border", this, decorateFileNames, + new Vector4Definition(0, 1, 0, 1)); + this.borderFrame.setSetAllPoints(true); + this.barFrame.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this, FramePoint.TOPLEFT, 0, 0)); + this.barFrame.addSetPoint(new SetPoint(FramePoint.BOTTOMLEFT, this, FramePoint.BOTTOMLEFT, 0, 0)); + add(this.barFrame); + add(this.borderFrame); + } + + public boolean isDecorateFileNames() { + return this.decorateFileNames; + } + + public void setValue(final float value) { + this.barFrame.setWidth(this.renderBounds.width * value); + } + + public TextureFrame getBarFrame() { + return this.barFrame; + } + + public TextureFrame getBorderFrame() { + return this.borderFrame; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/AudioBufferSource.java b/core/src/com/etheller/warsmash/viewer5/AudioBufferSource.java index 7a2976f..8e40532 100644 --- a/core/src/com/etheller/warsmash/viewer5/AudioBufferSource.java +++ b/core/src/com/etheller/warsmash/viewer5/AudioBufferSource.java @@ -9,9 +9,9 @@ public class AudioBufferSource { } - public void start(final int value) { + public void start(final int value, final float volume, final float pitch) { if (this.buffer != null) { - this.buffer.play(1); + this.buffer.play(volume, pitch, 0.0f); } } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectEmitterObject.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectEmitterObject.java index 68d07a5..38396ff 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectEmitterObject.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectEmitterObject.java @@ -22,328 +22,348 @@ import com.etheller.warsmash.viewer5.Texture; import com.etheller.warsmash.viewer5.handlers.EmitterObject; public class EventObjectEmitterObject extends GenericObject implements EmitterObject { - private static final class LoadGenericSoundCallback implements LoadGenericCallback { - private final String filename; + private static final class LoadGenericSoundCallback implements LoadGenericCallback { + private final String filename; - public LoadGenericSoundCallback(final String filename) { - this.filename = filename; - } + public LoadGenericSoundCallback(final String filename) { + this.filename = filename; + } - @Override - public Object call(final InputStream data) { - final FileHandle temp = new FileHandle(this.filename) { - @Override - public InputStream read() { - return data; - } + @Override + public Object call(final InputStream data) { + final FileHandle temp = new FileHandle(this.filename) { + @Override + public InputStream read() { + return data; + } - ; - }; - if (data != null) { - return Gdx.audio.newSound(temp); - } else { - System.err.println("Warning: missing sound file: " + this.filename); - return null; - } - } - } + ; + }; + if (data != null) { + return Gdx.audio.newSound(temp); + } + else { + System.err.println("Warning: missing sound file: " + this.filename); + return null; + } + } + } - private static final LoadGenericCallback mappedDataCallback = new LoadGenericCallback() { + private static final LoadGenericCallback mappedDataCallback = new LoadGenericCallback() { - @Override - public Object call(final InputStream data) { - final StringBuilder stringBuilder = new StringBuilder(); - try (BufferedReader reader = new BufferedReader(new InputStreamReader(data, "utf-8"))) { - String line; - while ((line = reader.readLine()) != null) { - stringBuilder.append(line); - stringBuilder.append("\n"); - } - } catch (final UnsupportedEncodingException e) { - throw new RuntimeException(e); - } catch (final IOException e) { - throw new RuntimeException(e); - } - return new MappedData(stringBuilder.toString()); - } - }; + @Override + public Object call(final InputStream data) { + final StringBuilder stringBuilder = new StringBuilder(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(data, "utf-8"))) { + String line; + while ((line = reader.readLine()) != null) { + stringBuilder.append(line); + stringBuilder.append("\n"); + } + } + catch (final UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + return new MappedData(stringBuilder.toString()); + } + }; - private int geometryEmitterType = -1; - public final String type; - private final String id; - public final long[] keyFrames; - private long globalSequence = -1; - private final long[] defval = {1}; - public MdxModel internalModel; - public Texture internalTexture; - public float[][] colors; - public float[] intervalTimes; - public float scale; - public int columns; - public int rows; - public float lifeSpan; - public int blendSrc; - public int blendDst; - public float[][] intervals; - public float distanceCutoff; - private float maxDistance; - public float minDistance; - private float pitch; - private float pitchVariance; - private float volume; - public List decodedBuffers = new ArrayList<>(); - /** - * If this is an SPL/UBR emitter object, ok will be set to true if the tables - * are loaded. - *

- * This is because, like the other geometry emitters, it is fine to use them - * even if the textures don't load. - *

- * The particles will simply be black. - */ - private boolean ok = false; + private int geometryEmitterType = -1; + public final String type; + private final String id; + public final long[] keyFrames; + private long globalSequence = -1; + private final long[] defval = { 1 }; + public MdxModel internalModel; + public Texture internalTexture; + public float[][] colors; + public float[] intervalTimes; + public float scale; + public int columns; + public int rows; + public float lifeSpan; + public int blendSrc; + public int blendDst; + public float[][] intervals; + public float distanceCutoff; + private float maxDistance; + public float minDistance; + public float pitch; + public float pitchVariance; + public float volume; + public List decodedBuffers = new ArrayList<>(); + /** + * If this is an SPL/UBR emitter object, ok will be set to true if the tables + * are loaded. + *

+ * This is because, like the other geometry emitters, it is fine to use them + * even if the textures don't load. + *

+ * The particles will simply be black. + */ + private boolean ok = false; - public EventObjectEmitterObject(final MdxModel model, - final com.etheller.warsmash.parsers.mdlx.EventObject eventObject, final int index) { - super(model, eventObject, index); + public EventObjectEmitterObject(final MdxModel model, + final com.etheller.warsmash.parsers.mdlx.EventObject eventObject, final int index) { + super(model, eventObject, index); - final ModelViewer viewer = model.viewer; - final String name = eventObject.getName(); - String type = name.substring(0, 3); - final String id = name.substring(4); + final ModelViewer viewer = model.viewer; + final String name = eventObject.getName(); + String type = name.substring(0, 3); + final String id = name.substring(4); - // Same thing - if ("FPT".equals(type)) { - type = "SPL"; - } + // Same thing + if ("FPT".equals(type)) { + type = "SPL"; + } - if ("SPL".equals(type)) { - this.geometryEmitterType = GeometryEmitterFuncs.EMITTER_SPLAT; - } else if ("UBR".equals(type)) { - this.geometryEmitterType = GeometryEmitterFuncs.EMITTER_UBERSPLAT; - } else if ("SPN".equals(type)) { - this.geometryEmitterType = GeometryEmitterFuncs.EMITTER_SPN; - } + if ("SPL".equals(type)) { + this.geometryEmitterType = GeometryEmitterFuncs.EMITTER_SPLAT; + } + else if ("UBR".equals(type)) { + this.geometryEmitterType = GeometryEmitterFuncs.EMITTER_UBERSPLAT; + } + else if ("SPN".equals(type)) { + this.geometryEmitterType = GeometryEmitterFuncs.EMITTER_SPN; + } - this.type = type; - this.id = id; - this.keyFrames = eventObject.getKeyFrames(); + this.type = type; + this.id = id; + this.keyFrames = eventObject.getKeyFrames(); - final int globalSequenceId = eventObject.getGlobalSequenceId(); - if (globalSequenceId != -1) { - this.globalSequence = model.getGlobalSequences().get(globalSequenceId); - } + final int globalSequenceId = eventObject.getGlobalSequenceId(); + if (globalSequenceId != -1) { + this.globalSequence = model.getGlobalSequences().get(globalSequenceId); + } - final List tables = new ArrayList<>(); - final PathSolver pathSolver = model.pathSolver; - final Object solverParams = model.solverParams; + final List tables = new ArrayList<>(); + final PathSolver pathSolver = model.pathSolver; + final Object solverParams = model.solverParams; - if ("SPN".equals(type)) { - tables.add(viewer.loadGeneric(pathSolver.solve("Splats\\SpawnData.slk", solverParams).finalSrc, - FetchDataTypeName.SLK, mappedDataCallback)); - } else if ("SPL".equals(type)) { - tables.add(viewer.loadGeneric(pathSolver.solve("Splats\\SplatData.slk", solverParams).finalSrc, - FetchDataTypeName.SLK, mappedDataCallback)); - } else if ("UBR".equals(type)) { - tables.add(viewer.loadGeneric(pathSolver.solve("Splats\\UberSplatData.slk", solverParams).finalSrc, - FetchDataTypeName.SLK, mappedDataCallback)); - } else if ("SND".equals(type)) { - if (!model.reforged) { - tables.add(viewer.loadGeneric(pathSolver.solve("UI\\SoundInfo\\AnimLookups.slk", solverParams).finalSrc, - FetchDataTypeName.SLK, mappedDataCallback)); - } - tables.add(viewer.loadGeneric(pathSolver.solve("UI\\SoundInfo\\AnimSounds.slk", solverParams).finalSrc, - FetchDataTypeName.SLK, mappedDataCallback)); - } else { - // Units\Critters\BlackStagMale\BlackStagMale.mdx has an event object named - // "Point01". - return; - } + if ("SPN".equals(type)) { + tables.add(viewer.loadGeneric(pathSolver.solve("Splats\\SpawnData.slk", solverParams).finalSrc, + FetchDataTypeName.SLK, mappedDataCallback)); + } + else if ("SPL".equals(type)) { + tables.add(viewer.loadGeneric(pathSolver.solve("Splats\\SplatData.slk", solverParams).finalSrc, + FetchDataTypeName.SLK, mappedDataCallback)); + } + else if ("UBR".equals(type)) { + tables.add(viewer.loadGeneric(pathSolver.solve("Splats\\UberSplatData.slk", solverParams).finalSrc, + FetchDataTypeName.SLK, mappedDataCallback)); + } + else if ("SND".equals(type)) { + if (!model.reforged) { + tables.add(viewer.loadGeneric(pathSolver.solve("UI\\SoundInfo\\AnimLookups.slk", solverParams).finalSrc, + FetchDataTypeName.SLK, mappedDataCallback)); + } + tables.add(viewer.loadGeneric(pathSolver.solve("UI\\SoundInfo\\AnimSounds.slk", solverParams).finalSrc, + FetchDataTypeName.SLK, mappedDataCallback)); + } + else { + // Units\Critters\BlackStagMale\BlackStagMale.mdx has an event object named + // "Point01". + return; + } - // TODO I am scrapping some async stuff with promises here from the JS and - // calling load - this.load(tables); - } + // TODO I am scrapping some async stuff with promises here from the JS and + // calling load + this.load(tables); + } - private float getFloat(final MappedDataRow row, final String name) { - final Float x = (Float) row.get(name); - if (x == null) { - return Float.NaN; - } else { - return x.floatValue(); - } - } + private float getFloat(final MappedDataRow row, final String name) { + final Float x = (Float) row.get(name); + if (x == null) { + return Float.NaN; + } + else { + return x.floatValue(); + } + } - private int getInt(final MappedDataRow row, final String name) { - return getInt(row, name, Integer.MIN_VALUE); - } + private int getInt(final MappedDataRow row, final String name) { + return getInt(row, name, Integer.MIN_VALUE); + } - private int getInt(final MappedDataRow row, final String name, final int defaultValue) { - final Number x = (Number) row.get(name); - if (x == null) { - return defaultValue; - } else { - return x.intValue(); - } - } + private int getInt(final MappedDataRow row, final String name, final int defaultValue) { + final Number x = (Number) row.get(name); + if (x == null) { + return defaultValue; + } + else { + return x.intValue(); + } + } - private void load(final List tables) { - final MappedData firstTable = (MappedData) tables.get(0).data; - if (firstTable == null) { - return; - } - final MappedDataRow row = firstTable.getRow(this.id.trim()); + private void load(final List tables) { + final MappedData firstTable = (MappedData) tables.get(0).data; + if (firstTable == null) { + return; + } + final MappedDataRow row = firstTable.getRow(this.id.trim()); - if (row != null) { - final MdxModel model = this.model; - final ModelViewer viewer = model.viewer; - final PathSolver pathSolver = model.pathSolver; + if (row != null) { + final MdxModel model = this.model; + final ModelViewer viewer = model.viewer; + final PathSolver pathSolver = model.pathSolver; - if ("SPN".equals(this.type)) { - this.internalModel = (MdxModel) viewer.load(((String) row.get("Model")).replace(".mdl", ".mdx"), - pathSolver, model.solverParams); + if ("SPN".equals(this.type)) { + this.internalModel = (MdxModel) viewer.load(((String) row.get("Model")).replace(".mdl", ".mdx"), + pathSolver, model.solverParams); - if (this.internalModel != null) { - // TODO javascript async code removed here + if (this.internalModel != null) { + // TODO javascript async code removed here // this.internalModel.whenLoaded((model) => this.ok = model.ok) - this.ok = this.internalModel.ok; - } - } else if ("SPL".equals(this.type) || "UBR".equals(this.type)) { - final String texturesExt = model.reforged ? ".dds" : ".blp"; + this.ok = this.internalModel.ok; + } + } + else if ("SPL".equals(this.type) || "UBR".equals(this.type)) { + final String texturesExt = model.reforged ? ".dds" : ".blp"; - this.internalTexture = (Texture) viewer.load( - "ReplaceableTextures\\Splats\\" + row.get("file") + texturesExt, pathSolver, - model.solverParams); + this.internalTexture = (Texture) viewer.load( + "ReplaceableTextures\\Splats\\" + row.get("file") + texturesExt, pathSolver, + model.solverParams); - this.scale = getFloat(row, "Scale"); - this.colors = new float[][]{ - {getFloat(row, "StartR"), getFloat(row, "StartG"), getFloat(row, "StartB"), - getFloat(row, "StartA")}, - {getFloat(row, "MiddleR"), getFloat(row, "MiddleG"), getFloat(row, "MiddleB"), - getFloat(row, "MiddleA")}, - {getFloat(row, "EndR"), getFloat(row, "EndG"), getFloat(row, "EndB"), - getFloat(row, "EndA")}}; + this.scale = getFloat(row, "Scale"); + this.colors = new float[][] { + { getFloat(row, "StartR"), getFloat(row, "StartG"), getFloat(row, "StartB"), + getFloat(row, "StartA") }, + { getFloat(row, "MiddleR"), getFloat(row, "MiddleG"), getFloat(row, "MiddleB"), + getFloat(row, "MiddleA") }, + { getFloat(row, "EndR"), getFloat(row, "EndG"), getFloat(row, "EndB"), + getFloat(row, "EndA") } }; - if ("SPL".equals(this.type)) { - this.columns = getInt(row, "Columns"); - this.rows = getInt(row, "Rows"); - this.lifeSpan = getFloat(row, "Lifespan") + getFloat(row, "Decay"); - this.intervalTimes = new float[]{getFloat(row, "Lifespan"), getFloat(row, "Decay")}; - this.intervals = new float[][]{ - {getFloat(row, "UVLifespanStart"), getFloat(row, "UVLifespanEnd"), - getFloat(row, "LifespanRepeat")}, - {getFloat(row, "UVDecayStart"), getFloat(row, "UVDecayEnd"), - getFloat(row, "DecayRepeat")},}; - } else { - this.columns = 1; - this.rows = 1; - this.lifeSpan = getFloat(row, "BirthTime") + getFloat(row, "PauseTime") + getFloat(row, "Decay"); - this.intervalTimes = new float[]{getFloat(row, "BirthTime"), getFloat(row, "PauseTime"), - getFloat(row, "Decay")}; - } + if ("SPL".equals(this.type)) { + this.columns = getInt(row, "Columns"); + this.rows = getInt(row, "Rows"); + this.lifeSpan = getFloat(row, "Lifespan") + getFloat(row, "Decay"); + this.intervalTimes = new float[] { getFloat(row, "Lifespan"), getFloat(row, "Decay") }; + this.intervals = new float[][] { + { getFloat(row, "UVLifespanStart"), getFloat(row, "UVLifespanEnd"), + getFloat(row, "LifespanRepeat") }, + { getFloat(row, "UVDecayStart"), getFloat(row, "UVDecayEnd"), + getFloat(row, "DecayRepeat") }, }; + } + else { + this.columns = 1; + this.rows = 1; + this.lifeSpan = getFloat(row, "BirthTime") + getFloat(row, "PauseTime") + getFloat(row, "Decay"); + this.intervalTimes = new float[] { getFloat(row, "BirthTime"), getFloat(row, "PauseTime"), + getFloat(row, "Decay") }; + } - final int[] blendModes = FilterMode - .emitterFilterMode(com.etheller.warsmash.parsers.mdlx.ParticleEmitter2.FilterMode - .fromId(getInt(row, "BlendMode"))); + final int[] blendModes = FilterMode + .emitterFilterMode(com.etheller.warsmash.parsers.mdlx.ParticleEmitter2.FilterMode + .fromId(getInt(row, "BlendMode"))); - this.blendSrc = blendModes[0]; - this.blendDst = blendModes[1]; + this.blendSrc = blendModes[0]; + this.blendDst = blendModes[1]; - this.ok = true; - } else if ("SND".equals(this.type)) { - // Only load sounds if audio is enabled. - // This is mostly to save on bandwidth and loading time, especially when loading - // full maps. - if (viewer.audioEnabled) { - final MappedData animSounds = (MappedData) tables.get(1).data; + this.ok = true; + } + else if ("SND".equals(this.type)) { + // Only load sounds if audio is enabled. + // This is mostly to save on bandwidth and loading time, especially when loading + // full maps. + if (viewer.audioEnabled) { + final MappedData animSounds = (MappedData) tables.get(1).data; - final MappedDataRow animSoundsRow = animSounds.getRow((String) row.get("SoundLabel")); + final MappedDataRow animSoundsRow = animSounds.getRow((String) row.get("SoundLabel")); - if (animSoundsRow != null) { - this.distanceCutoff = getFloat(animSoundsRow, "DistanceCutoff"); - this.maxDistance = getFloat(animSoundsRow, "MaxDistance"); - this.minDistance = getFloat(animSoundsRow, "MinDistance"); - this.pitch = getFloat(animSoundsRow, "Pitch"); - this.pitchVariance = getFloat(animSoundsRow, "PitchVariance"); - this.volume = getFloat(animSoundsRow, "Volume"); + if (animSoundsRow != null) { + this.distanceCutoff = getFloat(animSoundsRow, "DistanceCutoff"); + this.maxDistance = getFloat(animSoundsRow, "MaxDistance"); + this.minDistance = getFloat(animSoundsRow, "MinDistance"); + this.pitch = getFloat(animSoundsRow, "Pitch"); + this.pitchVariance = getFloat(animSoundsRow, "PitchVariance"); + this.volume = getFloat(animSoundsRow, "Volume") / 127f; - final String[] fileNames = ((String) animSoundsRow.get("FileNames")).split(","); - final GenericResource[] resources = new GenericResource[fileNames.length]; - for (int i = 0; i < fileNames.length; i++) { - final String path = ((String) animSoundsRow.get("DirectoryBase")) + fileNames[i]; - try { - final String pathString = pathSolver.solve(path, model.solverParams).finalSrc; - final GenericResource genericResource = viewer.loadGeneric(pathString, - FetchDataTypeName.ARRAY_BUFFER, new LoadGenericSoundCallback(pathString)); - if (genericResource == null) { - System.err.println("Null sound: " + fileNames[i]); - } - resources[i] = genericResource; - } catch (final Exception exc) { - System.err.println("Failed to load sound: " + path); - exc.printStackTrace(); - } - } + final String[] fileNames = ((String) animSoundsRow.get("FileNames")).split(","); + final GenericResource[] resources = new GenericResource[fileNames.length]; + for (int i = 0; i < fileNames.length; i++) { + final String path = ((String) animSoundsRow.get("DirectoryBase")) + fileNames[i]; + try { + final String pathString = pathSolver.solve(path, model.solverParams).finalSrc; + final GenericResource genericResource = viewer.loadGeneric(pathString, + FetchDataTypeName.ARRAY_BUFFER, new LoadGenericSoundCallback(pathString)); + if (genericResource == null) { + System.err.println("Null sound: " + fileNames[i]); + } + resources[i] = genericResource; + } + catch (final Exception exc) { + System.err.println("Failed to load sound: " + path); + exc.printStackTrace(); + } + } - // TODO JS async removed - for (final GenericResource resource : resources) { - if (resource != null) { - this.decodedBuffers.add((Sound) resource.data); - } - } - this.ok = true; - } - } - } else { - System.err.println("Unknown event object type: " + this.type + this.id); - } - } else { - System.err.println("Unknown event object ID: " + this.type + this.id); - } - } + // TODO JS async removed + for (final GenericResource resource : resources) { + if (resource != null) { + this.decodedBuffers.add((Sound) resource.data); + } + } + this.ok = true; + } + } + } + else { + System.err.println("Unknown event object type: " + this.type + this.id); + } + } + else { + System.err.println("Unknown event object ID: " + this.type + this.id); + } + } - public int getValue(final long[] out, final MdxComplexInstance instance) { - if (this.globalSequence != -1) { + public int getValue(final long[] out, final MdxComplexInstance instance) { + if (this.globalSequence != -1) { - return this.getValueAtTime(out, instance.counter % this.globalSequence, 0, this.globalSequence); - } else if (instance.sequence != -1) { - final long[] interval = this.model.getSequences().get(instance.sequence).getInterval(); + return this.getValueAtTime(out, instance.counter % this.globalSequence, 0, this.globalSequence); + } + else if (instance.sequence != -1) { + final long[] interval = this.model.getSequences().get(instance.sequence).getInterval(); - return this.getValueAtTime(out, instance.frame, interval[0], interval[1]); - } else { - out[0] = this.defval[0]; + return this.getValueAtTime(out, instance.frame, interval[0], interval[1]); + } + else { + out[0] = this.defval[0]; - return -1; - } - } + return -1; + } + } - public int getValueAtTime(final long[] out, final long frame, final long start, final long end) { - if ((frame >= start) && (frame <= end)) { - for (int i = this.keyFrames.length - 1; i > -1; i--) { - if (this.keyFrames[i] < start) { - out[0] = 0; + public int getValueAtTime(final long[] out, final long frame, final long start, final long end) { + if ((frame >= start) && (frame <= end)) { + for (int i = this.keyFrames.length - 1; i > -1; i--) { + if (this.keyFrames[i] < start) { + out[0] = 0; - return i; - } else if (this.keyFrames[i] <= frame) { - out[0] = 1; + return i; + } + else if (this.keyFrames[i] <= frame) { + out[0] = 1; - return i; - } - } - } + return i; + } + } + } - out[0] = 0; + out[0] = 0; - return -1; - } + return -1; + } - @Override - public boolean ok() { - return this.ok; - } + @Override + public boolean ok() { + return this.ok; + } - @Override - public int getGeometryEmitterType() { - return this.geometryEmitterType; - } + @Override + public int getGeometryEmitterType() { + return this.geometryEmitterType; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectSnd.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectSnd.java index 575fea3..8c38305 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectSnd.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectSnd.java @@ -47,7 +47,9 @@ public class EventObjectSnd extends EmittedObject sounds = new ArrayList<>(); private final float volume; private final float pitch; - private final float pitchVariation; + private final float pitchVariance; private final float minDistance; private final float maxDistance; private final float distanceCutoff; private Sound lastPlayedSound; - public static UnitAckSound create(final DataSource dataSource, final DataTable unitAckSounds, final String soundName, - final String soundType) { + public static UnitAckSound create(final DataSource dataSource, final DataTable unitAckSounds, + final String soundName, final String soundType) { final Element row = unitAckSounds.get(soundName + soundType); if (row == null) { return SILENT; @@ -40,13 +40,17 @@ public final class UnitAckSound { if ((directoryBase.length() > 1) && !directoryBase.endsWith("\\")) { directoryBase += "\\"; } - final float volume = row.getFieldFloatValue("Volume"); + final float volume = row.getFieldFloatValue("Volume") / 127f; final float pitch = row.getFieldFloatValue("Pitch"); - final float pitchVariation = row.getFieldFloatValue("PitchVariance"); + float pitchVariance = row.getFieldFloatValue("PitchVariance"); + if (pitchVariance == 1.0f) { + pitchVariance = 0.0f; + } final float minDistance = row.getFieldFloatValue("MinDistance"); final float maxDistance = row.getFieldFloatValue("MaxDistance"); final float distanceCutoff = row.getFieldFloatValue("DistanceCutoff"); - final UnitAckSound sound = new UnitAckSound(volume, pitch, pitchVariation, minDistance, maxDistance, distanceCutoff); + final UnitAckSound sound = new UnitAckSound(volume, pitch, pitchVariance, minDistance, maxDistance, + distanceCutoff); for (final String fileName : fileNames.split(",")) { String filePath = directoryBase + fileName; if (!filePath.toLowerCase().endsWith(".wav")) { @@ -63,7 +67,7 @@ public final class UnitAckSound { final float maxDistance, final float distanceCutoff) { this.volume = volume; this.pitch = pitch; - this.pitchVariation = pitchVariation; + this.pitchVariance = pitchVariation; this.minDistance = minDistance; this.maxDistance = maxDistance; this.distanceCutoff = distanceCutoff; @@ -96,7 +100,8 @@ public final class UnitAckSound { source.connect(panner); // Make a sound. - source.start(0); + source.start(0, this.volume, + (this.pitch + ((float) Math.random() * this.pitchVariance * 2)) - this.pitchVariance); this.lastPlayedSound = source.buffer; final float duration = Extensions.soundLengthExtension.getDuration(this.lastPlayedSound); unit.lastUnitResponseEndTimeMillis = millisTime + (long) (1000 * duration); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitSound.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitSound.java index f0f0585..62b910f 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitSound.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitSound.java @@ -22,7 +22,7 @@ public final class UnitSound { private final List sounds = new ArrayList<>(); private final float volume; private final float pitch; - private final float pitchVariation; + private final float pitchVariance; private final float minDistance; private final float maxDistance; private final float distanceCutoff; @@ -40,13 +40,16 @@ public final class UnitSound { if ((directoryBase.length() > 1) && !directoryBase.endsWith("\\")) { directoryBase += "\\"; } - final float volume = row.getFieldFloatValue("Volume"); + final float volume = row.getFieldFloatValue("Volume") / 127f; final float pitch = row.getFieldFloatValue("Pitch"); - final float pitchVariation = row.getFieldFloatValue("PitchVariance"); + float pitchVariance = row.getFieldFloatValue("PitchVariance"); + if (pitchVariance == 1.0f) { + pitchVariance = 0.0f; + } final float minDistance = row.getFieldFloatValue("MinDistance"); final float maxDistance = row.getFieldFloatValue("MaxDistance"); final float distanceCutoff = row.getFieldFloatValue("DistanceCutoff"); - final UnitSound sound = new UnitSound(volume, pitch, pitchVariation, minDistance, maxDistance, distanceCutoff); + final UnitSound sound = new UnitSound(volume, pitch, pitchVariance, minDistance, maxDistance, distanceCutoff); for (final String fileName : fileNames.split(",")) { String filePath = directoryBase + fileName; if (!filePath.toLowerCase().endsWith(".wav")) { @@ -63,7 +66,7 @@ public final class UnitSound { final float maxDistance, final float distanceCutoff) { this.volume = volume; this.pitch = pitch; - this.pitchVariation = pitchVariation; + this.pitchVariance = pitchVariation; this.minDistance = minDistance; this.maxDistance = maxDistance; this.distanceCutoff = distanceCutoff; @@ -112,7 +115,8 @@ public final class UnitSound { source.connect(panner); // Make a sound. - source.start(0); + source.start(0, this.volume, + (this.pitch + ((float) Math.random() * this.pitchVariance * 2)) - this.pitchVariance); this.lastPlayedSound = source.buffer; return true; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index c148071..fd1ade4 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -96,1402 +96,1446 @@ import mpq.MPQArchive; import mpq.MPQException; public class War3MapViewer extends ModelViewer { - private static final War3ID UNIT_FILE = War3ID.fromString("umdl"); - private static final War3ID UBER_SPLAT = War3ID.fromString("uubs"); - private static final War3ID UNIT_SHADOW = War3ID.fromString("ushu"); - private static final War3ID UNIT_SHADOW_X = War3ID.fromString("ushx"); - private static final War3ID UNIT_SHADOW_Y = War3ID.fromString("ushy"); - private static final War3ID UNIT_SHADOW_W = War3ID.fromString("ushw"); - private static final War3ID UNIT_SHADOW_H = War3ID.fromString("ushh"); - private static final War3ID BUILDING_SHADOW = War3ID.fromString("ushb"); - public static final War3ID UNIT_SELECT_SCALE = War3ID.fromString("ussc"); - private static final War3ID UNIT_SELECT_HEIGHT = War3ID.fromString("uslz"); - private static final War3ID UNIT_SOUNDSET = War3ID.fromString("usnd"); - private static final War3ID ITEM_FILE = War3ID.fromString("ifil"); - private static final War3ID UNIT_PATHING = War3ID.fromString("upat"); - private static final War3ID DESTRUCTABLE_PATHING = War3ID.fromString("bptx"); - private static final War3ID ELEVATION_SAMPLE_RADIUS = War3ID.fromString("uerd"); - private static final War3ID MAX_PITCH = War3ID.fromString("umxp"); - private static final War3ID MAX_ROLL = War3ID.fromString("umxr"); - private static final War3ID sloc = War3ID.fromString("sloc"); - private static final LoadGenericCallback stringDataCallback = new StringDataCallbackImplementation(); - private static final float[] rayHeap = new float[6]; - public static final Ray gdxRayHeap = new Ray(); - private static final Vector2 mousePosHeap = new Vector2(); - private static final Vector3 normalHeap = new Vector3(); - public static final Vector3 intersectionHeap = new Vector3(); - private static final Rectangle rectangleHeap = new Rectangle(); - public static final StreamDataCallbackImplementation streamDataCallback = new StreamDataCallbackImplementation(); - - public PathSolver wc3PathSolver = PathSolver.DEFAULT; - public SolverParams solverParams = new SolverParams(); - public WorldScene worldScene; - public boolean anyReady; - public MappedData terrainData = new MappedData(); - public MappedData cliffTypesData = new MappedData(); - public MappedData waterData = new MappedData(); - public boolean terrainReady; - public boolean cliffsReady; - public boolean doodadsAndDestructiblesLoaded; - public MappedData doodadsData = new MappedData(); - public MappedData doodadMetaData = new MappedData(); - public MappedData destructableMetaData = new MappedData(); - public List doodads = new ArrayList<>(); - public List terrainDoodads = new ArrayList<>(); - public boolean doodadsReady; - public boolean unitsAndItemsLoaded; - public MappedData unitsData = new MappedData(); - public MappedData unitMetaData = new MappedData(); - public List units = new ArrayList<>(); - public List items = new ArrayList<>(); - public List projectiles = new ArrayList<>(); - public boolean unitsReady; - public War3Map mapMpq; - public PathSolver mapPathSolver = PathSolver.DEFAULT; - - private final DataSource gameDataSource; - - public Terrain terrain; - public int renderPathing = 0; - public int renderLighting = 1; - - public List selModels = new ArrayList<>(); - public List selected = new ArrayList<>(); - private DataTable unitAckSoundsTable; - private DataTable unitCombatSoundsTable; - public DataTable miscData; - private DataTable unitGlobalStrings; - public DataTable uiSoundsTable; - private MdxComplexInstance confirmationInstance; - public MdxComplexInstance dncUnit; - public MdxComplexInstance dncUnitDay; - public MdxComplexInstance dncTerrain; - public MdxComplexInstance dncTarget; - public CSimulation simulation; - private float updateTime; - - // for World Editor, I think - public Vector2[] startLocations = new Vector2[WarsmashConstants.MAX_PLAYERS]; - - private final DynamicShadowManager dynamicShadowManager = new DynamicShadowManager(); - - private final Random seededRandom = new Random(1337L); - - private final Map filePathToPathingMap = new HashMap<>(); - - private final List selectionCircleSizes = new ArrayList<>(); - - private final Map unitToRenderPeer = new HashMap<>(); - private final Map unitIdToTypeData = new HashMap<>(); - private GameUI gameUI; - private Vector3 lightDirection; - - private Quadtree walkableObjectsTree; - private final QuadtreeIntersectorFindsWalkableRenderHeight walkablesIntersector = new QuadtreeIntersectorFindsWalkableRenderHeight(); - private final QuadtreeIntersectorFindsHitPoint walkablesIntersectionFinder = new QuadtreeIntersectorFindsHitPoint(); - private final QuadtreeIntersectorFindsHighestWalkable intersectorFindsHighestWalkable = new QuadtreeIntersectorFindsHighestWalkable(); - - private KeyedSounds uiSounds; - - public War3MapViewer(final DataSource dataSource, final CanvasProvider canvas) { - super(dataSource, canvas); - this.gameDataSource = dataSource; - - final WebGL webGL = this.webGL; - - this.addHandler(new MdxHandler()); - - this.wc3PathSolver = PathSolver.DEFAULT; - - this.worldScene = this.addWorldScene(); - - if (!this.dynamicShadowManager.setup(webGL)) { - throw new IllegalStateException("FrameBuffer setup failed"); - } - } - - public void loadSLKs(final WorldEditStrings worldEditStrings) throws IOException { - final GenericResource terrain = this.loadMapGeneric("TerrainArt\\Terrain.slk", FetchDataTypeName.SLK, - stringDataCallback); - final GenericResource cliffTypes = this.loadMapGeneric("TerrainArt\\CliffTypes.slk", FetchDataTypeName.SLK, - stringDataCallback); - final GenericResource water = this.loadMapGeneric("TerrainArt\\Water.slk", FetchDataTypeName.SLK, - stringDataCallback); - - // == when loaded, which is always in our system == - this.terrainData.load(terrain.data.toString()); - this.cliffTypesData.load(cliffTypes.data.toString()); - this.waterData.load(water.data.toString()); - // emit terrain loaded?? - - final GenericResource doodads = this.loadMapGeneric("Doodads\\Doodads.slk", FetchDataTypeName.SLK, - stringDataCallback); - final GenericResource doodadMetaData = this.loadMapGeneric("Doodads\\DoodadMetaData.slk", FetchDataTypeName.SLK, - stringDataCallback); - final GenericResource destructableData = this.loadMapGeneric("Units\\DestructableData.slk", - FetchDataTypeName.SLK, stringDataCallback); - final GenericResource destructableMetaData = this.loadMapGeneric("Units\\DestructableMetaData.slk", - FetchDataTypeName.SLK, stringDataCallback); - - // == when loaded, which is always in our system == - this.doodadsAndDestructiblesLoaded = true; - this.doodadsData.load(doodads.data.toString()); - this.doodadMetaData.load(doodadMetaData.data.toString()); - this.doodadsData.load(destructableData.data.toString()); - this.destructableMetaData.load(destructableData.data.toString()); - // emit doodads loaded - - final GenericResource unitData = this.loadMapGeneric("Units\\UnitData.slk", FetchDataTypeName.SLK, - stringDataCallback); - final GenericResource unitUi = this.loadMapGeneric("Units\\unitUI.slk", FetchDataTypeName.SLK, - stringDataCallback); - final GenericResource itemData = this.loadMapGeneric("Units\\ItemData.slk", FetchDataTypeName.SLK, - stringDataCallback); - final GenericResource unitMetaData = this.loadMapGeneric("Units\\UnitMetaData.slk", FetchDataTypeName.SLK, - stringDataCallback); - - // == when loaded, which is always in our system == - this.unitsAndItemsLoaded = true; - this.unitsData.load(unitData.data.toString()); - this.unitsData.load(unitUi.data.toString()); - this.unitsData.load(itemData.data.toString()); - this.unitMetaData.load(unitMetaData.data.toString()); - // emit loaded - - this.unitAckSoundsTable = new DataTable(worldEditStrings); - try (InputStream terrainSlkStream = this.dataSource.getResourceAsStream("UI\\SoundInfo\\UnitAckSounds.slk")) { - this.unitAckSoundsTable.readSLK(terrainSlkStream); - } - this.unitCombatSoundsTable = new DataTable(worldEditStrings); - try (InputStream terrainSlkStream = this.dataSource - .getResourceAsStream("UI\\SoundInfo\\UnitCombatSounds.slk")) { - this.unitCombatSoundsTable.readSLK(terrainSlkStream); - } - this.miscData = new DataTable(worldEditStrings); - try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("UI\\MiscData.txt")) { - this.miscData.readTXT(miscDataTxtStream, true); - } - try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\MiscData.txt")) { - this.miscData.readTXT(miscDataTxtStream, true); - } - try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\MiscGame.txt")) { - this.miscData.readTXT(miscDataTxtStream, true); - } - try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("UI\\SoundInfo\\MiscData.txt")) { - this.miscData.readTXT(miscDataTxtStream, true); - } - if (this.dataSource.has("war3mapMisc.txt")) { - try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("war3mapMisc.txt")) { - this.miscData.readTXT(miscDataTxtStream, true); - } - } - final Element light = this.miscData.get("Light"); - final float lightX = light.getFieldFloatValue("Direction", 0); - final float lightY = light.getFieldFloatValue("Direction", 1); - final float lightZ = light.getFieldFloatValue("Direction", 2); - this.lightDirection = new Vector3(lightX, lightY, lightZ).nor(); - this.unitGlobalStrings = new DataTable(worldEditStrings); - try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\UnitGlobalStrings.txt")) { - this.unitGlobalStrings.readTXT(miscDataTxtStream, true); - } - final Element categories = this.unitGlobalStrings.get("Categories"); - for (final CUnitClassification unitClassification : CUnitClassification.values()) { - if (unitClassification.getLocaleKey() != null) { - final String displayName = categories.getField(unitClassification.getLocaleKey()); - unitClassification.setDisplayName(displayName); - } - } - this.selectionCircleSizes.clear(); - final Element selectionCircleData = this.miscData.get("SelectionCircle"); - final int selectionCircleNumSizes = selectionCircleData.getFieldValue("NumSizes"); - for (int i = 0; i < selectionCircleNumSizes; i++) { - final String indexString = i < 10 ? "0" + i : Integer.toString(i); - final float size = selectionCircleData.getFieldFloatValue("Size" + indexString); - final String texture = selectionCircleData.getField("Texture" + indexString); - final String textureDotted = selectionCircleData.getField("TextureDotted" + indexString); - this.selectionCircleSizes.add(new SelectionCircleSize(size, texture, textureDotted)); - } - this.selectionCircleScaleFactor = selectionCircleData.getFieldFloatValue("ScaleFactor"); - - this.uiSoundsTable = new DataTable(worldEditStrings); - try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("UI\\SoundInfo\\UISounds.slk")) { - this.uiSoundsTable.readSLK(miscDataTxtStream); - } - } - - public GenericResource loadMapGeneric(final String path, final FetchDataTypeName dataType, - final LoadGenericCallback callback) { - if (this.mapMpq == null) { - return loadGeneric(path, dataType, callback); - } else { - return loadGeneric(path, dataType, callback, this.dataSource); - } - } - - public void loadMap(final String mapFilePath) throws IOException { - final War3Map war3Map = new War3Map(this.gameDataSource, mapFilePath); - - this.mapMpq = war3Map; - - final PathSolver wc3PathSolver = this.wc3PathSolver; - - char tileset = 'A'; - - final War3MapW3i w3iFile = this.mapMpq.readMapInformation(); - - tileset = w3iFile.getTileset(); - - DataSource tilesetSource; - try { - // Slightly complex. Here's the theory: - // 1.) Copy map into RAM - // 2.) Setup a Data Source that will read assets - // from either the map or the game, giving the map priority. - SeekableByteChannel sbc; - final CompoundDataSource compoundDataSource = war3Map.getCompoundDataSource(); - try (InputStream mapStream = compoundDataSource.getResourceAsStream(tileset + ".mpq")) { - if (mapStream == null) { - tilesetSource = new CompoundDataSource(Arrays.asList(compoundDataSource, - new SubdirDataSource(compoundDataSource, tileset + ".mpq/"), - new SubdirDataSource(compoundDataSource, "_tilesets/" + tileset + ".w3mod/"))); - } else { - final byte[] mapData = IOUtils.toByteArray(mapStream); - sbc = new SeekableInMemoryByteChannel(mapData); - final DataSource internalMpqContentsDataSource = new MpqDataSource(new MPQArchive(sbc), sbc); - tilesetSource = new CompoundDataSource( - Arrays.asList(compoundDataSource, internalMpqContentsDataSource)); - } - } catch (final IOException exc) { - tilesetSource = new CompoundDataSource( - Arrays.asList(compoundDataSource, new SubdirDataSource(compoundDataSource, tileset + ".mpq/"), - new SubdirDataSource(compoundDataSource, "_tilesets/" + tileset + ".w3mod/"))); - } - } catch (final MPQException e) { - throw new RuntimeException(e); - } - setDataSource(tilesetSource); - this.worldEditStrings = new WorldEditStrings(this.dataSource); - loadSLKs(this.worldEditStrings); - - this.solverParams.tileset = Character.toLowerCase(tileset); - - final War3MapW3e terrainData = this.mapMpq.readEnvironment(); - - final War3MapWpm terrainPathing = this.mapMpq.readPathing(); - - final StandardObjectData standardObjectData = new StandardObjectData(this.dataSource); - this.worldEditData = standardObjectData.getWorldEditData(); - - this.terrain = new Terrain(terrainData, terrainPathing, w3iFile, this.webGL, this.dataSource, - this.worldEditStrings, this, this.worldEditData); - - final float[] centerOffset = terrainData.getCenterOffset(); - final int[] mapSize = terrainData.getMapSize(); - - this.terrainReady = true; - this.anyReady = true; - this.cliffsReady = true; - - // Override the grid based on the map. - this.worldScene.grid = new Grid(centerOffset[0], centerOffset[1], (mapSize[0] * 128) - 128, - (mapSize[1] * 128) - 128, 16 * 128, 16 * 128); - - final MdxModel confirmation = (MdxModel) load("UI\\Feedback\\Confirmation\\Confirmation.mdx", - PathSolver.DEFAULT, null); - this.confirmationInstance = (MdxComplexInstance) confirmation.addInstance(); - this.confirmationInstance.setSequenceLoopMode(SequenceLoopMode.NEVER_LOOP_AND_HIDE_WHEN_DONE); - this.confirmationInstance.setSequence(0); - this.confirmationInstance.setScene(this.worldScene); - - this.allObjectData = this.mapMpq.readModifications(); - this.simulation = new CSimulation(this.miscData, this.allObjectData.getUnits(), - this.allObjectData.getAbilities(), new SimulationRenderController() { - private final Map keyToCombatSound = new HashMap<>(); - - @Override - public CAttackProjectile createAttackProjectile(final CSimulation simulation, final float launchX, - final float launchY, final float launchFacing, final CUnit source, - final CUnitAttackMissile unitAttack, final CWidget target, final float damage, - final int bounceIndex) { - final War3ID typeId = source.getTypeId(); - final int projectileSpeed = unitAttack.getProjectileSpeed(); - final float projectileArc = unitAttack.getProjectileArc(); - String missileArt = unitAttack.getProjectileArt(); - final float projectileLaunchX = simulation.getUnitData().getProjectileLaunchX(typeId); - final float projectileLaunchY = simulation.getUnitData().getProjectileLaunchY(typeId); - final float projectileLaunchZ = simulation.getUnitData().getProjectileLaunchZ(typeId); - - missileArt = mdx(missileArt); - final float facing = launchFacing; - final float sinFacing = (float) Math.sin(facing); - final float cosFacing = (float) Math.cos(facing); - final float x = (launchX + (projectileLaunchY * cosFacing)) + (projectileLaunchX * sinFacing); - final float y = (launchY + (projectileLaunchY * sinFacing)) - (projectileLaunchX * cosFacing); - - final float height = War3MapViewer.this.terrain.getGroundHeight(x, y) + source.getFlyHeight() - + projectileLaunchZ; - final CAttackProjectile simulationAttackProjectile = new CAttackProjectile(x, y, - projectileSpeed, target, source, damage, unitAttack, bounceIndex); - - final MdxModel model = (MdxModel) load(missileArt, War3MapViewer.this.mapPathSolver, - War3MapViewer.this.solverParams); - final MdxComplexInstance modelInstance = (MdxComplexInstance) model.addInstance(); - modelInstance.setTeamColor(source.getPlayerIndex()); - modelInstance.setScene(War3MapViewer.this.worldScene); - if (bounceIndex == 0) { - SequenceUtils.randomBirthSequence(modelInstance); - } else { - SequenceUtils.randomStandSequence(modelInstance); - } - modelInstance.setLocation(x, y, height); - final RenderAttackProjectile renderAttackProjectile = new RenderAttackProjectile( - simulationAttackProjectile, modelInstance, height, projectileArc, War3MapViewer.this); - - War3MapViewer.this.projectiles.add(renderAttackProjectile); - - return simulationAttackProjectile; - } - - @Override - public void createInstantAttackEffect(final CSimulation cSimulation, final CUnit source, - final CUnitAttackInstant unitAttack, final CWidget target) { - final War3ID typeId = source.getTypeId(); - - String missileArt = unitAttack.getProjectileArt(); - final float projectileLaunchX = War3MapViewer.this.simulation.getUnitData() - .getProjectileLaunchX(typeId); - final float projectileLaunchY = War3MapViewer.this.simulation.getUnitData() - .getProjectileLaunchY(typeId); - missileArt = mdx(missileArt); - final float facing = (float) Math.toRadians(source.getFacing()); - final float sinFacing = (float) Math.sin(facing); - final float cosFacing = (float) Math.cos(facing); - final float x = (source.getX() + (projectileLaunchY * cosFacing)) - + (projectileLaunchX * sinFacing); - final float y = (source.getY() + (projectileLaunchY * sinFacing)) - - (projectileLaunchX * cosFacing); - - final float targetX = target.getX(); - final float targetY = target.getY(); - final float angleToTarget = (float) Math.atan2(targetY - y, targetX - x); - - final float height = War3MapViewer.this.terrain.getGroundHeight(targetX, targetY) - + target.getFlyHeight() + target.getImpactZ(); - - final MdxModel model = (MdxModel) load(missileArt, War3MapViewer.this.mapPathSolver, - War3MapViewer.this.solverParams); - final MdxComplexInstance modelInstance = (MdxComplexInstance) model.addInstance(); - modelInstance.setTeamColor(source.getPlayerIndex()); - modelInstance.setScene(War3MapViewer.this.worldScene); - SequenceUtils.randomBirthSequence(modelInstance); - modelInstance.setLocation(targetX, targetY, height); - War3MapViewer.this.projectiles - .add(new RenderAttackInstant(modelInstance, War3MapViewer.this, angleToTarget)); - } - - @Override - public void spawnUnitDamageSound(final CUnit damagedUnit, final String weaponSound, - final String armorType) { - final String key = weaponSound + armorType; - UnitSound combatSound = this.keyToCombatSound.get(key); - if (combatSound == null) { - combatSound = UnitSound.create(War3MapViewer.this.dataSource, - War3MapViewer.this.unitCombatSoundsTable, weaponSound, armorType); - this.keyToCombatSound.put(key, combatSound); - } - combatSound.play(War3MapViewer.this.worldScene.audioContext, damagedUnit.getX(), - damagedUnit.getY()); - } - - @Override - public void spawnUnitConstructionSound(CUnit constructingUnit, CUnit constructedStructure) { - UnitSound constructingBuilding = uiSounds.getSound(gameUI.getSkinField("ConstructingBuilding")); - if (constructingBuilding != null) { - constructingBuilding.play(worldScene.audioContext, constructedStructure.getX(), constructedStructure.getY()); - } - } - - @Override - public void removeUnit(final CUnit unit) { - final RenderUnit renderUnit = War3MapViewer.this.unitToRenderPeer.remove(unit); - War3MapViewer.this.units.remove(renderUnit); - War3MapViewer.this.worldScene.removeInstance(renderUnit.instance); - } - - @Override - public BufferedImage getBuildingPathingPixelMap(final War3ID rawcode) { - return War3MapViewer.this - .getBuildingPathingPixelMap(War3MapViewer.this.allObjectData.getUnits().get(rawcode)); - } - - @Override - public void spawnUnitConstructionFinishSound(CUnit constructedStructure) { - UnitSound constructingBuilding = uiSounds.getSound(gameUI.getSkinField("JobDoneSound")); - if (constructingBuilding != null) { - constructingBuilding.play(worldScene.audioContext, constructedStructure.getX(), constructedStructure.getY()); - } - } - - @Override - public CUnit createUnit(final CSimulation simulation, final War3ID typeId, final int playerIndex, - final float x, final float y, final float facing) { - return createNewUnit(War3MapViewer.this.allObjectData, typeId, x, y, 0f, playerIndex, - (float) Math.toRadians(facing)); - } - }, this.terrain.pathingGrid, this.terrain.getEntireMap(), this.seededRandom, w3iFile.getPlayers()); - - this.walkableObjectsTree = new Quadtree<>(this.terrain.getEntireMap()); - if (this.doodadsAndDestructiblesLoaded) { - this.loadDoodadsAndDestructibles(this.allObjectData); - } else { - throw new IllegalStateException("transcription of JS has not loaded a map and has no JS async promises"); - } - - loadSounds(); - - this.terrain.createWaves(); - - loadLightsAndShading(tileset); - } - - private void loadSounds() { - this.uiSounds = new KeyedSounds(this.uiSoundsTable, this.mapMpq); - } - - /** - * Loads the map information that should be loaded after UI, such as units, who - * need to be able to setup their UI counterparts (icons, etc) for their - * abilities while loading. This allows the dynamic creation of units while the - * game is playing to better share code with the startup sequence's creation of - * units. - * - * @throws IOException - */ - public void loadAfterUI() throws IOException { - if (this.unitsAndItemsLoaded) { - this.loadUnitsAndItems(this.allObjectData); - } else { - throw new IllegalStateException("transcription of JS has not loaded a map and has no JS async promises"); - } - - // After we finish loading units, we need to update & create the stored shadow - // information for all unit shadows - this.terrain.initShadows(); - } - - private void loadLightsAndShading(final char tileset) { - // TODO this should be set by the war3map.j actually, not by the tileset, so the - // call to set day night models is just for testing to make the test look pretty - final Element defaultTerrainLights = this.worldEditData.get("TerrainLights"); - final Element defaultUnitLights = this.worldEditData.get("UnitLights"); - setDayNightModels(defaultTerrainLights.getField(Character.toString(tileset)), - defaultUnitLights.getField(Character.toString(tileset))); - - } - - private void loadDoodadsAndDestructibles(final Warcraft3MapObjectData modifications) throws IOException { - this.applyModificationFile(this.doodadsData, this.doodadMetaData, modifications.getDoodads(), - WorldEditorDataType.DOODADS); - this.applyModificationFile(this.doodadsData, this.destructableMetaData, modifications.getDestructibles(), - WorldEditorDataType.DESTRUCTIBLES); - - final War3MapDoo doo = this.mapMpq.readDoodads(); - - for (final com.etheller.warsmash.parsers.w3x.doo.Doodad doodad : doo.getDoodads()) { - WorldEditorDataType type = WorldEditorDataType.DOODADS; - MutableGameObject row = modifications.getDoodads().get(doodad.getId()); - if (row == null) { - row = modifications.getDestructibles().get(doodad.getId()); - type = WorldEditorDataType.DESTRUCTIBLES; - } - if (row != null) { - String file = row.readSLKTag("file"); - final int numVar = row.readSLKTagInt("numVar"); - - if (file.endsWith(".mdx") || file.endsWith(".mdl")) { - file = file.substring(0, file.length() - 4); - } - - String fileVar = file; - - file += ".mdx"; - - if (numVar > 1) { - fileVar += Math.min(doodad.getVariation(), numVar - 1); - } - - fileVar += ".mdx"; - - final float maxPitch = row.readSLKTagFloat("maxPitch"); - final float maxRoll = row.readSLKTagFloat("maxRoll"); - if (type == WorldEditorDataType.DESTRUCTIBLES) { - final String shadowString = row.readSLKTag("shadow"); - if ((shadowString != null) && (shadowString.length() > 0) && !"_".equals(shadowString)) { - this.terrain.addShadow(shadowString, doodad.getLocation()[0], doodad.getLocation()[1]); - } - - final String pathingTexture = row.readSLKTag("pathTex"); - if ((pathingTexture != null) && (pathingTexture.length() > 0) && !"_".equals(pathingTexture)) { - - BufferedImage bufferedImage = this.filePathToPathingMap.get(pathingTexture.toLowerCase()); - if (bufferedImage == null) { - if (this.mapMpq.has(pathingTexture)) { - try { - bufferedImage = TgaFile.readTGA(pathingTexture, - this.mapMpq.getResourceAsStream(pathingTexture)); - this.filePathToPathingMap.put(pathingTexture.toLowerCase(), bufferedImage); - } catch (final Exception exc) { - exc.printStackTrace(); - } - } - } - if (bufferedImage != null) { - this.terrain.pathingGrid.blitPathingOverlayTexture(doodad.getLocation()[0], - doodad.getLocation()[1], (int) Math.toDegrees(doodad.getAngle()), bufferedImage); - } - } - } - // First see if the model is local. - // Doodads referring to local models may have invalid variations, so if the - // variation doesn't exist, try without a variation. - - String path; - if (this.mapMpq.has(fileVar)) { - path = fileVar; - } else { - path = file; - } - MdxModel model; - if (this.mapMpq.has(path)) { - model = (MdxModel) this.load(path, this.mapPathSolver, this.solverParams); - } else { - model = (MdxModel) this.load(fileVar, this.mapPathSolver, this.solverParams); - } - - if (type == WorldEditorDataType.DESTRUCTIBLES) { - final RenderDestructable renderDestructable = new RenderDestructable(this, model, row, doodad, type, - maxPitch, maxRoll, doodad.getLife()); - if (row.readSLKTagBoolean("walkable")) { - final float x = doodad.getLocation()[0]; - final float y = doodad.getLocation()[1]; - final BoundingBox boundingBox = model.bounds.getBoundingBox(); - final float minX = boundingBox.min.x + x; - final float minY = boundingBox.min.y + y; - final Rectangle renderDestructableBounds = new Rectangle(minX, minY, boundingBox.getWidth(), - boundingBox.getHeight()); - this.walkableObjectsTree.add((MdxComplexInstance) renderDestructable.instance, - renderDestructableBounds); - } - this.doodads.add(renderDestructable); - } else { - this.doodads.add(new RenderDoodad(this, model, row, doodad, type, maxPitch, maxRoll)); - } - } - } - - // Cliff/Terrain doodads. - for (final com.etheller.warsmash.parsers.w3x.doo.TerrainDoodad doodad : doo.getTerrainDoodads()) { - final MutableGameObject row = modifications.getDoodads().get(doodad.getId()); - String file = row.readSLKTag("file");// - if ("".equals(file)) { - final String blaBla = row.readSLKTag("file"); - System.out.println("bla"); - } - if (file.toLowerCase().endsWith(".mdl")) { - file = file.substring(0, file.length() - 4); - } - if (!file.toLowerCase().endsWith(".mdx")) { - file += ".mdx"; - } - final MdxModel model = (MdxModel) this.load(file, this.mapPathSolver, this.solverParams); - - final String pathingTexture = row.readSLKTag("pathTex"); - BufferedImage pathingTextureImage; - if ((pathingTexture != null) && (pathingTexture.length() > 0) && !"_".equals(pathingTexture)) { - - pathingTextureImage = this.filePathToPathingMap.get(pathingTexture.toLowerCase()); - if (pathingTextureImage == null) { - if (this.mapMpq.has(pathingTexture)) { - try { - pathingTextureImage = TgaFile.readTGA(pathingTexture, - this.mapMpq.getResourceAsStream(pathingTexture)); - this.filePathToPathingMap.put(pathingTexture.toLowerCase(), pathingTextureImage); - } catch (final Exception exc) { - exc.printStackTrace(); - } - } - } - } else { - pathingTextureImage = null; - } - if (pathingTextureImage != null) { - // blit out terrain cells under this TerrainDoodad - final int textureWidth = pathingTextureImage.getWidth(); - final int textureHeight = pathingTextureImage.getHeight(); - final int textureWidthTerrainCells = textureWidth / 4; - final int textureHeightTerrainCells = textureHeight / 4; - final int minCellX = ((int) doodad.getLocation()[0]); - final int minCellY = ((int) doodad.getLocation()[1]); - final int maxCellX = (minCellX + textureWidthTerrainCells) - 1; - final int maxCellY = (minCellY + textureHeightTerrainCells) - 1; - for (int j = minCellY; j <= maxCellY; j++) { - for (int i = minCellX; i <= maxCellX; i++) { - this.terrain.removeTerrainCellWithoutFlush(i, j); - } - } - this.terrain.flushRemovedTerrainCells(); - } - - System.out.println("Loading terrain doodad: " + file); - this.terrainDoodads.add(new TerrainDoodad(this, model, row, doodad, pathingTextureImage)); - } - - this.doodadsReady = true; - this.anyReady = true; - } - - private void applyModificationFile(final MappedData doodadsData2, final MappedData doodadMetaData2, - final MutableObjectData destructibles, final WorldEditorDataType dataType) { - // TODO condense ported MappedData from Ghostwolf and MutableObjectData from - // Retera - - } - - private void loadUnitsAndItems(final Warcraft3MapObjectData modifications) throws IOException { - final War3Map mpq = this.mapMpq; - this.unitsReady = false; - - this.soundsetNameToSoundset = new HashMap<>(); - - if (this.dataSource.has("war3mapUnits.doo")) { - final War3MapUnitsDoo dooFile = mpq.readUnits(); - - // Collect the units and items data. - for (final com.etheller.warsmash.parsers.w3x.unitsdoo.Unit unit : dooFile.getUnits()) { - final War3ID unitId = unit.getId(); - final float unitX = unit.getLocation()[0]; - final float unitY = unit.getLocation()[1]; - final float unitZ = unit.getLocation()[2]; - final int playerIndex = unit.getPlayer(); - final float unitAngle = unit.getAngle(); - - createNewUnit(modifications, unitId, unitX, unitY, unitZ, playerIndex, unitAngle); - } - } - - this.terrain.loadSplats(); - - this.unitsReady = true; - this.anyReady = true; - } - - private CUnit createNewUnit(final Warcraft3MapObjectData modifications, final War3ID unitId, float unitX, - float unitY, final float unitZ, final int playerIndex, final float unitAngle) { - UnitSoundset soundset = null; - MutableGameObject row = null; - String path = null; - Splat unitShadowSplat = null; - BufferedImage buildingPathingPixelMap = null; - final float unitVertexScale = 1.0f; - - // Hardcoded? - WorldEditorDataType type = null; - if (sloc.equals(unitId)) { + private static final War3ID UNIT_FILE = War3ID.fromString("umdl"); + private static final War3ID UBER_SPLAT = War3ID.fromString("uubs"); + private static final War3ID UNIT_SHADOW = War3ID.fromString("ushu"); + private static final War3ID UNIT_SHADOW_X = War3ID.fromString("ushx"); + private static final War3ID UNIT_SHADOW_Y = War3ID.fromString("ushy"); + private static final War3ID UNIT_SHADOW_W = War3ID.fromString("ushw"); + private static final War3ID UNIT_SHADOW_H = War3ID.fromString("ushh"); + private static final War3ID BUILDING_SHADOW = War3ID.fromString("ushb"); + public static final War3ID UNIT_SELECT_SCALE = War3ID.fromString("ussc"); + private static final War3ID UNIT_SELECT_HEIGHT = War3ID.fromString("uslz"); + private static final War3ID UNIT_SOUNDSET = War3ID.fromString("usnd"); + private static final War3ID ITEM_FILE = War3ID.fromString("ifil"); + private static final War3ID UNIT_PATHING = War3ID.fromString("upat"); + private static final War3ID DESTRUCTABLE_PATHING = War3ID.fromString("bptx"); + private static final War3ID ELEVATION_SAMPLE_RADIUS = War3ID.fromString("uerd"); + private static final War3ID MAX_PITCH = War3ID.fromString("umxp"); + private static final War3ID MAX_ROLL = War3ID.fromString("umxr"); + private static final War3ID sloc = War3ID.fromString("sloc"); + private static final LoadGenericCallback stringDataCallback = new StringDataCallbackImplementation(); + private static final float[] rayHeap = new float[6]; + public static final Ray gdxRayHeap = new Ray(); + private static final Vector2 mousePosHeap = new Vector2(); + private static final Vector3 normalHeap = new Vector3(); + public static final Vector3 intersectionHeap = new Vector3(); + private static final Rectangle rectangleHeap = new Rectangle(); + public static final StreamDataCallbackImplementation streamDataCallback = new StreamDataCallbackImplementation(); + + public PathSolver wc3PathSolver = PathSolver.DEFAULT; + public SolverParams solverParams = new SolverParams(); + public WorldScene worldScene; + public boolean anyReady; + public MappedData terrainData = new MappedData(); + public MappedData cliffTypesData = new MappedData(); + public MappedData waterData = new MappedData(); + public boolean terrainReady; + public boolean cliffsReady; + public boolean doodadsAndDestructiblesLoaded; + public MappedData doodadsData = new MappedData(); + public MappedData doodadMetaData = new MappedData(); + public MappedData destructableMetaData = new MappedData(); + public List doodads = new ArrayList<>(); + public List terrainDoodads = new ArrayList<>(); + public boolean doodadsReady; + public boolean unitsAndItemsLoaded; + public MappedData unitsData = new MappedData(); + public MappedData unitMetaData = new MappedData(); + public List units = new ArrayList<>(); + public List items = new ArrayList<>(); + public List projectiles = new ArrayList<>(); + public boolean unitsReady; + public War3Map mapMpq; + public PathSolver mapPathSolver = PathSolver.DEFAULT; + + private final DataSource gameDataSource; + + public Terrain terrain; + public int renderPathing = 0; + public int renderLighting = 1; + + public List selModels = new ArrayList<>(); + public List selected = new ArrayList<>(); + private DataTable unitAckSoundsTable; + private DataTable unitCombatSoundsTable; + public DataTable miscData; + private DataTable unitGlobalStrings; + public DataTable uiSoundsTable; + private MdxComplexInstance confirmationInstance; + public MdxComplexInstance dncUnit; + public MdxComplexInstance dncUnitDay; + public MdxComplexInstance dncTerrain; + public MdxComplexInstance dncTarget; + public CSimulation simulation; + private float updateTime; + + // for World Editor, I think + public Vector2[] startLocations = new Vector2[WarsmashConstants.MAX_PLAYERS]; + + private final DynamicShadowManager dynamicShadowManager = new DynamicShadowManager(); + + private final Random seededRandom = new Random(1337L); + + private final Map filePathToPathingMap = new HashMap<>(); + + private final List selectionCircleSizes = new ArrayList<>(); + + private final Map unitToRenderPeer = new HashMap<>(); + private final Map unitIdToTypeData = new HashMap<>(); + private GameUI gameUI; + private Vector3 lightDirection; + + private Quadtree walkableObjectsTree; + private final QuadtreeIntersectorFindsWalkableRenderHeight walkablesIntersector = new QuadtreeIntersectorFindsWalkableRenderHeight(); + private final QuadtreeIntersectorFindsHitPoint walkablesIntersectionFinder = new QuadtreeIntersectorFindsHitPoint(); + private final QuadtreeIntersectorFindsHighestWalkable intersectorFindsHighestWalkable = new QuadtreeIntersectorFindsHighestWalkable(); + + private KeyedSounds uiSounds; + private int localPlayerIndex; + + public War3MapViewer(final DataSource dataSource, final CanvasProvider canvas) { + super(dataSource, canvas); + this.gameDataSource = dataSource; + + final WebGL webGL = this.webGL; + + this.addHandler(new MdxHandler()); + + this.wc3PathSolver = PathSolver.DEFAULT; + + this.worldScene = this.addWorldScene(); + + if (!this.dynamicShadowManager.setup(webGL)) { + throw new IllegalStateException("FrameBuffer setup failed"); + } + } + + public void loadSLKs(final WorldEditStrings worldEditStrings) throws IOException { + final GenericResource terrain = this.loadMapGeneric("TerrainArt\\Terrain.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource cliffTypes = this.loadMapGeneric("TerrainArt\\CliffTypes.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource water = this.loadMapGeneric("TerrainArt\\Water.slk", FetchDataTypeName.SLK, + stringDataCallback); + + // == when loaded, which is always in our system == + this.terrainData.load(terrain.data.toString()); + this.cliffTypesData.load(cliffTypes.data.toString()); + this.waterData.load(water.data.toString()); + // emit terrain loaded?? + + final GenericResource doodads = this.loadMapGeneric("Doodads\\Doodads.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource doodadMetaData = this.loadMapGeneric("Doodads\\DoodadMetaData.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource destructableData = this.loadMapGeneric("Units\\DestructableData.slk", + FetchDataTypeName.SLK, stringDataCallback); + final GenericResource destructableMetaData = this.loadMapGeneric("Units\\DestructableMetaData.slk", + FetchDataTypeName.SLK, stringDataCallback); + + // == when loaded, which is always in our system == + this.doodadsAndDestructiblesLoaded = true; + this.doodadsData.load(doodads.data.toString()); + this.doodadMetaData.load(doodadMetaData.data.toString()); + this.doodadsData.load(destructableData.data.toString()); + this.destructableMetaData.load(destructableData.data.toString()); + // emit doodads loaded + + final GenericResource unitData = this.loadMapGeneric("Units\\UnitData.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource unitUi = this.loadMapGeneric("Units\\unitUI.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource itemData = this.loadMapGeneric("Units\\ItemData.slk", FetchDataTypeName.SLK, + stringDataCallback); + final GenericResource unitMetaData = this.loadMapGeneric("Units\\UnitMetaData.slk", FetchDataTypeName.SLK, + stringDataCallback); + + // == when loaded, which is always in our system == + this.unitsAndItemsLoaded = true; + this.unitsData.load(unitData.data.toString()); + this.unitsData.load(unitUi.data.toString()); + this.unitsData.load(itemData.data.toString()); + this.unitMetaData.load(unitMetaData.data.toString()); + // emit loaded + + this.unitAckSoundsTable = new DataTable(worldEditStrings); + try (InputStream terrainSlkStream = this.dataSource.getResourceAsStream("UI\\SoundInfo\\UnitAckSounds.slk")) { + this.unitAckSoundsTable.readSLK(terrainSlkStream); + } + this.unitCombatSoundsTable = new DataTable(worldEditStrings); + try (InputStream terrainSlkStream = this.dataSource + .getResourceAsStream("UI\\SoundInfo\\UnitCombatSounds.slk")) { + this.unitCombatSoundsTable.readSLK(terrainSlkStream); + } + this.miscData = new DataTable(worldEditStrings); + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("UI\\MiscData.txt")) { + this.miscData.readTXT(miscDataTxtStream, true); + } + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\MiscData.txt")) { + this.miscData.readTXT(miscDataTxtStream, true); + } + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\MiscGame.txt")) { + this.miscData.readTXT(miscDataTxtStream, true); + } + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("UI\\SoundInfo\\MiscData.txt")) { + this.miscData.readTXT(miscDataTxtStream, true); + } + if (this.dataSource.has("war3mapMisc.txt")) { + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("war3mapMisc.txt")) { + this.miscData.readTXT(miscDataTxtStream, true); + } + } + final Element light = this.miscData.get("Light"); + final float lightX = light.getFieldFloatValue("Direction", 0); + final float lightY = light.getFieldFloatValue("Direction", 1); + final float lightZ = light.getFieldFloatValue("Direction", 2); + this.lightDirection = new Vector3(lightX, lightY, lightZ).nor(); + this.unitGlobalStrings = new DataTable(worldEditStrings); + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\UnitGlobalStrings.txt")) { + this.unitGlobalStrings.readTXT(miscDataTxtStream, true); + } + final Element categories = this.unitGlobalStrings.get("Categories"); + for (final CUnitClassification unitClassification : CUnitClassification.values()) { + if (unitClassification.getLocaleKey() != null) { + final String displayName = categories.getField(unitClassification.getLocaleKey()); + unitClassification.setDisplayName(displayName); + } + } + this.selectionCircleSizes.clear(); + final Element selectionCircleData = this.miscData.get("SelectionCircle"); + final int selectionCircleNumSizes = selectionCircleData.getFieldValue("NumSizes"); + for (int i = 0; i < selectionCircleNumSizes; i++) { + final String indexString = i < 10 ? "0" + i : Integer.toString(i); + final float size = selectionCircleData.getFieldFloatValue("Size" + indexString); + final String texture = selectionCircleData.getField("Texture" + indexString); + final String textureDotted = selectionCircleData.getField("TextureDotted" + indexString); + this.selectionCircleSizes.add(new SelectionCircleSize(size, texture, textureDotted)); + } + this.selectionCircleScaleFactor = selectionCircleData.getFieldFloatValue("ScaleFactor"); + + this.uiSoundsTable = new DataTable(worldEditStrings); + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("UI\\SoundInfo\\UISounds.slk")) { + this.uiSoundsTable.readSLK(miscDataTxtStream); + } + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("UI\\SoundInfo\\AmbienceSounds.slk")) { + this.uiSoundsTable.readSLK(miscDataTxtStream); + } + } + + public GenericResource loadMapGeneric(final String path, final FetchDataTypeName dataType, + final LoadGenericCallback callback) { + if (this.mapMpq == null) { + return loadGeneric(path, dataType, callback); + } + else { + return loadGeneric(path, dataType, callback, this.dataSource); + } + } + + public void loadMap(final String mapFilePath, final int localPlayerIndex) throws IOException { + this.localPlayerIndex = localPlayerIndex; + final War3Map war3Map = new War3Map(this.gameDataSource, mapFilePath); + + this.mapMpq = war3Map; + + final PathSolver wc3PathSolver = this.wc3PathSolver; + + char tileset = 'A'; + + final War3MapW3i w3iFile = this.mapMpq.readMapInformation(); + + tileset = w3iFile.getTileset(); + + DataSource tilesetSource; + try { + // Slightly complex. Here's the theory: + // 1.) Copy map into RAM + // 2.) Setup a Data Source that will read assets + // from either the map or the game, giving the map priority. + SeekableByteChannel sbc; + final CompoundDataSource compoundDataSource = war3Map.getCompoundDataSource(); + try (InputStream mapStream = compoundDataSource.getResourceAsStream(tileset + ".mpq")) { + if (mapStream == null) { + tilesetSource = new CompoundDataSource(Arrays.asList(compoundDataSource, + new SubdirDataSource(compoundDataSource, tileset + ".mpq/"), + new SubdirDataSource(compoundDataSource, "_tilesets/" + tileset + ".w3mod/"))); + } + else { + final byte[] mapData = IOUtils.toByteArray(mapStream); + sbc = new SeekableInMemoryByteChannel(mapData); + final DataSource internalMpqContentsDataSource = new MpqDataSource(new MPQArchive(sbc), sbc); + tilesetSource = new CompoundDataSource( + Arrays.asList(compoundDataSource, internalMpqContentsDataSource)); + } + } + catch (final IOException exc) { + tilesetSource = new CompoundDataSource( + Arrays.asList(compoundDataSource, new SubdirDataSource(compoundDataSource, tileset + ".mpq/"), + new SubdirDataSource(compoundDataSource, "_tilesets/" + tileset + ".w3mod/"))); + } + } + catch (final MPQException e) { + throw new RuntimeException(e); + } + setDataSource(tilesetSource); + this.worldEditStrings = new WorldEditStrings(this.dataSource); + loadSLKs(this.worldEditStrings); + + this.solverParams.tileset = Character.toLowerCase(tileset); + + final War3MapW3e terrainData = this.mapMpq.readEnvironment(); + + final War3MapWpm terrainPathing = this.mapMpq.readPathing(); + + final StandardObjectData standardObjectData = new StandardObjectData(this.dataSource); + this.worldEditData = standardObjectData.getWorldEditData(); + + this.terrain = new Terrain(terrainData, terrainPathing, w3iFile, this.webGL, this.dataSource, + this.worldEditStrings, this, this.worldEditData); + + final float[] centerOffset = terrainData.getCenterOffset(); + final int[] mapSize = terrainData.getMapSize(); + + this.terrainReady = true; + this.anyReady = true; + this.cliffsReady = true; + + // Override the grid based on the map. + this.worldScene.grid = new Grid(centerOffset[0], centerOffset[1], (mapSize[0] * 128) - 128, + (mapSize[1] * 128) - 128, 16 * 128, 16 * 128); + + final MdxModel confirmation = (MdxModel) load("UI\\Feedback\\Confirmation\\Confirmation.mdx", + PathSolver.DEFAULT, null); + this.confirmationInstance = (MdxComplexInstance) confirmation.addInstance(); + this.confirmationInstance.setSequenceLoopMode(SequenceLoopMode.NEVER_LOOP_AND_HIDE_WHEN_DONE); + this.confirmationInstance.setSequence(0); + this.confirmationInstance.setScene(this.worldScene); + + this.allObjectData = this.mapMpq.readModifications(); + this.simulation = new CSimulation(this.miscData, this.allObjectData.getUnits(), + this.allObjectData.getAbilities(), new SimulationRenderController() { + private final Map keyToCombatSound = new HashMap<>(); + + @Override + public CAttackProjectile createAttackProjectile(final CSimulation simulation, final float launchX, + final float launchY, final float launchFacing, final CUnit source, + final CUnitAttackMissile unitAttack, final CWidget target, final float damage, + final int bounceIndex) { + final War3ID typeId = source.getTypeId(); + final int projectileSpeed = unitAttack.getProjectileSpeed(); + final float projectileArc = unitAttack.getProjectileArc(); + String missileArt = unitAttack.getProjectileArt(); + final float projectileLaunchX = simulation.getUnitData().getProjectileLaunchX(typeId); + final float projectileLaunchY = simulation.getUnitData().getProjectileLaunchY(typeId); + final float projectileLaunchZ = simulation.getUnitData().getProjectileLaunchZ(typeId); + + missileArt = mdx(missileArt); + final float facing = launchFacing; + final float sinFacing = (float) Math.sin(facing); + final float cosFacing = (float) Math.cos(facing); + final float x = (launchX + (projectileLaunchY * cosFacing)) + (projectileLaunchX * sinFacing); + final float y = (launchY + (projectileLaunchY * sinFacing)) - (projectileLaunchX * cosFacing); + + final float height = War3MapViewer.this.terrain.getGroundHeight(x, y) + source.getFlyHeight() + + projectileLaunchZ; + final CAttackProjectile simulationAttackProjectile = new CAttackProjectile(x, y, + projectileSpeed, target, source, damage, unitAttack, bounceIndex); + + final MdxModel model = (MdxModel) load(missileArt, War3MapViewer.this.mapPathSolver, + War3MapViewer.this.solverParams); + final MdxComplexInstance modelInstance = (MdxComplexInstance) model.addInstance(); + modelInstance.setTeamColor(source.getPlayerIndex()); + modelInstance.setScene(War3MapViewer.this.worldScene); + if (bounceIndex == 0) { + SequenceUtils.randomBirthSequence(modelInstance); + } + else { + SequenceUtils.randomStandSequence(modelInstance); + } + modelInstance.setLocation(x, y, height); + final RenderAttackProjectile renderAttackProjectile = new RenderAttackProjectile( + simulationAttackProjectile, modelInstance, height, projectileArc, War3MapViewer.this); + + War3MapViewer.this.projectiles.add(renderAttackProjectile); + + return simulationAttackProjectile; + } + + @Override + public void createInstantAttackEffect(final CSimulation cSimulation, final CUnit source, + final CUnitAttackInstant unitAttack, final CWidget target) { + final War3ID typeId = source.getTypeId(); + + String missileArt = unitAttack.getProjectileArt(); + final float projectileLaunchX = War3MapViewer.this.simulation.getUnitData() + .getProjectileLaunchX(typeId); + final float projectileLaunchY = War3MapViewer.this.simulation.getUnitData() + .getProjectileLaunchY(typeId); + missileArt = mdx(missileArt); + final float facing = (float) Math.toRadians(source.getFacing()); + final float sinFacing = (float) Math.sin(facing); + final float cosFacing = (float) Math.cos(facing); + final float x = (source.getX() + (projectileLaunchY * cosFacing)) + + (projectileLaunchX * sinFacing); + final float y = (source.getY() + (projectileLaunchY * sinFacing)) + - (projectileLaunchX * cosFacing); + + final float targetX = target.getX(); + final float targetY = target.getY(); + final float angleToTarget = (float) Math.atan2(targetY - y, targetX - x); + + final float height = War3MapViewer.this.terrain.getGroundHeight(targetX, targetY) + + target.getFlyHeight() + target.getImpactZ(); + + final MdxModel model = (MdxModel) load(missileArt, War3MapViewer.this.mapPathSolver, + War3MapViewer.this.solverParams); + final MdxComplexInstance modelInstance = (MdxComplexInstance) model.addInstance(); + modelInstance.setTeamColor(source.getPlayerIndex()); + modelInstance.setScene(War3MapViewer.this.worldScene); + SequenceUtils.randomBirthSequence(modelInstance); + modelInstance.setLocation(targetX, targetY, height); + War3MapViewer.this.projectiles + .add(new RenderAttackInstant(modelInstance, War3MapViewer.this, angleToTarget)); + } + + @Override + public void spawnUnitDamageSound(final CUnit damagedUnit, final String weaponSound, + final String armorType) { + final String key = weaponSound + armorType; + UnitSound combatSound = this.keyToCombatSound.get(key); + if (combatSound == null) { + combatSound = UnitSound.create(War3MapViewer.this.dataSource, + War3MapViewer.this.unitCombatSoundsTable, weaponSound, armorType); + this.keyToCombatSound.put(key, combatSound); + } + combatSound.play(War3MapViewer.this.worldScene.audioContext, damagedUnit.getX(), + damagedUnit.getY()); + } + + @Override + public void spawnUnitConstructionSound(final CUnit constructingUnit, + final CUnit constructedStructure) { + final UnitSound constructingBuilding = War3MapViewer.this.uiSounds + .getSound(War3MapViewer.this.gameUI.getSkinField("ConstructingBuilding")); + if (constructingBuilding != null) { + constructingBuilding.playUnitResponse(War3MapViewer.this.worldScene.audioContext, + War3MapViewer.this.unitToRenderPeer.get(constructingUnit)); + } + } + + @Override + public void removeUnit(final CUnit unit) { + final RenderUnit renderUnit = War3MapViewer.this.unitToRenderPeer.remove(unit); + War3MapViewer.this.units.remove(renderUnit); + War3MapViewer.this.worldScene.removeInstance(renderUnit.instance); + } + + @Override + public BufferedImage getBuildingPathingPixelMap(final War3ID rawcode) { + return War3MapViewer.this + .getBuildingPathingPixelMap(War3MapViewer.this.allObjectData.getUnits().get(rawcode)); + } + + @Override + public void spawnUnitConstructionFinishSound(final CUnit constructedStructure) { + final UnitSound constructingBuilding = War3MapViewer.this.uiSounds + .getSound(War3MapViewer.this.gameUI.getSkinField("JobDoneSound")); + if (constructingBuilding != null) { + constructingBuilding.play(War3MapViewer.this.worldScene.audioContext, + constructedStructure.getX(), constructedStructure.getY()); + } + } + + @Override + public CUnit createUnit(final CSimulation simulation, final War3ID typeId, final int playerIndex, + final float x, final float y, final float facing) { + return createNewUnit(War3MapViewer.this.allObjectData, typeId, x, y, 0f, playerIndex, + (float) Math.toRadians(facing)); + } + }, this.terrain.pathingGrid, this.terrain.getEntireMap(), this.seededRandom, w3iFile.getPlayers()); + + this.walkableObjectsTree = new Quadtree<>(this.terrain.getEntireMap()); + if (this.doodadsAndDestructiblesLoaded) { + this.loadDoodadsAndDestructibles(this.allObjectData); + } + else { + throw new IllegalStateException("transcription of JS has not loaded a map and has no JS async promises"); + } + + loadSounds(); + + this.terrain.createWaves(); + + loadLightsAndShading(tileset); + } + + private void loadSounds() { + this.uiSounds = new KeyedSounds(this.uiSoundsTable, this.mapMpq); + } + + /** + * Loads the map information that should be loaded after UI, such as units, who + * need to be able to setup their UI counterparts (icons, etc) for their + * abilities while loading. This allows the dynamic creation of units while the + * game is playing to better share code with the startup sequence's creation of + * units. + * + * @throws IOException + */ + public void loadAfterUI() throws IOException { + if (this.unitsAndItemsLoaded) { + this.loadUnitsAndItems(this.allObjectData); + } + else { + throw new IllegalStateException("transcription of JS has not loaded a map and has no JS async promises"); + } + + // After we finish loading units, we need to update & create the stored shadow + // information for all unit shadows + this.terrain.initShadows(); + } + + private void loadLightsAndShading(final char tileset) { + // TODO this should be set by the war3map.j actually, not by the tileset, so the + // call to set day night models is just for testing to make the test look pretty + final Element defaultTerrainLights = this.worldEditData.get("TerrainLights"); + final Element defaultUnitLights = this.worldEditData.get("UnitLights"); + setDayNightModels(defaultTerrainLights.getField(Character.toString(tileset)), + defaultUnitLights.getField(Character.toString(tileset))); + + } + + private void loadDoodadsAndDestructibles(final Warcraft3MapObjectData modifications) throws IOException { + this.applyModificationFile(this.doodadsData, this.doodadMetaData, modifications.getDoodads(), + WorldEditorDataType.DOODADS); + this.applyModificationFile(this.doodadsData, this.destructableMetaData, modifications.getDestructibles(), + WorldEditorDataType.DESTRUCTIBLES); + + final War3MapDoo doo = this.mapMpq.readDoodads(); + + for (final com.etheller.warsmash.parsers.w3x.doo.Doodad doodad : doo.getDoodads()) { + WorldEditorDataType type = WorldEditorDataType.DOODADS; + MutableGameObject row = modifications.getDoodads().get(doodad.getId()); + if (row == null) { + row = modifications.getDestructibles().get(doodad.getId()); + type = WorldEditorDataType.DESTRUCTIBLES; + } + if (row != null) { + String file = row.readSLKTag("file"); + final int numVar = row.readSLKTagInt("numVar"); + + if (file.endsWith(".mdx") || file.endsWith(".mdl")) { + file = file.substring(0, file.length() - 4); + } + + String fileVar = file; + + file += ".mdx"; + + if (numVar > 1) { + fileVar += Math.min(doodad.getVariation(), numVar - 1); + } + + fileVar += ".mdx"; + + final float maxPitch = row.readSLKTagFloat("maxPitch"); + final float maxRoll = row.readSLKTagFloat("maxRoll"); + if (type == WorldEditorDataType.DESTRUCTIBLES) { + final String shadowString = row.readSLKTag("shadow"); + if ((shadowString != null) && (shadowString.length() > 0) && !"_".equals(shadowString)) { + this.terrain.addShadow(shadowString, doodad.getLocation()[0], doodad.getLocation()[1]); + } + + final String pathingTexture = row.readSLKTag("pathTex"); + if ((pathingTexture != null) && (pathingTexture.length() > 0) && !"_".equals(pathingTexture)) { + + BufferedImage bufferedImage = this.filePathToPathingMap.get(pathingTexture.toLowerCase()); + if (bufferedImage == null) { + if (this.mapMpq.has(pathingTexture)) { + try { + bufferedImage = TgaFile.readTGA(pathingTexture, + this.mapMpq.getResourceAsStream(pathingTexture)); + this.filePathToPathingMap.put(pathingTexture.toLowerCase(), bufferedImage); + } + catch (final Exception exc) { + exc.printStackTrace(); + } + } + } + if (bufferedImage != null) { + this.terrain.pathingGrid.blitPathingOverlayTexture(doodad.getLocation()[0], + doodad.getLocation()[1], (int) Math.toDegrees(doodad.getAngle()), bufferedImage); + } + } + } + // First see if the model is local. + // Doodads referring to local models may have invalid variations, so if the + // variation doesn't exist, try without a variation. + + String path; + if (this.mapMpq.has(fileVar)) { + path = fileVar; + } + else { + path = file; + } + MdxModel model; + if (this.mapMpq.has(path)) { + model = (MdxModel) this.load(path, this.mapPathSolver, this.solverParams); + } + else { + model = (MdxModel) this.load(fileVar, this.mapPathSolver, this.solverParams); + } + + if (type == WorldEditorDataType.DESTRUCTIBLES) { + final RenderDestructable renderDestructable = new RenderDestructable(this, model, row, doodad, type, + maxPitch, maxRoll, doodad.getLife()); + if (row.readSLKTagBoolean("walkable")) { + final float x = doodad.getLocation()[0]; + final float y = doodad.getLocation()[1]; + final BoundingBox boundingBox = model.bounds.getBoundingBox(); + final float minX = boundingBox.min.x + x; + final float minY = boundingBox.min.y + y; + final Rectangle renderDestructableBounds = new Rectangle(minX, minY, boundingBox.getWidth(), + boundingBox.getHeight()); + this.walkableObjectsTree.add((MdxComplexInstance) renderDestructable.instance, + renderDestructableBounds); + } + this.doodads.add(renderDestructable); + } + else { + this.doodads.add(new RenderDoodad(this, model, row, doodad, type, maxPitch, maxRoll)); + } + } + } + + // Cliff/Terrain doodads. + for (final com.etheller.warsmash.parsers.w3x.doo.TerrainDoodad doodad : doo.getTerrainDoodads()) { + final MutableGameObject row = modifications.getDoodads().get(doodad.getId()); + String file = row.readSLKTag("file");// + if ("".equals(file)) { + final String blaBla = row.readSLKTag("file"); + System.out.println("bla"); + } + if (file.toLowerCase().endsWith(".mdl")) { + file = file.substring(0, file.length() - 4); + } + if (!file.toLowerCase().endsWith(".mdx")) { + file += ".mdx"; + } + final MdxModel model = (MdxModel) this.load(file, this.mapPathSolver, this.solverParams); + + final String pathingTexture = row.readSLKTag("pathTex"); + BufferedImage pathingTextureImage; + if ((pathingTexture != null) && (pathingTexture.length() > 0) && !"_".equals(pathingTexture)) { + + pathingTextureImage = this.filePathToPathingMap.get(pathingTexture.toLowerCase()); + if (pathingTextureImage == null) { + if (this.mapMpq.has(pathingTexture)) { + try { + pathingTextureImage = TgaFile.readTGA(pathingTexture, + this.mapMpq.getResourceAsStream(pathingTexture)); + this.filePathToPathingMap.put(pathingTexture.toLowerCase(), pathingTextureImage); + } + catch (final Exception exc) { + exc.printStackTrace(); + } + } + } + } + else { + pathingTextureImage = null; + } + if (pathingTextureImage != null) { + // blit out terrain cells under this TerrainDoodad + final int textureWidth = pathingTextureImage.getWidth(); + final int textureHeight = pathingTextureImage.getHeight(); + final int textureWidthTerrainCells = textureWidth / 4; + final int textureHeightTerrainCells = textureHeight / 4; + final int minCellX = ((int) doodad.getLocation()[0]); + final int minCellY = ((int) doodad.getLocation()[1]); + final int maxCellX = (minCellX + textureWidthTerrainCells) - 1; + final int maxCellY = (minCellY + textureHeightTerrainCells) - 1; + for (int j = minCellY; j <= maxCellY; j++) { + for (int i = minCellX; i <= maxCellX; i++) { + this.terrain.removeTerrainCellWithoutFlush(i, j); + } + } + this.terrain.flushRemovedTerrainCells(); + } + + System.out.println("Loading terrain doodad: " + file); + this.terrainDoodads.add(new TerrainDoodad(this, model, row, doodad, pathingTextureImage)); + } + + this.doodadsReady = true; + this.anyReady = true; + } + + private void applyModificationFile(final MappedData doodadsData2, final MappedData doodadMetaData2, + final MutableObjectData destructibles, final WorldEditorDataType dataType) { + // TODO condense ported MappedData from Ghostwolf and MutableObjectData from + // Retera + + } + + private void loadUnitsAndItems(final Warcraft3MapObjectData modifications) throws IOException { + final War3Map mpq = this.mapMpq; + this.unitsReady = false; + + this.soundsetNameToSoundset = new HashMap<>(); + + if (this.dataSource.has("war3mapUnits.doo")) { + final War3MapUnitsDoo dooFile = mpq.readUnits(); + + // Collect the units and items data. + for (final com.etheller.warsmash.parsers.w3x.unitsdoo.Unit unit : dooFile.getUnits()) { + final War3ID unitId = unit.getId(); + final float unitX = unit.getLocation()[0]; + final float unitY = unit.getLocation()[1]; + final float unitZ = unit.getLocation()[2]; + final int playerIndex = unit.getPlayer(); + final float unitAngle = unit.getAngle(); + + createNewUnit(modifications, unitId, unitX, unitY, unitZ, playerIndex, unitAngle); + } + } + + this.terrain.loadSplats(); + + this.unitsReady = true; + this.anyReady = true; + } + + private CUnit createNewUnit(final Warcraft3MapObjectData modifications, final War3ID unitId, float unitX, + float unitY, final float unitZ, final int playerIndex, final float unitAngle) { + UnitSoundset soundset = null; + MutableGameObject row = null; + String path = null; + Splat unitShadowSplat = null; + BufferedImage buildingPathingPixelMap = null; + final float unitVertexScale = 1.0f; + + // Hardcoded? + WorldEditorDataType type = null; + if (sloc.equals(unitId)) { // path = "Objects\\StartLocation\\StartLocation.mdx"; - type = null; /// ?????? - this.startLocations[playerIndex] = new Vector2(unitX, unitY); - } else { - row = modifications.getUnits().get(unitId); - if (row == null) { - row = modifications.getItems().get(unitId); - if (row != null) { - type = WorldEditorDataType.ITEM; - path = row.getFieldAsString(ITEM_FILE, 0); + type = null; /// ?????? + this.startLocations[playerIndex] = new Vector2(unitX, unitY); + } + else { + row = modifications.getUnits().get(unitId); + if (row == null) { + row = modifications.getItems().get(unitId); + if (row != null) { + type = WorldEditorDataType.ITEM; + path = row.getFieldAsString(ITEM_FILE, 0); - if (path.toLowerCase().endsWith(".mdl") || path.toLowerCase().endsWith(".mdx")) { - path = path.substring(0, path.length() - 4); - } + if (path.toLowerCase().endsWith(".mdl") || path.toLowerCase().endsWith(".mdx")) { + path = path.substring(0, path.length() - 4); + } - Element misc = miscData.get("Misc"); - String itemShadowFile = misc.getField("ItemShadowFile"); - int itemShadowWidth = misc.getFieldValue("ItemShadowSize", 0); - int itemShadowHeight = misc.getFieldValue("ItemShadowSize", 1); - int itemShadowX = misc.getFieldValue("ItemShadowOffset", 0); - int itemShadowY = misc.getFieldValue("ItemShadowOffset", 1); - if ((itemShadowFile != null) && !"_".equals(itemShadowFile)) { - final String texture = "ReplaceableTextures\\Shadows\\" + itemShadowFile + ".blp"; - final float shadowX = itemShadowX; - final float shadowY = itemShadowY; - final float shadowWidth = itemShadowWidth; - final float shadowHeight = itemShadowHeight; - if (!this.terrain.splats.containsKey(texture)) { - final Splat splat = new Splat(); - splat.opacity = 0.5f; - this.terrain.splats.put(texture, splat); - } - final float x = unitX - shadowX; - final float y = unitY - shadowY; - this.terrain.splats.get(texture).locations - .add(new float[]{x, y, x + shadowWidth, y + shadowHeight, 3}); - unitShadowSplat = this.terrain.splats.get(texture); - } + final Element misc = this.miscData.get("Misc"); + final String itemShadowFile = misc.getField("ItemShadowFile"); + final int itemShadowWidth = misc.getFieldValue("ItemShadowSize", 0); + final int itemShadowHeight = misc.getFieldValue("ItemShadowSize", 1); + final int itemShadowX = misc.getFieldValue("ItemShadowOffset", 0); + final int itemShadowY = misc.getFieldValue("ItemShadowOffset", 1); + if ((itemShadowFile != null) && !"_".equals(itemShadowFile)) { + final String texture = "ReplaceableTextures\\Shadows\\" + itemShadowFile + ".blp"; + final float shadowX = itemShadowX; + final float shadowY = itemShadowY; + final float shadowWidth = itemShadowWidth; + final float shadowHeight = itemShadowHeight; + if (!this.terrain.splats.containsKey(texture)) { + final Splat splat = new Splat(); + splat.opacity = 0.5f; + this.terrain.splats.put(texture, splat); + } + final float x = unitX - shadowX; + final float y = unitY - shadowY; + this.terrain.splats.get(texture).locations + .add(new float[] { x, y, x + shadowWidth, y + shadowHeight, 3 }); + unitShadowSplat = this.terrain.splats.get(texture); + } - path += ".mdx"; - } - } else { - type = WorldEditorDataType.UNITS; - path = getUnitModelPath(row); + path += ".mdx"; + } + } + else { + type = WorldEditorDataType.UNITS; + path = getUnitModelPath(row); - buildingPathingPixelMap = getBuildingPathingPixelMap(row); - if (buildingPathingPixelMap != null) { - unitX = Math.round(unitX / 64f) * 64f; - unitY = Math.round(unitY / 64f) * 64f; - this.terrain.pathingGrid.blitPathingOverlayTexture(unitX, unitY, (int) Math.toDegrees(unitAngle), - buildingPathingPixelMap); - } + buildingPathingPixelMap = getBuildingPathingPixelMap(row); + if (buildingPathingPixelMap != null) { + unitX = Math.round(unitX / 64f) * 64f; + unitY = Math.round(unitY / 64f) * 64f; + this.terrain.pathingGrid.blitPathingOverlayTexture(unitX, unitY, (int) Math.toDegrees(unitAngle), + buildingPathingPixelMap); + } - final String uberSplat = row.getFieldAsString(UBER_SPLAT, 0); - if (uberSplat != null) { - final Element uberSplatInfo = this.terrain.uberSplatTable.get(uberSplat); - if (uberSplatInfo != null) { - final String texturePath = uberSplatInfo.getField("Dir") + "\\" + uberSplatInfo.getField("file") - + ".blp"; - final float s = uberSplatInfo.getFieldFloatValue("Scale"); - if (unitsReady) { - this.terrain.addUberSplat(texturePath, unitX, unitY, 1, s); - } else { - if (!this.terrain.splats.containsKey(texturePath)) { - this.terrain.splats.put(texturePath, new Splat()); - } - final float x = unitX; - final float y = unitY; - this.terrain.splats.get(texturePath).locations - .add(new float[]{x - s, y - s, x + s, y + s, 1}); - } - } - } + final String uberSplat = row.getFieldAsString(UBER_SPLAT, 0); + if (uberSplat != null) { + final Element uberSplatInfo = this.terrain.uberSplatTable.get(uberSplat); + if (uberSplatInfo != null) { + final String texturePath = uberSplatInfo.getField("Dir") + "\\" + uberSplatInfo.getField("file") + + ".blp"; + final float s = uberSplatInfo.getFieldFloatValue("Scale"); + if (this.unitsReady) { + this.terrain.addUberSplat(texturePath, unitX, unitY, 1, s); + } + else { + if (!this.terrain.splats.containsKey(texturePath)) { + this.terrain.splats.put(texturePath, new Splat()); + } + final float x = unitX; + final float y = unitY; + this.terrain.splats.get(texturePath).locations + .add(new float[] { x - s, y - s, x + s, y + s, 1 }); + } + } + } - final String unitShadow = row.getFieldAsString(UNIT_SHADOW, 0); - if ((unitShadow != null) && !"_".equals(unitShadow)) { - final String texture = "ReplaceableTextures\\Shadows\\" + unitShadow + ".blp"; - final float shadowX = row.getFieldAsFloat(UNIT_SHADOW_X, 0); - final float shadowY = row.getFieldAsFloat(UNIT_SHADOW_Y, 0); - final float shadowWidth = row.getFieldAsFloat(UNIT_SHADOW_W, 0); - final float shadowHeight = row.getFieldAsFloat(UNIT_SHADOW_H, 0); - if (this.mapMpq.has(texture)) { - if (!this.terrain.splats.containsKey(texture)) { - final Splat splat = new Splat(); - splat.opacity = 0.5f; - this.terrain.splats.put(texture, splat); - } - final float x = unitX - shadowX; - final float y = unitY - shadowY; - this.terrain.splats.get(texture).locations - .add(new float[]{x, y, x + shadowWidth, y + shadowHeight, 3}); - unitShadowSplat = this.terrain.splats.get(texture); - } - } + final String unitShadow = row.getFieldAsString(UNIT_SHADOW, 0); + if ((unitShadow != null) && !"_".equals(unitShadow)) { + final String texture = "ReplaceableTextures\\Shadows\\" + unitShadow + ".blp"; + final float shadowX = row.getFieldAsFloat(UNIT_SHADOW_X, 0); + final float shadowY = row.getFieldAsFloat(UNIT_SHADOW_Y, 0); + final float shadowWidth = row.getFieldAsFloat(UNIT_SHADOW_W, 0); + final float shadowHeight = row.getFieldAsFloat(UNIT_SHADOW_H, 0); + if (this.mapMpq.has(texture)) { + if (!this.terrain.splats.containsKey(texture)) { + final Splat splat = new Splat(); + splat.opacity = 0.5f; + this.terrain.splats.put(texture, splat); + } + final float x = unitX - shadowX; + final float y = unitY - shadowY; + this.terrain.splats.get(texture).locations + .add(new float[] { x, y, x + shadowWidth, y + shadowHeight, 3 }); + unitShadowSplat = this.terrain.splats.get(texture); + } + } - final String buildingShadow = row.getFieldAsString(BUILDING_SHADOW, 0); - if ((buildingShadow != null) && !"_".equals(buildingShadow)) { - this.terrain.addShadow(buildingShadow, unitX, unitY); - } + final String buildingShadow = row.getFieldAsString(BUILDING_SHADOW, 0); + if ((buildingShadow != null) && !"_".equals(buildingShadow)) { + this.terrain.addShadow(buildingShadow, unitX, unitY); + } - final String soundName = row.getFieldAsString(UNIT_SOUNDSET, 0); - UnitSoundset unitSoundset = this.soundsetNameToSoundset.get(soundName); - if (unitSoundset == null) { - unitSoundset = new UnitSoundset(this.dataSource, this.unitAckSoundsTable, soundName); - this.soundsetNameToSoundset.put(soundName, unitSoundset); - } - soundset = unitSoundset; + final String soundName = row.getFieldAsString(UNIT_SOUNDSET, 0); + UnitSoundset unitSoundset = this.soundsetNameToSoundset.get(soundName); + if (unitSoundset == null) { + unitSoundset = new UnitSoundset(this.dataSource, this.unitAckSoundsTable, soundName); + this.soundsetNameToSoundset.put(soundName, unitSoundset); + } + soundset = unitSoundset; - } - } + } + } - if (path != null) { - final MdxModel model = (MdxModel) this.load(path, this.mapPathSolver, this.solverParams); - MdxModel portraitModel; - final String portraitPath = path.substring(0, path.length() - 4) + "_portrait.mdx"; - if (this.dataSource.has(portraitPath)) { - portraitModel = (MdxModel) this.load(portraitPath, this.mapPathSolver, this.solverParams); - } else { - portraitModel = model; - } - if (type == WorldEditorDataType.UNITS) { - final float angle = (float) Math.toDegrees(unitAngle); - final CUnit simulationUnit = this.simulation.createUnit(row.getAlias(), playerIndex, unitX, unitY, - angle, buildingPathingPixelMap); - final RenderUnitTypeData typeData = getUnitTypeData(unitId, row); - final RenderUnit renderUnit = new RenderUnit(this, model, row, unitX, unitY, unitZ, playerIndex, - soundset, portraitModel, simulationUnit, typeData); - this.unitToRenderPeer.put(simulationUnit, renderUnit); - this.units.add(renderUnit); - if (unitShadowSplat != null) { - unitShadowSplat.unitMapping.add(new Consumer() { - @Override - public void accept(final SplatMover t) { - renderUnit.shadow = t; - } - }); - } - return simulationUnit; - } else { - this.items - .add(new RenderItem(this, model, row, unitX, unitY, unitZ, unitAngle, soundset, portraitModel)); // TODO - // store - // somewhere - if (unitShadowSplat != null) { - unitShadowSplat.unitMapping.add(new Consumer() { - @Override - public void accept(final SplatMover t) { + if (path != null) { + final MdxModel model = (MdxModel) this.load(path, this.mapPathSolver, this.solverParams); + MdxModel portraitModel; + final String portraitPath = path.substring(0, path.length() - 4) + "_portrait.mdx"; + if (this.dataSource.has(portraitPath)) { + portraitModel = (MdxModel) this.load(portraitPath, this.mapPathSolver, this.solverParams); + } + else { + portraitModel = model; + } + if (type == WorldEditorDataType.UNITS) { + final float angle = (float) Math.toDegrees(unitAngle); + final CUnit simulationUnit = this.simulation.createUnit(row.getAlias(), playerIndex, unitX, unitY, + angle, buildingPathingPixelMap); + final RenderUnitTypeData typeData = getUnitTypeData(unitId, row); + final RenderUnit renderUnit = new RenderUnit(this, model, row, unitX, unitY, unitZ, playerIndex, + soundset, portraitModel, simulationUnit, typeData); + this.unitToRenderPeer.put(simulationUnit, renderUnit); + this.units.add(renderUnit); + if (unitShadowSplat != null) { + unitShadowSplat.unitMapping.add(new Consumer() { + @Override + public void accept(final SplatMover t) { + renderUnit.shadow = t; + } + }); + } + return simulationUnit; + } + else { + this.items + .add(new RenderItem(this, model, row, unitX, unitY, unitZ, unitAngle, soundset, portraitModel)); // TODO + // store + // somewhere + if (unitShadowSplat != null) { + unitShadowSplat.unitMapping.add(new Consumer() { + @Override + public void accept(final SplatMover t) { - } - }); - } - } - } else { - System.err.println("Unknown unit ID: " + unitId); - } - return null; - } + } + }); + } + } + } + else { + System.err.println("Unknown unit ID: " + unitId); + } + return null; + } - public String getUnitModelPath(final MutableGameObject row) { - String path; - path = row.getFieldAsString(UNIT_FILE, 0); + public String getUnitModelPath(final MutableGameObject row) { + String path; + path = row.getFieldAsString(UNIT_FILE, 0); - if (path.toLowerCase().endsWith(".mdl") || path.toLowerCase().endsWith(".mdx")) { - path = path.substring(0, path.length() - 4); - } - if ((row.readSLKTagInt("fileVerFlags") == 2) && this.dataSource.has(path + "_V1.mdx")) { - path += "_V1"; - } + if (path.toLowerCase().endsWith(".mdl") || path.toLowerCase().endsWith(".mdx")) { + path = path.substring(0, path.length() - 4); + } + if ((row.readSLKTagInt("fileVerFlags") == 2) && this.dataSource.has(path + "_V1.mdx")) { + path += "_V1"; + } - path += ".mdx"; - return path; - } + path += ".mdx"; + return path; + } - private BufferedImage getBuildingPathingPixelMap(final MutableGameObject row) { - BufferedImage buildingPathingPixelMap = null; - final String pathingTexture = row.getFieldAsString(UNIT_PATHING, 0); - if ((pathingTexture != null) && (pathingTexture.length() > 0) && !"_".equals(pathingTexture)) { - buildingPathingPixelMap = this.filePathToPathingMap.get(pathingTexture.toLowerCase()); - if (buildingPathingPixelMap == null) { - try { - if (pathingTexture.toLowerCase().endsWith(".tga")) { - buildingPathingPixelMap = TgaFile.readTGA(pathingTexture, - this.mapMpq.getResourceAsStream(pathingTexture)); - } else { - try (InputStream stream = this.mapMpq.getResourceAsStream(pathingTexture)) { - buildingPathingPixelMap = ImageIO.read(stream); - System.out.println("LOADING BLP PATHING: " + pathingTexture); - } - } - this.filePathToPathingMap.put(pathingTexture.toLowerCase(), buildingPathingPixelMap); - } catch (final IOException exc) { - System.err.println("Failure to get pathing: " + exc.getClass() + ":" + exc.getMessage()); - } - } - } - return buildingPathingPixelMap; - } + private BufferedImage getBuildingPathingPixelMap(final MutableGameObject row) { + BufferedImage buildingPathingPixelMap = null; + final String pathingTexture = row.getFieldAsString(UNIT_PATHING, 0); + if ((pathingTexture != null) && (pathingTexture.length() > 0) && !"_".equals(pathingTexture)) { + buildingPathingPixelMap = this.filePathToPathingMap.get(pathingTexture.toLowerCase()); + if (buildingPathingPixelMap == null) { + try { + if (pathingTexture.toLowerCase().endsWith(".tga")) { + buildingPathingPixelMap = TgaFile.readTGA(pathingTexture, + this.mapMpq.getResourceAsStream(pathingTexture)); + } + else { + try (InputStream stream = this.mapMpq.getResourceAsStream(pathingTexture)) { + buildingPathingPixelMap = ImageIO.read(stream); + System.out.println("LOADING BLP PATHING: " + pathingTexture); + } + } + this.filePathToPathingMap.put(pathingTexture.toLowerCase(), buildingPathingPixelMap); + } + catch (final IOException exc) { + System.err.println("Failure to get pathing: " + exc.getClass() + ":" + exc.getMessage()); + } + } + } + return buildingPathingPixelMap; + } - public RenderUnitTypeData getUnitTypeData(final War3ID key, final MutableGameObject row) { - RenderUnitTypeData unitTypeData = this.unitIdToTypeData.get(key); - if (unitTypeData == null) { - unitTypeData = new RenderUnitTypeData(row.getFieldAsFloat(MAX_PITCH, 0), row.getFieldAsFloat(MAX_ROLL, 0), - row.getFieldAsFloat(ELEVATION_SAMPLE_RADIUS, 0)); - this.unitIdToTypeData.put(key, unitTypeData); - } - return unitTypeData; - } + public RenderUnitTypeData getUnitTypeData(final War3ID key, final MutableGameObject row) { + RenderUnitTypeData unitTypeData = this.unitIdToTypeData.get(key); + if (unitTypeData == null) { + unitTypeData = new RenderUnitTypeData(row.getFieldAsFloat(MAX_PITCH, 0), row.getFieldAsFloat(MAX_ROLL, 0), + row.getFieldAsFloat(ELEVATION_SAMPLE_RADIUS, 0)); + this.unitIdToTypeData.put(key, unitTypeData); + } + return unitTypeData; + } - @Override - public void update() { - if (this.anyReady) { - this.terrain.update(); + @Override + public void update() { + if (this.anyReady) { + this.terrain.update(); - super.update(); + super.update(); - for (final RenderUnit unit : this.units) { - unit.updateAnimations(this); - } - final Iterator projectileIterator = this.projectiles.iterator(); - while (projectileIterator.hasNext()) { - final RenderEffect projectile = projectileIterator.next(); - if (projectile.updateAnimations(this, Gdx.graphics.getDeltaTime())) { - projectileIterator.remove(); - } - } - for (final RenderItem item : this.items) { - final MdxComplexInstance instance = item.instance; - final MdxComplexInstance mdxComplexInstance = instance; - if (mdxComplexInstance.sequenceEnded || (mdxComplexInstance.sequence == -1)) { - SequenceUtils.randomStandSequence(mdxComplexInstance); - } - } - for (final RenderDoodad item : this.doodads) { - final ModelInstance instance = item.instance; - if (instance instanceof MdxComplexInstance) { - final MdxComplexInstance mdxComplexInstance = (MdxComplexInstance) instance; - if ((mdxComplexInstance.sequence == -1) || (mdxComplexInstance.sequenceEnded - && ((item.getAnimation() != AnimationTokens.PrimaryTag.DEATH) - || (((MdxModel) mdxComplexInstance.model).sequences.get(mdxComplexInstance.sequence) - .getFlags() == 0)))) { - SequenceUtils.randomSequence(mdxComplexInstance, item.getAnimation(), SequenceUtils.EMPTY, - true); + for (final RenderUnit unit : this.units) { + unit.updateAnimations(this); + } + final Iterator projectileIterator = this.projectiles.iterator(); + while (projectileIterator.hasNext()) { + final RenderEffect projectile = projectileIterator.next(); + if (projectile.updateAnimations(this, Gdx.graphics.getDeltaTime())) { + projectileIterator.remove(); + } + } + for (final RenderItem item : this.items) { + final MdxComplexInstance instance = item.instance; + final MdxComplexInstance mdxComplexInstance = instance; + if (mdxComplexInstance.sequenceEnded || (mdxComplexInstance.sequence == -1)) { + SequenceUtils.randomStandSequence(mdxComplexInstance); + } + } + for (final RenderDoodad item : this.doodads) { + final ModelInstance instance = item.instance; + if (instance instanceof MdxComplexInstance) { + final MdxComplexInstance mdxComplexInstance = (MdxComplexInstance) instance; + if ((mdxComplexInstance.sequence == -1) || (mdxComplexInstance.sequenceEnded + && ((item.getAnimation() != AnimationTokens.PrimaryTag.DEATH) + || (((MdxModel) mdxComplexInstance.model).sequences.get(mdxComplexInstance.sequence) + .getFlags() == 0)))) { + SequenceUtils.randomSequence(mdxComplexInstance, item.getAnimation(), SequenceUtils.EMPTY, + true); - } - } - } + } + } + } - final float rawDeltaTime = Gdx.graphics.getRawDeltaTime(); - this.updateTime += rawDeltaTime; - while (this.updateTime >= WarsmashConstants.SIMULATION_STEP_TIME) { - this.updateTime -= WarsmashConstants.SIMULATION_STEP_TIME; - this.simulation.update(); - } - this.dncTerrain.setFrameByRatio( - this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours()); - this.dncTerrain.update(rawDeltaTime, null); - this.dncUnit.setFrameByRatio( - this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours()); - this.dncUnit.update(rawDeltaTime, null); - this.dncUnitDay.setFrameByRatio(0.5f); - this.dncUnitDay.update(rawDeltaTime, null); - this.dncTarget.setFrameByRatio( - this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours()); - this.dncTarget.update(rawDeltaTime, null); - } - } + final float rawDeltaTime = Gdx.graphics.getRawDeltaTime(); + this.updateTime += rawDeltaTime; + while (this.updateTime >= WarsmashConstants.SIMULATION_STEP_TIME) { + this.updateTime -= WarsmashConstants.SIMULATION_STEP_TIME; + this.simulation.update(); + } + this.dncTerrain.setFrameByRatio( + this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours()); + this.dncTerrain.update(rawDeltaTime, null); + this.dncUnit.setFrameByRatio( + this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours()); + this.dncUnit.update(rawDeltaTime, null); + this.dncUnitDay.setFrameByRatio(0.5f); + this.dncUnitDay.update(rawDeltaTime, null); + this.dncTarget.setFrameByRatio( + this.simulation.getGameTimeOfDay() / this.simulation.getGameplayConstants().getGameDayHours()); + this.dncTarget.update(rawDeltaTime, null); + } + } - @Override - public void render() { - if (this.anyReady) { - final Scene worldScene = this.worldScene; + @Override + public void render() { + if (this.anyReady) { + final Scene worldScene = this.worldScene; - startFrame(); - worldScene.startFrame(); - worldScene.renderOpaque(this.dynamicShadowManager, this.webGL); - this.terrain.renderGround(this.dynamicShadowManager); - this.terrain.renderCliffs(); - worldScene.renderOpaque(); - this.terrain.renderUberSplats(); - this.terrain.renderWater(); - worldScene.renderTranslucent(); + startFrame(); + worldScene.startFrame(); + worldScene.renderOpaque(this.dynamicShadowManager, this.webGL); + this.terrain.renderGround(this.dynamicShadowManager); + this.terrain.renderCliffs(); + worldScene.renderOpaque(); + this.terrain.renderUberSplats(); + this.terrain.renderWater(); + worldScene.renderTranslucent(); - final List scenes = this.scenes; - for (final Scene scene : scenes) { - if (scene != worldScene) { - scene.startFrame(); - scene.renderOpaque(); - scene.renderTranslucent(); - } - } - } - } + final List scenes = this.scenes; + for (final Scene scene : scenes) { + if (scene != worldScene) { + scene.startFrame(); + scene.renderOpaque(); + scene.renderTranslucent(); + } + } + } + } - public void deselect() { - if (!this.selModels.isEmpty()) { - for (final SplatModel model : this.selModels) { - this.terrain.removeSplatBatchModel("selection"); - } - this.selModels.clear(); - for (final RenderUnit unit : this.selected) { - unit.selectionCircle = null; - } - } - this.selected.clear(); - } + public void deselect() { + if (!this.selModels.isEmpty()) { + for (final SplatModel model : this.selModels) { + this.terrain.removeSplatBatchModel("selection"); + } + this.selModels.clear(); + for (final RenderUnit unit : this.selected) { + unit.selectionCircle = null; + } + } + this.selected.clear(); + } - public void doSelectUnit(final List units) { - deselect(); - if (units.isEmpty()) { - return; - } + public void doSelectUnit(final List units) { + deselect(); + if (units.isEmpty()) { + return; + } - final Map splats = new HashMap(); - for (final RenderUnit unit : units) { - if (unit.row != null) { - if (unit.selectionScale > 0) { - final float selectionSize = unit.selectionScale * this.selectionCircleScaleFactor; - String path = null; - for (int i = 0; i < this.selectionCircleSizes.size(); i++) { - final SelectionCircleSize selectionCircleSize = this.selectionCircleSizes.get(i); - if ((selectionSize < selectionCircleSize.size) - || (i == (this.selectionCircleSizes.size() - 1))) { - path = selectionCircleSize.texture; - break; - } - } - if (!path.toLowerCase().endsWith(".blp")) { - path += ".blp"; - } - if (!splats.containsKey(path)) { - splats.put(path, new Splat()); - } - final float x = unit.location[0]; - final float y = unit.location[1]; - System.out.println("Selecting a unit at " + x + "," + y); - final float z = unit.row.getFieldAsFloat(UNIT_SELECT_HEIGHT, 0); - splats.get(path).locations.add(new float[]{x - (selectionSize / 2), y - (selectionSize / 2), - x + (selectionSize / 2), y + (selectionSize / 2), z + 5}); - splats.get(path).unitMapping.add(new Consumer() { - @Override - public void accept(final SplatMover t) { - unit.selectionCircle = t; - } - }); - } - this.selected.add(unit); - } - } - this.selModels.clear(); - for (final Map.Entry entry : splats.entrySet()) { - final String path = entry.getKey(); - final Splat locations = entry.getValue(); - final SplatModel model = new SplatModel(Gdx.gl30, (Texture) load(path, PathSolver.DEFAULT, null), - locations.locations, this.terrain.centerOffset, locations.unitMapping, true); - model.color[0] = 0; - model.color[1] = 1; - model.color[2] = 0; - model.color[3] = 1; - this.selModels.add(model); - this.terrain.addSplatBatchModel("selection", model); - } - } + final Map splats = new HashMap(); + for (final RenderUnit unit : units) { + if (unit.row != null) { + if (unit.selectionScale > 0) { + final float selectionSize = unit.selectionScale * this.selectionCircleScaleFactor; + String path = null; + for (int i = 0; i < this.selectionCircleSizes.size(); i++) { + final SelectionCircleSize selectionCircleSize = this.selectionCircleSizes.get(i); + if ((selectionSize < selectionCircleSize.size) + || (i == (this.selectionCircleSizes.size() - 1))) { + path = selectionCircleSize.texture; + break; + } + } + if (!path.toLowerCase().endsWith(".blp")) { + path += ".blp"; + } + if (!splats.containsKey(path)) { + splats.put(path, new Splat()); + } + final float x = unit.location[0]; + final float y = unit.location[1]; + System.out.println("Selecting a unit at " + x + "," + y); + final float z = unit.row.getFieldAsFloat(UNIT_SELECT_HEIGHT, 0); + splats.get(path).locations.add(new float[] { x - (selectionSize / 2), y - (selectionSize / 2), + x + (selectionSize / 2), y + (selectionSize / 2), z + 5 }); + splats.get(path).unitMapping.add(new Consumer() { + @Override + public void accept(final SplatMover t) { + unit.selectionCircle = t; + } + }); + } + this.selected.add(unit); + } + } + this.selModels.clear(); + for (final Map.Entry entry : splats.entrySet()) { + final String path = entry.getKey(); + final Splat locations = entry.getValue(); + final SplatModel model = new SplatModel(Gdx.gl30, (Texture) load(path, PathSolver.DEFAULT, null), + locations.locations, this.terrain.centerOffset, locations.unitMapping, true); + model.color[0] = 0; + model.color[1] = 1; + model.color[2] = 0; + model.color[3] = 1; + this.selModels.add(model); + this.terrain.addSplatBatchModel("selection", model); + } + } - public void getClickLocation(final Vector3 out, final int screenX, final int screenY) { - final float[] ray = rayHeap; - mousePosHeap.set(screenX, screenY); - this.worldScene.camera.screenToWorldRay(ray, mousePosHeap); - gdxRayHeap.set(ray[0], ray[1], ray[2], ray[3] - ray[0], ray[4] - ray[1], ray[5] - ray[2]); - gdxRayHeap.direction.nor();// needed for libgdx - RenderMathUtils.intersectRayTriangles(gdxRayHeap, this.terrain.softwareGroundMesh.vertices, - this.terrain.softwareGroundMesh.indices, 3, out); - rectangleHeap.set(Math.min(out.x, gdxRayHeap.origin.x), Math.min(out.y, gdxRayHeap.origin.y), - Math.abs(out.x - gdxRayHeap.origin.x), Math.abs(out.y - gdxRayHeap.origin.y)); - this.walkableObjectsTree.intersect(rectangleHeap, this.walkablesIntersectionFinder.reset(gdxRayHeap)); - if (this.walkablesIntersectionFinder.found) { - out.set(this.walkablesIntersectionFinder.intersection); - } else { - out.z = Math.max(getWalkableRenderHeight(out.x, out.y), this.terrain.getGroundHeight(out.x, out.y)); - } - } + public void getClickLocation(final Vector3 out, final int screenX, final int screenY) { + final float[] ray = rayHeap; + mousePosHeap.set(screenX, screenY); + this.worldScene.camera.screenToWorldRay(ray, mousePosHeap); + gdxRayHeap.set(ray[0], ray[1], ray[2], ray[3] - ray[0], ray[4] - ray[1], ray[5] - ray[2]); + gdxRayHeap.direction.nor();// needed for libgdx + RenderMathUtils.intersectRayTriangles(gdxRayHeap, this.terrain.softwareGroundMesh.vertices, + this.terrain.softwareGroundMesh.indices, 3, out); + rectangleHeap.set(Math.min(out.x, gdxRayHeap.origin.x), Math.min(out.y, gdxRayHeap.origin.y), + Math.abs(out.x - gdxRayHeap.origin.x), Math.abs(out.y - gdxRayHeap.origin.y)); + this.walkableObjectsTree.intersect(rectangleHeap, this.walkablesIntersectionFinder.reset(gdxRayHeap)); + if (this.walkablesIntersectionFinder.found) { + out.set(this.walkablesIntersectionFinder.intersection); + } + else { + out.z = Math.max(getWalkableRenderHeight(out.x, out.y), this.terrain.getGroundHeight(out.x, out.y)); + } + } - public void showConfirmation(final Vector3 position, final float red, final float green, final float blue) { - this.confirmationInstance.show(); - this.confirmationInstance.setSequence(0); - this.confirmationInstance.setLocation(position); - this.worldScene.instanceMoved(this.confirmationInstance, position.x, position.y); - this.confirmationInstance.vertexColor[0] = red; - this.confirmationInstance.vertexColor[1] = green; - this.confirmationInstance.vertexColor[2] = blue; - } + public void showConfirmation(final Vector3 position, final float red, final float green, final float blue) { + this.confirmationInstance.show(); + this.confirmationInstance.setSequence(0); + this.confirmationInstance.setLocation(position); + this.worldScene.instanceMoved(this.confirmationInstance, position.x, position.y); + this.confirmationInstance.vertexColor[0] = red; + this.confirmationInstance.vertexColor[1] = green; + this.confirmationInstance.vertexColor[2] = blue; + } - public List selectUnit(final float x, final float y, final boolean toggle) { - System.out.println("world: " + x + "," + y); - final float[] ray = rayHeap; - mousePosHeap.set(x, y); - this.worldScene.camera.screenToWorldRay(ray, mousePosHeap); - gdxRayHeap.set(ray[0], ray[1], ray[2], ray[3] - ray[0], ray[4] - ray[1], ray[5] - ray[2]); - gdxRayHeap.direction.nor();// needed for libgdx + public List selectUnit(final float x, final float y, final boolean toggle) { + System.out.println("world: " + x + "," + y); + final float[] ray = rayHeap; + mousePosHeap.set(x, y); + this.worldScene.camera.screenToWorldRay(ray, mousePosHeap); + gdxRayHeap.set(ray[0], ray[1], ray[2], ray[3] - ray[0], ray[4] - ray[1], ray[5] - ray[2]); + gdxRayHeap.direction.nor();// needed for libgdx - RenderUnit entity = null; - for (final RenderUnit unit : this.units) { - final MdxComplexInstance instance = unit.instance; - if (instance.isVisible(this.worldScene.camera) - && instance.intersectRayWithCollision(gdxRayHeap, intersectionHeap, - unit.getSimulationUnit().getUnitType().isBuilding(), false) - && !unit.getSimulationUnit().isDead()) { - if ((entity == null) || (entity.instance.depth > instance.depth)) { - entity = unit; - } - } - } - List sel; - if (entity != null) { - if (toggle) { - sel = new ArrayList<>(this.selected); - final int idx = sel.indexOf(entity); - if (idx >= 0) { - sel.remove(idx); - } else { - sel.add(entity); - } - } else { - sel = Arrays.asList(entity); - } - } else { - sel = Collections.emptyList(); - } - this.doSelectUnit(sel); - return sel; - } + RenderUnit entity = null; + for (final RenderUnit unit : this.units) { + final MdxComplexInstance instance = unit.instance; + if (instance.isVisible(this.worldScene.camera) + && instance.intersectRayWithCollision(gdxRayHeap, intersectionHeap, + unit.getSimulationUnit().getUnitType().isBuilding(), false) + && !unit.getSimulationUnit().isDead()) { + if ((entity == null) || (entity.instance.depth > instance.depth)) { + entity = unit; + } + } + } + List sel; + if (entity != null) { + if (toggle) { + sel = new ArrayList<>(this.selected); + final int idx = sel.indexOf(entity); + if (idx >= 0) { + sel.remove(idx); + } + else { + sel.add(entity); + } + } + else { + sel = Arrays.asList(entity); + } + } + else { + sel = Collections.emptyList(); + } + this.doSelectUnit(sel); + return sel; + } - public RenderUnit rayPickUnit(final float x, final float y) { - return rayPickUnit(x, y, CUnitFilterFunction.ACCEPT_ALL); - } + public RenderUnit rayPickUnit(final float x, final float y) { + return rayPickUnit(x, y, CUnitFilterFunction.ACCEPT_ALL); + } - public RenderUnit rayPickUnit(final float x, final float y, final CUnitFilterFunction filter) { - final float[] ray = rayHeap; - mousePosHeap.set(x, y); - this.worldScene.camera.screenToWorldRay(ray, mousePosHeap); - gdxRayHeap.set(ray[0], ray[1], ray[2], ray[3] - ray[0], ray[4] - ray[1], ray[5] - ray[2]); - gdxRayHeap.direction.nor();// needed for libgdx + public RenderUnit rayPickUnit(final float x, final float y, final CUnitFilterFunction filter) { + final float[] ray = rayHeap; + mousePosHeap.set(x, y); + this.worldScene.camera.screenToWorldRay(ray, mousePosHeap); + gdxRayHeap.set(ray[0], ray[1], ray[2], ray[3] - ray[0], ray[4] - ray[1], ray[5] - ray[2]); + gdxRayHeap.direction.nor();// needed for libgdx - RenderUnit entity = null; - for (final RenderUnit unit : this.units) { - final MdxComplexInstance instance = unit.instance; - if (instance.isVisible(this.worldScene.camera) && instance.intersectRayWithCollision(gdxRayHeap, - intersectionHeap, unit.getSimulationUnit().getUnitType().isBuilding(), false)) { - if (filter.call(unit.getSimulationUnit()) && (intersectionHeap.z > this.terrain - .getGroundHeight(intersectionHeap.x, intersectionHeap.y))) { - if ((entity == null) || (entity.instance.depth > instance.depth)) { - entity = unit; - } - } - } - } - return entity; - } + RenderUnit entity = null; + for (final RenderUnit unit : this.units) { + final MdxComplexInstance instance = unit.instance; + if (instance.isVisible(this.worldScene.camera) && instance.intersectRayWithCollision(gdxRayHeap, + intersectionHeap, unit.getSimulationUnit().getUnitType().isBuilding(), false)) { + if (filter.call(unit.getSimulationUnit()) && (intersectionHeap.z > this.terrain + .getGroundHeight(intersectionHeap.x, intersectionHeap.y))) { + if ((entity == null) || (entity.instance.depth > instance.depth)) { + entity = unit; + } + } + } + } + return entity; + } - private static final class MappedDataCallbackImplementation implements LoadGenericCallback { - @Override - public Object call(final InputStream data) { - final StringBuilder stringBuilder = new StringBuilder(); - try (BufferedReader reader = new BufferedReader(new InputStreamReader(data, "utf-8"))) { - String line; - while ((line = reader.readLine()) != null) { - stringBuilder.append(line); - stringBuilder.append("\n"); - } - } catch (final UnsupportedEncodingException e) { - throw new RuntimeException(e); - } catch (final IOException e) { - throw new RuntimeException(e); - } - return new MappedData(stringBuilder.toString()); - } - } + private static final class MappedDataCallbackImplementation implements LoadGenericCallback { + @Override + public Object call(final InputStream data) { + final StringBuilder stringBuilder = new StringBuilder(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(data, "utf-8"))) { + String line; + while ((line = reader.readLine()) != null) { + stringBuilder.append(line); + stringBuilder.append("\n"); + } + } + catch (final UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + return new MappedData(stringBuilder.toString()); + } + } - private static final class StringDataCallbackImplementation implements LoadGenericCallback { - @Override - public Object call(final InputStream data) { - if (data == null) { - System.err.println("data null"); - } - final StringBuilder stringBuilder = new StringBuilder(); - try (BufferedReader reader = new BufferedReader(new InputStreamReader(data, "utf-8"))) { - String line; - while ((line = reader.readLine()) != null) { - stringBuilder.append(line); - stringBuilder.append("\n"); - } - } catch (final UnsupportedEncodingException e) { - throw new RuntimeException(e); - } catch (final IOException e) { - throw new RuntimeException(e); - } - return stringBuilder.toString(); - } - } + private static final class StringDataCallbackImplementation implements LoadGenericCallback { + @Override + public Object call(final InputStream data) { + if (data == null) { + System.err.println("data null"); + } + final StringBuilder stringBuilder = new StringBuilder(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(data, "utf-8"))) { + String line; + while ((line = reader.readLine()) != null) { + stringBuilder.append(line); + stringBuilder.append("\n"); + } + } + catch (final UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + return stringBuilder.toString(); + } + } - private static final class StreamDataCallbackImplementation implements LoadGenericCallback { - @Override - public Object call(final InputStream data) { - return data; - } - } + private static final class StreamDataCallbackImplementation implements LoadGenericCallback { + @Override + public Object call(final InputStream data) { + return data; + } + } - public static final class SolverParams { - public char tileset; - public boolean reforged; - public boolean hd; - } + public static final class SolverParams { + public char tileset; + public boolean reforged; + public boolean hd; + } - public static final class CliffInfo { - public List locations = new ArrayList<>(); - public List textures = new ArrayList<>(); - } + public static final class CliffInfo { + public List locations = new ArrayList<>(); + public List textures = new ArrayList<>(); + } - private static final int MAXIMUM_ACCEPTED = 1 << 30; - private float selectionCircleScaleFactor; - private DataTable worldEditData; - private WorldEditStrings worldEditStrings; - private Warcraft3MapObjectData allObjectData; - private AbilityDataUI abilityDataUI; - private Map soundsetNameToSoundset; + private static final int MAXIMUM_ACCEPTED = 1 << 30; + private float selectionCircleScaleFactor; + private DataTable worldEditData; + private WorldEditStrings worldEditStrings; + private Warcraft3MapObjectData allObjectData; + private AbilityDataUI abilityDataUI; + private Map soundsetNameToSoundset; - /** - * Returns a power of two size for the given target capacity. - */ - private static final int pow2GreaterThan(final int capacity) { - int numElements = capacity - 1; - numElements |= numElements >>> 1; - numElements |= numElements >>> 2; - numElements |= numElements >>> 4; - numElements |= numElements >>> 8; - numElements |= numElements >>> 16; - return (numElements < 0) ? 1 : (numElements >= MAXIMUM_ACCEPTED) ? MAXIMUM_ACCEPTED : numElements + 1; - } + /** + * Returns a power of two size for the given target capacity. + */ + private static final int pow2GreaterThan(final int capacity) { + int numElements = capacity - 1; + numElements |= numElements >>> 1; + numElements |= numElements >>> 2; + numElements |= numElements >>> 4; + numElements |= numElements >>> 8; + numElements |= numElements >>> 16; + return (numElements < 0) ? 1 : (numElements >= MAXIMUM_ACCEPTED) ? MAXIMUM_ACCEPTED : numElements + 1; + } - public void standOnRepeat(final MdxComplexInstance instance) { - instance.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); - SequenceUtils.randomStandSequence(instance); - } + public void standOnRepeat(final MdxComplexInstance instance) { + instance.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + SequenceUtils.randomStandSequence(instance); + } - private static final class SelectionCircleSize { - private final float size; - private final String texture; - private final String textureDotted; + private static final class SelectionCircleSize { + private final float size; + private final String texture; + private final String textureDotted; - public SelectionCircleSize(final float size, final String texture, final String textureDotted) { - this.size = size; - this.texture = texture; - this.textureDotted = textureDotted; - } - } + public SelectionCircleSize(final float size, final String texture, final String textureDotted) { + this.size = size; + this.texture = texture; + this.textureDotted = textureDotted; + } + } - public void setDayNightModels(final String terrainDNCFile, final String unitDNCFile) { - final MdxModel terrainDNCModel = (MdxModel) load(mdx(terrainDNCFile), PathSolver.DEFAULT, null); - this.dncTerrain = (MdxComplexInstance) terrainDNCModel.addInstance(); - this.dncTerrain.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); - this.dncTerrain.setSequence(0); - final MdxModel unitDNCModel = (MdxModel) load(mdx(unitDNCFile), PathSolver.DEFAULT, null); - this.dncUnit = (MdxComplexInstance) unitDNCModel.addInstance(); - this.dncUnit.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); - this.dncUnit.setSequence(0); - this.dncUnitDay = (MdxComplexInstance) unitDNCModel.addInstance(); - this.dncUnitDay.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); - this.dncUnitDay.setSequence(0); - final MdxModel targetDNCModel = (MdxModel) load( - mdx("Environment\\DNC\\DNCLordaeron\\DNCLordaeronTarget\\DNCLordaeronTarget.mdl"), PathSolver.DEFAULT, - null); - this.dncTarget = (MdxComplexInstance) targetDNCModel.addInstance(); - this.dncTarget.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); - this.dncTarget.setSequence(0); - } + public void setDayNightModels(final String terrainDNCFile, final String unitDNCFile) { + final MdxModel terrainDNCModel = (MdxModel) load(mdx(terrainDNCFile), PathSolver.DEFAULT, null); + this.dncTerrain = (MdxComplexInstance) terrainDNCModel.addInstance(); + this.dncTerrain.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + this.dncTerrain.setSequence(0); + final MdxModel unitDNCModel = (MdxModel) load(mdx(unitDNCFile), PathSolver.DEFAULT, null); + this.dncUnit = (MdxComplexInstance) unitDNCModel.addInstance(); + this.dncUnit.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + this.dncUnit.setSequence(0); + this.dncUnitDay = (MdxComplexInstance) unitDNCModel.addInstance(); + this.dncUnitDay.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + this.dncUnitDay.setSequence(0); + final MdxModel targetDNCModel = (MdxModel) load( + mdx("Environment\\DNC\\DNCLordaeron\\DNCLordaeronTarget\\DNCLordaeronTarget.mdl"), PathSolver.DEFAULT, + null); + this.dncTarget = (MdxComplexInstance) targetDNCModel.addInstance(); + this.dncTarget.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + this.dncTarget.setSequence(0); + } - private static String mdx(String mdxPath) { - if (mdxPath.toLowerCase().endsWith(".mdl")) { - mdxPath = mdxPath.substring(0, mdxPath.length() - 4); - } - if (!mdxPath.toLowerCase().endsWith(".mdx")) { - mdxPath += ".mdx"; - } - return mdxPath; - } + private static String mdx(String mdxPath) { + if (mdxPath.toLowerCase().endsWith(".mdl")) { + mdxPath = mdxPath.substring(0, mdxPath.length() - 4); + } + if (!mdxPath.toLowerCase().endsWith(".mdx")) { + mdxPath += ".mdx"; + } + return mdxPath; + } - @Override - public SceneLightManager createLightManager(final boolean simple) { - if (simple) { - return new W3xScenePortraitLightManager(this, this.lightDirection); - } else { - return new W3xSceneWorldLightManager(this); - } - } + @Override + public SceneLightManager createLightManager(final boolean simple) { + if (simple) { + return new W3xScenePortraitLightManager(this, this.lightDirection); + } + else { + return new W3xSceneWorldLightManager(this); + } + } - public WorldEditStrings getWorldEditStrings() { - return this.worldEditStrings; - } + public WorldEditStrings getWorldEditStrings() { + return this.worldEditStrings; + } - public void setGameUI(final GameUI gameUI) { - this.gameUI = gameUI; - this.abilityDataUI = new AbilityDataUI(this.allObjectData.getAbilities(), this.allObjectData.getUnits(), - gameUI); - } + public void setGameUI(final GameUI gameUI) { + this.gameUI = gameUI; + this.abilityDataUI = new AbilityDataUI(this.allObjectData.getAbilities(), this.allObjectData.getUnits(), + gameUI); + } - public GameUI getGameUI() { - return this.gameUI; - } + public GameUI getGameUI() { + return this.gameUI; + } - public AbilityDataUI getAbilityDataUI() { - return this.abilityDataUI; - } + public AbilityDataUI getAbilityDataUI() { + return this.abilityDataUI; + } - public KeyedSounds getUiSounds() { - return this.uiSounds; - } + public KeyedSounds getUiSounds() { + return this.uiSounds; + } - public Warcraft3MapObjectData getAllObjectData() { - return this.allObjectData; - } + public Warcraft3MapObjectData getAllObjectData() { + return this.allObjectData; + } - public float getWalkableRenderHeight(final float x, final float y) { - this.walkableObjectsTree.intersect(x, y, this.walkablesIntersector.reset(x, y)); - return this.walkablesIntersector.z; - } + public float getWalkableRenderHeight(final float x, final float y) { + this.walkableObjectsTree.intersect(x, y, this.walkablesIntersector.reset(x, y)); + return this.walkablesIntersector.z; + } - public MdxComplexInstance getHighestWalkableUnder(final float x, final float y) { - this.walkableObjectsTree.intersect(x, y, this.intersectorFindsHighestWalkable.reset(x, y)); - return this.intersectorFindsHighestWalkable.highestInstance; - } + public MdxComplexInstance getHighestWalkableUnder(final float x, final float y) { + this.walkableObjectsTree.intersect(x, y, this.intersectorFindsHighestWalkable.reset(x, y)); + return this.intersectorFindsHighestWalkable.highestInstance; + } - private static final class QuadtreeIntersectorFindsWalkableRenderHeight - implements QuadtreeIntersector { - private float z; - private final Ray ray = new Ray(); - private final Vector3 intersection = new Vector3(); + public int getLocalPlayerIndex() { + return this.localPlayerIndex; + } - private QuadtreeIntersectorFindsWalkableRenderHeight reset(final float x, final float y) { - this.z = -Float.MAX_VALUE; - this.ray.set(x, y, 4096, 0, 0, -8192); - return this; - } + private static final class QuadtreeIntersectorFindsWalkableRenderHeight + implements QuadtreeIntersector { + private float z; + private final Ray ray = new Ray(); + private final Vector3 intersection = new Vector3(); - @Override - public boolean onIntersect(final MdxComplexInstance intersectingObject) { - if (intersectingObject.intersectRayWithCollision(this.ray, this.intersection, true, true)) { - this.z = Math.max(this.z, this.intersection.z); - } - return false; - } - } + private QuadtreeIntersectorFindsWalkableRenderHeight reset(final float x, final float y) { + this.z = -Float.MAX_VALUE; + this.ray.set(x, y, 4096, 0, 0, -8192); + return this; + } - private static final class QuadtreeIntersectorFindsHighestWalkable - implements QuadtreeIntersector { - private float z; - private final Ray ray = new Ray(); - private final Vector3 intersection = new Vector3(); - private MdxComplexInstance highestInstance; + @Override + public boolean onIntersect(final MdxComplexInstance intersectingObject) { + if (intersectingObject.intersectRayWithCollision(this.ray, this.intersection, true, true)) { + this.z = Math.max(this.z, this.intersection.z); + } + return false; + } + } - private QuadtreeIntersectorFindsHighestWalkable reset(final float x, final float y) { - this.z = -Float.MAX_VALUE; - this.ray.set(x, y, 4096, 0, 0, -8192); - this.highestInstance = null; - return this; - } + private static final class QuadtreeIntersectorFindsHighestWalkable + implements QuadtreeIntersector { + private float z; + private final Ray ray = new Ray(); + private final Vector3 intersection = new Vector3(); + private MdxComplexInstance highestInstance; - @Override - public boolean onIntersect(final MdxComplexInstance intersectingObject) { - if (intersectingObject.intersectRayWithCollision(this.ray, this.intersection, true, true)) { - if (this.intersection.z > this.z) { - this.z = this.intersection.z; - this.highestInstance = intersectingObject; - } - } - return false; - } - } + private QuadtreeIntersectorFindsHighestWalkable reset(final float x, final float y) { + this.z = -Float.MAX_VALUE; + this.ray.set(x, y, 4096, 0, 0, -8192); + this.highestInstance = null; + return this; + } - private static final class QuadtreeIntersectorFindsHitPoint implements QuadtreeIntersector { - private Ray ray; - private final Vector3 intersection = new Vector3(); - private boolean found; + @Override + public boolean onIntersect(final MdxComplexInstance intersectingObject) { + if (intersectingObject.intersectRayWithCollision(this.ray, this.intersection, true, true)) { + if (this.intersection.z > this.z) { + this.z = this.intersection.z; + this.highestInstance = intersectingObject; + } + } + return false; + } + } - private QuadtreeIntersectorFindsHitPoint reset(final Ray ray) { - this.ray = ray; - this.found = false; - return this; - } + private static final class QuadtreeIntersectorFindsHitPoint implements QuadtreeIntersector { + private Ray ray; + private final Vector3 intersection = new Vector3(); + private boolean found; - @Override - public boolean onIntersect(final MdxComplexInstance intersectingObject) { - if (intersectingObject.intersectRayWithCollision(this.ray, this.intersection, true, true)) { - this.found = true; - return true; - } - return false; - } - } + private QuadtreeIntersectorFindsHitPoint reset(final Ray ray) { + this.ray = ray; + this.found = false; + return this; + } + + @Override + public boolean onIntersect(final MdxComplexInstance intersectingObject) { + if (intersectingObject.intersectRayWithCollision(this.ray, this.intersection, true, true)) { + this.found = true; + return true; + } + return false; + } + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java index 8affe9f..cafeff8 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java @@ -12,10 +12,13 @@ import com.etheller.warsmash.util.RenderMathUtils; import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; -import com.etheller.warsmash.viewer5.handlers.w3x.*; +import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.SecondaryTag; +import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; import com.etheller.warsmash.viewer5.handlers.w3x.SplatModel.SplatMover; +import com.etheller.warsmash.viewer5.handlers.w3x.UnitSoundset; +import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.MovementType; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.AbilityDataUI; @@ -64,7 +67,6 @@ public class RenderUnit { private boolean corpse; private boolean boneCorpse; private final RenderUnitTypeData typeData; - public UnitSound buildSound; public RenderUnit(final War3MapViewer map, final MdxModel model, final MutableGameObject row, final float x, final float y, final float z, final int playerIndex, final UnitSoundset soundset, @@ -127,7 +129,6 @@ public class RenderUnit { final float blendTime = row.getFieldAsFloat(BLEND_TIME, 0); instance.setBlendTime(blendTime * 1000.0f); - buildSound = map.getUiSounds().getSound(row.getFieldAsString(BUILD_SOUND_LABEL, 0)); } this.instance = instance; @@ -350,8 +351,9 @@ public class RenderUnit { this.selectionCircle.move(dx, dy, map.terrain.centerOffset); } this.unitAnimationListenerImpl.update(); - if(!dead && simulationUnit.isConstructing()) { - instance.setFrameByRatio(simulationUnit.getConstructionProgress() / simulationUnit.getUnitType().getBuildTime()); + if (!dead && this.simulationUnit.isConstructing()) { + this.instance.setFrameByRatio( + this.simulationUnit.getConstructionProgress() / this.simulationUnit.getUnitType().getBuildTime()); } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java index 12cc1d8..034e7fd 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java @@ -50,6 +50,7 @@ public class CommandCardIcon extends AbstractRenderableFrame { this.activeHighlightFrame.setVisible(false); this.cooldownFrame.setVisible(false); this.autocastFrame.setVisible(false); + setVisible(false); } else { if (commandButton.isEnabled()) { @@ -74,6 +75,7 @@ public class CommandCardIcon extends AbstractRenderableFrame { public void setCommandButtonData(final Texture texture, final int abilityHandleId, final int orderId, final int autoCastOrderId, final boolean active, final boolean autoCastActive, final boolean menuButton) { this.menuButton = menuButton; + setVisible(true); this.iconFrame.setVisible(true); this.activeHighlightFrame.setVisible(active); this.cooldownFrame.setVisible(false); @@ -113,7 +115,7 @@ public class CommandCardIcon extends AbstractRenderableFrame { @Override public UIFrame touchDown(final float screenX, final float screenY, final int button) { - if (this.renderBounds.contains(screenX, screenY)) { + if (isVisible() && this.renderBounds.contains(screenX, screenY)) { return this; } return super.touchDown(screenX, screenY, button); @@ -121,7 +123,7 @@ public class CommandCardIcon extends AbstractRenderableFrame { @Override public UIFrame touchUp(final float screenX, final float screenY, final int button) { - if (this.renderBounds.contains(screenX, screenY)) { + if (isVisible() && this.renderBounds.contains(screenX, screenY)) { return this; } return super.touchUp(screenX, screenY, button); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java index 0b0a887..51ca702 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java @@ -25,6 +25,7 @@ import com.etheller.warsmash.parsers.fdf.datamodel.AnchorDefinition; import com.etheller.warsmash.parsers.fdf.datamodel.FramePoint; import com.etheller.warsmash.parsers.fdf.datamodel.TextJustify; import com.etheller.warsmash.parsers.fdf.frames.SetPoint; +import com.etheller.warsmash.parsers.fdf.frames.SimpleStatusBarFrame; import com.etheller.warsmash.parsers.fdf.frames.SpriteFrame; import com.etheller.warsmash.parsers.fdf.frames.StringFrame; import com.etheller.warsmash.parsers.fdf.frames.TextureFrame; @@ -79,1037 +80,1087 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.StringMsgAbili import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.CommandCardCommandListener; public class MeleeUI implements CUnitStateListener, CommandButtonListener, CommandCardCommandListener { - public static final float DEFAULT_COMMAND_CARD_ICON_WIDTH = 0.039f; - public static final float DEFAULT_COMMAND_CARD_ICON_PRESSED_WIDTH = 0.037f; - private static final int COMMAND_CARD_WIDTH = 4; - private static final int COMMAND_CARD_HEIGHT = 3; - - private static final Vector2 screenCoordsVector = new Vector2(); - private static final Vector3 clickLocationTemp = new Vector3(); - private static final Vector2 clickLocationTemp2 = new Vector2(); - private final DataSource dataSource; - private final ExtendViewport uiViewport; - private final FreeTypeFontGenerator fontGenerator; - private final Scene uiScene; - private final Scene portraitScene; - private final GameCameraManager cameraManager; - private final War3MapViewer war3MapViewer; - private final RootFrameListener rootFrameListener; - private GameUI rootFrame; - private UIFrame consoleUI; - private UIFrame resourceBar; - private StringFrame resourceBarGoldText; - private StringFrame resourceBarLumberText; - private StringFrame resourceBarSupplyText; - private StringFrame resourceBarUpkeepText; - private SpriteFrame timeIndicator; - private UIFrame unitPortrait; - private StringFrame unitLifeText; - private StringFrame unitManaText; - private Portrait portrait; - private final Rectangle tempRect = new Rectangle(); - private final Vector2 projectionTemp1 = new Vector2(); - private final Vector2 projectionTemp2 = new Vector2(); - private UIFrame simpleInfoPanelUnitDetail; - private StringFrame simpleNameValue; - private StringFrame simpleClassValue; - private StringFrame simpleBuildingActionLabel; - private UIFrame attack1Icon; - private TextureFrame attack1IconBackdrop; - private StringFrame attack1InfoPanelIconValue; - private StringFrame attack1InfoPanelIconLevel; - private UIFrame attack2Icon; - private TextureFrame attack2IconBackdrop; - private StringFrame attack2InfoPanelIconValue; - private StringFrame attack2InfoPanelIconLevel; - private UIFrame armorIcon; - private TextureFrame armorIconBackdrop; - private StringFrame armorInfoPanelIconValue; - private StringFrame armorInfoPanelIconLevel; - private InfoPanelIconBackdrops damageBackdrops; - private InfoPanelIconBackdrops defenseBackdrops; - private SpriteFrame buildTimeIndicator; - - private final CommandCardIcon[][] commandCard = new CommandCardIcon[COMMAND_CARD_HEIGHT][COMMAND_CARD_WIDTH]; - - private RenderUnit selectedUnit; - private final List subMenuOrderIdStack = new ArrayList<>(); - - // TODO remove this & replace with FDF - private final Texture activeButtonTexture; - private UIFrame inventoryCover; - private SpriteFrame cursorFrame; - private MeleeUIMinimap meleeUIMinimap; - private final CPlayerUnitOrderListener unitOrderListener; - private StringFrame errorMessageFrame; - - private CAbilityView activeCommand; - private int activeCommandOrderId; - private RenderUnit activeCommandUnit; - private MdxComplexInstance cursorModelInstance = null; - private BufferedImage cursorModelPathing; - - private int selectedSoundCount = 0; - private final ActiveCommandUnitTargetFilter activeCommandUnitTargetFilter; - - // TODO these corrections are used for old hardcoded UI stuff, we should - // probably remove them later - private final float widthRatioCorrection; - private final float heightRatioCorrection; - private CommandCardIcon mouseDownUIFrame; - - public MeleeUI(final DataSource dataSource, final ExtendViewport uiViewport, - final FreeTypeFontGenerator fontGenerator, final Scene uiScene, final Scene portraitScene, - final CameraPreset[] cameraPresets, final CameraRates cameraRates, final War3MapViewer war3MapViewer, - final RootFrameListener rootFrameListener, final CPlayerUnitOrderListener unitOrderListener) { - this.dataSource = dataSource; - this.uiViewport = uiViewport; - this.fontGenerator = fontGenerator; - this.uiScene = uiScene; - this.portraitScene = portraitScene; - this.war3MapViewer = war3MapViewer; - this.rootFrameListener = rootFrameListener; - this.unitOrderListener = unitOrderListener; - - this.cameraManager = new GameCameraManager(cameraPresets, cameraRates); - - this.cameraManager.setupCamera(war3MapViewer.worldScene); - if (this.war3MapViewer.startLocations[0] != null) { - this.cameraManager.target.x = this.war3MapViewer.startLocations[0].x; - this.cameraManager.target.y = this.war3MapViewer.startLocations[0].y; - } - - this.activeButtonTexture = ImageUtils.getBLPTexture(war3MapViewer.mapMpq, - "UI\\Widgets\\Console\\Human\\CommandButton\\human-activebutton.blp"); - this.activeCommandUnitTargetFilter = new ActiveCommandUnitTargetFilter(); - this.widthRatioCorrection = this.uiViewport.getMinWorldWidth() / 1600f; - this.heightRatioCorrection = this.uiViewport.getMinWorldHeight() / 1200f; - - } - - private MeleeUIMinimap createMinimap(final War3MapViewer war3MapViewer) { - final Rectangle minimapDisplayArea = new Rectangle(18.75f * this.widthRatioCorrection, - 13.75f * this.heightRatioCorrection, 278.75f * this.widthRatioCorrection, - 276.25f * this.heightRatioCorrection); - Texture minimapTexture = null; - if (war3MapViewer.dataSource.has("war3mapMap.tga")) { - try { - minimapTexture = ImageUtils.getTextureNoColorCorrection(TgaFile.readTGA("war3mapMap.tga", - war3MapViewer.dataSource.getResourceAsStream("war3mapMap.tga"))); - } catch (final IOException e) { - System.err.println("Could not load minimap TGA file"); - e.printStackTrace(); - } - } else if (war3MapViewer.dataSource.has("war3mapMap.blp")) { - try { - minimapTexture = ImageUtils - .getTexture(ImageIO.read(war3MapViewer.dataSource.getResourceAsStream("war3mapMap.blp"))); - } catch (final IOException e) { - System.err.println("Could not load minimap BLP file"); - e.printStackTrace(); - } - } - final Texture[] teamColors = new Texture[WarsmashConstants.MAX_PLAYERS]; - for (int i = 0; i < teamColors.length; i++) { - teamColors[i] = ImageUtils.getBLPTexture(war3MapViewer.dataSource, - "ReplaceableTextures\\" + ReplaceableIds.getPathString(1) + ReplaceableIds.getIdString(i) + ".blp"); - } - final Rectangle playableMapArea = war3MapViewer.terrain.getPlayableMapArea(); - return new MeleeUIMinimap(minimapDisplayArea, playableMapArea, minimapTexture, teamColors); - } - - /** - * Called "main" because this was originally written in JASS so that maps could - * override it, and I may convert it back to the JASS at some point. - */ - public void main() { - // ================================= - // Load skins and templates - // ================================= - this.rootFrame = new GameUI(this.dataSource, GameUI.loadSkin(this.dataSource, 0), this.uiViewport, - this.fontGenerator, this.uiScene, this.war3MapViewer); - this.rootFrameListener.onCreate(this.rootFrame); - try { - this.rootFrame.loadTOCFile("UI\\FrameDef\\FrameDef.toc"); - } catch (final IOException exc) { - throw new IllegalStateException("Unable to load FrameDef.toc", exc); - } - try { - this.rootFrame.loadTOCFile("UI\\FrameDef\\SmashFrameDef.toc"); - } catch (final IOException exc) { - throw new IllegalStateException("Unable to load SmashFrameDef.toc", exc); - } - this.damageBackdrops = new InfoPanelIconBackdrops(CAttackType.values(), this.rootFrame, "Damage", "Neutral"); - this.defenseBackdrops = new InfoPanelIconBackdrops(CDefenseType.values(), this.rootFrame, "Armor", "Neutral"); - - // ================================= - // Load major UI components - // ================================= - // Console UI is the background with the racial theme - this.consoleUI = this.rootFrame.createSimpleFrame("ConsoleUI", this.rootFrame, 0); - this.consoleUI.setSetAllPoints(true); - - // Resource bar is a 3 part bar with Gold, Lumber, and Food. - // Its template does not specify where to put it, so we must - // put it in the "TOPRIGHT" corner. - this.resourceBar = this.rootFrame.createSimpleFrame("ResourceBarFrame", this.consoleUI, 0); - this.resourceBar.addSetPoint(new SetPoint(FramePoint.TOPRIGHT, this.consoleUI, FramePoint.TOPRIGHT, 0, 0)); - this.resourceBarGoldText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarGoldText", 0); - this.resourceBarGoldText.setText("500"); - this.resourceBarLumberText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarLumberText", 0); - this.resourceBarLumberText.setText("150"); - this.resourceBarSupplyText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarSupplyText", 0); - this.resourceBarSupplyText.setText("12/100"); - this.resourceBarUpkeepText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarUpkeepText", 0); - this.resourceBarUpkeepText.setText("No Upkeep"); - this.resourceBarUpkeepText.setColor(Color.GREEN); - - // Create the Time Indicator (clock) - this.timeIndicator = (SpriteFrame) this.rootFrame.createFrame("TimeOfDayIndicator", this.rootFrame, 0, 0); - this.timeIndicator.setSequence(0); // play the stand - this.timeIndicator.setAnimationSpeed(0.0f); // do not advance automatically - - // Create the unit portrait stuff - this.portrait = new Portrait(this.war3MapViewer, this.portraitScene); - positionPortrait(); - this.unitPortrait = this.rootFrame.createSimpleFrame("UnitPortrait", this.consoleUI, 0); - this.unitLifeText = (StringFrame) this.rootFrame.getFrameByName("UnitPortraitHitPointText", 0); - this.unitManaText = (StringFrame) this.rootFrame.getFrameByName("UnitPortraitManaPointText", 0); - - this.simpleInfoPanelUnitDetail = this.rootFrame.createSimpleFrame("SimpleInfoPanelUnitDetail", this.consoleUI, - 0); - this.simpleInfoPanelUnitDetail - .addAnchor(new AnchorDefinition(FramePoint.BOTTOM, 0, GameUI.convertY(this.uiViewport, 0.0f))); - this.simpleInfoPanelUnitDetail.setWidth(GameUI.convertY(this.uiViewport, 0.180f)); - this.simpleInfoPanelUnitDetail.setHeight(GameUI.convertY(this.uiViewport, 0.105f)); - this.simpleNameValue = (StringFrame) this.rootFrame.getFrameByName("SimpleNameValue", 0); - this.simpleClassValue = (StringFrame) this.rootFrame.getFrameByName("SimpleClassValue", 0); - this.simpleBuildingActionLabel = (StringFrame) this.rootFrame.getFrameByName("SimpleBuildingActionLabel", 0); - - this.attack1Icon = this.rootFrame.createSimpleFrame("SimpleInfoPanelIconDamage", this.simpleInfoPanelUnitDetail, - 0); - this.attack1Icon.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, - FramePoint.TOPLEFT, 0, GameUI.convertY(this.uiViewport, -0.030125f))); - this.attack1IconBackdrop = (TextureFrame) this.rootFrame.getFrameByName("InfoPanelIconBackdrop", 0); - this.attack1InfoPanelIconValue = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconValue", 0); - this.attack1InfoPanelIconLevel = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconLevel", 0); - - this.attack2Icon = this.rootFrame.createSimpleFrame("SimpleInfoPanelIconDamage", this.simpleInfoPanelUnitDetail, - 1); - this.attack2Icon - .addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, FramePoint.TOPLEFT, - GameUI.convertX(this.uiViewport, 0.1f), GameUI.convertY(this.uiViewport, -0.030125f))); - this.attack2IconBackdrop = (TextureFrame) this.rootFrame.getFrameByName("InfoPanelIconBackdrop", 1); - this.attack2InfoPanelIconValue = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconValue", 1); - this.attack2InfoPanelIconLevel = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconLevel", 1); - - this.armorIcon = this.rootFrame.createSimpleFrame("SimpleInfoPanelIconArmor", this.simpleInfoPanelUnitDetail, - 1); - this.armorIcon.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, FramePoint.TOPLEFT, - GameUI.convertX(this.uiViewport, 0f), GameUI.convertY(this.uiViewport, -0.06025f))); - this.armorIconBackdrop = (TextureFrame) this.rootFrame.getFrameByName("InfoPanelIconBackdrop", 0); - this.armorInfoPanelIconValue = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconValue", 0); - this.armorInfoPanelIconLevel = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconLevel", 0); - - this.inventoryCover = this.rootFrame.createSimpleFrame("SmashConsoleInventoryCover", this.rootFrame, 0); - - this.errorMessageFrame = this.rootFrame.createStringFrame("SmashErrorMessageFrame", this.consoleUI, - new Color(0xFFFFCC00), TextJustify.LEFT, TextJustify.MIDDLE, 0.014f); - this.errorMessageFrame.addAnchor(new AnchorDefinition(FramePoint.BOTTOMLEFT, - GameUI.convertX(this.uiViewport, 0.275f), GameUI.convertY(this.uiViewport, 0.275f))); - this.errorMessageFrame.setWidth(GameUI.convertX(this.uiViewport, 0.25f)); - - int commandButtonIndex = 0; - for (int j = 0; j < COMMAND_CARD_HEIGHT; j++) { - for (int i = 0; i < COMMAND_CARD_WIDTH; i++) { - final CommandCardIcon commandCardIcon = new CommandCardIcon("SmashCommandButton_" + commandButtonIndex, - this.rootFrame, this); - this.rootFrame.add(commandCardIcon); - final TextureFrame iconFrame = this.rootFrame.createTextureFrame( - "SmashCommandButton_" + (commandButtonIndex) + "_Icon", this.rootFrame, false, null); - final TextureFrame activeHighlightFrame = this.rootFrame.createTextureFrame( - "SmashCommandButton_" + (commandButtonIndex) + "_ActiveHighlight", this.rootFrame, true, null, - FilterMode.ADDALPHA); - final SpriteFrame cooldownFrame = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", - "SmashCommandButton_" + (commandButtonIndex) + "_Cooldown", this.rootFrame, "", 0); - final SpriteFrame autocastFrame = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", - "SmashCommandButton_" + (commandButtonIndex) + "_Autocast", this.rootFrame, "", 0); - commandCardIcon.addAnchor(new AnchorDefinition(FramePoint.BOTTOMLEFT, - GameUI.convertX(this.uiViewport, 0.6175f + (0.0434f * i)), - GameUI.convertY(this.uiViewport, 0.095f - (0.044f * j)))); - commandCardIcon.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); - commandCardIcon.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); - iconFrame.addSetPoint(new SetPoint(FramePoint.CENTER, commandCardIcon, FramePoint.CENTER, 0, 0)); - iconFrame.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); - iconFrame.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); - iconFrame.setTexture(ImageUtils.DEFAULT_ICON_PATH, this.rootFrame); - activeHighlightFrame - .addSetPoint(new SetPoint(FramePoint.CENTER, commandCardIcon, FramePoint.CENTER, 0, 0)); - activeHighlightFrame.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); - activeHighlightFrame.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); - activeHighlightFrame.setTexture("CommandButtonActiveHighlight", this.rootFrame); - cooldownFrame.addSetPoint(new SetPoint(FramePoint.CENTER, commandCardIcon, FramePoint.CENTER, 0, 0)); - this.rootFrame.setSpriteFrameModel(cooldownFrame, this.rootFrame.getSkinField("CommandButtonCooldown")); - cooldownFrame.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); - cooldownFrame.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); - autocastFrame.addSetPoint(new SetPoint(FramePoint.CENTER, commandCardIcon, FramePoint.CENTER, 0, 0)); - this.rootFrame.setSpriteFrameModel(autocastFrame, this.rootFrame.getSkinField("CommandButtonAutocast")); - autocastFrame.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); - autocastFrame.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); - commandCardIcon.set(iconFrame, activeHighlightFrame, cooldownFrame, autocastFrame); - this.commandCard[j][i] = commandCardIcon; - commandCardIcon.setCommandButton(null); - commandButtonIndex++; - } - } - buildTimeIndicator = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", - "SmashBuildProgressBar", this.rootFrame, "", 0); - buildTimeIndicator.addSetPoint(new SetPoint(FramePoint.CENTER, simpleClassValue, FramePoint.CENTER, - GameUI.convertX(this.uiViewport, -0.04f), - GameUI.convertY(this.uiViewport, -0.025f))); - this.rootFrame.setSpriteFrameModel(buildTimeIndicator, this.rootFrame.getSkinField("BuildTimeIndicator")); - buildTimeIndicator.setAnimationSpeed(0); - - this.cursorFrame = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", "SmashCursorFrame", this.rootFrame, - "", 0); - this.rootFrame.setSpriteFrameModel(this.cursorFrame, this.rootFrame.getSkinField("Cursor")); - this.cursorFrame.setSequence("Normal"); - this.cursorFrame.setZDepth(1.0f); - Gdx.input.setCursorCatched(true); - - this.meleeUIMinimap = createMinimap(this.war3MapViewer); - - this.rootFrame.positionBounds(this.uiViewport); - selectUnit(null); - } - - @Override - public void startUsingAbility(final int abilityHandleId, final int orderId, final boolean rightClick) { - // TODO not O(N) - CAbilityView abilityToUse = null; - for (final CAbility ability : this.selectedUnit.getSimulationUnit().getAbilities()) { - if (ability.getHandleId() == abilityHandleId) { - abilityToUse = ability; - break; - } - } - if (abilityToUse != null) { - final StringMsgAbilityActivationReceiver stringMsgActivationReceiver = StringMsgAbilityActivationReceiver - .getInstance().reset(); - abilityToUse.checkCanUse(this.war3MapViewer.simulation, this.selectedUnit.getSimulationUnit(), orderId, - stringMsgActivationReceiver); - if (!stringMsgActivationReceiver.isUseOk()) { - showCommandError(stringMsgActivationReceiver.getMessage()); - } else { - final BooleanAbilityTargetCheckReceiver noTargetReceiver = BooleanAbilityTargetCheckReceiver - .getInstance().reset(); - abilityToUse.checkCanTargetNoTarget(this.war3MapViewer.simulation, - this.selectedUnit.getSimulationUnit(), orderId, noTargetReceiver); - if (noTargetReceiver.isTargetable()) { - this.unitOrderListener.issueImmediateOrder(this.selectedUnit.getSimulationUnit().getHandleId(), - abilityHandleId, orderId, isShiftDown()); - } else { - this.activeCommand = abilityToUse; - this.activeCommandOrderId = orderId; - this.activeCommandUnit = this.selectedUnit; - clearAndRepopulateCommandCard(); - } - } - } else { - this.unitOrderListener.issueImmediateOrder(this.selectedUnit.getSimulationUnit().getHandleId(), - abilityHandleId, orderId, isShiftDown()); - } - if (rightClick) { - this.war3MapViewer.getUiSounds().getSound("AutoCastButtonClick").play(this.uiScene.audioContext, 0, 0); - } - } - - @Override - public void openMenu(final int orderId) { - if (orderId == 0) { - this.subMenuOrderIdStack.clear(); - this.activeCommandUnit = null; - this.activeCommand = null; - this.activeCommandOrderId = -1; - } else { - this.subMenuOrderIdStack.add(orderId); - } - clearAndRepopulateCommandCard(); - } - - public void showCommandError(final String message) { - this.errorMessageFrame.setText(message); - } - - public void update(final float deltaTime) { - this.portrait.update(); - - final int baseMouseX = Gdx.input.getX(); - int mouseX = baseMouseX; - final int baseMouseY = Gdx.input.getY(); - int mouseY = baseMouseY; - final int minX = this.uiViewport.getScreenX(); - final int maxX = minX + this.uiViewport.getScreenWidth(); - final int minY = this.uiViewport.getScreenY(); - final int maxY = minY + this.uiViewport.getScreenHeight(); - final boolean left = mouseX <= (minX + 3); - final boolean right = mouseX >= (maxX - 3); - final boolean up = mouseY <= (minY + 3); - final boolean down = mouseY >= (maxY - 3); - this.cameraManager.applyVelocity(deltaTime, up, down, left, right); - - mouseX = Math.max(minX, Math.min(maxX, mouseX)); - mouseY = Math.max(minY, Math.min(maxY, mouseY)); - if (Gdx.input.isCursorCatched()) { - Gdx.input.setCursorPosition(mouseX, mouseY); - } - - screenCoordsVector.set(mouseX, mouseY); - this.uiViewport.unproject(screenCoordsVector); - this.cursorFrame.setFramePointX(FramePoint.LEFT, screenCoordsVector.x); - this.cursorFrame.setFramePointY(FramePoint.BOTTOM, screenCoordsVector.y); - - if (this.activeCommand != null) { - if (this.activeCommand instanceof AbstractCAbilityBuild) { - boolean justLoaded = false; - if (this.cursorModelInstance == null) { - final MutableObjectData unitData = this.war3MapViewer.getAllObjectData().getUnits(); - final War3ID buildingTypeId = new War3ID(this.activeCommandOrderId); - final String unitModelPath = this.war3MapViewer.getUnitModelPath(unitData.get(buildingTypeId)); - final MdxModel model = (MdxModel) this.war3MapViewer.load(unitModelPath, - this.war3MapViewer.mapPathSolver, this.war3MapViewer.solverParams); - this.cursorModelInstance = (MdxComplexInstance) model.addInstance(); - this.cursorModelInstance.setVertexColor(new float[]{1, 1, 1, 0.5f}); - this.cursorModelInstance.rotate(RenderUnit.tempQuat.setFromAxis(RenderMathUtils.VEC3_UNIT_Z, - this.war3MapViewer.simulation.getGameplayConstants().getBuildingAngle())); - this.cursorModelInstance.setAnimationSpeed(0f); - justLoaded = true; - final CUnitType buildingUnitType = this.war3MapViewer.simulation.getUnitData() - .getUnitType(buildingTypeId); - this.cursorModelPathing = buildingUnitType.getBuildingPathingPixelMap(); - } - this.war3MapViewer.getClickLocation(clickLocationTemp, baseMouseX, - Gdx.graphics.getHeight() - baseMouseY); - if (this.cursorModelPathing != null) { - clickLocationTemp.x = Math.round(clickLocationTemp.x / 64f) * 64f; - clickLocationTemp.y = Math.round(clickLocationTemp.y / 64f) * 64f; - clickLocationTemp.z = this.war3MapViewer.terrain.getGroundHeight(clickLocationTemp.x, - clickLocationTemp.y); - } - this.cursorModelInstance.setLocation(clickLocationTemp); - SequenceUtils.randomSequence(this.cursorModelInstance, PrimaryTag.STAND); - this.cursorFrame.setVisible(false); - if (justLoaded) { - this.cursorModelInstance.setScene(this.war3MapViewer.worldScene); - } - } else { - if (this.cursorModelInstance != null) { - this.cursorModelInstance.detach(); - this.cursorModelInstance = null; - this.cursorFrame.setVisible(true); - } - this.cursorFrame.setSequence("Target"); - } - } else { - if (this.cursorModelInstance != null) { - this.cursorModelInstance.detach(); - this.cursorModelInstance = null; - this.cursorFrame.setVisible(true); - } - if (down) { - if (left) { - this.cursorFrame.setSequence("Scroll Down Left"); - } else if (right) { - this.cursorFrame.setSequence("Scroll Down Right"); - } else { - this.cursorFrame.setSequence("Scroll Down"); - } - } else if (up) { - if (left) { - this.cursorFrame.setSequence("Scroll Up Left"); - } else if (right) { - this.cursorFrame.setSequence("Scroll Up Right"); - } else { - this.cursorFrame.setSequence("Scroll Up"); - } - } else if (left) { - this.cursorFrame.setSequence("Scroll Left"); - } else if (right) { - this.cursorFrame.setSequence("Scroll Right"); - } else { - this.cursorFrame.setSequence("Normal"); - } - } - if (this.buildTimeIndicator.isVisible() && this.selectedUnit != null) { - buildTimeIndicator.setFrameByRatio(Math.min(this.selectedUnit.getSimulationUnit().getConstructionProgress() / - this.selectedUnit.getSimulationUnit().getUnitType().getBuildTime(), 0.99f)); - } - final float groundHeight = Math.max( - this.war3MapViewer.terrain.getGroundHeight(this.cameraManager.target.x, this.cameraManager.target.y), - this.war3MapViewer.terrain.getWaterHeight(this.cameraManager.target.x, this.cameraManager.target.y)); - this.cameraManager.updateTargetZ(groundHeight); - this.cameraManager.updateCamera(); - } - - public void render(final SpriteBatch batch, final BitmapFont font20, final GlyphLayout glyphLayout) { - this.rootFrame.render(batch, font20, glyphLayout); - if (this.selectedUnit != null) { - font20.setColor(Color.WHITE); - - } - - this.meleeUIMinimap.render(batch, this.war3MapViewer.units); - this.timeIndicator.setFrameByRatio(this.war3MapViewer.simulation.getGameTimeOfDay() - / this.war3MapViewer.simulation.getGameplayConstants().getGameDayHours()); - } - - public void portraitTalk() { - this.portrait.talk(); - } - - private final class ActiveCommandUnitTargetFilter implements CUnitFilterFunction { - @Override - public boolean call(final CUnit unit) { - final BooleanAbilityTargetCheckReceiver targetReceiver = BooleanAbilityTargetCheckReceiver - .getInstance(); - MeleeUI.this.activeCommand.checkCanTarget(MeleeUI.this.war3MapViewer.simulation, - MeleeUI.this.activeCommandUnit.getSimulationUnit(), MeleeUI.this.activeCommandOrderId, unit, - targetReceiver); - return targetReceiver.isTargetable(); - } - } - - private static final class Portrait { - private MdxComplexInstance modelInstance; - private final PortraitCameraManager portraitCameraManager; - private final Scene portraitScene; - - public Portrait(final War3MapViewer war3MapViewer, final Scene portraitScene) { - this.portraitScene = portraitScene; - this.portraitCameraManager = new PortraitCameraManager(); - this.portraitCameraManager.setupCamera(this.portraitScene); - this.portraitScene.camera.viewport(new Rectangle(100, 0, 6400, 48)); - } - - public void update() { - this.portraitCameraManager.updateCamera(); - if ((this.modelInstance != null) - && (this.modelInstance.sequenceEnded || (this.modelInstance.sequence == -1))) { - SequenceUtils.randomSequence(this.modelInstance, PrimaryTag.PORTRAIT, SequenceUtils.EMPTY, true); - } - } - - public void talk() { - SequenceUtils.randomSequence(this.modelInstance, PrimaryTag.PORTRAIT, SequenceUtils.TALK, true); - } - - public void setSelectedUnit(final RenderUnit unit) { - if (unit == null) { - if (this.modelInstance != null) { - this.portraitScene.removeInstance(this.modelInstance); - } - this.modelInstance = null; - this.portraitCameraManager.setModelInstance(null, null); - } else { - final MdxModel portraitModel = unit.portraitModel; - if (portraitModel != null) { - if (this.modelInstance != null) { - this.portraitScene.removeInstance(this.modelInstance); - } - this.modelInstance = (MdxComplexInstance) portraitModel.addInstance(); - this.portraitCameraManager.setModelInstance(this.modelInstance, portraitModel); - this.modelInstance.setSequenceLoopMode(SequenceLoopMode.NEVER_LOOP); - this.modelInstance.setScene(this.portraitScene); - this.modelInstance.setVertexColor(unit.instance.vertexColor); - this.modelInstance.setTeamColor(unit.playerIndex); - } - } - } - } - - public void selectUnit(RenderUnit unit) { - this.subMenuOrderIdStack.clear(); - if ((unit != null) && unit.getSimulationUnit().isDead()) { - unit = null; - } - if (this.selectedUnit != null) { - this.selectedUnit.getSimulationUnit().removeStateListener(this); - } - this.portrait.setSelectedUnit(unit); - this.selectedUnit = unit; - if (unit == null) { - clearCommandCard(); - this.simpleNameValue.setText(""); - this.unitLifeText.setText(""); - this.unitManaText.setText(""); - this.simpleClassValue.setText(""); - this.simpleBuildingActionLabel.setText(""); - this.attack1Icon.setVisible(false); - this.attack2Icon.setVisible(false); - this.attack1InfoPanelIconLevel.setText(""); - this.attack2InfoPanelIconLevel.setText(""); - this.armorIcon.setVisible(false); - this.armorInfoPanelIconLevel.setText(""); - this.buildTimeIndicator.setVisible(false); - } else { - unit.getSimulationUnit().addStateListener(this); - this.simpleNameValue.setText(unit.getSimulationUnit().getUnitType().getName()); - String classText = null; - for (final CUnitClassification classification : unit.getSimulationUnit().getClassifications()) { - if ((classification == CUnitClassification.MECHANICAL) - && unit.getSimulationUnit().getUnitType().isBuilding()) { - // buildings dont display MECHANICAL - continue; - } - if (classification.getDisplayName() != null) { - classText = classification.getDisplayName(); - } - } - if (classText != null) { - this.simpleClassValue.setText(classText); - } else { - this.simpleClassValue.setText(""); - } - this.unitLifeText.setText(FastNumberFormat.formatWholeNumber(unit.getSimulationUnit().getLife()) + " / " - + unit.getSimulationUnit().getMaximumLife()); - final int maximumMana = unit.getSimulationUnit().getMaximumMana(); - if (maximumMana > 0) { - this.unitManaText.setText( - FastNumberFormat.formatWholeNumber(unit.getSimulationUnit().getMana()) + " / " + maximumMana); - } else { - this.unitManaText.setText(""); - } - this.simpleBuildingActionLabel.setText(""); - - final boolean anyAttacks = unit.getSimulationUnit().getUnitType().getAttacks().size() > 0; - boolean constructing = unit.getSimulationUnit().isConstructing(); - final UIFrame localArmorIcon = this.armorIcon; - final TextureFrame localArmorIconBackdrop = this.armorIconBackdrop; - final StringFrame localArmorInfoPanelIconValue = this.armorInfoPanelIconValue; - if (anyAttacks && !constructing) { - final CUnitAttack attackOne = unit.getSimulationUnit().getUnitType().getAttacks().get(0); - this.attack1Icon.setVisible(attackOne.isShowUI()); - this.attack1IconBackdrop.setTexture(this.damageBackdrops.getTexture(attackOne.getAttackType())); - this.attack1InfoPanelIconValue.setText(attackOne.getMinDamage() + " - " + attackOne.getMaxDamage()); - if (unit.getSimulationUnit().getUnitType().getAttacks().size() > 1) { - final CUnitAttack attackTwo = unit.getSimulationUnit().getUnitType().getAttacks().get(1); - this.attack2Icon.setVisible(attackTwo.isShowUI()); - this.attack2IconBackdrop.setTexture(this.damageBackdrops.getTexture(attackTwo.getAttackType())); - this.attack2InfoPanelIconValue.setText(attackTwo.getMinDamage() + " - " + attackTwo.getMaxDamage()); - } else { - this.attack2Icon.setVisible(false); - } - - this.armorIcon.addSetPoint( - new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, FramePoint.TOPLEFT, - GameUI.convertX(this.uiViewport, 0f), GameUI.convertY(this.uiViewport, -0.06025f))); - this.armorIcon.positionBounds(this.uiViewport); - } else { - this.attack1Icon.setVisible(false); - this.attack2Icon.setVisible(false); - - this.armorIcon.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, - FramePoint.TOPLEFT, 0, GameUI.convertY(this.uiViewport, -0.030125f))); - this.armorIcon.positionBounds(this.uiViewport); - } - - localArmorIcon.setVisible(!constructing); - buildTimeIndicator.setVisible(constructing); - if (constructing) { - buildTimeIndicator.setSequence(0); - } - final Texture defenseTexture = this.defenseBackdrops - .getTexture(unit.getSimulationUnit().getUnitType().getDefenseType()); - if (defenseTexture == null) { - throw new RuntimeException( - unit.getSimulationUnit().getUnitType().getDefenseType() + " can't find texture!"); - } - localArmorIconBackdrop.setTexture(defenseTexture); - localArmorInfoPanelIconValue.setText(Integer.toString(unit.getSimulationUnit().getDefense())); - clearAndRepopulateCommandCard(); - } - } - - private void clearCommandCard() { - for (int j = 0; j < COMMAND_CARD_HEIGHT; j++) { - for (int i = 0; i < COMMAND_CARD_WIDTH; i++) { - this.commandCard[j][i].setCommandButton(null); - } - } - } - - @Override - public void commandButton(final int buttonPositionX, final int buttonPositionY, final Texture icon, - final int abilityHandleId, final int orderId, final int autoCastId, final boolean active, - final boolean autoCastActive, final boolean menuButton) { - final int x = Math.max(0, Math.min(COMMAND_CARD_WIDTH - 1, buttonPositionX)); - final int y = Math.max(0, Math.min(COMMAND_CARD_HEIGHT - 1, buttonPositionY)); - this.commandCard[y][x].setCommandButtonData(icon, abilityHandleId, orderId, autoCastId, active, autoCastActive, - menuButton); - } - - public void resize(final Rectangle viewport) { - this.cameraManager.resize(viewport); - positionPortrait(); - } - - public void positionPortrait() { - this.projectionTemp1.x = 422 * this.widthRatioCorrection; - this.projectionTemp1.y = 57 * this.heightRatioCorrection; - this.projectionTemp2.x = (422 + 167) * this.widthRatioCorrection; - this.projectionTemp2.y = (57 + 170) * this.heightRatioCorrection; - this.uiViewport.project(this.projectionTemp1); - this.uiViewport.project(this.projectionTemp2); - - this.tempRect.x = this.projectionTemp1.x + this.uiViewport.getScreenX(); - this.tempRect.y = this.projectionTemp1.y + this.uiViewport.getScreenY(); - this.tempRect.width = this.projectionTemp2.x - this.projectionTemp1.x; - this.tempRect.height = this.projectionTemp2.y - this.projectionTemp1.y; - this.portrait.portraitScene.camera.viewport(this.tempRect); - } - - private static final class InfoPanelIconBackdrops { - private final Texture[] damageBackdropTextures; - - public InfoPanelIconBackdrops(final CodeKeyType[] attackTypes, final GameUI gameUI, final String prefix, - final String suffix) { - this.damageBackdropTextures = new Texture[attackTypes.length]; - for (int index = 0; index < attackTypes.length; index++) { - final CodeKeyType attackType = attackTypes[index]; - String skinLookupKey = "InfoPanelIcon" + prefix + attackType.getCodeKey() + suffix; - final Texture suffixTexture = gameUI.loadTexture(gameUI.getSkinField(skinLookupKey)); - if (suffixTexture != null) { - this.damageBackdropTextures[index] = suffixTexture; - } else { - skinLookupKey = "InfoPanelIcon" + prefix + attackType.getCodeKey(); - this.damageBackdropTextures[index] = gameUI.loadTexture(gameUI.getSkinField(skinLookupKey)); - } - } - } - - public Texture getTexture(final CodeKeyType attackType) { - if (attackType != null) { - final int ordinal = attackType.ordinal(); - if ((ordinal >= 0) && (ordinal < this.damageBackdropTextures.length)) { - return this.damageBackdropTextures[ordinal]; - } - } - return this.damageBackdropTextures[0]; - } - - private static String getSuffix(final CAttackType attackType) { - switch (attackType) { - case CHAOS: - return "Chaos"; - case HERO: - return "Hero"; - case MAGIC: - return "Magic"; - case NORMAL: - return "Normal"; - case PIERCE: - return "Pierce"; - case SIEGE: - return "Siege"; - case SPELLS: - return "Magic"; - case UNKNOWN: - return "Unknown"; - default: - throw new IllegalArgumentException("Unknown attack type: " + attackType); - } - - } - } - - @Override - public void lifeChanged() { - if (this.selectedUnit.getSimulationUnit().isDead()) { - selectUnit(null); - } else { - this.unitLifeText - .setText(FastNumberFormat.formatWholeNumber(this.selectedUnit.getSimulationUnit().getLife()) + " / " - + this.selectedUnit.getSimulationUnit().getMaximumLife()); - } - } - - @Override - public void ordersChanged(final int abilityHandleId, final int orderId) { - selectUnit(selectedUnit); - } - - private void clearAndRepopulateCommandCard() { - clearCommandCard(); - final AbilityDataUI abilityDataUI = this.war3MapViewer.getAbilityDataUI(); - final int menuOrderId = getSubMenuOrderId(); - if (this.activeCommand != null) { - final IconUI cancelUI = abilityDataUI.getCancelUI(); - this.commandButton(cancelUI.getButtonPositionX(), cancelUI.getButtonPositionY(), cancelUI.getIcon(), 0, - menuOrderId, 0, false, false, true); - } else if (this.selectedUnit.getSimulationUnit().isConstructing()) { - - } else { - if (menuOrderId != 0) { - final int exitOrderId = this.subMenuOrderIdStack.size() > 1 - ? this.subMenuOrderIdStack.get(this.subMenuOrderIdStack.size() - 2) - : 0; - final IconUI cancelUI = abilityDataUI.getCancelUI(); - this.commandButton(cancelUI.getButtonPositionX(), cancelUI.getButtonPositionY(), cancelUI.getIcon(), 0, - exitOrderId, 0, false, false, true); - } - this.selectedUnit.populateCommandCard(this.war3MapViewer.simulation, this, abilityDataUI, menuOrderId); - } - } - - private int getSubMenuOrderId() { - return this.subMenuOrderIdStack.isEmpty() ? 0 - : this.subMenuOrderIdStack.get(this.subMenuOrderIdStack.size() - 1); - } - - public RenderUnit getSelectedUnit() { - return this.selectedUnit; - } - - public boolean keyDown(final int keycode) { - return this.cameraManager.keyDown(keycode); - } - - public boolean keyUp(final int keycode) { - return this.cameraManager.keyUp(keycode); - } - - public void scrolled(final int amount) { - this.cameraManager.scrolled(amount); - } - - public boolean touchDown(final int screenX, final int screenY, final float worldScreenY, final int button) { - screenCoordsVector.set(screenX, screenY); - this.uiViewport.unproject(screenCoordsVector); - if (this.meleeUIMinimap.containsMouse(screenCoordsVector.x, screenCoordsVector.y)) { - final Vector2 worldPoint = this.meleeUIMinimap.getWorldPointFromScreen(screenCoordsVector.x, - screenCoordsVector.y); - this.cameraManager.target.x = worldPoint.x; - this.cameraManager.target.y = worldPoint.y; - return true; - } - final UIFrame clickedUIFrame = this.rootFrame.touchDown(screenCoordsVector.x, screenCoordsVector.y, button); - if (clickedUIFrame == null) { - // try to interact with world - if (this.activeCommand != null) { - if (button == Input.Buttons.RIGHT) { - this.activeCommandUnit = null; - this.activeCommand = null; - this.activeCommandOrderId = -1; - clearAndRepopulateCommandCard(); - } else { - final RenderUnit rayPickUnit = this.war3MapViewer.rayPickUnit(screenX, worldScreenY, - this.activeCommandUnitTargetFilter); - final boolean shiftDown = isShiftDown(); - if (rayPickUnit != null) { - this.unitOrderListener.issueTargetOrder( - this.activeCommandUnit.getSimulationUnit().getHandleId(), - this.activeCommand.getHandleId(), this.activeCommandOrderId, - rayPickUnit.getSimulationUnit().getHandleId(), shiftDown); - if (getSelectedUnit().soundset.yesAttack - .playUnitResponse(this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { - portraitTalk(); - } - this.selectedSoundCount = 0; - if (!shiftDown) { - this.subMenuOrderIdStack.clear(); - this.activeCommandUnit = null; - this.activeCommand = null; - this.activeCommandOrderId = -1; - clearAndRepopulateCommandCard(); - } - } else { - this.war3MapViewer.getClickLocation(clickLocationTemp, screenX, (int) worldScreenY); - clickLocationTemp2.set(clickLocationTemp.x, clickLocationTemp.y); - - this.activeCommand.checkCanTarget(this.war3MapViewer.simulation, - this.activeCommandUnit.getSimulationUnit(), this.activeCommandOrderId, - clickLocationTemp2, PointAbilityTargetCheckReceiver.INSTANCE); - final Vector2 target = PointAbilityTargetCheckReceiver.INSTANCE.getTarget(); - if (target != null) { - if (this.activeCommand instanceof CAbilityAttack) { - this.war3MapViewer.showConfirmation(clickLocationTemp, 1, 0, 0); - } else { - this.war3MapViewer.showConfirmation(clickLocationTemp, 0, 1, 0); - } - this.unitOrderListener.issuePointOrder( - this.activeCommandUnit.getSimulationUnit().getHandleId(), - this.activeCommand.getHandleId(), this.activeCommandOrderId, clickLocationTemp2.x, - clickLocationTemp2.y, shiftDown); - if (getSelectedUnit().soundset.yes - .playUnitResponse(this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { - portraitTalk(); - } - this.selectedSoundCount = 0; - if (this.activeCommand instanceof AbstractCAbilityBuild) { - this.war3MapViewer.getUiSounds().getSound("PlaceBuildingDefault") - .play(this.uiScene.audioContext, 0, 0); - } - if (!shiftDown) { - this.subMenuOrderIdStack.clear(); - this.activeCommandUnit = null; - this.activeCommand = null; - this.activeCommandOrderId = -1; - clearAndRepopulateCommandCard(); - } - - } - } - } - } else { - if (button == Input.Buttons.RIGHT) { - if (getSelectedUnit() != null) { - final RenderUnit rayPickUnit = this.war3MapViewer.rayPickUnit(screenX, worldScreenY); - if ((rayPickUnit != null) && !rayPickUnit.getSimulationUnit().isDead()) { - boolean ordered = false; - for (final RenderUnit unit : this.war3MapViewer.selected) { - for (final CAbility ability : unit.getSimulationUnit().getAbilities()) { - ability.checkCanTarget(this.war3MapViewer.simulation, unit.getSimulationUnit(), - OrderIds.smart, rayPickUnit.getSimulationUnit(), - CWidgetAbilityTargetCheckReceiver.INSTANCE); - final CWidget targetWidget = CWidgetAbilityTargetCheckReceiver.INSTANCE.getTarget(); - if (targetWidget != null) { - this.unitOrderListener.issueTargetOrder(unit.getSimulationUnit().getHandleId(), - ability.getHandleId(), OrderIds.smart, targetWidget.getHandleId(), - isShiftDown()); - ordered = true; - } - } - - } - if (ordered) { - if (getSelectedUnit().soundset.yesAttack.playUnitResponse( - this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { - portraitTalk(); - } - this.selectedSoundCount = 0; - } - } else { - this.war3MapViewer.getClickLocation(clickLocationTemp, screenX, (int) worldScreenY); - this.war3MapViewer.showConfirmation(clickLocationTemp, 0, 1, 0); - clickLocationTemp2.set(clickLocationTemp.x, clickLocationTemp.y); - - boolean ordered = false; - for (final RenderUnit unit : this.war3MapViewer.selected) { - for (final CAbility ability : unit.getSimulationUnit().getAbilities()) { - ability.checkCanUse(this.war3MapViewer.simulation, unit.getSimulationUnit(), - OrderIds.smart, BooleanAbilityActivationReceiver.INSTANCE); - if (BooleanAbilityActivationReceiver.INSTANCE.isOk()) { - ability.checkCanTarget(this.war3MapViewer.simulation, unit.getSimulationUnit(), - OrderIds.smart, clickLocationTemp2, - PointAbilityTargetCheckReceiver.INSTANCE); - final Vector2 target = PointAbilityTargetCheckReceiver.INSTANCE.getTarget(); - if (target != null) { - this.unitOrderListener.issuePointOrder( - unit.getSimulationUnit().getHandleId(), ability.getHandleId(), - OrderIds.smart, clickLocationTemp2.x, clickLocationTemp2.y, - isShiftDown()); - ordered = true; - } - } - } - - } - - if (ordered) { - if (getSelectedUnit().soundset.yes.playUnitResponse( - this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { - portraitTalk(); - } - this.selectedSoundCount = 0; - } - } - } - } else { - final List selectedUnits = this.war3MapViewer.selectUnit(screenX, worldScreenY, false); - if (!selectedUnits.isEmpty()) { - final RenderUnit unit = selectedUnits.get(0); - final boolean selectionChanged = getSelectedUnit() != unit; - boolean playedNewSound = false; - if (selectionChanged) { - this.selectedSoundCount = 0; - } - if (unit.soundset != null) { - UnitSound ackSoundToPlay = unit.soundset.what; - int soundIndex; - final int pissedSoundCount = unit.soundset.pissed.getSoundCount(); - if (unit.getSimulationUnit().isConstructing()) { - ackSoundToPlay = unit.buildSound; - soundIndex = 0; - } else { - if ((this.selectedSoundCount >= 3) && (pissedSoundCount > 0)) { - soundIndex = this.selectedSoundCount - 3; - ackSoundToPlay = unit.soundset.pissed; - } else { - soundIndex = (int) (Math.random() * ackSoundToPlay.getSoundCount()); - } - } - if (ackSoundToPlay != null && ackSoundToPlay.playUnitResponse(this.war3MapViewer.worldScene.audioContext, unit, - soundIndex)) { - this.selectedSoundCount++; - if ((this.selectedSoundCount - 3) >= pissedSoundCount) { - this.selectedSoundCount = 0; - } - playedNewSound = true; - } - } - if (selectionChanged) { - selectUnit(unit); - } - if (playedNewSound) { - portraitTalk(); - } - } else { - selectUnit(null); - } - } - } - } else { - if (clickedUIFrame instanceof CommandCardIcon) { - this.mouseDownUIFrame = (CommandCardIcon) clickedUIFrame; - this.mouseDownUIFrame.mouseDown(this.uiViewport); - } - } - return false; - } - - public boolean touchUp(final int screenX, final int screenY, final float worldScreenY, final int button) { - screenCoordsVector.set(screenX, screenY); - this.uiViewport.unproject(screenCoordsVector); - final UIFrame clickedUIFrame = this.rootFrame.touchUp(screenCoordsVector.x, screenCoordsVector.y, button); - if (this.mouseDownUIFrame != null) { - if (clickedUIFrame == this.mouseDownUIFrame) { - this.mouseDownUIFrame.onClick(button); - this.war3MapViewer.getUiSounds().getSound("InterfaceClick").play(this.uiScene.audioContext, 0, 0); - } - this.mouseDownUIFrame.mouseUp(this.uiViewport); - } - this.mouseDownUIFrame = null; - return false; - } - - private static boolean isShiftDown() { - return Gdx.input.isKeyPressed(Input.Keys.SHIFT_LEFT) || Gdx.input.isKeyPressed(Input.Keys.SHIFT_RIGHT); - } - - public boolean touchDragged(final int screenX, final int screenY, final float worldScreenY, final int pointer) { - screenCoordsVector.set(screenX, screenY); - this.uiViewport.unproject(screenCoordsVector); - - if (this.meleeUIMinimap.containsMouse(screenCoordsVector.x, screenCoordsVector.y)) { - final Vector2 worldPoint = this.meleeUIMinimap.getWorldPointFromScreen(screenCoordsVector.x, - screenCoordsVector.y); - this.cameraManager.target.x = worldPoint.x; - this.cameraManager.target.y = worldPoint.y; - } - return false; - } - - public float getHeightRatioCorrection() { - return this.heightRatioCorrection; - } + public static final float DEFAULT_COMMAND_CARD_ICON_WIDTH = 0.039f; + public static final float DEFAULT_COMMAND_CARD_ICON_PRESSED_WIDTH = 0.037f; + private static final int COMMAND_CARD_WIDTH = 4; + private static final int COMMAND_CARD_HEIGHT = 3; + + private static final Vector2 screenCoordsVector = new Vector2(); + private static final Vector3 clickLocationTemp = new Vector3(); + private static final Vector2 clickLocationTemp2 = new Vector2(); + private final DataSource dataSource; + private final ExtendViewport uiViewport; + private final FreeTypeFontGenerator fontGenerator; + private final Scene uiScene; + private final Scene portraitScene; + private final GameCameraManager cameraManager; + private final War3MapViewer war3MapViewer; + private final RootFrameListener rootFrameListener; + private GameUI rootFrame; + private UIFrame consoleUI; + private UIFrame resourceBar; + private StringFrame resourceBarGoldText; + private StringFrame resourceBarLumberText; + private StringFrame resourceBarSupplyText; + private StringFrame resourceBarUpkeepText; + private SpriteFrame timeIndicator; + private UIFrame unitPortrait; + private StringFrame unitLifeText; + private StringFrame unitManaText; + private Portrait portrait; + private final Rectangle tempRect = new Rectangle(); + private final Vector2 projectionTemp1 = new Vector2(); + private final Vector2 projectionTemp2 = new Vector2(); + private UIFrame simpleInfoPanelUnitDetail; + private StringFrame simpleNameValue; + private StringFrame simpleClassValue; + private StringFrame simpleBuildingActionLabel; + private SimpleStatusBarFrame simpleBuildTimeIndicator; + private UIFrame attack1Icon; + private TextureFrame attack1IconBackdrop; + private StringFrame attack1InfoPanelIconValue; + private StringFrame attack1InfoPanelIconLevel; + private UIFrame attack2Icon; + private TextureFrame attack2IconBackdrop; + private StringFrame attack2InfoPanelIconValue; + private StringFrame attack2InfoPanelIconLevel; + private UIFrame armorIcon; + private TextureFrame armorIconBackdrop; + private StringFrame armorInfoPanelIconValue; + private StringFrame armorInfoPanelIconLevel; + private InfoPanelIconBackdrops damageBackdrops; + private InfoPanelIconBackdrops defenseBackdrops; + + private final CommandCardIcon[][] commandCard = new CommandCardIcon[COMMAND_CARD_HEIGHT][COMMAND_CARD_WIDTH]; + + private RenderUnit selectedUnit; + private final List subMenuOrderIdStack = new ArrayList<>(); + + // TODO remove this & replace with FDF + private final Texture activeButtonTexture; + private UIFrame inventoryCover; + private SpriteFrame cursorFrame; + private MeleeUIMinimap meleeUIMinimap; + private final CPlayerUnitOrderListener unitOrderListener; + private StringFrame errorMessageFrame; + + private CAbilityView activeCommand; + private int activeCommandOrderId; + private RenderUnit activeCommandUnit; + private MdxComplexInstance cursorModelInstance = null; + private BufferedImage cursorModelPathing; + + private int selectedSoundCount = 0; + private final ActiveCommandUnitTargetFilter activeCommandUnitTargetFilter; + + // TODO these corrections are used for old hardcoded UI stuff, we should + // probably remove them later + private final float widthRatioCorrection; + private final float heightRatioCorrection; + private CommandCardIcon mouseDownUIFrame; + + public MeleeUI(final DataSource dataSource, final ExtendViewport uiViewport, + final FreeTypeFontGenerator fontGenerator, final Scene uiScene, final Scene portraitScene, + final CameraPreset[] cameraPresets, final CameraRates cameraRates, final War3MapViewer war3MapViewer, + final RootFrameListener rootFrameListener, final CPlayerUnitOrderListener unitOrderListener) { + this.dataSource = dataSource; + this.uiViewport = uiViewport; + this.fontGenerator = fontGenerator; + this.uiScene = uiScene; + this.portraitScene = portraitScene; + this.war3MapViewer = war3MapViewer; + this.rootFrameListener = rootFrameListener; + this.unitOrderListener = unitOrderListener; + + this.cameraManager = new GameCameraManager(cameraPresets, cameraRates); + + this.cameraManager.setupCamera(war3MapViewer.worldScene); + final float[] startLocation = this.war3MapViewer.simulation.getPlayer(war3MapViewer.getLocalPlayerIndex()) + .getStartLocation(); + this.cameraManager.target.x = startLocation[0]; + this.cameraManager.target.y = startLocation[1]; + + this.activeButtonTexture = ImageUtils.getBLPTexture(war3MapViewer.mapMpq, + "UI\\Widgets\\Console\\Human\\CommandButton\\human-activebutton.blp"); + this.activeCommandUnitTargetFilter = new ActiveCommandUnitTargetFilter(); + this.widthRatioCorrection = this.uiViewport.getMinWorldWidth() / 1600f; + this.heightRatioCorrection = this.uiViewport.getMinWorldHeight() / 1200f; + + } + + private MeleeUIMinimap createMinimap(final War3MapViewer war3MapViewer) { + final Rectangle minimapDisplayArea = new Rectangle(18.75f * this.widthRatioCorrection, + 13.75f * this.heightRatioCorrection, 278.75f * this.widthRatioCorrection, + 276.25f * this.heightRatioCorrection); + Texture minimapTexture = null; + if (war3MapViewer.dataSource.has("war3mapMap.tga")) { + try { + minimapTexture = ImageUtils.getTextureNoColorCorrection(TgaFile.readTGA("war3mapMap.tga", + war3MapViewer.dataSource.getResourceAsStream("war3mapMap.tga"))); + } + catch (final IOException e) { + System.err.println("Could not load minimap TGA file"); + e.printStackTrace(); + } + } + else if (war3MapViewer.dataSource.has("war3mapMap.blp")) { + try { + minimapTexture = ImageUtils + .getTexture(ImageIO.read(war3MapViewer.dataSource.getResourceAsStream("war3mapMap.blp"))); + } + catch (final IOException e) { + System.err.println("Could not load minimap BLP file"); + e.printStackTrace(); + } + } + final Texture[] teamColors = new Texture[WarsmashConstants.MAX_PLAYERS]; + for (int i = 0; i < teamColors.length; i++) { + teamColors[i] = ImageUtils.getBLPTexture(war3MapViewer.dataSource, + "ReplaceableTextures\\" + ReplaceableIds.getPathString(1) + ReplaceableIds.getIdString(i) + ".blp"); + } + final Rectangle playableMapArea = war3MapViewer.terrain.getPlayableMapArea(); + return new MeleeUIMinimap(minimapDisplayArea, playableMapArea, minimapTexture, teamColors); + } + + /** + * Called "main" because this was originally written in JASS so that maps could + * override it, and I may convert it back to the JASS at some point. + */ + public void main() { + // ================================= + // Load skins and templates + // ================================= + this.rootFrame = new GameUI(this.dataSource, GameUI.loadSkin(this.dataSource, 0), this.uiViewport, + this.fontGenerator, this.uiScene, this.war3MapViewer); + this.rootFrameListener.onCreate(this.rootFrame); + try { + this.rootFrame.loadTOCFile("UI\\FrameDef\\FrameDef.toc"); + } + catch (final IOException exc) { + throw new IllegalStateException("Unable to load FrameDef.toc", exc); + } + try { + this.rootFrame.loadTOCFile("UI\\FrameDef\\SmashFrameDef.toc"); + } + catch (final IOException exc) { + throw new IllegalStateException("Unable to load SmashFrameDef.toc", exc); + } + this.damageBackdrops = new InfoPanelIconBackdrops(CAttackType.values(), this.rootFrame, "Damage", "Neutral"); + this.defenseBackdrops = new InfoPanelIconBackdrops(CDefenseType.values(), this.rootFrame, "Armor", "Neutral"); + + // ================================= + // Load major UI components + // ================================= + // Console UI is the background with the racial theme + this.consoleUI = this.rootFrame.createSimpleFrame("ConsoleUI", this.rootFrame, 0); + this.consoleUI.setSetAllPoints(true); + + // Resource bar is a 3 part bar with Gold, Lumber, and Food. + // Its template does not specify where to put it, so we must + // put it in the "TOPRIGHT" corner. + this.resourceBar = this.rootFrame.createSimpleFrame("ResourceBarFrame", this.consoleUI, 0); + this.resourceBar.addSetPoint(new SetPoint(FramePoint.TOPRIGHT, this.consoleUI, FramePoint.TOPRIGHT, 0, 0)); + this.resourceBarGoldText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarGoldText", 0); + this.resourceBarGoldText.setText("500"); + this.resourceBarLumberText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarLumberText", 0); + this.resourceBarLumberText.setText("150"); + this.resourceBarSupplyText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarSupplyText", 0); + this.resourceBarSupplyText.setText("12/100"); + this.resourceBarUpkeepText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarUpkeepText", 0); + this.resourceBarUpkeepText.setText("No Upkeep"); + this.resourceBarUpkeepText.setColor(Color.GREEN); + + // Create the Time Indicator (clock) + this.timeIndicator = (SpriteFrame) this.rootFrame.createFrame("TimeOfDayIndicator", this.rootFrame, 0, 0); + this.timeIndicator.setSequence(0); // play the stand + this.timeIndicator.setAnimationSpeed(0.0f); // do not advance automatically + + // Create the unit portrait stuff + this.portrait = new Portrait(this.war3MapViewer, this.portraitScene); + positionPortrait(); + this.unitPortrait = this.rootFrame.createSimpleFrame("UnitPortrait", this.consoleUI, 0); + this.unitLifeText = (StringFrame) this.rootFrame.getFrameByName("UnitPortraitHitPointText", 0); + this.unitManaText = (StringFrame) this.rootFrame.getFrameByName("UnitPortraitManaPointText", 0); + + this.simpleInfoPanelUnitDetail = this.rootFrame.createSimpleFrame("SimpleInfoPanelUnitDetail", this.consoleUI, + 0); + this.simpleInfoPanelUnitDetail + .addAnchor(new AnchorDefinition(FramePoint.BOTTOM, 0, GameUI.convertY(this.uiViewport, 0.0f))); + this.simpleInfoPanelUnitDetail.setWidth(GameUI.convertY(this.uiViewport, 0.180f)); + this.simpleInfoPanelUnitDetail.setHeight(GameUI.convertY(this.uiViewport, 0.105f)); + this.simpleNameValue = (StringFrame) this.rootFrame.getFrameByName("SimpleNameValue", 0); + this.simpleClassValue = (StringFrame) this.rootFrame.getFrameByName("SimpleClassValue", 0); + this.simpleBuildingActionLabel = (StringFrame) this.rootFrame.getFrameByName("SimpleBuildingActionLabel", 0); + this.simpleBuildTimeIndicator = (SimpleStatusBarFrame) this.rootFrame.getFrameByName("SimpleBuildTimeIndicator", + 0); + final TextureFrame simpleBuildTimeIndicatorBar = this.simpleBuildTimeIndicator.getBarFrame(); + simpleBuildTimeIndicatorBar.setTexture("SimpleBuildTimeIndicator", this.rootFrame); + final TextureFrame simpleBuildTimeIndicatorBorder = this.simpleBuildTimeIndicator.getBorderFrame(); + simpleBuildTimeIndicatorBorder.setTexture("SimpleBuildTimeIndicatorBorder", this.rootFrame); + this.simpleBuildTimeIndicator.setWidth(GameUI.convertX(this.uiViewport, 0.10538f)); + this.simpleBuildTimeIndicator.setHeight(GameUI.convertY(this.uiViewport, 0.0103f)); + + this.attack1Icon = this.rootFrame.createSimpleFrame("SimpleInfoPanelIconDamage", this.simpleInfoPanelUnitDetail, + 0); + this.attack1Icon.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, + FramePoint.TOPLEFT, 0, GameUI.convertY(this.uiViewport, -0.030125f))); + this.attack1IconBackdrop = (TextureFrame) this.rootFrame.getFrameByName("InfoPanelIconBackdrop", 0); + this.attack1InfoPanelIconValue = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconValue", 0); + this.attack1InfoPanelIconLevel = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconLevel", 0); + + this.attack2Icon = this.rootFrame.createSimpleFrame("SimpleInfoPanelIconDamage", this.simpleInfoPanelUnitDetail, + 1); + this.attack2Icon + .addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, FramePoint.TOPLEFT, + GameUI.convertX(this.uiViewport, 0.1f), GameUI.convertY(this.uiViewport, -0.030125f))); + this.attack2IconBackdrop = (TextureFrame) this.rootFrame.getFrameByName("InfoPanelIconBackdrop", 1); + this.attack2InfoPanelIconValue = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconValue", 1); + this.attack2InfoPanelIconLevel = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconLevel", 1); + + this.armorIcon = this.rootFrame.createSimpleFrame("SimpleInfoPanelIconArmor", this.simpleInfoPanelUnitDetail, + 1); + this.armorIcon.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, FramePoint.TOPLEFT, + GameUI.convertX(this.uiViewport, 0f), GameUI.convertY(this.uiViewport, -0.06025f))); + this.armorIconBackdrop = (TextureFrame) this.rootFrame.getFrameByName("InfoPanelIconBackdrop", 0); + this.armorInfoPanelIconValue = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconValue", 0); + this.armorInfoPanelIconLevel = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconLevel", 0); + + this.inventoryCover = this.rootFrame.createSimpleFrame("SmashConsoleInventoryCover", this.rootFrame, 0); + + this.errorMessageFrame = this.rootFrame.createStringFrame("SmashErrorMessageFrame", this.consoleUI, + new Color(0xFFFFCC00), TextJustify.LEFT, TextJustify.MIDDLE, 0.014f); + this.errorMessageFrame.addAnchor(new AnchorDefinition(FramePoint.BOTTOMLEFT, + GameUI.convertX(this.uiViewport, 0.275f), GameUI.convertY(this.uiViewport, 0.275f))); + this.errorMessageFrame.setWidth(GameUI.convertX(this.uiViewport, 0.25f)); + + int commandButtonIndex = 0; + for (int j = 0; j < COMMAND_CARD_HEIGHT; j++) { + for (int i = 0; i < COMMAND_CARD_WIDTH; i++) { + final CommandCardIcon commandCardIcon = new CommandCardIcon("SmashCommandButton_" + commandButtonIndex, + this.rootFrame, this); + this.rootFrame.add(commandCardIcon); + final TextureFrame iconFrame = this.rootFrame.createTextureFrame( + "SmashCommandButton_" + (commandButtonIndex) + "_Icon", this.rootFrame, false, null); + final TextureFrame activeHighlightFrame = this.rootFrame.createTextureFrame( + "SmashCommandButton_" + (commandButtonIndex) + "_ActiveHighlight", this.rootFrame, true, null, + FilterMode.ADDALPHA); + final SpriteFrame cooldownFrame = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", + "SmashCommandButton_" + (commandButtonIndex) + "_Cooldown", this.rootFrame, "", 0); + final SpriteFrame autocastFrame = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", + "SmashCommandButton_" + (commandButtonIndex) + "_Autocast", this.rootFrame, "", 0); + commandCardIcon.addAnchor(new AnchorDefinition(FramePoint.BOTTOMLEFT, + GameUI.convertX(this.uiViewport, 0.6175f + (0.0434f * i)), + GameUI.convertY(this.uiViewport, 0.095f - (0.044f * j)))); + commandCardIcon.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); + commandCardIcon.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); + iconFrame.addSetPoint(new SetPoint(FramePoint.CENTER, commandCardIcon, FramePoint.CENTER, 0, 0)); + iconFrame.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); + iconFrame.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); + iconFrame.setTexture(ImageUtils.DEFAULT_ICON_PATH, this.rootFrame); + activeHighlightFrame + .addSetPoint(new SetPoint(FramePoint.CENTER, commandCardIcon, FramePoint.CENTER, 0, 0)); + activeHighlightFrame.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); + activeHighlightFrame.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); + activeHighlightFrame.setTexture("CommandButtonActiveHighlight", this.rootFrame); + cooldownFrame.addSetPoint(new SetPoint(FramePoint.CENTER, commandCardIcon, FramePoint.CENTER, 0, 0)); + this.rootFrame.setSpriteFrameModel(cooldownFrame, this.rootFrame.getSkinField("CommandButtonCooldown")); + cooldownFrame.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); + cooldownFrame.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); + autocastFrame.addSetPoint(new SetPoint(FramePoint.CENTER, commandCardIcon, FramePoint.CENTER, 0, 0)); + this.rootFrame.setSpriteFrameModel(autocastFrame, this.rootFrame.getSkinField("CommandButtonAutocast")); + autocastFrame.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); + autocastFrame.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); + commandCardIcon.set(iconFrame, activeHighlightFrame, cooldownFrame, autocastFrame); + this.commandCard[j][i] = commandCardIcon; + commandCardIcon.setCommandButton(null); + commandButtonIndex++; + } + } + + this.cursorFrame = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", "SmashCursorFrame", this.rootFrame, + "", 0); + this.rootFrame.setSpriteFrameModel(this.cursorFrame, this.rootFrame.getSkinField("Cursor")); + this.cursorFrame.setSequence("Normal"); + this.cursorFrame.setZDepth(1.0f); + Gdx.input.setCursorCatched(true); + + this.meleeUIMinimap = createMinimap(this.war3MapViewer); + + this.rootFrame.positionBounds(this.uiViewport); + selectUnit(null); + } + + @Override + public void startUsingAbility(final int abilityHandleId, final int orderId, final boolean rightClick) { + // TODO not O(N) + if (this.selectedUnit == null) { + return; + } + CAbilityView abilityToUse = null; + for (final CAbility ability : this.selectedUnit.getSimulationUnit().getAbilities()) { + if (ability.getHandleId() == abilityHandleId) { + abilityToUse = ability; + break; + } + } + if (abilityToUse != null) { + final StringMsgAbilityActivationReceiver stringMsgActivationReceiver = StringMsgAbilityActivationReceiver + .getInstance().reset(); + abilityToUse.checkCanUse(this.war3MapViewer.simulation, this.selectedUnit.getSimulationUnit(), orderId, + stringMsgActivationReceiver); + if (!stringMsgActivationReceiver.isUseOk()) { + showCommandError(stringMsgActivationReceiver.getMessage()); + } + else { + final BooleanAbilityTargetCheckReceiver noTargetReceiver = BooleanAbilityTargetCheckReceiver + .getInstance().reset(); + abilityToUse.checkCanTargetNoTarget(this.war3MapViewer.simulation, + this.selectedUnit.getSimulationUnit(), orderId, noTargetReceiver); + if (noTargetReceiver.isTargetable()) { + this.unitOrderListener.issueImmediateOrder(this.selectedUnit.getSimulationUnit().getHandleId(), + abilityHandleId, orderId, isShiftDown()); + } + else { + this.activeCommand = abilityToUse; + this.activeCommandOrderId = orderId; + this.activeCommandUnit = this.selectedUnit; + clearAndRepopulateCommandCard(); + } + } + } + else { + this.unitOrderListener.issueImmediateOrder(this.selectedUnit.getSimulationUnit().getHandleId(), + abilityHandleId, orderId, isShiftDown()); + } + if (rightClick) { + this.war3MapViewer.getUiSounds().getSound("AutoCastButtonClick").play(this.uiScene.audioContext, 0, 0); + } + } + + @Override + public void openMenu(final int orderId) { + if (orderId == 0) { + this.subMenuOrderIdStack.clear(); + this.activeCommandUnit = null; + this.activeCommand = null; + this.activeCommandOrderId = -1; + } + else { + this.subMenuOrderIdStack.add(orderId); + } + clearAndRepopulateCommandCard(); + } + + public void showCommandError(final String message) { + this.errorMessageFrame.setText(message); + } + + public void update(final float deltaTime) { + this.portrait.update(); + + final int baseMouseX = Gdx.input.getX(); + int mouseX = baseMouseX; + final int baseMouseY = Gdx.input.getY(); + int mouseY = baseMouseY; + final int minX = this.uiViewport.getScreenX(); + final int maxX = minX + this.uiViewport.getScreenWidth(); + final int minY = this.uiViewport.getScreenY(); + final int maxY = minY + this.uiViewport.getScreenHeight(); + final boolean left = mouseX <= (minX + 3); + final boolean right = mouseX >= (maxX - 3); + final boolean up = mouseY <= (minY + 3); + final boolean down = mouseY >= (maxY - 3); + this.cameraManager.applyVelocity(deltaTime, up, down, left, right); + + mouseX = Math.max(minX, Math.min(maxX, mouseX)); + mouseY = Math.max(minY, Math.min(maxY, mouseY)); + if (Gdx.input.isCursorCatched()) { + Gdx.input.setCursorPosition(mouseX, mouseY); + } + + screenCoordsVector.set(mouseX, mouseY); + this.uiViewport.unproject(screenCoordsVector); + this.cursorFrame.setFramePointX(FramePoint.LEFT, screenCoordsVector.x); + this.cursorFrame.setFramePointY(FramePoint.BOTTOM, screenCoordsVector.y); + + if (this.activeCommand != null) { + if (this.activeCommand instanceof AbstractCAbilityBuild) { + boolean justLoaded = false; + if (this.cursorModelInstance == null) { + final MutableObjectData unitData = this.war3MapViewer.getAllObjectData().getUnits(); + final War3ID buildingTypeId = new War3ID(this.activeCommandOrderId); + final String unitModelPath = this.war3MapViewer.getUnitModelPath(unitData.get(buildingTypeId)); + final MdxModel model = (MdxModel) this.war3MapViewer.load(unitModelPath, + this.war3MapViewer.mapPathSolver, this.war3MapViewer.solverParams); + this.cursorModelInstance = (MdxComplexInstance) model.addInstance(); + this.cursorModelInstance.setVertexColor(new float[] { 1, 1, 1, 0.5f }); + this.cursorModelInstance.rotate(RenderUnit.tempQuat.setFromAxis(RenderMathUtils.VEC3_UNIT_Z, + this.war3MapViewer.simulation.getGameplayConstants().getBuildingAngle())); + this.cursorModelInstance.setAnimationSpeed(0f); + justLoaded = true; + final CUnitType buildingUnitType = this.war3MapViewer.simulation.getUnitData() + .getUnitType(buildingTypeId); + this.cursorModelPathing = buildingUnitType.getBuildingPathingPixelMap(); + } + this.war3MapViewer.getClickLocation(clickLocationTemp, baseMouseX, + Gdx.graphics.getHeight() - baseMouseY); + if (this.cursorModelPathing != null) { + clickLocationTemp.x = Math.round(clickLocationTemp.x / 64f) * 64f; + clickLocationTemp.y = Math.round(clickLocationTemp.y / 64f) * 64f; + clickLocationTemp.z = this.war3MapViewer.terrain.getGroundHeight(clickLocationTemp.x, + clickLocationTemp.y); + } + this.cursorModelInstance.setLocation(clickLocationTemp); + SequenceUtils.randomSequence(this.cursorModelInstance, PrimaryTag.STAND); + this.cursorFrame.setVisible(false); + if (justLoaded) { + this.cursorModelInstance.setScene(this.war3MapViewer.worldScene); + } + } + else { + if (this.cursorModelInstance != null) { + this.cursorModelInstance.detach(); + this.cursorModelInstance = null; + this.cursorFrame.setVisible(true); + } + this.cursorFrame.setSequence("Target"); + } + } + else { + if (this.cursorModelInstance != null) { + this.cursorModelInstance.detach(); + this.cursorModelInstance = null; + this.cursorFrame.setVisible(true); + } + if (down) { + if (left) { + this.cursorFrame.setSequence("Scroll Down Left"); + } + else if (right) { + this.cursorFrame.setSequence("Scroll Down Right"); + } + else { + this.cursorFrame.setSequence("Scroll Down"); + } + } + else if (up) { + if (left) { + this.cursorFrame.setSequence("Scroll Up Left"); + } + else if (right) { + this.cursorFrame.setSequence("Scroll Up Right"); + } + else { + this.cursorFrame.setSequence("Scroll Up"); + } + } + else if (left) { + this.cursorFrame.setSequence("Scroll Left"); + } + else if (right) { + this.cursorFrame.setSequence("Scroll Right"); + } + else { + this.cursorFrame.setSequence("Normal"); + } + } + if (this.simpleBuildTimeIndicator.isVisible() && (this.selectedUnit != null)) { + this.simpleBuildTimeIndicator + .setValue(Math.min(this.selectedUnit.getSimulationUnit().getConstructionProgress() + / this.selectedUnit.getSimulationUnit().getUnitType().getBuildTime(), 0.99f)); + } + final float groundHeight = Math.max( + this.war3MapViewer.terrain.getGroundHeight(this.cameraManager.target.x, this.cameraManager.target.y), + this.war3MapViewer.terrain.getWaterHeight(this.cameraManager.target.x, this.cameraManager.target.y)); + this.cameraManager.updateTargetZ(groundHeight); + this.cameraManager.updateCamera(); + } + + public void render(final SpriteBatch batch, final BitmapFont font20, final GlyphLayout glyphLayout) { + this.rootFrame.render(batch, font20, glyphLayout); + if (this.selectedUnit != null) { + font20.setColor(Color.WHITE); + + } + + this.meleeUIMinimap.render(batch, this.war3MapViewer.units); + this.timeIndicator.setFrameByRatio(this.war3MapViewer.simulation.getGameTimeOfDay() + / this.war3MapViewer.simulation.getGameplayConstants().getGameDayHours()); + } + + public void portraitTalk() { + this.portrait.talk(); + } + + private final class ActiveCommandUnitTargetFilter implements CUnitFilterFunction { + @Override + public boolean call(final CUnit unit) { + final BooleanAbilityTargetCheckReceiver targetReceiver = BooleanAbilityTargetCheckReceiver + .getInstance(); + MeleeUI.this.activeCommand.checkCanTarget(MeleeUI.this.war3MapViewer.simulation, + MeleeUI.this.activeCommandUnit.getSimulationUnit(), MeleeUI.this.activeCommandOrderId, unit, + targetReceiver); + return targetReceiver.isTargetable(); + } + } + + private static final class Portrait { + private MdxComplexInstance modelInstance; + private final PortraitCameraManager portraitCameraManager; + private final Scene portraitScene; + + public Portrait(final War3MapViewer war3MapViewer, final Scene portraitScene) { + this.portraitScene = portraitScene; + this.portraitCameraManager = new PortraitCameraManager(); + this.portraitCameraManager.setupCamera(this.portraitScene); + this.portraitScene.camera.viewport(new Rectangle(100, 0, 6400, 48)); + } + + public void update() { + this.portraitCameraManager.updateCamera(); + if ((this.modelInstance != null) + && (this.modelInstance.sequenceEnded || (this.modelInstance.sequence == -1))) { + SequenceUtils.randomSequence(this.modelInstance, PrimaryTag.PORTRAIT, SequenceUtils.EMPTY, true); + } + } + + public void talk() { + SequenceUtils.randomSequence(this.modelInstance, PrimaryTag.PORTRAIT, SequenceUtils.TALK, true); + } + + public void setSelectedUnit(final RenderUnit unit) { + if (unit == null) { + if (this.modelInstance != null) { + this.portraitScene.removeInstance(this.modelInstance); + } + this.modelInstance = null; + this.portraitCameraManager.setModelInstance(null, null); + } + else { + final MdxModel portraitModel = unit.portraitModel; + if (portraitModel != null) { + if (this.modelInstance != null) { + this.portraitScene.removeInstance(this.modelInstance); + } + this.modelInstance = (MdxComplexInstance) portraitModel.addInstance(); + this.portraitCameraManager.setModelInstance(this.modelInstance, portraitModel); + this.modelInstance.setSequenceLoopMode(SequenceLoopMode.NEVER_LOOP); + this.modelInstance.setScene(this.portraitScene); + this.modelInstance.setVertexColor(unit.instance.vertexColor); + this.modelInstance.setTeamColor(unit.playerIndex); + } + } + } + } + + public void selectUnit(RenderUnit unit) { + this.subMenuOrderIdStack.clear(); + if ((unit != null) && unit.getSimulationUnit().isDead()) { + unit = null; + } + if (this.selectedUnit != null) { + this.selectedUnit.getSimulationUnit().removeStateListener(this); + } + this.portrait.setSelectedUnit(unit); + this.selectedUnit = unit; + if (unit == null) { + clearCommandCard(); + this.simpleNameValue.setText(""); + this.unitLifeText.setText(""); + this.unitManaText.setText(""); + this.simpleClassValue.setText(""); + this.simpleBuildingActionLabel.setText(""); + this.attack1Icon.setVisible(false); + this.attack2Icon.setVisible(false); + this.attack1InfoPanelIconLevel.setText(""); + this.attack2InfoPanelIconLevel.setText(""); + this.simpleBuildingActionLabel.setText(""); + this.armorIcon.setVisible(false); + this.armorInfoPanelIconLevel.setText(""); + this.simpleBuildTimeIndicator.setVisible(false); + } + else { + unit.getSimulationUnit().addStateListener(this); + reloadSelectedUnitUI(unit); + } + } + + private void reloadSelectedUnitUI(final RenderUnit unit) { + this.simpleNameValue.setText(unit.getSimulationUnit().getUnitType().getName()); + String classText = null; + for (final CUnitClassification classification : unit.getSimulationUnit().getClassifications()) { + if ((classification == CUnitClassification.MECHANICAL) + && unit.getSimulationUnit().getUnitType().isBuilding()) { + // buildings dont display MECHANICAL + continue; + } + if (classification.getDisplayName() != null) { + classText = classification.getDisplayName(); + } + } + if (classText != null) { + this.simpleClassValue.setText(classText); + } + else { + this.simpleClassValue.setText(""); + } + this.unitLifeText.setText(FastNumberFormat.formatWholeNumber(unit.getSimulationUnit().getLife()) + " / " + + unit.getSimulationUnit().getMaximumLife()); + final int maximumMana = unit.getSimulationUnit().getMaximumMana(); + if (maximumMana > 0) { + this.unitManaText.setText( + FastNumberFormat.formatWholeNumber(unit.getSimulationUnit().getMana()) + " / " + maximumMana); + } + else { + this.unitManaText.setText(""); + } + this.simpleBuildingActionLabel.setText(""); + + final boolean anyAttacks = unit.getSimulationUnit().getUnitType().getAttacks().size() > 0; + final boolean constructing = unit.getSimulationUnit().isConstructing(); + final UIFrame localArmorIcon = this.armorIcon; + final TextureFrame localArmorIconBackdrop = this.armorIconBackdrop; + final StringFrame localArmorInfoPanelIconValue = this.armorInfoPanelIconValue; + if (anyAttacks && !constructing) { + final CUnitAttack attackOne = unit.getSimulationUnit().getUnitType().getAttacks().get(0); + this.attack1Icon.setVisible(attackOne.isShowUI()); + this.attack1IconBackdrop.setTexture(this.damageBackdrops.getTexture(attackOne.getAttackType())); + this.attack1InfoPanelIconValue.setText(attackOne.getMinDamage() + " - " + attackOne.getMaxDamage()); + if (unit.getSimulationUnit().getUnitType().getAttacks().size() > 1) { + final CUnitAttack attackTwo = unit.getSimulationUnit().getUnitType().getAttacks().get(1); + this.attack2Icon.setVisible(attackTwo.isShowUI()); + this.attack2IconBackdrop.setTexture(this.damageBackdrops.getTexture(attackTwo.getAttackType())); + this.attack2InfoPanelIconValue.setText(attackTwo.getMinDamage() + " - " + attackTwo.getMaxDamage()); + } + else { + this.attack2Icon.setVisible(false); + } + + this.armorIcon + .addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, FramePoint.TOPLEFT, + GameUI.convertX(this.uiViewport, 0f), GameUI.convertY(this.uiViewport, -0.06025f))); + this.armorIcon.positionBounds(this.uiViewport); + } + else { + this.attack1Icon.setVisible(false); + this.attack2Icon.setVisible(false); + + this.armorIcon.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, + FramePoint.TOPLEFT, 0, GameUI.convertY(this.uiViewport, -0.030125f))); + this.armorIcon.positionBounds(this.uiViewport); + } + + localArmorIcon.setVisible(!constructing); + this.simpleBuildTimeIndicator.setVisible(constructing); + if (constructing) { + this.simpleBuildingActionLabel.setText(this.rootFrame.getTemplates().getDecoratedString("CONSTRUCTING")); + } + final Texture defenseTexture = this.defenseBackdrops + .getTexture(unit.getSimulationUnit().getUnitType().getDefenseType()); + if (defenseTexture == null) { + throw new RuntimeException( + unit.getSimulationUnit().getUnitType().getDefenseType() + " can't find texture!"); + } + localArmorIconBackdrop.setTexture(defenseTexture); + localArmorInfoPanelIconValue.setText(Integer.toString(unit.getSimulationUnit().getDefense())); + clearAndRepopulateCommandCard(); + } + + private void clearCommandCard() { + for (int j = 0; j < COMMAND_CARD_HEIGHT; j++) { + for (int i = 0; i < COMMAND_CARD_WIDTH; i++) { + this.commandCard[j][i].setCommandButton(null); + } + } + } + + @Override + public void commandButton(final int buttonPositionX, final int buttonPositionY, final Texture icon, + final int abilityHandleId, final int orderId, final int autoCastId, final boolean active, + final boolean autoCastActive, final boolean menuButton) { + final int x = Math.max(0, Math.min(COMMAND_CARD_WIDTH - 1, buttonPositionX)); + final int y = Math.max(0, Math.min(COMMAND_CARD_HEIGHT - 1, buttonPositionY)); + this.commandCard[y][x].setCommandButtonData(icon, abilityHandleId, orderId, autoCastId, active, autoCastActive, + menuButton); + } + + public void resize(final Rectangle viewport) { + this.cameraManager.resize(viewport); + positionPortrait(); + } + + public void positionPortrait() { + this.projectionTemp1.x = 422 * this.widthRatioCorrection; + this.projectionTemp1.y = 57 * this.heightRatioCorrection; + this.projectionTemp2.x = (422 + 167) * this.widthRatioCorrection; + this.projectionTemp2.y = (57 + 170) * this.heightRatioCorrection; + this.uiViewport.project(this.projectionTemp1); + this.uiViewport.project(this.projectionTemp2); + + this.tempRect.x = this.projectionTemp1.x + this.uiViewport.getScreenX(); + this.tempRect.y = this.projectionTemp1.y + this.uiViewport.getScreenY(); + this.tempRect.width = this.projectionTemp2.x - this.projectionTemp1.x; + this.tempRect.height = this.projectionTemp2.y - this.projectionTemp1.y; + this.portrait.portraitScene.camera.viewport(this.tempRect); + } + + private static final class InfoPanelIconBackdrops { + private final Texture[] damageBackdropTextures; + + public InfoPanelIconBackdrops(final CodeKeyType[] attackTypes, final GameUI gameUI, final String prefix, + final String suffix) { + this.damageBackdropTextures = new Texture[attackTypes.length]; + for (int index = 0; index < attackTypes.length; index++) { + final CodeKeyType attackType = attackTypes[index]; + String skinLookupKey = "InfoPanelIcon" + prefix + attackType.getCodeKey() + suffix; + final Texture suffixTexture = gameUI.loadTexture(gameUI.getSkinField(skinLookupKey)); + if (suffixTexture != null) { + this.damageBackdropTextures[index] = suffixTexture; + } + else { + skinLookupKey = "InfoPanelIcon" + prefix + attackType.getCodeKey(); + this.damageBackdropTextures[index] = gameUI.loadTexture(gameUI.getSkinField(skinLookupKey)); + } + } + } + + public Texture getTexture(final CodeKeyType attackType) { + if (attackType != null) { + final int ordinal = attackType.ordinal(); + if ((ordinal >= 0) && (ordinal < this.damageBackdropTextures.length)) { + return this.damageBackdropTextures[ordinal]; + } + } + return this.damageBackdropTextures[0]; + } + + private static String getSuffix(final CAttackType attackType) { + switch (attackType) { + case CHAOS: + return "Chaos"; + case HERO: + return "Hero"; + case MAGIC: + return "Magic"; + case NORMAL: + return "Normal"; + case PIERCE: + return "Pierce"; + case SIEGE: + return "Siege"; + case SPELLS: + return "Magic"; + case UNKNOWN: + return "Unknown"; + default: + throw new IllegalArgumentException("Unknown attack type: " + attackType); + } + + } + } + + @Override + public void lifeChanged() { + if (this.selectedUnit.getSimulationUnit().isDead()) { + selectUnit(null); + } + else { + this.unitLifeText + .setText(FastNumberFormat.formatWholeNumber(this.selectedUnit.getSimulationUnit().getLife()) + " / " + + this.selectedUnit.getSimulationUnit().getMaximumLife()); + } + } + + @Override + public void ordersChanged(final int abilityHandleId, final int orderId) { + reloadSelectedUnitUI(this.selectedUnit); + } + + private void clearAndRepopulateCommandCard() { + clearCommandCard(); + final AbilityDataUI abilityDataUI = this.war3MapViewer.getAbilityDataUI(); + final int menuOrderId = getSubMenuOrderId(); + if (this.activeCommand != null) { + final IconUI cancelUI = abilityDataUI.getCancelUI(); + this.commandButton(cancelUI.getButtonPositionX(), cancelUI.getButtonPositionY(), cancelUI.getIcon(), 0, + menuOrderId, 0, false, false, true); + } + else if (this.selectedUnit.getSimulationUnit().isConstructing()) { + + } + else { + if (menuOrderId != 0) { + final int exitOrderId = this.subMenuOrderIdStack.size() > 1 + ? this.subMenuOrderIdStack.get(this.subMenuOrderIdStack.size() - 2) + : 0; + final IconUI cancelUI = abilityDataUI.getCancelUI(); + this.commandButton(cancelUI.getButtonPositionX(), cancelUI.getButtonPositionY(), cancelUI.getIcon(), 0, + exitOrderId, 0, false, false, true); + } + this.selectedUnit.populateCommandCard(this.war3MapViewer.simulation, this, abilityDataUI, menuOrderId); + } + } + + private int getSubMenuOrderId() { + return this.subMenuOrderIdStack.isEmpty() ? 0 + : this.subMenuOrderIdStack.get(this.subMenuOrderIdStack.size() - 1); + } + + public RenderUnit getSelectedUnit() { + return this.selectedUnit; + } + + public boolean keyDown(final int keycode) { + return this.cameraManager.keyDown(keycode); + } + + public boolean keyUp(final int keycode) { + return this.cameraManager.keyUp(keycode); + } + + public void scrolled(final int amount) { + this.cameraManager.scrolled(amount); + } + + public boolean touchDown(final int screenX, final int screenY, final float worldScreenY, final int button) { + screenCoordsVector.set(screenX, screenY); + this.uiViewport.unproject(screenCoordsVector); + if (this.meleeUIMinimap.containsMouse(screenCoordsVector.x, screenCoordsVector.y)) { + final Vector2 worldPoint = this.meleeUIMinimap.getWorldPointFromScreen(screenCoordsVector.x, + screenCoordsVector.y); + this.cameraManager.target.x = worldPoint.x; + this.cameraManager.target.y = worldPoint.y; + return true; + } + final UIFrame clickedUIFrame = this.rootFrame.touchDown(screenCoordsVector.x, screenCoordsVector.y, button); + if (clickedUIFrame == null) { + // try to interact with world + if (this.activeCommand != null) { + if (button == Input.Buttons.RIGHT) { + this.activeCommandUnit = null; + this.activeCommand = null; + this.activeCommandOrderId = -1; + clearAndRepopulateCommandCard(); + } + else { + final RenderUnit rayPickUnit = this.war3MapViewer.rayPickUnit(screenX, worldScreenY, + this.activeCommandUnitTargetFilter); + final boolean shiftDown = isShiftDown(); + if (rayPickUnit != null) { + this.unitOrderListener.issueTargetOrder( + this.activeCommandUnit.getSimulationUnit().getHandleId(), + this.activeCommand.getHandleId(), this.activeCommandOrderId, + rayPickUnit.getSimulationUnit().getHandleId(), shiftDown); + if (getSelectedUnit().soundset.yesAttack + .playUnitResponse(this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { + portraitTalk(); + } + this.selectedSoundCount = 0; + if (!shiftDown) { + this.subMenuOrderIdStack.clear(); + this.activeCommandUnit = null; + this.activeCommand = null; + this.activeCommandOrderId = -1; + clearAndRepopulateCommandCard(); + } + } + else { + this.war3MapViewer.getClickLocation(clickLocationTemp, screenX, (int) worldScreenY); + clickLocationTemp2.set(clickLocationTemp.x, clickLocationTemp.y); + + this.activeCommand.checkCanTarget(this.war3MapViewer.simulation, + this.activeCommandUnit.getSimulationUnit(), this.activeCommandOrderId, + clickLocationTemp2, PointAbilityTargetCheckReceiver.INSTANCE); + final Vector2 target = PointAbilityTargetCheckReceiver.INSTANCE.getTarget(); + if (target != null) { + if (this.activeCommand instanceof CAbilityAttack) { + this.war3MapViewer.showConfirmation(clickLocationTemp, 1, 0, 0); + } + else { + this.war3MapViewer.showConfirmation(clickLocationTemp, 0, 1, 0); + } + this.unitOrderListener.issuePointOrder( + this.activeCommandUnit.getSimulationUnit().getHandleId(), + this.activeCommand.getHandleId(), this.activeCommandOrderId, clickLocationTemp2.x, + clickLocationTemp2.y, shiftDown); + if (getSelectedUnit().soundset.yes + .playUnitResponse(this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { + portraitTalk(); + } + this.selectedSoundCount = 0; + if (this.activeCommand instanceof AbstractCAbilityBuild) { + this.war3MapViewer.getUiSounds().getSound("PlaceBuildingDefault") + .play(this.uiScene.audioContext, 0, 0); + } + if (!shiftDown) { + this.subMenuOrderIdStack.clear(); + this.activeCommandUnit = null; + this.activeCommand = null; + this.activeCommandOrderId = -1; + clearAndRepopulateCommandCard(); + } + + } + } + } + } + else { + if (button == Input.Buttons.RIGHT) { + if (getSelectedUnit() != null) { + final RenderUnit rayPickUnit = this.war3MapViewer.rayPickUnit(screenX, worldScreenY); + if ((rayPickUnit != null) && !rayPickUnit.getSimulationUnit().isDead()) { + boolean ordered = false; + for (final RenderUnit unit : this.war3MapViewer.selected) { + for (final CAbility ability : unit.getSimulationUnit().getAbilities()) { + ability.checkCanTarget(this.war3MapViewer.simulation, unit.getSimulationUnit(), + OrderIds.smart, rayPickUnit.getSimulationUnit(), + CWidgetAbilityTargetCheckReceiver.INSTANCE); + final CWidget targetWidget = CWidgetAbilityTargetCheckReceiver.INSTANCE.getTarget(); + if (targetWidget != null) { + this.unitOrderListener.issueTargetOrder(unit.getSimulationUnit().getHandleId(), + ability.getHandleId(), OrderIds.smart, targetWidget.getHandleId(), + isShiftDown()); + ordered = true; + } + } + + } + if (ordered) { + if (getSelectedUnit().soundset.yesAttack.playUnitResponse( + this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { + portraitTalk(); + } + this.selectedSoundCount = 0; + } + } + else { + this.war3MapViewer.getClickLocation(clickLocationTemp, screenX, (int) worldScreenY); + this.war3MapViewer.showConfirmation(clickLocationTemp, 0, 1, 0); + clickLocationTemp2.set(clickLocationTemp.x, clickLocationTemp.y); + + boolean ordered = false; + for (final RenderUnit unit : this.war3MapViewer.selected) { + for (final CAbility ability : unit.getSimulationUnit().getAbilities()) { + ability.checkCanUse(this.war3MapViewer.simulation, unit.getSimulationUnit(), + OrderIds.smart, BooleanAbilityActivationReceiver.INSTANCE); + if (BooleanAbilityActivationReceiver.INSTANCE.isOk()) { + ability.checkCanTarget(this.war3MapViewer.simulation, unit.getSimulationUnit(), + OrderIds.smart, clickLocationTemp2, + PointAbilityTargetCheckReceiver.INSTANCE); + final Vector2 target = PointAbilityTargetCheckReceiver.INSTANCE.getTarget(); + if (target != null) { + this.unitOrderListener.issuePointOrder( + unit.getSimulationUnit().getHandleId(), ability.getHandleId(), + OrderIds.smart, clickLocationTemp2.x, clickLocationTemp2.y, + isShiftDown()); + ordered = true; + } + } + } + + } + + if (ordered) { + if (getSelectedUnit().soundset.yes.playUnitResponse( + this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { + portraitTalk(); + } + this.selectedSoundCount = 0; + } + } + } + } + else { + final List selectedUnits = this.war3MapViewer.selectUnit(screenX, worldScreenY, false); + if (!selectedUnits.isEmpty()) { + final RenderUnit unit = selectedUnits.get(0); + final boolean selectionChanged = getSelectedUnit() != unit; + boolean playedNewSound = false; + if (selectionChanged) { + this.selectedSoundCount = 0; + } + if (unit.soundset != null) { + UnitSound ackSoundToPlay = unit.soundset.what; + int soundIndex; + final int pissedSoundCount = unit.soundset.pissed.getSoundCount(); + if (unit.getSimulationUnit().isConstructing()) { + ackSoundToPlay = this.war3MapViewer.getUiSounds() + .getSound(this.rootFrame.getSkinField("ConstructingBuilding")); + soundIndex = (int) (Math.random() * ackSoundToPlay.getSoundCount()); + } + else { + if ((this.selectedSoundCount >= 3) && (pissedSoundCount > 0)) { + soundIndex = this.selectedSoundCount - 3; + ackSoundToPlay = unit.soundset.pissed; + } + else { + soundIndex = (int) (Math.random() * ackSoundToPlay.getSoundCount()); + } + } + if ((ackSoundToPlay != null) && ackSoundToPlay + .playUnitResponse(this.war3MapViewer.worldScene.audioContext, unit, soundIndex)) { + this.selectedSoundCount++; + if ((this.selectedSoundCount - 3) >= pissedSoundCount) { + this.selectedSoundCount = 0; + } + playedNewSound = true; + } + } + if (selectionChanged) { + selectUnit(unit); + } + if (playedNewSound) { + portraitTalk(); + } + } + else { + selectUnit(null); + } + } + } + } + else { + if (clickedUIFrame instanceof CommandCardIcon) { + this.mouseDownUIFrame = (CommandCardIcon) clickedUIFrame; + this.mouseDownUIFrame.mouseDown(this.uiViewport); + } + } + return false; + } + + public boolean touchUp(final int screenX, final int screenY, final float worldScreenY, final int button) { + screenCoordsVector.set(screenX, screenY); + this.uiViewport.unproject(screenCoordsVector); + final UIFrame clickedUIFrame = this.rootFrame.touchUp(screenCoordsVector.x, screenCoordsVector.y, button); + if (this.mouseDownUIFrame != null) { + if (clickedUIFrame == this.mouseDownUIFrame) { + this.mouseDownUIFrame.onClick(button); + this.war3MapViewer.getUiSounds().getSound("InterfaceClick").play(this.uiScene.audioContext, 0, 0); + } + this.mouseDownUIFrame.mouseUp(this.uiViewport); + } + this.mouseDownUIFrame = null; + return false; + } + + private static boolean isShiftDown() { + return Gdx.input.isKeyPressed(Input.Keys.SHIFT_LEFT) || Gdx.input.isKeyPressed(Input.Keys.SHIFT_RIGHT); + } + + public boolean touchDragged(final int screenX, final int screenY, final float worldScreenY, final int pointer) { + screenCoordsVector.set(screenX, screenY); + this.uiViewport.unproject(screenCoordsVector); + + if (this.meleeUIMinimap.containsMouse(screenCoordsVector.x, screenCoordsVector.y)) { + final Vector2 worldPoint = this.meleeUIMinimap.getWorldPointFromScreen(screenCoordsVector.x, + screenCoordsVector.y); + this.cameraManager.target.x = worldPoint.x; + this.cameraManager.target.y = worldPoint.y; + } + return false; + } + + public float getHeightRatioCorrection() { + return this.heightRatioCorrection; + } } From 1f9ccef70cc7295dd7c0591835cca5c7d6b282bb Mon Sep 17 00:00:00 2001 From: Retera Date: Sat, 14 Nov 2020 00:02:08 -0500 Subject: [PATCH 067/116] Nudge peon outside building when complete --- .../fdf/frames/SimpleStatusBarFrame.java | 2 + .../parsers/fdf/frames/TextureFrame.java | 8 ++ .../viewer5/handlers/w3x/War3MapViewer.java | 2 +- .../handlers/w3x/rendersim/RenderUnit.java | 22 ++++- .../handlers/w3x/simulation/CUnit.java | 98 +++++++++++++++---- .../behaviors/build/CBehaviorOrcBuild.java | 7 +- .../fdf/datamodel/Vector4Definition.java | 26 ++++- 7 files changed, 139 insertions(+), 26 deletions(-) diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/SimpleStatusBarFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/SimpleStatusBarFrame.java index 6fcef72..a7ea544 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/SimpleStatusBarFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/SimpleStatusBarFrame.java @@ -17,6 +17,7 @@ public class SimpleStatusBarFrame extends AbstractUIFrame { this.borderFrame.setSetAllPoints(true); this.barFrame.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this, FramePoint.TOPLEFT, 0, 0)); this.barFrame.addSetPoint(new SetPoint(FramePoint.BOTTOMLEFT, this, FramePoint.BOTTOMLEFT, 0, 0)); + this.barFrame.setSetAllPoints(true); add(this.barFrame); add(this.borderFrame); } @@ -26,6 +27,7 @@ public class SimpleStatusBarFrame extends AbstractUIFrame { } public void setValue(final float value) { + this.barFrame.setTexCoord(0, value, 0, 1); this.barFrame.setWidth(this.renderBounds.width * value); } diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/TextureFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/TextureFrame.java index c3f5190..86c5e39 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/TextureFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/TextureFrame.java @@ -44,6 +44,14 @@ public class TextureFrame extends AbstractRenderableFrame { } } + public void setTexCoord(final float x, final float y, final float z, final float w) { + this.texCoord.set(x, y, z, w); + if (this.texture != null) { + this.texture.setRegion(this.texCoord.getX(), this.texCoord.getZ(), this.texCoord.getY(), + this.texCoord.getW()); + } + } + public void setTexture(final Texture texture) { if (texture == null) { this.texture = null; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index fd1ade4..6b983e0 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -520,7 +520,7 @@ public class War3MapViewer extends ModelViewer { .getSound(War3MapViewer.this.gameUI.getSkinField("ConstructingBuilding")); if (constructingBuilding != null) { constructingBuilding.playUnitResponse(War3MapViewer.this.worldScene.audioContext, - War3MapViewer.this.unitToRenderPeer.get(constructingUnit)); + War3MapViewer.this.unitToRenderPeer.get(constructedStructure)); } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java index cafeff8..10f6bf9 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java @@ -146,9 +146,29 @@ public class RenderUnit { } public void updateAnimations(final War3MapViewer map) { - final float deltaTime = Gdx.graphics.getDeltaTime(); + final boolean wasHidden = this.instance.hidden(); + if (this.simulationUnit.isHidden()) { + if (!wasHidden) { + if (this.selectionCircle != null) { + this.selectionCircle.hide(); + } + if (this.shadow != null) { + this.shadow.hide(); + } + } + this.instance.hide(); + return; + } + else { + this.instance.show(); + } final float simulationX = this.simulationUnit.getX(); final float simulationY = this.simulationUnit.getY(); + if (wasHidden) { + this.x = simulationX; + this.y = simulationY; + } + final float deltaTime = Gdx.graphics.getDeltaTime(); final float simDx = simulationX - this.x; final float simDy = simulationY - this.y; final float distanceToSimulation = (float) Math.sqrt((simDx * simDx) + (simDy * simDy)); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java index e69812b..a2e10cc 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java @@ -72,6 +72,9 @@ public class CUnit extends CWidget { private transient CBehaviorStop stopBehavior; private boolean constructing = false; private float constructionProgress; + private boolean hidden = false; + private boolean updating = true; + private CUnit workerInside; public CUnit(final int handleId, final int playerIndex, final float x, final float y, final float life, final War3ID typeId, final float facing, final float mana, final int maximumLife, final int maximumMana, @@ -201,24 +204,32 @@ public class CUnit extends CWidget { return true; } } - else if (constructing) { - constructionProgress += WarsmashConstants.SIMULATION_STEP_TIME; - if (constructionProgress >= unitType.getBuildTime()) { - constructing = false; - game.unitConstructFinishEvent(this); - this.stateNotifier.ordersChanged(getCurrentAbilityHandleId(), getCurrentOrderId()); + else if (this.updating) { + if (this.constructing) { + this.constructionProgress += WarsmashConstants.SIMULATION_STEP_TIME; + if (this.constructionProgress >= this.unitType.getBuildTime()) { + this.constructing = false; + if (this.workerInside != null) { + this.workerInside.setHidden(false); + this.workerInside.setUpdating(true); + this.workerInside.nudgeAround(game, this); + this.workerInside = null; + } + game.unitConstructFinishEvent(this); + this.stateNotifier.ordersChanged(getCurrentAbilityHandleId(), getCurrentOrderId()); + } } - } - else if (this.currentBehavior != null) { - final CBehavior lastBehavior = this.currentBehavior; - this.currentBehavior = this.currentBehavior.update(game); - if (this.currentBehavior.getHighlightOrderId() != lastBehavior.getHighlightOrderId()) { - this.stateNotifier.ordersChanged(getCurrentAbilityHandleId(), getCurrentOrderId()); + else if (this.currentBehavior != null) { + final CBehavior lastBehavior = this.currentBehavior; + this.currentBehavior = this.currentBehavior.update(game); + if (this.currentBehavior.getHighlightOrderId() != lastBehavior.getHighlightOrderId()) { + this.stateNotifier.ordersChanged(getCurrentAbilityHandleId(), getCurrentOrderId()); + } + } + else { + // check to auto acquire targets + autoAcquireAttackTargets(game); } - } - else { - // check to auto acquire targets - autoAcquireAttackTargets(game); } return false; } @@ -680,22 +691,67 @@ public class CUnit extends CWidget { return getCurrentBehavior() instanceof CBehaviorMove; } - public void setConstructing(boolean constructing) { + public void setConstructing(final boolean constructing) { this.constructing = constructing; if (constructing) { - unitAnimationListener.playAnimation(true, PrimaryTag.BIRTH, SequenceUtils.EMPTY, 0.0f, true); + this.unitAnimationListener.playAnimation(true, PrimaryTag.BIRTH, SequenceUtils.EMPTY, 0.0f, true); } } - public void setConstructionProgress(float constructionProgress) { + public void setConstructionProgress(final float constructionProgress) { this.constructionProgress = constructionProgress; } public boolean isConstructing() { - return constructing; + return this.constructing; } public float getConstructionProgress() { - return constructionProgress; + return this.constructionProgress; + } + + public void setHidden(final boolean hidden) { + this.hidden = hidden; + } + + public void setUpdating(final boolean updating) { + this.updating = updating; + } + + public boolean isHidden() { + return this.hidden; + } + + public void setWorkerInside(final CUnit unit) { + this.workerInside = unit; + } + + public CUnit getWorkerInside() { + return this.workerInside; + } + + private void nudgeAround(final CSimulation simulation, final CUnit structure) { + float x, y; + if (structure.collisionRectangle != null) { + x = structure.collisionRectangle.x; + if (this.collisionRectangle != null) { + y = structure.collisionRectangle.y - this.collisionRectangle.height; + } + else { + y = structure.collisionRectangle.y; + } + } + else { + if (this.collisionRectangle != null) { + x = structure.getX() - (this.collisionRectangle.width / 2); + y = structure.getY() - (this.collisionRectangle.height / 2); + } + else { + x = structure.getX(); + y = structure.getY(); + } + } + setX(x, simulation.getWorldCollision()); + setY(y, simulation.getWorldCollision()); } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java index 83e7b3d..9a43644 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java @@ -35,9 +35,12 @@ public class CBehaviorOrcBuild extends CAbstractRangedPointTargetBehavior { @Override protected CBehavior update(final CSimulation simulation, final boolean withinRange) { - CUnit constructedStructure = simulation.createUnit(this.orderId, this.unit.getPlayerIndex(), this.targetX, this.targetY, - simulation.getGameplayConstants().getBuildingAngle()); + final CUnit constructedStructure = simulation.createUnit(this.orderId, this.unit.getPlayerIndex(), this.targetX, + this.targetY, simulation.getGameplayConstants().getBuildingAngle()); constructedStructure.setConstructing(true); + constructedStructure.setWorkerInside(this.unit); + this.unit.setHidden(true); + this.unit.setUpdating(false); simulation.unitConstructedEvent(this.unit, constructedStructure); return this.unit.pollNextOrderBehavior(simulation); } diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/Vector4Definition.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/Vector4Definition.java index c2741ed..1ea78fa 100644 --- a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/Vector4Definition.java +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/Vector4Definition.java @@ -1,7 +1,7 @@ package com.etheller.warsmash.parsers.fdf.datamodel; public class Vector4Definition { - private final float x, y, z, w; + private float x, y, z, w; public Vector4Definition(final float x, final float y, final float z, final float w) { this.x = x; @@ -25,4 +25,28 @@ public class Vector4Definition { public float getW() { return this.w; } + + public void setX(final float x) { + this.x = x; + } + + public void setY(final float y) { + this.y = y; + } + + public void setZ(final float z) { + this.z = z; + } + + public void setW(final float w) { + this.w = w; + } + + public void set(final float x2, final float y2, final float z2, final float w2) { + this.x = x2; + this.y = y2; + this.z = z2; + this.w = w2; + + } } From 4ffd0c3283b29350b91855569bd730deddaee45d Mon Sep 17 00:00:00 2001 From: Retera Date: Sat, 14 Nov 2020 09:43:21 -0500 Subject: [PATCH 068/116] Fix team color on build cursor --- .../com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java index 51ca702..59140e0 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java @@ -494,6 +494,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.war3MapViewer.mapPathSolver, this.war3MapViewer.solverParams); this.cursorModelInstance = (MdxComplexInstance) model.addInstance(); this.cursorModelInstance.setVertexColor(new float[] { 1, 1, 1, 0.5f }); + this.cursorModelInstance.setTeamColor(activeCommandUnit.getSimulationUnit().getPlayerIndex()); this.cursorModelInstance.rotate(RenderUnit.tempQuat.setFromAxis(RenderMathUtils.VEC3_UNIT_Z, this.war3MapViewer.simulation.getGameplayConstants().getBuildingAngle())); this.cursorModelInstance.setAnimationSpeed(0f); From f589afdf46b3c83bd8e1ec392ef20853b75efccb Mon Sep 17 00:00:00 2001 From: Retera Date: Sat, 14 Nov 2020 17:10:15 -0500 Subject: [PATCH 069/116] First draft of build queue, better working structures built list --- .../etheller/warsmash/WarsmashGdxMapGame.java | 2 +- .../etheller/warsmash/parsers/fdf/GameUI.java | 12 +- .../fdf/frames/AbstractRenderableFrame.java | 4 +- .../warsmash/util/WarsmashConstants.java | 2 + .../viewer5/handlers/w3x/SplatModel.java | 21 +- .../viewer5/handlers/w3x/War3MapViewer.java | 133 +- .../w3x/environment/BuildingShadow.java | 7 + .../handlers/w3x/environment/PathingGrid.java | 43 +- .../handlers/w3x/environment/Terrain.java | 2489 +++++++++-------- .../handlers/w3x/rendersim/RenderUnit.java | 95 +- .../w3x/rendersim/ability/AbilityDataUI.java | 45 +- .../CommandCardPopulatingAbilityVisitor.java | 46 +- .../handlers/w3x/simulation/CSimulation.java | 38 +- .../handlers/w3x/simulation/CUnit.java | 173 +- .../w3x/simulation/CUnitFilterFunction.java | 7 + .../w3x/simulation/CUnitStateListener.java | 9 + .../handlers/w3x/simulation/CUnitType.java | 15 +- .../handlers/w3x/simulation/CWidget.java | 2 +- .../simulation/abilities/CAbilityVisitor.java | 6 + .../build/AbstractCAbilityBuild.java | 23 +- .../build/CAbilityBuildInProgress.java | 85 + .../abilities/queue/CAbilityQueue.java | 133 + .../abilities/upgrade/CAbilityUpgrade.java | 5 + .../behaviors/build/CBehaviorOrcBuild.java | 5 + .../w3x/simulation/data/CUnitData.java | 50 +- .../w3x/simulation/players/CPlayer.java | 12 + .../util/SimulationRenderController.java | 6 + .../viewer5/handlers/w3x/ui/MeleeUI.java | 227 +- 28 files changed, 2276 insertions(+), 1419 deletions(-) create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/BuildingShadow.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityBuildInProgress.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityQueue.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/upgrade/CAbilityUpgrade.java diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java index 7944cc3..020d607 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java @@ -56,7 +56,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.CommandErrorListene public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProvider, InputProcessor { private static final boolean ENABLE_AUDIO = true; - private static final boolean ENABLE_MUSIC = true; + private static final boolean ENABLE_MUSIC = false; private DataSource codebase; private War3MapViewer viewer; private final Rectangle tempRect = new Rectangle(); diff --git a/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java index 24edd47..fd56287 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java @@ -276,7 +276,9 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { case Frame: if ("SIMPLEFRAME".equals(frameDefinition.getFrameType())) { final SimpleFrame simpleFrame = new SimpleFrame(frameDefinition.getName(), parent); - // TODO: we should not need to put ourselves in this map 2x + // TODO: we should not need to put ourselves in this map 2x, but we do + // since there are nested inflate calls happening before the general case + // mapping this.nameToFrame.put(frameDefinition.getName(), simpleFrame); for (final FrameDefinition childDefinition : frameDefinition.getInnerFrames()) { simpleFrame.add(inflate(childDefinition, simpleFrame, frameDefinition)); @@ -315,7 +317,13 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { } break; case Layer: - // NOT HANDLED YET + final SimpleFrame simpleFrame = new SimpleFrame(frameDefinition.getName(), parent); + simpleFrame.setSetAllPoints(true); + this.nameToFrame.put(frameDefinition.getName(), simpleFrame); + for (final FrameDefinition childDefinition : frameDefinition.getInnerFrames()) { + simpleFrame.add(inflate(childDefinition, simpleFrame, frameDefinition)); + } + inflatedFrame = simpleFrame; break; case String: final Float textLength = frameDefinition.getFloat("TextLength"); diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java index f6d0894..ace3f36 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java @@ -307,8 +307,8 @@ public abstract class AbstractRenderableFrame implements UIFrame { } } if (DEBUG_LOG) { - System.out.println( - getClass().getSimpleName() + ":" + this.name + " finishing position bounds: " + this.renderBounds); + System.out.println(getClass().getSimpleName() + ":" + this.name + ":" + hashCode() + + " finishing position bounds: " + this.renderBounds); } innerPositionBounds(viewport); } diff --git a/core/src/com/etheller/warsmash/util/WarsmashConstants.java b/core/src/com/etheller/warsmash/util/WarsmashConstants.java index 24a43ec..c7cd7a4 100644 --- a/core/src/com/etheller/warsmash/util/WarsmashConstants.java +++ b/core/src/com/etheller/warsmash/util/WarsmashConstants.java @@ -5,4 +5,6 @@ public class WarsmashConstants { public static final int REPLACEABLE_TEXTURE_LIMIT = 64; public static final float SIMULATION_STEP_TIME = 1 / 20f; public static final int PORT_NUMBER = 6115; + public static final float BUILDING_CONSTRUCT_START_LIFE = 0.1f; + public static final int BUILD_QUEUE_SIZE = 7; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SplatModel.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SplatModel.java index fa37dfb..eb93ebe 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SplatModel.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SplatModel.java @@ -225,9 +225,19 @@ public class SplatModel { } - public void add(float x, float y, float z, float scale, float[] centerOffset) { - locations.add(new float[]{x - scale, y - scale, x + scale, y + scale, z}); + public SplatMover add(final float x, final float y, final float w, final float h, final float zDepthUpward, + final float[] centerOffset) { + this.locations.add(new float[] { x, y, w, h, zDepthUpward }); + final SplatMover splatMover; + if (this.splatInstances != null) { + splatMover = new SplatMover(this); + this.splatInstances.add(splatMover); + } + else { + splatMover = null; + } compact(Gdx.gl30, centerOffset); + return splatMover; } private static final class Batch { @@ -413,5 +423,12 @@ public class SplatModel { gl.glBufferSubData(GL30.GL_ARRAY_BUFFER, this.uvsOffset + ((this.startOffset / 3) * 2), 4 * 2 * this.uvs.size(), RenderMathUtils.wrap(this.uvs)); } + + public void show(final float[] centerOffset) { + // It tries to only update if it is located at a new position... but here we are + // forcing it visible again by putting the position outside the map + this.ix0 = this.ix1 = this.iy0 = this.iy1 = Integer.MIN_VALUE; + move(0, 0, centerOffset); + } } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index 6b983e0..e773622 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -72,6 +72,8 @@ import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; import com.etheller.warsmash.viewer5.handlers.mdx.SequenceLoopMode; import com.etheller.warsmash.viewer5.handlers.tga.TgaFile; import com.etheller.warsmash.viewer5.handlers.w3x.SplatModel.SplatMover; +import com.etheller.warsmash.viewer5.handlers.w3x.environment.BuildingShadow; +import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.RemovablePathingMapInstance; import com.etheller.warsmash.viewer5.handlers.w3x.environment.Terrain; import com.etheller.warsmash.viewer5.handlers.w3x.environment.Terrain.Splat; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderAttackInstant; @@ -97,6 +99,7 @@ import mpq.MPQException; public class War3MapViewer extends ModelViewer { private static final War3ID UNIT_FILE = War3ID.fromString("umdl"); + private static final War3ID UNIT_SPECIAL = War3ID.fromString("uspa"); private static final War3ID UBER_SPLAT = War3ID.fromString("uubs"); private static final War3ID UNIT_SHADOW = War3ID.fromString("ushu"); private static final War3ID UNIT_SHADOW_X = War3ID.fromString("ushx"); @@ -553,6 +556,35 @@ public class War3MapViewer extends ModelViewer { return createNewUnit(War3MapViewer.this.allObjectData, typeId, x, y, 0f, playerIndex, (float) Math.toRadians(facing)); } + + @Override + public void spawnBuildingDeathEffect(final CUnit source) { + final RenderUnit renderUnit = War3MapViewer.this.unitToRenderPeer.get(source); + if (renderUnit.specialArtModel != null) { + final MdxComplexInstance modelInstance = (MdxComplexInstance) renderUnit.specialArtModel + .addInstance(); + modelInstance.setTeamColor(source.getPlayerIndex()); + modelInstance.setLocation(renderUnit.location); + modelInstance.setScene(War3MapViewer.this.worldScene); + SequenceUtils.randomBirthSequence(modelInstance); + War3MapViewer.this.projectiles + .add(new RenderAttackInstant(modelInstance, War3MapViewer.this, + (float) Math.toRadians(renderUnit.getSimulationUnit().getFacing()))); + } + } + + @Override + public void spawnUnitReadySound(final CUnit trainedUnit) { + final RenderUnit renderPeer = War3MapViewer.this.unitToRenderPeer.get(trainedUnit); + renderPeer.soundset.ready.playUnitResponse(War3MapViewer.this.worldScene.audioContext, + renderPeer); + } + + @Override + public void unitRepositioned(final CUnit cUnit) { + final RenderUnit renderPeer = War3MapViewer.this.unitToRenderPeer.get(cUnit); + renderPeer.repositioned(War3MapViewer.this); + } }, this.terrain.pathingGrid, this.terrain.getEntireMap(), this.seededRandom, w3iFile.getPlayers()); this.walkableObjectsTree = new Quadtree<>(this.terrain.getEntireMap()); @@ -664,7 +696,7 @@ public class War3MapViewer extends ModelViewer { } } if (bufferedImage != null) { - this.terrain.pathingGrid.blitPathingOverlayTexture(doodad.getLocation()[0], + this.terrain.pathingGrid.blitRemovablePathingOverlayTexture(doodad.getLocation()[0], doodad.getLocation()[1], (int) Math.toDegrees(doodad.getAngle()), bufferedImage); } } @@ -814,8 +846,13 @@ public class War3MapViewer extends ModelViewer { MutableGameObject row = null; String path = null; Splat unitShadowSplat = null; + SplatMover unitShadowSplatDynamicIngame = null; + Splat buildingUberSplat = null; + SplatMover buildingUberSplatDynamicIngame = null; BufferedImage buildingPathingPixelMap = null; final float unitVertexScale = 1.0f; + RemovablePathingMapInstance pathingInstance = null; + BuildingShadow buildingShadowInstance = null; // Hardcoded? WorldEditorDataType type = null; @@ -871,8 +908,8 @@ public class War3MapViewer extends ModelViewer { if (buildingPathingPixelMap != null) { unitX = Math.round(unitX / 64f) * 64f; unitY = Math.round(unitY / 64f) * 64f; - this.terrain.pathingGrid.blitPathingOverlayTexture(unitX, unitY, (int) Math.toDegrees(unitAngle), - buildingPathingPixelMap); + pathingInstance = this.terrain.pathingGrid.blitRemovablePathingOverlayTexture(unitX, unitY, + (int) Math.toDegrees(unitAngle), buildingPathingPixelMap); } final String uberSplat = row.getFieldAsString(UBER_SPLAT, 0); @@ -883,7 +920,7 @@ public class War3MapViewer extends ModelViewer { + ".blp"; final float s = uberSplatInfo.getFieldFloatValue("Scale"); if (this.unitsReady) { - this.terrain.addUberSplat(texturePath, unitX, unitY, 1, s); + buildingUberSplatDynamicIngame = this.terrain.addUberSplat(texturePath, unitX, unitY, 1, s); } else { if (!this.terrain.splats.containsKey(texturePath)) { @@ -891,8 +928,8 @@ public class War3MapViewer extends ModelViewer { } final float x = unitX; final float y = unitY; - this.terrain.splats.get(texturePath).locations - .add(new float[] { x - s, y - s, x + s, y + s, 1 }); + buildingUberSplat = this.terrain.splats.get(texturePath); + buildingUberSplat.locations.add(new float[] { x - s, y - s, x + s, y + s, 1 }); } } } @@ -905,22 +942,28 @@ public class War3MapViewer extends ModelViewer { final float shadowWidth = row.getFieldAsFloat(UNIT_SHADOW_W, 0); final float shadowHeight = row.getFieldAsFloat(UNIT_SHADOW_H, 0); if (this.mapMpq.has(texture)) { - if (!this.terrain.splats.containsKey(texture)) { - final Splat splat = new Splat(); - splat.opacity = 0.5f; - this.terrain.splats.put(texture, splat); - } final float x = unitX - shadowX; final float y = unitY - shadowY; - this.terrain.splats.get(texture).locations - .add(new float[] { x, y, x + shadowWidth, y + shadowHeight, 3 }); - unitShadowSplat = this.terrain.splats.get(texture); + if (this.unitsReady) { + unitShadowSplatDynamicIngame = this.terrain.addUnitShadowSplat(texture, x, y, + x + shadowWidth, y + shadowHeight, 3, 0.5f); + } + else { + if (!this.terrain.splats.containsKey(texture)) { + final Splat splat = new Splat(); + splat.opacity = 0.5f; + this.terrain.splats.put(texture, splat); + } + this.terrain.splats.get(texture).locations + .add(new float[] { x, y, x + shadowWidth, y + shadowHeight, 3 }); + unitShadowSplat = this.terrain.splats.get(texture); + } } } final String buildingShadow = row.getFieldAsString(BUILDING_SHADOW, 0); if ((buildingShadow != null) && !"_".equals(buildingShadow)) { - this.terrain.addShadow(buildingShadow, unitX, unitY); + buildingShadowInstance = this.terrain.addShadow(buildingShadow, unitX, unitY); } final String soundName = row.getFieldAsString(UNIT_SOUNDSET, 0); @@ -935,6 +978,21 @@ public class War3MapViewer extends ModelViewer { } if (path != null) { + final String unitSpecialArtPath = row.getFieldAsString(UNIT_SPECIAL, 0); + MdxModel specialArtModel; + if (unitSpecialArtPath != null) { + try { + specialArtModel = (MdxModel) this.load(mdx(unitSpecialArtPath), this.mapPathSolver, + this.solverParams); + } + catch (final Exception exc) { + exc.printStackTrace(); + specialArtModel = null; + } + } + else { + specialArtModel = null; + } final MdxModel model = (MdxModel) this.load(path, this.mapPathSolver, this.solverParams); MdxModel portraitModel; final String portraitPath = path.substring(0, path.length() - 4) + "_portrait.mdx"; @@ -947,10 +1005,10 @@ public class War3MapViewer extends ModelViewer { if (type == WorldEditorDataType.UNITS) { final float angle = (float) Math.toDegrees(unitAngle); final CUnit simulationUnit = this.simulation.createUnit(row.getAlias(), playerIndex, unitX, unitY, - angle, buildingPathingPixelMap); + angle, buildingPathingPixelMap, pathingInstance, buildingShadowInstance); final RenderUnitTypeData typeData = getUnitTypeData(unitId, row); final RenderUnit renderUnit = new RenderUnit(this, model, row, unitX, unitY, unitZ, playerIndex, - soundset, portraitModel, simulationUnit, typeData); + soundset, portraitModel, simulationUnit, typeData, specialArtModel); this.unitToRenderPeer.put(simulationUnit, renderUnit); this.units.add(renderUnit); if (unitShadowSplat != null) { @@ -961,6 +1019,20 @@ public class War3MapViewer extends ModelViewer { } }); } + if (unitShadowSplatDynamicIngame != null) { + renderUnit.shadow = unitShadowSplatDynamicIngame; + } + if (buildingUberSplat != null) { + buildingUberSplat.unitMapping.add(new Consumer() { + @Override + public void accept(final SplatMover t) { + renderUnit.uberSplat = t; + } + }); + } + if (buildingUberSplatDynamicIngame != null) { + renderUnit.uberSplat = buildingUberSplatDynamicIngame; + } return simulationUnit; } else { @@ -976,6 +1048,8 @@ public class War3MapViewer extends ModelViewer { } }); } + if (unitShadowSplatDynamicIngame != null) { + } } } else { @@ -1222,24 +1296,7 @@ public class War3MapViewer extends ModelViewer { public List selectUnit(final float x, final float y, final boolean toggle) { System.out.println("world: " + x + "," + y); - final float[] ray = rayHeap; - mousePosHeap.set(x, y); - this.worldScene.camera.screenToWorldRay(ray, mousePosHeap); - gdxRayHeap.set(ray[0], ray[1], ray[2], ray[3] - ray[0], ray[4] - ray[1], ray[5] - ray[2]); - gdxRayHeap.direction.nor();// needed for libgdx - - RenderUnit entity = null; - for (final RenderUnit unit : this.units) { - final MdxComplexInstance instance = unit.instance; - if (instance.isVisible(this.worldScene.camera) - && instance.intersectRayWithCollision(gdxRayHeap, intersectionHeap, - unit.getSimulationUnit().getUnitType().isBuilding(), false) - && !unit.getSimulationUnit().isDead()) { - if ((entity == null) || (entity.instance.depth > instance.depth)) { - entity = unit; - } - } - } + final RenderUnit entity = rayPickUnit(x, y, CUnitFilterFunction.ACCEPT_ALL_LIVING); List sel; if (entity != null) { if (toggle) { @@ -1277,8 +1334,8 @@ public class War3MapViewer extends ModelViewer { RenderUnit entity = null; for (final RenderUnit unit : this.units) { final MdxComplexInstance instance = unit.instance; - if (instance.isVisible(this.worldScene.camera) && instance.intersectRayWithCollision(gdxRayHeap, - intersectionHeap, unit.getSimulationUnit().getUnitType().isBuilding(), false)) { + if (instance.shown() && instance.isVisible(this.worldScene.camera) && instance.intersectRayWithCollision( + gdxRayHeap, intersectionHeap, unit.getSimulationUnit().getUnitType().isBuilding(), false)) { if (filter.call(unit.getSimulationUnit()) && (intersectionHeap.z > this.terrain .getGroundHeight(intersectionHeap.x, intersectionHeap.y))) { if ((entity == null) || (entity.instance.depth > instance.depth)) { @@ -1438,7 +1495,7 @@ public class War3MapViewer extends ModelViewer { public void setGameUI(final GameUI gameUI) { this.gameUI = gameUI; this.abilityDataUI = new AbilityDataUI(this.allObjectData.getAbilities(), this.allObjectData.getUnits(), - gameUI); + this.allObjectData.getUpgrades(), gameUI); } public GameUI getGameUI() { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/BuildingShadow.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/BuildingShadow.java new file mode 100644 index 0000000..942790e --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/BuildingShadow.java @@ -0,0 +1,7 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.environment; + +public interface BuildingShadow { + void remove(); + + void move(float x, float y); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java index 8345ebc..a042d37 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java @@ -1,7 +1,10 @@ package com.etheller.warsmash.viewer5.handlers.w3x.environment; import java.awt.image.BufferedImage; +import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; import com.etheller.warsmash.parsers.w3x.wpm.War3MapWpm; @@ -20,18 +23,20 @@ public class PathingGrid { private final short[] dynamicPathingOverlay; // for buildings and trees private final int[] pathingGridSizes; private final float[] centerOffset; + private final List dynamicPathingInstances; public PathingGrid(final War3MapWpm terrainPathing, final float[] centerOffset) { this.centerOffset = centerOffset; this.pathingGrid = terrainPathing.getPathing(); this.pathingGridSizes = terrainPathing.getSize(); this.dynamicPathingOverlay = new short[this.pathingGrid.length]; + this.dynamicPathingInstances = new ArrayList<>(); } // this blit function is basically copied from HiveWE, maybe remember to mention // that in credits as well: // https://github.com/stijnherfst/HiveWE/blob/master/Base/PathingMap.cpp - public void blitPathingOverlayTexture(final float positionX, final float positionY, final int rotationInput, + private void blitPathingOverlayTexture(final float positionX, final float positionY, final int rotationInput, final BufferedImage pathingTextureTga) { final int rotation = (rotationInput + 450) % 360; final int divW = ((rotation % 180) != 0) ? pathingTextureTga.getHeight() : pathingTextureTga.getWidth(); @@ -80,6 +85,15 @@ public class PathingGrid { } } + public RemovablePathingMapInstance blitRemovablePathingOverlayTexture(final float positionX, final float positionY, + final int rotationInput, final BufferedImage pathingTextureTga) { + final RemovablePathingMapInstance removablePathingMapInstance = new RemovablePathingMapInstance(positionX, + positionY, rotationInput, pathingTextureTga); + removablePathingMapInstance.blit(); + this.dynamicPathingInstances.add(removablePathingMapInstance); + return removablePathingMapInstance; + } + public int getWidth() { return this.pathingGridSizes[0]; } @@ -294,4 +308,31 @@ public class PathingGrid { this.preventionFlag = preventionFlag; } } + + public final class RemovablePathingMapInstance { + private final float positionX; + private final float positionY; + private final int rotationInput; + private final BufferedImage pathingTextureTga; + + public RemovablePathingMapInstance(final float positionX, final float positionY, final int rotationInput, + final BufferedImage pathingTextureTga) { + this.positionX = positionX; + this.positionY = positionY; + this.rotationInput = rotationInput; + this.pathingTextureTga = pathingTextureTga; + } + + private void blit() { + blitPathingOverlayTexture(this.positionX, this.positionY, this.rotationInput, this.pathingTextureTga); + } + + public void remove() { + PathingGrid.this.dynamicPathingInstances.remove(this); + Arrays.fill(PathingGrid.this.dynamicPathingOverlay, (short) 0); + for (final RemovablePathingMapInstance instance : PathingGrid.this.dynamicPathingInstances) { + instance.blit(); + } + } + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java index 471801b..930f5ee 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java @@ -5,7 +5,14 @@ import java.io.IOException; import java.io.InputStream; import java.nio.Buffer; import java.nio.FloatBuffer; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.TreeSet; import java.util.function.Consumer; import javax.imageio.ImageIO; @@ -48,1167 +55,1201 @@ import com.etheller.warsmash.viewer5.handlers.w3x.W3xShaders; import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; public class Terrain { - private static final String[] colorTags = {"R", "G", "B", "A"}; - private static final float[] sizeHeap = new float[2]; - private static final Vector3 normalHeap1 = new Vector3(); - private static final Vector3 normalHeap2 = new Vector3(); - private static final float[] fourComponentHeap = new float[4]; - private static final Matrix4 tempMatrix = new Matrix4(); - private static final boolean WIREFRAME_TERRAIN = false; + private static final String[] colorTags = { "R", "G", "B", "A" }; + private static final float[] sizeHeap = new float[2]; + private static final Vector3 normalHeap1 = new Vector3(); + private static final Vector3 normalHeap2 = new Vector3(); + private static final float[] fourComponentHeap = new float[4]; + private static final Matrix4 tempMatrix = new Matrix4(); + private static final boolean WIREFRAME_TERRAIN = false; - public ShaderProgram groundShader; - public ShaderProgram waterShader; - public ShaderProgram cliffShader; - public ShaderProgram testShader; - public float waterIndex; - public float waterIncreasePerFrame; - public float waterHeightOffset; + public ShaderProgram groundShader; + public ShaderProgram waterShader; + public ShaderProgram cliffShader; + public ShaderProgram testShader; + public float waterIndex; + public float waterIncreasePerFrame; + public float waterHeightOffset; - // - public List groundTextures = new ArrayList<>(); - public List cliffTextures = new ArrayList<>(); - public RenderCorner[][] corners; - public int columns; - public int rows; - public int blightTextureIndex = -1; - public float[] maxDeepColor = new float[4]; - public float[] minDeepColor = new float[4]; - public float[] maxShallowColor = new float[4]; - public float[] minShallowColor = new float[4]; + // + public List groundTextures = new ArrayList<>(); + public List cliffTextures = new ArrayList<>(); + public RenderCorner[][] corners; + public int columns; + public int rows; + public int blightTextureIndex = -1; + public float[] maxDeepColor = new float[4]; + public float[] minDeepColor = new float[4]; + public float[] maxShallowColor = new float[4]; + public float[] minShallowColor = new float[4]; - private final DataTable terrainTable; - private final DataTable cliffTable; - private final DataTable waterTable; - private final int waterTextureCount; - private int cliffTexturesSize; - private final List cliffMeshes = new ArrayList<>(); - private final Map pathToCliff = new HashMap<>(); - private final Map groundTextureToId = new HashMap<>(); - private final List cliffToGroundTexture = new ArrayList<>(); - private final List cliffs = new ArrayList<>(); - private final DataSource dataSource; - private final float[] groundHeights; - private final float[] groundCornerHeights; - private final short[] groundTextureList; - private final float[] waterHeights; - private final byte[] waterExistsData; + private final DataTable terrainTable; + private final DataTable cliffTable; + private final DataTable waterTable; + private final int waterTextureCount; + private int cliffTexturesSize; + private final List cliffMeshes = new ArrayList<>(); + private final Map pathToCliff = new HashMap<>(); + private final Map groundTextureToId = new HashMap<>(); + private final List cliffToGroundTexture = new ArrayList<>(); + private final List cliffs = new ArrayList<>(); + private final DataSource dataSource; + private final float[] groundHeights; + private final float[] groundCornerHeights; + private final short[] groundTextureList; + private final float[] waterHeights; + private final byte[] waterExistsData; - private int groundTextureData = -1; - private final int groundHeight; - private final int groundCornerHeight; - private final int groundCornerHeightLinear; - private final int cliffTextureArray; - private final int waterHeight; - private final int waterExists; - private final int waterTextureArray; - private final Camera camera; - private final War3MapViewer viewer; - public float[] centerOffset; - private final WebGL webGL; - private final ShaderProgram uberSplatShader; - public final DataTable uberSplatTable; + private int groundTextureData = -1; + private final int groundHeight; + private final int groundCornerHeight; + private final int groundCornerHeightLinear; + private final int cliffTextureArray; + private final int waterHeight; + private final int waterExists; + private final int waterTextureArray; + private final Camera camera; + private final War3MapViewer viewer; + public float[] centerOffset; + private final WebGL webGL; + private final ShaderProgram uberSplatShader; + public final DataTable uberSplatTable; - private final Map uberSplatModels; - private int shadowMap; - public final Map splats = new HashMap<>(); - public final Map> shadows = new HashMap<>(); - public final Map shadowTextures = new HashMap<>(); - private final int[] mapBounds; - private final float[] shaderMapBounds; - private final int[] mapSize; - public final SoftwareGroundMesh softwareGroundMesh; - private final int testArrayBuffer; - private final int testElementBuffer; - private boolean initShadowsFinished = false; - private byte[] shadowData; + private final Map uberSplatModels; + private int shadowMap; + public final Map splats = new HashMap<>(); + public final Map> shadows = new HashMap<>(); + public final Map shadowTextures = new HashMap<>(); + private final int[] mapBounds; + private final float[] shaderMapBounds; + private final int[] mapSize; + public final SoftwareGroundMesh softwareGroundMesh; + private final int testArrayBuffer; + private final int testElementBuffer; + private boolean initShadowsFinished = false; + private byte[] staticShadowData; + private byte[] shadowData; - public Terrain(final War3MapW3e w3eFile, final War3MapWpm terrainPathing, final War3MapW3i w3iFile, - final WebGL webGL, final DataSource dataSource, final WorldEditStrings worldEditStrings, - final War3MapViewer viewer, final DataTable worldEditData) throws IOException { - this.webGL = webGL; - this.viewer = viewer; - this.camera = viewer.worldScene.camera; - this.dataSource = dataSource; - final String texturesExt = ".blp"; - final Corner[][] corners = w3eFile.getCorners(); - this.corners = new RenderCorner[corners[0].length][corners.length]; - for (int i = 0; i < corners.length; i++) { - for (int j = 0; j < corners[i].length; j++) { - this.corners[j][i] = new RenderCorner(corners[i][j]); - } - } - final int width = w3eFile.getMapSize()[0]; - final int height = w3eFile.getMapSize()[1]; - this.columns = width; - this.rows = height; - for (int i = 0; i < (width - 1); i++) { - for (int j = 0; j < (height - 1); j++) { - final RenderCorner bottomLeft = this.corners[i][j]; - final RenderCorner bottomRight = this.corners[i + 1][j]; - final RenderCorner topLeft = this.corners[i][j + 1]; - final RenderCorner topRight = this.corners[i + 1][j + 1]; + public Terrain(final War3MapW3e w3eFile, final War3MapWpm terrainPathing, final War3MapW3i w3iFile, + final WebGL webGL, final DataSource dataSource, final WorldEditStrings worldEditStrings, + final War3MapViewer viewer, final DataTable worldEditData) throws IOException { + this.webGL = webGL; + this.viewer = viewer; + this.camera = viewer.worldScene.camera; + this.dataSource = dataSource; + final String texturesExt = ".blp"; + final Corner[][] corners = w3eFile.getCorners(); + this.corners = new RenderCorner[corners[0].length][corners.length]; + for (int i = 0; i < corners.length; i++) { + for (int j = 0; j < corners[i].length; j++) { + this.corners[j][i] = new RenderCorner(corners[i][j]); + } + } + final int width = w3eFile.getMapSize()[0]; + final int height = w3eFile.getMapSize()[1]; + this.columns = width; + this.rows = height; + for (int i = 0; i < (width - 1); i++) { + for (int j = 0; j < (height - 1); j++) { + final RenderCorner bottomLeft = this.corners[i][j]; + final RenderCorner bottomRight = this.corners[i + 1][j]; + final RenderCorner topLeft = this.corners[i][j + 1]; + final RenderCorner topRight = this.corners[i + 1][j + 1]; - bottomLeft.cliff = (bottomLeft.getLayerHeight() != bottomRight.getLayerHeight()) - || (bottomLeft.getLayerHeight() != topLeft.getLayerHeight()) - || (bottomLeft.getLayerHeight() != topRight.getLayerHeight()); - } - } + bottomLeft.cliff = (bottomLeft.getLayerHeight() != bottomRight.getLayerHeight()) + || (bottomLeft.getLayerHeight() != topLeft.getLayerHeight()) + || (bottomLeft.getLayerHeight() != topRight.getLayerHeight()); + } + } - this.terrainTable = new DataTable(worldEditStrings); - try (InputStream terrainSlkStream = dataSource.getResourceAsStream("TerrainArt\\Terrain.slk")) { - this.terrainTable.readSLK(terrainSlkStream); - } - this.cliffTable = new DataTable(worldEditStrings); - try (InputStream cliffSlkStream = dataSource.getResourceAsStream("TerrainArt\\CliffTypes.slk")) { - this.cliffTable.readSLK(cliffSlkStream); - } - this.waterTable = new DataTable(worldEditStrings); - try (InputStream waterSlkStream = dataSource.getResourceAsStream("TerrainArt\\Water.slk")) { - this.waterTable.readSLK(waterSlkStream); - } - this.uberSplatTable = new DataTable(worldEditStrings); - try (InputStream uberSlkStream = dataSource.getResourceAsStream("Splats\\UberSplatData.slk")) { - this.uberSplatTable.readSLK(uberSlkStream); - } + this.terrainTable = new DataTable(worldEditStrings); + try (InputStream terrainSlkStream = dataSource.getResourceAsStream("TerrainArt\\Terrain.slk")) { + this.terrainTable.readSLK(terrainSlkStream); + } + this.cliffTable = new DataTable(worldEditStrings); + try (InputStream cliffSlkStream = dataSource.getResourceAsStream("TerrainArt\\CliffTypes.slk")) { + this.cliffTable.readSLK(cliffSlkStream); + } + this.waterTable = new DataTable(worldEditStrings); + try (InputStream waterSlkStream = dataSource.getResourceAsStream("TerrainArt\\Water.slk")) { + this.waterTable.readSLK(waterSlkStream); + } + this.uberSplatTable = new DataTable(worldEditStrings); + try (InputStream uberSlkStream = dataSource.getResourceAsStream("Splats\\UberSplatData.slk")) { + this.uberSplatTable.readSLK(uberSlkStream); + } - final char tileset = w3eFile.getTileset(); - final Element waterInfo = this.waterTable.get(tileset + "Sha"); - if (waterInfo != null) { - this.waterHeightOffset = waterInfo.getFieldFloatValue("height"); - this.waterTextureCount = waterInfo.getFieldValue("numTex"); - this.waterIncreasePerFrame = waterInfo.getFieldValue("texRate") / 60f; - } else { - this.waterHeightOffset = 0; - this.waterTextureCount = 0; - this.waterIncreasePerFrame = 0; - } + final char tileset = w3eFile.getTileset(); + final Element waterInfo = this.waterTable.get(tileset + "Sha"); + if (waterInfo != null) { + this.waterHeightOffset = waterInfo.getFieldFloatValue("height"); + this.waterTextureCount = waterInfo.getFieldValue("numTex"); + this.waterIncreasePerFrame = waterInfo.getFieldValue("texRate") / 60f; + } + else { + this.waterHeightOffset = 0; + this.waterTextureCount = 0; + this.waterIncreasePerFrame = 0; + } - loadWaterColor(this.minShallowColor, "Smin", waterInfo); - loadWaterColor(this.maxShallowColor, "Smax", waterInfo); - loadWaterColor(this.minDeepColor, "Dmin", waterInfo); - loadWaterColor(this.maxDeepColor, "Dmax", waterInfo); - for (int i = 0; i < 3; i++) { - if (this.minDeepColor[i] > this.maxDeepColor[i]) { - this.maxDeepColor[i] = this.minDeepColor[i]; - } - } + loadWaterColor(this.minShallowColor, "Smin", waterInfo); + loadWaterColor(this.maxShallowColor, "Smax", waterInfo); + loadWaterColor(this.minDeepColor, "Dmin", waterInfo); + loadWaterColor(this.maxDeepColor, "Dmax", waterInfo); + for (int i = 0; i < 3; i++) { + if (this.minDeepColor[i] > this.maxDeepColor[i]) { + this.maxDeepColor[i] = this.minDeepColor[i]; + } + } - // Cliff Meshes + // Cliff Meshes - Map cliffVars = Variations.CLIFF_VARS; - for (final Map.Entry cliffVar : cliffVars.entrySet()) { - final Integer maxVariations = cliffVar.getValue(); - for (int variation = 0; variation <= maxVariations; variation++) { - final String fileName = "Doodads\\Terrain\\Cliffs\\Cliffs" + cliffVar.getKey() + variation + ".mdx"; - this.cliffMeshes.add(new CliffMesh(fileName, dataSource, Gdx.gl30)); - this.pathToCliff.put("Cliffs" + cliffVar.getKey() + variation, this.cliffMeshes.size() - 1); - } - } - cliffVars = Variations.CITY_CLIFF_VARS; - for (final Map.Entry cliffVar : cliffVars.entrySet()) { - final Integer maxVariations = cliffVar.getValue(); - for (int variation = 0; variation <= maxVariations; variation++) { - final String fileName = "Doodads\\Terrain\\CityCliffs\\CityCliffs" + cliffVar.getKey() + variation - + ".mdx"; - this.cliffMeshes.add(new CliffMesh(fileName, dataSource, Gdx.gl30)); - this.pathToCliff.put("CityCliffs" + cliffVar.getKey() + variation, this.cliffMeshes.size() - 1); - } - } + Map cliffVars = Variations.CLIFF_VARS; + for (final Map.Entry cliffVar : cliffVars.entrySet()) { + final Integer maxVariations = cliffVar.getValue(); + for (int variation = 0; variation <= maxVariations; variation++) { + final String fileName = "Doodads\\Terrain\\Cliffs\\Cliffs" + cliffVar.getKey() + variation + ".mdx"; + this.cliffMeshes.add(new CliffMesh(fileName, dataSource, Gdx.gl30)); + this.pathToCliff.put("Cliffs" + cliffVar.getKey() + variation, this.cliffMeshes.size() - 1); + } + } + cliffVars = Variations.CITY_CLIFF_VARS; + for (final Map.Entry cliffVar : cliffVars.entrySet()) { + final Integer maxVariations = cliffVar.getValue(); + for (int variation = 0; variation <= maxVariations; variation++) { + final String fileName = "Doodads\\Terrain\\CityCliffs\\CityCliffs" + cliffVar.getKey() + variation + + ".mdx"; + this.cliffMeshes.add(new CliffMesh(fileName, dataSource, Gdx.gl30)); + this.pathToCliff.put("CityCliffs" + cliffVar.getKey() + variation, this.cliffMeshes.size() - 1); + } + } - // Ground textures - for (final War3ID groundTile : w3eFile.getGroundTiles()) { - final Element terrainTileInfo = this.terrainTable.get(groundTile.asStringValue()); - if (terrainTileInfo == null) { - throw new RuntimeException("No terrain info for: " + groundTile.asStringValue()); - } - final String dir = terrainTileInfo.getField("dir"); - final String file = terrainTileInfo.getField("file"); - this.groundTextures.add(new GroundTexture(dir + "\\" + file + texturesExt, dataSource, Gdx.gl30)); - this.groundTextureToId.put(groundTile.asStringValue(), this.groundTextures.size() - 1); - } + // Ground textures + for (final War3ID groundTile : w3eFile.getGroundTiles()) { + final Element terrainTileInfo = this.terrainTable.get(groundTile.asStringValue()); + if (terrainTileInfo == null) { + throw new RuntimeException("No terrain info for: " + groundTile.asStringValue()); + } + final String dir = terrainTileInfo.getField("dir"); + final String file = terrainTileInfo.getField("file"); + this.groundTextures.add(new GroundTexture(dir + "\\" + file + texturesExt, dataSource, Gdx.gl30)); + this.groundTextureToId.put(groundTile.asStringValue(), this.groundTextures.size() - 1); + } - final Element tilesets = worldEditData.get("TileSets"); + final Element tilesets = worldEditData.get("TileSets"); - this.blightTextureIndex = this.groundTextures.size(); - this.groundTextures.add(new GroundTexture( - tilesets.getField(Character.toString(tileset)).split(",")[1] + texturesExt, dataSource, Gdx.gl30)); + this.blightTextureIndex = this.groundTextures.size(); + this.groundTextures.add(new GroundTexture( + tilesets.getField(Character.toString(tileset)).split(",")[1] + texturesExt, dataSource, Gdx.gl30)); - // Cliff Textures - for (final War3ID cliffTile : w3eFile.getCliffTiles()) { - final Element cliffInfo = this.cliffTable.get(cliffTile.asStringValue()); - final String texDir = cliffInfo.getField("texDir"); - final String texFile = cliffInfo.getField("texFile"); - try (InputStream imageStream = dataSource.getResourceAsStream(texDir + "\\" + texFile + texturesExt)) { - final BufferedImage image; - if (imageStream == null) { - final String tgaPath = texDir + "\\" + texFile + ".tga"; - try (final InputStream tgaStream = dataSource.getResourceAsStream(tgaPath)) { - if (tgaStream != null) { - image = TgaFile.readTGA(tgaPath, tgaStream); - } else { - throw new IllegalStateException( - "Missing cliff texture: " + texDir + "\\" + texFile + texturesExt); - } - } - } else { - image = ImageIO.read(imageStream); - if (image == null) { - throw new IllegalStateException( - "Missing cliff texture: " + texDir + "\\" + texFile + texturesExt); - } - } - this.cliffTextures.add(new UnloadedTexture(image.getWidth(), image.getHeight(), - ImageUtils.getTextureBuffer(ImageUtils.forceBufferedImagesRGB(image)), - cliffInfo.getField("cliffModelDir"), cliffInfo.getField("rampModelDir"))); - } - this.cliffTexturesSize = Math.max(this.cliffTexturesSize, - this.cliffTextures.get(this.cliffTextures.size() - 1).width); - this.cliffToGroundTexture.add(this.groundTextureToId.get(cliffInfo.getField("groundTile"))); - } + // Cliff Textures + for (final War3ID cliffTile : w3eFile.getCliffTiles()) { + final Element cliffInfo = this.cliffTable.get(cliffTile.asStringValue()); + final String texDir = cliffInfo.getField("texDir"); + final String texFile = cliffInfo.getField("texFile"); + try (InputStream imageStream = dataSource.getResourceAsStream(texDir + "\\" + texFile + texturesExt)) { + final BufferedImage image; + if (imageStream == null) { + final String tgaPath = texDir + "\\" + texFile + ".tga"; + try (final InputStream tgaStream = dataSource.getResourceAsStream(tgaPath)) { + if (tgaStream != null) { + image = TgaFile.readTGA(tgaPath, tgaStream); + } + else { + throw new IllegalStateException( + "Missing cliff texture: " + texDir + "\\" + texFile + texturesExt); + } + } + } + else { + image = ImageIO.read(imageStream); + if (image == null) { + throw new IllegalStateException( + "Missing cliff texture: " + texDir + "\\" + texFile + texturesExt); + } + } + this.cliffTextures.add(new UnloadedTexture(image.getWidth(), image.getHeight(), + ImageUtils.getTextureBuffer(ImageUtils.forceBufferedImagesRGB(image)), + cliffInfo.getField("cliffModelDir"), cliffInfo.getField("rampModelDir"))); + } + this.cliffTexturesSize = Math.max(this.cliffTexturesSize, + this.cliffTextures.get(this.cliffTextures.size() - 1).width); + this.cliffToGroundTexture.add(this.groundTextureToId.get(cliffInfo.getField("groundTile"))); + } - updateCliffMeshes(new Rectangle(0, 0, width - 1, height - 1)); + updateCliffMeshes(new Rectangle(0, 0, width - 1, height - 1)); - // prepare GPU data - this.groundHeights = new float[width * height]; - this.groundCornerHeights = new float[width * height]; - this.groundTextureList = new short[(width - 1) * (height - 1) * 4]; - this.waterHeights = new float[width * height]; - this.waterExistsData = new byte[width * height]; + // prepare GPU data + this.groundHeights = new float[width * height]; + this.groundCornerHeights = new float[width * height]; + this.groundTextureList = new short[(width - 1) * (height - 1) * 4]; + this.waterHeights = new float[width * height]; + this.waterExistsData = new byte[width * height]; - updateGroundTextures(new Rectangle(0, 0, width - 1, height - 1)); - for (int i = 0; i < width; i++) { - for (int j = 0; j < height; j++) { - this.groundCornerHeights[(j * width) + i] = this.corners[i][j].computeFinalGroundHeight(); - this.waterExistsData[(j * width) + i] = (byte) this.corners[i][j].getWater(); - this.groundHeights[(j * width) + i] = this.corners[i][j].getGroundHeight(); - this.waterHeights[(j * width) + i] = this.corners[i][j].getWaterHeight(); - } - } + updateGroundTextures(new Rectangle(0, 0, width - 1, height - 1)); + for (int i = 0; i < width; i++) { + for (int j = 0; j < height; j++) { + this.groundCornerHeights[(j * width) + i] = this.corners[i][j].computeFinalGroundHeight(); + this.waterExistsData[(j * width) + i] = (byte) this.corners[i][j].getWater(); + this.groundHeights[(j * width) + i] = this.corners[i][j].getGroundHeight(); + this.waterHeights[(j * width) + i] = this.corners[i][j].getWaterHeight(); + } + } - final GL30 gl = Gdx.gl30; - // Ground - this.groundTextureData = gl.glGenTexture(); - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundTextureData); - gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_RGBA16UI, width - 1, height - 1, 0, GL30.GL_RGBA_INTEGER, - GL30.GL_UNSIGNED_SHORT, RenderMathUtils.wrapShort(this.groundTextureList)); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_NEAREST); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_NEAREST); + final GL30 gl = Gdx.gl30; + // Ground + this.groundTextureData = gl.glGenTexture(); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundTextureData); + gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_RGBA16UI, width - 1, height - 1, 0, GL30.GL_RGBA_INTEGER, + GL30.GL_UNSIGNED_SHORT, RenderMathUtils.wrapShort(this.groundTextureList)); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_NEAREST); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_NEAREST); - this.groundHeight = gl.glGenTexture(); - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundHeight); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_LINEAR); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_LINEAR); + this.groundHeight = gl.glGenTexture(); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundHeight); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_LINEAR); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_LINEAR); - gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_R16F, width, height, 0, GL30.GL_RED, GL30.GL_FLOAT, - RenderMathUtils.wrap(this.groundHeights)); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_S, GL30.GL_CLAMP_TO_EDGE); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_T, GL30.GL_CLAMP_TO_EDGE); + gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_R16F, width, height, 0, GL30.GL_RED, GL30.GL_FLOAT, + RenderMathUtils.wrap(this.groundHeights)); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_S, GL30.GL_CLAMP_TO_EDGE); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_T, GL30.GL_CLAMP_TO_EDGE); - this.groundCornerHeight = gl.glGenTexture(); - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeight); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_NEAREST); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_NEAREST); + this.groundCornerHeight = gl.glGenTexture(); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeight); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_NEAREST); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_NEAREST); - gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_R16F, width, height, 0, GL30.GL_RED, GL30.GL_FLOAT, - RenderMathUtils.wrap(this.groundCornerHeights)); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_S, GL30.GL_CLAMP_TO_EDGE); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_T, GL30.GL_CLAMP_TO_EDGE); + gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_R16F, width, height, 0, GL30.GL_RED, GL30.GL_FLOAT, + RenderMathUtils.wrap(this.groundCornerHeights)); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_S, GL30.GL_CLAMP_TO_EDGE); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_T, GL30.GL_CLAMP_TO_EDGE); - this.groundCornerHeightLinear = gl.glGenTexture(); - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeightLinear); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_LINEAR); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_LINEAR); + this.groundCornerHeightLinear = gl.glGenTexture(); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeightLinear); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_LINEAR); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_LINEAR); - gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_R16F, width, height, 0, GL30.GL_RED, GL30.GL_FLOAT, - RenderMathUtils.wrap(this.groundCornerHeights)); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_S, GL30.GL_CLAMP_TO_EDGE); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_T, GL30.GL_CLAMP_TO_EDGE); + gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_R16F, width, height, 0, GL30.GL_RED, GL30.GL_FLOAT, + RenderMathUtils.wrap(this.groundCornerHeights)); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_S, GL30.GL_CLAMP_TO_EDGE); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_T, GL30.GL_CLAMP_TO_EDGE); - // Cliff - this.cliffTextureArray = gl.glGenTexture(); - gl.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.cliffTextureArray); - gl.glTexImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, GL30.GL_RGBA8, this.cliffTexturesSize, this.cliffTexturesSize, - this.cliffTextures.size(), 0, GL30.GL_RGBA, GL30.GL_UNSIGNED_BYTE, null); - gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_LINEAR_MIPMAP_LINEAR); - gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_BASE_LEVEL, 0); + // Cliff + this.cliffTextureArray = gl.glGenTexture(); + gl.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.cliffTextureArray); + gl.glTexImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, GL30.GL_RGBA8, this.cliffTexturesSize, this.cliffTexturesSize, + this.cliffTextures.size(), 0, GL30.GL_RGBA, GL30.GL_UNSIGNED_BYTE, null); + gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_LINEAR_MIPMAP_LINEAR); + gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_BASE_LEVEL, 0); - int sub = 0; - for (final UnloadedTexture i : this.cliffTextures) { - gl.glTexSubImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, 0, 0, sub, i.width, i.height, 1, GL30.GL_RGBA, - GL30.GL_UNSIGNED_BYTE, i.data); - sub += 1; - } - gl.glGenerateMipmap(GL30.GL_TEXTURE_2D_ARRAY); + int sub = 0; + for (final UnloadedTexture i : this.cliffTextures) { + gl.glTexSubImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, 0, 0, sub, i.width, i.height, 1, GL30.GL_RGBA, + GL30.GL_UNSIGNED_BYTE, i.data); + sub += 1; + } + gl.glGenerateMipmap(GL30.GL_TEXTURE_2D_ARRAY); - // Water - this.waterHeight = gl.glGenTexture(); - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.waterHeight); - gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_R16F, width, height, 0, GL30.GL_RED, GL30.GL_FLOAT, - RenderMathUtils.wrap(this.waterHeights)); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_NEAREST); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_NEAREST); + // Water + this.waterHeight = gl.glGenTexture(); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.waterHeight); + gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_R16F, width, height, 0, GL30.GL_RED, GL30.GL_FLOAT, + RenderMathUtils.wrap(this.waterHeights)); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_NEAREST); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_NEAREST); - this.waterExists = gl.glGenTexture(); - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.waterExists); - gl.glPixelStorei(GL30.GL_UNPACK_ALIGNMENT, 1); - gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_R8, width, height, 0, GL30.GL_RED, GL30.GL_UNSIGNED_BYTE, - RenderMathUtils.wrap(this.waterExistsData)); - gl.glPixelStorei(GL30.GL_UNPACK_ALIGNMENT, 4); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_NEAREST); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_NEAREST); + this.waterExists = gl.glGenTexture(); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.waterExists); + gl.glPixelStorei(GL30.GL_UNPACK_ALIGNMENT, 1); + gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_R8, width, height, 0, GL30.GL_RED, GL30.GL_UNSIGNED_BYTE, + RenderMathUtils.wrap(this.waterExistsData)); + gl.glPixelStorei(GL30.GL_UNPACK_ALIGNMENT, 4); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_NEAREST); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_NEAREST); - // Water textures - this.waterTextureArray = gl.glGenTexture(); - gl.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.waterTextureArray); - gl.glTexImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, GL30.GL_SRGB8_ALPHA8, 128, 128, this.waterTextureCount, 0, - GL30.GL_RGBA, GL30.GL_UNSIGNED_BYTE, null); - gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_WRAP_S, GL30.GL_CLAMP_TO_EDGE); - gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_WRAP_T, GL30.GL_CLAMP_TO_EDGE); - gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_BASE_LEVEL, 0); + // Water textures + this.waterTextureArray = gl.glGenTexture(); + gl.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.waterTextureArray); + gl.glTexImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, GL30.GL_SRGB8_ALPHA8, 128, 128, this.waterTextureCount, 0, + GL30.GL_RGBA, GL30.GL_UNSIGNED_BYTE, null); + gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_WRAP_S, GL30.GL_CLAMP_TO_EDGE); + gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_WRAP_T, GL30.GL_CLAMP_TO_EDGE); + gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_BASE_LEVEL, 0); - final String fileName = waterInfo.getField("texFile"); - for (int i = 0; i < this.waterTextureCount; i++) { + final String fileName = waterInfo.getField("texFile"); + for (int i = 0; i < this.waterTextureCount; i++) { - try (InputStream imageStream = dataSource - .getResourceAsStream(fileName + (i < 10 ? "0" : "") + Integer.toString(i) + texturesExt)) { - final BufferedImage image = ImageIO.read(imageStream); - if ((image.getWidth() != 128) || (image.getHeight() != 128)) { - System.err.println( - "Odd water texture size detected of " + image.getWidth() + " x " + image.getHeight()); - } + try (InputStream imageStream = dataSource + .getResourceAsStream(fileName + (i < 10 ? "0" : "") + Integer.toString(i) + texturesExt)) { + final BufferedImage image = ImageIO.read(imageStream); + if ((image.getWidth() != 128) || (image.getHeight() != 128)) { + System.err.println( + "Odd water texture size detected of " + image.getWidth() + " x " + image.getHeight()); + } - gl.glTexSubImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, 0, 0, i, image.getWidth(), image.getHeight(), 1, - GL30.GL_RGBA, GL30.GL_UNSIGNED_BYTE, ImageUtils.getTextureBuffer(image)); - } - } - gl.glGenerateMipmap(GL30.GL_TEXTURE_2D_ARRAY); + gl.glTexSubImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, 0, 0, i, image.getWidth(), image.getHeight(), 1, + GL30.GL_RGBA, GL30.GL_UNSIGNED_BYTE, ImageUtils.getTextureBuffer(image)); + } + } + gl.glGenerateMipmap(GL30.GL_TEXTURE_2D_ARRAY); - updateGroundHeights(new Rectangle(0, 0, width - 1, height - 1)); + updateGroundHeights(new Rectangle(0, 0, width - 1, height - 1)); - this.groundShader = webGL.createShaderProgram(TerrainShaders.Terrain.vert, TerrainShaders.Terrain.frag); - this.cliffShader = webGL.createShaderProgram(TerrainShaders.Cliffs.vert, TerrainShaders.Cliffs.frag); - this.waterShader = webGL.createShaderProgram(TerrainShaders.Water.vert, TerrainShaders.Water.frag); - this.testShader = webGL.createShaderProgram(TerrainShaders.Test.vert, TerrainShaders.Test.frag); + this.groundShader = webGL.createShaderProgram(TerrainShaders.Terrain.vert, TerrainShaders.Terrain.frag); + this.cliffShader = webGL.createShaderProgram(TerrainShaders.Cliffs.vert, TerrainShaders.Cliffs.frag); + this.waterShader = webGL.createShaderProgram(TerrainShaders.Water.vert, TerrainShaders.Water.frag); + this.testShader = webGL.createShaderProgram(TerrainShaders.Test.vert, TerrainShaders.Test.frag); - this.uberSplatShader = webGL.createShaderProgram(W3xShaders.UberSplat.vert, W3xShaders.UberSplat.frag); + this.uberSplatShader = webGL.createShaderProgram(W3xShaders.UberSplat.vert, W3xShaders.UberSplat.frag); - // TODO collision bodies (?) + // TODO collision bodies (?) - this.centerOffset = w3eFile.getCenterOffset(); - this.uberSplatModels = new LinkedHashMap<>(); - this.mapBounds = w3iFile.getCameraBoundsComplements(); - this.shaderMapBounds = new float[]{(this.mapBounds[0] * 128.0f) + this.centerOffset[0], - (this.mapBounds[2] * 128.0f) + this.centerOffset[1], - ((this.columns - this.mapBounds[1] - 1) * 128.0f) + this.centerOffset[0], - ((this.rows - this.mapBounds[3] - 1) * 128.0f) + this.centerOffset[1]}; - this.shaderMapBoundsRectangle = new Rectangle(this.shaderMapBounds[0], this.shaderMapBounds[1], - this.shaderMapBounds[2] - this.shaderMapBounds[0], this.shaderMapBounds[3] - this.shaderMapBounds[1]); - this.mapSize = w3eFile.getMapSize(); - this.entireMapRectangle = new Rectangle(this.centerOffset[0], this.centerOffset[1], - (this.mapSize[0] * 128f) - 128, (this.mapSize[1] * 128f) - 128); - this.softwareGroundMesh = new SoftwareGroundMesh(this.groundHeights, this.groundCornerHeights, - this.centerOffset, width, height); + this.centerOffset = w3eFile.getCenterOffset(); + this.uberSplatModels = new LinkedHashMap<>(); + this.mapBounds = w3iFile.getCameraBoundsComplements(); + this.shaderMapBounds = new float[] { (this.mapBounds[0] * 128.0f) + this.centerOffset[0], + (this.mapBounds[2] * 128.0f) + this.centerOffset[1], + ((this.columns - this.mapBounds[1] - 1) * 128.0f) + this.centerOffset[0], + ((this.rows - this.mapBounds[3] - 1) * 128.0f) + this.centerOffset[1] }; + this.shaderMapBoundsRectangle = new Rectangle(this.shaderMapBounds[0], this.shaderMapBounds[1], + this.shaderMapBounds[2] - this.shaderMapBounds[0], this.shaderMapBounds[3] - this.shaderMapBounds[1]); + this.mapSize = w3eFile.getMapSize(); + this.entireMapRectangle = new Rectangle(this.centerOffset[0], this.centerOffset[1], + (this.mapSize[0] * 128f) - 128, (this.mapSize[1] * 128f) - 128); + this.softwareGroundMesh = new SoftwareGroundMesh(this.groundHeights, this.groundCornerHeights, + this.centerOffset, width, height); - this.testArrayBuffer = gl.glGenBuffer(); - gl.glBindBuffer(GL30.GL_ARRAY_BUFFER, this.testArrayBuffer); - gl.glBufferData(GL30.GL_ARRAY_BUFFER, this.softwareGroundMesh.vertices.length, - RenderMathUtils.wrap(this.softwareGroundMesh.vertices), GL30.GL_STATIC_DRAW); + this.testArrayBuffer = gl.glGenBuffer(); + gl.glBindBuffer(GL30.GL_ARRAY_BUFFER, this.testArrayBuffer); + gl.glBufferData(GL30.GL_ARRAY_BUFFER, this.softwareGroundMesh.vertices.length, + RenderMathUtils.wrap(this.softwareGroundMesh.vertices), GL30.GL_STATIC_DRAW); - this.testElementBuffer = gl.glGenBuffer(); + this.testElementBuffer = gl.glGenBuffer(); // gl.glBindBuffer(GL30.GL_ELEMENT_ARRAY_BUFFER, this.testElementBuffer); // gl.glBufferData(GL30.GL_ELEMENT_ARRAY_BUFFER, this.softwareGroundMesh.indices.length, // RenderMathUtils.wrap(this.softwareGroundMesh.indices), GL30.GL_STATIC_DRAW); - this.waveBuilder = new WaveBuilder(this.mapSize, this.waterTable, viewer, this.corners, this.centerOffset, - this.waterHeightOffset, w3eFile, w3iFile); - this.pathingGrid = new PathingGrid(terrainPathing, this.centerOffset); - } - - public void createWaves() { - this.waveBuilder.createWaves(this); - } - - private void updateGroundHeights(final Rectangle area) { - for (int j = (int) area.y; j < (area.y + area.height); j++) { - for (int i = (int) area.x; i < (area.x + area.width); i++) { - this.groundHeights[(j * this.columns) + i] = this.corners[i][j].getGroundHeight(); - - float rampHeight = 0f; - // Check if in one of the configurations the bottom_left is a ramp - XLoop: - for (int xOffset = -1; xOffset <= 0; xOffset++) { - for (int yOffset = -1; yOffset <= 0; yOffset++) { - if (((i + xOffset) >= 0) && ((i + xOffset) < (this.columns - 1)) && ((j + yOffset) >= 0) - && ((j + yOffset) < (this.rows - 1))) { - final RenderCorner bottomLeft = this.corners[i + xOffset][j + yOffset]; - final RenderCorner bottomRight = this.corners[i + 1 + xOffset][j + yOffset]; - final RenderCorner topLeft = this.corners[i + xOffset][j + 1 + yOffset]; - final RenderCorner topRight = this.corners[i + 1 + xOffset][j + 1 + yOffset]; - - final int base = Math.min( - Math.min(bottomLeft.getLayerHeight(), bottomRight.getLayerHeight()), - Math.min(topLeft.getLayerHeight(), topRight.getLayerHeight())); - if (this.corners[i][j].getLayerHeight() != base) { - continue; - } - - if (isCornerRampEntrance(i + xOffset, j + yOffset)) { - rampHeight = 0.5f; - break XLoop; - } - } - } - } - - final RenderCorner corner = this.corners[i][j]; - final float newGroundCornerHeight = corner.computeFinalGroundHeight() + rampHeight; - this.groundCornerHeights[(j * this.columns) + i] = newGroundCornerHeight; - corner.depth = (corner.getWater() != 0) - ? (this.waterHeightOffset + corner.getWaterHeight()) - newGroundCornerHeight - : 0; - } - } - updateGroundHeights(); - updateCornerHeights(); - } - - private void updateGroundHeights() { - Gdx.gl30.glBindTexture(GL30.GL_TEXTURE_2D, this.groundHeight); - Gdx.gl30.glTexSubImage2D(GL30.GL_TEXTURE_2D, 0, 0, 0, this.columns, this.rows, GL30.GL_RED, GL30.GL_FLOAT, - RenderMathUtils.wrap(this.groundHeights)); - } - - private void updateCornerHeights() { - final FloatBuffer groundCornerHeightsWrapped = RenderMathUtils.wrap(this.groundCornerHeights); - Gdx.gl30.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeight); - Gdx.gl30.glTexSubImage2D(GL30.GL_TEXTURE_2D, 0, 0, 0, this.columns, this.rows, GL30.GL_RED, GL30.GL_FLOAT, - groundCornerHeightsWrapped); - Gdx.gl30.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeightLinear); - Gdx.gl30.glTexSubImage2D(GL30.GL_TEXTURE_2D, 0, 0, 0, this.columns, this.rows, GL30.GL_RED, GL30.GL_FLOAT, - groundCornerHeightsWrapped); - } - - /** - * calculateRamps() is copied from Riv whereas a lot of the rest of the terrain - * was copied from HiveWE - */ - private void calculateRamps() { - final int columns = this.mapSize[0]; - final int rows = this.mapSize[1]; - - final String[] ramps = {"AAHL", "AALH", "ABHL", "AHLA", "ALHA", "ALHB", "BALH", "BHLA", "HAAL", "HBAL", "HLAA", - "HLAB", "LAAH", "LABH", "LHAA", "LHBA"}; - - // Adjust terrain height inside ramps (set rampAdjust) - for (int y = 1; y < (rows - 1); ++y) { - for (int x = 1; x < (columns - 1); ++x) { - final RenderCorner o = this.corners[x][y]; - if (!o.isRamp()) { - continue; - } - final RenderCorner a = this.corners[x - 1][y - 1]; - final RenderCorner b = this.corners[x - 1][y]; - final RenderCorner c = this.corners[x - 1][y + 1]; - final RenderCorner d = this.corners[x][y + 1]; - final RenderCorner e = this.corners[x + 1][y + 1]; - final RenderCorner f = this.corners[x + 1][y]; - final RenderCorner g = this.corners[x + 1][y - 1]; - final RenderCorner h = this.corners[x][y - 1]; - final int base = o.getLayerHeight(); - if ((b.isRamp() && f.isRamp()) || (d.isRamp() && h.isRamp())) { - float adjust = 0; - if (b.isRamp() && f.isRamp()) { - adjust = Math.max(adjust, ((b.getLayerHeight() + f.getLayerHeight()) / 2) - base); - } - if (d.isRamp() && h.isRamp()) { - adjust = Math.max(adjust, ((d.getLayerHeight() + h.getLayerHeight()) / 2) - base); - } - if (a.isRamp() && e.isRamp()) { - adjust = Math.max(adjust, (((a.getLayerHeight() + e.getLayerHeight()) / 2) - base) / 2); - } - if (c.isRamp() && g.isRamp()) { - adjust = Math.max(adjust, (((c.getLayerHeight() + g.getLayerHeight()) / 2) - base) / 2); - } - o.rampAdjust = adjust; - } - } - } - } - - /// TODO clean - /// Function is a bit of a mess - /// Updates the cliff and ramp meshes for an area - private void updateCliffMeshes(final Rectangle area) throws IOException { - // Remove all existing cliff meshes in area - for (int i = this.cliffs.size(); i-- > 0; ) { - final IVec3 pos = this.cliffs.get(i); - if (area.contains(pos.x, pos.y)) { - this.cliffs.remove(i); - } - } - - for (int i = (int) area.getX(); i < (int) (area.getX() + area.getWidth()); i++) { - for (int j = (int) area.getY(); j < (int) (area.getY() + area.getHeight()); j++) { - this.corners[i][j].romp = false; - } - } - - final Rectangle adjusted = new Rectangle(area.x - 2, area.y - 2, area.width + 4, area.height + 4); - final Rectangle rampArea = new Rectangle(); - Intersector.intersectRectangles(new Rectangle(0, 0, this.columns, this.rows), adjusted, rampArea); - - // Add new cliff meshes - final int xLimit = (int) ((rampArea.getX() + rampArea.getWidth()) - 1); - for (int i = (int) rampArea.getX(); i < xLimit; i++) { - final int yLimit = (int) ((rampArea.getY() + rampArea.getHeight()) - 1); - for (int j = (int) rampArea.getY(); j < yLimit; j++) { - final RenderCorner bottomLeft = this.corners[i][j]; - final RenderCorner bottomRight = this.corners[i + 1][j]; - final RenderCorner topLeft = this.corners[i][j + 1]; - final RenderCorner topRight = this.corners[i + 1][j + 1]; - - if (bottomLeft.cliff && !bottomLeft.hideCliff) { - final int base = Math.min(Math.min(bottomLeft.getLayerHeight(), bottomRight.getLayerHeight()), - Math.min(topLeft.getLayerHeight(), topRight.getLayerHeight())); - - final boolean facingDown = (topLeft.getLayerHeight() >= bottomLeft.getLayerHeight()) - && (topRight.getLayerHeight() >= bottomRight.getLayerHeight()); - final boolean facingLeft = (bottomRight.getLayerHeight() >= bottomLeft.getLayerHeight()) - && (topRight.getLayerHeight() >= topLeft.getLayerHeight()); - - int bottomLeftCliffTex = bottomLeft.getCliffTexture(); - if (bottomLeftCliffTex == 15) { - bottomLeftCliffTex -= 14; - } - if (!(facingDown && (j == 0)) && !(!facingDown && (j >= (this.rows - 2))) - && !(facingLeft && (i == 0)) && !(!facingLeft && (i >= (this.columns - 2)))) { - final boolean br = ((bottomLeft.getRamp() != 0) != (bottomRight.getRamp() != 0)) - && ((topLeft.getRamp() != 0) != (topRight.getRamp() != 0)) - && !this.corners[i + (bottomRight.getRamp() != 0 ? 1 : 0)][j - + (facingDown ? -1 : 1)].cliff; - - final boolean bo = ((bottomLeft.getRamp() != 0) != (topLeft.getRamp() != 0)) - && ((bottomRight.getRamp() != 0) != (topRight.getRamp() != 0)) - && !this.corners[i + (facingLeft ? -1 : 1)][j + (topLeft.getRamp() != 0 ? 1 : 0)].cliff; - - if (br || bo) { - String fileName = "" + (char) ((bottomLeft.getRamp() != 0 ? 'L' : 'A') - + ((bottomLeft.getLayerHeight() - base) * (bottomLeft.getRamp() != 0 ? -4 : 1))) - + (char) ((topLeft.getRamp() != 0 ? 'L' : 'A') - + ((topLeft.getLayerHeight() - base) * (topLeft.getRamp() != 0 ? -4 : 1))) - + (char) ((topRight.getRamp() != 0 ? 'L' : 'A') - + ((topRight.getLayerHeight() - base) * (topRight.getRamp() != 0 ? -4 : 1))) - + (char) ((bottomRight.getRamp() != 0 ? 'L' : 'A') - + ((bottomRight.getLayerHeight() - base) - * (bottomRight.getRamp() != 0 ? -4 : 1))); - - final String rampModelDir = this.cliffTextures.get(bottomLeftCliffTex).rampModelDir; - fileName = "Doodads\\Terrain\\" + rampModelDir + "\\" + rampModelDir + fileName + "0.mdx"; - - if (this.dataSource.has(fileName)) { - if (!this.pathToCliff.containsKey(fileName)) { - this.cliffMeshes.add(new CliffMesh(fileName, this.dataSource, Gdx.gl30)); - this.pathToCliff.put(fileName, this.cliffMeshes.size() - 1); - } - - for (int ji = this.cliffs.size(); ji-- > 0; ) { - final IVec3 pos = this.cliffs.get(ji); - if ((pos.x == (i + ((bo ? 1 : 0) * (facingLeft ? 0 : 1)))) - && (pos.y == (j - ((br ? 1 : 0) * (facingDown ? 1 : 0))))) { - this.cliffs.remove(ji); - break; - } - } - - this.cliffs.add(new IVec3((i + ((bo ? 1 : 0) * (facingLeft ? 0 : 1))), - (j - ((br ? 1 : 0) * (facingDown ? 1 : 0))), this.pathToCliff.get(fileName))); - bottomLeft.romp = true; - - this.corners[i + ((facingLeft ? -1 : 1) * (bo ? 1 : 0))][j - + ((facingDown ? -1 : 1) * (br ? 1 : 0))].romp = true; - - continue; - } - } - } - - if (isCornerRampEntrance(i, j)) { - continue; - } - - // Ramps move 1 right/down in some cases and thus their area is one bigger to - // the top and left. - if (!area.contains(i, j)) { - continue; - } - - // Cliff model path - - String fileName = "" + (char) (('A' + bottomLeft.getLayerHeight()) - base) - + (char) (('A' + topLeft.getLayerHeight()) - base) - + (char) (('A' + topRight.getLayerHeight()) - base) - + (char) (('A' + bottomRight.getLayerHeight()) - base); - - if ("AAAA".equals(fileName)) { - continue; - } - - // Clamp to within max variations - - fileName = this.cliffTextures.get(bottomLeftCliffTex).cliffModelDir + fileName - + Variations.getCliffVariation(this.cliffTextures.get(bottomLeftCliffTex).cliffModelDir, - fileName, bottomLeft.getCliffVariation()); - if (!this.pathToCliff.containsKey(fileName)) { - throw new IllegalArgumentException("No such pathToCliff entry: " + fileName); - } - this.cliffs.add(new IVec3(i, j, this.pathToCliff.get(fileName))); - } - } - } - - } - - public void logRomp(final int x, final int y) { - System.out.println("romp: " + this.corners[x][y].romp); - System.out.println("ramp: " + this.corners[x][y].isRamp()); - System.out.println("cliff: " + this.corners[x][y].cliff); - } - - private void updateGroundTextures(final Rectangle area) { - final Rectangle adjusted = new Rectangle(area.x - 1, area.y - 1, area.width + 2, area.height + 2); - final Rectangle updateArea = new Rectangle(); - Intersector.intersectRectangles(new Rectangle(0, 0, this.columns - 1, this.rows - 1), adjusted, updateArea); - - for (int j = (int) (updateArea.getY()); j <= (int) ((updateArea.getY() + updateArea.getHeight()) - 1); j++) { - for (int i = (int) (updateArea.getX()); i <= (int) ((updateArea.getX() + updateArea.getWidth()) - 1); i++) { - getTextureVariations(i, j, this.groundTextureList, ((j * (this.columns - 1)) + i) * 4); - - if (this.corners[i][j].cliff || this.corners[i][j].romp) { - if (isCornerRampEntrance(i, j)) { - continue; - } - this.groundTextureList[(((j * (this.columns - 1)) + i) * 4) + 3] |= 0b1000000000000000; - } - } - } - - uploadGroundTexture(); - } - - public void removeTerrainCell(final int i, final int j) { - this.groundTextureList[(((j * (this.columns - 1)) + i) * 4) + 3] |= 0b1000000000000000; - this.corners[i][j].hideCliff = true; - uploadGroundTexture(); - try { - updateCliffMeshes(new Rectangle(i - 1, j - 1, 1, 1)); // TODO does this work? - } catch (final IOException e) { - throw new RuntimeException(e); - } - } - - public void removeTerrainCellWithoutFlush(final int i, final int j) { - this.groundTextureList[(((j * (this.columns - 1)) + i) * 4) + 3] |= 0b1000000000000000; - this.corners[i][j].hideCliff = true; - } - - public void flushRemovedTerrainCells() { - uploadGroundTexture(); - try { - updateCliffMeshes(new Rectangle(0, 0, this.columns - 1, this.rows - 1)); - } catch (final IOException e) { - throw new RuntimeException(e); - } - } - - private void uploadGroundTexture() { - if (this.groundTextureData != -1) { - Gdx.gl30.glBindTexture(GL30.GL_TEXTURE_2D, this.groundTextureData); - Gdx.gl30.glTexSubImage2D(GL30.GL_TEXTURE_2D, 0, 0, 0, this.columns - 1, this.rows - 1, GL30.GL_RGBA_INTEGER, - GL30.GL_UNSIGNED_SHORT, RenderMathUtils.wrapShort(this.groundTextureList)); - } - } - - /// The 4 ground textures of the tilepoint. First 5 bits are which texture array - /// to use and the next 5 bits are which subtexture to use - private void getTextureVariations(final int x, final int y, final short[] out, final int outStartOffset) { - final int bottomLeft = realTileTexture(x, y); - final int bottomRight = realTileTexture(x + 1, y); - final int topLeft = realTileTexture(x, y + 1); - final int topRight = realTileTexture(x + 1, y + 1); - - final TreeSet set = new TreeSet<>(); - set.add(bottomLeft); - set.add(bottomRight); - set.add(topLeft); - set.add(topRight); - Arrays.fill(out, outStartOffset, outStartOffset + 4, (short) 17); - int component = outStartOffset + 1; - - final Iterator iterator = set.iterator(); - iterator.hasNext(); - final short firstValue = iterator.next().shortValue(); - out[outStartOffset] = (short) (firstValue - + (getVariation(firstValue, this.corners[x][y].getGroundVariation()) << 5)); - - int index; - while (iterator.hasNext()) { - index = 0; - final int texture = iterator.next().intValue(); - index |= (bottomRight == texture ? 1 : 0) << 0; - index |= (bottomLeft == texture ? 1 : 0) << 1; - index |= (topRight == texture ? 1 : 0) << 2; - index |= (topLeft == texture ? 1 : 0) << 3; - - out[component++] = (short) (texture + (index << 5)); - } - } - - private int realTileTexture(final int x, final int y) { - ILoop: - for (int i = -1; i < 1; i++) { - for (int j = -1; j < 1; j++) { - if (((x + i) >= 0) && ((x + i) < this.columns) && ((y + j) >= 0) && ((y + j) < this.rows)) { - if (this.corners[x + i][y + j].cliff) { - if (((x + i) < (this.columns - 1)) && ((y + j) < (this.rows - 1))) { - final RenderCorner bottomLeft = this.corners[x + i][y + j]; - final RenderCorner bottomRight = this.corners[x + i + 1][y + j]; - final RenderCorner topLeft = this.corners[x + i][y + j + 1]; - final RenderCorner topRight = this.corners[x + i + 1][y + j + 1]; - - if ((bottomLeft.getRamp() != 0) && (topLeft.getRamp() != 0) && (bottomRight.getRamp() != 0) - && (topRight.getRamp() != 0) && (!bottomLeft.romp) && (!bottomRight.romp) - && (!topLeft.romp) && (!topRight.romp)) { - break ILoop; - } - } - } - if (this.corners[x + i][y + j].romp || this.corners[x + i][y + j].cliff) { - int texture = this.corners[x + i][y + j].getCliffTexture(); - // Number 15 seems to be something - if (texture == 15) { - texture -= 14; - } - - return this.cliffToGroundTexture.get(texture); - } - } - } - } - - if (this.corners[x][y].getBlight() != 0) { - return this.blightTextureIndex; - } - - return this.corners[x][y].getGroundTexture(); - } - - private boolean isCornerRampEntrance(final int x, final int y) { - if ((x == this.columns) || (y == this.rows)) { - return false; - } - - final RenderCorner bottomLeft = this.corners[x][y]; - final RenderCorner bottomRight = this.corners[x + 1][y]; - final RenderCorner topLeft = this.corners[x][y + 1]; - final RenderCorner topRight = this.corners[x + 1][y + 1]; - - return (bottomLeft.getRamp() != 0) && (topLeft.getRamp() != 0) && (bottomRight.getRamp() != 0) - && (topRight.getRamp() != 0) && !((bottomLeft.getLayerHeight() == topRight.getLayerHeight()) - && (topLeft.getLayerHeight() == bottomRight.getLayerHeight())); - } - - private static void loadWaterColor(final float[] out, final String prefix, final Element waterInfo) { - for (int i = 0; i < colorTags.length; i++) { - final String colorTag = colorTags[i]; - out[i] = waterInfo == null ? 0.0f : waterInfo.getFieldFloatValue(prefix + "_" + colorTag) / 255f; - } - } - - public short getVariation(final int groundTexture, final int variation) { - final GroundTexture texture = this.groundTextures.get(groundTexture); - - // Extended ? - if (texture.extended) { - if (variation <= 15) { - return (short) (16 + variation); - } else if (variation == 16) { - return 15; - } else { - return 0; - } - } else { - if (variation == 0) { - return 0; - } else { - return 15; - } - } - } - - public void update() { - this.waterIndex += this.waterIncreasePerFrame; - - if (this.waterIndex >= this.waterTextureCount) { - this.waterIndex = 0; - } - } - - public void renderGround(final DynamicShadowManager dynamicShadowManager) { - // Render tiles - - this.webGL.useShaderProgram(this.groundShader); - - final GL30 gl = Gdx.gl30; - gl.glEnable(GL20.GL_CULL_FACE); - gl.glDisable(GL30.GL_BLEND); - gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA); - gl.glEnable(GL20.GL_DEPTH_TEST); - gl.glDepthMask(true); - - gl.glUniformMatrix4fv(this.groundShader.getUniformLocation("MVP"), 1, false, - this.camera.viewProjectionMatrix.val, 0); - gl.glUniform1i(this.groundShader.getUniformLocation("show_pathing_map"), this.viewer.renderPathing); - gl.glUniform1i(this.groundShader.getUniformLocation("show_lighting"), this.viewer.renderLighting); - gl.glUniform1i(this.groundShader.getUniformLocation("height_texture"), 0); - gl.glUniform1i(this.groundShader.getUniformLocation("height_cliff_texture"), 1); - gl.glUniform1i(this.groundShader.getUniformLocation("terrain_texture_list"), 2); - gl.glUniform1i(this.groundShader.getUniformLocation("shadowMap"), 20); - gl.glUniform1f(this.groundShader.getUniformLocation("centerOffsetX"), this.centerOffset[0]); - gl.glUniform1f(this.groundShader.getUniformLocation("centerOffsetY"), this.centerOffset[1]); - - final W3xSceneLightManager lightManager = (W3xSceneLightManager) this.viewer.worldScene.getLightManager(); - final DataTexture unitLightsTexture = lightManager.getTerrainLightsTexture(); - - unitLightsTexture.bind(21); - gl.glUniform1i(this.groundShader.getUniformLocation("lightTexture"), 21); - gl.glUniform1f(this.groundShader.getUniformLocation("lightCount"), lightManager.getTerrainLightCount()); - gl.glUniform1f(this.groundShader.getUniformLocation("lightTextureHeight"), unitLightsTexture.getHeight()); - - gl.glUniformMatrix4fv(this.groundShader.getUniformLocation("DepthBiasMVP"), 1, false, - dynamicShadowManager.getDepthBiasMVP().val, 0); - - gl.glActiveTexture(GL30.GL_TEXTURE0); - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundHeight); - - gl.glActiveTexture(GL30.GL_TEXTURE1); - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeight); - - gl.glActiveTexture(GL30.GL_TEXTURE2); - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundTextureData); - - for (int i = 0; i < this.groundTextures.size(); i++) { - gl.glActiveTexture(GL30.GL_TEXTURE3 + i); - gl.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.groundTextures.get(i).id); - } + this.waveBuilder = new WaveBuilder(this.mapSize, this.waterTable, viewer, this.corners, this.centerOffset, + this.waterHeightOffset, w3eFile, w3iFile); + this.pathingGrid = new PathingGrid(terrainPathing, this.centerOffset); + } + + public void createWaves() { + this.waveBuilder.createWaves(this); + } + + private void updateGroundHeights(final Rectangle area) { + for (int j = (int) area.y; j < (area.y + area.height); j++) { + for (int i = (int) area.x; i < (area.x + area.width); i++) { + this.groundHeights[(j * this.columns) + i] = this.corners[i][j].getGroundHeight(); + + float rampHeight = 0f; + // Check if in one of the configurations the bottom_left is a ramp + XLoop: for (int xOffset = -1; xOffset <= 0; xOffset++) { + for (int yOffset = -1; yOffset <= 0; yOffset++) { + if (((i + xOffset) >= 0) && ((i + xOffset) < (this.columns - 1)) && ((j + yOffset) >= 0) + && ((j + yOffset) < (this.rows - 1))) { + final RenderCorner bottomLeft = this.corners[i + xOffset][j + yOffset]; + final RenderCorner bottomRight = this.corners[i + 1 + xOffset][j + yOffset]; + final RenderCorner topLeft = this.corners[i + xOffset][j + 1 + yOffset]; + final RenderCorner topRight = this.corners[i + 1 + xOffset][j + 1 + yOffset]; + + final int base = Math.min( + Math.min(bottomLeft.getLayerHeight(), bottomRight.getLayerHeight()), + Math.min(topLeft.getLayerHeight(), topRight.getLayerHeight())); + if (this.corners[i][j].getLayerHeight() != base) { + continue; + } + + if (isCornerRampEntrance(i + xOffset, j + yOffset)) { + rampHeight = 0.5f; + break XLoop; + } + } + } + } + + final RenderCorner corner = this.corners[i][j]; + final float newGroundCornerHeight = corner.computeFinalGroundHeight() + rampHeight; + this.groundCornerHeights[(j * this.columns) + i] = newGroundCornerHeight; + corner.depth = (corner.getWater() != 0) + ? (this.waterHeightOffset + corner.getWaterHeight()) - newGroundCornerHeight + : 0; + } + } + updateGroundHeights(); + updateCornerHeights(); + } + + private void updateGroundHeights() { + Gdx.gl30.glBindTexture(GL30.GL_TEXTURE_2D, this.groundHeight); + Gdx.gl30.glTexSubImage2D(GL30.GL_TEXTURE_2D, 0, 0, 0, this.columns, this.rows, GL30.GL_RED, GL30.GL_FLOAT, + RenderMathUtils.wrap(this.groundHeights)); + } + + private void updateCornerHeights() { + final FloatBuffer groundCornerHeightsWrapped = RenderMathUtils.wrap(this.groundCornerHeights); + Gdx.gl30.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeight); + Gdx.gl30.glTexSubImage2D(GL30.GL_TEXTURE_2D, 0, 0, 0, this.columns, this.rows, GL30.GL_RED, GL30.GL_FLOAT, + groundCornerHeightsWrapped); + Gdx.gl30.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeightLinear); + Gdx.gl30.glTexSubImage2D(GL30.GL_TEXTURE_2D, 0, 0, 0, this.columns, this.rows, GL30.GL_RED, GL30.GL_FLOAT, + groundCornerHeightsWrapped); + } + + /** + * calculateRamps() is copied from Riv whereas a lot of the rest of the terrain + * was copied from HiveWE + */ + private void calculateRamps() { + final int columns = this.mapSize[0]; + final int rows = this.mapSize[1]; + + final String[] ramps = { "AAHL", "AALH", "ABHL", "AHLA", "ALHA", "ALHB", "BALH", "BHLA", "HAAL", "HBAL", "HLAA", + "HLAB", "LAAH", "LABH", "LHAA", "LHBA" }; + + // Adjust terrain height inside ramps (set rampAdjust) + for (int y = 1; y < (rows - 1); ++y) { + for (int x = 1; x < (columns - 1); ++x) { + final RenderCorner o = this.corners[x][y]; + if (!o.isRamp()) { + continue; + } + final RenderCorner a = this.corners[x - 1][y - 1]; + final RenderCorner b = this.corners[x - 1][y]; + final RenderCorner c = this.corners[x - 1][y + 1]; + final RenderCorner d = this.corners[x][y + 1]; + final RenderCorner e = this.corners[x + 1][y + 1]; + final RenderCorner f = this.corners[x + 1][y]; + final RenderCorner g = this.corners[x + 1][y - 1]; + final RenderCorner h = this.corners[x][y - 1]; + final int base = o.getLayerHeight(); + if ((b.isRamp() && f.isRamp()) || (d.isRamp() && h.isRamp())) { + float adjust = 0; + if (b.isRamp() && f.isRamp()) { + adjust = Math.max(adjust, ((b.getLayerHeight() + f.getLayerHeight()) / 2) - base); + } + if (d.isRamp() && h.isRamp()) { + adjust = Math.max(adjust, ((d.getLayerHeight() + h.getLayerHeight()) / 2) - base); + } + if (a.isRamp() && e.isRamp()) { + adjust = Math.max(adjust, (((a.getLayerHeight() + e.getLayerHeight()) / 2) - base) / 2); + } + if (c.isRamp() && g.isRamp()) { + adjust = Math.max(adjust, (((c.getLayerHeight() + g.getLayerHeight()) / 2) - base) / 2); + } + o.rampAdjust = adjust; + } + } + } + } + + /// TODO clean + /// Function is a bit of a mess + /// Updates the cliff and ramp meshes for an area + private void updateCliffMeshes(final Rectangle area) throws IOException { + // Remove all existing cliff meshes in area + for (int i = this.cliffs.size(); i-- > 0;) { + final IVec3 pos = this.cliffs.get(i); + if (area.contains(pos.x, pos.y)) { + this.cliffs.remove(i); + } + } + + for (int i = (int) area.getX(); i < (int) (area.getX() + area.getWidth()); i++) { + for (int j = (int) area.getY(); j < (int) (area.getY() + area.getHeight()); j++) { + this.corners[i][j].romp = false; + } + } + + final Rectangle adjusted = new Rectangle(area.x - 2, area.y - 2, area.width + 4, area.height + 4); + final Rectangle rampArea = new Rectangle(); + Intersector.intersectRectangles(new Rectangle(0, 0, this.columns, this.rows), adjusted, rampArea); + + // Add new cliff meshes + final int xLimit = (int) ((rampArea.getX() + rampArea.getWidth()) - 1); + for (int i = (int) rampArea.getX(); i < xLimit; i++) { + final int yLimit = (int) ((rampArea.getY() + rampArea.getHeight()) - 1); + for (int j = (int) rampArea.getY(); j < yLimit; j++) { + final RenderCorner bottomLeft = this.corners[i][j]; + final RenderCorner bottomRight = this.corners[i + 1][j]; + final RenderCorner topLeft = this.corners[i][j + 1]; + final RenderCorner topRight = this.corners[i + 1][j + 1]; + + if (bottomLeft.cliff && !bottomLeft.hideCliff) { + final int base = Math.min(Math.min(bottomLeft.getLayerHeight(), bottomRight.getLayerHeight()), + Math.min(topLeft.getLayerHeight(), topRight.getLayerHeight())); + + final boolean facingDown = (topLeft.getLayerHeight() >= bottomLeft.getLayerHeight()) + && (topRight.getLayerHeight() >= bottomRight.getLayerHeight()); + final boolean facingLeft = (bottomRight.getLayerHeight() >= bottomLeft.getLayerHeight()) + && (topRight.getLayerHeight() >= topLeft.getLayerHeight()); + + int bottomLeftCliffTex = bottomLeft.getCliffTexture(); + if (bottomLeftCliffTex == 15) { + bottomLeftCliffTex -= 14; + } + if (!(facingDown && (j == 0)) && !(!facingDown && (j >= (this.rows - 2))) + && !(facingLeft && (i == 0)) && !(!facingLeft && (i >= (this.columns - 2)))) { + final boolean br = ((bottomLeft.getRamp() != 0) != (bottomRight.getRamp() != 0)) + && ((topLeft.getRamp() != 0) != (topRight.getRamp() != 0)) + && !this.corners[i + (bottomRight.getRamp() != 0 ? 1 : 0)][j + + (facingDown ? -1 : 1)].cliff; + + final boolean bo = ((bottomLeft.getRamp() != 0) != (topLeft.getRamp() != 0)) + && ((bottomRight.getRamp() != 0) != (topRight.getRamp() != 0)) + && !this.corners[i + (facingLeft ? -1 : 1)][j + (topLeft.getRamp() != 0 ? 1 : 0)].cliff; + + if (br || bo) { + String fileName = "" + (char) ((bottomLeft.getRamp() != 0 ? 'L' : 'A') + + ((bottomLeft.getLayerHeight() - base) * (bottomLeft.getRamp() != 0 ? -4 : 1))) + + (char) ((topLeft.getRamp() != 0 ? 'L' : 'A') + + ((topLeft.getLayerHeight() - base) * (topLeft.getRamp() != 0 ? -4 : 1))) + + (char) ((topRight.getRamp() != 0 ? 'L' : 'A') + + ((topRight.getLayerHeight() - base) * (topRight.getRamp() != 0 ? -4 : 1))) + + (char) ((bottomRight.getRamp() != 0 ? 'L' : 'A') + + ((bottomRight.getLayerHeight() - base) + * (bottomRight.getRamp() != 0 ? -4 : 1))); + + final String rampModelDir = this.cliffTextures.get(bottomLeftCliffTex).rampModelDir; + fileName = "Doodads\\Terrain\\" + rampModelDir + "\\" + rampModelDir + fileName + "0.mdx"; + + if (this.dataSource.has(fileName)) { + if (!this.pathToCliff.containsKey(fileName)) { + this.cliffMeshes.add(new CliffMesh(fileName, this.dataSource, Gdx.gl30)); + this.pathToCliff.put(fileName, this.cliffMeshes.size() - 1); + } + + for (int ji = this.cliffs.size(); ji-- > 0;) { + final IVec3 pos = this.cliffs.get(ji); + if ((pos.x == (i + ((bo ? 1 : 0) * (facingLeft ? 0 : 1)))) + && (pos.y == (j - ((br ? 1 : 0) * (facingDown ? 1 : 0))))) { + this.cliffs.remove(ji); + break; + } + } + + this.cliffs.add(new IVec3((i + ((bo ? 1 : 0) * (facingLeft ? 0 : 1))), + (j - ((br ? 1 : 0) * (facingDown ? 1 : 0))), this.pathToCliff.get(fileName))); + bottomLeft.romp = true; + + this.corners[i + ((facingLeft ? -1 : 1) * (bo ? 1 : 0))][j + + ((facingDown ? -1 : 1) * (br ? 1 : 0))].romp = true; + + continue; + } + } + } + + if (isCornerRampEntrance(i, j)) { + continue; + } + + // Ramps move 1 right/down in some cases and thus their area is one bigger to + // the top and left. + if (!area.contains(i, j)) { + continue; + } + + // Cliff model path + + String fileName = "" + (char) (('A' + bottomLeft.getLayerHeight()) - base) + + (char) (('A' + topLeft.getLayerHeight()) - base) + + (char) (('A' + topRight.getLayerHeight()) - base) + + (char) (('A' + bottomRight.getLayerHeight()) - base); + + if ("AAAA".equals(fileName)) { + continue; + } + + // Clamp to within max variations + + fileName = this.cliffTextures.get(bottomLeftCliffTex).cliffModelDir + fileName + + Variations.getCliffVariation(this.cliffTextures.get(bottomLeftCliffTex).cliffModelDir, + fileName, bottomLeft.getCliffVariation()); + if (!this.pathToCliff.containsKey(fileName)) { + throw new IllegalArgumentException("No such pathToCliff entry: " + fileName); + } + this.cliffs.add(new IVec3(i, j, this.pathToCliff.get(fileName))); + } + } + } + + } + + public void logRomp(final int x, final int y) { + System.out.println("romp: " + this.corners[x][y].romp); + System.out.println("ramp: " + this.corners[x][y].isRamp()); + System.out.println("cliff: " + this.corners[x][y].cliff); + } + + private void updateGroundTextures(final Rectangle area) { + final Rectangle adjusted = new Rectangle(area.x - 1, area.y - 1, area.width + 2, area.height + 2); + final Rectangle updateArea = new Rectangle(); + Intersector.intersectRectangles(new Rectangle(0, 0, this.columns - 1, this.rows - 1), adjusted, updateArea); + + for (int j = (int) (updateArea.getY()); j <= (int) ((updateArea.getY() + updateArea.getHeight()) - 1); j++) { + for (int i = (int) (updateArea.getX()); i <= (int) ((updateArea.getX() + updateArea.getWidth()) - 1); i++) { + getTextureVariations(i, j, this.groundTextureList, ((j * (this.columns - 1)) + i) * 4); + + if (this.corners[i][j].cliff || this.corners[i][j].romp) { + if (isCornerRampEntrance(i, j)) { + continue; + } + this.groundTextureList[(((j * (this.columns - 1)) + i) * 4) + 3] |= 0b1000000000000000; + } + } + } + + uploadGroundTexture(); + } + + public void removeTerrainCell(final int i, final int j) { + this.groundTextureList[(((j * (this.columns - 1)) + i) * 4) + 3] |= 0b1000000000000000; + this.corners[i][j].hideCliff = true; + uploadGroundTexture(); + try { + updateCliffMeshes(new Rectangle(i - 1, j - 1, 1, 1)); // TODO does this work? + } + catch (final IOException e) { + throw new RuntimeException(e); + } + } + + public void removeTerrainCellWithoutFlush(final int i, final int j) { + this.groundTextureList[(((j * (this.columns - 1)) + i) * 4) + 3] |= 0b1000000000000000; + this.corners[i][j].hideCliff = true; + } + + public void flushRemovedTerrainCells() { + uploadGroundTexture(); + try { + updateCliffMeshes(new Rectangle(0, 0, this.columns - 1, this.rows - 1)); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + } + + private void uploadGroundTexture() { + if (this.groundTextureData != -1) { + Gdx.gl30.glBindTexture(GL30.GL_TEXTURE_2D, this.groundTextureData); + Gdx.gl30.glTexSubImage2D(GL30.GL_TEXTURE_2D, 0, 0, 0, this.columns - 1, this.rows - 1, GL30.GL_RGBA_INTEGER, + GL30.GL_UNSIGNED_SHORT, RenderMathUtils.wrapShort(this.groundTextureList)); + } + } + + /// The 4 ground textures of the tilepoint. First 5 bits are which texture array + /// to use and the next 5 bits are which subtexture to use + private void getTextureVariations(final int x, final int y, final short[] out, final int outStartOffset) { + final int bottomLeft = realTileTexture(x, y); + final int bottomRight = realTileTexture(x + 1, y); + final int topLeft = realTileTexture(x, y + 1); + final int topRight = realTileTexture(x + 1, y + 1); + + final TreeSet set = new TreeSet<>(); + set.add(bottomLeft); + set.add(bottomRight); + set.add(topLeft); + set.add(topRight); + Arrays.fill(out, outStartOffset, outStartOffset + 4, (short) 17); + int component = outStartOffset + 1; + + final Iterator iterator = set.iterator(); + iterator.hasNext(); + final short firstValue = iterator.next().shortValue(); + out[outStartOffset] = (short) (firstValue + + (getVariation(firstValue, this.corners[x][y].getGroundVariation()) << 5)); + + int index; + while (iterator.hasNext()) { + index = 0; + final int texture = iterator.next().intValue(); + index |= (bottomRight == texture ? 1 : 0) << 0; + index |= (bottomLeft == texture ? 1 : 0) << 1; + index |= (topRight == texture ? 1 : 0) << 2; + index |= (topLeft == texture ? 1 : 0) << 3; + + out[component++] = (short) (texture + (index << 5)); + } + } + + private int realTileTexture(final int x, final int y) { + ILoop: for (int i = -1; i < 1; i++) { + for (int j = -1; j < 1; j++) { + if (((x + i) >= 0) && ((x + i) < this.columns) && ((y + j) >= 0) && ((y + j) < this.rows)) { + if (this.corners[x + i][y + j].cliff) { + if (((x + i) < (this.columns - 1)) && ((y + j) < (this.rows - 1))) { + final RenderCorner bottomLeft = this.corners[x + i][y + j]; + final RenderCorner bottomRight = this.corners[x + i + 1][y + j]; + final RenderCorner topLeft = this.corners[x + i][y + j + 1]; + final RenderCorner topRight = this.corners[x + i + 1][y + j + 1]; + + if ((bottomLeft.getRamp() != 0) && (topLeft.getRamp() != 0) && (bottomRight.getRamp() != 0) + && (topRight.getRamp() != 0) && (!bottomLeft.romp) && (!bottomRight.romp) + && (!topLeft.romp) && (!topRight.romp)) { + break ILoop; + } + } + } + if (this.corners[x + i][y + j].romp || this.corners[x + i][y + j].cliff) { + int texture = this.corners[x + i][y + j].getCliffTexture(); + // Number 15 seems to be something + if (texture == 15) { + texture -= 14; + } + + return this.cliffToGroundTexture.get(texture); + } + } + } + } + + if (this.corners[x][y].getBlight() != 0) { + return this.blightTextureIndex; + } + + return this.corners[x][y].getGroundTexture(); + } + + private boolean isCornerRampEntrance(final int x, final int y) { + if ((x == this.columns) || (y == this.rows)) { + return false; + } + + final RenderCorner bottomLeft = this.corners[x][y]; + final RenderCorner bottomRight = this.corners[x + 1][y]; + final RenderCorner topLeft = this.corners[x][y + 1]; + final RenderCorner topRight = this.corners[x + 1][y + 1]; + + return (bottomLeft.getRamp() != 0) && (topLeft.getRamp() != 0) && (bottomRight.getRamp() != 0) + && (topRight.getRamp() != 0) && !((bottomLeft.getLayerHeight() == topRight.getLayerHeight()) + && (topLeft.getLayerHeight() == bottomRight.getLayerHeight())); + } + + private static void loadWaterColor(final float[] out, final String prefix, final Element waterInfo) { + for (int i = 0; i < colorTags.length; i++) { + final String colorTag = colorTags[i]; + out[i] = waterInfo == null ? 0.0f : waterInfo.getFieldFloatValue(prefix + "_" + colorTag) / 255f; + } + } + + public short getVariation(final int groundTexture, final int variation) { + final GroundTexture texture = this.groundTextures.get(groundTexture); + + // Extended ? + if (texture.extended) { + if (variation <= 15) { + return (short) (16 + variation); + } + else if (variation == 16) { + return 15; + } + else { + return 0; + } + } + else { + if (variation == 0) { + return 0; + } + else { + return 15; + } + } + } + + public void update() { + this.waterIndex += this.waterIncreasePerFrame; + + if (this.waterIndex >= this.waterTextureCount) { + this.waterIndex = 0; + } + } + + public void renderGround(final DynamicShadowManager dynamicShadowManager) { + // Render tiles + + this.webGL.useShaderProgram(this.groundShader); + + final GL30 gl = Gdx.gl30; + gl.glEnable(GL20.GL_CULL_FACE); + gl.glDisable(GL30.GL_BLEND); + gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA); + gl.glEnable(GL20.GL_DEPTH_TEST); + gl.glDepthMask(true); + + gl.glUniformMatrix4fv(this.groundShader.getUniformLocation("MVP"), 1, false, + this.camera.viewProjectionMatrix.val, 0); + gl.glUniform1i(this.groundShader.getUniformLocation("show_pathing_map"), this.viewer.renderPathing); + gl.glUniform1i(this.groundShader.getUniformLocation("show_lighting"), this.viewer.renderLighting); + gl.glUniform1i(this.groundShader.getUniformLocation("height_texture"), 0); + gl.glUniform1i(this.groundShader.getUniformLocation("height_cliff_texture"), 1); + gl.glUniform1i(this.groundShader.getUniformLocation("terrain_texture_list"), 2); + gl.glUniform1i(this.groundShader.getUniformLocation("shadowMap"), 20); + gl.glUniform1f(this.groundShader.getUniformLocation("centerOffsetX"), this.centerOffset[0]); + gl.glUniform1f(this.groundShader.getUniformLocation("centerOffsetY"), this.centerOffset[1]); + + final W3xSceneLightManager lightManager = (W3xSceneLightManager) this.viewer.worldScene.getLightManager(); + final DataTexture unitLightsTexture = lightManager.getTerrainLightsTexture(); + + unitLightsTexture.bind(21); + gl.glUniform1i(this.groundShader.getUniformLocation("lightTexture"), 21); + gl.glUniform1f(this.groundShader.getUniformLocation("lightCount"), lightManager.getTerrainLightCount()); + gl.glUniform1f(this.groundShader.getUniformLocation("lightTextureHeight"), unitLightsTexture.getHeight()); + + gl.glUniformMatrix4fv(this.groundShader.getUniformLocation("DepthBiasMVP"), 1, false, + dynamicShadowManager.getDepthBiasMVP().val, 0); + + gl.glActiveTexture(GL30.GL_TEXTURE0); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundHeight); + + gl.glActiveTexture(GL30.GL_TEXTURE1); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeight); + + gl.glActiveTexture(GL30.GL_TEXTURE2); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundTextureData); + + for (int i = 0; i < this.groundTextures.size(); i++) { + gl.glActiveTexture(GL30.GL_TEXTURE3 + i); + gl.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.groundTextures.get(i).id); + } // gl.glActiveTexture(GL30.GL_TEXTURE20, /*pathingMap.getTextureStatic()*/); // gl.glActiveTexture(GL30.GL_TEXTURE21, /*pathingMap.getTextureDynamic()*/); - gl.glActiveTexture(GL30.GL_TEXTURE20); - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.shadowMap); + gl.glActiveTexture(GL30.GL_TEXTURE20); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.shadowMap); // gl.glEnableVertexAttribArray(0); - gl.glBindBuffer(GL30.GL_ARRAY_BUFFER, Shapes.INSTANCE.vertexBuffer); - gl.glVertexAttribPointer(0, 2, GL30.GL_FLOAT, false, 0, 0); + gl.glBindBuffer(GL30.GL_ARRAY_BUFFER, Shapes.INSTANCE.vertexBuffer); + gl.glVertexAttribPointer(0, 2, GL30.GL_FLOAT, false, 0, 0); - gl.glBindBuffer(GL30.GL_ELEMENT_ARRAY_BUFFER, Shapes.INSTANCE.indexBuffer); - if (WIREFRAME_TERRAIN) { - Extensions.wireframeExtension.glPolygonMode(GL20.GL_FRONT_AND_BACK, Extensions.GL_LINE); - } - gl.glDrawElementsInstanced(GL30.GL_TRIANGLES, Shapes.INSTANCE.quadIndices.length * 3, GL30.GL_UNSIGNED_INT, 0, - (this.columns - 1) * (this.rows - 1)); - if (WIREFRAME_TERRAIN) { - Extensions.wireframeExtension.glPolygonMode(GL20.GL_FRONT_AND_BACK, Extensions.GL_FILL); - } + gl.glBindBuffer(GL30.GL_ELEMENT_ARRAY_BUFFER, Shapes.INSTANCE.indexBuffer); + if (WIREFRAME_TERRAIN) { + Extensions.wireframeExtension.glPolygonMode(GL20.GL_FRONT_AND_BACK, Extensions.GL_LINE); + } + gl.glDrawElementsInstanced(GL30.GL_TRIANGLES, Shapes.INSTANCE.quadIndices.length * 3, GL30.GL_UNSIGNED_INT, 0, + (this.columns - 1) * (this.rows - 1)); + if (WIREFRAME_TERRAIN) { + Extensions.wireframeExtension.glPolygonMode(GL20.GL_FRONT_AND_BACK, Extensions.GL_FILL); + } // gl.glDisableVertexAttribArray(0); - gl.glEnable(GL30.GL_BLEND); + gl.glEnable(GL30.GL_BLEND); - } + } - private GL30 renderGroundIntersectionMesh() { - if (true) { - throw new UnsupportedOperationException("No longer supported"); - } - this.webGL.useShaderProgram(this.testShader); + private GL30 renderGroundIntersectionMesh() { + if (true) { + throw new UnsupportedOperationException("No longer supported"); + } + this.webGL.useShaderProgram(this.testShader); - final GL30 gl = Gdx.gl30; - gl.glDisable(GL30.GL_CULL_FACE); - gl.glDisable(GL30.GL_BLEND); - this.testShader.setUniformMatrix("MVP", this.camera.viewProjectionMatrix); - gl.glBindBuffer(GL30.GL_ARRAY_BUFFER, this.testArrayBuffer); - this.testShader.setVertexAttribute("vPosition", 3, GL30.GL_FLOAT, false, 12, 0); + final GL30 gl = Gdx.gl30; + gl.glDisable(GL30.GL_CULL_FACE); + gl.glDisable(GL30.GL_BLEND); + this.testShader.setUniformMatrix("MVP", this.camera.viewProjectionMatrix); + gl.glBindBuffer(GL30.GL_ARRAY_BUFFER, this.testArrayBuffer); + this.testShader.setVertexAttribute("vPosition", 3, GL30.GL_FLOAT, false, 12, 0); - gl.glBindBuffer(GL30.GL_ELEMENT_ARRAY_BUFFER, this.testElementBuffer); - gl.glDrawElements(GL30.GL_LINES, this.softwareGroundMesh.indices.length, GL30.GL_UNSIGNED_SHORT, 0);// ); + gl.glBindBuffer(GL30.GL_ELEMENT_ARRAY_BUFFER, this.testElementBuffer); + gl.glDrawElements(GL30.GL_LINES, this.softwareGroundMesh.indices.length, GL30.GL_UNSIGNED_SHORT, 0);// ); - gl.glEnable(GL30.GL_BLEND); - return gl; - } + gl.glEnable(GL30.GL_BLEND); + return gl; + } - public void renderUberSplats() { - final GL30 gl = Gdx.gl30; - final WebGL webGL = this.webGL; - final ShaderProgram shader = this.uberSplatShader; + public void renderUberSplats() { + final GL30 gl = Gdx.gl30; + final WebGL webGL = this.webGL; + final ShaderProgram shader = this.uberSplatShader; - gl.glDepthMask(false); - gl.glEnable(GL30.GL_BLEND); - gl.glBlendFunc(GL30.GL_SRC_ALPHA, GL30.GL_ONE_MINUS_SRC_ALPHA); - gl.glBlendEquation(GL30.GL_FUNC_ADD); + gl.glDepthMask(false); + gl.glEnable(GL30.GL_BLEND); + gl.glBlendFunc(GL30.GL_SRC_ALPHA, GL30.GL_ONE_MINUS_SRC_ALPHA); + gl.glBlendEquation(GL30.GL_FUNC_ADD); - webGL.useShaderProgram(this.uberSplatShader); + webGL.useShaderProgram(this.uberSplatShader); - shader.setUniformMatrix("u_mvp", this.camera.viewProjectionMatrix); - shader.setUniformi("u_heightMap", 0); - sizeHeap[0] = this.columns - 1; - sizeHeap[1] = this.rows - 1; - shader.setUniform2fv("u_size", sizeHeap, 0, 2); - sizeHeap[0] = 1 / (float) this.columns; - sizeHeap[1] = 1 / (float) this.rows; - shader.setUniform2fv("u_pixel", sizeHeap, 0, 2); - shader.setUniform2fv("u_centerOffset", this.centerOffset, 0, 2); - shader.setUniformi("u_texture", 1); - shader.setUniformi("u_shadowMap", 2); + shader.setUniformMatrix("u_mvp", this.camera.viewProjectionMatrix); + shader.setUniformi("u_heightMap", 0); + sizeHeap[0] = this.columns - 1; + sizeHeap[1] = this.rows - 1; + shader.setUniform2fv("u_size", sizeHeap, 0, 2); + sizeHeap[0] = 1 / (float) this.columns; + sizeHeap[1] = 1 / (float) this.rows; + shader.setUniform2fv("u_pixel", sizeHeap, 0, 2); + shader.setUniform2fv("u_centerOffset", this.centerOffset, 0, 2); + shader.setUniformi("u_texture", 1); + shader.setUniformi("u_shadowMap", 2); - gl.glActiveTexture(GL30.GL_TEXTURE0); - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeightLinear); + gl.glActiveTexture(GL30.GL_TEXTURE0); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeightLinear); - gl.glActiveTexture(GL30.GL_TEXTURE2); - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.shadowMap); + gl.glActiveTexture(GL30.GL_TEXTURE2); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.shadowMap); - final W3xSceneLightManager lightManager = (W3xSceneLightManager) this.viewer.worldScene.getLightManager(); - final DataTexture terrainLightsTexture = lightManager.getTerrainLightsTexture(); + final W3xSceneLightManager lightManager = (W3xSceneLightManager) this.viewer.worldScene.getLightManager(); + final DataTexture terrainLightsTexture = lightManager.getTerrainLightsTexture(); - terrainLightsTexture.bind(21); - gl.glUniform1i(shader.getUniformLocation("u_lightTexture"), 21); - gl.glUniform1f(shader.getUniformLocation("u_lightCount"), lightManager.getTerrainLightCount()); - gl.glUniform1f(shader.getUniformLocation("u_lightTextureHeight"), terrainLightsTexture.getHeight()); + terrainLightsTexture.bind(21); + gl.glUniform1i(shader.getUniformLocation("u_lightTexture"), 21); + gl.glUniform1f(shader.getUniformLocation("u_lightCount"), lightManager.getTerrainLightCount()); + gl.glUniform1f(shader.getUniformLocation("u_lightTextureHeight"), terrainLightsTexture.getHeight()); - // Render the cliffs - for (final SplatModel splat : this.uberSplatModels.values()) { - splat.render(gl, shader); - } - } + // Render the cliffs + for (final SplatModel splat : this.uberSplatModels.values()) { + splat.render(gl, shader); + } + } - public void renderWater() { - // Render water - this.webGL.useShaderProgram(this.waterShader); + public void renderWater() { + // Render water + this.webGL.useShaderProgram(this.waterShader); - final GL30 gl = Gdx.gl30; - gl.glDepthMask(false); - gl.glDisable(GL30.GL_CULL_FACE); - gl.glEnable(GL30.GL_BLEND); - gl.glBlendFunc(GL30.GL_SRC_ALPHA, GL30.GL_ONE_MINUS_SRC_ALPHA); + final GL30 gl = Gdx.gl30; + gl.glDepthMask(false); + gl.glDisable(GL30.GL_CULL_FACE); + gl.glEnable(GL30.GL_BLEND); + gl.glBlendFunc(GL30.GL_SRC_ALPHA, GL30.GL_ONE_MINUS_SRC_ALPHA); - gl.glUniformMatrix4fv(0, 1, false, this.camera.viewProjectionMatrix.val, 0); - gl.glUniform4fv(1, 1, this.minShallowColor, 0); - gl.glUniform4fv(2, 1, this.maxShallowColor, 0); - gl.glUniform4fv(3, 1, this.minDeepColor, 0); - gl.glUniform4fv(4, 1, this.maxDeepColor, 0); - gl.glUniform1f(5, this.waterHeightOffset); - gl.glUniform1i(6, (int) this.waterIndex); - gl.glUniform1f(this.waterShader.getUniformLocation("centerOffsetX"), this.centerOffset[0]); - gl.glUniform1f(this.waterShader.getUniformLocation("centerOffsetY"), this.centerOffset[1]); - gl.glUniform4fv(11, 1, this.shaderMapBounds, 0); + gl.glUniformMatrix4fv(0, 1, false, this.camera.viewProjectionMatrix.val, 0); + gl.glUniform4fv(1, 1, this.minShallowColor, 0); + gl.glUniform4fv(2, 1, this.maxShallowColor, 0); + gl.glUniform4fv(3, 1, this.minDeepColor, 0); + gl.glUniform4fv(4, 1, this.maxDeepColor, 0); + gl.glUniform1f(5, this.waterHeightOffset); + gl.glUniform1i(6, (int) this.waterIndex); + gl.glUniform1f(this.waterShader.getUniformLocation("centerOffsetX"), this.centerOffset[0]); + gl.glUniform1f(this.waterShader.getUniformLocation("centerOffsetY"), this.centerOffset[1]); + gl.glUniform4fv(11, 1, this.shaderMapBounds, 0); - final W3xSceneLightManager lightManager = (W3xSceneLightManager) this.viewer.worldScene.getLightManager(); - final DataTexture terrainLightsTexture = lightManager.getTerrainLightsTexture(); + final W3xSceneLightManager lightManager = (W3xSceneLightManager) this.viewer.worldScene.getLightManager(); + final DataTexture terrainLightsTexture = lightManager.getTerrainLightsTexture(); - terrainLightsTexture.bind(3); - gl.glUniform1f(9, lightManager.getTerrainLightCount()); - gl.glUniform1f(10, terrainLightsTexture.getHeight()); + terrainLightsTexture.bind(3); + gl.glUniform1f(9, lightManager.getTerrainLightCount()); + gl.glUniform1f(10, terrainLightsTexture.getHeight()); - gl.glActiveTexture(GL30.GL_TEXTURE0); - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.waterHeight); - gl.glActiveTexture(GL30.GL_TEXTURE1); - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeight); - gl.glActiveTexture(GL30.GL_TEXTURE2); - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.waterExists); - gl.glActiveTexture(GL30.GL_TEXTURE4); - gl.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.waterTextureArray); + gl.glActiveTexture(GL30.GL_TEXTURE0); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.waterHeight); + gl.glActiveTexture(GL30.GL_TEXTURE1); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeight); + gl.glActiveTexture(GL30.GL_TEXTURE2); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.waterExists); + gl.glActiveTexture(GL30.GL_TEXTURE4); + gl.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.waterTextureArray); - gl.glBindBuffer(GL30.GL_ARRAY_BUFFER, Shapes.INSTANCE.vertexBuffer); - gl.glVertexAttribPointer(0, 2, GL30.GL_FLOAT, false, 0, 0); + gl.glBindBuffer(GL30.GL_ARRAY_BUFFER, Shapes.INSTANCE.vertexBuffer); + gl.glVertexAttribPointer(0, 2, GL30.GL_FLOAT, false, 0, 0); - gl.glBindBuffer(GL30.GL_ELEMENT_ARRAY_BUFFER, Shapes.INSTANCE.indexBuffer); - gl.glDrawElementsInstanced(GL30.GL_TRIANGLES, Shapes.INSTANCE.quadIndices.length * 3, GL30.GL_UNSIGNED_INT, 0, - (this.columns - 1) * (this.rows - 1)); + gl.glBindBuffer(GL30.GL_ELEMENT_ARRAY_BUFFER, Shapes.INSTANCE.indexBuffer); + gl.glDrawElementsInstanced(GL30.GL_TRIANGLES, Shapes.INSTANCE.quadIndices.length * 3, GL30.GL_UNSIGNED_INT, 0, + (this.columns - 1) * (this.rows - 1)); - gl.glEnable(GL30.GL_BLEND); - } + gl.glEnable(GL30.GL_BLEND); + } - public void renderCliffs() { + public void renderCliffs() { - // Render cliffs - for (final IVec3 i : this.cliffs) { - final RenderCorner bottomLeft = this.corners[i.x][i.y]; - final RenderCorner bottomRight = this.corners[i.x + 1][i.y]; - final RenderCorner topLeft = this.corners[i.x][i.y + 1]; - final RenderCorner topRight = this.corners[i.x + 1][i.y + 1]; + // Render cliffs + for (final IVec3 i : this.cliffs) { + final RenderCorner bottomLeft = this.corners[i.x][i.y]; + final RenderCorner bottomRight = this.corners[i.x + 1][i.y]; + final RenderCorner topLeft = this.corners[i.x][i.y + 1]; + final RenderCorner topRight = this.corners[i.x + 1][i.y + 1]; - final float min = Math.min(Math.min(bottomLeft.getLayerHeight() - 2, bottomRight.getLayerHeight() - 2), - Math.min(topLeft.getLayerHeight() - 2, topRight.getLayerHeight() - 2)); + final float min = Math.min(Math.min(bottomLeft.getLayerHeight() - 2, bottomRight.getLayerHeight() - 2), + Math.min(topLeft.getLayerHeight() - 2, topRight.getLayerHeight() - 2)); - fourComponentHeap[0] = i.x; - fourComponentHeap[1] = i.y; - fourComponentHeap[2] = min; - fourComponentHeap[3] = bottomLeft.getCliffTexture(); - this.cliffMeshes.get(i.z).renderQueue(fourComponentHeap); - } + fourComponentHeap[0] = i.x; + fourComponentHeap[1] = i.y; + fourComponentHeap[2] = min; + fourComponentHeap[3] = bottomLeft.getCliffTexture(); + this.cliffMeshes.get(i.z).renderQueue(fourComponentHeap); + } - this.webGL.useShaderProgram(this.cliffShader); + this.webGL.useShaderProgram(this.cliffShader); - final GL30 gl = Gdx.gl30; - gl.glDisable(GL30.GL_BLEND); + final GL30 gl = Gdx.gl30; + gl.glDisable(GL30.GL_BLEND); - // WC3 models are 128x too large - tempMatrix.set(this.camera.viewProjectionMatrix); - gl.glUniformMatrix4fv(0, 1, false, tempMatrix.val, 0); - gl.glUniform1i(1, this.viewer.renderPathing); - gl.glUniform1i(2, this.viewer.renderLighting); + // WC3 models are 128x too large + tempMatrix.set(this.camera.viewProjectionMatrix); + gl.glUniformMatrix4fv(0, 1, false, tempMatrix.val, 0); + gl.glUniform1i(1, this.viewer.renderPathing); + gl.glUniform1i(2, this.viewer.renderLighting); - final W3xSceneLightManager lightManager = (W3xSceneLightManager) this.viewer.worldScene.getLightManager(); - final DataTexture unitLightsTexture = lightManager.getTerrainLightsTexture(); + final W3xSceneLightManager lightManager = (W3xSceneLightManager) this.viewer.worldScene.getLightManager(); + final DataTexture unitLightsTexture = lightManager.getTerrainLightsTexture(); - unitLightsTexture.bind(21); - gl.glUniform1i(this.cliffShader.getUniformLocation("lightTexture"), 21); - gl.glUniform1f(this.cliffShader.getUniformLocation("lightCount"), lightManager.getTerrainLightCount()); - gl.glUniform1f(this.cliffShader.getUniformLocation("lightTextureHeight"), unitLightsTexture.getHeight()); + unitLightsTexture.bind(21); + gl.glUniform1i(this.cliffShader.getUniformLocation("lightTexture"), 21); + gl.glUniform1f(this.cliffShader.getUniformLocation("lightCount"), lightManager.getTerrainLightCount()); + gl.glUniform1f(this.cliffShader.getUniformLocation("lightTextureHeight"), unitLightsTexture.getHeight()); - this.cliffShader.setUniformi("shadowMap", 2); - gl.glActiveTexture(GL30.GL_TEXTURE2); - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.shadowMap); + this.cliffShader.setUniformi("shadowMap", 2); + gl.glActiveTexture(GL30.GL_TEXTURE2); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.shadowMap); - gl.glActiveTexture(GL30.GL_TEXTURE0); - gl.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.cliffTextureArray); - gl.glActiveTexture(GL30.GL_TEXTURE1); - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundHeight); + gl.glActiveTexture(GL30.GL_TEXTURE0); + gl.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.cliffTextureArray); + gl.glActiveTexture(GL30.GL_TEXTURE1); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundHeight); // gl.glActiveTexture(GL30.GL_TEXTURE2); - for (final CliffMesh i : this.cliffMeshes) { - gl.glUniform1f(this.cliffShader.getUniformLocation("centerOffsetX"), this.centerOffset[0]); - gl.glUniform1f(this.cliffShader.getUniformLocation("centerOffsetY"), this.centerOffset[1]); - i.render(); - } - } + for (final CliffMesh i : this.cliffMeshes) { + gl.glUniform1f(this.cliffShader.getUniformLocation("centerOffsetX"), this.centerOffset[0]); + gl.glUniform1f(this.cliffShader.getUniformLocation("centerOffsetY"), this.centerOffset[1]); + i.render(); + } + } - public void addShadow(final String file, final float shadowX, final float shadowY) { - if (!this.shadows.containsKey(file)) { - final String path = "ReplaceableTextures\\Shadows\\" + file + ".blp"; - this.shadows.put(file, new ArrayList<>()); - this.shadowTextures.put(file, (Texture) this.viewer.load(path, PathSolver.DEFAULT, null)); - } - this.shadows.get(file).add(new float[]{shadowX, shadowY}); - if (initShadowsFinished) { - final Texture texture = shadowTextures.get(file); + public BuildingShadow addShadow(final String file, final float shadowX, final float shadowY) { + if (!this.shadows.containsKey(file)) { + final String path = "ReplaceableTextures\\Shadows\\" + file + ".blp"; + this.shadows.put(file, new ArrayList<>()); + this.shadowTextures.put(file, (Texture) this.viewer.load(path, PathSolver.DEFAULT, null)); + } + final List shadowList = this.shadows.get(file); + final float[] shadowPositionArray = new float[] { shadowX, shadowY }; + shadowList.add(shadowPositionArray); + if (this.initShadowsFinished) { + final Texture texture = this.shadowTextures.get(file); - final int columns = (this.columns - 1) * 4; - final int rows = (this.rows - 1) * 4; - blitShadowData(columns, rows, shadowX, shadowY, texture); - GL30 gl = Gdx.gl30; - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.shadowMap); - gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_R8, columns, rows, 0, GL30.GL_RED, GL30.GL_UNSIGNED_BYTE, - RenderMathUtils.wrap(shadowData)); - } - } + final int columns = (this.columns - 1) * 4; + final int rows = (this.rows - 1) * 4; + blitShadowData(columns, rows, shadowX, shadowY, texture); + final GL30 gl = Gdx.gl30; + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.shadowMap); + gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_R8, columns, rows, 0, GL30.GL_RED, GL30.GL_UNSIGNED_BYTE, + RenderMathUtils.wrap(this.shadowData)); + } + return new BuildingShadow() { + @Override + public void remove() { + shadowList.remove(shadowPositionArray); + reloadShadowDataToGPU(); + } - public void blitShadowData(int columns, int rows, float shadowX, float shadowY, Texture texture) { - final int width = texture.getWidth(); - final int height = texture.getHeight(); - final int ox = (int) Math.round(width * 0.3); - final int oy = (int) Math.round(height * 0.7); - blitShadowDataLocation(columns, rows, (RawOpenGLTextureResource) texture, width, height, ox, oy, centerOffset, shadowX, shadowY); - } + @Override + public void move(final float x, final float y) { + shadowPositionArray[0] = x; + shadowPositionArray[1] = y; + reloadShadowDataToGPU(); + } + }; + } - public void initShadows() throws IOException { - final GL30 gl = Gdx.gl30; - final float[] centerOffset = this.centerOffset; - final int columns = (this.columns - 1) * 4; - final int rows = (this.rows - 1) * 4; + public void blitShadowData(final int columns, final int rows, final float shadowX, final float shadowY, + final Texture texture) { + final int width = texture.getWidth(); + final int height = texture.getHeight(); + final int ox = (int) Math.round(width * 0.3); + final int oy = (int) Math.round(height * 0.7); + blitShadowDataLocation(columns, rows, (RawOpenGLTextureResource) texture, width, height, ox, oy, + this.centerOffset, shadowX, shadowY, this.shadowData); + } - final int shadowSize = columns * rows; - shadowData = new byte[columns * rows]; - if (this.viewer.mapMpq.has("war3map.shd")) { - final byte[] buffer; + public void initShadows() throws IOException { + final GL30 gl = Gdx.gl30; + final float[] centerOffset = this.centerOffset; + final int columns = (this.columns - 1) * 4; + final int rows = (this.rows - 1) * 4; - try (final InputStream shadowSource = this.viewer.mapMpq.getResourceAsStream("war3map.shd")) { - buffer = IOUtils.toByteArray(shadowSource); - } + final int shadowSize = columns * rows; + this.staticShadowData = new byte[columns * rows]; + this.shadowData = new byte[columns * rows]; + if (this.viewer.mapMpq.has("war3map.shd")) { + final byte[] buffer; - for (int i = 0; i < shadowSize; i++) { - shadowData[i] = (byte) ((buffer[i] & 0xFF) / 2f); - } - } + try (final InputStream shadowSource = this.viewer.mapMpq.getResourceAsStream("war3map.shd")) { + buffer = IOUtils.toByteArray(shadowSource); + } - for (final Map.Entry fileAndTexture : this.shadowTextures.entrySet()) { - final String file = fileAndTexture.getKey(); - final Texture texture = fileAndTexture.getValue(); + for (int i = 0; i < shadowSize; i++) { + this.staticShadowData[i] = (byte) ((buffer[i] & 0xFF) / 2f); + } + } - final int width = texture.getWidth(); - final int height = texture.getHeight(); - final int ox = (int) Math.round(width * 0.3); - final int oy = (int) Math.round(height * 0.7); - for (final float[] location : this.shadows.get(file)) { - blitShadowDataLocation(columns, rows, (RawOpenGLTextureResource) texture, width, height, ox, oy, centerOffset, location[0], location[1]); - } - } + final byte outsideArea = (byte) 204; + final int x0 = this.mapBounds[0] * 4, x1 = (this.mapSize[0] - this.mapBounds[1] - 1) * 4, + y0 = this.mapBounds[2] * 4, y1 = (this.mapSize[1] - this.mapBounds[3] - 1) * 4; + for (int y = y0; y < y1; ++y) { + for (int x = x0; x < x1; ++x) { + final RenderCorner c = this.corners[x >> 2][y >> 2]; + if (c.getBoundary() != 0) { + this.staticShadowData[(y * columns) + x] = outsideArea; + } + } + } + for (int y = 0; y < rows; ++y) { + for (int x = 0; x < x0; ++x) { + this.staticShadowData[(y * columns) + x] = outsideArea; + } + for (int x = x1; x < columns; ++x) { + this.staticShadowData[(y * columns) + x] = outsideArea; + } + } + for (int x = x0; x < x1; ++x) { + for (int y = 0; y < y0; ++y) { + this.staticShadowData[(y * columns) + x] = outsideArea; + } + for (int y = y1; y < rows; ++y) { + this.staticShadowData[(y * columns) + x] = outsideArea; + } + } + reloadShadowData(centerOffset, columns, rows); - final byte outsideArea = (byte) 204; - final int x0 = this.mapBounds[0] * 4, x1 = (this.mapSize[0] - this.mapBounds[1] - 1) * 4, - y0 = this.mapBounds[2] * 4, y1 = (this.mapSize[1] - this.mapBounds[3] - 1) * 4; - for (int y = y0; y < y1; ++y) { - for (int x = x0; x < x1; ++x) { - final RenderCorner c = this.corners[x >> 2][y >> 2]; - if (c.getBoundary() != 0) { - shadowData[(y * columns) + x] = outsideArea; - } - } - } - for (int y = 0; y < rows; ++y) { - for (int x = 0; x < x0; ++x) { - shadowData[(y * columns) + x] = outsideArea; - } - for (int x = x1; x < columns; ++x) { - shadowData[(y * columns) + x] = outsideArea; - } - } - for (int x = x0; x < x1; ++x) { - for (int y = 0; y < y0; ++y) { - shadowData[(y * columns) + x] = outsideArea; - } - for (int y = y1; y < rows; ++y) { - shadowData[(y * columns) + x] = outsideArea; - } - } + this.shadowMap = gl.glGenTexture(); + gl.glBindTexture(GL30.GL_TEXTURE_2D, this.shadowMap); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_LINEAR); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_LINEAR); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_S, GL30.GL_CLAMP_TO_EDGE); + gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_T, GL30.GL_CLAMP_TO_EDGE); + gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_R8, columns, rows, 0, GL30.GL_RED, GL30.GL_UNSIGNED_BYTE, + RenderMathUtils.wrap(this.shadowData)); + this.initShadowsFinished = true; + } - this.shadowMap = gl.glGenTexture(); - gl.glBindTexture(GL30.GL_TEXTURE_2D, this.shadowMap); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MAG_FILTER, GL30.GL_LINEAR); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_MIN_FILTER, GL30.GL_LINEAR); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_S, GL30.GL_CLAMP_TO_EDGE); - gl.glTexParameteri(GL30.GL_TEXTURE_2D, GL30.GL_TEXTURE_WRAP_T, GL30.GL_CLAMP_TO_EDGE); - gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_R8, columns, rows, 0, GL30.GL_RED, GL30.GL_UNSIGNED_BYTE, - RenderMathUtils.wrap(shadowData)); - this.initShadowsFinished = true; - } + private void reloadShadowData(final float[] centerOffset, final int columns, final int rows) { + System.arraycopy(this.staticShadowData, 0, this.shadowData, 0, this.staticShadowData.length); + for (final Map.Entry fileAndTexture : this.shadowTextures.entrySet()) { + final String file = fileAndTexture.getKey(); + final Texture texture = fileAndTexture.getValue(); - public void blitShadowDataLocation(int columns, int rows, RawOpenGLTextureResource texture, int width, int height, int x01, int y01, float[] centerOffset, float v, float v2) { - final int x0 = (int) Math.floor((v - centerOffset[0]) / 32.0) - x01; - final int y0 = (int) Math.floor((v2 - centerOffset[1]) / 32.0) + y01; - for (int y = 0; y < height; ++y) { - if (((y0 - y) < 0) || ((y0 - y) >= rows)) { - continue; - } - for (int x = 0; x < width; ++x) { - if (((x0 + x) < 0) || ((x0 + x) >= columns)) { - continue; - } - if (texture.getData().get((((y * width) + x) * 4) + 3) != 0) { - shadowData[((y0 - y) * columns) + x0 + x] = (byte) 128; - } - } - } - } + final int width = texture.getWidth(); + final int height = texture.getHeight(); + final int ox = (int) Math.round(width * 0.3); + final int oy = (int) Math.round(height * 0.7); + for (final float[] location : this.shadows.get(file)) { + blitShadowDataLocation(columns, rows, (RawOpenGLTextureResource) texture, width, height, ox, oy, + centerOffset, location[0], location[1], this.shadowData); + } + } + } + + public void blitShadowDataLocation(final int columns, final int rows, final RawOpenGLTextureResource texture, + final int width, final int height, final int x01, final int y01, final float[] centerOffset, final float v, + final float v2, final byte[] shadowData) { + final int x0 = (int) Math.floor((v - centerOffset[0]) / 32.0) - x01; + final int y0 = (int) Math.floor((v2 - centerOffset[1]) / 32.0) + y01; + for (int y = 0; y < height; ++y) { + if (((y0 - y) < 0) || ((y0 - y) >= rows)) { + continue; + } + for (int x = 0; x < width; ++x) { + if (((x0 + x) < 0) || ((x0 + x) >= columns)) { + continue; + } + if (texture.getData().get((((y * width) + x) * 4) + 3) != 0) { + shadowData[((y0 - y) * columns) + x0 + x] = (byte) 128; + } + } + } + } // public Vector3 groundNormal(final Vector3 out, int x, int y) { // final float[] centerOffset = this.centerOffset; @@ -1250,180 +1291,204 @@ public class Terrain { // return out; // } - private final WaveBuilder waveBuilder; - public PathingGrid pathingGrid; - private final Rectangle shaderMapBoundsRectangle; - private final Rectangle entireMapRectangle; + private final WaveBuilder waveBuilder; + public PathingGrid pathingGrid; + private final Rectangle shaderMapBoundsRectangle; + private final Rectangle entireMapRectangle; - private static final class UnloadedTexture { - private final int width; - private final int height; - private final Buffer data; - private final String cliffModelDir; - private final String rampModelDir; + private static final class UnloadedTexture { + private final int width; + private final int height; + private final Buffer data; + private final String cliffModelDir; + private final String rampModelDir; - public UnloadedTexture(final int width, final int height, final Buffer data, final String cliffModelDir, - final String rampModelDir) { - this.width = width; - this.height = height; - this.data = data; - this.cliffModelDir = cliffModelDir; - this.rampModelDir = rampModelDir; - } + public UnloadedTexture(final int width, final int height, final Buffer data, final String cliffModelDir, + final String rampModelDir) { + this.width = width; + this.height = height; + this.data = data; + this.cliffModelDir = cliffModelDir; + this.rampModelDir = rampModelDir; + } - } + } - public float getGroundHeight(final float x, final float y) { - final float userCellSpaceX = (x - this.centerOffset[0]) / 128.0f; - final float userCellSpaceY = (y - this.centerOffset[1]) / 128.0f; - final int cellX = (int) userCellSpaceX; - final int cellY = (int) userCellSpaceY; + public float getGroundHeight(final float x, final float y) { + final float userCellSpaceX = (x - this.centerOffset[0]) / 128.0f; + final float userCellSpaceY = (y - this.centerOffset[1]) / 128.0f; + final int cellX = (int) userCellSpaceX; + final int cellY = (int) userCellSpaceY; - if ((cellX >= 0) && (cellX < (this.mapSize[0] - 1)) && (cellY >= 0) && (cellY < (this.mapSize[1] - 1))) { - final float bottomLeft = this.groundCornerHeights[(cellY * this.columns) + cellX]; - final float bottomRight = this.groundCornerHeights[(cellY * this.columns) + cellX + 1]; - final float topLeft = this.groundCornerHeights[((cellY + 1) * this.columns) + cellX]; - final float topRight = this.groundCornerHeights[((cellY + 1) * this.columns) + cellX + 1]; - final float sqX = userCellSpaceX - cellX; - final float sqY = userCellSpaceY - cellY; - float height; + if ((cellX >= 0) && (cellX < (this.mapSize[0] - 1)) && (cellY >= 0) && (cellY < (this.mapSize[1] - 1))) { + final float bottomLeft = this.groundCornerHeights[(cellY * this.columns) + cellX]; + final float bottomRight = this.groundCornerHeights[(cellY * this.columns) + cellX + 1]; + final float topLeft = this.groundCornerHeights[((cellY + 1) * this.columns) + cellX]; + final float topRight = this.groundCornerHeights[((cellY + 1) * this.columns) + cellX + 1]; + final float sqX = userCellSpaceX - cellX; + final float sqY = userCellSpaceY - cellY; + float height; - if ((sqX + sqY) < 1) { - height = bottomLeft + ((bottomRight - bottomLeft) * sqX) + ((topLeft - bottomLeft) * sqY); - } else { - height = topRight + ((bottomRight - topRight) * (1 - sqY)) + ((topLeft - topRight) * (1 - sqX)); - } + if ((sqX + sqY) < 1) { + height = bottomLeft + ((bottomRight - bottomLeft) * sqX) + ((topLeft - bottomLeft) * sqY); + } + else { + height = topRight + ((bottomRight - topRight) * (1 - sqY)) + ((topLeft - topRight) * (1 - sqX)); + } - return height * 128.0f; - } + return height * 128.0f; + } - return 0; - } + return 0; + } - public float getWaterHeight(final float x, final float y) { - final float userCellSpaceX = (x - this.centerOffset[0]) / 128.0f; - final float userCellSpaceY = (y - this.centerOffset[1]) / 128.0f; - final int cellX = (int) userCellSpaceX; - final int cellY = (int) userCellSpaceY; + public float getWaterHeight(final float x, final float y) { + final float userCellSpaceX = (x - this.centerOffset[0]) / 128.0f; + final float userCellSpaceY = (y - this.centerOffset[1]) / 128.0f; + final int cellX = (int) userCellSpaceX; + final int cellY = (int) userCellSpaceY; - if ((cellX >= 0) && (cellX < (this.mapSize[0] - 1)) && (cellY >= 0) && (cellY < (this.mapSize[1] - 1))) { - final float bottomLeft = this.waterHeights[(cellY * this.columns) + cellX]; - final float bottomRight = this.waterHeights[(cellY * this.columns) + cellX + 1]; - final float topLeft = this.waterHeights[((cellY + 1) * this.columns) + cellX]; - final float topRight = this.waterHeights[((cellY + 1) * this.columns) + cellX + 1]; - final float sqX = userCellSpaceX - cellX; - final float sqY = userCellSpaceY - cellY; - float height; + if ((cellX >= 0) && (cellX < (this.mapSize[0] - 1)) && (cellY >= 0) && (cellY < (this.mapSize[1] - 1))) { + final float bottomLeft = this.waterHeights[(cellY * this.columns) + cellX]; + final float bottomRight = this.waterHeights[(cellY * this.columns) + cellX + 1]; + final float topLeft = this.waterHeights[((cellY + 1) * this.columns) + cellX]; + final float topRight = this.waterHeights[((cellY + 1) * this.columns) + cellX + 1]; + final float sqX = userCellSpaceX - cellX; + final float sqY = userCellSpaceY - cellY; + float height; - if ((sqX + sqY) < 1) { - height = bottomLeft + ((bottomRight - bottomLeft) * sqX) + ((topLeft - bottomLeft) * sqY); - } else { - height = topRight + ((bottomRight - topRight) * (1 - sqY)) + ((topLeft - topRight) * (1 - sqX)); - } + if ((sqX + sqY) < 1) { + height = bottomLeft + ((bottomRight - bottomLeft) * sqX) + ((topLeft - bottomLeft) * sqY); + } + else { + height = topRight + ((bottomRight - topRight) * (1 - sqY)) + ((topLeft - topRight) * (1 - sqX)); + } - return ((height + this.waterHeightOffset) * 128.0f); - } + return ((height + this.waterHeightOffset) * 128.0f); + } - return this.waterHeightOffset * 128.0f; - } + return this.waterHeightOffset * 128.0f; + } - public static final class Splat { - public List locations = new ArrayList<>(); - public List> unitMapping = new ArrayList<>(); - public float opacity = 1; - } + public static final class Splat { + public List locations = new ArrayList<>(); + public List> unitMapping = new ArrayList<>(); + public float opacity = 1; + } - public void loadSplats() throws IOException { - for (final Map.Entry entry : this.splats.entrySet()) { - final String path = entry.getKey(); - final Splat splat = entry.getValue(); + public void loadSplats() throws IOException { + for (final Map.Entry entry : this.splats.entrySet()) { + final String path = entry.getKey(); + final Splat splat = entry.getValue(); - final SplatModel splatModel = new SplatModel(Gdx.gl30, - (Texture) this.viewer.load(path, PathSolver.DEFAULT, null), splat.locations, this.centerOffset, - splat.unitMapping, false); - splatModel.color[3] = splat.opacity; - this.uberSplatModels.put(path, splatModel); - } - } + final SplatModel splatModel = new SplatModel(Gdx.gl30, + (Texture) this.viewer.load(path, PathSolver.DEFAULT, null), splat.locations, this.centerOffset, + splat.unitMapping, false); + splatModel.color[3] = splat.opacity; + this.uberSplatModels.put(path, splatModel); + } + } - public void removeSplatBatchModel(String path) { - this.uberSplatModels.remove(path); - } + public void removeSplatBatchModel(final String path) { + this.uberSplatModels.remove(path); + } - public void addSplatBatchModel(String path, final SplatModel model) { - this.uberSplatModels.put(path, model); - } + public void addSplatBatchModel(final String path, final SplatModel model) { + this.uberSplatModels.put(path, model); + } - public void addUberSplat(String path, float x, float y, float z, float scale) { - SplatModel splatModel = this.uberSplatModels.get(path); - if (splatModel == null) { - splatModel = new SplatModel(Gdx.gl30, (Texture) this.viewer.load(path, PathSolver.DEFAULT, null), - new ArrayList<>(), centerOffset, null, false); - this.uberSplatModels.put(path, splatModel); - } - splatModel.add(x, y, z, scale, centerOffset); - } + public SplatMover addUberSplat(final String path, final float x, final float y, final float z, final float scale) { + SplatModel splatModel = this.uberSplatModels.get(path); + if (splatModel == null) { + splatModel = new SplatModel(Gdx.gl30, (Texture) this.viewer.load(path, PathSolver.DEFAULT, null), + new ArrayList<>(), this.centerOffset, null, false); + this.uberSplatModels.put(path, splatModel); + } + return splatModel.add(x - scale, y - scale, x + scale, y + scale, z, this.centerOffset); + } - public static final class SoftwareGroundMesh { - public final float[] vertices; - public final int[] indices; + public SplatMover addUnitShadowSplat(final String texture, final float x, final float y, final float x2, + final float y2, final float zDepthUpward, final float opacity) { + SplatModel splatModel = this.uberSplatModels.get(texture); + if (splatModel == null) { + splatModel = new SplatModel(Gdx.gl30, (Texture) this.viewer.load(texture, PathSolver.DEFAULT, null), + new ArrayList<>(), this.centerOffset, null, false); + splatModel.color[3] = opacity; + this.uberSplatModels.put(texture, splatModel); + } + return splatModel.add(x, y, x2, y2, zDepthUpward, this.centerOffset); + } - private SoftwareGroundMesh(final float[] groundHeights, final float[] groundCornerHeights, - final float[] centerOffset, final int columns, final int rows) { - this.vertices = new float[(columns - 1) * (rows - 1) * Shapes.INSTANCE.quadVertices.length * 3]; - this.indices = new int[(columns - 1) * (rows - 1) * Shapes.INSTANCE.quadIndices.length * 3]; - for (int y = 0; y < (rows - 1); y++) { - for (int x = 0; x < (columns - 1); x++) { - final int instanceId = (y * (columns - 1)) + x; - for (int vertexId = 0; vertexId < Shapes.INSTANCE.quadVertices.length; vertexId++) { - final float vPositionX = Shapes.INSTANCE.quadVertices[vertexId][0]; - final float vPositionY = Shapes.INSTANCE.quadVertices[vertexId][1]; - final int groundCornerHeightIndex = (int) (((vPositionY + y) * (columns)) + (vPositionX + x)); - final float height = groundCornerHeights[groundCornerHeightIndex]; - this.vertices[(instanceId * 4 * 3) + (vertexId * 3)] = ((vPositionX + x) * 128f) - + centerOffset[0]; - this.vertices[(instanceId * 4 * 3) + (vertexId * 3) + 1] = ((vPositionY + y) * 128f) - + centerOffset[1]; - this.vertices[(instanceId * 4 * 3) + (vertexId * 3) + 2] = height * 128f; - } - for (int triangle = 0; triangle < Shapes.INSTANCE.quadIndices.length; triangle++) { - for (int vertexId = 0; vertexId < Shapes.INSTANCE.quadIndices[triangle].length; vertexId++) { - final int vertexIndex = Shapes.INSTANCE.quadIndices[triangle][vertexId]; - final int indexValue = (vertexIndex + (instanceId * 4)); - if ((indexValue * 3) >= this.vertices.length) { - throw new IllegalStateException(); - } - this.indices[(instanceId * 2 * 3) + (triangle * 3) + vertexId] = indexValue; - } - } - } - } - } - } + public static final class SoftwareGroundMesh { + public final float[] vertices; + public final int[] indices; - public boolean inPlayableArea(float x, float y) { - x = (x - this.centerOffset[0]) / 128.0f; - y = (y - this.centerOffset[1]) / 128.0f; - if (x < this.mapBounds[0]) { - return false; - } - if (x >= (this.mapSize[0] - this.mapBounds[1] - 1)) { - return false; - } - if (y < this.mapBounds[2]) { - return false; - } - if (y >= (this.mapSize[1] - this.mapBounds[3] - 1)) { - return false; - } // TODO why do we use floor if we can use int cast? - return this.corners[(int) Math.floor(x)][(int) Math.floor(y)].getBoundary() == 0; - } + private SoftwareGroundMesh(final float[] groundHeights, final float[] groundCornerHeights, + final float[] centerOffset, final int columns, final int rows) { + this.vertices = new float[(columns - 1) * (rows - 1) * Shapes.INSTANCE.quadVertices.length * 3]; + this.indices = new int[(columns - 1) * (rows - 1) * Shapes.INSTANCE.quadIndices.length * 3]; + for (int y = 0; y < (rows - 1); y++) { + for (int x = 0; x < (columns - 1); x++) { + final int instanceId = (y * (columns - 1)) + x; + for (int vertexId = 0; vertexId < Shapes.INSTANCE.quadVertices.length; vertexId++) { + final float vPositionX = Shapes.INSTANCE.quadVertices[vertexId][0]; + final float vPositionY = Shapes.INSTANCE.quadVertices[vertexId][1]; + final int groundCornerHeightIndex = (int) (((vPositionY + y) * (columns)) + (vPositionX + x)); + final float height = groundCornerHeights[groundCornerHeightIndex]; + this.vertices[(instanceId * 4 * 3) + (vertexId * 3)] = ((vPositionX + x) * 128f) + + centerOffset[0]; + this.vertices[(instanceId * 4 * 3) + (vertexId * 3) + 1] = ((vPositionY + y) * 128f) + + centerOffset[1]; + this.vertices[(instanceId * 4 * 3) + (vertexId * 3) + 2] = height * 128f; + } + for (int triangle = 0; triangle < Shapes.INSTANCE.quadIndices.length; triangle++) { + for (int vertexId = 0; vertexId < Shapes.INSTANCE.quadIndices[triangle].length; vertexId++) { + final int vertexIndex = Shapes.INSTANCE.quadIndices[triangle][vertexId]; + final int indexValue = (vertexIndex + (instanceId * 4)); + if ((indexValue * 3) >= this.vertices.length) { + throw new IllegalStateException(); + } + this.indices[(instanceId * 2 * 3) + (triangle * 3) + vertexId] = indexValue; + } + } + } + } + } + } - public Rectangle getPlayableMapArea() { - return this.shaderMapBoundsRectangle; - } + public boolean inPlayableArea(float x, float y) { + x = (x - this.centerOffset[0]) / 128.0f; + y = (y - this.centerOffset[1]) / 128.0f; + if (x < this.mapBounds[0]) { + return false; + } + if (x >= (this.mapSize[0] - this.mapBounds[1] - 1)) { + return false; + } + if (y < this.mapBounds[2]) { + return false; + } + if (y >= (this.mapSize[1] - this.mapBounds[3] - 1)) { + return false; + } // TODO why do we use floor if we can use int cast? + return this.corners[(int) Math.floor(x)][(int) Math.floor(y)].getBoundary() == 0; + } - public Rectangle getEntireMap() { - return this.entireMapRectangle; - } + public Rectangle getPlayableMapArea() { + return this.shaderMapBoundsRectangle; + } + + public Rectangle getEntireMap() { + return this.entireMapRectangle; + } + + private void reloadShadowDataToGPU() { + final int columns = (Terrain.this.columns - 1) * 4; + final int rows = (Terrain.this.rows - 1) * 4; + reloadShadowData(Terrain.this.centerOffset, columns, rows); + final GL30 gl = Gdx.gl30; + gl.glBindTexture(GL30.GL_TEXTURE_2D, Terrain.this.shadowMap); + gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_R8, columns, rows, 0, GL30.GL_RED, GL30.GL_UNSIGNED_BYTE, + RenderMathUtils.wrap(Terrain.this.shadowData)); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java index 10f6bf9..1af84bb 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java @@ -52,8 +52,6 @@ public class RenderUnit { public SplatMover shadow; public SplatMover selectionCircle; - private float x; - private float y; private float facing; private boolean swimming; @@ -67,13 +65,17 @@ public class RenderUnit { private boolean corpse; private boolean boneCorpse; private final RenderUnitTypeData typeData; + public final MdxModel specialArtModel; + public SplatMover uberSplat; public RenderUnit(final War3MapViewer map, final MdxModel model, final MutableGameObject row, final float x, final float y, final float z, final int playerIndex, final UnitSoundset soundset, - final MdxModel portraitModel, final CUnit simulationUnit, final RenderUnitTypeData typeData) { + final MdxModel portraitModel, final CUnit simulationUnit, final RenderUnitTypeData typeData, + final MdxModel specialArtModel) { this.portraitModel = portraitModel; this.simulationUnit = simulationUnit; this.typeData = typeData; + this.specialArtModel = specialArtModel; final MdxComplexInstance instance = (MdxComplexInstance) model.addInstance(); this.location[0] = x; @@ -83,8 +85,6 @@ public class RenderUnit { this.facing = simulationUnit.getFacing(); final float angle = (float) Math.toRadians(this.facing); // instance.localRotation.setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, angle); - this.x = simulationUnit.getX(); - this.y = simulationUnit.getY(); instance.rotate(tempQuat.setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, angle)); this.playerIndex = playerIndex & 0xFFFF; instance.setTeamColor(this.playerIndex); @@ -139,9 +139,11 @@ public class RenderUnit { public void populateCommandCard(final CSimulation game, final CommandButtonListener commandButtonListener, final AbilityDataUI abilityDataUI, final int subMenuOrderId) { + final CommandCardPopulatingAbilityVisitor commandCardPopulatingVisitor = CommandCardPopulatingAbilityVisitor.INSTANCE + .reset(game, this.simulationUnit, commandButtonListener, abilityDataUI, subMenuOrderId, + this.simulationUnit.isConstructing()); for (final CAbility ability : this.simulationUnit.getAbilities()) { - ability.visit(CommandCardPopulatingAbilityVisitor.INSTANCE.reset(game, this.simulationUnit, - commandButtonListener, abilityDataUI, subMenuOrderId)); + ability.visit(commandCardPopulatingVisitor); } } @@ -161,48 +163,48 @@ public class RenderUnit { } else { this.instance.show(); + if (wasHidden) { + if (this.shadow != null) { + this.shadow.show(map.terrain.centerOffset); + } + } } + final float prevX = this.location[0]; + final float prevY = this.location[1]; final float simulationX = this.simulationUnit.getX(); final float simulationY = this.simulationUnit.getY(); - if (wasHidden) { - this.x = simulationX; - this.y = simulationY; - } final float deltaTime = Gdx.graphics.getDeltaTime(); - final float simDx = simulationX - this.x; - final float simDy = simulationY - this.y; + final float simDx = simulationX - this.location[0]; + final float simDy = simulationY - this.location[1]; final float distanceToSimulation = (float) Math.sqrt((simDx * simDx) + (simDy * simDy)); final int speed = this.simulationUnit.getSpeed(); final float speedDelta = speed * deltaTime; if ((distanceToSimulation > speedDelta) && (deltaTime < 1.0)) { // The 1.0 here says that after 1 second of lag, units just teleport to show // where they actually are - this.x += (speedDelta * simDx) / distanceToSimulation; - this.y += (speedDelta * simDy) / distanceToSimulation; + this.location[0] += (speedDelta * simDx) / distanceToSimulation; + this.location[1] += (speedDelta * simDy) / distanceToSimulation; } else { - this.x = simulationX; - this.y = simulationY; + this.location[0] = simulationX; + this.location[1] = simulationY; } - final float x = this.x; - final float dx = x - this.location[0]; - this.location[0] = x; - final float y = this.y; - final float dy = y - this.location[1]; - this.location[1] = y; + final float dx = this.location[0] - prevX; + final float dy = this.location[1] - prevY; final float groundHeight; final MovementType movementType = this.simulationUnit.getUnitType().getMovementType(); - final short terrainPathing = map.terrain.pathingGrid.getPathing(x, y); + final short terrainPathing = map.terrain.pathingGrid.getPathing(this.location[0], this.location[1]); boolean swimming = (movementType == MovementType.AMPHIBIOUS) && PathingGrid.isPathingFlag(terrainPathing, PathingGrid.PathingType.SWIMMABLE) && !PathingGrid.isPathingFlag(terrainPathing, PathingGrid.PathingType.WALKABLE); - final float groundHeightTerrain = map.terrain.getGroundHeight(x, y); + final float groundHeightTerrain = map.terrain.getGroundHeight(this.location[0], this.location[1]); float groundHeightTerrainAndWater; MdxComplexInstance currentWalkableUnder; final boolean standingOnWater = (swimming) || (movementType == MovementType.FLOAT) || (movementType == MovementType.FLY) || (movementType == MovementType.HOVER); if (standingOnWater) { - groundHeightTerrainAndWater = Math.max(groundHeightTerrain, map.terrain.getWaterHeight(x, y)); + groundHeightTerrainAndWater = Math.max(groundHeightTerrain, + map.terrain.getWaterHeight(this.location[0], this.location[1])); } else { // land units will have their feet pass under the surface of the water @@ -214,8 +216,8 @@ public class RenderUnit { currentWalkableUnder = null; } else { - currentWalkableUnder = map.getHighestWalkableUnder(x, y); - War3MapViewer.gdxRayHeap.set(x, y, 4096, 0, 0, -8192); + currentWalkableUnder = map.getHighestWalkableUnder(this.location[0], this.location[1]); + War3MapViewer.gdxRayHeap.set(this.location[0], this.location[1], 4096, 0, 0, -8192); if ((currentWalkableUnder != null) && currentWalkableUnder.intersectRayWithCollision(War3MapViewer.gdxRayHeap, War3MapViewer.intersectionHeap, true, true) @@ -244,6 +246,10 @@ public class RenderUnit { this.shadow.destroy(Gdx.gl30, map.terrain.centerOffset); this.shadow = null; } + if (this.uberSplat != null) { + this.uberSplat.destroy(Gdx.gl30, map.terrain.centerOffset); + this.uberSplat = null; + } if (this.selectionCircle != null) { this.selectionCircle.destroy(Gdx.gl30, map.terrain.centerOffset); this.selectionCircle = null; @@ -311,15 +317,15 @@ public class RenderUnit { final float maxRoll = this.typeData.getMaxRoll(); final float sampleRadius = this.typeData.getElevationSampleRadius(); float pitch, roll; - final float pitchSampleForwardX = x + (sampleRadius * (float) Math.cos(facingRadians)); - final float pitchSampleForwardY = y + (sampleRadius * (float) Math.sin(facingRadians)); - final float pitchSampleBackwardX = x - (sampleRadius * (float) Math.cos(facingRadians)); - final float pitchSampleBackwardY = y - (sampleRadius * (float) Math.sin(facingRadians)); + final float pitchSampleForwardX = this.location[0] + (sampleRadius * (float) Math.cos(facingRadians)); + final float pitchSampleForwardY = this.location[1] + (sampleRadius * (float) Math.sin(facingRadians)); + final float pitchSampleBackwardX = this.location[0] - (sampleRadius * (float) Math.cos(facingRadians)); + final float pitchSampleBackwardY = this.location[1] - (sampleRadius * (float) Math.sin(facingRadians)); final double leftOfFacingAngle = facingRadians + (Math.PI / 2); - final float rollSampleForwardX = x + (sampleRadius * (float) Math.cos(leftOfFacingAngle)); - final float rollSampleForwardY = y + (sampleRadius * (float) Math.sin(leftOfFacingAngle)); - final float rollSampleBackwardX = x - (sampleRadius * (float) Math.cos(leftOfFacingAngle)); - final float rollSampleBackwardY = y - (sampleRadius * (float) Math.sin(leftOfFacingAngle)); + final float rollSampleForwardX = this.location[0] + (sampleRadius * (float) Math.cos(leftOfFacingAngle)); + final float rollSampleForwardY = this.location[1] + (sampleRadius * (float) Math.sin(leftOfFacingAngle)); + final float rollSampleBackwardX = this.location[0] - (sampleRadius * (float) Math.cos(leftOfFacingAngle)); + final float rollSampleBackwardY = this.location[1] - (sampleRadius * (float) Math.sin(leftOfFacingAngle)); final float pitchSampleGroundHeight1; final float pitchSampleGroundHeight2; final float rollSampleGroundHeight1; @@ -504,4 +510,21 @@ public class RenderUnit { this.allowRarityVariations = allowRarityVariations; } } + + public void repositioned(final War3MapViewer map) { + final float prevX = this.location[0]; + final float prevY = this.location[1]; + final float simulationX = this.simulationUnit.getX(); + final float simulationY = this.simulationUnit.getY(); + final float dx = simulationX - prevX; + final float dy = simulationY - prevY; + if (this.shadow != null) { + this.shadow.move(dx, dy, map.terrain.centerOffset); + } + if (this.selectionCircle != null) { + this.selectionCircle.move(dx, dy, map.terrain.centerOffset); + } + this.location[0] = this.simulationUnit.getX(); + this.location[1] = this.simulationUnit.getY(); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityDataUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityDataUI.java index a9b97a7..d136b3b 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityDataUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityDataUI.java @@ -1,6 +1,8 @@ package com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import com.badlogic.gdx.graphics.Texture; @@ -26,8 +28,14 @@ public class AbilityDataUI { private static final War3ID UNIT_ICON_NORMAL_Y = War3ID.fromString("ubpy"); private static final War3ID UNIT_ICON_NORMAL = War3ID.fromString("uico"); + private static final War3ID UPGRADE_ICON_NORMAL_X = War3ID.fromString("gbpx"); + private static final War3ID UPGRADE_ICON_NORMAL_Y = War3ID.fromString("gbpy"); + private static final War3ID UPGRADE_ICON_NORMAL = War3ID.fromString("gar1"); + private static final War3ID UPGRADE_LEVELS = War3ID.fromString("glvl"); + private final Map rawcodeToUI = new HashMap<>(); private final Map rawcodeToUnitUI = new HashMap<>(); + private final Map> rawcodeToUpgradeUI = new HashMap<>(); private final IconUI moveUI; private final IconUI stopUI; private final IconUI holdPosUI; @@ -41,8 +49,10 @@ public class AbilityDataUI { private final IconUI buildNeutralUI; private final IconUI buildNagaUI; private final IconUI cancelUI; + private final IconUI cancelBuildUI; - public AbilityDataUI(final MutableObjectData abilityData, final MutableObjectData unitData, final GameUI gameUI) { + public AbilityDataUI(final MutableObjectData abilityData, final MutableObjectData unitData, + final MutableObjectData upgradeData, final GameUI gameUI) { final String disabledPrefix = gameUI.getSkinField("CommandButtonDisabledArtPath"); for (final War3ID alias : abilityData.keySet()) { final MutableGameObject abilityTypeData = abilityData.get(alias); @@ -75,6 +85,21 @@ public class AbilityDataUI { final Texture iconNormalDisabled = gameUI.loadTexture(disable(iconNormalPath, disabledPrefix)); this.rawcodeToUnitUI.put(alias, new IconUI(iconNormal, iconNormalDisabled, iconNormalX, iconNormalY)); } + for (final War3ID alias : upgradeData.keySet()) { + final MutableGameObject upgradeTypeData = upgradeData.get(alias); + final int upgradeLevels = upgradeTypeData.getFieldAsInteger(UPGRADE_LEVELS, 0); + final int iconNormalX = upgradeTypeData.getFieldAsInteger(UPGRADE_ICON_NORMAL_X, 0); + final int iconNormalY = upgradeTypeData.getFieldAsInteger(UPGRADE_ICON_NORMAL_Y, 0); + final List upgradeIconsByLevel = new ArrayList<>(); + for (int i = 0; i < upgradeLevels; i++) { + final String iconNormalPath = gameUI + .trySkinField(upgradeTypeData.getFieldAsString(UPGRADE_ICON_NORMAL, i)); + final Texture iconNormal = gameUI.loadTexture(iconNormalPath); + final Texture iconNormalDisabled = gameUI.loadTexture(disable(iconNormalPath, disabledPrefix)); + upgradeIconsByLevel.add(new IconUI(iconNormal, iconNormalDisabled, iconNormalX, iconNormalY)); + } + this.rawcodeToUpgradeUI.put(alias, upgradeIconsByLevel); + } this.moveUI = createBuiltInIconUI(gameUI, "CmdMove", disabledPrefix); this.stopUI = createBuiltInIconUI(gameUI, "CmdStop", disabledPrefix); this.holdPosUI = createBuiltInIconUI(gameUI, "CmdHoldPos", disabledPrefix); @@ -88,6 +113,7 @@ public class AbilityDataUI { this.buildNeutralUI = createBuiltInIconUI(gameUI, "CmdBuild", disabledPrefix); this.attackGroundUI = createBuiltInIconUI(gameUI, "CmdAttackGround", disabledPrefix); this.cancelUI = createBuiltInIconUI(gameUI, "CmdCancel", disabledPrefix); + this.cancelBuildUI = createBuiltInIconUI(gameUI, "CmdCancelBuild", disabledPrefix); } private IconUI createBuiltInIconUI(final GameUI gameUI, final String key, final String disabledPrefix) { @@ -108,6 +134,19 @@ public class AbilityDataUI { return this.rawcodeToUnitUI.get(rawcode); } + public IconUI getUpgradeUI(final War3ID rawcode, final int level) { + final List upgradeUI = this.rawcodeToUpgradeUI.get(rawcode); + if (upgradeUI != null) { + if (level < upgradeUI.size()) { + return upgradeUI.get(level); + } + else { + return upgradeUI.get(upgradeUI.size() - 1); + } + } + return null; + } + private static String disable(final String path, final String disabledPrefix) { final int slashIndex = path.lastIndexOf('\\'); String name = path; @@ -169,4 +208,8 @@ public class AbilityDataUI { return this.cancelUI; } + public IconUI getCancelBuildUI() { + return this.cancelBuildUI; + } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java index 6b350d8..fd3e865 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java @@ -11,6 +11,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityG import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityMove; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.AbstractCAbilityBuild; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityBuildInProgress; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityHumanBuild; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityNagaBuild; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityNeutralBuild; @@ -18,7 +19,9 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAb import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityOrcBuild; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityUndeadBuild; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.combat.CAbilityColdArrows; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityQueue; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayer; public class CommandCardPopulatingAbilityVisitor implements CAbilityVisitor { public static final CommandCardPopulatingAbilityVisitor INSTANCE = new CommandCardPopulatingAbilityVisitor(); @@ -29,22 +32,24 @@ public class CommandCardPopulatingAbilityVisitor implements CAbilityVisitor(); this.newUnits = new ArrayList<>(); this.projectiles = new ArrayList<>(); @@ -118,14 +120,12 @@ public class CSimulation { } public CUnit createUnit(final War3ID typeId, final int playerIndex, final float x, final float y, - final float facing, final BufferedImage buildingPathingPixelMap) { + final float facing, final BufferedImage buildingPathingPixelMap, + final RemovablePathingMapInstance pathingInstance, final BuildingShadow buildingShadowInstance) { final CUnit unit = this.unitData.create(this, playerIndex, typeId, x, y, facing, buildingPathingPixelMap, - this.simulationRenderController, this.handleIdAllocator); + this.handleIdAllocator, pathingInstance, buildingShadowInstance); this.newUnits.add(unit); this.handleIdToUnit.put(unit.getHandleId(), unit); - for (final CAbility ability : unit.getAbilities()) { - this.handleIdToAbility.put(ability.getHandleId(), ability); - } this.worldCollision.addUnit(unit); return unit; } @@ -143,6 +143,10 @@ public class CSimulation { return this.handleIdToAbility.get(handleId); } + protected void onAbilityAddedToUnit(final CUnit unit, final CAbility ability) { + this.handleIdToAbility.put(ability.getHandleId(), ability); + } + public CAttackProjectile createProjectile(final CUnit source, final float launchX, final float launchY, final float launchFacing, final CUnitAttackMissile attack, final CWidget target, final float damage, final int bounceIndex) { @@ -223,7 +227,7 @@ public class CSimulation { this.simulationRenderController.spawnUnitDamageSound(damagedUnit, weaponSound, armorType); } - public void unitConstructedEvent(CUnit constructingUnit, CUnit constructedStructure) { + public void unitConstructedEvent(final CUnit constructingUnit, final CUnit constructedStructure) { this.simulationRenderController.spawnUnitConstructionSound(constructingUnit, constructedStructure); } @@ -235,7 +239,23 @@ public class CSimulation { return this.commandErrorListener; } - public void unitConstructFinishEvent(CUnit constructedStructure) { + public void unitConstructFinishEvent(final CUnit constructedStructure) { this.simulationRenderController.spawnUnitConstructionFinishSound(constructedStructure); - } + } + + public void createBuildingDeathEffect(final CUnit cUnit) { + this.simulationRenderController.spawnBuildingDeathEffect(cUnit); + } + + public HandleIdAllocator getHandleIdAllocator() { + return this.handleIdAllocator; + } + + public void unitTrainedEvent(final CUnit trainingUnit, final CUnit trainedUnit) { + this.simulationRenderController.spawnUnitReadySound(trainedUnit); + } + + public void unitRepositioned(final CUnit cUnit) { + this.simulationRenderController.unitRepositioned(cUnit); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java index a2e10cc..c34de81 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java @@ -3,6 +3,7 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation; import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.EnumSet; +import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Queue; @@ -12,8 +13,11 @@ import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.util.WarsmashConstants; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; +import com.etheller.warsmash.viewer5.handlers.w3x.environment.BuildingShadow; +import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.RemovablePathingMapInstance; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitStateListener.CUnitStateNotifier; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityBuildInProgress; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorAttack; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorFollow; @@ -49,6 +53,8 @@ public class CUnit extends CWidget { private final CUnitType unitType; private Rectangle collisionRectangle; + private RemovablePathingMapInstance pathingInstance; + private BuildingShadow buildingShadowInstance; private final EnumSet classifications = EnumSet.noneOf(CUnitClassification.class); @@ -75,10 +81,13 @@ public class CUnit extends CWidget { private boolean hidden = false; private boolean updating = true; private CUnit workerInside; + private final War3ID[] buildQueue = new War3ID[WarsmashConstants.BUILD_QUEUE_SIZE]; + private final QueueItemType[] buildQueueTypes = new QueueItemType[WarsmashConstants.BUILD_QUEUE_SIZE]; public CUnit(final int handleId, final int playerIndex, final float x, final float y, final float life, final War3ID typeId, final float facing, final float mana, final int maximumLife, final int maximumMana, - final int speed, final int defense, final CUnitType unitType) { + final int speed, final int defense, final CUnitType unitType, + final RemovablePathingMapInstance pathingInstance, final BuildingShadow buildingShadowInstance) { super(handleId, x, y, life); this.playerIndex = playerIndex; this.typeId = typeId; @@ -88,6 +97,8 @@ public class CUnit extends CWidget { this.maximumMana = maximumMana; this.speed = speed; this.defense = defense; + this.pathingInstance = pathingInstance; + this.buildingShadowInstance = buildingShadowInstance; this.flyHeight = unitType.getDefaultFlyingHeight(); this.unitType = unitType; this.classifications.addAll(unitType.getClassifications()); @@ -107,6 +118,7 @@ public class CUnit extends CWidget { public void add(final CSimulation simulation, final CAbility ability) { this.abilities.add(ability); + simulation.onAbilityAddedToUnit(this, ability); ability.onAdd(simulation, this); } @@ -207,33 +219,87 @@ public class CUnit extends CWidget { else if (this.updating) { if (this.constructing) { this.constructionProgress += WarsmashConstants.SIMULATION_STEP_TIME; - if (this.constructionProgress >= this.unitType.getBuildTime()) { + final int buildTime = this.unitType.getBuildTime(); + final float healthGain = (WarsmashConstants.SIMULATION_STEP_TIME / buildTime) + * (this.maximumLife * (1.0f - WarsmashConstants.BUILDING_CONSTRUCT_START_LIFE)); + setLife(game, Math.min(this.life + healthGain, this.maximumLife)); + if (this.constructionProgress >= buildTime) { this.constructing = false; - if (this.workerInside != null) { - this.workerInside.setHidden(false); - this.workerInside.setUpdating(true); - this.workerInside.nudgeAround(game, this); - this.workerInside = null; + this.constructionProgress = 0; + popoutWorker(game); + final Iterator abilityIterator = this.abilities.iterator(); + while (abilityIterator.hasNext()) { + final CAbility ability = abilityIterator.next(); + if (ability instanceof CAbilityBuildInProgress) { + abilityIterator.remove(); + } } game.unitConstructFinishEvent(this); this.stateNotifier.ordersChanged(getCurrentAbilityHandleId(), getCurrentOrderId()); } } - else if (this.currentBehavior != null) { - final CBehavior lastBehavior = this.currentBehavior; - this.currentBehavior = this.currentBehavior.update(game); - if (this.currentBehavior.getHighlightOrderId() != lastBehavior.getHighlightOrderId()) { - this.stateNotifier.ordersChanged(getCurrentAbilityHandleId(), getCurrentOrderId()); - } - } else { - // check to auto acquire targets - autoAcquireAttackTargets(game); + final War3ID queuedRawcode = this.buildQueue[0]; + if (queuedRawcode != null) { + // queue step forward + this.constructionProgress += WarsmashConstants.SIMULATION_STEP_TIME; + if (this.buildQueueTypes[0] == QueueItemType.UNIT) { + final CUnitType trainedUnitType = game.getUnitData().getUnitType(queuedRawcode); + if (this.constructionProgress >= trainedUnitType.getBuildTime()) { + this.constructionProgress = 0; + final CUnit trainedUnit = game.createUnit(queuedRawcode, this.playerIndex, getX(), getY(), + game.getGameplayConstants().getBuildingAngle()); + // nudge the trained unit out around us + trainedUnit.nudgeAround(game, this); + game.unitTrainedEvent(this, trainedUnit); + for (int i = 0; i < (this.buildQueue.length - 1); i++) { + this.buildQueue[i] = this.buildQueue[i + 1]; + this.buildQueueTypes[i] = this.buildQueueTypes[i + 1]; + } + this.buildQueue[this.buildQueue.length - 1] = null; + this.buildQueueTypes[this.buildQueue.length - 1] = null; + this.stateNotifier.queueChanged(); + } + } + else if (this.buildQueueTypes[0] == QueueItemType.RESEARCH) { + final CUnitType trainedUnitType = game.getUnitData().getUnitType(queuedRawcode); + if (this.constructionProgress >= trainedUnitType.getBuildTime()) { + this.constructionProgress = 0; + for (int i = 0; i < (this.buildQueue.length - 1); i++) { + this.buildQueue[i] = this.buildQueue[i + 1]; + this.buildQueueTypes[i] = this.buildQueueTypes[i + 1]; + } + this.buildQueue[this.buildQueue.length - 1] = null; + this.buildQueueTypes[this.buildQueue.length - 1] = null; + this.stateNotifier.queueChanged(); + } + } + } + if (this.currentBehavior != null) { + final CBehavior lastBehavior = this.currentBehavior; + this.currentBehavior = this.currentBehavior.update(game); + if (this.currentBehavior.getHighlightOrderId() != lastBehavior.getHighlightOrderId()) { + this.stateNotifier.ordersChanged(getCurrentAbilityHandleId(), getCurrentOrderId()); + } + } + else { + // check to auto acquire targets + autoAcquireAttackTargets(game); + } } } return false; } + private void popoutWorker(final CSimulation game) { + if (this.workerInside != null) { + this.workerInside.setHidden(false); + this.workerInside.setUpdating(true); + this.workerInside.nudgeAround(game, this); + this.workerInside = null; + } + } + public void autoAcquireAttackTargets(final CSimulation game) { if (!this.unitType.getAttacks().isEmpty()) { if (this.collisionRectangle != null) { @@ -452,7 +518,7 @@ public class CUnit extends CWidget { simulation.unitDamageEvent(this, weaponType, this.unitType.getArmorType()); this.stateNotifier.lifeChanged(); if (isDead()) { - if (!wasDead && !this.unitType.isBuilding()) { + if (!wasDead) { kill(simulation); } } @@ -474,7 +540,21 @@ public class CUnit extends CWidget { private void kill(final CSimulation simulation) { this.currentBehavior = null; this.orderQueue.clear(); - this.deathTurnTick = simulation.getGameTurnTick(); + if (this.constructing) { + simulation.createBuildingDeathEffect(this); + } + else { + this.deathTurnTick = simulation.getGameTurnTick(); + } + if (this.pathingInstance != null) { + this.pathingInstance.remove(); + this.pathingInstance = null; + } + if (this.buildingShadowInstance != null) { + this.buildingShadowInstance.remove(); + this.buildingShadowInstance = null; + } + popoutWorker(simulation); } public boolean canReach(final CWidget target, final float range) { @@ -753,5 +833,62 @@ public class CUnit extends CWidget { } setX(x, simulation.getWorldCollision()); setY(y, simulation.getWorldCollision()); + simulation.unitRepositioned(this); + } + + @Override + public void setLife(final CSimulation simulation, final float life) { + final boolean wasDead = isDead(); + super.setLife(simulation, life); + if (isDead() && !wasDead) { + kill(simulation); + } + this.stateNotifier.lifeChanged(); + } + + private void queue(final War3ID rawcode, final QueueItemType queueItemType) { + for (int i = 0; i < this.buildQueue.length; i++) { + if (this.buildQueue[i] == null) { + this.buildQueue[i] = rawcode; + this.buildQueueTypes[i] = queueItemType; + break; + } + } + } + + public War3ID[] getBuildQueue() { + return this.buildQueue; + } + + public QueueItemType[] getBuildQueueTypes() { + return this.buildQueueTypes; + } + + public float getBuildQueueTimeRemaining(final CSimulation simulation) { + if (this.buildQueueTypes[0] == null) { + return 0; + } + switch (this.buildQueueTypes[0]) { + case RESEARCH: + return 999; // TODO + case UNIT: + final CUnitType trainedUnitType = simulation.getUnitData().getUnitType(this.buildQueue[0]); + return trainedUnitType.getBuildTime(); + default: + return 0; + } + } + + public void queueTrainingUnit(final War3ID rawcode) { + queue(rawcode, QueueItemType.UNIT); + } + + public void queueResearch(final War3ID rawcode) { + queue(rawcode, QueueItemType.RESEARCH); + } + + public static enum QueueItemType { + UNIT, + RESEARCH; } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitFilterFunction.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitFilterFunction.java index 6cbc073..03722f5 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitFilterFunction.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitFilterFunction.java @@ -9,4 +9,11 @@ public interface CUnitFilterFunction { return true; } }; + + CUnitFilterFunction ACCEPT_ALL_LIVING = new CUnitFilterFunction() { + @Override + public boolean call(final CUnit unit) { + return !unit.isDead(); + } + }; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitStateListener.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitStateListener.java index 94b974c..b036dcf 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitStateListener.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitStateListener.java @@ -7,6 +7,8 @@ public interface CUnitStateListener { void ordersChanged(int abilityHandleId, int orderId); + void queueChanged(); + public static final class CUnitStateNotifier extends SubscriberSetNotifier implements CUnitStateListener { @Override @@ -22,5 +24,12 @@ public interface CUnitStateListener { listener.ordersChanged(abilityHandleId, orderId); } } + + @Override + public void queueChanged() { + for (final CUnitStateListener listener : set) { + listener.queueChanged(); + } + } } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java index 5606115..1e752da 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java @@ -38,6 +38,8 @@ public class CUnitType { private final float defaultAcquisitionRange; private final float minimumAttackRange; private final List structuresBuilt; + private final List unitsTrained; + private final List researchesAvailable; private final CUnitRace unitRace; private final int goldCost; private final int lumberCost; @@ -49,7 +51,8 @@ public class CUnitType { final boolean raise, final boolean decay, final CDefenseType defenseType, final float impactZ, final BufferedImage buildingPathingPixelMap, final float deathTime, final EnumSet targetedAs, final float defaultAcquisitionRange, final float minimumAttackRange, final List structuresBuilt, - final CUnitRace unitRace, final int goldCost, final int lumberCost, final int buildTime) { + final List unitsTrained, final List researchesAvailable, final CUnitRace unitRace, + final int goldCost, final int lumberCost, final int buildTime) { this.name = name; this.building = isBldg; this.movementType = movementType; @@ -68,6 +71,8 @@ public class CUnitType { this.defaultAcquisitionRange = defaultAcquisitionRange; this.minimumAttackRange = minimumAttackRange; this.structuresBuilt = structuresBuilt; + this.unitsTrained = unitsTrained; + this.researchesAvailable = researchesAvailable; this.unitRace = unitRace; this.goldCost = goldCost; this.lumberCost = lumberCost; @@ -146,6 +151,14 @@ public class CUnitType { return this.structuresBuilt; } + public List getUnitsTrained() { + return this.unitsTrained; + } + + public List getResearchesAvailable() { + return this.researchesAvailable; + } + public CUnitRace getRace() { return this.unitRace; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWidget.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWidget.java index a78217d..e047d7f 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWidget.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWidget.java @@ -42,7 +42,7 @@ public abstract class CWidget { this.y = y; } - public void setLife(final float life) { + public void setLife(final CSimulation simulation, final float life) { this.life = life; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityVisitor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityVisitor.java index 5d3a7c5..2aa8c0d 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityVisitor.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityVisitor.java @@ -1,5 +1,6 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityBuildInProgress; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityHumanBuild; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityNagaBuild; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityNeutralBuild; @@ -7,6 +8,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAb import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityOrcBuild; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityUndeadBuild; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.combat.CAbilityColdArrows; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityQueue; /** * A visitor for the lowest level inherent types of an ability. It's a bit of a @@ -36,4 +38,8 @@ public interface CAbilityVisitor { T accept(CAbilityNagaBuild ability); T accept(CAbilityNeutralBuild ability); + + T accept(CAbilityBuildInProgress ability); + + T accept(CAbilityQueue ability); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java index 487a78f..cf8d1b1 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java @@ -33,22 +33,29 @@ public abstract class AbstractCAbilityBuild extends AbstractCAbility implements @Override public void checkCanUse(final CSimulation game, final CUnit unit, final int orderId, final AbilityActivationReceiver receiver) { - final CUnitType unitType = game.getUnitData().getUnitType(new War3ID(orderId)); - if (unitType != null) { - final CPlayer player = game.getPlayer(unit.getPlayerIndex()); - if (player.getGold() >= unitType.getGoldCost()) { - if (player.getLumber() >= unitType.getLumberCost()) { - receiver.useOk(); + final War3ID orderIdAsRawtype = new War3ID(orderId); + if (this.structuresBuilt.contains(orderIdAsRawtype)) { + final CUnitType unitType = game.getUnitData().getUnitType(orderIdAsRawtype); + if (unitType != null) { + final CPlayer player = game.getPlayer(unit.getPlayerIndex()); + if (player.getGold() >= unitType.getGoldCost()) { + if (player.getLumber() >= unitType.getLumberCost()) { + receiver.useOk(); + } + else { + receiver.notEnoughResources(ResourceType.LUMBER); + } } else { - receiver.notEnoughResources(ResourceType.LUMBER); + receiver.notEnoughResources(ResourceType.GOLD); } } else { - receiver.notEnoughResources(ResourceType.GOLD); + receiver.useOk(); } } else { + /// ??? receiver.useOk(); } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityBuildInProgress.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityBuildInProgress.java new file mode 100644 index 0000000..1b5a906 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityBuildInProgress.java @@ -0,0 +1,85 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build; + +import com.badlogic.gdx.math.Vector2; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.AbstractCAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; + +public class CAbilityBuildInProgress extends AbstractCAbility { + + public CAbilityBuildInProgress(final int handleId) { + super(handleId); + } + + @Override + public void onAdd(final CSimulation game, final CUnit unit) { + + } + + @Override + public void onRemove(final CSimulation game, final CUnit unit) { + + } + + @Override + public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId) { + caster.setLife(game, 0); + return false; + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final CWidget target) { + return null; + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final Vector2 point) { + return null; + } + + @Override + public CBehavior beginNoTarget(final CSimulation game, final CUnit caster, final int orderId) { + return null; + } + + @Override + public void checkCanUse(final CSimulation game, final CUnit unit, final int orderId, + final AbilityActivationReceiver receiver) { + receiver.useOk(); + } + + @Override + public void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, final CWidget target, + final AbilityTargetCheckReceiver receiver) { + receiver.orderIdNotAccepted(); + } + + @Override + public void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, final Vector2 target, + final AbilityTargetCheckReceiver receiver) { + receiver.orderIdNotAccepted(); + } + + @Override + public void checkCanTargetNoTarget(final CSimulation game, final CUnit unit, final int orderId, + final AbilityTargetCheckReceiver receiver) { + if (orderId == OrderIds.cancel) { + receiver.targetOk(null); + } + else { + receiver.orderIdNotAccepted(); + } + } + + @Override + public T visit(final CAbilityVisitor visitor) { + return visitor.accept(this); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityQueue.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityQueue.java new file mode 100644 index 0000000..2f1af60 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityQueue.java @@ -0,0 +1,133 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue; + +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import com.badlogic.gdx.math.Vector2; +import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.AbstractCAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayer; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.ResourceType; + +public final class CAbilityQueue extends AbstractCAbility { + private final Set unitsTrained; + private final Set researchesAvailable; + + public CAbilityQueue(final int handleId, final List unitsTrained, final List researchesAvailable) { + super(handleId); + this.unitsTrained = new LinkedHashSet<>(unitsTrained); + this.researchesAvailable = new LinkedHashSet<>(researchesAvailable); + } + + public Set getUnitsTrained() { + return this.unitsTrained; + } + + public Set getResearchesAvailable() { + return this.researchesAvailable; + } + + @Override + public void checkCanUse(final CSimulation game, final CUnit unit, final int orderId, + final AbilityActivationReceiver receiver) { + final War3ID orderIdAsRawtype = new War3ID(orderId); + if (this.unitsTrained.contains(orderIdAsRawtype) || this.researchesAvailable.contains(orderIdAsRawtype)) { + final CUnitType unitType = game.getUnitData().getUnitType(orderIdAsRawtype); + if (unitType != null) { + final CPlayer player = game.getPlayer(unit.getPlayerIndex()); + if (player.getGold() >= unitType.getGoldCost()) { + if (player.getLumber() >= unitType.getLumberCost()) { + receiver.useOk(); + } + else { + receiver.notEnoughResources(ResourceType.LUMBER); + } + } + else { + receiver.notEnoughResources(ResourceType.GOLD); + } + } + else { + receiver.useOk(); + } + } + else { + /// ??? + receiver.useOk(); + } + } + + @Override + public final void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, final CWidget target, + final AbilityTargetCheckReceiver receiver) { + receiver.orderIdNotAccepted(); + } + + @Override + public final void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, final Vector2 target, + final AbilityTargetCheckReceiver receiver) { + receiver.orderIdNotAccepted(); + } + + @Override + public final void checkCanTargetNoTarget(final CSimulation game, final CUnit unit, final int orderId, + final AbilityTargetCheckReceiver receiver) { + if (this.unitsTrained.contains(new War3ID(orderId)) || this.researchesAvailable.contains(new War3ID(orderId))) { + receiver.targetOk(null); + } + else { + receiver.orderIdNotAccepted(); + } + } + + @Override + public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId) { + return true; + } + + @Override + public void onAdd(final CSimulation game, final CUnit unit) { + + } + + @Override + public void onRemove(final CSimulation game, final CUnit unit) { + + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final CWidget target) { + return null; + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final Vector2 point) { + return null; + } + + @Override + public CBehavior beginNoTarget(final CSimulation game, final CUnit caster, final int orderId) { + final War3ID rawcode = new War3ID(orderId); + if (this.unitsTrained.contains(rawcode)) { + caster.queueTrainingUnit(rawcode); + } + else if (this.researchesAvailable.contains(rawcode)) { + caster.queueResearch(rawcode); + } + return null; + } + + @Override + public T visit(final CAbilityVisitor visitor) { + return visitor.accept(this); + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/upgrade/CAbilityUpgrade.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/upgrade/CAbilityUpgrade.java new file mode 100644 index 0000000..fb52922 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/upgrade/CAbilityUpgrade.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.upgrade; + +public class CAbilityUpgrade { + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java index 9a43644..140aca2 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java @@ -1,9 +1,11 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.build; import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.util.WarsmashConstants; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityBuildInProgress; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CAbstractRangedPointTargetBehavior; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; @@ -39,6 +41,9 @@ public class CBehaviorOrcBuild extends CAbstractRangedPointTargetBehavior { this.targetY, simulation.getGameplayConstants().getBuildingAngle()); constructedStructure.setConstructing(true); constructedStructure.setWorkerInside(this.unit); + constructedStructure.setLife(simulation, + constructedStructure.getMaximumLife() * WarsmashConstants.BUILDING_CONSTRUCT_START_LIFE); + constructedStructure.add(simulation, new CAbilityBuildInProgress(simulation.getHandleIdAllocator().createId())); this.unit.setHidden(true); this.unit.setUpdating(false); simulation.unitConstructedEvent(this.unit, constructedStructure); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java index 037deec..510371a 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java @@ -10,12 +10,15 @@ import java.util.Map; import com.etheller.warsmash.units.manager.MutableObjectData; import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.environment.BuildingShadow; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid; +import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.RemovablePathingMapInstance; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitClassification; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.HandleIdAllocator; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityAttack; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityMove; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityHumanBuild; @@ -24,6 +27,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAb import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityNightElfBuild; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityOrcBuild; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityUndeadBuild; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityQueue; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CDefenseType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; @@ -128,6 +132,8 @@ public class CUnitData { private static final War3ID ABILITIES_NORMAL = War3ID.fromString("uabi"); private static final War3ID STRUCTURES_BUILT = War3ID.fromString("ubui"); + private static final War3ID UNITS_TRAINED = War3ID.fromString("utra"); + private static final War3ID RESEARCHES_AVAILABLE = War3ID.fromString("ures"); private static final War3ID UNIT_RACE = War3ID.fromString("urac"); private static final War3ID GOLD_COST = War3ID.fromString("ugol"); @@ -137,17 +143,19 @@ public class CUnitData { private final MutableObjectData unitData; private final Map unitIdToUnitType = new HashMap<>(); private final CAbilityData abilityData; - private SimulationRenderController simulationRenderController; + private final SimulationRenderController simulationRenderController; - public CUnitData(final MutableObjectData unitData, final CAbilityData abilityData) { + public CUnitData(final MutableObjectData unitData, final CAbilityData abilityData, + final SimulationRenderController simulationRenderController) { this.unitData = unitData; this.abilityData = abilityData; + this.simulationRenderController = simulationRenderController; } public CUnit create(final CSimulation simulation, final int playerIndex, final War3ID typeId, final float x, final float y, final float facing, final BufferedImage buildingPathingPixelMap, - final SimulationRenderController simulationRenderController, final HandleIdAllocator handleIdAllocator) { - this.simulationRenderController = simulationRenderController; + final HandleIdAllocator handleIdAllocator, final RemovablePathingMapInstance pathingInstance, + final BuildingShadow buildingShadowInstance) { final MutableGameObject unitType = this.unitData.get(typeId); final int handleId = handleIdAllocator.createId(); final int life = unitType.getFieldAsInteger(HIT_POINT_MAXIMUM, 0); @@ -160,7 +168,7 @@ public class CUnitData { final CUnitType unitTypeInstance = getUnitTypeInstance(typeId, buildingPathingPixelMap, unitType); final CUnit unit = new CUnit(handleId, playerIndex, x, y, life, typeId, facing, manaInitial, life, manaMaximum, - speed, defense, unitTypeInstance); + speed, defense, unitTypeInstance, pathingInstance, buildingShadowInstance); if (speed > 0) { unit.add(simulation, new CAbilityMove(handleIdAllocator.createId())); } @@ -193,9 +201,17 @@ public class CUnitData { break; } } + final List unitsTrained = unitTypeInstance.getUnitsTrained(); + final List researchesAvailable = unitTypeInstance.getResearchesAvailable(); + if (!unitsTrained.isEmpty() || !researchesAvailable.isEmpty()) { + unit.add(simulation, new CAbilityQueue(handleIdAllocator.createId(), unitsTrained, researchesAvailable)); + } for (final String ability : abilityList.split(",")) { if ((ability.length() > 0) && !"_".equals(ability)) { - unit.add(simulation, this.abilityData.createAbility(ability, handleIdAllocator.createId())); + final CAbility createAbility = this.abilityData.createAbility(ability, handleIdAllocator.createId()); + if (createAbility != null) { + unit.add(simulation, createAbility); + } } } return unit; @@ -338,6 +354,24 @@ public class CUnitData { final int lumberCost = unitType.getFieldAsInteger(LUMBER_COST, 0); final int buildTime = unitType.getFieldAsInteger(BUILD_TIME, 0); + final String unitsTrainedString = unitType.getFieldAsString(UNITS_TRAINED, 0); + final String[] unitsTrainedStringItems = unitsTrainedString.trim().split(","); + final List unitsTrained = new ArrayList<>(); + for (final String unitsTrainedStringItem : unitsTrainedStringItems) { + if (unitsTrainedStringItem.length() == 4) { + unitsTrained.add(War3ID.fromString(unitsTrainedStringItem)); + } + } + + final String researchesAvailableString = unitType.getFieldAsString(RESEARCHES_AVAILABLE, 0); + final String[] researchesAvailableStringItems = researchesAvailableString.trim().split(","); + final List researchesAvailable = new ArrayList<>(); + for (final String researchesAvailableStringItem : researchesAvailableStringItems) { + if (researchesAvailableStringItem.length() == 4) { + researchesAvailable.add(War3ID.fromString(researchesAvailableStringItem)); + } + } + final String structuresBuiltString = unitType.getFieldAsString(STRUCTURES_BUILT, 0); final String[] structuresBuiltStringItems = structuresBuiltString.split(","); final List structuresBuilt = new ArrayList<>(); @@ -352,8 +386,8 @@ public class CUnitData { unitTypeInstance = new CUnitType(unitName, isBldg, movementType, moveHeight, collisionSize, classifications, attacks, armorType, raise, decay, defenseType, impactZ, buildingPathingPixelMap, deathTime, - targetedAs, acquisitionRange, minimumAttackRange, structuresBuilt, unitRace, goldCost, lumberCost, - buildTime); + targetedAs, acquisitionRange, minimumAttackRange, structuresBuilt, unitsTrained, + researchesAvailable, unitRace, goldCost, lumberCost, buildTime); this.unitIdToUnitType.put(typeId, unitTypeInstance); } return unitTypeInstance; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayer.java index 93425ef..98747ea 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayer.java @@ -1,7 +1,10 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.players; import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; +import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.util.WarsmashConstants; public class CPlayer { @@ -15,6 +18,7 @@ public class CPlayer { private int gold = 5000; private int lumber = 5000; private final EnumSet[] alliances = new EnumSet[WarsmashConstants.MAX_PLAYERS]; + private final Map rawcodeToTechtreeUnlocked = new HashMap<>(); public CPlayer(final int id, final CMapControl controlType, final String name, final CRace race, final float[] startLocation) { @@ -106,4 +110,12 @@ public class CPlayer { public void setColorIndex(final int colorIndex) { this.colorIndex = colorIndex; } + + public int getTechtreeUnlocked(final War3ID rawcode) { + final Integer techtreeUnlocked = this.rawcodeToTechtreeUnlocked.get(rawcode); + if (techtreeUnlocked == null) { + return 0; + } + return techtreeUnlocked; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java index eeb1f73..876a1a2 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java @@ -28,4 +28,10 @@ public interface SimulationRenderController { BufferedImage getBuildingPathingPixelMap(War3ID rawcode); void spawnUnitConstructionFinishSound(CUnit constructedStructure); + + void spawnBuildingDeathEffect(CUnit cUnit); + + void spawnUnitReadySound(CUnit trainedUnit); + + void unitRepositioned(CUnit cUnit); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java index 59140e0..03499c6 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java @@ -57,6 +57,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.AbilityDataU import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.IconUI; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.commandbuttons.CommandButtonListener; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit.QueueItemType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitClassification; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitFilterFunction; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitStateListener; @@ -111,11 +112,19 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma private final Rectangle tempRect = new Rectangle(); private final Vector2 projectionTemp1 = new Vector2(); private final Vector2 projectionTemp2 = new Vector2(); + private UIFrame simpleInfoPanelUnitDetail; private StringFrame simpleNameValue; private StringFrame simpleClassValue; private StringFrame simpleBuildingActionLabel; private SimpleStatusBarFrame simpleBuildTimeIndicator; + + private UIFrame simpleInfoPanelBuildingDetail; + private StringFrame simpleBuildingNameValue; + private StringFrame simpleBuildingDescriptionValue; + private StringFrame simpleBuildingBuildingActionLabel; + private SimpleStatusBarFrame simpleBuildingBuildTimeIndicator; + private UIFrame attack1Icon; private TextureFrame attack1IconBackdrop; private StringFrame attack1InfoPanelIconValue; @@ -282,12 +291,15 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.unitLifeText = (StringFrame) this.rootFrame.getFrameByName("UnitPortraitHitPointText", 0); this.unitManaText = (StringFrame) this.rootFrame.getFrameByName("UnitPortraitManaPointText", 0); + // Create Simple Info Unit Detail this.simpleInfoPanelUnitDetail = this.rootFrame.createSimpleFrame("SimpleInfoPanelUnitDetail", this.consoleUI, 0); this.simpleInfoPanelUnitDetail .addAnchor(new AnchorDefinition(FramePoint.BOTTOM, 0, GameUI.convertY(this.uiViewport, 0.0f))); - this.simpleInfoPanelUnitDetail.setWidth(GameUI.convertY(this.uiViewport, 0.180f)); - this.simpleInfoPanelUnitDetail.setHeight(GameUI.convertY(this.uiViewport, 0.105f)); + final float infoPanelUnitDetailWidth = GameUI.convertY(this.uiViewport, 0.180f); + this.simpleInfoPanelUnitDetail.setWidth(infoPanelUnitDetailWidth); + final float infoPanelUnitDetailHeight = GameUI.convertY(this.uiViewport, 0.105f); + this.simpleInfoPanelUnitDetail.setHeight(infoPanelUnitDetailHeight); this.simpleNameValue = (StringFrame) this.rootFrame.getFrameByName("SimpleNameValue", 0); this.simpleClassValue = (StringFrame) this.rootFrame.getFrameByName("SimpleClassValue", 0); this.simpleBuildingActionLabel = (StringFrame) this.rootFrame.getFrameByName("SimpleBuildingActionLabel", 0); @@ -297,8 +309,33 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma simpleBuildTimeIndicatorBar.setTexture("SimpleBuildTimeIndicator", this.rootFrame); final TextureFrame simpleBuildTimeIndicatorBorder = this.simpleBuildTimeIndicator.getBorderFrame(); simpleBuildTimeIndicatorBorder.setTexture("SimpleBuildTimeIndicatorBorder", this.rootFrame); - this.simpleBuildTimeIndicator.setWidth(GameUI.convertX(this.uiViewport, 0.10538f)); - this.simpleBuildTimeIndicator.setHeight(GameUI.convertY(this.uiViewport, 0.0103f)); + final float buildTimeIndicatorWidth = GameUI.convertX(this.uiViewport, 0.10538f); + final float buildTimeIndicatorHeight = GameUI.convertY(this.uiViewport, 0.0103f); + this.simpleBuildTimeIndicator.setWidth(buildTimeIndicatorWidth); + this.simpleBuildTimeIndicator.setHeight(buildTimeIndicatorHeight); + + // Create Simple Info Panel Building Detail + this.simpleInfoPanelBuildingDetail = this.rootFrame.createSimpleFrame("SimpleInfoPanelBuildingDetail", + this.consoleUI, 0); + this.simpleInfoPanelBuildingDetail + .addAnchor(new AnchorDefinition(FramePoint.BOTTOM, 0, GameUI.convertY(this.uiViewport, 0.0f))); + this.simpleInfoPanelBuildingDetail.setWidth(infoPanelUnitDetailWidth); + this.simpleInfoPanelBuildingDetail.setHeight(infoPanelUnitDetailHeight); + this.simpleBuildingNameValue = (StringFrame) this.rootFrame.getFrameByName("SimpleBuildingNameValue", 0); + this.simpleBuildingDescriptionValue = (StringFrame) this.rootFrame + .getFrameByName("SimpleBuildingDescriptionValue", 0); + this.simpleBuildingBuildingActionLabel = (StringFrame) this.rootFrame + .getFrameByName("SimpleBuildingActionLabel", 0); + this.simpleBuildingBuildTimeIndicator = (SimpleStatusBarFrame) this.rootFrame + .getFrameByName("SimpleBuildTimeIndicator", 0); + final TextureFrame simpleBuildingBuildTimeIndicatorBar = this.simpleBuildingBuildTimeIndicator.getBarFrame(); + simpleBuildingBuildTimeIndicatorBar.setTexture("SimpleBuildTimeIndicator", this.rootFrame); + final TextureFrame simpleBuildingBuildTimeIndicatorBorder = this.simpleBuildingBuildTimeIndicator + .getBorderFrame(); + simpleBuildingBuildTimeIndicatorBorder.setTexture("SimpleBuildTimeIndicatorBorder", this.rootFrame); + this.simpleBuildingBuildTimeIndicator.setWidth(buildTimeIndicatorWidth); + this.simpleBuildingBuildTimeIndicator.setHeight(buildTimeIndicatorHeight); + this.simpleInfoPanelBuildingDetail.setVisible(false); this.attack1Icon = this.rootFrame.createSimpleFrame("SimpleInfoPanelIconDamage", this.simpleInfoPanelUnitDetail, 0); @@ -494,7 +531,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.war3MapViewer.mapPathSolver, this.war3MapViewer.solverParams); this.cursorModelInstance = (MdxComplexInstance) model.addInstance(); this.cursorModelInstance.setVertexColor(new float[] { 1, 1, 1, 0.5f }); - this.cursorModelInstance.setTeamColor(activeCommandUnit.getSimulationUnit().getPlayerIndex()); + this.cursorModelInstance.setTeamColor(this.activeCommandUnit.getSimulationUnit().getPlayerIndex()); this.cursorModelInstance.rotate(RenderUnit.tempQuat.setFromAxis(RenderMathUtils.VEC3_UNIT_Z, this.war3MapViewer.simulation.getGameplayConstants().getBuildingAngle())); this.cursorModelInstance.setAnimationSpeed(0f); @@ -570,6 +607,14 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma .setValue(Math.min(this.selectedUnit.getSimulationUnit().getConstructionProgress() / this.selectedUnit.getSimulationUnit().getUnitType().getBuildTime(), 0.99f)); } + if (this.simpleBuildingBuildTimeIndicator.isVisible() && (this.selectedUnit != null)) { + this.simpleBuildingBuildTimeIndicator + .setValue(Math.min( + this.selectedUnit.getSimulationUnit().getConstructionProgress() / this.selectedUnit + .getSimulationUnit().getBuildQueueTimeRemaining(this.war3MapViewer.simulation), + 0.99f)); + } + final float groundHeight = Math.max( this.war3MapViewer.terrain.getGroundHeight(this.cameraManager.target.x, this.cameraManager.target.y), this.war3MapViewer.terrain.getWaterHeight(this.cameraManager.target.x, this.cameraManager.target.y)); @@ -687,83 +732,110 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } private void reloadSelectedUnitUI(final RenderUnit unit) { - this.simpleNameValue.setText(unit.getSimulationUnit().getUnitType().getName()); - String classText = null; - for (final CUnitClassification classification : unit.getSimulationUnit().getClassifications()) { - if ((classification == CUnitClassification.MECHANICAL) - && unit.getSimulationUnit().getUnitType().isBuilding()) { - // buildings dont display MECHANICAL - continue; - } - if (classification.getDisplayName() != null) { - classText = classification.getDisplayName(); - } - } - if (classText != null) { - this.simpleClassValue.setText(classText); - } - else { - this.simpleClassValue.setText(""); - } - this.unitLifeText.setText(FastNumberFormat.formatWholeNumber(unit.getSimulationUnit().getLife()) + " / " - + unit.getSimulationUnit().getMaximumLife()); - final int maximumMana = unit.getSimulationUnit().getMaximumMana(); + final CUnit simulationUnit = unit.getSimulationUnit(); + this.unitLifeText.setText( + FastNumberFormat.formatWholeNumber(simulationUnit.getLife()) + " / " + simulationUnit.getMaximumLife()); + final int maximumMana = simulationUnit.getMaximumMana(); if (maximumMana > 0) { - this.unitManaText.setText( - FastNumberFormat.formatWholeNumber(unit.getSimulationUnit().getMana()) + " / " + maximumMana); + this.unitManaText + .setText(FastNumberFormat.formatWholeNumber(simulationUnit.getMana()) + " / " + maximumMana); } else { this.unitManaText.setText(""); } - this.simpleBuildingActionLabel.setText(""); + if (simulationUnit.getBuildQueue()[0] != null) { + this.simpleInfoPanelBuildingDetail.setVisible(true); + this.simpleInfoPanelUnitDetail.setVisible(false); + this.simpleBuildingNameValue.setText(simulationUnit.getUnitType().getName()); + this.simpleBuildingDescriptionValue.setText(""); - final boolean anyAttacks = unit.getSimulationUnit().getUnitType().getAttacks().size() > 0; - final boolean constructing = unit.getSimulationUnit().isConstructing(); - final UIFrame localArmorIcon = this.armorIcon; - final TextureFrame localArmorIconBackdrop = this.armorIconBackdrop; - final StringFrame localArmorInfoPanelIconValue = this.armorInfoPanelIconValue; - if (anyAttacks && !constructing) { - final CUnitAttack attackOne = unit.getSimulationUnit().getUnitType().getAttacks().get(0); - this.attack1Icon.setVisible(attackOne.isShowUI()); - this.attack1IconBackdrop.setTexture(this.damageBackdrops.getTexture(attackOne.getAttackType())); - this.attack1InfoPanelIconValue.setText(attackOne.getMinDamage() + " - " + attackOne.getMaxDamage()); - if (unit.getSimulationUnit().getUnitType().getAttacks().size() > 1) { - final CUnitAttack attackTwo = unit.getSimulationUnit().getUnitType().getAttacks().get(1); - this.attack2Icon.setVisible(attackTwo.isShowUI()); - this.attack2IconBackdrop.setTexture(this.damageBackdrops.getTexture(attackTwo.getAttackType())); - this.attack2InfoPanelIconValue.setText(attackTwo.getMinDamage() + " - " + attackTwo.getMaxDamage()); + this.simpleBuildingBuildTimeIndicator.setVisible(true); + this.simpleBuildTimeIndicator.setVisible(false); + if (simulationUnit.getBuildQueueTypes()[0] == QueueItemType.UNIT) { + this.simpleBuildingBuildingActionLabel + .setText(this.rootFrame.getTemplates().getDecoratedString("TRAINING")); } else { - this.attack2Icon.setVisible(false); + this.simpleBuildingBuildingActionLabel + .setText(this.rootFrame.getTemplates().getDecoratedString("RESEARCHING")); } - - this.armorIcon - .addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, FramePoint.TOPLEFT, - GameUI.convertX(this.uiViewport, 0f), GameUI.convertY(this.uiViewport, -0.06025f))); - this.armorIcon.positionBounds(this.uiViewport); - } - else { this.attack1Icon.setVisible(false); this.attack2Icon.setVisible(false); + this.armorIcon.setVisible(false); + } + else { + this.simpleInfoPanelBuildingDetail.setVisible(false); + this.simpleInfoPanelUnitDetail.setVisible(true); + this.simpleNameValue.setText(simulationUnit.getUnitType().getName()); + String classText = null; + for (final CUnitClassification classification : simulationUnit.getClassifications()) { + if ((classification == CUnitClassification.MECHANICAL) && simulationUnit.getUnitType().isBuilding()) { + // buildings dont display MECHANICAL + continue; + } + if (classification.getDisplayName() != null) { + classText = classification.getDisplayName(); + } + } + if (classText != null) { + this.simpleClassValue.setText(classText); + } + else { + this.simpleClassValue.setText(""); + } - this.armorIcon.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, - FramePoint.TOPLEFT, 0, GameUI.convertY(this.uiViewport, -0.030125f))); - this.armorIcon.positionBounds(this.uiViewport); - } + final boolean anyAttacks = simulationUnit.getUnitType().getAttacks().size() > 0; + final boolean constructing = simulationUnit.isConstructing(); + final UIFrame localArmorIcon = this.armorIcon; + final TextureFrame localArmorIconBackdrop = this.armorIconBackdrop; + final StringFrame localArmorInfoPanelIconValue = this.armorInfoPanelIconValue; + if (anyAttacks && !constructing) { + final CUnitAttack attackOne = simulationUnit.getUnitType().getAttacks().get(0); + this.attack1Icon.setVisible(attackOne.isShowUI()); + this.attack1IconBackdrop.setTexture(this.damageBackdrops.getTexture(attackOne.getAttackType())); + this.attack1InfoPanelIconValue.setText(attackOne.getMinDamage() + " - " + attackOne.getMaxDamage()); + if (simulationUnit.getUnitType().getAttacks().size() > 1) { + final CUnitAttack attackTwo = simulationUnit.getUnitType().getAttacks().get(1); + this.attack2Icon.setVisible(attackTwo.isShowUI()); + this.attack2IconBackdrop.setTexture(this.damageBackdrops.getTexture(attackTwo.getAttackType())); + this.attack2InfoPanelIconValue.setText(attackTwo.getMinDamage() + " - " + attackTwo.getMaxDamage()); + } + else { + this.attack2Icon.setVisible(false); + } - localArmorIcon.setVisible(!constructing); - this.simpleBuildTimeIndicator.setVisible(constructing); - if (constructing) { - this.simpleBuildingActionLabel.setText(this.rootFrame.getTemplates().getDecoratedString("CONSTRUCTING")); + this.armorIcon.addSetPoint( + new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, FramePoint.TOPLEFT, + GameUI.convertX(this.uiViewport, 0f), GameUI.convertY(this.uiViewport, -0.06025f))); + this.armorIcon.positionBounds(this.uiViewport); + } + else { + this.attack1Icon.setVisible(false); + this.attack2Icon.setVisible(false); + + this.armorIcon.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, + FramePoint.TOPLEFT, 0, GameUI.convertY(this.uiViewport, -0.030125f))); + this.armorIcon.positionBounds(this.uiViewport); + } + + localArmorIcon.setVisible(!constructing); + this.simpleBuildTimeIndicator.setVisible(constructing); + this.simpleBuildingBuildTimeIndicator.setVisible(false); + if (constructing) { + this.simpleBuildingActionLabel + .setText(this.rootFrame.getTemplates().getDecoratedString("CONSTRUCTING")); + } + else { + this.simpleBuildingActionLabel.setText(""); + } + final Texture defenseTexture = this.defenseBackdrops + .getTexture(simulationUnit.getUnitType().getDefenseType()); + if (defenseTexture == null) { + throw new RuntimeException(simulationUnit.getUnitType().getDefenseType() + " can't find texture!"); + } + localArmorIconBackdrop.setTexture(defenseTexture); + localArmorInfoPanelIconValue.setText(Integer.toString(simulationUnit.getDefense())); } - final Texture defenseTexture = this.defenseBackdrops - .getTexture(unit.getSimulationUnit().getUnitType().getDefenseType()); - if (defenseTexture == null) { - throw new RuntimeException( - unit.getSimulationUnit().getUnitType().getDefenseType() + " can't find texture!"); - } - localArmorIconBackdrop.setTexture(defenseTexture); - localArmorInfoPanelIconValue.setText(Integer.toString(unit.getSimulationUnit().getDefense())); clearAndRepopulateCommandCard(); } @@ -779,8 +851,15 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma public void commandButton(final int buttonPositionX, final int buttonPositionY, final Texture icon, final int abilityHandleId, final int orderId, final int autoCastId, final boolean active, final boolean autoCastActive, final boolean menuButton) { - final int x = Math.max(0, Math.min(COMMAND_CARD_WIDTH - 1, buttonPositionX)); - final int y = Math.max(0, Math.min(COMMAND_CARD_HEIGHT - 1, buttonPositionY)); + int x = Math.max(0, Math.min(COMMAND_CARD_WIDTH - 1, buttonPositionX)); + int y = Math.max(0, Math.min(COMMAND_CARD_HEIGHT - 1, buttonPositionY)); + while (this.commandCard[y][x].isVisible() && (y < COMMAND_CARD_HEIGHT) && (x < COMMAND_CARD_WIDTH)) { + x++; + if (x >= COMMAND_CARD_WIDTH) { + x = 0; + y++; + } + } this.commandCard[y][x].setCommandButtonData(icon, abilityHandleId, orderId, autoCastId, active, autoCastActive, menuButton); } @@ -877,6 +956,11 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma reloadSelectedUnitUI(this.selectedUnit); } + @Override + public void queueChanged() { + reloadSelectedUnitUI(this.selectedUnit); + } + private void clearAndRepopulateCommandCard() { clearCommandCard(); final AbilityDataUI abilityDataUI = this.war3MapViewer.getAbilityDataUI(); @@ -885,9 +969,6 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma final IconUI cancelUI = abilityDataUI.getCancelUI(); this.commandButton(cancelUI.getButtonPositionX(), cancelUI.getButtonPositionY(), cancelUI.getIcon(), 0, menuOrderId, 0, false, false, true); - } - else if (this.selectedUnit.getSimulationUnit().isConstructing()) { - } else { if (menuOrderId != 0) { From 9f80e62ead49d527b0dd8b5ff6b86fd5a324d9b3 Mon Sep 17 00:00:00 2001 From: Retera Date: Sun, 15 Nov 2020 05:00:11 -0500 Subject: [PATCH 070/116] Unit training queue display working --- .../etheller/warsmash/parsers/fdf/GameUI.java | 39 ++-- .../fdf/frames/AbstractRenderableFrame.java | 219 ++++++++---------- .../parsers/fdf/frames/AbstractUIFrame.java | 5 +- .../parsers/fdf/frames/AnchorPoint.java | 36 +++ .../fdf/frames/FramePointAssignment.java | 10 + .../warsmash/parsers/fdf/frames/SetPoint.java | 14 +- .../parsers/fdf/frames/SpriteFrame.java | 2 +- .../parsers/fdf/frames/StringFrame.java | 3 +- .../parsers/fdf/frames/TextureFrame.java | 2 +- .../warsmash/parsers/fdf/frames/UIFrame.java | 3 +- .../etheller/warsmash/parsers/jass/Jass2.java | 2 +- .../viewer5/handlers/w3x/SplatModel.java | 4 +- .../handlers/w3x/environment/Terrain.java | 6 +- .../w3x/simulation/data/CUnitData.java | 10 +- .../handlers/w3x/ui/CommandCardIcon.java | 18 +- .../viewer5/handlers/w3x/ui/MeleeUI.java | 136 ++++++++--- 16 files changed, 315 insertions(+), 194 deletions(-) create mode 100644 core/src/com/etheller/warsmash/parsers/fdf/frames/AnchorPoint.java create mode 100644 core/src/com/etheller/warsmash/parsers/fdf/frames/FramePointAssignment.java diff --git a/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java index fd56287..63b2fe8 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java @@ -212,9 +212,10 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { final FrameDefinition frameDefinition = this.templates.getFrame(name); if (frameDefinition.getFrameClass() == FrameClass.Frame) { if ("SPRITE".equals(frameDefinition.getFrameType())) { - final UIFrame inflated = inflate(frameDefinition, owner, null); + final UIFrame inflated = inflate(frameDefinition, owner, null, + frameDefinition.has("DecorateFileNames")); if (this.autoPosition) { - inflated.positionBounds(this.viewport); + inflated.positionBounds(this, this.viewport); } add(inflated); return inflated; @@ -225,10 +226,15 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { public UIFrame createSimpleFrame(final String name, final UIFrame owner, final int createContext) { final FrameDefinition frameDefinition = this.templates.getFrame(name); - if (frameDefinition.getFrameClass() == FrameClass.Frame) { - final UIFrame inflated = inflate(frameDefinition, owner, null); + if (frameDefinition == null) { + final SimpleFrame simpleFrame = new SimpleFrame(name, owner); + add(simpleFrame); + return simpleFrame; + } + else if (frameDefinition.getFrameClass() == FrameClass.Frame) { + final UIFrame inflated = inflate(frameDefinition, owner, null, frameDefinition.has("DecorateFileNames")); if (this.autoPosition) { - inflated.positionBounds(this.viewport); + inflated.positionBounds(this, this.viewport); } add(inflated); return inflated; @@ -268,7 +274,7 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { } public UIFrame inflate(final FrameDefinition frameDefinition, final UIFrame parent, - final FrameDefinition parentDefinitionIfAvailable) { + final FrameDefinition parentDefinitionIfAvailable, final boolean inDecorateFileNames) { UIFrame inflatedFrame = null; BitmapFont frameFont = null; Viewport viewport2 = this.viewport; @@ -281,7 +287,8 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { // mapping this.nameToFrame.put(frameDefinition.getName(), simpleFrame); for (final FrameDefinition childDefinition : frameDefinition.getInnerFrames()) { - simpleFrame.add(inflate(childDefinition, simpleFrame, frameDefinition)); + simpleFrame.add(inflate(childDefinition, simpleFrame, frameDefinition, + inDecorateFileNames || childDefinition.has("DecorateFileNames"))); } inflatedFrame = simpleFrame; } @@ -292,7 +299,8 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { final SimpleStatusBarFrame simpleStatusBarFrame = new SimpleStatusBarFrame(frameDefinition.getName(), parent, decorateFileNames); for (final FrameDefinition childDefinition : frameDefinition.getInnerFrames()) { - simpleStatusBarFrame.add(inflate(childDefinition, simpleStatusBarFrame, frameDefinition)); + simpleStatusBarFrame.add(inflate(childDefinition, simpleStatusBarFrame, frameDefinition, + inDecorateFileNames || childDefinition.has("DecorateFileNames"))); } inflatedFrame = simpleStatusBarFrame; } @@ -300,8 +308,7 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { final SpriteFrame spriteFrame = new SpriteFrame(frameDefinition.getName(), parent, this.uiScene, viewport2); String backgroundArt = frameDefinition.getString("BackgroundArt"); - if (frameDefinition.has("DecorateFileNames") || ((parentDefinitionIfAvailable != null) - && parentDefinitionIfAvailable.has("DecorateFileNames"))) { + if (frameDefinition.has("DecorateFileNames") || inDecorateFileNames) { if (this.skin.hasField(backgroundArt)) { backgroundArt = this.skin.getField(backgroundArt); } @@ -321,7 +328,8 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { simpleFrame.setSetAllPoints(true); this.nameToFrame.put(frameDefinition.getName(), simpleFrame); for (final FrameDefinition childDefinition : frameDefinition.getInnerFrames()) { - simpleFrame.add(inflate(childDefinition, simpleFrame, frameDefinition)); + simpleFrame.add(inflate(childDefinition, simpleFrame, frameDefinition, + inDecorateFileNames || childDefinition.has("DecorateFileNames"))); } inflatedFrame = simpleFrame; break; @@ -365,8 +373,7 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { break; case Texture: final String file = frameDefinition.getString("File"); - final boolean decorateFileNames = frameDefinition.has("DecorateFileNames") - || ((parentDefinitionIfAvailable != null) && parentDefinitionIfAvailable.has("DecorateFileNames")); + final boolean decorateFileNames = frameDefinition.has("DecorateFileNames") || inDecorateFileNames; final Vector4Definition texCoord = frameDefinition.getVector4("TexCoord"); final TextureFrame textureFrame = new TextureFrame(frameDefinition.getName(), parent, decorateFileNames, texCoord); @@ -432,7 +439,7 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { // TODO idk what inherits is doing yet, and I didn't implement createContext yet // even though it looked like just mapping/indexing on int final FrameDefinition frameDefinition = new FrameDefinition(FrameClass.Frame, typeName, name); - final UIFrame inflatedFrame = inflate(frameDefinition, owner, null); + final UIFrame inflatedFrame = inflate(frameDefinition, owner, null, frameDefinition.has("DecorateFileNames")); add(inflatedFrame); return inflatedFrame; } @@ -487,8 +494,8 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { } @Override - public final void positionBounds(final Viewport viewport) { - innerPositionBounds(viewport); + public final void positionBounds(final GameUI gameUI, final Viewport viewport) { + innerPositionBounds(this, viewport); } @Override diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java index ace3f36..e1ebe94 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java @@ -1,27 +1,35 @@ package com.etheller.warsmash.parsers.fdf.frames; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; +import java.util.EnumMap; import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.GlyphLayout; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.utils.viewport.Viewport; +import com.etheller.warsmash.parsers.fdf.GameUI; import com.etheller.warsmash.parsers.fdf.datamodel.AnchorDefinition; import com.etheller.warsmash.parsers.fdf.datamodel.FramePoint; public abstract class AbstractRenderableFrame implements UIFrame { + private static final FramePoint[] LEFT_ANCHOR_PRIORITY = { FramePoint.LEFT, FramePoint.TOPLEFT, + FramePoint.BOTTOMLEFT }; + private static final FramePoint[] RIGHT_ANCHOR_PRIORITY = { FramePoint.RIGHT, FramePoint.TOPRIGHT, + FramePoint.BOTTOMRIGHT }; + private static final FramePoint[] CENTER_HORIZ_ANCHOR_PRIORITY = { FramePoint.CENTER, FramePoint.TOP, + FramePoint.BOTTOM }; + private static final FramePoint[] CENTER_VERT_ANCHOR_PRIORITY = { FramePoint.CENTER, FramePoint.LEFT, + FramePoint.RIGHT }; + private static final FramePoint[] TOP_ANCHOR_PRIORITY = { FramePoint.TOP, FramePoint.TOPLEFT, FramePoint.TOPRIGHT }; + private static final FramePoint[] BOTTOM_ANCHOR_PRIORITY = { FramePoint.BOTTOM, FramePoint.BOTTOMLEFT, + FramePoint.BOTTOMRIGHT }; private static final boolean DEBUG_LOG = true; protected String name; protected UIFrame parent; protected boolean visible = true; protected int level; protected final Rectangle renderBounds = new Rectangle(0, 0, 0, 0); // in libgdx rendering space - protected List anchors = new ArrayList<>(); - protected List setPoints = new ArrayList<>(); - private boolean setAllPoints; + private final EnumMap framePointToAssignment = new EnumMap<>(FramePoint.class); public AbstractRenderableFrame(final String name, final UIFrame parent) { this.name = name; @@ -30,7 +38,11 @@ public abstract class AbstractRenderableFrame implements UIFrame { @Override public void setSetAllPoints(final boolean setAllPoints) { - this.setAllPoints = setAllPoints; + for (final FramePoint framePoint : FramePoint.values()) { + if (!this.framePointToAssignment.containsKey(framePoint)) { + this.framePointToAssignment.put(framePoint, new SetPoint(framePoint, this.parent, framePoint, 0, 0)); + } + } } @Override @@ -43,88 +55,38 @@ public abstract class AbstractRenderableFrame implements UIFrame { this.renderBounds.height = height; } - private boolean hasLeftAnchor() { - for (final AnchorDefinition anchor : this.anchors) { - switch (anchor.getMyPoint()) { - case CENTER: - case BOTTOM: - case TOP: - case BOTTOMRIGHT: - case RIGHT: - case TOPRIGHT: - break; - case BOTTOMLEFT: - case LEFT: - case TOPLEFT: - return true; - default: - break; + private FramePointAssignment getByPriority(final FramePoint[] priorities) { + for (final FramePoint priorityFramePoint : priorities) { + final FramePointAssignment framePointAssignment = this.framePointToAssignment.get(priorityFramePoint); + if (framePointAssignment != null) { + return framePointAssignment; } } - return false; + return null; } - private boolean hasRightAnchor() { - for (final AnchorDefinition anchor : this.anchors) { - switch (anchor.getMyPoint()) { - case CENTER: - case BOTTOM: - case TOP: - case BOTTOMLEFT: - case LEFT: - case TOPLEFT: - break; - case BOTTOMRIGHT: - case RIGHT: - case TOPRIGHT: - return true; - default: - break; - } - } - return false; + private FramePointAssignment getLeftAnchor() { + return getByPriority(LEFT_ANCHOR_PRIORITY); } - private boolean hasTopAnchor() { - for (final AnchorDefinition anchor : this.anchors) { - switch (anchor.getMyPoint()) { - case CENTER: - case BOTTOM: - case BOTTOMLEFT: - case LEFT: - case BOTTOMRIGHT: - case RIGHT: - break; - case TOP: - case TOPLEFT: - case TOPRIGHT: - return true; - default: - break; - } - } - return false; + private FramePointAssignment getRightAnchor() { + return getByPriority(RIGHT_ANCHOR_PRIORITY); } - private boolean hasBottomAnchor() { - for (final AnchorDefinition anchor : this.anchors) { - switch (anchor.getMyPoint()) { - case CENTER: - case LEFT: - case RIGHT: - case TOP: - case TOPLEFT: - case TOPRIGHT: - break; - case BOTTOM: - case BOTTOMLEFT: - case BOTTOMRIGHT: - return true; - default: - break; - } - } - return false; + private FramePointAssignment getTopAnchor() { + return getByPriority(TOP_ANCHOR_PRIORITY); + } + + private FramePointAssignment getBottomAnchor() { + return getByPriority(BOTTOM_ANCHOR_PRIORITY); + } + + private FramePointAssignment getCenterHorizontalAnchor() { + return getByPriority(CENTER_HORIZ_ANCHOR_PRIORITY); + } + + private FramePointAssignment getCenterVerticalAnchor() { + return getByPriority(CENTER_VERT_ANCHOR_PRIORITY); } @Override @@ -162,7 +124,7 @@ public abstract class AbstractRenderableFrame implements UIFrame { case BOTTOMLEFT: case LEFT: case TOPLEFT: - if (hasRightAnchor()) { + if (getRightAnchor() != null) { final float oldRightX = this.renderBounds.x + this.renderBounds.width; this.renderBounds.x = x; this.renderBounds.width = oldRightX - x; @@ -175,7 +137,7 @@ public abstract class AbstractRenderableFrame implements UIFrame { case BOTTOMRIGHT: case RIGHT: case TOPRIGHT: - if (hasLeftAnchor()) { + if (getLeftAnchor() != null) { this.renderBounds.width = x - this.renderBounds.x; } else { @@ -222,7 +184,7 @@ public abstract class AbstractRenderableFrame implements UIFrame { case TOPLEFT: case TOP: case TOPRIGHT: - if (hasBottomAnchor()) { + if (getBottomAnchor() != null) { this.renderBounds.height = y - this.renderBounds.y; } else { @@ -232,7 +194,7 @@ public abstract class AbstractRenderableFrame implements UIFrame { case BOTTOMLEFT: case BOTTOM: case BOTTOMRIGHT: - if (hasTopAnchor()) { + if (getTopAnchor() != null) { final float oldBottomY = this.renderBounds.y + this.renderBounds.height; this.renderBounds.y = y; this.renderBounds.height = oldBottomY - y; @@ -248,72 +210,83 @@ public abstract class AbstractRenderableFrame implements UIFrame { @Override public void addAnchor(final AnchorDefinition anchorDefinition) { - this.anchors.add(anchorDefinition); + this.framePointToAssignment.put(anchorDefinition.getMyPoint(), new SetPoint(anchorDefinition.getMyPoint(), + this.parent, anchorDefinition.getMyPoint(), anchorDefinition.getX(), anchorDefinition.getY())); } @Override public void addSetPoint(final SetPoint setPointDefinition) { - // TODO this is O(N) in the number of SetPoints, and that - // is not good performance. - final Iterator iterator = this.setPoints.iterator(); - while (iterator.hasNext()) { - final SetPoint setPoint = iterator.next(); - if (setPoint.getMyPoint() == setPointDefinition.getMyPoint()) { - iterator.remove(); - } - } - this.setPoints.add(setPointDefinition); + this.framePointToAssignment.put(setPointDefinition.getMyPoint(), setPointDefinition); } @Override - public void positionBounds(final Viewport viewport) { + public void positionBounds(final GameUI gameUI, final Viewport viewport) { if (this.parent == null) { // TODO this is a bit of a hack, remove later return; } - if (this.anchors.isEmpty() && this.setPoints.isEmpty()) { + if (this.framePointToAssignment.isEmpty()) { this.renderBounds.x = this.parent.getFramePointX(FramePoint.LEFT); this.renderBounds.y = this.parent.getFramePointY(FramePoint.BOTTOM); } else { - for (final AnchorDefinition anchor : this.anchors) { - final float parentPointX = this.parent.getFramePointX(anchor.getMyPoint()); - final float parentPointY = this.parent.getFramePointY(anchor.getMyPoint()); - setFramePointX(anchor.getMyPoint(), parentPointX + anchor.getX()); - setFramePointY(anchor.getMyPoint(), parentPointY + anchor.getY()); - if (DEBUG_LOG) { - System.out.println(getClass().getSimpleName() + ":" + this.name + " anchoring to: " + anchor); + final FramePointAssignment leftAnchor = getLeftAnchor(); + final FramePointAssignment rightAnchor = getRightAnchor(); + final FramePointAssignment topAnchor = getTopAnchor(); + final FramePointAssignment bottomAnchor = getBottomAnchor(); + final FramePointAssignment centerHorizontalAnchor = getCenterHorizontalAnchor(); + final FramePointAssignment centerVerticalAnchor = getCenterVerticalAnchor(); + if (leftAnchor != null) { + this.renderBounds.x = leftAnchor.getX(gameUI, viewport); + if (this.renderBounds.width == 0) { + if (rightAnchor != null) { + this.renderBounds.width = rightAnchor.getX(gameUI, viewport) - this.renderBounds.x; + } + else if (centerHorizontalAnchor != null) { + this.renderBounds.width = (centerHorizontalAnchor.getX(gameUI, viewport) - this.renderBounds.x) + * 2; + } } } - for (final SetPoint setPoint : this.setPoints) { - final UIFrame other = setPoint.getOther(); - if (other == null) { - continue; + else if (rightAnchor != null) { + this.renderBounds.x = rightAnchor.getX(gameUI, viewport) - this.renderBounds.width; + if (centerHorizontalAnchor != null) { + this.renderBounds.width = (this.renderBounds.x - centerHorizontalAnchor.getX(gameUI, viewport)) * 2; } - final float parentPointX = other.getFramePointX(setPoint.getOtherPoint()); - final float parentPointY = other.getFramePointY(setPoint.getOtherPoint()); - setFramePointX(setPoint.getMyPoint(), parentPointX + setPoint.getX()); - setFramePointY(setPoint.getMyPoint(), parentPointY + setPoint.getY()); } - } - if (this.setAllPoints) { - if (this.renderBounds.width == 0) { - this.renderBounds.width = this.parent.getFramePointX(FramePoint.RIGHT) - - this.parent.getFramePointX(FramePoint.LEFT); + else if (centerHorizontalAnchor != null) { + this.renderBounds.x = centerHorizontalAnchor.getX(gameUI, viewport) - (this.renderBounds.width / 2); } - if (this.renderBounds.height == 0) { - this.renderBounds.height = this.parent.getFramePointY(FramePoint.TOP) - - this.parent.getFramePointY(FramePoint.BOTTOM); + if (bottomAnchor != null) { + this.renderBounds.y = bottomAnchor.getY(gameUI, viewport); + if (this.renderBounds.height == 0) { + if (topAnchor != null) { + this.renderBounds.height = topAnchor.getY(gameUI, viewport) - this.renderBounds.y; + } + else if (centerVerticalAnchor != null) { + this.renderBounds.height = (centerVerticalAnchor.getY(gameUI, viewport) - this.renderBounds.y) + * 2; + } + } + } + else if (topAnchor != null) { + this.renderBounds.y = topAnchor.getY(gameUI, viewport) - this.renderBounds.height; + if (centerVerticalAnchor != null) { + this.renderBounds.height = (this.renderBounds.y - centerVerticalAnchor.getY(gameUI, viewport)) * 2; + } + } + else if (centerVerticalAnchor != null) { + this.renderBounds.y = centerVerticalAnchor.getY(gameUI, viewport) - (this.renderBounds.height / 2); } } if (DEBUG_LOG) { System.out.println(getClass().getSimpleName() + ":" + this.name + ":" + hashCode() + " finishing position bounds: " + this.renderBounds); } - innerPositionBounds(viewport); + innerPositionBounds(gameUI, viewport); } - protected abstract void innerPositionBounds(final Viewport viewport); + protected abstract void innerPositionBounds(GameUI gameUI, final Viewport viewport); public boolean isVisible() { return this.visible; diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractUIFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractUIFrame.java index 91e12d0..0f69ecd 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractUIFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractUIFrame.java @@ -7,6 +7,7 @@ import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.GlyphLayout; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.utils.viewport.Viewport; +import com.etheller.warsmash.parsers.fdf.GameUI; public abstract class AbstractUIFrame extends AbstractRenderableFrame implements UIFrame { private final List childFrames = new ArrayList<>(); @@ -30,9 +31,9 @@ public abstract class AbstractUIFrame extends AbstractRenderableFrame implements } @Override - protected void innerPositionBounds(final Viewport viewport) { + protected void innerPositionBounds(final GameUI gameUI, final Viewport viewport) { for (final UIFrame childFrame : this.childFrames) { - childFrame.positionBounds(viewport); + childFrame.positionBounds(gameUI, viewport); } } diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/AnchorPoint.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/AnchorPoint.java new file mode 100644 index 0000000..01566c9 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/AnchorPoint.java @@ -0,0 +1,36 @@ +package com.etheller.warsmash.parsers.fdf.frames; + +import com.badlogic.gdx.utils.viewport.Viewport; +import com.etheller.warsmash.parsers.fdf.GameUI; +import com.etheller.warsmash.parsers.fdf.datamodel.FramePoint; + +public class AnchorPoint implements FramePointAssignment { + private final FramePoint framePoint; + private final float x; + private final float y; + + public AnchorPoint(final FramePoint framePoint, final float x, final float y) { + this.framePoint = framePoint; + this.x = x; + this.y = y; + } + + public float getX() { + return this.x; + } + + public float getY() { + return this.y; + } + + @Override + public float getX(final GameUI gameUI, final Viewport uiViewport) { + return gameUI.getFramePointX(this.framePoint) + this.x; + } + + @Override + public float getY(final GameUI gameUI, final Viewport uiViewport) { + return gameUI.getFramePointY(this.framePoint) + this.y; + } + +} diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/FramePointAssignment.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/FramePointAssignment.java new file mode 100644 index 0000000..66706a7 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/FramePointAssignment.java @@ -0,0 +1,10 @@ +package com.etheller.warsmash.parsers.fdf.frames; + +import com.badlogic.gdx.utils.viewport.Viewport; +import com.etheller.warsmash.parsers.fdf.GameUI; + +public interface FramePointAssignment { + float getX(GameUI gameUI, Viewport uiViewport); + + float getY(GameUI gameUI, Viewport uiViewport); +} diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/SetPoint.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/SetPoint.java index 0c54c20..015fdb0 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/SetPoint.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/SetPoint.java @@ -1,8 +1,10 @@ package com.etheller.warsmash.parsers.fdf.frames; +import com.badlogic.gdx.utils.viewport.Viewport; +import com.etheller.warsmash.parsers.fdf.GameUI; import com.etheller.warsmash.parsers.fdf.datamodel.FramePoint; -public class SetPoint { +public class SetPoint implements FramePointAssignment { private final FramePoint myPoint; private final UIFrame other; private final FramePoint otherPoint; @@ -37,4 +39,14 @@ public class SetPoint { public float getY() { return this.y; } + + @Override + public float getX(final GameUI gameUI, final Viewport uiViewport) { + return this.other.getFramePointX(this.otherPoint) + this.x; + } + + @Override + public float getY(final GameUI gameUI, final Viewport uiViewport) { + return this.other.getFramePointY(this.otherPoint) + this.y; + } } diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/SpriteFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/SpriteFrame.java index f569e66..e05bb64 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/SpriteFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/SpriteFrame.java @@ -70,7 +70,7 @@ public class SpriteFrame extends AbstractRenderableFrame { } @Override - protected void innerPositionBounds(final Viewport viewport) { + protected void innerPositionBounds(final GameUI gameUI, final Viewport viewport) { updateInstanceLocation(viewport); } diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/StringFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/StringFrame.java index 9f7c888..b33fcc7 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/StringFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/StringFrame.java @@ -5,6 +5,7 @@ import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.GlyphLayout; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.utils.viewport.Viewport; +import com.etheller.warsmash.parsers.fdf.GameUI; import com.etheller.warsmash.parsers.fdf.datamodel.TextJustify; public class StringFrame extends AbstractRenderableFrame { @@ -69,7 +70,7 @@ public class StringFrame extends AbstractRenderableFrame { } @Override - protected void innerPositionBounds(final Viewport viewport) { + protected void innerPositionBounds(final GameUI gameUI, final Viewport viewport) { } } diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/TextureFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/TextureFrame.java index 86c5e39..c8f1412 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/TextureFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/TextureFrame.java @@ -31,7 +31,7 @@ public class TextureFrame extends AbstractRenderableFrame { } @Override - protected void innerPositionBounds(final Viewport viewport) { + protected void innerPositionBounds(final GameUI gameUI, final Viewport viewport) { } public void setTexture(String file, final GameUI gameUI) { diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/UIFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/UIFrame.java index cb819c0..151cfe5 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/UIFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/UIFrame.java @@ -4,6 +4,7 @@ import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.GlyphLayout; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.utils.viewport.Viewport; +import com.etheller.warsmash.parsers.fdf.GameUI; import com.etheller.warsmash.parsers.fdf.datamodel.AnchorDefinition; import com.etheller.warsmash.parsers.fdf.datamodel.FramePoint; @@ -18,7 +19,7 @@ public interface UIFrame { void setFramePointY(final FramePoint framePoint, final float y); - void positionBounds(final Viewport viewport); + void positionBounds(GameUI gameUI, final Viewport viewport); void addAnchor(final AnchorDefinition anchorDefinition); diff --git a/core/src/com/etheller/warsmash/parsers/jass/Jass2.java b/core/src/com/etheller/warsmash/parsers/jass/Jass2.java index e19a5a0..38f8540 100644 --- a/core/src/com/etheller/warsmash/parsers/jass/Jass2.java +++ b/core/src/com/etheller/warsmash/parsers/jass/Jass2.java @@ -284,7 +284,7 @@ public class Jass2 { public JassValue call(final List arguments, final GlobalScope globalScope, final TriggerExecutionScope triggerScope) { final UIFrame frame = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); - frame.positionBounds(uiViewport); + frame.positionBounds(JUIEnvironment.this.gameUI, uiViewport); return null; } }); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SplatModel.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SplatModel.java index eb93ebe..cd9c872 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SplatModel.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SplatModel.java @@ -36,7 +36,7 @@ public class SplatModel { this.color = new float[] { 1, 1, 1, 1 }; this.locations = locations; - if ((unitMapping != null) && (unitMapping.size() > 0)) { + if (unitMapping != null) { this.splatInstances = new ArrayList<>(); for (int i = 0; i < unitMapping.size(); i++) { this.splatInstances.add(new SplatMover(this)); @@ -46,7 +46,7 @@ public class SplatModel { this.splatInstances = null; } loadBatches(gl, centerOffset); - if ((unitMapping != null) && (unitMapping.size() > 0)) { + if (unitMapping != null) { if (this.splatInstances.size() != unitMapping.size()) { throw new IllegalStateException(); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java index 930f5ee..bdeff04 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java @@ -1383,7 +1383,7 @@ public class Terrain { final SplatModel splatModel = new SplatModel(Gdx.gl30, (Texture) this.viewer.load(path, PathSolver.DEFAULT, null), splat.locations, this.centerOffset, - splat.unitMapping, false); + splat.unitMapping.isEmpty() ? null : splat.unitMapping, false); splatModel.color[3] = splat.opacity; this.uberSplatModels.put(path, splatModel); } @@ -1401,7 +1401,7 @@ public class Terrain { SplatModel splatModel = this.uberSplatModels.get(path); if (splatModel == null) { splatModel = new SplatModel(Gdx.gl30, (Texture) this.viewer.load(path, PathSolver.DEFAULT, null), - new ArrayList<>(), this.centerOffset, null, false); + new ArrayList<>(), this.centerOffset, new ArrayList<>(), false); this.uberSplatModels.put(path, splatModel); } return splatModel.add(x - scale, y - scale, x + scale, y + scale, z, this.centerOffset); @@ -1412,7 +1412,7 @@ public class Terrain { SplatModel splatModel = this.uberSplatModels.get(texture); if (splatModel == null) { splatModel = new SplatModel(Gdx.gl30, (Texture) this.viewer.load(texture, PathSolver.DEFAULT, null), - new ArrayList<>(), this.centerOffset, null, false); + new ArrayList<>(), this.centerOffset, new ArrayList<>(), false); splatModel.color[3] = opacity; this.uberSplatModels.put(texture, splatModel); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java index 510371a..4869d37 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java @@ -270,7 +270,10 @@ public class CUnitData { final int damageUpgradeAmount = unitType.getFieldAsInteger(ATTACK1_DMG_UPGRADE_AMT, 0); final int maximumNumberOfTargets = unitType.getFieldAsInteger(ATTACK1_TARGET_COUNT, 0); final float projectileArc = unitType.getFieldAsFloat(ATTACK1_PROJECTILE_ARC, 0); - final String projectileArt = unitType.getFieldAsString(ATTACK1_MISSILE_ART, 0); + String projectileArt = unitType.getFieldAsString(ATTACK1_MISSILE_ART, 0); + if ("_".equals(projectileArt) || projectileArt.isEmpty()) { + projectileArt = unitType.getFieldAsString(ATTACK2_MISSILE_ART, 0); + } final boolean projectileHomingEnabled = unitType .getFieldAsBoolean(ATTACK1_PROJECTILE_HOMING_ENABLED, 0); final int projectileSpeed = unitType.getFieldAsInteger(ATTACK1_PROJECTILE_SPEED, 0); @@ -319,7 +322,10 @@ public class CUnitData { final int damageUpgradeAmount = unitType.getFieldAsInteger(ATTACK2_DMG_UPGRADE_AMT, 0); final int maximumNumberOfTargets = unitType.getFieldAsInteger(ATTACK2_TARGET_COUNT, 0); final float projectileArc = unitType.getFieldAsFloat(ATTACK2_PROJECTILE_ARC, 0); - final String projectileArt = unitType.getFieldAsString(ATTACK2_MISSILE_ART, 0); + String projectileArt = unitType.getFieldAsString(ATTACK2_MISSILE_ART, 0); + if ("_".equals(projectileArt) || projectileArt.isEmpty()) { + projectileArt = unitType.getFieldAsString(ATTACK1_MISSILE_ART, 0); + } final boolean projectileHomingEnabled = unitType .getFieldAsBoolean(ATTACK2_PROJECTILE_HOMING_ENABLED, 0); final int projectileSpeed = unitType.getFieldAsInteger(ATTACK2_PROJECTILE_SPEED, 0); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java index 034e7fd..f981edf 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java @@ -98,11 +98,11 @@ public class CommandCardIcon extends AbstractRenderableFrame { } @Override - protected void innerPositionBounds(final Viewport viewport) { - this.iconFrame.positionBounds(viewport); - this.activeHighlightFrame.positionBounds(viewport); - this.cooldownFrame.positionBounds(viewport); - this.autocastFrame.positionBounds(viewport); + protected void innerPositionBounds(final GameUI gameUI, final Viewport viewport) { + this.iconFrame.positionBounds(gameUI, viewport); + this.activeHighlightFrame.positionBounds(gameUI, viewport); + this.cooldownFrame.positionBounds(gameUI, viewport); + this.autocastFrame.positionBounds(gameUI, viewport); } @Override @@ -147,15 +147,15 @@ public class CommandCardIcon extends AbstractRenderableFrame { } } - public void mouseDown(final Viewport uiViewport) { + public void mouseDown(final GameUI gameUI, final Viewport uiViewport) { this.iconFrame.setWidth(GameUI.convertX(uiViewport, MeleeUI.DEFAULT_COMMAND_CARD_ICON_PRESSED_WIDTH)); this.iconFrame.setHeight(GameUI.convertY(uiViewport, MeleeUI.DEFAULT_COMMAND_CARD_ICON_PRESSED_WIDTH)); - positionBounds(uiViewport); + positionBounds(gameUI, uiViewport); } - public void mouseUp(final Viewport uiViewport) { + public void mouseUp(final GameUI gameUI, final Viewport uiViewport) { this.iconFrame.setWidth(GameUI.convertX(uiViewport, MeleeUI.DEFAULT_COMMAND_CARD_ICON_WIDTH)); this.iconFrame.setHeight(GameUI.convertY(uiViewport, MeleeUI.DEFAULT_COMMAND_CARD_ICON_WIDTH)); - positionBounds(uiViewport); + positionBounds(gameUI, uiViewport); } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java index 03499c6..0d0358c 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java @@ -24,7 +24,9 @@ import com.etheller.warsmash.parsers.fdf.GameUI; import com.etheller.warsmash.parsers.fdf.datamodel.AnchorDefinition; import com.etheller.warsmash.parsers.fdf.datamodel.FramePoint; import com.etheller.warsmash.parsers.fdf.datamodel.TextJustify; +import com.etheller.warsmash.parsers.fdf.datamodel.Vector4Definition; import com.etheller.warsmash.parsers.fdf.frames.SetPoint; +import com.etheller.warsmash.parsers.fdf.frames.SimpleFrame; import com.etheller.warsmash.parsers.fdf.frames.SimpleStatusBarFrame; import com.etheller.warsmash.parsers.fdf.frames.SpriteFrame; import com.etheller.warsmash.parsers.fdf.frames.StringFrame; @@ -124,6 +126,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma private StringFrame simpleBuildingDescriptionValue; private StringFrame simpleBuildingBuildingActionLabel; private SimpleStatusBarFrame simpleBuildingBuildTimeIndicator; + private final TextureFrame[] queueIconFrames = new TextureFrame[WarsmashConstants.BUILD_QUEUE_SIZE]; private UIFrame attack1Icon; private TextureFrame attack1IconBackdrop; @@ -167,6 +170,10 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma private final float widthRatioCorrection; private final float heightRatioCorrection; private CommandCardIcon mouseDownUIFrame; + private UIFrame smashSimpleInfoPanel; + private SimpleFrame smashAttack1IconWrapper; + private SimpleFrame smashAttack2IconWrapper; + private SimpleFrame smashArmorIconWrapper; public MeleeUI(final DataSource dataSource, final ExtendViewport uiViewport, final FreeTypeFontGenerator fontGenerator, final Scene uiScene, final Scene portraitScene, @@ -291,15 +298,17 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.unitLifeText = (StringFrame) this.rootFrame.getFrameByName("UnitPortraitHitPointText", 0); this.unitManaText = (StringFrame) this.rootFrame.getFrameByName("UnitPortraitManaPointText", 0); - // Create Simple Info Unit Detail - this.simpleInfoPanelUnitDetail = this.rootFrame.createSimpleFrame("SimpleInfoPanelUnitDetail", this.consoleUI, - 0); - this.simpleInfoPanelUnitDetail - .addAnchor(new AnchorDefinition(FramePoint.BOTTOM, 0, GameUI.convertY(this.uiViewport, 0.0f))); final float infoPanelUnitDetailWidth = GameUI.convertY(this.uiViewport, 0.180f); - this.simpleInfoPanelUnitDetail.setWidth(infoPanelUnitDetailWidth); final float infoPanelUnitDetailHeight = GameUI.convertY(this.uiViewport, 0.105f); - this.simpleInfoPanelUnitDetail.setHeight(infoPanelUnitDetailHeight); + this.smashSimpleInfoPanel = this.rootFrame.createSimpleFrame("SmashSimpleInfoPanel", this.rootFrame, 0); + this.smashSimpleInfoPanel + .addAnchor(new AnchorDefinition(FramePoint.BOTTOM, 0, GameUI.convertY(this.uiViewport, 0.0f))); + this.smashSimpleInfoPanel.setWidth(infoPanelUnitDetailWidth); + this.smashSimpleInfoPanel.setHeight(infoPanelUnitDetailHeight); + + // Create Simple Info Unit Detail + this.simpleInfoPanelUnitDetail = this.rootFrame.createSimpleFrame("SimpleInfoPanelUnitDetail", + this.smashSimpleInfoPanel, 0); this.simpleNameValue = (StringFrame) this.rootFrame.getFrameByName("SimpleNameValue", 0); this.simpleClassValue = (StringFrame) this.rootFrame.getFrameByName("SimpleClassValue", 0); this.simpleBuildingActionLabel = (StringFrame) this.rootFrame.getFrameByName("SimpleBuildingActionLabel", 0); @@ -316,11 +325,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma // Create Simple Info Panel Building Detail this.simpleInfoPanelBuildingDetail = this.rootFrame.createSimpleFrame("SimpleInfoPanelBuildingDetail", - this.consoleUI, 0); - this.simpleInfoPanelBuildingDetail - .addAnchor(new AnchorDefinition(FramePoint.BOTTOM, 0, GameUI.convertY(this.uiViewport, 0.0f))); - this.simpleInfoPanelBuildingDetail.setWidth(infoPanelUnitDetailWidth); - this.simpleInfoPanelBuildingDetail.setHeight(infoPanelUnitDetailHeight); + this.smashSimpleInfoPanel, 0); this.simpleBuildingNameValue = (StringFrame) this.rootFrame.getFrameByName("SimpleBuildingNameValue", 0); this.simpleBuildingDescriptionValue = (StringFrame) this.rootFrame .getFrameByName("SimpleBuildingDescriptionValue", 0); @@ -336,28 +341,59 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.simpleBuildingBuildTimeIndicator.setWidth(buildTimeIndicatorWidth); this.simpleBuildingBuildTimeIndicator.setHeight(buildTimeIndicatorHeight); this.simpleInfoPanelBuildingDetail.setVisible(false); + final TextureFrame simpleBuildQueueBackdrop = (TextureFrame) this.rootFrame + .getFrameByName("SimpleBuildQueueBackdrop", 0); + simpleBuildQueueBackdrop.setWidth(infoPanelUnitDetailWidth); + simpleBuildQueueBackdrop.setHeight(infoPanelUnitDetailWidth * 0.5f); - this.attack1Icon = this.rootFrame.createSimpleFrame("SimpleInfoPanelIconDamage", this.simpleInfoPanelUnitDetail, - 0); - this.attack1Icon.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, + this.queueIconFrames[0] = this.rootFrame.createTextureFrame("SmashBuildQueueIcon0", this.smashSimpleInfoPanel, + false, new Vector4Definition(0, 1, 0, 1)); + this.queueIconFrames[0].addAnchor(new AnchorDefinition(FramePoint.BOTTOMLEFT, + (infoPanelUnitDetailWidth * 15) / 256, (infoPanelUnitDetailWidth * 66) / 256)); + this.queueIconFrames[0].setWidth((infoPanelUnitDetailWidth * 38) / 256); + this.queueIconFrames[0].setHeight((infoPanelUnitDetailWidth * 38) / 256); + + for (int i = 1; i < this.queueIconFrames.length; i++) { + this.queueIconFrames[i] = this.rootFrame.createTextureFrame("SmashBuildQueueIcon" + i, + this.smashSimpleInfoPanel, false, new Vector4Definition(0, 1, 0, 1)); + this.queueIconFrames[i].addAnchor(new AnchorDefinition(FramePoint.BOTTOMLEFT, + (infoPanelUnitDetailWidth * (13 + (40 * (i - 1)))) / 256, (infoPanelUnitDetailWidth * 24) / 256)); + this.queueIconFrames[i].setWidth((infoPanelUnitDetailWidth * 29) / 256); + this.queueIconFrames[i].setHeight((infoPanelUnitDetailWidth * 29) / 256); + } + + this.smashAttack1IconWrapper = (SimpleFrame) this.rootFrame.createSimpleFrame("SmashSimpleInfoPanelIconDamage", + this.simpleInfoPanelUnitDetail, 0); + this.smashAttack1IconWrapper.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, FramePoint.TOPLEFT, 0, GameUI.convertY(this.uiViewport, -0.030125f))); + this.smashAttack1IconWrapper.setWidth(GameUI.convertX(this.uiViewport, 0.1f)); + this.smashAttack1IconWrapper.setHeight(GameUI.convertY(this.uiViewport, 0.030125f)); + this.attack1Icon = this.rootFrame.createSimpleFrame("SimpleInfoPanelIconDamage", this.smashAttack1IconWrapper, + 0); this.attack1IconBackdrop = (TextureFrame) this.rootFrame.getFrameByName("InfoPanelIconBackdrop", 0); this.attack1InfoPanelIconValue = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconValue", 0); this.attack1InfoPanelIconLevel = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconLevel", 0); - this.attack2Icon = this.rootFrame.createSimpleFrame("SimpleInfoPanelIconDamage", this.simpleInfoPanelUnitDetail, - 1); - this.attack2Icon + this.smashAttack2IconWrapper = (SimpleFrame) this.rootFrame.createSimpleFrame("SmashSimpleInfoPanelIconDamage", + this.simpleInfoPanelUnitDetail, 0); + this.smashAttack2IconWrapper .addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, FramePoint.TOPLEFT, GameUI.convertX(this.uiViewport, 0.1f), GameUI.convertY(this.uiViewport, -0.030125f))); + this.smashAttack2IconWrapper.setWidth(GameUI.convertX(this.uiViewport, 0.1f)); + this.smashAttack2IconWrapper.setHeight(GameUI.convertY(this.uiViewport, 0.030125f)); + this.attack2Icon = this.rootFrame.createSimpleFrame("SimpleInfoPanelIconDamage", this.smashAttack2IconWrapper, + 1); this.attack2IconBackdrop = (TextureFrame) this.rootFrame.getFrameByName("InfoPanelIconBackdrop", 1); this.attack2InfoPanelIconValue = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconValue", 1); this.attack2InfoPanelIconLevel = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconLevel", 1); - this.armorIcon = this.rootFrame.createSimpleFrame("SimpleInfoPanelIconArmor", this.simpleInfoPanelUnitDetail, - 1); - this.armorIcon.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, FramePoint.TOPLEFT, - GameUI.convertX(this.uiViewport, 0f), GameUI.convertY(this.uiViewport, -0.06025f))); + this.smashArmorIconWrapper = (SimpleFrame) this.rootFrame.createSimpleFrame("SmashSimpleInfoPanelIconArmor", + this.simpleInfoPanelUnitDetail, 0); + this.smashArmorIconWrapper.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, + FramePoint.TOPLEFT, GameUI.convertX(this.uiViewport, 0f), GameUI.convertY(this.uiViewport, -0.06025f))); + this.smashArmorIconWrapper.setWidth(GameUI.convertX(this.uiViewport, 0.1f)); + this.smashArmorIconWrapper.setHeight(GameUI.convertY(this.uiViewport, 0.030125f)); + this.armorIcon = this.rootFrame.createSimpleFrame("SimpleInfoPanelIconArmor", this.smashArmorIconWrapper, 0); this.armorIconBackdrop = (TextureFrame) this.rootFrame.getFrameByName("InfoPanelIconBackdrop", 0); this.armorInfoPanelIconValue = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconValue", 0); this.armorInfoPanelIconLevel = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconLevel", 0); @@ -423,7 +459,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.meleeUIMinimap = createMinimap(this.war3MapViewer); - this.rootFrame.positionBounds(this.uiViewport); + this.rootFrame.positionBounds(this.rootFrame, this.uiViewport); selectUnit(null); } @@ -720,10 +756,17 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.attack2Icon.setVisible(false); this.attack1InfoPanelIconLevel.setText(""); this.attack2InfoPanelIconLevel.setText(""); - this.simpleBuildingActionLabel.setText(""); + this.simpleBuildingBuildingActionLabel.setText(""); + this.simpleBuildingNameValue.setText(""); this.armorIcon.setVisible(false); this.armorInfoPanelIconLevel.setText(""); this.simpleBuildTimeIndicator.setVisible(false); + this.simpleBuildingBuildTimeIndicator.setVisible(false); + this.simpleInfoPanelBuildingDetail.setVisible(false); + this.simpleInfoPanelUnitDetail.setVisible(false); + for (final TextureFrame queueIconFrame : this.queueIconFrames) { + queueIconFrame.setVisible(false); + } } else { unit.getSimulationUnit().addStateListener(this); @@ -744,6 +787,28 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.unitManaText.setText(""); } if (simulationUnit.getBuildQueue()[0] != null) { + for (int i = 0; i < this.queueIconFrames.length; i++) { + final QueueItemType queueItemType = simulationUnit.getBuildQueueTypes()[i]; + if (queueItemType == null) { + this.queueIconFrames[i].setVisible(false); + } + else { + this.queueIconFrames[i].setVisible(true); + switch (queueItemType) { + case RESEARCH: + final IconUI upgradeUI = this.war3MapViewer.getAbilityDataUI() + .getUpgradeUI(simulationUnit.getBuildQueue()[i], 0); + this.queueIconFrames[i].setTexture(upgradeUI.getIcon()); + break; + case UNIT: + default: + final IconUI unitUI = this.war3MapViewer.getAbilityDataUI() + .getUnitUI(simulationUnit.getBuildQueue()[i]); + this.queueIconFrames[i].setTexture(unitUI.getIcon()); + break; + } + } + } this.simpleInfoPanelBuildingDetail.setVisible(true); this.simpleInfoPanelUnitDetail.setVisible(false); this.simpleBuildingNameValue.setText(simulationUnit.getUnitType().getName()); @@ -764,6 +829,9 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.armorIcon.setVisible(false); } else { + for (final TextureFrame queueIconFrame : this.queueIconFrames) { + queueIconFrame.setVisible(false); + } this.simpleInfoPanelBuildingDetail.setVisible(false); this.simpleInfoPanelUnitDetail.setVisible(true); this.simpleNameValue.setText(simulationUnit.getUnitType().getName()); @@ -804,18 +872,21 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.attack2Icon.setVisible(false); } - this.armorIcon.addSetPoint( + this.smashArmorIconWrapper.addSetPoint( new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, FramePoint.TOPLEFT, GameUI.convertX(this.uiViewport, 0f), GameUI.convertY(this.uiViewport, -0.06025f))); - this.armorIcon.positionBounds(this.uiViewport); + this.smashArmorIconWrapper.positionBounds(this.rootFrame, this.uiViewport); + this.armorIcon.positionBounds(this.rootFrame, this.uiViewport); } else { this.attack1Icon.setVisible(false); this.attack2Icon.setVisible(false); - this.armorIcon.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, - FramePoint.TOPLEFT, 0, GameUI.convertY(this.uiViewport, -0.030125f))); - this.armorIcon.positionBounds(this.uiViewport); + this.smashArmorIconWrapper.addSetPoint( + new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, FramePoint.TOPLEFT, + GameUI.convertX(this.uiViewport, 0f), GameUI.convertY(this.uiViewport, -0.030125f))); + this.smashArmorIconWrapper.positionBounds(this.rootFrame, this.uiViewport); + this.armorIcon.positionBounds(this.rootFrame, this.uiViewport); } localArmorIcon.setVisible(!constructing); @@ -824,6 +895,9 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma if (constructing) { this.simpleBuildingActionLabel .setText(this.rootFrame.getTemplates().getDecoratedString("CONSTRUCTING")); + this.queueIconFrames[0].setVisible(true); + this.queueIconFrames[0].setTexture(this.war3MapViewer.getAbilityDataUI() + .getUnitUI(this.selectedUnit.getSimulationUnit().getTypeId()).getIcon()); } else { this.simpleBuildingActionLabel.setText(""); @@ -1204,7 +1278,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma else { if (clickedUIFrame instanceof CommandCardIcon) { this.mouseDownUIFrame = (CommandCardIcon) clickedUIFrame; - this.mouseDownUIFrame.mouseDown(this.uiViewport); + this.mouseDownUIFrame.mouseDown(this.rootFrame, this.uiViewport); } } return false; @@ -1219,7 +1293,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.mouseDownUIFrame.onClick(button); this.war3MapViewer.getUiSounds().getSound("InterfaceClick").play(this.uiScene.audioContext, 0, 0); } - this.mouseDownUIFrame.mouseUp(this.uiViewport); + this.mouseDownUIFrame.mouseUp(this.rootFrame, this.uiViewport); } this.mouseDownUIFrame = null; return false; From d4595c5abc44083f6affa402f457fe9f3795401b Mon Sep 17 00:00:00 2001 From: Retera Date: Mon, 16 Nov 2020 23:49:43 -0500 Subject: [PATCH 071/116] Fix command card crashes --- .../warsmash/viewer5/handlers/w3x/ui/MeleeUI.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java index 0d0358c..666b7fb 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java @@ -927,15 +927,17 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma final boolean autoCastActive, final boolean menuButton) { int x = Math.max(0, Math.min(COMMAND_CARD_WIDTH - 1, buttonPositionX)); int y = Math.max(0, Math.min(COMMAND_CARD_HEIGHT - 1, buttonPositionY)); - while (this.commandCard[y][x].isVisible() && (y < COMMAND_CARD_HEIGHT) && (x < COMMAND_CARD_WIDTH)) { + while ((x < COMMAND_CARD_WIDTH) && (y < COMMAND_CARD_HEIGHT) && this.commandCard[y][x].isVisible()) { x++; if (x >= COMMAND_CARD_WIDTH) { x = 0; y++; } } - this.commandCard[y][x].setCommandButtonData(icon, abilityHandleId, orderId, autoCastId, active, autoCastActive, - menuButton); + if((x < COMMAND_CARD_WIDTH) && (y < COMMAND_CARD_HEIGHT)) { + this.commandCard[y][x].setCommandButtonData(icon, abilityHandleId, orderId, autoCastId, active, autoCastActive, + menuButton); + } } public void resize(final Rectangle viewport) { From 94d5d51f935705e159cc6c808116a32dee2d60d3 Mon Sep 17 00:00:00 2001 From: Retera Date: Tue, 24 Nov 2020 01:40:03 -0500 Subject: [PATCH 072/116] Update with attack ground aoe UI and other fixes --- .../etheller/warsmash/WarsmashGdxGame.java | 43 +- .../etheller/warsmash/WarsmashGdxMapGame.java | 5 +- .../warsmash/viewer5/ModelViewer.java | 3 + .../viewer5/handlers/mdx/Attachment.java | 12 +- .../handlers/mdx/EventObjectEmitter.java | 14 +- .../mdx/EventObjectEmitterObject.java | 2 +- .../viewer5/handlers/mdx/MdxViewer.java | 5 +- .../viewer5/handlers/w3x/SequenceUtils.java | 40 +- .../viewer5/handlers/w3x/SplatModel.java | 39 +- .../w3x/W3xScenePortraitLightManager.java | 52 +- .../viewer5/handlers/w3x/War3MapViewer.java | 20 +- .../handlers/w3x/environment/PathingGrid.java | 146 +++++ .../handlers/w3x/environment/Terrain.java | 15 +- .../w3x/rendersim/RenderAttackProjectile.java | 16 +- .../handlers/w3x/rendersim/RenderUnit.java | 22 +- .../w3x/rendersim/ability/AbilityDataUI.java | 14 +- .../CommandCardPopulatingAbilityVisitor.java | 60 +- .../w3x/simulation/CDestructable.java | 6 + .../handlers/w3x/simulation/CItem.java | 6 + .../handlers/w3x/simulation/CSimulation.java | 3 +- .../handlers/w3x/simulation/CUnit.java | 167 +++++- .../w3x/simulation/CUnitStateListener.java | 9 + .../handlers/w3x/simulation/CUnitType.java | 17 +- .../handlers/w3x/simulation/CWidget.java | 3 +- .../abilities/AbstractCAbility.java | 40 ++ .../w3x/simulation/abilities/CAbility.java | 8 +- .../simulation/abilities/CAbilityAttack.java | 46 +- .../simulation/abilities/CAbilityGeneric.java | 21 +- .../simulation/abilities/CAbilityMove.java | 23 +- .../simulation/abilities/CAbilityView.java | 10 +- .../simulation/abilities/CAbilityVisitor.java | 6 + .../build/AbstractCAbilityBuild.java | 8 +- .../build/CAbilityBuildInProgress.java | 11 +- .../abilities/build/CAbilityHumanBuild.java | 4 +- .../abilities/build/CAbilityNagaBuild.java | 4 +- .../abilities/build/CAbilityNeutralBuild.java | 4 +- .../build/CAbilityNightElfBuild.java | 4 +- .../abilities/build/CAbilityOrcBuild.java | 7 +- .../abilities/build/CAbilityUndeadBuild.java | 4 +- .../abilities/combat/CAbilityColdArrows.java | 23 +- .../GenericSingleIconActiveAbility.java | 10 + .../abilities/queue/CAbilityQueue.java | 30 +- .../abilities/queue/CAbilityRally.java | 102 ++++ .../targeting/AbilityPointTarget.java | 34 ++ .../abilities/targeting/AbilityTarget.java | 9 + .../AbilityTargetStillAliveVisitor.java | 30 + .../targeting/AbilityTargetVisitor.java | 15 + .../targeting/AbilityTargetWidgetVisitor.java | 31 ++ .../behaviors/CAbstractRangedBehavior.java | 17 +- .../CAbstractRangedPointTargetBehavior.java | 35 -- .../CAbstractRangedWidgetTargetBehavior.java | 39 -- .../simulation/behaviors/CBehaviorAttack.java | 53 +- .../simulation/behaviors/CBehaviorFollow.java | 5 +- .../simulation/behaviors/CBehaviorMove.java | 69 ++- .../simulation/behaviors/CBehaviorPatrol.java | 14 +- ...yDisableWhileUnderConstructionVisitor.java | 120 ++++ .../behaviors/build/CBehaviorOrcBuild.java | 56 +- .../w3x/simulation/combat/CWeaponType.java | 4 + .../combat/attacks/CUnitAttack.java | 4 +- .../combat/attacks/CUnitAttackInstant.java | 11 +- .../combat/attacks/CUnitAttackMissile.java | 13 +- .../attacks/CUnitAttackMissileBounce.java | 16 +- .../attacks/CUnitAttackMissileSplash.java | 10 +- .../combat/attacks/CUnitAttackNormal.java | 9 +- .../combat/projectile/CAttackProjectile.java | 10 +- .../w3x/simulation/data/CUnitData.java | 41 +- .../simulation/orders/COrderTargetPoint.java | 7 +- .../pathing/CBuildingPathingType.java | 30 + .../players/CPlayerUnitOrderExecutor.java | 10 +- .../players/CPlayerUnitOrderListener.java | 2 + .../util/AbilityActivationReceiver.java | 2 + .../BooleanAbilityActivationReceiver.java | 5 + .../util/PointAbilityTargetCheckReceiver.java | 10 +- .../util/SimulationRenderController.java | 3 +- .../StringMsgAbilityActivationReceiver.java | 5 + .../handlers/w3x/ui/CommandCardIcon.java | 10 +- .../viewer5/handlers/w3x/ui/MeleeUI.java | 521 +++++++++++++++--- .../viewer5/handlers/w3x/ui/QueueIcon.java | 99 ++++ .../w3x/ui/command/ClickableActionFrame.java | 13 + .../w3x/ui/command/QueueIconListener.java | 5 + .../warsmash/desktop/DesktopLauncher.java | 6 +- 81 files changed, 1982 insertions(+), 475 deletions(-) create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/GenericSingleIconActiveAbility.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityRally.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/targeting/AbilityPointTarget.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/targeting/AbilityTarget.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/targeting/AbilityTargetStillAliveVisitor.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/targeting/AbilityTargetVisitor.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/targeting/AbilityTargetWidgetVisitor.java delete mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CAbstractRangedPointTargetBehavior.java delete mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CAbstractRangedWidgetTargetBehavior.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/AbilityDisableWhileUnderConstructionVisitor.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/pathing/CBuildingPathingType.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/QueueIcon.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/ClickableActionFrame.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/QueueIconListener.java diff --git a/core/src/com/etheller/warsmash/WarsmashGdxGame.java b/core/src/com/etheller/warsmash/WarsmashGdxGame.java index 674083a..a5cb40a 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxGame.java @@ -3,7 +3,8 @@ package com.etheller.warsmash; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.IntBuffer; -import java.util.Arrays; +import java.util.ArrayList; +import java.util.List; import com.badlogic.gdx.ApplicationAdapter; import com.badlogic.gdx.Gdx; @@ -17,7 +18,10 @@ import com.etheller.warsmash.datasources.CompoundDataSourceDescriptor; import com.etheller.warsmash.datasources.DataSource; import com.etheller.warsmash.datasources.DataSourceDescriptor; import com.etheller.warsmash.datasources.FolderDataSourceDescriptor; +import com.etheller.warsmash.datasources.MpqDataSourceDescriptor; import com.etheller.warsmash.parsers.mdlx.Sequence; +import com.etheller.warsmash.units.DataTable; +import com.etheller.warsmash.units.Element; import com.etheller.warsmash.viewer5.Camera; import com.etheller.warsmash.viewer5.CanvasProvider; import com.etheller.warsmash.viewer5.ModelViewer; @@ -32,7 +36,7 @@ import com.etheller.warsmash.viewer5.handlers.mdx.SequenceLoopMode; public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvider { private static final boolean SPIN = false; - private static final boolean ADVANCE_ANIMS = false; + private static final boolean ADVANCE_ANIMS = true; private DataSource codebase; private ModelViewer viewer; private MdxModel model; @@ -42,6 +46,11 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide private BitmapFont font; private SpriteBatch batch; + private final DataTable warsmashIni; + + public WarsmashGdxGame(final DataTable warsmashIni) { + this.warsmashIni = warsmashIni; + } @Override public void create() { @@ -58,11 +67,26 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide final String renderer = Gdx.gl.glGetString(GL20.GL_RENDERER); System.err.println("Renderer: " + renderer); - final FolderDataSourceDescriptor war3mpq = new FolderDataSourceDescriptor("E:\\Backups\\Warcraft\\Data\\127"); - final FolderDataSourceDescriptor testingFolder = new FolderDataSourceDescriptor("E:\\Backups\\Warsmash\\Data"); - final FolderDataSourceDescriptor currentFolder = new FolderDataSourceDescriptor("."); - this.codebase = new CompoundDataSourceDescriptor( - Arrays.asList(war3mpq, testingFolder, currentFolder)).createDataSource(); + final Element dataSourcesConfig = this.warsmashIni.get("DataSources"); + final int dataSourcesCount = dataSourcesConfig.getFieldValue("Count"); + final List dataSourcesList = new ArrayList<>(); + for (int i = 0; i < dataSourcesCount; i++) { + final String type = dataSourcesConfig.getField("Type" + (i < 10 ? "0" : "") + i); + final String path = dataSourcesConfig.getField("Path" + (i < 10 ? "0" : "") + i); + switch (type) { + case "Folder": { + dataSourcesList.add(new FolderDataSourceDescriptor(path)); + break; + } + case "MPQ": { + dataSourcesList.add(new MpqDataSourceDescriptor(path)); + break; + } + default: + throw new RuntimeException("Unknown data source type: " + type); + } + } + this.codebase = new CompoundDataSourceDescriptor(dataSourcesList).createDataSource(); this.viewer = new MdxViewer(this.codebase, this); this.viewer.addHandler(new MdxHandler()); @@ -103,7 +127,8 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide // acolytesHarvestingSceneJoke2(scene); - singleModelScene(scene, "Buildings\\Undead\\Necropolis\\Necropolis.mdx", "birth"); +// singleModelScene(scene, "Buildings\\Undead\\Necropolis\\Necropolis.mdx", "birth"); + singleModelScene(scene, "Units\\Orc\\KotoBeast\\KotoBeast.mdx", "spell slam"); System.out.println("Loaded"); Gdx.gl30.glClearColor(0.5f, 0.5f, 0.5f, 1); // TODO remove white background @@ -168,6 +193,8 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide instance3.setSequence(animIndex); instance3.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + this.mainInstance = instance3; + this.mainModel = model2; } private void acolytesHarvestingScene(final Scene scene) { diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java index 020d607..b8647b3 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java @@ -231,7 +231,10 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv if (ENABLE_MUSIC) { final String musicField = rootFrame.getSkinField("Music_V1"); final String[] musics = musicField.split(";"); - final String musicPath = musics[(int) (Math.random() * musics.length)]; + String musicPath = musics[(int) (Math.random() * musics.length)]; + if (true) { + musicPath = "Sound\\Music\\mp3Music\\OrcTheme.mp3"; + } final Music music = Gdx.audio.newMusic( new DataSourceFileHandle(WarsmashGdxMapGame.this.viewer.dataSource, musicPath)); music.setVolume(0.2f); diff --git a/core/src/com/etheller/warsmash/viewer5/ModelViewer.java b/core/src/com/etheller/warsmash/viewer5/ModelViewer.java index 1800d2b..8df04fe 100644 --- a/core/src/com/etheller/warsmash/viewer5/ModelViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/ModelViewer.java @@ -308,6 +308,9 @@ public abstract class ModelViewer { } public void render() { + for (final Scene scene : this.scenes) { + scene.startFrame(); + } this.renderOpaque(); this.renderTranslucent(); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Attachment.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Attachment.java index 0bb591f..0ab5731 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Attachment.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Attachment.java @@ -3,6 +3,7 @@ package com.etheller.warsmash.viewer5.handlers.mdx; import com.etheller.warsmash.parsers.mdlx.AnimationMap; public class Attachment extends GenericObject { + protected String name; protected final String path; protected final int attachmentId; protected MdxModel internalModel; @@ -11,8 +12,9 @@ public class Attachment extends GenericObject { final int index) { super(model, attachment, index); - final String path = attachment.getPath().replace("\\", "/").toLowerCase().replace(".mdl", ".mdx"); + final String path = attachment.getPath().toLowerCase().replace(".mdl", ".mdx"); + this.name = attachment.getName().toLowerCase(); this.path = path; this.attachmentId = attachment.getAttachmentId(); this.internalModel = null; @@ -27,4 +29,12 @@ public class Attachment extends GenericObject { public int getVisibility(final float[] out, final int sequence, final int frame, final int counter) { return this.getScalarValue(out, AnimationMap.KATV.getWar3id(), sequence, frame, counter, 1); } + + public String getName() { + return this.name; + } + + public int getAttachmentId() { + return this.attachmentId; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectEmitter.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectEmitter.java index 17c3b96..9c5a225 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectEmitter.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectEmitter.java @@ -6,10 +6,11 @@ public abstract class EventObjectEmitter { private static final long[] valueHeap = { 0L }; - private long lastValue = 0; + private int lastEmissionKey; public EventObjectEmitter(final MdxComplexInstance instance, final EMITTER_OBJECT emitterObject) { super(instance, emitterObject); + this.lastEmissionKey = -1; } @Override @@ -19,21 +20,18 @@ public abstract class EventObjectEmitter goalTagSet, + final EnumSet tagsToTest) { + int matches = 0; + for (final AnimationTokens.SecondaryTag goalTag : goalTagSet) { + if (tagsToTest.contains(goalTag)) { + matches++; + } + } + return matches; + } + public static IndexedSequence selectSequence(final AnimationTokens.PrimaryTag type, final EnumSet tags, final List sequences, final boolean allowRarityVariations) { List filtered = filterSequences(type, tags, sequences); final Comparator sequenceComparator = STAND_SEQUENCE_COMPARATOR; - if (filtered.isEmpty() && !tags.isEmpty()) { - filtered = filterSequences(type, EMPTY, sequences); - } +// if (filtered.isEmpty() && !tags.isEmpty()) { +// filtered = filterSequences(type, EMPTY, sequences); +// } if (filtered.isEmpty()) { // find tags EnumSet fallbackTags = null; + int fallbackTagsMatchCount = 0; for (int i = 0, l = sequences.size(); i < l; i++) { final Sequence sequence = sequences.get(i); if (sequence.getPrimaryTags().contains(type)) { - if ((fallbackTags == null) || (sequence.getSecondaryTags().size() < fallbackTags.size()) - || ((sequence.getSecondaryTags().size() == fallbackTags.size()) - && (SecondaryTagSequenceComparator.getTagsOrdinal(sequence.getSecondaryTags(), - tags) > SecondaryTagSequenceComparator.getTagsOrdinal(fallbackTags, - tags)))) { + final int matchCount = matchCount(tags, sequence.getSecondaryTags()); + if (matchCount > fallbackTagsMatchCount) { fallbackTags = sequence.getSecondaryTags(); + fallbackTagsMatchCount = matchCount; + } + } + } + if (fallbackTags == null) { + for (int i = 0, l = sequences.size(); i < l; i++) { + final Sequence sequence = sequences.get(i); + if (sequence.getPrimaryTags().contains(type)) { + if ((fallbackTags == null) || (sequence.getSecondaryTags().size() < fallbackTags.size()) + || ((sequence.getSecondaryTags().size() == fallbackTags.size()) + && (SecondaryTagSequenceComparator.getTagsOrdinal(sequence.getSecondaryTags(), + tags) > SecondaryTagSequenceComparator.getTagsOrdinal(fallbackTags, + tags)))) { + fallbackTags = sequence.getSecondaryTags(); + } } } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SplatModel.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SplatModel.java index cd9c872..95e8167 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SplatModel.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SplatModel.java @@ -5,6 +5,7 @@ import java.util.List; import java.util.function.Consumer; import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.GL30; import com.badlogic.gdx.graphics.glutils.ShaderProgram; import com.etheller.warsmash.util.RenderMathUtils; @@ -27,11 +28,13 @@ public class SplatModel { private final List locations; private final List splatInstances; private final boolean unshaded; + private final boolean noDepthTest; public SplatModel(final GL30 gl, final Texture texture, final List locations, final float[] centerOffset, - final List> unitMapping, final boolean unshaded) { + final List> unitMapping, final boolean unshaded, final boolean noDepthTest) { this.texture = texture; this.unshaded = unshaded; + this.noDepthTest = noDepthTest; this.batches = new ArrayList<>(); this.color = new float[] { 1, 1, 1, 1 }; @@ -177,6 +180,13 @@ public class SplatModel { if (indices.size() > 0) { this.addBatch(gl, vertices, uvs, indices, batchRenderUnits); } + if (this.splatInstances != null) { + for (final SplatMover splatMover : this.splatInstances) { + if (splatMover.hidden) { + splatMover.hide(); + } + } + } } private void addBatch(final GL30 gl, final List vertices, final List uvs, @@ -205,6 +215,12 @@ public class SplatModel { public void render(final GL30 gl, final ShaderProgram shader) { // Texture + if (this.noDepthTest) { + gl.glDisable(GL20.GL_DEPTH_TEST); + } + else { + gl.glEnable(GL20.GL_DEPTH_TEST); + } gl.glActiveTexture(GL30.GL_TEXTURE1); gl.glBindTexture(GL30.GL_TEXTURE_2D, this.texture.getGlHandle()); shader.setUniformi("u_show_lighting", this.unshaded ? 0 : 1); @@ -225,6 +241,10 @@ public class SplatModel { } + public boolean isNoDepthTest() { + return this.noDepthTest; + } + public SplatMover add(final float x, final float y, final float w, final float h, final float zDepthUpward, final float[] centerOffset) { this.locations.add(new float[] { x, y, w, h, zDepthUpward }); @@ -273,6 +293,7 @@ public class SplatModel { private int indicesStartOffset; private int index; private final SplatModel splatModel; + private boolean hidden = false; private SplatMover(final SplatModel splatModel) { this.splatModel = splatModel; @@ -294,6 +315,20 @@ public class SplatModel { this.locs[2] += deltaX; this.locs[1] += deltaY; this.locs[3] += deltaY; + updateAfterMove(centerOffset); + } + + public void setLocation(final float x, final float y, final float[] centerOffset) { + final float width = this.locs[2] - this.locs[0]; + final float height = this.locs[3] - this.locs[1]; + this.locs[0] = x - (width / 2); + this.locs[2] = x + (width / 2); + this.locs[1] = y - (height / 2); + this.locs[3] = y + (height / 2); + updateAfterMove(centerOffset); + } + + private void updateAfterMove(final float[] centerOffset) { final float x0 = this.locs[0]; final float y0 = this.locs[1]; final float x1 = this.locs[2]; @@ -422,6 +457,7 @@ public class SplatModel { RenderMathUtils.wrapFaces(this.indices)); gl.glBufferSubData(GL30.GL_ARRAY_BUFFER, this.uvsOffset + ((this.startOffset / 3) * 2), 4 * 2 * this.uvs.size(), RenderMathUtils.wrap(this.uvs)); + this.hidden = true; } public void show(final float[] centerOffset) { @@ -429,6 +465,7 @@ public class SplatModel { // forcing it visible again by putting the position outside the map this.ix0 = this.ix1 = this.iy0 = this.iy1 = Integer.MIN_VALUE; move(0, 0, centerOffset); + this.hidden = false; } } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xScenePortraitLightManager.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xScenePortraitLightManager.java index 1880b42..df3f4be 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xScenePortraitLightManager.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xScenePortraitLightManager.java @@ -7,6 +7,7 @@ import java.util.ArrayList; import java.util.List; import com.badlogic.gdx.math.Vector3; +import com.etheller.warsmash.viewer5.ModelViewer; import com.etheller.warsmash.viewer5.SceneLightInstance; import com.etheller.warsmash.viewer5.SceneLightManager; import com.etheller.warsmash.viewer5.gl.DataTexture; @@ -14,14 +15,14 @@ import com.etheller.warsmash.viewer5.handlers.mdx.Light; import com.etheller.warsmash.viewer5.handlers.mdx.LightInstance; public class W3xScenePortraitLightManager implements SceneLightManager, W3xSceneLightManager { - private final War3MapViewer viewer; + private final ModelViewer viewer; private final Vector3 hardcodedLightDirection; public final List lights; private FloatBuffer lightDataCopyHeap; private final DataTexture unitLightsTexture; private int unitLightCount; - public W3xScenePortraitLightManager(final War3MapViewer viewer, final Vector3 lightDirection) { + public W3xScenePortraitLightManager(final ModelViewer viewer, final Vector3 lightDirection) { this.viewer = viewer; this.hardcodedLightDirection = lightDirection; this.lights = new ArrayList<>(); @@ -56,35 +57,24 @@ public class W3xScenePortraitLightManager implements SceneLightManager, W3xScene this.unitLightCount = 0; this.lightDataCopyHeap.clear(); int offset = 0; - if (this.hardcodedLightDirection == null) { - if (this.viewer.dncTarget != null) { - if (!this.viewer.dncTarget.lights.isEmpty()) { - this.viewer.dncTarget.lights.get(0).bind(0, this.lightDataCopyHeap); - offset += 16; - this.unitLightCount++; - } - } - } - else { - this.lightDataCopyHeap.put(offset, this.hardcodedLightDirection.y); - this.lightDataCopyHeap.put(offset + 1, -this.hardcodedLightDirection.x); - this.lightDataCopyHeap.put(offset + 2, -this.hardcodedLightDirection.z); - this.lightDataCopyHeap.put(offset + 3, -this.hardcodedLightDirection.z); - this.lightDataCopyHeap.put(offset + 4, Light.Type.DIRECTIONAL.ordinal()); - this.lightDataCopyHeap.put(offset + 5, 1); - this.lightDataCopyHeap.put(offset + 6, 2); - this.lightDataCopyHeap.put(offset + 7, 0); - this.lightDataCopyHeap.put(offset + 8, 1); - this.lightDataCopyHeap.put(offset + 9, 1); - this.lightDataCopyHeap.put(offset + 10, 1); - this.lightDataCopyHeap.put(offset + 11, 1); - this.lightDataCopyHeap.put(offset + 12, 1); - this.lightDataCopyHeap.put(offset + 13, 1); - this.lightDataCopyHeap.put(offset + 14, 1); - this.lightDataCopyHeap.put(offset + 15, 0.3f); - offset += 16; - this.unitLightCount++; - } + this.lightDataCopyHeap.put(offset, this.hardcodedLightDirection.y); + this.lightDataCopyHeap.put(offset + 1, -this.hardcodedLightDirection.x); + this.lightDataCopyHeap.put(offset + 2, -this.hardcodedLightDirection.z); + this.lightDataCopyHeap.put(offset + 3, -this.hardcodedLightDirection.z); + this.lightDataCopyHeap.put(offset + 4, Light.Type.DIRECTIONAL.ordinal()); + this.lightDataCopyHeap.put(offset + 5, 1); + this.lightDataCopyHeap.put(offset + 6, 2); + this.lightDataCopyHeap.put(offset + 7, 0); + this.lightDataCopyHeap.put(offset + 8, 1); + this.lightDataCopyHeap.put(offset + 9, 1); + this.lightDataCopyHeap.put(offset + 10, 1); + this.lightDataCopyHeap.put(offset + 11, 1); + this.lightDataCopyHeap.put(offset + 12, 1); + this.lightDataCopyHeap.put(offset + 13, 1); + this.lightDataCopyHeap.put(offset + 14, 1); + this.lightDataCopyHeap.put(offset + 15, 0.3f); + offset += 16; + this.unitLightCount++; for (final LightInstance light : this.lights) { light.bind(offset, this.lightDataCopyHeap); offset += 16; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index e773622..19810e0 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -88,6 +88,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitClassification; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitFilterFunction; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackInstant; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackMissile; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.projectile.CAttackProjectile; @@ -281,6 +282,9 @@ public class War3MapViewer extends ModelViewer { try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\MiscGame.txt")) { this.miscData.readTXT(miscDataTxtStream, true); } + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("UI\\MiscUI.txt")) { + this.miscData.readTXT(miscDataTxtStream, true); + } try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("UI\\SoundInfo\\MiscData.txt")) { this.miscData.readTXT(miscDataTxtStream, true); } @@ -423,7 +427,7 @@ public class War3MapViewer extends ModelViewer { @Override public CAttackProjectile createAttackProjectile(final CSimulation simulation, final float launchX, final float launchY, final float launchFacing, final CUnit source, - final CUnitAttackMissile unitAttack, final CWidget target, final float damage, + final CUnitAttackMissile unitAttack, final AbilityTarget target, final float damage, final int bounceIndex) { final War3ID typeId = source.getTypeId(); final int projectileSpeed = unitAttack.getProjectileSpeed(); @@ -920,7 +924,8 @@ public class War3MapViewer extends ModelViewer { + ".blp"; final float s = uberSplatInfo.getFieldFloatValue("Scale"); if (this.unitsReady) { - buildingUberSplatDynamicIngame = this.terrain.addUberSplat(texturePath, unitX, unitY, 1, s); + buildingUberSplatDynamicIngame = this.terrain.addUberSplat(texturePath, unitX, unitY, 1, s, + false, false); } else { if (!this.terrain.splats.containsKey(texturePath)) { @@ -1180,9 +1185,10 @@ public class War3MapViewer extends ModelViewer { this.terrain.renderGround(this.dynamicShadowManager); this.terrain.renderCliffs(); worldScene.renderOpaque(); - this.terrain.renderUberSplats(); + this.terrain.renderUberSplats(false); this.terrain.renderWater(); worldScene.renderTranslucent(); + this.terrain.renderUberSplats(true); final List scenes = this.scenes; for (final Scene scene : scenes) { @@ -1255,7 +1261,7 @@ public class War3MapViewer extends ModelViewer { final String path = entry.getKey(); final Splat locations = entry.getValue(); final SplatModel model = new SplatModel(Gdx.gl30, (Texture) load(path, PathSolver.DEFAULT, null), - locations.locations, this.terrain.centerOffset, locations.unitMapping, true); + locations.locations, this.terrain.centerOffset, locations.unitMapping, true, false); model.color[0] = 0; model.color[1] = 1; model.color[2] = 0; @@ -1468,7 +1474,7 @@ public class War3MapViewer extends ModelViewer { this.dncTarget.setSequence(0); } - private static String mdx(String mdxPath) { + public static String mdx(String mdxPath) { if (mdxPath.toLowerCase().endsWith(".mdl")) { mdxPath = mdxPath.substring(0, mdxPath.length() - 4); } @@ -1528,6 +1534,10 @@ public class War3MapViewer extends ModelViewer { return this.localPlayerIndex; } + public RenderUnit getRenderPeer(final CUnit unit) { + return this.unitToRenderPeer.get(unit); + } + private static final class QuadtreeIntersectorFindsWalkableRenderHeight implements QuadtreeIntersector { private float z; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java index a042d37..0912d7e 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java @@ -3,11 +3,16 @@ package com.etheller.warsmash.viewer5.handlers.w3x.environment; import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.Arrays; +import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Map; +import com.badlogic.gdx.math.Rectangle; import com.etheller.warsmash.parsers.w3x.wpm.War3MapWpm; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWorldCollision; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.pathing.CBuildingPathingType; public class PathingGrid { private static final Map movetpToMovementType = new HashMap<>(); @@ -85,6 +90,147 @@ public class PathingGrid { } } + public boolean checkPathingTexture(final float positionX, final float positionY, final int rotationInput, + final BufferedImage pathingTextureTga, final EnumSet preventPathingTypes, + final EnumSet requirePathingTypes, final CWorldCollision cWorldCollision, + final CUnit unitToExcludeFromCollisionChecks) { + final int rotation = (rotationInput + 450) % 360; + final int divW = ((rotation % 180) != 0) ? pathingTextureTga.getHeight() : pathingTextureTga.getWidth(); + final int divH = ((rotation % 180) != 0) ? pathingTextureTga.getWidth() : pathingTextureTga.getHeight(); + short anyPathingTypesInRegion = 0; + short pathingTypesFillingRegion = (short) 0xFFFF; + for (int j = 0; j < pathingTextureTga.getHeight(); j++) { + for (int i = 0; i < pathingTextureTga.getWidth(); i++) { + int x = i; + int y = j; + + switch (rotation) { + case 90: + x = pathingTextureTga.getHeight() - 1 - j; + y = i; + break; + case 180: + x = pathingTextureTga.getWidth() - 1 - i; + y = pathingTextureTga.getHeight() - 1 - j; + break; + case 270: + x = j; + y = pathingTextureTga.getWidth() - 1 - i; + break; + } + // Width and height for centering change if rotation is not divisible by 180 + final int xx = (getCellX(positionX) + x) - (divW / 2); + final int yy = (getCellY(positionY) + y) - (divH / 2); + + if ((xx < 0) || (xx > (this.pathingGridSizes[0] - 1)) || (yy < 0) + || (yy > (this.pathingGridSizes[1] - 1))) { + continue; + } + + final short cellPathing = getCellPathing(xx, yy); + anyPathingTypesInRegion |= cellPathing; + pathingTypesFillingRegion &= cellPathing; + } + } + final float width = pathingTextureTga.getWidth() * 32f; + final float height = pathingTextureTga.getHeight() * 32f; + final float offsetX = ((pathingTextureTga.getWidth() % 2) == 1) ? 16f : 0f; + final float offsetY = ((pathingTextureTga.getHeight() % 2) == 1) ? 16f : 0f; + final Rectangle pathingMapRectangle = new Rectangle((positionX - (width / 2)) + offsetX, + (positionY - (height / 2)) + offsetY, width, height); + short anyPathingTypeWithUnit = 0; + if (cWorldCollision.intersectsAnythingOtherThan(pathingMapRectangle, unitToExcludeFromCollisionChecks, + MovementType.AMPHIBIOUS)) { + anyPathingTypesInRegion |= PathingFlags.UNBUILDABLE | PathingFlags.UNWALKABLE | PathingFlags.UNSWIMABLE; + anyPathingTypeWithUnit |= PathingFlags.UNBUILDABLE | PathingFlags.UNWALKABLE | PathingFlags.UNSWIMABLE; + } + if (cWorldCollision.intersectsAnythingOtherThan(pathingMapRectangle, unitToExcludeFromCollisionChecks, + MovementType.FLOAT)) { + anyPathingTypesInRegion |= PathingFlags.UNSWIMABLE; + anyPathingTypeWithUnit |= PathingFlags.UNSWIMABLE; + } + if (cWorldCollision.intersectsAnythingOtherThan(pathingMapRectangle, unitToExcludeFromCollisionChecks, + MovementType.FLY)) { + anyPathingTypesInRegion |= PathingFlags.UNFLYABLE; + anyPathingTypeWithUnit |= PathingFlags.UNFLYABLE; + } + if (cWorldCollision.intersectsAnythingOtherThan(pathingMapRectangle, unitToExcludeFromCollisionChecks, + MovementType.FOOT)) { + anyPathingTypesInRegion |= PathingFlags.UNBUILDABLE | PathingFlags.UNWALKABLE; + anyPathingTypeWithUnit |= PathingFlags.UNBUILDABLE | PathingFlags.UNWALKABLE; + } + pathingTypesFillingRegion &= anyPathingTypeWithUnit; + for (final CBuildingPathingType pathingType : preventPathingTypes) { + switch (pathingType) { + case BLIGHTED: + throw new IllegalArgumentException("Blight pathing check system is Not Yet Implemented"); + case UNAMPH: + if (PathingFlags.isPathingFlag(anyPathingTypesInRegion, PathingFlags.UNWALKABLE) + && PathingFlags.isPathingFlag(anyPathingTypesInRegion, PathingFlags.UNSWIMABLE)) { + return false; + } + break; + case UNBUILDABLE: + if (PathingFlags.isPathingFlag(anyPathingTypesInRegion, PathingFlags.UNBUILDABLE)) { + return false; + } + break; + case UNFLOAT: + if (PathingFlags.isPathingFlag(anyPathingTypesInRegion, PathingFlags.UNSWIMABLE)) { + return false; + } + break; + case UNFLYABLE: + if (PathingFlags.isPathingFlag(anyPathingTypesInRegion, PathingFlags.UNFLYABLE)) { + return false; + } + break; + case UNWALKABLE: + if (PathingFlags.isPathingFlag(anyPathingTypesInRegion, PathingFlags.UNWALKABLE)) { + return false; + } + break; + default: + break; + } + } + for (final CBuildingPathingType pathingType : requirePathingTypes) { + switch (pathingType) { + case BLIGHTED: + throw new IllegalArgumentException("Blight pathing check system is Not Yet Implemented"); + case UNAMPH: + if (!PathingFlags.isPathingFlag(pathingTypesFillingRegion, PathingFlags.UNWALKABLE) + || !PathingFlags.isPathingFlag(pathingTypesFillingRegion, PathingFlags.UNSWIMABLE)) { + return false; + } + break; + case UNBUILDABLE: + if (!PathingFlags.isPathingFlag(pathingTypesFillingRegion, PathingFlags.UNBUILDABLE)) { + return false; + } + break; + case UNFLOAT: + if (!PathingFlags.isPathingFlag(pathingTypesFillingRegion, PathingFlags.UNSWIMABLE)) { + return false; + } + break; + case UNFLYABLE: + if (!PathingFlags.isPathingFlag(pathingTypesFillingRegion, PathingFlags.UNFLYABLE)) { + return false; + } + break; + case UNWALKABLE: + if (!PathingFlags.isPathingFlag(pathingTypesFillingRegion, PathingFlags.UNWALKABLE)) { + return false; + } + break; + default: + break; + } + } + return true; + } + public RemovablePathingMapInstance blitRemovablePathingOverlayTexture(final float positionX, final float positionY, final int rotationInput, final BufferedImage pathingTextureTga) { final RemovablePathingMapInstance removablePathingMapInstance = new RemovablePathingMapInstance(positionX, diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java index bdeff04..7bb3a3a 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java @@ -962,7 +962,7 @@ public class Terrain { return gl; } - public void renderUberSplats() { + public void renderUberSplats(final boolean onTopLayer) { final GL30 gl = Gdx.gl30; final WebGL webGL = this.webGL; final ShaderProgram shader = this.uberSplatShader; @@ -1002,7 +1002,9 @@ public class Terrain { // Render the cliffs for (final SplatModel splat : this.uberSplatModels.values()) { - splat.render(gl, shader); + if (splat.isNoDepthTest() == onTopLayer) { + splat.render(gl, shader); + } } } @@ -1383,7 +1385,7 @@ public class Terrain { final SplatModel splatModel = new SplatModel(Gdx.gl30, (Texture) this.viewer.load(path, PathSolver.DEFAULT, null), splat.locations, this.centerOffset, - splat.unitMapping.isEmpty() ? null : splat.unitMapping, false); + splat.unitMapping.isEmpty() ? null : splat.unitMapping, false, false); splatModel.color[3] = splat.opacity; this.uberSplatModels.put(path, splatModel); } @@ -1397,11 +1399,12 @@ public class Terrain { this.uberSplatModels.put(path, model); } - public SplatMover addUberSplat(final String path, final float x, final float y, final float z, final float scale) { + public SplatMover addUberSplat(final String path, final float x, final float y, final float z, final float scale, + final boolean unshaded, final boolean noDepthTest) { SplatModel splatModel = this.uberSplatModels.get(path); if (splatModel == null) { splatModel = new SplatModel(Gdx.gl30, (Texture) this.viewer.load(path, PathSolver.DEFAULT, null), - new ArrayList<>(), this.centerOffset, new ArrayList<>(), false); + new ArrayList<>(), this.centerOffset, new ArrayList<>(), unshaded, noDepthTest); this.uberSplatModels.put(path, splatModel); } return splatModel.add(x - scale, y - scale, x + scale, y + scale, z, this.centerOffset); @@ -1412,7 +1415,7 @@ public class Terrain { SplatModel splatModel = this.uberSplatModels.get(texture); if (splatModel == null) { splatModel = new SplatModel(Gdx.gl30, (Texture) this.viewer.load(texture, PathSolver.DEFAULT, null), - new ArrayList<>(), this.centerOffset, new ArrayList<>(), false); + new ArrayList<>(), this.centerOffset, new ArrayList<>(), false, false); splatModel.color[3] = opacity; this.uberSplatModels.put(texture, splatModel); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackProjectile.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackProjectile.java index a96dccb..d3ec6f0 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackProjectile.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackProjectile.java @@ -11,6 +11,8 @@ import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; import com.etheller.warsmash.viewer5.handlers.w3x.IndexedSequence; import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTargetWidgetVisitor; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.projectile.CAttackProjectile; @@ -48,12 +50,18 @@ public class RenderAttackProjectile implements RenderEffect { final float dyToTarget = targetY - this.y; final float d2DToTarget = (float) StrictMath.sqrt((dxToTarget * dxToTarget) + (dyToTarget * dyToTarget)); final float startingDistance = d2DToTarget + this.totalTravelDistance; - float impactZ = this.simulationProjectile.getTarget().getImpactZ(); - if (simulationProjectile.getUnitAttack().getWeaponType() == CWeaponType.ARTILLERY) { + final CWidget widgetTarget = this.simulationProjectile.getTarget().visit(AbilityTargetWidgetVisitor.INSTANCE); + float impactZ; + float flyHeight; + if ((simulationProjectile.getUnitAttack().getWeaponType() == CWeaponType.ARTILLERY) || (widgetTarget == null)) { impactZ = 0; + flyHeight = 0; } - this.targetHeight = (war3MapViewer.terrain.getGroundHeight(targetX, targetY) - + this.simulationProjectile.getTarget().getFlyHeight() + impactZ); + else { + impactZ = widgetTarget.getImpactZ(); + flyHeight = widgetTarget.getFlyHeight(); + } + this.targetHeight = (war3MapViewer.terrain.getGroundHeight(targetX, targetY) + flyHeight + impactZ); this.arcPeakHeight = arc * startingDistance; this.yaw = (float) StrictMath.atan2(dyToTarget, dxToTarget); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java index 1af84bb..aa9770c 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java @@ -55,6 +55,7 @@ public class RenderUnit { private float facing; private boolean swimming; + private boolean working; private boolean dead = false; @@ -140,8 +141,7 @@ public class RenderUnit { public void populateCommandCard(final CSimulation game, final CommandButtonListener commandButtonListener, final AbilityDataUI abilityDataUI, final int subMenuOrderId) { final CommandCardPopulatingAbilityVisitor commandCardPopulatingVisitor = CommandCardPopulatingAbilityVisitor.INSTANCE - .reset(game, this.simulationUnit, commandButtonListener, abilityDataUI, subMenuOrderId, - this.simulationUnit.isConstructing()); + .reset(game, this.simulationUnit, commandButtonListener, abilityDataUI, subMenuOrderId); for (final CAbility ability : this.simulationUnit.getAbilities()) { ability.visit(commandCardPopulatingVisitor); } @@ -197,6 +197,7 @@ public class RenderUnit { boolean swimming = (movementType == MovementType.AMPHIBIOUS) && PathingGrid.isPathingFlag(terrainPathing, PathingGrid.PathingType.SWIMMABLE) && !PathingGrid.isPathingFlag(terrainPathing, PathingGrid.PathingType.WALKABLE); + final boolean working = this.simulationUnit.getBuildQueueTypes()[0] != null; final float groundHeightTerrain = map.terrain.getGroundHeight(this.location[0], this.location[1]); float groundHeightTerrainAndWater; MdxComplexInstance currentWalkableUnder; @@ -236,7 +237,14 @@ public class RenderUnit { else if (!swimming && this.swimming) { this.unitAnimationListenerImpl.removeSecondaryTag(AnimationTokens.SecondaryTag.SWIM); } + if (working && !this.working) { + this.unitAnimationListenerImpl.addSecondaryTag(AnimationTokens.SecondaryTag.WORK); + } + else if (!working && this.working) { + this.unitAnimationListenerImpl.removeSecondaryTag(AnimationTokens.SecondaryTag.WORK); + } this.swimming = swimming; + this.working = working; final boolean dead = this.simulationUnit.isDead(); final boolean corpse = this.simulationUnit.isCorpse(); final boolean boneCorpse = this.simulationUnit.isBoneCorpse(); @@ -402,6 +410,10 @@ public class RenderUnit { return this.simulationUnit; } + public EnumSet getSecondaryAnimationTags() { + return this.unitAnimationListenerImpl.secondaryAnimationTags; + } + private static final class UnitAnimationListenerImpl implements CUnitAnimationListener { private final MdxComplexInstance instance; private final EnumSet secondaryAnimationTags = EnumSet @@ -435,7 +447,8 @@ public class RenderUnit { final EnumSet secondaryAnimationTags, final float speedRatio, final boolean allowRarityVariations) { this.animationQueue.clear(); - if (force || (animationName != this.currentAnimation)) { + if (force || (animationName != this.currentAnimation) + || !secondaryAnimationTags.equals(this.currentAnimationSecondaryTags)) { this.currentSpeedRatio = speedRatio; this.recycleSet.clear(); this.recycleSet.addAll(this.secondaryAnimationTags); @@ -454,7 +467,8 @@ public class RenderUnit { final EnumSet secondaryAnimationTags, final float duration, final boolean allowRarityVariations) { this.animationQueue.clear(); - if (force || (animationName != this.currentAnimation)) { + if (force || (animationName != this.currentAnimation) + || !secondaryAnimationTags.equals(this.currentAnimationSecondaryTags)) { this.recycleSet.clear(); this.recycleSet.addAll(this.secondaryAnimationTags); this.recycleSet.addAll(secondaryAnimationTags); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityDataUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityDataUI.java index d136b3b..7b3a57d 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityDataUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityDataUI.java @@ -50,6 +50,8 @@ public class AbilityDataUI { private final IconUI buildNagaUI; private final IconUI cancelUI; private final IconUI cancelBuildUI; + private final IconUI cancelTrainUI; + private final IconUI rallyUI; public AbilityDataUI(final MutableObjectData abilityData, final MutableObjectData unitData, final MutableObjectData upgradeData, final GameUI gameUI) { @@ -114,6 +116,8 @@ public class AbilityDataUI { this.attackGroundUI = createBuiltInIconUI(gameUI, "CmdAttackGround", disabledPrefix); this.cancelUI = createBuiltInIconUI(gameUI, "CmdCancel", disabledPrefix); this.cancelBuildUI = createBuiltInIconUI(gameUI, "CmdCancelBuild", disabledPrefix); + this.cancelTrainUI = createBuiltInIconUI(gameUI, "CmdCancelTrain", disabledPrefix); + this.rallyUI = createBuiltInIconUI(gameUI, "CmdRally", disabledPrefix); } private IconUI createBuiltInIconUI(final GameUI gameUI, final String key, final String disabledPrefix) { @@ -153,7 +157,7 @@ public class AbilityDataUI { if (slashIndex != -1) { name = path.substring(slashIndex + 1); } - return disabledPrefix + name; + return disabledPrefix + "DIS" + name; } public IconUI getMoveUI() { @@ -212,4 +216,12 @@ public class AbilityDataUI { return this.cancelBuildUI; } + public IconUI getCancelTrainUI() { + return this.cancelTrainUI; + } + + public IconUI getRallyUI() { + return this.rallyUI; + } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java index fd3e865..312c736 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java @@ -19,7 +19,10 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAb import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityOrcBuild; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityUndeadBuild; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.combat.CAbilityColdArrows; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic.GenericSingleIconActiveAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityQueue; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityRally; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayer; @@ -32,29 +35,38 @@ public class CommandCardPopulatingAbilityVisitor implements CAbilityVisitor targetsAllowed) { return false; } + + @Override + public T visit(final AbilityTargetVisitor visitor) { + return visitor.accept(this); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CItem.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CItem.java index 3d167a1..db6e5c1 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CItem.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CItem.java @@ -3,6 +3,7 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation; import java.util.EnumSet; import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTargetVisitor; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; @@ -36,4 +37,9 @@ public class CItem extends CWidget { final EnumSet targetsAllowed) { return targetsAllowed.contains(CTargetType.ITEM); } + + @Override + public T visit(final AbilityTargetVisitor visitor) { + return visitor.accept(this); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java index 381110c..30ba5fc 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java @@ -19,6 +19,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.environment.BuildingShadow; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.RemovablePathingMapInstance; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackInstant; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackMissile; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.projectile.CAttackProjectile; @@ -148,7 +149,7 @@ public class CSimulation { } public CAttackProjectile createProjectile(final CUnit source, final float launchX, final float launchY, - final float launchFacing, final CUnitAttackMissile attack, final CWidget target, final float damage, + final float launchFacing, final CUnitAttackMissile attack, final AbilityTarget target, final float damage, final int bounceIndex) { final CAttackProjectile projectile = this.simulationRenderController.createAttackProjectile(this, launchX, launchY, launchFacing, source, attack, target, damage, bounceIndex); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java index c34de81..7f8e55e 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java @@ -18,6 +18,9 @@ import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.Remova import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitStateListener.CUnitStateNotifier; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityBuildInProgress; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTargetVisitor; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorAttack; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorFollow; @@ -28,9 +31,13 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.COrder; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.COrderTargetPoint; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.COrderTargetWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CAllianceType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayer; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.BooleanAbilityActivationReceiver; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.BooleanAbilityTargetCheckReceiver; public class CUnit extends CWidget { private static final Rectangle tempRect = new Rectangle(); @@ -80,9 +87,11 @@ public class CUnit extends CWidget { private float constructionProgress; private boolean hidden = false; private boolean updating = true; + private boolean invulnerable = false; private CUnit workerInside; private final War3ID[] buildQueue = new War3ID[WarsmashConstants.BUILD_QUEUE_SIZE]; private final QueueItemType[] buildQueueTypes = new QueueItemType[WarsmashConstants.BUILD_QUEUE_SIZE]; + private AbilityTarget rallyPoint; public CUnit(final int handleId, final int playerIndex, final float x, final float y, final float life, final War3ID typeId, final float facing, final float mana, final int maximumLife, final int maximumMana, @@ -233,6 +242,10 @@ public class CUnit extends CWidget { if (ability instanceof CAbilityBuildInProgress) { abilityIterator.remove(); } + else { + ability.setDisabled(false); + ability.setIconShowing(true); + } } game.unitConstructFinishEvent(this); this.stateNotifier.ordersChanged(getCurrentAbilityHandleId(), getCurrentOrderId()); @@ -252,6 +265,11 @@ public class CUnit extends CWidget { // nudge the trained unit out around us trainedUnit.nudgeAround(game, this); game.unitTrainedEvent(this, trainedUnit); + if (this.rallyPoint != null) { + final int rallyOrderId = OrderIds.smart; + this.rallyPoint.visit( + UseAbilityOnTargetByIdVisitor.INSTANCE.reset(game, trainedUnit, rallyOrderId)); + } for (int i = 0; i < (this.buildQueue.length - 1); i++) { this.buildQueue[i] = this.buildQueue[i + 1]; this.buildQueueTypes[i] = this.buildQueueTypes[i + 1]; @@ -293,6 +311,7 @@ public class CUnit extends CWidget { private void popoutWorker(final CSimulation game) { if (this.workerInside != null) { + this.workerInside.setInvulnerable(false); this.workerInside.setHidden(false); this.workerInside.setUpdating(true); this.workerInside.nudgeAround(game, this); @@ -456,7 +475,7 @@ public class CUnit extends CWidget { return this.unitType.getImpactZ(); } - public double distance(final CWidget target) { + public double distance(final AbilityTarget target) { double dx = Math.abs(target.getX() - getX()); double dy = Math.abs(target.getY() - getY()); final float thisCollisionSize = this.unitType.getCollisionSize(); @@ -504,20 +523,22 @@ public class CUnit extends CWidget { public void damage(final CSimulation simulation, final CUnit source, final CAttackType attackType, final String weaponType, final float damage) { final boolean wasDead = isDead(); - final float damageRatioFromArmorClass = simulation.getGameplayConstants().getDamageRatioAgainst(attackType, - this.unitType.getDefenseType()); - final float damageRatioFromDefense; - if (this.defense >= 0) { - damageRatioFromDefense = 1f - (float) (((this.defense) * 0.06) / (1 + (0.06 * this.defense))); + if (!this.invulnerable) { + final float damageRatioFromArmorClass = simulation.getGameplayConstants().getDamageRatioAgainst(attackType, + this.unitType.getDefenseType()); + final float damageRatioFromDefense; + if (this.defense >= 0) { + damageRatioFromDefense = 1f - (float) (((this.defense) * 0.06) / (1 + (0.06 * this.defense))); + } + else { + damageRatioFromDefense = 2f - (float) StrictMath.pow(0.94, -this.defense); + } + final float trueDamage = damageRatioFromArmorClass * damageRatioFromDefense * damage; + this.life -= trueDamage; + this.stateNotifier.lifeChanged(); } - else { - damageRatioFromDefense = 2f - (float) StrictMath.pow(0.94, -this.defense); - } - final float trueDamage = damageRatioFromArmorClass * damageRatioFromDefense * damage; - this.life -= trueDamage; simulation.unitDamageEvent(this, weaponType, this.unitType.getArmorType()); - this.stateNotifier.lifeChanged(); - if (isDead()) { + if (!this.invulnerable && isDead()) { if (!wasDead) { kill(simulation); } @@ -557,7 +578,7 @@ public class CUnit extends CWidget { popoutWorker(simulation); } - public boolean canReach(final CWidget target, final float range) { + public boolean canReach(final AbilityTarget target, final float range) { final double distance = distance(target); if (target instanceof CUnit) { final CUnit targetUnit = (CUnit) target; @@ -574,6 +595,10 @@ public class CUnit extends CWidget { return distance <= range; } + public boolean canReach(final float x, final float y, final float range) { + return distance(x, y) <= range; // TODO use dist squared for performance + } + public boolean canReachToPathing(final float range, final float rotationForPathing, final BufferedImage buildingPathingPixelMap, final float targetX, final float targetY) { final int rotation = ((int) rotationForPathing + 450) % 360; @@ -802,6 +827,14 @@ public class CUnit extends CWidget { return this.hidden; } + public void setInvulnerable(final boolean invulnerable) { + this.invulnerable = invulnerable; + } + + public boolean isInvulnerable() { + return this.invulnerable; + } + public void setWorkerInside(final CUnit unit) { this.workerInside = unit; } @@ -879,6 +912,24 @@ public class CUnit extends CWidget { } } + public void cancelBuildQueueItem(final CSimulation game, final int cancelIndex) { + if ((cancelIndex >= 0) && (cancelIndex < this.buildQueueTypes.length)) { + if (this.buildQueueTypes[cancelIndex] != null) { + // TODO refund here! + if (cancelIndex == 0) { + this.constructionProgress = 0.0f; + } + for (int i = cancelIndex; i < (this.buildQueueTypes.length - 1); i++) { + this.buildQueueTypes[i] = this.buildQueueTypes[i + 1]; + this.buildQueue[i] = this.buildQueue[i + 1]; + } + this.buildQueueTypes[this.buildQueueTypes.length - 1] = null; + this.buildQueue[this.buildQueue.length - 1] = null; + this.stateNotifier.queueChanged(); + } + } + } + public void queueTrainingUnit(final War3ID rawcode) { queue(rawcode, QueueItemType.UNIT); } @@ -891,4 +942,92 @@ public class CUnit extends CWidget { UNIT, RESEARCH; } + + public void setRallyPoint(final AbilityTarget target) { + this.rallyPoint = target; + this.stateNotifier.rallyPointChanged(); + } + + public AbilityTarget getRallyPoint() { + return this.rallyPoint; + } + + private static interface RallyProvider { + float getX(); + + float getY(); + } + + @Override + public T visit(final AbilityTargetVisitor visitor) { + return visitor.accept(this); + } + + private static final class UseAbilityOnTargetByIdVisitor implements AbilityTargetVisitor { + private static final UseAbilityOnTargetByIdVisitor INSTANCE = new UseAbilityOnTargetByIdVisitor(); + private CSimulation game; + private CUnit trainedUnit; + private int rallyOrderId; + + private UseAbilityOnTargetByIdVisitor reset(final CSimulation game, final CUnit trainedUnit, + final int rallyOrderId) { + this.game = game; + this.trainedUnit = trainedUnit; + this.rallyOrderId = rallyOrderId; + return this; + } + + @Override + public Void accept(final AbilityPointTarget target) { + for (final CAbility ability : this.trainedUnit.getAbilities()) { + ability.checkCanUse(this.game, this.trainedUnit, this.rallyOrderId, + BooleanAbilityActivationReceiver.INSTANCE); + if (BooleanAbilityActivationReceiver.INSTANCE.isOk()) { + final BooleanAbilityTargetCheckReceiver targetCheckReceiver = BooleanAbilityTargetCheckReceiver + .getInstance().reset(); + ability.checkCanTarget(this.game, this.trainedUnit, this.rallyOrderId, target, targetCheckReceiver); + if (targetCheckReceiver.isTargetable()) { + this.trainedUnit.order(this.game, + new COrderTargetPoint(ability.getHandleId(), this.rallyOrderId, target), false); + return null; + } + } + } + return null; + } + + @Override + public Void accept(final CUnit targetUnit) { + return acceptWidget(this.game, this.trainedUnit, this.rallyOrderId, targetUnit); + } + + private Void acceptWidget(final CSimulation game, final CUnit trainedUnit, final int rallyOrderId, + final CWidget target) { + for (final CAbility ability : trainedUnit.getAbilities()) { + ability.checkCanUse(game, trainedUnit, rallyOrderId, BooleanAbilityActivationReceiver.INSTANCE); + if (BooleanAbilityActivationReceiver.INSTANCE.isOk()) { + final BooleanAbilityTargetCheckReceiver targetCheckReceiver = BooleanAbilityTargetCheckReceiver + .getInstance().reset(); + ability.checkCanTarget(game, trainedUnit, rallyOrderId, target, targetCheckReceiver); + if (targetCheckReceiver.isTargetable()) { + trainedUnit.order(game, + new COrderTargetWidget(ability.getHandleId(), rallyOrderId, target.getHandleId()), + false); + return null; + } + } + } + return null; + } + + @Override + public Void accept(final CDestructable target) { + return acceptWidget(this.game, this.trainedUnit, this.rallyOrderId, target); + } + + @Override + public Void accept(final CItem target) { + return acceptWidget(this.game, this.trainedUnit, this.rallyOrderId, target); + } + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitStateListener.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitStateListener.java index b036dcf..18d5bd8 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitStateListener.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitStateListener.java @@ -9,6 +9,8 @@ public interface CUnitStateListener { void queueChanged(); + void rallyPointChanged(); + public static final class CUnitStateNotifier extends SubscriberSetNotifier implements CUnitStateListener { @Override @@ -31,5 +33,12 @@ public interface CUnitStateListener { listener.queueChanged(); } } + + @Override + public void rallyPointChanged() { + for (final CUnitStateListener listener : set) { + listener.rallyPointChanged(); + } + } } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java index 1e752da..f8db48e 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java @@ -11,6 +11,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CDefenseType import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.data.CUnitRace; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.pathing.CBuildingPathingType; /** * The quick (symbol table instead of map) lookup for unit type values that we @@ -44,6 +45,8 @@ public class CUnitType { private final int goldCost; private final int lumberCost; private final int buildTime; + private final EnumSet preventedPathingTypes; + private final EnumSet requiredPathingTypes; public CUnitType(final String name, final boolean isBldg, final MovementType movementType, final float defaultFlyingHeight, final float collisionSize, @@ -52,7 +55,9 @@ public class CUnitType { final BufferedImage buildingPathingPixelMap, final float deathTime, final EnumSet targetedAs, final float defaultAcquisitionRange, final float minimumAttackRange, final List structuresBuilt, final List unitsTrained, final List researchesAvailable, final CUnitRace unitRace, - final int goldCost, final int lumberCost, final int buildTime) { + final int goldCost, final int lumberCost, final int buildTime, + final EnumSet preventedPathingTypes, + final EnumSet requiredPathingTypes) { this.name = name; this.building = isBldg; this.movementType = movementType; @@ -77,6 +82,8 @@ public class CUnitType { this.goldCost = goldCost; this.lumberCost = lumberCost; this.buildTime = buildTime; + this.preventedPathingTypes = preventedPathingTypes; + this.requiredPathingTypes = requiredPathingTypes; } public String getName() { @@ -174,4 +181,12 @@ public class CUnitType { public int getBuildTime() { return this.buildTime; } + + public EnumSet getPreventedPathingTypes() { + return this.preventedPathingTypes; + } + + public EnumSet getRequiredPathingTypes() { + return this.requiredPathingTypes; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWidget.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWidget.java index e047d7f..e59c433 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWidget.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWidget.java @@ -2,10 +2,11 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation; import java.util.EnumSet; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; -public abstract class CWidget { +public abstract class CWidget implements AbilityTarget { private final int handleId; private float x; private float y; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/AbstractCAbility.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/AbstractCAbility.java index c6fdb06..4b2a2d9 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/AbstractCAbility.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/AbstractCAbility.java @@ -1,7 +1,13 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; + public abstract class AbstractCAbility implements CAbility { private final int handleId; + private boolean disabled = false; + private boolean iconShowing = true; public AbstractCAbility(final int handleId) { this.handleId = handleId; @@ -11,4 +17,38 @@ public abstract class AbstractCAbility implements CAbility { public final int getHandleId() { return this.handleId; } + + @Override + public boolean isDisabled() { + return this.disabled; + } + + @Override + public void setDisabled(final boolean disabled) { + this.disabled = disabled; + } + + @Override + public boolean isIconShowing() { + return this.iconShowing; + } + + @Override + public void setIconShowing(final boolean iconShowing) { + this.iconShowing = iconShowing; + } + + @Override + public final void checkCanUse(final CSimulation game, final CUnit unit, final int orderId, + final AbilityActivationReceiver receiver) { + if (this.disabled) { + receiver.disabled(); + } + else { + innerCheckCanUse(game, unit, orderId, receiver); + } + } + + protected abstract void innerCheckCanUse(final CSimulation game, final CUnit unit, final int orderId, + final AbilityActivationReceiver receiver); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbility.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbility.java index 3de15cc..12a4cf0 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbility.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbility.java @@ -1,9 +1,9 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities; -import com.badlogic.gdx.math.Vector2; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; public interface CAbility extends CAbilityView { @@ -18,8 +18,12 @@ public interface CAbility extends CAbilityView { CBehavior begin(CSimulation game, CUnit caster, int orderId, CWidget target); - CBehavior begin(CSimulation game, CUnit caster, int orderId, Vector2 point); + CBehavior begin(CSimulation game, CUnit caster, int orderId, AbilityPointTarget point); CBehavior beginNoTarget(CSimulation game, CUnit caster, int orderId); + void setDisabled(boolean disabled); + + void setIconShowing(boolean iconShowing); + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java index cbe2994..70b8e3c 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java @@ -1,12 +1,11 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities; -import com.badlogic.gdx.math.Vector2; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorAttack; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CAllianceType; @@ -14,15 +13,14 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivat import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver.TargetType; -public class CAbilityAttack implements CAbility { - private final int handleId; +public class CAbilityAttack extends AbstractCAbility { public CAbilityAttack(final int handleId) { - this.handleId = handleId; + super(handleId); } @Override - public void checkCanUse(final CSimulation game, final CUnit unit, final int orderId, + protected void innerCheckCanUse(final CSimulation game, final CUnit unit, final int orderId, final AbilityActivationReceiver receiver) { receiver.useOk(); } @@ -66,8 +64,8 @@ public class CAbilityAttack implements CAbility { } @Override - public void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, final Vector2 target, - final AbilityTargetCheckReceiver receiver) { + public void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, + final AbilityPointTarget target, final AbilityTargetCheckReceiver receiver) { switch (orderId) { case OrderIds.attack: receiver.targetOk(target); @@ -75,8 +73,7 @@ public class CAbilityAttack implements CAbility { case OrderIds.attackground: boolean allowAttackGround = false; for (final CUnitAttack attack : unit.getUnitType().getAttacks()) { - if ((attack.getWeaponType() == CWeaponType.ARTILLERY) - || (attack.getWeaponType() == CWeaponType.ALINE)) { + if (attack.getWeaponType().isAttackGroundSupported()) { allowAttackGround = true; break; } @@ -119,14 +116,32 @@ public class CAbilityAttack implements CAbility { } } if (behavior == null) { - behavior = caster.getMoveBehavior().reset(OrderIds.attack, target.getX(), target.getY()); + behavior = caster.getMoveBehavior().reset(OrderIds.attack, target); } return behavior; } @Override - public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final Vector2 point) { - return caster.pollNextOrderBehavior(game); + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, + final AbilityPointTarget point) { + switch (orderId) { + case OrderIds.attack: + return caster.getMoveBehavior().reset(OrderIds.attack, point); + case OrderIds.attackground: + CBehavior behavior = null; + for (final CUnitAttack attack : caster.getUnitType().getAttacks()) { + if (attack.getWeaponType().isAttackGroundSupported()) { + behavior = caster.getAttackBehavior().reset(OrderIds.attackground, attack, point); + break; + } + } + if (behavior == null) { + behavior = caster.getMoveBehavior().reset(OrderIds.attackground, point); + } + return behavior; + default: + return caster.pollNextOrderBehavior(game); + } } @Override @@ -139,9 +154,4 @@ public class CAbilityAttack implements CAbility { return visitor.accept(this); } - @Override - public int getHandleId() { - return this.handleId; - } - } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityGeneric.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityGeneric.java index 52946c3..d3add11 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityGeneric.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityGeneric.java @@ -1,10 +1,10 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities; -import com.badlogic.gdx.math.Vector2; import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; @@ -12,13 +12,12 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetC /** * Represents an ability from the object data */ -public class CAbilityGeneric implements CAbility { +public class CAbilityGeneric extends AbstractCAbility { private final War3ID rawcode; - private final int handleId; public CAbilityGeneric(final War3ID rawcode, final int handleId) { + super(handleId); this.rawcode = rawcode; - this.handleId = handleId; } public War3ID getRawcode() { @@ -26,7 +25,7 @@ public class CAbilityGeneric implements CAbility { } @Override - public void checkCanUse(final CSimulation game, final CUnit unit, final int orderId, + protected void innerCheckCanUse(final CSimulation game, final CUnit unit, final int orderId, final AbilityActivationReceiver receiver) { receiver.notAnActiveAbility(); } @@ -38,8 +37,8 @@ public class CAbilityGeneric implements CAbility { } @Override - public void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, final Vector2 target, - final AbilityTargetCheckReceiver receiver) { + public void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, + final AbilityPointTarget target, final AbilityTargetCheckReceiver receiver) { receiver.orderIdNotAccepted(); } @@ -49,11 +48,6 @@ public class CAbilityGeneric implements CAbility { receiver.orderIdNotAccepted(); } - @Override - public int getHandleId() { - return this.handleId; - } - @Override public T visit(final CAbilityVisitor visitor) { return visitor.accept(this); @@ -78,7 +72,8 @@ public class CAbilityGeneric implements CAbility { } @Override - public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final Vector2 point) { + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, + final AbilityPointTarget point) { return caster.pollNextOrderBehavior(game); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityMove.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityMove.java index 64b1988..3cb7e90 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityMove.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityMove.java @@ -1,9 +1,9 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities; -import com.badlogic.gdx.math.Vector2; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorFollow; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorMove; @@ -13,15 +13,14 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivat import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver.TargetType; -public class CAbilityMove implements CAbility { - private final int handleId; +public class CAbilityMove extends AbstractCAbility { public CAbilityMove(final int handleId) { - this.handleId = handleId; + super(handleId); } @Override - public void checkCanUse(final CSimulation game, final CUnit unit, final int orderId, + protected void innerCheckCanUse(final CSimulation game, final CUnit unit, final int orderId, final AbilityActivationReceiver receiver) { receiver.useOk(); } @@ -46,8 +45,8 @@ public class CAbilityMove implements CAbility { } @Override - public void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, final Vector2 target, - final AbilityTargetCheckReceiver receiver) { + public void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, + final AbilityPointTarget target, final AbilityTargetCheckReceiver receiver) { switch (orderId) { case OrderIds.smart: case OrderIds.move: @@ -96,12 +95,13 @@ public class CAbilityMove implements CAbility { } @Override - public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final Vector2 point) { + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, + final AbilityPointTarget point) { if (orderId == OrderIds.patrol) { return caster.getPatrolBehavior().reset(point); } else { - return caster.getMoveBehavior().reset(OrderIds.move, point.x, point.y); + return caster.getMoveBehavior().reset(OrderIds.move, point); } } @@ -115,9 +115,4 @@ public class CAbilityMove implements CAbility { return visitor.accept(this); } - @Override - public int getHandleId() { - return this.handleId; - } - } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityView.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityView.java index 8ef4b1a..05cd811 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityView.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityView.java @@ -1,9 +1,9 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities; -import com.badlogic.gdx.math.Vector2; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; @@ -13,12 +13,16 @@ public interface CAbilityView { void checkCanTarget(CSimulation game, CUnit unit, int orderId, CWidget target, AbilityTargetCheckReceiver receiver); - void checkCanTarget(CSimulation game, CUnit unit, int orderId, Vector2 target, - AbilityTargetCheckReceiver receiver); + void checkCanTarget(CSimulation game, CUnit unit, int orderId, AbilityPointTarget target, + AbilityTargetCheckReceiver receiver); void checkCanTargetNoTarget(CSimulation game, CUnit unit, int orderId, AbilityTargetCheckReceiver receiver); int getHandleId(); + boolean isDisabled(); + + boolean isIconShowing(); + T visit(CAbilityVisitor visitor); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityVisitor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityVisitor.java index 2aa8c0d..7e29bd8 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityVisitor.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityVisitor.java @@ -8,7 +8,9 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAb import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityOrcBuild; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityUndeadBuild; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.combat.CAbilityColdArrows; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic.GenericSingleIconActiveAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityQueue; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityRally; /** * A visitor for the lowest level inherent types of an ability. It's a bit of a @@ -42,4 +44,8 @@ public interface CAbilityVisitor { T accept(CAbilityBuildInProgress ability); T accept(CAbilityQueue ability); + + T accept(GenericSingleIconActiveAbility ability); + + T accept(CAbilityRally ability); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java index cf8d1b1..9c0d731 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java @@ -5,7 +5,6 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Set; -import com.badlogic.gdx.math.Vector2; import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; @@ -13,6 +12,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.AbstractCAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.menu.CAbilityMenu; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayer; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; @@ -31,7 +31,7 @@ public abstract class AbstractCAbilityBuild extends AbstractCAbility implements } @Override - public void checkCanUse(final CSimulation game, final CUnit unit, final int orderId, + protected void innerCheckCanUse(final CSimulation game, final CUnit unit, final int orderId, final AbilityActivationReceiver receiver) { final War3ID orderIdAsRawtype = new War3ID(orderId); if (this.structuresBuilt.contains(orderIdAsRawtype)) { @@ -67,8 +67,8 @@ public abstract class AbstractCAbilityBuild extends AbstractCAbility implements } @Override - public final void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, final Vector2 target, - final AbilityTargetCheckReceiver receiver) { + public final void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, + final AbilityPointTarget target, final AbilityTargetCheckReceiver receiver) { if (this.structuresBuilt.contains(new War3ID(orderId))) { receiver.targetOk(target); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityBuildInProgress.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityBuildInProgress.java index 1b5a906..b5e94b2 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityBuildInProgress.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityBuildInProgress.java @@ -1,11 +1,11 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build; -import com.badlogic.gdx.math.Vector2; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.AbstractCAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; @@ -39,7 +39,8 @@ public class CAbilityBuildInProgress extends AbstractCAbility { } @Override - public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final Vector2 point) { + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, + final AbilityPointTarget point) { return null; } @@ -49,7 +50,7 @@ public class CAbilityBuildInProgress extends AbstractCAbility { } @Override - public void checkCanUse(final CSimulation game, final CUnit unit, final int orderId, + protected void innerCheckCanUse(final CSimulation game, final CUnit unit, final int orderId, final AbilityActivationReceiver receiver) { receiver.useOk(); } @@ -61,8 +62,8 @@ public class CAbilityBuildInProgress extends AbstractCAbility { } @Override - public void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, final Vector2 target, - final AbilityTargetCheckReceiver receiver) { + public void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, + final AbilityPointTarget target, final AbilityTargetCheckReceiver receiver) { receiver.orderIdNotAccepted(); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityHumanBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityHumanBuild.java index d8764c9..5646b88 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityHumanBuild.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityHumanBuild.java @@ -2,12 +2,12 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build; import java.util.List; -import com.badlogic.gdx.math.Vector2; import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; @@ -42,7 +42,7 @@ public class CAbilityHumanBuild extends AbstractCAbilityBuild { } @Override - public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final Vector2 point) { + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final AbilityPointTarget point) { // caster.getMoveBehavior().reset(point.x, point.y, ) return null; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityNagaBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityNagaBuild.java index 28a0af0..1c353fc 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityNagaBuild.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityNagaBuild.java @@ -2,12 +2,12 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build; import java.util.List; -import com.badlogic.gdx.math.Vector2; import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; @@ -36,7 +36,7 @@ public class CAbilityNagaBuild extends AbstractCAbilityBuild { } @Override - public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final Vector2 point) { + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final AbilityPointTarget point) { // TODO Auto-generated method stub return null; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityNeutralBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityNeutralBuild.java index f9517cf..1f26807 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityNeutralBuild.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityNeutralBuild.java @@ -2,12 +2,12 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build; import java.util.List; -import com.badlogic.gdx.math.Vector2; import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; @@ -36,7 +36,7 @@ public class CAbilityNeutralBuild extends AbstractCAbilityBuild { } @Override - public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final Vector2 point) { + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final AbilityPointTarget point) { // TODO Auto-generated method stub return null; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityNightElfBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityNightElfBuild.java index a69a4e0..5d837e0 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityNightElfBuild.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityNightElfBuild.java @@ -2,12 +2,12 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build; import java.util.List; -import com.badlogic.gdx.math.Vector2; import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; @@ -42,7 +42,7 @@ public class CAbilityNightElfBuild extends AbstractCAbilityBuild { } @Override - public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final Vector2 point) { + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final AbilityPointTarget point) { // TODO Auto-generated method stub return null; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityOrcBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityOrcBuild.java index c10fa33..36f0e51 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityOrcBuild.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityOrcBuild.java @@ -2,12 +2,12 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build; import java.util.List; -import com.badlogic.gdx.math.Vector2; import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.build.CBehaviorOrcBuild; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; @@ -39,8 +39,9 @@ public class CAbilityOrcBuild extends AbstractCAbilityBuild { } @Override - public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final Vector2 point) { - return this.buildBehavior.reset(point.x, point.y, orderId, getBaseOrderId()); + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, + final AbilityPointTarget point) { + return this.buildBehavior.reset(point, orderId, getBaseOrderId()); } @Override diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityUndeadBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityUndeadBuild.java index cd0b388..d49e55f 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityUndeadBuild.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityUndeadBuild.java @@ -2,12 +2,12 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build; import java.util.List; -import com.badlogic.gdx.math.Vector2; import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; @@ -42,7 +42,7 @@ public class CAbilityUndeadBuild extends AbstractCAbilityBuild { } @Override - public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final Vector2 point) { + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final AbilityPointTarget point) { // TODO Auto-generated method stub return null; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/combat/CAbilityColdArrows.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/combat/CAbilityColdArrows.java index 742e0ae..360ac0e 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/combat/CAbilityColdArrows.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/combat/CAbilityColdArrows.java @@ -1,12 +1,12 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.combat; -import com.badlogic.gdx.math.Vector2; import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.AbstractCAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; @@ -16,18 +16,17 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetC /** * Represents an ability from the object data */ -public class CAbilityColdArrows implements CAbility { +public class CAbilityColdArrows extends AbstractCAbility { private final War3ID rawcode; - private final int handleId; private boolean autoCastActive; public CAbilityColdArrows(final War3ID rawcode, final int handleId) { + super(handleId); this.rawcode = rawcode; - this.handleId = handleId; } @Override - public void checkCanUse(final CSimulation game, final CUnit unit, final int orderId, + protected void innerCheckCanUse(final CSimulation game, final CUnit unit, final int orderId, final AbilityActivationReceiver receiver) { receiver.useOk(); } @@ -46,8 +45,8 @@ public class CAbilityColdArrows implements CAbility { } @Override - public void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, final Vector2 target, - final AbilityTargetCheckReceiver receiver) { + public void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, + final AbilityPointTarget target, final AbilityTargetCheckReceiver receiver) { receiver.orderIdNotAccepted(); } @@ -69,11 +68,6 @@ public class CAbilityColdArrows implements CAbility { return this.rawcode; } - @Override - public int getHandleId() { - return this.handleId; - } - public boolean isAutoCastActive() { return this.autoCastActive; } @@ -119,7 +113,8 @@ public class CAbilityColdArrows implements CAbility { } @Override - public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final Vector2 point) { + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, + final AbilityPointTarget point) { return caster.pollNextOrderBehavior(game); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/GenericSingleIconActiveAbility.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/GenericSingleIconActiveAbility.java new file mode 100644 index 0000000..096fb86 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/GenericSingleIconActiveAbility.java @@ -0,0 +1,10 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic; + +import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; + +public interface GenericSingleIconActiveAbility extends CAbility { + War3ID getAlias(); + + int getBaseOrderId(); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityQueue.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityQueue.java index 2f1af60..2bd4d48 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityQueue.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityQueue.java @@ -4,7 +4,6 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Set; -import com.badlogic.gdx.math.Vector2; import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; @@ -12,7 +11,9 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.AbstractCAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayer; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; @@ -37,7 +38,7 @@ public final class CAbilityQueue extends AbstractCAbility { } @Override - public void checkCanUse(final CSimulation game, final CUnit unit, final int orderId, + protected void innerCheckCanUse(final CSimulation game, final CUnit unit, final int orderId, final AbilityActivationReceiver receiver) { final War3ID orderIdAsRawtype = new War3ID(orderId); if (this.unitsTrained.contains(orderIdAsRawtype) || this.researchesAvailable.contains(orderIdAsRawtype)) { @@ -73,8 +74,8 @@ public final class CAbilityQueue extends AbstractCAbility { } @Override - public final void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, final Vector2 target, - final AbilityTargetCheckReceiver receiver) { + public final void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, + final AbilityPointTarget target, final AbilityTargetCheckReceiver receiver) { receiver.orderIdNotAccepted(); } @@ -84,6 +85,9 @@ public final class CAbilityQueue extends AbstractCAbility { if (this.unitsTrained.contains(new War3ID(orderId)) || this.researchesAvailable.contains(new War3ID(orderId))) { receiver.targetOk(null); } + else if (orderId == OrderIds.cancel) { + receiver.targetOk(null); + } else { receiver.orderIdNotAccepted(); } @@ -110,18 +114,24 @@ public final class CAbilityQueue extends AbstractCAbility { } @Override - public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final Vector2 point) { + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, + final AbilityPointTarget point) { return null; } @Override public CBehavior beginNoTarget(final CSimulation game, final CUnit caster, final int orderId) { - final War3ID rawcode = new War3ID(orderId); - if (this.unitsTrained.contains(rawcode)) { - caster.queueTrainingUnit(rawcode); + if (orderId == OrderIds.cancel) { + caster.cancelBuildQueueItem(game, 0); } - else if (this.researchesAvailable.contains(rawcode)) { - caster.queueResearch(rawcode); + else { + final War3ID rawcode = new War3ID(orderId); + if (this.unitsTrained.contains(rawcode)) { + caster.queueTrainingUnit(rawcode); + } + else if (this.researchesAvailable.contains(rawcode)) { + caster.queueResearch(rawcode); + } } return null; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityRally.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityRally.java new file mode 100644 index 0000000..72a6465 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityRally.java @@ -0,0 +1,102 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.AbstractCAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; + +public class CAbilityRally extends AbstractCAbility { + + public CAbilityRally(final int handleId) { + super(handleId); + } + + @Override + public void onAdd(final CSimulation game, final CUnit unit) { + + } + + @Override + public void onRemove(final CSimulation game, final CUnit unit) { + + } + + @Override + protected void innerCheckCanUse(final CSimulation game, final CUnit unit, final int orderId, + final AbilityActivationReceiver receiver) { + receiver.useOk(); + } + + @Override + public void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, final CWidget target, + final AbilityTargetCheckReceiver receiver) { + switch (orderId) { + case OrderIds.smart: + case OrderIds.setrally: + receiver.targetOk(target); + break; + default: + receiver.orderIdNotAccepted(); + break; + } + } + + @Override + public void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, + final AbilityPointTarget target, final AbilityTargetCheckReceiver receiver) { + switch (orderId) { + case OrderIds.smart: + case OrderIds.setrally: + receiver.targetOk(target); + break; + default: + receiver.orderIdNotAccepted(); + break; + } + } + + @Override + public void checkCanTargetNoTarget(final CSimulation game, final CUnit unit, final int orderId, + final AbilityTargetCheckReceiver receiver) { + receiver.orderIdNotAccepted(); + } + + @Override + public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId) { + return true; + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final CWidget target) { + caster.setRallyPoint(target); + return caster.pollNextOrderBehavior(game); + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, + final AbilityPointTarget point) { + caster.setRallyPoint(point); + return caster.pollNextOrderBehavior(game); + } + + @Override + public CBehavior beginNoTarget(final CSimulation game, final CUnit caster, final int orderId) { + return null; + } + + @Override + public T visit(final CAbilityVisitor visitor) { + return visitor.accept(this); + } + + public int getBaseOrderId() { + return OrderIds.setrally; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/targeting/AbilityPointTarget.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/targeting/AbilityPointTarget.java new file mode 100644 index 0000000..bc443a6 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/targeting/AbilityPointTarget.java @@ -0,0 +1,34 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting; + +import com.badlogic.gdx.math.Vector2; + +public class AbilityPointTarget extends Vector2 implements AbilityTarget { + + public AbilityPointTarget() { + super(); + } + + public AbilityPointTarget(final float x, final float y) { + super(x, y); + } + + public AbilityPointTarget(final Vector2 v) { + super(v); + } + + @Override + public float getX() { + return this.x; + } + + @Override + public float getY() { + return this.y; + } + + @Override + public T visit(final AbilityTargetVisitor visitor) { + return visitor.accept(this); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/targeting/AbilityTarget.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/targeting/AbilityTarget.java new file mode 100644 index 0000000..c172d19 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/targeting/AbilityTarget.java @@ -0,0 +1,9 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting; + +public interface AbilityTarget { + float getX(); + + float getY(); + + T visit(AbilityTargetVisitor visitor); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/targeting/AbilityTargetStillAliveVisitor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/targeting/AbilityTargetStillAliveVisitor.java new file mode 100644 index 0000000..dfd796f --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/targeting/AbilityTargetStillAliveVisitor.java @@ -0,0 +1,30 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CDestructable; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CItem; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; + +public class AbilityTargetStillAliveVisitor implements AbilityTargetVisitor { + public static final AbilityTargetStillAliveVisitor INSTANCE = new AbilityTargetStillAliveVisitor(); + + @Override + public Boolean accept(final AbilityPointTarget target) { + return Boolean.TRUE; + } + + @Override + public Boolean accept(final CUnit target) { + return !target.isDead(); + } + + @Override + public Boolean accept(final CDestructable target) { + return !target.isDead(); + } + + @Override + public Boolean accept(final CItem target) { + return !target.isDead(); + } + +} \ No newline at end of file diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/targeting/AbilityTargetVisitor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/targeting/AbilityTargetVisitor.java new file mode 100644 index 0000000..4768f67 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/targeting/AbilityTargetVisitor.java @@ -0,0 +1,15 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CDestructable; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CItem; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; + +public interface AbilityTargetVisitor { + T accept(AbilityPointTarget target); + + T accept(CUnit target); + + T accept(CDestructable target); + + T accept(CItem target); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/targeting/AbilityTargetWidgetVisitor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/targeting/AbilityTargetWidgetVisitor.java new file mode 100644 index 0000000..7a3deb5 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/targeting/AbilityTargetWidgetVisitor.java @@ -0,0 +1,31 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CDestructable; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CItem; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; + +public class AbilityTargetWidgetVisitor implements AbilityTargetVisitor { + public static final AbilityTargetWidgetVisitor INSTANCE = new AbilityTargetWidgetVisitor(); + + @Override + public CWidget accept(final AbilityPointTarget target) { + return null; + } + + @Override + public CWidget accept(final CUnit target) { + return target; + } + + @Override + public CWidget accept(final CDestructable target) { + return target; + } + + @Override + public CWidget accept(final CItem target) { + return target; + } + +} \ No newline at end of file diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CAbstractRangedBehavior.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CAbstractRangedBehavior.java index bea9639..83b7141 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CAbstractRangedBehavior.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CAbstractRangedBehavior.java @@ -2,6 +2,7 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget; public abstract class CAbstractRangedBehavior implements CRangedBehavior { protected final CUnit unit; @@ -10,16 +11,18 @@ public abstract class CAbstractRangedBehavior implements CRangedBehavior { this.unit = unit; } + protected AbilityTarget target; private boolean wasWithinPropWindow = false; private boolean wasInRange = false; private CBehaviorMove moveBehavior; - protected final CAbstractRangedBehavior innerReset() { + protected final CAbstractRangedBehavior innerReset(final AbilityTarget target) { + this.target = target; this.wasWithinPropWindow = false; this.wasInRange = false; CBehaviorMove moveBehavior; if (!this.unit.isMovementDisabled()) { - moveBehavior = setupMoveBehavior(); + moveBehavior = this.unit.getMoveBehavior().reset(this.target, this); } else { moveBehavior = null; @@ -28,12 +31,6 @@ public abstract class CAbstractRangedBehavior implements CRangedBehavior { return this; } - protected abstract CBehaviorMove setupMoveBehavior(); - - protected abstract float getTargetX(); - - protected abstract float getTargetY(); - protected abstract CBehavior update(CSimulation simulation, boolean withinRange); protected abstract boolean checkTargetStillValid(CSimulation simulation); @@ -57,8 +54,8 @@ public abstract class CAbstractRangedBehavior implements CRangedBehavior { if (!this.unit.isMovementDisabled()) { final float prevX = this.unit.getX(); final float prevY = this.unit.getY(); - final float deltaY = getTargetY() - prevY; - final float deltaX = getTargetX() - prevX; + final float deltaX = this.target.getX() - prevX; + final float deltaY = this.target.getY() - prevY; final double goalAngleRad = Math.atan2(deltaY, deltaX); float goalAngle = (float) Math.toDegrees(goalAngleRad); if (goalAngle < 0) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CAbstractRangedPointTargetBehavior.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CAbstractRangedPointTargetBehavior.java deleted file mode 100644 index 1064290..0000000 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CAbstractRangedPointTargetBehavior.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors; - -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; - -public abstract class CAbstractRangedPointTargetBehavior extends CAbstractRangedBehavior { - - protected float targetX; - protected float targetY; - - public CAbstractRangedPointTargetBehavior(final CUnit unit) { - super(unit); - } - - protected final CAbstractRangedBehavior innerReset(final float targetX, final float targetY) { - this.targetX = targetX; - this.targetY = targetY; - return innerReset(); - } - - @Override - protected float getTargetX() { - return this.targetX; - } - - @Override - protected float getTargetY() { - return this.targetY; - } - - @Override - protected CBehaviorMove setupMoveBehavior() { - return this.unit.getMoveBehavior().reset(this.targetX, this.targetY, this); - } - -} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CAbstractRangedWidgetTargetBehavior.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CAbstractRangedWidgetTargetBehavior.java deleted file mode 100644 index 3b1ff9c..0000000 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CAbstractRangedWidgetTargetBehavior.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors; - -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; - -public abstract class CAbstractRangedWidgetTargetBehavior extends CAbstractRangedBehavior { - - protected CWidget target; - - public CAbstractRangedWidgetTargetBehavior(final CUnit unit) { - super(unit); - } - - protected final CAbstractRangedBehavior innerReset(final CWidget target) { - this.target = target; - return innerReset(); - } - - @Override - protected float getTargetX() { - return this.target.getX(); - } - - @Override - protected float getTargetY() { - return this.target.getY(); - } - - @Override - protected CBehaviorMove setupMoveBehavior() { - if ((this.target instanceof CUnit) && !((CUnit) this.target).getUnitType().isBuilding()) { - return this.unit.getMoveBehavior().reset((CUnit) this.target, this); - } - else { - return this.unit.getMoveBehavior().reset(this.target.getX(), this.target.getY(), this); - } - } - -} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java index 15b59ca..da73c43 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java @@ -3,17 +3,23 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors; import com.etheller.warsmash.util.WarsmashConstants; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CDestructable; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CItem; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTargetVisitor; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; -public class CBehaviorAttack extends CAbstractRangedWidgetTargetBehavior { +public class CBehaviorAttack extends CAbstractRangedBehavior { private int highlightOrderId; + private final AbilityTargetStillAliveAndTargetableVisitor abilityTargetStillAliveVisitor; public CBehaviorAttack(final CUnit unit) { super(unit); + this.abilityTargetStillAliveVisitor = new AbilityTargetStillAliveAndTargetableVisitor(); } private CUnitAttack unitAttack; @@ -21,7 +27,7 @@ public class CBehaviorAttack extends CAbstractRangedWidgetTargetBehavior { private int backSwingTime; private int thisOrderCooldownEndTime; - public CBehaviorAttack reset(final int highlightOrderId, final CUnitAttack unitAttack, final CWidget target) { + public CBehaviorAttack reset(final int highlightOrderId, final CUnitAttack unitAttack, final AbilityTarget target) { this.highlightOrderId = highlightOrderId; super.innerReset(target); this.unitAttack = unitAttack; @@ -48,8 +54,7 @@ public class CBehaviorAttack extends CAbstractRangedWidgetTargetBehavior { @Override protected boolean checkTargetStillValid(final CSimulation simulation) { - return !this.target.isDead() - && this.target.canBeTargetedBy(simulation, this.unit, this.unitAttack.getTargetsAllowed()); + return this.target.visit(this.abilityTargetStillAliveVisitor.reset(simulation, this.unit, this.unitAttack)); } @Override @@ -112,4 +117,42 @@ public class CBehaviorAttack extends CAbstractRangedWidgetTargetBehavior { return this; } + public static class AbilityTargetStillAliveAndTargetableVisitor implements AbilityTargetVisitor { + private CSimulation simulation; + private CUnit unit; + private CUnitAttack unitAttack; + + public AbilityTargetStillAliveAndTargetableVisitor reset(final CSimulation simulation, final CUnit unit, + final CUnitAttack unitAttack) { + this.simulation = simulation; + this.unit = unit; + this.unitAttack = unitAttack; + return this; + } + + @Override + public Boolean accept(final AbilityPointTarget target) { + return Boolean.TRUE; + } + + @Override + public Boolean accept(final CUnit target) { + return !target.isDead() + && target.canBeTargetedBy(this.simulation, this.unit, this.unitAttack.getTargetsAllowed()); + } + + @Override + public Boolean accept(final CDestructable target) { + return !target.isDead() + && target.canBeTargetedBy(this.simulation, this.unit, this.unitAttack.getTargetsAllowed()); + } + + @Override + public Boolean accept(final CItem target) { + return !target.isDead() + && target.canBeTargetedBy(this.simulation, this.unit, this.unitAttack.getTargetsAllowed()); + } + + } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorFollow.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorFollow.java index 02c98dd..612f4a0 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorFollow.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorFollow.java @@ -4,8 +4,9 @@ import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTargetStillAliveVisitor; -public class CBehaviorFollow extends CAbstractRangedWidgetTargetBehavior { +public class CBehaviorFollow extends CAbstractRangedBehavior { private int higlightOrderId; @@ -36,7 +37,7 @@ public class CBehaviorFollow extends CAbstractRangedWidgetTargetBehavior { @Override protected boolean checkTargetStillValid(final CSimulation simulation) { - return !this.target.isDead(); + return this.target.visit(AbilityTargetStillAliveVisitor.INSTANCE); } @Override diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorMove.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorMove.java index 6e1e995..209a6b4 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorMove.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorMove.java @@ -10,18 +10,26 @@ import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.MovementType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CDestructable; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CItem; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWorldCollision; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTargetVisitor; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.pathing.CPathfindingProcessor; public class CBehaviorMove implements CBehavior { + private static final Rectangle tempRect = new Rectangle(); private final CUnit unit; private int highlightOrderId; + private final TargetVisitingResetter targetVisitingResetter; public CBehaviorMove(final CUnit unit) { this.unit = unit; + this.targetVisitingResetter = new TargetVisitingResetter(); } private boolean wasWithinPropWindow = false; @@ -32,12 +40,19 @@ public class CBehaviorMove implements CBehavior { private CUnit followUnit; private CRangedBehavior rangedBehavior; - public CBehaviorMove reset(final int highlightOrderId, final float targetX, final float targetY) { - internalResetMove(highlightOrderId, targetX, targetY); + public CBehaviorMove reset(final int highlightOrderId, final AbilityTarget target) { + target.visit(this.targetVisitingResetter.reset(highlightOrderId)); this.rangedBehavior = null; return this; } + public CBehaviorMove reset(final AbilityTarget target, final CRangedBehavior rangedBehavior) { + final int highlightOrderId = rangedBehavior.getHighlightOrderId(); + target.visit(this.targetVisitingResetter.reset(highlightOrderId)); + this.rangedBehavior = rangedBehavior; + return this; + } + private void internalResetMove(final int highlightOrderId, final float targetX, final float targetY) { this.highlightOrderId = highlightOrderId; this.wasWithinPropWindow = false; @@ -50,24 +65,6 @@ public class CBehaviorMove implements CBehavior { this.followUnit = null; } - public CBehaviorMove reset(final float targetX, final float targetY, final CRangedBehavior rangedBehavior) { - internalResetMove(rangedBehavior.getHighlightOrderId(), targetX, targetY); - this.rangedBehavior = rangedBehavior; - return this; - } - - public CBehaviorMove reset(final int highlightOrderId, final CUnit followUnit) { - internalResetMove(highlightOrderId, followUnit); - this.rangedBehavior = null; - return this; - } - - public CBehaviorMove reset(final CUnit followUnit, final CRangedBehavior rangedBehavior) { - internalResetMove(rangedBehavior.getHighlightOrderId(), followUnit); - this.rangedBehavior = rangedBehavior; - return this; - } - private void internalResetMove(final int highlightOrderId, final CUnit followUnit) { this.highlightOrderId = highlightOrderId; this.wasWithinPropWindow = false; @@ -355,4 +352,36 @@ public class CBehaviorMove implements CBehavior { return this; } + private final class TargetVisitingResetter implements AbilityTargetVisitor { + private int highlightOrderId; + + private TargetVisitingResetter reset(final int highlightOrderId) { + this.highlightOrderId = highlightOrderId; + return this; + } + + @Override + public Void accept(final AbilityPointTarget target) { + internalResetMove(this.highlightOrderId, target.x, target.y); + return null; + } + + @Override + public Void accept(final CUnit target) { + internalResetMove(this.highlightOrderId, target); + return null; + } + + @Override + public Void accept(final CDestructable target) { + internalResetMove(this.highlightOrderId, target.getX(), target.getY()); + return null; + } + + @Override + public Void accept(final CItem target) { + internalResetMove(this.highlightOrderId, target.getX(), target.getY()); + return null; + } + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorPatrol.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorPatrol.java index f2d6c09..95b5d53 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorPatrol.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorPatrol.java @@ -1,23 +1,23 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors; -import com.badlogic.gdx.math.Vector2; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; public class CBehaviorPatrol implements CRangedBehavior { private final CUnit unit; - private Vector2 target; - private Vector2 startPoint; + private AbilityPointTarget target; + private AbilityPointTarget startPoint; public CBehaviorPatrol(final CUnit unit) { this.unit = unit; } - public CBehavior reset(final Vector2 target) { + public CBehavior reset(final AbilityPointTarget target) { this.target = target; - this.startPoint = new Vector2(this.unit.getX(), this.unit.getY()); + this.startPoint = new AbilityPointTarget(this.unit.getX(), this.unit.getY()); return this; } @@ -34,10 +34,10 @@ public class CBehaviorPatrol implements CRangedBehavior { @Override public CBehavior update(final CSimulation simulation) { - final Vector2 temp = this.target; + final AbilityPointTarget temp = this.target; this.target = this.startPoint; this.startPoint = temp; - return this.unit.getMoveBehavior().reset(this.target.x, this.target.y, this); + return this.unit.getMoveBehavior().reset(this.target, this); } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/AbilityDisableWhileUnderConstructionVisitor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/AbilityDisableWhileUnderConstructionVisitor.java new file mode 100644 index 0000000..cb71490 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/AbilityDisableWhileUnderConstructionVisitor.java @@ -0,0 +1,120 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.build; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityAttack; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityGeneric; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityMove; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityBuildInProgress; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityHumanBuild; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityNagaBuild; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityNeutralBuild; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityNightElfBuild; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityOrcBuild; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityUndeadBuild; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.combat.CAbilityColdArrows; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic.GenericSingleIconActiveAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityQueue; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityRally; + +public class AbilityDisableWhileUnderConstructionVisitor implements CAbilityVisitor { + public static final AbilityDisableWhileUnderConstructionVisitor INSTANCE = new AbilityDisableWhileUnderConstructionVisitor(); + + @Override + public Void accept(final CAbilityAttack ability) { + ability.setDisabled(true); + ability.setIconShowing(false); + return null; + } + + @Override + public Void accept(final CAbilityMove ability) { + ability.setDisabled(true); + ability.setIconShowing(false); + return null; + } + + @Override + public Void accept(final CAbilityOrcBuild ability) { + ability.setDisabled(true); + ability.setIconShowing(false); + return null; + } + + @Override + public Void accept(final CAbilityHumanBuild ability) { + ability.setDisabled(true); + ability.setIconShowing(false); + return null; + } + + @Override + public Void accept(final CAbilityUndeadBuild ability) { + ability.setDisabled(true); + ability.setIconShowing(false); + return null; + } + + @Override + public Void accept(final CAbilityNightElfBuild ability) { + ability.setDisabled(true); + ability.setIconShowing(false); + return null; + } + + @Override + public Void accept(final CAbilityGeneric ability) { + ability.setDisabled(true); + ability.setIconShowing(false); + return null; + } + + @Override + public Void accept(final CAbilityColdArrows ability) { + ability.setDisabled(true); + ability.setIconShowing(false); + return null; + } + + @Override + public Void accept(final CAbilityNagaBuild ability) { + ability.setDisabled(true); + ability.setIconShowing(false); + return null; + } + + @Override + public Void accept(final CAbilityNeutralBuild ability) { + ability.setDisabled(true); + ability.setIconShowing(false); + return null; + } + + @Override + public Void accept(final CAbilityBuildInProgress ability) { + ability.setDisabled(false); + ability.setIconShowing(false); + return null; + } + + @Override + public Void accept(final CAbilityQueue ability) { + ability.setDisabled(true); + ability.setIconShowing(false); + return null; + } + + @Override + public Void accept(final GenericSingleIconActiveAbility ability) { + ability.setDisabled(true); + ability.setIconShowing(false); + return null; + } + + @Override + public Void accept(final CAbilityRally ability) { + ability.setDisabled(false); + ability.setIconShowing(false); + return null; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java index 140aca2..518526b 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java @@ -1,15 +1,21 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.build; +import java.awt.image.BufferedImage; +import java.util.EnumSet; + import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.util.WarsmashConstants; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityBuildInProgress; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CAbstractRangedPointTargetBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CAbstractRangedBehavior; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.pathing.CBuildingPathingType; -public class CBehaviorOrcBuild extends CAbstractRangedPointTargetBehavior { +public class CBehaviorOrcBuild extends CAbstractRangedBehavior { private int highlightOrderId; private War3ID orderId; @@ -17,17 +23,17 @@ public class CBehaviorOrcBuild extends CAbstractRangedPointTargetBehavior { super(unit); } - public CBehavior reset(final float targetX, final float targetY, final int orderId, final int highlightOrderId) { + public CBehavior reset(final AbilityPointTarget target, final int orderId, final int highlightOrderId) { this.highlightOrderId = highlightOrderId; this.orderId = new War3ID(orderId); - return innerReset(targetX, targetY); + return innerReset(target); } @Override public boolean isWithinRange(final CSimulation simulation) { final CUnitType unitType = simulation.getUnitData().getUnitType(this.orderId); return this.unit.canReachToPathing(0, simulation.getGameplayConstants().getBuildingAngle(), - unitType.getBuildingPathingPixelMap(), this.targetX, this.targetY); + unitType.getBuildingPathingPixelMap(), this.target.getX(), this.target.getY()); } @Override @@ -37,16 +43,36 @@ public class CBehaviorOrcBuild extends CAbstractRangedPointTargetBehavior { @Override protected CBehavior update(final CSimulation simulation, final boolean withinRange) { - final CUnit constructedStructure = simulation.createUnit(this.orderId, this.unit.getPlayerIndex(), this.targetX, - this.targetY, simulation.getGameplayConstants().getBuildingAngle()); - constructedStructure.setConstructing(true); - constructedStructure.setWorkerInside(this.unit); - constructedStructure.setLife(simulation, - constructedStructure.getMaximumLife() * WarsmashConstants.BUILDING_CONSTRUCT_START_LIFE); - constructedStructure.add(simulation, new CAbilityBuildInProgress(simulation.getHandleIdAllocator().createId())); - this.unit.setHidden(true); - this.unit.setUpdating(false); - simulation.unitConstructedEvent(this.unit, constructedStructure); + final CUnitType unitTypeToCreate = simulation.getUnitData().getUnitType(this.orderId); + final BufferedImage buildingPathingPixelMap = unitTypeToCreate.getBuildingPathingPixelMap(); + boolean buildLocationObstructed = false; + if (buildingPathingPixelMap != null) { + final EnumSet preventedPathingTypes = unitTypeToCreate.getPreventedPathingTypes(); + final EnumSet requiredPathingTypes = unitTypeToCreate.getRequiredPathingTypes(); + + if (!simulation.getPathingGrid().checkPathingTexture(this.target.getX(), this.target.getY(), + (int) simulation.getGameplayConstants().getBuildingAngle(), buildingPathingPixelMap, + preventedPathingTypes, requiredPathingTypes, simulation.getWorldCollision(), this.unit)) { + buildLocationObstructed = true; + } + } + if (!buildLocationObstructed) { + final CUnit constructedStructure = simulation.createUnit(this.orderId, this.unit.getPlayerIndex(), + this.target.getX(), this.target.getY(), simulation.getGameplayConstants().getBuildingAngle()); + constructedStructure.setConstructing(true); + constructedStructure.setWorkerInside(this.unit); + constructedStructure.setLife(simulation, + constructedStructure.getMaximumLife() * WarsmashConstants.BUILDING_CONSTRUCT_START_LIFE); + constructedStructure.add(simulation, + new CAbilityBuildInProgress(simulation.getHandleIdAllocator().createId())); + for (final CAbility ability : constructedStructure.getAbilities()) { + ability.visit(AbilityDisableWhileUnderConstructionVisitor.INSTANCE); + } + this.unit.setHidden(true); + this.unit.setUpdating(false); + this.unit.setInvulnerable(true); + simulation.unitConstructedEvent(this.unit, constructedStructure); + } return this.unit.pollNextOrderBehavior(simulation); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/CWeaponType.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/CWeaponType.java index 6a9d634..b47162f 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/CWeaponType.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/CWeaponType.java @@ -13,4 +13,8 @@ public enum CWeaponType { public static CWeaponType parseWeaponType(final String weaponTypeString) { return valueOf(weaponTypeString.toUpperCase()); } + + public boolean isAttackGroundSupported() { + return (this == CWeaponType.ARTILLERY) || (this == CWeaponType.ALINE); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttack.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttack.java index b979cd5..b463192 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttack.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttack.java @@ -4,7 +4,7 @@ import java.util.EnumSet; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType; @@ -198,5 +198,5 @@ public abstract class CUnitAttack { return this.maxDamage; } - public abstract void launch(CSimulation simulation, CUnit unit, CWidget target, float damage); + public abstract void launch(CSimulation simulation, CUnit unit, AbilityTarget target, float damage); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackInstant.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackInstant.java index 80df2f1..131d8f7 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackInstant.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackInstant.java @@ -5,6 +5,8 @@ import java.util.EnumSet; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTargetWidgetVisitor; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType; @@ -32,9 +34,12 @@ public class CUnitAttackInstant extends CUnitAttack { } @Override - public void launch(final CSimulation simulation, final CUnit unit, final CWidget target, final float damage) { - simulation.createInstantAttackEffect(unit, this, target); - target.damage(simulation, unit, getAttackType(), getWeaponSound(), damage); + public void launch(final CSimulation simulation, final CUnit unit, final AbilityTarget target, final float damage) { + final CWidget widget = target.visit(AbilityTargetWidgetVisitor.INSTANCE); + if (widget != null) { + simulation.createInstantAttackEffect(unit, this, widget); + widget.damage(simulation, unit, getAttackType(), getWeaponSound(), damage); + } } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissile.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissile.java index f42c34c..bddceff 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissile.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissile.java @@ -5,6 +5,8 @@ import java.util.EnumSet; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTargetWidgetVisitor; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType; @@ -63,13 +65,16 @@ public class CUnitAttackMissile extends CUnitAttack { } @Override - public void launch(final CSimulation simulation, final CUnit unit, final CWidget target, final float damage) { + public void launch(final CSimulation simulation, final CUnit unit, final AbilityTarget target, final float damage) { simulation.createProjectile(unit, unit.getX(), unit.getY(), (float) Math.toRadians(unit.getFacing()), this, target, damage, 0); } - public void doDamage(final CSimulation cSimulation, final CUnit source, final CWidget target, final float damage, - final float x, final float y, final int bounceIndex) { - target.damage(cSimulation, source, getAttackType(), getWeaponSound(), damage); + public void doDamage(final CSimulation cSimulation, final CUnit source, final AbilityTarget target, + final float damage, final float x, final float y, final int bounceIndex) { + final CWidget widget = target.visit(AbilityTargetWidgetVisitor.INSTANCE); + if (widget != null) { + widget.damage(cSimulation, source, getAttackType(), getWeaponSound(), damage); + } } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileBounce.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileBounce.java index 0014ef2..c973afd 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileBounce.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileBounce.java @@ -7,6 +7,8 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitEnumFunction; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTargetWidgetVisitor; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType; @@ -51,12 +53,16 @@ public class CUnitAttackMissileBounce extends CUnitAttackMissile { } @Override - public void doDamage(final CSimulation cSimulation, final CUnit source, final CWidget target, final float damage, - final float x, final float y, final int bounceIndex) { + public void doDamage(final CSimulation cSimulation, final CUnit source, final AbilityTarget target, + final float damage, final float x, final float y, final int bounceIndex) { super.doDamage(cSimulation, source, target, damage, x, y, bounceIndex); - final int nextBounceIndex = bounceIndex + 1; - if (nextBounceIndex != this.maximumNumberOfTargets) { - BounceMissileConsumer.INSTANCE.nextBounce(cSimulation, source, target, this, x, y, damage, nextBounceIndex); + final CWidget widget = target.visit(AbilityTargetWidgetVisitor.INSTANCE); + if (widget != null) { + final int nextBounceIndex = bounceIndex + 1; + if (nextBounceIndex != this.maximumNumberOfTargets) { + BounceMissileConsumer.INSTANCE.nextBounce(cSimulation, source, widget, this, x, y, damage, + nextBounceIndex); + } } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileSplash.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileSplash.java index 453204e..409623a 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileSplash.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileSplash.java @@ -6,7 +6,7 @@ import com.badlogic.gdx.math.Rectangle; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitEnumFunction; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType; @@ -88,8 +88,8 @@ public class CUnitAttackMissileSplash extends CUnitAttackMissile { } @Override - public void doDamage(final CSimulation cSimulation, final CUnit source, final CWidget target, final float damage, - final float x, final float y, final int bounceIndex) { + public void doDamage(final CSimulation cSimulation, final CUnit source, final AbilityTarget target, + final float damage, final float x, final float y, final int bounceIndex) { SplashDamageConsumer.INSTANCE.doDamage(cSimulation, source, target, this, x, y, damage); if ((getWeaponType() != CWeaponType.ARTILLERY) && !SplashDamageConsumer.INSTANCE.hitTarget) { super.doDamage(cSimulation, source, target, damage * this.damageFactorSmall, x, y, bounceIndex); @@ -102,13 +102,13 @@ public class CUnitAttackMissileSplash extends CUnitAttackMissile { private CUnitAttackMissileSplash attack; private CSimulation simulation; private CUnit source; - private CWidget target; + private AbilityTarget target; private float x; private float y; private float damage; private boolean hitTarget; - public void doDamage(final CSimulation simulation, final CUnit source, final CWidget target, + public void doDamage(final CSimulation simulation, final CUnit source, final AbilityTarget target, final CUnitAttackMissileSplash attack, final float x, final float y, final float damage) { this.simulation = simulation; this.source = source; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackNormal.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackNormal.java index 211671e..60d8375 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackNormal.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackNormal.java @@ -5,6 +5,8 @@ import java.util.EnumSet; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTargetWidgetVisitor; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType; @@ -22,8 +24,11 @@ public class CUnitAttackNormal extends CUnitAttack { } @Override - public void launch(final CSimulation simulation, final CUnit unit, final CWidget target, final float damage) { - target.damage(simulation, unit, getAttackType(), getWeaponSound(), damage); + public void launch(final CSimulation simulation, final CUnit unit, final AbilityTarget target, final float damage) { + final CWidget widget = target.visit(AbilityTargetWidgetVisitor.INSTANCE); + if (widget != null) { + widget.damage(simulation, unit, getAttackType(), getWeaponSound(), damage); + } } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/projectile/CAttackProjectile.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/projectile/CAttackProjectile.java index 6b0353a..199ba5b 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/projectile/CAttackProjectile.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/projectile/CAttackProjectile.java @@ -3,7 +3,7 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.projectile; import com.etheller.warsmash.util.WarsmashConstants; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackMissile; @@ -13,15 +13,15 @@ public class CAttackProjectile { private final float initialTargetX; private final float initialTargetY; private final float speed; - private final CWidget target; + private final AbilityTarget target; private boolean done; private final CUnit source; private final float damage; private final CUnitAttackMissile unitAttack; private final int bounceIndex; - public CAttackProjectile(final float x, final float y, final float speed, final CWidget target, final CUnit source, - final float damage, final CUnitAttackMissile unitAttack, final int bounceIndex) { + public CAttackProjectile(final float x, final float y, final float speed, final AbilityTarget target, + final CUnit source, final float damage, final CUnitAttackMissile unitAttack, final int bounceIndex) { this.x = x; this.y = y; this.speed = speed; @@ -77,7 +77,7 @@ public class CAttackProjectile { return this.speed; } - public CWidget getTarget() { + public AbilityTarget getTarget() { return this.target; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java index 4869d37..0c05cf2 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java @@ -28,6 +28,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAb import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityOrcBuild; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityUndeadBuild; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityQueue; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityRally; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CDefenseType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; @@ -39,9 +40,11 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUni import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackMissileLine; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackMissileSplash; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackNormal; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.pathing.CBuildingPathingType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.SimulationRenderController; public class CUnitData { + private static final War3ID RALLY_RAWCODE = War3ID.fromString("ARal"); private static final War3ID MANA_INITIAL_AMOUNT = War3ID.fromString("umpi"); private static final War3ID MANA_MAXIMUM = War3ID.fromString("umpm"); private static final War3ID HIT_POINT_MAXIMUM = War3ID.fromString("uhpm"); @@ -140,6 +143,9 @@ public class CUnitData { private static final War3ID LUMBER_COST = War3ID.fromString("ulum"); private static final War3ID BUILD_TIME = War3ID.fromString("ubld"); + private static final War3ID REQUIRE_PLACE = War3ID.fromString("upar"); + private static final War3ID PREVENT_PLACE = War3ID.fromString("upap"); + private final MutableObjectData unitData; private final Map unitIdToUnitType = new HashMap<>(); private final CAbilityData abilityData; @@ -206,6 +212,9 @@ public class CUnitData { if (!unitsTrained.isEmpty() || !researchesAvailable.isEmpty()) { unit.add(simulation, new CAbilityQueue(handleIdAllocator.createId(), unitsTrained, researchesAvailable)); } + if (!unitsTrained.isEmpty()) { + unit.add(simulation, new CAbilityRally(handleIdAllocator.createId())); + } for (final String ability : abilityList.split(",")) { if ((ability.length() > 0) && !"_".equals(ability)) { final CAbility createAbility = this.abilityData.createAbility(ability, handleIdAllocator.createId()); @@ -270,10 +279,7 @@ public class CUnitData { final int damageUpgradeAmount = unitType.getFieldAsInteger(ATTACK1_DMG_UPGRADE_AMT, 0); final int maximumNumberOfTargets = unitType.getFieldAsInteger(ATTACK1_TARGET_COUNT, 0); final float projectileArc = unitType.getFieldAsFloat(ATTACK1_PROJECTILE_ARC, 0); - String projectileArt = unitType.getFieldAsString(ATTACK1_MISSILE_ART, 0); - if ("_".equals(projectileArt) || projectileArt.isEmpty()) { - projectileArt = unitType.getFieldAsString(ATTACK2_MISSILE_ART, 0); - } + final String projectileArt = unitType.getFieldAsString(ATTACK1_MISSILE_ART, 0); final boolean projectileHomingEnabled = unitType .getFieldAsBoolean(ATTACK1_PROJECTILE_HOMING_ENABLED, 0); final int projectileSpeed = unitType.getFieldAsInteger(ATTACK1_PROJECTILE_SPEED, 0); @@ -321,22 +327,35 @@ public class CUnitData { final float damageSpillRadius = unitType.getFieldAsFloat(ATTACK2_DMG_SPILL_RADIUS, 0); final int damageUpgradeAmount = unitType.getFieldAsInteger(ATTACK2_DMG_UPGRADE_AMT, 0); final int maximumNumberOfTargets = unitType.getFieldAsInteger(ATTACK2_TARGET_COUNT, 0); - final float projectileArc = unitType.getFieldAsFloat(ATTACK2_PROJECTILE_ARC, 0); + float projectileArc = unitType.getFieldAsFloat(ATTACK2_PROJECTILE_ARC, 0); String projectileArt = unitType.getFieldAsString(ATTACK2_MISSILE_ART, 0); + int projectileSpeed = unitType.getFieldAsInteger(ATTACK2_PROJECTILE_SPEED, 0); if ("_".equals(projectileArt) || projectileArt.isEmpty()) { projectileArt = unitType.getFieldAsString(ATTACK1_MISSILE_ART, 0); + projectileSpeed = unitType.getFieldAsInteger(ATTACK1_PROJECTILE_SPEED, 0); + projectileArc = unitType.getFieldAsFloat(ATTACK1_PROJECTILE_ARC, 0); } final boolean projectileHomingEnabled = unitType .getFieldAsBoolean(ATTACK2_PROJECTILE_HOMING_ENABLED, 0); - final int projectileSpeed = unitType.getFieldAsInteger(ATTACK2_PROJECTILE_SPEED, 0); final int range = unitType.getFieldAsInteger(ATTACK2_RANGE, 0); final float rangeMotionBuffer = unitType.getFieldAsFloat(ATTACK2_RANGE_MOTION_BUFFER, 0); - final boolean showUI = unitType.getFieldAsBoolean(ATTACK2_SHOW_UI, 0); + boolean showUI = unitType.getFieldAsBoolean(ATTACK2_SHOW_UI, 0); final EnumSet targetsAllowed = CTargetType .parseTargetTypeSet(unitType.getFieldAsString(ATTACK2_TARGETS_ALLOWED, 0)); final String weaponSound = unitType.getFieldAsString(ATTACK2_WEAPON_SOUND, 0); final CWeaponType weaponType = CWeaponType .parseWeaponType(unitType.getFieldAsString(ATTACK2_WEAPON_TYPE, 0)); + if (!attacks.isEmpty()) { + final CUnitAttack otherAttack = attacks.get(0); + if ((otherAttack.getAttackType() == attackType) && (targetsAllowed.size() == 1) + && (targetsAllowed.contains(CTargetType.TREE) + || (targetsAllowed.contains(CTargetType.STRUCTURE) + && (otherAttack.getDamageBase() == damageBase) + && (otherAttack.getDamageSidesPerDie() == damageSidesPerDie) + && (otherAttack.getDamageDice() == damageDice)))) { + showUI = false; + } + } attacks.add(createAttack(animationBackswingPoint, animationDamagePoint, areaOfEffectFullDamage, areaOfEffectMediumDamage, areaOfEffectSmallDamage, areaOfEffectTargets, attackType, cooldownTime, damageBase, damageFactorMedium, damageFactorSmall, damageLossFactor, @@ -387,13 +406,19 @@ public class CUnitData { } } + final EnumSet preventedPathingTypes = CBuildingPathingType + .parsePathingTypeListSet(unitType.getFieldAsString(PREVENT_PLACE, 0)); + final EnumSet requiredPathingTypes = CBuildingPathingType + .parsePathingTypeListSet(unitType.getFieldAsString(REQUIRE_PLACE, 0)); + final String raceString = unitType.getFieldAsString(UNIT_RACE, 0); final CUnitRace unitRace = CUnitRace.parseRace(raceString); unitTypeInstance = new CUnitType(unitName, isBldg, movementType, moveHeight, collisionSize, classifications, attacks, armorType, raise, decay, defenseType, impactZ, buildingPathingPixelMap, deathTime, targetedAs, acquisitionRange, minimumAttackRange, structuresBuilt, unitsTrained, - researchesAvailable, unitRace, goldCost, lumberCost, buildTime); + researchesAvailable, unitRace, goldCost, lumberCost, buildTime, preventedPathingTypes, + requiredPathingTypes); this.unitIdToUnitType.put(typeId, unitTypeInstance); } return unitTypeInstance; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetPoint.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetPoint.java index 462a3aa..3db81ce 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetPoint.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetPoint.java @@ -4,15 +4,16 @@ import com.badlogic.gdx.math.Vector2; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.StringMsgTargetCheckReceiver; public class COrderTargetPoint implements COrder { private final int abilityHandleId; private final int orderId; - private final Vector2 target; + private final AbilityPointTarget target; - public COrderTargetPoint(final int abilityHandleId, final int orderId, final Vector2 target) { + public COrderTargetPoint(final int abilityHandleId, final int orderId, final AbilityPointTarget target) { this.abilityHandleId = abilityHandleId; this.orderId = orderId; this.target = target; @@ -37,7 +38,7 @@ public class COrderTargetPoint implements COrder { final CAbility ability = game.getAbility(this.abilityHandleId); ability.checkCanUse(game, caster, this.orderId, this.abilityActivationReceiver.reset()); if (this.abilityActivationReceiver.isUseOk()) { - final StringMsgTargetCheckReceiver targetReceiver = (StringMsgTargetCheckReceiver) targetCheckReceiver; + final StringMsgTargetCheckReceiver targetReceiver = (StringMsgTargetCheckReceiver) targetCheckReceiver; ability.checkCanTarget(game, caster, this.orderId, this.target, targetReceiver); if (targetReceiver.getTarget() != null) { return ability.begin(game, caster, this.orderId, this.target); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/pathing/CBuildingPathingType.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/pathing/CBuildingPathingType.java new file mode 100644 index 0000000..b64c72e --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/pathing/CBuildingPathingType.java @@ -0,0 +1,30 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.pathing; + +import java.util.EnumSet; + +public enum CBuildingPathingType { + BLIGHTED, + UNBUILDABLE, + UNFLYABLE, + UNWALKABLE, + UNAMPH, + UNFLOAT; + + public static CBuildingPathingType parsePathingType(final String typeString) { + if ("_".equals(typeString) || "".equals(typeString)) { + return null; + } + return valueOf(typeString.toUpperCase()); + } + + public static EnumSet parsePathingTypeListSet(final String pathingListString) { + final EnumSet types = EnumSet.noneOf(CBuildingPathingType.class); + for (final String type : pathingListString.split(",")) { + final CBuildingPathingType parsedType = parsePathingType(type); + if (parsedType != null) { + types.add(parsedType); + } + } + return types; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderExecutor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderExecutor.java index c51c6f3..a68fc81 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderExecutor.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderExecutor.java @@ -1,8 +1,8 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.players; -import com.badlogic.gdx.math.Vector2; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.COrderNoTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.COrderTargetPoint; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.COrderTargetWidget; @@ -28,7 +28,7 @@ public class CPlayerUnitOrderExecutor implements CPlayerUnitOrderListener { public void issuePointOrder(final int unitHandleId, final int abilityHandleId, final int orderId, final float x, final float y, final boolean queue) { final CUnit unit = this.game.getUnit(unitHandleId); - unit.order(this.game, new COrderTargetPoint(abilityHandleId, orderId, new Vector2(x, y)), queue); + unit.order(this.game, new COrderTargetPoint(abilityHandleId, orderId, new AbilityPointTarget(x, y)), queue); } @Override @@ -38,4 +38,10 @@ public class CPlayerUnitOrderExecutor implements CPlayerUnitOrderListener { unit.order(this.game, new COrderNoTarget(abilityHandleId, orderId), queue); } + @Override + public void unitCancelTrainingItem(final int unitHandleId, final int cancelIndex) { + final CUnit unit = this.game.getUnit(unitHandleId); + unit.cancelBuildQueueItem(this.game, cancelIndex); + } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderListener.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderListener.java index e9bf4cd..187dc11 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderListener.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderListener.java @@ -9,4 +9,6 @@ public interface CPlayerUnitOrderListener { // boolean issueTargetAndPointOrder(int unitHandleId, int orderId, int targetHandleId, float x, float y); void issueImmediateOrder(int unitHandleId, int abilityHandleId, int orderId, boolean queue); + + void unitCancelTrainingItem(int unitHandleId, int cancelIndex); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/AbilityActivationReceiver.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/AbilityActivationReceiver.java index d248b69..ff76f71 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/AbilityActivationReceiver.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/AbilityActivationReceiver.java @@ -12,4 +12,6 @@ public interface AbilityActivationReceiver { void casterMovementDisabled(); void cargoCapacityUnavailable(); + + void disabled(); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/BooleanAbilityActivationReceiver.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/BooleanAbilityActivationReceiver.java index bfafcc3..aaec289 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/BooleanAbilityActivationReceiver.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/BooleanAbilityActivationReceiver.java @@ -34,6 +34,11 @@ public class BooleanAbilityActivationReceiver implements AbilityActivationReceiv this.ok = false; } + @Override + public void disabled() { + this.ok = false; + } + public boolean isOk() { return this.ok; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/PointAbilityTargetCheckReceiver.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/PointAbilityTargetCheckReceiver.java index ad86eb7..e49f53c 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/PointAbilityTargetCheckReceiver.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/PointAbilityTargetCheckReceiver.java @@ -1,14 +1,14 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.util; -import com.badlogic.gdx.math.Vector2; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; -public class PointAbilityTargetCheckReceiver implements AbilityTargetCheckReceiver { +public class PointAbilityTargetCheckReceiver implements AbilityTargetCheckReceiver { public static final PointAbilityTargetCheckReceiver INSTANCE = new PointAbilityTargetCheckReceiver(); - private Vector2 target; + private AbilityPointTarget target; @Override - public void targetOk(final Vector2 target) { + public void targetOk(final AbilityPointTarget target) { this.target = target; } @@ -52,7 +52,7 @@ public class PointAbilityTargetCheckReceiver implements AbilityTargetCheckReceiv this.target = null; } - public Vector2 getTarget() { + public AbilityPointTarget getTarget() { return this.target; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java index 876a1a2..1b6690a 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java @@ -6,13 +6,14 @@ import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackInstant; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackMissile; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.projectile.CAttackProjectile; public interface SimulationRenderController { CAttackProjectile createAttackProjectile(CSimulation simulation, float launchX, float launchY, float launchFacing, - CUnit source, CUnitAttackMissile attack, CWidget target, float damage, int bounceIndex); + CUnit source, CUnitAttackMissile attack, AbilityTarget target, float damage, int bounceIndex); CUnit createUnit(CSimulation simulation, final War3ID typeId, final int playerIndex, final float x, final float y, final float facing); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/StringMsgAbilityActivationReceiver.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/StringMsgAbilityActivationReceiver.java index 90b2208..9e8b8d9 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/StringMsgAbilityActivationReceiver.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/StringMsgAbilityActivationReceiver.java @@ -54,4 +54,9 @@ public class StringMsgAbilityActivationReceiver implements AbilityActivationRece this.message = "NOTEXTERN: Caster movement disabled."; } + @Override + public void disabled() { + this.message = "NOTEXTERN: Ability is disabled."; + } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java index f981edf..9efcd09 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java @@ -13,9 +13,10 @@ import com.etheller.warsmash.parsers.fdf.frames.TextureFrame; import com.etheller.warsmash.parsers.fdf.frames.UIFrame; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.commandbuttons.CommandButton; +import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.ClickableActionFrame; import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.CommandCardCommandListener; -public class CommandCardIcon extends AbstractRenderableFrame { +public class CommandCardIcon extends AbstractRenderableFrame implements ClickableActionFrame { private TextureFrame iconFrame; private TextureFrame activeHighlightFrame; @@ -116,7 +117,9 @@ public class CommandCardIcon extends AbstractRenderableFrame { @Override public UIFrame touchDown(final float screenX, final float screenY, final int button) { if (isVisible() && this.renderBounds.contains(screenX, screenY)) { - return this; + if (this.orderId != 0) { + return this; + } } return super.touchDown(screenX, screenY, button); } @@ -133,6 +136,7 @@ public class CommandCardIcon extends AbstractRenderableFrame { return this.menuButton; } + @Override public void onClick(final int button) { if (button == Input.Buttons.LEFT) { if (this.menuButton) { @@ -147,12 +151,14 @@ public class CommandCardIcon extends AbstractRenderableFrame { } } + @Override public void mouseDown(final GameUI gameUI, final Viewport uiViewport) { this.iconFrame.setWidth(GameUI.convertX(uiViewport, MeleeUI.DEFAULT_COMMAND_CARD_ICON_PRESSED_WIDTH)); this.iconFrame.setHeight(GameUI.convertY(uiViewport, MeleeUI.DEFAULT_COMMAND_CARD_ICON_PRESSED_WIDTH)); positionBounds(gameUI, uiViewport); } + @Override public void mouseUp(final GameUI gameUI, final Viewport uiViewport) { this.iconFrame.setWidth(GameUI.convertX(uiViewport, MeleeUI.DEFAULT_COMMAND_CARD_ICON_WIDTH)); this.iconFrame.setHeight(GameUI.convertY(uiViewport, MeleeUI.DEFAULT_COMMAND_CARD_ICON_WIDTH)); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java index 666b7fb..61c9ce2 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java @@ -3,6 +3,7 @@ package com.etheller.warsmash.viewer5.handlers.w3x.ui; import java.awt.image.BufferedImage; import java.io.IOException; import java.util.ArrayList; +import java.util.EnumSet; import java.util.List; import javax.imageio.ImageIO; @@ -25,6 +26,7 @@ import com.etheller.warsmash.parsers.fdf.datamodel.AnchorDefinition; import com.etheller.warsmash.parsers.fdf.datamodel.FramePoint; import com.etheller.warsmash.parsers.fdf.datamodel.TextJustify; import com.etheller.warsmash.parsers.fdf.datamodel.Vector4Definition; +import com.etheller.warsmash.parsers.fdf.frames.FilterModeTextureFrame; import com.etheller.warsmash.parsers.fdf.frames.SetPoint; import com.etheller.warsmash.parsers.fdf.frames.SimpleFrame; import com.etheller.warsmash.parsers.fdf.frames.SimpleStatusBarFrame; @@ -41,13 +43,18 @@ import com.etheller.warsmash.util.RenderMathUtils; import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.util.WarsmashConstants; import com.etheller.warsmash.viewer5.Scene; +import com.etheller.warsmash.viewer5.handlers.mdx.Attachment; import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; +import com.etheller.warsmash.viewer5.handlers.mdx.MdxNode; import com.etheller.warsmash.viewer5.handlers.mdx.ReplaceableIds; import com.etheller.warsmash.viewer5.handlers.mdx.SequenceLoopMode; import com.etheller.warsmash.viewer5.handlers.tga.TgaFile; +import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; +import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.SecondaryTag; import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; +import com.etheller.warsmash.viewer5.handlers.w3x.SplatModel.SplatMover; import com.etheller.warsmash.viewer5.handlers.w3x.UnitSound; import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; import com.etheller.warsmash.viewer5.handlers.w3x.camera.CameraPreset; @@ -58,6 +65,8 @@ import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderUnit; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.AbilityDataUI; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.IconUI; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.commandbuttons.CommandButtonListener; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CDestructable; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CItem; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit.QueueItemType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitClassification; @@ -67,12 +76,30 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityAttack; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityGeneric; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityMove; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityView; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.AbstractCAbilityBuild; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityBuildInProgress; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityHumanBuild; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityNagaBuild; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityNeutralBuild; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityNightElfBuild; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityOrcBuild; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityUndeadBuild; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.combat.CAbilityColdArrows; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic.GenericSingleIconActiveAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityQueue; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityRally; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTargetVisitor; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CDefenseType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CodeKeyType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackMissileSplash; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayerUnitOrderListener; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.BooleanAbilityActivationReceiver; @@ -80,9 +107,12 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.BooleanAbility import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.CWidgetAbilityTargetCheckReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.PointAbilityTargetCheckReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.StringMsgAbilityActivationReceiver; +import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.ClickableActionFrame; import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.CommandCardCommandListener; +import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.QueueIconListener; -public class MeleeUI implements CUnitStateListener, CommandButtonListener, CommandCardCommandListener { +public class MeleeUI + implements CUnitStateListener, CommandButtonListener, CommandCardCommandListener, QueueIconListener { public static final float DEFAULT_COMMAND_CARD_ICON_WIDTH = 0.039f; public static final float DEFAULT_COMMAND_CARD_ICON_PRESSED_WIDTH = 0.037f; private static final int COMMAND_CARD_WIDTH = 4; @@ -90,7 +120,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma private static final Vector2 screenCoordsVector = new Vector2(); private static final Vector3 clickLocationTemp = new Vector3(); - private static final Vector2 clickLocationTemp2 = new Vector2(); + private static final AbilityPointTarget clickLocationTemp2 = new AbilityPointTarget(); private final DataSource dataSource; private final ExtendViewport uiViewport; private final FreeTypeFontGenerator fontGenerator; @@ -126,7 +156,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma private StringFrame simpleBuildingDescriptionValue; private StringFrame simpleBuildingBuildingActionLabel; private SimpleStatusBarFrame simpleBuildingBuildTimeIndicator; - private final TextureFrame[] queueIconFrames = new TextureFrame[WarsmashConstants.BUILD_QUEUE_SIZE]; + private final QueueIcon[] queueIconFrames = new QueueIcon[WarsmashConstants.BUILD_QUEUE_SIZE]; private UIFrame attack1Icon; private TextureFrame attack1IconBackdrop; @@ -160,7 +190,10 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma private int activeCommandOrderId; private RenderUnit activeCommandUnit; private MdxComplexInstance cursorModelInstance = null; + private MdxComplexInstance rallyPointInstance = null; private BufferedImage cursorModelPathing; + private SplatMover placementCursor = null; + private final CursorTargetSetupVisitor cursorTargetSetupVisitor; private int selectedSoundCount = 0; private final ActiveCommandUnitTargetFilter activeCommandUnitTargetFilter; @@ -169,11 +202,12 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma // probably remove them later private final float widthRatioCorrection; private final float heightRatioCorrection; - private CommandCardIcon mouseDownUIFrame; + private ClickableActionFrame mouseDownUIFrame; private UIFrame smashSimpleInfoPanel; private SimpleFrame smashAttack1IconWrapper; private SimpleFrame smashAttack2IconWrapper; private SimpleFrame smashArmorIconWrapper; + private final RallyPositioningVisitor rallyPositioningVisitor; public MeleeUI(final DataSource dataSource, final ExtendViewport uiViewport, final FreeTypeFontGenerator fontGenerator, final Scene uiScene, final Scene portraitScene, @@ -201,7 +235,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.activeCommandUnitTargetFilter = new ActiveCommandUnitTargetFilter(); this.widthRatioCorrection = this.uiViewport.getMinWorldWidth() / 1600f; this.heightRatioCorrection = this.uiViewport.getMinWorldHeight() / 1200f; - + this.rallyPositioningVisitor = new RallyPositioningVisitor(); + this.cursorTargetSetupVisitor = new CursorTargetSetupVisitor(); } private MeleeUIMinimap createMinimap(final War3MapViewer war3MapViewer) { @@ -246,7 +281,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma // ================================= // Load skins and templates // ================================= - this.rootFrame = new GameUI(this.dataSource, GameUI.loadSkin(this.dataSource, 0), this.uiViewport, + this.rootFrame = new GameUI(this.dataSource, GameUI.loadSkin(this.dataSource, 2), this.uiViewport, this.fontGenerator, this.uiScene, this.war3MapViewer); this.rootFrameListener.onCreate(this.rootFrame); try { @@ -346,20 +381,38 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma simpleBuildQueueBackdrop.setWidth(infoPanelUnitDetailWidth); simpleBuildQueueBackdrop.setHeight(infoPanelUnitDetailWidth * 0.5f); - this.queueIconFrames[0] = this.rootFrame.createTextureFrame("SmashBuildQueueIcon0", this.smashSimpleInfoPanel, - false, new Vector4Definition(0, 1, 0, 1)); - this.queueIconFrames[0].addAnchor(new AnchorDefinition(FramePoint.BOTTOMLEFT, - (infoPanelUnitDetailWidth * 15) / 256, (infoPanelUnitDetailWidth * 66) / 256)); - this.queueIconFrames[0].setWidth((infoPanelUnitDetailWidth * 38) / 256); - this.queueIconFrames[0].setHeight((infoPanelUnitDetailWidth * 38) / 256); + this.queueIconFrames[0] = new QueueIcon("SmashBuildQueueIcon0", this.smashSimpleInfoPanel, this, 0); + final TextureFrame queueIconFrameBackdrop0 = new TextureFrame("SmashBuildQueueIcon0Backdrop", + this.queueIconFrames[0], false, new Vector4Definition(0, 1, 0, 1)); + queueIconFrameBackdrop0 + .addSetPoint(new SetPoint(FramePoint.CENTER, this.queueIconFrames[0], FramePoint.CENTER, 0, 0)); + this.queueIconFrames[0].set(queueIconFrameBackdrop0); + this.queueIconFrames[0] + .addSetPoint(new SetPoint(FramePoint.CENTER, this.smashSimpleInfoPanel, FramePoint.BOTTOMLEFT, + (infoPanelUnitDetailWidth * (15 + 19f)) / 256, (infoPanelUnitDetailWidth * (66 + 19f)) / 256)); + final float frontQueueIconWidth = (infoPanelUnitDetailWidth * 38) / 256; + this.queueIconFrames[0].setWidth(frontQueueIconWidth); + this.queueIconFrames[0].setHeight(frontQueueIconWidth); + queueIconFrameBackdrop0.setWidth(frontQueueIconWidth); + queueIconFrameBackdrop0.setHeight(frontQueueIconWidth); + this.rootFrame.add(this.queueIconFrames[0]); for (int i = 1; i < this.queueIconFrames.length; i++) { - this.queueIconFrames[i] = this.rootFrame.createTextureFrame("SmashBuildQueueIcon" + i, - this.smashSimpleInfoPanel, false, new Vector4Definition(0, 1, 0, 1)); - this.queueIconFrames[i].addAnchor(new AnchorDefinition(FramePoint.BOTTOMLEFT, - (infoPanelUnitDetailWidth * (13 + (40 * (i - 1)))) / 256, (infoPanelUnitDetailWidth * 24) / 256)); - this.queueIconFrames[i].setWidth((infoPanelUnitDetailWidth * 29) / 256); - this.queueIconFrames[i].setHeight((infoPanelUnitDetailWidth * 29) / 256); + this.queueIconFrames[i] = new QueueIcon("SmashBuildQueueIcon" + i, this.smashSimpleInfoPanel, this, i); + final TextureFrame queueIconFrameBackdrop = new TextureFrame("SmashBuildQueueIcon" + i + "Backdrop", + this.queueIconFrames[i], false, new Vector4Definition(0, 1, 0, 1)); + this.queueIconFrames[i].set(queueIconFrameBackdrop); + queueIconFrameBackdrop + .addSetPoint(new SetPoint(FramePoint.CENTER, this.queueIconFrames[i], FramePoint.CENTER, 0, 0)); + this.queueIconFrames[i].addSetPoint(new SetPoint(FramePoint.CENTER, this.smashSimpleInfoPanel, + FramePoint.BOTTOMLEFT, (infoPanelUnitDetailWidth * (13 + 14.5f + (40 * (i - 1)))) / 256, + (infoPanelUnitDetailWidth * (24 + 14.5f)) / 256)); + final float queueIconWidth = (infoPanelUnitDetailWidth * 29) / 256; + this.queueIconFrames[i].setWidth(queueIconWidth); + this.queueIconFrames[i].setHeight(queueIconWidth); + queueIconFrameBackdrop.setWidth(queueIconWidth); + queueIconFrameBackdrop.setHeight(queueIconWidth); + this.rootFrame.add(this.queueIconFrames[i]); } this.smashAttack1IconWrapper = (SimpleFrame) this.rootFrame.createSimpleFrame("SmashSimpleInfoPanelIconDamage", @@ -412,11 +465,11 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma final CommandCardIcon commandCardIcon = new CommandCardIcon("SmashCommandButton_" + commandButtonIndex, this.rootFrame, this); this.rootFrame.add(commandCardIcon); - final TextureFrame iconFrame = this.rootFrame.createTextureFrame( - "SmashCommandButton_" + (commandButtonIndex) + "_Icon", this.rootFrame, false, null); - final TextureFrame activeHighlightFrame = this.rootFrame.createTextureFrame( - "SmashCommandButton_" + (commandButtonIndex) + "_ActiveHighlight", this.rootFrame, true, null, - FilterMode.ADDALPHA); + final TextureFrame iconFrame = new TextureFrame("SmashCommandButton_" + (commandButtonIndex) + "_Icon", + this.rootFrame, false, null); + final FilterModeTextureFrame activeHighlightFrame = new FilterModeTextureFrame( + "SmashCommandButton_" + (commandButtonIndex) + "_ActiveHighlight", this.rootFrame, true, null); + activeHighlightFrame.setFilterMode(FilterMode.ADDALPHA); final SpriteFrame cooldownFrame = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", "SmashCommandButton_" + (commandButtonIndex) + "_Cooldown", this.rootFrame, "", 0); final SpriteFrame autocastFrame = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", @@ -459,7 +512,18 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.meleeUIMinimap = createMinimap(this.war3MapViewer); + final MdxModel rallyModel = (MdxModel) this.war3MapViewer.load( + War3MapViewer.mdx(this.rootFrame.getSkinField("RallyIndicatorDst")), this.war3MapViewer.mapPathSolver, + this.war3MapViewer.solverParams); + this.rallyPointInstance = (MdxComplexInstance) rallyModel.addInstance(); + this.rallyPointInstance.rotate(RenderUnit.tempQuat.setFromAxis(RenderMathUtils.VEC3_UNIT_Z, + this.war3MapViewer.simulation.getGameplayConstants().getBuildingAngle())); + this.rallyPointInstance.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + SequenceUtils.randomStandSequence(this.rallyPointInstance); + this.rallyPointInstance.hide(); + this.rootFrame.positionBounds(this.rootFrame, this.uiViewport); + selectUnit(null); } @@ -469,6 +533,9 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma if (this.selectedUnit == null) { return; } + if (orderId == 0) { + return; + } CAbilityView abilityToUse = null; for (final CAbility ability : this.selectedUnit.getSimulationUnit().getAbilities()) { if (ability.getHandleId() == abilityHandleId) { @@ -557,48 +624,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.cursorFrame.setFramePointY(FramePoint.BOTTOM, screenCoordsVector.y); if (this.activeCommand != null) { - if (this.activeCommand instanceof AbstractCAbilityBuild) { - boolean justLoaded = false; - if (this.cursorModelInstance == null) { - final MutableObjectData unitData = this.war3MapViewer.getAllObjectData().getUnits(); - final War3ID buildingTypeId = new War3ID(this.activeCommandOrderId); - final String unitModelPath = this.war3MapViewer.getUnitModelPath(unitData.get(buildingTypeId)); - final MdxModel model = (MdxModel) this.war3MapViewer.load(unitModelPath, - this.war3MapViewer.mapPathSolver, this.war3MapViewer.solverParams); - this.cursorModelInstance = (MdxComplexInstance) model.addInstance(); - this.cursorModelInstance.setVertexColor(new float[] { 1, 1, 1, 0.5f }); - this.cursorModelInstance.setTeamColor(this.activeCommandUnit.getSimulationUnit().getPlayerIndex()); - this.cursorModelInstance.rotate(RenderUnit.tempQuat.setFromAxis(RenderMathUtils.VEC3_UNIT_Z, - this.war3MapViewer.simulation.getGameplayConstants().getBuildingAngle())); - this.cursorModelInstance.setAnimationSpeed(0f); - justLoaded = true; - final CUnitType buildingUnitType = this.war3MapViewer.simulation.getUnitData() - .getUnitType(buildingTypeId); - this.cursorModelPathing = buildingUnitType.getBuildingPathingPixelMap(); - } - this.war3MapViewer.getClickLocation(clickLocationTemp, baseMouseX, - Gdx.graphics.getHeight() - baseMouseY); - if (this.cursorModelPathing != null) { - clickLocationTemp.x = Math.round(clickLocationTemp.x / 64f) * 64f; - clickLocationTemp.y = Math.round(clickLocationTemp.y / 64f) * 64f; - clickLocationTemp.z = this.war3MapViewer.terrain.getGroundHeight(clickLocationTemp.x, - clickLocationTemp.y); - } - this.cursorModelInstance.setLocation(clickLocationTemp); - SequenceUtils.randomSequence(this.cursorModelInstance, PrimaryTag.STAND); - this.cursorFrame.setVisible(false); - if (justLoaded) { - this.cursorModelInstance.setScene(this.war3MapViewer.worldScene); - } - } - else { - if (this.cursorModelInstance != null) { - this.cursorModelInstance.detach(); - this.cursorModelInstance = null; - this.cursorFrame.setVisible(true); - } - this.cursorFrame.setSequence("Target"); - } + this.activeCommand.visit(this.cursorTargetSetupVisitor.reset(baseMouseX, baseMouseY)); } else { if (this.cursorModelInstance != null) { @@ -606,6 +632,11 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.cursorModelInstance = null; this.cursorFrame.setVisible(true); } + else if (this.placementCursor != null) { + this.placementCursor.destroy(Gdx.gl30, this.war3MapViewer.terrain.centerOffset); + this.placementCursor = null; + this.cursorFrame.setVisible(true); + } if (down) { if (left) { this.cursorFrame.setSequence("Scroll Down Left"); @@ -638,17 +669,19 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.cursorFrame.setSequence("Normal"); } } - if (this.simpleBuildTimeIndicator.isVisible() && (this.selectedUnit != null)) { - this.simpleBuildTimeIndicator - .setValue(Math.min(this.selectedUnit.getSimulationUnit().getConstructionProgress() - / this.selectedUnit.getSimulationUnit().getUnitType().getBuildTime(), 0.99f)); - } - if (this.simpleBuildingBuildTimeIndicator.isVisible() && (this.selectedUnit != null)) { - this.simpleBuildingBuildTimeIndicator - .setValue(Math.min( - this.selectedUnit.getSimulationUnit().getConstructionProgress() / this.selectedUnit - .getSimulationUnit().getBuildQueueTimeRemaining(this.war3MapViewer.simulation), - 0.99f)); + if (this.selectedUnit != null) { + if (this.simpleBuildTimeIndicator.isVisible()) { + this.simpleBuildTimeIndicator + .setValue(Math.min(this.selectedUnit.getSimulationUnit().getConstructionProgress() + / this.selectedUnit.getSimulationUnit().getUnitType().getBuildTime(), 0.99f)); + } + if (this.simpleBuildingBuildTimeIndicator.isVisible()) { + this.simpleBuildingBuildTimeIndicator + .setValue(Math.min( + this.selectedUnit.getSimulationUnit().getConstructionProgress() / this.selectedUnit + .getSimulationUnit().getBuildQueueTimeRemaining(this.war3MapViewer.simulation), + 0.99f)); + } } final float groundHeight = Math.max( @@ -674,6 +707,240 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.portrait.talk(); } + private final class CursorTargetSetupVisitor implements CAbilityVisitor { + private int baseMouseX; + private int baseMouseY; + + private CursorTargetSetupVisitor reset(final int baseMouseX, final int baseMouseY) { + this.baseMouseX = baseMouseX; + this.baseMouseY = baseMouseY; + return this; + } + + @Override + public Void accept(final CAbilityAttack ability) { + if (MeleeUI.this.activeCommandOrderId == OrderIds.attackground) { + float radius = 0; + for (final CUnitAttack attack : MeleeUI.this.activeCommandUnit.getSimulationUnit().getUnitType() + .getAttacks()) { + if (attack.getWeaponType().isAttackGroundSupported()) { + if (attack instanceof CUnitAttackMissileSplash) { + final int areaOfEffectSmallDamage = ((CUnitAttackMissileSplash) attack) + .getAreaOfEffectSmallDamage(); + radius = areaOfEffectSmallDamage; + } + } + } + handlePlacementCursor(ability, radius); + } + else { + handleTargetCursor(ability); + } + return null; + } + + @Override + public Void accept(final CAbilityMove ability) { + handleTargetCursor(ability); + return null; + } + + @Override + public Void accept(final CAbilityOrcBuild ability) { + handleBuildCursor(ability); + return null; + } + + @Override + public Void accept(final CAbilityHumanBuild ability) { + handleBuildCursor(ability); + return null; + } + + @Override + public Void accept(final CAbilityUndeadBuild ability) { + handleBuildCursor(ability); + return null; + } + + @Override + public Void accept(final CAbilityNightElfBuild ability) { + handleBuildCursor(ability); + return null; + } + + @Override + public Void accept(final CAbilityGeneric ability) { + handleTargetCursor(ability); + return null; + } + + @Override + public Void accept(final CAbilityColdArrows ability) { + handleTargetCursor(ability); + return null; + } + + @Override + public Void accept(final CAbilityNagaBuild ability) { + handleBuildCursor(ability); + return null; + } + + @Override + public Void accept(final CAbilityNeutralBuild ability) { + handleBuildCursor(ability); + return null; + } + + @Override + public Void accept(final CAbilityBuildInProgress ability) { + handleTargetCursor(ability); + return null; + } + + @Override + public Void accept(final CAbilityQueue ability) { + handleTargetCursor(ability); + return null; + } + + @Override + public Void accept(final GenericSingleIconActiveAbility ability) { + handleTargetCursor(ability); + return null; + } + + @Override + public Void accept(final CAbilityRally ability) { + handleTargetCursor(ability); + return null; + } + + private void handleTargetCursor(final CAbility ability) { + if (MeleeUI.this.cursorModelInstance != null) { + MeleeUI.this.cursorModelInstance.detach(); + MeleeUI.this.cursorModelInstance = null; + MeleeUI.this.cursorFrame.setVisible(true); + } + MeleeUI.this.cursorFrame.setSequence("Target"); + } + + private void handleBuildCursor(final AbstractCAbilityBuild ability) { + boolean justLoaded = false; + final War3MapViewer viewer = MeleeUI.this.war3MapViewer; + if (MeleeUI.this.cursorModelInstance == null) { + final MutableObjectData unitData = viewer.getAllObjectData().getUnits(); + final War3ID buildingTypeId = new War3ID(MeleeUI.this.activeCommandOrderId); + final String unitModelPath = viewer.getUnitModelPath(unitData.get(buildingTypeId)); + final MdxModel model = (MdxModel) viewer.load(unitModelPath, viewer.mapPathSolver, viewer.solverParams); + MeleeUI.this.cursorModelInstance = (MdxComplexInstance) model.addInstance(); +// MeleeUI.this.cursorModelInstance.setVertexColor(new float[] { 1, 1, 1, 0.5f }); + final int playerColorIndex = viewer.simulation + .getPlayer(MeleeUI.this.activeCommandUnit.getSimulationUnit().getPlayerIndex()).getColorIndex(); + MeleeUI.this.cursorModelInstance.setTeamColor(playerColorIndex); + MeleeUI.this.cursorModelInstance.rotate(RenderUnit.tempQuat.setFromAxis(RenderMathUtils.VEC3_UNIT_Z, + viewer.simulation.getGameplayConstants().getBuildingAngle())); + MeleeUI.this.cursorModelInstance.setAnimationSpeed(0f); + justLoaded = true; + final CUnitType buildingUnitType = viewer.simulation.getUnitData().getUnitType(buildingTypeId); + MeleeUI.this.cursorModelPathing = buildingUnitType.getBuildingPathingPixelMap(); + } + viewer.getClickLocation(clickLocationTemp, this.baseMouseX, Gdx.graphics.getHeight() - this.baseMouseY); + if (MeleeUI.this.cursorModelPathing != null) { + clickLocationTemp.x = Math.round(clickLocationTemp.x / 64f) * 64f; + clickLocationTemp.y = Math.round(clickLocationTemp.y / 64f) * 64f; + clickLocationTemp.z = viewer.terrain.getGroundHeight(clickLocationTemp.x, clickLocationTemp.y); + } + MeleeUI.this.cursorModelInstance.setLocation(clickLocationTemp); + SequenceUtils.randomSequence(MeleeUI.this.cursorModelInstance, PrimaryTag.STAND); + MeleeUI.this.cursorFrame.setVisible(false); + if (justLoaded) { + MeleeUI.this.cursorModelInstance.setScene(viewer.worldScene); + } + } + + private void handlePlacementCursor(final CAbility ability, final float radius) { + final War3MapViewer viewer = MeleeUI.this.war3MapViewer; + viewer.getClickLocation(clickLocationTemp, this.baseMouseX, Gdx.graphics.getHeight() - this.baseMouseY); + if (MeleeUI.this.placementCursor == null) { + MeleeUI.this.placementCursor = viewer.terrain.addUberSplat( + MeleeUI.this.rootFrame.getSkinField("PlacementCursor"), clickLocationTemp.x, + clickLocationTemp.y, 10, radius, true, true); + } + MeleeUI.this.placementCursor.setLocation(clickLocationTemp.x, clickLocationTemp.y, + viewer.terrain.centerOffset); + MeleeUI.this.cursorFrame.setVisible(false); + } + } + + private final class RallyPositioningVisitor implements AbilityTargetVisitor { + @Override + public Void accept(final AbilityPointTarget target) { + MeleeUI.this.rallyPointInstance.setParent(null); + final float rallyPointX = target.getX(); + final float rallyPointY = target.getY(); + MeleeUI.this.rallyPointInstance.setLocation(rallyPointX, rallyPointY, + MeleeUI.this.war3MapViewer.terrain.getGroundHeight(rallyPointX, rallyPointY)); + return null; + } + + @Override + public Void accept(final CUnit target) { + final RenderUnit renderUnit = MeleeUI.this.war3MapViewer.getRenderPeer(target); + final MdxModel model = (MdxModel) renderUnit.instance.model; + int index = -1; + for (int i = 0; i < model.attachments.size(); i++) { + final Attachment attachment = model.attachments.get(i); + if (attachment.getName().startsWith("sprite first ref")) { + index = i; + break; + } + } + if (index == -1) { + for (int i = 0; i < model.attachments.size(); i++) { + final Attachment attachment = model.attachments.get(i); + if (attachment.getName().startsWith("overhead ref")) { + index = i; + } + } + } + if (index != -1) { + final MdxNode attachment = renderUnit.instance.getAttachment(index); + MeleeUI.this.rallyPointInstance.setParent(attachment); + MeleeUI.this.rallyPointInstance.setLocation(0, 0, 0); + } + else { + MeleeUI.this.rallyPointInstance.setParent(null); + final float rallyPointX = target.getX(); + final float rallyPointY = target.getY(); + MeleeUI.this.rallyPointInstance.setLocation(rallyPointX, rallyPointY, + MeleeUI.this.war3MapViewer.terrain.getGroundHeight(rallyPointX, rallyPointY)); + } + return null; + } + + @Override + public Void accept(final CDestructable target) { + MeleeUI.this.rallyPointInstance.setParent(null); + final float rallyPointX = target.getX(); + final float rallyPointY = target.getY(); + MeleeUI.this.rallyPointInstance.setLocation(rallyPointX, rallyPointY, + MeleeUI.this.war3MapViewer.terrain.getGroundHeight(rallyPointX, rallyPointY)); + return null; + } + + @Override + public Void accept(final CItem target) { + MeleeUI.this.rallyPointInstance.setParent(null); + final float rallyPointX = target.getX(); + final float rallyPointY = target.getY(); + MeleeUI.this.rallyPointInstance.setLocation(rallyPointX, rallyPointY, + MeleeUI.this.war3MapViewer.terrain.getGroundHeight(rallyPointX, rallyPointY)); + return null; + } + } + private final class ActiveCommandUnitTargetFilter implements CUnitFilterFunction { @Override public boolean call(final CUnit unit) { @@ -690,6 +957,9 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma private MdxComplexInstance modelInstance; private final PortraitCameraManager portraitCameraManager; private final Scene portraitScene; + private final EnumSet recycleSet = EnumSet + .noneOf(AnimationTokens.SecondaryTag.class); + private RenderUnit unit; public Portrait(final War3MapViewer war3MapViewer, final Scene portraitScene) { this.portraitScene = portraitScene; @@ -702,15 +972,21 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.portraitCameraManager.updateCamera(); if ((this.modelInstance != null) && (this.modelInstance.sequenceEnded || (this.modelInstance.sequence == -1))) { - SequenceUtils.randomSequence(this.modelInstance, PrimaryTag.PORTRAIT, SequenceUtils.EMPTY, true); + this.recycleSet.clear(); + this.recycleSet.addAll(this.unit.getSecondaryAnimationTags()); + SequenceUtils.randomSequence(this.modelInstance, PrimaryTag.PORTRAIT, this.recycleSet, true); } } public void talk() { - SequenceUtils.randomSequence(this.modelInstance, PrimaryTag.PORTRAIT, SequenceUtils.TALK, true); + this.recycleSet.clear(); + this.recycleSet.addAll(this.unit.getSecondaryAnimationTags()); + this.recycleSet.add(SecondaryTag.TALK); + SequenceUtils.randomSequence(this.modelInstance, PrimaryTag.PORTRAIT, this.recycleSet, true); } public void setSelectedUnit(final RenderUnit unit) { + this.unit = unit; if (unit == null) { if (this.modelInstance != null) { this.portraitScene.removeInstance(this.modelInstance); @@ -764,9 +1040,11 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.simpleBuildingBuildTimeIndicator.setVisible(false); this.simpleInfoPanelBuildingDetail.setVisible(false); this.simpleInfoPanelUnitDetail.setVisible(false); - for (final TextureFrame queueIconFrame : this.queueIconFrames) { + for (final QueueIcon queueIconFrame : this.queueIconFrames) { queueIconFrame.setVisible(false); } + this.rallyPointInstance.hide(); + this.rallyPointInstance.detach(); } else { unit.getSimulationUnit().addStateListener(this); @@ -774,6 +1052,30 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } } + @Override + public void rallyPointChanged() { + if (this.selectedUnit != null) { + final CUnit simulationUnit = this.selectedUnit.getSimulationUnit(); + repositionRallyPoint(simulationUnit); + } + } + + private void repositionRallyPoint(final CUnit simulationUnit) { + final AbilityTarget rallyPoint = simulationUnit.getRallyPoint(); + if (rallyPoint != null) { + this.rallyPointInstance.setTeamColor( + this.war3MapViewer.simulation.getPlayer(simulationUnit.getPlayerIndex()).getColorIndex()); + this.rallyPointInstance.show(); + this.rallyPointInstance.detach(); + rallyPoint.visit(this.rallyPositioningVisitor); + this.rallyPointInstance.setScene(this.war3MapViewer.worldScene); + } + else { + this.rallyPointInstance.hide(); + this.rallyPointInstance.detach(); + } + } + private void reloadSelectedUnitUI(final RenderUnit unit) { final CUnit simulationUnit = unit.getSimulationUnit(); this.unitLifeText.setText( @@ -786,6 +1088,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma else { this.unitManaText.setText(""); } + repositionRallyPoint(simulationUnit); if (simulationUnit.getBuildQueue()[0] != null) { for (int i = 0; i < this.queueIconFrames.length; i++) { final QueueItemType queueItemType = simulationUnit.getBuildQueueTypes()[i]; @@ -829,7 +1132,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.armorIcon.setVisible(false); } else { - for (final TextureFrame queueIconFrame : this.queueIconFrames) { + for (final QueueIcon queueIconFrame : this.queueIconFrames) { queueIconFrame.setVisible(false); } this.simpleInfoPanelBuildingDetail.setVisible(false); @@ -934,9 +1237,9 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma y++; } } - if((x < COMMAND_CARD_WIDTH) && (y < COMMAND_CARD_HEIGHT)) { - this.commandCard[y][x].setCommandButtonData(icon, abilityHandleId, orderId, autoCastId, active, autoCastActive, - menuButton); + if ((x < COMMAND_CARD_WIDTH) && (y < COMMAND_CARD_HEIGHT)) { + this.commandCard[y][x].setCommandButtonData(icon, abilityHandleId, orderId, autoCastId, active, + autoCastActive, menuButton); } } @@ -1114,6 +1417,10 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma portraitTalk(); } this.selectedSoundCount = 0; + if (this.activeCommand instanceof CAbilityRally) { + this.war3MapViewer.getUiSounds().getSound("RallyPointPlace").play(this.uiScene.audioContext, + 0, 0); + } if (!shiftDown) { this.subMenuOrderIdStack.clear(); this.activeCommandUnit = null; @@ -1131,7 +1438,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma clickLocationTemp2, PointAbilityTargetCheckReceiver.INSTANCE); final Vector2 target = PointAbilityTargetCheckReceiver.INSTANCE.getTarget(); if (target != null) { - if (this.activeCommand instanceof CAbilityAttack) { + if ((this.activeCommand instanceof CAbilityAttack) + && (this.activeCommandOrderId == OrderIds.attack)) { this.war3MapViewer.showConfirmation(clickLocationTemp, 1, 0, 0); } else { @@ -1150,6 +1458,10 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.war3MapViewer.getUiSounds().getSound("PlaceBuildingDefault") .play(this.uiScene.audioContext, 0, 0); } + else if (this.activeCommand instanceof CAbilityRally) { + this.war3MapViewer.getUiSounds().getSound("RallyPointPlace") + .play(this.uiScene.audioContext, 0, 0); + } if (!shiftDown) { this.subMenuOrderIdStack.clear(); this.activeCommandUnit = null; @@ -1168,6 +1480,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma final RenderUnit rayPickUnit = this.war3MapViewer.rayPickUnit(screenX, worldScreenY); if ((rayPickUnit != null) && !rayPickUnit.getSimulationUnit().isDead()) { boolean ordered = false; + boolean rallied = false; for (final RenderUnit unit : this.war3MapViewer.selected) { for (final CAbility ability : unit.getSimulationUnit().getAbilities()) { ability.checkCanTarget(this.war3MapViewer.simulation, unit.getSimulationUnit(), @@ -1178,6 +1491,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.unitOrderListener.issueTargetOrder(unit.getSimulationUnit().getHandleId(), ability.getHandleId(), OrderIds.smart, targetWidget.getHandleId(), isShiftDown()); + rallied |= ability instanceof CAbilityRally; ordered = true; } } @@ -1188,6 +1502,10 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { portraitTalk(); } + if (rallied) { + this.war3MapViewer.getUiSounds().getSound("RallyPointPlace") + .play(this.uiScene.audioContext, 0, 0); + } this.selectedSoundCount = 0; } } @@ -1197,6 +1515,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma clickLocationTemp2.set(clickLocationTemp.x, clickLocationTemp.y); boolean ordered = false; + boolean rallied = false; for (final RenderUnit unit : this.war3MapViewer.selected) { for (final CAbility ability : unit.getSimulationUnit().getAbilities()) { ability.checkCanUse(this.war3MapViewer.simulation, unit.getSimulationUnit(), @@ -1211,6 +1530,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma unit.getSimulationUnit().getHandleId(), ability.getHandleId(), OrderIds.smart, clickLocationTemp2.x, clickLocationTemp2.y, isShiftDown()); + rallied |= ability instanceof CAbilityRally; ordered = true; } } @@ -1223,6 +1543,10 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { portraitTalk(); } + if (rallied) { + this.war3MapViewer.getUiSounds().getSound("RallyPointPlace") + .play(this.uiScene.audioContext, 0, 0); + } this.selectedSoundCount = 0; } } @@ -1278,8 +1602,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } } else { - if (clickedUIFrame instanceof CommandCardIcon) { - this.mouseDownUIFrame = (CommandCardIcon) clickedUIFrame; + if (clickedUIFrame instanceof ClickableActionFrame) { + this.mouseDownUIFrame = (ClickableActionFrame) clickedUIFrame; this.mouseDownUIFrame.mouseDown(this.rootFrame, this.uiViewport); } } @@ -1321,4 +1645,29 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma public float getHeightRatioCorrection() { return this.heightRatioCorrection; } + + @Override + public void queueIconClicked(final int index) { + final CUnit simulationUnit = this.selectedUnit.getSimulationUnit(); + if (simulationUnit.isConstructing()) { + for (final CAbility ability : simulationUnit.getAbilities()) { + ability.checkCanUse(this.war3MapViewer.simulation, simulationUnit, OrderIds.cancel, + BooleanAbilityActivationReceiver.INSTANCE); + if (BooleanAbilityActivationReceiver.INSTANCE.isOk()) { + + final BooleanAbilityTargetCheckReceiver targetCheckReceiver = BooleanAbilityTargetCheckReceiver + .getInstance().reset(); + ability.checkCanTargetNoTarget(this.war3MapViewer.simulation, simulationUnit, OrderIds.cancel, + targetCheckReceiver); + if (targetCheckReceiver.isTargetable()) { + this.unitOrderListener.issueImmediateOrder(simulationUnit.getHandleId(), ability.getHandleId(), + OrderIds.cancel, false); + } + } + } + } + else { + this.unitOrderListener.unitCancelTrainingItem(simulationUnit.getHandleId(), index); + } + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/QueueIcon.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/QueueIcon.java new file mode 100644 index 0000000..375e4a5 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/QueueIcon.java @@ -0,0 +1,99 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.ui; + +import com.badlogic.gdx.graphics.Texture; +import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.g2d.GlyphLayout; +import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.badlogic.gdx.utils.viewport.Viewport; +import com.etheller.warsmash.parsers.fdf.GameUI; +import com.etheller.warsmash.parsers.fdf.frames.AbstractRenderableFrame; +import com.etheller.warsmash.parsers.fdf.frames.TextureFrame; +import com.etheller.warsmash.parsers.fdf.frames.UIFrame; +import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.ClickableActionFrame; +import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.QueueIconListener; + +public class QueueIcon extends AbstractRenderableFrame implements ClickableActionFrame { + + private TextureFrame iconFrame; + private final QueueIconListener clickListener; + private float defaultWidth; + private float defaultHeight; + private final int queueIconIndexId; + + public QueueIcon(final String name, final UIFrame parent, final QueueIconListener clickListener, + final int queueIconIndexId) { + super(name, parent); + this.clickListener = clickListener; + this.queueIconIndexId = queueIconIndexId; + } + + public void set(final TextureFrame iconFrame) { + this.iconFrame = iconFrame; + setVisible(true); + } + + public void clear() { + setVisible(false); + } + + public void setTexture(final Texture texture) { + this.iconFrame.setTexture(texture); + } + + @Override + protected void innerPositionBounds(final GameUI gameUI, final Viewport viewport) { + this.iconFrame.positionBounds(gameUI, viewport); + } + + @Override + protected void internalRender(final SpriteBatch batch, final BitmapFont baseFont, final GlyphLayout glyphLayout) { + this.iconFrame.render(batch, baseFont, glyphLayout); + } + + @Override + public UIFrame touchDown(final float screenX, final float screenY, final int button) { + if (isVisible() && this.renderBounds.contains(screenX, screenY)) { + return this; + } + return super.touchDown(screenX, screenY, button); + } + + @Override + public UIFrame touchUp(final float screenX, final float screenY, final int button) { + if (isVisible() && this.renderBounds.contains(screenX, screenY)) { + return this; + } + return super.touchUp(screenX, screenY, button); + } + + @Override + public void onClick(final int button) { + this.clickListener.queueIconClicked(this.queueIconIndexId); + } + + @Override + public void setWidth(final float width) { + this.defaultWidth = width; + super.setWidth(width); + } + + @Override + public void setHeight(final float height) { + this.defaultHeight = height; + super.setHeight(height); + } + + @Override + public void mouseDown(final GameUI gameUI, final Viewport uiViewport) { + this.iconFrame.setWidth(this.defaultWidth * 0.95f); + this.iconFrame.setHeight(this.defaultHeight * 0.95f); + positionBounds(gameUI, uiViewport); + } + + @Override + public void mouseUp(final GameUI gameUI, final Viewport uiViewport) { + this.iconFrame.setWidth(this.defaultWidth); + this.iconFrame.setHeight(this.defaultHeight); + positionBounds(gameUI, uiViewport); + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/ClickableActionFrame.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/ClickableActionFrame.java new file mode 100644 index 0000000..aa1612b --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/ClickableActionFrame.java @@ -0,0 +1,13 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.ui.command; + +import com.badlogic.gdx.utils.viewport.Viewport; +import com.etheller.warsmash.parsers.fdf.GameUI; +import com.etheller.warsmash.parsers.fdf.frames.UIFrame; + +public interface ClickableActionFrame extends UIFrame { + void mouseDown(final GameUI gameUI, final Viewport uiViewport); + + void mouseUp(final GameUI gameUI, final Viewport uiViewport); + + void onClick(int button); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/QueueIconListener.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/QueueIconListener.java new file mode 100644 index 0000000..b6a1537 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/QueueIconListener.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.ui.command; + +public interface QueueIconListener { + void queueIconClicked(int index); +} diff --git a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java index c53e166..62b52d7 100644 --- a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java +++ b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java @@ -90,14 +90,14 @@ public class DesktopLauncher { // config.vSyncEnabled = false; // config.foregroundFPS = 0; // config.backgroundFPS = 0; + final DisplayMode desktopDisplayMode = LwjglApplicationConfiguration.getDesktopDisplayMode(); + config.width = desktopDisplayMode.width; + config.height = desktopDisplayMode.height; if ((arg.length > 0) && "-windowed".equals(arg[0])) { config.fullscreen = false; } else { config.fullscreen = true; - final DisplayMode desktopDisplayMode = LwjglApplicationConfiguration.getDesktopDisplayMode(); - config.width = desktopDisplayMode.width; - config.height = desktopDisplayMode.height; } new LwjglApplication(new WarsmashGdxMapGame(warsmashIni), config); } From b60233a5a2aa865f9e5da0a8776580b521d8407f Mon Sep 17 00:00:00 2001 From: Retera Date: Fri, 27 Nov 2020 20:45:21 -0500 Subject: [PATCH 073/116] Update to support red and green status under buildings --- .../etheller/warsmash/viewer5/Texture.java | 4 +- .../viewer5/ViewerTextureRenderable.java | 29 ++++++ .../viewer5/handlers/w3x/SplatModel.java | 13 +-- .../viewer5/handlers/w3x/War3MapViewer.java | 10 ++- .../handlers/w3x/environment/PathingGrid.java | 90 ++++++------------- .../w3x/simulation/CWorldCollision.java | 6 ++ .../abilities/build/CAbilityOrcBuild.java | 13 +++ .../w3x/simulation/data/CUnitData.java | 1 - .../viewer5/handlers/w3x/ui/MeleeUI.java | 88 +++++++++++++++++- 9 files changed, 178 insertions(+), 76 deletions(-) create mode 100644 core/src/com/etheller/warsmash/viewer5/ViewerTextureRenderable.java diff --git a/core/src/com/etheller/warsmash/viewer5/Texture.java b/core/src/com/etheller/warsmash/viewer5/Texture.java index fc75b47..d55596f 100644 --- a/core/src/com/etheller/warsmash/viewer5/Texture.java +++ b/core/src/com/etheller/warsmash/viewer5/Texture.java @@ -2,7 +2,7 @@ package com.etheller.warsmash.viewer5; import com.etheller.warsmash.viewer5.handlers.ResourceHandler; -public abstract class Texture extends HandlerResource { +public abstract class Texture extends HandlerResource implements ViewerTextureRenderable { public Texture(final ModelViewer viewer, final String extension, final PathSolver pathSolver, final String fetchUrl, final ResourceHandler handler) { @@ -17,8 +17,10 @@ public abstract class Texture extends HandlerResource { public abstract int getHeight(); + @Override public abstract int getGlTarget(); + @Override public abstract int getGlHandle(); public abstract void setWrapS(final boolean wrapS); diff --git a/core/src/com/etheller/warsmash/viewer5/ViewerTextureRenderable.java b/core/src/com/etheller/warsmash/viewer5/ViewerTextureRenderable.java new file mode 100644 index 0000000..7f29f0e --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/ViewerTextureRenderable.java @@ -0,0 +1,29 @@ +package com.etheller.warsmash.viewer5; + +import com.badlogic.gdx.graphics.Texture; + +public interface ViewerTextureRenderable { + // TODO bind method makes more sense here + + int getGlTarget(); + + int getGlHandle(); + + class GdxViewerTextureRenderable implements ViewerTextureRenderable { + private final com.badlogic.gdx.graphics.Texture gdxTexture; + + public GdxViewerTextureRenderable(final Texture texture) { + this.gdxTexture = texture; + } + + @Override + public int getGlTarget() { + return this.gdxTexture.glTarget; + } + + @Override + public int getGlHandle() { + return this.gdxTexture.getTextureObjectHandle(); + } + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SplatModel.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SplatModel.java index 95e8167..0f8f348 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SplatModel.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SplatModel.java @@ -9,7 +9,7 @@ import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.GL30; import com.badlogic.gdx.graphics.glutils.ShaderProgram; import com.etheller.warsmash.util.RenderMathUtils; -import com.etheller.warsmash.viewer5.Texture; +import com.etheller.warsmash.viewer5.ViewerTextureRenderable; /** * TODO this is copied from RivSoft stuff. @@ -22,7 +22,7 @@ import com.etheller.warsmash.viewer5.Texture; */ public class SplatModel { private static final int MAX_VERTICES = 65000; - private final Texture texture; + private final ViewerTextureRenderable texture; private final List batches; public final float[] color; private final List locations; @@ -30,8 +30,9 @@ public class SplatModel { private final boolean unshaded; private final boolean noDepthTest; - public SplatModel(final GL30 gl, final Texture texture, final List locations, final float[] centerOffset, - final List> unitMapping, final boolean unshaded, final boolean noDepthTest) { + public SplatModel(final GL30 gl, final ViewerTextureRenderable texture, final List locations, + final float[] centerOffset, final List> unitMapping, final boolean unshaded, + final boolean noDepthTest) { this.texture = texture; this.unshaded = unshaded; this.noDepthTest = noDepthTest; @@ -245,9 +246,9 @@ public class SplatModel { return this.noDepthTest; } - public SplatMover add(final float x, final float y, final float w, final float h, final float zDepthUpward, + public SplatMover add(final float x, final float y, final float x2, final float y2, final float zDepthUpward, final float[] centerOffset) { - this.locations.add(new float[] { x, y, w, h, zDepthUpward }); + this.locations.add(new float[] { x, y, x2, y2, zDepthUpward }); final SplatMover splatMover; if (this.splatInstances != null) { splatMover = new SplatMover(this); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index 19810e0..7c77740 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -910,8 +910,14 @@ public class War3MapViewer extends ModelViewer { buildingPathingPixelMap = getBuildingPathingPixelMap(row); if (buildingPathingPixelMap != null) { - unitX = Math.round(unitX / 64f) * 64f; - unitY = Math.round(unitY / 64f) * 64f; + unitX = (float) Math.floor(unitX / 64f) * 64f; + unitY = (float) Math.floor(unitY / 64f) * 64f; + if (((buildingPathingPixelMap.getWidth() / 2) % 2) == 1) { + unitX += 32f; + } + if (((buildingPathingPixelMap.getHeight() / 2) % 2) == 1) { + unitY += 32f; + } pathingInstance = this.terrain.pathingGrid.blitRemovablePathingOverlayTexture(unitX, unitY, (int) Math.toDegrees(unitAngle), buildingPathingPixelMap); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java index 0912d7e..8c2f90d 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java @@ -141,91 +141,37 @@ public class PathingGrid { short anyPathingTypeWithUnit = 0; if (cWorldCollision.intersectsAnythingOtherThan(pathingMapRectangle, unitToExcludeFromCollisionChecks, MovementType.AMPHIBIOUS)) { + System.out.println("intersects amph unit"); anyPathingTypesInRegion |= PathingFlags.UNBUILDABLE | PathingFlags.UNWALKABLE | PathingFlags.UNSWIMABLE; anyPathingTypeWithUnit |= PathingFlags.UNBUILDABLE | PathingFlags.UNWALKABLE | PathingFlags.UNSWIMABLE; } if (cWorldCollision.intersectsAnythingOtherThan(pathingMapRectangle, unitToExcludeFromCollisionChecks, MovementType.FLOAT)) { + System.out.println("intersects float unit"); anyPathingTypesInRegion |= PathingFlags.UNSWIMABLE; anyPathingTypeWithUnit |= PathingFlags.UNSWIMABLE; } if (cWorldCollision.intersectsAnythingOtherThan(pathingMapRectangle, unitToExcludeFromCollisionChecks, MovementType.FLY)) { + System.out.println("intersects fly unit"); anyPathingTypesInRegion |= PathingFlags.UNFLYABLE; anyPathingTypeWithUnit |= PathingFlags.UNFLYABLE; } if (cWorldCollision.intersectsAnythingOtherThan(pathingMapRectangle, unitToExcludeFromCollisionChecks, MovementType.FOOT)) { + System.out.println("intersects foot unit"); anyPathingTypesInRegion |= PathingFlags.UNBUILDABLE | PathingFlags.UNWALKABLE; anyPathingTypeWithUnit |= PathingFlags.UNBUILDABLE | PathingFlags.UNWALKABLE; } pathingTypesFillingRegion &= anyPathingTypeWithUnit; for (final CBuildingPathingType pathingType : preventPathingTypes) { - switch (pathingType) { - case BLIGHTED: - throw new IllegalArgumentException("Blight pathing check system is Not Yet Implemented"); - case UNAMPH: - if (PathingFlags.isPathingFlag(anyPathingTypesInRegion, PathingFlags.UNWALKABLE) - && PathingFlags.isPathingFlag(anyPathingTypesInRegion, PathingFlags.UNSWIMABLE)) { - return false; - } - break; - case UNBUILDABLE: - if (PathingFlags.isPathingFlag(anyPathingTypesInRegion, PathingFlags.UNBUILDABLE)) { - return false; - } - break; - case UNFLOAT: - if (PathingFlags.isPathingFlag(anyPathingTypesInRegion, PathingFlags.UNSWIMABLE)) { - return false; - } - break; - case UNFLYABLE: - if (PathingFlags.isPathingFlag(anyPathingTypesInRegion, PathingFlags.UNFLYABLE)) { - return false; - } - break; - case UNWALKABLE: - if (PathingFlags.isPathingFlag(anyPathingTypesInRegion, PathingFlags.UNWALKABLE)) { - return false; - } - break; - default: - break; + if (PathingFlags.isPathingFlag(anyPathingTypesInRegion, pathingType)) { + return false; } } for (final CBuildingPathingType pathingType : requirePathingTypes) { - switch (pathingType) { - case BLIGHTED: - throw new IllegalArgumentException("Blight pathing check system is Not Yet Implemented"); - case UNAMPH: - if (!PathingFlags.isPathingFlag(pathingTypesFillingRegion, PathingFlags.UNWALKABLE) - || !PathingFlags.isPathingFlag(pathingTypesFillingRegion, PathingFlags.UNSWIMABLE)) { - return false; - } - break; - case UNBUILDABLE: - if (!PathingFlags.isPathingFlag(pathingTypesFillingRegion, PathingFlags.UNBUILDABLE)) { - return false; - } - break; - case UNFLOAT: - if (!PathingFlags.isPathingFlag(pathingTypesFillingRegion, PathingFlags.UNSWIMABLE)) { - return false; - } - break; - case UNFLYABLE: - if (!PathingFlags.isPathingFlag(pathingTypesFillingRegion, PathingFlags.UNFLYABLE)) { - return false; - } - break; - case UNWALKABLE: - if (!PathingFlags.isPathingFlag(pathingTypesFillingRegion, PathingFlags.UNWALKABLE)) { - return false; - } - break; - default: - break; + if (!PathingFlags.isPathingFlag(pathingTypesFillingRegion, pathingType)) { + return false; } } return true; @@ -383,6 +329,26 @@ public class PathingGrid { return (pathingValue & flag) != 0; } + public static boolean isPathingFlag(final short pathingValue, final CBuildingPathingType pathingType) { + switch (pathingType) { + case BLIGHTED: + throw new IllegalArgumentException("Blight pathing check system is Not Yet Implemented"); + case UNAMPH: + return PathingFlags.isPathingFlag(pathingValue, PathingFlags.UNWALKABLE) + && PathingFlags.isPathingFlag(pathingValue, PathingFlags.UNSWIMABLE); + case UNBUILDABLE: + return PathingFlags.isPathingFlag(pathingValue, PathingFlags.UNBUILDABLE); + case UNFLOAT: + return PathingFlags.isPathingFlag(pathingValue, PathingFlags.UNSWIMABLE); + case UNFLYABLE: + return PathingFlags.isPathingFlag(pathingValue, PathingFlags.UNFLYABLE); + case UNWALKABLE: + return PathingFlags.isPathingFlag(pathingValue, PathingFlags.UNWALKABLE); + default: + return false; + } + } + private PathingFlags() { } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWorldCollision.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWorldCollision.java index 72735c0..b488988 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWorldCollision.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWorldCollision.java @@ -190,6 +190,9 @@ public class CWorldCollision { @Override public boolean onIntersect(final CUnit intersectingObject) { + if (intersectingObject.isHidden()) { + return false; + } return (intersectingObject != this.firstUnit) && (intersectingObject != this.secondUnit); } } @@ -208,6 +211,9 @@ public class CWorldCollision { @Override public boolean onIntersect(final CUnit intersectingObject) { + if (intersectingObject.isHidden()) { + return false; + } if (this.done) { return true; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityOrcBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityOrcBuild.java index 36f0e51..27289ca 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityOrcBuild.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityOrcBuild.java @@ -1,5 +1,6 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build; +import java.awt.image.BufferedImage; import java.util.List; import com.etheller.warsmash.util.War3ID; @@ -41,6 +42,18 @@ public class CAbilityOrcBuild extends AbstractCAbilityBuild { @Override public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final AbilityPointTarget point) { + final BufferedImage buildingPathingPixelMap = game.getUnitData().getUnitType(new War3ID(orderId)) + .getBuildingPathingPixelMap(); + if (buildingPathingPixelMap != null) { + point.x = (float) Math.floor(point.x / 64f) * 64f; + point.y = (float) Math.floor(point.y / 64f) * 64f; + if (((buildingPathingPixelMap.getWidth() / 2) % 2) == 1) { + point.x += 32f; + } + if (((buildingPathingPixelMap.getHeight() / 2) % 2) == 1) { + point.y += 32f; + } + } return this.buildBehavior.reset(point, orderId, getBaseOrderId()); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java index 0c05cf2..957e7d7 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java @@ -44,7 +44,6 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.pathing.CBuildingPa import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.SimulationRenderController; public class CUnitData { - private static final War3ID RALLY_RAWCODE = War3ID.fromString("ARal"); private static final War3ID MANA_INITIAL_AMOUNT = War3ID.fromString("umpi"); private static final War3ID MANA_MAXIMUM = War3ID.fromString("umpm"); private static final War3ID HIT_POINT_MAXIMUM = War3ID.fromString("uhpm"); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java index 61c9ce2..4997cd2 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java @@ -11,11 +11,15 @@ import javax.imageio.ImageIO; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Input; import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.Pixmap; +import com.badlogic.gdx.graphics.Pixmap.Blending; +import com.badlogic.gdx.graphics.Pixmap.Format; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.GlyphLayout; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator; +import com.badlogic.gdx.graphics.glutils.PixmapTextureData; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.math.Vector3; @@ -43,6 +47,7 @@ import com.etheller.warsmash.util.RenderMathUtils; import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.util.WarsmashConstants; import com.etheller.warsmash.viewer5.Scene; +import com.etheller.warsmash.viewer5.ViewerTextureRenderable; import com.etheller.warsmash.viewer5.handlers.mdx.Attachment; import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; @@ -54,6 +59,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.SecondaryTag; import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; +import com.etheller.warsmash.viewer5.handlers.w3x.SplatModel; import com.etheller.warsmash.viewer5.handlers.w3x.SplatModel.SplatMover; import com.etheller.warsmash.viewer5.handlers.w3x.UnitSound; import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; @@ -61,6 +67,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.camera.CameraPreset; import com.etheller.warsmash.viewer5.handlers.w3x.camera.CameraRates; import com.etheller.warsmash.viewer5.handlers.w3x.camera.GameCameraManager; import com.etheller.warsmash.viewer5.handlers.w3x.camera.PortraitCameraManager; +import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.PathingFlags; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderUnit; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.AbilityDataUI; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.IconUI; @@ -101,6 +108,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CodeKeyType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackMissileSplash; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.pathing.CBuildingPathingType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayerUnitOrderListener; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.BooleanAbilityActivationReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.BooleanAbilityTargetCheckReceiver; @@ -113,6 +121,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.QueueIconListener; public class MeleeUI implements CUnitStateListener, CommandButtonListener, CommandCardCommandListener, QueueIconListener { + private static final String BUILDING_PATHING_PREVIEW_KEY = "buildingPathingPreview"; public static final float DEFAULT_COMMAND_CARD_ICON_WIDTH = 0.039f; public static final float DEFAULT_COMMAND_CARD_ICON_PRESSED_WIDTH = 0.037f; private static final int COMMAND_CARD_WIDTH = 4; @@ -192,6 +201,11 @@ public class MeleeUI private MdxComplexInstance cursorModelInstance = null; private MdxComplexInstance rallyPointInstance = null; private BufferedImage cursorModelPathing; + private Pixmap cursorModelUnderneathPathingRedGreenPixmap; + private Texture cursorModelUnderneathPathingRedGreenPixmapTexture; + private PixmapTextureData cursorModelUnderneathPathingRedGreenPixmapTextureData; + private SplatModel cursorModelUnderneathPathingRedGreenSplatModel; + private CUnitType cursorBuildingUnitType; private SplatMover placementCursor = null; private final CursorTargetSetupVisitor cursorTargetSetupVisitor; @@ -632,11 +646,15 @@ public class MeleeUI this.cursorModelInstance = null; this.cursorFrame.setVisible(true); } - else if (this.placementCursor != null) { + if (this.placementCursor != null) { this.placementCursor.destroy(Gdx.gl30, this.war3MapViewer.terrain.centerOffset); this.placementCursor = null; this.cursorFrame.setVisible(true); } + if (this.cursorModelUnderneathPathingRedGreenSplatModel != null) { + this.war3MapViewer.terrain.removeSplatBatchModel(BUILDING_PATHING_PREVIEW_KEY); + this.cursorModelUnderneathPathingRedGreenSplatModel = null; + } if (down) { if (left) { this.cursorFrame.setSequence("Scroll Down Left"); @@ -832,6 +850,7 @@ public class MeleeUI if (MeleeUI.this.cursorModelInstance == null) { final MutableObjectData unitData = viewer.getAllObjectData().getUnits(); final War3ID buildingTypeId = new War3ID(MeleeUI.this.activeCommandOrderId); + MeleeUI.this.cursorBuildingUnitType = viewer.simulation.getUnitData().getUnitType(buildingTypeId); final String unitModelPath = viewer.getUnitModelPath(unitData.get(buildingTypeId)); final MdxModel model = (MdxModel) viewer.load(unitModelPath, viewer.mapPathSolver, viewer.solverParams); MeleeUI.this.cursorModelInstance = (MdxComplexInstance) model.addInstance(); @@ -843,14 +862,75 @@ public class MeleeUI viewer.simulation.getGameplayConstants().getBuildingAngle())); MeleeUI.this.cursorModelInstance.setAnimationSpeed(0f); justLoaded = true; - final CUnitType buildingUnitType = viewer.simulation.getUnitData().getUnitType(buildingTypeId); + final CUnitType buildingUnitType = MeleeUI.this.cursorBuildingUnitType; MeleeUI.this.cursorModelPathing = buildingUnitType.getBuildingPathingPixelMap(); + + if (MeleeUI.this.cursorModelPathing != null) { + MeleeUI.this.cursorModelUnderneathPathingRedGreenPixmap = new Pixmap( + MeleeUI.this.cursorModelPathing.getWidth(), MeleeUI.this.cursorModelPathing.getHeight(), + Format.RGBA8888); + MeleeUI.this.cursorModelUnderneathPathingRedGreenPixmap.setBlending(Blending.None); + MeleeUI.this.cursorModelUnderneathPathingRedGreenPixmapTextureData = new PixmapTextureData( + MeleeUI.this.cursorModelUnderneathPathingRedGreenPixmap, Format.RGBA8888, false, false); + MeleeUI.this.cursorModelUnderneathPathingRedGreenPixmapTexture = new Texture( + MeleeUI.this.cursorModelUnderneathPathingRedGreenPixmapTextureData); + final ViewerTextureRenderable greenPixmap = new ViewerTextureRenderable.GdxViewerTextureRenderable( + MeleeUI.this.cursorModelUnderneathPathingRedGreenPixmapTexture); + MeleeUI.this.cursorModelUnderneathPathingRedGreenSplatModel = new SplatModel(Gdx.gl30, greenPixmap, + new ArrayList<>(), viewer.terrain.centerOffset, new ArrayList<>(), true, false); + MeleeUI.this.cursorModelUnderneathPathingRedGreenSplatModel.color[3] = 0.20f; + } } viewer.getClickLocation(clickLocationTemp, this.baseMouseX, Gdx.graphics.getHeight() - this.baseMouseY); if (MeleeUI.this.cursorModelPathing != null) { - clickLocationTemp.x = Math.round(clickLocationTemp.x / 64f) * 64f; - clickLocationTemp.y = Math.round(clickLocationTemp.y / 64f) * 64f; + clickLocationTemp.x = (float) Math.floor(clickLocationTemp.x / 64f) * 64f; + clickLocationTemp.y = (float) Math.floor(clickLocationTemp.y / 64f) * 64f; + if (((MeleeUI.this.cursorModelPathing.getWidth() / 2) % 2) == 1) { + clickLocationTemp.x += 32f; + } + if (((MeleeUI.this.cursorModelPathing.getHeight() / 2) % 2) == 1) { + clickLocationTemp.y += 32f; + } clickLocationTemp.z = viewer.terrain.getGroundHeight(clickLocationTemp.x, clickLocationTemp.y); + + final float halfRenderWidth = MeleeUI.this.cursorModelPathing.getWidth() * 16; + final float halfRenderHeight = MeleeUI.this.cursorModelPathing.getHeight() * 16; + for (int i = 0; i < MeleeUI.this.cursorModelUnderneathPathingRedGreenPixmap.getWidth(); i++) { + for (int j = 0; j < MeleeUI.this.cursorModelUnderneathPathingRedGreenPixmap.getHeight(); j++) { + boolean blocked = false; + final short pathing = viewer.simulation.getPathingGrid().getPathing( + (clickLocationTemp.x + (i * 32)) - halfRenderWidth, + (clickLocationTemp.y + (j * 32)) - halfRenderHeight); + for (final CBuildingPathingType preventedType : MeleeUI.this.cursorBuildingUnitType + .getPreventedPathingTypes()) { + if (PathingFlags.isPathingFlag(pathing, preventedType)) { + blocked = true; + } + } + for (final CBuildingPathingType requiredType : MeleeUI.this.cursorBuildingUnitType + .getRequiredPathingTypes()) { + if (!PathingFlags.isPathingFlag(pathing, requiredType)) { + blocked = true; + } + } + final int color = blocked ? Color.rgba8888(1, 0, 0, 1.0f) : Color.rgba8888(0, 1, 0, 1.0f); + MeleeUI.this.cursorModelUnderneathPathingRedGreenPixmap.drawPixel(i, + MeleeUI.this.cursorModelUnderneathPathingRedGreenPixmap.getHeight() - 1 - j, color); + } + } + MeleeUI.this.cursorModelUnderneathPathingRedGreenPixmapTexture + .load(MeleeUI.this.cursorModelUnderneathPathingRedGreenPixmapTextureData); + + if (justLoaded) { + viewer.terrain.addSplatBatchModel(BUILDING_PATHING_PREVIEW_KEY, + MeleeUI.this.cursorModelUnderneathPathingRedGreenSplatModel); + MeleeUI.this.placementCursor = MeleeUI.this.cursorModelUnderneathPathingRedGreenSplatModel.add( + clickLocationTemp.x - halfRenderWidth, clickLocationTemp.y - halfRenderHeight, + clickLocationTemp.x + halfRenderWidth, clickLocationTemp.y + halfRenderHeight, 10, + viewer.terrain.centerOffset); + } + MeleeUI.this.placementCursor.setLocation(clickLocationTemp.x, clickLocationTemp.y, + viewer.terrain.centerOffset); } MeleeUI.this.cursorModelInstance.setLocation(clickLocationTemp); SequenceUtils.randomSequence(MeleeUI.this.cursorModelInstance, PrimaryTag.STAND); From f647ce172efce67b2b0e43cf633b7ac45a1eccec Mon Sep 17 00:00:00 2001 From: Retera Date: Sat, 12 Dec 2020 18:32:14 -0500 Subject: [PATCH 074/116] Updates including basic FDF BACKDROP type and CantPlace errors when peon builds --- .../etheller/warsmash/WarsmashGdxGame.java | 44 +- .../etheller/warsmash/WarsmashGdxMapGame.java | 70 +- .../warsmash/WarsmashGdxMenuTestGame.java | 807 ++++++++++++++++++ .../warsmash/WarsmashPreviewApplication.java | 175 ++++ .../etheller/warsmash/parsers/fdf/GameUI.java | 214 ++++- .../parsers/fdf/frames/BackdropFrame.java | 184 ++++ .../warsmash/parsers/fdf/frames/SetPoint.java | 6 + .../fdf/frames/SmartBackdropFrame.java | 76 ++ .../parsers/fdf/frames/SpriteFrame.java | 8 +- .../parsers/fdf/frames/StringFrame.java | 28 +- .../warsmash/parsers/mdlx/Geoset.java | 52 ++ .../etheller/warsmash/parsers/mdlx/Layer.java | 36 +- .../warsmash/parsers/mdlx/MdlxModel.java | 3 + .../warsmash/parsers/mdlx/Texture.java | 12 + .../etheller/warsmash/units/DataTable.java | 7 + .../warsmash/units/HashedGameObject.java | 8 +- .../etheller/warsmash/util/ImageUtils.java | 12 +- .../warsmash/util/RenderMathUtils.java | 2 +- .../com/etheller/warsmash/viewer5/Camera.java | 4 +- .../warsmash/viewer5/SimpleScene.java | 4 +- .../handlers/AbstractMdxModelViewer.java | 21 + .../viewer5/handlers/mdx/MdxViewer.java | 14 +- .../viewer5/handlers/w3x/War3MapViewer.java | 18 +- .../handlers/w3x/camera/CameraManager.java | 8 +- .../handlers/w3x/simulation/CSimulation.java | 9 +- .../behaviors/build/CBehaviorOrcBuild.java | 3 + .../viewer5/handlers/w3x/ui/MeleeUI.java | 53 +- .../viewer5/handlers/w3x/ui/MenuUI.java | 148 ++++ .../w3x/ui/command/CommandErrorListener.java | 2 + .../command/SettableCommandErrorListener.java | 19 + .../warsmash/desktop/DesktopLauncher.java | 65 +- .../desktop/editor/mdx/MdxEditorMain.java | 33 + .../mdx/listeners/YseraGUIListener.java | 29 + .../mdx/ui/AnimationControllerFrame.java | 30 + .../mdx/ui/AnimationControllerPanel.java | 210 +++++ .../desktop/editor/mdx/ui/YseraFrame.java | 64 ++ .../desktop/editor/mdx/ui/YseraPanel.java | 193 +++++ .../desktop/editor/util/ExceptionPopup.java | 84 ++ .../fdf/datamodel/BackdropCornerFlags.java | 12 + .../fdf/datamodel/FrameDefinition.java | 9 + .../visitor/GetVector2FieldVisitor.java | 57 ++ 41 files changed, 2682 insertions(+), 151 deletions(-) create mode 100644 core/src/com/etheller/warsmash/WarsmashGdxMenuTestGame.java create mode 100644 core/src/com/etheller/warsmash/WarsmashPreviewApplication.java create mode 100644 core/src/com/etheller/warsmash/parsers/fdf/frames/BackdropFrame.java create mode 100644 core/src/com/etheller/warsmash/parsers/fdf/frames/SmartBackdropFrame.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/AbstractMdxModelViewer.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MenuUI.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/SettableCommandErrorListener.java create mode 100644 desktop/src/com/etheller/warsmash/desktop/editor/mdx/MdxEditorMain.java create mode 100644 desktop/src/com/etheller/warsmash/desktop/editor/mdx/listeners/YseraGUIListener.java create mode 100644 desktop/src/com/etheller/warsmash/desktop/editor/mdx/ui/AnimationControllerFrame.java create mode 100644 desktop/src/com/etheller/warsmash/desktop/editor/mdx/ui/AnimationControllerPanel.java create mode 100644 desktop/src/com/etheller/warsmash/desktop/editor/mdx/ui/YseraFrame.java create mode 100644 desktop/src/com/etheller/warsmash/desktop/editor/mdx/ui/YseraPanel.java create mode 100644 desktop/src/com/etheller/warsmash/desktop/editor/util/ExceptionPopup.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/visitor/GetVector2FieldVisitor.java diff --git a/core/src/com/etheller/warsmash/WarsmashGdxGame.java b/core/src/com/etheller/warsmash/WarsmashGdxGame.java index a5cb40a..d3f6a4b 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxGame.java @@ -3,25 +3,20 @@ package com.etheller.warsmash; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.IntBuffer; -import java.util.ArrayList; -import java.util.List; import com.badlogic.gdx.ApplicationAdapter; import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.audio.Music; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.math.Quaternion; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Vector3; -import com.etheller.warsmash.datasources.CompoundDataSourceDescriptor; import com.etheller.warsmash.datasources.DataSource; -import com.etheller.warsmash.datasources.DataSourceDescriptor; -import com.etheller.warsmash.datasources.FolderDataSourceDescriptor; -import com.etheller.warsmash.datasources.MpqDataSourceDescriptor; import com.etheller.warsmash.parsers.mdlx.Sequence; import com.etheller.warsmash.units.DataTable; -import com.etheller.warsmash.units.Element; +import com.etheller.warsmash.util.DataSourceFileHandle; import com.etheller.warsmash.viewer5.Camera; import com.etheller.warsmash.viewer5.CanvasProvider; import com.etheller.warsmash.viewer5.ModelViewer; @@ -67,26 +62,7 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide final String renderer = Gdx.gl.glGetString(GL20.GL_RENDERER); System.err.println("Renderer: " + renderer); - final Element dataSourcesConfig = this.warsmashIni.get("DataSources"); - final int dataSourcesCount = dataSourcesConfig.getFieldValue("Count"); - final List dataSourcesList = new ArrayList<>(); - for (int i = 0; i < dataSourcesCount; i++) { - final String type = dataSourcesConfig.getField("Type" + (i < 10 ? "0" : "") + i); - final String path = dataSourcesConfig.getField("Path" + (i < 10 ? "0" : "") + i); - switch (type) { - case "Folder": { - dataSourcesList.add(new FolderDataSourceDescriptor(path)); - break; - } - case "MPQ": { - dataSourcesList.add(new MpqDataSourceDescriptor(path)); - break; - } - default: - throw new RuntimeException("Unknown data source type: " + type); - } - } - this.codebase = new CompoundDataSourceDescriptor(dataSourcesList).createDataSource(); + this.codebase = WarsmashGdxMapGame.parseDataSources(this.warsmashIni); this.viewer = new MdxViewer(this.codebase, this); this.viewer.addHandler(new MdxHandler()); @@ -98,6 +74,12 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide this.cameraManager = new CameraManager(); this.cameraManager.setupCamera(scene); + final String musicPath = "Sound\\Music\\mp3Music\\Mainscreen.mp3"; + final Music music = Gdx.audio.newMusic(new DataSourceFileHandle(this.viewer.dataSource, musicPath)); +// music.setVolume(0.2f); + music.setLooping(true); + music.play(); + // this.mainModel = (MdxModel) this.viewer.load("Doodads\\Cinematic\\ArthasIllidanFight\\ArthasIllidanFight.mdx", // this.mainModel = (MdxModel) this.viewer.load("UI\\Glues\\SinglePlayer\\NightElf_Exp\\NightElf_Exp.mdx", // this.mainModel = (MdxModel) this.viewer.load("Abilities\\Spells\\Orc\\FeralSpirit\\feralspirittarget.mdx", @@ -128,7 +110,9 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide // acolytesHarvestingSceneJoke2(scene); // singleModelScene(scene, "Buildings\\Undead\\Necropolis\\Necropolis.mdx", "birth"); - singleModelScene(scene, "Units\\Orc\\KotoBeast\\KotoBeast.mdx", "spell slam"); +// singleModelScene(scene, "Units\\Orc\\KotoBeast\\KotoBeast.mdx", "spell slam"); + singleModelScene(scene, "UI\\Glues\\MainMenu\\MainMenu3D\\MainMenu3D.mdx", "Stand"); + this.modelCamera = this.mainModel.cameras.get(0); System.out.println("Loaded"); Gdx.gl30.glClearColor(0.5f, 0.5f, 0.5f, 1); // TODO remove white background @@ -544,4 +528,8 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide // // } } + + public DataSource getCodebase() { + return this.codebase; + } } diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java index b8647b3..5fa315d 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java @@ -52,7 +52,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.camera.CameraPreset; import com.etheller.warsmash.viewer5.handlers.w3x.camera.CameraRates; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayerUnitOrderExecutor; import com.etheller.warsmash.viewer5.handlers.w3x.ui.MeleeUI; -import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.CommandErrorListener; +import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.SettableCommandErrorListener; public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProvider, InputProcessor { private static final boolean ENABLE_AUDIO = true; @@ -78,6 +78,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv private Scene uiScene; private MeleeUI meleeUI; + private FreeTypeFontGenerator fontGenerator; public WarsmashGdxMapGame(final DataTable warsmashIni) { this.warsmashIni = warsmashIni; @@ -103,27 +104,9 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv final String renderer = Gdx.gl.glGetString(GL20.GL_RENDERER); System.err.println("Renderer: " + renderer); - final Element dataSourcesConfig = this.warsmashIni.get("DataSources"); - final int dataSourcesCount = dataSourcesConfig.getFieldValue("Count"); - final List dataSourcesList = new ArrayList<>(); - for (int i = 0; i < dataSourcesCount; i++) { - final String type = dataSourcesConfig.getField("Type" + (i < 10 ? "0" : "") + i); - final String path = dataSourcesConfig.getField("Path" + (i < 10 ? "0" : "") + i); - switch (type) { - case "Folder": { - dataSourcesList.add(new FolderDataSourceDescriptor(path)); - break; - } - case "MPQ": { - dataSourcesList.add(new MpqDataSourceDescriptor(path)); - break; - } - default: - throw new RuntimeException("Unknown data source type: " + type); - } - } - this.codebase = new CompoundDataSourceDescriptor(dataSourcesList).createDataSource(); - this.viewer = new War3MapViewer(this.codebase, this); + final SettableCommandErrorListener commandErrorListener = new SettableCommandErrorListener(); + this.codebase = parseDataSources(this.warsmashIni); + this.viewer = new War3MapViewer(this.codebase, this, commandErrorListener); if (ENABLE_AUDIO) { this.viewer.worldScene.enableAudio(); @@ -170,13 +153,13 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv final int width = Gdx.graphics.getWidth(); final int height = Gdx.graphics.getHeight(); - final FreeTypeFontGenerator fontGenerator = new FreeTypeFontGenerator( + this.fontGenerator = new FreeTypeFontGenerator( new DataSourceFileHandle(this.viewer.dataSource, "fonts\\FRIZQT__.TTF")); final FreeTypeFontParameter fontParam = new FreeTypeFontParameter(); fontParam.size = 32; - this.font = fontGenerator.generateFont(fontParam); + this.font = this.fontGenerator.generateFont(fontParam); fontParam.size = 20; - this.font20 = fontGenerator.generateFont(fontParam); + this.font20 = this.fontGenerator.generateFont(fontParam); this.glyphLayout = new GlyphLayout(); // Constructs a new OrthographicCamera, using the given viewport width and @@ -222,7 +205,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv cameraRatesElement.getFieldFloatValue("FOV"), cameraRatesElement.getFieldFloatValue("Rotation"), cameraRatesElement.getFieldFloatValue("Distance"), cameraRatesElement.getFieldFloatValue("Forward"), cameraRatesElement.getFieldFloatValue("Strafe")); - this.meleeUI = new MeleeUI(this.viewer.mapMpq, this.uiViewport, fontGenerator, this.uiScene, portraitScene, + this.meleeUI = new MeleeUI(this.viewer.mapMpq, this.uiViewport, this.fontGenerator, this.uiScene, portraitScene, cameraPresets, cameraRates, this.viewer, new RootFrameListener() { @Override public void onCreate(final GameUI rootFrame) { @@ -242,17 +225,12 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv music.play(); } } - }, new CPlayerUnitOrderExecutor(this.viewer.simulation, new CommandErrorListener() { - @Override - public void showCommandError(final String message) { - WarsmashGdxMapGame.this.meleeUI.showCommandError(message); - } - })); + }, new CPlayerUnitOrderExecutor(this.viewer.simulation, commandErrorListener)); + commandErrorListener.setDelegate(this.meleeUI); final ModelInstance libgdxContentInstance = new LibGDXContentLayerModel(null, this.viewer, "", this.viewer.mapPathSolver, "").addInstance(); libgdxContentInstance.setScene(this.uiScene); this.meleeUI.main(); - fontGenerator.dispose(); updateUIScene(); @@ -266,6 +244,29 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv } } + public static DataSource parseDataSources(final DataTable warsmashIni) { + final Element dataSourcesConfig = warsmashIni.get("DataSources"); + final int dataSourcesCount = dataSourcesConfig.getFieldValue("Count"); + final List dataSourcesList = new ArrayList<>(); + for (int i = 0; i < dataSourcesCount; i++) { + final String type = dataSourcesConfig.getField("Type" + (i < 10 ? "0" : "") + i); + final String path = dataSourcesConfig.getField("Path" + (i < 10 ? "0" : "") + i); + switch (type) { + case "Folder": { + dataSourcesList.add(new FolderDataSourceDescriptor(path)); + break; + } + case "MPQ": { + dataSourcesList.add(new MpqDataSourceDescriptor(path)); + break; + } + default: + throw new RuntimeException("Unknown data source type: " + type); + } + } + return new CompoundDataSourceDescriptor(dataSourcesList).createDataSource(); + } + private void updateUIScene() { this.tempRect.x = this.uiViewport.getScreenX(); this.tempRect.y = this.uiViewport.getScreenY(); @@ -280,7 +281,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv final float uiSceneHeight = 0.6f * yScale; final float uiSceneX = ((0.8f - uiSceneWidth) / 2); final float uiSceneY = ((0.6f - uiSceneHeight) / 2); - this.uiScene.camera.ortho(uiSceneX, uiSceneWidth + uiSceneX, uiSceneY, uiSceneHeight + uiSceneY, -1f, 1); + this.uiScene.camera.ortho(uiSceneX, uiSceneWidth + uiSceneX, uiSceneY, uiSceneHeight + uiSceneY, -1024f, 1024); } @Override @@ -328,6 +329,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv @Override public void dispose() { + this.fontGenerator.dispose(); } @Override diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMenuTestGame.java b/core/src/com/etheller/warsmash/WarsmashGdxMenuTestGame.java new file mode 100644 index 0000000..d411c47 --- /dev/null +++ b/core/src/com/etheller/warsmash/WarsmashGdxMenuTestGame.java @@ -0,0 +1,807 @@ +package com.etheller.warsmash; + +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.IntBuffer; + +import com.badlogic.gdx.ApplicationAdapter; +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.InputProcessor; +import com.badlogic.gdx.audio.Music; +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.GL20; +import com.badlogic.gdx.graphics.GL30; +import com.badlogic.gdx.graphics.OrthographicCamera; +import com.badlogic.gdx.graphics.Texture; +import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.g2d.GlyphLayout; +import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator; +import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator.FreeTypeFontParameter; +import com.badlogic.gdx.math.Matrix4; +import com.badlogic.gdx.math.Quaternion; +import com.badlogic.gdx.math.Rectangle; +import com.badlogic.gdx.math.Vector3; +import com.badlogic.gdx.utils.viewport.ExtendViewport; +import com.etheller.warsmash.datasources.DataSource; +import com.etheller.warsmash.parsers.fdf.GameUI; +import com.etheller.warsmash.parsers.jass.Jass2.RootFrameListener; +import com.etheller.warsmash.parsers.mdlx.Sequence; +import com.etheller.warsmash.units.DataTable; +import com.etheller.warsmash.util.DataSourceFileHandle; +import com.etheller.warsmash.util.ImageUtils; +import com.etheller.warsmash.viewer5.Camera; +import com.etheller.warsmash.viewer5.CanvasProvider; +import com.etheller.warsmash.viewer5.Model; +import com.etheller.warsmash.viewer5.ModelInstance; +import com.etheller.warsmash.viewer5.ModelViewer; +import com.etheller.warsmash.viewer5.PathSolver; +import com.etheller.warsmash.viewer5.RenderBatch; +import com.etheller.warsmash.viewer5.Scene; +import com.etheller.warsmash.viewer5.SolvedPath; +import com.etheller.warsmash.viewer5.TextureMapper; +import com.etheller.warsmash.viewer5.handlers.ModelHandler; +import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; +import com.etheller.warsmash.viewer5.handlers.mdx.MdxHandler; +import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; +import com.etheller.warsmash.viewer5.handlers.mdx.MdxViewer; +import com.etheller.warsmash.viewer5.handlers.mdx.SequenceLoopMode; +import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; +import com.etheller.warsmash.viewer5.handlers.w3x.ui.MenuUI; + +public class WarsmashGdxMenuTestGame extends ApplicationAdapter implements CanvasProvider, InputProcessor { + private static final boolean ENABLE_AUDIO = true; + private static final boolean ENABLE_MUSIC = true; + private DataSource codebase; + private MdxViewer viewer; + private MdxModel model; + private CameraManager cameraManager; + private final Rectangle tempRect = new Rectangle(); + + // libGDX stuff + private OrthographicCamera uiCamera; + private BitmapFont font; + private BitmapFont font20; + private SpriteBatch batch; + private ExtendViewport uiViewport; + private GlyphLayout glyphLayout; + + private final DataTable warsmashIni; + private Scene uiScene; + private Texture solidGreenTexture; + private MenuUI menuUI; + + public WarsmashGdxMenuTestGame(final DataTable warsmashIni) { + this.warsmashIni = warsmashIni; + } + + @Override + public void create() { + + final ByteBuffer tempByteBuffer = ByteBuffer.allocateDirect(4); + tempByteBuffer.order(ByteOrder.LITTLE_ENDIAN); + final IntBuffer temp = tempByteBuffer.asIntBuffer(); +// + Gdx.gl30.glGenVertexArrays(1, temp); + WarsmashGdxGame.VAO = temp.get(0); + + Gdx.gl30.glBindVertexArray(WarsmashGdxGame.VAO); + + final String renderer = Gdx.gl.glGetString(GL20.GL_RENDERER); + System.err.println("Renderer: " + renderer); + + this.codebase = WarsmashGdxMapGame.parseDataSources(this.warsmashIni); + this.viewer = new MdxViewer(this.codebase, this); + + this.viewer.addHandler(new MdxHandler()); + this.viewer.enableAudio(); + + final Scene scene = this.viewer.addSimpleScene(); + scene.enableAudio(); + + this.uiScene = this.viewer.addSimpleScene(); + this.uiScene.alpha = true; + if (ENABLE_AUDIO) { + this.uiScene.enableAudio(); + } + final int width = Gdx.graphics.getWidth(); + final int height = Gdx.graphics.getHeight(); + + final FreeTypeFontGenerator fontGenerator = new FreeTypeFontGenerator( + new DataSourceFileHandle(this.viewer.dataSource, "fonts\\FRIZQT__.TTF")); + final FreeTypeFontParameter fontParam = new FreeTypeFontParameter(); + fontParam.size = 32; + this.font = fontGenerator.generateFont(fontParam); + fontParam.size = 20; + this.font20 = fontGenerator.generateFont(fontParam); + this.glyphLayout = new GlyphLayout(); + + // Constructs a new OrthographicCamera, using the given viewport width and + // height + // Height is multiplied by aspect ratio. + this.uiCamera = new OrthographicCamera(); + int aspect3By4Width; + int aspect3By4Height; + if (width < ((height * 4) / 3)) { + aspect3By4Width = width; + aspect3By4Height = (width * 3) / 4; + } + else { + aspect3By4Width = (height * 4) / 3; + aspect3By4Height = height; + } + this.uiViewport = new ExtendViewport(aspect3By4Width, aspect3By4Height, this.uiCamera); + this.uiViewport.update(width, height); + + this.uiCamera.position.set(this.uiViewport.getMinWorldWidth() / 2, this.uiViewport.getMinWorldHeight() / 2, 0); + this.uiCamera.update(); + + this.batch = new SpriteBatch(); + +// this.consoleUITexture = new Texture(new DataSourceFileHandle(this.viewer.dataSource, "AlphaUi.png")); + + this.solidGreenTexture = ImageUtils.getBLPTexture(this.viewer.dataSource, + "ReplaceableTextures\\TeamColor\\TeamColor06.blp"); + + Gdx.input.setInputProcessor(this); + + this.cameraManager = new CameraManager(); + this.cameraManager.setupCamera(scene); + +// this.mainModel = (MdxModel) this.viewer.load("Doodads\\Cinematic\\ArthasIllidanFight\\ArthasIllidanFight.mdx", +// this.mainModel = (MdxModel) this.viewer.load("UI\\Glues\\SinglePlayer\\NightElf_Exp\\NightElf_Exp.mdx", +// this.mainModel = (MdxModel) this.viewer.load("Abilities\\Spells\\Orc\\FeralSpirit\\feralspirittarget.mdx", +// new PathSolver() { +// @Override +// public SolvedPath solve(final String src, final Object solverParams) { +// return new SolvedPath(src, src.substring(src.lastIndexOf('.')), true); +// } +// }, null); + +// final EventObjectEmitterObject evt = this.mainModel.getEventObjects().get(1); +// for (final Sequence seq : this.mainModel.getSequences()) { +// System.out.println(seq.getName() + ": " + Arrays.toString(seq.getInterval())); +// } +// System.out.println(Arrays.toString(evt.keyFrames)); +// System.out.println(evt.name); + +// this.mainInstance = (MdxComplexInstance) this.mainModel.addInstance(0); + +// this.mainInstance.setScene(scene); +// +// final int animIndex = 0; +// this.modelCamera = this.mainModel.cameras.get(animIndex); +// this.mainInstance.setSequence(animIndex); +// +// this.mainInstance.setSequenceLoopMode(SequenceLoopMode.LOOP_TO_NEXT_ANIMATION); + +// acolytesHarvestingSceneJoke2(scene); + +// singleModelScene(scene, "Buildings\\Undead\\Necropolis\\Necropolis.mdx", "birth"); +// singleModelScene(scene, "Units\\Orc\\KotoBeast\\KotoBeast.mdx", "spell slam"); + + System.out.println("Loaded"); + Gdx.gl30.glClearColor(0.0f, 0.0f, 0.0f, 1); + + this.menuUI = new MenuUI(this.viewer.dataSource, this.uiViewport, fontGenerator, this.uiScene, this.viewer, + new RootFrameListener() { + @Override + public void onCreate(final GameUI rootFrame) { +// WarsmashGdxMapGame.this.viewer.setGameUI(rootFrame); + + if (ENABLE_MUSIC) { + final String musicField = rootFrame.getSkinField("GlueMusic_V1"); + final String[] musics = musicField.split(";"); + final String musicPath = musics[(int) (Math.random() * musics.length)]; + final Music music = Gdx.audio.newMusic(new DataSourceFileHandle( + WarsmashGdxMenuTestGame.this.viewer.dataSource, musicPath)); + music.setVolume(0.2f); + music.setLooping(true); + music.play(); + } + + singleModelScene(scene, + War3MapViewer.mdx(rootFrame.getSkinField("GlueSpriteLayerBackground_V1")), "Stand"); + WarsmashGdxMenuTestGame.this.modelCamera = WarsmashGdxMenuTestGame.this.mainModel.cameras + .get(0); + } + }); + + final ModelInstance libgdxContentInstance = new LibGDXContentLayerModel(null, this.viewer, "", + PathSolver.DEFAULT, "").addInstance(); + libgdxContentInstance.setLocation(0f, 0f, -0.5f); + libgdxContentInstance.setScene(this.uiScene); + this.menuUI.main(); + fontGenerator.dispose(); + + updateUIScene(); + + resize(width, height); + + } + + private void updateUIScene() { + this.tempRect.x = this.uiViewport.getScreenX(); + this.tempRect.y = this.uiViewport.getScreenY(); + this.tempRect.width = this.uiViewport.getScreenWidth(); + this.tempRect.height = this.uiViewport.getScreenHeight(); + this.uiScene.camera.viewport(this.tempRect); + final float worldWidth = this.uiViewport.getWorldWidth(); + final float worldHeight = this.uiViewport.getWorldHeight(); + final float xScale = worldWidth / this.uiViewport.getMinWorldWidth(); + final float yScale = worldHeight / this.uiViewport.getMinWorldHeight(); + final float uiSceneWidth = 0.8f * xScale; + final float uiSceneHeight = 0.6f * yScale; + final float uiSceneX = ((0.8f - uiSceneWidth) / 2); + final float uiSceneY = ((0.6f - uiSceneHeight) / 2); + this.uiScene.camera.ortho(uiSceneX, uiSceneWidth + uiSceneX, uiSceneY, uiSceneHeight + uiSceneY, -1024f, 1024); + } + + private void makeDruidSquare(final Scene scene) { + final MdxModel model2 = (MdxModel) this.viewer.load("units\\nightelf\\druidoftheclaw\\druidoftheclaw.mdx", + new PathSolver() { + @Override + public SolvedPath solve(final String src, final Object solverParams) { + return new SolvedPath(src, src.substring(src.lastIndexOf('.')), true); + } + }, null); + makePerfectSquare(scene, model2, 15); + } + + private void singleAcolyteScene(final Scene scene) { + final MdxModel model2 = (MdxModel) this.viewer.load("units\\undead\\acolyte\\acolyte.mdx", new PathSolver() { + @Override + public SolvedPath solve(final String src, final Object solverParams) { + return new SolvedPath(src, src.substring(src.lastIndexOf('.')), true); + } + }, null); + + final MdxComplexInstance instance3 = (MdxComplexInstance) model2.addInstance(0); + + instance3.setScene(scene); + + int animIndex = 0; + for (final Sequence s : model2.getSequences()) { + if (s.getName().toLowerCase().startsWith("stand work")) { + animIndex = model2.getSequences().indexOf(s); + } + } + instance3.setSequence(animIndex); + + instance3.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + } + + private void singleModelScene(final Scene scene, final String path, final String animName) { + final MdxModel model2 = (MdxModel) this.viewer.load(path, new PathSolver() { + @Override + public SolvedPath solve(final String src, final Object solverParams) { + return new SolvedPath(src, src.substring(src.lastIndexOf('.')), true); + } + }, null); + + final MdxComplexInstance instance3 = (MdxComplexInstance) model2.addInstance(0); + + instance3.setScene(scene); + + int animIndex = 0; + for (final Sequence s : model2.getSequences()) { + if (s.getName().toLowerCase().startsWith(animName)) { + animIndex = model2.getSequences().indexOf(s); + break; + } + } + instance3.setSequence(animIndex); + + instance3.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + this.mainInstance = instance3; + this.mainModel = model2; + } + + private void acolytesHarvestingScene(final Scene scene) { + + final MdxModel acolyteModel = (MdxModel) this.viewer.load("units\\undead\\acolyte\\acolyte.mdx", + new PathSolver() { + @Override + public SolvedPath solve(final String src, final Object solverParams) { + return new SolvedPath(src, src.substring(src.lastIndexOf('.')), true); + } + }, null); + final MdxModel mineEffectModel = (MdxModel) this.viewer + .load("abilities\\spells\\undead\\undeadmine\\undeadminecircle.mdx", new PathSolver() { + @Override + public SolvedPath solve(final String src, final Object solverParams) { + return new SolvedPath(src, src.substring(src.lastIndexOf('.')), true); + } + }, null); + for (int i = 0; i < 5; i++) { + final MdxComplexInstance acolyteInstance = (MdxComplexInstance) acolyteModel.addInstance(0); + + acolyteInstance.setScene(scene); + + int animIndex = i % acolyteModel.getSequences().size(); + for (final Sequence s : acolyteModel.getSequences()) { + if (s.getName().toLowerCase().startsWith("stand work")) { + animIndex = acolyteModel.getSequences().indexOf(s); + } + } + acolyteInstance.setSequence(animIndex); + + acolyteInstance.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + + final double angle = ((Math.PI * 2) / 5) * i; + acolyteInstance.localLocation.x = (float) Math.cos(angle) * 256; + acolyteInstance.localLocation.y = (float) Math.sin(angle) * 256; + acolyteInstance.localRotation.setFromAxisRad(0, 0, 1, (float) (angle + Math.PI)); + + final MdxComplexInstance effectInstance = (MdxComplexInstance) mineEffectModel.addInstance(0); + + effectInstance.setScene(scene); + + effectInstance.setSequence(1); + + effectInstance.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + effectInstance.localLocation.x = (float) Math.cos(angle) * 256; + effectInstance.localLocation.y = (float) Math.sin(angle) * 256; + effectInstance.localRotation.setFromAxisRad(0, 0, 1, (float) (angle)); + + } + final MdxModel mineModel = (MdxModel) this.viewer.load("buildings\\undead\\hauntedmine\\hauntedmine.mdx", + new PathSolver() { + @Override + public SolvedPath solve(final String src, final Object solverParams) { + return new SolvedPath(src, src.substring(src.lastIndexOf('.')), true); + } + }, null); + final MdxComplexInstance mineInstance = (MdxComplexInstance) mineModel.addInstance(0); + + mineInstance.setScene(scene); + + mineInstance.setSequence(2); + + mineInstance.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + } + + private void acolytesHarvestingSceneJoke2(final Scene scene) { + + final MdxModel acolyteModel = (MdxModel) this.viewer.load("units\\undead\\acolyte\\acolyte.mdx", + new PathSolver() { + @Override + public SolvedPath solve(final String src, final Object solverParams) { + return new SolvedPath(src, src.substring(src.lastIndexOf('.')), true); + } + }, null); + final MdxModel mineEffectModel = (MdxModel) this.viewer + .load("abilities\\spells\\undead\\undeadmine\\undeadminecircle.mdx", new PathSolver() { + @Override + public SolvedPath solve(final String src, final Object solverParams) { + return new SolvedPath(src, src.substring(src.lastIndexOf('.')), true); + } + }, null); + for (int i = 0; i < 5; i++) { + final MdxComplexInstance acolyteInstance = (MdxComplexInstance) acolyteModel.addInstance(0); + + acolyteInstance.setScene(scene); + + int animIndex = i % acolyteModel.getSequences().size(); + for (final Sequence s : acolyteModel.getSequences()) { + if (s.getName().toLowerCase().startsWith("stand work")) { + animIndex = acolyteModel.getSequences().indexOf(s); + } + } + acolyteInstance.setSequence(animIndex); + + acolyteInstance.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + + final double angle = ((Math.PI * 2) / 5) * i; + acolyteInstance.localLocation.x = (float) Math.cos(angle) * 256; + acolyteInstance.localLocation.y = (float) Math.sin(angle) * 256; + acolyteInstance.localRotation.setFromAxisRad(0, 0, 1, (float) (angle + Math.PI)); + + final MdxComplexInstance effectInstance = (MdxComplexInstance) mineEffectModel.addInstance(0); + + effectInstance.setScene(scene); + + effectInstance.setSequence(1); + + effectInstance.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + effectInstance.localLocation.x = (float) Math.cos(angle) * 256; + effectInstance.localLocation.y = (float) Math.sin(angle) * 256; + effectInstance.localRotation.setFromAxisRad(0, 0, 1, (float) (angle)); + + } + final MdxModel mineModel = (MdxModel) this.viewer.load("units\\orc\\spiritwolf\\spiritwolf.mdx", + new PathSolver() { + @Override + public SolvedPath solve(final String src, final Object solverParams) { + return new SolvedPath(src, src.substring(src.lastIndexOf('.')), true); + } + }, null); + final MdxComplexInstance mineInstance = (MdxComplexInstance) mineModel.addInstance(0); + + mineInstance.setScene(scene); + + mineInstance.setSequence(0); + mineInstance.localScale.x = 2; + mineInstance.localScale.y = 2; + mineInstance.localScale.z = 2; + + mineInstance.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + final MdxModel mineModel2 = (MdxModel) this.viewer + .load("abilities\\spells\\undead\\unsummon\\unsummontarget.mdx", new PathSolver() { + @Override + public SolvedPath solve(final String src, final Object solverParams) { + return new SolvedPath(src, src.substring(src.lastIndexOf('.')), true); + } + }, null); + final MdxComplexInstance mineInstance2 = (MdxComplexInstance) mineModel2.addInstance(0); + + mineInstance2.setScene(scene); + + mineInstance2.setSequence(0); + + mineInstance2.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + } + + private void makeFourHundred(final Scene scene, final MdxModel model2) { + for (int i = 0; i < 400; i++) { + final MdxComplexInstance instance3 = (MdxComplexInstance) model2.addInstance(0); + instance3.localLocation.x = (((i % 20) - 10) * 128); + instance3.localLocation.y = (((i / 20) - 10) * 128); + + instance3.setScene(scene); + + final int animIndex = i % model2.getSequences().size(); + instance3.setSequence(animIndex); + + instance3.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + } + } + + private void makePerfectSquare(final Scene scene, final MdxModel model2, final int n) { + final int n2 = n * n; + for (int i = 0; i < n2; i++) { + final MdxComplexInstance instance3 = (MdxComplexInstance) model2.addInstance(0); + instance3.localLocation.x = (((i % n) - (n / 2)) * 128); + instance3.localLocation.y = (((i / n) - (n / 2)) * 128); + + instance3.setScene(scene); + + final int animIndex = i % model2.getSequences().size(); + instance3.setSequence(animIndex); + + instance3.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + } + } + + public static void bindDefaultVertexArray() { + Gdx.gl30.glBindVertexArray(WarsmashGdxGame.VAO); + } + + private final int frame = 0; + private MdxComplexInstance mainInstance; + private MdxModel mainModel; + private com.etheller.warsmash.viewer5.handlers.mdx.Camera modelCamera; + private final float[] cameraPositionTemp = new float[3]; + private final float[] cameraTargetTemp = new float[3]; + private final boolean firstFrame = true; + + @Override + public void render() { + + Gdx.gl30.glEnable(GL30.GL_SCISSOR_TEST); + final float deltaTime = Gdx.graphics.getDeltaTime(); + Gdx.gl30.glBindVertexArray(WarsmashGdxGame.VAO); + this.cameraManager.updateCamera(); + this.menuUI.update(deltaTime); + this.viewer.updateAndRender(); + + Gdx.gl30.glDisable(GL30.GL_SCISSOR_TEST); + + Gdx.gl30.glDisable(GL30.GL_CULL_FACE); + + this.viewer.webGL.useShaderProgram(null); + + Gdx.gl30.glActiveTexture(GL30.GL_TEXTURE0); + } + + @Override + public void dispose() { + } + + @Override + public float getWidth() { + return Gdx.graphics.getWidth(); + } + + @Override + public float getHeight() { + return Gdx.graphics.getHeight(); + } + + @Override + public void resize(final int width, final int height) { + this.tempRect.width = width; + this.tempRect.height = height; + this.cameraManager.camera.viewport(this.tempRect); + + super.resize(width, height); + + this.uiViewport.update(width, height); + this.uiCamera.position.set(this.uiViewport.getMinWorldWidth() / 2, this.uiViewport.getMinWorldHeight() / 2, 0); + + this.menuUI.resize(); + updateUIScene(); + + } + + class CameraManager { + private CanvasProvider canvas; + private Camera camera; + private float moveSpeed; + private float rotationSpeed; + private float zoomFactor; + private float horizontalAngle; + private float verticalAngle; + private float distance; + private Vector3 position; + private Vector3 target; + private Vector3 worldUp; + private Vector3 vecHeap; + private Vector3 vecHeap2; + private Quaternion quatHeap; + private Quaternion quatHeap2; + + // An orbit camera setup example. + // Left mouse button controls the orbit itself. + // The right mouse button allows to move the camera and the point it's looking + // at on the XY plane. + // Scrolling zooms in and out. + private void setupCamera(final Scene scene) { + this.canvas = scene.viewer.canvas; + this.camera = scene.camera; + this.moveSpeed = 2; + this.rotationSpeed = (float) (Math.PI / 180); + this.zoomFactor = 0.1f; + this.horizontalAngle = (float) (Math.PI / 2); + this.verticalAngle = (float) (Math.PI / 4); + this.distance = 500; + this.position = new Vector3(); + this.target = new Vector3(0, 0, 50); + this.worldUp = new Vector3(0, 0, 1); + this.vecHeap = new Vector3(); + this.vecHeap2 = new Vector3(); + this.quatHeap = new Quaternion(); + this.quatHeap2 = new Quaternion(); + + updateCamera(); + +// cameraUpdate(); + } + + private void updateCamera() { + // Limit the vertical angle so it doesn't flip. + // Since the camera uses a quaternion, flips don't matter to it, but this feels + // better. + this.verticalAngle = (float) Math.min(Math.max(0.01, this.verticalAngle), Math.PI - 0.01); + + this.quatHeap.idt(); + this.quatHeap.setFromAxisRad(0, 0, 1, this.horizontalAngle); + this.quatHeap2.idt(); + this.quatHeap2.setFromAxisRad(1, 0, 0, this.verticalAngle); + this.quatHeap.mul(this.quatHeap2); + + this.position.set(0, 0, 1); + this.quatHeap.transform(this.position); + this.position.scl(this.distance); + this.position = this.position.add(this.target); + if (WarsmashGdxMenuTestGame.this.modelCamera != null) { + WarsmashGdxMenuTestGame.this.modelCamera.getPositionTranslation( + WarsmashGdxMenuTestGame.this.cameraPositionTemp, + WarsmashGdxMenuTestGame.this.mainInstance.sequence, + WarsmashGdxMenuTestGame.this.mainInstance.frame, + WarsmashGdxMenuTestGame.this.mainInstance.counter); + WarsmashGdxMenuTestGame.this.modelCamera.getTargetTranslation( + WarsmashGdxMenuTestGame.this.cameraTargetTemp, + WarsmashGdxMenuTestGame.this.mainInstance.sequence, + WarsmashGdxMenuTestGame.this.mainInstance.frame, + WarsmashGdxMenuTestGame.this.mainInstance.counter); + + this.position.set(WarsmashGdxMenuTestGame.this.modelCamera.position); + this.target.set(WarsmashGdxMenuTestGame.this.modelCamera.targetPosition); +// this.vecHeap2.set(this.target); +// this.vecHeap2.sub(this.position); +// this.vecHeap.set(this.vecHeap2); +// this.vecHeap.crs(this.worldUp); +// this.vecHeap.crs(this.vecHeap2); +// this.vecHeap.nor(); +// this.vecHeap.scl(this.camera.rect.height / 2f); +// this.position.add(this.vecHeap); + + this.position.add(WarsmashGdxMenuTestGame.this.cameraPositionTemp[0], + WarsmashGdxMenuTestGame.this.cameraPositionTemp[1], + WarsmashGdxMenuTestGame.this.cameraPositionTemp[2]); + this.target.add(WarsmashGdxMenuTestGame.this.cameraTargetTemp[0], + WarsmashGdxMenuTestGame.this.cameraTargetTemp[1], + WarsmashGdxMenuTestGame.this.cameraTargetTemp[2]); + this.camera.perspective(WarsmashGdxMenuTestGame.this.modelCamera.fieldOfView * 0.75f, + Gdx.graphics.getWidth() / (float) Gdx.graphics.getHeight(), + WarsmashGdxMenuTestGame.this.modelCamera.nearClippingPlane, + WarsmashGdxMenuTestGame.this.modelCamera.farClippingPlane); + } + else { + this.camera.perspective(70, this.camera.getAspect(), 100, 5000); + } + + this.camera.moveToAndFace(this.position, this.target, this.worldUp); + } + +// private void cameraUpdate() { +// +// } + } + + public DataSource getCodebase() { + return this.codebase; + } + + @Override + public boolean keyDown(final int keycode) { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean keyUp(final int keycode) { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean keyTyped(final char character) { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean touchDown(final int screenX, final int screenY, final int pointer, final int button) { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean touchUp(final int screenX, final int screenY, final int pointer, final int button) { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean touchDragged(final int screenX, final int screenY, final int pointer) { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean mouseMoved(final int screenX, final int screenY) { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean scrolled(final int amount) { + // TODO Auto-generated method stub + return false; + } + + private void renderLibGDXContent() { + + Gdx.gl30.glDisable(GL30.GL_SCISSOR_TEST); + + Gdx.gl30.glDisable(GL30.GL_CULL_FACE); + + this.viewer.webGL.useShaderProgram(null); + + Gdx.gl30.glActiveTexture(GL30.GL_TEXTURE0); + + this.uiViewport.apply(); + this.batch.setProjectionMatrix(this.uiCamera.combined); + this.batch.begin(); + this.menuUI.render(this.batch, this.font20, this.glyphLayout); + this.font.setColor(Color.YELLOW); + final String fpsString = "FPS: " + Gdx.graphics.getFramesPerSecond(); + this.glyphLayout.setText(this.font, fpsString); + if (false) { + this.font.draw(this.batch, fpsString, (this.uiViewport.getMinWorldWidth() - this.glyphLayout.width) / 2, + 1100 * this.menuUI.getHeightRatioCorrection()); + } + this.batch.end(); + + Gdx.gl30.glEnable(GL30.GL_SCISSOR_TEST); + Gdx.gl30.glBindVertexArray(WarsmashGdxGame.VAO); + } + + private class LibGDXContentLayerModelInstance extends ModelInstance { + + public LibGDXContentLayerModelInstance(final Model model) { + super(model); + } + + @Override + public void updateAnimations(final float dt) { + + } + + @Override + public void clearEmittedObjects() { + + } + + @Override + protected void updateLights(final Scene scene2) { + + } + + @Override + public void renderOpaque(final Matrix4 mvp) { + + } + + @Override + public void renderTranslucent() { + renderLibGDXContent(); + } + + @Override + public void load() { + } + + @Override + protected RenderBatch getBatch(final TextureMapper textureMapper2) { + throw new UnsupportedOperationException("NOT API"); + } + + @Override + public void setReplaceableTexture(final int replaceableTextureId, final String replaceableTextureFile) { + + } + + @Override + public boolean isBatched() { + return super.isBatched(); + } + + @Override + protected void removeLights(final Scene scene2) { + // TODO Auto-generated method stub + + } + + } + + private class LibGDXContentLayerModel extends Model { + + public LibGDXContentLayerModel(final ModelHandler handler, final ModelViewer viewer, final String extension, + final PathSolver pathSolver, final String fetchUrl) { + super(handler, viewer, extension, pathSolver, fetchUrl); + this.ok = true; + } + + @Override + protected ModelInstance createInstance(final int type) { + return new LibGDXContentLayerModelInstance(this); + } + + @Override + protected void lateLoad() { + } + + @Override + protected void load(final InputStream src, final Object options) { + } + + @Override + protected void error(final Exception e) { + } + + } +} diff --git a/core/src/com/etheller/warsmash/WarsmashPreviewApplication.java b/core/src/com/etheller/warsmash/WarsmashPreviewApplication.java new file mode 100644 index 0000000..3809063 --- /dev/null +++ b/core/src/com/etheller/warsmash/WarsmashPreviewApplication.java @@ -0,0 +1,175 @@ +package com.etheller.warsmash; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.IntBuffer; + +import com.badlogic.gdx.ApplicationAdapter; +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.graphics.GL20; +import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.badlogic.gdx.math.Rectangle; +import com.etheller.warsmash.datasources.DataSource; +import com.etheller.warsmash.parsers.mdlx.MdlxModel; +import com.etheller.warsmash.units.DataTable; +import com.etheller.warsmash.viewer5.CanvasProvider; +import com.etheller.warsmash.viewer5.ModelViewer; +import com.etheller.warsmash.viewer5.PathSolver; +import com.etheller.warsmash.viewer5.Scene; +import com.etheller.warsmash.viewer5.handlers.ResourceHandlerConstructionParams; +import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; +import com.etheller.warsmash.viewer5.handlers.mdx.MdxHandler; +import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; +import com.etheller.warsmash.viewer5.handlers.mdx.MdxViewer; +import com.etheller.warsmash.viewer5.handlers.w3x.camera.PortraitCameraManager; + +public class WarsmashPreviewApplication extends ApplicationAdapter implements CanvasProvider { + private DataSource codebase; + private ModelViewer viewer; + private MdxModel model; + private PortraitCameraManager cameraManager; + public static int VAO; + private final Rectangle tempRect = new Rectangle(); + + private BitmapFont font; + private SpriteBatch batch; + private final DataTable warsmashIni; + + public WarsmashPreviewApplication(final DataTable warsmashIni) { + this.warsmashIni = warsmashIni; + } + + @Override + public void create() { + + final ByteBuffer tempByteBuffer = ByteBuffer.allocateDirect(4); + tempByteBuffer.order(ByteOrder.LITTLE_ENDIAN); + final IntBuffer temp = tempByteBuffer.asIntBuffer(); +// + Gdx.gl30.glGenVertexArrays(1, temp); + VAO = temp.get(0); + + Gdx.gl30.glBindVertexArray(VAO); + + final String renderer = Gdx.gl.glGetString(GL20.GL_RENDERER); + System.err.println("Renderer: " + renderer); + + this.codebase = WarsmashGdxMapGame.parseDataSources(this.warsmashIni); + this.viewer = new MdxViewer(this.codebase, this); + + this.mdxHandler = new MdxHandler(); + this.viewer.addHandler(this.mdxHandler); + this.viewer.enableAudio(); + + this.scene = this.viewer.addSimpleScene(); + this.scene.enableAudio(); + + this.cameraManager = new PortraitCameraManager(); + this.cameraManager.setupCamera(this.scene); + this.cameraManager.distance = 500; + this.cameraManager.horizontalAngle = (float) ((Math.PI) / 2); + + System.out.println("Loaded"); + Gdx.gl30.glClearColor(0.5f, 0.5f, 0.5f, 1); // TODO remove white background + + this.font = new BitmapFont(); + this.batch = new SpriteBatch(); + } + + public static void bindDefaultVertexArray() { + Gdx.gl30.glBindVertexArray(VAO); + } + + private int frame = 0; + private MdxComplexInstance mainInstance; + private MdxModel mainModel; + private com.etheller.warsmash.viewer5.handlers.mdx.Camera modelCamera; + private final float[] cameraPositionTemp = new float[3]; + private final float[] cameraTargetTemp = new float[3]; + private final boolean firstFrame = true; + private Scene scene; + private MdxHandler mdxHandler; + + @Override + public void render() { + Gdx.gl30.glBindVertexArray(VAO); + this.cameraManager.updateCamera(); + this.viewer.updateAndRender(); + + this.frame++; + if ((this.frame % 1000) == 0) { + System.out.println(Integer.toString(Gdx.graphics.getFramesPerSecond())); + } + + } + + @Override + public void dispose() { + } + + @Override + public float getWidth() { + return Gdx.graphics.getWidth(); + } + + @Override + public float getHeight() { + return Gdx.graphics.getHeight(); + } + + @Override + public void resize(final int width, final int height) { + this.tempRect.width = width; + this.tempRect.height = height; + this.cameraManager.camera.viewport(this.tempRect); + } + + public DataSource getCodebase() { + return this.codebase; + } + + public MdlxModel loadCustomModel(final String filename) { + clearMainInstance(); + final MdxModel mdx = (MdxModel) this.mdxHandler.construct(new ResourceHandlerConstructionParams(this.viewer, + this.mdxHandler, ".mdx", PathSolver.DEFAULT, filename)); + final MdlxModel mdlxModel; + try (FileInputStream stream = new FileInputStream(filename)) { + mdlxModel = new MdlxModel(stream); + mdx.load(mdlxModel); + mdx.ok = true; +// mdx.lateLoad(); + } + catch (final FileNotFoundException e) { + throw new RuntimeException(e); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + this.mainModel = mdx; + final MdxComplexInstance instance = (MdxComplexInstance) mdx.addInstance(); +// this.cameraManager.setModelInstance(instance, mdx); + instance.setScene(this.scene); + this.mainInstance = instance; + return mdlxModel; + } + + private void clearMainInstance() { + if (this.mainInstance != null) { + this.mainInstance.detach(); + this.mainInstance = null; + } + this.mainModel = null; + } + + public PortraitCameraManager getCameraManager() { + return this.cameraManager; + } + + public MdxComplexInstance getMainInstance() { + return this.mainInstance; + } +} diff --git a/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java index 63b2fe8..c512c03 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java @@ -4,12 +4,15 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.util.EnumSet; import java.util.HashMap; import java.util.Map; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.g2d.GlyphLayout; +import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator; import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator.FreeTypeFontParameter; import com.badlogic.gdx.utils.viewport.ExtendViewport; @@ -19,14 +22,17 @@ import com.etheller.warsmash.datasources.DataSource; import com.etheller.warsmash.fdfparser.FDFParser; import com.etheller.warsmash.fdfparser.FrameDefinitionVisitor; import com.etheller.warsmash.parsers.fdf.datamodel.AnchorDefinition; +import com.etheller.warsmash.parsers.fdf.datamodel.BackdropCornerFlags; import com.etheller.warsmash.parsers.fdf.datamodel.FontDefinition; import com.etheller.warsmash.parsers.fdf.datamodel.FrameClass; import com.etheller.warsmash.parsers.fdf.datamodel.FrameDefinition; import com.etheller.warsmash.parsers.fdf.datamodel.FrameTemplateEnvironment; import com.etheller.warsmash.parsers.fdf.datamodel.SetPointDefinition; import com.etheller.warsmash.parsers.fdf.datamodel.TextJustify; +import com.etheller.warsmash.parsers.fdf.datamodel.Vector2Definition; import com.etheller.warsmash.parsers.fdf.datamodel.Vector4Definition; import com.etheller.warsmash.parsers.fdf.frames.AbstractUIFrame; +import com.etheller.warsmash.parsers.fdf.frames.BackdropFrame; import com.etheller.warsmash.parsers.fdf.frames.FilterModeTextureFrame; import com.etheller.warsmash.parsers.fdf.frames.SetPoint; import com.etheller.warsmash.parsers.fdf.frames.SimpleFrame; @@ -41,15 +47,15 @@ import com.etheller.warsmash.units.Element; import com.etheller.warsmash.util.ImageUtils; import com.etheller.warsmash.util.StringBundle; import com.etheller.warsmash.viewer5.Scene; +import com.etheller.warsmash.viewer5.handlers.AbstractMdxModelViewer; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; -import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; public final class GameUI extends AbstractUIFrame implements UIFrame { private final DataSource dataSource; private final Element skin; private final Viewport viewport; private final Scene uiScene; - private final War3MapViewer modelViewer; + private final AbstractMdxModelViewer modelViewer; private final FrameTemplateEnvironment templates; private final Map pathToTexture = new HashMap<>(); private final boolean autoPosition = false; @@ -58,9 +64,10 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { private final Map nameToFrame = new HashMap<>(); private final Viewport fdfCoordinateResolutionDummyViewport; private final DataTable skinData; + private final Element errorStrings; public GameUI(final DataSource dataSource, final Element skin, final Viewport viewport, - final FreeTypeFontGenerator fontGenerator, final Scene uiScene, final War3MapViewer modelViewer) { + final FreeTypeFontGenerator fontGenerator, final Scene uiScene, final AbstractMdxModelViewer modelViewer) { super("GameUI", null); this.dataSource = dataSource; this.skin = skin; @@ -95,6 +102,7 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { catch (final IOException e) { throw new RuntimeException(e); } + this.errorStrings = this.skinData.get("Errors"); } public static Element loadSkin(final DataSource dataSource, final String skin) { @@ -220,6 +228,15 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { add(inflated); return inflated; } + else if ("FRAME".equals(frameDefinition.getFrameType())) { + final UIFrame inflated = inflate(frameDefinition, owner, null, + frameDefinition.has("DecorateFileNames")); + if (this.autoPosition) { + inflated.positionBounds(this, this.viewport); + } + add(inflated); + return inflated; + } } throw new UnsupportedOperationException("Not yet implemented"); } @@ -264,7 +281,7 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { final TextJustify justifyH, final TextJustify justifyV, final float fdfFontSize) { this.fontParam.size = (int) convertY(this.viewport, fdfFontSize); if (this.fontParam.size == 0) { - this.fontParam.size = 24; + this.fontParam.size = 128; } final BitmapFont frameFont = this.fontGenerator.generateFont(this.fontParam); final StringFrame stringFrame = new StringFrame(name, parent, color, justifyH, justifyV, frameFont); @@ -309,19 +326,170 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { viewport2); String backgroundArt = frameDefinition.getString("BackgroundArt"); if (frameDefinition.has("DecorateFileNames") || inDecorateFileNames) { - if (this.skin.hasField(backgroundArt)) { - backgroundArt = this.skin.getField(backgroundArt); - } - else { - throw new IllegalStateException("Decorated file name lookup not available: " + backgroundArt); + if (backgroundArt != null) { + if (this.skin.hasField(backgroundArt)) { + backgroundArt = this.skin.getField(backgroundArt); + } + else { + throw new IllegalStateException( + "Decorated file name lookup not available: " + backgroundArt); + } } } if (backgroundArt != null) { setSpriteFrameModel(spriteFrame, backgroundArt); } - viewport2 = this.fdfCoordinateResolutionDummyViewport; + viewport2 = this.viewport; // TODO was fdfCoordinateResolutionDummyViewport here previously, but is that + // a good idea? inflatedFrame = spriteFrame; } + else if ("FRAME".equals(frameDefinition.getFrameType())) { + final SimpleFrame simpleFrame = new SimpleFrame(frameDefinition.getName(), parent); + // TODO: we should not need to put ourselves in this map 2x, but we do + // since there are nested inflate calls happening before the general case + // mapping + this.nameToFrame.put(frameDefinition.getName(), simpleFrame); + for (final FrameDefinition childDefinition : frameDefinition.getInnerFrames()) { + simpleFrame.add(inflate(childDefinition, simpleFrame, frameDefinition, + inDecorateFileNames || childDefinition.has("DecorateFileNames"))); + } + inflatedFrame = simpleFrame; + } + else if ("TEXT".equals(frameDefinition.getFrameType())) { + final Float textLength = frameDefinition.getFloat("TextLength"); + TextJustify justifyH = frameDefinition.getTextJustify("FontJustificationH"); + if (justifyH == null) { + justifyH = TextJustify.CENTER; + } + TextJustify justifyV = frameDefinition.getTextJustify("FontJustificationV"); + if (justifyV == null) { + justifyV = TextJustify.MIDDLE; + } + + Color fontColor; + final Vector4Definition fontColorDefinition = frameDefinition.getVector4("FontColor"); + if (fontColorDefinition == null) { + fontColor = Color.WHITE; + } + else { + fontColor = new Color(fontColorDefinition.getX(), fontColorDefinition.getY(), + fontColorDefinition.getZ(), fontColorDefinition.getW()); + } + + Color fontShadowColor; + final Vector4Definition fontShadowColorDefinition = frameDefinition.getVector4("FontShadowColor"); + if (fontShadowColorDefinition == null) { + fontShadowColor = null; + } + else { + fontShadowColor = new Color(fontShadowColorDefinition.getX(), fontShadowColorDefinition.getY(), + fontShadowColorDefinition.getZ(), fontShadowColorDefinition.getW()); + } + final FontDefinition font = frameDefinition.getFont("FrameFont"); + final Float height = frameDefinition.getFloat("Height"); + this.fontParam.size = (int) convertY(viewport2, + font == null ? (height == null ? 0.06f : height) : font.getFontSize()); + if (this.fontParam.size == 0) { + this.fontParam.size = 24; + } + frameFont = this.fontGenerator.generateFont(this.fontParam); + final StringFrame stringFrame = new StringFrame(frameDefinition.getName(), parent, fontColor, justifyH, + justifyV, frameFont); + if (fontShadowColor != null) { + final Vector2Definition shadowOffset = frameDefinition.getVector2("FontShadowOffset"); + stringFrame.setFontShadowColor(fontShadowColor); + stringFrame.setFontShadowOffsetX(convertX(viewport2, shadowOffset.getX())); + stringFrame.setFontShadowOffsetY(convertY(viewport2, shadowOffset.getY())); + } + inflatedFrame = stringFrame; + String text = frameDefinition.getString("Text"); + if (text != null) { + final String decoratedString = this.templates.getDecoratedString(text); + if (decoratedString != text) { + text = decoratedString; + } + stringFrame.setText(text); + } + } + else if ("GLUETEXTBUTTON".equals(frameDefinition.getFrameType())) { + // ButtonText & ControlBackdrop + final SimpleFrame simpleFrame = new SimpleFrame(frameDefinition.getName(), parent); + // TODO: we should not need to put ourselves in this map 2x, but we do + // since there are nested inflate calls happening before the general case + // mapping + this.nameToFrame.put(frameDefinition.getName(), simpleFrame); + final String buttonTextKey = frameDefinition.getString("ButtonText"); + final String controlBackdropKey = frameDefinition.getString("ControlBackdrop"); + for (final FrameDefinition childDefinition : frameDefinition.getInnerFrames()) { + if (childDefinition.getName().equals(buttonTextKey) + || childDefinition.getName().equals(controlBackdropKey)) { + final UIFrame inflatedChild = inflate(childDefinition, simpleFrame, frameDefinition, + inDecorateFileNames || childDefinition.has("DecorateFileNames")); + inflatedChild.setSetAllPoints(true); + simpleFrame.add(inflatedChild); + } + } + inflatedFrame = simpleFrame; + } + else if ("GLUEBUTTON".equals(frameDefinition.getFrameType())) { + // ButtonText & ControlBackdrop + final SimpleFrame simpleFrame = new SimpleFrame(frameDefinition.getName(), parent); + // TODO: we should not need to put ourselves in this map 2x, but we do + // since there are nested inflate calls happening before the general case + // mapping + this.nameToFrame.put(frameDefinition.getName(), simpleFrame); + final String controlBackdropKey = frameDefinition.getString("ControlBackdrop"); + for (final FrameDefinition childDefinition : frameDefinition.getInnerFrames()) { + if (childDefinition.getName().equals(controlBackdropKey)) { + final UIFrame inflatedChild = inflate(childDefinition, simpleFrame, frameDefinition, + inDecorateFileNames || childDefinition.has("DecorateFileNames")); + inflatedChild.setSetAllPoints(true); + simpleFrame.add(inflatedChild); + } + } + inflatedFrame = simpleFrame; + } + else if ("BACKDROP".equals(frameDefinition.getFrameType())) { + final boolean tileBackground = frameDefinition.has("BackdropTileBackground"); + final String backgroundString = frameDefinition.getString("BackdropBackground"); + String cornerFlagsString = frameDefinition.getString("BackdropCornerFlags"); + if (cornerFlagsString == null) { + cornerFlagsString = ""; + } + final EnumSet cornerFlags = BackdropCornerFlags + .parseCornerFlags(cornerFlagsString); + final Float cornerSizeNullable = frameDefinition.getFloat("BackdropCornerSize"); + final float cornerSize = GameUI.convertX(viewport2, + cornerSizeNullable == null ? 0.0f : cornerSizeNullable); + final Float backgroundSizeNullable = frameDefinition.getFloat("BackdropBackgroundSize"); + final float backgroundSize = GameUI.convertX(viewport2, + backgroundSizeNullable == null ? 0.0f : backgroundSizeNullable); + Vector4Definition backgroundInsets = frameDefinition.getVector4("BackdropBackgroundInsets"); + if (backgroundInsets != null) { + backgroundInsets.setX(GameUI.convertX(viewport2, backgroundInsets.getX())); + backgroundInsets.setY(GameUI.convertY(viewport2, backgroundInsets.getY())); + backgroundInsets.setZ(GameUI.convertX(viewport2, backgroundInsets.getZ())); + backgroundInsets.setW(GameUI.convertY(viewport2, backgroundInsets.getW())); + } + else { + backgroundInsets = new Vector4Definition(0, 0, 0, 0); + } + final String edgeFileString = frameDefinition.getString("BackdropEdgeFile"); + System.out.println(frameDefinition.getName() + " wants edge file: " + edgeFileString); + final Texture background = backgroundString == null ? null : loadTexture(backgroundString); + final Texture edgeFile = edgeFileString == null ? null : loadTexture(edgeFileString); + System.out.println(frameDefinition.getName() + " got edge file: " + edgeFile); + + final BackdropFrame backdropFrame = new BackdropFrame(frameDefinition.getName(), parent, + inDecorateFileNames || frameDefinition.has("DecorateFileNames"), tileBackground, background, + cornerFlags, cornerSize, backgroundSize, backgroundInsets, edgeFile); + this.nameToFrame.put(frameDefinition.getName(), backdropFrame); + for (final FrameDefinition childDefinition : frameDefinition.getInnerFrames()) { + backdropFrame.add(inflate(childDefinition, backdropFrame, frameDefinition, + inDecorateFileNames || childDefinition.has("DecorateFileNames"))); + } + inflatedFrame = backdropFrame; + } break; case Layer: final SimpleFrame simpleFrame = new SimpleFrame(frameDefinition.getName(), parent); @@ -407,14 +575,19 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { inflatedFrame.setHeight(convertY(viewport2, frameDefinition.getFont("Font").getFontSize())); } for (final AnchorDefinition anchor : frameDefinition.getAnchors()) { - inflatedFrame.addAnchor(new AnchorDefinition(anchor.getMyPoint(), convertX(viewport2, anchor.getX()), - convertY(viewport2, anchor.getY()))); + inflatedFrame.addAnchor(new AnchorDefinition(anchor.getMyPoint(), + convertX(this.viewport, anchor.getX()), convertY(this.viewport, anchor.getY()))); } for (final SetPointDefinition setPointDefinition : frameDefinition.getSetPoints()) { - inflatedFrame.addSetPoint(new SetPoint(setPointDefinition.getMyPoint(), - getFrameByName(setPointDefinition.getOther(), 0 /* TODO: createContext */), - setPointDefinition.getOtherPoint(), convertX(viewport2, setPointDefinition.getX()), - convertY(viewport2, setPointDefinition.getY()))); + final UIFrame otherFrameByName = getFrameByName(setPointDefinition.getOther(), + 0 /* TODO: createContext */); + if (otherFrameByName == null) { + throw new IllegalStateException("Failing to pin " + frameDefinition.getName() + " to " + + setPointDefinition.getOther() + " because it was null!"); + } + inflatedFrame.addSetPoint(new SetPoint(setPointDefinition.getMyPoint(), otherFrameByName, + setPointDefinition.getOtherPoint(), convertX(this.viewport, setPointDefinition.getX()), + convertY(this.viewport, setPointDefinition.getY()))); } this.nameToFrame.put(frameDefinition.getName(), inflatedFrame); } @@ -498,6 +671,11 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { innerPositionBounds(this, viewport); } + @Override + protected void internalRender(final SpriteBatch batch, final BitmapFont baseFont, final GlyphLayout glyphLayout) { + super.internalRender(batch, baseFont, glyphLayout); + } + @Override public void add(final UIFrame childFrame) { super.add(childFrame); @@ -511,4 +689,8 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { public FrameTemplateEnvironment getTemplates() { return this.templates; } + + public String getErrorString(final String key) { + return this.errorStrings.getField(key); + } } diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/BackdropFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/BackdropFrame.java new file mode 100644 index 0000000..2b05c29 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/BackdropFrame.java @@ -0,0 +1,184 @@ +package com.etheller.warsmash.parsers.fdf.frames; + +import java.util.EnumSet; + +import com.badlogic.gdx.graphics.Texture; +import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.g2d.GlyphLayout; +import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.etheller.warsmash.parsers.fdf.datamodel.BackdropCornerFlags; +import com.etheller.warsmash.parsers.fdf.datamodel.Vector4Definition; + +public class BackdropFrame extends AbstractUIFrame { + private final boolean decorateFileNames; + private final boolean tileBackground; + private final Texture background; + private final EnumSet cornerFlags; + private final float cornerSize; + private final float backgroundSize; + private final Vector4Definition backgroundInsets; + private final Texture edgeFile; + private final float edgeFileWidth; + private final float edgeUVWidth; + private final float edgeFileHeight; + private final float edgeUVHeight; + + public BackdropFrame(final String name, final UIFrame parent, final boolean decorateFileNames, + final boolean tileBackground, final Texture background, final EnumSet cornerFlags, + final float cornerSize, final float backgroundSize, final Vector4Definition backgroundInsets, + final Texture edgeFile) { + super(name, parent); + this.decorateFileNames = decorateFileNames; + this.tileBackground = tileBackground; + this.background = background; + this.cornerFlags = cornerFlags; + this.cornerSize = cornerSize; + this.backgroundSize = backgroundSize; + this.backgroundInsets = backgroundInsets; + this.edgeFile = edgeFile; + this.edgeFileWidth = edgeFile == null ? 0.0f : edgeFile.getWidth(); + this.edgeFileHeight = edgeFile == null ? 0.0f : edgeFile.getHeight(); + this.edgeUVWidth = 1f / 8f; + this.edgeUVHeight = 1f; + } + + @Override + protected void internalRender(final SpriteBatch batch, final BitmapFont baseFont, final GlyphLayout glyphLayout) { + if (this.background != null) { + final float backgroundX = this.renderBounds.x + this.backgroundInsets.getX(); + final float backgroundY = this.renderBounds.y + this.backgroundInsets.getY(); + final float backgroundWidth = this.renderBounds.width - this.backgroundInsets.getX() + - this.backgroundInsets.getZ(); + final float backgroundHeight = this.renderBounds.height - this.backgroundInsets.getY() + - this.backgroundInsets.getW(); + if (this.tileBackground) { + final float backgroundVerticalRepeatCount = (backgroundHeight / this.backgroundSize); + final float backgroundHorizontalRepeatCount = backgroundWidth / this.backgroundSize; + final float backgroundHeightRemainder = backgroundHeight % this.backgroundSize; + final float backgroundHeightRemainderRatio = backgroundHeightRemainder / this.backgroundSize; + final float backgroundWidthRemainder = backgroundWidth % this.backgroundSize; + final float backgroundWidthRemainderRatio = backgroundWidthRemainder / this.backgroundSize; + final int backgroundVerticalFloorRepeatCount = (int) Math.floor(backgroundVerticalRepeatCount); + final int backgroundHorizontalFloorRepeatCount = (int) Math.floor(backgroundHorizontalRepeatCount); + for (int j = 0; j < backgroundVerticalFloorRepeatCount; j++) { + for (int i = 0; i < backgroundHorizontalFloorRepeatCount; i++) { + batch.draw(this.background, backgroundX + (i * this.backgroundSize), + backgroundY + (j * this.backgroundSize), this.backgroundSize, this.backgroundSize); + } + batch.draw(this.background, + backgroundX + ((backgroundHorizontalFloorRepeatCount) * this.backgroundSize), + backgroundY + (j * this.backgroundSize), backgroundWidthRemainder, this.backgroundSize, 0, + 1.0f, backgroundWidthRemainderRatio, 0); + } + for (int i = 0; i < backgroundHorizontalFloorRepeatCount; i++) { + batch.draw(this.background, backgroundX + (i * this.backgroundSize), + backgroundY + (backgroundVerticalFloorRepeatCount * this.backgroundSize), + this.backgroundSize, backgroundHeightRemainder, 0, 1.0f, 1.0f, + backgroundHeightRemainderRatio); + } + batch.draw(this.background, + backgroundX + ((backgroundHorizontalFloorRepeatCount) * this.backgroundSize), + backgroundY + (backgroundVerticalFloorRepeatCount * this.backgroundSize), + backgroundWidthRemainder, backgroundHeightRemainder, 0, 1.0f, backgroundWidthRemainderRatio, + backgroundHeightRemainderRatio); + } + else { + batch.draw(this.background, backgroundX, backgroundY, backgroundWidth, backgroundHeight); + } + } + if (this.edgeFile != null) { + if (this.cornerFlags.contains(BackdropCornerFlags.BL)) { + batch.draw(this.edgeFile, this.renderBounds.x, this.renderBounds.y, this.cornerSize, this.cornerSize, + this.edgeUVWidth * 6, this.edgeUVHeight, this.edgeUVWidth * 7, 0); + } + if (this.cornerFlags.contains(BackdropCornerFlags.BR)) { + batch.draw(this.edgeFile, (this.renderBounds.x + this.renderBounds.width) - this.cornerSize, + this.renderBounds.y, this.cornerSize, this.cornerSize, this.edgeUVWidth * 7, this.edgeUVHeight, + this.edgeUVWidth * 8, 0); + } + if (this.cornerFlags.contains(BackdropCornerFlags.UL)) { + batch.draw(this.edgeFile, this.renderBounds.x, + this.renderBounds.y + (this.renderBounds.height - this.cornerSize), this.cornerSize, + this.cornerSize, this.edgeUVWidth * 4, this.edgeUVHeight, this.edgeUVWidth * 5, 0); + } + if (this.cornerFlags.contains(BackdropCornerFlags.UR)) { + batch.draw(this.edgeFile, (this.renderBounds.x + this.renderBounds.width) - this.cornerSize, + this.renderBounds.y + (this.renderBounds.height - this.cornerSize), this.cornerSize, + this.cornerSize, this.edgeUVWidth * 5, this.edgeUVHeight, this.edgeUVWidth * 6, 0); + } + final float borderVerticalRepeatCount = (this.renderBounds.height / this.cornerSize); + final float heightRemainder = this.renderBounds.height % this.cornerSize; + final float heightRemainderRatio = heightRemainder / this.cornerSize; + final int borderVerticalRepeatCountLessOne = (int) (borderVerticalRepeatCount - 1); + if (this.cornerFlags.contains(BackdropCornerFlags.L)) { + for (int i = 1; i < borderVerticalRepeatCountLessOne; i++) { + batch.draw(this.edgeFile, this.renderBounds.x, this.renderBounds.y + (this.cornerSize * i), + this.cornerSize, this.cornerSize, this.edgeUVWidth * 0, this.edgeUVHeight, + this.edgeUVWidth * 1, 0); + } + if (borderVerticalRepeatCountLessOne > 0) { + batch.draw(this.edgeFile, this.renderBounds.x, + this.renderBounds.y + (this.cornerSize * borderVerticalRepeatCountLessOne), this.cornerSize, + heightRemainder, this.edgeUVWidth * 0, heightRemainderRatio, this.edgeUVWidth * 1, 0); + } + } + if (this.cornerFlags.contains(BackdropCornerFlags.R)) { + for (int i = 1; i < borderVerticalRepeatCountLessOne; i++) { + batch.draw(this.edgeFile, (this.renderBounds.x + this.renderBounds.width) - this.cornerSize, + this.renderBounds.y + (this.cornerSize * i), this.cornerSize, this.cornerSize, + this.edgeUVWidth * 1, this.edgeUVHeight, this.edgeUVWidth * 2, 0); + } + if (borderVerticalRepeatCountLessOne > 0) { + batch.draw(this.edgeFile, (this.renderBounds.x + this.renderBounds.width) - this.cornerSize, + this.renderBounds.y + (this.cornerSize * borderVerticalRepeatCountLessOne), this.cornerSize, + heightRemainder, this.edgeUVWidth * 1, heightRemainderRatio, this.edgeUVWidth * 2, 0); + } + } + + final float borderHorizontalRepeatCount = this.renderBounds.width / this.cornerSize; + final float widthRemainder = (this.renderBounds.width % this.cornerSize) / this.cornerSize; + final int widthRemainderByHeight = (int) (widthRemainder * this.edgeFileHeight); + final int widthRemainderByCornerSize = (int) (widthRemainder * this.cornerSize); + final int borderHorizontalRepeatCountLessOne = (int) (borderHorizontalRepeatCount - 1); + final float halfPi = 270; + if (this.cornerFlags.contains(BackdropCornerFlags.B)) { + for (int i = 1; i < borderHorizontalRepeatCountLessOne; i++) { + batch.draw(this.edgeFile, this.renderBounds.x + (this.cornerSize * i), this.renderBounds.y, + this.cornerSize / 2, this.cornerSize / 2, this.cornerSize, this.cornerSize, 1.0f, 1.0f, + halfPi, (int) ((this.edgeFileWidth * 3f) / 8f), 0, (int) (this.edgeFileWidth / 8), + (int) this.edgeFileHeight, false, false); + } + if (borderHorizontalRepeatCountLessOne > 0) { + batch.draw(this.edgeFile, + (this.renderBounds.x + (this.cornerSize * borderHorizontalRepeatCountLessOne)) + - ((this.cornerSize - widthRemainderByCornerSize) / 2), + this.renderBounds.y + ((this.cornerSize - widthRemainderByCornerSize) / 2), + this.cornerSize / 2, widthRemainderByCornerSize / 2, this.cornerSize, + widthRemainderByCornerSize, 1.0f, 1.0f, halfPi, (int) ((this.edgeFileWidth * 3f) / 8f), 0, + (int) (this.edgeFileWidth / 8), widthRemainderByHeight, false, false); + } + } + if (this.cornerFlags.contains(BackdropCornerFlags.T)) { + for (int i = 1; i < borderHorizontalRepeatCountLessOne; i++) { + batch.draw(this.edgeFile, this.renderBounds.x + (this.cornerSize * i), + this.renderBounds.y + (this.renderBounds.height - this.cornerSize), this.cornerSize / 2, + this.cornerSize / 2, this.cornerSize, this.cornerSize, 1.0f, 1.0f, halfPi, + (int) ((this.edgeFileWidth * 2f) / 8f), 0, (int) (this.edgeFileWidth / 8), + (int) this.edgeFileHeight, false, false); + } + if (borderHorizontalRepeatCountLessOne > 0) { + batch.draw(this.edgeFile, + (this.renderBounds.x + (this.cornerSize * borderHorizontalRepeatCountLessOne)) + - ((this.cornerSize - widthRemainderByCornerSize) / 2), + this.renderBounds.y + + (this.renderBounds.height - ((this.cornerSize + widthRemainderByCornerSize) / 2)), + this.cornerSize / 2, widthRemainderByCornerSize / 2, this.cornerSize, + widthRemainderByCornerSize, 1.0f, 1.0f, halfPi, (int) ((this.edgeFileWidth * 2f) / 8f), 0, + (int) (this.edgeFileWidth / 8), widthRemainderByHeight, false, false); + } + } + } + super.internalRender(batch, baseFont, glyphLayout); + } + +} diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/SetPoint.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/SetPoint.java index 015fdb0..f8ff31e 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/SetPoint.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/SetPoint.java @@ -49,4 +49,10 @@ public class SetPoint implements FramePointAssignment { public float getY(final GameUI gameUI, final Viewport uiViewport) { return this.other.getFramePointY(this.otherPoint) + this.y; } + + @Override + public String toString() { + return "SetPoint [myPoint=" + this.myPoint + ", other=" + this.other + ", otherPoint=" + this.otherPoint + + ", x=" + this.x + ", y=" + this.y + "]"; + } } diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/SmartBackdropFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/SmartBackdropFrame.java new file mode 100644 index 0000000..4c70736 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/SmartBackdropFrame.java @@ -0,0 +1,76 @@ +package com.etheller.warsmash.parsers.fdf.frames; + +import java.util.EnumSet; + +import com.badlogic.gdx.utils.viewport.Viewport; +import com.etheller.warsmash.parsers.fdf.GameUI; +import com.etheller.warsmash.parsers.fdf.datamodel.BackdropCornerFlags; +import com.etheller.warsmash.parsers.fdf.datamodel.Vector4Definition; +import com.etheller.warsmash.parsers.mdlx.Geoset; +import com.etheller.warsmash.parsers.mdlx.Layer; +import com.etheller.warsmash.parsers.mdlx.Layer.FilterMode; +import com.etheller.warsmash.parsers.mdlx.Material; +import com.etheller.warsmash.parsers.mdlx.MdlxModel; +import com.etheller.warsmash.parsers.mdlx.Texture; +import com.etheller.warsmash.viewer5.Scene; + +public class SmartBackdropFrame extends SpriteFrame { + private final boolean decorateFileNames; + private final boolean tileBackground; + private final String backgroundString; + private final EnumSet cornerFlags; + private final float cornerSize; + private final float backgroundSize; + private final Vector4Definition backgroundInsets; + private final String edgeFileString; + + public SmartBackdropFrame(final String name, final UIFrame parent, final Scene scene, final Viewport uiViewport, + final boolean decorateFileNames, final boolean tileBackground, final String backgroundString, + final EnumSet cornerFlags, final float cornerSize, final float backgroundSize, + final Vector4Definition backgroundInsets, final String edgeFileString) { + super(name, parent, scene, uiViewport); + this.decorateFileNames = decorateFileNames; + this.tileBackground = tileBackground; + this.backgroundString = backgroundString; + this.cornerFlags = cornerFlags; + this.cornerSize = cornerSize; + this.backgroundSize = backgroundSize; + this.backgroundInsets = backgroundInsets; + this.edgeFileString = edgeFileString; + } + + @Override + protected void innerPositionBounds(final GameUI gameUI, final Viewport viewport) { + generateBackdropModel(); + super.innerPositionBounds(gameUI, viewport); + } + + private MdlxModel generateBackdropModel() { + final MdlxModel model = new MdlxModel(); + final int edgeFileMaterialId = generateMaterial(model, this.edgeFileString, true); + final int backgroundMaterialId = generateMaterial(model, this.backgroundString, this.tileBackground); + final Geoset edgeGeoset = new Geoset(); + final float[] edgeGeosetVertices = new float[32 * 4]; + return model; + } + + private int generateMaterial(final MdlxModel model, final String path, final boolean wrap) { + final Texture edgeFileReference = new Texture(); + if (wrap) { + edgeFileReference.setFlags(0x2 | 0x1); + } + edgeFileReference.setPath(path); + final int textureId = model.getTextures().size(); + model.getTextures().add(edgeFileReference); + final Material edgeFileMaterial = new Material(); + final Layer edgeFileMaterialLayer = new Layer(); + edgeFileMaterialLayer.setAlpha(1.0f); + edgeFileMaterialLayer.setFilterMode(FilterMode.BLEND); + edgeFileMaterialLayer.setTextureId(textureId); + edgeFileMaterial.getLayers().add(edgeFileMaterialLayer); + final int materialId = model.getMaterials().size(); + model.getMaterials().add(edgeFileMaterial); + return materialId; + } + +} diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/SpriteFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/SpriteFrame.java index e05bb64..e8167dc 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/SpriteFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/SpriteFrame.java @@ -15,10 +15,10 @@ import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; public class SpriteFrame extends AbstractRenderableFrame { - private final Scene scene; + protected final Scene scene; + protected final Viewport uiViewport; private MdxComplexInstance instance; private float zDepth; - private final Viewport uiViewport; public SpriteFrame(final String name, final UIFrame parent, final Scene scene, final Viewport uiViewport) { super(name, parent); @@ -123,4 +123,8 @@ public class SpriteFrame extends AbstractRenderableFrame { } } + public boolean isSequenceEnded() { + return this.instance.sequenceEnded; + } + } diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/StringFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/StringFrame.java index b33fcc7..0d64fab 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/StringFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/StringFrame.java @@ -14,6 +14,10 @@ public class StringFrame extends AbstractRenderableFrame { private final TextJustify justifyH; private final TextJustify justifyV; private final BitmapFont frameFont; + private Color fontShadowColor; + private float fontShadowOffsetX; + private float fontShadowOffsetY; + private float alpha = 1.0f; public StringFrame(final String name, final UIFrame parent, final Color color, final TextJustify justifyH, final TextJustify justifyV, final BitmapFont frameFont) { @@ -36,9 +40,20 @@ public class StringFrame extends AbstractRenderableFrame { this.color = color; } + public void setFontShadowColor(final Color fontShadowColor) { + this.fontShadowColor = fontShadowColor; + } + + public void setFontShadowOffsetX(final float fontShadowOffsetX) { + this.fontShadowOffsetX = fontShadowOffsetX; + } + + public void setFontShadowOffsetY(final float fontShadowOffsetY) { + this.fontShadowOffsetY = fontShadowOffsetY; + } + @Override protected void internalRender(final SpriteBatch batch, final BitmapFont baseFont, final GlyphLayout glyphLayout) { - this.frameFont.setColor(this.color); glyphLayout.setText(this.frameFont, this.text); final float x; switch (this.justifyH) { @@ -66,6 +81,12 @@ public class StringFrame extends AbstractRenderableFrame { y = this.renderBounds.y + this.frameFont.getLineHeight(); break; } + if (this.fontShadowColor != null) { + this.frameFont.setColor(this.fontShadowColor.r, this.fontShadowColor.g, this.fontShadowColor.b, + this.fontShadowColor.a * this.alpha); + this.frameFont.draw(batch, this.text, x + this.fontShadowOffsetX, y + this.fontShadowOffsetY); + } + this.frameFont.setColor(this.color.r, this.color.g, this.color.b, this.color.a * this.alpha); this.frameFont.draw(batch, this.text, x, y); } @@ -73,4 +94,9 @@ public class StringFrame extends AbstractRenderableFrame { protected void innerPositionBounds(final GameUI gameUI, final Viewport viewport) { } + public void setAlpha(final float alpha) { + this.alpha = alpha; + + } + } diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/Geoset.java b/core/src/com/etheller/warsmash/parsers/mdlx/Geoset.java index 6c3d7df..be75c61 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/Geoset.java +++ b/core/src/com/etheller/warsmash/parsers/mdlx/Geoset.java @@ -373,4 +373,56 @@ public class Geoset implements MdlxBlock, Chunk { return this.uvSets; } + public void setVertices(final float[] vertices) { + this.vertices = vertices; + } + + public void setNormals(final float[] normals) { + this.normals = normals; + } + + public void setFaceTypeGroups(final long[] faceTypeGroups) { + this.faceTypeGroups = faceTypeGroups; + } + + public void setFaceGroups(final long[] faceGroups) { + this.faceGroups = faceGroups; + } + + public void setFaces(final int[] faces) { + this.faces = faces; + } + + public void setVertexGroups(final short[] vertexGroups) { + this.vertexGroups = vertexGroups; + } + + public void setMatrixGroups(final long[] matrixGroups) { + this.matrixGroups = matrixGroups; + } + + public void setMatrixIndices(final long[] matrixIndices) { + this.matrixIndices = matrixIndices; + } + + public void setMaterialId(final long materialId) { + this.materialId = materialId; + } + + public void setSelectionGroup(final long selectionGroup) { + this.selectionGroup = selectionGroup; + } + + public void setSelectionFlags(final long selectionFlags) { + this.selectionFlags = selectionFlags; + } + + public void setSequenceExtents(final Extent[] sequenceExtents) { + this.sequenceExtents = sequenceExtents; + } + + public void setUvSets(final float[][] uvSets) { + this.uvSets = uvSets; + } + } diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/Layer.java b/core/src/com/etheller/warsmash/parsers/mdlx/Layer.java index 4621ed5..a8df76e 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/Layer.java +++ b/core/src/com/etheller/warsmash/parsers/mdlx/Layer.java @@ -194,27 +194,51 @@ public class Layer extends AnimatedObject { } public FilterMode getFilterMode() { - return filterMode; + return this.filterMode; } public int getFlags() { - return flags; + return this.flags; } public int getTextureId() { - return textureId; + return this.textureId; } public int getTextureAnimationId() { - return textureAnimationId; + return this.textureAnimationId; } public long getCoordId() { - return coordId; + return this.coordId; } public float getAlpha() { - return alpha; + return this.alpha; + } + + public void setFilterMode(final FilterMode filterMode) { + this.filterMode = filterMode; + } + + public void setFlags(final int flags) { + this.flags = flags; + } + + public void setTextureId(final int textureId) { + this.textureId = textureId; + } + + public void setTextureAnimationId(final int textureAnimationId) { + this.textureAnimationId = textureAnimationId; + } + + public void setCoordId(final long coordId) { + this.coordId = coordId; + } + + public void setAlpha(final float alpha) { + this.alpha = alpha; } } diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/MdlxModel.java b/core/src/com/etheller/warsmash/parsers/mdlx/MdlxModel.java index 8c7ef42..828829e 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/MdlxModel.java +++ b/core/src/com/etheller/warsmash/parsers/mdlx/MdlxModel.java @@ -93,6 +93,9 @@ public class MdlxModel { } } + public MdlxModel() { + } + public void loadMdx(final InputStream buffer) throws IOException { final LittleEndianDataInputStream stream = new LittleEndianDataInputStream(buffer); if (Integer.reverseBytes(stream.readInt()) != MDLX) { diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/Texture.java b/core/src/com/etheller/warsmash/parsers/mdlx/Texture.java index d55b505..0b62834 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/Texture.java +++ b/core/src/com/etheller/warsmash/parsers/mdlx/Texture.java @@ -90,4 +90,16 @@ public class Texture implements MdlxBlock { return this.flags; } + public void setReplaceableId(final int replaceableId) { + this.replaceableId = replaceableId; + } + + public void setPath(final String path) { + this.path = path; + } + + public void setFlags(final int flags) { + this.flags = flags; + } + } diff --git a/core/src/com/etheller/warsmash/units/DataTable.java b/core/src/com/etheller/warsmash/units/DataTable.java index 319ab95..a2c2ebb 100644 --- a/core/src/com/etheller/warsmash/units/DataTable.java +++ b/core/src/com/etheller/warsmash/units/DataTable.java @@ -113,8 +113,14 @@ public class DataTable implements ObjectData { final StringBuilder builder = new StringBuilder(); boolean withinQuotedString = false; final String fieldName = input.substring(0, eIndex); + boolean wasSlash = false; for (int i = 0; i < fieldValue.length(); i++) { final char c = fieldValue.charAt(i); + final boolean isSlash = c == '/'; + if (isSlash && wasSlash && !withinQuotedString) { + builder.setLength(builder.length() - 1); + break; // comment starts here + } if (c == '\"') { withinQuotedString = !withinQuotedString; } @@ -125,6 +131,7 @@ public class DataTable implements ObjectData { else { builder.append(c); } + wasSlash = isSlash; } if (builder.length() > 0) { if (currentUnit == null) { diff --git a/core/src/com/etheller/warsmash/units/HashedGameObject.java b/core/src/com/etheller/warsmash/units/HashedGameObject.java index 326a500..e80625b 100644 --- a/core/src/com/etheller/warsmash/units/HashedGameObject.java +++ b/core/src/com/etheller/warsmash/units/HashedGameObject.java @@ -59,7 +59,7 @@ public abstract class HashedGameObject implements GameObject { public int getFieldValue(final String field) { int i = 0; try { - i = Integer.parseInt(getField(field)); + i = Integer.parseInt(getField(field).trim()); } catch (final NumberFormatException e) { @@ -71,7 +71,7 @@ public abstract class HashedGameObject implements GameObject { public float getFieldFloatValue(final String field) { float i = 0; try { - i = Float.parseFloat(getField(field)); + i = Float.parseFloat(getField(field).trim()); } catch (final NumberFormatException e) { @@ -83,7 +83,7 @@ public abstract class HashedGameObject implements GameObject { public float getFieldFloatValue(final String field, final int index) { float i = 0; try { - i = Float.parseFloat(getField(field, index)); + i = Float.parseFloat(getField(field, index).trim()); } catch (final NumberFormatException e) { @@ -133,7 +133,7 @@ public abstract class HashedGameObject implements GameObject { public int getFieldValue(final String field, final int index) { int i = 0; try { - i = Integer.parseInt(getField(field, index)); + i = Integer.parseInt(getField(field, index).trim()); } catch (final NumberFormatException e) { diff --git a/core/src/com/etheller/warsmash/util/ImageUtils.java b/core/src/com/etheller/warsmash/util/ImageUtils.java index 9a75d85..2f92574 100644 --- a/core/src/com/etheller/warsmash/util/ImageUtils.java +++ b/core/src/com/etheller/warsmash/util/ImageUtils.java @@ -30,6 +30,14 @@ public final class ImageUtils { public static final String DEFAULT_ICON_PATH = "ReplaceableTextures\\CommandButtons\\BTNTemp.blp"; public static Texture getBLPTexture(final DataSource dataSource, final String path) { + final BufferedImage image = getBLPImage(dataSource, path); + if (image != null) { + return ImageUtils.getTexture(image); + } + return null; + } + + public static BufferedImage getBLPImage(final DataSource dataSource, final String path) { try { try (final InputStream resourceAsStream = dataSource.getResourceAsStream(path)) { if ((resourceAsStream == null) || path.endsWith(".tga")) { @@ -39,7 +47,7 @@ public final class ImageUtils { throw new IllegalStateException("missing resource: " + path); } else { - return ImageUtils.getTexture(TgaFile.readTGA(tgaPath, tgaStream)); + return TgaFile.readTGA(tgaPath, tgaStream); } } } @@ -47,7 +55,7 @@ public final class ImageUtils { if (image == null) { throw new IllegalStateException("corrupt resource: " + path); } - return ImageUtils.getTexture(image); + return image; } } diff --git a/core/src/com/etheller/warsmash/util/RenderMathUtils.java b/core/src/com/etheller/warsmash/util/RenderMathUtils.java index 5238433..0ee0318 100644 --- a/core/src/com/etheller/warsmash/util/RenderMathUtils.java +++ b/core/src/com/etheller/warsmash/util/RenderMathUtils.java @@ -357,7 +357,7 @@ public enum RenderMathUtils { for (int i = 0; i < 6; i++) { final int index = (first + i) % 6; - if (distanceToPlane3(planes[index], x, y, z) <= -r) { + if (distanceToPlane3(planes[index], x, y, z) < -r) { return index; } } diff --git a/core/src/com/etheller/warsmash/viewer5/Camera.java b/core/src/com/etheller/warsmash/viewer5/Camera.java index 6ad07b4..840359f 100644 --- a/core/src/com/etheller/warsmash/viewer5/Camera.java +++ b/core/src/com/etheller/warsmash/viewer5/Camera.java @@ -21,7 +21,7 @@ public class Camera { private float fov; private float aspect; - private boolean isOrtho; + public boolean isOrtho; private float leftClipPlane; private float rightClipPlane; private float bottomClipPlane; @@ -136,6 +136,8 @@ public class Camera { this.topClipPlane = top; this.nearClipPlane = near; this.farClipPlane = far; + + this.dirty = true; } public void viewport(final Rectangle viewport) { diff --git a/core/src/com/etheller/warsmash/viewer5/SimpleScene.java b/core/src/com/etheller/warsmash/viewer5/SimpleScene.java index 6f7d7c1..f68a8ff 100644 --- a/core/src/com/etheller/warsmash/viewer5/SimpleScene.java +++ b/core/src/com/etheller/warsmash/viewer5/SimpleScene.java @@ -41,7 +41,9 @@ public class SimpleScene extends Scene { for (final ModelInstance instance : new ArrayList<>(this.allInstances)) { // Below: current SimpleScene is not checking instance visibility. // It's meant to be simple. Low number of models. Render everything, - // dont check visible + // dont check visible. Then I had to add a call to isVisible() because it + // assigns depth, which is crazy. + instance.isVisible(this.camera); if (instance.rendered && (instance.cullFrame < frame)) { instance.cullFrame = frame; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/AbstractMdxModelViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/AbstractMdxModelViewer.java new file mode 100644 index 0000000..012b055 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/AbstractMdxModelViewer.java @@ -0,0 +1,21 @@ +package com.etheller.warsmash.viewer5.handlers; + +import com.etheller.warsmash.datasources.DataSource; +import com.etheller.warsmash.util.StringBundle; +import com.etheller.warsmash.viewer5.CanvasProvider; +import com.etheller.warsmash.viewer5.ModelViewer; +import com.etheller.warsmash.viewer5.PathSolver; +import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer.SolverParams; + +public abstract class AbstractMdxModelViewer extends ModelViewer { + public PathSolver wc3PathSolver = PathSolver.DEFAULT; + public PathSolver mapPathSolver = PathSolver.DEFAULT; + public SolverParams solverParams = new SolverParams(); + + public AbstractMdxModelViewer(final DataSource dataSource, final CanvasProvider canvas) { + super(dataSource, canvas); + } + + public abstract StringBundle getWorldEditStrings(); + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxViewer.java index d34411b..2ddb277 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxViewer.java @@ -2,15 +2,20 @@ package com.etheller.warsmash.viewer5.handlers.mdx; import com.badlogic.gdx.math.Vector3; import com.etheller.warsmash.datasources.DataSource; +import com.etheller.warsmash.util.StringBundle; +import com.etheller.warsmash.util.WorldEditStrings; import com.etheller.warsmash.viewer5.CanvasProvider; -import com.etheller.warsmash.viewer5.ModelViewer; import com.etheller.warsmash.viewer5.SceneLightManager; +import com.etheller.warsmash.viewer5.handlers.AbstractMdxModelViewer; import com.etheller.warsmash.viewer5.handlers.w3x.W3xScenePortraitLightManager; -public class MdxViewer extends ModelViewer { +public class MdxViewer extends AbstractMdxModelViewer { + + private final WorldEditStrings worldEditStrings; public MdxViewer(final DataSource dataSource, final CanvasProvider canvas) { super(dataSource, canvas); + this.worldEditStrings = new WorldEditStrings(this.dataSource); } @Override @@ -18,4 +23,9 @@ public class MdxViewer extends ModelViewer { return new W3xScenePortraitLightManager(this, new Vector3(0.3f, 0.3f, -0.25f)); } + @Override + public StringBundle getWorldEditStrings() { + return this.worldEditStrings; + } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index 7c77740..bf9083d 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -59,13 +59,13 @@ import com.etheller.warsmash.viewer5.CanvasProvider; import com.etheller.warsmash.viewer5.GenericResource; import com.etheller.warsmash.viewer5.Grid; import com.etheller.warsmash.viewer5.ModelInstance; -import com.etheller.warsmash.viewer5.ModelViewer; import com.etheller.warsmash.viewer5.PathSolver; import com.etheller.warsmash.viewer5.Scene; import com.etheller.warsmash.viewer5.SceneLightManager; import com.etheller.warsmash.viewer5.Texture; import com.etheller.warsmash.viewer5.WorldScene; import com.etheller.warsmash.viewer5.gl.WebGL; +import com.etheller.warsmash.viewer5.handlers.AbstractMdxModelViewer; import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxHandler; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; @@ -93,12 +93,13 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUni import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackMissile; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.projectile.CAttackProjectile; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.SimulationRenderController; +import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.CommandErrorListener; import com.etheller.warsmash.viewer5.handlers.w3x.ui.sound.KeyedSounds; import mpq.MPQArchive; import mpq.MPQException; -public class War3MapViewer extends ModelViewer { +public class War3MapViewer extends AbstractMdxModelViewer { private static final War3ID UNIT_FILE = War3ID.fromString("umdl"); private static final War3ID UNIT_SPECIAL = War3ID.fromString("uspa"); private static final War3ID UBER_SPLAT = War3ID.fromString("uubs"); @@ -127,8 +128,6 @@ public class War3MapViewer extends ModelViewer { private static final Rectangle rectangleHeap = new Rectangle(); public static final StreamDataCallbackImplementation streamDataCallback = new StreamDataCallbackImplementation(); - public PathSolver wc3PathSolver = PathSolver.DEFAULT; - public SolverParams solverParams = new SolverParams(); public WorldScene worldScene; public boolean anyReady; public MappedData terrainData = new MappedData(); @@ -151,7 +150,6 @@ public class War3MapViewer extends ModelViewer { public List projectiles = new ArrayList<>(); public boolean unitsReady; public War3Map mapMpq; - public PathSolver mapPathSolver = PathSolver.DEFAULT; private final DataSource gameDataSource; @@ -197,8 +195,10 @@ public class War3MapViewer extends ModelViewer { private KeyedSounds uiSounds; private int localPlayerIndex; + private final CommandErrorListener commandErrorListener; - public War3MapViewer(final DataSource dataSource, final CanvasProvider canvas) { + public War3MapViewer(final DataSource dataSource, final CanvasProvider canvas, + final CommandErrorListener errorListener) { super(dataSource, canvas); this.gameDataSource = dataSource; @@ -213,6 +213,8 @@ public class War3MapViewer extends ModelViewer { if (!this.dynamicShadowManager.setup(webGL)) { throw new IllegalStateException("FrameBuffer setup failed"); } + + this.commandErrorListener = errorListener; } public void loadSLKs(final WorldEditStrings worldEditStrings) throws IOException { @@ -589,7 +591,8 @@ public class War3MapViewer extends ModelViewer { final RenderUnit renderPeer = War3MapViewer.this.unitToRenderPeer.get(cUnit); renderPeer.repositioned(War3MapViewer.this); } - }, this.terrain.pathingGrid, this.terrain.getEntireMap(), this.seededRandom, w3iFile.getPlayers()); + }, this.terrain.pathingGrid, this.terrain.getEntireMap(), this.seededRandom, w3iFile.getPlayers(), + this.commandErrorListener); this.walkableObjectsTree = new Quadtree<>(this.terrain.getEntireMap()); if (this.doodadsAndDestructiblesLoaded) { @@ -1500,6 +1503,7 @@ public class War3MapViewer extends ModelViewer { } } + @Override public WorldEditStrings getWorldEditStrings() { return this.worldEditStrings; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/camera/CameraManager.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/camera/CameraManager.java index dc84859..d44d73e 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/camera/CameraManager.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/camera/CameraManager.java @@ -11,13 +11,13 @@ public abstract class CameraManager { protected final float[] cameraPositionTemp = new float[3]; protected final float[] cameraTargetTemp = new float[3]; protected CanvasProvider canvas; - protected Camera camera; + public Camera camera; protected float moveSpeed; protected float rotationSpeed; protected float zoomFactor; - protected float horizontalAngle; - protected float verticalAngle; - protected float distance; + public float horizontalAngle; + public float verticalAngle; + public float distance; protected Vector3 position; public Vector3 target; protected Vector3 worldUp; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java index 30ba5fc..69f03c7 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java @@ -57,7 +57,7 @@ public class CSimulation { public CSimulation(final DataTable miscData, final MutableObjectData parsedUnitData, final MutableObjectData parsedAbilityData, final SimulationRenderController simulationRenderController, final PathingGrid pathingGrid, final Rectangle entireMapBounds, final Random seededRandom, - final List playerInfos) { + final List playerInfos, final CommandErrorListener commandErrorListener) { this.gameplayConstants = new CGameplayConstants(miscData); this.simulationRenderController = simulationRenderController; this.pathingGrid = pathingGrid; @@ -99,12 +99,7 @@ public class CSimulation { neutralPassive.setAlliance(cPlayer, CAllianceType.PASSIVE, true); } - this.commandErrorListener = new CommandErrorListener() { - @Override - public void showCommandError(final String message) { - throw new RuntimeException(message); - } - }; + this.commandErrorListener = commandErrorListener; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java index 518526b..87675ee 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java @@ -73,6 +73,9 @@ public class CBehaviorOrcBuild extends CAbstractRangedBehavior { this.unit.setInvulnerable(true); simulation.unitConstructedEvent(this.unit, constructedStructure); } + else { + simulation.getCommandErrorListener().showCantPlaceError(); + } return this.unit.pollNextOrderBehavior(simulation); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java index 4997cd2..48dee2e 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java @@ -5,6 +5,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.EnumSet; import java.util.List; +import java.util.concurrent.TimeUnit; import javax.imageio.ImageIO; @@ -23,6 +24,7 @@ import com.badlogic.gdx.graphics.glutils.PixmapTextureData; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.math.Vector3; +import com.badlogic.gdx.utils.TimeUtils; import com.badlogic.gdx.utils.viewport.ExtendViewport; import com.etheller.warsmash.datasources.DataSource; import com.etheller.warsmash.parsers.fdf.GameUI; @@ -40,6 +42,7 @@ import com.etheller.warsmash.parsers.fdf.frames.TextureFrame; import com.etheller.warsmash.parsers.fdf.frames.UIFrame; import com.etheller.warsmash.parsers.jass.Jass2.RootFrameListener; import com.etheller.warsmash.parsers.mdlx.Layer.FilterMode; +import com.etheller.warsmash.units.Element; import com.etheller.warsmash.units.manager.MutableObjectData; import com.etheller.warsmash.util.FastNumberFormat; import com.etheller.warsmash.util.ImageUtils; @@ -117,10 +120,15 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.PointAbilityTa import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.StringMsgAbilityActivationReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.ClickableActionFrame; import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.CommandCardCommandListener; +import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.CommandErrorListener; import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.QueueIconListener; -public class MeleeUI - implements CUnitStateListener, CommandButtonListener, CommandCardCommandListener, QueueIconListener { +public class MeleeUI implements CUnitStateListener, CommandButtonListener, CommandCardCommandListener, + QueueIconListener, CommandErrorListener { + private static final long WORLD_FRAME_MESSAGE_FADEOUT_MILLIS = TimeUnit.SECONDS.toMillis(9); + private static final long WORLD_FRAME_MESSAGE_EXPIRE_MILLIS = TimeUnit.SECONDS.toMillis(10); + private static final long WORLD_FRAME_MESSAGE_FADE_DURATION = WORLD_FRAME_MESSAGE_EXPIRE_MILLIS + - WORLD_FRAME_MESSAGE_FADEOUT_MILLIS; private static final String BUILDING_PATHING_PREVIEW_KEY = "buildingPathingPreview"; public static final float DEFAULT_COMMAND_CARD_ICON_WIDTH = 0.039f; public static final float DEFAULT_COMMAND_CARD_ICON_PRESSED_WIDTH = 0.037f; @@ -194,6 +202,8 @@ public class MeleeUI private MeleeUIMinimap meleeUIMinimap; private final CPlayerUnitOrderListener unitOrderListener; private StringFrame errorMessageFrame; + private long lastErrorMessageExpireTime; + private long lastErrorMessageFadeTime; private CAbilityView activeCommand; private int activeCommandOrderId; @@ -295,7 +305,7 @@ public class MeleeUI // ================================= // Load skins and templates // ================================= - this.rootFrame = new GameUI(this.dataSource, GameUI.loadSkin(this.dataSource, 2), this.uiViewport, + this.rootFrame = new GameUI(this.dataSource, GameUI.loadSkin(this.dataSource, 0), this.uiViewport, this.fontGenerator, this.uiScene, this.war3MapViewer); this.rootFrameListener.onCreate(this.rootFrame); try { @@ -467,11 +477,19 @@ public class MeleeUI this.inventoryCover = this.rootFrame.createSimpleFrame("SmashConsoleInventoryCover", this.rootFrame, 0); - this.errorMessageFrame = this.rootFrame.createStringFrame("SmashErrorMessageFrame", this.consoleUI, - new Color(0xFFFFCC00), TextJustify.LEFT, TextJustify.MIDDLE, 0.014f); + final Element fontHeights = this.war3MapViewer.miscData.get("FontHeights"); + final float worldFrameMessageFontHeight = fontHeights.getFieldFloatValue("WorldFrameMessage"); + this.errorMessageFrame = this.rootFrame.createStringFrame("SmashErrorMessageFrame", this.rootFrame, + new Color(0xFFCC00FF), TextJustify.LEFT, TextJustify.MIDDLE, worldFrameMessageFontHeight); this.errorMessageFrame.addAnchor(new AnchorDefinition(FramePoint.BOTTOMLEFT, - GameUI.convertX(this.uiViewport, 0.275f), GameUI.convertY(this.uiViewport, 0.275f))); + GameUI.convertX(this.uiViewport, 0.212f), GameUI.convertY(this.uiViewport, 0.182f))); this.errorMessageFrame.setWidth(GameUI.convertX(this.uiViewport, 0.25f)); + this.errorMessageFrame.setHeight(GameUI.convertY(this.uiViewport, worldFrameMessageFontHeight)); + + this.errorMessageFrame.setFontShadowColor(new Color(0f, 0f, 0f, 0.9f)); + this.errorMessageFrame.setFontShadowOffsetX(GameUI.convertX(this.uiViewport, 0.001f)); + this.errorMessageFrame.setFontShadowOffsetY(GameUI.convertY(this.uiViewport, -0.001f)); + this.errorMessageFrame.setVisible(false); int commandButtonIndex = 0; for (int j = 0; j < COMMAND_CARD_HEIGHT; j++) { @@ -521,7 +539,7 @@ public class MeleeUI "", 0); this.rootFrame.setSpriteFrameModel(this.cursorFrame, this.rootFrame.getSkinField("Cursor")); this.cursorFrame.setSequence("Normal"); - this.cursorFrame.setZDepth(1.0f); + this.cursorFrame.setZDepth(-1.0f); Gdx.input.setCursorCatched(true); this.meleeUIMinimap = createMinimap(this.war3MapViewer); @@ -605,8 +623,20 @@ public class MeleeUI clearAndRepopulateCommandCard(); } + @Override public void showCommandError(final String message) { this.errorMessageFrame.setText(message); + this.errorMessageFrame.setVisible(true); + this.lastErrorMessageExpireTime = TimeUtils.millis() + WORLD_FRAME_MESSAGE_EXPIRE_MILLIS; + this.lastErrorMessageFadeTime = TimeUtils.millis() + WORLD_FRAME_MESSAGE_FADEOUT_MILLIS; + this.errorMessageFrame.setAlpha(1.0f); + } + + @Override + public void showCantPlaceError() { + showCommandError(this.rootFrame.getErrorString("Cantplace")); + this.war3MapViewer.getUiSounds().getSound(this.rootFrame.getSkinField("CantPlaceSound")) + .play(this.uiScene.audioContext, 0, 0); } public void update(final float deltaTime) { @@ -707,6 +737,15 @@ public class MeleeUI this.war3MapViewer.terrain.getWaterHeight(this.cameraManager.target.x, this.cameraManager.target.y)); this.cameraManager.updateTargetZ(groundHeight); this.cameraManager.updateCamera(); + final long currentMillis = TimeUtils.millis(); + if (currentMillis > this.lastErrorMessageExpireTime) { + this.errorMessageFrame.setVisible(false); + } + else if (currentMillis > this.lastErrorMessageFadeTime) { + final float fadeAlpha = (this.lastErrorMessageExpireTime - currentMillis) + / (float) WORLD_FRAME_MESSAGE_FADE_DURATION; + this.errorMessageFrame.setAlpha(fadeAlpha); + } } public void render(final SpriteBatch batch, final BitmapFont font20, final GlyphLayout glyphLayout) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MenuUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MenuUI.java new file mode 100644 index 0000000..4291b65 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MenuUI.java @@ -0,0 +1,148 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.ui; + +import java.io.IOException; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.g2d.GlyphLayout; +import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator; +import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.utils.viewport.ExtendViewport; +import com.etheller.warsmash.datasources.DataSource; +import com.etheller.warsmash.parsers.fdf.GameUI; +import com.etheller.warsmash.parsers.fdf.datamodel.FramePoint; +import com.etheller.warsmash.parsers.fdf.frames.SpriteFrame; +import com.etheller.warsmash.parsers.fdf.frames.UIFrame; +import com.etheller.warsmash.parsers.jass.Jass2.RootFrameListener; +import com.etheller.warsmash.viewer5.Scene; +import com.etheller.warsmash.viewer5.handlers.mdx.MdxViewer; + +public class MenuUI { + private static final Vector2 screenCoordsVector = new Vector2(); + + private final DataSource dataSource; + private final Scene uiScene; + private final ExtendViewport uiViewport; + private final FreeTypeFontGenerator fontGenerator; + private final MdxViewer viewer; + private final RootFrameListener rootFrameListener; + private final float widthRatioCorrection; + private final float heightRatioCorrection; + private GameUI rootFrame; + private SpriteFrame cursorFrame; + + private UIFrame mainMenuFrame; + + private SpriteFrame glueSpriteLayerTopRight; + + private SpriteFrame glueSpriteLayerTopLeft; + + public MenuUI(final DataSource dataSource, final ExtendViewport uiViewport, + final FreeTypeFontGenerator fontGenerator, final Scene uiScene, final MdxViewer viewer, + final RootFrameListener rootFrameListener) { + this.dataSource = dataSource; + this.uiViewport = uiViewport; + this.fontGenerator = fontGenerator; + this.uiScene = uiScene; + this.viewer = viewer; + this.rootFrameListener = rootFrameListener; + + this.widthRatioCorrection = this.uiViewport.getMinWorldWidth() / 1600f; + this.heightRatioCorrection = this.uiViewport.getMinWorldHeight() / 1200f; + } + + public float getHeightRatioCorrection() { + return this.heightRatioCorrection; + } + + /** + * Called "main" because this was originally written in JASS so that maps could + * override it, and I may convert it back to the JASS at some point. + */ + public void main() { + // ================================= + // Load skins and templates + // ================================= + this.rootFrame = new GameUI(this.dataSource, GameUI.loadSkin(this.dataSource, 1), this.uiViewport, + this.fontGenerator, this.uiScene, this.viewer); + this.rootFrameListener.onCreate(this.rootFrame); + try { + this.rootFrame.loadTOCFile("UI\\FrameDef\\FrameDef.toc"); + } + catch (final IOException exc) { + throw new IllegalStateException("Unable to load FrameDef.toc", exc); + } + try { + this.rootFrame.loadTOCFile("UI\\FrameDef\\SmashFrameDef.toc"); + } + catch (final IOException exc) { + throw new IllegalStateException("Unable to load SmashFrameDef.toc", exc); + } + this.mainMenuFrame = this.rootFrame.createFrame("MainMenuFrame", this.rootFrame, 0, 0); + + final SpriteFrame warcraftIIILogo = (SpriteFrame) this.rootFrame.getFrameByName("WarCraftIIILogo", 0); + this.rootFrame.setSpriteFrameModel(warcraftIIILogo, this.rootFrame.getSkinField("MainMenuLogo_V1")); + this.rootFrame.getFrameByName("RealmSelect", 0).setVisible(false); + + this.glueSpriteLayerTopRight = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", + "SmashGlueSpriteLayerTopRight", this.rootFrame, "", 0); + this.glueSpriteLayerTopRight.setSetAllPoints(true); + final String topRightModel = this.rootFrame.getSkinField("GlueSpriteLayerTopRight_V1"); + this.rootFrame.setSpriteFrameModel(this.glueSpriteLayerTopRight, topRightModel); + this.glueSpriteLayerTopRight.setSequence("MainMenu Birth"); + + this.glueSpriteLayerTopLeft = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", + "SmashGlueSpriteLayerTopLeft", this.rootFrame, "", 0); + this.glueSpriteLayerTopLeft.setSetAllPoints(true); + final String topLeftModel = this.rootFrame.getSkinField("GlueSpriteLayerTopLeft_V1"); + this.rootFrame.setSpriteFrameModel(this.glueSpriteLayerTopLeft, topLeftModel); + this.glueSpriteLayerTopLeft.setSequence("MainMenu Birth"); + + this.cursorFrame = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", "SmashCursorFrame", this.rootFrame, + "", 0); + this.rootFrame.setSpriteFrameModel(this.cursorFrame, this.rootFrame.getSkinField("Cursor")); + this.cursorFrame.setSequence("Normal"); + this.cursorFrame.setZDepth(-1.0f); + Gdx.input.setCursorCatched(true); + + this.rootFrame.positionBounds(this.rootFrame, this.uiViewport); + + } + + public void resize() { + + } + + public void render(final SpriteBatch batch, final BitmapFont font20, final GlyphLayout glyphLayout) { + this.rootFrame.render(batch, font20, glyphLayout); + } + + public void update(final float deltaTime) { + + final int baseMouseX = Gdx.input.getX(); + int mouseX = baseMouseX; + final int baseMouseY = Gdx.input.getY(); + int mouseY = baseMouseY; + final int minX = this.uiViewport.getScreenX(); + final int maxX = minX + this.uiViewport.getScreenWidth(); + final int minY = this.uiViewport.getScreenY(); + final int maxY = minY + this.uiViewport.getScreenHeight(); + + mouseX = Math.max(minX, Math.min(maxX, mouseX)); + mouseY = Math.max(minY, Math.min(maxY, mouseY)); + if (Gdx.input.isCursorCatched()) { + Gdx.input.setCursorPosition(mouseX, mouseY); + } + + screenCoordsVector.set(mouseX, mouseY); + this.uiViewport.unproject(screenCoordsVector); + this.cursorFrame.setFramePointX(FramePoint.LEFT, screenCoordsVector.x); + this.cursorFrame.setFramePointY(FramePoint.BOTTOM, screenCoordsVector.y); + this.cursorFrame.setSequence("Normal"); + + if (this.glueSpriteLayerTopRight.isSequenceEnded()) { + this.glueSpriteLayerTopRight.setSequence("MainMenu Stand"); + } + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/CommandErrorListener.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/CommandErrorListener.java index 0da222f..3a8f77c 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/CommandErrorListener.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/CommandErrorListener.java @@ -2,4 +2,6 @@ package com.etheller.warsmash.viewer5.handlers.w3x.ui.command; public interface CommandErrorListener { void showCommandError(String message); + + void showCantPlaceError(); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/SettableCommandErrorListener.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/SettableCommandErrorListener.java new file mode 100644 index 0000000..5b43c62 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/SettableCommandErrorListener.java @@ -0,0 +1,19 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.ui.command; + +public class SettableCommandErrorListener implements CommandErrorListener { + private CommandErrorListener delegate; + + @Override + public void showCommandError(final String message) { + this.delegate.showCommandError(message); + } + + @Override + public void showCantPlaceError() { + this.delegate.showCantPlaceError(); + } + + public void setDelegate(final CommandErrorListener delegate) { + this.delegate = delegate; + } +} diff --git a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java index 62b52d7..1f12b81 100644 --- a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java +++ b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java @@ -25,6 +25,43 @@ import com.etheller.warsmash.viewer5.gl.WireframeExtension; public class DesktopLauncher { public static void main(final String[] arg) { + loadExtensions(); + final DataTable warsmashIni = loadWarsmashIni(); + final LwjglApplicationConfiguration config = new LwjglApplicationConfiguration(); + config.useGL30 = true; + config.gles30ContextMajorVersion = 3; + config.gles30ContextMinorVersion = 3; + // config.samples = 16; +// config.vSyncEnabled = false; +// config.foregroundFPS = 0; +// config.backgroundFPS = 0; + final DisplayMode desktopDisplayMode = LwjglApplicationConfiguration.getDesktopDisplayMode(); + config.width = desktopDisplayMode.width; + config.height = desktopDisplayMode.height; + if ((arg.length > 0) && "-windowed".equals(arg[0])) { + config.fullscreen = false; + } + else { + config.fullscreen = true; + } + new LwjglApplication(new WarsmashGdxMapGame(warsmashIni), config); + } + + public static DataTable loadWarsmashIni() { + final DataTable warsmashIni = new DataTable(StringBundle.EMPTY); + try (FileInputStream warsmashIniInputStream = new FileInputStream("warsmash.ini")) { + warsmashIni.readTXT(warsmashIniInputStream, true); + } + catch (final FileNotFoundException e) { + throw new RuntimeException(e); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + return warsmashIni; + } + + public static void loadExtensions() { Extensions.angleInstancedArrays = new ANGLEInstancedArrays() { @Override public void glVertexAttribDivisorANGLE(final int index, final int divisor) { @@ -70,35 +107,7 @@ public class DesktopLauncher { return ((OpenALSound) sound).duration(); } }; - final DataTable warsmashIni = new DataTable(StringBundle.EMPTY); - try (FileInputStream warsmashIniInputStream = new FileInputStream("warsmash.ini")) { - warsmashIni.readTXT(warsmashIniInputStream, true); - } - catch (final FileNotFoundException e) { - throw new RuntimeException(e); - } - catch (final IOException e) { - throw new RuntimeException(e); - } Extensions.GL_LINE = GL11.GL_LINE; Extensions.GL_FILL = GL11.GL_FILL; - final LwjglApplicationConfiguration config = new LwjglApplicationConfiguration(); - config.useGL30 = true; - config.gles30ContextMajorVersion = 3; - config.gles30ContextMinorVersion = 3; - // config.samples = 16; -// config.vSyncEnabled = false; -// config.foregroundFPS = 0; -// config.backgroundFPS = 0; - final DisplayMode desktopDisplayMode = LwjglApplicationConfiguration.getDesktopDisplayMode(); - config.width = desktopDisplayMode.width; - config.height = desktopDisplayMode.height; - if ((arg.length > 0) && "-windowed".equals(arg[0])) { - config.fullscreen = false; - } - else { - config.fullscreen = true; - } - new LwjglApplication(new WarsmashGdxMapGame(warsmashIni), config); } } diff --git a/desktop/src/com/etheller/warsmash/desktop/editor/mdx/MdxEditorMain.java b/desktop/src/com/etheller/warsmash/desktop/editor/mdx/MdxEditorMain.java new file mode 100644 index 0000000..ae7430b --- /dev/null +++ b/desktop/src/com/etheller/warsmash/desktop/editor/mdx/MdxEditorMain.java @@ -0,0 +1,33 @@ +package com.etheller.warsmash.desktop.editor.mdx; + +import javax.swing.SwingUtilities; +import javax.swing.UIManager; + +import com.etheller.warsmash.desktop.DesktopLauncher; +import com.etheller.warsmash.desktop.editor.mdx.ui.YseraFrame; +import com.etheller.warsmash.units.DataTable; + +public class MdxEditorMain { + + public static void main(final String[] args) { + DesktopLauncher.loadExtensions(); + + try { + // UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsClassicLookAndFeel"); + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } + catch (final Exception exc) { + } + + final DataTable warsmashIni = DesktopLauncher.loadWarsmashIni(); + + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + final YseraFrame frame = new YseraFrame(warsmashIni); + frame.setVisible(true); + } + }); + } + +} diff --git a/desktop/src/com/etheller/warsmash/desktop/editor/mdx/listeners/YseraGUIListener.java b/desktop/src/com/etheller/warsmash/desktop/editor/mdx/listeners/YseraGUIListener.java new file mode 100644 index 0000000..df9f784 --- /dev/null +++ b/desktop/src/com/etheller/warsmash/desktop/editor/mdx/listeners/YseraGUIListener.java @@ -0,0 +1,29 @@ +package com.etheller.warsmash.desktop.editor.mdx.listeners; + +import com.etheller.warsmash.parsers.mdlx.MdlxModel; +import com.etheller.warsmash.util.SubscriberSetNotifier; + +public interface YseraGUIListener { + void openModel(MdlxModel model); + + // probably repaint + void stateChanged(); + + class YseraGUINotifier extends SubscriberSetNotifier implements YseraGUIListener { + + @Override + public void openModel(final MdlxModel model) { + for (final YseraGUIListener listener : set) { + listener.openModel(model); + } + } + + @Override + public void stateChanged() { + for (final YseraGUIListener listener : set) { + listener.stateChanged(); + } + } + + } +} diff --git a/desktop/src/com/etheller/warsmash/desktop/editor/mdx/ui/AnimationControllerFrame.java b/desktop/src/com/etheller/warsmash/desktop/editor/mdx/ui/AnimationControllerFrame.java new file mode 100644 index 0000000..1e84df4 --- /dev/null +++ b/desktop/src/com/etheller/warsmash/desktop/editor/mdx/ui/AnimationControllerFrame.java @@ -0,0 +1,30 @@ +package com.etheller.warsmash.desktop.editor.mdx.ui; + +import javax.swing.JFrame; +import javax.swing.WindowConstants; + +import com.etheller.warsmash.WarsmashPreviewApplication; +import com.etheller.warsmash.desktop.editor.mdx.listeners.YseraGUIListener; +import com.etheller.warsmash.parsers.mdlx.MdlxModel; + +public class AnimationControllerFrame extends JFrame implements YseraGUIListener { + private final AnimationControllerPanel animationControllerPanel; + + public AnimationControllerFrame(final WarsmashPreviewApplication warsmashPreviewApplication) { + super("Animation Controller"); + this.animationControllerPanel = new AnimationControllerPanel(warsmashPreviewApplication); + setContentPane(this.animationControllerPanel); + setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE); + pack(); + } + + @Override + public void openModel(final MdlxModel model) { + this.animationControllerPanel.openModel(model); + } + + @Override + public void stateChanged() { + this.animationControllerPanel.stateChanged(); + } +} diff --git a/desktop/src/com/etheller/warsmash/desktop/editor/mdx/ui/AnimationControllerPanel.java b/desktop/src/com/etheller/warsmash/desktop/editor/mdx/ui/AnimationControllerPanel.java new file mode 100644 index 0000000..b865e9e --- /dev/null +++ b/desktop/src/com/etheller/warsmash/desktop/editor/mdx/ui/AnimationControllerPanel.java @@ -0,0 +1,210 @@ +package com.etheller.warsmash.desktop.editor.mdx.ui; + +import java.awt.Component; +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseWheelEvent; +import java.awt.event.MouseWheelListener; + +import javax.swing.ButtonGroup; +import javax.swing.DefaultComboBoxModel; +import javax.swing.GroupLayout; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.JSlider; +import javax.swing.JSpinner; +import javax.swing.SpinnerNumberModel; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.plaf.basic.BasicComboBoxRenderer; + +import com.etheller.warsmash.WarsmashPreviewApplication; +import com.etheller.warsmash.desktop.editor.mdx.listeners.YseraGUIListener; +import com.etheller.warsmash.parsers.mdlx.MdlxModel; +import com.etheller.warsmash.parsers.mdlx.Sequence; +import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; +import com.etheller.warsmash.viewer5.handlers.mdx.SequenceLoopMode; + +public class AnimationControllerPanel extends JPanel implements YseraGUIListener { + private final WarsmashPreviewApplication previewApplication; + private final DefaultComboBoxModel animations; + private final JComboBox animationBox; + private MdlxModel model; + private JRadioButton defaultLoopButton; + private JRadioButton alwaysLoopButton; + private JRadioButton neverLoopButton; + private JSlider speedSlider; + private JLabel speedSliderLabel; + + public AnimationControllerPanel(final WarsmashPreviewApplication previewApplication) { + this.previewApplication = previewApplication; + + this.animations = new DefaultComboBoxModel<>(); + repopulateSequenceList(); + this.animationBox = new JComboBox<>(this.animations); + this.animationBox.setRenderer(new BasicComboBoxRenderer() { + @Override + public Component getListCellRendererComponent(final JList list, final Object value, final int index, + final boolean isSelected, final boolean cellHasFocus) { + Object display = value == null ? "(Unanimated)" : ((Sequence) value).getName(); + if ((value != null) && (AnimationControllerPanel.this.model != null)) { + display = "(" + AnimationControllerPanel.this.model.getSequences().indexOf(value) + ") " + display; + } + return super.getListCellRendererComponent(list, display, index, isSelected, cellHasFocus); + } + }); + this.animationBox.addActionListener(new ActionListener() { + @Override + public void actionPerformed(final ActionEvent e) { + update(true); + } + }); + this.animationBox.setMaximumSize(new Dimension(99999999, 35)); + this.animationBox.setFocusable(true); + this.animationBox.addMouseWheelListener(new MouseWheelListener() { + @Override + public void mouseWheelMoved(final MouseWheelEvent e) { + final int wheelRotation = e.getWheelRotation(); + int previousSelectedIndex = AnimationControllerPanel.this.animationBox.getSelectedIndex(); + if (previousSelectedIndex < 0) { + previousSelectedIndex = 0; + } + int newIndex = previousSelectedIndex + wheelRotation; + if (newIndex > (AnimationControllerPanel.this.animations.getSize() - 1)) { + newIndex = AnimationControllerPanel.this.animations.getSize() - 1; + } + else if (newIndex < 0) { + newIndex = 0; + } + if (newIndex != previousSelectedIndex) { + AnimationControllerPanel.this.animationBox.setSelectedIndex(newIndex); + // animationBox.setSelectedIndex( + // ((newIndex % animations.getSize()) + animations.getSize()) % + // animations.getSize()); + } + } + }); + this.speedSlider = new JSlider(0, 100, 50); + this.speedSliderLabel = new JLabel("Speed: 100%"); + this.speedSlider.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(final ChangeEvent e) { + update(false); + } + }); + + final JButton playAnimationButton = new JButton("Play Animation"); + final ActionListener playAnimationActionListener = new ActionListener() { + @Override + public void actionPerformed(final ActionEvent e) { + update(true); + } + }; + playAnimationButton.addActionListener(playAnimationActionListener); + + this.defaultLoopButton = new JRadioButton("Default Loop"); + this.alwaysLoopButton = new JRadioButton("Always Loop"); + this.neverLoopButton = new JRadioButton("Never Loop"); + + final ButtonGroup buttonGroup = new ButtonGroup(); + buttonGroup.add(this.defaultLoopButton); + buttonGroup.add(this.alwaysLoopButton); + buttonGroup.add(this.neverLoopButton); + final ActionListener setLoopTypeActionListener = new ActionListener() { + @Override + public void actionPerformed(final ActionEvent e) { + update(true); + } + }; + this.defaultLoopButton.addActionListener(setLoopTypeActionListener); + this.alwaysLoopButton.addActionListener(setLoopTypeActionListener); + this.neverLoopButton.addActionListener(setLoopTypeActionListener); + + final JLabel levelOfDetailLabel = new JLabel("Level of Detail"); + final JSpinner levelOfDetailSpinner = new JSpinner(new SpinnerNumberModel(0, 0, 5, 1)); + levelOfDetailSpinner.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(final ChangeEvent e) { +// listener.setLevelOfDetail(((Number) levelOfDetailSpinner.getValue()).intValue()); + } + }); + levelOfDetailSpinner.setMaximumSize(new Dimension(99999, 25)); + levelOfDetailLabel.setVisible(false); + levelOfDetailSpinner.setVisible(false); + + final GroupLayout groupLayout = new GroupLayout(this); + + groupLayout.setHorizontalGroup(groupLayout.createParallelGroup().addComponent(this.animationBox) + .addGroup(groupLayout.createSequentialGroup().addGap(8) + .addGroup(groupLayout.createParallelGroup().addComponent(playAnimationButton) + .addComponent(this.defaultLoopButton).addComponent(this.alwaysLoopButton) + .addComponent(this.neverLoopButton).addComponent(this.speedSliderLabel) + .addComponent(this.speedSlider).addComponent(levelOfDetailLabel) + .addComponent(levelOfDetailSpinner)) + .addGap(8) + + )); + groupLayout.setVerticalGroup(groupLayout.createSequentialGroup().addComponent(this.animationBox).addGap(32) + .addComponent(playAnimationButton).addGap(16).addComponent(this.defaultLoopButton) + .addComponent(this.alwaysLoopButton).addComponent(this.neverLoopButton).addGap(16) + .addComponent(this.speedSliderLabel).addComponent(this.speedSlider).addGap(16) + .addComponent(levelOfDetailLabel).addComponent(levelOfDetailSpinner) + + ); + setLayout(groupLayout); + + this.defaultLoopButton.doClick(); + } + + private void update(final boolean playSequence) { + SequenceLoopMode loopType; + if (this.defaultLoopButton.isSelected()) { + loopType = SequenceLoopMode.MODEL_LOOP; + } + else if (this.alwaysLoopButton.isSelected()) { + loopType = SequenceLoopMode.ALWAYS_LOOP; + } + else if (this.neverLoopButton.isSelected()) { + loopType = SequenceLoopMode.NEVER_LOOP; + } + else { + throw new IllegalStateException(); + } + this.speedSliderLabel.setText("Speed: " + (this.speedSlider.getValue() * 2) + "%"); + final MdxComplexInstance mainInstance = this.previewApplication.getMainInstance(); + if (mainInstance != null) { + mainInstance.setAnimationSpeed(this.speedSlider.getValue() / 50f); + mainInstance.setSequenceLoopMode(loopType); + if (playSequence) { + mainInstance.setSequence(this.animationBox.getSelectedIndex() - 1); + } + } + } + + @Override + public void openModel(final MdlxModel model) { + this.model = model; + + repopulateSequenceList(); + } + + private void repopulateSequenceList() { + this.animations.removeAllElements(); + this.animations.addElement(null); + if (this.model != null) { + for (final Sequence animation : this.model.getSequences()) { + this.animations.addElement(animation); + } + } + } + + @Override + public void stateChanged() { + + } +} diff --git a/desktop/src/com/etheller/warsmash/desktop/editor/mdx/ui/YseraFrame.java b/desktop/src/com/etheller/warsmash/desktop/editor/mdx/ui/YseraFrame.java new file mode 100644 index 0000000..e75c53f --- /dev/null +++ b/desktop/src/com/etheller/warsmash/desktop/editor/mdx/ui/YseraFrame.java @@ -0,0 +1,64 @@ +package com.etheller.warsmash.desktop.editor.mdx.ui; + +import java.awt.event.WindowEvent; +import java.awt.event.WindowListener; + +import javax.swing.JFrame; + +import com.badlogic.gdx.Gdx; +import com.etheller.warsmash.WarsmashPreviewApplication; +import com.etheller.warsmash.units.DataTable; + +public class YseraFrame extends JFrame { + public YseraFrame(final DataTable warsmashIni) { + super("Warsmash Model Editor"); + setDefaultCloseOperation(DISPOSE_ON_CLOSE); + final WarsmashPreviewApplication warsmashPreviewApplication = new WarsmashPreviewApplication(warsmashIni); + final YseraPanel contentPane = new YseraPanel(warsmashPreviewApplication); + setContentPane(contentPane); +// setIconImage(ImageUtils.getBLPImage(warsmashGdxGame.getCodebase(), +// "ReplaceableTextures\\CommandButtons\\BTNGreenDragon.blp")); + setJMenuBar(contentPane.createJMenuBar(this)); + pack(); + setLocationRelativeTo(null); + + addWindowListener(new WindowListener() { + + @Override + public void windowOpened(final WindowEvent e) { + + } + + @Override + public void windowIconified(final WindowEvent e) { + + } + + @Override + public void windowDeiconified(final WindowEvent e) { + + } + + @Override + public void windowDeactivated(final WindowEvent e) { + + } + + @Override + public void windowClosing(final WindowEvent e) { + Gdx.app.exit(); + } + + @Override + public void windowClosed(final WindowEvent e) { + + } + + @Override + public void windowActivated(final WindowEvent e) { + + } + }); + } + +} diff --git a/desktop/src/com/etheller/warsmash/desktop/editor/mdx/ui/YseraPanel.java b/desktop/src/com/etheller/warsmash/desktop/editor/mdx/ui/YseraPanel.java new file mode 100644 index 0000000..51a40bf --- /dev/null +++ b/desktop/src/com/etheller/warsmash/desktop/editor/mdx/ui/YseraPanel.java @@ -0,0 +1,193 @@ +package com.etheller.warsmash.desktop.editor.mdx.ui; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; + +import javax.swing.JFileChooser; +import javax.swing.JFrame; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JMenuItem; +import javax.swing.JPanel; +import javax.swing.filechooser.FileNameExtensionFilter; + +import org.lwjgl.util.vector.Quaternion; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.Input; +import com.badlogic.gdx.InputProcessor; +import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration; +import com.badlogic.gdx.backends.lwjgl.LwjglCanvas; +import com.badlogic.gdx.math.Vector3; +import com.etheller.warsmash.WarsmashPreviewApplication; +import com.etheller.warsmash.desktop.editor.mdx.listeners.YseraGUIListener; +import com.etheller.warsmash.desktop.editor.util.ExceptionPopup; +import com.etheller.warsmash.parsers.mdlx.MdlxModel; +import com.etheller.warsmash.viewer5.handlers.w3x.camera.PortraitCameraManager; + +public class YseraPanel extends JPanel { + private static final Quaternion IDENTITY = new Quaternion(); + private final WarsmashPreviewApplication warsmashPreviewApplication; + private final JFileChooser userFileChooser = new JFileChooser(); + private final YseraGUIListener.YseraGUINotifier notifier = new YseraGUIListener.YseraGUINotifier(); + + private MdlxModel model; + + private AnimationControllerFrame animationControllerFrame; + + public YseraPanel(final WarsmashPreviewApplication warsmashPreviewApplication) { + this.warsmashPreviewApplication = warsmashPreviewApplication; + setLayout(new BorderLayout()); + final LwjglApplicationConfiguration config = new LwjglApplicationConfiguration(); + config.useGL30 = true; + config.gles30ContextMajorVersion = 3; + config.gles30ContextMinorVersion = 3; + final LwjglCanvas lwjglCanvas = new LwjglCanvas(warsmashPreviewApplication, config); + add(BorderLayout.CENTER, lwjglCanvas.getCanvas()); + setPreferredSize(new Dimension(640, 480)); + this.userFileChooser + .setFileFilter(new FileNameExtensionFilter("Warcraft III Model or Texture", "mdx", "mdl", "blp")); + + final CameraMouseHandler cameraMouseHandler = new CameraMouseHandler(warsmashPreviewApplication); + Gdx.input.setInputProcessor(cameraMouseHandler); + + } + + public JMenuBar createJMenuBar(final JFrame frame) { + final JMenuBar jMenuBar = new JMenuBar(); + + final JMenu fileMenu = new JMenu("File"); + final JMenuItem openItem = new JMenuItem("Open"); + openItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(final ActionEvent e) { + try { + final int userResult = YseraPanel.this.userFileChooser.showOpenDialog(frame); + if (userResult == JFileChooser.APPROVE_OPTION) { + final File selectedFile = YseraPanel.this.userFileChooser.getSelectedFile(); + if (selectedFile != null) { + YseraPanel.this.model = YseraPanel.this.warsmashPreviewApplication + .loadCustomModel(selectedFile.getPath()); + YseraPanel.this.notifier.openModel(YseraPanel.this.model); + } + } + } + catch (final Exception exc) { + ExceptionPopup.display(exc); + } + } + }); + fileMenu.add(openItem); + jMenuBar.add(fileMenu); + jMenuBar.add(new JMenu("Recent Files")); + jMenuBar.add(new JMenu("Edit")); + jMenuBar.add(new JMenu("View")); + jMenuBar.add(new JMenu("Team Color")); + final JMenu windowMenu = new JMenu("Windows"); + final JMenuItem modelEditorItem = new JMenuItem("Model Editor"); + windowMenu.add(modelEditorItem); + final JMenuItem animationControllerItem = new JMenuItem("Animation Controller"); + animationControllerItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(final ActionEvent e) { + if (YseraPanel.this.animationControllerFrame == null) { + YseraPanel.this.animationControllerFrame = new AnimationControllerFrame( + YseraPanel.this.warsmashPreviewApplication); + YseraPanel.this.notifier.subscribe(YseraPanel.this.animationControllerFrame); + YseraPanel.this.animationControllerFrame.setLocationRelativeTo(frame); + } + YseraPanel.this.animationControllerFrame.setVisible(true); + YseraPanel.this.animationControllerFrame.toFront(); + } + }); + windowMenu.add(animationControllerItem); + jMenuBar.add(windowMenu); + jMenuBar.add(new JMenu("Extras")); + jMenuBar.add(new JMenu("Help")); + + return jMenuBar; + } + + private static final class CameraMouseHandler implements InputProcessor { + private int lastX, lastY; + private final Vector3 screenDimension = new Vector3(); + private int button; + private final WarsmashPreviewApplication warsmashPreviewApplication; + + public CameraMouseHandler(final WarsmashPreviewApplication warsmashPreviewApplication) { + this.warsmashPreviewApplication = warsmashPreviewApplication; + } + + @Override + public boolean keyDown(final int keycode) { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean keyUp(final int keycode) { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean keyTyped(final char character) { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean touchDown(final int screenX, final int screenY, final int pointer, final int button) { + this.lastX = screenX; + this.lastY = screenY; + this.button = button; + return false; + } + + @Override + public boolean touchUp(final int screenX, final int screenY, final int pointer, final int button) { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean touchDragged(final int screenX, final int screenY, final int pointer) { + final int newX = screenX; + final int newY = screenY; + final int dx = newX - this.lastX; + final int dy = newY - this.lastY; + final PortraitCameraManager cameraManager = this.warsmashPreviewApplication.getCameraManager(); + if (this.button == Input.Buttons.RIGHT) { + this.screenDimension.set(-1, 0, 0); + this.screenDimension.unrotate(cameraManager.camera.viewProjectionMatrix); + cameraManager.target.add(this.screenDimension.nor().scl(dx * 5)); + this.screenDimension.set(0, 1, 0); + this.screenDimension.unrotate(cameraManager.camera.viewProjectionMatrix); + cameraManager.target.add(this.screenDimension.nor().scl(dy * 5)); + } + else if (this.button == Input.Buttons.LEFT) { + cameraManager.horizontalAngle -= Math.toRadians(dx); + cameraManager.verticalAngle -= Math.toRadians(dy); + } + this.lastX = newX; + this.lastY = newY; + return false; + } + + @Override + public boolean mouseMoved(final int screenX, final int screenY) { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean scrolled(final int amount) { + final PortraitCameraManager cameraManager = this.warsmashPreviewApplication.getCameraManager(); + cameraManager.distance += amount * 100; + return false; + } + } +} diff --git a/desktop/src/com/etheller/warsmash/desktop/editor/util/ExceptionPopup.java b/desktop/src/com/etheller/warsmash/desktop/editor/util/ExceptionPopup.java new file mode 100644 index 0000000..588414a --- /dev/null +++ b/desktop/src/com/etheller/warsmash/desktop/editor/util/ExceptionPopup.java @@ -0,0 +1,84 @@ +package com.etheller.warsmash.desktop.editor.util; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; + +import javax.swing.JOptionPane; +import javax.swing.JTextPane; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; + +public class ExceptionPopup { + public static void display(final Throwable e) { + + final JTextPane pane = new JTextPane(); + final OutputStream stream = new OutputStream() { + public void updateStreamWith(final String s) { + final Document doc = pane.getDocument(); + try { + doc.insertString(doc.getLength(), s, null); + } + catch (final BadLocationException e) { + JOptionPane.showMessageDialog(null, "MDL open error popup failed to create info popup."); + e.printStackTrace(); + } + } + + @Override + public void write(final int b) throws IOException { + updateStreamWith(String.valueOf((char) b)); + } + + @Override + public void write(final byte[] b, final int off, final int len) throws IOException { + updateStreamWith(new String(b, off, len)); + } + + @Override + public void write(final byte[] b) throws IOException { + write(b, 0, b.length); + } + }; + final PrintStream ps = new PrintStream(stream); + ps.println("Unknown error occurred:"); + e.printStackTrace(ps); + JOptionPane.showMessageDialog(null, pane); + } + + public static void display(final String s, final Exception e) { + + final JTextPane pane = new JTextPane(); + final OutputStream stream = new OutputStream() { + public void updateStreamWith(final String s) { + final Document doc = pane.getDocument(); + try { + doc.insertString(doc.getLength(), s, null); + } + catch (final BadLocationException e) { + JOptionPane.showMessageDialog(null, "MDL open error popup failed to create info popup."); + e.printStackTrace(); + } + } + + @Override + public void write(final int b) throws IOException { + updateStreamWith(String.valueOf((char) b)); + } + + @Override + public void write(final byte[] b, final int off, final int len) throws IOException { + updateStreamWith(new String(b, off, len)); + } + + @Override + public void write(final byte[] b) throws IOException { + write(b, 0, b.length); + } + }; + final PrintStream ps = new PrintStream(stream); + ps.println(s + ":"); + e.printStackTrace(ps); + JOptionPane.showMessageDialog(null, pane); + } +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/BackdropCornerFlags.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/BackdropCornerFlags.java index eae2f0d..116f939 100644 --- a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/BackdropCornerFlags.java +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/BackdropCornerFlags.java @@ -1,5 +1,7 @@ package com.etheller.warsmash.parsers.fdf.datamodel; +import java.util.EnumSet; + public enum BackdropCornerFlags { UL, UR, @@ -9,4 +11,14 @@ public enum BackdropCornerFlags { L, B, R; + + public static EnumSet parseCornerFlags(final String cornerFlags) { + final EnumSet set = EnumSet.noneOf(BackdropCornerFlags.class); + for (final String flag : cornerFlags.split("\\|")) { + if (!"".equals(flag)) { + set.add(BackdropCornerFlags.valueOf(flag)); + } + } + return set; + } } diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FrameDefinition.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FrameDefinition.java index b655c6d..dc0b1cf 100644 --- a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FrameDefinition.java +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FrameDefinition.java @@ -12,6 +12,7 @@ import com.etheller.warsmash.parsers.fdf.datamodel.fields.visitor.GetFloatFieldV import com.etheller.warsmash.parsers.fdf.datamodel.fields.visitor.GetFontFieldVisitor; import com.etheller.warsmash.parsers.fdf.datamodel.fields.visitor.GetStringFieldVisitor; import com.etheller.warsmash.parsers.fdf.datamodel.fields.visitor.GetTextJustifyFieldVisitor; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.visitor.GetVector2FieldVisitor; import com.etheller.warsmash.parsers.fdf.datamodel.fields.visitor.GetVector4FieldVisitor; /** @@ -126,6 +127,14 @@ public class FrameDefinition { return null; } + public Vector2Definition getVector2(final String id) { + final FrameDefinitionField frameDefinitionField = this.nameToField.get(id); + if (frameDefinitionField != null) { + return frameDefinitionField.visit(GetVector2FieldVisitor.INSTANCE); + } + return null; + } + public FontDefinition getFont(final String id) { final FrameDefinitionField frameDefinitionField = this.nameToField.get(id); if (frameDefinitionField != null) { diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/visitor/GetVector2FieldVisitor.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/visitor/GetVector2FieldVisitor.java new file mode 100644 index 0000000..e49490d --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/visitor/GetVector2FieldVisitor.java @@ -0,0 +1,57 @@ +package com.etheller.warsmash.parsers.fdf.datamodel.fields.visitor; + +import com.etheller.warsmash.parsers.fdf.datamodel.Vector2Definition; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.FloatFrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.FontFrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.FrameDefinitionFieldVisitor; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.StringFrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.StringPairFrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.TextJustifyFrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.Vector2FrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.Vector3FrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.Vector4FrameDefinitionField; + +public class GetVector2FieldVisitor implements FrameDefinitionFieldVisitor { + public static GetVector2FieldVisitor INSTANCE = new GetVector2FieldVisitor(); + + @Override + public Vector2Definition accept(final StringFrameDefinitionField field) { + return null; + } + + @Override + public Vector2Definition accept(final StringPairFrameDefinitionField field) { + return null; + } + + @Override + public Vector2Definition accept(final FloatFrameDefinitionField field) { + return null; + } + + @Override + public Vector2Definition accept(final Vector3FrameDefinitionField field) { + return null; + } + + @Override + public Vector2Definition accept(final Vector2FrameDefinitionField field) { + return field.getValue(); + } + + @Override + public Vector2Definition accept(final Vector4FrameDefinitionField field) { + return null; + } + + @Override + public Vector2Definition accept(final FontFrameDefinitionField field) { + return null; + } + + @Override + public Vector2Definition accept(final TextJustifyFrameDefinitionField field) { + return null; + } + +} From 4f4f585beea9ef90b54bd8a813320a4810989303 Mon Sep 17 00:00:00 2001 From: Retera Date: Thu, 17 Dec 2020 00:10:12 -0500 Subject: [PATCH 075/116] Update unit pushing functions and add unstuck on unit movement --- .../handlers/w3x/simulation/CUnit.java | 70 +++++++++++++------ .../simulation/behaviors/CBehaviorMove.java | 9 +++ 2 files changed, 56 insertions(+), 23 deletions(-) diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java index 7f8e55e..af090d9 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java @@ -14,6 +14,7 @@ import com.etheller.warsmash.util.WarsmashConstants; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; import com.etheller.warsmash.viewer5.handlers.w3x.environment.BuildingShadow; +import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.RemovablePathingMapInstance; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitStateListener.CUnitStateNotifier; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; @@ -452,6 +453,51 @@ public class CUnit extends CWidget { } } + public void setPointAndCheckUnstuck(final float newX, final float newY, final CSimulation game) { + final CWorldCollision collision = game.getWorldCollision(); + final PathingGrid pathingGrid = game.getPathingGrid(); + ; + float outputX = newX, outputY = newY; + int checkX = 0; + int checkY = 0; + float collisionSize; + if (this.unitType.getBuildingPathingPixelMap() != null) { + tempRect.setSize(this.unitType.getBuildingPathingPixelMap().getWidth() * 32, + this.unitType.getBuildingPathingPixelMap().getHeight() * 32); + collisionSize = tempRect.getWidth() / 2; + } + else if (this.collisionRectangle != null) { + tempRect.set(this.collisionRectangle); + collisionSize = this.unitType.getCollisionSize(); + } + else { + tempRect.setSize(16, 16); + collisionSize = this.unitType.getCollisionSize(); + } + boolean repos = false; + for (int i = 0; i < 300; i++) { + final float centerX = newX + (checkX * 64); + final float centerY = newY + (checkY * 64); + tempRect.setCenter(centerX, centerY); + if (!collision.intersectsAnythingOtherThan(tempRect, this, this.unitType.getMovementType()) + && pathingGrid.isPathable(centerX, centerY, this.unitType.getMovementType(), collisionSize)) { + outputX = centerX; + outputY = centerY; + if (i != 0) { + repos = true; + } + break; + } + final double angle = ((((int) Math.floor(Math.sqrt((4 * i) + 1))) % 4) * Math.PI) / 2; + checkX -= (int) Math.cos(angle); + checkY -= (int) Math.sin(angle); + } + setPoint(outputX, outputY, collision); + if (repos) { + game.unitRepositioned(this); + } + } + public void setPoint(final float newX, final float newY, final CWorldCollision collision) { final float prevX = getX(); final float prevY = getY(); @@ -844,29 +890,7 @@ public class CUnit extends CWidget { } private void nudgeAround(final CSimulation simulation, final CUnit structure) { - float x, y; - if (structure.collisionRectangle != null) { - x = structure.collisionRectangle.x; - if (this.collisionRectangle != null) { - y = structure.collisionRectangle.y - this.collisionRectangle.height; - } - else { - y = structure.collisionRectangle.y; - } - } - else { - if (this.collisionRectangle != null) { - x = structure.getX() - (this.collisionRectangle.width / 2); - y = structure.getY() - (this.collisionRectangle.height / 2); - } - else { - x = structure.getX(); - y = structure.getY(); - } - } - setX(x, simulation.getWorldCollision()); - setY(y, simulation.getWorldCollision()); - simulation.unitRepositioned(this); + setPointAndCheckUnstuck(structure.getX(), structure.getY(), simulation); } @Override diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorMove.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorMove.java index 209a6b4..6e0c415 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorMove.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorMove.java @@ -39,6 +39,7 @@ public class CBehaviorMove implements CBehavior { private int searchCycles = 0; private CUnit followUnit; private CRangedBehavior rangedBehavior; + private boolean firstUpdate = true; public CBehaviorMove reset(final int highlightOrderId, final AbilityTarget target) { target.visit(this.targetVisitingResetter.reset(highlightOrderId)); @@ -63,6 +64,7 @@ public class CBehaviorMove implements CBehavior { this.path = null; this.searchCycles = 0; this.followUnit = null; + this.firstUpdate = true; } private void internalResetMove(final int highlightOrderId, final CUnit followUnit) { @@ -75,6 +77,7 @@ public class CBehaviorMove implements CBehavior { this.path = null; this.searchCycles = 0; this.followUnit = followUnit; + this.firstUpdate = true; } @Override @@ -87,6 +90,12 @@ public class CBehaviorMove implements CBehavior { if ((this.rangedBehavior != null) && this.rangedBehavior.isWithinRange(simulation)) { return this.rangedBehavior.update(simulation); } + if (this.firstUpdate) { + // when units start moving, if they're on top of other units, maybe push them to + // the side + this.unit.setPointAndCheckUnstuck(this.unit.getX(), this.unit.getY(), simulation); + this.firstUpdate = false; + } final float prevX = this.unit.getX(); final float prevY = this.unit.getY(); From 03c3d34aa362464d2f3649c02fd8daf8489eb323 Mon Sep 17 00:00:00 2001 From: Retera Date: Fri, 18 Dec 2020 23:59:21 -0500 Subject: [PATCH 076/116] Add worker icon to constructing structure --- .../viewer5/handlers/w3x/War3MapViewer.java | 3 + .../handlers/w3x/simulation/CUnit.java | 9 +- .../viewer5/handlers/w3x/ui/MeleeUI.java | 150 +++++++++++------- 3 files changed, 100 insertions(+), 62 deletions(-) diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index bf9083d..46231d9 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -1259,6 +1259,9 @@ public class War3MapViewer extends AbstractMdxModelViewer { @Override public void accept(final SplatMover t) { unit.selectionCircle = t; + if (unit.instance.hidden()) { + unit.selectionCircle.hide(); + } } }); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java index af090d9..11c7e44 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java @@ -474,7 +474,7 @@ public class CUnit extends CWidget { tempRect.setSize(16, 16); collisionSize = this.unitType.getCollisionSize(); } - boolean repos = false; + final boolean repos = false; for (int i = 0; i < 300; i++) { final float centerX = newX + (checkX * 64); final float centerY = newY + (checkY * 64); @@ -483,9 +483,6 @@ public class CUnit extends CWidget { && pathingGrid.isPathable(centerX, centerY, this.unitType.getMovementType(), collisionSize)) { outputX = centerX; outputY = centerY; - if (i != 0) { - repos = true; - } break; } final double angle = ((((int) Math.floor(Math.sqrt((4 * i) + 1))) % 4) * Math.PI) / 2; @@ -493,9 +490,7 @@ public class CUnit extends CWidget { checkY -= (int) Math.sin(angle); } setPoint(outputX, outputY, collision); - if (repos) { - game.unitRepositioned(this); - } + game.unitRepositioned(this); } public void setPoint(final float newX, final float newY, final CWorldCollision collision) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java index 48dee2e..3695ad6 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java @@ -3,6 +3,7 @@ package com.etheller.warsmash.viewer5.handlers.w3x.ui; import java.awt.image.BufferedImage; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.EnumSet; import java.util.List; import java.util.concurrent.TimeUnit; @@ -174,6 +175,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma private StringFrame simpleBuildingBuildingActionLabel; private SimpleStatusBarFrame simpleBuildingBuildTimeIndicator; private final QueueIcon[] queueIconFrames = new QueueIcon[WarsmashConstants.BUILD_QUEUE_SIZE]; + private QueueIcon selectWorkerInsideFrame; private UIFrame attack1Icon; private TextureFrame attack1IconBackdrop; @@ -438,6 +440,19 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma queueIconFrameBackdrop.setHeight(queueIconWidth); this.rootFrame.add(this.queueIconFrames[i]); } + this.selectWorkerInsideFrame = new QueueIcon("SmashBuildQueueWorkerIcon", this.smashSimpleInfoPanel, this, 1); + final TextureFrame selectWorkerInsideIconFrameBackdrop = new TextureFrame("SmashBuildQueueWorkerIconBackdrop", + this.queueIconFrames[0], false, new Vector4Definition(0, 1, 0, 1)); + this.selectWorkerInsideFrame.set(selectWorkerInsideIconFrameBackdrop); + selectWorkerInsideIconFrameBackdrop + .addSetPoint(new SetPoint(FramePoint.CENTER, this.selectWorkerInsideFrame, FramePoint.CENTER, 0, 0)); + this.selectWorkerInsideFrame + .addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.queueIconFrames[1], FramePoint.TOPLEFT, 0, 0)); + this.selectWorkerInsideFrame.setWidth(frontQueueIconWidth); + this.selectWorkerInsideFrame.setHeight(frontQueueIconWidth); + selectWorkerInsideIconFrameBackdrop.setWidth(frontQueueIconWidth); + selectWorkerInsideIconFrameBackdrop.setHeight(frontQueueIconWidth); + this.rootFrame.add(this.selectWorkerInsideFrame); this.smashAttack1IconWrapper = (SimpleFrame) this.rootFrame.createSimpleFrame("SmashSimpleInfoPanelIconDamage", this.simpleInfoPanelUnitDetail, 0); @@ -1162,6 +1177,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma for (final QueueIcon queueIconFrame : this.queueIconFrames) { queueIconFrame.setVisible(false); } + this.selectWorkerInsideFrame.setVisible(false); this.rallyPointInstance.hide(); this.rallyPointInstance.detach(); } @@ -1320,9 +1336,19 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.queueIconFrames[0].setVisible(true); this.queueIconFrames[0].setTexture(this.war3MapViewer.getAbilityDataUI() .getUnitUI(this.selectedUnit.getSimulationUnit().getTypeId()).getIcon()); + + if (this.selectedUnit.getSimulationUnit().getWorkerInside() != null) { + this.selectWorkerInsideFrame.setVisible(true); + this.selectWorkerInsideFrame.setTexture(this.war3MapViewer.getAbilityDataUI() + .getUnitUI(this.selectedUnit.getSimulationUnit().getWorkerInside().getTypeId()).getIcon()); + } + else { + this.selectWorkerInsideFrame.setVisible(false); + } } else { this.simpleBuildingActionLabel.setText(""); + this.selectWorkerInsideFrame.setVisible(false); } final Texture defenseTexture = this.defenseBackdrops .getTexture(simulationUnit.getUnitType().getDefenseType()); @@ -1673,50 +1699,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } else { final List selectedUnits = this.war3MapViewer.selectUnit(screenX, worldScreenY, false); - if (!selectedUnits.isEmpty()) { - final RenderUnit unit = selectedUnits.get(0); - final boolean selectionChanged = getSelectedUnit() != unit; - boolean playedNewSound = false; - if (selectionChanged) { - this.selectedSoundCount = 0; - } - if (unit.soundset != null) { - UnitSound ackSoundToPlay = unit.soundset.what; - int soundIndex; - final int pissedSoundCount = unit.soundset.pissed.getSoundCount(); - if (unit.getSimulationUnit().isConstructing()) { - ackSoundToPlay = this.war3MapViewer.getUiSounds() - .getSound(this.rootFrame.getSkinField("ConstructingBuilding")); - soundIndex = (int) (Math.random() * ackSoundToPlay.getSoundCount()); - } - else { - if ((this.selectedSoundCount >= 3) && (pissedSoundCount > 0)) { - soundIndex = this.selectedSoundCount - 3; - ackSoundToPlay = unit.soundset.pissed; - } - else { - soundIndex = (int) (Math.random() * ackSoundToPlay.getSoundCount()); - } - } - if ((ackSoundToPlay != null) && ackSoundToPlay - .playUnitResponse(this.war3MapViewer.worldScene.audioContext, unit, soundIndex)) { - this.selectedSoundCount++; - if ((this.selectedSoundCount - 3) >= pissedSoundCount) { - this.selectedSoundCount = 0; - } - playedNewSound = true; - } - } - if (selectionChanged) { - selectUnit(unit); - } - if (playedNewSound) { - portraitTalk(); - } - } - else { - selectUnit(null); - } + selectUnits(selectedUnits); } } } @@ -1729,6 +1712,53 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma return false; } + private void selectUnits(final List selectedUnits) { + if (!selectedUnits.isEmpty()) { + final RenderUnit unit = selectedUnits.get(0); + final boolean selectionChanged = getSelectedUnit() != unit; + boolean playedNewSound = false; + if (selectionChanged) { + this.selectedSoundCount = 0; + } + if (unit.soundset != null) { + UnitSound ackSoundToPlay = unit.soundset.what; + int soundIndex; + final int pissedSoundCount = unit.soundset.pissed.getSoundCount(); + if (unit.getSimulationUnit().isConstructing()) { + ackSoundToPlay = this.war3MapViewer.getUiSounds() + .getSound(this.rootFrame.getSkinField("ConstructingBuilding")); + soundIndex = (int) (Math.random() * ackSoundToPlay.getSoundCount()); + } + else { + if ((this.selectedSoundCount >= 3) && (pissedSoundCount > 0)) { + soundIndex = this.selectedSoundCount - 3; + ackSoundToPlay = unit.soundset.pissed; + } + else { + soundIndex = (int) (Math.random() * ackSoundToPlay.getSoundCount()); + } + } + if ((ackSoundToPlay != null) && ackSoundToPlay + .playUnitResponse(this.war3MapViewer.worldScene.audioContext, unit, soundIndex)) { + this.selectedSoundCount++; + if ((this.selectedSoundCount - 3) >= pissedSoundCount) { + this.selectedSoundCount = 0; + } + playedNewSound = true; + } + } + if (selectionChanged) { + selectUnit(unit); + } + if (playedNewSound) { + portraitTalk(); + } + } + else { + selectUnit(null); + } + } + public boolean touchUp(final int screenX, final int screenY, final float worldScreenY, final int button) { screenCoordsVector.set(screenX, screenY); this.uiViewport.unproject(screenCoordsVector); @@ -1769,20 +1799,30 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma public void queueIconClicked(final int index) { final CUnit simulationUnit = this.selectedUnit.getSimulationUnit(); if (simulationUnit.isConstructing()) { - for (final CAbility ability : simulationUnit.getAbilities()) { - ability.checkCanUse(this.war3MapViewer.simulation, simulationUnit, OrderIds.cancel, - BooleanAbilityActivationReceiver.INSTANCE); - if (BooleanAbilityActivationReceiver.INSTANCE.isOk()) { + switch (index) { + case 0: + for (final CAbility ability : simulationUnit.getAbilities()) { + ability.checkCanUse(this.war3MapViewer.simulation, simulationUnit, OrderIds.cancel, + BooleanAbilityActivationReceiver.INSTANCE); + if (BooleanAbilityActivationReceiver.INSTANCE.isOk()) { - final BooleanAbilityTargetCheckReceiver targetCheckReceiver = BooleanAbilityTargetCheckReceiver - .getInstance().reset(); - ability.checkCanTargetNoTarget(this.war3MapViewer.simulation, simulationUnit, OrderIds.cancel, - targetCheckReceiver); - if (targetCheckReceiver.isTargetable()) { - this.unitOrderListener.issueImmediateOrder(simulationUnit.getHandleId(), ability.getHandleId(), - OrderIds.cancel, false); + final BooleanAbilityTargetCheckReceiver targetCheckReceiver = BooleanAbilityTargetCheckReceiver + .getInstance().reset(); + ability.checkCanTargetNoTarget(this.war3MapViewer.simulation, simulationUnit, OrderIds.cancel, + targetCheckReceiver); + if (targetCheckReceiver.isTargetable()) { + this.unitOrderListener.issueImmediateOrder(simulationUnit.getHandleId(), + ability.getHandleId(), OrderIds.cancel, false); + } } } + break; + case 1: + final List unitList = Arrays.asList( + this.war3MapViewer.getRenderPeer(this.selectedUnit.getSimulationUnit().getWorkerInside())); + this.war3MapViewer.doSelectUnit(unitList); + selectUnits(unitList); + break; } } else { From a5d4085e473128c025c160009549b0fc861683ee Mon Sep 17 00:00:00 2001 From: Retera Date: Sat, 19 Dec 2020 01:02:20 -0500 Subject: [PATCH 077/116] Backport terrain shaders to version 330 core --- .../handlers/w3x/environment/CliffMesh.java | 16 +- .../handlers/w3x/environment/Terrain.java | 84 ++-- .../w3x/environment/TerrainShaders.java | 383 ++++++------------ 3 files changed, 177 insertions(+), 306 deletions(-) diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/CliffMesh.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/CliffMesh.java index ab8411e..876e3b3 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/CliffMesh.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/CliffMesh.java @@ -8,6 +8,7 @@ import java.nio.FloatBuffer; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.GL30; +import com.badlogic.gdx.graphics.glutils.ShaderProgram; import com.etheller.warsmash.datasources.DataSource; import com.etheller.warsmash.parsers.mdlx.Geoset; import com.etheller.warsmash.parsers.mdlx.MdlxModel; @@ -71,7 +72,7 @@ public class CliffMesh { this.renderJobs.put(position); } - public void render() { + public void render(final ShaderProgram cliffShader) { if (this.renderJobs.position() == 0) { return; } @@ -82,23 +83,24 @@ public class CliffMesh { GL20.GL_DYNAMIC_DRAW); this.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.vertexBuffer); - this.gl.glVertexAttribPointer(0, 3, GL20.GL_FLOAT, false, 0, 0); + this.gl.glVertexAttribPointer(cliffShader.getAttributeLocation("vPosition"), 3, GL20.GL_FLOAT, false, 0, 0); this.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.uvBuffer); - this.gl.glVertexAttribPointer(1, 2, GL20.GL_FLOAT, false, 0, 0); + this.gl.glVertexAttribPointer(cliffShader.getAttributeLocation("vUV"), 2, GL20.GL_FLOAT, false, 0, 0); this.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.normalBuffer); - this.gl.glVertexAttribPointer(2, 3, GL20.GL_FLOAT, false, 0, 0); + this.gl.glVertexAttribPointer(cliffShader.getAttributeLocation("vNormal"), 3, GL20.GL_FLOAT, false, 0, 0); this.gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.instanceBuffer); - this.gl.glVertexAttribPointer(3, 4, GL20.GL_FLOAT, false, 0, 0); - this.gl.glVertexAttribDivisor(3, 1); + final int offsetAttributeLocation = cliffShader.getAttributeLocation("vOffset"); + this.gl.glVertexAttribPointer(offsetAttributeLocation, 4, GL20.GL_FLOAT, false, 0, 0); + this.gl.glVertexAttribDivisor(offsetAttributeLocation, 1); this.gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, this.indexBuffer); this.gl.glDrawElementsInstanced(GL20.GL_TRIANGLES, this.indices, GL30.GL_UNSIGNED_SHORT, 0, this.renderJobs.remaining() / 4); - this.gl.glVertexAttribDivisor(3, 0); // ToDo use vao + this.gl.glVertexAttribDivisor(offsetAttributeLocation, 0); // ToDo use vao this.renderJobs.clear(); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java index 7bb3a3a..921320e 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java @@ -66,7 +66,6 @@ public class Terrain { public ShaderProgram groundShader; public ShaderProgram waterShader; public ShaderProgram cliffShader; - public ShaderProgram testShader; public float waterIndex; public float waterIncreasePerFrame; public float waterHeightOffset; @@ -399,7 +398,6 @@ public class Terrain { this.groundShader = webGL.createShaderProgram(TerrainShaders.Terrain.vert, TerrainShaders.Terrain.frag); this.cliffShader = webGL.createShaderProgram(TerrainShaders.Cliffs.vert, TerrainShaders.Cliffs.frag); this.waterShader = webGL.createShaderProgram(TerrainShaders.Water.vert, TerrainShaders.Water.frag); - this.testShader = webGL.createShaderProgram(TerrainShaders.Test.vert, TerrainShaders.Test.frag); this.uberSplatShader = webGL.createShaderProgram(W3xShaders.UberSplat.vert, W3xShaders.UberSplat.frag); @@ -902,15 +900,35 @@ public class Terrain { gl.glUniformMatrix4fv(this.groundShader.getUniformLocation("DepthBiasMVP"), 1, false, dynamicShadowManager.getDepthBiasMVP().val, 0); + gl.glUniform1i(this.groundShader.getUniformLocation("cliff_textures"), 0); gl.glActiveTexture(GL30.GL_TEXTURE0); gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundHeight); gl.glActiveTexture(GL30.GL_TEXTURE1); gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundCornerHeight); + gl.glUniform1i(this.groundShader.getUniformLocation("pathing_map_static"), 2); gl.glActiveTexture(GL30.GL_TEXTURE2); gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundTextureData); + gl.glUniform1i(this.groundShader.getUniformLocation("sample0"), 3); + gl.glUniform1i(this.groundShader.getUniformLocation("sample1"), 4); + gl.glUniform1i(this.groundShader.getUniformLocation("sample2"), 5); + gl.glUniform1i(this.groundShader.getUniformLocation("sample3"), 6); + gl.glUniform1i(this.groundShader.getUniformLocation("sample4"), 7); + gl.glUniform1i(this.groundShader.getUniformLocation("sample5"), 8); + gl.glUniform1i(this.groundShader.getUniformLocation("sample6"), 9); + gl.glUniform1i(this.groundShader.getUniformLocation("sample7"), 10); + gl.glUniform1i(this.groundShader.getUniformLocation("sample8"), 11); + gl.glUniform1i(this.groundShader.getUniformLocation("sample9"), 12); + gl.glUniform1i(this.groundShader.getUniformLocation("sample10"), 13); + gl.glUniform1i(this.groundShader.getUniformLocation("sample11"), 14); + gl.glUniform1i(this.groundShader.getUniformLocation("sample12"), 15); + gl.glUniform1i(this.groundShader.getUniformLocation("sample13"), 16); + gl.glUniform1i(this.groundShader.getUniformLocation("sample14"), 17); + gl.glUniform1i(this.groundShader.getUniformLocation("sample15"), 18); + gl.glUniform1i(this.groundShader.getUniformLocation("sample16"), 19); + gl.glUniform1i(this.groundShader.getUniformLocation("shadowMap"), 20); for (int i = 0; i < this.groundTextures.size(); i++) { gl.glActiveTexture(GL30.GL_TEXTURE3 + i); gl.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.groundTextures.get(i).id); @@ -924,7 +942,7 @@ public class Terrain { // gl.glEnableVertexAttribArray(0); gl.glBindBuffer(GL30.GL_ARRAY_BUFFER, Shapes.INSTANCE.vertexBuffer); - gl.glVertexAttribPointer(0, 2, GL30.GL_FLOAT, false, 0, 0); + gl.glVertexAttribPointer(this.groundShader.getAttributeLocation("vPosition"), 2, GL30.GL_FLOAT, false, 0, 0); gl.glBindBuffer(GL30.GL_ELEMENT_ARRAY_BUFFER, Shapes.INSTANCE.indexBuffer); if (WIREFRAME_TERRAIN) { @@ -942,26 +960,6 @@ public class Terrain { } - private GL30 renderGroundIntersectionMesh() { - if (true) { - throw new UnsupportedOperationException("No longer supported"); - } - this.webGL.useShaderProgram(this.testShader); - - final GL30 gl = Gdx.gl30; - gl.glDisable(GL30.GL_CULL_FACE); - gl.glDisable(GL30.GL_BLEND); - this.testShader.setUniformMatrix("MVP", this.camera.viewProjectionMatrix); - gl.glBindBuffer(GL30.GL_ARRAY_BUFFER, this.testArrayBuffer); - this.testShader.setVertexAttribute("vPosition", 3, GL30.GL_FLOAT, false, 12, 0); - - gl.glBindBuffer(GL30.GL_ELEMENT_ARRAY_BUFFER, this.testElementBuffer); - gl.glDrawElements(GL30.GL_LINES, this.softwareGroundMesh.indices.length, GL30.GL_UNSIGNED_SHORT, 0);// ); - - gl.glEnable(GL30.GL_BLEND); - return gl; - } - public void renderUberSplats(final boolean onTopLayer) { final GL30 gl = Gdx.gl30; final WebGL webGL = this.webGL; @@ -1018,24 +1016,29 @@ public class Terrain { gl.glEnable(GL30.GL_BLEND); gl.glBlendFunc(GL30.GL_SRC_ALPHA, GL30.GL_ONE_MINUS_SRC_ALPHA); - gl.glUniformMatrix4fv(0, 1, false, this.camera.viewProjectionMatrix.val, 0); - gl.glUniform4fv(1, 1, this.minShallowColor, 0); - gl.glUniform4fv(2, 1, this.maxShallowColor, 0); - gl.glUniform4fv(3, 1, this.minDeepColor, 0); - gl.glUniform4fv(4, 1, this.maxDeepColor, 0); - gl.glUniform1f(5, this.waterHeightOffset); - gl.glUniform1i(6, (int) this.waterIndex); - gl.glUniform1f(this.waterShader.getUniformLocation("centerOffsetX"), this.centerOffset[0]); - gl.glUniform1f(this.waterShader.getUniformLocation("centerOffsetY"), this.centerOffset[1]); - gl.glUniform4fv(11, 1, this.shaderMapBounds, 0); + this.waterShader.setUniformMatrix4fv("MVP", this.camera.viewProjectionMatrix.val, 0, 16); + this.waterShader.setUniform4fv("shallow_color_min", this.minShallowColor, 0, 4); + this.waterShader.setUniform4fv("shallow_color_max", this.maxShallowColor, 0, 4); + this.waterShader.setUniform4fv("deep_color_min", this.minDeepColor, 0, 4); + this.waterShader.setUniform4fv("deep_color_max", this.maxDeepColor, 0, 4); + this.waterShader.setUniformf("water_offset", this.waterHeightOffset); + this.waterShader.setUniformi("current_texture", (int) this.waterIndex); + this.waterShader.setUniformf("centerOffsetX", this.centerOffset[0]); + this.waterShader.setUniformf("centerOffsetY", this.centerOffset[1]); + this.waterShader.setUniform4fv("mapBounds", this.shaderMapBounds, 0, 4); final W3xSceneLightManager lightManager = (W3xSceneLightManager) this.viewer.worldScene.getLightManager(); final DataTexture terrainLightsTexture = lightManager.getTerrainLightsTexture(); terrainLightsTexture.bind(3); - gl.glUniform1f(9, lightManager.getTerrainLightCount()); - gl.glUniform1f(10, terrainLightsTexture.getHeight()); + this.waterShader.setUniformi("lightTexture", 3); + this.waterShader.setUniformf("lightCount", lightManager.getTerrainLightCount()); + this.waterShader.setUniformf("lightTextureHeight", terrainLightsTexture.getHeight()); + this.waterShader.setUniformi("water_height_texture", 0); + this.waterShader.setUniformi("ground_height_texture", 1); + this.waterShader.setUniformi("water_exists_texture", 2); + this.waterShader.setUniformi("water_textures", 4); gl.glActiveTexture(GL30.GL_TEXTURE0); gl.glBindTexture(GL30.GL_TEXTURE_2D, this.waterHeight); gl.glActiveTexture(GL30.GL_TEXTURE1); @@ -1046,7 +1049,7 @@ public class Terrain { gl.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.waterTextureArray); gl.glBindBuffer(GL30.GL_ARRAY_BUFFER, Shapes.INSTANCE.vertexBuffer); - gl.glVertexAttribPointer(0, 2, GL30.GL_FLOAT, false, 0, 0); + gl.glVertexAttribPointer(this.waterShader.getAttributeLocation("vPosition"), 2, GL30.GL_FLOAT, false, 0, 0); gl.glBindBuffer(GL30.GL_ELEMENT_ARRAY_BUFFER, Shapes.INSTANCE.indexBuffer); gl.glDrawElementsInstanced(GL30.GL_TRIANGLES, Shapes.INSTANCE.quadIndices.length * 3, GL30.GL_UNSIGNED_INT, 0, @@ -1081,9 +1084,8 @@ public class Terrain { // WC3 models are 128x too large tempMatrix.set(this.camera.viewProjectionMatrix); - gl.glUniformMatrix4fv(0, 1, false, tempMatrix.val, 0); - gl.glUniform1i(1, this.viewer.renderPathing); - gl.glUniform1i(2, this.viewer.renderLighting); + gl.glUniformMatrix4fv(this.cliffShader.getUniformLocation("MVP"), 1, false, tempMatrix.val, 0); + gl.glUniform1i(this.cliffShader.getUniformLocation("show_lighting"), this.viewer.renderLighting); final W3xSceneLightManager lightManager = (W3xSceneLightManager) this.viewer.worldScene.getLightManager(); final DataTexture unitLightsTexture = lightManager.getTerrainLightsTexture(); @@ -1097,15 +1099,17 @@ public class Terrain { gl.glActiveTexture(GL30.GL_TEXTURE2); gl.glBindTexture(GL30.GL_TEXTURE_2D, this.shadowMap); + this.cliffShader.setUniformi("cliff_textures", 0); gl.glActiveTexture(GL30.GL_TEXTURE0); gl.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.cliffTextureArray); + this.cliffShader.setUniformi("height_texture", 1); gl.glActiveTexture(GL30.GL_TEXTURE1); gl.glBindTexture(GL30.GL_TEXTURE_2D, this.groundHeight); // gl.glActiveTexture(GL30.GL_TEXTURE2); for (final CliffMesh i : this.cliffMeshes) { gl.glUniform1f(this.cliffShader.getUniformLocation("centerOffsetX"), this.centerOffset[0]); gl.glUniform1f(this.cliffShader.getUniformLocation("centerOffsetY"), this.centerOffset[1]); - i.render(); + i.render(this.cliffShader); } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/TerrainShaders.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/TerrainShaders.java index 7164836..0864adb 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/TerrainShaders.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/TerrainShaders.java @@ -10,29 +10,29 @@ public class TerrainShaders { private Cliffs() { } - public static final String vert = "#version 450 core\r\n" + // + public static final String vert = "#version 330 core\r\n" + // "\r\n" + // - "layout (location = 0) in vec3 vPosition;\r\n" + // - "layout (location = 1) in vec2 vUV;\r\n" + // - "layout (location = 2) in vec3 vNormal;\r\n" + // - "layout (location = 3) in vec4 vOffset;\r\n" + // + "in vec3 vPosition;\r\n" + // + "in vec2 vUV;\r\n" + // + "in vec3 vNormal;\r\n" + // + "in vec4 vOffset;\r\n" + // "\r\n" + // - "layout (location = 0) uniform mat4 MVP;\r\n" + // + "uniform mat4 MVP;\r\n" + // "\r\n" + // - "layout (binding = 1) uniform sampler2D height_texture;\r\n" + // - "layout (binding = 3) uniform sampler2D shadowMap;\r\n" + // - "layout (location = 3) uniform float centerOffsetX;\r\n" + // - "layout (location = 4) uniform float centerOffsetY;\r\n" + // - "layout (location = 5) uniform sampler2D lightTexture;\r\n" + // - "layout (location = 6) uniform float lightCount;\r\n" + // - "layout (location = 7) uniform float lightTextureHeight;\r\n" + // + "uniform sampler2D height_texture;\r\n" + // + "uniform sampler2D shadowMap;\r\n" + // + "uniform float centerOffsetX;\r\n" + // + "uniform float centerOffsetY;\r\n" + // + "uniform sampler2D lightTexture;\r\n" + // + "uniform float lightCount;\r\n" + // + "uniform float lightTextureHeight;\r\n" + // "\r\n" + // - "layout (location = 0) out vec3 UV;\r\n" + // - "layout (location = 1) out vec3 Normal;\r\n" + // - "layout (location = 2) out vec2 pathing_map_uv;\r\n" + // - "layout (location = 3) out vec3 position;\r\n" + // - "layout (location = 4) out vec2 v_suv;\r\n" + // - "layout (location = 5) out vec3 shadeColor;\r\n" + // + "out vec3 UV;\r\n" + // + "out vec3 Normal;\r\n" + // + "out vec2 pathing_map_uv;\r\n" + // + "out vec3 position;\r\n" + // + "out vec2 v_suv;\r\n" + // + "out vec3 shadeColor;\r\n" + // "\r\n" + // "void main() {\r\n" + // " pathing_map_uv = (vec2(vPosition.x + 128, vPosition.y) / 128 + vOffset.xy) * 4;\r\n" + // @@ -61,26 +61,25 @@ public class TerrainShaders { " vec3 terrain_normal = normalize(vNormal);//vec3(hL - hR, hD - hU, 2.0)+vNormal);\r\n" + // "\r\n" + // " Normal = terrain_normal;\r\n" + // - Shaders.lightSystem("terrain_normal", "myposition.xyz", "lightTexture", "lightTextureHeight", "lightCount", - true) + Shaders.lightSystem("terrain_normal", "myposition.xyz", "lightTexture", "lightTextureHeight", + "lightCount", true) + "\r\n" + // " shadeColor = clamp(lightFactor, 0.0, 1.0);\r\n" + // "}"; - public static final String frag = "#version 450 core\r\n" + // + public static final String frag = "#version 330 core\r\n" + // "\r\n" + // - "layout (binding = 0) uniform sampler2DArray cliff_textures;\r\n" + // - "layout (binding = 2) uniform usampler2D pathing_map_static;\r\n" + // - "layout (binding = 3) uniform sampler2D shadowMap;\r\n" + // + "uniform sampler2DArray cliff_textures;\r\n" + // + "uniform sampler2D shadowMap;\r\n" + // "\r\n" + // - "layout (location = 1) uniform bool show_pathing_map_static;\r\n" + // - "layout (location = 2) uniform bool show_lighting;\r\n" + // + "uniform bool show_lighting;\r\n" + // "\r\n" + // - "layout (location = 0) in vec3 UV;\r\n" + // - "layout (location = 1) in vec3 Normal;\r\n" + // - "layout (location = 2) in vec2 pathing_map_uv;\r\n" + // - "layout (location = 4) in vec2 v_suv;\r\n" + // - "layout (location = 5) in vec3 shadeColor;\r\n" + // + "in vec3 UV;\r\n" + // + "in vec3 Normal;\r\n" + // + "in vec2 pathing_map_uv;\r\n" + // + "in vec3 position;\r\n" + // + "in vec2 v_suv;\r\n" + // + "in vec3 shadeColor;\r\n" + // "\r\n" + // "out vec4 color;\r\n" + // "\r\n" + // @@ -93,31 +92,6 @@ public class TerrainShaders { " color.rgb *= shadeColor;\r\n" + // " }\r\n" + // "\r\n" + // - " uvec4 byte = texelFetch(pathing_map_static, ivec2(pathing_map_uv), 0);\r\n" + // - " if (show_pathing_map_static) {\r\n" + // - " vec4 pathing_color = vec4(min(byte.r & 2, 1), min(byte.r & 4, 1), min(byte.r & 8, 1), 0.25);\r\n" - + // - " color = length(pathing_color.rgb) > 0 ? color * 0.75 + pathing_color * 0.5 : color;\r\n" + // - " }\r\n" + // - "}"; - - public static final String posFrag = "#version 450 core\r\n" + // - "\r\n" + // - "layout (binding = 0) uniform sampler2DArray cliff_textures;\r\n" + // - "layout (binding = 2) uniform usampler2D pathing_map_static;\r\n" + // - "\r\n" + // - "layout (location = 1) uniform bool show_pathing_map_static;\r\n" + // - "layout (location = 2) uniform bool show_lighting;\r\n" + // - "\r\n" + // - "layout (location = 0) in vec3 UV;\r\n" + // - "layout (location = 1) in vec3 Normal;\r\n" + // - "layout (location = 2) in vec2 pathing_map_uv;\r\n" + // - "layout (location = 3) in vec3 position;\r\n" + // - "\r\n" + // - "out vec4 color;\r\n" + // - "\r\n" + // - "void main() {\r\n" + /// - " color = vec4(position.xyz, 1.0);\r\n" + // "}"; } @@ -125,28 +99,28 @@ public class TerrainShaders { private Terrain() { } - public static final String vert = "#version 450 core\r\n" + // + public static final String vert = "#version 330 core\r\n" + // "\r\n" + // - "layout (location = 0) in vec2 vPosition;\r\n" + // - "layout (location = 1) uniform mat4 MVP;\r\n" + // - "layout (location = 6) uniform mat4 DepthBiasMVP;\r\n" + // + "in vec2 vPosition;\r\n" + // + "uniform mat4 MVP;\r\n" + // + "uniform mat4 DepthBiasMVP;\r\n" + // "\r\n" + // - "layout (binding = 0) uniform sampler2D height_texture;\r\n" + // - "layout (binding = 1) uniform sampler2D height_cliff_texture;\r\n" + // - "layout (binding = 2) uniform usampler2D terrain_texture_list;\r\n" + // - "layout (location = 4) uniform float centerOffsetX;\r\n" + // - "layout (location = 5) uniform float centerOffsetY;\r\n" + // - "layout (location = 7) uniform sampler2D lightTexture;\r\n" + // - "layout (location = 8) uniform float lightCount;\r\n" + // - "layout (location = 9) uniform float lightTextureHeight;\r\n" + // + "uniform sampler2D height_texture;\r\n" + // + "uniform sampler2D height_cliff_texture;\r\n" + // + "uniform usampler2D terrain_texture_list;\r\n" + // + "uniform float centerOffsetX;\r\n" + // + "uniform float centerOffsetY;\r\n" + // + "uniform sampler2D lightTexture;\r\n" + // + "uniform float lightCount;\r\n" + // + "uniform float lightTextureHeight;\r\n" + // "\r\n" + // - "layout (location = 0) out vec2 UV;\r\n" + // - "layout (location = 1) out flat uvec4 texture_indices;\r\n" + // - "layout (location = 2) out vec2 pathing_map_uv;\r\n" + // - "layout (location = 4) out vec3 position;\r\n" + // - "layout (location = 5) out vec3 ShadowCoord;\r\n" + // - "layout (location = 6) out vec2 v_suv;\r\n" + // - "layout (location = 7) out vec3 shadeColor;\r\n" + // + "out vec2 UV;\r\n" + // + "flat out uvec4 texture_indices;\r\n" + // + "out vec2 pathing_map_uv;\r\n" + // + "out vec3 position;\r\n" + // + "out vec3 ShadowCoord;\r\n" + // + "out vec2 v_suv;\r\n" + // + "out vec3 shadeColor;\r\n" + // "\r\n" + // "void main() { \r\n" + // " ivec2 size = textureSize(terrain_texture_list, 0);\r\n" + // @@ -170,9 +144,9 @@ public class TerrainShaders { " vec3 positionWorld = vec3((vPosition.x + pos.x)*128.0 + centerOffsetX, (vPosition.y + pos.y)*128.0 + centerOffsetY, height.r*128.0);\r\n" + // " position = positionWorld;\r\n" + // - " gl_Position = ((texture_indices.a & 32768) == 0) ? MVP * vec4(position.xyz, 1) : vec4(2.0, 0.0, 0.0, 1.0);\r\n" + " gl_Position = ((texture_indices.a & 32768u) == 0u) ? MVP * vec4(position.xyz, 1) : vec4(2.0, 0.0, 0.0, 1.0);\r\n" + // - " ShadowCoord = (((texture_indices.a & 32768) == 0) ? DepthBiasMVP * vec4(position.xyz, 1) : vec4(2.0, 0.0, 0.0, 1.0)).xyz;\r\n" + " ShadowCoord = (((texture_indices.a & 32768u) == 0u) ? DepthBiasMVP * vec4(position.xyz, 1) : vec4(2.0, 0.0, 0.0, 1.0)).xyz;\r\n" + // " v_suv = (vPosition + pos) / size;\r\n" + // " position.x = (position.x - centerOffsetX) / (size.x * 128.0);\r\n" + // @@ -182,42 +156,42 @@ public class TerrainShaders { " shadeColor = clamp(lightFactor, 0.0, 1.0);\r\n" + // "}"; - public static final String frag = "#version 450 core\r\n" + // + public static final String frag = "#version 330 core\r\n" + // "\r\n" + // - "layout (location = 2) uniform bool show_pathing_map;\r\n" + // - "layout (location = 3) uniform bool show_lighting;\r\n" + // + "uniform bool show_pathing_map;\r\n" + // + "uniform bool show_lighting;\r\n" + // "\r\n" + // - "layout (binding = 3) uniform sampler2DArray sample0;\r\n" + // - "layout (binding = 4) uniform sampler2DArray sample1;\r\n" + // - "layout (binding = 5) uniform sampler2DArray sample2;\r\n" + // - "layout (binding = 6) uniform sampler2DArray sample3;\r\n" + // - "layout (binding = 7) uniform sampler2DArray sample4;\r\n" + // - "layout (binding = 8) uniform sampler2DArray sample5;\r\n" + // - "layout (binding = 9) uniform sampler2DArray sample6;\r\n" + // - "layout (binding = 10) uniform sampler2DArray sample7;\r\n" + // - "layout (binding = 11) uniform sampler2DArray sample8;\r\n" + // - "layout (binding = 12) uniform sampler2DArray sample9;\r\n" + // - "layout (binding = 13) uniform sampler2DArray sample10;\r\n" + // - "layout (binding = 14) uniform sampler2DArray sample11;\r\n" + // - "layout (binding = 15) uniform sampler2DArray sample12;\r\n" + // - "layout (binding = 16) uniform sampler2DArray sample13;\r\n" + // - "layout (binding = 17) uniform sampler2DArray sample14;\r\n" + // - "layout (binding = 18) uniform sampler2DArray sample15;\r\n" + // - "layout (binding = 19) uniform sampler2DArray sample16;\r\n" + // + "uniform sampler2DArray sample0;\r\n" + // + "uniform sampler2DArray sample1;\r\n" + // + "uniform sampler2DArray sample2;\r\n" + // + "uniform sampler2DArray sample3;\r\n" + // + "uniform sampler2DArray sample4;\r\n" + // + "uniform sampler2DArray sample5;\r\n" + // + "uniform sampler2DArray sample6;\r\n" + // + "uniform sampler2DArray sample7;\r\n" + // + "uniform sampler2DArray sample8;\r\n" + // + "uniform sampler2DArray sample9;\r\n" + // + "uniform sampler2DArray sample10;\r\n" + // + "uniform sampler2DArray sample11;\r\n" + // + "uniform sampler2DArray sample12;\r\n" + // + "uniform sampler2DArray sample13;\r\n" + // + "uniform sampler2DArray sample14;\r\n" + // + "uniform sampler2DArray sample15;\r\n" + // + "uniform sampler2DArray sample16;\r\n" + // "\r\n" + // // "layout (binding = 20) uniform usampler2D pathing_map_static;\r\n" + // // "layout (binding = 21) uniform usampler2D pathing_map_dynamic;\r\n" + // - "layout (binding = 20) uniform sampler2D shadowMap;\r\n" + // + "uniform sampler2D shadowMap;\r\n" + // "\r\n" + // - "layout (location = 0) in vec2 UV;\r\n" + // - "layout (location = 1) in flat uvec4 texture_indices;\r\n" + // - "layout (location = 2) in vec2 pathing_map_uv;\r\n" + // - "layout (location = 4) in vec3 position;\r\n" + // - "layout (location = 5) in vec3 ShadowCoord;\r\n" + // - "layout (location = 6) in vec2 v_suv;\r\n" + // - "layout (location = 7) in vec3 shadeColor;\r\n" + // + "in vec2 UV;\r\n" + // + "flat in uvec4 texture_indices;\r\n" + // + "in vec2 pathing_map_uv;\r\n" + // + "in vec3 position;\r\n" + // + "in vec3 ShadowCoord;\r\n" + // + "in vec2 v_suv;\r\n" + // + "in vec3 shadeColor;\r\n" + // "\r\n" + // - "layout (location = 0) out vec4 color;\r\n" + // + "out vec4 color;\r\n" + // // "layout (location = 1) out vec4 position;\r\n" + // "\r\n" + // "vec4 get_fragment(uint id, vec3 uv) {\r\n" + // @@ -225,53 +199,53 @@ public class TerrainShaders { " vec2 dy = dFdy(uv.xy);\r\n" + // "\r\n" + // " switch(id) {\r\n" + // - " case 0:\r\n" + // + " case 0u:\r\n" + // " return textureGrad(sample0, uv, dx, dy);\r\n" + // - " case 1:\r\n" + // + " case 1u:\r\n" + // " return textureGrad(sample1, uv, dx, dy);\r\n" + // - " case 2:\r\n" + // + " case 2u:\r\n" + // " return textureGrad(sample2, uv, dx, dy);\r\n" + // - " case 3:\r\n" + // + " case 3u:\r\n" + // " return textureGrad(sample3, uv, dx, dy);\r\n" + // - " case 4:\r\n" + // + " case 4u:\r\n" + // " return textureGrad(sample4, uv, dx, dy);\r\n" + // - " case 5:\r\n" + // + " case 5u:\r\n" + // " return textureGrad(sample5, uv, dx, dy);\r\n" + // - " case 6:\r\n" + // + " case 6u:\r\n" + // " return textureGrad(sample6, uv, dx, dy);\r\n" + // - " case 7:\r\n" + // + " case 7u:\r\n" + // " return textureGrad(sample7, uv, dx, dy);\r\n" + // - " case 8:\r\n" + // + " case 8u:\r\n" + // " return textureGrad(sample8, uv, dx, dy);\r\n" + // - " case 9:\r\n" + // + " case 9u:\r\n" + // " return textureGrad(sample9, uv, dx, dy);\r\n" + // - " case 10:\r\n" + // + " case 10u:\r\n" + // " return textureGrad(sample10, uv, dx, dy);\r\n" + // - " case 11:\r\n" + // + " case 11u:\r\n" + // " return textureGrad(sample11, uv, dx, dy);\r\n" + // - " case 12:\r\n" + // + " case 12u:\r\n" + // " return textureGrad(sample12, uv, dx, dy);\r\n" + // - " case 13:\r\n" + // + " case 13u:\r\n" + // " return textureGrad(sample13, uv, dx, dy);\r\n" + // - " case 14:\r\n" + // + " case 14u:\r\n" + // " return textureGrad(sample14, uv, dx, dy);\r\n" + // - " case 15:\r\n" + // + " case 15u:\r\n" + // " return textureGrad(sample15, uv, dx, dy);\r\n" + // - " case 16:\r\n" + // + " case 16u:\r\n" + // " return textureGrad(sample16, uv, dx, dy);\r\n" + // - " case 17:\r\n" + // + " case 17u:\r\n" + // " return vec4(0, 0, 0, 0);\r\n" + // " }\r\n" + // "}\r\n" + // "\r\n" + // "\r\n" + // "void main() {\r\n" + // - " color = get_fragment(texture_indices.a & 31, vec3(UV, texture_indices.a >> 5));\r\n" + // - " color = color * color.a + get_fragment(texture_indices.b & 31, vec3(UV, texture_indices.b >> 5)) * (1 - color.a);\r\n" + " color = get_fragment(texture_indices.a & 31u, vec3(UV, texture_indices.a >> 5));\r\n" + // + " color = color * color.a + get_fragment(texture_indices.b & 31u, vec3(UV, texture_indices.b >> 5)) * (1 - color.a);\r\n" + // - " color = color * color.a + get_fragment(texture_indices.g & 31, vec3(UV, texture_indices.g >> 5)) * (1 - color.a);\r\n" + " color = color * color.a + get_fragment(texture_indices.g & 31u, vec3(UV, texture_indices.g >> 5)) * (1 - color.a);\r\n" + // - " color = color * color.a + get_fragment(texture_indices.r & 31, vec3(UV, texture_indices.r >> 5)) * (1 - color.a);\r\n" + " color = color * color.a + get_fragment(texture_indices.r & 31u, vec3(UV, texture_indices.r >> 5)) * (1 - color.a);\r\n" + // " float shadow = texture2D(shadowMap, v_suv).r;\r\n" + // // " float visibility = 1.0;\r\n" + // @@ -297,140 +271,31 @@ public class TerrainShaders { // + // // " }\r\n" + // "}"; - - public static final String posFrag = "#version 450 core\r\n" + // - "\r\n" + // - "layout (location = 2) uniform bool show_pathing_map;\r\n" + // - "layout (location = 3) uniform bool show_lighting;\r\n" + // - "\r\n" + // - "layout (binding = 3) uniform sampler2DArray sample0;\r\n" + // - "layout (binding = 4) uniform sampler2DArray sample1;\r\n" + // - "layout (binding = 5) uniform sampler2DArray sample2;\r\n" + // - "layout (binding = 6) uniform sampler2DArray sample3;\r\n" + // - "layout (binding = 7) uniform sampler2DArray sample4;\r\n" + // - "layout (binding = 8) uniform sampler2DArray sample5;\r\n" + // - "layout (binding = 9) uniform sampler2DArray sample6;\r\n" + // - "layout (binding = 10) uniform sampler2DArray sample7;\r\n" + // - "layout (binding = 11) uniform sampler2DArray sample8;\r\n" + // - "layout (binding = 12) uniform sampler2DArray sample9;\r\n" + // - "layout (binding = 13) uniform sampler2DArray sample10;\r\n" + // - "layout (binding = 14) uniform sampler2DArray sample11;\r\n" + // - "layout (binding = 15) uniform sampler2DArray sample12;\r\n" + // - "layout (binding = 16) uniform sampler2DArray sample13;\r\n" + // - "layout (binding = 17) uniform sampler2DArray sample14;\r\n" + // - "layout (binding = 18) uniform sampler2DArray sample15;\r\n" + // - "layout (binding = 19) uniform sampler2DArray sample16;\r\n" + // - "\r\n" + // - "layout (binding = 20) uniform usampler2D pathing_map_static;\r\n" + // - "layout (binding = 21) uniform usampler2D pathing_map_dynamic;\r\n" + // - "\r\n" + // - "layout (location = 0) in vec2 UV;\r\n" + // - "layout (location = 1) in flat uvec4 texture_indices;\r\n" + // - "layout (location = 2) in vec2 pathing_map_uv;\r\n" + // - "layout (location = 3) in vec3 normal;\r\n" + // - "layout (location = 4) in vec3 position;\r\n" + // - "\r\n" + // - "layout (location = 0) out vec4 color;\r\n" + // - "\r\n" + // - "vec4 get_fragment(uint id, vec3 uv) {\r\n" + // - " vec2 dx = dFdx(uv.xy);\r\n" + // - " vec2 dy = dFdy(uv.xy);\r\n" + // - "\r\n" + // - " switch(id) {\r\n" + // - " case 0:\r\n" + // - " return textureGrad(sample0, uv, dx, dy);\r\n" + // - " case 1:\r\n" + // - " return textureGrad(sample1, uv, dx, dy);\r\n" + // - " case 2:\r\n" + // - " return textureGrad(sample2, uv, dx, dy);\r\n" + // - " case 3:\r\n" + // - " return textureGrad(sample3, uv, dx, dy);\r\n" + // - " case 4:\r\n" + // - " return textureGrad(sample4, uv, dx, dy);\r\n" + // - " case 5:\r\n" + // - " return textureGrad(sample5, uv, dx, dy);\r\n" + // - " case 6:\r\n" + // - " return textureGrad(sample6, uv, dx, dy);\r\n" + // - " case 7:\r\n" + // - " return textureGrad(sample7, uv, dx, dy);\r\n" + // - " case 8:\r\n" + // - " return textureGrad(sample8, uv, dx, dy);\r\n" + // - " case 9:\r\n" + // - " return textureGrad(sample9, uv, dx, dy);\r\n" + // - " case 10:\r\n" + // - " return textureGrad(sample10, uv, dx, dy);\r\n" + // - " case 11:\r\n" + // - " return textureGrad(sample11, uv, dx, dy);\r\n" + // - " case 12:\r\n" + // - " return textureGrad(sample12, uv, dx, dy);\r\n" + // - " case 13:\r\n" + // - " return textureGrad(sample13, uv, dx, dy);\r\n" + // - " case 14:\r\n" + // - " return textureGrad(sample14, uv, dx, dy);\r\n" + // - " case 15:\r\n" + // - " return textureGrad(sample15, uv, dx, dy);\r\n" + // - " case 16:\r\n" + // - " return textureGrad(sample16, uv, dx, dy);\r\n" + // - " case 17:\r\n" + // - " return vec4(0, 0, 0, 0);\r\n" + // - " }\r\n" + // - "}\r\n" + // - "\r\n" + // - "\r\n" + // - "void main() {\r\n" + // - " color = vec4(position.xyz, 1.0);\r\n" + // - "}"; - } - - public static final class Test { - private Test() { - } - - public static final String vert = "#version 450 core\r\n" + // - "\r\n" + // - "layout (location = 0) in vec3 vPosition;\r\n" + // - "layout (location = 1) uniform mat4 MVP;\r\n" + // - "\r\n" + // - "\r\n" + // - "void main() { \r\n" + // - " gl_Position = MVP * vec4(vPosition, 1.0);\r\n" + // - "}"; - - public static final String frag = "#version 450 core\r\n" + // - "\r\n" + // - "\r\n" + // - "layout (location = 0) out vec4 color;\r\n" + // - "\r\n" + // - "\r\n" + // - "\r\n" + // - "void main() {\r\n" + // - " color = vec4(1.0,1.0,1.0,1.0);\r\n" + // - "}"; } public static final class Water { private Water() { } - public static final String vert = "#version 450 core\r\n" + // + public static final String vert = "#version 330 core\r\n" + // "\r\n" + // - "layout (location = 0) in vec2 vPosition;\r\n" + // + "in vec2 vPosition;\r\n" + // "\r\n" + // - "layout (binding = 0) uniform sampler2D water_height_texture;\r\n" + // - "layout (binding = 1) uniform sampler2D ground_height_texture;\r\n" + // - "layout (binding = 2) uniform sampler2D water_exists_texture;\r\n" + // - "layout (location = 7) uniform float centerOffsetX;\r\n" + // - "layout (location = 8) uniform float centerOffsetY;\r\n" + // + "uniform sampler2D water_height_texture;\r\n" + // + "uniform sampler2D ground_height_texture;\r\n" + // + "uniform sampler2D water_exists_texture;\r\n" + // + "uniform float centerOffsetX;\r\n" + // + "uniform float centerOffsetY;\r\n" + // "\r\n" + // - "layout (location = 0) uniform mat4 MVP;\r\n" + // - "layout (location = 1) uniform vec4 shallow_color_min;\r\n" + // - "layout (location = 2) uniform vec4 shallow_color_max;\r\n" + // - "layout (location = 3) uniform vec4 deep_color_min;\r\n" + // - "layout (location = 4) uniform vec4 deep_color_max;\r\n" + // - "layout (location = 5) uniform float water_offset;\r\n" + // - "layout (binding = 3) uniform sampler2D lightTexture;\r\n" + // - "layout (location = 9) uniform float lightCount;\r\n" + // - "layout (location = 10) uniform float lightTextureHeight;\r\n" + // + "uniform mat4 MVP;\r\n" + // + "uniform vec4 shallow_color_min;\r\n" + // + "uniform vec4 shallow_color_max;\r\n" + // + "uniform vec4 deep_color_min;\r\n" + // + "uniform vec4 deep_color_max;\r\n" + // + "uniform float water_offset;\r\n" + // + "uniform sampler2D lightTexture;\r\n" + // + "uniform float lightCount;\r\n" + // + "uniform float lightTextureHeight;\r\n" + // "\r\n" + // "out vec2 UV;\r\n" + // "out vec4 Color;\r\n" + // @@ -475,14 +340,14 @@ public class TerrainShaders { " shadeColor = clamp(lightFactor, 0.0, 1.0);\r\n" + // " }"; - public static final String frag = "#version 450 core\r\n" + // + public static final String frag = "#version 330 core\r\n" + // "\r\n" + // - "layout (binding = 4) uniform sampler2DArray water_textures;\r\n" + // - "layout (binding = 2) uniform sampler2D water_exists_texture;\r\n" + // + "uniform sampler2DArray water_textures;\r\n" + // + "uniform sampler2D water_exists_texture;\r\n" + // "\r\n" + // "\r\n" + // - "layout (location = 6) uniform int current_texture;\r\n" + // - "layout (location = 11) uniform vec4 mapBounds;\r\n" + // + "uniform int current_texture;\r\n" + // + "uniform vec4 mapBounds;\r\n" + // "\r\n" + // "in vec2 UV;\r\n" + // "in vec4 Color;\r\n" + // From 7ab1fa319360cadd1463d3c60204c2d5b8fb8fef Mon Sep 17 00:00:00 2001 From: Retera Date: Sun, 20 Dec 2020 23:36:32 -0600 Subject: [PATCH 078/116] GNU+Linux support with folder data source patch --- .../warsmash/datasources/FolderDataSource.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/core/src/com/etheller/warsmash/datasources/FolderDataSource.java b/core/src/com/etheller/warsmash/datasources/FolderDataSource.java index ba903a7..b7eb8fc 100644 --- a/core/src/com/etheller/warsmash/datasources/FolderDataSource.java +++ b/core/src/com/etheller/warsmash/datasources/FolderDataSource.java @@ -33,7 +33,8 @@ public class FolderDataSource implements DataSource { } @Override - public InputStream getResourceAsStream(final String filepath) throws IOException { + public InputStream getResourceAsStream(String filepath) throws IOException { + filepath=fixFilepath(filepath); if (!has(filepath)) { return null; } @@ -41,7 +42,8 @@ public class FolderDataSource implements DataSource { } @Override - public File getFile(final String filepath) throws IOException { + public File getFile(String filepath) throws IOException { + filepath=fixFilepath(filepath); if (!has(filepath)) { return null; } @@ -49,7 +51,8 @@ public class FolderDataSource implements DataSource { } @Override - public boolean has(final String filepath) { + public boolean has(String filepath) { + filepath=fixFilepath(filepath); if ("".equals(filepath)) { return false; // special case for folder data source, dont do this } @@ -66,4 +69,7 @@ public class FolderDataSource implements DataSource { public void close() { } + private static String fixFilepath(String filepath) { + return filepath.replace('\\', File.separatorChar).replace('/', File.separatorChar); + } } From 6849d269ac6fb5c2b921ccbb71d1c71d3ff32e6a Mon Sep 17 00:00:00 2001 From: Retera Date: Tue, 22 Dec 2020 00:27:10 -0500 Subject: [PATCH 079/116] Begin working on techtree pricing costs and related systems --- core/src/com/etheller/warsmash/TestMain.java | 23 +++- .../etheller/warsmash/parsers/fdf/GameUI.java | 20 +++- .../etheller/warsmash/parsers/jass/Jass2.java | 2 +- .../warsmash/units/HashedGameObject.java | 3 + .../viewer5/handlers/w3x/War3MapViewer.java | 12 ++ .../handlers/w3x/simulation/CAbilityType.java | 39 +++++++ .../w3x/simulation/CAbilityTypeLevelData.java | 17 +++ .../w3x/simulation/CPlayerStateListener.java | 44 +++++++ .../handlers/w3x/simulation/CSimulation.java | 19 ++- .../handlers/w3x/simulation/CUnit.java | 110 ++++++++++++++---- .../handlers/w3x/simulation/CUnitType.java | 14 ++- .../abilities/AbstractCAbility.java | 8 +- .../build/CAbilityBuildInProgress.java | 3 + .../abilities/build/CAbilityOrcBuild.java | 9 +- ...bstractGenericSingleIconActiveAbility.java | 69 +++++++++++ .../GenericSingleIconActiveAbility.java | 2 + .../abilities/harvest/CAbilityHarvest.java | 62 ++++++++++ .../abilities/queue/CAbilityQueue.java | 11 +- .../w3x/simulation/data/CAbilityData.java | 32 +++++ .../w3x/simulation/data/CUnitData.java | 8 +- .../w3x/simulation/players/CPlayer.java | 67 ++++++++++- .../util/AbilityActivationErrorHandler.java | 22 ++++ .../MeleeUIAbilityActivationReceiver.java | 85 ++++++++++++++ .../w3x/simulation/util/ResourceType.java | 3 +- .../StringMsgAbilityActivationReceiver.java | 6 - .../viewer5/handlers/w3x/ui/MeleeUI.java | 100 +++++++++++++--- .../viewer5/handlers/w3x/ui/MenuUI.java | 2 +- 27 files changed, 723 insertions(+), 69 deletions(-) create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CAbilityType.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CAbilityTypeLevelData.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CPlayerStateListener.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/AbstractGenericSingleIconActiveAbility.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityHarvest.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/AbilityActivationErrorHandler.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/MeleeUIAbilityActivationReceiver.java diff --git a/core/src/com/etheller/warsmash/TestMain.java b/core/src/com/etheller/warsmash/TestMain.java index 18f1c2d..6ab5d22 100644 --- a/core/src/com/etheller/warsmash/TestMain.java +++ b/core/src/com/etheller/warsmash/TestMain.java @@ -2,6 +2,27 @@ package com.etheller.warsmash; public class TestMain { public static void main(final String[] args) { - System.out.println(Integer.parseInt("4294967295")); +// System.out.println(Integer.parseInt("4294967295")); + for (int i = 1; i <= 30; i++) { +// System.out.println(a(i)); + } + + int checkX = 0; + int checkY = 0; + for (int i = 0; i < 300; i++) { + System.out.println(checkX + "," + checkY); + final double angle = ((((int) Math.floor(Math.sqrt((4 * i) + 1))) % 4) * Math.PI) / 2; + checkX += (int) Math.sin(angle); + checkY += (int) Math.cos(angle); + } + } + + public static int a(final int n) { + if (n == 1) { + return 0; + } + else { + return a(n - 1) - (int) Math.sin(((Math.floor(Math.sqrt((4 * (n - 2)) + 1)) % 4) * Math.PI) / 2); + } } } diff --git a/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java index c512c03..a1b8897 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java @@ -56,6 +56,7 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { private final Viewport viewport; private final Scene uiScene; private final AbstractMdxModelViewer modelViewer; + private final int racialCommandIndex; private final FrameTemplateEnvironment templates; private final Map pathToTexture = new HashMap<>(); private final boolean autoPosition = false; @@ -67,13 +68,15 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { private final Element errorStrings; public GameUI(final DataSource dataSource, final Element skin, final Viewport viewport, - final FreeTypeFontGenerator fontGenerator, final Scene uiScene, final AbstractMdxModelViewer modelViewer) { + final FreeTypeFontGenerator fontGenerator, final Scene uiScene, final AbstractMdxModelViewer modelViewer, + final int racialCommandIndex) { super("GameUI", null); this.dataSource = dataSource; this.skin = skin; this.viewport = viewport; this.uiScene = uiScene; this.modelViewer = modelViewer; + this.racialCommandIndex = racialCommandIndex; if (viewport instanceof ExtendViewport) { this.renderBounds.set(0, 0, ((ExtendViewport) viewport).getMinWorldWidth(), ((ExtendViewport) viewport).getMinWorldHeight()); @@ -162,7 +165,13 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { final String skinsField = main.getField("Skins"); final String[] skins = skinsField.split(","); final Element defaultSkin = skinsTable.get("Default"); - final Element userSkin = skinsTable.get(skins[skinIndex]); + final Element userSkin; + if ((skinIndex >= 0) && (skinIndex < skins.length)) { + userSkin = skinsTable.get(skins[skinIndex]); + } + else { + userSkin = new Element("UserSkin", skinsTable); + } final Element customSkin = skinsTable.get("CustomSkin"); for (final String key : defaultSkin.keySet()) { if (!userSkin.hasField(key)) { @@ -319,6 +328,11 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { simpleStatusBarFrame.add(inflate(childDefinition, simpleStatusBarFrame, frameDefinition, inDecorateFileNames || childDefinition.has("DecorateFileNames"))); } + final String barTexture = frameDefinition.getString("BarTexture"); + if (barTexture != null) { + simpleStatusBarFrame.getBarFrame().setTexture(barTexture, this); + simpleStatusBarFrame.getBorderFrame().setTexture(barTexture + "Border", this); + } inflatedFrame = simpleStatusBarFrame; } else if ("SPRITE".equals(frameDefinition.getFrameType())) { @@ -691,6 +705,6 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { } public String getErrorString(final String key) { - return this.errorStrings.getField(key); + return this.errorStrings.getField(key, this.racialCommandIndex); } } diff --git a/core/src/com/etheller/warsmash/parsers/jass/Jass2.java b/core/src/com/etheller/warsmash/parsers/jass/Jass2.java index 38f8540..15c6690 100644 --- a/core/src/com/etheller/warsmash/parsers/jass/Jass2.java +++ b/core/src/com/etheller/warsmash/parsers/jass/Jass2.java @@ -138,7 +138,7 @@ public class Jass2 { final String skinArg = arguments.get(0).visit(StringJassValueVisitor.getInstance()); final Element skin = GameUI.loadSkin(dataSource, skinArg); final GameUI gameUI = new GameUI(dataSource, skin, uiViewport, fontGenerator, uiScene, - war3MapViewer); + war3MapViewer, 0); JUIEnvironment.this.gameUI = gameUI; JUIEnvironment.this.skin = skin; rootFrameListener.onCreate(gameUI); diff --git a/core/src/com/etheller/warsmash/units/HashedGameObject.java b/core/src/com/etheller/warsmash/units/HashedGameObject.java index e80625b..3e9f109 100644 --- a/core/src/com/etheller/warsmash/units/HashedGameObject.java +++ b/core/src/com/etheller/warsmash/units/HashedGameObject.java @@ -124,6 +124,9 @@ public abstract class HashedGameObject implements GameObject { if (list.size() > index) { value = list.get(index); } + else if (list.size() > 0) { + value = list.get(list.size() - 1); + } } } return value; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index 46231d9..1b7dd28 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -295,6 +295,17 @@ public class War3MapViewer extends AbstractMdxModelViewer { this.miscData.readTXT(miscDataTxtStream, true); } } + final Element misc = this.miscData.get("Misc"); + // TODO Find the upkeep constants inside the assets files ????? + if (!misc.hasField("UpkeepUsage")) { + misc.setField("UpkeepUsage", "50,80,10000,10000,10000,10000,10000,10000,10000,10000"); + } + if (!misc.hasField("UpkeepGoldTax")) { + misc.setField("UpkeepGoldTax", "0.00,0.30,0.60,0.60,0.60,0.60,0.60,0.60,0.60,0.60"); + } + if (!misc.hasField("UpkeepLumberTax")) { + misc.setField("UpkeepLumberTax", "0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00"); + } final Element light = this.miscData.get("Light"); final float lightX = light.getFieldFloatValue("Direction", 0); final float lightY = light.getFieldFloatValue("Direction", 1); @@ -840,6 +851,7 @@ public class War3MapViewer extends AbstractMdxModelViewer { createNewUnit(modifications, unitId, unitX, unitY, unitZ, playerIndex, unitAngle); } } + this.simulation.unitsLoaded(); this.terrain.loadSplats(); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CAbilityType.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CAbilityType.java new file mode 100644 index 0000000..de8b0cd --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CAbilityType.java @@ -0,0 +1,39 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation; + +import java.util.EnumSet; +import java.util.List; + +import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; + +public class CAbilityType { + /* alias: defines which ability editor ability to use */ + private final War3ID alias; + /* code: defines which CAbility class to use */ + private final War3ID code; + + private final List levelData; + + public CAbilityType(final War3ID alias, final War3ID code, final List levelData) { + this.alias = alias; + this.code = code; + this.levelData = levelData; + } + + public War3ID getAlias() { + return this.alias; + } + + public War3ID getCode() { + return this.code; + } + + public EnumSet getTargetsAllowed(final int level) { + return getLevelData(level).getTargetsAllowed(); + } + + private CAbilityTypeLevelData getLevelData(final int level) { + return this.levelData.get(level); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CAbilityTypeLevelData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CAbilityTypeLevelData.java new file mode 100644 index 0000000..975abe7 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CAbilityTypeLevelData.java @@ -0,0 +1,17 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation; + +import java.util.EnumSet; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; + +public class CAbilityTypeLevelData { + private final EnumSet targetsAllowed; + + public CAbilityTypeLevelData(final EnumSet targetsAllowed) { + this.targetsAllowed = targetsAllowed; + } + + public EnumSet getTargetsAllowed() { + return this.targetsAllowed; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CPlayerStateListener.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CPlayerStateListener.java new file mode 100644 index 0000000..3fbea2e --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CPlayerStateListener.java @@ -0,0 +1,44 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation; + +import com.etheller.warsmash.util.SubscriberSetNotifier; + +public interface CPlayerStateListener { + void goldChanged(); + + void lumberChanged(); + + void foodChanged(); + + void upkeepChanged(); + + public static final class CPlayerStateNotifier extends SubscriberSetNotifier + implements CPlayerStateListener { + @Override + public void goldChanged() { + for (final CPlayerStateListener listener : set) { + listener.goldChanged(); + } + } + + @Override + public void lumberChanged() { + for (final CPlayerStateListener listener : set) { + listener.lumberChanged(); + } + } + + @Override + public void foodChanged() { + for (final CPlayerStateListener listener : set) { + listener.foodChanged(); + } + } + + @Override + public void upkeepChanged() { + for (final CPlayerStateListener listener : set) { + listener.upkeepChanged(); + } + } + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java index 69f03c7..190951f 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java @@ -182,8 +182,7 @@ public class CSimulation { this.simulationRenderController.removeUnit(unit); } } - this.units.addAll(this.newUnits); - this.newUnits.clear(); + finishAddingNewUnits(); final Iterator projectileIterator = this.projectiles.iterator(); while (projectileIterator.hasNext()) { final CAttackProjectile projectile = projectileIterator.next(); @@ -198,6 +197,11 @@ public class CSimulation { % this.gameplayConstants.getGameDayLength(); } + private void finishAddingNewUnits() { + this.units.addAll(this.newUnits); + this.newUnits.clear(); + } + public float getGameTimeOfDay() { return (this.currentGameDayTimeElapsed / this.gameplayConstants.getGameDayLength()) * this.gameplayConstants.getGameDayHours(); @@ -254,4 +258,15 @@ public class CSimulation { public void unitRepositioned(final CUnit cUnit) { this.simulationRenderController.unitRepositioned(cUnit); } + + public void unitsLoaded() { + // called on startup after the system loads the map's units layer, but not any + // custom scripts yet + finishAddingNewUnits(); + for (final CUnit unit : this.units) { + final CPlayer player = this.players.get(unit.getPlayerIndex()); + player.setUnitFoodUsed(unit, unit.getUnitType().getFoodUsed()); + player.setUnitFoodMade(unit, unit.getUnitType().getFoodMade()); + } + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java index 11c7e44..f785f84 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java @@ -94,6 +94,9 @@ public class CUnit extends CWidget { private final QueueItemType[] buildQueueTypes = new QueueItemType[WarsmashConstants.BUILD_QUEUE_SIZE]; private AbilityTarget rallyPoint; + private int foodMade; + private int foodUsed; + public CUnit(final int handleId, final int playerIndex, final float x, final float y, final float life, final War3ID typeId, final float facing, final float mana, final int maximumLife, final int maximumMana, final int speed, final int defense, final CUnitType unitType, @@ -248,6 +251,10 @@ public class CUnit extends CWidget { ability.setIconShowing(true); } } + if (this.unitType.getFoodMade() != 0) { + final CPlayer player = game.getPlayer(this.playerIndex); + player.setFoodCap(player.getFoodCap() + this.unitType.getFoodMade()); + } game.unitConstructFinishEvent(this); this.stateNotifier.ordersChanged(getCurrentAbilityHandleId(), getCurrentOrderId()); } @@ -263,6 +270,10 @@ public class CUnit extends CWidget { this.constructionProgress = 0; final CUnit trainedUnit = game.createUnit(queuedRawcode, this.playerIndex, getX(), getY(), game.getGameplayConstants().getBuildingAngle()); + // dont add food cost to player 2x + trainedUnit.setFoodUsed(trainedUnitType.getFoodUsed()); + game.getPlayer(this.playerIndex).setUnitFoodMade(trainedUnit, + trainedUnitType.getFoodMade()); // nudge the trained unit out around us trainedUnit.nudgeAround(game, this); game.unitTrainedEvent(this, trainedUnit); @@ -272,11 +283,9 @@ public class CUnit extends CWidget { UseAbilityOnTargetByIdVisitor.INSTANCE.reset(game, trainedUnit, rallyOrderId)); } for (int i = 0; i < (this.buildQueue.length - 1); i++) { - this.buildQueue[i] = this.buildQueue[i + 1]; - this.buildQueueTypes[i] = this.buildQueueTypes[i + 1]; + setBuildQueueItem(game, i, this.buildQueue[i + 1], this.buildQueueTypes[i + 1]); } - this.buildQueue[this.buildQueue.length - 1] = null; - this.buildQueueTypes[this.buildQueue.length - 1] = null; + setBuildQueueItem(game, this.buildQueue.length - 1, null, null); this.stateNotifier.queueChanged(); } } @@ -285,11 +294,9 @@ public class CUnit extends CWidget { if (this.constructionProgress >= trainedUnitType.getBuildTime()) { this.constructionProgress = 0; for (int i = 0; i < (this.buildQueue.length - 1); i++) { - this.buildQueue[i] = this.buildQueue[i + 1]; - this.buildQueueTypes[i] = this.buildQueueTypes[i + 1]; + setBuildQueueItem(game, i, this.buildQueue[i + 1], this.buildQueueTypes[i + 1]); } - this.buildQueue[this.buildQueue.length - 1] = null; - this.buildQueueTypes[this.buildQueue.length - 1] = null; + setBuildQueueItem(game, this.buildQueue.length - 1, null, null); this.stateNotifier.queueChanged(); } } @@ -617,6 +624,13 @@ public class CUnit extends CWidget { this.buildingShadowInstance = null; } popoutWorker(simulation); + final CPlayer player = simulation.getPlayer(this.playerIndex); + if (this.foodMade != 0) { + player.setUnitFoodMade(this, 0); + } + if (this.foodUsed != 0) { + player.setUnitFoodUsed(this, 0); + } } public boolean canReach(final AbilityTarget target, final float range) { @@ -898,14 +912,14 @@ public class CUnit extends CWidget { this.stateNotifier.lifeChanged(); } - private void queue(final War3ID rawcode, final QueueItemType queueItemType) { + private boolean queue(final CSimulation game, final War3ID rawcode, final QueueItemType queueItemType) { for (int i = 0; i < this.buildQueue.length; i++) { if (this.buildQueue[i] == null) { - this.buildQueue[i] = rawcode; - this.buildQueueTypes[i] = queueItemType; - break; + setBuildQueueItem(game, i, rawcode, queueItemType); + return true; } } + return false; } public War3ID[] getBuildQueue() { @@ -933,28 +947,62 @@ public class CUnit extends CWidget { public void cancelBuildQueueItem(final CSimulation game, final int cancelIndex) { if ((cancelIndex >= 0) && (cancelIndex < this.buildQueueTypes.length)) { - if (this.buildQueueTypes[cancelIndex] != null) { + final QueueItemType cancelledType = this.buildQueueTypes[cancelIndex]; + if (cancelledType != null) { // TODO refund here! if (cancelIndex == 0) { this.constructionProgress = 0.0f; + switch (cancelledType) { + case RESEARCH: + break; + case UNIT: + final CPlayer player = game.getPlayer(this.playerIndex); + final CUnitType unitType = game.getUnitData().getUnitType(this.buildQueue[cancelIndex]); + player.setFoodUsed(player.getFoodUsed() - unitType.getFoodUsed()); + break; + } + } + switch (cancelledType) { + case RESEARCH: + break; + case UNIT: + final CPlayer player = game.getPlayer(this.playerIndex); + final CUnitType unitType = game.getUnitData().getUnitType(this.buildQueue[cancelIndex]); + player.refundFor(unitType); + break; } for (int i = cancelIndex; i < (this.buildQueueTypes.length - 1); i++) { - this.buildQueueTypes[i] = this.buildQueueTypes[i + 1]; - this.buildQueue[i] = this.buildQueue[i + 1]; + setBuildQueueItem(game, i, this.buildQueue[i + 1], this.buildQueueTypes[i + 1]); } - this.buildQueueTypes[this.buildQueueTypes.length - 1] = null; - this.buildQueue[this.buildQueue.length - 1] = null; + setBuildQueueItem(game, this.buildQueue.length - 1, null, null); this.stateNotifier.queueChanged(); } } } - public void queueTrainingUnit(final War3ID rawcode) { - queue(rawcode, QueueItemType.UNIT); + public void setBuildQueueItem(final CSimulation game, final int index, final War3ID rawcode, + final QueueItemType queueItemType) { + this.buildQueue[index] = rawcode; + this.buildQueueTypes[index] = queueItemType; + if ((index == 0) && (rawcode != null) && (queueItemType == QueueItemType.UNIT)) { + final CPlayer player = game.getPlayer(this.playerIndex); + final CUnitType unitType = game.getUnitData().getUnitType(this.buildQueue[index]); + if (unitType.getFoodUsed() != 0) { + player.setFoodUsed(player.getFoodUsed() + unitType.getFoodUsed()); + } + } } - public void queueResearch(final War3ID rawcode) { - queue(rawcode, QueueItemType.RESEARCH); + public void queueTrainingUnit(final CSimulation game, final War3ID rawcode) { + if (queue(game, rawcode, QueueItemType.UNIT)) { + final CPlayer player = game.getPlayer(this.playerIndex); + final CUnitType unitType = game.getUnitData().getUnitType(rawcode); + player.chargeFor(unitType); + } + } + + public void queueResearch(final CSimulation game, final War3ID rawcode) { + queue(game, rawcode, QueueItemType.RESEARCH); } public static enum QueueItemType { @@ -1049,4 +1097,24 @@ public class CUnit extends CWidget { return acceptWidget(this.game, this.trainedUnit, this.rallyOrderId, target); } } + + public int getFoodMade() { + return this.foodMade; + } + + public int getFoodUsed() { + return this.foodUsed; + } + + public int setFoodMade(final int foodMade) { + final int delta = foodMade - this.foodMade; + this.foodMade = foodMade; + return delta; + } + + public int setFoodUsed(final int foodUsed) { + final int delta = foodUsed - this.foodUsed; + this.foodUsed = foodUsed; + return delta; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java index f8db48e..6cb05de 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java @@ -44,6 +44,8 @@ public class CUnitType { private final CUnitRace unitRace; private final int goldCost; private final int lumberCost; + private final int foodUsed; + private final int foodMade; private final int buildTime; private final EnumSet preventedPathingTypes; private final EnumSet requiredPathingTypes; @@ -55,7 +57,7 @@ public class CUnitType { final BufferedImage buildingPathingPixelMap, final float deathTime, final EnumSet targetedAs, final float defaultAcquisitionRange, final float minimumAttackRange, final List structuresBuilt, final List unitsTrained, final List researchesAvailable, final CUnitRace unitRace, - final int goldCost, final int lumberCost, final int buildTime, + final int goldCost, final int lumberCost, final int foodUsed, final int foodMade, final int buildTime, final EnumSet preventedPathingTypes, final EnumSet requiredPathingTypes) { this.name = name; @@ -81,6 +83,8 @@ public class CUnitType { this.unitRace = unitRace; this.goldCost = goldCost; this.lumberCost = lumberCost; + this.foodUsed = foodUsed; + this.foodMade = foodMade; this.buildTime = buildTime; this.preventedPathingTypes = preventedPathingTypes; this.requiredPathingTypes = requiredPathingTypes; @@ -178,6 +182,14 @@ public class CUnitType { return this.lumberCost; } + public int getFoodUsed() { + return this.foodUsed; + } + + public int getFoodMade() { + return this.foodMade; + } + public int getBuildTime() { return this.buildTime; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/AbstractCAbility.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/AbstractCAbility.java index 4b2a2d9..7aab18f 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/AbstractCAbility.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/AbstractCAbility.java @@ -19,22 +19,22 @@ public abstract class AbstractCAbility implements CAbility { } @Override - public boolean isDisabled() { + public final boolean isDisabled() { return this.disabled; } @Override - public void setDisabled(final boolean disabled) { + public final void setDisabled(final boolean disabled) { this.disabled = disabled; } @Override - public boolean isIconShowing() { + public final boolean isIconShowing() { return this.iconShowing; } @Override - public void setIconShowing(final boolean iconShowing) { + public final void setIconShowing(final boolean iconShowing) { this.iconShowing = iconShowing; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityBuildInProgress.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityBuildInProgress.java index b5e94b2..60cafd6 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityBuildInProgress.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityBuildInProgress.java @@ -8,6 +8,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityV import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayer; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; @@ -29,6 +30,8 @@ public class CAbilityBuildInProgress extends AbstractCAbility { @Override public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId) { + final CPlayer player = game.getPlayer(caster.getPlayerIndex()); + player.refundFor(caster.getUnitType()); caster.setLife(game, 0); return false; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityOrcBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityOrcBuild.java index 27289ca..3967432 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityOrcBuild.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityOrcBuild.java @@ -6,12 +6,14 @@ import java.util.List; import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.build.CBehaviorOrcBuild; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayer; public class CAbilityOrcBuild extends AbstractCAbilityBuild { private CBehaviorOrcBuild buildBehavior; @@ -42,8 +44,9 @@ public class CAbilityOrcBuild extends AbstractCAbilityBuild { @Override public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final AbilityPointTarget point) { - final BufferedImage buildingPathingPixelMap = game.getUnitData().getUnitType(new War3ID(orderId)) - .getBuildingPathingPixelMap(); + final War3ID orderIdAsRawtype = new War3ID(orderId); + final CUnitType unitType = game.getUnitData().getUnitType(orderIdAsRawtype); + final BufferedImage buildingPathingPixelMap = unitType.getBuildingPathingPixelMap(); if (buildingPathingPixelMap != null) { point.x = (float) Math.floor(point.x / 64f) * 64f; point.y = (float) Math.floor(point.y / 64f) * 64f; @@ -54,6 +57,8 @@ public class CAbilityOrcBuild extends AbstractCAbilityBuild { point.y += 32f; } } + final CPlayer player = game.getPlayer(caster.getPlayerIndex()); + player.chargeFor(unitType); return this.buildBehavior.reset(point, orderId, getBaseOrderId()); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/AbstractGenericSingleIconActiveAbility.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/AbstractGenericSingleIconActiveAbility.java new file mode 100644 index 0000000..7c36b20 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/AbstractGenericSingleIconActiveAbility.java @@ -0,0 +1,69 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic; + +import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.AbstractCAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; + +public abstract class AbstractGenericSingleIconActiveAbility extends AbstractCAbility + implements GenericSingleIconActiveAbility { + private final War3ID alias; + + public AbstractGenericSingleIconActiveAbility(final int handleId, final War3ID alias) { + super(handleId); + this.alias = alias; + } + + @Override + public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId) { + return true; + } + + @Override + public void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, final CWidget target, + final AbilityTargetCheckReceiver receiver) { + if (orderId == getBaseOrderId()) { + receiver.targetOk(target); + } + else { + receiver.orderIdNotAccepted(); + } + } + + @Override + public void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, + final AbilityPointTarget target, final AbilityTargetCheckReceiver receiver) { + if (orderId == getBaseOrderId()) { + receiver.targetOk(target); + } + else { + receiver.orderIdNotAccepted(); + } + } + + @Override + public void checkCanTargetNoTarget(final CSimulation game, final CUnit unit, final int orderId, + final AbilityTargetCheckReceiver receiver) { + if (orderId == getBaseOrderId()) { + receiver.targetOk(null); + } + else { + receiver.orderIdNotAccepted(); + } + } + + @Override + public T visit(final CAbilityVisitor visitor) { + return visitor.accept(this); + } + + @Override + public War3ID getAlias() { + return this.alias; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/GenericSingleIconActiveAbility.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/GenericSingleIconActiveAbility.java index 096fb86..5a9f8ce 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/GenericSingleIconActiveAbility.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/GenericSingleIconActiveAbility.java @@ -7,4 +7,6 @@ public interface GenericSingleIconActiveAbility extends CAbility { War3ID getAlias(); int getBaseOrderId(); + + boolean isToggleOn(); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityHarvest.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityHarvest.java new file mode 100644 index 0000000..f11a60e --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityHarvest.java @@ -0,0 +1,62 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.harvest; + +import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic.AbstractGenericSingleIconActiveAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.ResourceType; + +public class CAbilityHarvest extends AbstractGenericSingleIconActiveAbility { + private int carriedResourceAmount; + private ResourceType carriedResourceType; + + public CAbilityHarvest(final int handleId, final War3ID alias) { + super(handleId, alias); + } + + @Override + public void onAdd(final CSimulation game, final CUnit unit) { + } + + @Override + public void onRemove(final CSimulation game, final CUnit unit) { + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final CWidget target) { + return caster.pollNextOrderBehavior(game); + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, + final AbilityPointTarget point) { + return caster.pollNextOrderBehavior(game); + } + + @Override + public CBehavior beginNoTarget(final CSimulation game, final CUnit caster, final int orderId) { + return caster.pollNextOrderBehavior(game); + } + + @Override + public int getBaseOrderId() { + return OrderIds.harvest; + } + + @Override + public boolean isToggleOn() { + return this.carriedResourceAmount > 0; + } + + @Override + protected void innerCheckCanUse(final CSimulation game, final CUnit unit, final int orderId, + final AbilityActivationReceiver receiver) { + receiver.useOk(); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityQueue.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityQueue.java index 2bd4d48..1b5acba 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityQueue.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityQueue.java @@ -47,7 +47,12 @@ public final class CAbilityQueue extends AbstractCAbility { final CPlayer player = game.getPlayer(unit.getPlayerIndex()); if (player.getGold() >= unitType.getGoldCost()) { if (player.getLumber() >= unitType.getLumberCost()) { - receiver.useOk(); + if ((player.getFoodUsed() + unitType.getFoodUsed()) <= player.getFoodCap()) { + receiver.useOk(); + } + else { + receiver.notEnoughResources(ResourceType.FOOD); + } } else { receiver.notEnoughResources(ResourceType.LUMBER); @@ -127,10 +132,10 @@ public final class CAbilityQueue extends AbstractCAbility { else { final War3ID rawcode = new War3ID(orderId); if (this.unitsTrained.contains(rawcode)) { - caster.queueTrainingUnit(rawcode); + caster.queueTrainingUnit(game, rawcode); } else if (this.researchesAvailable.contains(rawcode)) { - caster.queueResearch(rawcode); + caster.queueResearch(game, rawcode); } } return null; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CAbilityData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CAbilityData.java index 6d75cfe..42cb3ed 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CAbilityData.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CAbilityData.java @@ -1,17 +1,49 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.data; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + import com.etheller.warsmash.units.manager.MutableObjectData; +import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CAbilityType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CAbilityTypeLevelData; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityGeneric; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.combat.CAbilityColdArrows; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; public class CAbilityData { + private static final War3ID TARGETS_ALLOWED = War3ID.fromString("atar"); + private static final War3ID LEVELS = War3ID.fromString("alev"); + private static final War3ID COLD_ARROWS = War3ID.fromString("ACcw"); private final MutableObjectData abilityData; + private Map aliasToAbilityType = new HashMap<>(); public CAbilityData(final MutableObjectData abilityData) { this.abilityData = abilityData; + this.aliasToAbilityType = new HashMap<>(); + } + + public CAbilityType getAbilityType(final War3ID alias) { + CAbilityType abilityType = this.aliasToAbilityType.get(alias); + if (abilityType == null) { + final MutableGameObject mutableGameObject = this.abilityData.get(alias); + final int levels = mutableGameObject.getFieldAsInteger(LEVELS, 0); + final List levelData = new ArrayList<>(); + for (int level = 0; level < levels; level++) { + final String targetsAllowedAtLevelString = mutableGameObject.getFieldAsString(TARGETS_ALLOWED, level); + final EnumSet targetsAllowedAtLevel = CTargetType + .parseTargetTypeSet(targetsAllowedAtLevelString); + levelData.add(new CAbilityTypeLevelData(targetsAllowedAtLevel)); + } + abilityType = new CAbilityType(alias, mutableGameObject.getCode(), levelData); + } + return abilityType; } public CAbility createAbility(final String ability, final int handleId) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java index 957e7d7..122d456 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java @@ -141,6 +141,8 @@ public class CUnitData { private static final War3ID GOLD_COST = War3ID.fromString("ugol"); private static final War3ID LUMBER_COST = War3ID.fromString("ulum"); private static final War3ID BUILD_TIME = War3ID.fromString("ubld"); + private static final War3ID FOOD_USED = War3ID.fromString("ufoo"); + private static final War3ID FOOD_MADE = War3ID.fromString("ufma"); private static final War3ID REQUIRE_PLACE = War3ID.fromString("upar"); private static final War3ID PREVENT_PLACE = War3ID.fromString("upap"); @@ -377,6 +379,8 @@ public class CUnitData { final int goldCost = unitType.getFieldAsInteger(GOLD_COST, 0); final int lumberCost = unitType.getFieldAsInteger(LUMBER_COST, 0); final int buildTime = unitType.getFieldAsInteger(BUILD_TIME, 0); + final int foodUsed = unitType.getFieldAsInteger(FOOD_USED, 0); + final int foodMade = unitType.getFieldAsInteger(FOOD_MADE, 0); final String unitsTrainedString = unitType.getFieldAsString(UNITS_TRAINED, 0); final String[] unitsTrainedStringItems = unitsTrainedString.trim().split(","); @@ -416,8 +420,8 @@ public class CUnitData { unitTypeInstance = new CUnitType(unitName, isBldg, movementType, moveHeight, collisionSize, classifications, attacks, armorType, raise, decay, defenseType, impactZ, buildingPathingPixelMap, deathTime, targetedAs, acquisitionRange, minimumAttackRange, structuresBuilt, unitsTrained, - researchesAvailable, unitRace, goldCost, lumberCost, buildTime, preventedPathingTypes, - requiredPathingTypes); + researchesAvailable, unitRace, goldCost, lumberCost, foodUsed, foodMade, buildTime, + preventedPathingTypes, requiredPathingTypes); this.unitIdToUnitType.put(typeId, unitTypeInstance); } return unitTypeInstance; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayer.java index 98747ea..078c8f2 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayer.java @@ -6,6 +6,10 @@ import java.util.Map; import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.util.WarsmashConstants; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CPlayerStateListener; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CPlayerStateListener.CPlayerStateNotifier; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitType; public class CPlayer { private final int id; @@ -15,11 +19,18 @@ public class CPlayer { private final CRace race; private final float[] startLocation; private final EnumSet racePrefs; - private int gold = 5000; - private int lumber = 5000; + private int gold = 500; + private int lumber = 150; + private int foodCap; + private int foodUsed; private final EnumSet[] alliances = new EnumSet[WarsmashConstants.MAX_PLAYERS]; private final Map rawcodeToTechtreeUnlocked = new HashMap<>(); + // if you use triggers for this then the transient tag here becomes really + // questionable -- it already was -- but I meant for those to inform us + // which fields shouldn't be persisted if we do game state save later + private transient CPlayerStateNotifier stateNotifier = new CPlayerStateNotifier(); + public CPlayer(final int id, final CMapControl controlType, final String name, final CRace race, final float[] startLocation) { this.id = id; @@ -95,16 +106,36 @@ public class CPlayer { return this.lumber; } + public int getFoodCap() { + return this.foodCap; + } + + public int getFoodUsed() { + return this.foodUsed; + } + public float[] getStartLocation() { return this.startLocation; } public void setGold(final int gold) { this.gold = gold; + this.stateNotifier.goldChanged(); } public void setLumber(final int lumber) { this.lumber = lumber; + this.stateNotifier.lumberChanged(); + } + + public void setFoodCap(final int foodCap) { + this.foodCap = foodCap; + this.stateNotifier.foodChanged(); + } + + public void setFoodUsed(final int foodUsed) { + this.foodUsed = foodUsed; + this.stateNotifier.foodChanged(); } public void setColorIndex(final int colorIndex) { @@ -118,4 +149,36 @@ public class CPlayer { } return techtreeUnlocked; } + + public void addStateListener(final CPlayerStateListener listener) { + this.stateNotifier.subscribe(listener); + } + + public void removeStateListener(final CPlayerStateListener listener) { + this.stateNotifier.unsubscribe(listener); + } + + public void chargeFor(final CUnitType unitType) { + this.lumber -= unitType.getLumberCost(); + this.gold -= unitType.getGoldCost(); + this.stateNotifier.lumberChanged(); + this.stateNotifier.goldChanged(); + } + + public void refundFor(final CUnitType unitType) { + this.lumber += unitType.getLumberCost(); + this.gold += unitType.getGoldCost(); + this.stateNotifier.lumberChanged(); + this.stateNotifier.goldChanged(); + } + + public void setUnitFoodUsed(final CUnit unit, final int foodUsed) { + this.foodUsed += unit.setFoodUsed(foodUsed); + this.stateNotifier.foodChanged(); + } + + public void setUnitFoodMade(final CUnit unit, final int foodMade) { + this.foodCap += unit.setFoodMade(foodMade); + this.stateNotifier.foodChanged(); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/AbilityActivationErrorHandler.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/AbilityActivationErrorHandler.java new file mode 100644 index 0000000..fafc5b2 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/AbilityActivationErrorHandler.java @@ -0,0 +1,22 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.util; + +import com.etheller.warsmash.viewer5.AudioContext; +import com.etheller.warsmash.viewer5.handlers.w3x.UnitSound; +import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.CommandErrorListener; + +public class AbilityActivationErrorHandler { + private final String errorString; + private final UnitSound errorSound; + + public AbilityActivationErrorHandler(final String errorString, final UnitSound errorSound) { + this.errorString = errorString; + this.errorSound = errorSound; + } + + public void onClick(final CommandErrorListener commandErrorListener, final AudioContext worldSceneAudioContext, + final RenderUnit commandedUnit) { + commandErrorListener.showCommandError(this.errorString); + this.errorSound.playUnitResponse(worldSceneAudioContext, commandedUnit); + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/MeleeUIAbilityActivationReceiver.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/MeleeUIAbilityActivationReceiver.java new file mode 100644 index 0000000..7012c8b --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/MeleeUIAbilityActivationReceiver.java @@ -0,0 +1,85 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.util; + +import com.etheller.warsmash.viewer5.AudioContext; +import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.CommandErrorListener; + +public class MeleeUIAbilityActivationReceiver implements AbilityActivationReceiver { + private final AbilityActivationErrorHandler noGoldError; + private final AbilityActivationErrorHandler noLumberError; + private final AbilityActivationErrorHandler noFoodError; + private final AbilityActivationErrorHandler genericError; + + private boolean ok = false; + private CommandErrorListener commandErrorListener; + private AudioContext worldSceneAudioContext; + private RenderUnit commandedUnit; + + public MeleeUIAbilityActivationReceiver(final AbilityActivationErrorHandler noGoldError, + final AbilityActivationErrorHandler noLumberError, final AbilityActivationErrorHandler noFoodError, + final AbilityActivationErrorHandler genericError) { + this.noGoldError = noGoldError; + this.noLumberError = noLumberError; + this.noFoodError = noFoodError; + this.genericError = genericError; + } + + public MeleeUIAbilityActivationReceiver reset(final CommandErrorListener commandErrorListener, + final AudioContext worldSceneAudioContext, final RenderUnit commandedUnit) { + this.commandErrorListener = commandErrorListener; + this.worldSceneAudioContext = worldSceneAudioContext; + this.commandedUnit = commandedUnit; + this.ok = false; + return this; + } + + @Override + public void useOk() { + this.ok = true; + } + + @Override + public void notEnoughResources(final ResourceType resource) { + switch (resource) { + case GOLD: + this.noGoldError.onClick(this.commandErrorListener, this.worldSceneAudioContext, this.commandedUnit); + break; + case LUMBER: + this.noLumberError.onClick(this.commandErrorListener, this.worldSceneAudioContext, this.commandedUnit); + break; + case FOOD: + this.noFoodError.onClick(this.commandErrorListener, this.worldSceneAudioContext, this.commandedUnit); + break; + } + } + + @Override + public void notAnActiveAbility() { + this.genericError.onClick(this.commandErrorListener, this.worldSceneAudioContext, this.commandedUnit); + } + + @Override + public void missingRequirement(final String name) { + this.genericError.onClick(this.commandErrorListener, this.worldSceneAudioContext, this.commandedUnit); + } + + @Override + public void casterMovementDisabled() { + this.genericError.onClick(this.commandErrorListener, this.worldSceneAudioContext, this.commandedUnit); + } + + @Override + public void cargoCapacityUnavailable() { + this.genericError.onClick(this.commandErrorListener, this.worldSceneAudioContext, this.commandedUnit); + } + + @Override + public void disabled() { + this.genericError.onClick(this.commandErrorListener, this.worldSceneAudioContext, this.commandedUnit); + } + + public boolean isUseOk() { + return this.ok; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/ResourceType.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/ResourceType.java index dd123b7..a375252 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/ResourceType.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/ResourceType.java @@ -2,6 +2,7 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.util; public enum ResourceType { GOLD, - LUMBER; + LUMBER, + FOOD; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/StringMsgAbilityActivationReceiver.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/StringMsgAbilityActivationReceiver.java index 9e8b8d9..5ec8516 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/StringMsgAbilityActivationReceiver.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/StringMsgAbilityActivationReceiver.java @@ -1,12 +1,6 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.util; public class StringMsgAbilityActivationReceiver implements AbilityActivationReceiver { - private static final StringMsgAbilityActivationReceiver INSTANCE = new StringMsgAbilityActivationReceiver(); - - public static StringMsgAbilityActivationReceiver getInstance() { - return INSTANCE; - } - private String message; private boolean useOk = false; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java index 3695ad6..93d4aef 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java @@ -78,6 +78,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.IconUI; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.commandbuttons.CommandButtonListener; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CDestructable; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CItem; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CPlayerStateListener; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit.QueueItemType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitClassification; @@ -113,19 +114,22 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUni import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackMissileSplash; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.pathing.CBuildingPathingType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayer; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayerUnitOrderListener; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CRace; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationErrorHandler; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.BooleanAbilityActivationReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.BooleanAbilityTargetCheckReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.CWidgetAbilityTargetCheckReceiver; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.MeleeUIAbilityActivationReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.PointAbilityTargetCheckReceiver; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.StringMsgAbilityActivationReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.ClickableActionFrame; import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.CommandCardCommandListener; import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.CommandErrorListener; import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.QueueIconListener; public class MeleeUI implements CUnitStateListener, CommandButtonListener, CommandCardCommandListener, - QueueIconListener, CommandErrorListener { + QueueIconListener, CommandErrorListener, CPlayerStateListener { private static final long WORLD_FRAME_MESSAGE_FADEOUT_MILLIS = TimeUnit.SECONDS.toMillis(9); private static final long WORLD_FRAME_MESSAGE_EXPIRE_MILLIS = TimeUnit.SECONDS.toMillis(10); private static final long WORLD_FRAME_MESSAGE_FADE_DURATION = WORLD_FRAME_MESSAGE_EXPIRE_MILLIS @@ -234,6 +238,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma private SimpleFrame smashAttack2IconWrapper; private SimpleFrame smashArmorIconWrapper; private final RallyPositioningVisitor rallyPositioningVisitor; + private final CPlayer localPlayer; + private MeleeUIAbilityActivationReceiver meleeUIAbilityActivationReceiver; public MeleeUI(final DataSource dataSource, final ExtendViewport uiViewport, final FreeTypeFontGenerator fontGenerator, final Scene uiScene, final Scene portraitScene, @@ -251,8 +257,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.cameraManager = new GameCameraManager(cameraPresets, cameraRates); this.cameraManager.setupCamera(war3MapViewer.worldScene); - final float[] startLocation = this.war3MapViewer.simulation.getPlayer(war3MapViewer.getLocalPlayerIndex()) - .getStartLocation(); + this.localPlayer = this.war3MapViewer.simulation.getPlayer(war3MapViewer.getLocalPlayerIndex()); + final float[] startLocation = this.localPlayer.getStartLocation(); this.cameraManager.target.x = startLocation[0]; this.cameraManager.target.y = startLocation[1]; @@ -263,6 +269,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.heightRatioCorrection = this.uiViewport.getMinWorldHeight() / 1200f; this.rallyPositioningVisitor = new RallyPositioningVisitor(); this.cursorTargetSetupVisitor = new CursorTargetSetupVisitor(); + + this.localPlayer.addStateListener(this); } private MeleeUIMinimap createMinimap(final War3MapViewer war3MapViewer) { @@ -307,8 +315,35 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma // ================================= // Load skins and templates // ================================= - this.rootFrame = new GameUI(this.dataSource, GameUI.loadSkin(this.dataSource, 0), this.uiViewport, - this.fontGenerator, this.uiScene, this.war3MapViewer); + final CRace race = this.localPlayer.getRace(); + final int racialSkinIndex; + int racialCommandIndex; + switch (race) { + case HUMAN: + racialSkinIndex = 1; + racialCommandIndex = 0; + break; + case ORC: + racialSkinIndex = 0; + racialCommandIndex = 1; + break; + case NIGHTELF: + racialSkinIndex = 2; + racialCommandIndex = 3; + break; + case UNDEAD: + racialSkinIndex = 3; + racialCommandIndex = 2; + break; + case DEMON: + case OTHER: + default: + racialSkinIndex = -1; + racialCommandIndex = 0; + break; + } + this.rootFrame = new GameUI(this.dataSource, GameUI.loadSkin(this.dataSource, racialSkinIndex), this.uiViewport, + this.fontGenerator, this.uiScene, this.war3MapViewer, racialCommandIndex); this.rootFrameListener.onCreate(this.rootFrame); try { this.rootFrame.loadTOCFile("UI\\FrameDef\\FrameDef.toc"); @@ -338,14 +373,13 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.resourceBar = this.rootFrame.createSimpleFrame("ResourceBarFrame", this.consoleUI, 0); this.resourceBar.addSetPoint(new SetPoint(FramePoint.TOPRIGHT, this.consoleUI, FramePoint.TOPRIGHT, 0, 0)); this.resourceBarGoldText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarGoldText", 0); - this.resourceBarGoldText.setText("500"); + goldChanged(); this.resourceBarLumberText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarLumberText", 0); - this.resourceBarLumberText.setText("150"); + lumberChanged(); this.resourceBarSupplyText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarSupplyText", 0); - this.resourceBarSupplyText.setText("12/100"); + foodChanged(); this.resourceBarUpkeepText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarUpkeepText", 0); - this.resourceBarUpkeepText.setText("No Upkeep"); - this.resourceBarUpkeepText.setColor(Color.GREEN); + upkeepChanged(); // Create the Time Indicator (clock) this.timeIndicator = (SpriteFrame) this.rootFrame.createFrame("TimeOfDayIndicator", this.rootFrame, 0, 0); @@ -559,6 +593,15 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.meleeUIMinimap = createMinimap(this.war3MapViewer); + this.meleeUIAbilityActivationReceiver = new MeleeUIAbilityActivationReceiver( + new AbilityActivationErrorHandler(this.rootFrame.getErrorString("NoGold"), + this.war3MapViewer.getUiSounds().getSound(this.rootFrame.getSkinField("NoGoldSound"))), + new AbilityActivationErrorHandler(this.rootFrame.getErrorString("NoLumber"), + this.war3MapViewer.getUiSounds().getSound(this.rootFrame.getSkinField("NoLumberSound"))), + new AbilityActivationErrorHandler(this.rootFrame.getErrorString("NoFood"), + this.war3MapViewer.getUiSounds().getSound(this.rootFrame.getSkinField("NoFoodSound"))), + new AbilityActivationErrorHandler("", this.war3MapViewer.getUiSounds().getSound("InterfaceError"))); + final MdxModel rallyModel = (MdxModel) this.war3MapViewer.load( War3MapViewer.mdx(this.rootFrame.getSkinField("RallyIndicatorDst")), this.war3MapViewer.mapPathSolver, this.war3MapViewer.solverParams); @@ -591,14 +634,10 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } } if (abilityToUse != null) { - final StringMsgAbilityActivationReceiver stringMsgActivationReceiver = StringMsgAbilityActivationReceiver - .getInstance().reset(); abilityToUse.checkCanUse(this.war3MapViewer.simulation, this.selectedUnit.getSimulationUnit(), orderId, - stringMsgActivationReceiver); - if (!stringMsgActivationReceiver.isUseOk()) { - showCommandError(stringMsgActivationReceiver.getMessage()); - } - else { + this.meleeUIAbilityActivationReceiver.reset(this, this.war3MapViewer.worldScene.audioContext, + this.selectedUnit)); + if (this.meleeUIAbilityActivationReceiver.isUseOk()) { final BooleanAbilityTargetCheckReceiver noTargetReceiver = BooleanAbilityTargetCheckReceiver .getInstance().reset(); abilityToUse.checkCanTargetNoTarget(this.war3MapViewer.simulation, @@ -1026,7 +1065,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma int index = -1; for (int i = 0; i < model.attachments.size(); i++) { final Attachment attachment = model.attachments.get(i); - if (attachment.getName().startsWith("sprite first ref")) { + if (attachment.getName().startsWith("sprite")) { index = i; break; } @@ -1475,6 +1514,29 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } } + @Override + public void goldChanged() { + this.resourceBarGoldText.setText(Integer.toString(this.localPlayer.getGold())); + } + + @Override + public void lumberChanged() { + this.resourceBarLumberText.setText(Integer.toString(this.localPlayer.getLumber())); + } + + @Override + public void foodChanged() { + this.resourceBarSupplyText.setText(this.localPlayer.getFoodUsed() + "/" + this.localPlayer.getFoodCap()); + this.resourceBarSupplyText + .setColor(this.localPlayer.getFoodUsed() > this.localPlayer.getFoodCap() ? Color.RED : Color.WHITE); + } + + @Override + public void upkeepChanged() { + this.resourceBarUpkeepText.setText("Upkeep NYI"); + this.resourceBarUpkeepText.setColor(Color.CYAN); + } + @Override public void ordersChanged(final int abilityHandleId, final int orderId) { reloadSelectedUnitUI(this.selectedUnit); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MenuUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MenuUI.java index 4291b65..270693a 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MenuUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MenuUI.java @@ -65,7 +65,7 @@ public class MenuUI { // Load skins and templates // ================================= this.rootFrame = new GameUI(this.dataSource, GameUI.loadSkin(this.dataSource, 1), this.uiViewport, - this.fontGenerator, this.uiScene, this.viewer); + this.fontGenerator, this.uiScene, this.viewer, 0); this.rootFrameListener.onCreate(this.rootFrame); try { this.rootFrame.loadTOCFile("UI\\FrameDef\\FrameDef.toc"); From 5283b7a038bc20b5c508da4a8cd53d7a8ba9ee8e Mon Sep 17 00:00:00 2001 From: Retera Date: Thu, 24 Dec 2020 08:46:56 -0500 Subject: [PATCH 080/116] Fix command card cancel menu icon, fix terrain handling missing cliff, improve build food cost handling --- .../warsmash/viewer5/handlers/w3x/environment/Terrain.java | 4 ++++ .../simulation/abilities/build/AbstractCAbilityBuild.java | 7 ++++++- .../w3x/simulation/abilities/build/CAbilityOrcBuild.java | 3 +++ .../w3x/simulation/behaviors/build/CBehaviorOrcBuild.java | 4 ++++ .../warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java | 2 +- 5 files changed, 18 insertions(+), 2 deletions(-) diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java index 921320e..2acdd98 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java @@ -244,6 +244,10 @@ public class Terrain { // Cliff Textures for (final War3ID cliffTile : w3eFile.getCliffTiles()) { final Element cliffInfo = this.cliffTable.get(cliffTile.asStringValue()); + if(cliffInfo == null) { + System.err.println("Missing cliff type: " + cliffTile.asStringValue()); + continue; + } final String texDir = cliffInfo.getField("texDir"); final String texFile = cliffInfo.getField("texFile"); try (InputStream imageStream = dataSource.getResourceAsStream(texDir + "\\" + texFile + texturesExt)) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java index 9c0d731..7aef252 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java @@ -40,7 +40,12 @@ public abstract class AbstractCAbilityBuild extends AbstractCAbility implements final CPlayer player = game.getPlayer(unit.getPlayerIndex()); if (player.getGold() >= unitType.getGoldCost()) { if (player.getLumber() >= unitType.getLumberCost()) { - receiver.useOk(); + if ((player.getFoodUsed() + unitType.getFoodUsed()) <= player.getFoodCap()) { + receiver.useOk(); + } + else { + receiver.notEnoughResources(ResourceType.FOOD); + } } else { receiver.notEnoughResources(ResourceType.LUMBER); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityOrcBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityOrcBuild.java index 3967432..37a9f57 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityOrcBuild.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityOrcBuild.java @@ -59,6 +59,9 @@ public class CAbilityOrcBuild extends AbstractCAbilityBuild { } final CPlayer player = game.getPlayer(caster.getPlayerIndex()); player.chargeFor(unitType); + if (unitType.getFoodUsed() != 0) { + player.setFoodUsed(player.getFoodUsed() + unitType.getFoodUsed()); + } return this.buildBehavior.reset(point, orderId, getBaseOrderId()); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java index 87675ee..f8da3db 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java @@ -14,6 +14,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CAbstractRangedBehavior; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.pathing.CBuildingPathingType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayer; public class CBehaviorOrcBuild extends CAbstractRangedBehavior { private int highlightOrderId; @@ -63,6 +64,7 @@ public class CBehaviorOrcBuild extends CAbstractRangedBehavior { constructedStructure.setWorkerInside(this.unit); constructedStructure.setLife(simulation, constructedStructure.getMaximumLife() * WarsmashConstants.BUILDING_CONSTRUCT_START_LIFE); + constructedStructure.setFoodUsed(unitTypeToCreate.getFoodUsed()); constructedStructure.add(simulation, new CAbilityBuildInProgress(simulation.getHandleIdAllocator().createId())); for (final CAbility ability : constructedStructure.getAbilities()) { @@ -74,6 +76,8 @@ public class CBehaviorOrcBuild extends CAbstractRangedBehavior { simulation.unitConstructedEvent(this.unit, constructedStructure); } else { + CPlayer player = simulation.getPlayer(this.unit.getPlayerIndex()); + player.setFoodUsed(player.getFoodUsed() - unitTypeToCreate.getFoodUsed()); simulation.getCommandErrorListener().showCantPlaceError(); } return this.unit.pollNextOrderBehavior(simulation); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java index 9efcd09..c0c6d97 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java @@ -117,7 +117,7 @@ public class CommandCardIcon extends AbstractRenderableFrame implements Clickabl @Override public UIFrame touchDown(final float screenX, final float screenY, final int button) { if (isVisible() && this.renderBounds.contains(screenX, screenY)) { - if (this.orderId != 0) { + if (this.orderId != 0 || menuButton) { return this; } } From 9210544e08d840c9008f7c454d99ef528b51ccba Mon Sep 17 00:00:00 2001 From: Retera Date: Sat, 9 Jan 2021 23:20:48 -0500 Subject: [PATCH 081/116] Updates to UI --- core/assets/warsmash.ini | 40 ++++---- core/assets/warsmash127.ini | 37 ++++++++ core/assets/warsmash131.ini | 22 ----- .../warsmash/parsers/mdlx/MdlxModel.java | 2 +- .../com/etheller/warsmash/util/MdlUtils.java | 2 +- .../warsmash/util/WarsmashConstants.java | 2 +- .../viewer5/handlers/w3x/War3MapViewer.java | 6 +- .../CommandCardPopulatingAbilityVisitor.java | 12 ++- .../handlers/w3x/simulation/CUnit.java | 18 ++++ .../simulation/abilities/CAbilityVisitor.java | 3 + .../build/AbstractCAbilityBuild.java | 3 +- .../generic/AbstractGenericNoIconAbility.java | 32 +++++++ ...bstractGenericSingleIconActiveAbility.java | 15 ++- .../generic/GenericNoIconAbility.java | 8 ++ .../abilities/harvest/CAbilityHarvest.java | 30 ++++++ .../abilities/mine/CAbilityGoldMine.java | 92 +++++++++++++++++++ .../abilities/queue/CAbilityQueue.java | 3 +- .../{ => abilities/types}/CAbilityType.java | 13 ++- .../types}/CAbilityTypeLevelData.java | 2 +- .../definitions/CAbilityTypeDefinition.java | 9 ++ .../impl/AbstractCAbilityTypeDefinition.java | 31 +++++++ .../CAbilityTypeDefinitionColdArrows.java | 27 ++++++ .../impl/CAbilityTypeDefinitionGoldMine.java | 37 ++++++++ .../types/impl/CAbilityTypeColdArrows.java | 22 +++++ .../impl/CAbilityTypeColdArrowsLevelData.java | 14 +++ .../types/impl/CAbilityTypeGoldMine.java | 24 +++++ .../impl/CAbilityTypeGoldMineLevelData.java | 32 +++++++ ...yDisableWhileUnderConstructionVisitor.java | 8 ++ .../w3x/simulation/data/CAbilityData.java | 43 ++++----- .../util/AbilityTargetCheckReceiver.java | 4 +- .../BooleanAbilityTargetCheckReceiver.java | 5 + .../CWidgetAbilityTargetCheckReceiver.java | 4 + .../util/PointAbilityTargetCheckReceiver.java | 5 + .../util/StringMsgTargetCheckReceiver.java | 5 + .../viewer5/handlers/w3x/ui/MeleeUI.java | 56 ++++++----- 35 files changed, 559 insertions(+), 109 deletions(-) create mode 100644 core/assets/warsmash127.ini delete mode 100644 core/assets/warsmash131.ini create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/AbstractGenericNoIconAbility.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/GenericNoIconAbility.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/mine/CAbilityGoldMine.java rename core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/{ => abilities/types}/CAbilityType.java (62%) rename core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/{ => abilities/types}/CAbilityTypeLevelData.java (83%) create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/definitions/CAbilityTypeDefinition.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/definitions/impl/AbstractCAbilityTypeDefinition.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/definitions/impl/CAbilityTypeDefinitionColdArrows.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/definitions/impl/CAbilityTypeDefinitionGoldMine.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeColdArrows.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeColdArrowsLevelData.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeGoldMine.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeGoldMineLevelData.java diff --git a/core/assets/warsmash.ini b/core/assets/warsmash.ini index 6569b5d..8b7bf66 100644 --- a/core/assets/warsmash.ini +++ b/core/assets/warsmash.ini @@ -1,32 +1,24 @@ [DataSources] -Count=7 -Type00=MPQ -Path00="D:\Games\Warcraft III Patch 1.22\war3.mpq" -Type01=MPQ -Path01="D:\Games\Warcraft III Patch 1.22\War3x.mpq" -Type02=MPQ -Path02="D:\Games\Warcraft III Patch 1.22\War3xlocal.mpq" -Type03=MPQ -Path03="D:\Games\Warcraft III Patch 1.22\War3Patch.mpq" +Count=5 +Type00=Folder +Path00="D:\Games\Warcraft III CASC 1.31\war3.w3mod" +Type01=Folder +Path01="D:\Games\Warcraft III CASC 1.31\war3.w3mod\_locales\enus.w3mod" +Type02=Folder +Path02="..\..\resources" +Type03=Folder +Path03="D:\Backups\Warsmash\Data" Type04=Folder -Path04="..\..\resources" -Type05=Folder -Path05="D:\Backups\Warsmash\Data" -Type06=Folder -Path06="." +Path04="." [Map] -//FilePath="CombatUnitTests.w3x" //FilePath="PitchRoll.w3x" -FilePath="PeonStartingBase.w3x" -//FilePath="DungeonGoldMine.w3m" -//FilePath="PlayerPeasants.w3m" -//FilePath="FireLord.w3x" +//FilePath="ReforgedGeorgeVacation.w3x" //FilePath="Maps\Campaign\NightElf03.w3m" -//FilePath="PhoenixAttack.w3x" -//FilePath="LightEnvironmentTest.w3x" +//FilePath="PrivateDontShare/Cult 8.w3x" //FilePath="TorchLight2.w3x" //FilePath="OrcAssault.w3x" -//FilePath="FrostyVsFarm.w3m" -//FilePath="ModelTest.w3x" -//FilePath="SpinningSample.w3x" \ No newline at end of file +FilePath="PeonStartingBase.w3x" +//FilePath="PhoenixAttack.w3x" +//FilePath="OperationReforged.w3x" +//FilePath="AzerothRoleplay1.909t03DecoratedV2.w3x" diff --git a/core/assets/warsmash127.ini b/core/assets/warsmash127.ini new file mode 100644 index 0000000..d6740ea --- /dev/null +++ b/core/assets/warsmash127.ini @@ -0,0 +1,37 @@ +[DataSources] +Count=7 +Type00=MPQ +Path00="D:\Games\Warcraft III Patch 1.22\war3.mpq" +Type01=MPQ +Path01="D:\Games\Warcraft III Patch 1.22\War3x.mpq" +Type02=MPQ +Path02="D:\Games\Warcraft III Patch 1.22\War3xlocal.mpq" +Type03=MPQ +Path03="D:\Games\Warcraft III Patch 1.22\War3Patch.mpq" +Type04=Folder +Path04="..\..\resources" +Type05=Folder +Path05="D:\Backups\Warsmash\Data" +Type06=Folder +Path06="." + +[Map] +//FilePath="CombatUnitTests.w3x" +//FilePath="PitchRoll.w3x" +FilePath="PeonStartingBase.w3x" +//FilePath="ColdArrows.w3m" +//FilePath="DungeonGoldMine.w3m" +//FilePath="PlayerPeasants.w3m" +//FilePath="FireLord.w3x" +//FilePath="Maps\Campaign\NightElf03.w3m" +//FilePath="PhoenixAttack.w3x" +//FilePath="LightEnvironmentTest.w3x" +//FilePath="TorchLight2.w3x" +//FilePath="OrcAssault.w3x" +//FilePath="FrostyVsFarm.w3m" +//FilePath="ModelTest.w3x" +//FilePath="SpinningSample.w3x" +//FilePath="Maps\Campaign\Prologue02.w3m" +//FilePath="Pathing.w3x" +//FilePath="ItemFacing.w3x" +//FilePath=SomeParticleTests.w3x diff --git a/core/assets/warsmash131.ini b/core/assets/warsmash131.ini deleted file mode 100644 index 0133118..0000000 --- a/core/assets/warsmash131.ini +++ /dev/null @@ -1,22 +0,0 @@ -[DataSources] -Count=5 -Type00=Folder -Path00="E:\Games\Warcraft III CASC 1.31\war3.w3mod" -Type01=Folder -Path01="E:\Games\Warcraft III CASC 1.31\war3.w3mod\_locales\enus.w3mod" -Type02=Folder -Path02="..\..\resources" -Type03=Folder -Path03="E:\Backups\Warsmash\Data" -Type04=Folder -Path04="." - -[Map] -//FilePath="PitchRoll.w3x" -//FilePath="ReforgedGeorgeVacation.w3x" -//FilePath="Maps\Campaign\NightElf03.w3m" -//FilePath="PrivateDontShare/Cult 8.w3x" -//FilePath="TorchLight2.w3x" -//FilePath="OrcAssault.w3x" -//FilePath="PeonStartingBase.w3x" -FilePath="PhoenixAttack.w3x" diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/MdlxModel.java b/core/src/com/etheller/warsmash/parsers/mdlx/MdlxModel.java index 828829e..f89a61f 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/MdlxModel.java +++ b/core/src/com/etheller/warsmash/parsers/mdlx/MdlxModel.java @@ -360,7 +360,7 @@ public class MdlxModel { break; case MdlUtils.TOKEN_TEXTURE_ANIMS: this.loadNumberedObjectBlock(this.textureAnimations, MdlxBlockDescriptor.TEXTURE_ANIMATION, - MdlUtils.TOKEN_TEXTURE_ANIM, stream); + MdlUtils.TOKEN_TVERTEX_ANIM, stream); break; case MdlUtils.TOKEN_GEOSET: this.loadObject(this.geosets, MdlxBlockDescriptor.GEOSET, stream); diff --git a/core/src/com/etheller/warsmash/util/MdlUtils.java b/core/src/com/etheller/warsmash/util/MdlUtils.java index 7712229..3e3623f 100644 --- a/core/src/com/etheller/warsmash/util/MdlUtils.java +++ b/core/src/com/etheller/warsmash/util/MdlUtils.java @@ -30,6 +30,7 @@ public class MdlUtils { public static final String TOKEN_BITMAP = "Bitmap"; public static final String TOKEN_TVERTEX_ANIM_SPACE = "TVertexAnim "; + public static final String TOKEN_TVERTEX_ANIM = "TVertexAnim"; public static final String TOKEN_DONT_INTERP = "DontInterp"; public static final String TOKEN_LINEAR = "Linear"; @@ -177,7 +178,6 @@ public class MdlUtils { public static final String TOKEN_TEXTURES = "Textures"; public static final String TOKEN_MATERIALS = "Materials"; public static final String TOKEN_TEXTURE_ANIMS = "TextureAnims"; - public static final String TOKEN_TEXTURE_ANIM = "TextureAnim"; public static final String TOKEN_PIVOT_POINTS = "PivotPoints"; public static final String TOKEN_ATTACHMENT = "Attachment"; diff --git a/core/src/com/etheller/warsmash/util/WarsmashConstants.java b/core/src/com/etheller/warsmash/util/WarsmashConstants.java index c7cd7a4..86eaa74 100644 --- a/core/src/com/etheller/warsmash/util/WarsmashConstants.java +++ b/core/src/com/etheller/warsmash/util/WarsmashConstants.java @@ -1,7 +1,7 @@ package com.etheller.warsmash.util; public class WarsmashConstants { - public static final int MAX_PLAYERS = 16; + public static final int MAX_PLAYERS = 28; public static final int REPLACEABLE_TEXTURE_LIMIT = 64; public static final float SIMULATION_STEP_TIME = 1 / 20f; public static final int PORT_NUMBER = 6115; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index 1b7dd28..1459057 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -848,7 +848,11 @@ public class War3MapViewer extends AbstractMdxModelViewer { final int playerIndex = unit.getPlayer(); final float unitAngle = unit.getAngle(); - createNewUnit(modifications, unitId, unitX, unitY, unitZ, playerIndex, unitAngle); + final CUnit unitCreated = createNewUnit(modifications, unitId, unitX, unitY, unitZ, playerIndex, + unitAngle); + if (unit.getGoldAmount() != 0) { + unitCreated.setGold(unit.getGoldAmount()); + } } } this.simulation.unitsLoaded(); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java index 312c736..2cf48f9 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java @@ -2,6 +2,7 @@ package com.etheller.warsmash.viewer5.handlers.w3x.rendersim.commandbuttons; import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.AbilityDataUI; +import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.AbilityIconUI; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.IconUI; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; @@ -19,6 +20,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAb import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityOrcBuild; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityUndeadBuild; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.combat.CAbilityColdArrows; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic.GenericNoIconAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic.GenericSingleIconActiveAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityQueue; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityRally; @@ -101,12 +103,18 @@ public class CommandCardPopulatingAbilityVisitor implements CAbilityVisitor { T accept(GenericSingleIconActiveAbility ability); T accept(CAbilityRally ability); + + T accept(GenericNoIconAbility ability); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java index 7aef252..1fb4116 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java @@ -40,7 +40,8 @@ public abstract class AbstractCAbilityBuild extends AbstractCAbility implements final CPlayer player = game.getPlayer(unit.getPlayerIndex()); if (player.getGold() >= unitType.getGoldCost()) { if (player.getLumber() >= unitType.getLumberCost()) { - if ((player.getFoodUsed() + unitType.getFoodUsed()) <= player.getFoodCap()) { + if ((unitType.getFoodUsed() == 0) + || ((player.getFoodUsed() + unitType.getFoodUsed()) <= player.getFoodCap())) { receiver.useOk(); } else { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/AbstractGenericNoIconAbility.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/AbstractGenericNoIconAbility.java new file mode 100644 index 0000000..f215c1c --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/AbstractGenericNoIconAbility.java @@ -0,0 +1,32 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic; + +import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.AbstractCAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; + +public abstract class AbstractGenericNoIconAbility extends AbstractCAbility implements GenericNoIconAbility { + private final War3ID alias; + + public AbstractGenericNoIconAbility(final int handleId, final War3ID alias) { + super(handleId); + this.alias = alias; + } + + @Override + public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId) { + return true; + } + + @Override + public T visit(final CAbilityVisitor visitor) { + return visitor.accept(this); + } + + @Override + public War3ID getAlias() { + return this.alias; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/AbstractGenericSingleIconActiveAbility.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/AbstractGenericSingleIconActiveAbility.java index 7c36b20..579970e 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/AbstractGenericSingleIconActiveAbility.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/AbstractGenericSingleIconActiveAbility.java @@ -27,35 +27,44 @@ public abstract class AbstractGenericSingleIconActiveAbility extends AbstractCAb public void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, final CWidget target, final AbilityTargetCheckReceiver receiver) { if (orderId == getBaseOrderId()) { - receiver.targetOk(target); + innerCheckCanTarget(game, unit, orderId, target, receiver); } else { receiver.orderIdNotAccepted(); } } + protected abstract void innerCheckCanTarget(CSimulation game, CUnit unit, int orderId, CWidget target, + AbilityTargetCheckReceiver receiver); + @Override public void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, final AbilityPointTarget target, final AbilityTargetCheckReceiver receiver) { if (orderId == getBaseOrderId()) { - receiver.targetOk(target); + innerCheckCanTarget(game, unit, orderId, target, receiver); } else { receiver.orderIdNotAccepted(); } } + protected abstract void innerCheckCanTarget(CSimulation game, CUnit unit, int orderId, AbilityPointTarget target, + AbilityTargetCheckReceiver receiver); + @Override public void checkCanTargetNoTarget(final CSimulation game, final CUnit unit, final int orderId, final AbilityTargetCheckReceiver receiver) { if (orderId == getBaseOrderId()) { - receiver.targetOk(null); + innerCheckCanTargetNoTarget(game, unit, orderId, receiver); } else { receiver.orderIdNotAccepted(); } } + protected abstract void innerCheckCanTargetNoTarget(CSimulation game, CUnit unit, int orderId, + AbilityTargetCheckReceiver receiver); + @Override public T visit(final CAbilityVisitor visitor) { return visitor.accept(this); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/GenericNoIconAbility.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/GenericNoIconAbility.java new file mode 100644 index 0000000..fb6dd4c --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/GenericNoIconAbility.java @@ -0,0 +1,8 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic; + +import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; + +public interface GenericNoIconAbility extends CAbility { + War3ID getAlias(); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityHarvest.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityHarvest.java index f11a60e..9f939b2 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityHarvest.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityHarvest.java @@ -9,6 +9,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.ResourceType; public class CAbilityHarvest extends AbstractGenericSingleIconActiveAbility { @@ -59,4 +60,33 @@ public class CAbilityHarvest extends AbstractGenericSingleIconActiveAbility { receiver.useOk(); } + @Override + protected void innerCheckCanTarget(final CSimulation game, final CUnit unit, final int orderId, + final CWidget target, final AbilityTargetCheckReceiver receiver) { + if (target instanceof CUnit) { + final CUnit targetUnit = (CUnit) target; + if (targetUnit.getGold() > 0) { + receiver.targetOk(target); + } + else { + receiver.mustTargetResources(); + } + } + else { + receiver.mustTargetResources(); + } + } + + @Override + protected void innerCheckCanTarget(final CSimulation game, final CUnit unit, final int orderId, + final AbilityPointTarget target, final AbilityTargetCheckReceiver receiver) { + + } + + @Override + protected void innerCheckCanTargetNoTarget(final CSimulation game, final CUnit unit, final int orderId, + final AbilityTargetCheckReceiver receiver) { + + } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/mine/CAbilityGoldMine.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/mine/CAbilityGoldMine.java new file mode 100644 index 0000000..5c0c797 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/mine/CAbilityGoldMine.java @@ -0,0 +1,92 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.mine; + +import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic.AbstractGenericNoIconAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; + +public class CAbilityGoldMine extends AbstractGenericNoIconAbility { + private int gold; + private final float miningDuration; + private final int miningCapacity; + + public CAbilityGoldMine(final int handleId, final War3ID alias, final int maxGold, final float miningDuration, + final int miningCapacity) { + super(handleId, alias); + this.gold = maxGold; + this.miningDuration = miningDuration; + this.miningCapacity = miningCapacity; + } + + @Override + public void onAdd(final CSimulation game, final CUnit unit) { + + } + + @Override + public void onRemove(final CSimulation game, final CUnit unit) { + + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final CWidget target) { + return null; + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, + final AbilityPointTarget point) { + return null; + } + + @Override + public CBehavior beginNoTarget(final CSimulation game, final CUnit caster, final int orderId) { + return null; + } + + @Override + public void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, final CWidget target, + final AbilityTargetCheckReceiver receiver) { + receiver.orderIdNotAccepted(); + } + + @Override + public void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, + final AbilityPointTarget target, final AbilityTargetCheckReceiver receiver) { + receiver.orderIdNotAccepted(); + } + + @Override + public void checkCanTargetNoTarget(final CSimulation game, final CUnit unit, final int orderId, + final AbilityTargetCheckReceiver receiver) { + receiver.orderIdNotAccepted(); + } + + @Override + protected void innerCheckCanUse(final CSimulation game, final CUnit unit, final int orderId, + final AbilityActivationReceiver receiver) { + receiver.notAnActiveAbility(); + } + + public int getGold() { + return this.gold; + } + + public void setGold(final int gold) { + this.gold = gold; + } + + public int getMiningCapacity() { + return this.miningCapacity; + } + + public float getMiningDuration() { + return this.miningDuration; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityQueue.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityQueue.java index 1b5acba..22c066c 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityQueue.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityQueue.java @@ -47,7 +47,8 @@ public final class CAbilityQueue extends AbstractCAbility { final CPlayer player = game.getPlayer(unit.getPlayerIndex()); if (player.getGold() >= unitType.getGoldCost()) { if (player.getLumber() >= unitType.getLumberCost()) { - if ((player.getFoodUsed() + unitType.getFoodUsed()) <= player.getFoodCap()) { + if ((unitType.getFoodUsed() == 0) + || ((player.getFoodUsed() + unitType.getFoodUsed()) <= player.getFoodCap())) { receiver.useOk(); } else { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CAbilityType.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/CAbilityType.java similarity index 62% rename from core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CAbilityType.java rename to core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/CAbilityType.java index de8b0cd..5c8725b 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CAbilityType.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/CAbilityType.java @@ -1,20 +1,21 @@ -package com.etheller.warsmash.viewer5.handlers.w3x.simulation; +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types; import java.util.EnumSet; import java.util.List; import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; -public class CAbilityType { +public abstract class CAbilityType { /* alias: defines which ability editor ability to use */ private final War3ID alias; /* code: defines which CAbility class to use */ private final War3ID code; - private final List levelData; + private final List levelData; - public CAbilityType(final War3ID alias, final War3ID code, final List levelData) { + public CAbilityType(final War3ID alias, final War3ID code, final List levelData) { this.alias = alias; this.code = code; this.levelData = levelData; @@ -32,8 +33,10 @@ public class CAbilityType { return getLevelData(level).getTargetsAllowed(); } - private CAbilityTypeLevelData getLevelData(final int level) { + protected final TYPE_LEVEL_DATA getLevelData(final int level) { return this.levelData.get(level); } + public abstract CAbility createAbility(int handleId); + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CAbilityTypeLevelData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/CAbilityTypeLevelData.java similarity index 83% rename from core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CAbilityTypeLevelData.java rename to core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/CAbilityTypeLevelData.java index 975abe7..d023452 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CAbilityTypeLevelData.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/CAbilityTypeLevelData.java @@ -1,4 +1,4 @@ -package com.etheller.warsmash.viewer5.handlers.w3x.simulation; +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types; import java.util.EnumSet; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/definitions/CAbilityTypeDefinition.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/definitions/CAbilityTypeDefinition.java new file mode 100644 index 0000000..361e244 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/definitions/CAbilityTypeDefinition.java @@ -0,0 +1,9 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.definitions; + +import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; +import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.CAbilityType; + +public interface CAbilityTypeDefinition { + CAbilityType createAbilityType(War3ID rawcode, MutableGameObject abilityEditorData); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/definitions/impl/AbstractCAbilityTypeDefinition.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/definitions/impl/AbstractCAbilityTypeDefinition.java new file mode 100644 index 0000000..4bb6718 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/definitions/impl/AbstractCAbilityTypeDefinition.java @@ -0,0 +1,31 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.definitions.impl; + +import java.util.ArrayList; +import java.util.List; + +import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; +import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.CAbilityType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.CAbilityTypeLevelData; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.definitions.CAbilityTypeDefinition; + +public abstract class AbstractCAbilityTypeDefinition + implements CAbilityTypeDefinition { + protected static final War3ID TARGETS_ALLOWED = War3ID.fromString("atar"); + private static final War3ID LEVELS = War3ID.fromString("alev"); + + @Override + public CAbilityType createAbilityType(final War3ID alias, final MutableGameObject abilityEditorData) { + final int levels = abilityEditorData.getFieldAsInteger(LEVELS, 0); + final List levelData = new ArrayList<>(); + for (int level = 0; level < levels; level++) { + levelData.add(createLevelData(abilityEditorData, level)); + } + return innerCreateAbilityType(alias, abilityEditorData, levelData); + } + + protected abstract TYPE_LEVEL_DATA createLevelData(MutableGameObject abilityEditorData, int level); + + protected abstract CAbilityType innerCreateAbilityType(War3ID alias, MutableGameObject abilityEditorData, + List levelData); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/definitions/impl/CAbilityTypeDefinitionColdArrows.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/definitions/impl/CAbilityTypeDefinitionColdArrows.java new file mode 100644 index 0000000..55a6557 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/definitions/impl/CAbilityTypeDefinitionColdArrows.java @@ -0,0 +1,27 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.definitions.impl; + +import java.util.List; + +import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; +import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.CAbilityType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.impl.CAbilityTypeColdArrows; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.impl.CAbilityTypeColdArrowsLevelData; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; + +public class CAbilityTypeDefinitionColdArrows extends AbstractCAbilityTypeDefinition { + + @Override + protected CAbilityTypeColdArrowsLevelData createLevelData(final MutableGameObject abilityEditorData, + final int level) { + return new CAbilityTypeColdArrowsLevelData( + CTargetType.parseTargetTypeSet(abilityEditorData.getFieldAsString(TARGETS_ALLOWED, level))); + } + + @Override + protected CAbilityType innerCreateAbilityType(final War3ID alias, final MutableGameObject abilityEditorData, + final List levelData) { + return new CAbilityTypeColdArrows(alias, abilityEditorData.getCode(), levelData); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/definitions/impl/CAbilityTypeDefinitionGoldMine.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/definitions/impl/CAbilityTypeDefinitionGoldMine.java new file mode 100644 index 0000000..0b86b09 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/definitions/impl/CAbilityTypeDefinitionGoldMine.java @@ -0,0 +1,37 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.definitions.impl; + +import java.util.EnumSet; +import java.util.List; + +import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; +import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.CAbilityType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.definitions.CAbilityTypeDefinition; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.impl.CAbilityTypeGoldMine; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.impl.CAbilityTypeGoldMineLevelData; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; + +public class CAbilityTypeDefinitionGoldMine extends AbstractCAbilityTypeDefinition + implements CAbilityTypeDefinition { + protected static final War3ID MAX_GOLD = War3ID.fromString("Gld1"); + protected static final War3ID MINING_DURATION = War3ID.fromString("Gld2"); + protected static final War3ID MINING_CAPACITY = War3ID.fromString("Gld3"); + + @Override + protected CAbilityTypeGoldMineLevelData createLevelData(final MutableGameObject abilityEditorData, + final int level) { + final String targetsAllowedAtLevelString = abilityEditorData.getFieldAsString(TARGETS_ALLOWED, level); + final EnumSet targetsAllowedAtLevel = CTargetType.parseTargetTypeSet(targetsAllowedAtLevelString); + final int maxGold = abilityEditorData.getFieldAsInteger(MAX_GOLD, level); + final float miningDuration = abilityEditorData.getFieldAsFloat(MINING_DURATION, level); + final int miningCapacity = abilityEditorData.getFieldAsInteger(MINING_CAPACITY, level); + return new CAbilityTypeGoldMineLevelData(targetsAllowedAtLevel, maxGold, miningDuration, miningCapacity); + } + + @Override + protected CAbilityType innerCreateAbilityType(final War3ID alias, final MutableGameObject abilityEditorData, + final List levelData) { + return new CAbilityTypeGoldMine(alias, abilityEditorData.getCode(), levelData); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeColdArrows.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeColdArrows.java new file mode 100644 index 0000000..5e86f3b --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeColdArrows.java @@ -0,0 +1,22 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.impl; + +import java.util.List; + +import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.combat.CAbilityColdArrows; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.CAbilityType; + +public class CAbilityTypeColdArrows extends CAbilityType { + + public CAbilityTypeColdArrows(final War3ID alias, final War3ID code, + final List levelData) { + super(alias, code, levelData); + } + + @Override + public CAbility createAbility(final int handleId) { + return new CAbilityColdArrows(getAlias(), handleId); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeColdArrowsLevelData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeColdArrowsLevelData.java new file mode 100644 index 0000000..7b16958 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeColdArrowsLevelData.java @@ -0,0 +1,14 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.impl; + +import java.util.EnumSet; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.CAbilityTypeLevelData; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; + +public class CAbilityTypeColdArrowsLevelData extends CAbilityTypeLevelData { + + public CAbilityTypeColdArrowsLevelData(final EnumSet targetsAllowed) { + super(targetsAllowed); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeGoldMine.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeGoldMine.java new file mode 100644 index 0000000..bd0f428 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeGoldMine.java @@ -0,0 +1,24 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.impl; + +import java.util.List; + +import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.mine.CAbilityGoldMine; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.CAbilityType; + +public class CAbilityTypeGoldMine extends CAbilityType { + + public CAbilityTypeGoldMine(final War3ID alias, final War3ID code, + final List levelData) { + super(alias, code, levelData); + } + + @Override + public CAbility createAbility(final int handleId) { + final CAbilityTypeGoldMineLevelData levelData = getLevelData(0); + return new CAbilityGoldMine(handleId, getAlias(), levelData.getMaxGold(), levelData.getMiningDuration(), + levelData.getMiningCapacity()); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeGoldMineLevelData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeGoldMineLevelData.java new file mode 100644 index 0000000..522fd62 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeGoldMineLevelData.java @@ -0,0 +1,32 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.impl; + +import java.util.EnumSet; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.CAbilityTypeLevelData; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; + +public class CAbilityTypeGoldMineLevelData extends CAbilityTypeLevelData { + private final int maxGold; + private final float miningDuration; + private final int miningCapacity; + + public CAbilityTypeGoldMineLevelData(final EnumSet targetsAllowed, final int maxGold, + final float miningDuration, final int miningCapacity) { + super(targetsAllowed); + this.maxGold = maxGold; + this.miningDuration = miningDuration; + this.miningCapacity = miningCapacity; + } + + public int getMaxGold() { + return this.maxGold; + } + + public float getMiningDuration() { + return this.miningDuration; + } + + public int getMiningCapacity() { + return this.miningCapacity; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/AbilityDisableWhileUnderConstructionVisitor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/AbilityDisableWhileUnderConstructionVisitor.java index cb71490..9280afb 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/AbilityDisableWhileUnderConstructionVisitor.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/AbilityDisableWhileUnderConstructionVisitor.java @@ -12,6 +12,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAb import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityOrcBuild; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityUndeadBuild; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.combat.CAbilityColdArrows; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic.GenericNoIconAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic.GenericSingleIconActiveAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityQueue; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityRally; @@ -117,4 +118,11 @@ public class AbilityDisableWhileUnderConstructionVisitor implements CAbilityVisi return null; } + @Override + public Void accept(final GenericNoIconAbility ability) { + ability.setDisabled(false); + ability.setIconShowing(false); + return null; + } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CAbilityData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CAbilityData.java index 42cb3ed..578b1b5 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CAbilityData.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CAbilityData.java @@ -1,55 +1,50 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.data; -import java.util.ArrayList; -import java.util.EnumSet; import java.util.HashMap; -import java.util.List; import java.util.Map; import com.etheller.warsmash.units.manager.MutableObjectData; import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; import com.etheller.warsmash.util.War3ID; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CAbilityType; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CAbilityTypeLevelData; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityGeneric; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.combat.CAbilityColdArrows; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.CAbilityType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.definitions.CAbilityTypeDefinition; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.definitions.impl.CAbilityTypeDefinitionColdArrows; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.definitions.impl.CAbilityTypeDefinitionGoldMine; public class CAbilityData { - private static final War3ID TARGETS_ALLOWED = War3ID.fromString("atar"); - private static final War3ID LEVELS = War3ID.fromString("alev"); - private static final War3ID COLD_ARROWS = War3ID.fromString("ACcw"); private final MutableObjectData abilityData; - private Map aliasToAbilityType = new HashMap<>(); + private Map> aliasToAbilityType = new HashMap<>(); + private final Map codeToAbilityTypeDefinition = new HashMap<>(); public CAbilityData(final MutableObjectData abilityData) { this.abilityData = abilityData; this.aliasToAbilityType = new HashMap<>(); } - public CAbilityType getAbilityType(final War3ID alias) { - CAbilityType abilityType = this.aliasToAbilityType.get(alias); + private void registerCodes() { + this.codeToAbilityTypeDefinition.put(War3ID.fromString("ACcw"), new CAbilityTypeDefinitionColdArrows()); + this.codeToAbilityTypeDefinition.put(War3ID.fromString("Agld"), new CAbilityTypeDefinitionGoldMine()); + } + + public CAbilityType getAbilityType(final War3ID alias) { + CAbilityType abilityType = this.aliasToAbilityType.get(alias); if (abilityType == null) { final MutableGameObject mutableGameObject = this.abilityData.get(alias); - final int levels = mutableGameObject.getFieldAsInteger(LEVELS, 0); - final List levelData = new ArrayList<>(); - for (int level = 0; level < levels; level++) { - final String targetsAllowedAtLevelString = mutableGameObject.getFieldAsString(TARGETS_ALLOWED, level); - final EnumSet targetsAllowedAtLevel = CTargetType - .parseTargetTypeSet(targetsAllowedAtLevelString); - levelData.add(new CAbilityTypeLevelData(targetsAllowedAtLevel)); - } - abilityType = new CAbilityType(alias, mutableGameObject.getCode(), levelData); + final War3ID code = mutableGameObject.getCode(); + final CAbilityTypeDefinition abilityTypeDefinition = this.codeToAbilityTypeDefinition.get(code); + abilityType = abilityTypeDefinition.createAbilityType(alias, mutableGameObject); } return abilityType; } public CAbility createAbility(final String ability, final int handleId) { final War3ID war3Id = War3ID.fromString(ability); - if (war3Id.equals(COLD_ARROWS)) { - return new CAbilityColdArrows(war3Id, handleId); + final CAbilityType abilityType = getAbilityType(war3Id); + if (abilityType != null) { + return abilityType.createAbility(handleId); } return new CAbilityGeneric(war3Id, handleId); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/AbilityTargetCheckReceiver.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/AbilityTargetCheckReceiver.java index 2d11d29..2959ee6 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/AbilityTargetCheckReceiver.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/AbilityTargetCheckReceiver.java @@ -7,6 +7,8 @@ public interface AbilityTargetCheckReceiver { void mustTargetType(TargetType correctType); + void mustTargetResources(); + void targetOutsideRange(double howMuch); void notAnActiveAbility(); @@ -29,6 +31,6 @@ public interface AbilityTargetCheckReceiver { UNIT, POINT, UNIT_OR_POINT, - NO_TARGET, + NO_TARGET } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/BooleanAbilityTargetCheckReceiver.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/BooleanAbilityTargetCheckReceiver.java index ab1a986..bd65215 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/BooleanAbilityTargetCheckReceiver.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/BooleanAbilityTargetCheckReceiver.java @@ -33,6 +33,11 @@ public final class BooleanAbilityTargetCheckReceiver implements Abi this.targetable = false; } + @Override + public void mustTargetResources() { + this.targetable = false; + } + @Override public void targetOutsideRange(final double howMuch) { this.targetable = false; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/CWidgetAbilityTargetCheckReceiver.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/CWidgetAbilityTargetCheckReceiver.java index aa960e4..3dd15f7 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/CWidgetAbilityTargetCheckReceiver.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/CWidgetAbilityTargetCheckReceiver.java @@ -20,7 +20,11 @@ public class CWidgetAbilityTargetCheckReceiver implements AbilityTargetCheckRece @Override public void mustTargetType(final TargetType correctType) { this.target = null; + } + @Override + public void mustTargetResources() { + this.target = null; } @Override diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/PointAbilityTargetCheckReceiver.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/PointAbilityTargetCheckReceiver.java index e49f53c..0a17401 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/PointAbilityTargetCheckReceiver.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/PointAbilityTargetCheckReceiver.java @@ -22,6 +22,11 @@ public class PointAbilityTargetCheckReceiver implements AbilityTargetCheckReceiv this.target = null; } + @Override + public void mustTargetResources() { + this.target = null; + } + @Override public void targetOutsideRange(final double howMuch) { this.target = null; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/StringMsgTargetCheckReceiver.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/StringMsgTargetCheckReceiver.java index 1622261..15008bc 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/StringMsgTargetCheckReceiver.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/StringMsgTargetCheckReceiver.java @@ -63,6 +63,11 @@ public final class StringMsgTargetCheckReceiver implements AbilityT } } + @Override + public void mustTargetResources() { + this.message = "NOTEXTERN: Must target resources."; + } + @Override public void targetOutsideRange(final double howMuch) { this.message = "NOTEXTERN: Target is outside range."; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java index 93d4aef..4ed5c0c 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java @@ -101,6 +101,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAb import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityOrcBuild; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityUndeadBuild; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.combat.CAbilityColdArrows; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic.GenericNoIconAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic.GenericSingleIconActiveAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityQueue; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityRally; @@ -318,29 +319,35 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma final CRace race = this.localPlayer.getRace(); final int racialSkinIndex; int racialCommandIndex; - switch (race) { - case HUMAN: + if (race == null) { racialSkinIndex = 1; racialCommandIndex = 0; - break; - case ORC: - racialSkinIndex = 0; - racialCommandIndex = 1; - break; - case NIGHTELF: - racialSkinIndex = 2; - racialCommandIndex = 3; - break; - case UNDEAD: - racialSkinIndex = 3; - racialCommandIndex = 2; - break; - case DEMON: - case OTHER: - default: - racialSkinIndex = -1; - racialCommandIndex = 0; - break; + } + else { + switch (race) { + case HUMAN: + racialSkinIndex = 1; + racialCommandIndex = 0; + break; + case ORC: + racialSkinIndex = 0; + racialCommandIndex = 1; + break; + case NIGHTELF: + racialSkinIndex = 2; + racialCommandIndex = 3; + break; + case UNDEAD: + racialSkinIndex = 3; + racialCommandIndex = 2; + break; + case DEMON: + case OTHER: + default: + racialSkinIndex = -1; + racialCommandIndex = 0; + break; + } } this.rootFrame = new GameUI(this.dataSource, GameUI.loadSkin(this.dataSource, racialSkinIndex), this.uiViewport, this.fontGenerator, this.uiScene, this.war3MapViewer, racialCommandIndex); @@ -922,6 +929,13 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma return null; } + @Override + public Void accept(final GenericNoIconAbility ability) { + // this should probably never happen + handleTargetCursor(ability); + return null; + } + @Override public Void accept(final CAbilityRally ability) { handleTargetCursor(ability); From 29c58be93066b3757bb4a87e88a5a9df55472857 Mon Sep 17 00:00:00 2001 From: Retera Date: Sat, 9 Jan 2021 23:43:36 -0500 Subject: [PATCH 082/116] Update harvest ability --- .../w3x/simulation/abilities/harvest/CAbilityHarvest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityHarvest.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityHarvest.java index 9f939b2..0072070 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityHarvest.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityHarvest.java @@ -80,7 +80,7 @@ public class CAbilityHarvest extends AbstractGenericSingleIconActiveAbility { @Override protected void innerCheckCanTarget(final CSimulation game, final CUnit unit, final int orderId, final AbilityPointTarget target, final AbilityTargetCheckReceiver receiver) { - + receiver.orderIdNotAccepted(); } @Override From eeed5953cc0ad43c44446919627464b19467e1f9 Mon Sep 17 00:00:00 2001 From: Retera Date: Mon, 11 Jan 2021 01:59:25 -0500 Subject: [PATCH 083/116] Add new harvest ability --- core/assets/warsmash.ini | 45 +++-- core/assets/warsmash127.ini | 37 ----- core/assets/warsmash131.ini | 24 +++ .../warsmash/util/WarsmashConstants.java | 2 +- .../handlers/w3x/rendersim/RenderUnit.java | 5 + .../handlers/w3x/simulation/CUnit.java | 37 ++++- .../simulation/CUnitAnimationListener.java | 4 + .../abilities/harvest/CAbilityHarvest.java | 59 ++++++- .../harvest/CAbilityReturnResources.java | 81 +++++++++ .../abilities/mine/CAbilityGoldMine.java | 9 + ...yTargetStillAliveAndTargetableVisitor.java | 44 +++++ .../impl/CAbilityTypeDefinitionHarvest.java | 36 ++++ ...CAbilityTypeDefinitionReturnResources.java | 35 ++++ .../types/impl/CAbilityTypeHarvest.java | 24 +++ .../impl/CAbilityTypeHarvestLevelData.java | 33 ++++ .../impl/CAbilityTypeReturnResources.java | 32 ++++ .../CAbilityTypeReturnResourcesLevelData.java | 28 ++++ .../simulation/behaviors/CBehaviorAttack.java | 46 +---- .../behaviors/build/CBehaviorOrcBuild.java | 4 +- .../behaviors/harvest/CBehaviorHarvest.java | 157 ++++++++++++++++++ .../w3x/simulation/data/CAbilityData.java | 10 +- 21 files changed, 639 insertions(+), 113 deletions(-) delete mode 100644 core/assets/warsmash127.ini create mode 100644 core/assets/warsmash131.ini create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityReturnResources.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/targeting/AbilityTargetStillAliveAndTargetableVisitor.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/definitions/impl/CAbilityTypeDefinitionHarvest.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/definitions/impl/CAbilityTypeDefinitionReturnResources.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeHarvest.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeHarvestLevelData.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeReturnResources.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeReturnResourcesLevelData.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/harvest/CBehaviorHarvest.java diff --git a/core/assets/warsmash.ini b/core/assets/warsmash.ini index 8b7bf66..d6740ea 100644 --- a/core/assets/warsmash.ini +++ b/core/assets/warsmash.ini @@ -1,24 +1,37 @@ [DataSources] -Count=5 -Type00=Folder -Path00="D:\Games\Warcraft III CASC 1.31\war3.w3mod" -Type01=Folder -Path01="D:\Games\Warcraft III CASC 1.31\war3.w3mod\_locales\enus.w3mod" -Type02=Folder -Path02="..\..\resources" -Type03=Folder -Path03="D:\Backups\Warsmash\Data" +Count=7 +Type00=MPQ +Path00="D:\Games\Warcraft III Patch 1.22\war3.mpq" +Type01=MPQ +Path01="D:\Games\Warcraft III Patch 1.22\War3x.mpq" +Type02=MPQ +Path02="D:\Games\Warcraft III Patch 1.22\War3xlocal.mpq" +Type03=MPQ +Path03="D:\Games\Warcraft III Patch 1.22\War3Patch.mpq" Type04=Folder -Path04="." +Path04="..\..\resources" +Type05=Folder +Path05="D:\Backups\Warsmash\Data" +Type06=Folder +Path06="." [Map] +//FilePath="CombatUnitTests.w3x" //FilePath="PitchRoll.w3x" -//FilePath="ReforgedGeorgeVacation.w3x" +FilePath="PeonStartingBase.w3x" +//FilePath="ColdArrows.w3m" +//FilePath="DungeonGoldMine.w3m" +//FilePath="PlayerPeasants.w3m" +//FilePath="FireLord.w3x" //FilePath="Maps\Campaign\NightElf03.w3m" -//FilePath="PrivateDontShare/Cult 8.w3x" +//FilePath="PhoenixAttack.w3x" +//FilePath="LightEnvironmentTest.w3x" //FilePath="TorchLight2.w3x" //FilePath="OrcAssault.w3x" -FilePath="PeonStartingBase.w3x" -//FilePath="PhoenixAttack.w3x" -//FilePath="OperationReforged.w3x" -//FilePath="AzerothRoleplay1.909t03DecoratedV2.w3x" +//FilePath="FrostyVsFarm.w3m" +//FilePath="ModelTest.w3x" +//FilePath="SpinningSample.w3x" +//FilePath="Maps\Campaign\Prologue02.w3m" +//FilePath="Pathing.w3x" +//FilePath="ItemFacing.w3x" +//FilePath=SomeParticleTests.w3x diff --git a/core/assets/warsmash127.ini b/core/assets/warsmash127.ini deleted file mode 100644 index d6740ea..0000000 --- a/core/assets/warsmash127.ini +++ /dev/null @@ -1,37 +0,0 @@ -[DataSources] -Count=7 -Type00=MPQ -Path00="D:\Games\Warcraft III Patch 1.22\war3.mpq" -Type01=MPQ -Path01="D:\Games\Warcraft III Patch 1.22\War3x.mpq" -Type02=MPQ -Path02="D:\Games\Warcraft III Patch 1.22\War3xlocal.mpq" -Type03=MPQ -Path03="D:\Games\Warcraft III Patch 1.22\War3Patch.mpq" -Type04=Folder -Path04="..\..\resources" -Type05=Folder -Path05="D:\Backups\Warsmash\Data" -Type06=Folder -Path06="." - -[Map] -//FilePath="CombatUnitTests.w3x" -//FilePath="PitchRoll.w3x" -FilePath="PeonStartingBase.w3x" -//FilePath="ColdArrows.w3m" -//FilePath="DungeonGoldMine.w3m" -//FilePath="PlayerPeasants.w3m" -//FilePath="FireLord.w3x" -//FilePath="Maps\Campaign\NightElf03.w3m" -//FilePath="PhoenixAttack.w3x" -//FilePath="LightEnvironmentTest.w3x" -//FilePath="TorchLight2.w3x" -//FilePath="OrcAssault.w3x" -//FilePath="FrostyVsFarm.w3m" -//FilePath="ModelTest.w3x" -//FilePath="SpinningSample.w3x" -//FilePath="Maps\Campaign\Prologue02.w3m" -//FilePath="Pathing.w3x" -//FilePath="ItemFacing.w3x" -//FilePath=SomeParticleTests.w3x diff --git a/core/assets/warsmash131.ini b/core/assets/warsmash131.ini new file mode 100644 index 0000000..8b7bf66 --- /dev/null +++ b/core/assets/warsmash131.ini @@ -0,0 +1,24 @@ +[DataSources] +Count=5 +Type00=Folder +Path00="D:\Games\Warcraft III CASC 1.31\war3.w3mod" +Type01=Folder +Path01="D:\Games\Warcraft III CASC 1.31\war3.w3mod\_locales\enus.w3mod" +Type02=Folder +Path02="..\..\resources" +Type03=Folder +Path03="D:\Backups\Warsmash\Data" +Type04=Folder +Path04="." + +[Map] +//FilePath="PitchRoll.w3x" +//FilePath="ReforgedGeorgeVacation.w3x" +//FilePath="Maps\Campaign\NightElf03.w3m" +//FilePath="PrivateDontShare/Cult 8.w3x" +//FilePath="TorchLight2.w3x" +//FilePath="OrcAssault.w3x" +FilePath="PeonStartingBase.w3x" +//FilePath="PhoenixAttack.w3x" +//FilePath="OperationReforged.w3x" +//FilePath="AzerothRoleplay1.909t03DecoratedV2.w3x" diff --git a/core/src/com/etheller/warsmash/util/WarsmashConstants.java b/core/src/com/etheller/warsmash/util/WarsmashConstants.java index 86eaa74..c7cd7a4 100644 --- a/core/src/com/etheller/warsmash/util/WarsmashConstants.java +++ b/core/src/com/etheller/warsmash/util/WarsmashConstants.java @@ -1,7 +1,7 @@ package com.etheller.warsmash.util; public class WarsmashConstants { - public static final int MAX_PLAYERS = 28; + public static final int MAX_PLAYERS = 16; public static final int REPLACEABLE_TEXTURE_LIMIT = 64; public static final float SIMULATION_STEP_TIME = 1 / 20f; public static final int PORT_NUMBER = 6115; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java index aa9770c..965a54e 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java @@ -164,6 +164,9 @@ public class RenderUnit { else { this.instance.show(); if (wasHidden) { + if (this.selectionCircle != null) { + this.selectionCircle.show(map.terrain.centerOffset); + } if (this.shadow != null) { this.shadow.show(map.terrain.centerOffset); } @@ -430,12 +433,14 @@ public class RenderUnit { this.instance = instance; } + @Override public void addSecondaryTag(final AnimationTokens.SecondaryTag tag) { this.secondaryAnimationTags.add(tag); playAnimation(true, this.currentAnimation, this.currentAnimationSecondaryTags, this.currentSpeedRatio, this.currentlyAllowingRarityVariations); } + @Override public void removeSecondaryTag(final AnimationTokens.SecondaryTag tag) { this.secondaryAnimationTags.remove(tag); playAnimation(true, this.currentAnimation, this.currentAnimationSecondaryTags, this.currentSpeedRatio, diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java index fbff7cd..4827968 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java @@ -18,6 +18,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.RemovablePathingMapInstance; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitStateListener.CUnitStateNotifier; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityBuildInProgress; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.mine.CAbilityGoldMine; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; @@ -88,7 +89,8 @@ public class CUnit extends CWidget { private boolean constructing = false; private float constructionProgress; private boolean hidden = false; - private boolean updating = true; + private boolean paused = false; + private boolean acceptingOrders = true; private boolean invulnerable = false; private CUnit workerInside; private final War3ID[] buildQueue = new War3ID[WarsmashConstants.BUILD_QUEUE_SIZE]; @@ -230,7 +232,7 @@ public class CUnit extends CWidget { return true; } } - else if (this.updating) { + else if (!this.paused) { if (this.constructing) { this.constructionProgress += WarsmashConstants.SIMULATION_STEP_TIME; final int buildTime = this.unitType.getBuildTime(); @@ -322,7 +324,7 @@ public class CUnit extends CWidget { if (this.workerInside != null) { this.workerInside.setInvulnerable(false); this.workerInside.setHidden(false); - this.workerInside.setUpdating(true); + this.workerInside.setPaused(false); this.workerInside.nudgeAround(game, this); this.workerInside = null; } @@ -367,7 +369,7 @@ public class CUnit extends CWidget { } } - if (queue && (this.currentOrder != null)) { + if ((queue || !this.acceptingOrders) && (this.currentOrder != null)) { this.orderQueue.add(order); } else { @@ -408,6 +410,25 @@ public class CUnit extends CWidget { return this.abilities; } + public T getAbility(final CAbilityVisitor visitor) { + for (final CAbility ability : this.abilities) { + final T visited = ability.visit(visitor); + if (visited != null) { + return visited; + } + } + return null; + } + + public T getFirstAbilityOfType(final Class cAbilityClass) { + for (final CAbility ability : this.abilities) { + if (cAbilityClass.isAssignableFrom(ability.getClass())) { + return (T) ability; + } + } + return null; + } + public void setCooldownEndTime(final int cooldownEndTime) { this.cooldownEndTime = cooldownEndTime; } @@ -875,8 +896,12 @@ public class CUnit extends CWidget { this.hidden = hidden; } - public void setUpdating(final boolean updating) { - this.updating = updating; + public void setPaused(final boolean paused) { + this.paused = paused; + } + + public void setAcceptingOrders(final boolean acceptingOrders) { + this.acceptingOrders = acceptingOrders; } public boolean isHidden() { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitAnimationListener.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitAnimationListener.java index 28dd9da..2a0029b 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitAnimationListener.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitAnimationListener.java @@ -11,4 +11,8 @@ public interface CUnitAnimationListener { void queueAnimation(final PrimaryTag animationName, final EnumSet secondaryAnimationTags, boolean allowRarityVariations); + + void addSecondaryTag(SecondaryTag secondaryTag); + + void removeSecondaryTag(SecondaryTag secondaryTag); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityHarvest.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityHarvest.java index 0072070..64c0f26 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityHarvest.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityHarvest.java @@ -4,24 +4,36 @@ import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic.AbstractGenericSingleIconActiveAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.mine.CAbilityGoldMine; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.harvest.CBehaviorHarvest; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.ResourceType; public class CAbilityHarvest extends AbstractGenericSingleIconActiveAbility { + private final int damageToTree; + private final int goldCapacity; + private final int lumberCapacity; + private CBehaviorHarvest behaviorHarvest; private int carriedResourceAmount; private ResourceType carriedResourceType; - public CAbilityHarvest(final int handleId, final War3ID alias) { + public CAbilityHarvest(final int handleId, final War3ID alias, final int damageToTree, final int goldCapacity, + final int lumberCapacity) { super(handleId, alias); + this.damageToTree = damageToTree; + this.goldCapacity = goldCapacity; + this.lumberCapacity = lumberCapacity; } @Override public void onAdd(final CSimulation game, final CUnit unit) { + this.behaviorHarvest = new CBehaviorHarvest(unit, this); } @Override @@ -30,7 +42,7 @@ public class CAbilityHarvest extends AbstractGenericSingleIconActiveAbility { @Override public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final CWidget target) { - return caster.pollNextOrderBehavior(game); + return this.behaviorHarvest.reset(target); } @Override @@ -65,12 +77,20 @@ public class CAbilityHarvest extends AbstractGenericSingleIconActiveAbility { final CWidget target, final AbilityTargetCheckReceiver receiver) { if (target instanceof CUnit) { final CUnit targetUnit = (CUnit) target; - if (targetUnit.getGold() > 0) { - receiver.targetOk(target); - } - else { - receiver.mustTargetResources(); + for (final CAbility ability : targetUnit.getAbilities()) { + if (ability instanceof CAbilityGoldMine) { + receiver.targetOk(target); + return; + } + else if ((this.carriedResourceType != null) && (ability instanceof CAbilityReturnResources)) { + final CAbilityReturnResources abilityReturn = (CAbilityReturnResources) ability; + if (abilityReturn.accepts(this.carriedResourceType)) { + receiver.targetOk(target); + return; + } + } } + receiver.mustTargetResources(); } else { receiver.mustTargetResources(); @@ -89,4 +109,29 @@ public class CAbilityHarvest extends AbstractGenericSingleIconActiveAbility { } + public int getDamageToTree() { + return this.damageToTree; + } + + public int getGoldCapacity() { + return this.goldCapacity; + } + + public int getLumberCapacity() { + return this.lumberCapacity; + } + + public int getCarriedResourceAmount() { + return this.carriedResourceAmount; + } + + public ResourceType getCarriedResourceType() { + return this.carriedResourceType; + } + + public void setCarriedResources(final ResourceType carriedResourceType, final int carriedResourceAmount) { + this.carriedResourceType = carriedResourceType; + this.carriedResourceAmount = carriedResourceAmount; + } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityReturnResources.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityReturnResources.java new file mode 100644 index 0000000..24695ad --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityReturnResources.java @@ -0,0 +1,81 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.harvest; + +import java.util.EnumSet; + +import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic.AbstractGenericNoIconAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.ResourceType; + +/** + * Was probably named CAbilityReturn in 2002, idk + */ +public class CAbilityReturnResources extends AbstractGenericNoIconAbility { + private final EnumSet acceptedResourceTypes; + + public CAbilityReturnResources(final int handleId, final War3ID alias, + final EnumSet acceptedResourceTypes) { + super(handleId, alias); + this.acceptedResourceTypes = acceptedResourceTypes; + } + + @Override + public void onAdd(final CSimulation game, final CUnit unit) { + + } + + @Override + public void onRemove(final CSimulation game, final CUnit unit) { + + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final CWidget target) { + return null; + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, + final AbilityPointTarget point) { + return null; + } + + @Override + public CBehavior beginNoTarget(final CSimulation game, final CUnit caster, final int orderId) { + return null; + } + + @Override + public void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, final CWidget target, + final AbilityTargetCheckReceiver receiver) { + receiver.orderIdNotAccepted(); + } + + @Override + public void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, + final AbilityPointTarget target, final AbilityTargetCheckReceiver receiver) { + receiver.orderIdNotAccepted(); + } + + @Override + public void checkCanTargetNoTarget(final CSimulation game, final CUnit unit, final int orderId, + final AbilityTargetCheckReceiver receiver) { + receiver.orderIdNotAccepted(); + } + + @Override + protected void innerCheckCanUse(final CSimulation game, final CUnit unit, final int orderId, + final AbilityActivationReceiver receiver) { + receiver.notAnActiveAbility(); + } + + public boolean accepts(final ResourceType resourceType) { + return this.acceptedResourceTypes.contains(resourceType); + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/mine/CAbilityGoldMine.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/mine/CAbilityGoldMine.java index 5c0c797..d3b16fe 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/mine/CAbilityGoldMine.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/mine/CAbilityGoldMine.java @@ -12,6 +12,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetC public class CAbilityGoldMine extends AbstractGenericNoIconAbility { private int gold; + private int activeMiners; private final float miningDuration; private final int miningCapacity; @@ -81,6 +82,14 @@ public class CAbilityGoldMine extends AbstractGenericNoIconAbility { this.gold = gold; } + public int getActiveMiners() { + return this.activeMiners; + } + + public void setActiveMiners(final int activeMiners) { + this.activeMiners = activeMiners; + } + public int getMiningCapacity() { return this.miningCapacity; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/targeting/AbilityTargetStillAliveAndTargetableVisitor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/targeting/AbilityTargetStillAliveAndTargetableVisitor.java new file mode 100644 index 0000000..e54e6c5 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/targeting/AbilityTargetStillAliveAndTargetableVisitor.java @@ -0,0 +1,44 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting; + +import java.util.EnumSet; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CDestructable; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CItem; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; + +public final class AbilityTargetStillAliveAndTargetableVisitor implements AbilityTargetVisitor { + private CSimulation simulation; + private CUnit unit; + private EnumSet targetsAllowed; + + public AbilityTargetStillAliveAndTargetableVisitor reset(final CSimulation simulation, final CUnit unit, + final EnumSet targetsAllowed) { + this.simulation = simulation; + this.unit = unit; + this.targetsAllowed = targetsAllowed; + return this; + } + + @Override + public Boolean accept(final AbilityPointTarget target) { + return Boolean.TRUE; + } + + @Override + public Boolean accept(final CUnit target) { + return !target.isDead() && target.canBeTargetedBy(this.simulation, this.unit, this.targetsAllowed); + } + + @Override + public Boolean accept(final CDestructable target) { + return !target.isDead() && target.canBeTargetedBy(this.simulation, this.unit, this.targetsAllowed); + } + + @Override + public Boolean accept(final CItem target) { + return !target.isDead() && target.canBeTargetedBy(this.simulation, this.unit, this.targetsAllowed); + } + +} \ No newline at end of file diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/definitions/impl/CAbilityTypeDefinitionHarvest.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/definitions/impl/CAbilityTypeDefinitionHarvest.java new file mode 100644 index 0000000..dd7f98e --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/definitions/impl/CAbilityTypeDefinitionHarvest.java @@ -0,0 +1,36 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.definitions.impl; + +import java.util.EnumSet; +import java.util.List; + +import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; +import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.CAbilityType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.definitions.CAbilityTypeDefinition; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.impl.CAbilityTypeHarvest; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.impl.CAbilityTypeHarvestLevelData; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; + +public class CAbilityTypeDefinitionHarvest extends AbstractCAbilityTypeDefinition + implements CAbilityTypeDefinition { + protected static final War3ID DAMAGE_TO_TREE = War3ID.fromString("Har1"); + protected static final War3ID GOLD_CAPACITY = War3ID.fromString("Har2"); + protected static final War3ID LUMBER_CAPACITY = War3ID.fromString("Har3"); + + @Override + protected CAbilityTypeHarvestLevelData createLevelData(final MutableGameObject abilityEditorData, final int level) { + final String targetsAllowedAtLevelString = abilityEditorData.getFieldAsString(TARGETS_ALLOWED, level); + final EnumSet targetsAllowedAtLevel = CTargetType.parseTargetTypeSet(targetsAllowedAtLevelString); + final int damageToTree = abilityEditorData.getFieldAsInteger(DAMAGE_TO_TREE, level); + final int goldCapacity = abilityEditorData.getFieldAsInteger(GOLD_CAPACITY, level); + final int lumberCapacity = abilityEditorData.getFieldAsInteger(LUMBER_CAPACITY, level); + return new CAbilityTypeHarvestLevelData(targetsAllowedAtLevel, damageToTree, goldCapacity, lumberCapacity); + } + + @Override + protected CAbilityType innerCreateAbilityType(final War3ID alias, final MutableGameObject abilityEditorData, + final List levelData) { + return new CAbilityTypeHarvest(alias, abilityEditorData.getCode(), levelData); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/definitions/impl/CAbilityTypeDefinitionReturnResources.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/definitions/impl/CAbilityTypeDefinitionReturnResources.java new file mode 100644 index 0000000..07c1178 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/definitions/impl/CAbilityTypeDefinitionReturnResources.java @@ -0,0 +1,35 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.definitions.impl; + +import java.util.EnumSet; +import java.util.List; + +import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; +import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.CAbilityType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.definitions.CAbilityTypeDefinition; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.impl.CAbilityTypeReturnResources; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.impl.CAbilityTypeReturnResourcesLevelData; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; + +public class CAbilityTypeDefinitionReturnResources + extends AbstractCAbilityTypeDefinition implements CAbilityTypeDefinition { + protected static final War3ID ACCEPTS_GOLD = War3ID.fromString("Rtn1"); + protected static final War3ID ACCEPTS_LUMBER = War3ID.fromString("Rtn2"); + + @Override + protected CAbilityTypeReturnResourcesLevelData createLevelData(final MutableGameObject abilityEditorData, + final int level) { + final String targetsAllowedAtLevelString = abilityEditorData.getFieldAsString(TARGETS_ALLOWED, level); + final EnumSet targetsAllowedAtLevel = CTargetType.parseTargetTypeSet(targetsAllowedAtLevelString); + final boolean acceptsGold = abilityEditorData.getFieldAsBoolean(ACCEPTS_GOLD, level); + final boolean acceptsLumber = abilityEditorData.getFieldAsBoolean(ACCEPTS_LUMBER, level); + return new CAbilityTypeReturnResourcesLevelData(targetsAllowedAtLevel, acceptsGold, acceptsLumber); + } + + @Override + protected CAbilityType innerCreateAbilityType(final War3ID alias, final MutableGameObject abilityEditorData, + final List levelData) { + return new CAbilityTypeReturnResources(alias, abilityEditorData.getCode(), levelData); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeHarvest.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeHarvest.java new file mode 100644 index 0000000..29ffbac --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeHarvest.java @@ -0,0 +1,24 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.impl; + +import java.util.List; + +import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.harvest.CAbilityHarvest; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.CAbilityType; + +public class CAbilityTypeHarvest extends CAbilityType { + + public CAbilityTypeHarvest(final War3ID alias, final War3ID code, + final List levelData) { + super(alias, code, levelData); + } + + @Override + public CAbility createAbility(final int handleId) { + final CAbilityTypeHarvestLevelData levelData = getLevelData(0); + return new CAbilityHarvest(handleId, getAlias(), levelData.getDamageToTree(), levelData.getGoldCapacity(), + levelData.getLumberCapacity()); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeHarvestLevelData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeHarvestLevelData.java new file mode 100644 index 0000000..812817a --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeHarvestLevelData.java @@ -0,0 +1,33 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.impl; + +import java.util.EnumSet; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.CAbilityTypeLevelData; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; + +public class CAbilityTypeHarvestLevelData extends CAbilityTypeLevelData { + private final int damageToTree; + private final int goldCapacity; + private final int lumberCapacity; + + public CAbilityTypeHarvestLevelData(final EnumSet targetsAllowed, final int damageToTree, + final int goldCapacity, final int lumberCapacity) { + super(targetsAllowed); + this.damageToTree = damageToTree; + this.goldCapacity = goldCapacity; + this.lumberCapacity = lumberCapacity; + } + + public int getDamageToTree() { + return this.damageToTree; + } + + public int getGoldCapacity() { + return this.goldCapacity; + } + + public int getLumberCapacity() { + return this.lumberCapacity; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeReturnResources.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeReturnResources.java new file mode 100644 index 0000000..a10ed6d --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeReturnResources.java @@ -0,0 +1,32 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.impl; + +import java.util.EnumSet; +import java.util.List; + +import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.harvest.CAbilityReturnResources; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.CAbilityType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.ResourceType; + +public class CAbilityTypeReturnResources extends CAbilityType { + + public CAbilityTypeReturnResources(final War3ID alias, final War3ID code, + final List levelData) { + super(alias, code, levelData); + } + + @Override + public CAbility createAbility(final int handleId) { + final CAbilityTypeReturnResourcesLevelData levelData = getLevelData(0); + final EnumSet acceptedResourceTypes = EnumSet.noneOf(ResourceType.class); + if (levelData.isAcceptsGold()) { + acceptedResourceTypes.add(ResourceType.GOLD); + } + if (levelData.isAcceptsLumber()) { + acceptedResourceTypes.add(ResourceType.LUMBER); + } + return new CAbilityReturnResources(handleId, getAlias(), acceptedResourceTypes); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeReturnResourcesLevelData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeReturnResourcesLevelData.java new file mode 100644 index 0000000..2366f35 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeReturnResourcesLevelData.java @@ -0,0 +1,28 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.impl; + +import java.util.EnumSet; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.CAbilityTypeLevelData; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; + +public class CAbilityTypeReturnResourcesLevelData extends CAbilityTypeLevelData { + + private final boolean acceptsGold; + private final boolean acceptsLumber; + + public CAbilityTypeReturnResourcesLevelData(final EnumSet targetsAllowed, final boolean acceptsGold, + final boolean acceptsLumber) { + super(targetsAllowed); + this.acceptsGold = acceptsGold; + this.acceptsLumber = acceptsLumber; + } + + public boolean isAcceptsGold() { + return this.acceptsGold; + } + + public boolean isAcceptsLumber() { + return this.acceptsLumber; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java index da73c43..78c28d0 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java @@ -3,13 +3,10 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors; import com.etheller.warsmash.util.WarsmashConstants; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CDestructable; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CItem; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTargetVisitor; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTargetStillAliveAndTargetableVisitor; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; public class CBehaviorAttack extends CAbstractRangedBehavior { @@ -54,7 +51,8 @@ public class CBehaviorAttack extends CAbstractRangedBehavior { @Override protected boolean checkTargetStillValid(final CSimulation simulation) { - return this.target.visit(this.abilityTargetStillAliveVisitor.reset(simulation, this.unit, this.unitAttack)); + return this.target.visit( + this.abilityTargetStillAliveVisitor.reset(simulation, this.unit, this.unitAttack.getTargetsAllowed())); } @Override @@ -117,42 +115,4 @@ public class CBehaviorAttack extends CAbstractRangedBehavior { return this; } - public static class AbilityTargetStillAliveAndTargetableVisitor implements AbilityTargetVisitor { - private CSimulation simulation; - private CUnit unit; - private CUnitAttack unitAttack; - - public AbilityTargetStillAliveAndTargetableVisitor reset(final CSimulation simulation, final CUnit unit, - final CUnitAttack unitAttack) { - this.simulation = simulation; - this.unit = unit; - this.unitAttack = unitAttack; - return this; - } - - @Override - public Boolean accept(final AbilityPointTarget target) { - return Boolean.TRUE; - } - - @Override - public Boolean accept(final CUnit target) { - return !target.isDead() - && target.canBeTargetedBy(this.simulation, this.unit, this.unitAttack.getTargetsAllowed()); - } - - @Override - public Boolean accept(final CDestructable target) { - return !target.isDead() - && target.canBeTargetedBy(this.simulation, this.unit, this.unitAttack.getTargetsAllowed()); - } - - @Override - public Boolean accept(final CItem target) { - return !target.isDead() - && target.canBeTargetedBy(this.simulation, this.unit, this.unitAttack.getTargetsAllowed()); - } - - } - } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java index f8da3db..37a5726 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java @@ -71,12 +71,12 @@ public class CBehaviorOrcBuild extends CAbstractRangedBehavior { ability.visit(AbilityDisableWhileUnderConstructionVisitor.INSTANCE); } this.unit.setHidden(true); - this.unit.setUpdating(false); + this.unit.setPaused(true); this.unit.setInvulnerable(true); simulation.unitConstructedEvent(this.unit, constructedStructure); } else { - CPlayer player = simulation.getPlayer(this.unit.getPlayerIndex()); + final CPlayer player = simulation.getPlayer(this.unit.getPlayerIndex()); player.setFoodUsed(player.getFoodUsed() - unitTypeToCreate.getFoodUsed()); simulation.getCommandErrorListener().showCantPlaceError(); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/harvest/CBehaviorHarvest.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/harvest/CBehaviorHarvest.java new file mode 100644 index 0000000..7a08a0e --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/harvest/CBehaviorHarvest.java @@ -0,0 +1,157 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.harvest; + +import com.etheller.warsmash.util.WarsmashConstants; +import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.SecondaryTag; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CDestructable; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CItem; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.harvest.CAbilityHarvest; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.harvest.CAbilityReturnResources; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.mine.CAbilityGoldMine; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTargetStillAliveVisitor; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTargetVisitor; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CAbstractRangedBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayer; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.ResourceType; + +public class CBehaviorHarvest extends CAbstractRangedBehavior implements AbilityTargetVisitor { + private final CAbilityHarvest abilityHarvest; + private CSimulation simulation; + private int popoutFromMineTurnTick = 0; + private CAbilityGoldMine abilityGoldMine; + + public CBehaviorHarvest(final CUnit unit, final CAbilityHarvest abilityHarvest) { + super(unit); + this.abilityHarvest = abilityHarvest; + } + + public CBehaviorHarvest reset(final CWidget target) { + innerReset(target); + this.abilityGoldMine = null; + if (this.popoutFromMineTurnTick != 0) { + // TODO this check is probably only for debug and should be removed after + // extensive testing + throw new IllegalStateException("A unit took action while within a gold mine."); + } + return this; + } + + @Override + public boolean isWithinRange(final CSimulation simulation) { + // TODO this is probably not what the CloseEnoughRange constant is for + return this.unit.canReach(this.target, simulation.getGameplayConstants().getCloseEnoughRange()); + } + + @Override + public int getHighlightOrderId() { + return OrderIds.harvest; + } + + @Override + protected CBehavior update(final CSimulation simulation, final boolean withinRange) { + this.simulation = simulation; + return this.target.visit(this); + } + + @Override + public CBehavior accept(final AbilityPointTarget target) { + return CBehaviorHarvest.this.unit.pollNextOrderBehavior(this.simulation); + } + + @Override + public CBehavior accept(final CUnit target) { + if (this.popoutFromMineTurnTick == 0) { + if ((this.abilityHarvest.getCarriedResourceAmount() == 0) + || (this.abilityHarvest.getCarriedResourceType() != ResourceType.GOLD)) { + for (final CAbility ability : target.getAbilities()) { + if (ability instanceof CAbilityGoldMine) { + final CAbilityGoldMine abilityGoldMine = (CAbilityGoldMine) ability; + final int activeMiners = abilityGoldMine.getActiveMiners(); + if (activeMiners < abilityGoldMine.getMiningCapacity()) { + abilityGoldMine.setActiveMiners(activeMiners + 1); + this.unit.setHidden(true); + this.unit.setInvulnerable(true); + this.popoutFromMineTurnTick = this.simulation.getGameTurnTick() + + (int) (abilityGoldMine.getMiningDuration() + / WarsmashConstants.SIMULATION_STEP_TIME); + this.abilityGoldMine = abilityGoldMine; + break; + } + } + } + } + else { + for (final CAbility ability : target.getAbilities()) { + if (ability instanceof CAbilityReturnResources) { + final CAbilityReturnResources abilityReturnResources = (CAbilityReturnResources) ability; + if (abilityReturnResources.accepts(this.abilityHarvest.getCarriedResourceType())) { + final CPlayer player = this.simulation.getPlayer(this.unit.getPlayerIndex()); + switch (this.abilityHarvest.getCarriedResourceType()) { + case FOOD: + throw new IllegalStateException("Unit used Harvest skill to carry FOOD resource!"); + case GOLD: + player.setGold(player.getGold() + this.abilityHarvest.getCarriedResourceAmount()); + this.unit.getUnitAnimationListener().removeSecondaryTag(SecondaryTag.GOLD); + break; + case LUMBER: + player.setLumber(player.getLumber() + this.abilityHarvest.getCarriedResourceAmount()); + this.unit.getUnitAnimationListener().removeSecondaryTag(SecondaryTag.LUMBER); + break; + } + this.abilityHarvest.setCarriedResources(null, 0); + return this.unit.pollNextOrderBehavior(this.simulation); + } + } + } + } + } + else { + if (this.simulation.getGameTurnTick() >= this.popoutFromMineTurnTick) { + this.popoutFromMineTurnTick = 0; + this.unit.setHidden(false); + this.unit.setInvulnerable(false); + this.abilityGoldMine.setActiveMiners(this.abilityGoldMine.getActiveMiners() - 1); + int mineGoldRemaining = this.abilityGoldMine.getGold(); + final int goldMined = Math.min(mineGoldRemaining, this.abilityHarvest.getGoldCapacity()); + this.abilityHarvest.setCarriedResources(ResourceType.GOLD, goldMined); + mineGoldRemaining -= goldMined; + this.abilityGoldMine.setGold(mineGoldRemaining); + if (mineGoldRemaining <= 0) { + target.setLife(this.simulation, 0); + } + this.unit.getUnitAnimationListener().addSecondaryTag(SecondaryTag.GOLD); + this.simulation.unitRepositioned(this.unit); + return this.unit.pollNextOrderBehavior(this.simulation); + } + } + return this; + } + + @Override + public CBehavior accept(final CDestructable target) { + // TODO cut trees! + return this.unit.pollNextOrderBehavior(this.simulation); + } + + @Override + public CBehavior accept(final CItem target) { + return this.unit.pollNextOrderBehavior(this.simulation); + } + + @Override + protected boolean checkTargetStillValid(final CSimulation simulation) { + return this.target.visit(AbilityTargetStillAliveVisitor.INSTANCE); + } + + @Override + protected void resetBeforeMoving(final CSimulation simulation) { + + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CAbilityData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CAbilityData.java index 578b1b5..c496c93 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CAbilityData.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CAbilityData.java @@ -12,6 +12,8 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.CAb import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.definitions.CAbilityTypeDefinition; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.definitions.impl.CAbilityTypeDefinitionColdArrows; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.definitions.impl.CAbilityTypeDefinitionGoldMine; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.definitions.impl.CAbilityTypeDefinitionHarvest; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.definitions.impl.CAbilityTypeDefinitionReturnResources; public class CAbilityData { @@ -22,11 +24,14 @@ public class CAbilityData { public CAbilityData(final MutableObjectData abilityData) { this.abilityData = abilityData; this.aliasToAbilityType = new HashMap<>(); + registerCodes(); } private void registerCodes() { this.codeToAbilityTypeDefinition.put(War3ID.fromString("ACcw"), new CAbilityTypeDefinitionColdArrows()); this.codeToAbilityTypeDefinition.put(War3ID.fromString("Agld"), new CAbilityTypeDefinitionGoldMine()); + this.codeToAbilityTypeDefinition.put(War3ID.fromString("Artn"), new CAbilityTypeDefinitionReturnResources()); + this.codeToAbilityTypeDefinition.put(War3ID.fromString("Ahar"), new CAbilityTypeDefinitionHarvest()); } public CAbilityType getAbilityType(final War3ID alias) { @@ -35,7 +40,10 @@ public class CAbilityData { final MutableGameObject mutableGameObject = this.abilityData.get(alias); final War3ID code = mutableGameObject.getCode(); final CAbilityTypeDefinition abilityTypeDefinition = this.codeToAbilityTypeDefinition.get(code); - abilityType = abilityTypeDefinition.createAbilityType(alias, mutableGameObject); + if (abilityTypeDefinition != null) { + abilityType = abilityTypeDefinition.createAbilityType(alias, mutableGameObject); + this.aliasToAbilityType.put(alias, abilityType); + } } return abilityType; } From 9a2dc090ca0926dc53f53db6d76e8b0f71a2fffa Mon Sep 17 00:00:00 2001 From: Retera Date: Sun, 17 Jan 2021 21:40:22 -0500 Subject: [PATCH 084/116] Updates to harvesting, reduce max depth of the quad trees --- core/assets/warsmash.ini | 5 +- .../com/etheller/warsmash/util/Quadtree.java | 12 +- core/src/com/etheller/warsmash/util/Test.java | 2 + .../warsmash/util/WarsmashConstants.java | 7 + .../viewer5/handlers/mdx/Particle.java | 4 +- .../handlers/mdx/ParticleEmitter2.java | 4 +- .../handlers/mdx/ParticleEmitter2Object.java | 4 +- .../handlers/w3x/environment/PathingGrid.java | 8 +- .../handlers/w3x/simulation/CUnit.java | 8 +- .../w3x/simulation/CWorldCollision.java | 20 +- ...bstractGenericSingleIconActiveAbility.java | 13 ++ ...GenericSingleIconNoSmartActiveAbility.java | 28 +++ .../abilities/harvest/CAbilityHarvest.java | 23 +++ .../behaviors/CAbstractRangedBehavior.java | 6 +- .../simulation/behaviors/CBehaviorAttack.java | 2 +- .../simulation/behaviors/CBehaviorFollow.java | 2 +- .../simulation/behaviors/CBehaviorMove.java | 25 ++- .../simulation/behaviors/CBehaviorPatrol.java | 2 +- .../behaviors/build/CBehaviorOrcBuild.java | 2 +- .../behaviors/harvest/CBehaviorHarvest.java | 67 +++---- .../harvest/CBehaviorReturnResources.java | 172 ++++++++++++++++++ .../attacks/CUnitAttackMissileSplash.java | 5 + .../viewer5/handlers/w3x/ui/MeleeUI.java | 12 +- 23 files changed, 369 insertions(+), 64 deletions(-) create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/AbstractGenericSingleIconNoSmartActiveAbility.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/harvest/CBehaviorReturnResources.java diff --git a/core/assets/warsmash.ini b/core/assets/warsmash.ini index d6740ea..5405aca 100644 --- a/core/assets/warsmash.ini +++ b/core/assets/warsmash.ini @@ -18,7 +18,7 @@ Path06="." [Map] //FilePath="CombatUnitTests.w3x" //FilePath="PitchRoll.w3x" -FilePath="PeonStartingBase.w3x" +//FilePath="PeonStartingBase.w3x" //FilePath="ColdArrows.w3m" //FilePath="DungeonGoldMine.w3m" //FilePath="PlayerPeasants.w3m" @@ -35,3 +35,6 @@ FilePath="PeonStartingBase.w3x" //FilePath="Pathing.w3x" //FilePath="ItemFacing.w3x" //FilePath=SomeParticleTests.w3x +FilePath="PeonMiningMultiHall.w3x" +//FilePath="QuadtreeBugs.w3x" +//FilePath="test2.w3x" diff --git a/core/src/com/etheller/warsmash/util/Quadtree.java b/core/src/com/etheller/warsmash/util/Quadtree.java index 9489fd8..cfaed78 100644 --- a/core/src/com/etheller/warsmash/util/Quadtree.java +++ b/core/src/com/etheller/warsmash/util/Quadtree.java @@ -6,7 +6,7 @@ import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.utils.Array; public class Quadtree { - private static final int MAX_DEPTH = 64; + private static final int MAX_DEPTH = 9; private static final int SPLIT_THRESHOLD = 6; private final Rectangle bounds; @@ -75,7 +75,7 @@ public class Quadtree { } } - public boolean intersect(float x, float y, final QuadtreeIntersector intersector) { + public boolean intersect(final float x, final float y, final QuadtreeIntersector intersector) { if (this.leaf) { for (int i = 0; i < this.nodes.size; i++) { final Node node = this.nodes.get(i); @@ -123,17 +123,25 @@ public class Quadtree { return; } } + boolean overlapsAny = false; if (this.northeast.bounds.overlaps(node.bounds)) { this.northeast.add(node, depth + 1); + overlapsAny = true; } if (this.northwest.bounds.overlaps(node.bounds)) { this.northwest.add(node, depth + 1); + overlapsAny = true; } if (this.southwest.bounds.overlaps(node.bounds)) { this.southwest.add(node, depth + 1); + overlapsAny = true; } if (this.southeast.bounds.overlaps(node.bounds)) { this.southeast.add(node, depth + 1); + overlapsAny = true; + } + if (!overlapsAny) { + throw new IllegalStateException("Does not overlap anything!"); } } diff --git a/core/src/com/etheller/warsmash/util/Test.java b/core/src/com/etheller/warsmash/util/Test.java index 8c0a983..9d88d96 100644 --- a/core/src/com/etheller/warsmash/util/Test.java +++ b/core/src/com/etheller/warsmash/util/Test.java @@ -15,6 +15,8 @@ public class Test { else { System.out.println("no match"); } + +// Quadtree myQT = new Quadtree<>(new Rectangle(-, y, width, height)) } } diff --git a/core/src/com/etheller/warsmash/util/WarsmashConstants.java b/core/src/com/etheller/warsmash/util/WarsmashConstants.java index c7cd7a4..d1876cc 100644 --- a/core/src/com/etheller/warsmash/util/WarsmashConstants.java +++ b/core/src/com/etheller/warsmash/util/WarsmashConstants.java @@ -7,4 +7,11 @@ public class WarsmashConstants { public static final int PORT_NUMBER = 6115; public static final float BUILDING_CONSTRUCT_START_LIFE = 0.1f; public static final int BUILD_QUEUE_SIZE = 7; + // It looks like in Patch 1.22, "Particle" in video settings will change this + // factor: + // Low - unknown ? + // Medium - 1.0f + // High - 1.5f + public static final float MODEL_DETAIL_PARTICLE_FACTOR = 1.5f; + public static final float MODEL_DETAIL_PARTICLE_FACTOR_INVERSE = 1f / MODEL_DETAIL_PARTICLE_FACTOR; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Particle.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Particle.java index ff01c77..f146e7c 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Particle.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Particle.java @@ -57,9 +57,9 @@ public class Particle extends EmittedObject // Local rotation rotationHeap.idt(); - rotationHeap.mulLeft(rotationHeap2.setFromAxisRad(0, 0, 1, + rotationHeap.mul(rotationHeap2.setFromAxisRad(0, 0, 1, RenderMathUtils.randomInRange((float) -Math.PI, (float) Math.PI))); - rotationHeap.mulLeft(rotationHeap2.setFromAxisRad(0, 1, 0, + rotationHeap.mul(rotationHeap2.setFromAxisRad(0, 1, 0, RenderMathUtils.randomInRange(-latitudeHeap[0], latitudeHeap[0]))); velocity.set(RenderMathUtils.VEC3_UNIT_Z); rotationHeap.transform(velocity); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ParticleEmitter2.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ParticleEmitter2.java index 0394053..77b2a30 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ParticleEmitter2.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ParticleEmitter2.java @@ -1,5 +1,7 @@ package com.etheller.warsmash.viewer5.handlers.mdx; +import com.etheller.warsmash.util.WarsmashConstants; + public class ParticleEmitter2 extends MdxEmitter { private static final float[] emissionRateHeap = new float[1]; @@ -30,7 +32,7 @@ public class ParticleEmitter2 extends MdxEmitter movetpToMovementType = new HashMap<>(); static { for (final MovementType movementType : MovementType.values()) { - if (movementType != MovementType.DISABLED) { + if (!movementType.typeKey.isEmpty()) { movetpToMovementType.put(movementType.typeKey, movementType); } } @@ -360,6 +360,12 @@ public class PathingGrid { return !PathingFlags.isPathingFlag(pathingValue, PathingFlags.UNWALKABLE); } }, + FOOT_NO_COLLISION("") { + @Override + public boolean isPathable(final short pathingValue) { + return !PathingFlags.isPathingFlag(pathingValue, PathingFlags.UNWALKABLE); + } + }, HORSE("horse") { @Override public boolean isPathable(final short pathingValue) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java index 4827968..3d732b8 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java @@ -571,6 +571,12 @@ public class CUnit extends CWidget { return groundDistance; } + public double distanceSquaredNoCollision(final AbilityTarget target) { + final double dx = Math.abs(target.getX() - getX()); + final double dy = Math.abs(target.getY() - getY()); + return (dx * dx) + (dy * dy); + } + public double distance(final float x, final float y) { double dx = Math.abs(x - getX()); double dy = Math.abs(y - getY()); @@ -687,7 +693,7 @@ public class CUnit extends CWidget { : buildingPathingPixelMap.getHeight(); final int relativeGridX = (int) Math.floor(relativeOffsetX / 32f) + (gridWidth / 2); final int relativeGridY = (int) Math.floor(relativeOffsetY / 32f) + (gridHeight / 2); - final int rangeInCells = (int) Math.floor(range / 32f); + final int rangeInCells = (int) Math.floor(range / 32f) + 1; final int rangeInCellsSquare = rangeInCells * rangeInCells; int minCheckX = relativeGridX - rangeInCells; int minCheckY = relativeGridY - rangeInCells; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWorldCollision.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWorldCollision.java index b488988..fdcffd5 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWorldCollision.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWorldCollision.java @@ -9,6 +9,7 @@ import com.etheller.warsmash.util.QuadtreeIntersector; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.MovementType; public class CWorldCollision { + private static final float MINIMUM_COLLISION_SIZE = 0.001f /* THIS IS TO STOP QUADTREE FROM BUSTING */; private final Quadtree groundUnitCollision; private final Quadtree airUnitCollision; private final Quadtree seaUnitCollision; @@ -30,7 +31,8 @@ public class CWorldCollision { public void addUnit(final CUnit unit) { Rectangle bounds = unit.getCollisionRectangle(); if (bounds == null) { - final float collisionSize = Math.min(this.maxCollisionRadius, unit.getUnitType().getCollisionSize()); + final float collisionSize = Math.max(MINIMUM_COLLISION_SIZE, + Math.min(this.maxCollisionRadius, unit.getUnitType().getCollisionSize())); bounds = new Rectangle(unit.getX() - collisionSize, unit.getY() - collisionSize, collisionSize * 2, collisionSize * 2); unit.setCollisionRectangle(bounds); @@ -54,9 +56,11 @@ public class CWorldCollision { case FLY: this.airUnitCollision.add(unit, bounds); break; - default: case DISABLED: + break; + default: case FOOT: + case FOOT_NO_COLLISION: case HORSE: case HOVER: this.groundUnitCollision.add(unit, bounds); @@ -86,9 +90,11 @@ public class CWorldCollision { case FLY: this.airUnitCollision.remove(unit, bounds); break; - default: case DISABLED: + break; + default: case FOOT: + case FOOT_NO_COLLISION: case HORSE: case HOVER: this.groundUnitCollision.remove(unit, bounds); @@ -133,8 +139,10 @@ public class CWorldCollision { case FLY: return this.airUnitCollision.intersect(newPossibleRectangle, this.anyUnitExceptTwoIntersector.reset(sourceUnitToIgnore, sourceSecondUnitToIgnore)); - default: case DISABLED: + case FOOT_NO_COLLISION: + return false; + default: case FOOT: case HORSE: case HOVER: @@ -167,9 +175,11 @@ public class CWorldCollision { case FLY: this.airUnitCollision.translate(unit, bounds, xShift, yShift); break; - default: case DISABLED: + break; + default: case FOOT: + case FOOT_NO_COLLISION: case HORSE: case HOVER: this.groundUnitCollision.translate(unit, bounds, xShift, yShift); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/AbstractGenericSingleIconActiveAbility.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/AbstractGenericSingleIconActiveAbility.java index 579970e..155f253 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/AbstractGenericSingleIconActiveAbility.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/AbstractGenericSingleIconActiveAbility.java @@ -7,6 +7,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.AbstractCAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; public abstract class AbstractGenericSingleIconActiveAbility extends AbstractCAbility @@ -29,6 +30,9 @@ public abstract class AbstractGenericSingleIconActiveAbility extends AbstractCAb if (orderId == getBaseOrderId()) { innerCheckCanTarget(game, unit, orderId, target, receiver); } + else if (orderId == OrderIds.smart) { + innerCheckCanSmartTarget(game, unit, orderId, target, receiver); + } else { receiver.orderIdNotAccepted(); } @@ -37,12 +41,18 @@ public abstract class AbstractGenericSingleIconActiveAbility extends AbstractCAb protected abstract void innerCheckCanTarget(CSimulation game, CUnit unit, int orderId, CWidget target, AbilityTargetCheckReceiver receiver); + protected abstract void innerCheckCanSmartTarget(CSimulation game, CUnit unit, int orderId, CWidget target, + AbilityTargetCheckReceiver receiver); + @Override public void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, final AbilityPointTarget target, final AbilityTargetCheckReceiver receiver) { if (orderId == getBaseOrderId()) { innerCheckCanTarget(game, unit, orderId, target, receiver); } + else if (orderId == OrderIds.smart) { + innerCheckCanSmartTarget(game, unit, orderId, target, receiver); + } else { receiver.orderIdNotAccepted(); } @@ -51,6 +61,9 @@ public abstract class AbstractGenericSingleIconActiveAbility extends AbstractCAb protected abstract void innerCheckCanTarget(CSimulation game, CUnit unit, int orderId, AbilityPointTarget target, AbilityTargetCheckReceiver receiver); + protected abstract void innerCheckCanSmartTarget(CSimulation game, CUnit unit, int orderId, + AbilityPointTarget target, AbilityTargetCheckReceiver receiver); + @Override public void checkCanTargetNoTarget(final CSimulation game, final CUnit unit, final int orderId, final AbilityTargetCheckReceiver receiver) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/AbstractGenericSingleIconNoSmartActiveAbility.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/AbstractGenericSingleIconNoSmartActiveAbility.java new file mode 100644 index 0000000..aa26147 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/AbstractGenericSingleIconNoSmartActiveAbility.java @@ -0,0 +1,28 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic; + +import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; + +public abstract class AbstractGenericSingleIconNoSmartActiveAbility extends AbstractGenericSingleIconActiveAbility { + + public AbstractGenericSingleIconNoSmartActiveAbility(final int handleId, final War3ID alias) { + super(handleId, alias); + } + + @Override + protected void innerCheckCanSmartTarget(final CSimulation game, final CUnit unit, final int orderId, + final AbilityPointTarget target, final AbilityTargetCheckReceiver receiver) { + receiver.orderIdNotAccepted(); + } + + @Override + protected void innerCheckCanSmartTarget(final CSimulation game, final CUnit unit, final int orderId, + final CWidget target, final AbilityTargetCheckReceiver receiver) { + receiver.orderIdNotAccepted(); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityHarvest.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityHarvest.java index 64c0f26..849c83d 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityHarvest.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityHarvest.java @@ -10,6 +10,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.mine.CAbi import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.harvest.CBehaviorHarvest; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.harvest.CBehaviorReturnResources; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; @@ -20,6 +21,7 @@ public class CAbilityHarvest extends AbstractGenericSingleIconActiveAbility { private final int goldCapacity; private final int lumberCapacity; private CBehaviorHarvest behaviorHarvest; + private CBehaviorReturnResources behaviorReturnResources; private int carriedResourceAmount; private ResourceType carriedResourceType; @@ -34,6 +36,7 @@ public class CAbilityHarvest extends AbstractGenericSingleIconActiveAbility { @Override public void onAdd(final CSimulation game, final CUnit unit) { this.behaviorHarvest = new CBehaviorHarvest(unit, this); + this.behaviorReturnResources = new CBehaviorReturnResources(unit, this); } @Override @@ -97,12 +100,24 @@ public class CAbilityHarvest extends AbstractGenericSingleIconActiveAbility { } } + @Override + protected void innerCheckCanSmartTarget(final CSimulation game, final CUnit unit, final int orderId, + final CWidget target, final AbilityTargetCheckReceiver receiver) { + innerCheckCanTarget(game, unit, orderId, target, receiver); + } + @Override protected void innerCheckCanTarget(final CSimulation game, final CUnit unit, final int orderId, final AbilityPointTarget target, final AbilityTargetCheckReceiver receiver) { receiver.orderIdNotAccepted(); } + @Override + protected void innerCheckCanSmartTarget(final CSimulation game, final CUnit unit, final int orderId, + final AbilityPointTarget target, final AbilityTargetCheckReceiver receiver) { + receiver.orderIdNotAccepted(); + } + @Override protected void innerCheckCanTargetNoTarget(final CSimulation game, final CUnit unit, final int orderId, final AbilityTargetCheckReceiver receiver) { @@ -134,4 +149,12 @@ public class CAbilityHarvest extends AbstractGenericSingleIconActiveAbility { this.carriedResourceAmount = carriedResourceAmount; } + public CBehaviorHarvest getBehaviorHarvest() { + return this.behaviorHarvest; + } + + public CBehaviorReturnResources getBehaviorReturnResources() { + return this.behaviorReturnResources; + } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CAbstractRangedBehavior.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CAbstractRangedBehavior.java index 83b7141..8ebc15c 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CAbstractRangedBehavior.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CAbstractRangedBehavior.java @@ -6,9 +6,11 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting public abstract class CAbstractRangedBehavior implements CRangedBehavior { protected final CUnit unit; + private final boolean disableCollision; - public CAbstractRangedBehavior(final CUnit unit) { + public CAbstractRangedBehavior(final CUnit unit, final boolean disableCollision) { this.unit = unit; + this.disableCollision = disableCollision; } protected AbilityTarget target; @@ -22,7 +24,7 @@ public abstract class CAbstractRangedBehavior implements CRangedBehavior { this.wasInRange = false; CBehaviorMove moveBehavior; if (!this.unit.isMovementDisabled()) { - moveBehavior = this.unit.getMoveBehavior().reset(this.target, this); + moveBehavior = this.unit.getMoveBehavior().reset(this.target, this, this.disableCollision); } else { moveBehavior = null; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java index 78c28d0..650a2ae 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java @@ -15,7 +15,7 @@ public class CBehaviorAttack extends CAbstractRangedBehavior { private final AbilityTargetStillAliveAndTargetableVisitor abilityTargetStillAliveVisitor; public CBehaviorAttack(final CUnit unit) { - super(unit); + super(unit, false); this.abilityTargetStillAliveVisitor = new AbilityTargetStillAliveAndTargetableVisitor(); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorFollow.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorFollow.java index 612f4a0..36733b5 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorFollow.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorFollow.java @@ -11,7 +11,7 @@ public class CBehaviorFollow extends CAbstractRangedBehavior { private int higlightOrderId; public CBehaviorFollow(final CUnit unit) { - super(unit); + super(unit, false); } public CBehavior reset(final int higlightOrderId, final CUnit target) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorMove.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorMove.java index 6e0c415..3b6f827 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorMove.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorMove.java @@ -40,17 +40,21 @@ public class CBehaviorMove implements CBehavior { private CUnit followUnit; private CRangedBehavior rangedBehavior; private boolean firstUpdate = true; + private boolean disableCollision = false; public CBehaviorMove reset(final int highlightOrderId, final AbilityTarget target) { target.visit(this.targetVisitingResetter.reset(highlightOrderId)); this.rangedBehavior = null; + this.disableCollision = false; return this; } - public CBehaviorMove reset(final AbilityTarget target, final CRangedBehavior rangedBehavior) { + public CBehaviorMove reset(final AbilityTarget target, final CRangedBehavior rangedBehavior, + final boolean disableCollision) { final int highlightOrderId = rangedBehavior.getHighlightOrderId(); target.visit(this.targetVisitingResetter.reset(highlightOrderId)); this.rangedBehavior = rangedBehavior; + this.disableCollision = disableCollision; return this; } @@ -99,7 +103,13 @@ public class CBehaviorMove implements CBehavior { final float prevX = this.unit.getX(); final float prevY = this.unit.getY(); - final MovementType movementType = this.unit.getUnitType().getMovementType(); + MovementType movementType = this.unit.getUnitType().getMovementType(); + if (movementType == null) { + movementType = MovementType.DISABLED; + } + else if ((movementType == MovementType.FOOT) && this.disableCollision) { + movementType = MovementType.FOOT_NO_COLLISION; + } final PathingGrid pathingGrid = simulation.getPathingGrid(); final CWorldCollision worldCollision = simulation.getWorldCollision(); final float collisionSize = this.unit.getUnitType().getCollisionSize(); @@ -111,7 +121,7 @@ public class CBehaviorMove implements CBehavior { this.target.y = this.followUnit.getY(); } this.path = simulation.findNaiveSlowPath(this.unit, this.followUnit, startFloatingX, startFloatingY, - this.target, movementType == null ? MovementType.FOOT : movementType, collisionSize, true); + this.target, movementType, collisionSize, true); System.out.println("init path " + this.path); // check for smoothing if (!this.path.isEmpty()) { @@ -130,8 +140,7 @@ public class CBehaviorMove implements CBehavior { if ((totalPathDistance < (1.15 * nextPossiblePathElement.distance(smoothingGroupStartX, smoothingGroupStartY))) && pathingGrid.isPathable((smoothingGroupStartX + nextPossiblePathElement.x) / 2, - (smoothingGroupStartY + nextPossiblePathElement.y) / 2, - movementType == null ? MovementType.DISABLED : movementType)) { + (smoothingGroupStartY + nextPossiblePathElement.y) / 2, movementType)) { if (smoothingStartIndex == -1) { smoothingStartIndex = i; } @@ -164,8 +173,7 @@ public class CBehaviorMove implements CBehavior { this.target.x = this.followUnit.getX(); this.target.y = this.followUnit.getY(); this.path = simulation.findNaiveSlowPath(this.unit, this.followUnit, startFloatingX, startFloatingY, - this.target, movementType == null ? MovementType.FOOT : movementType, collisionSize, - this.searchCycles < 4); + this.target, movementType, collisionSize, this.searchCycles < 4); System.out.println("new path (for target) " + this.path); if (this.path.isEmpty()) { return this.unit.pollNextOrderBehavior(simulation); @@ -334,8 +342,7 @@ public class CBehaviorMove implements CBehavior { this.target.y = this.followUnit.getY(); } this.path = simulation.findNaiveSlowPath(this.unit, this.followUnit, startFloatingX, startFloatingY, - this.target, movementType == null ? MovementType.FOOT : movementType, collisionSize, - this.searchCycles < 4); + this.target, movementType, collisionSize, this.searchCycles < 4); this.searchCycles++; System.out.println("new path " + this.path); if (this.path.isEmpty() || (this.searchCycles > 5)) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorPatrol.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorPatrol.java index 95b5d53..09f0c42 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorPatrol.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorPatrol.java @@ -37,7 +37,7 @@ public class CBehaviorPatrol implements CRangedBehavior { final AbilityPointTarget temp = this.target; this.target = this.startPoint; this.startPoint = temp; - return this.unit.getMoveBehavior().reset(this.target, this); + return this.unit.getMoveBehavior().reset(this.target, this, false); } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java index 37a5726..ae47a1e 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java @@ -21,7 +21,7 @@ public class CBehaviorOrcBuild extends CAbstractRangedBehavior { private War3ID orderId; public CBehaviorOrcBuild(final CUnit unit) { - super(unit); + super(unit, false); } public CBehavior reset(final AbilityPointTarget target, final int orderId, final int highlightOrderId) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/harvest/CBehaviorHarvest.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/harvest/CBehaviorHarvest.java index 7a08a0e..ba9b284 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/harvest/CBehaviorHarvest.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/harvest/CBehaviorHarvest.java @@ -1,7 +1,9 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.harvest; import com.etheller.warsmash.util.WarsmashConstants; +import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.SecondaryTag; +import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CDestructable; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CItem; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; @@ -9,7 +11,6 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.harvest.CAbilityHarvest; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.harvest.CAbilityReturnResources; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.mine.CAbilityGoldMine; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTargetStillAliveVisitor; @@ -17,7 +18,6 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CAbstractRangedBehavior; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayer; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.ResourceType; public class CBehaviorHarvest extends CAbstractRangedBehavior implements AbilityTargetVisitor { @@ -27,7 +27,7 @@ public class CBehaviorHarvest extends CAbstractRangedBehavior implements Ability private CAbilityGoldMine abilityGoldMine; public CBehaviorHarvest(final CUnit unit, final CAbilityHarvest abilityHarvest) { - super(unit); + super(unit, true); this.abilityHarvest = abilityHarvest; } @@ -44,8 +44,7 @@ public class CBehaviorHarvest extends CAbstractRangedBehavior implements Ability @Override public boolean isWithinRange(final CSimulation simulation) { - // TODO this is probably not what the CloseEnoughRange constant is for - return this.unit.canReach(this.target, simulation.getGameplayConstants().getCloseEnoughRange()); + return this.unit.canReach(this.target, this.unit.getUnitType().getCollisionSize()); } @Override @@ -75,40 +74,31 @@ public class CBehaviorHarvest extends CAbstractRangedBehavior implements Ability final int activeMiners = abilityGoldMine.getActiveMiners(); if (activeMiners < abilityGoldMine.getMiningCapacity()) { abilityGoldMine.setActiveMiners(activeMiners + 1); + if (activeMiners == 0) { + target.getUnitAnimationListener().addSecondaryTag(SecondaryTag.WORK); + } this.unit.setHidden(true); this.unit.setInvulnerable(true); this.popoutFromMineTurnTick = this.simulation.getGameTurnTick() + (int) (abilityGoldMine.getMiningDuration() / WarsmashConstants.SIMULATION_STEP_TIME); this.abilityGoldMine = abilityGoldMine; - break; } + else { + // we are stuck waiting to mine, let's make sure we play stand animation + this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.STAND, + SequenceUtils.EMPTY, 1.0f, true); + } + return this; } } + // weird invalid target and we have no resources, consider harvesting done + return this.unit.pollNextOrderBehavior(this.simulation); } else { - for (final CAbility ability : target.getAbilities()) { - if (ability instanceof CAbilityReturnResources) { - final CAbilityReturnResources abilityReturnResources = (CAbilityReturnResources) ability; - if (abilityReturnResources.accepts(this.abilityHarvest.getCarriedResourceType())) { - final CPlayer player = this.simulation.getPlayer(this.unit.getPlayerIndex()); - switch (this.abilityHarvest.getCarriedResourceType()) { - case FOOD: - throw new IllegalStateException("Unit used Harvest skill to carry FOOD resource!"); - case GOLD: - player.setGold(player.getGold() + this.abilityHarvest.getCarriedResourceAmount()); - this.unit.getUnitAnimationListener().removeSecondaryTag(SecondaryTag.GOLD); - break; - case LUMBER: - player.setLumber(player.getLumber() + this.abilityHarvest.getCarriedResourceAmount()); - this.unit.getUnitAnimationListener().removeSecondaryTag(SecondaryTag.LUMBER); - break; - } - this.abilityHarvest.setCarriedResources(null, 0); - return this.unit.pollNextOrderBehavior(this.simulation); - } - } - } + // we have some GOLD and we're not in a mine (?) lets do a return resources + // order + return this.abilityHarvest.getBehaviorReturnResources().reset(this.simulation); } } else { @@ -116,7 +106,11 @@ public class CBehaviorHarvest extends CAbstractRangedBehavior implements Ability this.popoutFromMineTurnTick = 0; this.unit.setHidden(false); this.unit.setInvulnerable(false); - this.abilityGoldMine.setActiveMiners(this.abilityGoldMine.getActiveMiners() - 1); + final int activeMiners = this.abilityGoldMine.getActiveMiners() - 1; + this.abilityGoldMine.setActiveMiners(activeMiners); + if (activeMiners == 0) { + target.getUnitAnimationListener().removeSecondaryTag(SecondaryTag.WORK); + } int mineGoldRemaining = this.abilityGoldMine.getGold(); final int goldMined = Math.min(mineGoldRemaining, this.abilityHarvest.getGoldCapacity()); this.abilityHarvest.setCarriedResources(ResourceType.GOLD, goldMined); @@ -127,16 +121,25 @@ public class CBehaviorHarvest extends CAbstractRangedBehavior implements Ability } this.unit.getUnitAnimationListener().addSecondaryTag(SecondaryTag.GOLD); this.simulation.unitRepositioned(this.unit); - return this.unit.pollNextOrderBehavior(this.simulation); + // we just finished getting gold, lets do a return resources order + return this.abilityHarvest.getBehaviorReturnResources().reset(this.simulation); + } + else { + // continue working inside mine + return this; } } - return this; } @Override public CBehavior accept(final CDestructable target) { // TODO cut trees! - return this.unit.pollNextOrderBehavior(this.simulation); + if (String.valueOf(target).length() > 5) { + return this.unit.pollNextOrderBehavior(this.simulation); + } + else { + return null; + } } @Override diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/harvest/CBehaviorReturnResources.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/harvest/CBehaviorReturnResources.java new file mode 100644 index 0000000..e0e6fa3 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/harvest/CBehaviorReturnResources.java @@ -0,0 +1,172 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.harvest; + +import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.SecondaryTag; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CDestructable; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CItem; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.harvest.CAbilityHarvest; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.harvest.CAbilityReturnResources; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.mine.CAbilityGoldMine; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTargetStillAliveVisitor; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTargetVisitor; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CAbstractRangedBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayer; + +public class CBehaviorReturnResources extends CAbstractRangedBehavior implements AbilityTargetVisitor { + private final CAbilityHarvest abilityHarvest; + private CSimulation simulation; + + public CBehaviorReturnResources(final CUnit unit, final CAbilityHarvest abilityHarvest) { + super(unit, true); + this.abilityHarvest = abilityHarvest; + } + + public CBehaviorReturnResources reset(final CSimulation simulation) { + innerReset(findNearestDropoffPoint(simulation)); + return this; + } + + @Override + public boolean isWithinRange(final CSimulation simulation) { + // TODO this is probably not what the CloseEnoughRange constant is for + return this.unit.canReach(this.target, this.unit.getUnitType().getCollisionSize()); + } + + @Override + public int getHighlightOrderId() { + return OrderIds.returnresources; + } + + @Override + protected CBehavior update(final CSimulation simulation, final boolean withinRange) { + this.simulation = simulation; + return this.target.visit(this); + } + + @Override + public CBehavior accept(final AbilityPointTarget target) { + return CBehaviorReturnResources.this.unit.pollNextOrderBehavior(this.simulation); + } + + @Override + public CBehavior accept(final CUnit target) { + for (final CAbility ability : target.getAbilities()) { + if (ability instanceof CAbilityReturnResources) { + final CAbilityReturnResources abilityReturnResources = (CAbilityReturnResources) ability; + if (abilityReturnResources.accepts(this.abilityHarvest.getCarriedResourceType())) { + final CPlayer player = this.simulation.getPlayer(this.unit.getPlayerIndex()); + switch (this.abilityHarvest.getCarriedResourceType()) { + case FOOD: + throw new IllegalStateException("Unit used Harvest skill to carry FOOD resource!"); + case GOLD: + player.setGold(player.getGold() + this.abilityHarvest.getCarriedResourceAmount()); + this.unit.getUnitAnimationListener().removeSecondaryTag(SecondaryTag.GOLD); + break; + case LUMBER: + player.setLumber(player.getLumber() + this.abilityHarvest.getCarriedResourceAmount()); + this.unit.getUnitAnimationListener().removeSecondaryTag(SecondaryTag.LUMBER); + break; + } + this.abilityHarvest.setCarriedResources(null, 0); + final CUnit nearestMine = findNearestMine(this.unit, this.simulation); + if (nearestMine != null) { + return this.abilityHarvest.getBehaviorHarvest().reset(nearestMine); + } + return this.unit.pollNextOrderBehavior(this.simulation); + } + } + } + return this; + } + + @Override + public CBehavior accept(final CDestructable target) { + // TODO cut trees! + return this.unit.pollNextOrderBehavior(this.simulation); + } + + @Override + public CBehavior accept(final CItem target) { + return this.unit.pollNextOrderBehavior(this.simulation); + } + + @Override + protected boolean checkTargetStillValid(final CSimulation simulation) { + final boolean aliveCheck = this.target.visit(AbilityTargetStillAliveVisitor.INSTANCE); + if (!aliveCheck) { + final CUnit nearestDropoff = findNearestDropoffPoint(simulation); + if (nearestDropoff == null) { + return false; + } + else { + this.target = nearestDropoff; + return true; + } + } + return true; + } + + @Override + protected void resetBeforeMoving(final CSimulation simulation) { + + } + + private CUnit findNearestDropoffPoint(final CSimulation simulation) { + CUnit nearestDropoffPoint = null; + double nearestDropoffDistance = Float.MAX_VALUE; + for (final CUnit unit : simulation.getUnits()) { + if (unit.getPlayerIndex() == this.unit.getPlayerIndex()) { + boolean acceptedUnit = false; + for (final CAbility ability : unit.getAbilities()) { + if (ability instanceof CAbilityReturnResources) { + final CAbilityReturnResources abilityReturnResources = (CAbilityReturnResources) ability; + if (abilityReturnResources.accepts(this.abilityHarvest.getCarriedResourceType())) { + acceptedUnit = true; + break; + } + } + } + if (acceptedUnit) { + // TODO maybe use distance squared, problem is that we're using this + // inefficient more complex distance function on unit + final double distance = unit.distanceSquaredNoCollision(this.unit); + if (distance < nearestDropoffDistance) { + nearestDropoffDistance = distance; + nearestDropoffPoint = unit; + } + } + } + } + return nearestDropoffPoint; + } + + private static CUnit findNearestMine(final CUnit worker, final CSimulation simulation) { + CUnit nearestMine = null; + double nearestMineDistance = Float.MAX_VALUE; + for (final CUnit unit : simulation.getUnits()) { + boolean acceptedUnit = false; + for (final CAbility ability : unit.getAbilities()) { + if (ability instanceof CAbilityGoldMine) { + acceptedUnit = true; + break; + } + } + if (acceptedUnit) { + // TODO maybe use distance squared, problem is that we're using this + // inefficient more complex distance function on unit + final double distance = unit.distanceSquaredNoCollision(worker); + if (distance < nearestMineDistance) { + nearestMineDistance = distance; + nearestMine = unit; + } + } + } + return nearestMine; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileSplash.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileSplash.java index 409623a..9cc4aab 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileSplash.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileSplash.java @@ -39,6 +39,11 @@ public class CUnitAttackMissileSplash extends CUnitAttackMissile { this.damageFactorSmall = damageFactorSmall; } + @Override + public int getRange() { + return super.getRange(); + } + public int getAreaOfEffectFullDamage() { return this.areaOfEffectFullDamage; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java index 4ed5c0c..56ce02a 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java @@ -1540,9 +1540,15 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma @Override public void foodChanged() { - this.resourceBarSupplyText.setText(this.localPlayer.getFoodUsed() + "/" + this.localPlayer.getFoodCap()); - this.resourceBarSupplyText - .setColor(this.localPlayer.getFoodUsed() > this.localPlayer.getFoodCap() ? Color.RED : Color.WHITE); + final int foodCap = this.localPlayer.getFoodCap(); + if (foodCap == 0) { + this.resourceBarSupplyText.setText(Integer.toString(this.localPlayer.getFoodUsed())); + this.resourceBarSupplyText.setColor(Color.WHITE); + } + else { + this.resourceBarSupplyText.setText(this.localPlayer.getFoodUsed() + "/" + foodCap); + this.resourceBarSupplyText.setColor(this.localPlayer.getFoodUsed() > foodCap ? Color.RED : Color.WHITE); + } } @Override From fa1656922ff3779681d6e9f25ac6942abe3bb65d Mon Sep 17 00:00:00 2001 From: Retera Date: Mon, 18 Jan 2021 08:20:01 -0500 Subject: [PATCH 085/116] Improve hold position and show shift click waypoints --- core/assets/warsmash.ini | 1 + .../com/etheller/warsmash/util/Quadtree.java | 2 +- .../viewer5/handlers/w3x/War3MapViewer.java | 2 + .../w3x/rendersim/RenderAttackInstant.java | 23 ++-- .../CommandCardPopulatingAbilityVisitor.java | 3 +- .../handlers/w3x/simulation/CUnit.java | 128 +++++++++++++----- .../w3x/simulation/CUnitStateListener.java | 15 +- .../simulation/abilities/CAbilityAttack.java | 4 +- .../simulation/abilities/CAbilityMove.java | 5 + .../abilities/combat/CAbilityColdArrows.java | 2 +- ...yTargetStillAliveAndTargetableVisitor.java | 3 +- .../AbilityTargetStillAliveVisitor.java | 2 +- .../behaviors/CAbstractRangedBehavior.java | 7 +- .../w3x/simulation/behaviors/CBehavior.java | 4 + .../simulation/behaviors/CBehaviorAttack.java | 14 +- .../simulation/behaviors/CBehaviorFollow.java | 10 ++ .../behaviors/CBehaviorHoldPosition.java | 42 ++++++ .../simulation/behaviors/CBehaviorMove.java | 10 ++ .../simulation/behaviors/CBehaviorPatrol.java | 10 ++ .../simulation/behaviors/CBehaviorStop.java | 14 +- .../behaviors/build/CBehaviorOrcBuild.java | 10 ++ .../behaviors/harvest/CBehaviorHarvest.java | 10 ++ .../harvest/CBehaviorReturnResources.java | 42 ++++-- .../w3x/simulation/orders/COrder.java | 5 + .../w3x/simulation/orders/COrderNoTarget.java | 35 ++++- .../simulation/orders/COrderTargetPoint.java | 14 +- .../simulation/orders/COrderTargetWidget.java | 17 ++- .../players/CPlayerUnitOrderExecutor.java | 19 ++- .../viewer5/handlers/w3x/ui/MeleeUI.java | 115 ++++++++++++++-- 29 files changed, 465 insertions(+), 103 deletions(-) create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorHoldPosition.java diff --git a/core/assets/warsmash.ini b/core/assets/warsmash.ini index 5405aca..f39a12d 100644 --- a/core/assets/warsmash.ini +++ b/core/assets/warsmash.ini @@ -38,3 +38,4 @@ Path06="." FilePath="PeonMiningMultiHall.w3x" //FilePath="QuadtreeBugs.w3x" //FilePath="test2.w3x" +//FilePath="FarseerHoldPositionTest.w3x" diff --git a/core/src/com/etheller/warsmash/util/Quadtree.java b/core/src/com/etheller/warsmash/util/Quadtree.java index cfaed78..6ab8595 100644 --- a/core/src/com/etheller/warsmash/util/Quadtree.java +++ b/core/src/com/etheller/warsmash/util/Quadtree.java @@ -6,7 +6,7 @@ import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.utils.Array; public class Quadtree { - private static final int MAX_DEPTH = 9; + private static final int MAX_DEPTH = 9; // 2^9 = 512, and 512 is the biggest map size... private static final int SPLIT_THRESHOLD = 6; private final Rectangle bounds; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index 1459057..182411d 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -508,6 +508,8 @@ public class War3MapViewer extends AbstractMdxModelViewer { final float height = War3MapViewer.this.terrain.getGroundHeight(targetX, targetY) + target.getFlyHeight() + target.getImpactZ(); + System.out.println( + "Spawning INSTANT: " + missileArt + " at " + targetX + ", " + targetY + ", " + height); final MdxModel model = (MdxModel) load(missileArt, War3MapViewer.this.mapPathSolver, War3MapViewer.this.solverParams); final MdxComplexInstance modelInstance = (MdxComplexInstance) model.addInstance(); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackInstant.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackInstant.java index bde7317..9de36b8 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackInstant.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackInstant.java @@ -1,14 +1,7 @@ package com.etheller.warsmash.viewer5.handlers.w3x.rendersim; -import java.util.List; - -import com.etheller.warsmash.parsers.mdlx.Sequence; import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; -import com.etheller.warsmash.viewer5.handlers.mdx.SequenceLoopMode; -import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; -import com.etheller.warsmash.viewer5.handlers.w3x.IndexedSequence; -import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; public class RenderAttackInstant implements RenderEffect { @@ -18,14 +11,15 @@ public class RenderAttackInstant implements RenderEffect { final float yaw) { this.modelInstance = modelInstance; final MdxModel model = (MdxModel) this.modelInstance.model; - final List sequences = model.getSequences(); - final IndexedSequence sequence = SequenceUtils.selectSequence(PrimaryTag.DEATH, SequenceUtils.EMPTY, sequences, - true); - if (sequence != null) { - this.modelInstance.setSequenceLoopMode(SequenceLoopMode.NEVER_LOOP); - this.modelInstance.setSequence(sequence.index); - } +// final List sequences = model.getSequences(); +// final IndexedSequence sequence = SequenceUtils.selectSequence(PrimaryTag.DEATH, SequenceUtils.EMPTY, sequences, +// true); +// if ((sequence != null) && (sequence.index != -1)) { +// this.modelInstance.setSequenceLoopMode(SequenceLoopMode.NEVER_LOOP); +// this.modelInstance.setSequence(sequence.index); +// } this.modelInstance.localRotation.setFromAxisRad(0, 0, 1, yaw); + System.out.println("creating " + this); } @Override @@ -33,6 +27,7 @@ public class RenderAttackInstant implements RenderEffect { final boolean everythingDone = this.modelInstance.sequenceEnded; if (everythingDone) { + System.out.println("removing " + this); war3MapViewer.worldScene.removeInstance(this.modelInstance); } return everythingDone; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java index 2cf48f9..b789909 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java @@ -79,8 +79,7 @@ public class CommandCardPopulatingAbilityVisitor implements CAbilityVisitor abilities = new ArrayList<>(); private CBehavior currentBehavior; - private COrder currentOrder; private final Queue orderQueue = new LinkedList<>(); private final CUnitType unitType; @@ -86,12 +86,15 @@ public class CUnit extends CWidget { private transient CBehaviorFollow followBehavior; private transient CBehaviorPatrol patrolBehavior; private transient CBehaviorStop stopBehavior; + private transient CBehaviorHoldPosition holdPositionBehavior; private boolean constructing = false; private float constructionProgress; private boolean hidden = false; private boolean paused = false; private boolean acceptingOrders = true; private boolean invulnerable = false; + private boolean holdingPosition = false; + private COrder currentOrder = null; private CUnit workerInside; private final War3ID[] buildQueue = new War3ID[WarsmashConstants.BUILD_QUEUE_SIZE]; private final QueueItemType[] buildQueueTypes = new QueueItemType[WarsmashConstants.BUILD_QUEUE_SIZE]; @@ -259,7 +262,7 @@ public class CUnit extends CWidget { player.setFoodCap(player.getFoodCap() + this.unitType.getFoodMade()); } game.unitConstructFinishEvent(this); - this.stateNotifier.ordersChanged(getCurrentAbilityHandleId(), getCurrentOrderId()); + this.stateNotifier.ordersChanged(); } } else { @@ -307,13 +310,17 @@ public class CUnit extends CWidget { if (this.currentBehavior != null) { final CBehavior lastBehavior = this.currentBehavior; this.currentBehavior = this.currentBehavior.update(game); + if (lastBehavior != this.currentBehavior) { + lastBehavior.end(game); + this.currentBehavior.begin(game); + } if (this.currentBehavior.getHighlightOrderId() != lastBehavior.getHighlightOrderId()) { - this.stateNotifier.ordersChanged(getCurrentAbilityHandleId(), getCurrentOrderId()); + this.stateNotifier.ordersChanged(); } } else { // check to auto acquire targets - autoAcquireAttackTargets(game); + autoAcquireAttackTargets(game, false); } } } @@ -330,7 +337,7 @@ public class CUnit extends CWidget { } } - public void autoAcquireAttackTargets(final CSimulation game) { + public boolean autoAcquireAttackTargets(final CSimulation game, final boolean disableMove) { if (!this.unitType.getAttacks().isEmpty()) { if (this.collisionRectangle != null) { tempRect.set(this.collisionRectangle); @@ -343,8 +350,11 @@ public class CUnit extends CWidget { tempRect.y -= halfSize; tempRect.width += halfSize * 2; tempRect.height += halfSize * 2; - game.getWorldCollision().enumUnitsInRect(tempRect, autoAttackTargetFinderEnum.reset(game, this)); + game.getWorldCollision().enumUnitsInRect(tempRect, + autoAttackTargetFinderEnum.reset(game, this, disableMove)); + return autoAttackTargetFinderEnum.foundAnyTarget; } + return false; } public float getEndingDecayTime(final CSimulation game) { @@ -359,26 +369,35 @@ public class CUnit extends CWidget { return; } - final CAbility ability = game.getAbility(order.getAbilityHandleId()); - if (ability != null) { - // Allow the ability to response to the order without actually placing itself in - // the queue, nor modifying (interrupting) the queue. - if (!ability.checkBeforeQueue(game, this, order.getOrderId())) { - this.stateNotifier.ordersChanged(getCurrentAbilityHandleId(), getCurrentOrderId()); - return; + if (order != null) { + final CAbility ability = game.getAbility(order.getAbilityHandleId()); + if (ability != null) { + // Allow the ability to response to the order without actually placing itself in + // the queue, nor modifying (interrupting) the queue. + if (!ability.checkBeforeQueue(game, this, order.getOrderId())) { + this.stateNotifier.ordersChanged(); + return; + } } } - if ((queue || !this.acceptingOrders) && (this.currentOrder != null)) { + if ((queue || !this.acceptingOrders) && ((this.currentBehavior != this.stopBehavior) + && (this.currentBehavior != this.holdPositionBehavior))) { this.orderQueue.add(order); + this.stateNotifier.waypointsChanged(); } else { - this.currentBehavior = beginOrder(game, order); - this.orderQueue.clear(); - final boolean omitNotify = (this.currentOrder == null) && (order == null); - if (!omitNotify) { - this.stateNotifier.ordersChanged(getCurrentAbilityHandleId(), getCurrentOrderId()); + setHoldingPosition(false); + if (this.currentBehavior != null) { + this.currentBehavior.end(game); } + this.currentBehavior = beginOrder(game, order); + if (this.currentBehavior != null) { + this.currentBehavior.begin(game); + } + this.orderQueue.clear(); + this.stateNotifier.ordersChanged(); + this.stateNotifier.waypointsChanged(); } } @@ -389,7 +408,12 @@ public class CUnit extends CWidget { nextBehavior = order.begin(game, this); } else { - nextBehavior = this.stopBehavior; + if (this.holdingPosition) { + nextBehavior = this.holdPositionBehavior; + } + else { + nextBehavior = this.stopBehavior; + } } return nextBehavior; } @@ -398,14 +422,6 @@ public class CUnit extends CWidget { return this.currentBehavior; } - public int getCurrentAbilityHandleId() { - return this.currentOrder == null ? 0 : this.currentOrder.getAbilityHandleId(); - } - - public int getCurrentOrderId() { - return this.currentOrder == null ? OrderIds.stop : this.currentOrder.getOrderId(); - } - public List getAbilities() { return this.abilities; } @@ -625,7 +641,8 @@ public class CUnit extends CWidget { CAllianceType.PASSIVE)) { for (final CUnitAttack attack : this.unitType.getAttacks()) { if (source.canBeTargetedBy(simulation, this, attack.getTargetsAllowed())) { - this.currentBehavior = getAttackBehavior().reset(OrderIds.attack, attack, source); + this.currentBehavior = getAttackBehavior().reset(OrderIds.attack, attack, source, false); + this.currentBehavior.begin(simulation); break; } } @@ -635,6 +652,9 @@ public class CUnit extends CWidget { } private void kill(final CSimulation simulation) { + if (this.currentBehavior != null) { + this.currentBehavior.end(simulation); + } this.currentBehavior = null; this.orderQueue.clear(); if (this.constructing) { @@ -809,10 +829,15 @@ public class CUnit extends CWidget { private static final class AutoAttackTargetFinderEnum implements CUnitEnumFunction { private CSimulation game; private CUnit source; + private boolean disableMove; + private boolean foundAnyTarget; - private AutoAttackTargetFinderEnum reset(final CSimulation game, final CUnit source) { + private AutoAttackTargetFinderEnum reset(final CSimulation game, final CUnit source, + final boolean disableMove) { this.game = game; this.source = source; + this.disableMove = disableMove; + this.foundAnyTarget = false; return this; } @@ -824,8 +849,13 @@ public class CUnit extends CWidget { if (this.source.canReach(unit, this.source.acquisitionRange) && unit.canBeTargetedBy(this.game, this.source, attack.getTargetsAllowed()) && (this.source.distance(unit) >= this.source.getUnitType().getMinimumAttackRange())) { + if (this.source.currentBehavior != null) { + this.source.currentBehavior.end(this.game); + } this.source.currentBehavior = this.source.getAttackBehavior().reset(OrderIds.attack, attack, - unit); + unit, this.disableMove); + this.source.currentBehavior.begin(this.game); + this.foundAnyTarget = true; return true; } } @@ -862,6 +892,10 @@ public class CUnit extends CWidget { this.patrolBehavior = patrolBehavior; } + public void setHoldPositionBehavior(final CBehaviorHoldPosition holdPositionBehavior) { + this.holdPositionBehavior = holdPositionBehavior; + } + public CBehaviorFollow getFollowBehavior() { return this.followBehavior; } @@ -870,9 +904,20 @@ public class CUnit extends CWidget { return this.patrolBehavior; } + public CBehaviorHoldPosition getHoldPositionBehavior() { + return this.holdPositionBehavior; + } + public CBehavior pollNextOrderBehavior(final CSimulation game) { + if (this.holdingPosition) { + // kind of a stupid hack, meant to align in feel with some behaviors that were + // observed on War3 + return this.holdPositionBehavior; + } final COrder order = this.orderQueue.poll(); - return beginOrder(game, order); + final CBehavior nextOrderBehavior = beginOrder(game, order); + this.stateNotifier.waypointsChanged(); + return nextOrderBehavior; } public boolean isMoving() { @@ -1087,7 +1132,7 @@ public class CUnit extends CWidget { ability.checkCanTarget(this.game, this.trainedUnit, this.rallyOrderId, target, targetCheckReceiver); if (targetCheckReceiver.isTargetable()) { this.trainedUnit.order(this.game, - new COrderTargetPoint(ability.getHandleId(), this.rallyOrderId, target), false); + new COrderTargetPoint(ability.getHandleId(), this.rallyOrderId, target, false), false); return null; } } @@ -1109,9 +1154,8 @@ public class CUnit extends CWidget { .getInstance().reset(); ability.checkCanTarget(game, trainedUnit, rallyOrderId, target, targetCheckReceiver); if (targetCheckReceiver.isTargetable()) { - trainedUnit.order(game, - new COrderTargetWidget(ability.getHandleId(), rallyOrderId, target.getHandleId()), - false); + trainedUnit.order(game, new COrderTargetWidget(ability.getHandleId(), rallyOrderId, + target.getHandleId(), false), false); return null; } } @@ -1166,4 +1210,16 @@ public class CUnit extends CWidget { } } } + + public void setHoldingPosition(final boolean holdingPosition) { + this.holdingPosition = holdingPosition; + } + + public Queue getOrderQueue() { + return this.orderQueue; + } + + public COrder getCurrentOrder() { + return this.currentOrder; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitStateListener.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitStateListener.java index 18d5bd8..2ec7c85 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitStateListener.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitStateListener.java @@ -5,12 +5,14 @@ import com.etheller.warsmash.util.SubscriberSetNotifier; public interface CUnitStateListener { void lifeChanged(); // hp (current) changes - void ordersChanged(int abilityHandleId, int orderId); + void ordersChanged(); void queueChanged(); void rallyPointChanged(); + void waypointsChanged(); + public static final class CUnitStateNotifier extends SubscriberSetNotifier implements CUnitStateListener { @Override @@ -21,9 +23,9 @@ public interface CUnitStateListener { } @Override - public void ordersChanged(final int abilityHandleId, final int orderId) { + public void ordersChanged() { for (final CUnitStateListener listener : set) { - listener.ordersChanged(abilityHandleId, orderId); + listener.ordersChanged(); } } @@ -40,5 +42,12 @@ public interface CUnitStateListener { listener.rallyPointChanged(); } } + + @Override + public void waypointsChanged() { + for (final CUnitStateListener listener : set) { + listener.waypointsChanged(); + } + } } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java index 70b8e3c..d675673 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java @@ -111,7 +111,7 @@ public class CAbilityAttack extends AbstractCAbility { CBehavior behavior = null; for (final CUnitAttack attack : caster.getUnitType().getAttacks()) { if (target.canBeTargetedBy(game, caster, attack.getTargetsAllowed())) { - behavior = caster.getAttackBehavior().reset(OrderIds.attack, attack, target); + behavior = caster.getAttackBehavior().reset(OrderIds.attack, attack, target, false); break; } } @@ -131,7 +131,7 @@ public class CAbilityAttack extends AbstractCAbility { CBehavior behavior = null; for (final CUnitAttack attack : caster.getUnitType().getAttacks()) { if (attack.getWeaponType().isAttackGroundSupported()) { - behavior = caster.getAttackBehavior().reset(OrderIds.attackground, attack, point); + behavior = caster.getAttackBehavior().reset(OrderIds.attackground, attack, point, false); break; } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityMove.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityMove.java index 3cb7e90..77c2f20 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityMove.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityMove.java @@ -6,6 +6,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorFollow; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorHoldPosition; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorMove; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorPatrol; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; @@ -77,6 +78,7 @@ public class CAbilityMove extends AbstractCAbility { unit.setMoveBehavior(new CBehaviorMove(unit)); unit.setFollowBehavior(new CBehaviorFollow(unit)); unit.setPatrolBehavior(new CBehaviorPatrol(unit)); + unit.setHoldPositionBehavior(new CBehaviorHoldPosition(unit)); } @Override @@ -107,6 +109,9 @@ public class CAbilityMove extends AbstractCAbility { @Override public CBehavior beginNoTarget(final CSimulation game, final CUnit caster, final int orderId) { + if (orderId == OrderIds.holdposition) { + caster.setHoldingPosition(true); + } return caster.pollNextOrderBehavior(game); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/combat/CAbilityColdArrows.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/combat/CAbilityColdArrows.java index 360ac0e..3d8f780 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/combat/CAbilityColdArrows.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/combat/CAbilityColdArrows.java @@ -102,7 +102,7 @@ public class CAbilityColdArrows extends AbstractCAbility { CBehavior behavior = null; for (final CUnitAttack attack : caster.getUnitType().getAttacks()) { if (target.canBeTargetedBy(game, caster, attack.getTargetsAllowed())) { - behavior = caster.getAttackBehavior().reset(OrderIds.coldarrowstarg, attack, target); + behavior = caster.getAttackBehavior().reset(OrderIds.coldarrowstarg, attack, target, false); break; } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/targeting/AbilityTargetStillAliveAndTargetableVisitor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/targeting/AbilityTargetStillAliveAndTargetableVisitor.java index e54e6c5..571a3c1 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/targeting/AbilityTargetStillAliveAndTargetableVisitor.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/targeting/AbilityTargetStillAliveAndTargetableVisitor.java @@ -28,7 +28,8 @@ public final class AbilityTargetStillAliveAndTargetableVisitor implements Abilit @Override public Boolean accept(final CUnit target) { - return !target.isDead() && target.canBeTargetedBy(this.simulation, this.unit, this.targetsAllowed); + return !target.isDead() && !target.isHidden() + && target.canBeTargetedBy(this.simulation, this.unit, this.targetsAllowed); } @Override diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/targeting/AbilityTargetStillAliveVisitor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/targeting/AbilityTargetStillAliveVisitor.java index dfd796f..bd70964 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/targeting/AbilityTargetStillAliveVisitor.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/targeting/AbilityTargetStillAliveVisitor.java @@ -14,7 +14,7 @@ public class AbilityTargetStillAliveVisitor implements AbilityTargetVisitor targetCheckReceiver = new StringMsgTargetCheckReceiver<>(); final StringMsgAbilityActivationReceiver abilityActivationReceiver = new StringMsgAbilityActivationReceiver(); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderNoTarget.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderNoTarget.java index 9f3687d..a815381 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderNoTarget.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderNoTarget.java @@ -3,15 +3,19 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.StringMsgTargetCheckReceiver; public class COrderNoTarget implements COrder { private final int abilityHandleId; private final int orderId; + private final boolean queued; - public COrderNoTarget(final int abilityHandleId, final int orderId) { + public COrderNoTarget(final int abilityHandleId, final int orderId, final boolean queued) { this.abilityHandleId = abilityHandleId; this.orderId = orderId; + this.queued = queued; } @Override @@ -24,14 +28,35 @@ public class COrderNoTarget implements COrder { return this.orderId; } + @Override + public boolean isQueued() { + return this.queued; + } + @Override public CBehavior begin(final CSimulation game, final CUnit caster) { final CAbility ability = game.getAbility(this.abilityHandleId); - if ((ability == null) && (this.orderId == OrderIds.stop)) { - // stop - return caster.getStopBehavior(); + ability.checkCanUse(game, caster, this.orderId, this.abilityActivationReceiver.reset()); + if (this.abilityActivationReceiver.isUseOk()) { + final StringMsgTargetCheckReceiver targetReceiver = (StringMsgTargetCheckReceiver) targetCheckReceiver; + ability.checkCanTargetNoTarget(game, caster, this.orderId, targetReceiver); + if (targetReceiver.getMessage() == null) { + return ability.beginNoTarget(game, caster, this.orderId); + } + else { + game.getCommandErrorListener().showCommandError(targetReceiver.getMessage()); + return caster.pollNextOrderBehavior(game); + } } - return ability.beginNoTarget(game, caster, this.orderId); + else { + game.getCommandErrorListener().showCommandError(this.abilityActivationReceiver.getMessage()); + return caster.pollNextOrderBehavior(game); + } + } + + @Override + public AbilityTarget getTarget(final CSimulation game) { + return null; } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetPoint.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetPoint.java index 3db81ce..e64eaf4 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetPoint.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetPoint.java @@ -1,6 +1,5 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders; -import com.badlogic.gdx.math.Vector2; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; @@ -12,11 +11,14 @@ public class COrderTargetPoint implements COrder { private final int abilityHandleId; private final int orderId; private final AbilityPointTarget target; + private final boolean queued; - public COrderTargetPoint(final int abilityHandleId, final int orderId, final AbilityPointTarget target) { + public COrderTargetPoint(final int abilityHandleId, final int orderId, final AbilityPointTarget target, + final boolean queued) { this.abilityHandleId = abilityHandleId; this.orderId = orderId; this.target = target; + this.queued = queued; } @Override @@ -29,10 +31,16 @@ public class COrderTargetPoint implements COrder { return this.orderId; } - public Vector2 getTarget() { + @Override + public AbilityPointTarget getTarget(final CSimulation game) { return this.target; } + @Override + public boolean isQueued() { + return this.queued; + } + @Override public CBehavior begin(final CSimulation game, final CUnit caster) { final CAbility ability = game.getAbility(this.abilityHandleId); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetWidget.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetWidget.java index 8b4a1d0..902dcf5 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetWidget.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetWidget.java @@ -4,6 +4,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.StringMsgTargetCheckReceiver; @@ -11,11 +12,14 @@ public class COrderTargetWidget implements COrder { private final int abilityHandleId; private final int orderId; private final int targetHandleId; + private final boolean queued; - public COrderTargetWidget(final int abilityHandleId, final int orderId, final int targetHandleId) { + public COrderTargetWidget(final int abilityHandleId, final int orderId, final int targetHandleId, + final boolean queued) { this.abilityHandleId = abilityHandleId; this.orderId = orderId; this.targetHandleId = targetHandleId; + this.queued = queued; } @Override @@ -28,6 +32,17 @@ public class COrderTargetWidget implements COrder { return this.orderId; } + @Override + public AbilityTarget getTarget(final CSimulation game) { + final CUnit target = game.getUnit(this.targetHandleId); + return target; + } + + @Override + public boolean isQueued() { + return this.queued; + } + @Override public CBehavior begin(final CSimulation game, final CUnit caster) { final CAbility ability = game.getAbility(this.abilityHandleId); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderExecutor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderExecutor.java index a68fc81..16db009 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderExecutor.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderExecutor.java @@ -6,6 +6,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.COrderNoTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.COrderTargetPoint; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.COrderTargetWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.CommandErrorListener; public class CPlayerUnitOrderExecutor implements CPlayerUnitOrderListener { @@ -21,21 +22,33 @@ public class CPlayerUnitOrderExecutor implements CPlayerUnitOrderListener { public void issueTargetOrder(final int unitHandleId, final int abilityHandleId, final int orderId, final int targetHandleId, final boolean queue) { final CUnit unit = this.game.getUnit(unitHandleId); - unit.order(this.game, new COrderTargetWidget(abilityHandleId, orderId, targetHandleId), queue); + unit.order(this.game, new COrderTargetWidget(abilityHandleId, orderId, targetHandleId, queue), queue); } @Override public void issuePointOrder(final int unitHandleId, final int abilityHandleId, final int orderId, final float x, final float y, final boolean queue) { final CUnit unit = this.game.getUnit(unitHandleId); - unit.order(this.game, new COrderTargetPoint(abilityHandleId, orderId, new AbilityPointTarget(x, y)), queue); + unit.order(this.game, new COrderTargetPoint(abilityHandleId, orderId, new AbilityPointTarget(x, y), queue), + queue); } @Override public void issueImmediateOrder(final int unitHandleId, final int abilityHandleId, final int orderId, final boolean queue) { final CUnit unit = this.game.getUnit(unitHandleId); - unit.order(this.game, new COrderNoTarget(abilityHandleId, orderId), queue); + if (abilityHandleId == 0) { + if (orderId == OrderIds.stop) { + unit.order(this.game, null, queue); + } + else if (orderId == OrderIds.holdposition) { + unit.order(this.game, null, queue); + unit.setHoldingPosition(true); + } + } + else { + unit.order(this.game, new COrderNoTarget(abilityHandleId, orderId, queue), queue); + } } @Override diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java index 56ce02a..6138c31 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java @@ -4,8 +4,11 @@ import java.awt.image.BufferedImage; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.EnumSet; +import java.util.Iterator; import java.util.List; +import java.util.Queue; import java.util.concurrent.TimeUnit; import javax.imageio.ImageIO; @@ -113,6 +116,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CDefenseType import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CodeKeyType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackMissileSplash; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.COrder; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.pathing.CBuildingPathingType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayer; @@ -241,6 +245,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma private final RallyPositioningVisitor rallyPositioningVisitor; private final CPlayer localPlayer; private MeleeUIAbilityActivationReceiver meleeUIAbilityActivationReceiver; + private MdxModel waypointModel; + private final List waypointModelInstances = new ArrayList<>(); public MeleeUI(final DataSource dataSource, final ExtendViewport uiViewport, final FreeTypeFontGenerator fontGenerator, final Scene uiScene, final Scene portraitScene, @@ -618,6 +624,9 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.rallyPointInstance.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); SequenceUtils.randomStandSequence(this.rallyPointInstance); this.rallyPointInstance.hide(); + this.waypointModel = (MdxModel) this.war3MapViewer.load( + War3MapViewer.mdx(this.rootFrame.getSkinField("WaypointIndicator")), this.war3MapViewer.mapPathSolver, + this.war3MapViewer.solverParams); this.rootFrame.positionBounds(this.rootFrame, this.uiViewport); @@ -1062,12 +1071,19 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } private final class RallyPositioningVisitor implements AbilityTargetVisitor { + private MdxComplexInstance rallyPointInstance = null; + + public RallyPositioningVisitor reset(final MdxComplexInstance rallyPointInstance) { + this.rallyPointInstance = rallyPointInstance; + return this; + } + @Override public Void accept(final AbilityPointTarget target) { - MeleeUI.this.rallyPointInstance.setParent(null); + this.rallyPointInstance.setParent(null); final float rallyPointX = target.getX(); final float rallyPointY = target.getY(); - MeleeUI.this.rallyPointInstance.setLocation(rallyPointX, rallyPointY, + this.rallyPointInstance.setLocation(rallyPointX, rallyPointY, MeleeUI.this.war3MapViewer.terrain.getGroundHeight(rallyPointX, rallyPointY)); return null; } @@ -1094,14 +1110,14 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } if (index != -1) { final MdxNode attachment = renderUnit.instance.getAttachment(index); - MeleeUI.this.rallyPointInstance.setParent(attachment); - MeleeUI.this.rallyPointInstance.setLocation(0, 0, 0); + this.rallyPointInstance.setParent(attachment); + this.rallyPointInstance.setLocation(0, 0, 0); } else { - MeleeUI.this.rallyPointInstance.setParent(null); + this.rallyPointInstance.setParent(null); final float rallyPointX = target.getX(); final float rallyPointY = target.getY(); - MeleeUI.this.rallyPointInstance.setLocation(rallyPointX, rallyPointY, + this.rallyPointInstance.setLocation(rallyPointX, rallyPointY, MeleeUI.this.war3MapViewer.terrain.getGroundHeight(rallyPointX, rallyPointY)); } return null; @@ -1109,20 +1125,20 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma @Override public Void accept(final CDestructable target) { - MeleeUI.this.rallyPointInstance.setParent(null); + this.rallyPointInstance.setParent(null); final float rallyPointX = target.getX(); final float rallyPointY = target.getY(); - MeleeUI.this.rallyPointInstance.setLocation(rallyPointX, rallyPointY, + this.rallyPointInstance.setLocation(rallyPointX, rallyPointY, MeleeUI.this.war3MapViewer.terrain.getGroundHeight(rallyPointX, rallyPointY)); return null; } @Override public Void accept(final CItem target) { - MeleeUI.this.rallyPointInstance.setParent(null); + this.rallyPointInstance.setParent(null); final float rallyPointX = target.getX(); final float rallyPointY = target.getY(); - MeleeUI.this.rallyPointInstance.setLocation(rallyPointX, rallyPointY, + this.rallyPointInstance.setLocation(rallyPointX, rallyPointY, MeleeUI.this.war3MapViewer.terrain.getGroundHeight(rallyPointX, rallyPointY)); return null; } @@ -1233,6 +1249,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.selectWorkerInsideFrame.setVisible(false); this.rallyPointInstance.hide(); this.rallyPointInstance.detach(); + repositionWaypointFlags(null); } else { unit.getSimulationUnit().addStateListener(this); @@ -1255,7 +1272,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.war3MapViewer.simulation.getPlayer(simulationUnit.getPlayerIndex()).getColorIndex()); this.rallyPointInstance.show(); this.rallyPointInstance.detach(); - rallyPoint.visit(this.rallyPositioningVisitor); + rallyPoint.visit(this.rallyPositioningVisitor.reset(this.rallyPointInstance)); this.rallyPointInstance.setScene(this.war3MapViewer.worldScene); } else { @@ -1264,6 +1281,79 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } } + @Override + public void waypointsChanged() { + if (this.selectedUnit != null) { + final CUnit simulationUnit = this.selectedUnit.getSimulationUnit(); + repositionWaypointFlags(simulationUnit); + } + else { + repositionWaypointFlags(null); + } + } + + private void repositionWaypointFlags(final CUnit simulationUnit) { + final Iterator iterator; + int orderIndex = 0; + if (simulationUnit != null) { + final Queue orderQueue = simulationUnit.getOrderQueue(); + iterator = orderQueue.iterator(); + final COrder order = simulationUnit.getCurrentOrder(); + if ((order != null) && order.isQueued()) { + final MdxComplexInstance waypointModelInstance = getOrCreateWaypointIndicator(orderIndex); + final AbilityTarget target = order.getTarget(this.war3MapViewer.simulation); + if (target != null) { + waypointModelInstance.show(); + waypointModelInstance.detach(); + target.visit(this.rallyPositioningVisitor.reset(waypointModelInstance)); + waypointModelInstance.setScene(this.war3MapViewer.worldScene); + } + else { + waypointModelInstance.hide(); + waypointModelInstance.detach(); + } + orderIndex++; + } + } + else { + iterator = Collections.emptyIterator(); + } + for (; (orderIndex < this.waypointModelInstances.size()) || (iterator.hasNext()); orderIndex++) { + final MdxComplexInstance waypointModelInstance = getOrCreateWaypointIndicator(orderIndex); + if (iterator.hasNext()) { + final COrder order = iterator.next(); + final AbilityTarget target = order.getTarget(this.war3MapViewer.simulation); + if (target != null) { + waypointModelInstance.show(); + waypointModelInstance.detach(); + target.visit(this.rallyPositioningVisitor.reset(waypointModelInstance)); + waypointModelInstance.setScene(this.war3MapViewer.worldScene); + } + else { + waypointModelInstance.hide(); + waypointModelInstance.detach(); + } + } + else { + waypointModelInstance.hide(); + waypointModelInstance.detach(); + } + } + } + + private MdxComplexInstance getOrCreateWaypointIndicator(final int index) { + while (index >= this.waypointModelInstances.size()) { + final MdxComplexInstance waypointModelInstance = (MdxComplexInstance) this.waypointModel.addInstance(); + waypointModelInstance.rotate(RenderUnit.tempQuat.setFromAxis(RenderMathUtils.VEC3_UNIT_Z, + this.war3MapViewer.simulation.getGameplayConstants().getBuildingAngle())); + waypointModelInstance.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + SequenceUtils.randomStandSequence(waypointModelInstance); + waypointModelInstance.hide(); + this.waypointModelInstances.add(waypointModelInstance); + } + return this.waypointModelInstances.get(index); + } + private void reloadSelectedUnitUI(final RenderUnit unit) { final CUnit simulationUnit = unit.getSimulationUnit(); this.unitLifeText.setText( @@ -1277,6 +1367,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.unitManaText.setText(""); } repositionRallyPoint(simulationUnit); + repositionWaypointFlags(simulationUnit); if (simulationUnit.getBuildQueue()[0] != null) { for (int i = 0; i < this.queueIconFrames.length; i++) { final QueueItemType queueItemType = simulationUnit.getBuildQueueTypes()[i]; @@ -1558,7 +1649,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } @Override - public void ordersChanged(final int abilityHandleId, final int orderId) { + public void ordersChanged() { reloadSelectedUnitUI(this.selectedUnit); } From 11e9a965dc82c96f66711a352916081056304e3f Mon Sep 17 00:00:00 2001 From: Retera Date: Mon, 18 Jan 2021 08:23:35 -0500 Subject: [PATCH 086/116] Update instant attack effects not to setScene until location is assigned --- .../viewer5/handlers/w3x/War3MapViewer.java | 4 +--- .../w3x/rendersim/RenderAttackInstant.java | 23 +++++++++++-------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index 182411d..1ea4ba0 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -508,15 +508,13 @@ public class War3MapViewer extends AbstractMdxModelViewer { final float height = War3MapViewer.this.terrain.getGroundHeight(targetX, targetY) + target.getFlyHeight() + target.getImpactZ(); - System.out.println( - "Spawning INSTANT: " + missileArt + " at " + targetX + ", " + targetY + ", " + height); final MdxModel model = (MdxModel) load(missileArt, War3MapViewer.this.mapPathSolver, War3MapViewer.this.solverParams); final MdxComplexInstance modelInstance = (MdxComplexInstance) model.addInstance(); modelInstance.setTeamColor(source.getPlayerIndex()); - modelInstance.setScene(War3MapViewer.this.worldScene); SequenceUtils.randomBirthSequence(modelInstance); modelInstance.setLocation(targetX, targetY, height); + modelInstance.setScene(War3MapViewer.this.worldScene); War3MapViewer.this.projectiles .add(new RenderAttackInstant(modelInstance, War3MapViewer.this, angleToTarget)); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackInstant.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackInstant.java index 9de36b8..fe82026 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackInstant.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackInstant.java @@ -1,7 +1,14 @@ package com.etheller.warsmash.viewer5.handlers.w3x.rendersim; +import java.util.List; + +import com.etheller.warsmash.parsers.mdlx.Sequence; import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; +import com.etheller.warsmash.viewer5.handlers.mdx.SequenceLoopMode; +import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; +import com.etheller.warsmash.viewer5.handlers.w3x.IndexedSequence; +import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; public class RenderAttackInstant implements RenderEffect { @@ -11,15 +18,14 @@ public class RenderAttackInstant implements RenderEffect { final float yaw) { this.modelInstance = modelInstance; final MdxModel model = (MdxModel) this.modelInstance.model; -// final List sequences = model.getSequences(); -// final IndexedSequence sequence = SequenceUtils.selectSequence(PrimaryTag.DEATH, SequenceUtils.EMPTY, sequences, -// true); -// if ((sequence != null) && (sequence.index != -1)) { -// this.modelInstance.setSequenceLoopMode(SequenceLoopMode.NEVER_LOOP); -// this.modelInstance.setSequence(sequence.index); -// } + final List sequences = model.getSequences(); + final IndexedSequence sequence = SequenceUtils.selectSequence(PrimaryTag.DEATH, SequenceUtils.EMPTY, sequences, + true); + if ((sequence != null) && (sequence.index != -1)) { + this.modelInstance.setSequenceLoopMode(SequenceLoopMode.NEVER_LOOP); + this.modelInstance.setSequence(sequence.index); + } this.modelInstance.localRotation.setFromAxisRad(0, 0, 1, yaw); - System.out.println("creating " + this); } @Override @@ -27,7 +33,6 @@ public class RenderAttackInstant implements RenderEffect { final boolean everythingDone = this.modelInstance.sequenceEnded; if (everythingDone) { - System.out.println("removing " + this); war3MapViewer.worldScene.removeInstance(this.modelInstance); } return everythingDone; From c0e224234a8d50f10aaa791a52113b00fd69a2c0 Mon Sep 17 00:00:00 2001 From: Retera Date: Fri, 22 Jan 2021 00:41:08 -0500 Subject: [PATCH 087/116] Snapshot while working towards support for destructables --- core/assets/warsmash.ini | 4 +- .../etheller/warsmash/WarsmashGdxMapGame.java | 4 + .../etheller/warsmash/parsers/fdf/GameUI.java | 42 +++----- .../fdf/frames/AbstractRenderableFrame.java | 5 + .../parsers/fdf/frames/AbstractUIFrame.java | 11 ++ .../warsmash/parsers/fdf/frames/UIFrame.java | 2 + .../handlers/mdx/MdxComplexInstance.java | 7 +- .../handlers/w3x/RenderDestructable.java | 55 +++++----- .../viewer5/handlers/w3x/War3MapViewer.java | 96 ++++++++++++----- .../handlers/w3x/environment/PathingGrid.java | 11 ++ .../handlers/w3x/rendersim/RenderUnit.java | 9 +- .../w3x/simulation/CDestructable.java | 24 ++++- .../w3x/simulation/CDestructableType.java | 57 ++++++++++ .../handlers/w3x/simulation/CSimulation.java | 27 +++-- .../handlers/w3x/simulation/CUnit.java | 45 ++++---- .../handlers/w3x/simulation/CUnitType.java | 39 ++++++- .../w3x/simulation/abilities/CAbility.java | 2 + .../simulation/abilities/CAbilityAttack.java | 7 ++ .../simulation/abilities/CAbilityGeneric.java | 4 + .../simulation/abilities/CAbilityMove.java | 10 +- .../build/AbstractCAbilityBuild.java | 4 + .../build/CAbilityBuildInProgress.java | 4 + .../abilities/combat/CAbilityColdArrows.java | 4 + .../abilities/harvest/CAbilityHarvest.java | 16 ++- .../harvest/CAbilityReturnResources.java | 4 + .../abilities/mine/CAbilityGoldMine.java | 44 +++++++- .../abilities/queue/CAbilityQueue.java | 4 + .../abilities/queue/CAbilityRally.java | 4 + .../simulation/behaviors/CBehaviorPatrol.java | 12 ++- .../behaviors/harvest/CBehaviorHarvest.java | 101 ++++++++---------- .../simulation/data/CDestructableData.java | 87 +++++++++++++++ .../w3x/simulation/data/CUnitData.java | 36 ++++--- .../players/CPlayerUnitOrderExecutor.java | 6 +- .../util/SimulationRenderController.java | 7 ++ .../handlers/w3x/ui/CommandCardIcon.java | 15 ++- .../viewer5/handlers/w3x/ui/MeleeUI.java | 36 +++++++ .../viewer5/handlers/w3x/ui/QueueIcon.java | 13 +++ .../w3x/ui/command/ClickableActionFrame.java | 2 + resources/UI/FrameDef/SmashFrameDef.toc | 1 + resources/UI/FrameDef/SmashUI/ToolTip.fdf | 42 ++++++++ 40 files changed, 698 insertions(+), 205 deletions(-) create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CDestructableType.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CDestructableData.java create mode 100644 resources/UI/FrameDef/SmashUI/ToolTip.fdf diff --git a/core/assets/warsmash.ini b/core/assets/warsmash.ini index f39a12d..9e25f41 100644 --- a/core/assets/warsmash.ini +++ b/core/assets/warsmash.ini @@ -18,7 +18,7 @@ Path06="." [Map] //FilePath="CombatUnitTests.w3x" //FilePath="PitchRoll.w3x" -//FilePath="PeonStartingBase.w3x" +FilePath="PeonStartingBase.w3x" //FilePath="ColdArrows.w3m" //FilePath="DungeonGoldMine.w3m" //FilePath="PlayerPeasants.w3m" @@ -35,7 +35,7 @@ Path06="." //FilePath="Pathing.w3x" //FilePath="ItemFacing.w3x" //FilePath=SomeParticleTests.w3x -FilePath="PeonMiningMultiHall.w3x" +//FilePath="PeonMiningMultiHall.w3x" //FilePath="QuadtreeBugs.w3x" //FilePath="test2.w3x" //FilePath="FarseerHoldPositionTest.w3x" diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java index 5fa315d..1d6bf08 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java @@ -415,6 +415,10 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv @Override public boolean mouseMoved(final int screenX, final int screenY) { + final float worldScreenY = getHeight() - screenY; + if (this.meleeUI.mouseMoved(screenX, screenY, worldScreenY)) { + return false; + } return false; } diff --git a/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java index a1b8897..976f9e5 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java @@ -227,27 +227,9 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { public UIFrame createFrame(final String name, final UIFrame owner, final int priority, final int createContext) { final FrameDefinition frameDefinition = this.templates.getFrame(name); - if (frameDefinition.getFrameClass() == FrameClass.Frame) { - if ("SPRITE".equals(frameDefinition.getFrameType())) { - final UIFrame inflated = inflate(frameDefinition, owner, null, - frameDefinition.has("DecorateFileNames")); - if (this.autoPosition) { - inflated.positionBounds(this, this.viewport); - } - add(inflated); - return inflated; - } - else if ("FRAME".equals(frameDefinition.getFrameType())) { - final UIFrame inflated = inflate(frameDefinition, owner, null, - frameDefinition.has("DecorateFileNames")); - if (this.autoPosition) { - inflated.positionBounds(this, this.viewport); - } - add(inflated); - return inflated; - } - } - throw new UnsupportedOperationException("Not yet implemented"); + final UIFrame inflatedFrame = inflate(frameDefinition, owner, null, frameDefinition.has("DecorateFileNames")); + add(inflatedFrame); + return inflatedFrame; } public UIFrame createSimpleFrame(final String name, final UIFrame owner, final int createContext) { @@ -465,7 +447,7 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { } else if ("BACKDROP".equals(frameDefinition.getFrameType())) { final boolean tileBackground = frameDefinition.has("BackdropTileBackground"); - final String backgroundString = frameDefinition.getString("BackdropBackground"); + String backgroundString = frameDefinition.getString("BackdropBackground"); String cornerFlagsString = frameDefinition.getString("BackdropCornerFlags"); if (cornerFlagsString == null) { cornerFlagsString = ""; @@ -488,19 +470,25 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { else { backgroundInsets = new Vector4Definition(0, 0, 0, 0); } - final String edgeFileString = frameDefinition.getString("BackdropEdgeFile"); + final boolean decorateFileNames = frameDefinition.has("DecorateFileNames") || inDecorateFileNames; + String edgeFileString = frameDefinition.getString("BackdropEdgeFile"); System.out.println(frameDefinition.getName() + " wants edge file: " + edgeFileString); + if (decorateFileNames && (edgeFileString != null)) { + edgeFileString = getSkinField(edgeFileString); + } + if (decorateFileNames && (edgeFileString != null)) { + backgroundString = getSkinField(backgroundString); + } final Texture background = backgroundString == null ? null : loadTexture(backgroundString); final Texture edgeFile = edgeFileString == null ? null : loadTexture(edgeFileString); System.out.println(frameDefinition.getName() + " got edge file: " + edgeFile); final BackdropFrame backdropFrame = new BackdropFrame(frameDefinition.getName(), parent, - inDecorateFileNames || frameDefinition.has("DecorateFileNames"), tileBackground, background, - cornerFlags, cornerSize, backgroundSize, backgroundInsets, edgeFile); + decorateFileNames, tileBackground, background, cornerFlags, cornerSize, backgroundSize, + backgroundInsets, edgeFile); this.nameToFrame.put(frameDefinition.getName(), backdropFrame); for (final FrameDefinition childDefinition : frameDefinition.getInnerFrames()) { - backdropFrame.add(inflate(childDefinition, backdropFrame, frameDefinition, - inDecorateFileNames || childDefinition.has("DecorateFileNames"))); + backdropFrame.add(inflate(childDefinition, backdropFrame, frameDefinition, decorateFileNames)); } inflatedFrame = backdropFrame; } diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java index e1ebe94..9eadfc4 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java @@ -324,6 +324,11 @@ public abstract class AbstractRenderableFrame implements UIFrame { return null; } + @Override + public UIFrame getFrameChildUnderMouse(final float screenX, final float screenY) { + return null; + } + @Override public String getName() { return this.name; diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractUIFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractUIFrame.java index 0f69ecd..df60e18 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractUIFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractUIFrame.java @@ -58,4 +58,15 @@ public abstract class AbstractUIFrame extends AbstractRenderableFrame implements } return super.touchUp(screenX, screenY, button); } + + @Override + public UIFrame getFrameChildUnderMouse(final float screenX, final float screenY) { + for (final UIFrame childFrame : this.childFrames) { + final UIFrame clickedChild = childFrame.getFrameChildUnderMouse(screenX, screenY); + if (clickedChild != null) { + return clickedChild; + } + } + return super.getFrameChildUnderMouse(screenX, screenY); + } } diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/UIFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/UIFrame.java index 151cfe5..003522a 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/UIFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/UIFrame.java @@ -37,5 +37,7 @@ public interface UIFrame { UIFrame touchUp(float screenX, float screenY, int button); + UIFrame getFrameChildUnderMouse(float screenX, float screenY); + String getName(); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java index 9b05474..e885d0b 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java @@ -542,7 +542,8 @@ public class MdxComplexInstance extends ModelInstance { this.counter += integerFrameTime; this.allowParticleSpawn = true; - if (this.floatingFrame >= interval[1]) { + final long animEnd = interval[1] - 1; + if (this.floatingFrame >= animEnd) { if ((this.sequenceLoopMode == SequenceLoopMode.ALWAYS_LOOP) || ((this.sequenceLoopMode == SequenceLoopMode.MODEL_LOOP) && (sequence.getFlags() == 0))) { this.floatingFrame = this.frame = (int) interval[0]; // TODO not cast @@ -551,7 +552,7 @@ public class MdxComplexInstance extends ModelInstance { } else if (this.sequenceLoopMode == SequenceLoopMode.LOOP_TO_NEXT_ANIMATION) { // faux queued animation // mode - final float framesPast = this.floatingFrame - interval[1]; + final float framesPast = this.floatingFrame - animEnd; final List sequences = model.sequences; this.sequence = (this.sequence + 1) % sequences.size(); @@ -562,7 +563,7 @@ public class MdxComplexInstance extends ModelInstance { this.forced = true; } else { - this.floatingFrame = this.frame = (int) interval[1]; // TODO not cast + this.floatingFrame = this.frame = (int) animEnd; // TODO not cast this.counter -= integerFrameTime; this.allowParticleSpawn = false; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/RenderDestructable.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/RenderDestructable.java index ed8b672..31e011a 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/RenderDestructable.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/RenderDestructable.java @@ -1,39 +1,42 @@ package com.etheller.warsmash.viewer5.handlers.w3x; +import com.badlogic.gdx.math.Rectangle; import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; import com.etheller.warsmash.units.manager.MutableObjectData.WorldEditorDataType; import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; +import com.etheller.warsmash.viewer5.handlers.w3x.environment.BuildingShadow; public class RenderDestructable extends RenderDoodad { - private static final War3ID TEX_FILE = War3ID.fromString("btxf"); - private static final War3ID TEX_ID = War3ID.fromString("btxi"); + private static final War3ID TEX_FILE = War3ID.fromString("btxf"); + private static final War3ID TEX_ID = War3ID.fromString("btxi"); - private final float life; + private final float life; + public Rectangle walkableBounds; - public RenderDestructable(final War3MapViewer map, final MdxModel model, final MutableGameObject row, - final com.etheller.warsmash.parsers.w3x.doo.Doodad doodad, final WorldEditorDataType type, - final float maxPitch, final float maxRoll, final float life) { - super(map, model, row, doodad, type, maxPitch, maxRoll); - this.life = life; - String replaceableTextureFile = row.getFieldAsString(TEX_FILE, 0); - final int replaceableTextureId = row.getFieldAsInteger(TEX_ID, 0); - if ((replaceableTextureFile != null) && (replaceableTextureFile.length() > 1)) { - int dotIndex = replaceableTextureFile.lastIndexOf('.'); - if (dotIndex != -1) { - replaceableTextureFile = replaceableTextureFile.substring(0, dotIndex); - } - replaceableTextureFile += ".blp"; - instance.setReplaceableTexture(replaceableTextureId, replaceableTextureFile); - } - } + public RenderDestructable(final War3MapViewer map, final MdxModel model, final MutableGameObject row, + final com.etheller.warsmash.parsers.w3x.doo.Doodad doodad, final WorldEditorDataType type, + final float maxPitch, final float maxRoll, final float life, final BuildingShadow destructableShadow) { + super(map, model, row, doodad, type, maxPitch, maxRoll); + this.life = life; + String replaceableTextureFile = row.getFieldAsString(TEX_FILE, 0); + final int replaceableTextureId = row.getFieldAsInteger(TEX_ID, 0); + if ((replaceableTextureFile != null) && (replaceableTextureFile.length() > 1)) { + final int dotIndex = replaceableTextureFile.lastIndexOf('.'); + if (dotIndex != -1) { + replaceableTextureFile = replaceableTextureFile.substring(0, dotIndex); + } + replaceableTextureFile += ".blp"; + this.instance.setReplaceableTexture(replaceableTextureId, replaceableTextureFile); + } + } - @Override - public PrimaryTag getAnimation() { - if (this.life <= 0) { - return PrimaryTag.DEATH; - } - return super.getAnimation(); - } + @Override + public PrimaryTag getAnimation() { + if (this.life <= 0) { + return PrimaryTag.DEATH; + } + return super.getAnimation(); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index 1ea4ba0..764e29c 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -83,6 +83,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderItem; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderUnit; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderUnitTypeData; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.AbilityDataUI; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CDestructable; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitClassification; @@ -115,6 +116,7 @@ public class War3MapViewer extends AbstractMdxModelViewer { private static final War3ID ITEM_FILE = War3ID.fromString("ifil"); private static final War3ID UNIT_PATHING = War3ID.fromString("upat"); private static final War3ID DESTRUCTABLE_PATHING = War3ID.fromString("bptx"); + private static final War3ID DESTRUCTABLE_PATHING_DEATH = War3ID.fromString("bptd"); private static final War3ID ELEVATION_SAMPLE_RADIUS = War3ID.fromString("uerd"); private static final War3ID MAX_PITCH = War3ID.fromString("umxp"); private static final War3ID MAX_ROLL = War3ID.fromString("umxr"); @@ -184,6 +186,7 @@ public class War3MapViewer extends AbstractMdxModelViewer { private final List selectionCircleSizes = new ArrayList<>(); private final Map unitToRenderPeer = new HashMap<>(); + private final Map destructableToRenderPeer = new HashMap<>(); private final Map unitIdToTypeData = new HashMap<>(); private GameUI gameUI; private Vector3 lightDirection; @@ -434,7 +437,8 @@ public class War3MapViewer extends AbstractMdxModelViewer { this.allObjectData = this.mapMpq.readModifications(); this.simulation = new CSimulation(this.miscData, this.allObjectData.getUnits(), - this.allObjectData.getAbilities(), new SimulationRenderController() { + this.allObjectData.getDestructibles(), this.allObjectData.getAbilities(), + new SimulationRenderController() { private final Map keyToCombatSound = new HashMap<>(); @Override @@ -551,12 +555,35 @@ public class War3MapViewer extends AbstractMdxModelViewer { War3MapViewer.this.worldScene.removeInstance(renderUnit.instance); } + @Override + public void removeDestructable(final CDestructable dest) { + final RenderDestructable renderPeer = War3MapViewer.this.destructableToRenderPeer.remove(dest); + War3MapViewer.this.doodads.remove(renderPeer); + War3MapViewer.this.worldScene.removeInstance(renderPeer.instance); + if (renderPeer.walkableBounds != null) { + War3MapViewer.this.walkableObjectsTree.remove((MdxComplexInstance) renderPeer.instance, + renderPeer.walkableBounds); + } + } + @Override public BufferedImage getBuildingPathingPixelMap(final War3ID rawcode) { return War3MapViewer.this .getBuildingPathingPixelMap(War3MapViewer.this.allObjectData.getUnits().get(rawcode)); } + @Override + public BufferedImage getDestructablePathingDeathPixelMap(final War3ID rawcode) { + return War3MapViewer.this.getDestructablePathingDeathPixelMap( + War3MapViewer.this.allObjectData.getDestructibles().get(rawcode)); + } + + @Override + public BufferedImage getDestructablePathingPixelMap(final War3ID rawcode) { + return War3MapViewer.this.getDestructablePathingPixelMap( + War3MapViewer.this.allObjectData.getDestructibles().get(rawcode)); + } + @Override public void spawnUnitConstructionFinishSound(final CUnit constructedStructure) { final UnitSound constructingBuilding = War3MapViewer.this.uiSounds @@ -620,6 +647,14 @@ public class War3MapViewer extends AbstractMdxModelViewer { loadLightsAndShading(tileset); } + protected BufferedImage getDestructablePathingPixelMap(final MutableGameObject row) { + return loadPathingTexture(row.getFieldAsString(DESTRUCTABLE_PATHING, 0)); + } + + protected BufferedImage getDestructablePathingDeathPixelMap(final MutableGameObject row) { + return loadPathingTexture(row.getFieldAsString(DESTRUCTABLE_PATHING_DEATH, 0)); + } + private void loadSounds() { this.uiSounds = new KeyedSounds(this.uiSoundsTable, this.mapMpq); } @@ -672,6 +707,9 @@ public class War3MapViewer extends AbstractMdxModelViewer { type = WorldEditorDataType.DESTRUCTIBLES; } if (row != null) { + BuildingShadow destructableShadow = null; + RemovablePathingMapInstance destructablePathing = null; + RemovablePathingMapInstance destructablePathingDeath = null; String file = row.readSLKTag("file"); final int numVar = row.readSLKTagInt("numVar"); @@ -694,28 +732,26 @@ public class War3MapViewer extends AbstractMdxModelViewer { if (type == WorldEditorDataType.DESTRUCTIBLES) { final String shadowString = row.readSLKTag("shadow"); if ((shadowString != null) && (shadowString.length() > 0) && !"_".equals(shadowString)) { - this.terrain.addShadow(shadowString, doodad.getLocation()[0], doodad.getLocation()[1]); + destructableShadow = this.terrain.addShadow(shadowString, doodad.getLocation()[0], + doodad.getLocation()[1]); } - final String pathingTexture = row.readSLKTag("pathTex"); - if ((pathingTexture != null) && (pathingTexture.length() > 0) && !"_".equals(pathingTexture)) { - - BufferedImage bufferedImage = this.filePathToPathingMap.get(pathingTexture.toLowerCase()); - if (bufferedImage == null) { - if (this.mapMpq.has(pathingTexture)) { - try { - bufferedImage = TgaFile.readTGA(pathingTexture, - this.mapMpq.getResourceAsStream(pathingTexture)); - this.filePathToPathingMap.put(pathingTexture.toLowerCase(), bufferedImage); - } - catch (final Exception exc) { - exc.printStackTrace(); - } - } + final BufferedImage destructablePathingPixelMap = getDestructablePathingPixelMap(row); + if (destructablePathingPixelMap != null) { + destructablePathing = this.terrain.pathingGrid.createRemovablePathingOverlayTexture( + doodad.getLocation()[0], doodad.getLocation()[1], + (int) Math.toDegrees(doodad.getAngle()), destructablePathingPixelMap); + if (doodad.getLife() > 0) { + destructablePathing.add(); } - if (bufferedImage != null) { - this.terrain.pathingGrid.blitRemovablePathingOverlayTexture(doodad.getLocation()[0], - doodad.getLocation()[1], (int) Math.toDegrees(doodad.getAngle()), bufferedImage); + } + final BufferedImage destructablePathingDeathPixelMap = getDestructablePathingDeathPixelMap(row); + if (destructablePathingDeathPixelMap != null) { + destructablePathingDeath = this.terrain.pathingGrid.createRemovablePathingOverlayTexture( + doodad.getLocation()[0], doodad.getLocation()[1], + (int) Math.toDegrees(doodad.getAngle()), destructablePathingDeathPixelMap); + if (doodad.getLife() <= 0) { + destructablePathingDeath.add(); } } } @@ -739,11 +775,13 @@ public class War3MapViewer extends AbstractMdxModelViewer { } if (type == WorldEditorDataType.DESTRUCTIBLES) { + final float x = doodad.getLocation()[0]; + final float y = doodad.getLocation()[1]; + this.simulation.createDestructable(row.getAlias(), x, y, destructablePathing, + destructablePathingDeath); final RenderDestructable renderDestructable = new RenderDestructable(this, model, row, doodad, type, - maxPitch, maxRoll, doodad.getLife()); + maxPitch, maxRoll, doodad.getLife(), destructableShadow); if (row.readSLKTagBoolean("walkable")) { - final float x = doodad.getLocation()[0]; - final float y = doodad.getLocation()[1]; final BoundingBox boundingBox = model.bounds.getBoundingBox(); final float minX = boundingBox.min.x + x; final float minY = boundingBox.min.y + y; @@ -751,6 +789,7 @@ public class War3MapViewer extends AbstractMdxModelViewer { boundingBox.getHeight()); this.walkableObjectsTree.add((MdxComplexInstance) renderDestructable.instance, renderDestructableBounds); + renderDestructable.walkableBounds = renderDestructableBounds; } this.doodads.add(renderDestructable); } @@ -1035,10 +1074,10 @@ public class War3MapViewer extends AbstractMdxModelViewer { if (type == WorldEditorDataType.UNITS) { final float angle = (float) Math.toDegrees(unitAngle); final CUnit simulationUnit = this.simulation.createUnit(row.getAlias(), playerIndex, unitX, unitY, - angle, buildingPathingPixelMap, pathingInstance, buildingShadowInstance); + angle, buildingPathingPixelMap, pathingInstance); final RenderUnitTypeData typeData = getUnitTypeData(unitId, row); final RenderUnit renderUnit = new RenderUnit(this, model, row, unitX, unitY, unitZ, playerIndex, - soundset, portraitModel, simulationUnit, typeData, specialArtModel); + soundset, portraitModel, simulationUnit, typeData, specialArtModel, buildingShadowInstance); this.unitToRenderPeer.put(simulationUnit, renderUnit); this.units.add(renderUnit); if (unitShadowSplat != null) { @@ -1104,8 +1143,13 @@ public class War3MapViewer extends AbstractMdxModelViewer { } private BufferedImage getBuildingPathingPixelMap(final MutableGameObject row) { - BufferedImage buildingPathingPixelMap = null; final String pathingTexture = row.getFieldAsString(UNIT_PATHING, 0); + final BufferedImage buildingPathingPixelMap = loadPathingTexture(pathingTexture); + return buildingPathingPixelMap; + } + + private BufferedImage loadPathingTexture(final String pathingTexture) { + BufferedImage buildingPathingPixelMap = null; if ((pathingTexture != null) && (pathingTexture.length() > 0) && !"_".equals(pathingTexture)) { buildingPathingPixelMap = this.filePathToPathingMap.get(pathingTexture.toLowerCase()); if (buildingPathingPixelMap == null) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java index 8ff07ca..10eac37 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java @@ -186,6 +186,12 @@ public class PathingGrid { return removablePathingMapInstance; } + public RemovablePathingMapInstance createRemovablePathingOverlayTexture(final float positionX, + final float positionY, final int rotationInput, final BufferedImage pathingTextureTga) { + return new RemovablePathingMapInstance(positionX, + positionY, rotationInput, pathingTextureTga); + } + public int getWidth() { return this.pathingGridSizes[0]; } @@ -452,5 +458,10 @@ public class PathingGrid { instance.blit(); } } + + public void add() { + PathingGrid.this.dynamicPathingInstances.add(this); + blit(); + } } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java index 965a54e..e1fb4e6 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java @@ -19,6 +19,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; import com.etheller.warsmash.viewer5.handlers.w3x.SplatModel.SplatMover; import com.etheller.warsmash.viewer5.handlers.w3x.UnitSoundset; import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; +import com.etheller.warsmash.viewer5.handlers.w3x.environment.BuildingShadow; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.MovementType; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.AbilityDataUI; @@ -50,6 +51,7 @@ public class RenderUnit { public int playerIndex; private final CUnit simulationUnit; public SplatMover shadow; + private BuildingShadow buildingShadowInstance; public SplatMover selectionCircle; private float facing; @@ -72,11 +74,12 @@ public class RenderUnit { public RenderUnit(final War3MapViewer map, final MdxModel model, final MutableGameObject row, final float x, final float y, final float z, final int playerIndex, final UnitSoundset soundset, final MdxModel portraitModel, final CUnit simulationUnit, final RenderUnitTypeData typeData, - final MdxModel specialArtModel) { + final MdxModel specialArtModel, final BuildingShadow buildingShadow) { this.portraitModel = portraitModel; this.simulationUnit = simulationUnit; this.typeData = typeData; this.specialArtModel = specialArtModel; + this.buildingShadowInstance = buildingShadow; final MdxComplexInstance instance = (MdxComplexInstance) model.addInstance(); this.location[0] = x; @@ -257,6 +260,10 @@ public class RenderUnit { this.shadow.destroy(Gdx.gl30, map.terrain.centerOffset); this.shadow = null; } + if (this.buildingShadowInstance != null) { + this.buildingShadowInstance.remove(); + this.buildingShadowInstance = null; + } if (this.uberSplat != null) { this.uberSplat.destroy(Gdx.gl30, map.terrain.centerOffset); this.uberSplat = null; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CDestructable.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CDestructable.java index 854824d..7f03004 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CDestructable.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CDestructable.java @@ -2,14 +2,24 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation; import java.util.EnumSet; +import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.RemovablePathingMapInstance; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTargetVisitor; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; public class CDestructable extends CWidget { - public CDestructable(final int handleId, final float x, final float y, final float life) { + private final CDestructableType destType; + private final RemovablePathingMapInstance pathingInstance; + private final RemovablePathingMapInstance pathingInstanceDeath; + + public CDestructable(final int handleId, final float x, final float y, final float life, + final CDestructableType destTypeInstance, final RemovablePathingMapInstance pathingInstance, + final RemovablePathingMapInstance pathingInstanceDeath) { super(handleId, x, y, life); + this.destType = destTypeInstance; + this.pathingInstance = pathingInstance; + this.pathingInstanceDeath = pathingInstanceDeath; } @Override @@ -31,6 +41,18 @@ public class CDestructable extends CWidget { @Override public boolean canBeTargetedBy(final CSimulation simulation, final CUnit source, final EnumSet targetsAllowed) { + if (targetsAllowed.containsAll(this.destType.getTargetedAs())) { + if (isDead()) { + return targetsAllowed.contains(CTargetType.DEAD); + } + else { + return !targetsAllowed.contains(CTargetType.DEAD) || targetsAllowed.contains(CTargetType.ALIVE); + } + } + else { + System.err.println("No targeting because " + targetsAllowed + " does not contain all of " + + this.destType.getTargetedAs()); + } return false; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CDestructableType.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CDestructableType.java new file mode 100644 index 0000000..4cb7182 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CDestructableType.java @@ -0,0 +1,57 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation; + +import java.awt.image.BufferedImage; +import java.util.EnumSet; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; + +public class CDestructableType { + + private final String name; + private final int life; + private final EnumSet targetedAs; + private final String armorType; + private final int buildTime; + private final BufferedImage pathingPixelMap; + private final BufferedImage pathingDeathPixelMap; + + public CDestructableType(final String name, final int life, final EnumSet targetedAs, + final String armorType, final int buildTime, final BufferedImage pathingPixelMap, + final BufferedImage pathingDeathPixelMap) { + this.name = name; + this.life = life; + this.targetedAs = targetedAs; + this.armorType = armorType; + this.buildTime = buildTime; + this.pathingPixelMap = pathingPixelMap; + this.pathingDeathPixelMap = pathingDeathPixelMap; + } + + public String getName() { + return this.name; + } + + public int getLife() { + return this.life; + } + + public EnumSet getTargetedAs() { + return this.targetedAs; + } + + public String getArmorType() { + return this.armorType; + } + + public int getBuildTime() { + return this.buildTime; + } + + public BufferedImage getPathingPixelMap() { + return this.pathingPixelMap; + } + + public BufferedImage getPathingDeathPixelMap() { + return this.pathingDeathPixelMap; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java index 190951f..eb71848 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java @@ -15,7 +15,6 @@ import com.etheller.warsmash.units.DataTable; import com.etheller.warsmash.units.manager.MutableObjectData; import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.util.WarsmashConstants; -import com.etheller.warsmash.viewer5.handlers.w3x.environment.BuildingShadow; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.RemovablePathingMapInstance; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; @@ -24,6 +23,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUni import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackMissile; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.projectile.CAttackProjectile; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.data.CAbilityData; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.data.CDestructableData; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.data.CUnitData; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.pathing.CPathfindingProcessor; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CAllianceType; @@ -36,8 +36,10 @@ import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.CommandErrorListene public class CSimulation { private final CAbilityData abilityData; private final CUnitData unitData; + private final CDestructableData destructableData; private final List units; private final List newUnits; + private final List destructables; private final List players; private final List projectiles; private final List newProjectiles; @@ -51,20 +53,24 @@ public class CSimulation { private final Random seededRandom; private float currentGameDayTimeElapsed; private final Map handleIdToUnit = new HashMap<>(); + private final Map handleIdToDestructable = new HashMap<>(); private final Map handleIdToAbility = new HashMap<>(); private transient CommandErrorListener commandErrorListener; public CSimulation(final DataTable miscData, final MutableObjectData parsedUnitData, - final MutableObjectData parsedAbilityData, final SimulationRenderController simulationRenderController, - final PathingGrid pathingGrid, final Rectangle entireMapBounds, final Random seededRandom, - final List playerInfos, final CommandErrorListener commandErrorListener) { + final MutableObjectData parsedDestructableData, final MutableObjectData parsedAbilityData, + final SimulationRenderController simulationRenderController, final PathingGrid pathingGrid, + final Rectangle entireMapBounds, final Random seededRandom, final List playerInfos, + final CommandErrorListener commandErrorListener) { this.gameplayConstants = new CGameplayConstants(miscData); this.simulationRenderController = simulationRenderController; this.pathingGrid = pathingGrid; this.abilityData = new CAbilityData(parsedAbilityData); this.unitData = new CUnitData(parsedUnitData, this.abilityData, this.simulationRenderController); + this.destructableData = new CDestructableData(parsedDestructableData, simulationRenderController); this.units = new ArrayList<>(); this.newUnits = new ArrayList<>(); + this.destructables = new ArrayList<>(); this.projectiles = new ArrayList<>(); this.newProjectiles = new ArrayList<>(); this.handleIdAllocator = new HandleIdAllocator(); @@ -117,15 +123,24 @@ public class CSimulation { public CUnit createUnit(final War3ID typeId, final int playerIndex, final float x, final float y, final float facing, final BufferedImage buildingPathingPixelMap, - final RemovablePathingMapInstance pathingInstance, final BuildingShadow buildingShadowInstance) { + final RemovablePathingMapInstance pathingInstance) { final CUnit unit = this.unitData.create(this, playerIndex, typeId, x, y, facing, buildingPathingPixelMap, - this.handleIdAllocator, pathingInstance, buildingShadowInstance); + this.handleIdAllocator, pathingInstance); this.newUnits.add(unit); this.handleIdToUnit.put(unit.getHandleId(), unit); this.worldCollision.addUnit(unit); return unit; } + public CDestructable createDestructable(final War3ID typeId, final float x, final float y, + final RemovablePathingMapInstance pathingInstance, final RemovablePathingMapInstance pathingInstanceDeath) { + final CDestructable dest = this.destructableData.create(this, typeId, x, y, this.handleIdAllocator, + pathingInstance, pathingInstanceDeath); + this.handleIdToDestructable.put(dest.getHandleId(), dest); + this.destructables.add(dest); + return dest; + } + public CUnit createUnit(final War3ID typeId, final int playerIndex, final float x, final float y, final float facing) { return this.simulationRenderController.createUnit(this, typeId, playerIndex, x, y, facing); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java index bfe696d..edcbc29 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java @@ -13,7 +13,6 @@ import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.util.WarsmashConstants; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; -import com.etheller.warsmash.viewer5.handlers.w3x.environment.BuildingShadow; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.RemovablePathingMapInstance; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitStateListener.CUnitStateNotifier; @@ -64,7 +63,6 @@ public class CUnit extends CWidget { private Rectangle collisionRectangle; private RemovablePathingMapInstance pathingInstance; - private BuildingShadow buildingShadowInstance; private final EnumSet classifications = EnumSet.noneOf(CUnitClassification.class); @@ -93,7 +91,7 @@ public class CUnit extends CWidget { private boolean paused = false; private boolean acceptingOrders = true; private boolean invulnerable = false; - private boolean holdingPosition = false; + private CBehavior defaultBehavior; private COrder currentOrder = null; private CUnit workerInside; private final War3ID[] buildQueue = new War3ID[WarsmashConstants.BUILD_QUEUE_SIZE]; @@ -106,7 +104,7 @@ public class CUnit extends CWidget { public CUnit(final int handleId, final int playerIndex, final float x, final float y, final float life, final War3ID typeId, final float facing, final float mana, final int maximumLife, final int maximumMana, final int speed, final int defense, final CUnitType unitType, - final RemovablePathingMapInstance pathingInstance, final BuildingShadow buildingShadowInstance) { + final RemovablePathingMapInstance pathingInstance) { super(handleId, x, y, life); this.playerIndex = playerIndex; this.typeId = typeId; @@ -117,13 +115,13 @@ public class CUnit extends CWidget { this.speed = speed; this.defense = defense; this.pathingInstance = pathingInstance; - this.buildingShadowInstance = buildingShadowInstance; this.flyHeight = unitType.getDefaultFlyingHeight(); this.unitType = unitType; this.classifications.addAll(unitType.getClassifications()); this.acquisitionRange = unitType.getDefaultAcquisitionRange(); this.stopBehavior = new CBehaviorStop(this); - this.currentBehavior = this.stopBehavior; + this.defaultBehavior = this.stopBehavior; + this.currentBehavior = this.defaultBehavior; } public void setUnitAnimationListener(final CUnitAnimationListener unitAnimationListener) { @@ -307,14 +305,20 @@ public class CUnit extends CWidget { } } } + for (final CAbility ability : this.abilities) { + ability.onTick(game, this); + } if (this.currentBehavior != null) { final CBehavior lastBehavior = this.currentBehavior; + final int lastBehaviorHighlightOrderId = lastBehavior.getHighlightOrderId(); this.currentBehavior = this.currentBehavior.update(game); if (lastBehavior != this.currentBehavior) { lastBehavior.end(game); this.currentBehavior.begin(game); } - if (this.currentBehavior.getHighlightOrderId() != lastBehavior.getHighlightOrderId()) { + if (this.currentBehavior.getHighlightOrderId() != lastBehaviorHighlightOrderId) { + System.out.println("order ID change detected from " + + this.currentBehavior.getHighlightOrderId() + " to " + lastBehaviorHighlightOrderId); this.stateNotifier.ordersChanged(); } } @@ -387,7 +391,7 @@ public class CUnit extends CWidget { this.stateNotifier.waypointsChanged(); } else { - setHoldingPosition(false); + setDefaultBehavior(this.stopBehavior); if (this.currentBehavior != null) { this.currentBehavior.end(game); } @@ -408,12 +412,7 @@ public class CUnit extends CWidget { nextBehavior = order.begin(game, this); } else { - if (this.holdingPosition) { - nextBehavior = this.holdPositionBehavior; - } - else { - nextBehavior = this.stopBehavior; - } + nextBehavior = this.defaultBehavior; } return nextBehavior; } @@ -636,7 +635,7 @@ public class CUnit extends CWidget { } } else { - if (this.currentBehavior == null) { + if ((this.currentBehavior == null) || (this.currentBehavior == this.defaultBehavior)) { if (!simulation.getPlayer(getPlayerIndex()).hasAlliance(source.getPlayerIndex(), CAllianceType.PASSIVE)) { for (final CUnitAttack attack : this.unitType.getAttacks()) { @@ -667,10 +666,6 @@ public class CUnit extends CWidget { this.pathingInstance.remove(); this.pathingInstance = null; } - if (this.buildingShadowInstance != null) { - this.buildingShadowInstance.remove(); - this.buildingShadowInstance = null; - } popoutWorker(simulation); final CPlayer player = simulation.getPlayer(this.playerIndex); if (this.foodMade != 0) { @@ -909,10 +904,10 @@ public class CUnit extends CWidget { } public CBehavior pollNextOrderBehavior(final CSimulation game) { - if (this.holdingPosition) { + if (this.defaultBehavior != this.stopBehavior) { // kind of a stupid hack, meant to align in feel with some behaviors that were // observed on War3 - return this.holdPositionBehavior; + return this.defaultBehavior; } final COrder order = this.orderQueue.poll(); final CBehavior nextOrderBehavior = beginOrder(game, order); @@ -1194,6 +1189,10 @@ public class CUnit extends CWidget { return delta; } + public void setDefaultBehavior(final CBehavior defaultBehavior) { + this.defaultBehavior = defaultBehavior; + } + public int getGold() { for (final CAbility ability : this.abilities) { if (ability instanceof CAbilityGoldMine) { @@ -1211,10 +1210,6 @@ public class CUnit extends CWidget { } } - public void setHoldingPosition(final boolean holdingPosition) { - this.holdingPosition = holdingPosition; - } - public Queue getOrderQueue() { return this.orderQueue; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java index 6cb05de..5015faa 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java @@ -19,6 +19,12 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.pathing.CBuildingPa */ public class CUnitType { private final String name; + private final int life; + private final int manaInitial; + private final int manaMaximum; + private final int speed; + private final int defense; + private final String abilityList; private final boolean building; private final PathingGrid.MovementType movementType; private final float defaultFlyingHeight; @@ -50,7 +56,8 @@ public class CUnitType { private final EnumSet preventedPathingTypes; private final EnumSet requiredPathingTypes; - public CUnitType(final String name, final boolean isBldg, final MovementType movementType, + public CUnitType(final String name, final int life, final int manaInitial, final int manaMaximum, final int speed, + final int defense, final String abilityList, final boolean isBldg, final MovementType movementType, final float defaultFlyingHeight, final float collisionSize, final EnumSet classifications, final List attacks, final String armorType, final boolean raise, final boolean decay, final CDefenseType defenseType, final float impactZ, @@ -61,6 +68,12 @@ public class CUnitType { final EnumSet preventedPathingTypes, final EnumSet requiredPathingTypes) { this.name = name; + this.life = life; + this.manaInitial = manaInitial; + this.manaMaximum = manaMaximum; + this.speed = speed; + this.defense = defense; + this.abilityList = abilityList; this.building = isBldg; this.movementType = movementType; this.defaultFlyingHeight = defaultFlyingHeight; @@ -94,6 +107,30 @@ public class CUnitType { return this.name; } + public int getLife() { + return this.life; + } + + public int getManaInitial() { + return this.manaInitial; + } + + public int getManaMaximum() { + return this.manaMaximum; + } + + public int getSpeed() { + return this.speed; + } + + public int getDefense() { + return this.defense; + } + + public String getAbilityList() { + return this.abilityList; + } + public float getDefaultFlyingHeight() { return this.defaultFlyingHeight; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbility.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbility.java index 12a4cf0..a566f7c 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbility.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbility.java @@ -13,6 +13,8 @@ public interface CAbility extends CAbilityView { /* should fire when ability removed from unit */ void onRemove(CSimulation game, CUnit unit); + void onTick(CSimulation game, CUnit unit); + /* return false to not do anything, such as for toggling autocast */ boolean checkBeforeQueue(CSimulation game, CUnit caster, int orderId); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java index d675673..d72a024 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java @@ -106,6 +106,10 @@ public class CAbilityAttack extends AbstractCAbility { public void onRemove(final CSimulation game, final CUnit unit) { } + @Override + public void onTick(final CSimulation game, final CUnit unit) { + } + @Override public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final CWidget target) { CBehavior behavior = null; @@ -126,6 +130,9 @@ public class CAbilityAttack extends AbstractCAbility { final AbilityPointTarget point) { switch (orderId) { case OrderIds.attack: + if (caster.getMoveBehavior() == null) { + return caster.pollNextOrderBehavior(game); + } return caster.getMoveBehavior().reset(OrderIds.attack, point); case OrderIds.attackground: CBehavior behavior = null; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityGeneric.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityGeneric.java index d3add11..65945b3 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityGeneric.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityGeneric.java @@ -61,6 +61,10 @@ public class CAbilityGeneric extends AbstractCAbility { public void onRemove(final CSimulation game, final CUnit unit) { } + @Override + public void onTick(final CSimulation game, final CUnit unit) { + } + @Override public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId) { return false; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityMove.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityMove.java index 77c2f20..52cb431 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityMove.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityMove.java @@ -86,6 +86,10 @@ public class CAbilityMove extends AbstractCAbility { } + @Override + public void onTick(final CSimulation game, final CUnit unit) { + } + @Override public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId) { return true; @@ -100,7 +104,9 @@ public class CAbilityMove extends AbstractCAbility { public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final AbilityPointTarget point) { if (orderId == OrderIds.patrol) { - return caster.getPatrolBehavior().reset(point); + final CBehavior patrolBehavior = caster.getPatrolBehavior().reset(point); + caster.setDefaultBehavior(patrolBehavior); + return patrolBehavior; } else { return caster.getMoveBehavior().reset(OrderIds.move, point); @@ -110,7 +116,7 @@ public class CAbilityMove extends AbstractCAbility { @Override public CBehavior beginNoTarget(final CSimulation game, final CUnit caster, final int orderId) { if (orderId == OrderIds.holdposition) { - caster.setHoldingPosition(true); + caster.setDefaultBehavior(caster.getHoldPositionBehavior()); } return caster.pollNextOrderBehavior(game); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java index 1fb4116..78db5ac 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java @@ -93,4 +93,8 @@ public abstract class AbstractCAbilityBuild extends AbstractCAbility implements public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId) { return true; } + + @Override + public void onTick(final CSimulation game, final CUnit unit) { + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityBuildInProgress.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityBuildInProgress.java index 60cafd6..e161795 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityBuildInProgress.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityBuildInProgress.java @@ -28,6 +28,10 @@ public class CAbilityBuildInProgress extends AbstractCAbility { } + @Override + public void onTick(final CSimulation game, final CUnit unit) { + } + @Override public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId) { final CPlayer player = game.getPlayer(caster.getPlayerIndex()); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/combat/CAbilityColdArrows.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/combat/CAbilityColdArrows.java index 3d8f780..a878277 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/combat/CAbilityColdArrows.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/combat/CAbilityColdArrows.java @@ -85,6 +85,10 @@ public class CAbilityColdArrows extends AbstractCAbility { public void onRemove(final CSimulation game, final CUnit unit) { } + @Override + public void onTick(final CSimulation game, final CUnit unit) { + } + @Override public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId) { switch (orderId) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityHarvest.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityHarvest.java index 849c83d..ef56125 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityHarvest.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityHarvest.java @@ -43,6 +43,10 @@ public class CAbilityHarvest extends AbstractGenericSingleIconActiveAbility { public void onRemove(final CSimulation game, final CUnit unit) { } + @Override + public void onTick(final CSimulation game, final CUnit unit) { + } + @Override public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final CWidget target) { return this.behaviorHarvest.reset(target); @@ -56,12 +60,15 @@ public class CAbilityHarvest extends AbstractGenericSingleIconActiveAbility { @Override public CBehavior beginNoTarget(final CSimulation game, final CUnit caster, final int orderId) { + if (isToggleOn() && (orderId == OrderIds.returnresources)) { + return this.behaviorReturnResources.reset(game); + } return caster.pollNextOrderBehavior(game); } @Override public int getBaseOrderId() { - return OrderIds.harvest; + return isToggleOn() ? OrderIds.returnresources : OrderIds.harvest; } @Override @@ -121,7 +128,12 @@ public class CAbilityHarvest extends AbstractGenericSingleIconActiveAbility { @Override protected void innerCheckCanTargetNoTarget(final CSimulation game, final CUnit unit, final int orderId, final AbilityTargetCheckReceiver receiver) { - + if ((orderId == OrderIds.returnresources) && isToggleOn()) { + receiver.targetOk(null); + } + else { + receiver.orderIdNotAccepted(); + } } public int getDamageToTree() { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityReturnResources.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityReturnResources.java index 24695ad..c069acc 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityReturnResources.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityReturnResources.java @@ -35,6 +35,10 @@ public class CAbilityReturnResources extends AbstractGenericNoIconAbility { } + @Override + public void onTick(final CSimulation game, final CUnit unit) { + } + @Override public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final CWidget target) { return null; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/mine/CAbilityGoldMine.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/mine/CAbilityGoldMine.java index d3b16fe..6cf532a 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/mine/CAbilityGoldMine.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/mine/CAbilityGoldMine.java @@ -1,20 +1,26 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.mine; +import java.util.ArrayList; +import java.util.List; + import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.SecondaryTag; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic.AbstractGenericNoIconAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.harvest.CBehaviorHarvest; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; public class CAbilityGoldMine extends AbstractGenericNoIconAbility { private int gold; - private int activeMiners; private final float miningDuration; private final int miningCapacity; + private final List activeMiners; + private final boolean wasEmpty; public CAbilityGoldMine(final int handleId, final War3ID alias, final int maxGold, final float miningDuration, final int miningCapacity) { @@ -22,6 +28,8 @@ public class CAbilityGoldMine extends AbstractGenericNoIconAbility { this.gold = maxGold; this.miningDuration = miningDuration; this.miningCapacity = miningCapacity; + this.activeMiners = new ArrayList<>(); + this.wasEmpty = this.activeMiners.isEmpty(); } @Override @@ -34,6 +42,32 @@ public class CAbilityGoldMine extends AbstractGenericNoIconAbility { } + @Override + public void onTick(final CSimulation game, final CUnit unit) { + final boolean empty = this.activeMiners.isEmpty(); + if (empty != this.wasEmpty) { + if (empty) { + unit.getUnitAnimationListener().removeSecondaryTag(SecondaryTag.WORK); + } + else { + unit.getUnitAnimationListener().addSecondaryTag(SecondaryTag.WORK); + } + } + for (int i = this.activeMiners.size() - 1; i >= 0; i--) { + final CBehaviorHarvest activeMiner = this.activeMiners.get(i); + if (game.getGameTurnTick() >= activeMiner.getPopoutFromMineTurnTick()) { + + final int goldMined = Math.min(this.gold, activeMiner.getGoldCapacity()); + this.gold -= goldMined; + if (this.gold <= 0) { + unit.setLife(game, 0); + } + activeMiner.popoutFromMine(goldMined); + this.activeMiners.remove(i); + } + } + } + @Override public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final CWidget target) { return null; @@ -82,12 +116,12 @@ public class CAbilityGoldMine extends AbstractGenericNoIconAbility { this.gold = gold; } - public int getActiveMiners() { - return this.activeMiners; + public int getActiveMinerCount() { + return this.activeMiners.size(); } - public void setActiveMiners(final int activeMiners) { - this.activeMiners = activeMiners; + public void addMiner(final CBehaviorHarvest miner) { + this.activeMiners.add(miner); } public int getMiningCapacity() { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityQueue.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityQueue.java index 22c066c..e868633 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityQueue.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityQueue.java @@ -114,6 +114,10 @@ public final class CAbilityQueue extends AbstractCAbility { } + @Override + public void onTick(final CSimulation game, final CUnit unit) { + } + @Override public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final CWidget target) { return null; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityRally.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityRally.java index 72a6465..3609354 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityRally.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityRally.java @@ -27,6 +27,10 @@ public class CAbilityRally extends AbstractCAbility { } + @Override + public void onTick(final CSimulation game, final CUnit unit) { + } + @Override protected void innerCheckCanUse(final CSimulation game, final CUnit unit, final int orderId, final AbilityActivationReceiver receiver) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorPatrol.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorPatrol.java index 004d8d7..fa4c097 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorPatrol.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorPatrol.java @@ -10,6 +10,7 @@ public class CBehaviorPatrol implements CRangedBehavior { private final CUnit unit; private AbilityPointTarget target; private AbilityPointTarget startPoint; + private boolean justAutoAttacked = false; public CBehaviorPatrol(final CUnit unit) { this.unit = unit; @@ -28,12 +29,19 @@ public class CBehaviorPatrol implements CRangedBehavior { @Override public boolean isWithinRange(final CSimulation simulation) { - return this.unit.distance(this.target.x, this.target.y) <= simulation.getGameplayConstants() - .getCloseEnoughRange(); // TODO this is not how it was meant to be used + if (this.justAutoAttacked = this.unit.autoAcquireAttackTargets(simulation, false)) { + // kind of a hack + return true; + } + return this.unit.distance(this.target.x, this.target.y) <= 16f; // TODO this is not how it was meant to be used } @Override public CBehavior update(final CSimulation simulation) { + if (this.justAutoAttacked) { + this.justAutoAttacked = false; + return this.unit.getCurrentBehavior(); + } final AbilityPointTarget temp = this.target; this.target = this.startPoint; this.startPoint = temp; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/harvest/CBehaviorHarvest.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/harvest/CBehaviorHarvest.java index c27b89a..864a63a 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/harvest/CBehaviorHarvest.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/harvest/CBehaviorHarvest.java @@ -65,72 +65,49 @@ public class CBehaviorHarvest extends CAbstractRangedBehavior implements Ability @Override public CBehavior accept(final CUnit target) { - if (this.popoutFromMineTurnTick == 0) { - if ((this.abilityHarvest.getCarriedResourceAmount() == 0) - || (this.abilityHarvest.getCarriedResourceType() != ResourceType.GOLD)) { - for (final CAbility ability : target.getAbilities()) { - if (ability instanceof CAbilityGoldMine) { - final CAbilityGoldMine abilityGoldMine = (CAbilityGoldMine) ability; - final int activeMiners = abilityGoldMine.getActiveMiners(); - if (activeMiners < abilityGoldMine.getMiningCapacity()) { - abilityGoldMine.setActiveMiners(activeMiners + 1); - if (activeMiners == 0) { - target.getUnitAnimationListener().addSecondaryTag(SecondaryTag.WORK); - } - this.unit.setHidden(true); - this.unit.setInvulnerable(true); - this.popoutFromMineTurnTick = this.simulation.getGameTurnTick() - + (int) (abilityGoldMine.getMiningDuration() - / WarsmashConstants.SIMULATION_STEP_TIME); - this.abilityGoldMine = abilityGoldMine; - } - else { - // we are stuck waiting to mine, let's make sure we play stand animation - this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.STAND, - SequenceUtils.EMPTY, 1.0f, true); - } - return this; + if ((this.abilityHarvest.getCarriedResourceAmount() == 0) + || (this.abilityHarvest.getCarriedResourceType() != ResourceType.GOLD)) { + for (final CAbility ability : target.getAbilities()) { + if (ability instanceof CAbilityGoldMine) { + final CAbilityGoldMine abilityGoldMine = (CAbilityGoldMine) ability; + final int activeMiners = abilityGoldMine.getActiveMinerCount(); + if (activeMiners < abilityGoldMine.getMiningCapacity()) { + abilityGoldMine.addMiner(this); + this.unit.setHidden(true); + this.unit.setInvulnerable(true); + this.unit.setPaused(true); + this.popoutFromMineTurnTick = this.simulation.getGameTurnTick() + + (int) (abilityGoldMine.getMiningDuration() / WarsmashConstants.SIMULATION_STEP_TIME); + this.abilityGoldMine = abilityGoldMine; } + else { + // we are stuck waiting to mine, let's make sure we play stand animation + this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.STAND, SequenceUtils.EMPTY, + 1.0f, true); + } + return this; } - // weird invalid target and we have no resources, consider harvesting done - return this.unit.pollNextOrderBehavior(this.simulation); - } - else { - // we have some GOLD and we're not in a mine (?) lets do a return resources - // order - return this.abilityHarvest.getBehaviorReturnResources().reset(this.simulation); } + // weird invalid target and we have no resources, consider harvesting done + return this.unit.pollNextOrderBehavior(this.simulation); } else { - if (this.simulation.getGameTurnTick() >= this.popoutFromMineTurnTick) { - this.popoutFromMineTurnTick = 0; - this.unit.setHidden(false); - this.unit.setInvulnerable(false); - final int activeMiners = this.abilityGoldMine.getActiveMiners() - 1; - this.abilityGoldMine.setActiveMiners(activeMiners); - if (activeMiners == 0) { - target.getUnitAnimationListener().removeSecondaryTag(SecondaryTag.WORK); - } - int mineGoldRemaining = this.abilityGoldMine.getGold(); - final int goldMined = Math.min(mineGoldRemaining, this.abilityHarvest.getGoldCapacity()); - this.abilityHarvest.setCarriedResources(ResourceType.GOLD, goldMined); - mineGoldRemaining -= goldMined; - this.abilityGoldMine.setGold(mineGoldRemaining); - if (mineGoldRemaining <= 0) { - target.setLife(this.simulation, 0); - } - this.unit.getUnitAnimationListener().addSecondaryTag(SecondaryTag.GOLD); - this.simulation.unitRepositioned(this.unit); - // we just finished getting gold, lets do a return resources order - return this.abilityHarvest.getBehaviorReturnResources().reset(this.simulation); - } - else { - // continue working inside mine - return this; - } + // we have some GOLD and we're not in a mine (?) lets do a return resources + // order + return this.abilityHarvest.getBehaviorReturnResources().reset(this.simulation); } } + public void popoutFromMine(final int goldMined) { + this.popoutFromMineTurnTick = 0; + this.unit.setHidden(false); + this.unit.setInvulnerable(false); + this.unit.setPaused(false); + this.abilityHarvest.setCarriedResources(ResourceType.GOLD, goldMined); + this.unit.getUnitAnimationListener().addSecondaryTag(SecondaryTag.GOLD); + this.simulation.unitRepositioned(this.unit); + } + @Override public CBehavior accept(final CDestructable target) { // TODO cut trees! @@ -167,4 +144,12 @@ public class CBehaviorHarvest extends CAbstractRangedBehavior implements Ability } + public int getPopoutFromMineTurnTick() { + return this.popoutFromMineTurnTick; + } + + public int getGoldCapacity() { + return this.abilityHarvest.getGoldCapacity(); + } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CDestructableData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CDestructableData.java new file mode 100644 index 0000000..62e7713 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CDestructableData.java @@ -0,0 +1,87 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.data; + +import java.awt.image.BufferedImage; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; + +import com.etheller.warsmash.units.manager.MutableObjectData; +import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; +import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.RemovablePathingMapInstance; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CDestructable; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CDestructableType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.HandleIdAllocator; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.SimulationRenderController; + +public class CDestructableData { + private static final War3ID NAME = War3ID.fromString("bnam"); + private static final War3ID HIT_POINT_MAXIMUM = War3ID.fromString("bhps"); + private static final War3ID TARGETED_AS = War3ID.fromString("btar"); + private static final War3ID ARMOR_TYPE = War3ID.fromString("barm"); + + private static final War3ID BUILD_TIME = War3ID.fromString("bbut"); + private static final War3ID REPAIR_TIME = War3ID.fromString("bret"); + private static final War3ID GOLD_REPAIR = War3ID.fromString("breg"); + private static final War3ID LUMBER_REPAIR = War3ID.fromString("brel"); + + private final MutableObjectData unitData; + private final Map unitIdToUnitType = new HashMap<>(); + private final SimulationRenderController simulationRenderController; + + public CDestructableData(final MutableObjectData unitData, + final SimulationRenderController simulationRenderController) { + this.unitData = unitData; + this.simulationRenderController = simulationRenderController; + } + + public CDestructable create(final CSimulation simulation, final War3ID typeId, final float x, final float y, + final HandleIdAllocator handleIdAllocator, final RemovablePathingMapInstance pathingInstance, + final RemovablePathingMapInstance pathingInstanceDeath) { + final MutableGameObject unitType = this.unitData.get(typeId); + final int handleId = handleIdAllocator.createId(); + + final CDestructableType unitTypeInstance = getUnitTypeInstance(typeId, unitType); + + final int life = unitTypeInstance.getLife(); + + final CDestructable destructable = new CDestructable(handleId, x, y, life, unitTypeInstance, pathingInstance, + pathingInstanceDeath); + return destructable; + } + + private CDestructableType getUnitTypeInstance(final War3ID typeId, final MutableGameObject unitType) { + CDestructableType unitTypeInstance = this.unitIdToUnitType.get(typeId); + if (unitTypeInstance == null) { + final BufferedImage buildingPathingPixelMap = this.simulationRenderController + .getDestructablePathingPixelMap(typeId); + final BufferedImage buildingPathingDeathPixelMap = this.simulationRenderController + .getDestructablePathingDeathPixelMap(typeId); + final String name = unitType.getFieldAsString(NAME, 0); + final int life = unitType.getFieldAsInteger(HIT_POINT_MAXIMUM, 0); + final EnumSet targetedAs = CTargetType + .parseTargetTypeSet(unitType.getFieldAsString(TARGETED_AS, 0)); + final String armorType = unitType.getFieldAsString(ARMOR_TYPE, 0); + final int buildTime = unitType.getFieldAsInteger(BUILD_TIME, 0); + + unitTypeInstance = new CDestructableType(name, life, targetedAs, armorType, buildTime, + buildingPathingPixelMap, buildingPathingDeathPixelMap); + this.unitIdToUnitType.put(typeId, unitTypeInstance); + } + return unitTypeInstance; + } + + public CDestructableType getUnitType(final War3ID rawcode) { + final CDestructableType unitTypeInstance = this.unitIdToUnitType.get(rawcode); + if (unitTypeInstance != null) { + return unitTypeInstance; + } + final MutableGameObject unitType = this.unitData.get(rawcode); + if (unitType == null) { + return null; + } + return getUnitTypeInstance(rawcode, unitType); + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java index 122d456..7b4ce48 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java @@ -10,7 +10,6 @@ import java.util.Map; import com.etheller.warsmash.units.manager.MutableObjectData; import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; import com.etheller.warsmash.util.War3ID; -import com.etheller.warsmash.viewer5.handlers.w3x.environment.BuildingShadow; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.RemovablePathingMapInstance; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; @@ -161,21 +160,19 @@ public class CUnitData { public CUnit create(final CSimulation simulation, final int playerIndex, final War3ID typeId, final float x, final float y, final float facing, final BufferedImage buildingPathingPixelMap, - final HandleIdAllocator handleIdAllocator, final RemovablePathingMapInstance pathingInstance, - final BuildingShadow buildingShadowInstance) { + final HandleIdAllocator handleIdAllocator, final RemovablePathingMapInstance pathingInstance) { final MutableGameObject unitType = this.unitData.get(typeId); final int handleId = handleIdAllocator.createId(); - final int life = unitType.getFieldAsInteger(HIT_POINT_MAXIMUM, 0); - final int manaInitial = unitType.getFieldAsInteger(MANA_INITIAL_AMOUNT, 0); - final int manaMaximum = unitType.getFieldAsInteger(MANA_MAXIMUM, 0); - final int speed = unitType.getFieldAsInteger(MOVEMENT_SPEED_BASE, 0); - final int defense = unitType.getFieldAsInteger(DEFENSE, 0); - final String abilityList = unitType.getFieldAsString(ABILITIES_NORMAL, 0); final CUnitType unitTypeInstance = getUnitTypeInstance(typeId, buildingPathingPixelMap, unitType); + final int life = unitTypeInstance.getLife(); + final int manaInitial = unitTypeInstance.getManaInitial(); + final int manaMaximum = unitTypeInstance.getManaMaximum(); + final int speed = unitTypeInstance.getSpeed(); + final int defense = unitTypeInstance.getDefense(); final CUnit unit = new CUnit(handleId, playerIndex, x, y, life, typeId, facing, manaInitial, life, manaMaximum, - speed, defense, unitTypeInstance, pathingInstance, buildingShadowInstance); + speed, defense, unitTypeInstance, pathingInstance); if (speed > 0) { unit.add(simulation, new CAbilityMove(handleIdAllocator.createId())); } @@ -216,7 +213,7 @@ public class CUnitData { if (!unitsTrained.isEmpty()) { unit.add(simulation, new CAbilityRally(handleIdAllocator.createId())); } - for (final String ability : abilityList.split(",")) { + for (final String ability : unitTypeInstance.getAbilityList().split(",")) { if ((ability.length() > 0) && !"_".equals(ability)) { final CAbility createAbility = this.abilityData.createAbility(ability, handleIdAllocator.createId()); if (createAbility != null) { @@ -231,6 +228,13 @@ public class CUnitData { final MutableGameObject unitType) { CUnitType unitTypeInstance = this.unitIdToUnitType.get(typeId); if (unitTypeInstance == null) { + final int life = unitType.getFieldAsInteger(HIT_POINT_MAXIMUM, 0); + final int manaInitial = unitType.getFieldAsInteger(MANA_INITIAL_AMOUNT, 0); + final int manaMaximum = unitType.getFieldAsInteger(MANA_MAXIMUM, 0); + final int speed = unitType.getFieldAsInteger(MOVEMENT_SPEED_BASE, 0); + final int defense = unitType.getFieldAsInteger(DEFENSE, 0); + final String abilityList = unitType.getFieldAsString(ABILITIES_NORMAL, 0); + final float moveHeight = unitType.getFieldAsFloat(MOVE_HEIGHT, 0); final String movetp = unitType.getFieldAsString(MOVE_TYPE, 0); final float collisionSize = unitType.getFieldAsFloat(COLLISION_SIZE, 0); @@ -417,11 +421,11 @@ public class CUnitData { final String raceString = unitType.getFieldAsString(UNIT_RACE, 0); final CUnitRace unitRace = CUnitRace.parseRace(raceString); - unitTypeInstance = new CUnitType(unitName, isBldg, movementType, moveHeight, collisionSize, classifications, - attacks, armorType, raise, decay, defenseType, impactZ, buildingPathingPixelMap, deathTime, - targetedAs, acquisitionRange, minimumAttackRange, structuresBuilt, unitsTrained, - researchesAvailable, unitRace, goldCost, lumberCost, foodUsed, foodMade, buildTime, - preventedPathingTypes, requiredPathingTypes); + unitTypeInstance = new CUnitType(unitName, life, manaInitial, manaMaximum, speed, defense, abilityList, + isBldg, movementType, moveHeight, collisionSize, classifications, attacks, armorType, raise, decay, + defenseType, impactZ, buildingPathingPixelMap, deathTime, targetedAs, acquisitionRange, + minimumAttackRange, structuresBuilt, unitsTrained, researchesAvailable, unitRace, goldCost, + lumberCost, foodUsed, foodMade, buildTime, preventedPathingTypes, requiredPathingTypes); this.unitIdToUnitType.put(typeId, unitTypeInstance); } return unitTypeInstance; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderExecutor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderExecutor.java index 16db009..104ee22 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderExecutor.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderExecutor.java @@ -3,6 +3,7 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.players; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorHoldPosition; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.COrderNoTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.COrderTargetPoint; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.COrderTargetWidget; @@ -43,7 +44,10 @@ public class CPlayerUnitOrderExecutor implements CPlayerUnitOrderListener { } else if (orderId == OrderIds.holdposition) { unit.order(this.game, null, queue); - unit.setHoldingPosition(true); + final CBehaviorHoldPosition holdPositionBehavior = unit.getHoldPositionBehavior(); + if (holdPositionBehavior != null) { + unit.setDefaultBehavior(holdPositionBehavior); + } } } else { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java index 1b6690a..0d070c4 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java @@ -3,6 +3,7 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.util; import java.awt.image.BufferedImage; import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CDestructable; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; @@ -26,8 +27,14 @@ public interface SimulationRenderController { void removeUnit(CUnit unit); + void removeDestructable(CDestructable dest); + BufferedImage getBuildingPathingPixelMap(War3ID rawcode); + BufferedImage getDestructablePathingPixelMap(War3ID rawcode); + + BufferedImage getDestructablePathingDeathPixelMap(War3ID rawcode); + void spawnUnitConstructionFinishSound(CUnit constructedStructure); void spawnBuildingDeathEffect(CUnit cUnit); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java index c0c6d97..351e4d3 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java @@ -117,7 +117,7 @@ public class CommandCardIcon extends AbstractRenderableFrame implements Clickabl @Override public UIFrame touchDown(final float screenX, final float screenY, final int button) { if (isVisible() && this.renderBounds.contains(screenX, screenY)) { - if (this.orderId != 0 || menuButton) { + if ((this.orderId != 0) || this.menuButton) { return this; } } @@ -164,4 +164,17 @@ public class CommandCardIcon extends AbstractRenderableFrame implements Clickabl this.iconFrame.setHeight(GameUI.convertY(uiViewport, MeleeUI.DEFAULT_COMMAND_CARD_ICON_WIDTH)); positionBounds(gameUI, uiViewport); } + + @Override + public UIFrame getFrameChildUnderMouse(final float screenX, final float screenY) { + if (isVisible() && this.renderBounds.contains(screenX, screenY)) { + return this; + } + return null; + } + + @Override + public String getToolTip() { + return "CommandCardIcon w/ OID=" + this.orderId; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java index 6138c31..cf10732 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java @@ -172,6 +172,10 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma private final Vector2 projectionTemp1 = new Vector2(); private final Vector2 projectionTemp2 = new Vector2(); + // tooltip + private UIFrame tooltipFrame; + private StringFrame tooltipText; + private UIFrame simpleInfoPanelUnitDetail; private StringFrame simpleNameValue; private StringFrame simpleClassValue; @@ -238,6 +242,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma private final float widthRatioCorrection; private final float heightRatioCorrection; private ClickableActionFrame mouseDownUIFrame; + private ClickableActionFrame mouseOverUIFrame; private UIFrame smashSimpleInfoPanel; private SimpleFrame smashAttack1IconWrapper; private SimpleFrame smashAttack2IconWrapper; @@ -597,6 +602,14 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } } + this.tooltipFrame = this.rootFrame.createFrame("SmashToolTip", this.rootFrame, 0, 0); + this.tooltipFrame.addAnchor(new AnchorDefinition(FramePoint.BOTTOMRIGHT, GameUI.convertX(this.uiViewport, 0.f), + GameUI.convertY(this.uiViewport, 0.176f))); + this.tooltipFrame.setWidth(GameUI.convertX(this.uiViewport, 0.176f)); + this.tooltipText = (StringFrame) this.rootFrame.getFrameByName("SmashToolTipText", 0); + this.tooltipFrame.setVisible(false); +// this.tooltipFrame = this.rootFrame.createFrameByType("BACKDROP", "SmashToolTipBackdrop", this.rootFrame, "", 0); + this.cursorFrame = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", "SmashCursorFrame", this.rootFrame, "", 0); this.rootFrame.setSpriteFrameModel(this.cursorFrame, this.rootFrame.getSkinField("Cursor")); @@ -1964,6 +1977,29 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma return false; } + public boolean mouseMoved(final int screenX, final int screenY, final float worldScreenY) { + screenCoordsVector.set(screenX, screenY); + this.uiViewport.unproject(screenCoordsVector); + final UIFrame mousedUIFrame = this.rootFrame.getFrameChildUnderMouse(screenCoordsVector.x, + screenCoordsVector.y); + if (mousedUIFrame != this.mouseOverUIFrame) { + if (mousedUIFrame instanceof ClickableActionFrame) { + this.mouseOverUIFrame = (ClickableActionFrame) mousedUIFrame; + final String toolTip = this.mouseOverUIFrame.getToolTip(); + this.tooltipText.setText(toolTip); + System.out.println("tooltip text assign to " + toolTip); + this.tooltipFrame.setHeight(GameUI.convertY(this.uiViewport, 0.020f)); + this.tooltipFrame.positionBounds(this.rootFrame, this.uiViewport); + this.tooltipFrame.setVisible(true); + } + else { + this.mouseOverUIFrame = null; + this.tooltipFrame.setVisible(false); + } + } + return false; + } + public float getHeightRatioCorrection() { return this.heightRatioCorrection; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/QueueIcon.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/QueueIcon.java index 375e4a5..9fc52a6 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/QueueIcon.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/QueueIcon.java @@ -96,4 +96,17 @@ public class QueueIcon extends AbstractRenderableFrame implements ClickableActio this.iconFrame.setHeight(this.defaultHeight); positionBounds(gameUI, uiViewport); } + + @Override + public UIFrame getFrameChildUnderMouse(final float screenX, final float screenY) { + if (isVisible() && this.renderBounds.contains(screenX, screenY)) { + return this; + } + return null; + } + + @Override + public String getToolTip() { + return "QueueIcon " + this.queueIconIndexId; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/ClickableActionFrame.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/ClickableActionFrame.java index aa1612b..e1ccfb9 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/ClickableActionFrame.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/ClickableActionFrame.java @@ -10,4 +10,6 @@ public interface ClickableActionFrame extends UIFrame { void mouseUp(final GameUI gameUI, final Viewport uiViewport); void onClick(int button); + + String getToolTip(); } diff --git a/resources/UI/FrameDef/SmashFrameDef.toc b/resources/UI/FrameDef/SmashFrameDef.toc index 71053d7..26aa133 100644 --- a/resources/UI/FrameDef/SmashFrameDef.toc +++ b/resources/UI/FrameDef/SmashFrameDef.toc @@ -1,3 +1,4 @@ UI\FrameDef\SmashUI\TimeOfDayIndicator.fdf UI\FrameDef\SmashUI\UnitPortrait.fdf UI\FrameDef\SmashUI\InventoryCover.fdf +UI\FrameDef\SmashUI\ToolTip.fdf diff --git a/resources/UI/FrameDef/SmashUI/ToolTip.fdf b/resources/UI/FrameDef/SmashUI/ToolTip.fdf new file mode 100644 index 0000000..bb64ccd --- /dev/null +++ b/resources/UI/FrameDef/SmashUI/ToolTip.fdf @@ -0,0 +1,42 @@ +/* + * ToolTip.fdf + * --------------------- + * These are some definitions to help us externalize the art of the tooltip. + * We want to use the following: + + ToolTipBackground=UI\Widgets\ToolTips\Human\human-tooltip-background.blp + ToolTipBorder=UI\Widgets\ToolTips\Human\human-tooltip-border.blp + ToolTipGoldIcon=UI\Widgets\ToolTips\Human\ToolTipGoldIcon.blp + ToolTipLumberIcon=UI\Widgets\ToolTips\Human\ToolTipLumberIcon.blp + ToolTipStonesIcon=UI\Widgets\ToolTips\Human\ToolTipStonesIcon.blp + ToolTipManaIcon=UI\Widgets\ToolTips\Human\ToolTipManaIcon.blp + ToolTipSupplyIcon=UI\Widgets\ToolTips\Human\ToolTipSupplyIcon.blp + */ + +Frame "FRAME" "SmashToolTip" { + Frame "BACKDROP" "SmashToolTipBackdrop" { + SetAllPoints, + DecorateFileNames, + BackdropTileBackground, + BackdropBackground "ToolTipBackground", + BackdropCornerFlags "UL|UR|BL|BR|T|L|B|R", + BackdropCornerSize 0.008, + BackdropBackgroundSize 0.036, + BackdropBackgroundInsets 0.0035 0.0035 0.0035 0.0035, + BackdropEdgeFile "ToolTipBorder", + BackdropBlendAll, + } + Frame "TEXT" "SmashToolTipText" { + SetAllPoints, + DecorateFileNames, + FrameFont "MasterFont", 0.013, "", + FontJustificationH JUSTIFYLEFT, + FontJustificationV JUSTIFYTOP, + FontFlags "FIXEDSIZE", + FontColor 0.99 0.827 0.0705 1.0, + FontHighlightColor 1.0 1.0 1.0 1.0, + FontDisabledColor 0.2 0.2 0.2 1.0, + FontShadowColor 0.0 0.0 0.0 0.9, + FontShadowOffset 0.001 -0.001, + } +} \ No newline at end of file From 9da2160565c6e3c39c7d9c9904775b5f902c6f62 Mon Sep 17 00:00:00 2001 From: Retera Date: Fri, 22 Jan 2021 00:42:45 -0500 Subject: [PATCH 088/116] Remove parse bogus classes --- .../handlers/w3x/rendersim/ParseBogus.java | 49 ------------ .../handlers/w3x/rendersim/ParseBogus2.java | 60 -------------- .../handlers/w3x/rendersim/ParseBogus3.java | 19 ----- .../handlers/w3x/rendersim/ParseBogus4.java | 80 ------------------- 4 files changed, 208 deletions(-) delete mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ParseBogus.java delete mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ParseBogus2.java delete mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ParseBogus3.java delete mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ParseBogus4.java diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ParseBogus.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ParseBogus.java deleted file mode 100644 index 1a764b4..0000000 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ParseBogus.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.etheller.warsmash.viewer5.handlers.w3x.rendersim; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.util.List; - -public class ParseBogus { - - public static void main(final String[] args) { - final File bogusDataFile = new File("E:\\Games\\Warcraft III Patch 1.22\\Logs\\MyReteraTest.pre"); - final float[][] cubeIndexToData = new float[9][44]; - try { - final List allLines = Files.readAllLines(bogusDataFile.toPath()); - for (final String line : allLines) { - final int cubeStringIndex = line.indexOf("Cube"); - if (cubeStringIndex != -1) { - final int colonStringIndex = line.indexOf(':'); - final int cubeIndex = Integer.parseInt(line.substring(cubeStringIndex + 4, colonStringIndex)); - final int dataIndex = Integer.parseInt(line.substring(line.indexOf('"') + 1, cubeStringIndex)); - final float value = Float.parseFloat(line.substring(colonStringIndex + 1, line.indexOf(".txt"))); - cubeIndexToData[cubeIndex][dataIndex] = value; - } - } - } - catch (final IOException e) { - e.printStackTrace(); - } - final boolean[] spun = new boolean[9]; - for (int j = 0; j < cubeIndexToData[0].length; j++) { - final StringBuilder sb = new StringBuilder(); - for (int i = 0; i < cubeIndexToData.length; i++) { - if (sb.length() > 0) { - sb.append(','); - } - float value = cubeIndexToData[i][j]; - if (value > 280) { - spun[i] = true; - } - else if ((value < 100) && spun[i]) { - value += 360; - } - sb.append(Math.toRadians(value)); - } - System.out.println(sb); - } - } - -} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ParseBogus2.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ParseBogus2.java deleted file mode 100644 index da2e022..0000000 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ParseBogus2.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.etheller.warsmash.viewer5.handlers.w3x.rendersim; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.util.List; - -public class ParseBogus2 { - - private static final int _383 = 280; - - public static void main(final String[] args) { - final float[][] cubeIndexToData = new float[9][_383]; - for (int fileIdx = 0; fileIdx < _383; fileIdx++) { - final File bogusDataFile = new File( - "E:\\Games\\Warcraft III Patch 1.22\\Logs\\MyReteraTest" + (fileIdx + 1) + ".pre"); - try { - final List allLines = Files.readAllLines(bogusDataFile.toPath()); - for (final String line : allLines) { - final int cubeStringIndex = line.indexOf("Cube"); - if (cubeStringIndex != -1) { - final int colonStringIndex = line.indexOf(':'); - final int cubeIndex = Integer.parseInt(line.substring(cubeStringIndex + 4, colonStringIndex)); - final int dataIndex = Integer.parseInt(line.substring(line.indexOf('"') + 1, cubeStringIndex)); - final float value = Float - .parseFloat(line.substring(colonStringIndex + 1, line.indexOf(".txt"))); - cubeIndexToData[cubeIndex][dataIndex] = value; - } - } - } - catch (final IOException e) { - e.printStackTrace(); - } - } - final boolean[] spun = new boolean[9]; - for (int j = 0; j < cubeIndexToData[0].length; j++) { - if ((j % 3) == 0) { - final StringBuilder sb = new StringBuilder(); - for (int i = 0; i < cubeIndexToData.length; i++) { - if (sb.length() > 0) { - sb.append(','); - } - float value = cubeIndexToData[i][j]; -// if (value > 280) { -// spun[i] = true; -// } -// else if ((value < 100) && spun[i]) { -// value += 360; -// } - if ((j / 3) < 32) { - value += 360; - } - sb.append(Math.toRadians(value)); - } - System.out.println(sb); - } - } - } - -} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ParseBogus3.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ParseBogus3.java deleted file mode 100644 index f239eec..0000000 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ParseBogus3.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.etheller.warsmash.viewer5.handlers.w3x.rendersim; - -public class ParseBogus3 { - public static float endingAccelCutoff(final float maxVelocity, final float endingAccel) { - final float endingAccelFinishingTime = maxVelocity / endingAccel; - final float endingDistanceRequired = (maxVelocity * endingAccelFinishingTime) - - ((endingAccel / 2) * (endingAccelFinishingTime * endingAccelFinishingTime)); - return endingDistanceRequired; - } - - public static void main(final String[] args) { - System.out.println(endingAccelCutoff(0.1f, 0.04f)); - - for (final OrientationInterpolation oi : OrientationInterpolation.values()) { - System.out.println(oi + ": " + oi.getStartingAcceleration() + "," + oi.getMaxVelocity() + "," - + oi.getEndingNegativeAcceleration() + "->" + oi.getEndingAccelCutoff()); - } - } -} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ParseBogus4.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ParseBogus4.java deleted file mode 100644 index 054e481..0000000 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ParseBogus4.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.etheller.warsmash.viewer5.handlers.w3x.rendersim; - -import java.util.Arrays; - -public class ParseBogus4 { - - public static void main(final String[] args) { - - final OrientationInterpolation[] interpolations = OrientationInterpolation.values(); - final float[] velocities = new float[interpolations.length]; - final float[] signs = new float[interpolations.length]; - Arrays.fill(signs, -1); - signs[1] = signs[6] = 1; - final float[] angles = new float[interpolations.length]; - Arrays.fill(angles, (float) ((3 * Math.PI) / 2)); - final boolean[] spun = new boolean[interpolations.length]; - - for (int j = 0; j < 140; j++) { - final StringBuilder sb = new StringBuilder(); - for (int i = 0; i < interpolations.length; i++) { - float simulationFacing = 90; - if (simulationFacing < 0) { - simulationFacing += 360; - } - float renderFacing = (float) Math.toDegrees(angles[i]); - if (renderFacing < 0) { - renderFacing += 360; - } - float facingDelta = simulationFacing - renderFacing; - if (facingDelta < -180) { - facingDelta = 360 + facingDelta; - } - if (facingDelta > 180) { - facingDelta = -360 + facingDelta; - } - final float absoluteFacingDelta = Math.abs(facingDelta); - - final float absoluteFacingDeltaRadians = (float) Math.toRadians(absoluteFacingDelta); - float acceleration; - final boolean endPhase = (absoluteFacingDeltaRadians <= interpolations[i].getEndingAccelCutoff()) - && ((velocities[i] * signs[i]) > 0); - if (endPhase) { - acceleration = -interpolations[i].getEndingNegativeAcceleration() * signs[i]; - if (((velocities[i] + acceleration) * signs[i]) <= 0.001) { - velocities[i] = absoluteFacingDeltaRadians * signs[i] * 0.25f; - } - else { - velocities[i] = velocities[i] + acceleration; - } - } - else { - acceleration = interpolations[i].getStartingAcceleration() * signs[i]; - velocities[i] = velocities[i] + acceleration; - } - if ((velocities[i] * signs[i]) > interpolations[i].getMaxVelocity()) { - velocities[i] = interpolations[i].getMaxVelocity() * signs[i]; - } - - float angleToAdd = (float) (/* Math.signum(facingDelta) **/ Math - .toDegrees(velocities[i]) /* * deltaTime */); - if (absoluteFacingDelta < Math.abs(angleToAdd)) { - angleToAdd = facingDelta; - } - double newDegreesAngle = (((Math.toDegrees(angles[i]) + angleToAdd) % 360) + 360) % 360; - if (newDegreesAngle > 280) { - spun[i] = true; - } - else if ((newDegreesAngle < 100) && spun[i]) { - newDegreesAngle += 360; - } - angles[i] = (float) Math.toRadians(newDegreesAngle); - if (sb.length() > 0) { - sb.append(','); - } - sb.append(angles[i]); - } - System.out.println(sb.toString()); - } - } -} From 4f7368eb57201743e6f88b158199f0271398f5e4 Mon Sep 17 00:00:00 2001 From: Retera Date: Sat, 23 Jan 2021 03:09:55 -0500 Subject: [PATCH 089/116] Clickable destructables, some cliff changes, shadows on bridges --- core/assets/warsmash.ini | 9 +- .../etheller/warsmash/WarsmashGdxMapGame.java | 4 +- .../warsmash/util/FixedIntersector.java | 82 +++++++++++ .../warsmash/util/RenderMathUtils.java | 2 +- .../handlers/w3x/RenderDestructable.java | 42 ------ .../viewer5/handlers/w3x/RenderDoodad.java | 83 ----------- .../viewer5/handlers/w3x/SplatModel.java | 59 +++++++- .../viewer5/handlers/w3x/Variations.java | 9 +- .../viewer5/handlers/w3x/W3xShaders.java | 30 ++-- .../viewer5/handlers/w3x/War3MapViewer.java | 128 +++++++++-------- .../handlers/w3x/environment/Terrain.java | 8 +- .../w3x/environment/TerrainShaders.java | 12 +- .../w3x/rendersim/RenderDestructable.java | 105 ++++++++++++++ .../handlers/w3x/rendersim/RenderDoodad.java | 92 ++++++++++++ .../handlers/w3x/rendersim/RenderUnit.java | 58 +++++++- .../handlers/w3x/rendersim/RenderWidget.java | 28 ++++ .../w3x/simulation/CDestructable.java | 7 +- .../handlers/w3x/simulation/CSimulation.java | 19 ++- .../w3x/simulation/CUnitFilterFunction.java | 19 --- .../w3x/simulation/CWidgetFilterFunction.java | 19 +++ .../simulation/behaviors/CBehaviorAttack.java | 4 +- .../attacks/CUnitAttackMissileSplash.java | 13 +- .../combat/projectile/CAttackProjectile.java | 13 +- .../simulation/orders/COrderTargetWidget.java | 4 +- .../util/SimulationRenderController.java | 2 +- .../viewer5/handlers/w3x/ui/MeleeUI.java | 136 ++++++++++-------- 26 files changed, 670 insertions(+), 317 deletions(-) create mode 100644 core/src/com/etheller/warsmash/util/FixedIntersector.java delete mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/RenderDestructable.java delete mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/RenderDoodad.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderDestructable.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderDoodad.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderWidget.java delete mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitFilterFunction.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWidgetFilterFunction.java diff --git a/core/assets/warsmash.ini b/core/assets/warsmash.ini index 9e25f41..c395553 100644 --- a/core/assets/warsmash.ini +++ b/core/assets/warsmash.ini @@ -1,5 +1,5 @@ [DataSources] -Count=7 +Count=8 Type00=MPQ Path00="D:\Games\Warcraft III Patch 1.22\war3.mpq" Type01=MPQ @@ -13,12 +13,15 @@ Path04="..\..\resources" Type05=Folder Path05="D:\Backups\Warsmash\Data" Type06=Folder -Path06="." +Path06="D:\Games\Warcraft III Patch 1.22\Maps" +Type07=Folder +Path07="." [Map] //FilePath="CombatUnitTests.w3x" //FilePath="PitchRoll.w3x" -FilePath="PeonStartingBase.w3x" +//FilePath="PeonStartingBase.w3x" +FilePath="MyStromguarde.w3m" //FilePath="ColdArrows.w3m" //FilePath="DungeonGoldMine.w3m" //FilePath="PlayerPeasants.w3m" diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java index 1d6bf08..1779796 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java @@ -56,7 +56,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.SettableCommandErro public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProvider, InputProcessor { private static final boolean ENABLE_AUDIO = true; - private static final boolean ENABLE_MUSIC = false; + private static final boolean ENABLE_MUSIC = true; private DataSource codebase; private War3MapViewer viewer; private final Rectangle tempRect = new Rectangle(); @@ -215,7 +215,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv final String musicField = rootFrame.getSkinField("Music_V1"); final String[] musics = musicField.split(";"); String musicPath = musics[(int) (Math.random() * musics.length)]; - if (true) { + if (false) { musicPath = "Sound\\Music\\mp3Music\\OrcTheme.mp3"; } final Music music = Gdx.audio.newMusic( diff --git a/core/src/com/etheller/warsmash/util/FixedIntersector.java b/core/src/com/etheller/warsmash/util/FixedIntersector.java new file mode 100644 index 0000000..0ecbcd8 --- /dev/null +++ b/core/src/com/etheller/warsmash/util/FixedIntersector.java @@ -0,0 +1,82 @@ +package com.etheller.warsmash.util; + +import com.badlogic.gdx.math.Intersector; +import com.badlogic.gdx.math.MathUtils; +import com.badlogic.gdx.math.Plane; +import com.badlogic.gdx.math.Plane.PlaneSide; +import com.badlogic.gdx.math.Vector3; +import com.badlogic.gdx.math.collision.Ray; + +public class FixedIntersector { + + private final static Vector3 v0 = new Vector3(); + private final static Vector3 v1 = new Vector3(); + private final static Vector3 v2 = new Vector3(); + + private static final Plane p = new Plane(new Vector3(), 0); + private static final Vector3 i = new Vector3(); + + /** + * Intersect a {@link Ray} and a triangle, returning the intersection point in + * intersection. + * + * @param ray The ray + * @param t1 The first vertex of the triangle + * @param t2 The second vertex of the triangle + * @param t3 The third vertex of the triangle + * @param intersection The intersection point (optional) + * @return True in case an intersection is present. + */ + public static boolean intersectRayTriangle(final Ray ray, final Vector3 t1, final Vector3 t2, final Vector3 t3, + final Vector3 intersection) { + if (t2.epsilonEquals(t3)) { + return false; + } + final Vector3 edge1 = v0.set(t2).sub(t1); + final Vector3 edge2 = v1.set(t3).sub(t1); + + final Vector3 pvec = v2.set(ray.direction).crs(edge2); + float det = edge1.dot(pvec); + if (MathUtils.isZero(det)) { + p.set(t1, t2, t3); + if ((p.testPoint(ray.origin) == PlaneSide.OnPlane) + && Intersector.isPointInTriangle(ray.origin, t1, t2, t3)) { + if (intersection != null) { + intersection.set(ray.origin); + } + return true; + } + return false; + } + + det = 1.0f / det; + + final Vector3 tvec = i.set(ray.origin).sub(t1); + final float u = tvec.dot(pvec) * det; + if ((u < 0.0f) || (u > 1.0f)) { + return false; + } + + final Vector3 qvec = tvec.crs(edge1); + final float v = ray.direction.dot(qvec) * det; + if ((v < 0.0f) || ((u + v) > 1.0f)) { + return false; + } + + final float t = edge2.dot(qvec) * det; + if (t < 0) { + return false; + } + + if (intersection != null) { + if (t <= MathUtils.FLOAT_ROUNDING_ERROR) { + intersection.set(ray.origin); + } + else { + ray.getEndPoint(intersection, t); + } + } + + return true; + } +} diff --git a/core/src/com/etheller/warsmash/util/RenderMathUtils.java b/core/src/com/etheller/warsmash/util/RenderMathUtils.java index 0ee0318..f5a3941 100644 --- a/core/src/com/etheller/warsmash/util/RenderMathUtils.java +++ b/core/src/com/etheller/warsmash/util/RenderMathUtils.java @@ -486,7 +486,7 @@ public enum RenderMathUtils { final int i2 = indices[i + 1] * vertexSize; final int i3 = indices[i + 2] * vertexSize; - final boolean result = Intersector.intersectRayTriangle(ray, + final boolean result = FixedIntersector.intersectRayTriangle(ray, tmp1.set(vertices[i1], vertices[i1 + 1], vertices[i1 + 2]), tmp2.set(vertices[i2], vertices[i2 + 1], vertices[i2 + 2]), tmp3.set(vertices[i3], vertices[i3 + 1], vertices[i3 + 2]), tmp); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/RenderDestructable.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/RenderDestructable.java deleted file mode 100644 index 31e011a..0000000 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/RenderDestructable.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.etheller.warsmash.viewer5.handlers.w3x; - -import com.badlogic.gdx.math.Rectangle; -import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; -import com.etheller.warsmash.units.manager.MutableObjectData.WorldEditorDataType; -import com.etheller.warsmash.util.War3ID; -import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; -import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; -import com.etheller.warsmash.viewer5.handlers.w3x.environment.BuildingShadow; - -public class RenderDestructable extends RenderDoodad { - private static final War3ID TEX_FILE = War3ID.fromString("btxf"); - private static final War3ID TEX_ID = War3ID.fromString("btxi"); - - private final float life; - public Rectangle walkableBounds; - - public RenderDestructable(final War3MapViewer map, final MdxModel model, final MutableGameObject row, - final com.etheller.warsmash.parsers.w3x.doo.Doodad doodad, final WorldEditorDataType type, - final float maxPitch, final float maxRoll, final float life, final BuildingShadow destructableShadow) { - super(map, model, row, doodad, type, maxPitch, maxRoll); - this.life = life; - String replaceableTextureFile = row.getFieldAsString(TEX_FILE, 0); - final int replaceableTextureId = row.getFieldAsInteger(TEX_ID, 0); - if ((replaceableTextureFile != null) && (replaceableTextureFile.length() > 1)) { - final int dotIndex = replaceableTextureFile.lastIndexOf('.'); - if (dotIndex != -1) { - replaceableTextureFile = replaceableTextureFile.substring(0, dotIndex); - } - replaceableTextureFile += ".blp"; - this.instance.setReplaceableTexture(replaceableTextureId, replaceableTextureFile); - } - } - - @Override - public PrimaryTag getAnimation() { - if (this.life <= 0) { - return PrimaryTag.DEATH; - } - return super.getAnimation(); - } -} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/RenderDoodad.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/RenderDoodad.java deleted file mode 100644 index 504e9fc..0000000 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/RenderDoodad.java +++ /dev/null @@ -1,83 +0,0 @@ -package com.etheller.warsmash.viewer5.handlers.w3x; - -import com.badlogic.gdx.math.Quaternion; -import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; -import com.etheller.warsmash.units.manager.MutableObjectData.WorldEditorDataType; -import com.etheller.warsmash.util.RenderMathUtils; -import com.etheller.warsmash.util.War3ID; -import com.etheller.warsmash.viewer5.ModelInstance; -import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; -import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; -import com.etheller.warsmash.viewer5.handlers.mdx.SequenceLoopMode; -import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; - -public class RenderDoodad { - private static final int SAMPLE_RADIUS = 4; - public final ModelInstance instance; - private final MutableGameObject row; - private final float maxPitch; - private final float maxRoll; - - public RenderDoodad(final War3MapViewer map, final MdxModel model, final MutableGameObject row, - final com.etheller.warsmash.parsers.w3x.doo.Doodad doodad, final WorldEditorDataType type, - final float maxPitch, final float maxRoll) { - this.maxPitch = maxPitch; - this.maxRoll = maxRoll; - final boolean isSimple = row.readSLKTagBoolean("lightweight"); - ModelInstance instance; - - if (isSimple && false) { - instance = model.addInstance(1); - } else { - instance = model.addInstance(); - ((MdxComplexInstance) instance).setSequenceLoopMode(SequenceLoopMode.NEVER_LOOP); - } - - instance.move(doodad.getLocation()); - // TODO: the following pitch/roll system is a heuristic, and we probably want to - // revisit it later. - // Specifically, I was pretty convinced that whichever is applied first - // (pitch/roll) should be used to do a projection onto the already-tilted plane - // to find the angle used for the other of the two - // (instead of measuring down from an imaginary flat ground plane, as we do - // currently). - final float facingRadians = doodad.getAngle(); - float pitch, roll; - final float x = doodad.getLocation()[0]; - final float y = doodad.getLocation()[1]; - final float pitchSampleForwardX = x + (SAMPLE_RADIUS * (float) Math.cos(facingRadians)); - final float pitchSampleForwardY = y + (SAMPLE_RADIUS * (float) Math.sin(facingRadians)); - final float pitchSampleBackwardX = x - (SAMPLE_RADIUS * (float) Math.cos(facingRadians)); - final float pitchSampleBackwardY = y - (SAMPLE_RADIUS * (float) Math.sin(facingRadians)); - final float pitchSampleGroundHeight1 = map.terrain.getGroundHeight(pitchSampleBackwardX, pitchSampleBackwardY); - final float pitchSampleGorundHeight2 = map.terrain.getGroundHeight(pitchSampleForwardX, pitchSampleForwardY); - pitch = Math.max(-maxPitch, Math.min(maxPitch, - (float) Math.atan2(pitchSampleGorundHeight2 - pitchSampleGroundHeight1, SAMPLE_RADIUS * 2))); - final double leftOfFacingAngle = facingRadians + (Math.PI / 2); - final float rollSampleForwardX = x + (SAMPLE_RADIUS * (float) Math.cos(leftOfFacingAngle)); - final float rollSampleForwardY = y + (SAMPLE_RADIUS * (float) Math.sin(leftOfFacingAngle)); - final float rollSampleBackwardX = x - (SAMPLE_RADIUS * (float) Math.cos(leftOfFacingAngle)); - final float rollSampleBackwardY = y - (SAMPLE_RADIUS * (float) Math.sin(leftOfFacingAngle)); - final float rollSampleGroundHeight1 = map.terrain.getGroundHeight(rollSampleBackwardX, rollSampleBackwardY); - final float rollSampleGroundHeight2 = map.terrain.getGroundHeight(rollSampleForwardX, rollSampleForwardY); - roll = Math.max(-maxRoll, Math.min(maxRoll, - (float) Math.atan2(rollSampleGroundHeight2 - rollSampleGroundHeight1, SAMPLE_RADIUS * 2))); - instance.rotate(new Quaternion().setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, facingRadians)); - instance.rotate(new Quaternion().setFromAxisRad(RenderMathUtils.VEC3_UNIT_Y, -pitch)); - instance.rotate(new Quaternion().setFromAxisRad(RenderMathUtils.VEC3_UNIT_X, roll)); -// instance.rotate(new Quaternion().setEulerAnglesRad(facingRadians, 0, 0)); - instance.scale(doodad.getScale()); - if (type == WorldEditorDataType.DOODADS) { - final float defScale = row.readSLKTagFloat("defScale"); - instance.uniformScale(defScale); - } - instance.setScene(map.worldScene); - - this.instance = instance; - this.row = row; - } - - public PrimaryTag getAnimation() { - return PrimaryTag.STAND; - } -} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SplatModel.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SplatModel.java index 0f8f348..ffa3c76 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SplatModel.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SplatModel.java @@ -22,6 +22,7 @@ import com.etheller.warsmash.viewer5.ViewerTextureRenderable; */ public class SplatModel { private static final int MAX_VERTICES = 65000; + private static final float NO_ABS_HEIGHT = -257f; private final ViewerTextureRenderable texture; private final List batches; public final float[] color; @@ -77,6 +78,7 @@ public class SplatModel { private void loadBatches(final GL30 gl, final float[] centerOffset) { final List vertices = new ArrayList<>(); final List uvs = new ArrayList<>(); + final List absoluteHeights = new ArrayList<>(); final List indices = new ArrayList<>(); final List batchRenderUnits = new ArrayList<>(); final int instances = this.locations.size(); @@ -112,9 +114,10 @@ public class SplatModel { final int step = (ix1 - ix0) + 1; if ((start + numVertsToCrate) > MAX_VERTICES) { - this.addBatch(gl, vertices, uvs, indices, batchRenderUnits); + this.addBatch(gl, vertices, uvs, absoluteHeights, indices, batchRenderUnits); vertices.clear(); uvs.clear(); + absoluteHeights.clear(); indices.clear(); batchRenderUnits.clear(); start = 0; @@ -130,9 +133,12 @@ public class SplatModel { vertices.add(vertex); final float[] uv = new float[] { (x - x0) / uvXScale, 1.0f - ((y - y0) / uvYScale) }; uvs.add(uv); + final float[] absHeight = new float[] { NO_ABS_HEIGHT }; + absoluteHeights.add(absHeight); if (splatMover != null) { splatMover.vertices.add(vertex); splatMover.uvs.add(uv); + splatMover.absoluteHeights.add(absHeight); } } } @@ -152,8 +158,11 @@ public class SplatModel { vertices.add(vertex); final float[] uv = new float[] { (x - x0) / uvXScale, 1.0f - ((y - y0) / uvYScale) }; uvs.add(uv); + final float[] absHeight = new float[] { NO_ABS_HEIGHT }; + absoluteHeights.add(absHeight); splatMover.vertices.add(vertex); splatMover.uvs.add(uv); + splatMover.absoluteHeights.add(absHeight); } } for (int i = 0; i < (iy1 - iy0); ++i) { @@ -179,7 +188,7 @@ public class SplatModel { } if (indices.size() > 0) { - this.addBatch(gl, vertices, uvs, indices, batchRenderUnits); + this.addBatch(gl, vertices, uvs, absoluteHeights, indices, batchRenderUnits); } if (this.splatInstances != null) { for (final SplatMover splatMover : this.splatInstances) { @@ -191,25 +200,30 @@ public class SplatModel { } private void addBatch(final GL30 gl, final List vertices, final List uvs, - final List indices, final List batchRenderUnits) { + final List absoluteHeights, final List indices, final List batchRenderUnits) { final int uvsOffset = vertices.size() * 3 * 4; + final int paramsOffset = uvsOffset + (uvs.size() * 4 * 2); final int vertexBuffer = gl.glGenBuffer(); gl.glBindBuffer(GL30.GL_ARRAY_BUFFER, vertexBuffer); - gl.glBufferData(GL30.GL_ARRAY_BUFFER, uvsOffset + (uvs.size() * 4 * 2), null, GL30.GL_STATIC_DRAW); - gl.glBufferSubData(GL30.GL_ARRAY_BUFFER, 0, vertices.size() * 4 * 5, RenderMathUtils.wrap(vertices)); + gl.glBufferData(GL30.GL_ARRAY_BUFFER, uvsOffset + (uvs.size() * 4 * 2) + (absoluteHeights.size() * 4), null, + GL30.GL_STATIC_DRAW); + gl.glBufferSubData(GL30.GL_ARRAY_BUFFER, 0, vertices.size() * 4 * 3, RenderMathUtils.wrap(vertices)); gl.glBufferSubData(GL30.GL_ARRAY_BUFFER, uvsOffset, uvs.size() * 4 * 2, RenderMathUtils.wrap(uvs)); + gl.glBufferSubData(GL30.GL_ARRAY_BUFFER, paramsOffset, absoluteHeights.size() * 4, + RenderMathUtils.wrap(absoluteHeights)); final int faceBuffer = gl.glGenBuffer(); gl.glBindBuffer(GL30.GL_ELEMENT_ARRAY_BUFFER, faceBuffer); gl.glBufferData(GL30.GL_ELEMENT_ARRAY_BUFFER, indices.size() * 6 * 2, RenderMathUtils.wrapFaces(indices), GL30.GL_STATIC_DRAW); - this.batches.add(new Batch(uvsOffset, vertexBuffer, faceBuffer, indices.size() * 6)); + this.batches.add(new Batch(uvsOffset, vertexBuffer, faceBuffer, indices.size() * 6, paramsOffset)); for (final SplatMover mover : batchRenderUnits) { mover.vertexBuffer = vertexBuffer; mover.uvsOffset = uvsOffset; mover.faceBuffer = faceBuffer; + mover.absHeightsOffset = paramsOffset; } } @@ -232,6 +246,7 @@ public class SplatModel { gl.glBindBuffer(GL30.GL_ARRAY_BUFFER, b.vertexBuffer); shader.setVertexAttribute("a_position", 3, GL30.GL_FLOAT, false, 12, 0); shader.setVertexAttribute("a_uv", 2, GL30.GL_FLOAT, false, 8, b.uvsOffset); + shader.setVertexAttribute("a_absoluteHeight", 1, GL30.GL_FLOAT, false, 4, b.paramsOffset); // Faces. gl.glBindBuffer(GL30.GL_ELEMENT_ARRAY_BUFFER, b.faceBuffer); @@ -266,16 +281,20 @@ public class SplatModel { private final int vertexBuffer; private final int faceBuffer; private final int elements; + private final int paramsOffset; - public Batch(final int uvsOffset, final int vertexBuffer, final int faceBuffer, final int elements) { + public Batch(final int uvsOffset, final int vertexBuffer, final int faceBuffer, final int elements, + final int paramsOffset) { this.uvsOffset = uvsOffset; this.vertexBuffer = vertexBuffer; this.faceBuffer = faceBuffer; this.elements = elements; + this.paramsOffset = paramsOffset; } } public static final class SplatMover { + public int absHeightsOffset; public int faceBuffer; public int uvsOffset; public int iy1; @@ -290,11 +309,14 @@ public class SplatModel { private int start; private final List vertices = new ArrayList<>(); private final List uvs = new ArrayList<>(); + private final List absoluteHeights = new ArrayList<>(); private final List indices = new ArrayList<>(); private int indicesStartOffset; private int index; private final SplatModel splatModel; private boolean hidden = false; + private boolean heightIsAbsolute = false; + private float absoluteHeightValue = 0.0f; private SplatMover(final SplatModel splatModel) { this.splatModel = splatModel; @@ -307,6 +329,7 @@ public class SplatModel { this.index = index; this.vertices.clear(); this.uvs.clear(); + this.absoluteHeights.clear(); this.indices.clear(); return this; } @@ -423,6 +446,9 @@ public class SplatModel { } gl.glBufferSubData(GL30.GL_ARRAY_BUFFER, this.uvsOffset + ((this.startOffset / 3) * 2), 4 * 2 * this.uvs.size(), RenderMathUtils.wrap(this.uvs)); + if (this.heightIsAbsolute) { + updateAbsoluteHeightParams(); + } } public void destroy(final GL30 gl, final float[] centerOffset) { @@ -468,5 +494,24 @@ public class SplatModel { move(0, 0, centerOffset); this.hidden = false; } + + public void setHeightAbsolute(final boolean absolute, final float absoluteHeightValue) { + this.absoluteHeightValue = absoluteHeightValue; + if (absolute != this.heightIsAbsolute) { + this.heightIsAbsolute = absolute; + updateAbsoluteHeightParams(); + } + } + + private void updateAbsoluteHeightParams() { + final GL30 gl = Gdx.gl30; + final float height = this.heightIsAbsolute ? this.absoluteHeightValue : NO_ABS_HEIGHT; + for (final float[] absHeight : this.absoluteHeights) { + absHeight[0] = height; + } + gl.glBindBuffer(GL30.GL_ARRAY_BUFFER, this.vertexBuffer); + gl.glBufferSubData(GL30.GL_ARRAY_BUFFER, this.absHeightsOffset + (this.startOffset / 3), + this.absoluteHeights.size() * 4, RenderMathUtils.wrap(this.absoluteHeights)); + } } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Variations.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Variations.java index f2133c0..d121da5 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Variations.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/Variations.java @@ -144,11 +144,16 @@ public class Variations { } public static int getCliffVariation(final String dir, final String tag, final int variation) { + final Integer vars; if ("Cliffs".equals(dir)) { - return Math.min(variation, CLIFF_VARS.get(tag)); + vars = CLIFF_VARS.get(tag); } else { - return Math.min(variation, CITY_CLIFF_VARS.get(tag)); + vars = CITY_CLIFF_VARS.get(tag); } + if (variation < vars) { + return variation; + } + return variation % (vars + 1); } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xShaders.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xShaders.java index c56fd50..891120a 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xShaders.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/W3xShaders.java @@ -20,6 +20,7 @@ public class W3xShaders { " uniform float u_lightTextureHeight;\r\n" + // " attribute vec3 a_position;\r\n" + // " attribute vec2 a_uv;\r\n" + // + " attribute float a_absoluteHeight;\r\n" + // " varying vec2 v_uv;\r\n" + // " varying vec2 v_suv;\r\n" + // " varying vec3 v_normal;\r\n" + // @@ -29,19 +30,28 @@ public class W3xShaders { " void main() {\r\n" + // " vec2 halfPixel = u_pixel * 0.5;\r\n" + // " vec2 base = (a_position.xy - u_centerOffset) / 128.0;\r\n" + // - " float height = texture2D(u_heightMap, base * u_pixel + halfPixel).r;\r\n" + // - " float hL = texture2D(u_heightMap, vec2(base - vec2(normalDist, 0.0)) * u_pixel + halfPixel).r;\r\n" - + // - " float hR = texture2D(u_heightMap, vec2(base + vec2(normalDist, 0.0)) * u_pixel + halfPixel).r;\r\n" - + // - " float hD = texture2D(u_heightMap, vec2(base - vec2(0.0, normalDist)) * u_pixel + halfPixel).r;\r\n" - + // - " float hU = texture2D(u_heightMap, vec2(base + vec2(0.0, normalDist)) * u_pixel + halfPixel).r;\r\n" - + // + " float height;\r\n" + // + " float hL;\r\n" + // + " float hR;\r\n" + // + " float hD;\r\n" + // + " float hU;\r\n" + // + " if (a_absoluteHeight < -256.0) {\r\n" + // + " height = texture2D(u_heightMap, base * u_pixel + halfPixel).r * 128.0;\r\n" + // + " hL = texture2D(u_heightMap, vec2(base - vec2(normalDist, 0.0)) * u_pixel + halfPixel).r;\r\n" + // + " hR = texture2D(u_heightMap, vec2(base + vec2(normalDist, 0.0)) * u_pixel + halfPixel).r;\r\n" + // + " hD = texture2D(u_heightMap, vec2(base - vec2(0.0, normalDist)) * u_pixel + halfPixel).r;\r\n" + // + " hU = texture2D(u_heightMap, vec2(base + vec2(0.0, normalDist)) * u_pixel + halfPixel).r;\r\n" + // + " } else {\r\n" + // + " height = a_absoluteHeight;\r\n" + // + " hL = a_absoluteHeight;\r\n" + // + " hR = a_absoluteHeight;\r\n" + // + " hD = a_absoluteHeight;\r\n" + // + " hU = a_absoluteHeight;\r\n" + // + " }\r\n" + // " v_normal = normalize(vec3(hL - hR, hD - hU, normalDist * 2.0));\r\n" + // " v_uv = a_uv;\r\n" + // " v_suv = base / u_size;\r\n" + // - " vec3 myposition = vec3(a_position.xy, height * 128.0 + a_position.z);\r\n" + // + " vec3 myposition = vec3(a_position.xy, height + a_position.z);\r\n" + // " gl_Position = u_mvp * vec4(myposition.xyz, 1.0);\r\n" + // " a_positionHeight = a_position.z;\r\n" + // Shaders.lightSystem("v_normal", "myposition", "u_lightTexture", "u_lightTextureHeight", "u_lightCount", diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index 764e29c..91cb27b 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -78,17 +78,20 @@ import com.etheller.warsmash.viewer5.handlers.w3x.environment.Terrain; import com.etheller.warsmash.viewer5.handlers.w3x.environment.Terrain.Splat; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderAttackInstant; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderAttackProjectile; +import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderDestructable; +import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderDoodad; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderEffect; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderItem; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderUnit; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderUnitTypeData; +import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderWidget; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.AbilityDataUI; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CDestructable; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitClassification; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitFilterFunction; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidgetFilterFunction; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackInstant; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackMissile; @@ -111,7 +114,6 @@ public class War3MapViewer extends AbstractMdxModelViewer { private static final War3ID UNIT_SHADOW_H = War3ID.fromString("ushh"); private static final War3ID BUILDING_SHADOW = War3ID.fromString("ushb"); public static final War3ID UNIT_SELECT_SCALE = War3ID.fromString("ussc"); - private static final War3ID UNIT_SELECT_HEIGHT = War3ID.fromString("uslz"); private static final War3ID UNIT_SOUNDSET = War3ID.fromString("usnd"); private static final War3ID ITEM_FILE = War3ID.fromString("ifil"); private static final War3ID UNIT_PATHING = War3ID.fromString("upat"); @@ -147,6 +149,7 @@ public class War3MapViewer extends AbstractMdxModelViewer { public boolean unitsAndItemsLoaded; public MappedData unitsData = new MappedData(); public MappedData unitMetaData = new MappedData(); + public List widgets = new ArrayList<>(); public List units = new ArrayList<>(); public List items = new ArrayList<>(); public List projectiles = new ArrayList<>(); @@ -160,7 +163,7 @@ public class War3MapViewer extends AbstractMdxModelViewer { public int renderLighting = 1; public List selModels = new ArrayList<>(); - public List selected = new ArrayList<>(); + public List selected = new ArrayList<>(); private DataTable unitAckSoundsTable; private DataTable unitCombatSoundsTable; public DataTable miscData; @@ -336,6 +339,7 @@ public class War3MapViewer extends AbstractMdxModelViewer { this.selectionCircleSizes.add(new SelectionCircleSize(size, texture, textureDotted)); } this.selectionCircleScaleFactor = selectionCircleData.getFieldFloatValue("ScaleFactor"); + this.imageWalkableZOffset = selectionCircleData.getFieldValue("ImageWalkableZOffset"); this.uiSoundsTable = new DataTable(worldEditStrings); try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("UI\\SoundInfo\\UISounds.slk")) { @@ -524,7 +528,7 @@ public class War3MapViewer extends AbstractMdxModelViewer { } @Override - public void spawnUnitDamageSound(final CUnit damagedUnit, final String weaponSound, + public void spawnDamageSound(final CWidget damagedDestructable, final String weaponSound, final String armorType) { final String key = weaponSound + armorType; UnitSound combatSound = this.keyToCombatSound.get(key); @@ -533,8 +537,8 @@ public class War3MapViewer extends AbstractMdxModelViewer { War3MapViewer.this.unitCombatSoundsTable, weaponSound, armorType); this.keyToCombatSound.put(key, combatSound); } - combatSound.play(War3MapViewer.this.worldScene.audioContext, damagedUnit.getX(), - damagedUnit.getY()); + combatSound.play(War3MapViewer.this.worldScene.audioContext, damagedDestructable.getX(), + damagedDestructable.getY()); } @Override @@ -551,6 +555,7 @@ public class War3MapViewer extends AbstractMdxModelViewer { @Override public void removeUnit(final CUnit unit) { final RenderUnit renderUnit = War3MapViewer.this.unitToRenderPeer.remove(unit); + War3MapViewer.this.widgets.remove(renderUnit); War3MapViewer.this.units.remove(renderUnit); War3MapViewer.this.worldScene.removeInstance(renderUnit.instance); } @@ -777,10 +782,10 @@ public class War3MapViewer extends AbstractMdxModelViewer { if (type == WorldEditorDataType.DESTRUCTIBLES) { final float x = doodad.getLocation()[0]; final float y = doodad.getLocation()[1]; - this.simulation.createDestructable(row.getAlias(), x, y, destructablePathing, - destructablePathingDeath); + final CDestructable simulationDestructable = this.simulation.createDestructable(row.getAlias(), x, + y, destructablePathing, destructablePathingDeath); final RenderDestructable renderDestructable = new RenderDestructable(this, model, row, doodad, type, - maxPitch, maxRoll, doodad.getLife(), destructableShadow); + maxPitch, maxRoll, doodad.getLife(), destructableShadow, simulationDestructable); if (row.readSLKTagBoolean("walkable")) { final BoundingBox boundingBox = model.bounds.getBoundingBox(); final float minX = boundingBox.min.x + x; @@ -792,6 +797,7 @@ public class War3MapViewer extends AbstractMdxModelViewer { renderDestructable.walkableBounds = renderDestructableBounds; } this.doodads.add(renderDestructable); + this.widgets.add(renderDestructable); } else { this.doodads.add(new RenderDoodad(this, model, row, doodad, type, maxPitch, maxRoll)); @@ -1077,8 +1083,10 @@ public class War3MapViewer extends AbstractMdxModelViewer { angle, buildingPathingPixelMap, pathingInstance); final RenderUnitTypeData typeData = getUnitTypeData(unitId, row); final RenderUnit renderUnit = new RenderUnit(this, model, row, unitX, unitY, unitZ, playerIndex, - soundset, portraitModel, simulationUnit, typeData, specialArtModel, buildingShadowInstance); + soundset, portraitModel, simulationUnit, typeData, specialArtModel, buildingShadowInstance, + this.selectionCircleScaleFactor); this.unitToRenderPeer.put(simulationUnit, renderUnit); + this.widgets.add(renderUnit); this.units.add(renderUnit); if (unitShadowSplat != null) { unitShadowSplat.unitMapping.add(new Consumer() { @@ -1191,7 +1199,7 @@ public class War3MapViewer extends AbstractMdxModelViewer { super.update(); - for (final RenderUnit unit : this.units) { + for (final RenderWidget unit : this.widgets) { unit.updateAnimations(this); } final Iterator projectileIterator = this.projectiles.iterator(); @@ -1276,57 +1284,54 @@ public class War3MapViewer extends AbstractMdxModelViewer { this.terrain.removeSplatBatchModel("selection"); } this.selModels.clear(); - for (final RenderUnit unit : this.selected) { - unit.selectionCircle = null; + for (final RenderWidget unit : this.selected) { + unit.unassignSelectionCircle(); } } this.selected.clear(); } - public void doSelectUnit(final List units) { + public void doSelectUnit(final List units) { deselect(); if (units.isEmpty()) { return; } final Map splats = new HashMap(); - for (final RenderUnit unit : units) { - if (unit.row != null) { - if (unit.selectionScale > 0) { - final float selectionSize = unit.selectionScale * this.selectionCircleScaleFactor; - String path = null; - for (int i = 0; i < this.selectionCircleSizes.size(); i++) { - final SelectionCircleSize selectionCircleSize = this.selectionCircleSizes.get(i); - if ((selectionSize < selectionCircleSize.size) - || (i == (this.selectionCircleSizes.size() - 1))) { - path = selectionCircleSize.texture; - break; - } + for (final RenderWidget unit : units) { + if (unit.getSelectionScale() > 0) { + final float selectionSize = unit.getSelectionScale(); + String path = null; + for (int i = 0; i < this.selectionCircleSizes.size(); i++) { + final SelectionCircleSize selectionCircleSize = this.selectionCircleSizes.get(i); + if ((selectionSize < selectionCircleSize.size) || (i == (this.selectionCircleSizes.size() - 1))) { + path = selectionCircleSize.texture; + break; } - if (!path.toLowerCase().endsWith(".blp")) { - path += ".blp"; - } - if (!splats.containsKey(path)) { - splats.put(path, new Splat()); - } - final float x = unit.location[0]; - final float y = unit.location[1]; - System.out.println("Selecting a unit at " + x + "," + y); - final float z = unit.row.getFieldAsFloat(UNIT_SELECT_HEIGHT, 0); - splats.get(path).locations.add(new float[] { x - (selectionSize / 2), y - (selectionSize / 2), - x + (selectionSize / 2), y + (selectionSize / 2), z + 5 }); - splats.get(path).unitMapping.add(new Consumer() { - @Override - public void accept(final SplatMover t) { - unit.selectionCircle = t; - if (unit.instance.hidden()) { - unit.selectionCircle.hide(); - } - } - }); } - this.selected.add(unit); + if (!path.toLowerCase().endsWith(".blp")) { + path += ".blp"; + } + if (!splats.containsKey(path)) { + splats.put(path, new Splat()); + } + final float x = unit.getX(); + final float y = unit.getY(); + System.out.println("Selecting a unit at " + x + "," + y); + final float z = unit.getSelectionHeight(); + splats.get(path).locations.add(new float[] { x - (selectionSize / 2), y - (selectionSize / 2), + x + (selectionSize / 2), y + (selectionSize / 2), z + 5 }); + splats.get(path).unitMapping.add(new Consumer() { + @Override + public void accept(final SplatMover t) { + unit.assignSelectionCircle(t); + if (unit.getInstance().hidden()) { + t.hide(); + } + } + }); } + this.selected.add(unit); } this.selModels.clear(); for (final Map.Entry entry : splats.entrySet()) { @@ -1372,10 +1377,10 @@ public class War3MapViewer extends AbstractMdxModelViewer { this.confirmationInstance.vertexColor[2] = blue; } - public List selectUnit(final float x, final float y, final boolean toggle) { + public List selectUnit(final float x, final float y, final boolean toggle) { System.out.println("world: " + x + "," + y); - final RenderUnit entity = rayPickUnit(x, y, CUnitFilterFunction.ACCEPT_ALL_LIVING); - List sel; + final RenderWidget entity = rayPickUnit(x, y, CWidgetFilterFunction.ACCEPT_ALL_LIVING); + List sel; if (entity != null) { if (toggle) { sel = new ArrayList<>(this.selected); @@ -1398,25 +1403,25 @@ public class War3MapViewer extends AbstractMdxModelViewer { return sel; } - public RenderUnit rayPickUnit(final float x, final float y) { - return rayPickUnit(x, y, CUnitFilterFunction.ACCEPT_ALL); + public RenderWidget rayPickUnit(final float x, final float y) { + return rayPickUnit(x, y, CWidgetFilterFunction.ACCEPT_ALL); } - public RenderUnit rayPickUnit(final float x, final float y, final CUnitFilterFunction filter) { + public RenderWidget rayPickUnit(final float x, final float y, final CWidgetFilterFunction filter) { final float[] ray = rayHeap; mousePosHeap.set(x, y); this.worldScene.camera.screenToWorldRay(ray, mousePosHeap); gdxRayHeap.set(ray[0], ray[1], ray[2], ray[3] - ray[0], ray[4] - ray[1], ray[5] - ray[2]); gdxRayHeap.direction.nor();// needed for libgdx - RenderUnit entity = null; - for (final RenderUnit unit : this.units) { - final MdxComplexInstance instance = unit.instance; - if (instance.shown() && instance.isVisible(this.worldScene.camera) && instance.intersectRayWithCollision( - gdxRayHeap, intersectionHeap, unit.getSimulationUnit().getUnitType().isBuilding(), false)) { - if (filter.call(unit.getSimulationUnit()) && (intersectionHeap.z > this.terrain + RenderWidget entity = null; + for (final RenderWidget unit : this.widgets) { + final MdxComplexInstance instance = unit.getInstance(); + if (instance.shown() && instance.isVisible(this.worldScene.camera) && instance + .intersectRayWithCollision(gdxRayHeap, intersectionHeap, unit.isIntersectedOnMeshAlways(), false)) { + if (filter.call(unit.getSimulationWidget()) && (intersectionHeap.z > this.terrain .getGroundHeight(intersectionHeap.x, intersectionHeap.y))) { - if ((entity == null) || (entity.instance.depth > instance.depth)) { + if ((entity == null) || (entity.getInstance().depth > instance.depth)) { entity = unit; } } @@ -1495,6 +1500,7 @@ public class War3MapViewer extends AbstractMdxModelViewer { private Warcraft3MapObjectData allObjectData; private AbilityDataUI abilityDataUI; private Map soundsetNameToSoundset; + public int imageWalkableZOffset; /** * Returns a power of two size for the given target capacity. diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java index 2acdd98..8fef83a 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java @@ -244,7 +244,7 @@ public class Terrain { // Cliff Textures for (final War3ID cliffTile : w3eFile.getCliffTiles()) { final Element cliffInfo = this.cliffTable.get(cliffTile.asStringValue()); - if(cliffInfo == null) { + if (cliffInfo == null) { System.err.println("Missing cliff type: " + cliffTile.asStringValue()); continue; } @@ -656,10 +656,10 @@ public class Terrain { // Cliff model path - String fileName = "" + (char) (('A' + bottomLeft.getLayerHeight()) - base) - + (char) (('A' + topLeft.getLayerHeight()) - base) + String fileName = "" + (char) (('A' + topLeft.getLayerHeight()) - base) + (char) (('A' + topRight.getLayerHeight()) - base) - + (char) (('A' + bottomRight.getLayerHeight()) - base); + + (char) (('A' + bottomRight.getLayerHeight()) - base) + + (char) (('A' + bottomLeft.getLayerHeight()) - base); if ("AAAA".equals(fileName)) { continue; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/TerrainShaders.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/TerrainShaders.java index 0864adb..1741b07 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/TerrainShaders.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/TerrainShaders.java @@ -35,15 +35,16 @@ public class TerrainShaders { "out vec3 shadeColor;\r\n" + // "\r\n" + // "void main() {\r\n" + // - " pathing_map_uv = (vec2(vPosition.x + 128, vPosition.y) / 128 + vOffset.xy) * 4;\r\n" + // + " pathing_map_uv = (vec2(vPosition.y, -vPosition.x) / 128 + vOffset.xy) * 4;\r\n" + // " \r\n" + // " ivec2 size = textureSize(height_texture, 0);\r\n" + // " ivec2 shadowSize = textureSize(shadowMap, 0);\r\n" + // " v_suv = pathing_map_uv / shadowSize;\r\n" + // - " float value = texture(height_texture, (vOffset.xy + vec2(vPosition.x + 192, vPosition.y + 64) / 128.0) / vec2(size)).r;\r\n" + " float value = texture(height_texture, (vOffset.xy + vec2(vPosition.y + 64, -vPosition.x + 64) / 128.0) / vec2(size)).r;\r\n" + // "\r\n" + // - " position = (vPosition + vec3(vOffset.xy + vec2(1, 0), vOffset.z + value) * 128 );\r\n" + // + " position = (vec3(vPosition.y, -vPosition.x, vPosition.z) + vec3(vOffset.xy, vOffset.z + value) * 128 );\r\n" + + // " vec4 myposition = vec4(position, 1);\r\n" + // " myposition.x += centerOffsetX;\r\n" + // " myposition.y += centerOffsetY;\r\n" + // @@ -52,13 +53,14 @@ public class TerrainShaders { " gl_Position = MVP * myposition;\r\n" + // " UV = vec3(vUV, vOffset.a);\r\n" + // "\r\n" + // - " ivec2 height_pos = ivec2(vOffset.xy + vec2(vPosition.x + 128, vPosition.y) / 128);\r\n" + // + " ivec2 height_pos = ivec2(vOffset.xy + vec2(vPosition.y, -vPosition.x) / 128);\r\n" + // " ivec3 off = ivec3(1, 1, 0);\r\n" + // " float hL = texelFetch(height_texture, height_pos - off.xz, 0).r;\r\n" + // " float hR = texelFetch(height_texture, height_pos + off.xz, 0).r;\r\n" + // " float hD = texelFetch(height_texture, height_pos - off.zy, 0).r;\r\n" + // " float hU = texelFetch(height_texture, height_pos + off.zy, 0).r;\r\n" + // - " vec3 terrain_normal = normalize(vNormal);//vec3(hL - hR, hD - hU, 2.0)+vNormal);\r\n" + // + " vec3 terrain_normal = normalize(vec3(vNormal.y, -vNormal.x, vNormal.z));//vec3(hL - hR, hD - hU, 2.0)+vNormal);\r\n" + + // "\r\n" + // " Normal = terrain_normal;\r\n" + // Shaders.lightSystem("terrain_normal", "myposition.xyz", "lightTexture", "lightTextureHeight", diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderDestructable.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderDestructable.java new file mode 100644 index 0000000..c81eb09 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderDestructable.java @@ -0,0 +1,105 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.rendersim; + +import com.badlogic.gdx.math.Rectangle; +import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; +import com.etheller.warsmash.units.manager.MutableObjectData.WorldEditorDataType; +import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; +import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; +import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; +import com.etheller.warsmash.viewer5.handlers.w3x.SplatModel.SplatMover; +import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; +import com.etheller.warsmash.viewer5.handlers.w3x.environment.BuildingShadow; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CDestructable; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; + +public class RenderDestructable extends RenderDoodad implements RenderWidget { + private static final War3ID TEX_FILE = War3ID.fromString("btxf"); + private static final War3ID TEX_ID = War3ID.fromString("btxi"); + private static final War3ID SEL_CIRCLE_SIZE = War3ID.fromString("bgsc"); + + private final float life; + public Rectangle walkableBounds; + private final CDestructable simulationDestructable; + private SplatMover selectionCircle; + + public RenderDestructable(final War3MapViewer map, final MdxModel model, final MutableGameObject row, + final com.etheller.warsmash.parsers.w3x.doo.Doodad doodad, final WorldEditorDataType type, + final float maxPitch, final float maxRoll, final float life, final BuildingShadow destructableShadow, + final CDestructable simulationDestructable) { + super(map, model, row, doodad, type, maxPitch, maxRoll); + this.life = life; + this.simulationDestructable = simulationDestructable; + String replaceableTextureFile = row.getFieldAsString(TEX_FILE, 0); + final int replaceableTextureId = row.getFieldAsInteger(TEX_ID, 0); + if ((replaceableTextureFile != null) && (replaceableTextureFile.length() > 1)) { + final int dotIndex = replaceableTextureFile.lastIndexOf('.'); + if (dotIndex != -1) { + replaceableTextureFile = replaceableTextureFile.substring(0, dotIndex); + } + replaceableTextureFile += ".blp"; + this.instance.setReplaceableTexture(replaceableTextureId, replaceableTextureFile); + } + this.selectionScale *= row.getFieldAsFloat(SEL_CIRCLE_SIZE, 0); + } + + @Override + public PrimaryTag getAnimation() { + if (this.life <= 0) { + return PrimaryTag.DEATH; + } + return super.getAnimation(); + } + + @Override + public MdxComplexInstance getInstance() { + return (MdxComplexInstance) this.instance; + } + + @Override + public CWidget getSimulationWidget() { + return this.simulationDestructable; + } + + @Override + public void updateAnimations(final War3MapViewer war3MapViewer) { + // TODO maybe move getAnimation behaviors to here and make this thing not a + // doodad + } + + @Override + public boolean isIntersectedOnMeshAlways() { + return false; + } + + @Override + public float getSelectionScale() { + return this.selectionScale; + } + + @Override + public float getX() { + return this.x; + } + + @Override + public float getY() { + return this.y; + } + + @Override + public float getSelectionHeight() { + return 0; + } + + @Override + public void unassignSelectionCircle() { + this.selectionCircle = null; + } + + @Override + public void assignSelectionCircle(final SplatMover selectionCircle) { + this.selectionCircle = selectionCircle; + + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderDoodad.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderDoodad.java new file mode 100644 index 0000000..0b56b77 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderDoodad.java @@ -0,0 +1,92 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.rendersim; + +import com.badlogic.gdx.math.Quaternion; +import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; +import com.etheller.warsmash.units.manager.MutableObjectData.WorldEditorDataType; +import com.etheller.warsmash.util.RenderMathUtils; +import com.etheller.warsmash.viewer5.ModelInstance; +import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; +import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; +import com.etheller.warsmash.viewer5.handlers.mdx.SequenceLoopMode; +import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; +import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; + +public class RenderDoodad { + private static final int SAMPLE_RADIUS = 4; + public final ModelInstance instance; + private final MutableGameObject row; + private final float maxPitch; + private final float maxRoll; + protected float x; + protected float y; + protected float selectionScale; + + public RenderDoodad(final War3MapViewer map, final MdxModel model, final MutableGameObject row, + final com.etheller.warsmash.parsers.w3x.doo.Doodad doodad, final WorldEditorDataType type, + final float maxPitch, final float maxRoll) { + this.maxPitch = maxPitch; + this.maxRoll = maxRoll; + final boolean isSimple = row.readSLKTagBoolean("lightweight"); + ModelInstance instance; + + if (isSimple && false) { + instance = model.addInstance(1); + } + else { + instance = model.addInstance(); + ((MdxComplexInstance) instance).setSequenceLoopMode(SequenceLoopMode.NEVER_LOOP); + } + + instance.move(doodad.getLocation()); + // TODO: the following pitch/roll system is a heuristic, and we probably want to + // revisit it later. + // Specifically, I was pretty convinced that whichever is applied first + // (pitch/roll) should be used to do a projection onto the already-tilted plane + // to find the angle used for the other of the two + // (instead of measuring down from an imaginary flat ground plane, as we do + // currently). + final float facingRadians = doodad.getAngle(); + float pitch, roll; + this.x = doodad.getLocation()[0]; + this.y = doodad.getLocation()[1]; + final float pitchSampleForwardX = this.x + (SAMPLE_RADIUS * (float) Math.cos(facingRadians)); + final float pitchSampleForwardY = this.y + (SAMPLE_RADIUS * (float) Math.sin(facingRadians)); + final float pitchSampleBackwardX = this.x - (SAMPLE_RADIUS * (float) Math.cos(facingRadians)); + final float pitchSampleBackwardY = this.y - (SAMPLE_RADIUS * (float) Math.sin(facingRadians)); + final float pitchSampleGroundHeight1 = map.terrain.getGroundHeight(pitchSampleBackwardX, pitchSampleBackwardY); + final float pitchSampleGorundHeight2 = map.terrain.getGroundHeight(pitchSampleForwardX, pitchSampleForwardY); + pitch = Math.max(-maxPitch, Math.min(maxPitch, + (float) Math.atan2(pitchSampleGorundHeight2 - pitchSampleGroundHeight1, SAMPLE_RADIUS * 2))); + final double leftOfFacingAngle = facingRadians + (Math.PI / 2); + final float rollSampleForwardX = this.x + (SAMPLE_RADIUS * (float) Math.cos(leftOfFacingAngle)); + final float rollSampleForwardY = this.y + (SAMPLE_RADIUS * (float) Math.sin(leftOfFacingAngle)); + final float rollSampleBackwardX = this.x - (SAMPLE_RADIUS * (float) Math.cos(leftOfFacingAngle)); + final float rollSampleBackwardY = this.y - (SAMPLE_RADIUS * (float) Math.sin(leftOfFacingAngle)); + final float rollSampleGroundHeight1 = map.terrain.getGroundHeight(rollSampleBackwardX, rollSampleBackwardY); + final float rollSampleGroundHeight2 = map.terrain.getGroundHeight(rollSampleForwardX, rollSampleForwardY); + roll = Math.max(-maxRoll, Math.min(maxRoll, + (float) Math.atan2(rollSampleGroundHeight2 - rollSampleGroundHeight1, SAMPLE_RADIUS * 2))); + instance.rotate(new Quaternion().setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, facingRadians)); + instance.rotate(new Quaternion().setFromAxisRad(RenderMathUtils.VEC3_UNIT_Y, -pitch)); + instance.rotate(new Quaternion().setFromAxisRad(RenderMathUtils.VEC3_UNIT_X, roll)); +// instance.rotate(new Quaternion().setEulerAnglesRad(facingRadians, 0, 0)); + final float[] scale = doodad.getScale(); + instance.scale(scale); + if (type == WorldEditorDataType.DOODADS) { + final float defScale = row.readSLKTagFloat("defScale"); + instance.uniformScale(defScale); + this.selectionScale = defScale; + } + else { + this.selectionScale = (float) Math.sqrt((scale[0]) * (scale[1]) * (scale[2])); + } + instance.setScene(map.worldScene); + + this.instance = instance; + this.row = row; + } + + public PrimaryTag getAnimation() { + return PrimaryTag.STAND; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java index e1fb4e6..6d91a26 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java @@ -28,9 +28,10 @@ import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.commandbuttons.Comma import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitAnimationListener; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; -public class RenderUnit { +public class RenderUnit implements RenderWidget { public static final Quaternion tempQuat = new Quaternion(); private static final War3ID RED = War3ID.fromString("uclr"); private static final War3ID GREEN = War3ID.fromString("uclg"); @@ -41,6 +42,7 @@ public class RenderUnit { private static final War3ID ANIM_PROPS = War3ID.fromString("uani"); private static final War3ID BLEND_TIME = War3ID.fromString("uble"); private static final War3ID BUILD_SOUND_LABEL = War3ID.fromString("ubsl"); + private static final War3ID UNIT_SELECT_HEIGHT = War3ID.fromString("uslz"); private static final float[] heapZ = new float[3]; public final MdxComplexInstance instance; public final MutableGameObject row; @@ -74,7 +76,8 @@ public class RenderUnit { public RenderUnit(final War3MapViewer map, final MdxModel model, final MutableGameObject row, final float x, final float y, final float z, final int playerIndex, final UnitSoundset soundset, final MdxModel portraitModel, final CUnit simulationUnit, final RenderUnitTypeData typeData, - final MdxModel specialArtModel, final BuildingShadow buildingShadow) { + final MdxModel specialArtModel, final BuildingShadow buildingShadow, + final float selectionCircleScaleFactor) { this.portraitModel = portraitModel; this.simulationUnit = simulationUnit; this.typeData = typeData; @@ -123,7 +126,7 @@ public class RenderUnit { (row.getFieldAsInteger(green, 0)) / 255f, (row.getFieldAsInteger(blue, 0)) / 255f }); instance.uniformScale(row.getFieldAsFloat(scale, 0)); - this.selectionScale = row.getFieldAsFloat(War3MapViewer.UNIT_SELECT_SCALE, 0); + this.selectionScale = row.getFieldAsFloat(War3MapViewer.UNIT_SELECT_SCALE, 0) * selectionCircleScaleFactor; int orientationInterpolationOrdinal = row.getFieldAsInteger(ORIENTATION_INTERPOLATION, 0); if ((orientationInterpolationOrdinal < 0) || (orientationInterpolationOrdinal >= OrientationInterpolation.VALUES.length)) { @@ -150,6 +153,7 @@ public class RenderUnit { } } + @Override public void updateAnimations(final War3MapViewer map) { final boolean wasHidden = this.instance.hidden(); if (this.simulationUnit.isHidden()) { @@ -390,9 +394,12 @@ public class RenderUnit { map.worldScene.instanceMoved(this.instance, this.location[0], this.location[1]); if (this.shadow != null) { this.shadow.move(dx, dy, map.terrain.centerOffset); + this.shadow.setHeightAbsolute(currentWalkableUnder != null, this.location[2] + map.imageWalkableZOffset); } if (this.selectionCircle != null) { this.selectionCircle.move(dx, dy, map.terrain.centerOffset); + this.selectionCircle.setHeightAbsolute(currentWalkableUnder != null, + this.location[2] + map.imageWalkableZOffset); } this.unitAnimationListenerImpl.update(); if (!dead && this.simulationUnit.isConstructing()) { @@ -553,4 +560,49 @@ public class RenderUnit { this.location[0] = this.simulationUnit.getX(); this.location[1] = this.simulationUnit.getY(); } + + @Override + public MdxComplexInstance getInstance() { + return this.instance; + } + + @Override + public CWidget getSimulationWidget() { + return this.simulationUnit; + } + + @Override + public boolean isIntersectedOnMeshAlways() { + return this.simulationUnit.getUnitType().isBuilding(); + } + + @Override + public float getSelectionHeight() { + return this.row.getFieldAsFloat(UNIT_SELECT_HEIGHT, 0); + } + + @Override + public float getSelectionScale() { + return this.selectionScale; + } + + @Override + public float getX() { + return this.location[0]; + } + + @Override + public float getY() { + return this.location[1]; + } + + @Override + public void unassignSelectionCircle() { + this.selectionCircle = null; + } + + @Override + public void assignSelectionCircle(final SplatMover t) { + this.selectionCircle = t; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderWidget.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderWidget.java new file mode 100644 index 0000000..fe5b96f --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderWidget.java @@ -0,0 +1,28 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.rendersim; + +import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; +import com.etheller.warsmash.viewer5.handlers.w3x.SplatModel.SplatMover; +import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; + +public interface RenderWidget { + MdxComplexInstance getInstance(); + + CWidget getSimulationWidget(); + + void updateAnimations(War3MapViewer war3MapViewer); + + boolean isIntersectedOnMeshAlways(); + + float getSelectionScale(); + + float getX(); + + float getY(); + + float getSelectionHeight(); + + void unassignSelectionCircle(); + + void assignSelectionCircle(SplatMover t); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CDestructable.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CDestructable.java index 7f03004..e59224d 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CDestructable.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CDestructable.java @@ -36,6 +36,7 @@ public class CDestructable extends CWidget { public void damage(final CSimulation simulation, final CUnit source, final CAttackType attackType, final String weaponType, final float damage) { this.life -= damage; + simulation.destructableDamageEvent(this, weaponType, this.destType.getArmorType()); } @Override @@ -50,7 +51,7 @@ public class CDestructable extends CWidget { } } else { - System.err.println("No targeting because " + targetsAllowed + " does not contain all of " + System.err.println("Not targeting because " + targetsAllowed + " does not contain all of " + this.destType.getTargetedAs()); } return false; @@ -60,4 +61,8 @@ public class CDestructable extends CWidget { public T visit(final AbilityTargetVisitor visitor) { return visitor.accept(this); } + + public CDestructableType getDestType() { + return this.destType; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java index eb71848..e9aec31 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java @@ -239,7 +239,12 @@ public class CSimulation { } public void unitDamageEvent(final CUnit damagedUnit, final String weaponSound, final String armorType) { - this.simulationRenderController.spawnUnitDamageSound(damagedUnit, weaponSound, armorType); + this.simulationRenderController.spawnDamageSound(damagedUnit, weaponSound, armorType); + } + + public void destructableDamageEvent(final CDestructable damagedDestructable, final String weaponSound, + final String armorType) { + this.simulationRenderController.spawnDamageSound(damagedDestructable, weaponSound, armorType); } public void unitConstructedEvent(final CUnit constructingUnit, final CUnit constructedStructure) { @@ -284,4 +289,16 @@ public class CSimulation { player.setUnitFoodMade(unit, unit.getUnitType().getFoodMade()); } } + + public CWidget getWidget(final int handleId) { + final CUnit unit = this.handleIdToUnit.get(handleId); + if (unit != null) { + return unit; + } + final CDestructable destructable = this.handleIdToDestructable.get(handleId); + if (destructable != null) { + return destructable; + } + return null; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitFilterFunction.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitFilterFunction.java deleted file mode 100644 index 03722f5..0000000 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitFilterFunction.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.etheller.warsmash.viewer5.handlers.w3x.simulation; - -public interface CUnitFilterFunction { - boolean call(CUnit unit); - - CUnitFilterFunction ACCEPT_ALL = new CUnitFilterFunction() { - @Override - public boolean call(final CUnit unit) { - return true; - } - }; - - CUnitFilterFunction ACCEPT_ALL_LIVING = new CUnitFilterFunction() { - @Override - public boolean call(final CUnit unit) { - return !unit.isDead(); - } - }; -} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWidgetFilterFunction.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWidgetFilterFunction.java new file mode 100644 index 0000000..dc1a7bd --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWidgetFilterFunction.java @@ -0,0 +1,19 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation; + +public interface CWidgetFilterFunction { + boolean call(CWidget unit); + + CWidgetFilterFunction ACCEPT_ALL = new CWidgetFilterFunction() { + @Override + public boolean call(final CWidget unit) { + return true; + } + }; + + CWidgetFilterFunction ACCEPT_ALL_LIVING = new CWidgetFilterFunction() { + @Override + public boolean call(final CWidget unit) { + return !unit.isDead(); + } + }; +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java index 6dab933..47faf30 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java @@ -47,8 +47,10 @@ public class CBehaviorAttack extends CAbstractRangedBehavior { if (simulation.getGameTurnTick() < this.unit.getCooldownEndTime()) { range += this.unitAttack.getRangeMotionBuffer(); } + final double rangeCheckDistance = this.unit.distance(this.target); + System.out.println("rangeCheckDistance=" + rangeCheckDistance); return this.unit.canReach(this.target, range) - && (this.unit.distance(this.target) >= this.unit.getUnitType().getMinimumAttackRange()); + && (rangeCheckDistance >= this.unit.getUnitType().getMinimumAttackRange()); } @Override diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileSplash.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileSplash.java index 9cc4aab..266d6da 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileSplash.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileSplash.java @@ -123,8 +123,7 @@ public class CUnitAttackMissileSplash extends CUnitAttackMissile { this.y = y; this.damage = damage; this.hitTarget = false; - final float doubleMaxArea = attack.areaOfEffectSmallDamage - + (this.simulation.getGameplayConstants().getCloseEnoughRange() * 2); + final float doubleMaxArea = (attack.areaOfEffectSmallDamage) * 2; final float maxArea = doubleMaxArea / 2; this.rect.set(x - maxArea, y - maxArea, doubleMaxArea, doubleMaxArea); simulation.getWorldCollision().enumUnitsInRect(this.rect, this); @@ -133,17 +132,17 @@ public class CUnitAttackMissileSplash extends CUnitAttackMissile { @Override public boolean call(final CUnit enumUnit) { if (enumUnit.canBeTargetedBy(this.simulation, this.source, this.attack.areaOfEffectTargets)) { - final double distance = enumUnit.distance(this.x, this.y) - - this.simulation.getGameplayConstants().getCloseEnoughRange(); - if (distance <= (this.attack.areaOfEffectFullDamage / 2)) { + final double distance = enumUnit.distance(this.x, this.y); + System.out.println("enum distance=" + distance); + if (distance <= (this.attack.areaOfEffectFullDamage)) { enumUnit.damage(this.simulation, this.source, this.attack.getAttackType(), this.attack.getWeaponSound(), this.damage); } - else if (distance <= (this.attack.areaOfEffectMediumDamage / 2)) { + else if (distance <= (this.attack.areaOfEffectMediumDamage)) { enumUnit.damage(this.simulation, this.source, this.attack.getAttackType(), this.attack.getWeaponSound(), this.damage * this.attack.damageFactorMedium); } - else if (distance <= (this.attack.areaOfEffectSmallDamage / 2)) { + else if (distance <= (this.attack.areaOfEffectSmallDamage)) { enumUnit.damage(this.simulation, this.source, this.attack.getAttackType(), this.attack.getWeaponSound(), this.damage * this.attack.damageFactorSmall); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/projectile/CAttackProjectile.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/projectile/CAttackProjectile.java index 199ba5b..23df874 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/projectile/CAttackProjectile.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/projectile/CAttackProjectile.java @@ -47,12 +47,8 @@ public class CAttackProjectile { final float d1y = dtsy / c; float travelDistance = Math.min(c, this.speed * WarsmashConstants.SIMULATION_STEP_TIME); - if (c <= travelDistance) { - if (!this.done) { - this.unitAttack.doDamage(cSimulation, this.source, this.target, this.damage, this.x, this.y, - this.bounceIndex); - } - this.done = true; + final boolean done = c <= travelDistance; + if (done) { travelDistance = c; } @@ -62,6 +58,11 @@ public class CAttackProjectile { this.x = this.x + dx; this.y = this.y + dy; + if (done && !this.done) { + this.unitAttack.doDamage(cSimulation, this.source, this.target, this.damage, this.x, this.y, + this.bounceIndex); + this.done = true; + } return this.done; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetWidget.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetWidget.java index 902dcf5..ffd751e 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetWidget.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetWidget.java @@ -34,7 +34,7 @@ public class COrderTargetWidget implements COrder { @Override public AbilityTarget getTarget(final CSimulation game) { - final CUnit target = game.getUnit(this.targetHandleId); + final CWidget target = game.getWidget(this.targetHandleId); return target; } @@ -48,7 +48,7 @@ public class COrderTargetWidget implements COrder { final CAbility ability = game.getAbility(this.abilityHandleId); ability.checkCanUse(game, caster, this.orderId, abilityActivationReceiver.reset()); if (abilityActivationReceiver.isUseOk()) { - final CUnit target = game.getUnit(this.targetHandleId); + final CWidget target = game.getWidget(this.targetHandleId); final StringMsgTargetCheckReceiver targetReceiver = (StringMsgTargetCheckReceiver) targetCheckReceiver; ability.checkCanTarget(game, caster, this.orderId, target, targetReceiver); if (targetReceiver.getTarget() != null) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java index 0d070c4..2ebed45 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java @@ -21,7 +21,7 @@ public interface SimulationRenderController { void createInstantAttackEffect(CSimulation cSimulation, CUnit source, CUnitAttackInstant attack, CWidget target); - void spawnUnitDamageSound(CUnit damagedUnit, final String weaponSound, String armorType); + void spawnDamageSound(CWidget damagedDestructable, String weaponSound, String armorType); void spawnUnitConstructionSound(CUnit constructingUnit, CUnit constructedStructure); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java index cf10732..1e63f3a 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java @@ -76,6 +76,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.camera.GameCameraManager; import com.etheller.warsmash.viewer5.handlers.w3x.camera.PortraitCameraManager; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.PathingFlags; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderWidget; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.AbilityDataUI; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.IconUI; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.commandbuttons.CommandButtonListener; @@ -85,10 +86,10 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CPlayerStateListene import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit.QueueItemType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitClassification; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitFilterFunction; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitStateListener; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidgetFilterFunction; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityAttack; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityGeneric; @@ -252,6 +253,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma private MeleeUIAbilityActivationReceiver meleeUIAbilityActivationReceiver; private MdxModel waypointModel; private final List waypointModelInstances = new ArrayList<>(); + private List selectedUnits; public MeleeUI(final DataSource dataSource, final ExtendViewport uiViewport, final FreeTypeFontGenerator fontGenerator, final Scene uiScene, final Scene portraitScene, @@ -1157,9 +1159,9 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } } - private final class ActiveCommandUnitTargetFilter implements CUnitFilterFunction { + private final class ActiveCommandUnitTargetFilter implements CWidgetFilterFunction { @Override - public boolean call(final CUnit unit) { + public boolean call(final CWidget unit) { final BooleanAbilityTargetCheckReceiver targetReceiver = BooleanAbilityTargetCheckReceiver .getInstance(); MeleeUI.this.activeCommand.checkCanTarget(MeleeUI.this.war3MapViewer.simulation, @@ -1735,16 +1737,18 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma clearAndRepopulateCommandCard(); } else { - final RenderUnit rayPickUnit = this.war3MapViewer.rayPickUnit(screenX, worldScreenY, + final RenderWidget rayPickUnit = this.war3MapViewer.rayPickUnit(screenX, worldScreenY, this.activeCommandUnitTargetFilter); final boolean shiftDown = isShiftDown(); if (rayPickUnit != null) { this.unitOrderListener.issueTargetOrder( this.activeCommandUnit.getSimulationUnit().getHandleId(), this.activeCommand.getHandleId(), this.activeCommandOrderId, - rayPickUnit.getSimulationUnit().getHandleId(), shiftDown); - if (getSelectedUnit().soundset.yesAttack - .playUnitResponse(this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { + rayPickUnit.getSimulationWidget().getHandleId(), shiftDown); + final UnitSound yesSound = (this.activeCommand instanceof CAbilityAttack) + ? getSelectedUnit().soundset.yesAttack + : getSelectedUnit().soundset.yes; + if (yesSound.playUnitResponse(this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { portraitTalk(); } this.selectedSoundCount = 0; @@ -1808,14 +1812,15 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma else { if (button == Input.Buttons.RIGHT) { if (getSelectedUnit() != null) { - final RenderUnit rayPickUnit = this.war3MapViewer.rayPickUnit(screenX, worldScreenY); - if ((rayPickUnit != null) && !rayPickUnit.getSimulationUnit().isDead()) { + final RenderWidget rayPickUnit = this.war3MapViewer.rayPickUnit(screenX, worldScreenY); + if ((rayPickUnit != null) && !rayPickUnit.getSimulationWidget().isDead()) { boolean ordered = false; boolean rallied = false; - for (final RenderUnit unit : this.war3MapViewer.selected) { + boolean attacked = false; + for (final RenderUnit unit : this.selectedUnits) { for (final CAbility ability : unit.getSimulationUnit().getAbilities()) { ability.checkCanTarget(this.war3MapViewer.simulation, unit.getSimulationUnit(), - OrderIds.smart, rayPickUnit.getSimulationUnit(), + OrderIds.smart, rayPickUnit.getSimulationWidget(), CWidgetAbilityTargetCheckReceiver.INSTANCE); final CWidget targetWidget = CWidgetAbilityTargetCheckReceiver.INSTANCE.getTarget(); if (targetWidget != null) { @@ -1823,14 +1828,17 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma ability.getHandleId(), OrderIds.smart, targetWidget.getHandleId(), isShiftDown()); rallied |= ability instanceof CAbilityRally; + attacked |= ability instanceof CAbilityAttack; ordered = true; } } } if (ordered) { - if (getSelectedUnit().soundset.yesAttack.playUnitResponse( - this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { + final UnitSound yesSound = attacked ? getSelectedUnit().soundset.yesAttack + : getSelectedUnit().soundset.yes; + if (yesSound.playUnitResponse(this.war3MapViewer.worldScene.audioContext, + getSelectedUnit())) { portraitTalk(); } if (rallied) { @@ -1839,53 +1847,19 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } this.selectedSoundCount = 0; } + else { + rightClickMove(screenX, worldScreenY); + } } else { - this.war3MapViewer.getClickLocation(clickLocationTemp, screenX, (int) worldScreenY); - this.war3MapViewer.showConfirmation(clickLocationTemp, 0, 1, 0); - clickLocationTemp2.set(clickLocationTemp.x, clickLocationTemp.y); - - boolean ordered = false; - boolean rallied = false; - for (final RenderUnit unit : this.war3MapViewer.selected) { - for (final CAbility ability : unit.getSimulationUnit().getAbilities()) { - ability.checkCanUse(this.war3MapViewer.simulation, unit.getSimulationUnit(), - OrderIds.smart, BooleanAbilityActivationReceiver.INSTANCE); - if (BooleanAbilityActivationReceiver.INSTANCE.isOk()) { - ability.checkCanTarget(this.war3MapViewer.simulation, unit.getSimulationUnit(), - OrderIds.smart, clickLocationTemp2, - PointAbilityTargetCheckReceiver.INSTANCE); - final Vector2 target = PointAbilityTargetCheckReceiver.INSTANCE.getTarget(); - if (target != null) { - this.unitOrderListener.issuePointOrder( - unit.getSimulationUnit().getHandleId(), ability.getHandleId(), - OrderIds.smart, clickLocationTemp2.x, clickLocationTemp2.y, - isShiftDown()); - rallied |= ability instanceof CAbilityRally; - ordered = true; - } - } - } - - } - - if (ordered) { - if (getSelectedUnit().soundset.yes.playUnitResponse( - this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { - portraitTalk(); - } - if (rallied) { - this.war3MapViewer.getUiSounds().getSound("RallyPointPlace") - .play(this.uiScene.audioContext, 0, 0); - } - this.selectedSoundCount = 0; - } + rightClickMove(screenX, worldScreenY); } } } else { - final List selectedUnits = this.war3MapViewer.selectUnit(screenX, worldScreenY, false); - selectUnits(selectedUnits); + final List selectedUnits = this.war3MapViewer.selectUnit(screenX, worldScreenY, + false); + selectWidgets(selectedUnits); } } } @@ -1898,7 +1872,57 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma return false; } + private void rightClickMove(final int screenX, final float worldScreenY) { + this.war3MapViewer.getClickLocation(clickLocationTemp, screenX, (int) worldScreenY); + this.war3MapViewer.showConfirmation(clickLocationTemp, 0, 1, 0); + clickLocationTemp2.set(clickLocationTemp.x, clickLocationTemp.y); + + boolean ordered = false; + boolean rallied = false; + for (final RenderUnit unit : this.selectedUnits) { + for (final CAbility ability : unit.getSimulationUnit().getAbilities()) { + ability.checkCanUse(this.war3MapViewer.simulation, unit.getSimulationUnit(), OrderIds.smart, + BooleanAbilityActivationReceiver.INSTANCE); + if (BooleanAbilityActivationReceiver.INSTANCE.isOk()) { + ability.checkCanTarget(this.war3MapViewer.simulation, unit.getSimulationUnit(), OrderIds.smart, + clickLocationTemp2, PointAbilityTargetCheckReceiver.INSTANCE); + final Vector2 target = PointAbilityTargetCheckReceiver.INSTANCE.getTarget(); + if (target != null) { + this.unitOrderListener.issuePointOrder(unit.getSimulationUnit().getHandleId(), + ability.getHandleId(), OrderIds.smart, clickLocationTemp2.x, clickLocationTemp2.y, + isShiftDown()); + rallied |= ability instanceof CAbilityRally; + ordered = true; + } + } + } + + } + + if (ordered) { + if (getSelectedUnit().soundset.yes.playUnitResponse(this.war3MapViewer.worldScene.audioContext, + getSelectedUnit())) { + portraitTalk(); + } + if (rallied) { + this.war3MapViewer.getUiSounds().getSound("RallyPointPlace").play(this.uiScene.audioContext, 0, 0); + } + this.selectedSoundCount = 0; + } + } + + private void selectWidgets(final List selectedUnits) { + final List units = new ArrayList<>(); + for (final RenderWidget widget : selectedUnits) { + if (widget instanceof RenderUnit) { + units.add((RenderUnit) widget); + } + } + selectUnits(units); + } + private void selectUnits(final List selectedUnits) { + this.selectedUnits = selectedUnits; if (!selectedUnits.isEmpty()) { final RenderUnit unit = selectedUnits.get(0); final boolean selectionChanged = getSelectedUnit() != unit; @@ -2027,10 +2051,10 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } break; case 1: - final List unitList = Arrays.asList( + final List unitList = Arrays.asList( this.war3MapViewer.getRenderPeer(this.selectedUnit.getSimulationUnit().getWorkerInside())); this.war3MapViewer.doSelectUnit(unitList); - selectUnits(unitList); + selectWidgets(unitList); break; } } From ec1510adcdaf506fd2056189f1a60182dc985225 Mon Sep 17 00:00:00 2001 From: Retera Date: Sat, 23 Jan 2021 23:03:03 -0500 Subject: [PATCH 090/116] Update ramps and cliffs --- .../handlers/w3x/environment/Terrain.java | 54 +++++------ .../warsmash/desktop/util/TerrainView.java | 36 +++++++ .../desktop/util/TerrainViewPanel.java | 93 +++++++++++++++++++ 3 files changed, 156 insertions(+), 27 deletions(-) create mode 100644 desktop/src/com/etheller/warsmash/desktop/util/TerrainView.java create mode 100644 desktop/src/com/etheller/warsmash/desktop/util/TerrainViewPanel.java diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java index 8fef83a..fcb3325 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java @@ -594,25 +594,24 @@ public class Terrain { } if (!(facingDown && (j == 0)) && !(!facingDown && (j >= (this.rows - 2))) && !(facingLeft && (i == 0)) && !(!facingLeft && (i >= (this.columns - 2)))) { - final boolean br = ((bottomLeft.getRamp() != 0) != (bottomRight.getRamp() != 0)) - && ((topLeft.getRamp() != 0) != (topRight.getRamp() != 0)) - && !this.corners[i + (bottomRight.getRamp() != 0 ? 1 : 0)][j - + (facingDown ? -1 : 1)].cliff; + final boolean verticalRamp = ((bottomLeft.isRamp()) != (bottomRight.isRamp())) + && ((topLeft.isRamp()) != (topRight.isRamp())) + && !this.corners[i][j + (facingDown ? -1 : 1)].cliff; - final boolean bo = ((bottomLeft.getRamp() != 0) != (topLeft.getRamp() != 0)) - && ((bottomRight.getRamp() != 0) != (topRight.getRamp() != 0)) - && !this.corners[i + (facingLeft ? -1 : 1)][j + (topLeft.getRamp() != 0 ? 1 : 0)].cliff; + final boolean horizontalRamp = ((bottomLeft.isRamp()) != (topLeft.isRamp())) + && ((bottomRight.isRamp()) != (topRight.isRamp())) + && !this.corners[i + (facingLeft ? -1 : 1)][j].cliff; - if (br || bo) { - String fileName = "" + (char) ((bottomLeft.getRamp() != 0 ? 'L' : 'A') - + ((bottomLeft.getLayerHeight() - base) * (bottomLeft.getRamp() != 0 ? -4 : 1))) - + (char) ((topLeft.getRamp() != 0 ? 'L' : 'A') - + ((topLeft.getLayerHeight() - base) * (topLeft.getRamp() != 0 ? -4 : 1))) - + (char) ((topRight.getRamp() != 0 ? 'L' : 'A') - + ((topRight.getLayerHeight() - base) * (topRight.getRamp() != 0 ? -4 : 1))) - + (char) ((bottomRight.getRamp() != 0 ? 'L' : 'A') - + ((bottomRight.getLayerHeight() - base) - * (bottomRight.getRamp() != 0 ? -4 : 1))); + if (verticalRamp || horizontalRamp) { + String fileName = "" + + (char) ((topLeft.isRamp() ? 'L' : 'A') + + ((topLeft.getLayerHeight() - base) * (topLeft.isRamp() ? -4 : 1))) + + (char) ((topRight.isRamp() ? 'L' : 'A') + + ((topRight.getLayerHeight() - base) * (topRight.isRamp() ? -4 : 1))) + + (char) ((bottomRight.isRamp() ? 'L' : 'A') + + ((bottomRight.getLayerHeight() - base) * (bottomRight.isRamp() ? -4 : 1))) + + (char) ((bottomLeft.isRamp() ? 'L' : 'A') + + ((bottomLeft.getLayerHeight() - base) * (bottomLeft.isRamp() ? -4 : 1))); final String rampModelDir = this.cliffTextures.get(bottomLeftCliffTex).rampModelDir; fileName = "Doodads\\Terrain\\" + rampModelDir + "\\" + rampModelDir + fileName + "0.mdx"; @@ -625,19 +624,20 @@ public class Terrain { for (int ji = this.cliffs.size(); ji-- > 0;) { final IVec3 pos = this.cliffs.get(ji); - if ((pos.x == (i + ((bo ? 1 : 0) * (facingLeft ? 0 : 1)))) - && (pos.y == (j - ((br ? 1 : 0) * (facingDown ? 1 : 0))))) { + if ((pos.x == (i + ((horizontalRamp ? 1 : 0) * (facingLeft ? -1 : 0)))) + && (pos.y == (j - ((verticalRamp ? 1 : 0) * (facingDown ? 1 : 0))))) { this.cliffs.remove(ji); break; } } - this.cliffs.add(new IVec3((i + ((bo ? 1 : 0) * (facingLeft ? 0 : 1))), - (j - ((br ? 1 : 0) * (facingDown ? 1 : 0))), this.pathToCliff.get(fileName))); + this.cliffs.add(new IVec3((i + ((horizontalRamp ? 1 : 0) * (facingLeft ? -1 : 0))), + (j - ((verticalRamp ? 1 : 0) * (facingDown ? 1 : 0))), + this.pathToCliff.get(fileName))); bottomLeft.romp = true; - this.corners[i + ((facingLeft ? -1 : 1) * (bo ? 1 : 0))][j - + ((facingDown ? -1 : 1) * (br ? 1 : 0))].romp = true; + this.corners[i + ((facingLeft ? -1 : 1) * (horizontalRamp ? 1 : 0))][j + + ((facingDown ? -1 : 1) * (verticalRamp ? 1 : 0))].romp = true; continue; } @@ -788,8 +788,8 @@ public class Terrain { final RenderCorner topLeft = this.corners[x + i][y + j + 1]; final RenderCorner topRight = this.corners[x + i + 1][y + j + 1]; - if ((bottomLeft.getRamp() != 0) && (topLeft.getRamp() != 0) && (bottomRight.getRamp() != 0) - && (topRight.getRamp() != 0) && (!bottomLeft.romp) && (!bottomRight.romp) + if ((bottomLeft.isRamp()) && (topLeft.isRamp()) && (bottomRight.isRamp()) + && (topRight.isRamp()) && (!bottomLeft.romp) && (!bottomRight.romp) && (!topLeft.romp) && (!topRight.romp)) { break ILoop; } @@ -825,8 +825,8 @@ public class Terrain { final RenderCorner topLeft = this.corners[x][y + 1]; final RenderCorner topRight = this.corners[x + 1][y + 1]; - return (bottomLeft.getRamp() != 0) && (topLeft.getRamp() != 0) && (bottomRight.getRamp() != 0) - && (topRight.getRamp() != 0) && !((bottomLeft.getLayerHeight() == topRight.getLayerHeight()) + return (bottomLeft.isRamp()) && (topLeft.isRamp()) && (bottomRight.isRamp()) && (topRight.isRamp()) + && !((bottomLeft.getLayerHeight() == topRight.getLayerHeight()) && (topLeft.getLayerHeight() == bottomRight.getLayerHeight())); } diff --git a/desktop/src/com/etheller/warsmash/desktop/util/TerrainView.java b/desktop/src/com/etheller/warsmash/desktop/util/TerrainView.java new file mode 100644 index 0000000..3ef9214 --- /dev/null +++ b/desktop/src/com/etheller/warsmash/desktop/util/TerrainView.java @@ -0,0 +1,36 @@ +package com.etheller.warsmash.desktop.util; + +import java.io.IOException; + +import javax.swing.JFrame; +import javax.swing.JScrollPane; +import javax.swing.WindowConstants; + +import com.etheller.warsmash.WarsmashGdxMapGame; +import com.etheller.warsmash.datasources.DataSource; +import com.etheller.warsmash.desktop.DesktopLauncher; +import com.etheller.warsmash.parsers.w3x.War3Map; +import com.etheller.warsmash.parsers.w3x.w3e.War3MapW3e; +import com.etheller.warsmash.units.DataTable; + +public class TerrainView { + public static void main(final String[] args) { + final DataTable warsmashIni = DesktopLauncher.loadWarsmashIni(); + final DataSource dataSources = WarsmashGdxMapGame.parseDataSources(warsmashIni); + final War3Map war3Map = new War3Map(dataSources, warsmashIni.get("Map").getField("FilePath")); + try { + final War3MapW3e environmentFile = war3Map.readEnvironment(); + final TerrainViewPanel terrainViewPanel = new TerrainViewPanel(environmentFile); + + final JFrame frame = new JFrame("TerrainView"); + frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); + frame.setContentPane(new JScrollPane(terrainViewPanel)); + frame.setBounds(0, 0, 800, 600); + frame.setLocationRelativeTo(null); + frame.setVisible(true); + } + catch (final IOException e) { + e.printStackTrace(); + } + } +} diff --git a/desktop/src/com/etheller/warsmash/desktop/util/TerrainViewPanel.java b/desktop/src/com/etheller/warsmash/desktop/util/TerrainViewPanel.java new file mode 100644 index 0000000..d1055c7 --- /dev/null +++ b/desktop/src/com/etheller/warsmash/desktop/util/TerrainViewPanel.java @@ -0,0 +1,93 @@ +package com.etheller.warsmash.desktop.util; + +import java.awt.Dimension; +import java.awt.Font; +import java.awt.Graphics; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; + +import javax.swing.Box; +import javax.swing.JPanel; + +import com.etheller.warsmash.parsers.w3x.w3e.Corner; +import com.etheller.warsmash.parsers.w3x.w3e.War3MapW3e; + +public class TerrainViewPanel extends JPanel { + private final War3MapW3e environmentFile; + private final Font baseFont; + private final Font biggerFont; + private int cliffMode = 0; + + public TerrainViewPanel(final War3MapW3e environmentFile) { + this.environmentFile = environmentFile; + add(Box.createRigidArea(new Dimension(this.environmentFile.getCorners()[0].length * 32, + this.environmentFile.getCorners().length * 32))); + this.baseFont = getFont(); + this.biggerFont = this.baseFont.deriveFont(24f); + addMouseListener(new MouseListener() { + @Override + public void mouseReleased(final MouseEvent e) { + + } + + @Override + public void mousePressed(final MouseEvent e) { + TerrainViewPanel.this.cliffMode = (TerrainViewPanel.this.cliffMode + 1) % 4; + repaint(); + } + + @Override + public void mouseExited(final MouseEvent e) { + + } + + @Override + public void mouseEntered(final MouseEvent e) { + + } + + @Override + public void mouseClicked(final MouseEvent e) { + + } + }); + } + + @Override + protected void paintComponent(final Graphics g) { + super.paintComponent(g); + final Corner[][] corners = this.environmentFile.getCorners(); + for (int i = 0; i < corners.length; i++) { + final int length = corners[i].length; + for (int j = 0; j < length; j++) { + int base = 0; + final int value; + switch (this.cliffMode) { + case 0: + value = corners[i][j].getRamp(); + break; + case 1: + value = corners[i][j].getLayerHeight(); + base = 2; + break; + case 2: + value = corners[i][j].getGroundTexture(); + break; + case 3: + value = corners[i][j].getCliffTexture(); + break; + default: + value = 0; + break; + } + if (value != base) { + g.setFont(this.biggerFont); + } + else { + g.setFont(this.baseFont); + } + g.drawString(value + "", j * 32, (length - i - 1) * 32); + } + } + } +} From 056eed55daed258e8817bc959e57b76e988aeb9c Mon Sep 17 00:00:00 2001 From: Retera Date: Sat, 23 Jan 2021 23:35:40 -0500 Subject: [PATCH 091/116] Update ini --- core/assets/warsmash.ini | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/assets/warsmash.ini b/core/assets/warsmash.ini index c395553..e0585c6 100644 --- a/core/assets/warsmash.ini +++ b/core/assets/warsmash.ini @@ -21,7 +21,7 @@ Path07="." //FilePath="CombatUnitTests.w3x" //FilePath="PitchRoll.w3x" //FilePath="PeonStartingBase.w3x" -FilePath="MyStromguarde.w3m" +//FilePath="MyStromguarde.w3m" //FilePath="ColdArrows.w3m" //FilePath="DungeonGoldMine.w3m" //FilePath="PlayerPeasants.w3m" @@ -42,3 +42,5 @@ FilePath="MyStromguarde.w3m" //FilePath="QuadtreeBugs.w3x" //FilePath="test2.w3x" //FilePath="FarseerHoldPositionTest.w3x" +FilePath="Ramps.w3m" +//FilePath="V1\Farm.w3x" From 2d8b3e7cae753fb143a4a02eaea851ef0afaeca8 Mon Sep 17 00:00:00 2001 From: Retera Date: Sun, 24 Jan 2021 12:15:35 -0500 Subject: [PATCH 092/116] Improve ramps and ramp shading with some heuristics --- core/assets/warsmash.ini | 4 +- .../etheller/warsmash/WarsmashGdxMapGame.java | 2 +- .../warsmash/parsers/w3x/w3e/Corner.java | 8 ++ .../handlers/w3x/environment/Terrain.java | 129 +++++++++++++----- .../w3x/environment/TerrainShaders.java | 16 ++- 5 files changed, 117 insertions(+), 42 deletions(-) diff --git a/core/assets/warsmash.ini b/core/assets/warsmash.ini index e0585c6..ce51670 100644 --- a/core/assets/warsmash.ini +++ b/core/assets/warsmash.ini @@ -42,5 +42,5 @@ Path07="." //FilePath="QuadtreeBugs.w3x" //FilePath="test2.w3x" //FilePath="FarseerHoldPositionTest.w3x" -FilePath="Ramps.w3m" -//FilePath="V1\Farm.w3x" +//FilePath="Ramps.w3m" +FilePath="V1\Farm.w3x" diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java index 1779796..2a9846f 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java @@ -56,7 +56,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.SettableCommandErro public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProvider, InputProcessor { private static final boolean ENABLE_AUDIO = true; - private static final boolean ENABLE_MUSIC = true; + private static final boolean ENABLE_MUSIC = false; private DataSource codebase; private War3MapViewer viewer; private final Rectangle tempRect = new Rectangle(); diff --git a/core/src/com/etheller/warsmash/parsers/w3x/w3e/Corner.java b/core/src/com/etheller/warsmash/parsers/w3x/w3e/Corner.java index 398a82c..ff64e7c 100644 --- a/core/src/com/etheller/warsmash/parsers/w3x/w3e/Corner.java +++ b/core/src/com/etheller/warsmash/parsers/w3x/w3e/Corner.java @@ -99,6 +99,10 @@ public class Corner { return this.ramp != 0; } + public void setRamp(final int ramp) { + this.ramp = ramp; + } + public int getBlight() { return this.blight; } @@ -127,6 +131,10 @@ public class Corner { return this.cliffTexture; } + public void setCliffTexture(final int cliffTexture) { + this.cliffTexture = cliffTexture; + } + public int getLayerHeight() { return this.layerHeight; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java index fcb3325..232ab5a 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java @@ -62,6 +62,11 @@ public class Terrain { private static final float[] fourComponentHeap = new float[4]; private static final Matrix4 tempMatrix = new Matrix4(); private static final boolean WIREFRAME_TERRAIN = false; + // In WC3 they didn't finish developing the height 3 ramps + // There are a couple of models for some of them but generally they are just bad + // voodoo. Enabling this setting should be coupled with creating + // new artwork for advanced ramp use cases that don't exist in WC3. + private static final boolean DISALLOW_HEIGHT_3_RAMPS = true; public ShaderProgram groundShader; public ShaderProgram waterShader; @@ -595,51 +600,83 @@ public class Terrain { if (!(facingDown && (j == 0)) && !(!facingDown && (j >= (this.rows - 2))) && !(facingLeft && (i == 0)) && !(!facingLeft && (i >= (this.columns - 2)))) { final boolean verticalRamp = ((bottomLeft.isRamp()) != (bottomRight.isRamp())) - && ((topLeft.isRamp()) != (topRight.isRamp())) - && !this.corners[i][j + (facingDown ? -1 : 1)].cliff; + && ((topLeft.isRamp()) != (topRight.isRamp())); final boolean horizontalRamp = ((bottomLeft.isRamp()) != (topLeft.isRamp())) - && ((bottomRight.isRamp()) != (topRight.isRamp())) - && !this.corners[i + (facingLeft ? -1 : 1)][j].cliff; + && ((bottomRight.isRamp()) != (topRight.isRamp())); if (verticalRamp || horizontalRamp) { - String fileName = "" - + (char) ((topLeft.isRamp() ? 'L' : 'A') - + ((topLeft.getLayerHeight() - base) * (topLeft.isRamp() ? -4 : 1))) - + (char) ((topRight.isRamp() ? 'L' : 'A') - + ((topRight.getLayerHeight() - base) * (topRight.isRamp() ? -4 : 1))) - + (char) ((bottomRight.isRamp() ? 'L' : 'A') - + ((bottomRight.getLayerHeight() - base) * (bottomRight.isRamp() ? -4 : 1))) - + (char) ((bottomLeft.isRamp() ? 'L' : 'A') - + ((bottomLeft.getLayerHeight() - base) * (bottomLeft.isRamp() ? -4 : 1))); - - final String rampModelDir = this.cliffTextures.get(bottomLeftCliffTex).rampModelDir; - fileName = "Doodads\\Terrain\\" + rampModelDir + "\\" + rampModelDir + fileName + "0.mdx"; - - if (this.dataSource.has(fileName)) { - if (!this.pathToCliff.containsKey(fileName)) { - this.cliffMeshes.add(new CliffMesh(fileName, this.dataSource, Gdx.gl30)); - this.pathToCliff.put(fileName, this.cliffMeshes.size() - 1); + final boolean rampBlockedByCliff = ((verticalRamp + && this.corners[i][j + (facingDown ? -1 : 1)].cliff) + || (horizontalRamp && this.corners[i + (facingLeft ? -1 : 1)][j].cliff)); + final int topLeftHeight = topLeft.getLayerHeight() - base; + final int topRightHeight = topRight.getLayerHeight() - base; + final int bottomRightHeight = bottomRight.getLayerHeight() - base; + final int bottomLeftHeight = bottomLeft.getLayerHeight() - base; + boolean invalidRamp = false; + if (DISALLOW_HEIGHT_3_RAMPS) { + if (topLeftHeight > 1) { + invalidRamp = true; + topLeft.setRamp(0); } + if (topRightHeight > 1) { + invalidRamp = true; + topRight.setRamp(0); + } + if (bottomRightHeight > 1) { + invalidRamp = true; + bottomRight.setRamp(0); + } + if (bottomLeftHeight > 1) { + invalidRamp = true; + bottomLeft.setRamp(0); + } + if (rampBlockedByCliff) { + invalidRamp = true; + } + } + if (!invalidRamp) { + String fileName = "" + getRampLetter(topLeftHeight, topLeft.isRamp()) + + getRampLetter(topRightHeight, topRight.isRamp()) + + getRampLetter(bottomRightHeight, bottomRight.isRamp()) + + getRampLetter(bottomLeftHeight, bottomLeft.isRamp()); - for (int ji = this.cliffs.size(); ji-- > 0;) { - final IVec3 pos = this.cliffs.get(ji); - if ((pos.x == (i + ((horizontalRamp ? 1 : 0) * (facingLeft ? -1 : 0)))) - && (pos.y == (j - ((verticalRamp ? 1 : 0) * (facingDown ? 1 : 0))))) { - this.cliffs.remove(ji); - break; + final String rampModelDir = this.cliffTextures.get(bottomLeftCliffTex).rampModelDir; + fileName = "Doodads\\Terrain\\" + rampModelDir + "\\" + rampModelDir + fileName + + "0.mdx"; + + if (this.dataSource.has(fileName)) { + if (!this.pathToCliff.containsKey(fileName)) { + this.cliffMeshes.add(new CliffMesh(fileName, this.dataSource, Gdx.gl30)); + this.pathToCliff.put(fileName, this.cliffMeshes.size() - 1); } + + for (int ji = this.cliffs.size(); ji-- > 0;) { + final IVec3 pos = this.cliffs.get(ji); + if ((pos.x == (i + ((horizontalRamp ? 1 : 0) * (facingLeft ? -1 : 0)))) + && (pos.y == (j - ((verticalRamp ? 1 : 0) * (facingDown ? 1 : 0))))) { + this.cliffs.remove(ji); + break; + } + } + + this.cliffs.add(new IVec3((i + ((horizontalRamp ? 1 : 0) * (facingLeft ? -1 : 0))), + (j - ((verticalRamp ? 1 : 0) * (facingDown ? 1 : 0))), + this.pathToCliff.get(fileName))); + bottomLeft.romp = true; + bottomLeft.setCliffTexture(bottomLeftCliffTex); + bottomRight.setCliffTexture(bottomLeftCliffTex); + topLeft.setCliffTexture(bottomLeftCliffTex); + topRight.setCliffTexture(bottomLeftCliffTex); + this.corners[i + ((facingLeft ? -1 : 1) * (horizontalRamp ? 1 : 0))][j + + ((facingDown ? -1 : 1) * (verticalRamp ? 1 : 0))] + .setCliffTexture(bottomLeftCliffTex); + + this.corners[i + ((facingLeft ? -1 : 1) * (horizontalRamp ? 1 : 0))][j + + ((facingDown ? -1 : 1) * (verticalRamp ? 1 : 0))].romp = true; + + continue; } - - this.cliffs.add(new IVec3((i + ((horizontalRamp ? 1 : 0) * (facingLeft ? -1 : 0))), - (j - ((verticalRamp ? 1 : 0) * (facingDown ? 1 : 0))), - this.pathToCliff.get(fileName))); - bottomLeft.romp = true; - - this.corners[i + ((facingLeft ? -1 : 1) * (horizontalRamp ? 1 : 0))][j - + ((facingDown ? -1 : 1) * (verticalRamp ? 1 : 0))].romp = true; - - continue; } } } @@ -1502,4 +1539,22 @@ public class Terrain { gl.glTexImage2D(GL30.GL_TEXTURE_2D, 0, GL30.GL_R8, columns, rows, 0, GL30.GL_RED, GL30.GL_UNSIGNED_BYTE, RenderMathUtils.wrap(Terrain.this.shadowData)); } + + private static char getRampLetter(final int layerHeightOffset, final boolean isRamp) { + if (isRamp) { + switch (layerHeightOffset) { + case 0: + return 'L'; + case 1: + return 'H'; + case 2: + return 'X'; + default: + throw new IllegalArgumentException("Invalid ramp"); + } + } + else { + return (char) ('A' + layerHeightOffset); + } + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/TerrainShaders.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/TerrainShaders.java index 1741b07..f54e4e7 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/TerrainShaders.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/TerrainShaders.java @@ -59,8 +59,20 @@ public class TerrainShaders { " float hR = texelFetch(height_texture, height_pos + off.xz, 0).r;\r\n" + // " float hD = texelFetch(height_texture, height_pos - off.zy, 0).r;\r\n" + // " float hU = texelFetch(height_texture, height_pos + off.zy, 0).r;\r\n" + // - " vec3 terrain_normal = normalize(vec3(vNormal.y, -vNormal.x, vNormal.z));//vec3(hL - hR, hD - hU, 2.0)+vNormal);\r\n" - + // + " bool edgeX = (vPosition.y) == float((int(vPosition.y))/128*128);\r\n" + // + " bool edgeY = (vPosition.x) == float((int(vPosition.x))/128*128);\r\n" + // + " bool edgeZ = (vPosition.z) == float((int(vPosition.z))/128*128);\r\n" + // + " vec3 terrain_normal = vec3(vNormal.y, -vNormal.x, vNormal.z);\r\n" + // + " if(edgeX) {\r\n" + // + " terrain_normal.x = hL - hR;\r\n" + // + " }\r\n" + // + " if(edgeY) {\r\n" + // + " terrain_normal.y = hD - hU;\r\n" + // + " }\r\n" + // + " if(edgeZ) {\r\n" + // + " terrain_normal.z = 2.0;\r\n" + // + " }\r\n" + // + " terrain_normal = normalize(terrain_normal);\r\n" + // "\r\n" + // " Normal = terrain_normal;\r\n" + // Shaders.lightSystem("terrain_normal", "myposition.xyz", "lightTexture", "lightTextureHeight", From cacdf7f266c76c80efbdf3e8b3cbf3c81d762913 Mon Sep 17 00:00:00 2001 From: Retera Date: Sun, 24 Jan 2021 12:46:34 -0500 Subject: [PATCH 093/116] Disable auto attack for workers, fix NPE in return resources --- .../warsmash/viewer5/handlers/w3x/simulation/CUnit.java | 3 ++- .../w3x/simulation/behaviors/CBehaviorAttack.java | 4 +--- .../behaviors/harvest/CBehaviorReturnResources.java | 9 +++++++-- .../combat/attacks/CUnitAttackMissileSplash.java | 1 - 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java index edcbc29..e16e779 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java @@ -342,7 +342,8 @@ public class CUnit extends CWidget { } public boolean autoAcquireAttackTargets(final CSimulation game, final boolean disableMove) { - if (!this.unitType.getAttacks().isEmpty()) { + if (!this.unitType.getAttacks().isEmpty() + && !this.unitType.getClassifications().contains(CUnitClassification.PEON)) { if (this.collisionRectangle != null) { tempRect.set(this.collisionRectangle); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java index 47faf30..6dab933 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java @@ -47,10 +47,8 @@ public class CBehaviorAttack extends CAbstractRangedBehavior { if (simulation.getGameTurnTick() < this.unit.getCooldownEndTime()) { range += this.unitAttack.getRangeMotionBuffer(); } - final double rangeCheckDistance = this.unit.distance(this.target); - System.out.println("rangeCheckDistance=" + rangeCheckDistance); return this.unit.canReach(this.target, range) - && (rangeCheckDistance >= this.unit.getUnitType().getMinimumAttackRange()); + && (this.unit.distance(this.target) >= this.unit.getUnitType().getMinimumAttackRange()); } @Override diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/harvest/CBehaviorReturnResources.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/harvest/CBehaviorReturnResources.java index 294f3ba..31cef51 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/harvest/CBehaviorReturnResources.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/harvest/CBehaviorReturnResources.java @@ -26,8 +26,13 @@ public class CBehaviorReturnResources extends CAbstractRangedBehavior implements this.abilityHarvest = abilityHarvest; } - public CBehaviorReturnResources reset(final CSimulation simulation) { - innerReset(findNearestDropoffPoint(simulation)); + public CBehavior reset(final CSimulation simulation) { + final CUnit nearestDropoffPoint = findNearestDropoffPoint(simulation); + if (nearestDropoffPoint == null) { + // TODO it is unconventional not to return self here + return this.unit.pollNextOrderBehavior(simulation); + } + innerReset(nearestDropoffPoint); return this; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileSplash.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileSplash.java index 266d6da..5f0ae91 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileSplash.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileSplash.java @@ -133,7 +133,6 @@ public class CUnitAttackMissileSplash extends CUnitAttackMissile { public boolean call(final CUnit enumUnit) { if (enumUnit.canBeTargetedBy(this.simulation, this.source, this.attack.areaOfEffectTargets)) { final double distance = enumUnit.distance(this.x, this.y); - System.out.println("enum distance=" + distance); if (distance <= (this.attack.areaOfEffectFullDamage)) { enumUnit.damage(this.simulation, this.source, this.attack.getAttackType(), this.attack.getWeaponSound(), this.damage); From d4245e2e65b4e147ca4df6cf984107ec330c0b25 Mon Sep 17 00:00:00 2001 From: Retera Date: Sun, 24 Jan 2021 14:37:12 -0500 Subject: [PATCH 094/116] Change pathfinding processor to have a per player per frame cycle limit --- .../viewer5/handlers/w3x/SplatModel.java | 22 +- .../handlers/w3x/environment/Terrain.java | 15 +- .../handlers/w3x/simulation/CSimulation.java | 25 +- .../handlers/w3x/simulation/CUnit.java | 2 - .../handlers/w3x/simulation/CUnitType.java | 14 +- .../simulation/behaviors/CBehaviorMove.java | 182 ++++--- .../w3x/simulation/data/CUnitData.java | 6 +- .../pathing/CPathfindingProcessor.java | 484 ++++++++++-------- 8 files changed, 463 insertions(+), 287 deletions(-) diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SplatModel.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SplatModel.java index ffa3c76..2c79b02 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SplatModel.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SplatModel.java @@ -20,7 +20,7 @@ import com.etheller.warsmash.viewer5.ViewerTextureRenderable; * move around the unit selection circles without memory allocations? For now I * plan to simply port the RivSoft stuff, and come back later. */ -public class SplatModel { +public class SplatModel implements Comparable { private static final int MAX_VERTICES = 65000; private static final float NO_ABS_HEIGHT = -257f; private final ViewerTextureRenderable texture; @@ -514,4 +514,24 @@ public class SplatModel { this.absoluteHeights.size() * 4, RenderMathUtils.wrap(this.absoluteHeights)); } } + + @Override + public int compareTo(final SplatModel other) { + if (this.locations.isEmpty()) { + if (other.locations.isEmpty()) { + return 0; + } + else { + return 1; + } + } + else { + if (other.locations.isEmpty()) { + return -1; + } + else { + return Float.compare(this.locations.get(0)[4], other.locations.get(0)[4]); + } + } + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java index 232ab5a..fbd765c 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java @@ -7,6 +7,7 @@ import java.nio.Buffer; import java.nio.FloatBuffer; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; @@ -120,6 +121,7 @@ public class Terrain { public final DataTable uberSplatTable; private final Map uberSplatModels; + private final List uberSplatModelsList; private int shadowMap; public final Map splats = new HashMap<>(); public final Map> shadows = new HashMap<>(); @@ -414,6 +416,7 @@ public class Terrain { this.centerOffset = w3eFile.getCenterOffset(); this.uberSplatModels = new LinkedHashMap<>(); + this.uberSplatModelsList = new ArrayList<>(); this.mapBounds = w3iFile.getCameraBoundsComplements(); this.shaderMapBounds = new float[] { (this.mapBounds[0] * 128.0f) + this.centerOffset[0], (this.mapBounds[2] * 128.0f) + this.centerOffset[1], @@ -1040,7 +1043,7 @@ public class Terrain { gl.glUniform1f(shader.getUniformLocation("u_lightTextureHeight"), terrainLightsTexture.getHeight()); // Render the cliffs - for (final SplatModel splat : this.uberSplatModels.values()) { + for (final SplatModel splat : this.uberSplatModelsList) { if (splat.isNoDepthTest() == onTopLayer) { splat.render(gl, shader); } @@ -1432,16 +1435,18 @@ public class Terrain { (Texture) this.viewer.load(path, PathSolver.DEFAULT, null), splat.locations, this.centerOffset, splat.unitMapping.isEmpty() ? null : splat.unitMapping, false, false); splatModel.color[3] = splat.opacity; - this.uberSplatModels.put(path, splatModel); + this.addSplatBatchModel(path, splatModel); } } public void removeSplatBatchModel(final String path) { - this.uberSplatModels.remove(path); + this.uberSplatModelsList.remove(this.uberSplatModels.remove(path)); } public void addSplatBatchModel(final String path, final SplatModel model) { this.uberSplatModels.put(path, model); + this.uberSplatModelsList.add(model); + Collections.sort(this.uberSplatModelsList); } public SplatMover addUberSplat(final String path, final float x, final float y, final float z, final float scale, @@ -1450,7 +1455,7 @@ public class Terrain { if (splatModel == null) { splatModel = new SplatModel(Gdx.gl30, (Texture) this.viewer.load(path, PathSolver.DEFAULT, null), new ArrayList<>(), this.centerOffset, new ArrayList<>(), unshaded, noDepthTest); - this.uberSplatModels.put(path, splatModel); + this.addSplatBatchModel(path, splatModel); } return splatModel.add(x - scale, y - scale, x + scale, y + scale, z, this.centerOffset); } @@ -1462,7 +1467,7 @@ public class Terrain { splatModel = new SplatModel(Gdx.gl30, (Texture) this.viewer.load(texture, PathSolver.DEFAULT, null), new ArrayList<>(), this.centerOffset, new ArrayList<>(), false, false); splatModel.color[3] = opacity; - this.uberSplatModels.put(texture, splatModel); + this.addSplatBatchModel(texture, splatModel); } return splatModel.add(x, y, x2, y2, zDepthUpward, this.centerOffset); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java index e9aec31..5bb923f 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java @@ -19,6 +19,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.RemovablePathingMapInstance; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorMove; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackInstant; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackMissile; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.projectile.CAttackProjectile; @@ -48,7 +49,7 @@ public class CSimulation { private int gameTurnTick = 0; private final PathingGrid pathingGrid; private final CWorldCollision worldCollision; - private final CPathfindingProcessor pathfindingProcessor; + private final CPathfindingProcessor[] pathfindingProcessors; private final CGameplayConstants gameplayConstants; private final Random seededRandom; private float currentGameDayTimeElapsed; @@ -75,7 +76,10 @@ public class CSimulation { this.newProjectiles = new ArrayList<>(); this.handleIdAllocator = new HandleIdAllocator(); this.worldCollision = new CWorldCollision(entireMapBounds, this.gameplayConstants.getMaxCollisionRadius()); - this.pathfindingProcessor = new CPathfindingProcessor(pathingGrid, this.worldCollision); + this.pathfindingProcessors = new CPathfindingProcessor[WarsmashConstants.MAX_PLAYERS]; + for (int i = 0; i < this.pathfindingProcessors.length; i++) { + this.pathfindingProcessors[i] = new CPathfindingProcessor(pathingGrid, this.worldCollision); + } this.seededRandom = seededRandom; this.players = new ArrayList<>(); for (int i = 0; i < (WarsmashConstants.MAX_PLAYERS - 4); i++) { @@ -175,13 +179,19 @@ public class CSimulation { return this.pathingGrid; } - public List findNaiveSlowPath(final CUnit ignoreIntersectionsWithThisUnit, + public void findNaiveSlowPath(final CUnit ignoreIntersectionsWithThisUnit, final CUnit ignoreIntersectionsWithThisSecondUnit, final float startX, final float startY, final Point2D.Float goal, final PathingGrid.MovementType movementType, final float collisionSize, - final boolean allowSmoothing) { - return this.pathfindingProcessor.findNaiveSlowPath(ignoreIntersectionsWithThisUnit, + final boolean allowSmoothing, final CBehaviorMove queueItem) { + final int playerIndex = queueItem.getUnit().getPlayerIndex(); + this.pathfindingProcessors[playerIndex].findNaiveSlowPath(ignoreIntersectionsWithThisUnit, ignoreIntersectionsWithThisSecondUnit, startX, startY, goal, movementType, collisionSize, - allowSmoothing); + allowSmoothing, queueItem); + } + + public void removeFromPathfindingQueue(final CBehaviorMove behaviorMove) { + final int playerIndex = behaviorMove.getUnit().getPlayerIndex(); + this.pathfindingProcessors[playerIndex].removeFromPathfindingQueue(behaviorMove); } public void update() { @@ -207,6 +217,9 @@ public class CSimulation { } this.projectiles.addAll(this.newProjectiles); this.newProjectiles.clear(); + for (final CPathfindingProcessor pathfindingProcessor : this.pathfindingProcessors) { + pathfindingProcessor.update(this); + } this.gameTurnTick++; this.currentGameDayTimeElapsed = (this.currentGameDayTimeElapsed + WarsmashConstants.SIMULATION_STEP_TIME) % this.gameplayConstants.getGameDayLength(); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java index e16e779..4598942 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java @@ -317,8 +317,6 @@ public class CUnit extends CWidget { this.currentBehavior.begin(game); } if (this.currentBehavior.getHighlightOrderId() != lastBehaviorHighlightOrderId) { - System.out.println("order ID change detected from " - + this.currentBehavior.getHighlightOrderId() + " to " + lastBehaviorHighlightOrderId); this.stateNotifier.ordersChanged(); } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java index 5015faa..9130586 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java @@ -55,6 +55,8 @@ public class CUnitType { private final int buildTime; private final EnumSet preventedPathingTypes; private final EnumSet requiredPathingTypes; + private final float propWindow; + private final float turnRate; public CUnitType(final String name, final int life, final int manaInitial, final int manaMaximum, final int speed, final int defense, final String abilityList, final boolean isBldg, final MovementType movementType, @@ -66,7 +68,7 @@ public class CUnitType { final List unitsTrained, final List researchesAvailable, final CUnitRace unitRace, final int goldCost, final int lumberCost, final int foodUsed, final int foodMade, final int buildTime, final EnumSet preventedPathingTypes, - final EnumSet requiredPathingTypes) { + final EnumSet requiredPathingTypes, final float propWindow, final float turnRate) { this.name = name; this.life = life; this.manaInitial = manaInitial; @@ -101,6 +103,8 @@ public class CUnitType { this.buildTime = buildTime; this.preventedPathingTypes = preventedPathingTypes; this.requiredPathingTypes = requiredPathingTypes; + this.propWindow = propWindow; + this.turnRate = turnRate; } public String getName() { @@ -238,4 +242,12 @@ public class CUnitType { public EnumSet getRequiredPathingTypes() { return this.requiredPathingTypes; } + + public float getPropWindow() { + return this.propWindow; + } + + public float getTurnRate() { + return this.turnRate; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorMove.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorMove.java index 383c693..3255241 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorMove.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorMove.java @@ -21,6 +21,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting import com.etheller.warsmash.viewer5.handlers.w3x.simulation.pathing.CPathfindingProcessor; public class CBehaviorMove implements CBehavior { + private static boolean ALWAYS_INTERRUPT_MOVE = false; private static final Rectangle tempRect = new Rectangle(); private final CUnit unit; @@ -41,6 +42,9 @@ public class CBehaviorMove implements CBehavior { private CRangedBehavior rangedBehavior; private boolean firstUpdate = true; private boolean disableCollision = false; + private boolean pathfindingActive = false; + private boolean firstPathfindJob = false; + private boolean pathfindingFailedGiveUp; public CBehaviorMove reset(final int highlightOrderId, final AbilityTarget target) { target.visit(this.targetVisitingResetter.reset(highlightOrderId)); @@ -69,6 +73,7 @@ public class CBehaviorMove implements CBehavior { this.searchCycles = 0; this.followUnit = null; this.firstUpdate = true; + this.pathfindingFailedGiveUp = false; } private void internalResetMove(final int highlightOrderId, final CUnit followUnit) { @@ -82,6 +87,7 @@ public class CBehaviorMove implements CBehavior { this.searchCycles = 0; this.followUnit = followUnit; this.firstUpdate = true; + this.pathfindingFailedGiveUp = false; } @Override @@ -100,6 +106,9 @@ public class CBehaviorMove implements CBehavior { this.unit.setPointAndCheckUnstuck(this.unit.getX(), this.unit.getY(), simulation); this.firstUpdate = false; } + if (this.pathfindingFailedGiveUp) { + return this.unit.pollNextOrderBehavior(simulation); + } final float prevX = this.unit.getX(); final float prevY = this.unit.getY(); @@ -116,72 +125,31 @@ public class CBehaviorMove implements CBehavior { final float startFloatingX = prevX; final float startFloatingY = prevY; if (this.path == null) { - if (this.followUnit != null) { - this.target.x = this.followUnit.getX(); - this.target.y = this.followUnit.getY(); - } - this.path = simulation.findNaiveSlowPath(this.unit, this.followUnit, startFloatingX, startFloatingY, - this.target, movementType, collisionSize, true); - System.out.println("init path " + this.path); - // check for smoothing - if (!this.path.isEmpty()) { - float lastX = startFloatingX; - float lastY = startFloatingY; - float smoothingGroupStartX = startFloatingX; - float smoothingGroupStartY = startFloatingY; - final Point2D.Float firstPathElement = this.path.get(0); - double totalPathDistance = firstPathElement.distance(lastX, lastY); - lastX = firstPathElement.x; - lastY = firstPathElement.y; - int smoothingStartIndex = -1; - for (int i = 0; i < (this.path.size() - 1); i++) { - final Point2D.Float nextPossiblePathElement = this.path.get(i + 1); - totalPathDistance += nextPossiblePathElement.distance(lastX, lastY); - if ((totalPathDistance < (1.15 - * nextPossiblePathElement.distance(smoothingGroupStartX, smoothingGroupStartY))) - && pathingGrid.isPathable((smoothingGroupStartX + nextPossiblePathElement.x) / 2, - (smoothingGroupStartY + nextPossiblePathElement.y) / 2, movementType)) { - if (smoothingStartIndex == -1) { - smoothingStartIndex = i; - } - } - else { - if (smoothingStartIndex != -1) { - for (int j = i - 1; j >= smoothingStartIndex; j--) { - this.path.remove(j); - } - i = smoothingStartIndex; - } - smoothingStartIndex = -1; - final Point2D.Float smoothGroupNext = this.path.get(i); - smoothingGroupStartX = smoothGroupNext.x; - smoothingGroupStartY = smoothGroupNext.y; - totalPathDistance = nextPossiblePathElement.distance(smoothGroupNext); - } - lastX = nextPossiblePathElement.x; - lastY = nextPossiblePathElement.y; - } - if (smoothingStartIndex != -1) { - for (int j = smoothingStartIndex; j < (this.path.size() - 1); j++) { - final Point2D.Float removed = this.path.remove(j); - } + if (!this.pathfindingActive) { + if (this.followUnit != null) { + this.target.x = this.followUnit.getX(); + this.target.y = this.followUnit.getY(); } + simulation.findNaiveSlowPath(this.unit, this.followUnit, startFloatingX, startFloatingY, this.target, + movementType, collisionSize, true, this); + this.pathfindingActive = true; + this.firstPathfindJob = true; } } else if ((this.followUnit != null) && (this.path.size() > 1) && (this.target.distance(this.followUnit.getX(), this.followUnit.getY()) > (0.1 * this.target.distance(this.unit.getX(), this.unit.getY())))) { this.target.x = this.followUnit.getX(); this.target.y = this.followUnit.getY(); - this.path = simulation.findNaiveSlowPath(this.unit, this.followUnit, startFloatingX, startFloatingY, - this.target, movementType, collisionSize, this.searchCycles < 4); - System.out.println("new path (for target) " + this.path); - if (this.path.isEmpty()) { - return this.unit.pollNextOrderBehavior(simulation); + if (this.pathfindingActive) { + simulation.removeFromPathfindingQueue(this); } + simulation.findNaiveSlowPath(this.unit, this.followUnit, startFloatingX, startFloatingY, this.target, + movementType, collisionSize, this.searchCycles < 4, this); + this.pathfindingActive = true; } float currentTargetX; float currentTargetY; - if (this.path.isEmpty()) { + if ((this.path == null) || this.path.isEmpty()) { if (this.followUnit != null) { currentTargetX = this.followUnit.getX(); currentTargetY = this.followUnit.getY(); @@ -212,8 +180,8 @@ public class CBehaviorMove implements CBehavior { } float facing = this.unit.getFacing(); float delta = goalAngle - facing; - final float propulsionWindow = simulation.getUnitData().getPropulsionWindow(this.unit.getTypeId()); - final float turnRate = simulation.getUnitData().getTurnRate(this.unit.getTypeId()); + final float propulsionWindow = this.unit.getUnitType().getPropWindow(); + final float turnRate = this.unit.getUnitType().getTurnRate(); final int speed = this.unit.getSpeed(); if (delta < -180) { @@ -235,7 +203,7 @@ public class CBehaviorMove implements CBehavior { facing += angleToAdd; this.unit.setFacing(facing); } - if (absDelta < propulsionWindow) { + if ((this.path != null) && !this.pathfindingActive && (absDelta < propulsionWindow)) { final float speedTick = speed * WarsmashConstants.SIMULATION_STEP_TIME; double continueDistance = speedTick; do { @@ -262,9 +230,9 @@ public class CBehaviorMove implements CBehavior { tempRect.set(this.unit.getCollisionRectangle()); tempRect.setCenter(nextX, nextY); if ((movementType == null) || (pathingGrid.isPathable(nextX, nextY, movementType, collisionSize)// ((int) - // collisionSize - // / 16) - // * 16 + // collisionSize + // / 16) + // * 16 && !worldCollision.intersectsAnythingOtherThan(tempRect, this.unit, movementType))) { this.unit.setPoint(nextX, nextY, worldCollision); if (done) { @@ -341,12 +309,12 @@ public class CBehaviorMove implements CBehavior { this.target.x = this.followUnit.getX(); this.target.y = this.followUnit.getY(); } - this.path = simulation.findNaiveSlowPath(this.unit, this.followUnit, startFloatingX, startFloatingY, - this.target, movementType, collisionSize, this.searchCycles < 4); - this.searchCycles++; - System.out.println("new path " + this.path); - if (this.path.isEmpty() || (this.searchCycles > 5)) { - return this.unit.pollNextOrderBehavior(simulation); + if (!this.pathfindingActive) { + simulation.findNaiveSlowPath(this.unit, this.followUnit, startFloatingX, startFloatingY, + this.target, movementType, collisionSize, this.searchCycles < 4, this); + this.pathfindingActive = true; + this.searchCycles++; + return this; } } this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.WALK, SequenceUtils.EMPTY, 1.0f, @@ -408,6 +376,86 @@ public class CBehaviorMove implements CBehavior { @Override public void end(final CSimulation game) { + if (ALWAYS_INTERRUPT_MOVE) { + game.removeFromPathfindingQueue(this); + this.pathfindingActive = false; + } + } + public CUnit getUnit() { + return this.unit; + } + + public void pathFound(final List waypoints, final CSimulation simulation) { + this.pathfindingActive = false; + + final float prevX = this.unit.getX(); + final float prevY = this.unit.getY(); + + MovementType movementType = this.unit.getUnitType().getMovementType(); + if (movementType == null) { + movementType = MovementType.DISABLED; + } + else if ((movementType == MovementType.FOOT) && this.disableCollision) { + movementType = MovementType.FOOT_NO_COLLISION; + } + final PathingGrid pathingGrid = simulation.getPathingGrid(); + final CWorldCollision worldCollision = simulation.getWorldCollision(); + final float collisionSize = this.unit.getUnitType().getCollisionSize(); + final float startFloatingX = prevX; + final float startFloatingY = prevY; + + this.path = waypoints; + if (this.firstPathfindJob) { + this.firstPathfindJob = false; + System.out.println("init path " + this.path); + // check for smoothing + if (!this.path.isEmpty()) { + float lastX = startFloatingX; + float lastY = startFloatingY; + float smoothingGroupStartX = startFloatingX; + float smoothingGroupStartY = startFloatingY; + final Point2D.Float firstPathElement = this.path.get(0); + double totalPathDistance = firstPathElement.distance(lastX, lastY); + lastX = firstPathElement.x; + lastY = firstPathElement.y; + int smoothingStartIndex = -1; + for (int i = 0; i < (this.path.size() - 1); i++) { + final Point2D.Float nextPossiblePathElement = this.path.get(i + 1); + totalPathDistance += nextPossiblePathElement.distance(lastX, lastY); + if ((totalPathDistance < (1.15 + * nextPossiblePathElement.distance(smoothingGroupStartX, smoothingGroupStartY))) + && pathingGrid.isPathable((smoothingGroupStartX + nextPossiblePathElement.x) / 2, + (smoothingGroupStartY + nextPossiblePathElement.y) / 2, movementType)) { + if (smoothingStartIndex == -1) { + smoothingStartIndex = i; + } + } + else { + if (smoothingStartIndex != -1) { + for (int j = i - 1; j >= smoothingStartIndex; j--) { + this.path.remove(j); + } + i = smoothingStartIndex; + } + smoothingStartIndex = -1; + final Point2D.Float smoothGroupNext = this.path.get(i); + smoothingGroupStartX = smoothGroupNext.x; + smoothingGroupStartY = smoothGroupNext.y; + totalPathDistance = nextPossiblePathElement.distance(smoothGroupNext); + } + lastX = nextPossiblePathElement.x; + lastY = nextPossiblePathElement.y; + } + if (smoothingStartIndex != -1) { + for (int j = smoothingStartIndex; j < (this.path.size() - 1); j++) { + final Point2D.Float removed = this.path.remove(j); + } + } + } + } + else if (this.path.isEmpty() || (this.searchCycles > 6)) { + this.pathfindingFailedGiveUp = true; + } } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java index 7b4ce48..13e5bc7 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java @@ -238,6 +238,9 @@ public class CUnitData { final float moveHeight = unitType.getFieldAsFloat(MOVE_HEIGHT, 0); final String movetp = unitType.getFieldAsString(MOVE_TYPE, 0); final float collisionSize = unitType.getFieldAsFloat(COLLISION_SIZE, 0); + final float propWindow = unitType.getFieldAsFloat(PROPULSION_WINDOW, 0); + final float turnRate = unitType.getFieldAsFloat(TURN_RATE, 0); + final boolean isBldg = unitType.getFieldAsBoolean(IS_BLDG, 0); final PathingGrid.MovementType movementType = PathingGrid.getMovementType(movetp); final String unitName = unitType.getFieldAsString(NAME, 0); @@ -425,7 +428,8 @@ public class CUnitData { isBldg, movementType, moveHeight, collisionSize, classifications, attacks, armorType, raise, decay, defenseType, impactZ, buildingPathingPixelMap, deathTime, targetedAs, acquisitionRange, minimumAttackRange, structuresBuilt, unitsTrained, researchesAvailable, unitRace, goldCost, - lumberCost, foodUsed, foodMade, buildTime, preventedPathingTypes, requiredPathingTypes); + lumberCost, foodUsed, foodMade, buildTime, preventedPathingTypes, requiredPathingTypes, propWindow, + turnRate); this.unitIdToUnitType.put(typeId, unitTypeInstance); } return unitTypeInstance; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/pathing/CPathfindingProcessor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/pathing/CPathfindingProcessor.java index 0af31d0..da77227 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/pathing/CPathfindingProcessor.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/pathing/CPathfindingProcessor.java @@ -4,19 +4,24 @@ import java.awt.geom.Point2D; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; +import java.util.Iterator; import java.util.LinkedList; -import java.util.List; import java.util.PriorityQueue; import com.badlogic.gdx.math.Rectangle; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid; +import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.MovementType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWorldCollision; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorMove; public class CPathfindingProcessor { private static final Rectangle tempRect = new Rectangle(); private final PathingGrid pathingGrid; private final CWorldCollision worldCollision; + private final LinkedList moveQueue = new LinkedList<>(); + // things with modified state per current job: private final Node[][] nodes; private final Node[][] cornerNodes; private final Node[] goalSet = new Node[4]; @@ -52,217 +57,29 @@ public class CPathfindingProcessor { * * @param start * @param goal + * @param playerIndex + * @param queueItem * @return */ - public List findNaiveSlowPath(final CUnit ignoreIntersectionsWithThisUnit, final float startX, - final float startY, final Point2D.Float goal, final PathingGrid.MovementType movementType, - final float collisionSize, final boolean allowSmoothing) { - return findNaiveSlowPath(ignoreIntersectionsWithThisUnit, null, startX, startY, goal, movementType, - collisionSize, allowSmoothing); - } - - public List findNaiveSlowPath(final CUnit ignoreIntersectionsWithThisUnit, + public void findNaiveSlowPath(final CUnit ignoreIntersectionsWithThisUnit, final CUnit ignoreIntersectionsWithThisSecondUnit, final float startX, final float startY, final Point2D.Float goal, final PathingGrid.MovementType movementType, final float collisionSize, - final boolean allowSmoothing) { - final float goalX = goal.x; - final float goalY = goal.y; - float weightForHittingWalls = 1E9f; - if (!this.pathingGrid.isPathable(goalX, goalY, movementType, collisionSize) || !isPathableDynamically(goalX, - goalY, ignoreIntersectionsWithThisUnit, ignoreIntersectionsWithThisSecondUnit, movementType)) { - weightForHittingWalls = 5E2f; - } - System.out.println("beginning findNaiveSlowPath for " + startX + "," + startY + "," + goalX + "," + goalY); - if ((startX == goalX) && (startY == goalY)) { - return Collections.emptyList(); - } - tempRect.set(0, 0, collisionSize * 2, collisionSize * 2); - Node[][] searchGraph; - GridMapping gridMapping; - if (isCollisionSizeBetterSuitedForCorners(collisionSize)) { - searchGraph = this.cornerNodes; - gridMapping = GridMapping.CORNERS; - System.out.println("using corners"); - } - else { - searchGraph = this.nodes; - gridMapping = GridMapping.CELLS; - System.out.println("using cells"); - } - final int goalCellY = gridMapping.getY(this.pathingGrid, goalY); - final int goalCellX = gridMapping.getX(this.pathingGrid, goalX); - final Node mostLikelyGoal = searchGraph[goalCellY][goalCellX]; - final double bestGoalDistance = mostLikelyGoal.point.distance(goalX, goalY); - Arrays.fill(this.goalSet, null); - this.goals = 0; - for (int i = goalCellX - 1; i <= (goalCellX + 1); i++) { - for (int j = goalCellY - 1; j <= (goalCellY + 1); j++) { - final Node possibleGoal = searchGraph[j][i]; - if (possibleGoal.point.distance(goalX, goalY) <= bestGoalDistance) { - this.goalSet[this.goals++] = possibleGoal; - } - } - } - final int startGridY = gridMapping.getY(this.pathingGrid, startY); - final int startGridX = gridMapping.getX(this.pathingGrid, startX); - for (int i = 0; i < searchGraph.length; i++) { - for (int j = 0; j < searchGraph[i].length; j++) { - final Node node = searchGraph[i][j]; - node.g = Float.POSITIVE_INFINITY; - node.f = Float.POSITIVE_INFINITY; - node.cameFrom = null; - node.cameFromDirection = null; - } - } - final PriorityQueue openSet = new PriorityQueue<>(new Comparator() { - @Override - public int compare(final Node a, final Node b) { - return Double.compare(f(a), f(b)); - } - }); + final boolean allowSmoothing, final CBehaviorMove queueItem) { + this.moveQueue.offer(new PathfindingJob(ignoreIntersectionsWithThisUnit, ignoreIntersectionsWithThisSecondUnit, + startX, startY, goal, movementType, collisionSize, allowSmoothing, queueItem)); + } - final Node start = searchGraph[startGridY][startGridX]; - int startGridMinX; - int startGridMinY; - int startGridMaxX; - int startGridMaxY; - if (startX > start.point.x) { - startGridMinX = startGridX; - startGridMaxX = startGridX + 1; - } - else if (startX < start.point.x) { - startGridMinX = startGridX - 1; - startGridMaxX = startGridX; - } - else { - startGridMinX = startGridX; - startGridMaxX = startGridX; - } - if (startY > start.point.y) { - startGridMinY = startGridY; - startGridMaxY = startGridY + 1; - } - else if (startY < start.point.y) { - startGridMinY = startGridY - 1; - startGridMaxY = startGridY; - } - else { - startGridMinY = startGridY; - startGridMaxY = startGridY; - } - for (int cellX = startGridMinX; cellX <= startGridMaxX; cellX++) { - for (int cellY = startGridMinY; cellY <= startGridMaxY; cellY++) { - if ((cellX >= 0) && (cellX < this.pathingGrid.getWidth()) && (cellY >= 0) - && (cellY < this.pathingGrid.getHeight())) { - final Node possibleNode = searchGraph[cellY][cellX]; - final float x = possibleNode.point.x; - final float y = possibleNode.point.y; - if (pathableBetween(ignoreIntersectionsWithThisUnit, ignoreIntersectionsWithThisSecondUnit, startX, - startY, movementType, collisionSize, x, y)) { - - final double tentativeScore = possibleNode.point.distance(startX, startY); - possibleNode.g = tentativeScore; - possibleNode.f = tentativeScore + h(possibleNode); - openSet.add(possibleNode); - - } - else { - final double tentativeScore = weightForHittingWalls; - possibleNode.g = tentativeScore; - possibleNode.f = tentativeScore + h(possibleNode); - openSet.add(possibleNode); - - } - } + public void removeFromPathfindingQueue(final CBehaviorMove behaviorMove) { + // TODO because of silly java things, this remove is O(N) for now, + // we could do some refactors to make it O(1) but do we care? + final Iterator iterator = this.moveQueue.iterator(); + while (iterator.hasNext()) { + final PathfindingJob job = iterator.next(); + if (job.queueItem == behaviorMove) { + iterator.remove(); } } - int searchIterations = 0; - while (!openSet.isEmpty() && searchIterations < 150000) { - Node current = openSet.poll(); - if (isGoal(current)) { - final LinkedList totalPath = new LinkedList<>(); - Direction lastCameFromDirection = null; - - if ((current.cameFrom != null) - && pathableBetween(ignoreIntersectionsWithThisUnit, ignoreIntersectionsWithThisSecondUnit, - current.point.x, current.point.y, movementType, collisionSize, goalX, goalY) - && pathableBetween(ignoreIntersectionsWithThisUnit, ignoreIntersectionsWithThisSecondUnit, - current.cameFrom.point.x, current.cameFrom.point.y, movementType, collisionSize, - current.point.x, current.point.y) - && pathableBetween(ignoreIntersectionsWithThisUnit, ignoreIntersectionsWithThisSecondUnit, - current.cameFrom.point.x, current.cameFrom.point.y, movementType, collisionSize, goalX, - goalY) - && allowSmoothing) { - // do some basic smoothing to walk straight to the goal if it is not obstructed, - // skipping the last grid location - totalPath.addFirst(goal); - current = current.cameFrom; - } - else { - totalPath.addFirst(goal); - totalPath.addFirst(current.point); - } - lastCameFromDirection = current.cameFromDirection; - Node lastNode = null; - while (current.cameFrom != null) { - lastNode = current; - current = current.cameFrom; - if ((lastCameFromDirection == null) || (current.cameFromDirection != lastCameFromDirection) - || (current.cameFromDirection == null)) { - if ((current.cameFromDirection != null) || (lastNode == null) - || !pathableBetween(ignoreIntersectionsWithThisUnit, - ignoreIntersectionsWithThisSecondUnit, startX, startY, movementType, - collisionSize, current.point.x, current.point.y) - || !pathableBetween(ignoreIntersectionsWithThisUnit, - ignoreIntersectionsWithThisSecondUnit, current.point.x, current.point.y, - movementType, collisionSize, lastNode.point.x, lastNode.point.y) - || !pathableBetween(ignoreIntersectionsWithThisUnit, - ignoreIntersectionsWithThisSecondUnit, startX, startY, movementType, - collisionSize, lastNode.point.x, lastNode.point.y) - || !allowSmoothing) { - // Add the point if it's not the first one, or if we can only complete - // the journey by specifically walking to the first one - totalPath.addFirst(current.point); - lastCameFromDirection = current.cameFromDirection; - } - } - } - return totalPath; - } - - for (final Direction direction : Direction.VALUES) { - final float x = current.point.x + (direction.xOffset * 32); - final float y = current.point.y + (direction.yOffset * 32); - if (this.pathingGrid.contains(x, y)) { - double turnCost; - if ((current.cameFromDirection != null) && (direction != current.cameFromDirection)) { - turnCost = 0.25; - } - else { - turnCost = 0; - } - double tentativeScore = current.g + ((direction.length + turnCost) * 32); - if (!pathableBetween(ignoreIntersectionsWithThisUnit, ignoreIntersectionsWithThisSecondUnit, - current.point.x, current.point.y, movementType, collisionSize, x, y)) { - tentativeScore += (direction.length) * weightForHittingWalls; - } - final Node neighbor = searchGraph[gridMapping.getY(this.pathingGrid, y)][gridMapping - .getX(this.pathingGrid, x)]; - if (tentativeScore < neighbor.g) { - neighbor.cameFrom = current; - neighbor.cameFromDirection = direction; - neighbor.g = tentativeScore; - neighbor.f = tentativeScore + h(neighbor); - if (!openSet.contains(neighbor)) { - openSet.add(neighbor); - } - } - } - } - searchIterations++; - } - return Collections.emptyList(); } private boolean pathableBetween(final CUnit ignoreIntersectionsWithThisUnit, @@ -384,4 +201,263 @@ public class CPathfindingProcessor { }; } + + public void update(final CSimulation simulation) { + int workIterations = 0; + JobsLoop: while (!this.moveQueue.isEmpty()) { + final PathfindingJob job = this.moveQueue.peek(); + if (!job.jobStarted) { + job.jobStarted = true; + System.out.println("starting job with smoothing=" + job.allowSmoothing); + workIterations += 5; // setup of job predicted cost + job.goalX = job.goal.x; + job.goalY = job.goal.y; + job.weightForHittingWalls = 1E9f; + if (!this.pathingGrid.isPathable(job.goalX, job.goalY, job.movementType, job.collisionSize) + || !isPathableDynamically(job.goalX, job.goalY, job.ignoreIntersectionsWithThisUnit, + job.ignoreIntersectionsWithThisSecondUnit, job.movementType)) { + job.weightForHittingWalls = 5E2f; + } + System.out.println("beginning findNaiveSlowPath for " + job.startX + "," + job.startY + "," + job.goalX + + "," + job.goalY); + if ((job.startX == job.goalX) && (job.startY == job.goalY)) { + job.queueItem.pathFound(Collections.emptyList(), simulation); + this.moveQueue.poll(); + continue JobsLoop; + } + tempRect.set(0, 0, job.collisionSize * 2, job.collisionSize * 2); + if (isCollisionSizeBetterSuitedForCorners(job.collisionSize)) { + job.searchGraph = this.cornerNodes; + job.gridMapping = GridMapping.CORNERS; + System.out.println("using corners"); + } + else { + job.searchGraph = this.nodes; + job.gridMapping = GridMapping.CELLS; + System.out.println("using cells"); + } + final int goalCellY = job.gridMapping.getY(this.pathingGrid, job.goalY); + final int goalCellX = job.gridMapping.getX(this.pathingGrid, job.goalX); + final Node mostLikelyGoal = job.searchGraph[goalCellY][goalCellX]; + final double bestGoalDistance = mostLikelyGoal.point.distance(job.goalX, job.goalY); + Arrays.fill(this.goalSet, null); + this.goals = 0; + for (int i = goalCellX - 1; i <= (goalCellX + 1); i++) { + for (int j = goalCellY - 1; j <= (goalCellY + 1); j++) { + final Node possibleGoal = job.searchGraph[j][i]; + if (possibleGoal.point.distance(job.goalX, job.goalY) <= bestGoalDistance) { + this.goalSet[this.goals++] = possibleGoal; + } + } + } + final int startGridY = job.gridMapping.getY(this.pathingGrid, job.startY); + final int startGridX = job.gridMapping.getX(this.pathingGrid, job.startX); + for (int i = 0; i < job.searchGraph.length; i++) { + for (int j = 0; j < job.searchGraph[i].length; j++) { + final Node node = job.searchGraph[i][j]; + node.g = Float.POSITIVE_INFINITY; + node.f = Float.POSITIVE_INFINITY; + node.cameFrom = null; + node.cameFromDirection = null; + workIterations++; + } + } + job.openSet = new PriorityQueue<>(new Comparator() { + @Override + public int compare(final Node a, final Node b) { + return Double.compare(f(a), f(b)); + } + }); + + job.start = job.searchGraph[startGridY][startGridX]; + if (job.startX > job.start.point.x) { + job.startGridMinX = startGridX; + job.startGridMaxX = startGridX + 1; + } + else if (job.startX < job.start.point.x) { + job.startGridMinX = startGridX - 1; + job.startGridMaxX = startGridX; + } + else { + job.startGridMinX = startGridX; + job.startGridMaxX = startGridX; + } + if (job.startY > job.start.point.y) { + job.startGridMinY = startGridY; + job.startGridMaxY = startGridY + 1; + } + else if (job.startY < job.start.point.y) { + job.startGridMinY = startGridY - 1; + job.startGridMaxY = startGridY; + } + else { + job.startGridMinY = startGridY; + job.startGridMaxY = startGridY; + } + for (int cellX = job.startGridMinX; cellX <= job.startGridMaxX; cellX++) { + for (int cellY = job.startGridMinY; cellY <= job.startGridMaxY; cellY++) { + if ((cellX >= 0) && (cellX < this.pathingGrid.getWidth()) && (cellY >= 0) + && (cellY < this.pathingGrid.getHeight())) { + final Node possibleNode = job.searchGraph[cellY][cellX]; + final float x = possibleNode.point.x; + final float y = possibleNode.point.y; + if (pathableBetween(job.ignoreIntersectionsWithThisUnit, + job.ignoreIntersectionsWithThisSecondUnit, job.startX, job.startY, job.movementType, + job.collisionSize, x, y)) { + + final double tentativeScore = possibleNode.point.distance(job.startX, job.startY); + possibleNode.g = tentativeScore; + possibleNode.f = tentativeScore + h(possibleNode); + job.openSet.add(possibleNode); + + } + else { + final double tentativeScore = job.weightForHittingWalls; + possibleNode.g = tentativeScore; + possibleNode.f = tentativeScore + h(possibleNode); + job.openSet.add(possibleNode); + + } + } + } + } + } + + while (!job.openSet.isEmpty()) { + Node current = job.openSet.poll(); + if (isGoal(current)) { + final LinkedList totalPath = new LinkedList<>(); + Direction lastCameFromDirection = null; + + if ((current.cameFrom != null) + && pathableBetween(job.ignoreIntersectionsWithThisUnit, + job.ignoreIntersectionsWithThisSecondUnit, current.point.x, current.point.y, + job.movementType, job.collisionSize, job.goalX, job.goalY) + && pathableBetween(job.ignoreIntersectionsWithThisUnit, + job.ignoreIntersectionsWithThisSecondUnit, current.cameFrom.point.x, + current.cameFrom.point.y, job.movementType, job.collisionSize, current.point.x, + current.point.y) + && pathableBetween(job.ignoreIntersectionsWithThisUnit, + job.ignoreIntersectionsWithThisSecondUnit, current.cameFrom.point.x, + current.cameFrom.point.y, job.movementType, job.collisionSize, job.goalX, job.goalY) + && job.allowSmoothing) { + // do some basic smoothing to walk straight to the goal if it is not obstructed, + // skipping the last grid location + totalPath.addFirst(job.goal); + current = current.cameFrom; + } + else { + totalPath.addFirst(job.goal); + totalPath.addFirst(current.point); + } + lastCameFromDirection = current.cameFromDirection; + Node lastNode = null; + while (current.cameFrom != null) { + lastNode = current; + current = current.cameFrom; + if ((lastCameFromDirection == null) || (current.cameFromDirection != lastCameFromDirection) + || (current.cameFromDirection == null)) { + if ((current.cameFromDirection != null) || (lastNode == null) + || !pathableBetween(job.ignoreIntersectionsWithThisUnit, + job.ignoreIntersectionsWithThisSecondUnit, job.startX, job.startY, + job.movementType, job.collisionSize, current.point.x, current.point.y) + || !pathableBetween(job.ignoreIntersectionsWithThisUnit, + job.ignoreIntersectionsWithThisSecondUnit, current.point.x, current.point.y, + job.movementType, job.collisionSize, lastNode.point.x, lastNode.point.y) + || !pathableBetween(job.ignoreIntersectionsWithThisUnit, + job.ignoreIntersectionsWithThisSecondUnit, job.startX, job.startY, + job.movementType, job.collisionSize, lastNode.point.x, lastNode.point.y) + || !job.allowSmoothing) { + // Add the point if it's not the first one, or if we can only complete + // the journey by specifically walking to the first one + totalPath.addFirst(current.point); + lastCameFromDirection = current.cameFromDirection; + } + } + } + job.queueItem.pathFound(totalPath, simulation); + this.moveQueue.poll(); + continue JobsLoop; + } + + for (final Direction direction : Direction.VALUES) { + final float x = current.point.x + (direction.xOffset * 32); + final float y = current.point.y + (direction.yOffset * 32); + if (this.pathingGrid.contains(x, y)) { + double turnCost; + if ((current.cameFromDirection != null) && (direction != current.cameFromDirection)) { + turnCost = 0.25; + } + else { + turnCost = 0; + } + double tentativeScore = current.g + ((direction.length + turnCost) * 32); + if (!pathableBetween(job.ignoreIntersectionsWithThisUnit, + job.ignoreIntersectionsWithThisSecondUnit, current.point.x, current.point.y, + job.movementType, job.collisionSize, x, y)) { + tentativeScore += (direction.length) * job.weightForHittingWalls; + } + final Node neighbor = job.searchGraph[job.gridMapping.getY(this.pathingGrid, y)][job.gridMapping + .getX(this.pathingGrid, x)]; + if (tentativeScore < neighbor.g) { + neighbor.cameFrom = current; + neighbor.cameFromDirection = direction; + neighbor.g = tentativeScore; + neighbor.f = tentativeScore + h(neighbor); + if (!job.openSet.contains(neighbor)) { + job.openSet.add(neighbor); + } + } + } + } + workIterations++; + if (workIterations >= 15000) { + // breaking jobs loop will implicitly exit without calling pathFound() below + break JobsLoop; + } + } + job.queueItem.pathFound(Collections.emptyList(), simulation); + this.moveQueue.poll(); + } + } + + public static final class PathfindingJob { + private final CUnit ignoreIntersectionsWithThisUnit; + private final CUnit ignoreIntersectionsWithThisSecondUnit; + private final float startX; + private final float startY; + private final Point2D.Float goal; + private final MovementType movementType; + private final float collisionSize; + private final boolean allowSmoothing; + private final CBehaviorMove queueItem; + private boolean jobStarted; + public float goalY; + public float goalX; + public float weightForHittingWalls; + Node[][] searchGraph; + GridMapping gridMapping; + PriorityQueue openSet; + Node start; + int startGridMinX; + int startGridMinY; + int startGridMaxX; + int startGridMaxY; + + public PathfindingJob(final CUnit ignoreIntersectionsWithThisUnit, + final CUnit ignoreIntersectionsWithThisSecondUnit, final float startX, final float startY, + final Point2D.Float goal, final PathingGrid.MovementType movementType, final float collisionSize, + final boolean allowSmoothing, final CBehaviorMove queueItem) { + this.ignoreIntersectionsWithThisUnit = ignoreIntersectionsWithThisUnit; + this.ignoreIntersectionsWithThisSecondUnit = ignoreIntersectionsWithThisSecondUnit; + this.startX = startX; + this.startY = startY; + this.goal = goal; + this.movementType = movementType; + this.collisionSize = collisionSize; + this.allowSmoothing = allowSmoothing; + this.queueItem = queueItem; + this.jobStarted = false; + } + } } From 8111441f16df305fd01ecf4c6146d0438691525b Mon Sep 17 00:00:00 2001 From: Retera Date: Sun, 24 Jan 2021 15:10:13 -0500 Subject: [PATCH 095/116] Ignore repeat orders --- .../warsmash/viewer5/AudioContext.java | 1 + .../handlers/w3x/simulation/CUnit.java | 12 ++++-- .../w3x/simulation/orders/COrderNoTarget.java | 34 +++++++++++++++ .../simulation/orders/COrderTargetPoint.java | 43 +++++++++++++++++++ .../simulation/orders/COrderTargetWidget.java | 38 ++++++++++++++++ 5 files changed, 125 insertions(+), 3 deletions(-) diff --git a/core/src/com/etheller/warsmash/viewer5/AudioContext.java b/core/src/com/etheller/warsmash/viewer5/AudioContext.java index 77e5154..60bbdaa 100644 --- a/core/src/com/etheller/warsmash/viewer5/AudioContext.java +++ b/core/src/com/etheller/warsmash/viewer5/AudioContext.java @@ -64,4 +64,5 @@ public class AudioContext { public AudioBufferSource createBufferSource() { return new AudioBufferSource(); } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java index 4598942..97895c8 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java @@ -92,7 +92,7 @@ public class CUnit extends CWidget { private boolean acceptingOrders = true; private boolean invulnerable = false; private CBehavior defaultBehavior; - private COrder currentOrder = null; + private COrder lastStartedOrder = null; private CUnit workerInside; private final War3ID[] buildQueue = new War3ID[WarsmashConstants.BUILD_QUEUE_SIZE]; private final QueueItemType[] buildQueueTypes = new QueueItemType[WarsmashConstants.BUILD_QUEUE_SIZE]; @@ -384,6 +384,12 @@ public class CUnit extends CWidget { } } + if ((this.lastStartedOrder != null) && this.lastStartedOrder.equals(order) + && (this.lastStartedOrder.getOrderId() == OrderIds.smart)) { + // I skip your spammed move orders, TODO this will probably break some repeat + // attack order or something later + return; + } if ((queue || !this.acceptingOrders) && ((this.currentBehavior != this.stopBehavior) && (this.currentBehavior != this.holdPositionBehavior))) { this.orderQueue.add(order); @@ -405,7 +411,7 @@ public class CUnit extends CWidget { } private CBehavior beginOrder(final CSimulation game, final COrder order) { - this.currentOrder = order; + this.lastStartedOrder = order; CBehavior nextBehavior; if (order != null) { nextBehavior = order.begin(game, this); @@ -1214,6 +1220,6 @@ public class CUnit extends CWidget { } public COrder getCurrentOrder() { - return this.currentOrder; + return this.lastStartedOrder; } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderNoTarget.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderNoTarget.java index a815381..a1537b3 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderNoTarget.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderNoTarget.java @@ -59,4 +59,38 @@ public class COrderNoTarget implements COrder { return null; } + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = (prime * result) + this.abilityHandleId; + result = (prime * result) + this.orderId; + result = (prime * result) + (this.queued ? 1231 : 1237); + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final COrderNoTarget other = (COrderNoTarget) obj; + if (this.abilityHandleId != other.abilityHandleId) { + return false; + } + if (this.orderId != other.orderId) { + return false; + } + if (this.queued != other.queued) { + return false; + } + return true; + } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetPoint.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetPoint.java index e64eaf4..d3948db 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetPoint.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetPoint.java @@ -63,4 +63,47 @@ public class COrderTargetPoint implements COrder { } + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = (prime * result) + this.abilityHandleId; + result = (prime * result) + this.orderId; + result = (prime * result) + (this.queued ? 1231 : 1237); + result = (prime * result) + ((this.target == null) ? 0 : this.target.hashCode()); + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final COrderTargetPoint other = (COrderTargetPoint) obj; + if (this.abilityHandleId != other.abilityHandleId) { + return false; + } + if (this.orderId != other.orderId) { + return false; + } + if (this.queued != other.queued) { + return false; + } + if (this.target == null) { + if (other.target != null) { + return false; + } + } + else if (!this.target.equals(other.target)) { + return false; + } + return true; + } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetWidget.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetWidget.java index ffd751e..d9c4c01 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetWidget.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetWidget.java @@ -64,4 +64,42 @@ public class COrderTargetWidget implements COrder { return caster.pollNextOrderBehavior(game); } } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = (prime * result) + this.abilityHandleId; + result = (prime * result) + this.orderId; + result = (prime * result) + (this.queued ? 1231 : 1237); + result = (prime * result) + this.targetHandleId; + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final COrderTargetWidget other = (COrderTargetWidget) obj; + if (this.abilityHandleId != other.abilityHandleId) { + return false; + } + if (this.orderId != other.orderId) { + return false; + } + if (this.queued != other.queued) { + return false; + } + if (this.targetHandleId != other.targetHandleId) { + return false; + } + return true; + } } From 28a24471fcb196ea5654b92cf5cebc75ed3a1fd4 Mon Sep 17 00:00:00 2001 From: Retera Date: Sun, 24 Jan 2021 17:22:06 -0500 Subject: [PATCH 096/116] Update with basic positional audio using an override on libgdx --- core/assets/warsmash.ini | 4 +- .../warsmash/viewer5/AudioBufferSource.java | 9 +- .../warsmash/viewer5/AudioContext.java | 86 +-- .../warsmash/viewer5/AudioPanner.java | 31 +- .../com/etheller/warsmash/viewer5/Scene.java | 5 +- .../warsmash/viewer5/gl/AudioExtension.java | 13 + .../warsmash/viewer5/gl/Extensions.java | 2 +- .../viewer5/gl/SoundLengthExtension.java | 7 - .../viewer5/handlers/mdx/EventObjectSnd.java | 3 +- .../viewer5/handlers/w3x/SplatModel.java | 8 +- .../viewer5/handlers/w3x/UnitAckSound.java | 5 +- .../viewer5/handlers/w3x/UnitSound.java | 5 +- .../viewer5/handlers/w3x/War3MapViewer.java | 7 +- .../handlers/w3x/environment/Terrain.java | 10 +- .../w3x/rendersim/RenderDestructable.java | 5 - .../handlers/w3x/rendersim/RenderUnit.java | 16 +- .../handlers/w3x/rendersim/RenderWidget.java | 2 - .../viewer5/handlers/w3x/ui/MeleeUI.java | 4 +- .../gdx/backends/lwjgl/LwjglApplication.java | 487 ++++++++++++++++ .../audio/JavaSoundAudioRecorder.java | 66 +++ .../src/com/etheller/warsmash/audio/Mp3.java | 163 ++++++ .../src/com/etheller/warsmash/audio/Ogg.java | 89 +++ .../warsmash/audio/OggInputStream.java | 520 ++++++++++++++++++ .../etheller/warsmash/audio/OpenALAudio.java | 477 ++++++++++++++++ .../warsmash/audio/OpenALAudioDevice.java | 265 +++++++++ .../etheller/warsmash/audio/OpenALMusic.java | 396 +++++++++++++ .../etheller/warsmash/audio/OpenALSound.java | 249 +++++++++ .../src/com/etheller/warsmash/audio/Wav.java | 196 +++++++ .../warsmash/desktop/DesktopLauncher.java | 91 ++- 29 files changed, 3135 insertions(+), 86 deletions(-) create mode 100644 core/src/com/etheller/warsmash/viewer5/gl/AudioExtension.java delete mode 100644 core/src/com/etheller/warsmash/viewer5/gl/SoundLengthExtension.java create mode 100644 desktop/src/com/badlogic/gdx/backends/lwjgl/LwjglApplication.java create mode 100644 desktop/src/com/etheller/warsmash/audio/JavaSoundAudioRecorder.java create mode 100644 desktop/src/com/etheller/warsmash/audio/Mp3.java create mode 100644 desktop/src/com/etheller/warsmash/audio/Ogg.java create mode 100644 desktop/src/com/etheller/warsmash/audio/OggInputStream.java create mode 100644 desktop/src/com/etheller/warsmash/audio/OpenALAudio.java create mode 100644 desktop/src/com/etheller/warsmash/audio/OpenALAudioDevice.java create mode 100644 desktop/src/com/etheller/warsmash/audio/OpenALMusic.java create mode 100644 desktop/src/com/etheller/warsmash/audio/OpenALSound.java create mode 100644 desktop/src/com/etheller/warsmash/audio/Wav.java diff --git a/core/assets/warsmash.ini b/core/assets/warsmash.ini index ce51670..137f96a 100644 --- a/core/assets/warsmash.ini +++ b/core/assets/warsmash.ini @@ -20,7 +20,7 @@ Path07="." [Map] //FilePath="CombatUnitTests.w3x" //FilePath="PitchRoll.w3x" -//FilePath="PeonStartingBase.w3x" +FilePath="PeonStartingBase.w3x" //FilePath="MyStromguarde.w3m" //FilePath="ColdArrows.w3m" //FilePath="DungeonGoldMine.w3m" @@ -43,4 +43,4 @@ Path07="." //FilePath="test2.w3x" //FilePath="FarseerHoldPositionTest.w3x" //FilePath="Ramps.w3m" -FilePath="V1\Farm.w3x" +//FilePath="V1\Farm.w3x" diff --git a/core/src/com/etheller/warsmash/viewer5/AudioBufferSource.java b/core/src/com/etheller/warsmash/viewer5/AudioBufferSource.java index 8e40532..5e55119 100644 --- a/core/src/com/etheller/warsmash/viewer5/AudioBufferSource.java +++ b/core/src/com/etheller/warsmash/viewer5/AudioBufferSource.java @@ -1,17 +1,22 @@ package com.etheller.warsmash.viewer5; import com.badlogic.gdx.audio.Sound; +import com.etheller.warsmash.viewer5.gl.Extensions; public class AudioBufferSource { public Sound buffer; + private AudioPanner panner; public void connect(final AudioPanner panner) { - + this.panner = panner; } public void start(final int value, final float volume, final float pitch) { if (this.buffer != null) { - this.buffer.play(volume, pitch, 0.0f); + if (!this.panner.listener.is3DSupported() || this.panner.isWithinListenerDistance()) { + Extensions.audio.play(this.buffer, volume, pitch, this.panner.x, this.panner.y, this.panner.z, + this.panner.listener.is3DSupported(), this.panner.maxDistance, this.panner.refDistance); + } } } } diff --git a/core/src/com/etheller/warsmash/viewer5/AudioContext.java b/core/src/com/etheller/warsmash/viewer5/AudioContext.java index 60bbdaa..68383f2 100644 --- a/core/src/com/etheller/warsmash/viewer5/AudioContext.java +++ b/core/src/com/etheller/warsmash/viewer5/AudioContext.java @@ -2,9 +2,13 @@ package com.etheller.warsmash.viewer5; public class AudioContext { private boolean running = false; - public Listener listener = new Listener(); - public AudioDestination destination = new AudioDestination() { - }; + public Listener listener; + public AudioDestination destination; + + public AudioContext(final Listener listener, final AudioDestination destination) { + this.listener = listener; + this.destination = destination; + } public void suspend() { this.running = false; @@ -18,45 +22,65 @@ public class AudioContext { this.running = true; } - public static class Listener { - private float x; - private float y; - private float z; - private float forwardX; - private float forwardY; - private float forwardZ; - private float upX; - private float upY; - private float upZ; + public static interface Listener { - public void setPosition(final float x, final float y, final float z) { - this.x = x; - this.y = y; - this.z = z; - } + float getX(); + + float getY(); + + float getZ(); + + public void setPosition(final float x, final float y, final float z); public void setOrientation(final float forwardX, final float forwardY, final float forwardZ, final float upX, - final float upY, final float upZ) { - this.forwardX = forwardX; - this.forwardY = forwardY; - this.forwardZ = forwardZ; - this.upX = upX; - this.upY = upY; - this.upZ = upZ; + final float upY, final float upZ); - } - } + boolean is3DSupported(); + + Listener DO_NOTHING = new Listener() { + private float x; + private float y; + private float z; - public AudioPanner createPanner() { - return new AudioPanner() { @Override public void setPosition(final float x, final float y, final float z) { - System.err.println("audio panner set position not implemented"); + this.x = x; + this.y = y; + this.z = z; } + @Override + public float getX() { + return x; + } + + @Override + public float getY() { + return y; + } + + @Override + public float getZ() { + return z; + } + + @Override + public void setOrientation(final float forwardX, final float forwardY, final float forwardZ, + final float upX, final float upY, final float upZ) { + + } + + @Override + public boolean is3DSupported() { + return false; + } + }; + } + + public AudioPanner createPanner() { + return new AudioPanner(this.listener) { @Override public void connect(final AudioDestination destination) { - System.err.println("audio panner connect dest not implemented"); } }; } diff --git a/core/src/com/etheller/warsmash/viewer5/AudioPanner.java b/core/src/com/etheller/warsmash/viewer5/AudioPanner.java index 463b874..a83b365 100644 --- a/core/src/com/etheller/warsmash/viewer5/AudioPanner.java +++ b/core/src/com/etheller/warsmash/viewer5/AudioPanner.java @@ -1,10 +1,39 @@ package com.etheller.warsmash.viewer5; +import com.etheller.warsmash.viewer5.AudioContext.Listener; + public abstract class AudioPanner { - public abstract void setPosition(float x, float y, float z); + public Listener listener; + public float x; + public float y; + public float z; + + public AudioPanner(final Listener listener) { + this.listener = listener; + } + + public void setPosition(final float x, final float y, final float z) { + this.x = x; + this.y = y; + this.z = z; + } + + public void setDistances(final float maxDistance, final float refDistance) { + this.maxDistance = maxDistance; + this.refDistance = refDistance; + this.maxDistanceSq = maxDistance * maxDistance; + } public float maxDistance; public float refDistance; + public float maxDistanceSq; public abstract void connect(AudioDestination destination); + + public boolean isWithinListenerDistance() { + final float dx = this.listener.getX() - this.x; + final float dy = this.listener.getY() - this.y; + final float dz = this.listener.getZ() - this.z; + return ((dx * dx) + (dy * dy) + (dz * dz)) <= this.maxDistanceSq; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/Scene.java b/core/src/com/etheller/warsmash/viewer5/Scene.java index c34aac5..3b3b593 100644 --- a/core/src/com/etheller/warsmash/viewer5/Scene.java +++ b/core/src/com/etheller/warsmash/viewer5/Scene.java @@ -12,6 +12,7 @@ import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.GL30; import com.badlogic.gdx.math.Matrix4; import com.badlogic.gdx.math.Rectangle; +import com.etheller.warsmash.viewer5.gl.Extensions; import com.etheller.warsmash.viewer5.gl.WebGL; import com.etheller.warsmash.viewer5.handlers.w3x.DynamicShadowManager; @@ -92,7 +93,7 @@ public abstract class Scene { public boolean enableAudio() { if (this.audioContext == null) { - this.audioContext = new AudioContext(); + this.audioContext = Extensions.audio.createContext(this instanceof WorldScene); } if (!this.audioContext.isRunning()) { this.audioContext.resume(); @@ -197,7 +198,7 @@ public abstract class Scene { final float upZ = this.camera.directionZ.z; final AudioContext.Listener listener = this.audioContext.listener; - listener.setPosition(-x, -y, -z); + listener.setPosition(x, y, z); listener.setOrientation(forwardX, forwardY, forwardZ, upX, upY, upZ); } diff --git a/core/src/com/etheller/warsmash/viewer5/gl/AudioExtension.java b/core/src/com/etheller/warsmash/viewer5/gl/AudioExtension.java new file mode 100644 index 0000000..619633c --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/gl/AudioExtension.java @@ -0,0 +1,13 @@ +package com.etheller.warsmash.viewer5.gl; + +import com.badlogic.gdx.audio.Sound; +import com.etheller.warsmash.viewer5.AudioContext; + +public interface AudioExtension { + AudioContext createContext(boolean world); + + float getDuration(Sound sound); + + void play(Sound buffer, final float volume, final float pitch, final float x, final float y, final float z, + final boolean is3DSound, float maxDistance, float refDistance); +} diff --git a/core/src/com/etheller/warsmash/viewer5/gl/Extensions.java b/core/src/com/etheller/warsmash/viewer5/gl/Extensions.java index f9ed610..3bc0699 100644 --- a/core/src/com/etheller/warsmash/viewer5/gl/Extensions.java +++ b/core/src/com/etheller/warsmash/viewer5/gl/Extensions.java @@ -7,7 +7,7 @@ public class Extensions { public static WireframeExtension wireframeExtension; - public static SoundLengthExtension soundLengthExtension; + public static AudioExtension audio; public static int GL_LINE = 0; public static int GL_FILL = 0; diff --git a/core/src/com/etheller/warsmash/viewer5/gl/SoundLengthExtension.java b/core/src/com/etheller/warsmash/viewer5/gl/SoundLengthExtension.java deleted file mode 100644 index 56de8bd..0000000 --- a/core/src/com/etheller/warsmash/viewer5/gl/SoundLengthExtension.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.etheller.warsmash.viewer5.gl; - -import com.badlogic.gdx.audio.Sound; - -public interface SoundLengthExtension { - float getDuration(Sound sound); -} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectSnd.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectSnd.java index 8c38305..75248a5 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectSnd.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectSnd.java @@ -38,8 +38,7 @@ public class EventObjectSnd extends EmittedObject { private final List splatInstances; private final boolean unshaded; private final boolean noDepthTest; + private final boolean highPriority; public SplatModel(final GL30 gl, final ViewerTextureRenderable texture, final List locations, final float[] centerOffset, final List> unitMapping, final boolean unshaded, - final boolean noDepthTest) { + final boolean noDepthTest, final boolean highPriority) { this.texture = texture; this.unshaded = unshaded; this.noDepthTest = noDepthTest; + this.highPriority = highPriority; this.batches = new ArrayList<>(); this.color = new float[] { 1, 1, 1, 1 }; @@ -261,6 +263,10 @@ public class SplatModel implements Comparable { return this.noDepthTest; } + public boolean isHighPriority() { + return this.highPriority; + } + public SplatMover add(final float x, final float y, final float x2, final float y2, final float zDepthUpward, final float[] centerOffset) { this.locations.add(new float[] { x, y, x2, y2, zDepthUpward }); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitAckSound.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitAckSound.java index 8eaa6d2..c8f40f8 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitAckSound.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitAckSound.java @@ -91,8 +91,7 @@ public final class UnitAckSound { // Panner settings panner.setPosition(unit.location[0], unit.location[1], unit.location[2]); - panner.maxDistance = this.distanceCutoff; - panner.refDistance = this.minDistance; + panner.setDistances(this.distanceCutoff, this.minDistance); panner.connect(audioContext.destination); // Source. @@ -103,7 +102,7 @@ public final class UnitAckSound { source.start(0, this.volume, (this.pitch + ((float) Math.random() * this.pitchVariance * 2)) - this.pitchVariance); this.lastPlayedSound = source.buffer; - final float duration = Extensions.soundLengthExtension.getDuration(this.lastPlayedSound); + final float duration = Extensions.audio.getDuration(this.lastPlayedSound); unit.lastUnitResponseEndTimeMillis = millisTime + (long) (1000 * duration); return true; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitSound.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitSound.java index 62b910f..17ca30b 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitSound.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitSound.java @@ -82,7 +82,7 @@ public final class UnitSound { return false; } if (play(audioContext, unit.location[0], unit.location[1])) { - final float duration = Extensions.soundLengthExtension.getDuration(this.lastPlayedSound); + final float duration = Extensions.audio.getDuration(this.lastPlayedSound); unit.lastUnitResponseEndTimeMillis = millisTime + (long) (1000 * duration); return true; } @@ -106,8 +106,7 @@ public final class UnitSound { // Panner settings panner.setPosition(x, y, 0); - panner.maxDistance = this.distanceCutoff; - panner.refDistance = this.minDistance; + panner.setDistances(this.distanceCutoff, this.minDistance); panner.connect(audioContext.destination); // Source. diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index 91cb27b..eaa471d 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -995,7 +995,7 @@ public class War3MapViewer extends AbstractMdxModelViewer { final float s = uberSplatInfo.getFieldFloatValue("Scale"); if (this.unitsReady) { buildingUberSplatDynamicIngame = this.terrain.addUberSplat(texturePath, unitX, unitY, 1, s, - false, false); + false, false, false); } else { if (!this.terrain.splats.containsKey(texturePath)) { @@ -1318,9 +1318,8 @@ public class War3MapViewer extends AbstractMdxModelViewer { final float x = unit.getX(); final float y = unit.getY(); System.out.println("Selecting a unit at " + x + "," + y); - final float z = unit.getSelectionHeight(); splats.get(path).locations.add(new float[] { x - (selectionSize / 2), y - (selectionSize / 2), - x + (selectionSize / 2), y + (selectionSize / 2), z + 5 }); + x + (selectionSize / 2), y + (selectionSize / 2), 5 }); splats.get(path).unitMapping.add(new Consumer() { @Override public void accept(final SplatMover t) { @@ -1338,7 +1337,7 @@ public class War3MapViewer extends AbstractMdxModelViewer { final String path = entry.getKey(); final Splat locations = entry.getValue(); final SplatModel model = new SplatModel(Gdx.gl30, (Texture) load(path, PathSolver.DEFAULT, null), - locations.locations, this.terrain.centerOffset, locations.unitMapping, true, false); + locations.locations, this.terrain.centerOffset, locations.unitMapping, true, false, true); model.color[0] = 0; model.color[1] = 1; model.color[2] = 0; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java index fbd765c..3cd4d94 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java @@ -1044,7 +1044,7 @@ public class Terrain { // Render the cliffs for (final SplatModel splat : this.uberSplatModelsList) { - if (splat.isNoDepthTest() == onTopLayer) { + if (splat.isHighPriority() == onTopLayer) { splat.render(gl, shader); } } @@ -1433,7 +1433,7 @@ public class Terrain { final SplatModel splatModel = new SplatModel(Gdx.gl30, (Texture) this.viewer.load(path, PathSolver.DEFAULT, null), splat.locations, this.centerOffset, - splat.unitMapping.isEmpty() ? null : splat.unitMapping, false, false); + splat.unitMapping.isEmpty() ? null : splat.unitMapping, false, false, false); splatModel.color[3] = splat.opacity; this.addSplatBatchModel(path, splatModel); } @@ -1450,11 +1450,11 @@ public class Terrain { } public SplatMover addUberSplat(final String path, final float x, final float y, final float z, final float scale, - final boolean unshaded, final boolean noDepthTest) { + final boolean unshaded, final boolean noDepthTest, final boolean highPriority) { SplatModel splatModel = this.uberSplatModels.get(path); if (splatModel == null) { splatModel = new SplatModel(Gdx.gl30, (Texture) this.viewer.load(path, PathSolver.DEFAULT, null), - new ArrayList<>(), this.centerOffset, new ArrayList<>(), unshaded, noDepthTest); + new ArrayList<>(), this.centerOffset, new ArrayList<>(), unshaded, noDepthTest, highPriority); this.addSplatBatchModel(path, splatModel); } return splatModel.add(x - scale, y - scale, x + scale, y + scale, z, this.centerOffset); @@ -1465,7 +1465,7 @@ public class Terrain { SplatModel splatModel = this.uberSplatModels.get(texture); if (splatModel == null) { splatModel = new SplatModel(Gdx.gl30, (Texture) this.viewer.load(texture, PathSolver.DEFAULT, null), - new ArrayList<>(), this.centerOffset, new ArrayList<>(), false, false); + new ArrayList<>(), this.centerOffset, new ArrayList<>(), false, false, false); splatModel.color[3] = opacity; this.addSplatBatchModel(texture, splatModel); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderDestructable.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderDestructable.java index c81eb09..efef1e2 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderDestructable.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderDestructable.java @@ -87,11 +87,6 @@ public class RenderDestructable extends RenderDoodad implements RenderWidget { return this.y; } - @Override - public float getSelectionHeight() { - return 0; - } - @Override public void unassignSelectionCircle() { this.selectionCircle = null; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java index 6d91a26..d045d0e 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java @@ -72,6 +72,7 @@ public class RenderUnit implements RenderWidget { private final RenderUnitTypeData typeData; public final MdxModel specialArtModel; public SplatMover uberSplat; + private float selectionHeight; public RenderUnit(final War3MapViewer map, final MdxModel model, final MutableGameObject row, final float x, final float y, final float z, final int playerIndex, final UnitSoundset soundset, @@ -127,6 +128,7 @@ public class RenderUnit implements RenderWidget { instance.uniformScale(row.getFieldAsFloat(scale, 0)); this.selectionScale = row.getFieldAsFloat(War3MapViewer.UNIT_SELECT_SCALE, 0) * selectionCircleScaleFactor; + this.selectionHeight = row.getFieldAsFloat(UNIT_SELECT_HEIGHT, 0); int orientationInterpolationOrdinal = row.getFieldAsInteger(ORIENTATION_INTERPOLATION, 0); if ((orientationInterpolationOrdinal < 0) || (orientationInterpolationOrdinal >= OrientationInterpolation.VALUES.length)) { @@ -289,6 +291,7 @@ public class RenderUnit implements RenderWidget { this.corpse = corpse; this.boneCorpse = boneCorpse; this.location[2] = this.simulationUnit.getFlyHeight() + groundHeight; + final float selectionCircleHeight = this.selectionHeight + groundHeight; this.instance.moveTo(this.location); float simulationFacing = this.simulationUnit.getFacing(); if (simulationFacing < 0) { @@ -394,12 +397,14 @@ public class RenderUnit implements RenderWidget { map.worldScene.instanceMoved(this.instance, this.location[0], this.location[1]); if (this.shadow != null) { this.shadow.move(dx, dy, map.terrain.centerOffset); - this.shadow.setHeightAbsolute(currentWalkableUnder != null, this.location[2] + map.imageWalkableZOffset); + this.shadow.setHeightAbsolute(currentWalkableUnder != null, groundHeight + map.imageWalkableZOffset); } if (this.selectionCircle != null) { this.selectionCircle.move(dx, dy, map.terrain.centerOffset); - this.selectionCircle.setHeightAbsolute(currentWalkableUnder != null, - this.location[2] + map.imageWalkableZOffset); + this.selectionCircle.setHeightAbsolute( + (currentWalkableUnder != null) + || ((movementType == MovementType.FLY) || (movementType == MovementType.HOVER)), + selectionCircleHeight + map.imageWalkableZOffset); } this.unitAnimationListenerImpl.update(); if (!dead && this.simulationUnit.isConstructing()) { @@ -576,11 +581,6 @@ public class RenderUnit implements RenderWidget { return this.simulationUnit.getUnitType().isBuilding(); } - @Override - public float getSelectionHeight() { - return this.row.getFieldAsFloat(UNIT_SELECT_HEIGHT, 0); - } - @Override public float getSelectionScale() { return this.selectionScale; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderWidget.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderWidget.java index fe5b96f..4016abe 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderWidget.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderWidget.java @@ -20,8 +20,6 @@ public interface RenderWidget { float getY(); - float getSelectionHeight(); - void unassignSelectionCircle(); void assignSelectionCircle(SplatMover t); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java index 1e63f3a..a877a57 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java @@ -1008,7 +1008,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma final ViewerTextureRenderable greenPixmap = new ViewerTextureRenderable.GdxViewerTextureRenderable( MeleeUI.this.cursorModelUnderneathPathingRedGreenPixmapTexture); MeleeUI.this.cursorModelUnderneathPathingRedGreenSplatModel = new SplatModel(Gdx.gl30, greenPixmap, - new ArrayList<>(), viewer.terrain.centerOffset, new ArrayList<>(), true, false); + new ArrayList<>(), viewer.terrain.centerOffset, new ArrayList<>(), true, false, true); MeleeUI.this.cursorModelUnderneathPathingRedGreenSplatModel.color[3] = 0.20f; } } @@ -1077,7 +1077,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma if (MeleeUI.this.placementCursor == null) { MeleeUI.this.placementCursor = viewer.terrain.addUberSplat( MeleeUI.this.rootFrame.getSkinField("PlacementCursor"), clickLocationTemp.x, - clickLocationTemp.y, 10, radius, true, true); + clickLocationTemp.y, 10, radius, true, true, true); } MeleeUI.this.placementCursor.setLocation(clickLocationTemp.x, clickLocationTemp.y, viewer.terrain.centerOffset); diff --git a/desktop/src/com/badlogic/gdx/backends/lwjgl/LwjglApplication.java b/desktop/src/com/badlogic/gdx/backends/lwjgl/LwjglApplication.java new file mode 100644 index 0000000..2123195 --- /dev/null +++ b/desktop/src/com/badlogic/gdx/backends/lwjgl/LwjglApplication.java @@ -0,0 +1,487 @@ +/******************************************************************************* + * Copyright 2011 See AUTHORS file. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************/ + +package com.badlogic.gdx.backends.lwjgl; + +import java.awt.Canvas; +import java.io.File; + +import org.lwjgl.LWJGLException; +import org.lwjgl.opengl.Display; + +import com.badlogic.gdx.Application; +import com.badlogic.gdx.ApplicationListener; +import com.badlogic.gdx.ApplicationLogger; +import com.badlogic.gdx.Audio; +import com.badlogic.gdx.Files; +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.Input; +import com.badlogic.gdx.LifecycleListener; +import com.badlogic.gdx.Net; +import com.badlogic.gdx.Preferences; +import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.Clipboard; +import com.badlogic.gdx.utils.GdxRuntimeException; +import com.badlogic.gdx.utils.ObjectMap; +import com.badlogic.gdx.utils.SnapshotArray; +import com.etheller.warsmash.audio.OpenALAudio; + +/** + * An OpenGL surface fullscreen or in a lightweight window. This was modified by + * Retera in accordance with the permission to do so from the Apache 2.0 license + * listed at the top of the file. The ONLY reason for this modified file + * override currently is to use a replacement for the OpenALAudio class that + * will now support the 3D sound! + */ +public class LwjglApplication implements Application { + protected final LwjglGraphics graphics; + protected OpenALAudio audio; + protected final LwjglFiles files; + protected final LwjglInput input; + protected final LwjglNet net; + protected final ApplicationListener listener; + protected Thread mainLoopThread; + protected boolean running = true; + protected final Array runnables = new Array(); + protected final Array executedRunnables = new Array(); + protected final SnapshotArray lifecycleListeners = new SnapshotArray( + LifecycleListener.class); + protected int logLevel = LOG_INFO; + protected ApplicationLogger applicationLogger; + protected String preferencesdir; + protected Files.FileType preferencesFileType; + + public LwjglApplication(final ApplicationListener listener, final String title, final int width, final int height) { + this(listener, createConfig(title, width, height)); + } + + public LwjglApplication(final ApplicationListener listener) { + this(listener, null, 640, 480); + } + + public LwjglApplication(final ApplicationListener listener, final LwjglApplicationConfiguration config) { + this(listener, config, new LwjglGraphics(config)); + } + + public LwjglApplication(final ApplicationListener listener, final Canvas canvas) { + this(listener, new LwjglApplicationConfiguration(), new LwjglGraphics(canvas)); + } + + public LwjglApplication(final ApplicationListener listener, final LwjglApplicationConfiguration config, + final Canvas canvas) { + this(listener, config, new LwjglGraphics(canvas, config)); + } + + public LwjglApplication(final ApplicationListener listener, final LwjglApplicationConfiguration config, + final LwjglGraphics graphics) { + LwjglNativesLoader.load(); + setApplicationLogger(new LwjglApplicationLogger()); + + if (config.title == null) { + config.title = listener.getClass().getSimpleName(); + } + this.graphics = graphics; + if (!LwjglApplicationConfiguration.disableAudio) { + try { + this.audio = new OpenALAudio(config.audioDeviceSimultaneousSources, config.audioDeviceBufferCount, + config.audioDeviceBufferSize); + } + catch (final Throwable t) { + log("LwjglApplication", "Couldn't initialize audio, disabling audio", t); + LwjglApplicationConfiguration.disableAudio = true; + } + } + this.files = new LwjglFiles(); + this.input = new LwjglInput(); + this.net = new LwjglNet(); + this.listener = listener; + this.preferencesdir = config.preferencesDirectory; + this.preferencesFileType = config.preferencesFileType; + + Gdx.app = this; + Gdx.graphics = graphics; + Gdx.audio = this.audio; + Gdx.files = this.files; + Gdx.input = this.input; + Gdx.net = this.net; + initialize(); + } + + private static LwjglApplicationConfiguration createConfig(final String title, final int width, final int height) { + final LwjglApplicationConfiguration config = new LwjglApplicationConfiguration(); + config.title = title; + config.width = width; + config.height = height; + config.vSyncEnabled = true; + return config; + } + + private void initialize() { + this.mainLoopThread = new Thread("LWJGL Application") { + @Override + public void run() { + LwjglApplication.this.graphics.setVSync(LwjglApplication.this.graphics.config.vSyncEnabled); + try { + LwjglApplication.this.mainLoop(); + } + catch (final Throwable t) { + if (LwjglApplication.this.audio != null) { + LwjglApplication.this.audio.dispose(); + } + Gdx.input.setCursorCatched(false); + if (t instanceof RuntimeException) { + throw (RuntimeException) t; + } + else { + throw new GdxRuntimeException(t); + } + } + } + }; + this.mainLoopThread.start(); + } + + void mainLoop() { + final SnapshotArray lifecycleListeners = this.lifecycleListeners; + + try { + this.graphics.setupDisplay(); + } + catch (final LWJGLException e) { + throw new GdxRuntimeException(e); + } + + this.listener.create(); + this.graphics.resize = true; + + int lastWidth = this.graphics.getWidth(); + int lastHeight = this.graphics.getHeight(); + + this.graphics.lastTime = System.nanoTime(); + boolean wasActive = true; + while (this.running) { + Display.processMessages(); + if (Display.isCloseRequested()) { + exit(); + } + + final boolean isActive = Display.isActive(); + if (wasActive && !isActive) { // if it's just recently minimized from active state + wasActive = false; + synchronized (lifecycleListeners) { + final LifecycleListener[] listeners = lifecycleListeners.begin(); + for (int i = 0, n = lifecycleListeners.size; i < n; ++i) { + listeners[i].pause(); + } + lifecycleListeners.end(); + } + this.listener.pause(); + } + if (!wasActive && isActive) { // if it's just recently focused from minimized state + wasActive = true; + synchronized (lifecycleListeners) { + final LifecycleListener[] listeners = lifecycleListeners.begin(); + for (int i = 0, n = lifecycleListeners.size; i < n; ++i) { + listeners[i].resume(); + } + lifecycleListeners.end(); + } + this.listener.resume(); + } + + boolean shouldRender = false; + + if (this.graphics.canvas != null) { + final int width = this.graphics.canvas.getWidth(); + final int height = this.graphics.canvas.getHeight(); + if ((lastWidth != width) || (lastHeight != height)) { + lastWidth = width; + lastHeight = height; + Gdx.gl.glViewport(0, 0, lastWidth, lastHeight); + this.listener.resize(lastWidth, lastHeight); + shouldRender = true; + } + } + else { + this.graphics.config.x = Display.getX(); + this.graphics.config.y = Display.getY(); + if (this.graphics.resize || Display.wasResized() + || ((int) (Display.getWidth() * Display.getPixelScaleFactor()) != this.graphics.config.width) + || ((int) (Display.getHeight() + * Display.getPixelScaleFactor()) != this.graphics.config.height)) { + this.graphics.resize = false; + this.graphics.config.width = (int) (Display.getWidth() * Display.getPixelScaleFactor()); + this.graphics.config.height = (int) (Display.getHeight() * Display.getPixelScaleFactor()); + Gdx.gl.glViewport(0, 0, this.graphics.config.width, this.graphics.config.height); + if (this.listener != null) { + this.listener.resize(this.graphics.config.width, this.graphics.config.height); + } + this.graphics.requestRendering(); + } + } + + if (executeRunnables()) { + shouldRender = true; + } + + // If one of the runnables set running to false, for example after an exit(). + if (!this.running) { + break; + } + + this.input.update(); + shouldRender |= this.graphics.shouldRender(); + this.input.processEvents(); + if (this.audio != null) { + this.audio.update(); + } + + if (!isActive && (this.graphics.config.backgroundFPS == -1)) { + shouldRender = false; + } + int frameRate = isActive ? this.graphics.config.foregroundFPS : this.graphics.config.backgroundFPS; + if (shouldRender) { + this.graphics.updateTime(); + this.graphics.frameId++; + this.listener.render(); + Display.update(false); + } + else { + // Sleeps to avoid wasting CPU in an empty loop. + if (frameRate == -1) { + frameRate = 10; + } + if (frameRate == 0) { + frameRate = this.graphics.config.backgroundFPS; + } + if (frameRate == 0) { + frameRate = 30; + } + } + if (frameRate > 0) { + Display.sync(frameRate); + } + } + + synchronized (lifecycleListeners) { + final LifecycleListener[] listeners = lifecycleListeners.begin(); + for (int i = 0, n = lifecycleListeners.size; i < n; ++i) { + listeners[i].pause(); + listeners[i].dispose(); + } + lifecycleListeners.end(); + } + this.listener.pause(); + this.listener.dispose(); + Display.destroy(); + if (this.audio != null) { + this.audio.dispose(); + } + if (this.graphics.config.forceExit) { + System.exit(-1); + } + } + + public boolean executeRunnables() { + synchronized (this.runnables) { + for (int i = this.runnables.size - 1; i >= 0; i--) { + this.executedRunnables.add(this.runnables.get(i)); + } + this.runnables.clear(); + } + if (this.executedRunnables.size == 0) { + return false; + } + do { + this.executedRunnables.pop().run(); + } + while (this.executedRunnables.size > 0); + return true; + } + + @Override + public ApplicationListener getApplicationListener() { + return this.listener; + } + + @Override + public Audio getAudio() { + return this.audio; + } + + @Override + public Files getFiles() { + return this.files; + } + + @Override + public LwjglGraphics getGraphics() { + return this.graphics; + } + + @Override + public Input getInput() { + return this.input; + } + + @Override + public Net getNet() { + return this.net; + } + + @Override + public ApplicationType getType() { + return ApplicationType.Desktop; + } + + @Override + public int getVersion() { + return 0; + } + + public void stop() { + this.running = false; + try { + this.mainLoopThread.join(); + } + catch (final Exception ex) { + } + } + + @Override + public long getJavaHeap() { + return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); + } + + @Override + public long getNativeHeap() { + return getJavaHeap(); + } + + ObjectMap preferences = new ObjectMap(); + + @Override + public Preferences getPreferences(final String name) { + if (this.preferences.containsKey(name)) { + return this.preferences.get(name); + } + else { + final Preferences prefs = new LwjglPreferences( + new LwjglFileHandle(new File(this.preferencesdir, name), this.preferencesFileType)); + this.preferences.put(name, prefs); + return prefs; + } + } + + @Override + public Clipboard getClipboard() { + return new LwjglClipboard(); + } + + @Override + public void postRunnable(final Runnable runnable) { + synchronized (this.runnables) { + this.runnables.add(runnable); + Gdx.graphics.requestRendering(); + } + } + + @Override + public void debug(final String tag, final String message) { + if (this.logLevel >= LOG_DEBUG) { + getApplicationLogger().debug(tag, message); + } + } + + @Override + public void debug(final String tag, final String message, final Throwable exception) { + if (this.logLevel >= LOG_DEBUG) { + getApplicationLogger().debug(tag, message, exception); + } + } + + @Override + public void log(final String tag, final String message) { + if (this.logLevel >= LOG_INFO) { + getApplicationLogger().log(tag, message); + } + } + + @Override + public void log(final String tag, final String message, final Throwable exception) { + if (this.logLevel >= LOG_INFO) { + getApplicationLogger().log(tag, message, exception); + } + } + + @Override + public void error(final String tag, final String message) { + if (this.logLevel >= LOG_ERROR) { + getApplicationLogger().error(tag, message); + } + } + + @Override + public void error(final String tag, final String message, final Throwable exception) { + if (this.logLevel >= LOG_ERROR) { + getApplicationLogger().error(tag, message, exception); + } + } + + @Override + public void setLogLevel(final int logLevel) { + this.logLevel = logLevel; + } + + @Override + public int getLogLevel() { + return this.logLevel; + } + + @Override + public void setApplicationLogger(final ApplicationLogger applicationLogger) { + this.applicationLogger = applicationLogger; + } + + @Override + public ApplicationLogger getApplicationLogger() { + return this.applicationLogger; + } + + @Override + public void exit() { + postRunnable(new Runnable() { + @Override + public void run() { + LwjglApplication.this.running = false; + } + }); + } + + @Override + public void addLifecycleListener(final LifecycleListener listener) { + synchronized (this.lifecycleListeners) { + this.lifecycleListeners.add(listener); + } + } + + @Override + public void removeLifecycleListener(final LifecycleListener listener) { + synchronized (this.lifecycleListeners) { + this.lifecycleListeners.removeValue(listener, true); + } + } +} diff --git a/desktop/src/com/etheller/warsmash/audio/JavaSoundAudioRecorder.java b/desktop/src/com/etheller/warsmash/audio/JavaSoundAudioRecorder.java new file mode 100644 index 0000000..a9f663f --- /dev/null +++ b/desktop/src/com/etheller/warsmash/audio/JavaSoundAudioRecorder.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * Copyright 2011 See AUTHORS file. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************/ + +package com.etheller.warsmash.audio; + +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioFormat.Encoding; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.TargetDataLine; + +import com.badlogic.gdx.audio.AudioRecorder; +import com.badlogic.gdx.utils.GdxRuntimeException; + +/** @author mzechner */ +public class JavaSoundAudioRecorder implements AudioRecorder { + private TargetDataLine line; + private byte[] buffer = new byte[1024 * 4]; + + public JavaSoundAudioRecorder(final int samplingRate, final boolean isMono) { + try { + final AudioFormat format = new AudioFormat(Encoding.PCM_SIGNED, samplingRate, 16, isMono ? 1 : 2, + isMono ? 2 : 4, samplingRate, false); + this.line = AudioSystem.getTargetDataLine(format); + this.line.open(format, this.buffer.length); + this.line.start(); + } + catch (final Exception ex) { + throw new GdxRuntimeException("Error creating JavaSoundAudioRecorder.", ex); + } + } + + @Override + public void read(final short[] samples, final int offset, final int numSamples) { + if (this.buffer.length < (numSamples * 2)) { + this.buffer = new byte[numSamples * 2]; + } + + final int toRead = numSamples * 2; + int read = 0; + while (read != toRead) { + read += this.line.read(this.buffer, read, toRead - read); + } + + for (int i = 0, j = 0; i < (numSamples * 2); i += 2, j++) { + samples[offset + j] = (short) ((this.buffer[i + 1] << 8) | (this.buffer[i] & 0xff)); + } + } + + @Override + public void dispose() { + this.line.close(); + } +} diff --git a/desktop/src/com/etheller/warsmash/audio/Mp3.java b/desktop/src/com/etheller/warsmash/audio/Mp3.java new file mode 100644 index 0000000..02bc10c --- /dev/null +++ b/desktop/src/com/etheller/warsmash/audio/Mp3.java @@ -0,0 +1,163 @@ +/******************************************************************************* + * Copyright 2011 See AUTHORS file. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************/ + +package com.etheller.warsmash.audio; + +import java.io.ByteArrayOutputStream; + +import com.badlogic.gdx.files.FileHandle; +import com.badlogic.gdx.utils.GdxRuntimeException; + +import javazoom.jl.decoder.Bitstream; +import javazoom.jl.decoder.BitstreamException; +import javazoom.jl.decoder.Header; +import javazoom.jl.decoder.MP3Decoder; +import javazoom.jl.decoder.OutputBuffer; + +/** @author Nathan Sweet */ +public class Mp3 { + static public class Music extends OpenALMusic { + // Note: This uses a slightly modified version of JLayer. + + private Bitstream bitstream; + private OutputBuffer outputBuffer; + private MP3Decoder decoder; + + public Music(final OpenALAudio audio, final FileHandle file) { + super(audio, file); + if (audio.noDevice) { + return; + } + this.bitstream = new Bitstream(file.read()); + this.decoder = new MP3Decoder(); + this.bufferOverhead = 4096; + try { + final Header header = this.bitstream.readFrame(); + if (header == null) { + throw new GdxRuntimeException("Empty MP3"); + } + final int channels = header.mode() == Header.SINGLE_CHANNEL ? 1 : 2; + this.outputBuffer = new OutputBuffer(channels, false); + this.decoder.setOutputBuffer(this.outputBuffer); + setup(channels, header.getSampleRate()); + } + catch (final BitstreamException e) { + throw new GdxRuntimeException("error while preloading mp3", e); + } + } + + @Override + public int read(final byte[] buffer) { + try { + boolean setup = this.bitstream == null; + if (setup) { + this.bitstream = new Bitstream(this.file.read()); + this.decoder = new MP3Decoder(); + } + + int totalLength = 0; + final int minRequiredLength = buffer.length - (OutputBuffer.BUFFERSIZE * 2); + while (totalLength <= minRequiredLength) { + final Header header = this.bitstream.readFrame(); + if (header == null) { + break; + } + if (setup) { + final int channels = header.mode() == Header.SINGLE_CHANNEL ? 1 : 2; + this.outputBuffer = new OutputBuffer(channels, false); + this.decoder.setOutputBuffer(this.outputBuffer); + setup(channels, header.getSampleRate()); + setup = false; + } + try { + this.decoder.decodeFrame(header, this.bitstream); + } + catch (final Exception ignored) { + // JLayer's decoder throws ArrayIndexOutOfBoundsException sometimes!? + } + this.bitstream.closeFrame(); + + final int length = this.outputBuffer.reset(); + System.arraycopy(this.outputBuffer.getBuffer(), 0, buffer, totalLength, length); + totalLength += length; + } + return totalLength; + } + catch (final Throwable ex) { + reset(); + throw new GdxRuntimeException("Error reading audio data.", ex); + } + } + + @Override + public void reset() { + if (this.bitstream == null) { + return; + } + try { + this.bitstream.close(); + } + catch (final BitstreamException ignored) { + } + this.bitstream = null; + } + } + + static public class Sound extends OpenALSound { + // Note: This uses a slightly modified version of JLayer. + + public Sound(final OpenALAudio audio, final FileHandle file) { + super(audio); + if (audio.noDevice) { + return; + } + final ByteArrayOutputStream output = new ByteArrayOutputStream(4096); + + final Bitstream bitstream = new Bitstream(file.read()); + final MP3Decoder decoder = new MP3Decoder(); + + try { + OutputBuffer outputBuffer = null; + int sampleRate = -1, channels = -1; + while (true) { + final Header header = bitstream.readFrame(); + if (header == null) { + break; + } + if (outputBuffer == null) { + channels = header.mode() == Header.SINGLE_CHANNEL ? 1 : 2; + outputBuffer = new OutputBuffer(channels, false); + decoder.setOutputBuffer(outputBuffer); + sampleRate = header.getSampleRate(); + } + try { + decoder.decodeFrame(header, bitstream); + } + catch (final Exception ignored) { + // JLayer's decoder throws ArrayIndexOutOfBoundsException sometimes!? + } + bitstream.closeFrame(); + output.write(outputBuffer.getBuffer(), 0, outputBuffer.reset()); + } + bitstream.close(); + setup(output.toByteArray(), channels, sampleRate); + } + catch (final Throwable ex) { + throw new GdxRuntimeException("Error reading audio data.", ex); + } + } + } +} diff --git a/desktop/src/com/etheller/warsmash/audio/Ogg.java b/desktop/src/com/etheller/warsmash/audio/Ogg.java new file mode 100644 index 0000000..f61fb19 --- /dev/null +++ b/desktop/src/com/etheller/warsmash/audio/Ogg.java @@ -0,0 +1,89 @@ +/******************************************************************************* + * Copyright 2011 See AUTHORS file. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************/ + +package com.etheller.warsmash.audio; + +import java.io.ByteArrayOutputStream; + +import com.badlogic.gdx.files.FileHandle; +import com.badlogic.gdx.utils.StreamUtils; + +/** @author Nathan Sweet */ +public class Ogg { + static public class Music extends OpenALMusic { + private OggInputStream input; + private OggInputStream previousInput; + + public Music(final OpenALAudio audio, final FileHandle file) { + super(audio, file); + if (audio.noDevice) { + return; + } + this.input = new OggInputStream(file.read()); + setup(this.input.getChannels(), this.input.getSampleRate()); + } + + @Override + public int read(final byte[] buffer) { + if (this.input == null) { + this.input = new OggInputStream(this.file.read(), this.previousInput); + setup(this.input.getChannels(), this.input.getSampleRate()); + this.previousInput = null; // release this reference + } + return this.input.read(buffer); + } + + @Override + public void reset() { + StreamUtils.closeQuietly(this.input); + this.previousInput = null; + this.input = null; + } + + @Override + protected void loop() { + StreamUtils.closeQuietly(this.input); + this.previousInput = this.input; + this.input = null; + } + } + + static public class Sound extends OpenALSound { + public Sound(final OpenALAudio audio, final FileHandle file) { + super(audio); + if (audio.noDevice) { + return; + } + OggInputStream input = null; + try { + input = new OggInputStream(file.read()); + final ByteArrayOutputStream output = new ByteArrayOutputStream(4096); + final byte[] buffer = new byte[2048]; + while (!input.atEnd()) { + final int length = input.read(buffer); + if (length == -1) { + break; + } + output.write(buffer, 0, length); + } + setup(output.toByteArray(), input.getChannels(), input.getSampleRate()); + } + finally { + StreamUtils.closeQuietly(input); + } + } + } +} diff --git a/desktop/src/com/etheller/warsmash/audio/OggInputStream.java b/desktop/src/com/etheller/warsmash/audio/OggInputStream.java new file mode 100644 index 0000000..9fed1cf --- /dev/null +++ b/desktop/src/com/etheller/warsmash/audio/OggInputStream.java @@ -0,0 +1,520 @@ +/** + * Copyright (c) 2007, Slick 2D + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following + * conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the distribution. Neither the name of the Slick 2D nor the names of + * its contributors may be used to endorse or promote products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, + * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.etheller.warsmash.audio; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteOrder; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.utils.GdxRuntimeException; +import com.badlogic.gdx.utils.StreamUtils; +import com.jcraft.jogg.Packet; +import com.jcraft.jogg.Page; +import com.jcraft.jogg.StreamState; +import com.jcraft.jogg.SyncState; +import com.jcraft.jorbis.Block; +import com.jcraft.jorbis.Comment; +import com.jcraft.jorbis.DspState; +import com.jcraft.jorbis.Info; + +/** + * An input stream to read Ogg Vorbis. + * + * @author kevin + */ +public class OggInputStream extends InputStream { + private final static int BUFFER_SIZE = 512; + + /** The conversion buffer size */ + private int convsize = BUFFER_SIZE * 4; + /** The buffer used to read OGG file */ + private byte[] convbuffer; + /** The stream we're reading the OGG file from */ + private final InputStream input; + /** The audio information from the OGG header */ + private final Info oggInfo = new Info(); // struct that stores all the static vorbis bitstream settings + /** True if we're at the end of the available data */ + private boolean endOfStream; + + /** The Vorbis SyncState used to decode the OGG */ + private final SyncState syncState = new SyncState(); // sync and verify incoming physical bitstream + /** The Vorbis Stream State used to decode the OGG */ + private final StreamState streamState = new StreamState(); // take physical pages, weld into a logical stream of + // packets + /** The current OGG page */ + private final Page page = new Page(); // one Ogg bitstream page. Vorbis packets are inside + /** The current packet page */ + private final Packet packet = new Packet(); // one raw packet of data for decode + + /** The comment read from the OGG file */ + private final Comment comment = new Comment(); // struct that stores all the bitstream user comments + /** The Vorbis DSP stat eused to decode the OGG */ + private final DspState dspState = new DspState(); // central working state for the packet->PCM decoder + /** The OGG block we're currently working with to convert PCM */ + private final Block vorbisBlock = new Block(this.dspState); // local working space for packet->PCM decode + + /** Temporary scratch buffer */ + byte[] buffer; + /** The number of bytes read */ + int bytes = 0; + /** The true if we should be reading big endian */ + boolean bigEndian = ByteOrder.nativeOrder().equals(ByteOrder.BIG_ENDIAN); + /** True if we're reached the end of the current bit stream */ + boolean endOfBitStream = true; + /** True if we're initialise the OGG info block */ + boolean inited = false; + + /** The index into the byte array we currently read from */ + private int readIndex; + /** The byte array store used to hold the data read from the ogg */ + private byte[] outBuffer; + private int outIndex; + /** The total number of bytes */ + private int total; + + /** + * Create a new stream to decode OGG data + * + * @param input The input stream from which to read the OGG file + */ + public OggInputStream(final InputStream input) { + this(input, null); + } + + /** + * Create a new stream to decode OGG data, reusing buffers from another stream. + * + * It's not a good idea to use the old stream instance afterwards. + * + * @param input The input stream from which to read the OGG file + * @param previousStream The stream instance to reuse buffers from, may be null + */ + public OggInputStream(final InputStream input, final OggInputStream previousStream) { + if (previousStream == null) { + this.convbuffer = new byte[this.convsize]; + this.outBuffer = new byte[4096 * 500]; + } + else { + this.convbuffer = previousStream.convbuffer; + this.outBuffer = previousStream.outBuffer; + } + + this.input = input; + try { + this.total = input.available(); + } + catch (final IOException ex) { + throw new GdxRuntimeException(ex); + } + + init(); + } + + /** + * Get the number of bytes on the stream + * + * @return The number of the bytes on the stream + */ + public int getLength() { + return this.total; + } + + public int getChannels() { + return this.oggInfo.channels; + } + + public int getSampleRate() { + return this.oggInfo.rate; + } + + /** Initialise the streams and thread involved in the streaming of OGG data */ + private void init() { + initVorbis(); + readPCM(); + } + + /** @see java.io.InputStream#available() */ + @Override + public int available() { + return this.endOfStream ? 0 : 1; + } + + /** Initialise the vorbis decoding */ + private void initVorbis() { + this.syncState.init(); + } + + /** + * Get a page and packet from that page + * + * @return True if there was a page available + */ + private boolean getPageAndPacket() { + // grab some data at the head of the stream. We want the first page + // (which is guaranteed to be small and only contain the Vorbis + // stream initial header) We need the first page to get the stream + // serialno. + + // submit a 4k block to libvorbis' Ogg layer + int index = this.syncState.buffer(BUFFER_SIZE); + if (index == -1) { + return false; + } + + this.buffer = this.syncState.data; + if (this.buffer == null) { + this.endOfStream = true; + return false; + } + + try { + this.bytes = this.input.read(this.buffer, index, BUFFER_SIZE); + } + catch (final Exception e) { + throw new GdxRuntimeException("Failure reading Vorbis.", e); + } + this.syncState.wrote(this.bytes); + + // Get the first page. + if (this.syncState.pageout(this.page) != 1) { + // have we simply run out of data? If so, we're done. + if (this.bytes < BUFFER_SIZE) { + return false; + } + + // error case. Must not be Vorbis data + throw new GdxRuntimeException("Input does not appear to be an Ogg bitstream."); + } + + // Get the serial number and set up the rest of decode. + // serialno first; use it to set up a logical stream + this.streamState.init(this.page.serialno()); + + // extract the initial header from the first page and verify that the + // Ogg bitstream is in fact Vorbis data + + // I handle the initial header first instead of just having the code + // read all three Vorbis headers at once because reading the initial + // header is an easy way to identify a Vorbis bitstream and it's + // useful to see that functionality seperated out. + + this.oggInfo.init(); + this.comment.init(); + if (this.streamState.pagein(this.page) < 0) { + // error; stream version mismatch perhaps + throw new GdxRuntimeException("Error reading first page of Ogg bitstream."); + } + + if (this.streamState.packetout(this.packet) != 1) { + // no page? must not be vorbis + throw new GdxRuntimeException("Error reading initial header packet."); + } + + if (this.oggInfo.synthesis_headerin(this.comment, this.packet) < 0) { + // error case; not a vorbis header + throw new GdxRuntimeException("Ogg bitstream does not contain Vorbis audio data."); + } + + // At this point, we're sure we're Vorbis. We've set up the logical + // (Ogg) bitstream decoder. Get the comment and codebook headers and + // set up the Vorbis decoder + + // The next two packets in order are the comment and codebook headers. + // They're likely large and may span multiple pages. Thus we reead + // and submit data until we get our two pacakets, watching that no + // pages are missing. If a page is missing, error out; losing a + // header page is the only place where missing data is fatal. */ + + int i = 0; + while (i < 2) { + while (i < 2) { + int result = this.syncState.pageout(this.page); + if (result == 0) { + break; // Need more data + // Don't complain about missing or corrupt data yet. We'll + // catch it at the packet output phase + } + + if (result == 1) { + this.streamState.pagein(this.page); // we can ignore any errors here + // as they'll also become apparent + // at packetout + while (i < 2) { + result = this.streamState.packetout(this.packet); + if (result == 0) { + break; + } + if (result == -1) { + // Uh oh; data at some point was corrupted or missing! + // We can't tolerate that in a header. Die. + throw new GdxRuntimeException("Corrupt secondary header."); + } + + this.oggInfo.synthesis_headerin(this.comment, this.packet); + i++; + } + } + } + // no harm in not checking before adding more + index = this.syncState.buffer(BUFFER_SIZE); + if (index == -1) { + return false; + } + this.buffer = this.syncState.data; + try { + this.bytes = this.input.read(this.buffer, index, BUFFER_SIZE); + } + catch (final Exception e) { + throw new GdxRuntimeException("Failed to read Vorbis.", e); + } + if ((this.bytes == 0) && (i < 2)) { + throw new GdxRuntimeException("End of file before finding all Vorbis headers."); + } + this.syncState.wrote(this.bytes); + } + + this.convsize = BUFFER_SIZE / this.oggInfo.channels; + + // OK, got and parsed all three headers. Initialize the Vorbis + // packet->PCM decoder. + this.dspState.synthesis_init(this.oggInfo); // central decode state + this.vorbisBlock.init(this.dspState); // local state for most of the decode + // so multiple block decodes can + // proceed in parallel. We could init + // multiple vorbis_block structures + // for vd here + + return true; + } + + /** Decode the OGG file as shown in the jogg/jorbis examples */ + private void readPCM() { + boolean wrote = false; + + while (true) { // we repeat if the bitstream is chained + if (this.endOfBitStream) { + if (!getPageAndPacket()) { + break; + } + this.endOfBitStream = false; + } + + if (!this.inited) { + this.inited = true; + return; + } + + final float[][][] _pcm = new float[1][][]; + final int[] _index = new int[this.oggInfo.channels]; + // The rest is just a straight decode loop until end of stream + while (!this.endOfBitStream) { + while (!this.endOfBitStream) { + int result = this.syncState.pageout(this.page); + + if (result == 0) { + break; // need more data + } + + if (result == -1) { // missing or corrupt data at this page position + // throw new GdxRuntimeException("Corrupt or missing data in bitstream."); + Gdx.app.log("gdx-audio", "Error reading OGG: Corrupt or missing data in bitstream."); + } + else { + this.streamState.pagein(this.page); // can safely ignore errors at + // this point + while (true) { + result = this.streamState.packetout(this.packet); + + if (result == 0) { + break; // need more data + } + if (result == -1) { // missing or corrupt data at this page position + // no reason to complain; already complained above + } + else { + // we have a packet. Decode it + int samples; + if (this.vorbisBlock.synthesis(this.packet) == 0) { // test for success! + this.dspState.synthesis_blockin(this.vorbisBlock); + } + + // **pcm is a multichannel float vector. In stereo, for + // example, pcm[0] is left, and pcm[1] is right. samples is + // the size of each channel. Convert the float values + // (-1.<=range<=1.) to whatever PCM format and write it out + + while ((samples = this.dspState.synthesis_pcmout(_pcm, _index)) > 0) { + final float[][] pcm = _pcm[0]; + // boolean clipflag = false; + final int bout = (samples < this.convsize ? samples : this.convsize); + + // convert floats to 16 bit signed ints (host order) and + // interleave + for (int i = 0; i < this.oggInfo.channels; i++) { + int ptr = i * 2; + // int ptr=i; + final int mono = _index[i]; + for (int j = 0; j < bout; j++) { + int val = (int) (pcm[i][mono + j] * 32767.); + // might as well guard against clipping + if (val > 32767) { + val = 32767; + } + if (val < -32768) { + val = -32768; + } + if (val < 0) { + val = val | 0x8000; + } + + if (this.bigEndian) { + this.convbuffer[ptr] = (byte) (val >>> 8); + this.convbuffer[ptr + 1] = (byte) (val); + } + else { + this.convbuffer[ptr] = (byte) (val); + this.convbuffer[ptr + 1] = (byte) (val >>> 8); + } + ptr += 2 * (this.oggInfo.channels); + } + } + + final int bytesToWrite = 2 * this.oggInfo.channels * bout; + if ((this.outIndex + bytesToWrite) > this.outBuffer.length) { + throw new GdxRuntimeException("Ogg block too big to be buffered: " + + bytesToWrite + ", " + (this.outBuffer.length - this.outIndex)); + } + else { + System.arraycopy(this.convbuffer, 0, this.outBuffer, this.outIndex, + bytesToWrite); + this.outIndex += bytesToWrite; + } + + wrote = true; + this.dspState.synthesis_read(bout); // tell libvorbis how + // many samples we + // actually consumed + } + } + } + if (this.page.eos() != 0) { + this.endOfBitStream = true; + } + + if ((!this.endOfBitStream) && (wrote)) { + return; + } + } + } + + if (!this.endOfBitStream) { + this.bytes = 0; + final int index = this.syncState.buffer(BUFFER_SIZE); + if (index >= 0) { + this.buffer = this.syncState.data; + try { + this.bytes = this.input.read(this.buffer, index, BUFFER_SIZE); + } + catch (final Exception e) { + throw new GdxRuntimeException("Error during Vorbis decoding.", e); + } + } + else { + this.bytes = 0; + } + this.syncState.wrote(this.bytes); + if (this.bytes == 0) { + this.endOfBitStream = true; + } + } + } + + // clean up this logical bitstream; before exit we see if we're + // followed by another [chained] + this.streamState.clear(); + + // ogg_page and ogg_packet structs always point to storage in + // libvorbis. They're never freed or manipulated directly + + this.vorbisBlock.clear(); + this.dspState.clear(); + this.oggInfo.clear(); // must be called last + } + + // OK, clean up the framer + this.syncState.clear(); + this.endOfStream = true; + } + + @Override + public int read() { + if (this.readIndex >= this.outIndex) { + this.outIndex = 0; + readPCM(); + this.readIndex = 0; + if (this.outIndex == 0) { + return -1; + } + } + + int value = this.outBuffer[this.readIndex]; + if (value < 0) { + value = 256 + value; + } + this.readIndex++; + + return value; + } + + public boolean atEnd() { + return this.endOfStream && (this.readIndex >= this.outIndex); + } + + @Override + public int read(final byte[] b, final int off, final int len) { + for (int i = 0; i < len; i++) { + final int value = read(); + if (value >= 0) { + b[i] = (byte) value; + } + else { + if (i == 0) { + return -1; + } + return i; + } + } + return len; + } + + @Override + public int read(final byte[] b) { + return read(b, 0, b.length); + } + + @Override + public void close() { + StreamUtils.closeQuietly(this.input); + } +} diff --git a/desktop/src/com/etheller/warsmash/audio/OpenALAudio.java b/desktop/src/com/etheller/warsmash/audio/OpenALAudio.java new file mode 100644 index 0000000..ad3077a --- /dev/null +++ b/desktop/src/com/etheller/warsmash/audio/OpenALAudio.java @@ -0,0 +1,477 @@ +/******************************************************************************* + * Copyright 2011 See AUTHORS file. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************/ + +package com.etheller.warsmash.audio; + +import static org.lwjgl.openal.AL10.AL_BUFFER; +import static org.lwjgl.openal.AL10.AL_NO_ERROR; +import static org.lwjgl.openal.AL10.AL_ORIENTATION; +import static org.lwjgl.openal.AL10.AL_PAUSED; +import static org.lwjgl.openal.AL10.AL_PLAYING; +import static org.lwjgl.openal.AL10.AL_POSITION; +import static org.lwjgl.openal.AL10.AL_SOURCE_STATE; +import static org.lwjgl.openal.AL10.AL_STOPPED; +import static org.lwjgl.openal.AL10.AL_VELOCITY; +import static org.lwjgl.openal.AL10.alDeleteSources; +import static org.lwjgl.openal.AL10.alGenSources; +import static org.lwjgl.openal.AL10.alGetError; +import static org.lwjgl.openal.AL10.alGetSourcei; +import static org.lwjgl.openal.AL10.alListener; +import static org.lwjgl.openal.AL10.alSourcePause; +import static org.lwjgl.openal.AL10.alSourcePlay; +import static org.lwjgl.openal.AL10.alSourceStop; +import static org.lwjgl.openal.AL10.alSourcei; + +import java.nio.FloatBuffer; + +import org.lwjgl.BufferUtils; +import org.lwjgl.LWJGLException; +import org.lwjgl.openal.AL; +import org.lwjgl.openal.AL10; + +import com.badlogic.gdx.Audio; +import com.badlogic.gdx.audio.AudioDevice; +import com.badlogic.gdx.audio.AudioRecorder; +import com.badlogic.gdx.files.FileHandle; +import com.badlogic.gdx.math.MathUtils; +import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.GdxRuntimeException; +import com.badlogic.gdx.utils.IntArray; +import com.badlogic.gdx.utils.IntMap; +import com.badlogic.gdx.utils.LongMap; +import com.badlogic.gdx.utils.ObjectMap; + +/** @author Nathan Sweet */ +public class OpenALAudio implements Audio { + private final int deviceBufferSize; + private final int deviceBufferCount; + private IntArray idleSources, allSources; + private LongMap soundIdToSource; + private IntMap sourceToSoundId; + private long nextSoundId = 0; + private final ObjectMap> extensionToSoundClass = new ObjectMap(); + private final ObjectMap> extensionToMusicClass = new ObjectMap(); + private OpenALSound[] recentSounds; + private int mostRecetSound = -1; + + Array music = new Array(false, 1, OpenALMusic.class); + boolean noDevice = false; + + public OpenALAudio() { + this(16, 9, 512); + } + + public OpenALAudio(final int simultaneousSources, final int deviceBufferCount, final int deviceBufferSize) { + this.deviceBufferSize = deviceBufferSize; + this.deviceBufferCount = deviceBufferCount; + + registerSound("ogg", Ogg.Sound.class); + registerMusic("ogg", Ogg.Music.class); + registerSound("wav", Wav.Sound.class); + registerMusic("wav", Wav.Music.class); + registerSound("mp3", Mp3.Sound.class); + registerMusic("mp3", Mp3.Music.class); + + try { + AL.create(); + } + catch (final LWJGLException ex) { + this.noDevice = true; + ex.printStackTrace(); + return; + } + + this.allSources = new IntArray(false, simultaneousSources); + for (int i = 0; i < simultaneousSources; i++) { + final int sourceID = alGenSources(); + if (alGetError() != AL_NO_ERROR) { + break; + } + this.allSources.add(sourceID); + } + this.idleSources = new IntArray(this.allSources); + this.soundIdToSource = new LongMap(); + this.sourceToSoundId = new IntMap(); + + final FloatBuffer orientation = (FloatBuffer) BufferUtils.createFloatBuffer(6) + .put(new float[] { 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f }).flip(); + alListener(AL_ORIENTATION, orientation); + final FloatBuffer velocity = (FloatBuffer) BufferUtils.createFloatBuffer(3) + .put(new float[] { 0.0f, 0.0f, 0.0f }).flip(); + alListener(AL_VELOCITY, velocity); + final FloatBuffer position = (FloatBuffer) BufferUtils.createFloatBuffer(3) + .put(new float[] { 0.0f, 0.0f, 0.0f }).flip(); + alListener(AL_POSITION, position); + + this.recentSounds = new OpenALSound[simultaneousSources]; + } + + public void registerSound(final String extension, final Class soundClass) { + if (extension == null) { + throw new IllegalArgumentException("extension cannot be null."); + } + if (soundClass == null) { + throw new IllegalArgumentException("soundClass cannot be null."); + } + this.extensionToSoundClass.put(extension, soundClass); + } + + public void registerMusic(final String extension, final Class musicClass) { + if (extension == null) { + throw new IllegalArgumentException("extension cannot be null."); + } + if (musicClass == null) { + throw new IllegalArgumentException("musicClass cannot be null."); + } + this.extensionToMusicClass.put(extension, musicClass); + } + + @Override + public OpenALSound newSound(final FileHandle file) { + if (file == null) { + throw new IllegalArgumentException("file cannot be null."); + } + final Class soundClass = this.extensionToSoundClass.get(file.extension().toLowerCase()); + if (soundClass == null) { + throw new GdxRuntimeException("Unknown file extension for sound: " + file); + } + try { + return soundClass.getConstructor(new Class[] { OpenALAudio.class, FileHandle.class }).newInstance(this, + file); + } + catch (final Exception ex) { + throw new GdxRuntimeException("Error creating sound " + soundClass.getName() + " for file: " + file, ex); + } + } + + @Override + public OpenALMusic newMusic(final FileHandle file) { + if (file == null) { + throw new IllegalArgumentException("file cannot be null."); + } + final Class musicClass = this.extensionToMusicClass.get(file.extension().toLowerCase()); + if (musicClass == null) { + throw new GdxRuntimeException("Unknown file extension for music: " + file); + } + try { + return musicClass.getConstructor(new Class[] { OpenALAudio.class, FileHandle.class }).newInstance(this, + file); + } + catch (final Exception ex) { + throw new GdxRuntimeException("Error creating music " + musicClass.getName() + " for file: " + file, ex); + } + } + + int obtainSource(final boolean isMusic) { + if (this.noDevice) { + return 0; + } + for (int i = 0, n = this.idleSources.size; i < n; i++) { + final int sourceId = this.idleSources.get(i); + final int state = alGetSourcei(sourceId, AL_SOURCE_STATE); + if ((state != AL_PLAYING) && (state != AL_PAUSED)) { + if (isMusic) { + this.idleSources.removeIndex(i); + } + else { + if (this.sourceToSoundId.containsKey(sourceId)) { + final long soundId = this.sourceToSoundId.get(sourceId); + this.sourceToSoundId.remove(sourceId); + this.soundIdToSource.remove(soundId); + } + + final long soundId = this.nextSoundId++; + this.sourceToSoundId.put(sourceId, soundId); + this.soundIdToSource.put(soundId, sourceId); + } + alSourceStop(sourceId); + alSourcei(sourceId, AL_BUFFER, 0); + AL10.alSourcef(sourceId, AL10.AL_GAIN, 1); + AL10.alSourcef(sourceId, AL10.AL_PITCH, 1); + AL10.alSource3f(sourceId, AL10.AL_POSITION, 0, 0, 1f); + return sourceId; + } + } + return -1; + } + + void freeSource(final int sourceID) { + if (this.noDevice) { + return; + } + alSourceStop(sourceID); + alSourcei(sourceID, AL_BUFFER, 0); + if (this.sourceToSoundId.containsKey(sourceID)) { + final long soundId = this.sourceToSoundId.remove(sourceID); + this.soundIdToSource.remove(soundId); + } + this.idleSources.add(sourceID); + } + + void freeBuffer(final int bufferID) { + if (this.noDevice) { + return; + } + for (int i = 0, n = this.idleSources.size; i < n; i++) { + final int sourceID = this.idleSources.get(i); + if (alGetSourcei(sourceID, AL_BUFFER) == bufferID) { + if (this.sourceToSoundId.containsKey(sourceID)) { + final long soundId = this.sourceToSoundId.remove(sourceID); + this.soundIdToSource.remove(soundId); + } + alSourceStop(sourceID); + alSourcei(sourceID, AL_BUFFER, 0); + } + } + } + + void stopSourcesWithBuffer(final int bufferID) { + if (this.noDevice) { + return; + } + for (int i = 0, n = this.idleSources.size; i < n; i++) { + final int sourceID = this.idleSources.get(i); + if (alGetSourcei(sourceID, AL_BUFFER) == bufferID) { + if (this.sourceToSoundId.containsKey(sourceID)) { + final long soundId = this.sourceToSoundId.remove(sourceID); + this.soundIdToSource.remove(soundId); + } + alSourceStop(sourceID); + } + } + } + + void pauseSourcesWithBuffer(final int bufferID) { + if (this.noDevice) { + return; + } + for (int i = 0, n = this.idleSources.size; i < n; i++) { + final int sourceID = this.idleSources.get(i); + if (alGetSourcei(sourceID, AL_BUFFER) == bufferID) { + alSourcePause(sourceID); + } + } + } + + void resumeSourcesWithBuffer(final int bufferID) { + if (this.noDevice) { + return; + } + for (int i = 0, n = this.idleSources.size; i < n; i++) { + final int sourceID = this.idleSources.get(i); + if (alGetSourcei(sourceID, AL_BUFFER) == bufferID) { + if (alGetSourcei(sourceID, AL_SOURCE_STATE) == AL_PAUSED) { + alSourcePlay(sourceID); + } + } + } + } + + public void update() { + if (this.noDevice) { + return; + } + for (int i = 0; i < this.music.size; i++) { + this.music.items[i].update(); + } + } + + public long getSoundId(final int sourceId) { + if (!this.sourceToSoundId.containsKey(sourceId)) { + return -1; + } + return this.sourceToSoundId.get(sourceId); + } + + public void stopSound(final long soundId) { + if (!this.soundIdToSource.containsKey(soundId)) { + return; + } + final int sourceId = this.soundIdToSource.get(soundId); + alSourceStop(sourceId); + } + + public void pauseSound(final long soundId) { + if (!this.soundIdToSource.containsKey(soundId)) { + return; + } + final int sourceId = this.soundIdToSource.get(soundId); + alSourcePause(sourceId); + } + + public void resumeSound(final long soundId) { + if (!this.soundIdToSource.containsKey(soundId)) { + return; + } + final int sourceId = this.soundIdToSource.get(soundId); + if (alGetSourcei(sourceId, AL_SOURCE_STATE) == AL_PAUSED) { + alSourcePlay(sourceId); + } + } + + public void setSoundGain(final long soundId, final float volume) { + if (!this.soundIdToSource.containsKey(soundId)) { + return; + } + final int sourceId = this.soundIdToSource.get(soundId); + AL10.alSourcef(sourceId, AL10.AL_GAIN, volume); + } + + public void setSoundLooping(final long soundId, final boolean looping) { + if (!this.soundIdToSource.containsKey(soundId)) { + return; + } + final int sourceId = this.soundIdToSource.get(soundId); + alSourcei(sourceId, AL10.AL_LOOPING, looping ? AL10.AL_TRUE : AL10.AL_FALSE); + } + + public void setSoundPitch(final long soundId, final float pitch) { + if (!this.soundIdToSource.containsKey(soundId)) { + return; + } + final int sourceId = this.soundIdToSource.get(soundId); + AL10.alSourcef(sourceId, AL10.AL_PITCH, pitch); + } + + public void setSoundPan(final long soundId, final float pan, final float volume) { + if (!this.soundIdToSource.containsKey(soundId)) { + return; + } + final int sourceId = this.soundIdToSource.get(soundId); + + AL10.alSource3f(sourceId, AL10.AL_POSITION, MathUtils.cos(((pan - 1) * MathUtils.PI) / 2), 0, + MathUtils.sin(((pan + 1) * MathUtils.PI) / 2)); + AL10.alSourcef(sourceId, AL10.AL_GAIN, volume); + } + + public void setSoundPosition(final long soundId, final float x, final float y, final float z, + final boolean is3DSound, final float maxDistance, final float refDistance) { + if (!this.soundIdToSource.containsKey(soundId)) { + return; + } + final int sourceId = this.soundIdToSource.get(soundId); + + AL10.alSource3f(sourceId, AL10.AL_POSITION, x, y, z); + AL10.alSourcef(sourceId, AL10.AL_MAX_DISTANCE, maxDistance); + AL10.alSourcef(sourceId, AL10.AL_REFERENCE_DISTANCE, refDistance); + AL10.alSourcef(sourceId, AL10.AL_ROLLOFF_FACTOR, 1.0f); + AL10.alSourcei(sourceId, AL10.AL_SOURCE_RELATIVE, is3DSound ? AL10.AL_FALSE : AL10.AL_TRUE); + } + + public void dispose() { + if (this.noDevice) { + return; + } + for (int i = 0, n = this.allSources.size; i < n; i++) { + final int sourceID = this.allSources.get(i); + final int state = alGetSourcei(sourceID, AL_SOURCE_STATE); + if (state != AL_STOPPED) { + alSourceStop(sourceID); + } + alDeleteSources(sourceID); + } + + this.sourceToSoundId.clear(); + this.soundIdToSource.clear(); + + AL.destroy(); + while (AL.isCreated()) { + try { + Thread.sleep(10); + } + catch (final InterruptedException e) { + } + } + } + + @Override + public AudioDevice newAudioDevice(final int sampleRate, final boolean isMono) { + if (this.noDevice) { + return new AudioDevice() { + @Override + public void writeSamples(final float[] samples, final int offset, final int numSamples) { + } + + @Override + public void writeSamples(final short[] samples, final int offset, final int numSamples) { + } + + @Override + public void setVolume(final float volume) { + } + + @Override + public boolean isMono() { + return isMono; + } + + @Override + public int getLatency() { + return 0; + } + + @Override + public void dispose() { + } + }; + } + return new OpenALAudioDevice(this, sampleRate, isMono, this.deviceBufferSize, this.deviceBufferCount); + } + + @Override + public AudioRecorder newAudioRecorder(final int samplingRate, final boolean isMono) { + if (this.noDevice) { + return new AudioRecorder() { + @Override + public void read(final short[] samples, final int offset, final int numSamples) { + } + + @Override + public void dispose() { + } + }; + } + return new JavaSoundAudioRecorder(samplingRate, isMono); + } + + /** + * Retains a list of the most recently played sounds and stops the sound played + * least recently if necessary for a new sound to play + */ + protected void retain(final OpenALSound sound, final boolean stop) { + // Move the pointer ahead and wrap + this.mostRecetSound++; + this.mostRecetSound %= this.recentSounds.length; + + if (stop) { + // Stop the least recent sound (the one we are about to bump off the buffer) + if (this.recentSounds[this.mostRecetSound] != null) { + this.recentSounds[this.mostRecetSound].stop(); + } + } + + this.recentSounds[this.mostRecetSound] = sound; + } + + /** Removes the disposed sound from the least recently played list */ + public void forget(final OpenALSound sound) { + for (int i = 0; i < this.recentSounds.length; i++) { + if (this.recentSounds[i] == sound) { + this.recentSounds[i] = null; + } + } + } +} diff --git a/desktop/src/com/etheller/warsmash/audio/OpenALAudioDevice.java b/desktop/src/com/etheller/warsmash/audio/OpenALAudioDevice.java new file mode 100644 index 0000000..a251466 --- /dev/null +++ b/desktop/src/com/etheller/warsmash/audio/OpenALAudioDevice.java @@ -0,0 +1,265 @@ +/******************************************************************************* + * Copyright 2011 See AUTHORS file. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************/ + +package com.etheller.warsmash.audio; + +import static org.lwjgl.openal.AL10.AL_BUFFERS_PROCESSED; +import static org.lwjgl.openal.AL10.AL_FALSE; +import static org.lwjgl.openal.AL10.AL_FORMAT_MONO16; +import static org.lwjgl.openal.AL10.AL_FORMAT_STEREO16; +import static org.lwjgl.openal.AL10.AL_GAIN; +import static org.lwjgl.openal.AL10.AL_INVALID_VALUE; +import static org.lwjgl.openal.AL10.AL_LOOPING; +import static org.lwjgl.openal.AL10.AL_NO_ERROR; +import static org.lwjgl.openal.AL10.AL_PLAYING; +import static org.lwjgl.openal.AL10.AL_SOURCE_STATE; +import static org.lwjgl.openal.AL10.alBufferData; +import static org.lwjgl.openal.AL10.alDeleteBuffers; +import static org.lwjgl.openal.AL10.alGenBuffers; +import static org.lwjgl.openal.AL10.alGetError; +import static org.lwjgl.openal.AL10.alGetSourcef; +import static org.lwjgl.openal.AL10.alGetSourcei; +import static org.lwjgl.openal.AL10.alSourcePlay; +import static org.lwjgl.openal.AL10.alSourceQueueBuffers; +import static org.lwjgl.openal.AL10.alSourceUnqueueBuffers; +import static org.lwjgl.openal.AL10.alSourcef; +import static org.lwjgl.openal.AL10.alSourcei; + +import java.nio.ByteBuffer; +import java.nio.IntBuffer; + +import org.lwjgl.BufferUtils; +import org.lwjgl.openal.AL11; + +import com.badlogic.gdx.audio.AudioDevice; +import com.badlogic.gdx.math.MathUtils; +import com.badlogic.gdx.utils.GdxRuntimeException; + +/** @author Nathan Sweet */ +public class OpenALAudioDevice implements AudioDevice { + static private final int bytesPerSample = 2; + + private final OpenALAudio audio; + private final int channels; + private IntBuffer buffers; + private int sourceID = -1; + private final int format, sampleRate; + private boolean isPlaying; + private float volume = 1; + private float renderedSeconds; + + private final float secondsPerBuffer; + private byte[] bytes; + private final int bufferSize; + private final int bufferCount; + private final ByteBuffer tempBuffer; + + public OpenALAudioDevice(final OpenALAudio audio, final int sampleRate, final boolean isMono, final int bufferSize, + final int bufferCount) { + this.audio = audio; + this.channels = isMono ? 1 : 2; + this.bufferSize = bufferSize; + this.bufferCount = bufferCount; + this.format = this.channels > 1 ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16; + this.sampleRate = sampleRate; + this.secondsPerBuffer = (float) bufferSize / bytesPerSample / this.channels / sampleRate; + this.tempBuffer = BufferUtils.createByteBuffer(bufferSize); + } + + @Override + public void writeSamples(final short[] samples, final int offset, final int numSamples) { + if ((this.bytes == null) || (this.bytes.length < (numSamples * 2))) { + this.bytes = new byte[numSamples * 2]; + } + final int end = Math.min(offset + numSamples, samples.length); + for (int i = offset, ii = 0; i < end; i++) { + final short sample = samples[i]; + this.bytes[ii++] = (byte) (sample & 0xFF); + this.bytes[ii++] = (byte) ((sample >> 8) & 0xFF); + } + writeSamples(this.bytes, 0, numSamples * 2); + } + + @Override + public void writeSamples(final float[] samples, final int offset, final int numSamples) { + if ((this.bytes == null) || (this.bytes.length < (numSamples * 2))) { + this.bytes = new byte[numSamples * 2]; + } + final int end = Math.min(offset + numSamples, samples.length); + for (int i = offset, ii = 0; i < end; i++) { + float floatSample = samples[i]; + floatSample = MathUtils.clamp(floatSample, -1f, 1f); + final int intSample = (int) (floatSample * 32767); + this.bytes[ii++] = (byte) (intSample & 0xFF); + this.bytes[ii++] = (byte) ((intSample >> 8) & 0xFF); + } + writeSamples(this.bytes, 0, numSamples * 2); + } + + public void writeSamples(final byte[] data, int offset, int length) { + if (length < 0) { + throw new IllegalArgumentException("length cannot be < 0."); + } + + if (this.sourceID == -1) { + this.sourceID = this.audio.obtainSource(true); + if (this.sourceID == -1) { + return; + } + if (this.buffers == null) { + this.buffers = BufferUtils.createIntBuffer(this.bufferCount); + alGenBuffers(this.buffers); + if (alGetError() != AL_NO_ERROR) { + throw new GdxRuntimeException("Unabe to allocate audio buffers."); + } + } + alSourcei(this.sourceID, AL_LOOPING, AL_FALSE); + alSourcef(this.sourceID, AL_GAIN, this.volume); + // Fill initial buffers. + int queuedBuffers = 0; + for (int i = 0; i < this.bufferCount; i++) { + final int bufferID = this.buffers.get(i); + final int written = Math.min(this.bufferSize, length); + this.tempBuffer.clear(); + this.tempBuffer.put(data, offset, written).flip(); + alBufferData(bufferID, this.format, this.tempBuffer, this.sampleRate); + alSourceQueueBuffers(this.sourceID, bufferID); + length -= written; + offset += written; + queuedBuffers++; + } + // Queue rest of buffers, empty. + this.tempBuffer.clear().flip(); + for (int i = queuedBuffers; i < this.bufferCount; i++) { + final int bufferID = this.buffers.get(i); + alBufferData(bufferID, this.format, this.tempBuffer, this.sampleRate); + alSourceQueueBuffers(this.sourceID, bufferID); + } + alSourcePlay(this.sourceID); + this.isPlaying = true; + } + + while (length > 0) { + final int written = fillBuffer(data, offset, length); + length -= written; + offset += written; + } + } + + /** Blocks until some of the data could be buffered. */ + private int fillBuffer(final byte[] data, final int offset, final int length) { + final int written = Math.min(this.bufferSize, length); + + outer: while (true) { + int buffers = alGetSourcei(this.sourceID, AL_BUFFERS_PROCESSED); + while (buffers-- > 0) { + final int bufferID = alSourceUnqueueBuffers(this.sourceID); + if (bufferID == AL_INVALID_VALUE) { + break; + } + this.renderedSeconds += this.secondsPerBuffer; + + this.tempBuffer.clear(); + this.tempBuffer.put(data, offset, written).flip(); + alBufferData(bufferID, this.format, this.tempBuffer, this.sampleRate); + + alSourceQueueBuffers(this.sourceID, bufferID); + break outer; + } + // Wait for buffer to be free. + try { + Thread.sleep((long) (1000 * this.secondsPerBuffer)); + } + catch (final InterruptedException ignored) { + } + } + + // A buffer underflow will cause the source to stop. + if (!this.isPlaying || (alGetSourcei(this.sourceID, AL_SOURCE_STATE) != AL_PLAYING)) { + alSourcePlay(this.sourceID); + this.isPlaying = true; + } + + return written; + } + + public void stop() { + if (this.sourceID == -1) { + return; + } + this.audio.freeSource(this.sourceID); + this.sourceID = -1; + this.renderedSeconds = 0; + this.isPlaying = false; + } + + public boolean isPlaying() { + if (this.sourceID == -1) { + return false; + } + return this.isPlaying; + } + + @Override + public void setVolume(final float volume) { + this.volume = volume; + if (this.sourceID != -1) { + alSourcef(this.sourceID, AL_GAIN, volume); + } + } + + public float getPosition() { + if (this.sourceID == -1) { + return 0; + } + return this.renderedSeconds + alGetSourcef(this.sourceID, AL11.AL_SEC_OFFSET); + } + + public void setPosition(final float position) { + this.renderedSeconds = position; + } + + public int getChannels() { + return this.format == AL_FORMAT_STEREO16 ? 2 : 1; + } + + public int getRate() { + return this.sampleRate; + } + + @Override + public void dispose() { + if (this.buffers == null) { + return; + } + if (this.sourceID != -1) { + this.audio.freeSource(this.sourceID); + this.sourceID = -1; + } + alDeleteBuffers(this.buffers); + this.buffers = null; + } + + @Override + public boolean isMono() { + return this.channels == 1; + } + + @Override + public int getLatency() { + return (int) (this.secondsPerBuffer * this.bufferCount * 1000); + } +} diff --git a/desktop/src/com/etheller/warsmash/audio/OpenALMusic.java b/desktop/src/com/etheller/warsmash/audio/OpenALMusic.java new file mode 100644 index 0000000..279fea3 --- /dev/null +++ b/desktop/src/com/etheller/warsmash/audio/OpenALMusic.java @@ -0,0 +1,396 @@ +/******************************************************************************* + * Copyright 2011 See AUTHORS file. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************/ + +package com.etheller.warsmash.audio; + +import static org.lwjgl.openal.AL10.AL_BUFFERS_PROCESSED; +import static org.lwjgl.openal.AL10.AL_BUFFERS_QUEUED; +import static org.lwjgl.openal.AL10.AL_FALSE; +import static org.lwjgl.openal.AL10.AL_FORMAT_MONO16; +import static org.lwjgl.openal.AL10.AL_FORMAT_STEREO16; +import static org.lwjgl.openal.AL10.AL_GAIN; +import static org.lwjgl.openal.AL10.AL_INVALID_VALUE; +import static org.lwjgl.openal.AL10.AL_LOOPING; +import static org.lwjgl.openal.AL10.AL_NO_ERROR; +import static org.lwjgl.openal.AL10.AL_PLAYING; +import static org.lwjgl.openal.AL10.AL_POSITION; +import static org.lwjgl.openal.AL10.AL_SOURCE_STATE; +import static org.lwjgl.openal.AL10.alBufferData; +import static org.lwjgl.openal.AL10.alDeleteBuffers; +import static org.lwjgl.openal.AL10.alGenBuffers; +import static org.lwjgl.openal.AL10.alGetError; +import static org.lwjgl.openal.AL10.alGetSourcef; +import static org.lwjgl.openal.AL10.alGetSourcei; +import static org.lwjgl.openal.AL10.alSource3f; +import static org.lwjgl.openal.AL10.alSourcePause; +import static org.lwjgl.openal.AL10.alSourcePlay; +import static org.lwjgl.openal.AL10.alSourceQueueBuffers; +import static org.lwjgl.openal.AL10.alSourceStop; +import static org.lwjgl.openal.AL10.alSourceUnqueueBuffers; +import static org.lwjgl.openal.AL10.alSourcef; +import static org.lwjgl.openal.AL10.alSourcei; + +import java.nio.ByteBuffer; +import java.nio.IntBuffer; + +import org.lwjgl.BufferUtils; +import org.lwjgl.openal.AL11; + +import com.badlogic.gdx.audio.Music; +import com.badlogic.gdx.files.FileHandle; +import com.badlogic.gdx.math.MathUtils; +import com.badlogic.gdx.utils.FloatArray; +import com.badlogic.gdx.utils.GdxRuntimeException; + +/** @author Nathan Sweet */ +public abstract class OpenALMusic implements Music { + static private final int bufferSize = 4096 * 10; + static private final int bufferCount = 3; + static private final int bytesPerSample = 2; + static private final byte[] tempBytes = new byte[bufferSize]; + static private final ByteBuffer tempBuffer = BufferUtils.createByteBuffer(bufferSize); + + private final FloatArray renderedSecondsQueue = new FloatArray(bufferCount); + + private final OpenALAudio audio; + private IntBuffer buffers; + private int sourceID = -1; + private int format, sampleRate; + private boolean isLooping, isPlaying; + private float volume = 1; + private float pan = 0; + private float renderedSeconds, maxSecondsPerBuffer; + + protected final FileHandle file; + protected int bufferOverhead = 0; + + private OnCompletionListener onCompletionListener; + + public OpenALMusic(final OpenALAudio audio, final FileHandle file) { + this.audio = audio; + this.file = file; + this.onCompletionListener = null; + } + + protected void setup(final int channels, final int sampleRate) { + this.format = channels > 1 ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16; + this.sampleRate = sampleRate; + this.maxSecondsPerBuffer = (float) (bufferSize - this.bufferOverhead) + / (bytesPerSample * channels * sampleRate); + } + + @Override + public void play() { + if (this.audio.noDevice) { + return; + } + if (this.sourceID == -1) { + this.sourceID = this.audio.obtainSource(true); + if (this.sourceID == -1) { + return; + } + + this.audio.music.add(this); + + if (this.buffers == null) { + this.buffers = BufferUtils.createIntBuffer(bufferCount); + alGenBuffers(this.buffers); + final int errorCode = alGetError(); + if (errorCode != AL_NO_ERROR) { + throw new GdxRuntimeException("Unable to allocate audio buffers. AL Error: " + errorCode); + } + } + alSourcei(this.sourceID, AL_LOOPING, AL_FALSE); + setPan(this.pan, this.volume); + + boolean filled = false; // Check if there's anything to actually play. + for (int i = 0; i < bufferCount; i++) { + final int bufferID = this.buffers.get(i); + if (!fill(bufferID)) { + break; + } + filled = true; + alSourceQueueBuffers(this.sourceID, bufferID); + } + if (!filled && (this.onCompletionListener != null)) { + this.onCompletionListener.onCompletion(this); + } + + if (alGetError() != AL_NO_ERROR) { + stop(); + return; + } + } + if (!this.isPlaying) { + alSourcePlay(this.sourceID); + this.isPlaying = true; + } + } + + @Override + public void stop() { + if (this.audio.noDevice) { + return; + } + if (this.sourceID == -1) { + return; + } + this.audio.music.removeValue(this, true); + reset(); + this.audio.freeSource(this.sourceID); + this.sourceID = -1; + this.renderedSeconds = 0; + this.renderedSecondsQueue.clear(); + this.isPlaying = false; + } + + @Override + public void pause() { + if (this.audio.noDevice) { + return; + } + if (this.sourceID != -1) { + alSourcePause(this.sourceID); + } + this.isPlaying = false; + } + + @Override + public boolean isPlaying() { + if (this.audio.noDevice) { + return false; + } + if (this.sourceID == -1) { + return false; + } + return this.isPlaying; + } + + @Override + public void setLooping(final boolean isLooping) { + this.isLooping = isLooping; + } + + @Override + public boolean isLooping() { + return this.isLooping; + } + + @Override + public void setVolume(final float volume) { + this.volume = volume; + if (this.audio.noDevice) { + return; + } + if (this.sourceID != -1) { + alSourcef(this.sourceID, AL_GAIN, volume); + } + } + + @Override + public float getVolume() { + return this.volume; + } + + @Override + public void setPan(final float pan, final float volume) { + this.volume = volume; + this.pan = pan; + if (this.audio.noDevice) { + return; + } + if (this.sourceID == -1) { + return; + } + alSource3f(this.sourceID, AL_POSITION, MathUtils.cos(((pan - 1) * MathUtils.PI) / 2), 0, + MathUtils.sin(((pan + 1) * MathUtils.PI) / 2)); + alSourcef(this.sourceID, AL_GAIN, volume); + } + + @Override + public void setPosition(final float position) { + if (this.audio.noDevice) { + return; + } + if (this.sourceID == -1) { + return; + } + final boolean wasPlaying = this.isPlaying; + this.isPlaying = false; + alSourceStop(this.sourceID); + alSourceUnqueueBuffers(this.sourceID, this.buffers); + while (this.renderedSecondsQueue.size > 0) { + this.renderedSeconds = this.renderedSecondsQueue.pop(); + } + if (position <= this.renderedSeconds) { + reset(); + this.renderedSeconds = 0; + } + while (this.renderedSeconds < (position - this.maxSecondsPerBuffer)) { + if (read(tempBytes) <= 0) { + break; + } + this.renderedSeconds += this.maxSecondsPerBuffer; + } + this.renderedSecondsQueue.add(this.renderedSeconds); + boolean filled = false; + for (int i = 0; i < bufferCount; i++) { + final int bufferID = this.buffers.get(i); + if (!fill(bufferID)) { + break; + } + filled = true; + alSourceQueueBuffers(this.sourceID, bufferID); + } + this.renderedSecondsQueue.pop(); + if (!filled) { + stop(); + if (this.onCompletionListener != null) { + this.onCompletionListener.onCompletion(this); + } + } + alSourcef(this.sourceID, AL11.AL_SEC_OFFSET, position - this.renderedSeconds); + if (wasPlaying) { + alSourcePlay(this.sourceID); + this.isPlaying = true; + } + } + + @Override + public float getPosition() { + if (this.audio.noDevice) { + return 0; + } + if (this.sourceID == -1) { + return 0; + } + return this.renderedSeconds + alGetSourcef(this.sourceID, AL11.AL_SEC_OFFSET); + } + + /** + * Fills as much of the buffer as possible and returns the number of bytes + * filled. Returns <= 0 to indicate the end of the stream. + */ + abstract public int read(byte[] buffer); + + /** Resets the stream to the beginning. */ + abstract public void reset(); + + /** + * By default, does just the same as reset(). Used to add special behaviour in + * Ogg.Music. + */ + protected void loop() { + reset(); + } + + public int getChannels() { + return this.format == AL_FORMAT_STEREO16 ? 2 : 1; + } + + public int getRate() { + return this.sampleRate; + } + + public void update() { + if (this.audio.noDevice) { + return; + } + if (this.sourceID == -1) { + return; + } + + boolean end = false; + int buffers = alGetSourcei(this.sourceID, AL_BUFFERS_PROCESSED); + while (buffers-- > 0) { + final int bufferID = alSourceUnqueueBuffers(this.sourceID); + if (bufferID == AL_INVALID_VALUE) { + break; + } + this.renderedSeconds = this.renderedSecondsQueue.pop(); + if (end) { + continue; + } + if (fill(bufferID)) { + alSourceQueueBuffers(this.sourceID, bufferID); + } + else { + end = true; + } + } + if (end && (alGetSourcei(this.sourceID, AL_BUFFERS_QUEUED) == 0)) { + stop(); + if (this.onCompletionListener != null) { + this.onCompletionListener.onCompletion(this); + } + } + + // A buffer underflow will cause the source to stop. + if (this.isPlaying && (alGetSourcei(this.sourceID, AL_SOURCE_STATE) != AL_PLAYING)) { + alSourcePlay(this.sourceID); + } + } + + private boolean fill(final int bufferID) { + tempBuffer.clear(); + int length = read(tempBytes); + if (length <= 0) { + if (this.isLooping) { + loop(); + length = read(tempBytes); + if (length <= 0) { + return false; + } + if (this.renderedSecondsQueue.size > 0) { + this.renderedSecondsQueue.set(0, 0); + } + } + else { + return false; + } + } + final float previousLoadedSeconds = this.renderedSecondsQueue.size > 0 ? this.renderedSecondsQueue.first() : 0; + final float currentBufferSeconds = (this.maxSecondsPerBuffer * length) / bufferSize; + this.renderedSecondsQueue.insert(0, previousLoadedSeconds + currentBufferSeconds); + + tempBuffer.put(tempBytes, 0, length).flip(); + alBufferData(bufferID, this.format, tempBuffer, this.sampleRate); + return true; + } + + @Override + public void dispose() { + stop(); + if (this.audio.noDevice) { + return; + } + if (this.buffers == null) { + return; + } + alDeleteBuffers(this.buffers); + this.buffers = null; + this.onCompletionListener = null; + } + + @Override + public void setOnCompletionListener(final OnCompletionListener listener) { + this.onCompletionListener = listener; + } + + public int getSourceId() { + return this.sourceID; + } +} diff --git a/desktop/src/com/etheller/warsmash/audio/OpenALSound.java b/desktop/src/com/etheller/warsmash/audio/OpenALSound.java new file mode 100644 index 0000000..b7f086d --- /dev/null +++ b/desktop/src/com/etheller/warsmash/audio/OpenALSound.java @@ -0,0 +1,249 @@ +/******************************************************************************* + * Copyright 2011 See AUTHORS file. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************/ + +package com.etheller.warsmash.audio; + +import static org.lwjgl.openal.AL10.AL_BUFFER; +import static org.lwjgl.openal.AL10.AL_FALSE; +import static org.lwjgl.openal.AL10.AL_FORMAT_MONO16; +import static org.lwjgl.openal.AL10.AL_FORMAT_STEREO16; +import static org.lwjgl.openal.AL10.AL_GAIN; +import static org.lwjgl.openal.AL10.AL_LOOPING; +import static org.lwjgl.openal.AL10.AL_TRUE; +import static org.lwjgl.openal.AL10.alBufferData; +import static org.lwjgl.openal.AL10.alDeleteBuffers; +import static org.lwjgl.openal.AL10.alGenBuffers; +import static org.lwjgl.openal.AL10.alSourcePlay; +import static org.lwjgl.openal.AL10.alSourcef; +import static org.lwjgl.openal.AL10.alSourcei; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import com.badlogic.gdx.audio.Sound; + +/** @author Nathan Sweet */ +public class OpenALSound implements Sound { + private int bufferID = -1; + private final OpenALAudio audio; + private float duration; + + public OpenALSound(final OpenALAudio audio) { + this.audio = audio; + } + + void setup(final byte[] pcm, final int channels, final int sampleRate) { + final int bytes = pcm.length - (pcm.length % (channels > 1 ? 4 : 2)); + final int samples = bytes / (2 * channels); + this.duration = samples / (float) sampleRate; + + final ByteBuffer buffer = ByteBuffer.allocateDirect(bytes); + buffer.order(ByteOrder.nativeOrder()); + buffer.put(pcm, 0, bytes); + buffer.flip(); + + if (this.bufferID == -1) { + this.bufferID = alGenBuffers(); + alBufferData(this.bufferID, channels > 1 ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16, buffer.asShortBuffer(), + sampleRate); + } + } + + @Override + public long play() { + return play(1); + } + + @Override + public long play(final float volume) { + if (this.audio.noDevice) { + return 0; + } + int sourceID = this.audio.obtainSource(false); + if (sourceID == -1) { + // Attempt to recover by stopping the least recently played sound + this.audio.retain(this, true); + sourceID = this.audio.obtainSource(false); + } + else { + this.audio.retain(this, false); + } + // In case it still didn't work + if (sourceID == -1) { + return -1; + } + final long soundId = this.audio.getSoundId(sourceID); + alSourcei(sourceID, AL_BUFFER, this.bufferID); + alSourcei(sourceID, AL_LOOPING, AL_FALSE); + alSourcef(sourceID, AL_GAIN, volume); + alSourcePlay(sourceID); + return soundId; + } + + @Override + public long loop() { + return loop(1); + } + + @Override + public long loop(final float volume) { + if (this.audio.noDevice) { + return 0; + } + final int sourceID = this.audio.obtainSource(false); + if (sourceID == -1) { + return -1; + } + final long soundId = this.audio.getSoundId(sourceID); + alSourcei(sourceID, AL_BUFFER, this.bufferID); + alSourcei(sourceID, AL_LOOPING, AL_TRUE); + alSourcef(sourceID, AL_GAIN, volume); + alSourcePlay(sourceID); + return soundId; + } + + @Override + public void stop() { + if (this.audio.noDevice) { + return; + } + this.audio.stopSourcesWithBuffer(this.bufferID); + } + + @Override + public void dispose() { + if (this.audio.noDevice) { + return; + } + if (this.bufferID == -1) { + return; + } + this.audio.freeBuffer(this.bufferID); + alDeleteBuffers(this.bufferID); + this.bufferID = -1; + this.audio.forget(this); + } + + @Override + public void stop(final long soundId) { + if (this.audio.noDevice) { + return; + } + this.audio.stopSound(soundId); + } + + @Override + public void pause() { + if (this.audio.noDevice) { + return; + } + this.audio.pauseSourcesWithBuffer(this.bufferID); + } + + @Override + public void pause(final long soundId) { + if (this.audio.noDevice) { + return; + } + this.audio.pauseSound(soundId); + } + + @Override + public void resume() { + if (this.audio.noDevice) { + return; + } + this.audio.resumeSourcesWithBuffer(this.bufferID); + } + + @Override + public void resume(final long soundId) { + if (this.audio.noDevice) { + return; + } + this.audio.resumeSound(soundId); + } + + @Override + public void setPitch(final long soundId, final float pitch) { + if (this.audio.noDevice) { + return; + } + this.audio.setSoundPitch(soundId, pitch); + } + + @Override + public void setVolume(final long soundId, final float volume) { + if (this.audio.noDevice) { + return; + } + this.audio.setSoundGain(soundId, volume); + } + + @Override + public void setLooping(final long soundId, final boolean looping) { + if (this.audio.noDevice) { + return; + } + this.audio.setSoundLooping(soundId, looping); + } + + @Override + public void setPan(final long soundId, final float pan, final float volume) { + if (this.audio.noDevice) { + return; + } + this.audio.setSoundPan(soundId, pan, volume); + } + + public void setPosition(final long soundId, final float x, final float y, final float z, final boolean is3DSound, + final float maxDistance, final float refDistance) { + if (this.audio.noDevice) { + return; + } + this.audio.setSoundPosition(soundId, x, y, z, is3DSound, maxDistance, refDistance); + } + + @Override + public long play(final float volume, final float pitch, final float pan) { + final long id = play(); + setPitch(id, pitch); + setPan(id, pan, volume); + return id; + } + + public long play(final float volume, final float pitch, final float x, final float y, final float z, + final boolean is3DSound, final float maxDistance, final float refDistance) { + final long id = play(); + setPitch(id, pitch); + setVolume(id, volume); + setPosition(id, x, y, z, is3DSound, maxDistance, refDistance); + return id; + } + + @Override + public long loop(final float volume, final float pitch, final float pan) { + final long id = loop(); + setPitch(id, pitch); + setPan(id, pan, volume); + return id; + } + + /** Returns the length of the sound in seconds. */ + public float duration() { + return this.duration; + } +} diff --git a/desktop/src/com/etheller/warsmash/audio/Wav.java b/desktop/src/com/etheller/warsmash/audio/Wav.java new file mode 100644 index 0000000..eaa493e --- /dev/null +++ b/desktop/src/com/etheller/warsmash/audio/Wav.java @@ -0,0 +1,196 @@ +/******************************************************************************* + * Copyright 2011 See AUTHORS file. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************/ + +package com.etheller.warsmash.audio; + +import java.io.EOFException; +import java.io.FilterInputStream; +import java.io.IOException; + +import com.badlogic.gdx.files.FileHandle; +import com.badlogic.gdx.utils.GdxRuntimeException; +import com.badlogic.gdx.utils.StreamUtils; + +public class Wav { + static public class Music extends OpenALMusic { + private WavInputStream input; + + public Music(final OpenALAudio audio, final FileHandle file) { + super(audio, file); + this.input = new WavInputStream(file); + if (audio.noDevice) { + return; + } + setup(this.input.channels, this.input.sampleRate); + } + + @Override + public int read(final byte[] buffer) { + if (this.input == null) { + this.input = new WavInputStream(this.file); + setup(this.input.channels, this.input.sampleRate); + } + try { + return this.input.read(buffer); + } + catch (final IOException ex) { + throw new GdxRuntimeException("Error reading WAV file: " + this.file, ex); + } + } + + @Override + public void reset() { + StreamUtils.closeQuietly(this.input); + this.input = null; + } + } + + static public class Sound extends OpenALSound { + public Sound(final OpenALAudio audio, final FileHandle file) { + super(audio); + if (audio.noDevice) { + return; + } + + WavInputStream input = null; + try { + input = new WavInputStream(file); + setup(StreamUtils.copyStreamToByteArray(input, input.dataRemaining), input.channels, input.sampleRate); + } + catch (final IOException ex) { + throw new GdxRuntimeException("Error reading WAV file: " + file, ex); + } + finally { + StreamUtils.closeQuietly(input); + } + } + } + + /** @author Nathan Sweet */ + static public class WavInputStream extends FilterInputStream { + + public int channels, sampleRate, dataRemaining; + + public WavInputStream(final FileHandle file) { + super(file.read()); + try { + if ((read() != 'R') || (read() != 'I') || (read() != 'F') || (read() != 'F')) { + throw new GdxRuntimeException("RIFF header not found: " + file); + } + + skipFully(4); + + if ((read() != 'W') || (read() != 'A') || (read() != 'V') || (read() != 'E')) { + throw new GdxRuntimeException("Invalid wave file header: " + file); + } + + final int fmtChunkLength = seekToChunk('f', 'm', 't', ' '); + + // http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html + // http://soundfile.sapp.org/doc/WaveFormat/ + final int type = (read() & 0xff) | ((read() & 0xff) << 8); + if (type != 1) { + String name; + switch (type) { + case 0x0002: + name = "ADPCM"; + break; + case 0x0003: + name = "IEEE float"; + break; + case 0x0006: + name = "8-bit ITU-T G.711 A-law"; + break; + case 0x0007: + name = "8-bit ITU-T G.711 u-law"; + break; + case 0xFFFE: + name = "Extensible"; + break; + default: + name = "Unknown"; + } + throw new GdxRuntimeException( + "WAV files must be PCM, unsupported format: " + name + " (" + type + ")"); + } + + this.channels = (read() & 0xff) | ((read() & 0xff) << 8); + if ((this.channels != 1) && (this.channels != 2)) { + throw new GdxRuntimeException("WAV files must have 1 or 2 channels: " + this.channels); + } + + this.sampleRate = (read() & 0xff) | ((read() & 0xff) << 8) | ((read() & 0xff) << 16) + | ((read() & 0xff) << 24); + + skipFully(6); + + final int bitsPerSample = (read() & 0xff) | ((read() & 0xff) << 8); + if (bitsPerSample != 16) { + throw new GdxRuntimeException("WAV files must have 16 bits per sample: " + bitsPerSample); + } + + skipFully(fmtChunkLength - 16); + + this.dataRemaining = seekToChunk('d', 'a', 't', 'a'); + } + catch (final Throwable ex) { + StreamUtils.closeQuietly(this); + throw new GdxRuntimeException("Error reading WAV file: " + file, ex); + } + } + + private int seekToChunk(final char c1, final char c2, final char c3, final char c4) throws IOException { + while (true) { + boolean found = read() == c1; + found &= read() == c2; + found &= read() == c3; + found &= read() == c4; + final int chunkLength = (read() & 0xff) | ((read() & 0xff) << 8) | ((read() & 0xff) << 16) + | ((read() & 0xff) << 24); + if (chunkLength == -1) { + throw new IOException("Chunk not found: " + c1 + c2 + c3 + c4); + } + if (found) { + return chunkLength; + } + skipFully(chunkLength); + } + } + + private void skipFully(int count) throws IOException { + while (count > 0) { + final long skipped = this.in.skip(count); + if (skipped <= 0) { + throw new EOFException("Unable to skip."); + } + count -= skipped; + } + } + + @Override + public int read(final byte[] buffer) throws IOException { + if (this.dataRemaining == 0) { + return -1; + } + final int length = Math.min(super.read(buffer), this.dataRemaining); + if (length == -1) { + return -1; + } + this.dataRemaining -= length; + return length; + } + } +} diff --git a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java index 1f12b81..e17725c 100644 --- a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java +++ b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java @@ -1,9 +1,15 @@ package com.etheller.warsmash.desktop; +import static org.lwjgl.openal.AL10.AL_ORIENTATION; +import static org.lwjgl.openal.AL10.AL_POSITION; +import static org.lwjgl.openal.AL10.alListener; + import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; +import java.nio.FloatBuffer; +import org.lwjgl.BufferUtils; import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL31; import org.lwjgl.opengl.GL32; @@ -13,20 +19,22 @@ import com.badlogic.gdx.Graphics.DisplayMode; import com.badlogic.gdx.audio.Sound; import com.badlogic.gdx.backends.lwjgl.LwjglApplication; import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration; -import com.badlogic.gdx.backends.lwjgl.audio.OpenALSound; +import com.badlogic.gdx.backends.lwjgl.LwjglNativesLoader; import com.etheller.warsmash.WarsmashGdxMapGame; +import com.etheller.warsmash.audio.OpenALSound; import com.etheller.warsmash.units.DataTable; import com.etheller.warsmash.util.StringBundle; +import com.etheller.warsmash.viewer5.AudioContext; +import com.etheller.warsmash.viewer5.AudioContext.Listener; +import com.etheller.warsmash.viewer5.AudioDestination; import com.etheller.warsmash.viewer5.gl.ANGLEInstancedArrays; +import com.etheller.warsmash.viewer5.gl.AudioExtension; import com.etheller.warsmash.viewer5.gl.DynamicShadowExtension; import com.etheller.warsmash.viewer5.gl.Extensions; -import com.etheller.warsmash.viewer5.gl.SoundLengthExtension; import com.etheller.warsmash.viewer5.gl.WireframeExtension; public class DesktopLauncher { public static void main(final String[] arg) { - loadExtensions(); - final DataTable warsmashIni = loadWarsmashIni(); final LwjglApplicationConfiguration config = new LwjglApplicationConfiguration(); config.useGL30 = true; config.gles30ContextMajorVersion = 3; @@ -44,6 +52,8 @@ public class DesktopLauncher { else { config.fullscreen = true; } + loadExtensions(); + final DataTable warsmashIni = loadWarsmashIni(); new LwjglApplication(new WarsmashGdxMapGame(warsmashIni), config); } @@ -62,6 +72,7 @@ public class DesktopLauncher { } public static void loadExtensions() { + LwjglNativesLoader.load(); Extensions.angleInstancedArrays = new ANGLEInstancedArrays() { @Override public void glVertexAttribDivisorANGLE(final int index, final int divisor) { @@ -98,7 +109,10 @@ public class DesktopLauncher { GL11.glPolygonMode(face, mode); } }; - Extensions.soundLengthExtension = new SoundLengthExtension() { + Extensions.audio = new AudioExtension() { + final FloatBuffer orientation = (FloatBuffer) BufferUtils.createFloatBuffer(6).clear(); + final FloatBuffer position = (FloatBuffer) BufferUtils.createFloatBuffer(3).clear(); + @Override public float getDuration(final Sound sound) { if (sound == null) { @@ -106,6 +120,73 @@ public class DesktopLauncher { } return ((OpenALSound) sound).duration(); } + + @Override + public void play(final Sound buffer, final float volume, final float pitch, final float x, final float y, + final float z, final boolean is3dSound, final float maxDistance, final float refDistance) { + ((OpenALSound) buffer).play(volume, pitch, x, y, z, is3dSound, maxDistance, refDistance); + } + + @Override + public AudioContext createContext(final boolean world) { + Listener listener; + if (world) { + listener = new Listener() { + private float x; + private float y; + private float z; + + @Override + public void setPosition(final float x, final float y, final float z) { + this.x = x; + this.y = y; + this.z = z; + position.put(0, x); + position.put(1, y); + position.put(2, z); + alListener(AL_POSITION, position); + } + + @Override + public float getX() { + return this.x; + } + + @Override + public float getY() { + return this.y; + } + + @Override + public float getZ() { + return this.z; + } + + @Override + public void setOrientation(final float forwardX, final float forwardY, final float forwardZ, + final float upX, final float upY, final float upZ) { + orientation.put(0, forwardX); + orientation.put(1, forwardY); + orientation.put(2, forwardZ); + orientation.put(3, upX); + orientation.put(4, upY); + orientation.put(5, upZ); + alListener(AL_ORIENTATION, orientation); + } + + @Override + public boolean is3DSupported() { + return true; + } + }; + } + else { + listener = Listener.DO_NOTHING; + } + + return new AudioContext(listener, new AudioDestination() { + }); + } }; Extensions.GL_LINE = GL11.GL_LINE; Extensions.GL_FILL = GL11.GL_FILL; From 12ef127d0e59628a89b8534b6390e9dddb49ce9a Mon Sep 17 00:00:00 2001 From: Retera Date: Sun, 31 Jan 2021 14:22:17 -0500 Subject: [PATCH 097/116] Attempt to improve pathfinding performance --- .../abilities/harvest/CAbilityHarvest.java | 4 ++ .../pathing/CPathfindingProcessor.java | 41 ++++++++++++++----- 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityHarvest.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityHarvest.java index ef56125..ffc1f40 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityHarvest.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityHarvest.java @@ -1,6 +1,7 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.harvest; import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CDestructable; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; @@ -102,6 +103,9 @@ public class CAbilityHarvest extends AbstractGenericSingleIconActiveAbility { } receiver.mustTargetResources(); } + else if (target instanceof CDestructable) { + receiver.mustTargetResources(); + } else { receiver.mustTargetResources(); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/pathing/CPathfindingProcessor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/pathing/CPathfindingProcessor.java index da77227..14913b5 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/pathing/CPathfindingProcessor.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/pathing/CPathfindingProcessor.java @@ -26,6 +26,9 @@ public class CPathfindingProcessor { private final Node[][] cornerNodes; private final Node[] goalSet = new Node[4]; private int goals = 0; + private int pathfindJobId = 0; + private int totalIterations = 0; + private int totalJobLoops = 0; public CPathfindingProcessor(final PathingGrid pathingGrid, final CWorldCollision worldCollision) { this.pathingGrid = pathingGrid; @@ -140,10 +143,21 @@ public class CPathfindingProcessor { private double f; private double g; private Node cameFrom; + private int pathfindJobId; private Node(final Point2D.Float point) { this.point = point; } + + private void touch(final int pathfindJobId) { + if (pathfindJobId != this.pathfindJobId) { + this.g = Float.POSITIVE_INFINITY; + this.f = Float.POSITIVE_INFINITY; + this.cameFrom = null; + this.cameFromDirection = null; + this.pathfindJobId = pathfindJobId; + } + } } private static enum Direction { @@ -205,8 +219,12 @@ public class CPathfindingProcessor { public void update(final CSimulation simulation) { int workIterations = 0; JobsLoop: while (!this.moveQueue.isEmpty()) { + this.totalJobLoops++; final PathfindingJob job = this.moveQueue.peek(); if (!job.jobStarted) { + this.pathfindJobId++; + this.totalIterations = 0; + this.totalJobLoops = 0; job.jobStarted = true; System.out.println("starting job with smoothing=" + job.allowSmoothing); workIterations += 5; // setup of job predicted cost @@ -239,12 +257,14 @@ public class CPathfindingProcessor { final int goalCellY = job.gridMapping.getY(this.pathingGrid, job.goalY); final int goalCellX = job.gridMapping.getX(this.pathingGrid, job.goalX); final Node mostLikelyGoal = job.searchGraph[goalCellY][goalCellX]; + mostLikelyGoal.touch(this.pathfindJobId); final double bestGoalDistance = mostLikelyGoal.point.distance(job.goalX, job.goalY); Arrays.fill(this.goalSet, null); this.goals = 0; for (int i = goalCellX - 1; i <= (goalCellX + 1); i++) { for (int j = goalCellY - 1; j <= (goalCellY + 1); j++) { final Node possibleGoal = job.searchGraph[j][i]; + possibleGoal.touch(this.pathfindJobId); if (possibleGoal.point.distance(job.goalX, job.goalY) <= bestGoalDistance) { this.goalSet[this.goals++] = possibleGoal; } @@ -252,16 +272,6 @@ public class CPathfindingProcessor { } final int startGridY = job.gridMapping.getY(this.pathingGrid, job.startY); final int startGridX = job.gridMapping.getX(this.pathingGrid, job.startX); - for (int i = 0; i < job.searchGraph.length; i++) { - for (int j = 0; j < job.searchGraph[i].length; j++) { - final Node node = job.searchGraph[i][j]; - node.g = Float.POSITIVE_INFINITY; - node.f = Float.POSITIVE_INFINITY; - node.cameFrom = null; - node.cameFromDirection = null; - workIterations++; - } - } job.openSet = new PriorityQueue<>(new Comparator() { @Override public int compare(final Node a, final Node b) { @@ -270,6 +280,7 @@ public class CPathfindingProcessor { }); job.start = job.searchGraph[startGridY][startGridX]; + job.start.touch(this.pathfindJobId); if (job.startX > job.start.point.x) { job.startGridMinX = startGridX; job.startGridMaxX = startGridX + 1; @@ -299,6 +310,7 @@ public class CPathfindingProcessor { if ((cellX >= 0) && (cellX < this.pathingGrid.getWidth()) && (cellY >= 0) && (cellY < this.pathingGrid.getHeight())) { final Node possibleNode = job.searchGraph[cellY][cellX]; + possibleNode.touch(this.pathfindJobId); final float x = possibleNode.point.x; final float y = possibleNode.point.y; if (pathableBetween(job.ignoreIntersectionsWithThisUnit, @@ -325,6 +337,7 @@ public class CPathfindingProcessor { while (!job.openSet.isEmpty()) { Node current = job.openSet.poll(); + current.touch(this.pathfindJobId); if (isGoal(current)) { final LinkedList totalPath = new LinkedList<>(); Direction lastCameFromDirection = null; @@ -377,6 +390,8 @@ public class CPathfindingProcessor { } job.queueItem.pathFound(totalPath, simulation); this.moveQueue.poll(); + System.out.println("Task " + this.pathfindJobId + " took " + this.totalIterations + + " iterations and " + this.totalJobLoops + " job loops!"); continue JobsLoop; } @@ -399,6 +414,7 @@ public class CPathfindingProcessor { } final Node neighbor = job.searchGraph[job.gridMapping.getY(this.pathingGrid, y)][job.gridMapping .getX(this.pathingGrid, x)]; + neighbor.touch(this.pathfindJobId); if (tentativeScore < neighbor.g) { neighbor.cameFrom = current; neighbor.cameFromDirection = direction; @@ -411,13 +427,16 @@ public class CPathfindingProcessor { } } workIterations++; - if (workIterations >= 15000) { + this.totalIterations++; + if (workIterations >= 7500) { // breaking jobs loop will implicitly exit without calling pathFound() below break JobsLoop; } } job.queueItem.pathFound(Collections.emptyList(), simulation); this.moveQueue.poll(); + System.out.println("Task " + this.pathfindJobId + " took " + this.totalIterations + " iterations and " + + this.totalJobLoops + " job loops!"); } } From 600ec9536b614493d6c759cbc9d7fcab4f69c8d3 Mon Sep 17 00:00:00 2001 From: Retera Date: Sun, 31 Jan 2021 23:09:19 -0500 Subject: [PATCH 098/116] Initial commit for experimental effort to parse 1.32 format --- build.gradle | 10 +- core/assets/warsmashRF.ini | 41 + .../etheller/warsmash/WarsmashGdxGame.java | 2 +- .../etheller/warsmash/WarsmashGdxMapGame.java | 11 +- .../warsmash/WarsmashGdxMenuTestGame.java | 4 +- .../warsmash/WarsmashPreviewApplication.java | 5 +- .../warsmash/WarsmashTestGameAttributes2.java | 11 +- .../warsmash/WarsmashTestMyTextureGame.java | 11 +- .../warsmash/datasources/CascDataSource.java | 225 ++ .../datasources/CascDataSourceDescriptor.java | 96 + .../datasources/CompoundDataSource.java | 34 +- .../warsmash/datasources/DataSource.java | 3 + .../datasources/FolderDataSource.java | 22 +- .../warsmash/datasources/MpqDataSource.java | 23 + .../datasources/MpqDataSourceDescriptor.java | 3 + .../datasources/SubdirDataSource.java | 6 + .../etheller/warsmash/parsers/fdf/GameUI.java | 4 +- .../warsmash/parsers/fdf/ModelExport.java | 7 +- .../fdf/frames/FilterModeTextureFrame.java | 2 +- .../fdf/frames/SmartBackdropFrame.java | 23 +- .../warsmash/parsers/mdlx/AnimationMap.java | 99 - .../warsmash/parsers/mdlx/Attachment.java | 107 - .../etheller/warsmash/parsers/mdlx/Bone.java | 95 - .../warsmash/parsers/mdlx/Camera.java | 152 - .../etheller/warsmash/parsers/mdlx/Chunk.java | 5 - .../warsmash/parsers/mdlx/Extent.java | 55 - .../warsmash/parsers/mdlx/GenericObject.java | 350 --- .../warsmash/parsers/mdlx/Geoset.java | 428 --- .../parsers/mdlx/GeosetAnimation.java | 114 - .../warsmash/parsers/mdlx/Helper.java | 29 - .../parsers/mdlx/InterpolationType.java | 11 - .../etheller/warsmash/parsers/mdlx/Layer.java | 244 -- .../warsmash/parsers/mdlx/Material.java | 141 - .../parsers/mdlx/MdlTokenInputStream.java | 35 - .../parsers/mdlx/MdlTokenOutputStream.java | 59 - .../warsmash/parsers/mdlx/MdlxBlock.java | 16 - .../parsers/mdlx/MdlxBlockDescriptor.java | 125 - .../warsmash/parsers/mdlx/MdlxModel.java | 744 ----- .../warsmash/parsers/mdlx/MdlxTest.java | 64 - .../warsmash/parsers/mdlx/Sequence.java | 170 -- .../warsmash/parsers/mdlx/Texture.java | 105 - .../parsers/mdlx/TextureAnimation.java | 57 - .../parsers/mdlx/TimelineDescriptor.java | 38 - .../warsmash/parsers/mdlx/UnknownChunk.java | 35 - .../mdlx/timeline/FloatArrayTimeline.java | 49 - .../parsers/mdlx/timeline/FloatTimeline.java | 37 - .../parsers/mdlx/timeline/UInt32Timeline.java | 38 - .../warsmash/parsers/w3x/War3Map.java | 6 + .../warsmash/units/StandardObjectData.java | 9 + .../warsmash/util/DataSourceFileHandle.java | 12 +- .../etheller/warsmash/util/ImageUtils.java | 79 +- .../warsmash/viewer5/ModelViewer.java | 12 +- .../viewer5/RawOpenGLTextureResource.java | 6 +- .../viewer5/handlers/blp/BlpGdxTexture.java | 2 +- .../viewer5/handlers/blp/BlpHandler.java | 2 +- .../viewer5/handlers/blp/BlpTexture.java | 2 +- .../viewer5/handlers/blp/DdsHandler.java | 28 + .../viewer5/handlers/blp/DdsTexture.java | 38 + .../viewer5/handlers/mdx/AnimatedObject.java | 27 +- .../viewer5/handlers/mdx/Attachment.java | 6 +- .../warsmash/viewer5/handlers/mdx/Bone.java | 4 +- .../warsmash/viewer5/handlers/mdx/Camera.java | 5 +- .../viewer5/handlers/mdx/CollisionShape.java | 4 +- .../mdx/EventObjectEmitterObject.java | 8 +- .../viewer5/handlers/mdx/FilterMode.java | 7 +- .../viewer5/handlers/mdx/GenericObject.java | 6 +- .../warsmash/viewer5/handlers/mdx/Geoset.java | 5 +- .../viewer5/handlers/mdx/GeosetAnimation.java | 6 +- .../warsmash/viewer5/handlers/mdx/Helper.java | 5 +- .../warsmash/viewer5/handlers/mdx/Layer.java | 12 +- .../warsmash/viewer5/handlers/mdx/Light.java | 11 +- .../handlers/mdx/MdxComplexInstance.java | 4 +- .../viewer5/handlers/mdx/MdxHandler.java | 7 +- .../viewer5/handlers/mdx/MdxModel.java | 72 +- .../handlers/mdx/ParticleEmitter2Object.java | 13 +- .../handlers/mdx/ParticleEmitterObject.java | 6 +- .../viewer5/handlers/mdx/QuaternionSd.java | 4 +- .../handlers/mdx/RibbonEmitterObject.java | 6 +- .../viewer5/handlers/mdx/ScalarSd.java | 4 +- .../warsmash/viewer5/handlers/mdx/Sd.java | 5 +- .../viewer5/handlers/mdx/SdSequence.java | 14 +- .../viewer5/handlers/mdx/Sequence.java | 78 + .../viewer5/handlers/mdx/SetupGeosets.java | 8 +- .../handlers/mdx/TextureAnimation.java | 6 +- .../viewer5/handlers/mdx/UInt32Sd.java | 4 +- .../viewer5/handlers/mdx/VectorSd.java | 4 +- .../viewer5/handlers/tga/TgaTexture.java | 2 +- .../viewer5/handlers/w3x/IndexedSequence.java | 2 +- .../viewer5/handlers/w3x/SequenceUtils.java | 2 +- .../viewer5/handlers/w3x/UnitAckSound.java | 113 - .../viewer5/handlers/w3x/UnitSound.java | 9 +- .../handlers/w3x/environment/CliffMesh.java | 12 +- .../w3x/environment/GroundTexture.java | 32 +- .../handlers/w3x/environment/Terrain.java | 73 +- .../w3x/rendersim/RenderAttackInstant.java | 2 +- .../w3x/rendersim/RenderAttackProjectile.java | 2 +- .../handlers/w3x/rendersim/RenderUnit.java | 2 +- .../viewer5/handlers/w3x/ui/MeleeUI.java | 17 +- .../src/com/hiveworkshop/ReteraCASCUtils.java | 53 + .../blizzard/casc/ConfigurationFile.java | 118 + .../com/hiveworkshop/blizzard/casc/Key.java | 78 + .../blizzard/casc/StorageReference.java | 80 + .../blizzard/casc/info/FieldDataType.java | 25 + .../blizzard/casc/info/FieldDescriptor.java | 65 + .../hiveworkshop/blizzard/casc/info/Info.java | 149 + .../blizzard/casc/io/WarcraftIIICASC.java | 291 ++ .../blizzard/casc/io/package-info.java | 6 + .../casc/nio/HashMismatchException.java | 15 + .../casc/nio/LittleHashBlockProcessor.java | 76 + .../nio/MalformedCASCStructureException.java | 15 + .../blizzard/casc/storage/BLTEContent.java | 132 + .../blizzard/casc/storage/BankStream.java | 185 ++ .../blizzard/casc/storage/IndexEntry.java | 60 + .../blizzard/casc/storage/IndexFile.java | 159 + .../blizzard/casc/storage/Storage.java | 334 +++ .../casc/storage/StorageContainer.java | 92 + .../blizzard/casc/trash/LocalDataFiles.java | 343 +++ .../blizzard/casc/trash/LocalIndexFile.java | 171 ++ .../casc/trash/VirtualFileSystem.java | 86 + .../blizzard/casc/vfs/FileNode.java | 24 + .../blizzard/casc/vfs/PathNode.java | 27 + .../blizzard/casc/vfs/PrefixNode.java | 26 + .../blizzard/casc/vfs/StorageReference.java | 81 + .../blizzard/casc/vfs/TVFSDecoder.java | 253 ++ .../blizzard/casc/vfs/TVFSFile.java | 54 + .../blizzard/casc/vfs/VirtualFileSystem.java | 681 +++++ core/src/com/hiveworkshop/json/JSONArray.java | 1458 ++++++++++ .../com/hiveworkshop/json/JSONException.java | 45 + .../src/com/hiveworkshop/json/JSONObject.java | 2551 +++++++++++++++++ .../com/hiveworkshop/json/JSONPointer.java | 293 ++ .../json/JSONPointerException.java | 45 + .../hiveworkshop/json/JSONPropertyIgnore.java | 43 + .../hiveworkshop/json/JSONPropertyName.java | 47 + .../src/com/hiveworkshop/json/JSONString.java | 18 + .../com/hiveworkshop/json/JSONStringer.java | 79 + .../com/hiveworkshop/json/JSONTokener.java | 531 ++++ .../src/com/hiveworkshop/json/JSONWriter.java | 413 +++ core/src/com/hiveworkshop/lang/Hex.java | 82 + .../nio/ByteBufferInputStream.java | 39 + .../rms/parsers/mdlx/AnimationMap.java | 266 ++ .../rms/parsers/mdlx/InterpolationType.java | 26 + .../rms/parsers/mdlx/MdlxAnimatedObject.java} | 64 +- .../rms/parsers/mdlx/MdlxAttachment.java | 105 + .../rms/parsers/mdlx/MdlxBlock.java | 16 + .../rms/parsers/mdlx/MdlxBlockDescriptor.java | 44 + .../rms/parsers/mdlx/MdlxBone.java | 104 + .../rms/parsers/mdlx/MdlxCamera.java | 160 ++ .../rms/parsers/mdlx/MdlxChunk.java | 5 + .../rms/parsers/mdlx/MdlxCollisionShape.java} | 92 +- .../rms/parsers/mdlx/MdlxEventObject.java} | 71 +- .../rms/parsers/mdlx/MdlxExtent.java | 62 + .../rms/parsers/mdlx/MdlxFaceEffect.java | 45 + .../rms/parsers/mdlx/MdlxGenericObject.java | 278 ++ .../rms/parsers/mdlx/MdlxGeoset.java | 592 ++++ .../rms/parsers/mdlx/MdlxGeosetAnimation.java | 136 + .../rms/parsers/mdlx/MdlxHelper.java | 28 + .../rms/parsers/mdlx/MdlxLayer.java | 369 +++ .../rms/parsers/mdlx/MdlxLight.java} | 143 +- .../rms/parsers/mdlx/MdlxMaterial.java | 173 ++ .../rms/parsers/mdlx/MdlxModel.java | 967 +++++++ .../parsers/mdlx/MdlxParticleEmitter.java} | 141 +- .../parsers/mdlx/MdlxParticleEmitter2.java} | 396 ++- .../mdlx/MdlxParticleEmitterPopcorn.java | 180 ++ .../rms/parsers/mdlx/MdlxRibbonEmitter.java} | 153 +- .../rms/parsers/mdlx/MdlxSequence.java | 121 + .../rms/parsers/mdlx/MdlxTexture.java | 120 + .../parsers/mdlx/MdlxTextureAnimation.java | 56 + .../parsers/mdlx/MdlxTimelineDescriptor.java | 18 + .../rms/parsers/mdlx/MdlxUnknownChunk.java | 31 + .../mdlx/mdl/MdlTokenInputStream.java} | 122 +- .../mdlx/mdl/MdlTokenOutputStream.java} | 121 +- .../rms/parsers/mdlx/mdl/MdlUtils.java | 204 ++ .../mdlx/timeline/MdlxFloatArrayTimeline.java | 45 + .../mdlx/timeline/MdlxFloatTimeline.java | 33 + .../parsers/mdlx/timeline/MdlxTimeline.java} | 134 +- .../mdlx/timeline/MdlxUInt32Timeline.java | 34 + .../rms/parsers/mdlx/util/MdxUtils.java | 32 + .../hiveworkshop/rms/util/BinaryReader.java | 216 ++ .../hiveworkshop/rms/util/BinaryWriter.java | 140 + .../com/hiveworkshop/rms/util/Descriptor.java | 6 + .../src/com/etheller/warsmash/audio/Flac.java | 186 ++ .../etheller/warsmash/audio/OpenALAudio.java | 2 + .../src/com/etheller/warsmash/audio/Wav.java | 13 +- .../mdx/listeners/YseraGUIListener.java | 2 +- .../mdx/ui/AnimationControllerFrame.java | 2 +- .../mdx/ui/AnimationControllerPanel.java | 12 +- .../desktop/editor/mdx/ui/YseraPanel.java | 2 +- .../io/nayuki/flac/app/DecodeFlacToWav.java | 141 + .../io/nayuki/flac/app/EncodeWavToFlac.java | 176 ++ .../flac/app/SeekableFlacPlayerGui.java | 236 ++ .../io/nayuki/flac/app/ShowFlacFileStats.java | 277 ++ .../src/io/nayuki/flac/common/FrameInfo.java | 456 +++ .../src/io/nayuki/flac/common/SeekTable.java | 204 ++ .../src/io/nayuki/flac/common/StreamInfo.java | 310 ++ .../decode/AbstractFlacLowLevelInput.java | 354 +++ .../flac/decode/ByteArrayFlacInput.java | 86 + .../flac/decode/DataFormatException.java | 47 + .../io/nayuki/flac/decode/FlacDecoder.java | 337 +++ .../nayuki/flac/decode/FlacLowLevelInput.java | 129 + .../io/nayuki/flac/decode/FrameDecoder.java | 387 +++ .../flac/decode/SeekableFileFlacInput.java | 83 + .../flac/encode/AdvancedFlacEncoder.java | 115 + .../nayuki/flac/encode/BitOutputStream.java | 174 ++ .../nayuki/flac/encode/ConstantEncoder.java | 79 + .../io/nayuki/flac/encode/FastDotProduct.java | 97 + .../flac/encode/FixedPredictionEncoder.java | 85 + .../io/nayuki/flac/encode/FlacEncoder.java | 67 + .../io/nayuki/flac/encode/FrameEncoder.java | 174 ++ .../flac/encode/LinearPredictiveEncoder.java | 277 ++ .../encode/RandomAccessFileOutputStream.java | 81 + .../io/nayuki/flac/encode/RiceEncoder.java | 216 ++ .../io/nayuki/flac/encode/SizeEstimate.java | 61 + .../nayuki/flac/encode/SubframeEncoder.java | 247 ++ .../nayuki/flac/encode/VerbatimEncoder.java | 58 + 214 files changed, 20804 insertions(+), 4491 deletions(-) create mode 100644 core/assets/warsmashRF.ini create mode 100644 core/src/com/etheller/warsmash/datasources/CascDataSource.java create mode 100644 core/src/com/etheller/warsmash/datasources/CascDataSourceDescriptor.java delete mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/AnimationMap.java delete mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/Attachment.java delete mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/Bone.java delete mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/Camera.java delete mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/Chunk.java delete mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/Extent.java delete mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/GenericObject.java delete mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/Geoset.java delete mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/GeosetAnimation.java delete mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/Helper.java delete mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/InterpolationType.java delete mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/Layer.java delete mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/Material.java delete mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/MdlTokenInputStream.java delete mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/MdlTokenOutputStream.java delete mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/MdlxBlock.java delete mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/MdlxBlockDescriptor.java delete mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/MdlxModel.java delete mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/MdlxTest.java delete mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/Sequence.java delete mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/Texture.java delete mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/TextureAnimation.java delete mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/TimelineDescriptor.java delete mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/UnknownChunk.java delete mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatArrayTimeline.java delete mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatTimeline.java delete mode 100644 core/src/com/etheller/warsmash/parsers/mdlx/timeline/UInt32Timeline.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/blp/DdsHandler.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/blp/DdsTexture.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/mdx/Sequence.java delete mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitAckSound.java create mode 100644 core/src/com/hiveworkshop/ReteraCASCUtils.java create mode 100644 core/src/com/hiveworkshop/blizzard/casc/ConfigurationFile.java create mode 100644 core/src/com/hiveworkshop/blizzard/casc/Key.java create mode 100644 core/src/com/hiveworkshop/blizzard/casc/StorageReference.java create mode 100644 core/src/com/hiveworkshop/blizzard/casc/info/FieldDataType.java create mode 100644 core/src/com/hiveworkshop/blizzard/casc/info/FieldDescriptor.java create mode 100644 core/src/com/hiveworkshop/blizzard/casc/info/Info.java create mode 100644 core/src/com/hiveworkshop/blizzard/casc/io/WarcraftIIICASC.java create mode 100644 core/src/com/hiveworkshop/blizzard/casc/io/package-info.java create mode 100644 core/src/com/hiveworkshop/blizzard/casc/nio/HashMismatchException.java create mode 100644 core/src/com/hiveworkshop/blizzard/casc/nio/LittleHashBlockProcessor.java create mode 100644 core/src/com/hiveworkshop/blizzard/casc/nio/MalformedCASCStructureException.java create mode 100644 core/src/com/hiveworkshop/blizzard/casc/storage/BLTEContent.java create mode 100644 core/src/com/hiveworkshop/blizzard/casc/storage/BankStream.java create mode 100644 core/src/com/hiveworkshop/blizzard/casc/storage/IndexEntry.java create mode 100644 core/src/com/hiveworkshop/blizzard/casc/storage/IndexFile.java create mode 100644 core/src/com/hiveworkshop/blizzard/casc/storage/Storage.java create mode 100644 core/src/com/hiveworkshop/blizzard/casc/storage/StorageContainer.java create mode 100644 core/src/com/hiveworkshop/blizzard/casc/trash/LocalDataFiles.java create mode 100644 core/src/com/hiveworkshop/blizzard/casc/trash/LocalIndexFile.java create mode 100644 core/src/com/hiveworkshop/blizzard/casc/trash/VirtualFileSystem.java create mode 100644 core/src/com/hiveworkshop/blizzard/casc/vfs/FileNode.java create mode 100644 core/src/com/hiveworkshop/blizzard/casc/vfs/PathNode.java create mode 100644 core/src/com/hiveworkshop/blizzard/casc/vfs/PrefixNode.java create mode 100644 core/src/com/hiveworkshop/blizzard/casc/vfs/StorageReference.java create mode 100644 core/src/com/hiveworkshop/blizzard/casc/vfs/TVFSDecoder.java create mode 100644 core/src/com/hiveworkshop/blizzard/casc/vfs/TVFSFile.java create mode 100644 core/src/com/hiveworkshop/blizzard/casc/vfs/VirtualFileSystem.java create mode 100644 core/src/com/hiveworkshop/json/JSONArray.java create mode 100644 core/src/com/hiveworkshop/json/JSONException.java create mode 100644 core/src/com/hiveworkshop/json/JSONObject.java create mode 100644 core/src/com/hiveworkshop/json/JSONPointer.java create mode 100644 core/src/com/hiveworkshop/json/JSONPointerException.java create mode 100644 core/src/com/hiveworkshop/json/JSONPropertyIgnore.java create mode 100644 core/src/com/hiveworkshop/json/JSONPropertyName.java create mode 100644 core/src/com/hiveworkshop/json/JSONString.java create mode 100644 core/src/com/hiveworkshop/json/JSONStringer.java create mode 100644 core/src/com/hiveworkshop/json/JSONTokener.java create mode 100644 core/src/com/hiveworkshop/json/JSONWriter.java create mode 100644 core/src/com/hiveworkshop/lang/Hex.java create mode 100644 core/src/com/hiveworkshop/nio/ByteBufferInputStream.java create mode 100644 core/src/com/hiveworkshop/rms/parsers/mdlx/AnimationMap.java create mode 100644 core/src/com/hiveworkshop/rms/parsers/mdlx/InterpolationType.java rename core/src/com/{etheller/warsmash/parsers/mdlx/AnimatedObject.java => hiveworkshop/rms/parsers/mdlx/MdlxAnimatedObject.java} (50%) create mode 100644 core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxAttachment.java create mode 100644 core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxBlock.java create mode 100644 core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxBlockDescriptor.java create mode 100644 core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxBone.java create mode 100644 core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxCamera.java create mode 100644 core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxChunk.java rename core/src/com/{etheller/warsmash/parsers/mdlx/CollisionShape.java => hiveworkshop/rms/parsers/mdlx/MdlxCollisionShape.java} (58%) rename core/src/com/{etheller/warsmash/parsers/mdlx/EventObject.java => hiveworkshop/rms/parsers/mdlx/MdlxEventObject.java} (51%) create mode 100644 core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxExtent.java create mode 100644 core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxFaceEffect.java create mode 100644 core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxGenericObject.java create mode 100644 core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxGeoset.java create mode 100644 core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxGeosetAnimation.java create mode 100644 core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxHelper.java create mode 100644 core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxLayer.java rename core/src/com/{etheller/warsmash/parsers/mdlx/Light.java => hiveworkshop/rms/parsers/mdlx/MdlxLight.java} (50%) create mode 100644 core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxMaterial.java create mode 100644 core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxModel.java rename core/src/com/{etheller/warsmash/parsers/mdlx/ParticleEmitter.java => hiveworkshop/rms/parsers/mdlx/MdlxParticleEmitter.java} (53%) rename core/src/com/{etheller/warsmash/parsers/mdlx/ParticleEmitter2.java => hiveworkshop/rms/parsers/mdlx/MdlxParticleEmitter2.java} (52%) create mode 100644 core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxParticleEmitterPopcorn.java rename core/src/com/{etheller/warsmash/parsers/mdlx/RibbonEmitter.java => hiveworkshop/rms/parsers/mdlx/MdlxRibbonEmitter.java} (52%) create mode 100644 core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxSequence.java create mode 100644 core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxTexture.java create mode 100644 core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxTextureAnimation.java create mode 100644 core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxTimelineDescriptor.java create mode 100644 core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxUnknownChunk.java rename core/src/com/{etheller/warsmash/parsers/mdlx/mdl/GhostwolfTokenInputStream.java => hiveworkshop/rms/parsers/mdlx/mdl/MdlTokenInputStream.java} (59%) rename core/src/com/{etheller/warsmash/parsers/mdlx/mdl/GhostwolfTokenOutputStream.java => hiveworkshop/rms/parsers/mdlx/mdl/MdlTokenOutputStream.java} (64%) create mode 100644 core/src/com/hiveworkshop/rms/parsers/mdlx/mdl/MdlUtils.java create mode 100644 core/src/com/hiveworkshop/rms/parsers/mdlx/timeline/MdlxFloatArrayTimeline.java create mode 100644 core/src/com/hiveworkshop/rms/parsers/mdlx/timeline/MdlxFloatTimeline.java rename core/src/com/{etheller/warsmash/parsers/mdlx/timeline/Timeline.java => hiveworkshop/rms/parsers/mdlx/timeline/MdlxTimeline.java} (55%) create mode 100644 core/src/com/hiveworkshop/rms/parsers/mdlx/timeline/MdlxUInt32Timeline.java create mode 100644 core/src/com/hiveworkshop/rms/parsers/mdlx/util/MdxUtils.java create mode 100644 core/src/com/hiveworkshop/rms/util/BinaryReader.java create mode 100644 core/src/com/hiveworkshop/rms/util/BinaryWriter.java create mode 100644 core/src/com/hiveworkshop/rms/util/Descriptor.java create mode 100644 desktop/src/com/etheller/warsmash/audio/Flac.java create mode 100644 desktop/src/io/nayuki/flac/app/DecodeFlacToWav.java create mode 100644 desktop/src/io/nayuki/flac/app/EncodeWavToFlac.java create mode 100644 desktop/src/io/nayuki/flac/app/SeekableFlacPlayerGui.java create mode 100644 desktop/src/io/nayuki/flac/app/ShowFlacFileStats.java create mode 100644 desktop/src/io/nayuki/flac/common/FrameInfo.java create mode 100644 desktop/src/io/nayuki/flac/common/SeekTable.java create mode 100644 desktop/src/io/nayuki/flac/common/StreamInfo.java create mode 100644 desktop/src/io/nayuki/flac/decode/AbstractFlacLowLevelInput.java create mode 100644 desktop/src/io/nayuki/flac/decode/ByteArrayFlacInput.java create mode 100644 desktop/src/io/nayuki/flac/decode/DataFormatException.java create mode 100644 desktop/src/io/nayuki/flac/decode/FlacDecoder.java create mode 100644 desktop/src/io/nayuki/flac/decode/FlacLowLevelInput.java create mode 100644 desktop/src/io/nayuki/flac/decode/FrameDecoder.java create mode 100644 desktop/src/io/nayuki/flac/decode/SeekableFileFlacInput.java create mode 100644 desktop/src/io/nayuki/flac/encode/AdvancedFlacEncoder.java create mode 100644 desktop/src/io/nayuki/flac/encode/BitOutputStream.java create mode 100644 desktop/src/io/nayuki/flac/encode/ConstantEncoder.java create mode 100644 desktop/src/io/nayuki/flac/encode/FastDotProduct.java create mode 100644 desktop/src/io/nayuki/flac/encode/FixedPredictionEncoder.java create mode 100644 desktop/src/io/nayuki/flac/encode/FlacEncoder.java create mode 100644 desktop/src/io/nayuki/flac/encode/FrameEncoder.java create mode 100644 desktop/src/io/nayuki/flac/encode/LinearPredictiveEncoder.java create mode 100644 desktop/src/io/nayuki/flac/encode/RandomAccessFileOutputStream.java create mode 100644 desktop/src/io/nayuki/flac/encode/RiceEncoder.java create mode 100644 desktop/src/io/nayuki/flac/encode/SizeEstimate.java create mode 100644 desktop/src/io/nayuki/flac/encode/SubframeEncoder.java create mode 100644 desktop/src/io/nayuki/flac/encode/VerbatimEncoder.java diff --git a/build.gradle b/build.gradle index 30c58f8..3e9b05e 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,4 @@ buildscript { - - repositories { mavenLocal() flatDir { @@ -9,13 +7,10 @@ buildscript { mavenCentral() maven { url "https://plugins.gradle.org/m2/" } maven { url "https://oss.sonatype.org/content/repositories/snapshots/" } + maven { url "https://maven.nikr.net/" } jcenter() google() } - dependencies { - - - } } allprojects { @@ -39,6 +34,7 @@ allprojects { google() maven { url "https://oss.sonatype.org/content/repositories/snapshots/" } maven { url "https://oss.sonatype.org/content/repositories/releases/" } + maven { url "https://maven.nikr.net/" } maven { url 'https://jitpack.io' } } } @@ -54,6 +50,7 @@ project(":desktop") { compile "com.badlogicgames.gdx:gdx-box2d-platform:$gdxVersion:natives-desktop" compile "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-desktop" compile "com.google.guava:guava:23.5-jre" + compile "net.nikr:dds:1.0.0" implementation 'com.github.inwc3:wc3libs:-SNAPSHOT' compile files(fileTree(dir:'../jars', includes: ['*.jar'])) @@ -71,6 +68,7 @@ project(":core") { compile "com.badlogicgames.gdx:gdx-box2d:$gdxVersion" compile "com.badlogicgames.gdx:gdx-freetype:$gdxVersion" compile "com.google.guava:guava:23.5-jre" + compile "net.nikr:dds:1.0.0" implementation 'com.github.inwc3:wc3libs:-SNAPSHOT' compile files(fileTree(dir:'../jars', includes: ['*.jar'])) diff --git a/core/assets/warsmashRF.ini b/core/assets/warsmashRF.ini new file mode 100644 index 0000000..c736fbc --- /dev/null +++ b/core/assets/warsmashRF.ini @@ -0,0 +1,41 @@ +[DataSources] +Count=5 +Type00=CASC +Path00="C:\Program Files\Warcraft III" +Prefixes00=war3.w3mod,war3.w3mod\_deprecated.w3mod,war3.w3mod\_locales\enus.w3mod +Type01=Folder +Path01="..\..\resources" +Type02=Folder +Path02="D:\Backups\Warsmash\Data" +Type03=Folder +Path03="D:\Games\Warcraft III Patch 1.22\Maps" +Type04=Folder +Path04="." + +[Map] +//FilePath="CombatUnitTests.w3x" +//FilePath="PitchRoll.w3x" +FilePath="PeonStartingBase.w3x" +//FilePath="MyStromguarde.w3m" +//FilePath="ColdArrows.w3m" +//FilePath="DungeonGoldMine.w3m" +//FilePath="PlayerPeasants.w3m" +//FilePath="FireLord.w3x" +//FilePath="Maps\Campaign\NightElf03.w3m" +//FilePath="PhoenixAttack.w3x" +//FilePath="LightEnvironmentTest.w3x" +//FilePath="TorchLight2.w3x" +//FilePath="OrcAssault.w3x" +//FilePath="FrostyVsFarm.w3m" +//FilePath="ModelTest.w3x" +//FilePath="SpinningSample.w3x" +//FilePath="Maps\Campaign\Prologue02.w3m" +//FilePath="Pathing.w3x" +//FilePath="ItemFacing.w3x" +//FilePath=SomeParticleTests.w3x +//FilePath="PeonMiningMultiHall.w3x" +//FilePath="QuadtreeBugs.w3x" +//FilePath="test2.w3x" +//FilePath="FarseerHoldPositionTest.w3x" +//FilePath="Ramps.w3m" +//FilePath="V1\Farm.w3x" diff --git a/core/src/com/etheller/warsmash/WarsmashGdxGame.java b/core/src/com/etheller/warsmash/WarsmashGdxGame.java index d3f6a4b..c5bb2f9 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxGame.java @@ -14,7 +14,6 @@ import com.badlogic.gdx.math.Quaternion; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Vector3; import com.etheller.warsmash.datasources.DataSource; -import com.etheller.warsmash.parsers.mdlx.Sequence; import com.etheller.warsmash.units.DataTable; import com.etheller.warsmash.util.DataSourceFileHandle; import com.etheller.warsmash.viewer5.Camera; @@ -27,6 +26,7 @@ import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxHandler; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; import com.etheller.warsmash.viewer5.handlers.mdx.MdxViewer; +import com.etheller.warsmash.viewer5.handlers.mdx.Sequence; import com.etheller.warsmash.viewer5.handlers.mdx.SequenceLoopMode; public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvider { diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java index 2a9846f..e5963d0 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java @@ -6,6 +6,7 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.IntBuffer; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import com.badlogic.gdx.ApplicationAdapter; @@ -26,6 +27,7 @@ import com.badlogic.gdx.graphics.glutils.ShapeRenderer; import com.badlogic.gdx.math.Matrix4; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.utils.viewport.ExtendViewport; +import com.etheller.warsmash.datasources.CascDataSourceDescriptor; import com.etheller.warsmash.datasources.CompoundDataSourceDescriptor; import com.etheller.warsmash.datasources.DataSource; import com.etheller.warsmash.datasources.DataSourceDescriptor; @@ -56,7 +58,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.SettableCommandErro public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProvider, InputProcessor { private static final boolean ENABLE_AUDIO = true; - private static final boolean ENABLE_MUSIC = false; + private static final boolean ENABLE_MUSIC = true; private DataSource codebase; private War3MapViewer viewer; private final Rectangle tempRect = new Rectangle(); @@ -186,7 +188,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv // this.consoleUITexture = new Texture(new DataSourceFileHandle(this.viewer.dataSource, "AlphaUi.png")); - this.solidGreenTexture = ImageUtils.getBLPTexture(this.viewer.dataSource, + this.solidGreenTexture = ImageUtils.getAnyExtensionTexture(this.viewer.dataSource, "ReplaceableTextures\\TeamColor\\TeamColor06.blp"); Gdx.input.setInputProcessor(this); @@ -260,6 +262,11 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv dataSourcesList.add(new MpqDataSourceDescriptor(path)); break; } + case "CASC": { + final String prefixes = dataSourcesConfig.getField("Prefixes" + (i < 10 ? "0" : "") + i); + dataSourcesList.add(new CascDataSourceDescriptor(path, Arrays.asList(prefixes.split(",")))); + break; + } default: throw new RuntimeException("Unknown data source type: " + type); } diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMenuTestGame.java b/core/src/com/etheller/warsmash/WarsmashGdxMenuTestGame.java index d411c47..064f9ee 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMenuTestGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMenuTestGame.java @@ -27,7 +27,6 @@ import com.badlogic.gdx.utils.viewport.ExtendViewport; import com.etheller.warsmash.datasources.DataSource; import com.etheller.warsmash.parsers.fdf.GameUI; import com.etheller.warsmash.parsers.jass.Jass2.RootFrameListener; -import com.etheller.warsmash.parsers.mdlx.Sequence; import com.etheller.warsmash.units.DataTable; import com.etheller.warsmash.util.DataSourceFileHandle; import com.etheller.warsmash.util.ImageUtils; @@ -46,6 +45,7 @@ import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxHandler; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; import com.etheller.warsmash.viewer5.handlers.mdx.MdxViewer; +import com.etheller.warsmash.viewer5.handlers.mdx.Sequence; import com.etheller.warsmash.viewer5.handlers.mdx.SequenceLoopMode; import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; import com.etheller.warsmash.viewer5.handlers.w3x.ui.MenuUI; @@ -141,7 +141,7 @@ public class WarsmashGdxMenuTestGame extends ApplicationAdapter implements Canva // this.consoleUITexture = new Texture(new DataSourceFileHandle(this.viewer.dataSource, "AlphaUi.png")); - this.solidGreenTexture = ImageUtils.getBLPTexture(this.viewer.dataSource, + this.solidGreenTexture = ImageUtils.getAnyExtensionTexture(this.viewer.dataSource, "ReplaceableTextures\\TeamColor\\TeamColor06.blp"); Gdx.input.setInputProcessor(this); diff --git a/core/src/com/etheller/warsmash/WarsmashPreviewApplication.java b/core/src/com/etheller/warsmash/WarsmashPreviewApplication.java index 3809063..253cb39 100644 --- a/core/src/com/etheller/warsmash/WarsmashPreviewApplication.java +++ b/core/src/com/etheller/warsmash/WarsmashPreviewApplication.java @@ -14,7 +14,6 @@ import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.math.Rectangle; import com.etheller.warsmash.datasources.DataSource; -import com.etheller.warsmash.parsers.mdlx.MdlxModel; import com.etheller.warsmash.units.DataTable; import com.etheller.warsmash.viewer5.CanvasProvider; import com.etheller.warsmash.viewer5.ModelViewer; @@ -26,6 +25,8 @@ import com.etheller.warsmash.viewer5.handlers.mdx.MdxHandler; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; import com.etheller.warsmash.viewer5.handlers.mdx.MdxViewer; import com.etheller.warsmash.viewer5.handlers.w3x.camera.PortraitCameraManager; +import com.hiveworkshop.rms.parsers.mdlx.MdlxModel; +import com.hiveworkshop.rms.parsers.mdlx.util.MdxUtils; public class WarsmashPreviewApplication extends ApplicationAdapter implements CanvasProvider { private DataSource codebase; @@ -138,7 +139,7 @@ public class WarsmashPreviewApplication extends ApplicationAdapter implements Ca this.mdxHandler, ".mdx", PathSolver.DEFAULT, filename)); final MdlxModel mdlxModel; try (FileInputStream stream = new FileInputStream(filename)) { - mdlxModel = new MdlxModel(stream); + mdlxModel = MdxUtils.loadMdlx(stream); mdx.load(mdlxModel); mdx.ok = true; // mdx.lateLoad(); diff --git a/core/src/com/etheller/warsmash/WarsmashTestGameAttributes2.java b/core/src/com/etheller/warsmash/WarsmashTestGameAttributes2.java index b125319..bc65a6c 100644 --- a/core/src/com/etheller/warsmash/WarsmashTestGameAttributes2.java +++ b/core/src/com/etheller/warsmash/WarsmashTestGameAttributes2.java @@ -1,7 +1,6 @@ package com.etheller.warsmash; import java.io.IOException; -import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; @@ -17,8 +16,8 @@ import com.etheller.warsmash.datasources.CompoundDataSourceDescriptor; import com.etheller.warsmash.datasources.DataSource; import com.etheller.warsmash.datasources.DataSourceDescriptor; import com.etheller.warsmash.datasources.FolderDataSourceDescriptor; -import com.etheller.warsmash.parsers.mdlx.Geoset; -import com.etheller.warsmash.parsers.mdlx.MdlxModel; +import com.hiveworkshop.rms.parsers.mdlx.MdlxGeoset; +import com.hiveworkshop.rms.parsers.mdlx.MdlxModel; public class WarsmashTestGameAttributes2 extends ApplicationAdapter { private int arrayBuffer; @@ -36,8 +35,8 @@ public class WarsmashTestGameAttributes2 extends ApplicationAdapter { .createDataSource(); final MdlxModel model; - try (InputStream modelStream = this.codebase.getResourceAsStream("Buildings\\Other\\TempArtB\\TempArtB.mdx")) { - model = new MdlxModel(modelStream); + try { + model = new MdlxModel(this.codebase.read("Buildings\\Other\\TempArtB\\TempArtB.mdx")); } catch (final IOException e) { throw new RuntimeException(e); @@ -67,7 +66,7 @@ public class WarsmashTestGameAttributes2 extends ApplicationAdapter { Gdx.gl.glBindBuffer(GL20.GL_ELEMENT_ARRAY_BUFFER, this.elementBuffer); - final Geoset geoset0 = model.getGeosets().get(0); + final MdlxGeoset geoset0 = model.getGeosets().get(0); final float[] vertices = geoset0.getVertices(); final ByteBuffer vertexByteBuffer = ByteBuffer.allocateDirect(4 * 9); vertexByteBuffer.order(ByteOrder.LITTLE_ENDIAN); diff --git a/core/src/com/etheller/warsmash/WarsmashTestMyTextureGame.java b/core/src/com/etheller/warsmash/WarsmashTestMyTextureGame.java index b7079d2..edc1cec 100644 --- a/core/src/com/etheller/warsmash/WarsmashTestMyTextureGame.java +++ b/core/src/com/etheller/warsmash/WarsmashTestMyTextureGame.java @@ -1,10 +1,7 @@ package com.etheller.warsmash; -import java.io.IOException; import java.util.Arrays; -import javax.imageio.ImageIO; - import com.badlogic.gdx.ApplicationAdapter; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.GL20; @@ -32,13 +29,7 @@ public class WarsmashTestMyTextureGame extends ApplicationAdapter { this.codebase = new CompoundDataSourceDescriptor( Arrays.asList(war3mpq, testingFolder, currentFolder)).createDataSource(); - try { - this.texture = ImageUtils - .getTexture(ImageIO.read(this.codebase.getResourceAsStream("Textures\\Dust3x.blp"))); - } - catch (final IOException e) { - e.printStackTrace(); - } + this.texture = ImageUtils.getAnyExtensionTexture(this.codebase, "Textures\\Dust3x.blp"); Gdx.gl.glClearColor(0, 0, 0, 1); this.batch = new SpriteBatch(); this.batch.enableBlending(); diff --git a/core/src/com/etheller/warsmash/datasources/CascDataSource.java b/core/src/com/etheller/warsmash/datasources/CascDataSource.java new file mode 100644 index 0000000..5f92f16 --- /dev/null +++ b/core/src/com/etheller/warsmash/datasources/CascDataSource.java @@ -0,0 +1,225 @@ +package com.etheller.warsmash.datasources; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import com.hiveworkshop.blizzard.casc.io.WarcraftIIICASC; +import com.hiveworkshop.blizzard.casc.io.WarcraftIIICASC.FileSystem; +import com.hiveworkshop.json.JSONArray; +import com.hiveworkshop.json.JSONObject; +import com.hiveworkshop.json.JSONTokener; + +public class CascDataSource implements DataSource { + private final String[] prefixes; + private WarcraftIIICASC warcraftIIICASC; + private FileSystem rootFileSystem; + private List listFile; + private Map fileAliases; + + public CascDataSource(final String warcraft3InstallPath, final String[] prefixes) { + this.prefixes = prefixes; + for (int i = 0; i < (prefixes.length / 2); i++) { + final String temp = prefixes[i]; + prefixes[i] = prefixes[prefixes.length - i - 1]; + prefixes[prefixes.length - i - 1] = temp; + } + + try { + this.warcraftIIICASC = new WarcraftIIICASC(Paths.get(warcraft3InstallPath), true); + this.rootFileSystem = this.warcraftIIICASC.getRootFileSystem(); + this.listFile = this.rootFileSystem.enumerateFiles(); + this.fileAliases = new HashMap<>(); + if (this.has("filealiases.json")) { + try (InputStream stream = this.getResourceAsStream("filealiases.json")) { + stream.mark(4); + if ('\ufeff' != stream.read()) { + stream.reset(); // not the BOM marker + } + final JSONArray jsonObject = new JSONArray(new JSONTokener(stream)); + for (int i = 0; i < jsonObject.length(); i++) { + final JSONObject alias = jsonObject.getJSONObject(i); + final String src = alias.getString("src"); + final String dest = alias.getString("dest"); + this.fileAliases.put(src.toLowerCase(Locale.US).replace('/', '\\'), + dest.toLowerCase(Locale.US).replace('/', '\\')); + if ((src.toLowerCase(Locale.US).contains(".blp") + || dest.toLowerCase(Locale.US).contains(".blp")) + && (!alias.has("assetType") || "Texture".equals(alias.getString("assetType")))) { + // This case: I saw a texture that resolves in game but was failing in our code + // here, because of this entry: + // {"src":"Units/Human/WarWagon/SiegeEngine.blp", + // "dest":"Textures/Steamtank.blp", "assetType": "Texture"}, + // Our repo here checks BLP then DDS at a high-up application level thing, and + // the problem is that this entry is written using .BLP but we must be able to + // resolve .DDS when we go to look it up. The actual model is .BLP so maybe + // that's how the game does it, but my alias mapping is happening after the + // .BLP->.DDS dynamic fix, and not before. + this.fileAliases.put(src.toLowerCase(Locale.US).replace('/', '\\').replace(".blp", ".dds"), + dest.toLowerCase(Locale.US).replace('/', '\\').replace(".blp", ".dds")); + } + } + } + } + } + catch (final IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public InputStream getResourceAsStream(String filepath) { + filepath = filepath.toLowerCase(Locale.US).replace('/', '\\').replace(':', '\\'); + final String resolvedAlias = this.fileAliases.get(filepath); + if (resolvedAlias != null) { + filepath = resolvedAlias; + } + for (final String prefix : this.prefixes) { + final String tempFilepath = prefix + "\\" + filepath; + final InputStream stream = internalGetResourceAsStream(tempFilepath); + if (stream != null) { + return stream; + } + } + return internalGetResourceAsStream(filepath); + } + + private InputStream internalGetResourceAsStream(final String tempFilepath) { + try { + if (this.rootFileSystem.isFile(tempFilepath) && this.rootFileSystem.isFileAvailable(tempFilepath)) { + final ByteBuffer buffer = this.rootFileSystem.readFileData(tempFilepath); + if (buffer.hasArray()) { + return new ByteArrayInputStream(buffer.array()); + } + final byte[] data = new byte[buffer.remaining()]; + buffer.clear(); + buffer.get(data); + return new ByteArrayInputStream(data); + } + } + catch (final IOException e) { + throw new RuntimeException("CASC parser error for: " + tempFilepath, e); + } + return null; + } + + @Override + public ByteBuffer read(String path) { + path = path.toLowerCase(Locale.US).replace('/', '\\').replace(':', '\\'); + final String resolvedAlias = this.fileAliases.get(path); + if (resolvedAlias != null) { + path = resolvedAlias; + } + for (final String prefix : this.prefixes) { + final String tempFilepath = prefix + "\\" + path; + final ByteBuffer stream = internalRead(tempFilepath); + if (stream != null) { + return stream; + } + } + return internalRead(path); + } + + private ByteBuffer internalRead(final String tempFilepath) { + try { + if (this.rootFileSystem.isFile(tempFilepath) && this.rootFileSystem.isFileAvailable(tempFilepath)) { + final ByteBuffer buffer = this.rootFileSystem.readFileData(tempFilepath); + return buffer; + } + } + catch (final IOException e) { + throw new RuntimeException("CASC parser error for: " + tempFilepath, e); + } + return null; + } + + @Override + public File getFile(String filepath) { + filepath = filepath.toLowerCase(Locale.US).replace('/', '\\').replace(':', '\\'); + final String resolvedAlias = this.fileAliases.get(filepath); + if (resolvedAlias != null) { + filepath = resolvedAlias; + } + for (final String prefix : this.prefixes) { + final String tempFilepath = prefix + "\\" + filepath; + final File file = internalGetFile(tempFilepath); + if (file != null) { + return file; + } + } + return internalGetFile(filepath); + } + + private File internalGetFile(final String tempFilepath) { + try { + if (this.rootFileSystem.isFile(tempFilepath) && this.rootFileSystem.isFileAvailable(tempFilepath)) { + final ByteBuffer buffer = this.rootFileSystem.readFileData(tempFilepath); + String tmpdir = System.getProperty("java.io.tmpdir"); + if (!tmpdir.endsWith(File.separator)) { + tmpdir += File.separator; + } + final String tempDir = tmpdir + "MatrixEaterExtract/"; + final File tempProduct = new File(tempDir + tempFilepath.replace('\\', File.separatorChar)); + tempProduct.delete(); + tempProduct.getParentFile().mkdirs(); + try (final FileChannel fileChannel = FileChannel.open(tempProduct.toPath(), StandardOpenOption.CREATE, + StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.TRUNCATE_EXISTING)) { + fileChannel.write(buffer); + } + tempProduct.deleteOnExit(); + return tempProduct; + } + } + catch (final IOException e) { + throw new RuntimeException("CASC parser error for: " + tempFilepath, e); + } + return null; + } + + @Override + public boolean has(String filepath) { + filepath = filepath.toLowerCase(Locale.US).replace('/', '\\').replace(':', '\\'); + final String resolvedAlias = this.fileAliases.get(filepath); + if (resolvedAlias != null) { + filepath = resolvedAlias; + } + for (final String prefix : this.prefixes) { + final String tempFilepath = prefix + "\\" + filepath; + try { + if (this.rootFileSystem.isFile(tempFilepath)) { + return true; + } + } + catch (final IOException e) { + throw new RuntimeException("CASC parser error for: " + tempFilepath, e); + } + } + try { + return this.rootFileSystem.isFile(filepath); + } + catch (final IOException e) { + throw new RuntimeException("CASC parser error for: " + filepath, e); + } + } + + @Override + public Collection getListfile() { + return this.listFile; + } + + @Override + public void close() throws IOException { + this.warcraftIIICASC.close(); + } + +} diff --git a/core/src/com/etheller/warsmash/datasources/CascDataSourceDescriptor.java b/core/src/com/etheller/warsmash/datasources/CascDataSourceDescriptor.java new file mode 100644 index 0000000..4c590a4 --- /dev/null +++ b/core/src/com/etheller/warsmash/datasources/CascDataSourceDescriptor.java @@ -0,0 +1,96 @@ +package com.etheller.warsmash.datasources; + +import java.util.Collections; +import java.util.List; + +public class CascDataSourceDescriptor implements DataSourceDescriptor { + /** + * Generated serial id + */ + private static final long serialVersionUID = 832549098549298820L; + private final String gameInstallPath; + private final List prefixes; + + public CascDataSourceDescriptor(final String gameInstallPath, final List prefixes) { + this.gameInstallPath = gameInstallPath; + this.prefixes = prefixes; + } + + @Override + public DataSource createDataSource() { + return new CascDataSource(this.gameInstallPath, this.prefixes.toArray(new String[this.prefixes.size()])); + } + + @Override + public String getDisplayName() { + return "CASC: " + this.gameInstallPath; + } + + public void addPrefix(final String prefix) { + this.prefixes.add(prefix); + } + + public void deletePrefix(final int index) { + this.prefixes.remove(index); + } + + public void movePrefixUp(final int index) { + if (index > 0) { + Collections.swap(this.prefixes, index, index - 1); + } + } + + public void movePrefixDown(final int index) { + if (index < (this.prefixes.size() - 1)) { + Collections.swap(this.prefixes, index, index + 1); + } + } + + public String getGameInstallPath() { + return this.gameInstallPath; + } + + public List getPrefixes() { + return this.prefixes; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = (prime * result) + ((this.gameInstallPath == null) ? 0 : this.gameInstallPath.hashCode()); + result = (prime * result) + ((this.prefixes == null) ? 0 : this.prefixes.hashCode()); + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final CascDataSourceDescriptor other = (CascDataSourceDescriptor) obj; + if (this.gameInstallPath == null) { + if (other.gameInstallPath != null) { + return false; + } + } + else if (!this.gameInstallPath.equals(other.gameInstallPath)) { + return false; + } + if (this.prefixes == null) { + if (other.prefixes != null) { + return false; + } + } + else if (!this.prefixes.equals(other.prefixes)) { + return false; + } + return true; + } +} diff --git a/core/src/com/etheller/warsmash/datasources/CompoundDataSource.java b/core/src/com/etheller/warsmash/datasources/CompoundDataSource.java index 3a33343..c427302 100644 --- a/core/src/com/etheller/warsmash/datasources/CompoundDataSource.java +++ b/core/src/com/etheller/warsmash/datasources/CompoundDataSource.java @@ -3,12 +3,12 @@ package com.etheller.warsmash.datasources; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.Set; @@ -44,11 +44,23 @@ public class CompoundDataSource implements DataSource { // TODO Auto-generated catch block e.printStackTrace(); } - if (filepath.toLowerCase(Locale.US).endsWith(".blp")) { - return getFile(filepath.substring(0, filepath.lastIndexOf(".")) + ".dds"); + return null; + } + + @Override + public ByteBuffer read(final String path) throws IOException { + try { + for (int i = this.mpqList.size() - 1; i >= 0; i--) { + final DataSource mpq = this.mpqList.get(i); + final ByteBuffer buffer = mpq.read(path); + if (buffer != null) { + return buffer; + } + } } - if (filepath.toLowerCase(Locale.US).endsWith(".tif")) { - return getFile(filepath.substring(0, filepath.lastIndexOf(".")) + ".dds"); + catch (final IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); } return null; } @@ -68,12 +80,6 @@ public class CompoundDataSource implements DataSource { // TODO Auto-generated catch block e.printStackTrace(); } - if (filepath.toLowerCase(Locale.US).endsWith(".blp")) { - return getResourceAsStream(filepath.substring(0, filepath.lastIndexOf(".")) + ".dds"); - } - if (filepath.toLowerCase(Locale.US).endsWith(".tif")) { - return getResourceAsStream(filepath.substring(0, filepath.lastIndexOf(".")) + ".dds"); - } return null; } @@ -88,12 +94,6 @@ public class CompoundDataSource implements DataSource { return true; } } - if (filepath.toLowerCase(Locale.US).endsWith(".blp")) { - return has(filepath.substring(0, filepath.lastIndexOf(".")) + ".dds"); - } - if (filepath.toLowerCase(Locale.US).endsWith(".tif")) { - return has(filepath.substring(0, filepath.lastIndexOf(".")) + ".dds"); - } return false; } diff --git a/core/src/com/etheller/warsmash/datasources/DataSource.java b/core/src/com/etheller/warsmash/datasources/DataSource.java index 8a279d1..bb82b22 100644 --- a/core/src/com/etheller/warsmash/datasources/DataSource.java +++ b/core/src/com/etheller/warsmash/datasources/DataSource.java @@ -3,6 +3,7 @@ package com.etheller.warsmash.datasources; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.nio.ByteBuffer; import java.util.Collection; public interface DataSource { @@ -27,6 +28,8 @@ public interface DataSource { */ File getFile(String filepath) throws IOException; + ByteBuffer read(String path) throws IOException; + /** * Returns true if the data source contains a valid entry for a particular file. * Some data sources (MPQs) may contain files for which this returns true, even diff --git a/core/src/com/etheller/warsmash/datasources/FolderDataSource.java b/core/src/com/etheller/warsmash/datasources/FolderDataSource.java index b7eb8fc..d429cdd 100644 --- a/core/src/com/etheller/warsmash/datasources/FolderDataSource.java +++ b/core/src/com/etheller/warsmash/datasources/FolderDataSource.java @@ -3,8 +3,10 @@ package com.etheller.warsmash.datasources; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.nio.ByteBuffer; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import java.util.Collection; import java.util.HashSet; @@ -34,7 +36,7 @@ public class FolderDataSource implements DataSource { @Override public InputStream getResourceAsStream(String filepath) throws IOException { - filepath=fixFilepath(filepath); + filepath = fixFilepath(filepath); if (!has(filepath)) { return null; } @@ -43,16 +45,25 @@ public class FolderDataSource implements DataSource { @Override public File getFile(String filepath) throws IOException { - filepath=fixFilepath(filepath); + filepath = fixFilepath(filepath); if (!has(filepath)) { return null; } return new File(this.folderPath.toString() + File.separatorChar + filepath); } + @Override + public ByteBuffer read(String path) throws IOException { + path = fixFilepath(path); + if (!has(path)) { + return null; + } + return ByteBuffer.wrap(Files.readAllBytes(Paths.get(path))); + } + @Override public boolean has(String filepath) { - filepath=fixFilepath(filepath); + filepath = fixFilepath(filepath); if ("".equals(filepath)) { return false; // special case for folder data source, dont do this } @@ -69,7 +80,8 @@ public class FolderDataSource implements DataSource { public void close() { } - private static String fixFilepath(String filepath) { - return filepath.replace('\\', File.separatorChar).replace('/', File.separatorChar); + private static String fixFilepath(final String filepath) { + return filepath.replace('\\', File.separatorChar).replace('/', File.separatorChar).replace(':', + File.separatorChar); } } diff --git a/core/src/com/etheller/warsmash/datasources/MpqDataSource.java b/core/src/com/etheller/warsmash/datasources/MpqDataSource.java index 2d168d0..9bdfbbd 100644 --- a/core/src/com/etheller/warsmash/datasources/MpqDataSource.java +++ b/core/src/com/etheller/warsmash/datasources/MpqDataSource.java @@ -5,6 +5,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.nio.ByteBuffer; import java.nio.channels.Channels; import java.nio.channels.SeekableByteChannel; import java.nio.file.Files; @@ -57,6 +58,28 @@ public class MpqDataSource implements DataSource { return newInputStream; } + @Override + public ByteBuffer read(final String path) throws IOException { + ArchivedFile file = null; + try { + file = this.archive.lookupHash2(new HashLookup(path)); + } + catch (final MPQException exc) { + if (exc.getMessage().equals("lookup not found")) { + return null; + } + else { + throw new IOException(exc); + } + } + try (final ArchivedFileStream stream = new ArchivedFileStream(this.inputChannel, this.extractor, file)) { + final long size = stream.size(); + final ByteBuffer buffer = ByteBuffer.allocate((int) size); + stream.read(buffer); + return buffer; + } + } + @Override public File getFile(final String filepath) throws IOException { // TODO Auto-generated method stub diff --git a/core/src/com/etheller/warsmash/datasources/MpqDataSourceDescriptor.java b/core/src/com/etheller/warsmash/datasources/MpqDataSourceDescriptor.java index ce168e2..276a666 100644 --- a/core/src/com/etheller/warsmash/datasources/MpqDataSourceDescriptor.java +++ b/core/src/com/etheller/warsmash/datasources/MpqDataSourceDescriptor.java @@ -72,4 +72,7 @@ public class MpqDataSourceDescriptor implements DataSourceDescriptor { return true; } + public String getMpqFilePath() { + return this.mpqFilePath; + } } diff --git a/core/src/com/etheller/warsmash/datasources/SubdirDataSource.java b/core/src/com/etheller/warsmash/datasources/SubdirDataSource.java index d591390..708d340 100644 --- a/core/src/com/etheller/warsmash/datasources/SubdirDataSource.java +++ b/core/src/com/etheller/warsmash/datasources/SubdirDataSource.java @@ -3,6 +3,7 @@ package com.etheller.warsmash.datasources; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -21,6 +22,11 @@ public class SubdirDataSource implements DataSource { return this.dataSource.getFile(this.subdir + filepath); } + @Override + public ByteBuffer read(final String path) throws IOException { + return this.dataSource.read(this.subdir + path); + } + @Override public InputStream getResourceAsStream(final String filepath) throws IOException { return this.dataSource.getResourceAsStream(this.subdir + filepath); diff --git a/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java index 976f9e5..97766d0 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java @@ -41,7 +41,6 @@ import com.etheller.warsmash.parsers.fdf.frames.SpriteFrame; import com.etheller.warsmash.parsers.fdf.frames.StringFrame; import com.etheller.warsmash.parsers.fdf.frames.TextureFrame; import com.etheller.warsmash.parsers.fdf.frames.UIFrame; -import com.etheller.warsmash.parsers.mdlx.Layer.FilterMode; import com.etheller.warsmash.units.DataTable; import com.etheller.warsmash.units.Element; import com.etheller.warsmash.util.ImageUtils; @@ -49,6 +48,7 @@ import com.etheller.warsmash.util.StringBundle; import com.etheller.warsmash.viewer5.Scene; import com.etheller.warsmash.viewer5.handlers.AbstractMdxModelViewer; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; +import com.hiveworkshop.rms.parsers.mdlx.MdlxLayer.FilterMode; public final class GameUI extends AbstractUIFrame implements UIFrame { private final DataSource dataSource; @@ -658,7 +658,7 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { Texture texture = this.pathToTexture.get(path); if (texture == null) { try { - texture = ImageUtils.getBLPTexture(this.dataSource, path); + texture = ImageUtils.getAnyExtensionTexture(this.dataSource, path); this.pathToTexture.put(path, texture); } catch (final Exception exc) { diff --git a/core/src/com/etheller/warsmash/parsers/fdf/ModelExport.java b/core/src/com/etheller/warsmash/parsers/fdf/ModelExport.java index 6967bff..243a455 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/ModelExport.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/ModelExport.java @@ -10,7 +10,8 @@ import com.etheller.warsmash.datasources.CompoundDataSourceDescriptor; import com.etheller.warsmash.datasources.DataSource; import com.etheller.warsmash.datasources.DataSourceDescriptor; import com.etheller.warsmash.datasources.FolderDataSourceDescriptor; -import com.etheller.warsmash.parsers.mdlx.MdlxModel; +import com.hiveworkshop.rms.parsers.mdlx.MdlxModel; +import com.hiveworkshop.rms.parsers.mdlx.util.MdxUtils; public class ModelExport { @@ -24,9 +25,9 @@ public class ModelExport { try (InputStream modelStream = dataSource .getResourceAsStream("UI\\Glues\\MainMenu\\MainMenu3D\\MainMenu3D.mdx")) { - final MdlxModel model = new MdlxModel(modelStream); + final MdlxModel model = new MdlxModel(dataSource.read("UI\\Glues\\MainMenu\\MainMenu3D\\MainMenu3D.mdx")); try (FileOutputStream fos = new FileOutputStream(new File("C:\\Temp\\MainMenu3D.mdl"))) { - model.saveMdl(fos); + MdxUtils.saveMdl(model, fos); } } catch (final IOException e) { diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/FilterModeTextureFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/FilterModeTextureFrame.java index 653c9df..3b72c3a 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/FilterModeTextureFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/FilterModeTextureFrame.java @@ -4,7 +4,7 @@ import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.GlyphLayout; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.etheller.warsmash.parsers.fdf.datamodel.Vector4Definition; -import com.etheller.warsmash.parsers.mdlx.Layer.FilterMode; +import com.hiveworkshop.rms.parsers.mdlx.MdlxLayer.FilterMode; public class FilterModeTextureFrame extends TextureFrame { private int blendSrc; diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/SmartBackdropFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/SmartBackdropFrame.java index 4c70736..d7ea575 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/SmartBackdropFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/SmartBackdropFrame.java @@ -6,13 +6,14 @@ import com.badlogic.gdx.utils.viewport.Viewport; import com.etheller.warsmash.parsers.fdf.GameUI; import com.etheller.warsmash.parsers.fdf.datamodel.BackdropCornerFlags; import com.etheller.warsmash.parsers.fdf.datamodel.Vector4Definition; -import com.etheller.warsmash.parsers.mdlx.Geoset; -import com.etheller.warsmash.parsers.mdlx.Layer; -import com.etheller.warsmash.parsers.mdlx.Layer.FilterMode; -import com.etheller.warsmash.parsers.mdlx.Material; -import com.etheller.warsmash.parsers.mdlx.MdlxModel; -import com.etheller.warsmash.parsers.mdlx.Texture; import com.etheller.warsmash.viewer5.Scene; +import com.hiveworkshop.rms.parsers.mdlx.MdlxGeoset; +import com.hiveworkshop.rms.parsers.mdlx.MdlxLayer; +import com.hiveworkshop.rms.parsers.mdlx.MdlxLayer.FilterMode; +import com.hiveworkshop.rms.parsers.mdlx.MdlxMaterial; +import com.hiveworkshop.rms.parsers.mdlx.MdlxModel; +import com.hiveworkshop.rms.parsers.mdlx.MdlxTexture; +import com.hiveworkshop.rms.parsers.mdlx.MdlxTexture.WrapMode; public class SmartBackdropFrame extends SpriteFrame { private final boolean decorateFileNames; @@ -49,21 +50,21 @@ public class SmartBackdropFrame extends SpriteFrame { final MdlxModel model = new MdlxModel(); final int edgeFileMaterialId = generateMaterial(model, this.edgeFileString, true); final int backgroundMaterialId = generateMaterial(model, this.backgroundString, this.tileBackground); - final Geoset edgeGeoset = new Geoset(); + final MdlxGeoset edgeGeoset = new MdlxGeoset(); final float[] edgeGeosetVertices = new float[32 * 4]; return model; } private int generateMaterial(final MdlxModel model, final String path, final boolean wrap) { - final Texture edgeFileReference = new Texture(); + final MdlxTexture edgeFileReference = new MdlxTexture(); if (wrap) { - edgeFileReference.setFlags(0x2 | 0x1); + edgeFileReference.setWrapMode(WrapMode.REPEAT_BOTH); } edgeFileReference.setPath(path); final int textureId = model.getTextures().size(); model.getTextures().add(edgeFileReference); - final Material edgeFileMaterial = new Material(); - final Layer edgeFileMaterialLayer = new Layer(); + final MdlxMaterial edgeFileMaterial = new MdlxMaterial(); + final MdlxLayer edgeFileMaterialLayer = new MdlxLayer(); edgeFileMaterialLayer.setAlpha(1.0f); edgeFileMaterialLayer.setFilterMode(FilterMode.BLEND); edgeFileMaterialLayer.setTextureId(textureId); diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/AnimationMap.java b/core/src/com/etheller/warsmash/parsers/mdlx/AnimationMap.java deleted file mode 100644 index ec9110f..0000000 --- a/core/src/com/etheller/warsmash/parsers/mdlx/AnimationMap.java +++ /dev/null @@ -1,99 +0,0 @@ -package com.etheller.warsmash.parsers.mdlx; - -import java.util.HashMap; -import java.util.Map; - -import com.etheller.warsmash.util.MdlUtils; -import com.etheller.warsmash.util.War3ID; - -/** - * A map from MDX animation tags to their equivalent MDL tokens, and the - * implementation objects. - * - *

- * Based on the works of Chananya Freiman. - * - */ -public enum AnimationMap { - // Layer - KMTF(MdlUtils.TOKEN_TEXTURE_ID, TimelineDescriptor.UINT32_TIMELINE), - KMTA(MdlUtils.TOKEN_ALPHA, TimelineDescriptor.FLOAT_TIMELINE), - // TextureAnimation - KTAT(MdlUtils.TOKEN_TRANSLATION, TimelineDescriptor.VECTOR3_TIMELINE), - KTAR(MdlUtils.TOKEN_ROTATION, TimelineDescriptor.VECTOR4_TIMELINE), - KTAS(MdlUtils.TOKEN_SCALING, TimelineDescriptor.VECTOR3_TIMELINE), - // GeosetAnimation - KGAO(MdlUtils.TOKEN_ALPHA, TimelineDescriptor.FLOAT_TIMELINE), - KGAC(MdlUtils.TOKEN_COLOR, TimelineDescriptor.VECTOR3_TIMELINE), - // Light - KLAS(MdlUtils.TOKEN_ATTENUATION_START, TimelineDescriptor.FLOAT_TIMELINE), - KLAE(MdlUtils.TOKEN_ATTENUATION_END, TimelineDescriptor.FLOAT_TIMELINE), - KLAC(MdlUtils.TOKEN_COLOR, TimelineDescriptor.VECTOR3_TIMELINE), - KLAI(MdlUtils.TOKEN_INTENSITY, TimelineDescriptor.FLOAT_TIMELINE), - KLBI(MdlUtils.TOKEN_AMB_INTENSITY, TimelineDescriptor.FLOAT_TIMELINE), - KLBC(MdlUtils.TOKEN_AMB_COLOR, TimelineDescriptor.VECTOR3_TIMELINE), - KLAV(MdlUtils.TOKEN_VISIBILITY, TimelineDescriptor.FLOAT_TIMELINE), - // Attachment - KATV(MdlUtils.TOKEN_VISIBILITY, TimelineDescriptor.FLOAT_TIMELINE), - // ParticleEmitter - KPEE(MdlUtils.TOKEN_EMISSION_RATE, TimelineDescriptor.FLOAT_TIMELINE), - KPEG(MdlUtils.TOKEN_GRAVITY, TimelineDescriptor.FLOAT_TIMELINE), - KPLN(MdlUtils.TOKEN_LONGITUDE, TimelineDescriptor.FLOAT_TIMELINE), - KPLT(MdlUtils.TOKEN_LATITUDE, TimelineDescriptor.FLOAT_TIMELINE), - KPEL(MdlUtils.TOKEN_LIFE_SPAN, TimelineDescriptor.FLOAT_TIMELINE), - KPES(MdlUtils.TOKEN_INIT_VELOCITY, TimelineDescriptor.FLOAT_TIMELINE), - KPEV(MdlUtils.TOKEN_VISIBILITY, TimelineDescriptor.FLOAT_TIMELINE), - // ParticleEmitter2 - KP2S("Speed", TimelineDescriptor.FLOAT_TIMELINE), - KP2R("Variation", TimelineDescriptor.FLOAT_TIMELINE), - KP2L("Latitude", TimelineDescriptor.FLOAT_TIMELINE), - KP2G("Gravity", TimelineDescriptor.FLOAT_TIMELINE), - KP2E("EmissionRate", TimelineDescriptor.FLOAT_TIMELINE), - KP2N("Length", TimelineDescriptor.FLOAT_TIMELINE), - KP2W("Width", TimelineDescriptor.FLOAT_TIMELINE), - KP2V("Visibility", TimelineDescriptor.FLOAT_TIMELINE), - // RibbonEmitter - KRHA("HeightAbove", TimelineDescriptor.FLOAT_TIMELINE), - KRHB("HeightBelow", TimelineDescriptor.FLOAT_TIMELINE), - KRAL("Alpha", TimelineDescriptor.FLOAT_TIMELINE), - KRCO("Color", TimelineDescriptor.VECTOR3_TIMELINE), - KRTX("TextureSlot", TimelineDescriptor.UINT32_TIMELINE), - KRVS("Visibility", TimelineDescriptor.FLOAT_TIMELINE), - // Camera - KCTR(MdlUtils.TOKEN_TRANSLATION, TimelineDescriptor.VECTOR3_TIMELINE), - KTTR(MdlUtils.TOKEN_TRANSLATION, TimelineDescriptor.VECTOR3_TIMELINE), - KCRL(MdlUtils.TOKEN_ROTATION, TimelineDescriptor.UINT32_TIMELINE), - // GenericObject - KGTR(MdlUtils.TOKEN_TRANSLATION, TimelineDescriptor.VECTOR3_TIMELINE), - KGRT(MdlUtils.TOKEN_ROTATION, TimelineDescriptor.VECTOR4_TIMELINE), - KGSC(MdlUtils.TOKEN_SCALING, TimelineDescriptor.VECTOR3_TIMELINE); - private final String mdlToken; - private final TimelineDescriptor implementation; - private final War3ID war3id; - - private AnimationMap(final String mdlToken, final TimelineDescriptor implementation) { - this.mdlToken = mdlToken; - this.implementation = implementation; - this.war3id = War3ID.fromString(this.name()); - } - - public String getMdlToken() { - return this.mdlToken; - } - - public TimelineDescriptor getImplementation() { - return this.implementation; - } - - public War3ID getWar3id() { - return this.war3id; - } - - public static final Map ID_TO_TAG = new HashMap<>(); - - static { - for (final AnimationMap tag : AnimationMap.values()) { - ID_TO_TAG.put(tag.getWar3id(), tag); - } - } -} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/Attachment.java b/core/src/com/etheller/warsmash/parsers/mdlx/Attachment.java deleted file mode 100644 index bf12d25..0000000 --- a/core/src/com/etheller/warsmash/parsers/mdlx/Attachment.java +++ /dev/null @@ -1,107 +0,0 @@ -package com.etheller.warsmash.parsers.mdlx; - -import java.io.IOException; - -import com.etheller.warsmash.util.MdlUtils; -import com.etheller.warsmash.util.ParseUtils; -import com.google.common.io.LittleEndianDataInputStream; -import com.google.common.io.LittleEndianDataOutputStream; - -public class Attachment extends GenericObject { - private String path = ""; - private int attachmentId; - - public Attachment() { - super(0x800); - } - - /** - * Restricts us to only be able to parse models on one thread at a time, in - * return for high performance. - */ - private static final byte[] PATH_BYTES_HEAP = new byte[260]; - - @Override - public void readMdx(final LittleEndianDataInputStream stream) throws IOException { - final long size = ParseUtils.readUInt32(stream); - - super.readMdx(stream); - - this.path = ParseUtils.readString(stream, PATH_BYTES_HEAP); - this.attachmentId = stream.readInt(); - - this.readTimelines(stream, size - this.getByteLength()); - } - - @Override - public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException { - ParseUtils.writeUInt32(stream, getByteLength()); - - super.writeMdx(stream); - - final byte[] bytes = this.path.getBytes(ParseUtils.UTF8); - stream.write(bytes); - for (int i = 0; i < (260 - bytes.length); i++) { - stream.write((byte) 0); - } - stream.writeInt(this.attachmentId); // Used to be Int32 in JS - - this.writeNonGenericAnimationChunks(stream); - } - - @Override - public void readMdl(final MdlTokenInputStream stream) throws IOException { - for (final String token : super.readMdlGeneric(stream)) { - if (MdlUtils.TOKEN_ATTACHMENT_ID.equals(token)) { - this.attachmentId = stream.readInt(); - } - else if (MdlUtils.TOKEN_PATH.equals(token)) { - this.path = stream.read(); - } - else if (MdlUtils.TOKEN_VISIBILITY.equals(token)) { - this.readTimeline(stream, AnimationMap.KATV); - } - else { - throw new IOException("Unknown token in Attachment " + this.name + ": " + token); - } - } - } - - @Override - public void writeMdl(final MdlTokenOutputStream stream) throws IOException { - stream.startObjectBlock(MdlUtils.TOKEN_ATTACHMENT, this.name); - this.writeGenericHeader(stream); - - // flowtsohg asks in his JS: - // Is this needed? MDX supplies it, but MdlxConv does not use it. - // Retera: - // I tried to preserve it when it was shown, but omit it when it was omitted - // for MDL in Matrix Eater. Matrix Eater's MDL -> MDX is generating them - // and discarding what was read from the MDL. The Matrix Eater is notably - // buggy from a cursory read through, and would always omit AttachmentID 0 - // in MDL output. - stream.writeAttrib(MdlUtils.TOKEN_ATTACHMENT_ID, this.attachmentId); - - if ((this.path != null) && (this.path.length() > 0)) { - stream.writeStringAttrib(MdlUtils.TOKEN_PATH, this.path); - } - - this.writeTimeline(stream, AnimationMap.KATV); - - this.writeGenericTimelines(stream); - stream.endBlock(); - } - - @Override - public long getByteLength() { - return 268 + super.getByteLength(); - } - - public String getPath() { - return this.path; - } - - public int getAttachmentId() { - return this.attachmentId; - } -} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/Bone.java b/core/src/com/etheller/warsmash/parsers/mdlx/Bone.java deleted file mode 100644 index a1fbb31..0000000 --- a/core/src/com/etheller/warsmash/parsers/mdlx/Bone.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.etheller.warsmash.parsers.mdlx; - -import java.io.IOException; - -import com.etheller.warsmash.util.MdlUtils; -import com.google.common.io.LittleEndianDataInputStream; -import com.google.common.io.LittleEndianDataOutputStream; - -public class Bone extends GenericObject { - private int geosetId = -1; - private int geosetAnimationId = -1; - - public Bone() { - super(0x100); - } - - @Override - public void readMdx(final LittleEndianDataInputStream stream) throws IOException { - super.readMdx(stream); - - this.geosetId = stream.readInt(); - this.geosetAnimationId = stream.readInt(); - } - - @Override - public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException { - super.writeMdx(stream); - stream.writeInt(this.geosetId); - stream.writeInt(this.geosetAnimationId); - } - - @Override - public void readMdl(final MdlTokenInputStream stream) { - for (String token : super.readMdlGeneric(stream)) { - if (MdlUtils.TOKEN_GEOSETID.equals(token)) { - token = stream.read(); - - if (MdlUtils.TOKEN_MULTIPLE.equals(token)) { - this.geosetId = -1; - } - else { - this.geosetId = Integer.parseInt(token); - } - } - else if (MdlUtils.TOKEN_GEOSETANIMID.equals(token)) { - token = stream.read(); - - if (MdlUtils.TOKEN_NONE.equals(token)) { - this.geosetAnimationId = -1; - } - else { - this.geosetAnimationId = Integer.parseInt(token); - } - } - else { - throw new RuntimeException("Unknown token in Bone " + this.name + ": " + token); - } - } - } - - @Override - public void writeMdl(final MdlTokenOutputStream stream) throws IOException { - stream.startObjectBlock(MdlUtils.TOKEN_BONE, this.name); - this.writeGenericHeader(stream); - - if (this.geosetId == -1) { - stream.writeAttrib(MdlUtils.TOKEN_GEOSETID, MdlUtils.TOKEN_MULTIPLE); - } - else { - stream.writeAttrib(MdlUtils.TOKEN_GEOSETID, this.geosetId); - } - if (this.geosetAnimationId == -1) { - stream.writeAttrib(MdlUtils.TOKEN_GEOSETANIMID, MdlUtils.TOKEN_NONE); - } - else { - stream.writeAttrib(MdlUtils.TOKEN_GEOSETANIMID, this.geosetAnimationId); - } - - this.writeGenericTimelines(stream); - stream.endBlock(); - } - - @Override - public long getByteLength() { - return 8 + super.getByteLength(); - } - - public int getGeosetAnimationId() { - return this.geosetAnimationId; - } - - public int getGeosetId() { - return this.geosetId; - } -} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/Camera.java b/core/src/com/etheller/warsmash/parsers/mdlx/Camera.java deleted file mode 100644 index 75c8770..0000000 --- a/core/src/com/etheller/warsmash/parsers/mdlx/Camera.java +++ /dev/null @@ -1,152 +0,0 @@ -package com.etheller.warsmash.parsers.mdlx; - -import java.io.IOException; - -import com.etheller.warsmash.util.MdlUtils; -import com.etheller.warsmash.util.ParseUtils; -import com.google.common.io.LittleEndianDataInputStream; -import com.google.common.io.LittleEndianDataOutputStream; - -public class Camera extends AnimatedObject { - protected String name; - private final float[] position; - private float fieldOfView; - private float farClippingPlane; - private float nearClippingPlane; - private final float[] targetPosition; - - public Camera() { - this.position = new float[3]; - this.targetPosition = new float[3]; - } - - /** - * Restricts us to only be able to parse models on one thread at a time, in - * return for high performance. - */ - private static final byte[] NAME_BYTES_HEAP = new byte[80]; - - @Override - public void readMdx(final LittleEndianDataInputStream stream) throws IOException { - final long size = ParseUtils.readUInt32(stream); - - this.name = ParseUtils.readString(stream, NAME_BYTES_HEAP); - ParseUtils.readFloatArray(stream, this.position); - this.fieldOfView = stream.readFloat(); - this.farClippingPlane = stream.readFloat(); - this.nearClippingPlane = stream.readFloat(); - ParseUtils.readFloatArray(stream, this.targetPosition); - - readTimelines(stream, size - 120); - } - - @Override - public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException { - ParseUtils.writeUInt32(stream, getByteLength()); - final byte[] bytes = this.name.getBytes(ParseUtils.UTF8); - stream.write(bytes); - for (int i = 0; i < (80 - bytes.length); i++) { - stream.write((byte) 0); - } - ParseUtils.writeFloatArray(stream, this.position); - stream.writeFloat(this.fieldOfView); - stream.writeFloat(this.farClippingPlane); - stream.writeFloat(this.nearClippingPlane); - ParseUtils.writeFloatArray(stream, this.targetPosition); - - writeTimelines(stream); - } - - @Override - public void readMdl(final MdlTokenInputStream stream) throws IOException { - this.name = stream.read(); - - for (final String token : stream.readBlock()) { - switch (token) { - case MdlUtils.TOKEN_POSITION: - stream.readFloatArray(this.position); - break; - case MdlUtils.TOKEN_TRANSLATION: - readTimeline(stream, AnimationMap.KCTR); - break; - case MdlUtils.TOKEN_ROTATION: - readTimeline(stream, AnimationMap.KCRL); - break; - case MdlUtils.TOKEN_FIELDOFVIEW: - this.fieldOfView = stream.readFloat(); - break; - case MdlUtils.TOKEN_FARCLIP: - this.farClippingPlane = stream.readFloat(); - break; - case MdlUtils.TOKEN_NEARCLIP: - this.nearClippingPlane = stream.readFloat(); - break; - case MdlUtils.TOKEN_TARGET: - for (final String subToken : stream.readBlock()) { - switch (subToken) { - case MdlUtils.TOKEN_POSITION: - stream.readFloatArray(this.targetPosition); - break; - case MdlUtils.TOKEN_TRANSLATION: - readTimeline(stream, AnimationMap.KTTR); - break; - default: - throw new IllegalStateException( - "Unknown token in Camera " + this.name + "'s Target: " + subToken); - } - } - break; - default: - throw new IllegalStateException("Unknown token in Camera " + this.name + ": " + token); - } - } - } - - @Override - public void writeMdl(final MdlTokenOutputStream stream) throws IOException { - stream.startObjectBlock(MdlUtils.TOKEN_CAMERA, this.name); - - stream.writeFloatArrayAttrib(MdlUtils.TOKEN_POSITION, this.position); - writeTimeline(stream, AnimationMap.KCTR); - writeTimeline(stream, AnimationMap.KCRL); - stream.writeFloatAttrib(MdlUtils.TOKEN_FIELDOFVIEW, this.fieldOfView); - stream.writeFloatAttrib(MdlUtils.TOKEN_FARCLIP, this.farClippingPlane); - stream.writeFloatAttrib(MdlUtils.TOKEN_NEARCLIP, this.nearClippingPlane); - - stream.startBlock(MdlUtils.TOKEN_TARGET); - stream.writeFloatArrayAttrib(MdlUtils.TOKEN_POSITION, this.targetPosition); - writeTimeline(stream, AnimationMap.KTTR); - stream.endBlock(); - - stream.endBlock(); - } - - @Override - public long getByteLength() { - return 120 + super.getByteLength(); - } - - public String getName() { - return this.name; - } - - public float[] getPosition() { - return this.position; - } - - public float getFieldOfView() { - return this.fieldOfView; - } - - public float getFarClippingPlane() { - return this.farClippingPlane; - } - - public float getNearClippingPlane() { - return this.nearClippingPlane; - } - - public float[] getTargetPosition() { - return this.targetPosition; - } -} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/Chunk.java b/core/src/com/etheller/warsmash/parsers/mdlx/Chunk.java deleted file mode 100644 index 0f109ba..0000000 --- a/core/src/com/etheller/warsmash/parsers/mdlx/Chunk.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.etheller.warsmash.parsers.mdlx; - -public interface Chunk { - long getByteLength(); -} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/Extent.java b/core/src/com/etheller/warsmash/parsers/mdlx/Extent.java deleted file mode 100644 index 17b15fe..0000000 --- a/core/src/com/etheller/warsmash/parsers/mdlx/Extent.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.etheller.warsmash.parsers.mdlx; - -import java.io.IOException; - -import com.etheller.warsmash.util.MdlUtils; -import com.etheller.warsmash.util.ParseUtils; -import com.google.common.io.LittleEndianDataInputStream; -import com.google.common.io.LittleEndianDataOutputStream; - -public class Extent { - protected float boundsRadius = 0; - protected final float[] min = new float[3]; - protected final float[] max = new float[3]; - - public void readMdx(final LittleEndianDataInputStream stream) throws IOException { - this.boundsRadius = stream.readFloat(); - ParseUtils.readFloatArray(stream, this.min); - ParseUtils.readFloatArray(stream, this.max); - } - - public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException { - stream.writeFloat(this.boundsRadius); - ParseUtils.writeFloatArray(stream, this.min); - ParseUtils.writeFloatArray(stream, this.max); - } - - public void writeMdl(final MdlTokenOutputStream stream) { - if ((this.min[0] != 0) || (this.min[1] != 0) || (this.min[2] != 0)) { - stream.writeFloatArrayAttrib(MdlUtils.TOKEN_MINIMUM_EXTENT, this.min); - } - if ((this.max[0] != 0) || (this.max[1] != 0) || (this.max[2] != 0)) { - stream.writeFloatArrayAttrib(MdlUtils.TOKEN_MAXIMUM_EXTENT, this.max); - } - - if (this.boundsRadius != 0) { - stream.writeFloatAttrib(MdlUtils.TOKEN_BOUNDSRADIUS, this.boundsRadius); - } - } - - public void setBoundsRadius(final float boundsRadius) { - this.boundsRadius = boundsRadius; - } - - public float getBoundsRadius() { - return this.boundsRadius; - } - - public float[] getMin() { - return this.min; - } - - public float[] getMax() { - return this.max; - } -} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/GenericObject.java b/core/src/com/etheller/warsmash/parsers/mdlx/GenericObject.java deleted file mode 100644 index 66171fb..0000000 --- a/core/src/com/etheller/warsmash/parsers/mdlx/GenericObject.java +++ /dev/null @@ -1,350 +0,0 @@ -package com.etheller.warsmash.parsers.mdlx; - -import java.io.IOException; -import java.util.Iterator; -import java.util.List; - -import com.etheller.warsmash.parsers.mdlx.timeline.Timeline; -import com.etheller.warsmash.util.MdlUtils; -import com.etheller.warsmash.util.ParseUtils; -import com.etheller.warsmash.util.War3ID; -import com.google.common.io.LittleEndianDataInputStream; -import com.google.common.io.LittleEndianDataOutputStream; - -/** - * A generic object. - * - * The parent class for all objects that exist in the world, and may contain - * spatial animations. This includes bones, particle emitters, and many other - * things. - * - * Based on the works of Chananya Freiman. - */ -public abstract class GenericObject extends AnimatedObject implements Chunk { - protected String name; - private int objectId; - private int parentId; - protected int flags; - - /** - * Restricts us to only be able to parse models on one thread at a time, in - * return for high performance. - */ - private static final byte[] NAME_BYTES_HEAP = new byte[80]; - - public GenericObject(final int flags) { - this.name = ""; - this.objectId = -1; - this.parentId = -1; - this.flags = flags; - } - - @Override - public void readMdx(final LittleEndianDataInputStream stream) throws IOException { - final long size = ParseUtils.readUInt32(stream); - this.name = ParseUtils.readString(stream, NAME_BYTES_HEAP); - this.objectId = stream.readInt(); - this.parentId = stream.readInt(); - this.flags = stream.readInt(); // Used to be Int32 in JS - readTimelines(stream, size - 96); - } - - @Override - public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException { - ParseUtils.writeUInt32(stream, getGenericByteLength()); - final byte[] bytes = this.name.getBytes(ParseUtils.UTF8); - stream.write(bytes); - for (int i = 0; i < (80 - bytes.length); i++) { - stream.write((byte) 0); - } - stream.writeInt(this.objectId); - stream.writeInt(this.parentId); - stream.writeInt(this.flags); // UInt32 in ghostwolf JS, shouldn't matter for Java - - for (final Timeline timeline : eachTimeline(true)) { - timeline.writeMdx(stream); - } - } - - public void writeNonGenericAnimationChunks(final LittleEndianDataOutputStream stream) throws IOException { - for (final Timeline timeline : eachTimeline(false)) { - timeline.writeMdx(stream); - } - } - - protected final Iterable readMdlGeneric(final MdlTokenInputStream stream) { - this.name = stream.read(); - return new Iterable() { - @Override - public Iterator iterator() { - return new WrappedMdlTokenIterator(GenericObject.this.readAnimatedBlock(stream), GenericObject.this, - stream); - } - }; - } - - public void writeGenericHeader(final MdlTokenOutputStream stream) { - stream.writeAttrib(MdlUtils.TOKEN_OBJECTID, this.objectId); - - if (this.parentId != -1) { - stream.writeAttrib("Parent", this.parentId); - } - - if ((this.flags & 0x40) != 0) { - stream.writeFlag(MdlUtils.TOKEN_BILLBOARDED_LOCK_Z); - } - - if ((this.flags & 0x20) != 0) { - stream.writeFlag(MdlUtils.TOKEN_BILLBOARDED_LOCK_Y); - } - - if ((this.flags & 0x10) != 0) { - stream.writeFlag(MdlUtils.TOKEN_BILLBOARDED_LOCK_X); - } - - if ((this.flags & 0x8) != 0) { - stream.writeFlag(MdlUtils.TOKEN_BILLBOARDED); - } - - if ((this.flags & 0x80) != 0) { - stream.writeFlag(MdlUtils.TOKEN_CAMERA_ANCHORED); - } - - if ((this.flags & 0x2) != 0) { - stream.writeFlag(MdlUtils.TOKEN_DONT_INHERIT + " { " + MdlUtils.TOKEN_ROTATION + " }"); - } - - if ((this.flags & 0x1) != 0) { - stream.writeFlag(MdlUtils.TOKEN_DONT_INHERIT + " { " + MdlUtils.TOKEN_TRANSLATION + " }"); - } - - if ((this.flags & 0x4) != 0) { - stream.writeFlag(MdlUtils.TOKEN_DONT_INHERIT + " { " + MdlUtils.TOKEN_SCALING + " }"); - } - } - - public void writeGenericTimelines(final MdlTokenOutputStream stream) throws IOException { - this.writeTimeline(stream, AnimationMap.KGTR); - this.writeTimeline(stream, AnimationMap.KGRT); - this.writeTimeline(stream, AnimationMap.KGSC); - } - - public Iterable> eachTimeline(final boolean generic) { - return new TimelineMaskingIterable(generic); - } - - public long getGenericByteLength() { - long size = 96; - for (final Chunk animation : eachTimeline(true)) { - size += animation.getByteLength(); - } - return size; - } - - @Override - public long getByteLength() { - return 96 + super.getByteLength(); - } - - private final class TimelineMaskingIterable implements Iterable> { - private final boolean generic; - - private TimelineMaskingIterable(final boolean generic) { - this.generic = generic; - } - - @Override - public Iterator> iterator() { - return new TimelineMaskingIterator(this.generic, GenericObject.this.timelines); - } - } - - private static final class TimelineMaskingIterator implements Iterator> { - private final boolean wantGeneric; - private final Iterator> delegate; - private boolean hasNext; - private Timeline next; - - public TimelineMaskingIterator(final boolean wantGeneric, final List> timelines) { - this.wantGeneric = wantGeneric; - this.delegate = timelines.iterator(); - scanUntilNext(); - } - - private boolean isGeneric(final Timeline timeline) { - final War3ID name = timeline.getName(); - final boolean generic = AnimationMap.KGTR.getWar3id().equals(name) - || AnimationMap.KGRT.getWar3id().equals(name) || AnimationMap.KGSC.getWar3id().equals(name); - return generic; - } - - private void scanUntilNext() { - boolean hasNext = false; - if (hasNext = this.delegate.hasNext()) { - do { - this.next = this.delegate.next(); - } - while ((isGeneric(this.next) != this.wantGeneric) && (hasNext = this.delegate.hasNext())); - } - if (!hasNext) { - this.next = null; - } - } - - @Override - public boolean hasNext() { - return this.next != null; - } - - @Override - public Timeline next() { - final Timeline last = this.next; - scanUntilNext(); - return last; - } - - @Override - public void remove() { - throw new UnsupportedOperationException("Remove is not supported"); - } - - } - - private static final class WrappedMdlTokenIterator implements Iterator { - private final Iterator delegate; - private final GenericObject updatingObject; - private final MdlTokenInputStream stream; - private String next; - private boolean hasLoaded = false; - - public WrappedMdlTokenIterator(final Iterator delegate, final GenericObject updatingObject, - final MdlTokenInputStream stream) { - this.delegate = delegate; - this.updatingObject = updatingObject; - this.stream = stream; - } - - @Override - public boolean hasNext() { - if (this.delegate.hasNext()) { - this.next = read(); - this.hasLoaded = true; - return this.next != null; - } - return false; - } - - @Override - public String next() { - if (!this.hasLoaded) { - this.next = read(); - } - this.hasLoaded = false; - return this.next; - } - - private String read() { - String token; - InteriorParsing: do { - token = this.delegate.next(); - if (token == null) { - break; - } - switch (token) { - case MdlUtils.TOKEN_OBJECTID: - this.updatingObject.objectId = Integer.parseInt(this.delegate.next()); - token = null; - break; - case MdlUtils.TOKEN_PARENT: - this.updatingObject.parentId = Integer.parseInt(this.delegate.next()); - token = null; - break; - case MdlUtils.TOKEN_BILLBOARDED_LOCK_Z: - this.updatingObject.flags |= 0x40; - token = null; - break; - case MdlUtils.TOKEN_BILLBOARDED_LOCK_Y: - this.updatingObject.flags |= 0x20; - token = null; - break; - case MdlUtils.TOKEN_BILLBOARDED_LOCK_X: - this.updatingObject.flags |= 0x10; - token = null; - break; - case MdlUtils.TOKEN_BILLBOARDED: - this.updatingObject.flags |= 0x8; - token = null; - break; - case MdlUtils.TOKEN_CAMERA_ANCHORED: - this.updatingObject.flags |= 0x80; - token = null; - break; - case MdlUtils.TOKEN_DONT_INHERIT: - for (final String subToken : this.stream.readBlock()) { - switch (subToken) { - case MdlUtils.TOKEN_ROTATION: - this.updatingObject.flags |= 0x2; - break; - case MdlUtils.TOKEN_TRANSLATION: - this.updatingObject.flags |= 0x1; - break; - case MdlUtils.TOKEN_SCALING: - this.updatingObject.flags |= 0x0; - break; - } - } - token = null; - break; - case MdlUtils.TOKEN_TRANSLATION: - try { - this.updatingObject.readTimeline(this.stream, AnimationMap.KGTR); - } - catch (final IOException e) { - throw new RuntimeException(e); - } - token = null; - break; - case MdlUtils.TOKEN_ROTATION: - try { - this.updatingObject.readTimeline(this.stream, AnimationMap.KGRT); - } - catch (final IOException e) { - throw new RuntimeException(e); - } - token = null; - break; - case MdlUtils.TOKEN_SCALING: - try { - this.updatingObject.readTimeline(this.stream, AnimationMap.KGSC); - } - catch (final IOException e) { - throw new RuntimeException(e); - } - token = null; - break; - default: - break InteriorParsing; - } - } - while (this.delegate.hasNext()); - return token; - } - - } - - public String getName() { - return this.name; - } - - public int getObjectId() { - return this.objectId; - } - - public int getParentId() { - return this.parentId; - } - - public int getFlags() { - return this.flags; - } -} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/Geoset.java b/core/src/com/etheller/warsmash/parsers/mdlx/Geoset.java deleted file mode 100644 index be75c61..0000000 --- a/core/src/com/etheller/warsmash/parsers/mdlx/Geoset.java +++ /dev/null @@ -1,428 +0,0 @@ -package com.etheller.warsmash.parsers.mdlx; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import com.etheller.warsmash.util.MdlUtils; -import com.etheller.warsmash.util.ParseUtils; -import com.etheller.warsmash.util.War3ID; -import com.google.common.io.LittleEndianDataInputStream; -import com.google.common.io.LittleEndianDataOutputStream; - -public class Geoset implements MdlxBlock, Chunk { - private static final War3ID VRTX = War3ID.fromString("VRTX"); - private static final War3ID NRMS = War3ID.fromString("NRMS"); - private static final War3ID PTYP = War3ID.fromString("PTYP"); - private static final War3ID PCNT = War3ID.fromString("PCNT"); - private static final War3ID PVTX = War3ID.fromString("PVTX"); - private static final War3ID GNDX = War3ID.fromString("GNDX"); - private static final War3ID MTGC = War3ID.fromString("MTGC"); - private static final War3ID MATS = War3ID.fromString("MATS"); - private static final War3ID UVAS = War3ID.fromString("UVAS"); - private static final War3ID UVBS = War3ID.fromString("UVBS"); - private float[] vertices; - private float[] normals; - private long[] faceTypeGroups; // unsigned int[] - private long[] faceGroups; // unsigned int[] - private int[] faces; // unsigned short[] - private short[] vertexGroups; // unsigned byte[] - private long[] matrixGroups; // unsigned int[] - private long[] matrixIndices; // unsigned int[] - private long materialId = 0; - private long selectionGroup = 0; - private long selectionFlags = 0; - private final Extent extent = new Extent(); - private Extent[] sequenceExtents; - private float[][] uvSets; - - @Override - public void readMdx(final LittleEndianDataInputStream stream) throws IOException { - final long mySize = ParseUtils.readUInt32(stream); - final int vrtx = stream.readInt(); // skip VRTX - this.vertices = ParseUtils.readFloatArray(stream, (int) (ParseUtils.readUInt32(stream) * 3)); - final int nrms = stream.readInt(); // skip NRMS - this.normals = ParseUtils.readFloatArray(stream, (int) (ParseUtils.readUInt32(stream) * 3)); - final int ptyp = stream.readInt(); // skip PTYP - this.faceTypeGroups = ParseUtils.readUInt32Array(stream, (int) ParseUtils.readUInt32(stream)); - stream.readInt(); // skip PCNT - this.faceGroups = ParseUtils.readUInt32Array(stream, (int) ParseUtils.readUInt32(stream)); - stream.readInt(); // skip PVTX - this.faces = ParseUtils.readUInt16Array(stream, (int) ParseUtils.readUInt32(stream)); - stream.readInt(); // skip GNDX - this.vertexGroups = ParseUtils.readUInt8Array(stream, (int) ParseUtils.readUInt32(stream)); - stream.readInt(); // skip MTGC - this.matrixGroups = ParseUtils.readUInt32Array(stream, (int) ParseUtils.readUInt32(stream)); - stream.readInt(); // skip MATS - this.matrixIndices = ParseUtils.readUInt32Array(stream, (int) ParseUtils.readUInt32(stream)); - this.materialId = ParseUtils.readUInt32(stream); - this.selectionGroup = ParseUtils.readUInt32(stream); - this.selectionFlags = ParseUtils.readUInt32(stream); - this.extent.readMdx(stream); - - final long numExtents = ParseUtils.readUInt32(stream); - this.sequenceExtents = new Extent[(int) numExtents]; - for (int i = 0; i < numExtents; i++) { - final Extent extent = new Extent(); - extent.readMdx(stream); - this.sequenceExtents[i] = extent; - } - - stream.readInt(); // skip UVAS - - final long numUVLayers = ParseUtils.readUInt32(stream); - this.uvSets = new float[(int) numUVLayers][]; - for (int i = 0; i < numUVLayers; i++) { - stream.readInt(); // skip UVBS - this.uvSets[i] = ParseUtils.readFloatArray(stream, (int) (ParseUtils.readUInt32(stream) * 2)); - } - } - - @Override - public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException { - ParseUtils.writeUInt32(stream, this.getByteLength()); - ParseUtils.writeWar3ID(stream, VRTX); - ParseUtils.writeUInt32(stream, this.vertices.length / 3); - ParseUtils.writeFloatArray(stream, this.vertices); - ParseUtils.writeWar3ID(stream, NRMS); - ParseUtils.writeUInt32(stream, this.normals.length / 3); - ParseUtils.writeFloatArray(stream, this.normals); - ParseUtils.writeWar3ID(stream, PTYP); - ParseUtils.writeUInt32(stream, this.faceTypeGroups.length); - ParseUtils.writeUInt32Array(stream, this.faceTypeGroups); - ParseUtils.writeWar3ID(stream, PCNT); - ParseUtils.writeUInt32(stream, this.faceGroups.length); - ParseUtils.writeUInt32Array(stream, this.faceGroups); - ParseUtils.writeWar3ID(stream, PVTX); - ParseUtils.writeUInt32(stream, this.faces.length); - ParseUtils.writeUInt16Array(stream, this.faces); - ParseUtils.writeWar3ID(stream, GNDX); - ParseUtils.writeUInt32(stream, this.vertexGroups.length); - ParseUtils.writeUInt8Array(stream, this.vertexGroups); - ParseUtils.writeWar3ID(stream, MTGC); - ParseUtils.writeUInt32(stream, this.matrixGroups.length); - ParseUtils.writeUInt32Array(stream, this.matrixGroups); - ParseUtils.writeWar3ID(stream, MATS); - ParseUtils.writeUInt32(stream, this.matrixIndices.length); - ParseUtils.writeUInt32Array(stream, this.matrixIndices); - ParseUtils.writeUInt32(stream, this.materialId); - ParseUtils.writeUInt32(stream, this.selectionGroup); - ParseUtils.writeUInt32(stream, this.selectionFlags); - this.extent.writeMdx(stream); - ParseUtils.writeUInt32(stream, this.sequenceExtents.length); - - for (final Extent sequenceExtent : this.sequenceExtents) { - sequenceExtent.writeMdx(stream); - } - - ParseUtils.writeWar3ID(stream, UVAS); - ParseUtils.writeUInt32(stream, this.uvSets.length); - for (final float[] uvSet : this.uvSets) { - ParseUtils.writeWar3ID(stream, UVBS); - ParseUtils.writeUInt32(stream, uvSet.length / 2); - ParseUtils.writeFloatArray(stream, uvSet); - } - } - - @Override - public void readMdl(final MdlTokenInputStream stream) { - this.uvSets = new float[0][]; - final List sequenceExtents = new ArrayList<>(); - for (final String token : stream.readBlock()) { - switch (token) { - case MdlUtils.TOKEN_VERTICES: - this.vertices = stream.readVectorArray(new float[stream.readInt() * 3], 3); - break; - case MdlUtils.TOKEN_NORMALS: - this.normals = stream.readVectorArray(new float[stream.readInt() * 3], 3); - break; - case MdlUtils.TOKEN_TVERTICES: - this.uvSets = Arrays.copyOf(this.uvSets, this.uvSets.length + 1); - this.uvSets[this.uvSets.length - 1] = stream.readVectorArray(new float[stream.readInt() * 2], 2); - break; - case MdlUtils.TOKEN_VERTEX_GROUP: { - // Vertex groups are stored in a block with no count, can't allocate the buffer - // yet. - final List vertexGroups = new ArrayList<>(); - for (final String vertexGroup : stream.readBlock()) { - vertexGroups.add(Short.valueOf(vertexGroup)); - } - - this.vertexGroups = new short[vertexGroups.size()]; - int i = 0; - for (final Short vertexGroup : vertexGroups) { - this.vertexGroups[i++] = vertexGroup.shortValue(); - } - } - break; - case MdlUtils.TOKEN_FACES: - // For now hardcoded for triangles, until I see a model with something - // different. - this.faceTypeGroups = new long[] { 4L }; - - stream.readInt(); // number of groups - - final int count = stream.readInt(); - - stream.read(); // { - stream.read(); // Triangles - stream.read(); // { - - this.faces = stream.readUInt16Array(new int[count]); - this.faceGroups = new long[] { count }; - - stream.read(); // } - stream.read(); // } - break; - case MdlUtils.TOKEN_GROUPS: { - final List indices = new ArrayList<>(); - final List groups = new ArrayList<>(); - - stream.readInt(); // matrices count - stream.readInt(); // total indices - - // eslint-disable-next-line no-unused-vars - for (final String matrix : stream.readBlock()) { - int size = 0; - - for (final String index : stream.readBlock()) { - indices.add(Integer.valueOf(index)); - size += 1; - } - groups.add(size); - } - - this.matrixIndices = new long[indices.size()]; - int i = 0; - for (final Integer index : indices) { - this.matrixIndices[i++] = index.intValue(); - } - this.matrixGroups = new long[groups.size()]; - i = 0; - for (final Integer group : groups) { - this.matrixGroups[i++] = group.intValue(); - } - } - break; - case MdlUtils.TOKEN_MINIMUM_EXTENT: - stream.readFloatArray(this.extent.min); - break; - case MdlUtils.TOKEN_MAXIMUM_EXTENT: - stream.readFloatArray(this.extent.max); - break; - case MdlUtils.TOKEN_BOUNDSRADIUS: - this.extent.boundsRadius = stream.readFloat(); - break; - case MdlUtils.TOKEN_ANIM: - final Extent extent = new Extent(); - - for (final String subToken : stream.readBlock()) { - switch (subToken) { - case MdlUtils.TOKEN_MINIMUM_EXTENT: - stream.readFloatArray(extent.min); - break; - case MdlUtils.TOKEN_MAXIMUM_EXTENT: - stream.readFloatArray(extent.max); - break; - case MdlUtils.TOKEN_BOUNDSRADIUS: - extent.boundsRadius = stream.readFloat(); - break; - } - } - - sequenceExtents.add(extent); - break; - case MdlUtils.TOKEN_MATERIAL_ID: - this.materialId = stream.readInt(); - break; - case MdlUtils.TOKEN_SELECTION_GROUP: - this.selectionGroup = stream.readInt(); - break; - case MdlUtils.TOKEN_UNSELECTABLE: - this.selectionFlags = 4; - break; - default: - throw new RuntimeException("Unknown token in Geoset: " + token); - } - } - this.sequenceExtents = sequenceExtents.toArray(new Extent[sequenceExtents.size()]); - } - - @Override - public void writeMdl(final MdlTokenOutputStream stream) { - stream.startBlock(MdlUtils.TOKEN_GEOSET); - - stream.writeVectorArray(MdlUtils.TOKEN_VERTICES, this.vertices, 3); - stream.writeVectorArray(MdlUtils.TOKEN_NORMALS, this.normals, 3); - - for (final float[] uvSet : this.uvSets) { - stream.writeVectorArray(MdlUtils.TOKEN_TVERTICES, uvSet, 2); - } - - stream.startBlock(MdlUtils.TOKEN_VERTEX_GROUP); - for (int i = 0; i < this.vertexGroups.length; i++) { - stream.writeLine(this.vertexGroups[i] + ","); - } - stream.endBlock(); - - // For now hardcoded for triangles, until I see a model with something - // different. - stream.startBlock(MdlUtils.TOKEN_FACES, 1, this.faces.length); - stream.startBlock(MdlUtils.TOKEN_TRIANGLES); - final StringBuffer facesBuffer = new StringBuffer(); - for (final int faceValue : this.faces) { - if (facesBuffer.length() > 0) { - facesBuffer.append(", "); - } - facesBuffer.append(faceValue); - } - stream.writeLine("{ " + facesBuffer.toString() + " },"); - stream.endBlock(); - stream.endBlock(); - - stream.startBlock(MdlUtils.TOKEN_GROUPS, this.matrixGroups.length, this.matrixIndices.length); - int index = 0; - for (final long groupSize : this.matrixGroups) { - stream.writeLongSubArrayAttrib(MdlUtils.TOKEN_MATRICES, this.matrixIndices, index, - (int) (index + groupSize)); - index += groupSize; - } - stream.endBlock(); - - this.extent.writeMdl(stream); - - for (final Extent sequenceExtent : this.sequenceExtents) { - stream.startBlock(MdlUtils.TOKEN_ANIM); - sequenceExtent.writeMdl(stream); - stream.endBlock(); - } - - stream.writeAttribUInt32("MaterialID", this.materialId); - stream.writeAttribUInt32("SelectionGroup", this.selectionGroup); - if (this.selectionFlags == 4) { - stream.writeFlag("Unselectable"); - } - stream.endBlock(); - } - - @Override - public long getByteLength() { - long size = 120 + (this.vertices.length * 4) + (this.normals.length * 4) + (this.faceTypeGroups.length * 4) - + (this.faceGroups.length * 4) + (this.faces.length * 2) + this.vertexGroups.length - + (this.matrixGroups.length * 4) + (this.matrixIndices.length * 4) + (this.sequenceExtents.length * 28); - for (final float[] uvSet : this.uvSets) { - size += 8 + (uvSet.length * 4); - } - return size; - } - - public float[] getVertices() { - return this.vertices; - } - - public float[] getNormals() { - return this.normals; - } - - public long[] getFaceTypeGroups() { - return this.faceTypeGroups; - } - - public long[] getFaceGroups() { - return this.faceGroups; - } - - public int[] getFaces() { - return this.faces; - } - - public short[] getVertexGroups() { - return this.vertexGroups; - } - - public long[] getMatrixGroups() { - return this.matrixGroups; - } - - public long[] getMatrixIndices() { - return this.matrixIndices; - } - - public long getMaterialId() { - return this.materialId; - } - - public long getSelectionGroup() { - return this.selectionGroup; - } - - public long getSelectionFlags() { - return this.selectionFlags; - } - - public Extent getExtent() { - return this.extent; - } - - public Extent[] getSequenceExtents() { - return this.sequenceExtents; - } - - public float[][] getUvSets() { - return this.uvSets; - } - - public void setVertices(final float[] vertices) { - this.vertices = vertices; - } - - public void setNormals(final float[] normals) { - this.normals = normals; - } - - public void setFaceTypeGroups(final long[] faceTypeGroups) { - this.faceTypeGroups = faceTypeGroups; - } - - public void setFaceGroups(final long[] faceGroups) { - this.faceGroups = faceGroups; - } - - public void setFaces(final int[] faces) { - this.faces = faces; - } - - public void setVertexGroups(final short[] vertexGroups) { - this.vertexGroups = vertexGroups; - } - - public void setMatrixGroups(final long[] matrixGroups) { - this.matrixGroups = matrixGroups; - } - - public void setMatrixIndices(final long[] matrixIndices) { - this.matrixIndices = matrixIndices; - } - - public void setMaterialId(final long materialId) { - this.materialId = materialId; - } - - public void setSelectionGroup(final long selectionGroup) { - this.selectionGroup = selectionGroup; - } - - public void setSelectionFlags(final long selectionFlags) { - this.selectionFlags = selectionFlags; - } - - public void setSequenceExtents(final Extent[] sequenceExtents) { - this.sequenceExtents = sequenceExtents; - } - - public void setUvSets(final float[][] uvSets) { - this.uvSets = uvSets; - } - -} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/GeosetAnimation.java b/core/src/com/etheller/warsmash/parsers/mdlx/GeosetAnimation.java deleted file mode 100644 index 6ea4fc1..0000000 --- a/core/src/com/etheller/warsmash/parsers/mdlx/GeosetAnimation.java +++ /dev/null @@ -1,114 +0,0 @@ -package com.etheller.warsmash.parsers.mdlx; - -import java.io.IOException; -import java.util.Iterator; - -import com.etheller.warsmash.util.MdlUtils; -import com.etheller.warsmash.util.ParseUtils; -import com.google.common.io.LittleEndianDataInputStream; -import com.google.common.io.LittleEndianDataOutputStream; - -public class GeosetAnimation extends AnimatedObject { - private float alpha = 1; - private int flags = 0; - private final float[] color = { 1, 1, 1 }; - private int geosetId = -1; - - @Override - public void readMdx(final LittleEndianDataInputStream stream) throws IOException { - final long size = ParseUtils.readUInt32(stream); - - this.alpha = stream.readFloat(); - this.flags = stream.readInt();// ParseUtils.readUInt32(stream); - ParseUtils.readFloatArray(stream, this.color); - this.geosetId = stream.readInt(); - - readTimelines(stream, size - 28); - } - - @Override - public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException { - ParseUtils.writeUInt32(stream, getByteLength()); - stream.writeFloat(this.alpha); - stream.writeInt(this.flags);// ParseUtils.writeUInt32(stream, this.flags); - ParseUtils.writeFloatArray(stream, this.color); - stream.writeInt(this.geosetId); - - writeTimelines(stream); - } - - @Override - public void readMdl(final MdlTokenInputStream stream) throws IOException { - final Iterator blockIterator = readAnimatedBlock(stream); - while (blockIterator.hasNext()) { - final String token = blockIterator.next(); - switch (token) { - case MdlUtils.TOKEN_DROP_SHADOW: - this.flags |= 0x1; - break; - case MdlUtils.TOKEN_STATIC_ALPHA: - this.alpha = stream.readFloat(); - break; - case MdlUtils.TOKEN_ALPHA: - this.readTimeline(stream, AnimationMap.KGAO); - break; - case MdlUtils.TOKEN_STATIC_COLOR: - this.flags |= 0x2; - stream.readColor(this.color); - break; - case MdlUtils.TOKEN_COLOR: - this.flags |= 0x2; - readTimeline(stream, AnimationMap.KGAC); - break; - case MdlUtils.TOKEN_GEOSETID: - this.geosetId = stream.readInt(); - break; - default: - throw new IllegalStateException("Unknown token in GeosetAnimation: " + token); - } - } - } - - @Override - public void writeMdl(final MdlTokenOutputStream stream) throws IOException { - stream.startBlock(MdlUtils.TOKEN_GEOSETANIM); - - if ((this.flags & 0x1) != 0) { - stream.writeFlag(MdlUtils.TOKEN_DROP_SHADOW); - } - - if (!this.writeTimeline(stream, AnimationMap.KGAO)) { - stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_ALPHA, this.alpha); - } - - if ((this.flags & 0x2) != 0) { - if (!this.writeTimeline(stream, AnimationMap.KGAC) - && ((this.color[0] != 0) || (this.color[1] != 0) || (this.color[2] != 0))) { - stream.writeColor(MdlUtils.TOKEN_STATIC_COLOR + " ", this.color); // TODO why the space? - } - } - - if (this.geosetId != -1) { // TODO Retera added -1 check here, why wasn't it there before in JS??? - stream.writeAttrib(MdlUtils.TOKEN_GEOSETID, this.geosetId); - } - - stream.endBlock(); - } - - @Override - public long getByteLength() { - return 28 + super.getByteLength(); - } - - public float[] getColor() { - return this.color; - } - - public float getAlpha() { - return this.alpha; - } - - public int getGeosetId() { - return this.geosetId; - } -} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/Helper.java b/core/src/com/etheller/warsmash/parsers/mdlx/Helper.java deleted file mode 100644 index 6426f28..0000000 --- a/core/src/com/etheller/warsmash/parsers/mdlx/Helper.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.etheller.warsmash.parsers.mdlx; - -import java.io.IOException; - -import com.etheller.warsmash.util.MdlUtils; - -public class Helper extends GenericObject { - - public Helper() { - super(0x0); // NOTE: ghostwolf JS didn't pass the 0x1 flag???? - // ANOTHER NOTE: setting the 0x1 flag causes other fan programs to spam error - // messages - } - - @Override - public void readMdl(final MdlTokenInputStream stream) { - for (final String token : readMdlGeneric(stream)) { - throw new IllegalStateException("Unknown token in Helper: " + token); - } - } - - @Override - public void writeMdl(final MdlTokenOutputStream stream) throws IOException { - stream.startObjectBlock(MdlUtils.TOKEN_HELPER, this.name); - writeGenericHeader(stream); - writeGenericTimelines(stream); - stream.endBlock(); - } -} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/InterpolationType.java b/core/src/com/etheller/warsmash/parsers/mdlx/InterpolationType.java deleted file mode 100644 index 962ff58..0000000 --- a/core/src/com/etheller/warsmash/parsers/mdlx/InterpolationType.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.etheller.warsmash.parsers.mdlx; - -public enum InterpolationType { - DONT_INTERP, LINEAR, BEZIER, HERMITE; - - public static final InterpolationType[] VALUES = values(); - - public boolean tangential() { - return ordinal() > 1; - } -} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/Layer.java b/core/src/com/etheller/warsmash/parsers/mdlx/Layer.java deleted file mode 100644 index a8df76e..0000000 --- a/core/src/com/etheller/warsmash/parsers/mdlx/Layer.java +++ /dev/null @@ -1,244 +0,0 @@ -package com.etheller.warsmash.parsers.mdlx; - -import java.io.IOException; -import java.util.Iterator; - -import com.etheller.warsmash.util.MdlUtils; -import com.etheller.warsmash.util.ParseUtils; -import com.google.common.io.LittleEndianDataInputStream; -import com.google.common.io.LittleEndianDataOutputStream; - -public class Layer extends AnimatedObject { - // 0: none - // 1: transparent - // 2: blend - // 3: additive - // 4: add alpha - // 5: modulate - // 6: modulate 2x - public static enum FilterMode { - NONE("None"), - TRANSPARENT("Transparent"), - BLEND("Blend"), - ADDITIVE("Additive"), - ADDALPHA("AddAlpha"), - MODULATE("Modulate"), - MODULATE2X("Modulate2x"); - - String mdlText; - - FilterMode(final String str) { - this.mdlText = str; - } - - public String getMdlText() { - return this.mdlText; - } - - public static FilterMode fromId(final int id) { - return values()[id]; - } - - public static int nameToId(final String name) { - for (final FilterMode mode : values()) { - if (mode.getMdlText().equals(name)) { - return mode.ordinal(); - } - } - return -1; - } - - @Override - public String toString() { - return getMdlText(); - } - } - - private FilterMode filterMode; - private int flags = 0; - private int textureId = -1; - private int textureAnimationId = -1; - private long coordId = 0; - private float alpha = 1; - - @Override - public void readMdx(final LittleEndianDataInputStream stream) throws IOException { - final long size = ParseUtils.readUInt32(stream); - - this.filterMode = FilterMode.fromId((int) ParseUtils.readUInt32(stream)); - this.flags = stream.readInt(); // UInt32 in JS - this.textureId = stream.readInt(); - this.textureAnimationId = stream.readInt(); - this.coordId = ParseUtils.readUInt32(stream); - this.alpha = stream.readFloat(); - - readTimelines(stream, size - 28); - } - - @Override - public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException { - ParseUtils.writeUInt32(stream, getByteLength()); - ParseUtils.writeUInt32(stream, this.filterMode.ordinal()); - ParseUtils.writeUInt32(stream, this.flags); - stream.writeInt(this.textureId); - stream.writeInt(this.textureAnimationId); - ParseUtils.writeUInt32(stream, this.coordId); - stream.writeFloat(this.alpha); - - writeTimelines(stream); - } - - @Override - public void readMdl(final MdlTokenInputStream stream) throws IOException { - final Iterator iterator = readAnimatedBlock(stream); - while (iterator.hasNext()) { - final String token = iterator.next(); - switch (token) { - case MdlUtils.TOKEN_FILTER_MODE: - this.filterMode = FilterMode.fromId(FilterMode.nameToId(stream.read())); - break; - case MdlUtils.TOKEN_UNSHADED: - this.flags |= 0x1; - break; - case MdlUtils.TOKEN_SPHERE_ENV_MAP: - this.flags |= 0x2; - break; - case MdlUtils.TOKEN_TWO_SIDED: - this.flags |= 0x10; - break; - case MdlUtils.TOKEN_UNFOGGED: - this.flags |= 0x20; - break; - case MdlUtils.TOKEN_NO_DEPTH_TEST: - this.flags |= 0x40; - break; - case MdlUtils.TOKEN_NO_DEPTH_SET: - this.flags |= 0x100; - break; - case MdlUtils.TOKEN_STATIC_TEXTURE_ID: - this.textureId = stream.readInt(); - break; - case MdlUtils.TOKEN_TEXTURE_ID: - readTimeline(stream, AnimationMap.KMTF); - break; - case MdlUtils.TOKEN_TVERTEX_ANIM_ID: - this.textureAnimationId = stream.readInt(); - break; - case MdlUtils.TOKEN_COORD_ID: - this.coordId = stream.readInt(); - break; - case MdlUtils.TOKEN_STATIC_ALPHA: - this.alpha = stream.readFloat(); - break; - case MdlUtils.TOKEN_ALPHA: - readTimeline(stream, AnimationMap.KMTA); - break; - default: - throw new IllegalStateException("Unknown token in Layer: " + token); - } - } - } - - @Override - public void writeMdl(final MdlTokenOutputStream stream) throws IOException { - stream.startBlock(MdlUtils.TOKEN_LAYER); - - stream.writeAttrib(MdlUtils.TOKEN_FILTER_MODE, this.filterMode.getMdlText()); - - if ((this.flags & 0x1) != 0) { - stream.writeFlag(MdlUtils.TOKEN_UNSHADED); - } - - if ((this.flags & 0x2) != 0) { - stream.writeFlag(MdlUtils.TOKEN_SPHERE_ENV_MAP); - } - - if ((this.flags & 0x10) != 0) { - stream.writeFlag(MdlUtils.TOKEN_TWO_SIDED); - } - - if ((this.flags & 0x20) != 0) { - stream.writeFlag(MdlUtils.TOKEN_UNFOGGED); - } - - if ((this.flags & 0x40) != 0) { - stream.writeFlag(MdlUtils.TOKEN_NO_DEPTH_TEST); - } - - if ((this.flags & 0x100) != 0) { - stream.writeFlag(MdlUtils.TOKEN_NO_DEPTH_SET); - } - - if (!writeTimeline(stream, AnimationMap.KMTF)) { - stream.writeAttrib(MdlUtils.TOKEN_STATIC_TEXTURE_ID, this.textureId); - } - - if (this.textureAnimationId != -1) { - stream.writeAttrib(MdlUtils.TOKEN_TVERTEX_ANIM_ID, this.textureAnimationId); - } - - if (this.coordId != 0) { - stream.writeAttribUInt32(MdlUtils.TOKEN_COORD_ID, this.coordId); - } - - if (!writeTimeline(stream, AnimationMap.KMTA) && (this.alpha != 1)) { - stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_ALPHA, this.alpha); - } - - stream.endBlock(); - } - - @Override - public long getByteLength() { - return 28 + super.getByteLength(); - } - - public FilterMode getFilterMode() { - return this.filterMode; - } - - public int getFlags() { - return this.flags; - } - - public int getTextureId() { - return this.textureId; - } - - public int getTextureAnimationId() { - return this.textureAnimationId; - } - - public long getCoordId() { - return this.coordId; - } - - public float getAlpha() { - return this.alpha; - } - - public void setFilterMode(final FilterMode filterMode) { - this.filterMode = filterMode; - } - - public void setFlags(final int flags) { - this.flags = flags; - } - - public void setTextureId(final int textureId) { - this.textureId = textureId; - } - - public void setTextureAnimationId(final int textureAnimationId) { - this.textureAnimationId = textureAnimationId; - } - - public void setCoordId(final long coordId) { - this.coordId = coordId; - } - - public void setAlpha(final float alpha) { - this.alpha = alpha; - } - -} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/Material.java b/core/src/com/etheller/warsmash/parsers/mdlx/Material.java deleted file mode 100644 index 7060a6e..0000000 --- a/core/src/com/etheller/warsmash/parsers/mdlx/Material.java +++ /dev/null @@ -1,141 +0,0 @@ -package com.etheller.warsmash.parsers.mdlx; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -import com.etheller.warsmash.util.MdlUtils; -import com.etheller.warsmash.util.ParseUtils; -import com.etheller.warsmash.util.War3ID; -import com.google.common.io.LittleEndianDataInputStream; -import com.google.common.io.LittleEndianDataOutputStream; - -public class Material implements MdlxBlock, Chunk { - private static final War3ID LAYS = War3ID.fromString("LAYS"); - private int priorityPlane = 0; - private int flags; - private final List layers = new ArrayList<>(); - - @Override - public void readMdx(final LittleEndianDataInputStream stream) throws IOException { - ParseUtils.readUInt32(stream); // Don't care about the size - - this.priorityPlane = stream.readInt();// ParseUtils.readUInt32(stream); - this.flags = stream.readInt();// ParseUtils.readUInt32(stream); - - stream.readInt(); // skip LAYS - - final long layerCount = ParseUtils.readUInt32(stream); - for (int i = 0; i < layerCount; i++) { - final Layer layer = new Layer(); - layer.readMdx(stream); - this.layers.add(layer); - } - } - - @Override - public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException { - ParseUtils.writeUInt32(stream, getByteLength()); - stream.writeInt(this.priorityPlane); // was UInt32 in JS, but I *really* thought I used -1 in a past model - stream.writeInt(this.flags); // UInt32 in JS - ParseUtils.writeWar3ID(stream, LAYS); - ParseUtils.writeUInt32(stream, this.layers.size()); - - for (final Layer layer : this.layers) { - layer.writeMdx(stream); - } - } - - @Override - public void readMdl(final MdlTokenInputStream stream) throws IOException { - for (final String token : stream.readBlock()) { - switch (token) { - case MdlUtils.TOKEN_CONSTANT_COLOR: - this.flags |= 0x1; - break; - case MdlUtils.TOKEN_SORT_PRIMS_NEAR_Z: - this.flags |= 0x8; - break; - case MdlUtils.TOKEN_SORT_PRIMS_FAR_Z: - this.flags |= 0x10; - break; - case MdlUtils.TOKEN_FULL_RESOLUTION: - this.flags |= 0x20; - break; - case MdlUtils.TOKEN_PRIORITY_PLANE: - this.priorityPlane = stream.readInt(); - break; - case MdlUtils.TOKEN_LAYER: { - final Layer layer = new Layer(); - layer.readMdl(stream); - this.layers.add(layer); - break; - } - default: - throw new IllegalStateException("Unknown token in Material: " + token); - } - } - } - - @Override - public void writeMdl(final MdlTokenOutputStream stream) throws IOException { - stream.startBlock(MdlUtils.TOKEN_MATERIAL); - - if ((this.flags & 0x1) != 0) { - stream.writeFlag(MdlUtils.TOKEN_CONSTANT_COLOR); - } - - if ((this.flags & 0x8) != 0) { - stream.writeFlag(MdlUtils.TOKEN_SORT_PRIMS_NEAR_Z); - } - - if ((this.flags & 0x10) != 0) { - stream.writeFlag(MdlUtils.TOKEN_SORT_PRIMS_FAR_Z); - } - - if ((this.flags & 0x20) != 0) { - stream.writeFlag(MdlUtils.TOKEN_FULL_RESOLUTION); - } - - if (this.priorityPlane != 0) { - stream.writeAttrib(MdlUtils.TOKEN_PRIORITY_PLANE, this.priorityPlane); - } - - for (final Layer layer : this.layers) { - layer.writeMdl(stream); - } - - stream.endBlock(); - } - - @Override - public long getByteLength() { - long size = 20; - - for (final Layer layer : this.layers) { - size += layer.getByteLength(); - } - - return size; - } - - public int getPriorityPlane() { - return this.priorityPlane; - } - - public void setPriorityPlane(final int priorityPlane) { - this.priorityPlane = priorityPlane; - } - - public int getFlags() { - return this.flags; - } - - public void setFlags(final int flags) { - this.flags = flags; - } - - public List getLayers() { - return this.layers; - } -} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/MdlTokenInputStream.java b/core/src/com/etheller/warsmash/parsers/mdlx/MdlTokenInputStream.java deleted file mode 100644 index 0049b8e..0000000 --- a/core/src/com/etheller/warsmash/parsers/mdlx/MdlTokenInputStream.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.etheller.warsmash.parsers.mdlx; - -public interface MdlTokenInputStream { - String read(); - - long readUInt32(); - - int readInt(); - - float readFloat(); - - void readIntArray(long[] values); - - float[] readFloatArray(float[] values); // is this same as read keyframe??? - - void readKeyframe(float[] values); - - float[] readVectorArray(float[] array, int vectorLength); - - int[] readUInt16Array(int[] values); - - short[] readUInt8Array(short[] values); - - String peek(); - - // needs crazy generator function behavior that I can call this multiple times - // and it allocates a new iterator that is changing the same underlying - // stream position, and needs nesting of blocks within blocks - // (see crazy transcribed generator in GenericObject, only makes good sense - // in JS) - Iterable readBlock(); - - void readColor(float[] color); - -} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/MdlTokenOutputStream.java b/core/src/com/etheller/warsmash/parsers/mdlx/MdlTokenOutputStream.java deleted file mode 100644 index 511430c..0000000 --- a/core/src/com/etheller/warsmash/parsers/mdlx/MdlTokenOutputStream.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.etheller.warsmash.parsers.mdlx; - -public interface MdlTokenOutputStream { - void writeKeyframe(String prefix, long uInt32Value); - - void writeKeyframe(String prefix, float floatValue); - - void writeKeyframe(String prefix, float[] floatArrayValues); - - void indent(); - - void unindent(); - - void startObjectBlock(String name, String objectName); - - void startBlock(String name, int blockSize); - - void startBlock(String name); - - void writeFlag(String token); - - void writeFlagUInt32(long flag); - - void writeAttrib(String string, int globalSequenceId); - - void writeAttribUInt32(String attribName, long uInt); - - void writeAttrib(String string, String value); - - void writeFloatAttrib(String attribName, float value); - - // if this matches writeAttrib(String,String), - // then remove it - void writeStringAttrib(String attribName, String value); - - void writeFloatArrayAttrib(String attribName, float[] floatArray); - - void writeLongSubArrayAttrib(String attribName, long[] array, int startIndexInclusive, int endIndexExclusive); - - void writeFloatArray(float[] floatArray); - - void writeVectorArray(String token, float[] vectors, int vectorLength); - - void endBlock(); - - void endBlockComma(); - - void writeLine(String string); - - void startBlock(String tokenFaces, int sizeNumberProbably, int length); - - void writeColor(String tokenStaticColor, float[] color); - - void writeArrayAttrib(String tokenAlpha, short[] uint8Array); - - void writeArrayAttrib(String tokenAlpha, int[] uint16Array); - - void writeArrayAttrib(String tokenAlpha, long[] uint32Array); -} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/MdlxBlock.java b/core/src/com/etheller/warsmash/parsers/mdlx/MdlxBlock.java deleted file mode 100644 index 680cbec..0000000 --- a/core/src/com/etheller/warsmash/parsers/mdlx/MdlxBlock.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.etheller.warsmash.parsers.mdlx; - -import java.io.IOException; - -import com.google.common.io.LittleEndianDataInputStream; -import com.google.common.io.LittleEndianDataOutputStream; - -public interface MdlxBlock { - void readMdx(final LittleEndianDataInputStream stream) throws IOException; - - void writeMdx(final LittleEndianDataOutputStream stream) throws IOException; - - void readMdl(final MdlTokenInputStream stream) throws IOException; - - void writeMdl(final MdlTokenOutputStream stream) throws IOException; -} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/MdlxBlockDescriptor.java b/core/src/com/etheller/warsmash/parsers/mdlx/MdlxBlockDescriptor.java deleted file mode 100644 index 2844003..0000000 --- a/core/src/com/etheller/warsmash/parsers/mdlx/MdlxBlockDescriptor.java +++ /dev/null @@ -1,125 +0,0 @@ -package com.etheller.warsmash.parsers.mdlx; - -import com.etheller.warsmash.util.Descriptor; - -public interface MdlxBlockDescriptor extends Descriptor { - - public static final MdlxBlockDescriptor ATTACHMENT = new MdlxBlockDescriptor() { - @Override - public Attachment create() { - return new Attachment(); - } - }; - - public static final MdlxBlockDescriptor BONE = new MdlxBlockDescriptor() { - @Override - public Bone create() { - return new Bone(); - } - }; - - public static final MdlxBlockDescriptor CAMERA = new MdlxBlockDescriptor() { - @Override - public Camera create() { - return new Camera(); - } - }; - - public static final MdlxBlockDescriptor COLLISION_SHAPE = new MdlxBlockDescriptor() { - @Override - public CollisionShape create() { - return new CollisionShape(); - } - }; - - public static final MdlxBlockDescriptor EVENT_OBJECT = new MdlxBlockDescriptor() { - @Override - public EventObject create() { - return new EventObject(); - } - }; - - public static final MdlxBlockDescriptor GEOSET = new MdlxBlockDescriptor() { - @Override - public Geoset create() { - return new Geoset(); - } - }; - - public static final MdlxBlockDescriptor GEOSET_ANIMATION = new MdlxBlockDescriptor() { - @Override - public GeosetAnimation create() { - return new GeosetAnimation(); - } - }; - - public static final MdlxBlockDescriptor HELPER = new MdlxBlockDescriptor() { - @Override - public Helper create() { - return new Helper(); - } - }; - - public static final MdlxBlockDescriptor LIGHT = new MdlxBlockDescriptor() { - @Override - public Light create() { - return new Light(); - } - }; - - public static final MdlxBlockDescriptor LAYER = new MdlxBlockDescriptor() { - @Override - public Layer create() { - return new Layer(); - } - }; - - public static final MdlxBlockDescriptor MATERIAL = new MdlxBlockDescriptor() { - @Override - public Material create() { - return new Material(); - } - }; - - public static final MdlxBlockDescriptor PARTICLE_EMITTER = new MdlxBlockDescriptor() { - @Override - public ParticleEmitter create() { - return new ParticleEmitter(); - } - }; - - public static final MdlxBlockDescriptor PARTICLE_EMITTER2 = new MdlxBlockDescriptor() { - @Override - public ParticleEmitter2 create() { - return new ParticleEmitter2(); - } - }; - - public static final MdlxBlockDescriptor RIBBON_EMITTER = new MdlxBlockDescriptor() { - @Override - public RibbonEmitter create() { - return new RibbonEmitter(); - } - }; - - public static final MdlxBlockDescriptor SEQUENCE = new MdlxBlockDescriptor() { - @Override - public Sequence create() { - return new Sequence(); - } - }; - - public static final MdlxBlockDescriptor TEXTURE = new MdlxBlockDescriptor() { - @Override - public Texture create() { - return new Texture(); - } - }; - - public static final MdlxBlockDescriptor TEXTURE_ANIMATION = new MdlxBlockDescriptor() { - @Override - public TextureAnimation create() { - return new TextureAnimation(); - } - }; -} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/MdlxModel.java b/core/src/com/etheller/warsmash/parsers/mdlx/MdlxModel.java deleted file mode 100644 index f89a61f..0000000 --- a/core/src/com/etheller/warsmash/parsers/mdlx/MdlxModel.java +++ /dev/null @@ -1,744 +0,0 @@ -package com.etheller.warsmash.parsers.mdlx; - -import java.io.BufferedWriter; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; - -import com.badlogic.gdx.utils.StreamUtils; -import com.etheller.warsmash.parsers.mdlx.mdl.GhostwolfTokenInputStream; -import com.etheller.warsmash.parsers.mdlx.mdl.GhostwolfTokenOutputStream; -import com.etheller.warsmash.util.MdlUtils; -import com.etheller.warsmash.util.ParseUtils; -import com.etheller.warsmash.util.War3ID; -import com.google.common.io.LittleEndianDataInputStream; -import com.google.common.io.LittleEndianDataOutputStream; - -/** - * A Warcraft 3 model. Supports loading from and saving to both the binary MDX - * and text MDL file formats. - */ -public class MdlxModel { - // Below, these can't call a function on a string to make their value - // because - // switch/case statements require the value to be compile-time defined in - // order - // to be legal, and it appears to only allow basic binary operators for - // that. - // I would love a clearer way to just type 'MDLX' in a character constant in - // Java for this - private static final int MDLX = ('M' << 24) | ('D' << 16) | ('L' << 8) | ('X');// War3ID.fromString("MDLX").getValue(); - private static final int VERS = ('V' << 24) | ('E' << 16) | ('R' << 8) | ('S');// War3ID.fromString("VERS").getValue(); - private static final int MODL = ('M' << 24) | ('O' << 16) | ('D' << 8) | ('L');// War3ID.fromString("MODL").getValue(); - private static final int SEQS = ('S' << 24) | ('E' << 16) | ('Q' << 8) | ('S');// War3ID.fromString("SEQS").getValue(); - private static final int GLBS = ('G' << 24) | ('L' << 16) | ('B' << 8) | ('S');// War3ID.fromString("GLBS").getValue(); - private static final int MTLS = ('M' << 24) | ('T' << 16) | ('L' << 8) | ('S');// War3ID.fromString("MTLS").getValue(); - private static final int TEXS = ('T' << 24) | ('E' << 16) | ('X' << 8) | ('S');// War3ID.fromString("TEXS").getValue(); - private static final int TXAN = ('T' << 24) | ('X' << 16) | ('A' << 8) | ('N');// War3ID.fromString("TXAN").getValue(); - private static final int GEOS = ('G' << 24) | ('E' << 16) | ('O' << 8) | ('S');// War3ID.fromString("GEOS").getValue(); - private static final int GEOA = ('G' << 24) | ('E' << 16) | ('O' << 8) | ('A');// War3ID.fromString("GEOA").getValue(); - private static final int BONE = ('B' << 24) | ('O' << 16) | ('N' << 8) | ('E');// War3ID.fromString("BONE").getValue(); - private static final int LITE = ('L' << 24) | ('I' << 16) | ('T' << 8) | ('E');// War3ID.fromString("LITE").getValue(); - private static final int HELP = ('H' << 24) | ('E' << 16) | ('L' << 8) | ('P');// War3ID.fromString("HELP").getValue(); - private static final int ATCH = ('A' << 24) | ('T' << 16) | ('C' << 8) | ('H');// War3ID.fromString("ATCH").getValue(); - private static final int PIVT = ('P' << 24) | ('I' << 16) | ('V' << 8) | ('T');// War3ID.fromString("PIVT").getValue(); - private static final int PREM = ('P' << 24) | ('R' << 16) | ('E' << 8) | ('M');// War3ID.fromString("PREM").getValue(); - private static final int PRE2 = ('P' << 24) | ('R' << 16) | ('E' << 8) | ('2');// War3ID.fromString("PRE2").getValue(); - private static final int RIBB = ('R' << 24) | ('I' << 16) | ('B' << 8) | ('B');// War3ID.fromString("RIBB").getValue(); - private static final int CAMS = ('C' << 24) | ('A' << 16) | ('M' << 8) | ('S');// War3ID.fromString("CAMS").getValue(); - private static final int EVTS = ('E' << 24) | ('V' << 16) | ('T' << 8) | ('S');// War3ID.fromString("EVTS").getValue(); - private static final int CLID = ('C' << 24) | ('L' << 16) | ('I' << 8) | ('D');// War3ID.fromString("CLID").getValue(); - private int version = 800; - private String name = ""; - /** - * (Comment copied from Ghostwolf JS) To the best of my knowledge, this should - * always be left empty. This is probably a leftover from the Warcraft 3 beta. - * (WS game note: No, I never saw any animation files in the RoC 2001-2002 Beta. - * So it must be from the Alpha) - * - * @member {string} - */ - private String animationFile = ""; - private final Extent extent = new Extent(); - private long blendTime = 0; - private final List sequences = new ArrayList(); - private final List globalSequences = new ArrayList<>(); - private final List materials = new ArrayList<>(); - private final List textures = new ArrayList<>(); - private final List textureAnimations = new ArrayList<>(); - private final List geosets = new ArrayList<>(); - private final List geosetAnimations = new ArrayList<>(); - private final List bones = new ArrayList<>(); - private final List lights = new ArrayList<>(); - private final List helpers = new ArrayList<>(); - private final List attachments = new ArrayList<>(); - private final List pivotPoints = new ArrayList<>(); - private final List particleEmitters = new ArrayList<>(); - private final List particleEmitters2 = new ArrayList<>(); - private final List ribbonEmitters = new ArrayList<>(); - private final List cameras = new ArrayList<>(); - private final List eventObjects = new ArrayList<>(); - private final List collisionShapes = new ArrayList<>(); - private final List unknownChunks = new ArrayList<>(); - - public MdlxModel(final InputStream buffer) throws IOException { - if (buffer != null) { - // In ghostwolf JS, this function called load() - // which decided whether the buffer was an MDL. - loadMdx(buffer); - } - } - - public MdlxModel() { - } - - public void loadMdx(final InputStream buffer) throws IOException { - final LittleEndianDataInputStream stream = new LittleEndianDataInputStream(buffer); - if (Integer.reverseBytes(stream.readInt()) != MDLX) { - throw new IllegalStateException("WrongMagicNumber"); - } - - while (stream.available() > 0) { - final int tag = Integer.reverseBytes(stream.readInt()); - final long size = ParseUtils.readUInt32(stream); - - switch (tag) { - case VERS: - loadVersionChunk(stream); - break; - case MODL: - loadModelChunk(stream); - break; - case SEQS: - loadStaticObjects(this.sequences, MdlxBlockDescriptor.SEQUENCE, stream, size / 132); - break; - case GLBS: - loadGlobalSequenceChunk(stream, size); - break; - case MTLS: - loadDynamicObjects(this.materials, MdlxBlockDescriptor.MATERIAL, stream, size); - break; - case TEXS: - loadStaticObjects(this.textures, MdlxBlockDescriptor.TEXTURE, stream, size / 268); - break; - case TXAN: - loadDynamicObjects(this.textureAnimations, MdlxBlockDescriptor.TEXTURE_ANIMATION, stream, size); - break; - case GEOS: - loadDynamicObjects(this.geosets, MdlxBlockDescriptor.GEOSET, stream, size); - break; - case GEOA: - loadDynamicObjects(this.geosetAnimations, MdlxBlockDescriptor.GEOSET_ANIMATION, stream, size); - break; - case BONE: - loadDynamicObjects(this.bones, MdlxBlockDescriptor.BONE, stream, size); - break; - case LITE: - loadDynamicObjects(this.lights, MdlxBlockDescriptor.LIGHT, stream, size); - break; - case HELP: - loadDynamicObjects(this.helpers, MdlxBlockDescriptor.HELPER, stream, size); - break; - case ATCH: - loadDynamicObjects(this.attachments, MdlxBlockDescriptor.ATTACHMENT, stream, size); - break; - case PIVT: - loadPivotPointChunk(stream, size); - break; - case PREM: - loadDynamicObjects(this.particleEmitters, MdlxBlockDescriptor.PARTICLE_EMITTER, stream, size); - break; - case PRE2: - loadDynamicObjects(this.particleEmitters2, MdlxBlockDescriptor.PARTICLE_EMITTER2, stream, size); - break; - case RIBB: - loadDynamicObjects(this.ribbonEmitters, MdlxBlockDescriptor.RIBBON_EMITTER, stream, size); - break; - case CAMS: - loadDynamicObjects(this.cameras, MdlxBlockDescriptor.CAMERA, stream, size); - break; - case EVTS: - loadDynamicObjects(this.eventObjects, MdlxBlockDescriptor.EVENT_OBJECT, stream, size); - break; - case CLID: - loadDynamicObjects(this.collisionShapes, MdlxBlockDescriptor.COLLISION_SHAPE, stream, size); - break; - default: - this.unknownChunks.add(new UnknownChunk(stream, size, new War3ID(tag))); - } - } - - } - - private void loadVersionChunk(final LittleEndianDataInputStream stream) throws IOException { - this.version = (int) ParseUtils.readUInt32(stream); - } - - /** - * Restricts us to only be able to parse models on one thread at a time, in - * return for high performance. - */ - private static final byte[] NAME_BYTES_HEAP = new byte[80]; - private static final byte[] ANIMATION_FILE_BYTES_HEAP = new byte[260]; - - private void loadModelChunk(final LittleEndianDataInputStream stream) throws IOException { - this.name = ParseUtils.readString(stream, NAME_BYTES_HEAP); - this.animationFile = ParseUtils.readString(stream, ANIMATION_FILE_BYTES_HEAP); - this.extent.readMdx(stream); - this.blendTime = ParseUtils.readUInt32(stream); - } - - private void loadStaticObjects(final List out, final MdlxBlockDescriptor constructor, - final LittleEndianDataInputStream stream, final long count) throws IOException { - for (int i = 0; i < count; i++) { - final E object = constructor.create(); - - object.readMdx(stream); - - out.add(object); - } - } - - private void loadGlobalSequenceChunk(final LittleEndianDataInputStream stream, final long size) throws IOException { - for (long i = 0, l = size / 4; i < l; i++) { - this.globalSequences.add(ParseUtils.readUInt32(stream)); - } - } - - private void loadDynamicObjects(final List out, - final MdlxBlockDescriptor constructor, final LittleEndianDataInputStream stream, final long size) - throws IOException { - long totalSize = 0; - while (totalSize < size) { - final E object = constructor.create(); - - object.readMdx(stream); - - totalSize += object.getByteLength(); - - out.add(object); - } - } - - private void loadPivotPointChunk(final LittleEndianDataInputStream stream, final long size) throws IOException { - for (long i = 0, l = size / 12; i < l; i++) { - this.pivotPoints.add(ParseUtils.readFloatArray(stream, 3)); - } - } - - public void saveMdx(final OutputStream outputStream) throws IOException { - final LittleEndianDataOutputStream stream = new LittleEndianDataOutputStream(outputStream); - stream.writeInt(Integer.reverseBytes(MDLX)); - this.saveVersionChunk(stream); - this.saveModelChunk(stream); - this.saveStaticObjectChunk(stream, SEQS, this.sequences, 132); - this.saveGlobalSequenceChunk(stream); - this.saveDynamicObjectChunk(stream, MTLS, this.materials); - this.saveStaticObjectChunk(stream, TEXS, this.textures, 268); - this.saveDynamicObjectChunk(stream, TXAN, this.textureAnimations); - this.saveDynamicObjectChunk(stream, GEOS, this.geosets); - this.saveDynamicObjectChunk(stream, GEOA, this.geosetAnimations); - this.saveDynamicObjectChunk(stream, BONE, this.bones); - this.saveDynamicObjectChunk(stream, LITE, this.lights); - this.saveDynamicObjectChunk(stream, HELP, this.helpers); - this.saveDynamicObjectChunk(stream, ATCH, this.attachments); - this.savePivotPointChunk(stream); - this.saveDynamicObjectChunk(stream, PREM, this.particleEmitters); - this.saveDynamicObjectChunk(stream, PRE2, this.particleEmitters2); - this.saveDynamicObjectChunk(stream, RIBB, this.ribbonEmitters); - this.saveDynamicObjectChunk(stream, CAMS, this.cameras); - this.saveDynamicObjectChunk(stream, EVTS, this.eventObjects); - this.saveDynamicObjectChunk(stream, CLID, this.collisionShapes); - - for (final UnknownChunk chunk : this.unknownChunks) { - chunk.writeMdx(stream); - } - } - - private void saveVersionChunk(final LittleEndianDataOutputStream stream) throws IOException { - stream.writeInt(Integer.reverseBytes(VERS)); - ParseUtils.writeUInt32(stream, 4); - ParseUtils.writeUInt32(stream, this.version); - } - - private void saveModelChunk(final LittleEndianDataOutputStream stream) throws IOException { - stream.writeInt(Integer.reverseBytes(MODL)); - ParseUtils.writeUInt32(stream, 372); - final byte[] bytes = this.name.getBytes(ParseUtils.UTF8); - stream.write(bytes); - for (int i = 0; i < (NAME_BYTES_HEAP.length - bytes.length); i++) { - stream.write((byte) 0); - } - final byte[] animationFileBytes = this.animationFile.getBytes(ParseUtils.UTF8); - stream.write(animationFileBytes); - for (int i = 0; i < (ANIMATION_FILE_BYTES_HEAP.length - animationFileBytes.length); i++) { - stream.write((byte) 0); - } - this.extent.writeMdx(stream); - ParseUtils.writeUInt32(stream, this.blendTime); - } - - private void saveStaticObjectChunk(final LittleEndianDataOutputStream stream, final int name, - final List objects, final long size) throws IOException { - if (!objects.isEmpty()) { - stream.writeInt(Integer.reverseBytes(name)); - ParseUtils.writeUInt32(stream, objects.size() * size); - - for (final E object : objects) { - object.writeMdx(stream); - } - } - } - - private void saveGlobalSequenceChunk(final LittleEndianDataOutputStream stream) throws IOException { - if (!this.globalSequences.isEmpty()) { - stream.writeInt(Integer.reverseBytes(GLBS)); - ParseUtils.writeUInt32(stream, this.globalSequences.size() * 4); - - for (final Long globalSequence : this.globalSequences) { - ParseUtils.writeUInt32(stream, globalSequence); - } - } - } - - private void saveDynamicObjectChunk(final LittleEndianDataOutputStream stream, - final int name, final List objects) throws IOException { - if (!objects.isEmpty()) { - stream.writeInt(Integer.reverseBytes(name)); - ParseUtils.writeUInt32(stream, getObjectsByteLength(objects)); - - for (final E object : objects) { - object.writeMdx(stream); - } - } - } - - private void savePivotPointChunk(final LittleEndianDataOutputStream stream) throws IOException { - if (this.pivotPoints.size() > 0) { - stream.writeInt(Integer.reverseBytes(PIVT)); - ParseUtils.writeUInt32(stream, this.pivotPoints.size() * 12); - - for (final float[] pivotPoint : this.pivotPoints) { - ParseUtils.writeFloatArray(stream, pivotPoint); - } - } - } - - public void loadMdl(final InputStream inputStream) throws IOException { - final byte[] array = StreamUtils.copyStreamToByteArray(inputStream); - loadMdl(ByteBuffer.wrap(array)); - } - - public void loadMdl(final ByteBuffer inputStream) throws IOException { - String token; - final MdlTokenInputStream stream = new GhostwolfTokenInputStream(inputStream); - - while ((token = stream.read()) != null) { - switch (token) { - case MdlUtils.TOKEN_VERSION: - this.loadVersionBlock(stream); - break; - case MdlUtils.TOKEN_MODEL: - this.loadModelBlock(stream); - break; - case MdlUtils.TOKEN_SEQUENCES: - this.loadNumberedObjectBlock(this.sequences, MdlxBlockDescriptor.SEQUENCE, MdlUtils.TOKEN_ANIM, stream); - break; - case MdlUtils.TOKEN_GLOBAL_SEQUENCES: - this.loadGlobalSequenceBlock(stream); - break; - case MdlUtils.TOKEN_TEXTURES: - this.loadNumberedObjectBlock(this.textures, MdlxBlockDescriptor.TEXTURE, MdlUtils.TOKEN_BITMAP, stream); - break; - case MdlUtils.TOKEN_MATERIALS: - this.loadNumberedObjectBlock(this.materials, MdlxBlockDescriptor.MATERIAL, MdlUtils.TOKEN_MATERIAL, - stream); - break; - case MdlUtils.TOKEN_TEXTURE_ANIMS: - this.loadNumberedObjectBlock(this.textureAnimations, MdlxBlockDescriptor.TEXTURE_ANIMATION, - MdlUtils.TOKEN_TVERTEX_ANIM, stream); - break; - case MdlUtils.TOKEN_GEOSET: - this.loadObject(this.geosets, MdlxBlockDescriptor.GEOSET, stream); - break; - case MdlUtils.TOKEN_GEOSETANIM: - this.loadObject(this.geosetAnimations, MdlxBlockDescriptor.GEOSET_ANIMATION, stream); - break; - case MdlUtils.TOKEN_BONE: - this.loadObject(this.bones, MdlxBlockDescriptor.BONE, stream); - break; - case MdlUtils.TOKEN_LIGHT: - this.loadObject(this.lights, MdlxBlockDescriptor.LIGHT, stream); - break; - case MdlUtils.TOKEN_HELPER: - this.loadObject(this.helpers, MdlxBlockDescriptor.HELPER, stream); - break; - case MdlUtils.TOKEN_ATTACHMENT: - this.loadObject(this.attachments, MdlxBlockDescriptor.ATTACHMENT, stream); - break; - case MdlUtils.TOKEN_PIVOT_POINTS: - this.loadPivotPointBlock(stream); - break; - case MdlUtils.TOKEN_PARTICLE_EMITTER: - this.loadObject(this.particleEmitters, MdlxBlockDescriptor.PARTICLE_EMITTER, stream); - break; - case MdlUtils.TOKEN_PARTICLE_EMITTER2: - this.loadObject(this.particleEmitters2, MdlxBlockDescriptor.PARTICLE_EMITTER2, stream); - break; - case MdlUtils.TOKEN_RIBBON_EMITTER: - this.loadObject(this.ribbonEmitters, MdlxBlockDescriptor.RIBBON_EMITTER, stream); - break; - case MdlUtils.TOKEN_CAMERA: - this.loadObject(this.cameras, MdlxBlockDescriptor.CAMERA, stream); - break; - case MdlUtils.TOKEN_EVENT_OBJECT: - this.loadObject(this.eventObjects, MdlxBlockDescriptor.EVENT_OBJECT, stream); - break; - case MdlUtils.TOKEN_COLLISION_SHAPE: - this.loadObject(this.collisionShapes, MdlxBlockDescriptor.COLLISION_SHAPE, stream); - break; - default: - throw new IllegalStateException("Unsupported block: " + token); - } - } - } - - private void loadVersionBlock(final MdlTokenInputStream stream) { - for (final String token : stream.readBlock()) { - switch (token) { - case MdlUtils.TOKEN_FORMAT_VERSION: - this.version = stream.readInt(); - break; - default: - throw new IllegalStateException("Unknown token in Version: " + token); - } - } - } - - private void loadModelBlock(final MdlTokenInputStream stream) { - this.name = stream.read(); - for (final String token : stream.readBlock()) { - if (token.startsWith("Num")) { - /*- - * Don't care about the number of things, the arrays will grow as they wish. - * This includes: - * NumGeosets - * NumGeosetAnims - * NumHelpers - * NumLights - * NumBones - * NumAttachments - * NumParticleEmitters - * NumParticleEmitters2 - * NumRibbonEmitters - * NumEvents - */ - stream.read(); - } - else { - switch (token) { - case MdlUtils.TOKEN_BLEND_TIME: - this.blendTime = stream.readUInt32(); - break; - case MdlUtils.TOKEN_MINIMUM_EXTENT: - stream.readFloatArray(this.extent.min); - break; - case MdlUtils.TOKEN_MAXIMUM_EXTENT: - stream.readFloatArray(this.extent.max); - break; - case MdlUtils.TOKEN_BOUNDSRADIUS: - this.extent.boundsRadius = stream.readFloat(); - break; - default: - throw new IllegalStateException("Unknown token in Model: " + token); - } - } - } - } - - private void loadNumberedObjectBlock(final List out, - final MdlxBlockDescriptor constructor, final String name, final MdlTokenInputStream stream) - throws IOException { - stream.read(); // Don't care about the number, the array will grow - - for (final String token : stream.readBlock()) { - if (token.equals(name)) { - final E object = constructor.create(); - - object.readMdl(stream); - - out.add(object); - } - else { - throw new IllegalStateException("Unknown token in " + name + ": " + token); - } - } - } - - private void loadGlobalSequenceBlock(final MdlTokenInputStream stream) { - stream.read(); // Don't care about the number, the array will grow - - for (final String token : stream.readBlock()) { - if (token.equals(MdlUtils.TOKEN_DURATION)) { - this.globalSequences.add(stream.readUInt32()); - } - else { - throw new IllegalStateException("Unknown token in GlobalSequences: " + token); - } - } - } - - private void loadObject(final List out, final MdlxBlockDescriptor descriptor, - final MdlTokenInputStream stream) throws IOException { - final E object = descriptor.create(); - - object.readMdl(stream); - - out.add(object); - } - - private void loadPivotPointBlock(final MdlTokenInputStream stream) { - final int count = stream.readInt(); - - stream.read(); // { - - for (int i = 0; i < count; i++) { - this.pivotPoints.add(stream.readFloatArray(new float[3])); - } - - stream.read(); // } - } - - public void saveMdl(final OutputStream outputStream) throws IOException { - try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream))) { - final MdlTokenOutputStream stream = new GhostwolfTokenOutputStream(writer); - this.saveVersionBlock(stream); - this.saveModelBlock(stream); - this.saveStaticObjectsBlock(stream, MdlUtils.TOKEN_SEQUENCES, this.sequences); - this.saveGlobalSequenceBlock(stream); - this.saveStaticObjectsBlock(stream, MdlUtils.TOKEN_TEXTURES, this.textures); - this.saveStaticObjectsBlock(stream, MdlUtils.TOKEN_MATERIALS, this.materials); - this.saveStaticObjectsBlock(stream, MdlUtils.TOKEN_TEXTURE_ANIMS, this.textureAnimations); - this.saveObjects(stream, this.geosets); - this.saveObjects(stream, this.geosetAnimations); - this.saveObjects(stream, this.bones); - this.saveObjects(stream, this.lights); - this.saveObjects(stream, this.helpers); - this.saveObjects(stream, this.attachments); - this.savePivotPointBlock(stream); - this.saveObjects(stream, this.particleEmitters); - this.saveObjects(stream, this.particleEmitters2); - this.saveObjects(stream, this.ribbonEmitters); - this.saveObjects(stream, this.cameras); - this.saveObjects(stream, this.eventObjects); - this.saveObjects(stream, this.collisionShapes); - } - } - - private void saveVersionBlock(final MdlTokenOutputStream stream) { - stream.startBlock(MdlUtils.TOKEN_VERSION); - stream.writeAttrib(MdlUtils.TOKEN_FORMAT_VERSION, this.version); - stream.endBlock(); - } - - private void saveModelBlock(final MdlTokenOutputStream stream) { - stream.startObjectBlock(MdlUtils.TOKEN_MODEL, this.name); - stream.writeAttribUInt32(MdlUtils.TOKEN_BLEND_TIME, this.blendTime); - this.extent.writeMdl(stream); - stream.endBlock(); - } - - private void saveStaticObjectsBlock(final MdlTokenOutputStream stream, final String name, - final List objects) throws IOException { - if (!objects.isEmpty()) { - stream.startBlock(name, objects.size()); - - for (final MdlxBlock object : objects) { - object.writeMdl(stream); - } - - stream.endBlock(); - } - } - - private void saveGlobalSequenceBlock(final MdlTokenOutputStream stream) { - if (!this.globalSequences.isEmpty()) { - stream.startBlock(MdlUtils.TOKEN_GLOBAL_SEQUENCES, this.globalSequences.size()); - - for (final Long globalSequence : this.globalSequences) { - stream.writeAttribUInt32(MdlUtils.TOKEN_DURATION, globalSequence); - } - - stream.endBlock(); - } - } - - private void saveObjects(final MdlTokenOutputStream stream, final List objects) - throws IOException { - for (final MdlxBlock object : objects) { - object.writeMdl(stream); - } - } - - private void savePivotPointBlock(final MdlTokenOutputStream stream) { - if (!this.pivotPoints.isEmpty()) { - stream.startBlock(MdlUtils.TOKEN_PIVOT_POINTS, this.pivotPoints.size()); - - for (final float[] pivotPoint : this.pivotPoints) { - stream.writeFloatArray(pivotPoint); - } - - stream.endBlock(); - } - } - - private long getByteLength() { - long size = 396; - - size += getStaticObjectsChunkByteLength(this.sequences, 132); - size += this.getStaticObjectsChunkByteLength(this.globalSequences, 4); - size += this.getDynamicObjectsChunkByteLength(this.materials); - size += this.getStaticObjectsChunkByteLength(this.textures, 268); - size += this.getDynamicObjectsChunkByteLength(this.textureAnimations); - size += this.getDynamicObjectsChunkByteLength(this.geosets); - size += this.getDynamicObjectsChunkByteLength(this.geosetAnimations); - size += this.getDynamicObjectsChunkByteLength(this.bones); - size += this.getDynamicObjectsChunkByteLength(this.lights); - size += this.getDynamicObjectsChunkByteLength(this.helpers); - size += this.getDynamicObjectsChunkByteLength(this.attachments); - size += this.getStaticObjectsChunkByteLength(this.pivotPoints, 12); - size += this.getDynamicObjectsChunkByteLength(this.particleEmitters); - size += this.getDynamicObjectsChunkByteLength(this.particleEmitters2); - size += this.getDynamicObjectsChunkByteLength(this.ribbonEmitters); - size += this.getDynamicObjectsChunkByteLength(this.cameras); - size += this.getDynamicObjectsChunkByteLength(this.eventObjects); - size += this.getDynamicObjectsChunkByteLength(this.collisionShapes); - size += this.getObjectsByteLength(this.unknownChunks); - - return size; - } - - private long getObjectsByteLength(final List objects) { - long size = 0; - for (final E object : objects) { - size += object.getByteLength(); - } - return size; - } - - private long getDynamicObjectsChunkByteLength(final List objects) { - if (!objects.isEmpty()) { - return 8 + this.getObjectsByteLength(objects); - } - - return 0; - } - - private long getStaticObjectsChunkByteLength(final List objects, final long size) { - if (!objects.isEmpty()) { - return 8 + (objects.size() * size); - } - - return 0; - } - - public List getGlobalSequences() { - return this.globalSequences; - } - - public List getSequences() { - return this.sequences; - } - - public List getPivotPoints() { - return this.pivotPoints; - } - - public int getVersion() { - return this.version; - } - - public String getName() { - return this.name; - } - - public String getAnimationFile() { - return this.animationFile; - } - - public Extent getExtent() { - return this.extent; - } - - public long getBlendTime() { - return this.blendTime; - } - - public List getMaterials() { - return this.materials; - } - - public List getTextures() { - return this.textures; - } - - public List getTextureAnimations() { - return this.textureAnimations; - } - - public List getGeosets() { - return this.geosets; - } - - public List getGeosetAnimations() { - return this.geosetAnimations; - } - - public List getBones() { - return this.bones; - } - - public List getLights() { - return this.lights; - } - - public List getHelpers() { - return this.helpers; - } - - public List getAttachments() { - return this.attachments; - } - - public List getParticleEmitters() { - return this.particleEmitters; - } - - public List getParticleEmitters2() { - return this.particleEmitters2; - } - - public List getRibbonEmitters() { - return this.ribbonEmitters; - } - - public List getCameras() { - return this.cameras; - } - - public List getEventObjects() { - return this.eventObjects; - } - - public List getCollisionShapes() { - return this.collisionShapes; - } - - public List getUnknownChunks() { - return this.unknownChunks; - } -} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/MdlxTest.java b/core/src/com/etheller/warsmash/parsers/mdlx/MdlxTest.java deleted file mode 100644 index ca2e8da..0000000 --- a/core/src/com/etheller/warsmash/parsers/mdlx/MdlxTest.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.etheller.warsmash.parsers.mdlx; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; - -public class MdlxTest { - - public static void main(final String[] args) { - try (FileInputStream stream = new FileInputStream( - new File("C:\\Users\\micro\\OneDrive\\Documents\\Warcraft III\\Models\\ArcaneEpic13.mdx"))) { - final MdlxModel model = new MdlxModel(stream); - try (FileOutputStream mdlStream = new FileOutputStream(new File( - "C:\\Users\\micro\\OneDrive\\Documents\\Warcraft III\\Models\\Test\\MyOutAutomated.mdl"))) { - - model.saveMdl(mdlStream); - } - } - catch (final FileNotFoundException e) { - e.printStackTrace(); - } - catch (final IOException e) { - e.printStackTrace(); - } - - System.out.println("Created MDL, now reparsing to MDX"); - - try (FileInputStream stream = new FileInputStream( - new File("C:\\Users\\micro\\OneDrive\\Documents\\Warcraft III\\Models\\Test\\MyOutAutomated.mdl"))) { - final MdlxModel model = new MdlxModel(null); - model.loadMdl(stream); - try (FileOutputStream mdlStream = new FileOutputStream(new File( - "C:\\Users\\micro\\OneDrive\\Documents\\Warcraft III\\Models\\Test\\MyOutAutomatedMDX.mdx"))) { - - model.saveMdx(mdlStream); - } - } - catch (final FileNotFoundException e) { - e.printStackTrace(); - } - catch (final IOException e) { - e.printStackTrace(); - } - - try (FileInputStream stream = new FileInputStream( - new File("C:\\Users\\micro\\OneDrive\\Documents\\Warcraft III\\Models\\Test\\MyOutAutomatedMDX.mdx"))) { - final MdlxModel model = new MdlxModel(stream); - try (FileOutputStream mdlStream = new FileOutputStream(new File( - "C:\\Users\\micro\\OneDrive\\Documents\\Warcraft III\\Models\\Test\\MyOutAutomatedMDXBack2MDL.mdl"))) { - - model.saveMdl(mdlStream); - } - } - catch (final FileNotFoundException e) { - e.printStackTrace(); - } - catch (final IOException e) { - e.printStackTrace(); - } - } - -} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/Sequence.java b/core/src/com/etheller/warsmash/parsers/mdlx/Sequence.java deleted file mode 100644 index 9f89374..0000000 --- a/core/src/com/etheller/warsmash/parsers/mdlx/Sequence.java +++ /dev/null @@ -1,170 +0,0 @@ -package com.etheller.warsmash.parsers.mdlx; - -import java.io.IOException; -import java.util.EnumSet; - -import com.etheller.warsmash.util.MdlUtils; -import com.etheller.warsmash.util.ParseUtils; -import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens; -import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; -import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.SecondaryTag; -import com.google.common.io.LittleEndianDataInputStream; -import com.google.common.io.LittleEndianDataOutputStream; - -public class Sequence implements MdlxBlock { - private String name = ""; - private final long[] interval = new long[2]; - private float moveSpeed = 0; - private int flags = 0; - private float rarity = 0; - private long syncPoint = 0; - private final Extent extent = new Extent(); - private final EnumSet primaryTags = EnumSet.noneOf(AnimationTokens.PrimaryTag.class); - private final EnumSet secondaryTags = EnumSet - .noneOf(AnimationTokens.SecondaryTag.class); - - /** - * Restricts us to only be able to parse models on one thread at a time, in - * return for high performance. - */ - private static final byte[] NAME_BYTES_HEAP = new byte[80]; - - @Override - public void readMdx(final LittleEndianDataInputStream stream) throws IOException { - this.name = ParseUtils.readString(stream, NAME_BYTES_HEAP); - ParseUtils.readUInt32Array(stream, this.interval); - this.moveSpeed = stream.readFloat(); - this.flags = (int) ParseUtils.readUInt32(stream); - this.rarity = stream.readFloat(); - this.syncPoint = ParseUtils.readUInt32(stream); - this.extent.readMdx(stream); - populateTags(); - } - - @Override - public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException { - final byte[] bytes = this.name.getBytes(ParseUtils.UTF8); - stream.write(bytes); - for (int i = 0; i < (NAME_BYTES_HEAP.length - bytes.length); i++) { - stream.write((byte) 0); - } - ParseUtils.writeUInt32Array(stream, this.interval); - stream.writeFloat(this.moveSpeed); - ParseUtils.writeUInt32(stream, this.flags); - stream.writeFloat(this.rarity); - ParseUtils.writeUInt32(stream, this.syncPoint); - this.extent.writeMdx(stream); - } - - @Override - public void readMdl(final MdlTokenInputStream stream) { - this.name = stream.read(); - - for (final String token : stream.readBlock()) { - switch (token) { - case MdlUtils.TOKEN_INTERVAL: - stream.readIntArray(this.interval); - break; - case MdlUtils.TOKEN_NONLOOPING: - this.flags = 1; - break; - case MdlUtils.TOKEN_MOVESPEED: - this.moveSpeed = stream.readFloat(); - break; - case MdlUtils.TOKEN_RARITY: - this.rarity = stream.readFloat(); - break; - case MdlUtils.TOKEN_MINIMUM_EXTENT: - stream.readFloatArray(this.extent.min); - break; - case MdlUtils.TOKEN_MAXIMUM_EXTENT: - stream.readFloatArray(this.extent.max); - break; - case MdlUtils.TOKEN_BOUNDSRADIUS: - this.extent.boundsRadius = stream.readFloat(); - break; - default: - throw new IllegalStateException("Unknown token in Sequence \"" + this.name + "\": " + token); - } - } - populateTags(); - } - - @Override - public void writeMdl(final MdlTokenOutputStream stream) { - stream.startObjectBlock(MdlUtils.TOKEN_ANIM, this.name); - stream.writeArrayAttrib(MdlUtils.TOKEN_INTERVAL, this.interval); - - if (this.flags == 1) { - stream.writeFlag(MdlUtils.TOKEN_NONLOOPING); - } - - if (this.moveSpeed != 0) { - stream.writeFloatAttrib(MdlUtils.TOKEN_MOVESPEED, this.moveSpeed); - } - - if (this.rarity != 0) { - stream.writeFloatAttrib(MdlUtils.TOKEN_RARITY, this.rarity); - } - - this.extent.writeMdl(stream); - stream.endBlock(); - } - - private void populateTags() { - this.primaryTags.clear(); - this.secondaryTags.clear(); - TokenLoop: for (final String token : this.name.split("\\s+")) { - final String upperCaseToken = token.toUpperCase(); - for (final PrimaryTag primaryTag : PrimaryTag.values()) { - if (upperCaseToken.equals(primaryTag.name())) { - this.primaryTags.add(primaryTag); - continue TokenLoop; - } - } - for (final SecondaryTag secondaryTag : SecondaryTag.values()) { - if (upperCaseToken.equals(secondaryTag.name())) { - this.secondaryTags.add(secondaryTag); - continue TokenLoop; - } - } - break; - } - } - - public long[] getInterval() { - return this.interval; - } - - public int getFlags() { - return this.flags; - } - - public String getName() { - return this.name; - } - - public float getRarity() { - return this.rarity; - } - - public float getMoveSpeed() { - return this.moveSpeed; - } - - public long getSyncPoint() { - return this.syncPoint; - } - - public Extent getExtent() { - return this.extent; - } - - public EnumSet getPrimaryTags() { - return this.primaryTags; - } - - public EnumSet getSecondaryTags() { - return this.secondaryTags; - } -} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/Texture.java b/core/src/com/etheller/warsmash/parsers/mdlx/Texture.java deleted file mode 100644 index 0b62834..0000000 --- a/core/src/com/etheller/warsmash/parsers/mdlx/Texture.java +++ /dev/null @@ -1,105 +0,0 @@ -package com.etheller.warsmash.parsers.mdlx; - -import java.io.IOException; - -import com.etheller.warsmash.util.MdlUtils; -import com.etheller.warsmash.util.ParseUtils; -import com.google.common.io.LittleEndianDataInputStream; -import com.google.common.io.LittleEndianDataOutputStream; - -public class Texture implements MdlxBlock { - private int replaceableId = 0; - private String path = ""; - private int flags = 0; - - /** - * Restricts us to only be able to parse models on one thread at a time, in - * return for high performance. - */ - private static final byte[] PATH_BYTES_HEAP = new byte[260]; - - @Override - public void readMdx(final LittleEndianDataInputStream stream) throws IOException { - this.replaceableId = (int) ParseUtils.readUInt32(stream); - this.path = ParseUtils.readString(stream, PATH_BYTES_HEAP); - this.flags = (int) ParseUtils.readUInt32(stream); - } - - @Override - public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException { - ParseUtils.writeUInt32(stream, this.replaceableId); - final byte[] bytes = this.path.getBytes(ParseUtils.UTF8); - stream.write(bytes); - for (int i = 0; i < (PATH_BYTES_HEAP.length - bytes.length); i++) { - stream.write((byte) 0); - } - ParseUtils.writeUInt32(stream, this.flags); - } - - @Override - public void readMdl(final MdlTokenInputStream stream) throws IOException { - for (final String token : stream.readBlock()) { - switch (token) { - case MdlUtils.TOKEN_IMAGE: - this.path = stream.read(); - break; - case MdlUtils.TOKEN_REPLACEABLE_ID: - this.replaceableId = stream.readInt(); - break; - case MdlUtils.TOKEN_WRAP_WIDTH: - this.flags |= 0x1; - break; - case MdlUtils.TOKEN_WRAP_HEIGHT: - this.flags |= 0x2; - break; - default: - throw new IllegalStateException("Unknown token in Texture: " + token); - } - } - } - - @Override - public void writeMdl(final MdlTokenOutputStream stream) throws IOException { - stream.startBlock(MdlUtils.TOKEN_BITMAP); - stream.writeStringAttrib(MdlUtils.TOKEN_IMAGE, this.path); - - if (this.replaceableId != 0) { - stream.writeAttrib(MdlUtils.TOKEN_REPLACEABLE_ID, this.replaceableId); - } - - if ((this.flags & 0x1) != 0) { - stream.writeFlag(MdlUtils.TOKEN_WRAP_WIDTH); - } - - if ((this.flags & 0x2) != 0) { - stream.writeFlag(MdlUtils.TOKEN_WRAP_HEIGHT); - } - - stream.endBlock(); - } - - public int getReplaceableId() { - return this.replaceableId; - } - - public String getPath() { - return this.path; - } - - public int getFlags() { - return this.flags; - } - - public void setReplaceableId(final int replaceableId) { - this.replaceableId = replaceableId; - } - - public void setPath(final String path) { - this.path = path; - } - - public void setFlags(final int flags) { - this.flags = flags; - } - -} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/TextureAnimation.java b/core/src/com/etheller/warsmash/parsers/mdlx/TextureAnimation.java deleted file mode 100644 index a5b55c1..0000000 --- a/core/src/com/etheller/warsmash/parsers/mdlx/TextureAnimation.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.etheller.warsmash.parsers.mdlx; - -import java.io.IOException; - -import com.etheller.warsmash.util.MdlUtils; -import com.etheller.warsmash.util.ParseUtils; -import com.google.common.io.LittleEndianDataInputStream; -import com.google.common.io.LittleEndianDataOutputStream; - -public class TextureAnimation extends AnimatedObject { - - @Override - public void readMdx(final LittleEndianDataInputStream stream) throws IOException { - final long size = ParseUtils.readUInt32(stream); - this.readTimelines(stream, size - 4); - } - - @Override - public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException { - ParseUtils.writeUInt32(stream, this.getByteLength()); - this.writeTimelines(stream); - } - - @Override - public void readMdl(final MdlTokenInputStream stream) throws IOException { - for (final String token : stream.readBlock()) { - switch (token) { - case MdlUtils.TOKEN_TRANSLATION: - this.readTimeline(stream, AnimationMap.KTAT); - break; - case MdlUtils.TOKEN_ROTATION: - this.readTimeline(stream, AnimationMap.KTAR); - break; - case MdlUtils.TOKEN_SCALING: - this.readTimeline(stream, AnimationMap.KTAS); - break; - default: - throw new IllegalStateException("Unknown token in TextureAnimation: " + token); - } - } - } - - @Override - public void writeMdl(final MdlTokenOutputStream stream) throws IOException { - stream.startBlock(MdlUtils.TOKEN_TVERTEX_ANIM_SPACE); - this.writeTimeline(stream, AnimationMap.KTAT); - this.writeTimeline(stream, AnimationMap.KTAR); - this.writeTimeline(stream, AnimationMap.KTAS); - stream.endBlock(); - } - - @Override - public long getByteLength() { - return 4 + super.getByteLength(); - } - -} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/TimelineDescriptor.java b/core/src/com/etheller/warsmash/parsers/mdlx/TimelineDescriptor.java deleted file mode 100644 index 1de3e7d..0000000 --- a/core/src/com/etheller/warsmash/parsers/mdlx/TimelineDescriptor.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.etheller.warsmash.parsers.mdlx; - -import com.etheller.warsmash.parsers.mdlx.timeline.FloatArrayTimeline; -import com.etheller.warsmash.parsers.mdlx.timeline.FloatTimeline; -import com.etheller.warsmash.parsers.mdlx.timeline.Timeline; -import com.etheller.warsmash.parsers.mdlx.timeline.UInt32Timeline; - -public interface TimelineDescriptor { - Timeline createTimeline(); - - public static final TimelineDescriptor UINT32_TIMELINE = new TimelineDescriptor() { - @Override - public Timeline createTimeline() { - return new UInt32Timeline(); - } - }; - - public static final TimelineDescriptor FLOAT_TIMELINE = new TimelineDescriptor() { - @Override - public Timeline createTimeline() { - return new FloatTimeline(); - } - }; - - public static final TimelineDescriptor VECTOR3_TIMELINE = new TimelineDescriptor() { - @Override - public Timeline createTimeline() { - return new FloatArrayTimeline(3); - } - }; - - public static final TimelineDescriptor VECTOR4_TIMELINE = new TimelineDescriptor() { - @Override - public Timeline createTimeline() { - return new FloatArrayTimeline(4); - } - }; -} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/UnknownChunk.java b/core/src/com/etheller/warsmash/parsers/mdlx/UnknownChunk.java deleted file mode 100644 index a46fdcb..0000000 --- a/core/src/com/etheller/warsmash/parsers/mdlx/UnknownChunk.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.etheller.warsmash.parsers.mdlx; - -import java.io.IOException; - -import com.etheller.warsmash.util.ParseUtils; -import com.etheller.warsmash.util.War3ID; -import com.google.common.io.LittleEndianDataInputStream; -import com.google.common.io.LittleEndianDataOutputStream; - -public class UnknownChunk implements Chunk { - private final short[] chunk; - private final War3ID tag; - - public UnknownChunk(final LittleEndianDataInputStream stream, final long size, final War3ID tag) - throws IOException { - System.err.println("Loading unknown chunk: " + tag); - this.chunk = ParseUtils.readUInt8Array(stream, (int) size); - this.tag = tag; - } - - public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException { - ParseUtils.writeWar3ID(stream, this.tag); - // Below: Byte.BYTES used because it's mean as a UInt8 array. This is - // not using Short.BYTES, deliberately, despite using a short[] as the - // type for the array. This is a Java problem that did not exist in the original - // JavaScript implementation by Ghostwolf - ParseUtils.writeUInt32(stream, this.chunk.length * Byte.BYTES); - ParseUtils.writeUInt8Array(stream, this.chunk); - } - - @Override - public long getByteLength() { - return 8 + (this.chunk.length * Byte.BYTES); - } -} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatArrayTimeline.java b/core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatArrayTimeline.java deleted file mode 100644 index 6bba03d..0000000 --- a/core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatArrayTimeline.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.etheller.warsmash.parsers.mdlx.timeline; - -import java.io.IOException; - -import com.etheller.warsmash.parsers.mdlx.MdlTokenInputStream; -import com.etheller.warsmash.parsers.mdlx.MdlTokenOutputStream; -import com.etheller.warsmash.util.ParseUtils; -import com.google.common.io.LittleEndianDataInputStream; -import com.google.common.io.LittleEndianDataOutputStream; - -public final class FloatArrayTimeline extends Timeline { - private final int arraySize; - - public FloatArrayTimeline(final int arraySize) { - this.arraySize = arraySize; - } - - @Override - protected int size() { - return this.arraySize; - } - - @Override - protected float[] readMdxValue(final LittleEndianDataInputStream stream) throws IOException { - return ParseUtils.readFloatArray(stream, this.arraySize); - } - - @Override - protected float[] readMdlValue(final MdlTokenInputStream stream) { - final float[] output = new float[this.arraySize]; - stream.readKeyframe(output); - return output; - } - - @Override - protected void writeMdxValue(final LittleEndianDataOutputStream stream, final float[] value) throws IOException { - ParseUtils.writeFloatArray(stream, value); - } - - @Override - protected void writeMdlValue(final MdlTokenOutputStream stream, final String prefix, final float[] value) { - stream.writeKeyframe(prefix, value); - } - - public int getArraySize() { - return this.arraySize; - } - -} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatTimeline.java b/core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatTimeline.java deleted file mode 100644 index 6e23287..0000000 --- a/core/src/com/etheller/warsmash/parsers/mdlx/timeline/FloatTimeline.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.etheller.warsmash.parsers.mdlx.timeline; - -import java.io.IOException; - -import com.etheller.warsmash.parsers.mdlx.MdlTokenInputStream; -import com.etheller.warsmash.parsers.mdlx.MdlTokenOutputStream; -import com.google.common.io.LittleEndianDataInputStream; -import com.google.common.io.LittleEndianDataOutputStream; - -public final class FloatTimeline extends Timeline { - - @Override - protected int size() { - return 1; - } - - @Override - protected float[] readMdxValue(final LittleEndianDataInputStream stream) throws IOException { - return new float[] { stream.readFloat() }; - } - - @Override - protected float[] readMdlValue(final MdlTokenInputStream stream) { - return new float[] { stream.readFloat() }; - } - - @Override - protected void writeMdxValue(final LittleEndianDataOutputStream stream, final float[] value) throws IOException { - stream.writeFloat(value[0]); - } - - @Override - protected void writeMdlValue(final MdlTokenOutputStream stream, final String prefix, final float[] value) { - stream.writeKeyframe(prefix, value[0]); - } - -} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/timeline/UInt32Timeline.java b/core/src/com/etheller/warsmash/parsers/mdlx/timeline/UInt32Timeline.java deleted file mode 100644 index 2d56ee7..0000000 --- a/core/src/com/etheller/warsmash/parsers/mdlx/timeline/UInt32Timeline.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.etheller.warsmash.parsers.mdlx.timeline; - -import java.io.IOException; - -import com.etheller.warsmash.parsers.mdlx.MdlTokenInputStream; -import com.etheller.warsmash.parsers.mdlx.MdlTokenOutputStream; -import com.etheller.warsmash.util.ParseUtils; -import com.google.common.io.LittleEndianDataInputStream; -import com.google.common.io.LittleEndianDataOutputStream; - -public final class UInt32Timeline extends Timeline { - - @Override - protected int size() { - return 1; - } - - @Override - protected long[] readMdxValue(final LittleEndianDataInputStream stream) throws IOException { - return new long[] { ParseUtils.readUInt32(stream) }; - } - - @Override - protected long[] readMdlValue(final MdlTokenInputStream stream) { - return new long[] { stream.readUInt32() }; - } - - @Override - protected void writeMdxValue(final LittleEndianDataOutputStream stream, final long[] uint32) throws IOException { - ParseUtils.writeUInt32(stream, uint32[0]); - } - - @Override - protected void writeMdlValue(final MdlTokenOutputStream stream, final String prefix, final long[] uint32) { - stream.writeKeyframe(prefix, uint32[0]); - } - -} diff --git a/core/src/com/etheller/warsmash/parsers/w3x/War3Map.java b/core/src/com/etheller/warsmash/parsers/w3x/War3Map.java index b888c54..a647f4b 100644 --- a/core/src/com/etheller/warsmash/parsers/w3x/War3Map.java +++ b/core/src/com/etheller/warsmash/parsers/w3x/War3Map.java @@ -3,6 +3,7 @@ package com.etheller.warsmash.parsers.w3x; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.nio.ByteBuffer; import java.nio.channels.SeekableByteChannel; import java.util.Arrays; import java.util.Collection; @@ -119,6 +120,11 @@ public class War3Map implements DataSource { return this.dataSource.has(filepath); } + @Override + public ByteBuffer read(final String path) throws IOException { + return this.dataSource.read(path); + } + @Override public Collection getListfile() { return this.internalMpqContentsDataSource.getListfile(); diff --git a/core/src/com/etheller/warsmash/units/StandardObjectData.java b/core/src/com/etheller/warsmash/units/StandardObjectData.java index 913f982..6cd2129 100644 --- a/core/src/com/etheller/warsmash/units/StandardObjectData.java +++ b/core/src/com/etheller/warsmash/units/StandardObjectData.java @@ -101,6 +101,10 @@ public class StandardObjectData { try { destructableData.readSLK(this.source.getResourceAsStream("Units\\DestructableData.slk")); + final InputStream unitSkin = this.source.getResourceAsStream("Units\\DestructableSkin.txt"); + if (unitSkin != null) { + destructableData.readTXT(unitSkin, true); + } } catch (final IOException e) { throw new RuntimeException(e); @@ -191,6 +195,11 @@ public class StandardObjectData { profile.readTXT(this.source.getResourceAsStream("Units\\ItemAbilityFunc.txt"), true); profile.readTXT(this.source.getResourceAsStream("Units\\ItemAbilityStrings.txt"), true); + final InputStream unitSkin = this.source.getResourceAsStream("Units\\AbilitySkin.txt"); + if (unitSkin != null) { + profile.readTXT(unitSkin, true); + } + abilityData.readSLK(this.source.getResourceAsStream("Units\\AbilityData.slk")); } catch (final IOException e) { diff --git a/core/src/com/etheller/warsmash/util/DataSourceFileHandle.java b/core/src/com/etheller/warsmash/util/DataSourceFileHandle.java index e9ef934..4bc4a5d 100644 --- a/core/src/com/etheller/warsmash/util/DataSourceFileHandle.java +++ b/core/src/com/etheller/warsmash/util/DataSourceFileHandle.java @@ -10,7 +10,7 @@ public class DataSourceFileHandle extends FileHandle { private final DataSource dataSource; public DataSourceFileHandle(final DataSource dataSource, final String path) { - super(path); + super(fixPath(dataSource, path)); this.dataSource = dataSource; } @@ -28,4 +28,14 @@ public class DataSourceFileHandle extends FileHandle { throw new RuntimeException("Failed to load FileHandle from DataSource: " + path()); } } + + private static String fixPath(final DataSource dataSource, String path) { + if (!dataSource.has(path) && (path.toLowerCase().endsWith(".wav") || path.toLowerCase().endsWith(".mp3"))) { + final String otherPossiblePath = path.substring(0, path.lastIndexOf('.')) + ".flac"; + if (dataSource.has(otherPossiblePath)) { + path = otherPossiblePath; + } + } + return path; + } } diff --git a/core/src/com/etheller/warsmash/util/ImageUtils.java b/core/src/com/etheller/warsmash/util/ImageUtils.java index 2f92574..c9c6844 100644 --- a/core/src/com/etheller/warsmash/util/ImageUtils.java +++ b/core/src/com/etheller/warsmash/util/ImageUtils.java @@ -29,14 +29,79 @@ public final class ImageUtils { private static final int BYTES_PER_PIXEL = 4; public static final String DEFAULT_ICON_PATH = "ReplaceableTextures\\CommandButtons\\BTNTemp.blp"; - public static Texture getBLPTexture(final DataSource dataSource, final String path) { - final BufferedImage image = getBLPImage(dataSource, path); - if (image != null) { - return ImageUtils.getTexture(image); + public static Texture getAnyExtensionTexture(final DataSource dataSource, final String path) { + BufferedImage image; + try { + final AnyExtensionImage imageInfo = getAnyExtensionImageFixRGB(dataSource, path, "texture"); + image = imageInfo.getImageData(); + if (image != null) { + return ImageUtils.getTexture(image, imageInfo.isNeedsSRGBFix()); + } + } + catch (final IOException e) { + return null; } return null; } + public static AnyExtensionImage getAnyExtensionImageFixRGB(final DataSource dataSource, final String path, + final String errorType) throws IOException { + if (path.toLowerCase().endsWith(".blp")) { + try (InputStream stream = dataSource.getResourceAsStream(path)) { + if (stream == null) { + final String tgaPath = path.substring(0, path.length() - 4) + ".tga"; + try (final InputStream tgaStream = dataSource.getResourceAsStream(tgaPath)) { + if (tgaStream != null) { + final BufferedImage tgaData = TgaFile.readTGA(tgaPath, tgaStream); + return new AnyExtensionImage(false, tgaData); + } + else { + final String ddsPath = path.substring(0, path.length() - 4) + ".dds"; + try (final InputStream ddsStream = dataSource.getResourceAsStream(ddsPath)) { + if (ddsStream != null) { + final BufferedImage image = ImageIO.read(ddsStream); + return new AnyExtensionImage(false, image); + } + else { + throw new IllegalStateException("Missing " + errorType + ": " + path); + } + } + } + } + } + else { + final BufferedImage image = ImageIO.read(stream); + return new AnyExtensionImage(true, image); + } + } + } + else { + throw new IllegalStateException("Missing " + errorType + ": " + path); + } + } + + public static final class AnyExtensionImage { + private final boolean needsSRGBFix; + private final BufferedImage imageData; + + public AnyExtensionImage(final boolean needsSRGBFix, final BufferedImage imageData) { + this.needsSRGBFix = needsSRGBFix; + this.imageData = imageData; + } + + public BufferedImage getImageData() { + return this.imageData; + } + + public BufferedImage getRGBCorrectImageData() { + return this.needsSRGBFix ? forceBufferedImagesRGB(this.imageData) : this.imageData; + } + + public boolean isNeedsSRGBFix() { + return this.needsSRGBFix; + } + } + public static BufferedImage getBLPImage(final DataSource dataSource, final String path) { try { try (final InputStream resourceAsStream = dataSource.getResourceAsStream(path)) { @@ -64,7 +129,7 @@ public final class ImageUtils { } } - public static Texture getTexture(final BufferedImage image) { + public static Texture getTexture(final BufferedImage image, final boolean sRGBFix) { final int[] pixels = new int[image.getWidth() * image.getHeight()]; image.getRGB(0, 0, image.getWidth(), image.getHeight(), pixels, 0, image.getWidth()); @@ -75,12 +140,12 @@ public final class ImageUtils { // for // RGB - final Pixmap pixmap = new Pixmap(image.getWidth(), image.getHeight(), Format.RGBA8888) { + final Pixmap pixmap = sRGBFix ? new Pixmap(image.getWidth(), image.getHeight(), Format.RGBA8888) { @Override public int getGLInternalFormat() { return GL30.GL_SRGB8_ALPHA8; } - }; + } : new Pixmap(image.getWidth(), image.getHeight(), Format.RGBA8888); for (int y = 0; y < image.getHeight(); y++) { for (int x = 0; x < image.getWidth(); x++) { final int pixel = pixels[(y * image.getWidth()) + x]; diff --git a/core/src/com/etheller/warsmash/viewer5/ModelViewer.java b/core/src/com/etheller/warsmash/viewer5/ModelViewer.java index 8df04fe..730d242 100644 --- a/core/src/com/etheller/warsmash/viewer5/ModelViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/ModelViewer.java @@ -148,6 +148,15 @@ public abstract class ModelViewer { final SolvedPath solved = pathSolver.solve(src, solverParams); finalSrc = solved.getFinalSrc(); + if (!this.dataSource.has(finalSrc)) { + final String ddsPath = finalSrc.substring(0, finalSrc.lastIndexOf('.')) + ".dds"; + if (this.dataSource.has(ddsPath)) { + finalSrc = ddsPath; + } + else { + System.err.println("Attempting to load non-existant file: " + finalSrc); + } + } extension = solved.getExtension(); isFetch = solved.isFetch(); @@ -185,9 +194,6 @@ public abstract class ModelViewer { // TODO this is a synchronous hack, skipped some Ghostwolf code try { - if (!this.dataSource.has(finalSrc)) { - System.err.println("Attempting to load non-existant file: " + finalSrc); - } resource.loadData(this.dataSource.getResourceAsStream(finalSrc), null); } catch (final IOException e) { diff --git a/core/src/com/etheller/warsmash/viewer5/RawOpenGLTextureResource.java b/core/src/com/etheller/warsmash/viewer5/RawOpenGLTextureResource.java index 1bd7311..0d09915 100644 --- a/core/src/com/etheller/warsmash/viewer5/RawOpenGLTextureResource.java +++ b/core/src/com/etheller/warsmash/viewer5/RawOpenGLTextureResource.java @@ -91,7 +91,7 @@ public abstract class RawOpenGLTextureResource extends Texture { final GL20 gl = this.viewer.gl; } - public void update(final BufferedImage image) { + public void update(final BufferedImage image, final boolean sRGBFix) { final GL20 gl = this.viewer.gl; final int imageWidth = image.getWidth(); @@ -129,8 +129,8 @@ public abstract class RawOpenGLTextureResource extends Texture { // GL20.GL_UNSIGNED_BYTE, buffer); // } // else { - gl.glTexImage2D(GL20.GL_TEXTURE_2D, 0, GL30.GL_SRGB8_ALPHA8, imageWidth, imageHeight, 0, GL20.GL_RGBA, - GL20.GL_UNSIGNED_BYTE, buffer); + gl.glTexImage2D(GL20.GL_TEXTURE_2D, 0, sRGBFix ? GL30.GL_SRGB8_ALPHA8 : GL30.GL_RGBA8, imageWidth, imageHeight, + 0, GL20.GL_RGBA, GL20.GL_UNSIGNED_BYTE, buffer); this.width = imageWidth; this.height = imageHeight; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/blp/BlpGdxTexture.java b/core/src/com/etheller/warsmash/viewer5/handlers/blp/BlpGdxTexture.java index 8641e03..a29e6d6 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/blp/BlpGdxTexture.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/blp/BlpGdxTexture.java @@ -29,7 +29,7 @@ public class BlpGdxTexture extends GdxTextureResource { BufferedImage img; try { img = ImageIO.read(src); - setGdxTexture(ImageUtils.getTexture(img)); + setGdxTexture(ImageUtils.getTexture(img, true)); } catch (final IOException e) { throw new RuntimeException(e); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/blp/BlpHandler.java b/core/src/com/etheller/warsmash/viewer5/handlers/blp/BlpHandler.java index 478871f..372ad6a 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/blp/BlpHandler.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/blp/BlpHandler.java @@ -2,8 +2,8 @@ package com.etheller.warsmash.viewer5.handlers.blp; import java.util.ArrayList; -import com.etheller.warsmash.viewer5.ModelViewer; import com.etheller.warsmash.viewer5.HandlerResource; +import com.etheller.warsmash.viewer5.ModelViewer; import com.etheller.warsmash.viewer5.handlers.ResourceHandler; import com.etheller.warsmash.viewer5.handlers.ResourceHandlerConstructionParams; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/blp/BlpTexture.java b/core/src/com/etheller/warsmash/viewer5/handlers/blp/BlpTexture.java index dfd09c5..7ba8968 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/blp/BlpTexture.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/blp/BlpTexture.java @@ -28,7 +28,7 @@ public class BlpTexture extends RawOpenGLTextureResource { BufferedImage img; try { img = ImageIO.read(src); - update(img); + update(img, true); } catch (final IOException e) { throw new RuntimeException(e); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/blp/DdsHandler.java b/core/src/com/etheller/warsmash/viewer5/handlers/blp/DdsHandler.java new file mode 100644 index 0000000..69f5f78 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/blp/DdsHandler.java @@ -0,0 +1,28 @@ +package com.etheller.warsmash.viewer5.handlers.blp; + +import java.util.ArrayList; + +import com.etheller.warsmash.viewer5.HandlerResource; +import com.etheller.warsmash.viewer5.ModelViewer; +import com.etheller.warsmash.viewer5.handlers.ResourceHandler; +import com.etheller.warsmash.viewer5.handlers.ResourceHandlerConstructionParams; + +public class DdsHandler extends ResourceHandler { + + public DdsHandler() { + this.extensions = new ArrayList<>(); + this.extensions.add(new String[] { ".dds", "arrayBuffer" }); + } + + @Override + public boolean load(final ModelViewer modelViewer) { + return true; + } + + @Override + public HandlerResource construct(final ResourceHandlerConstructionParams params) { + return new DdsTexture(params.getViewer(), params.getHandler(), params.getExtension(), params.getPathSolver(), + params.getFetchUrl()); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/blp/DdsTexture.java b/core/src/com/etheller/warsmash/viewer5/handlers/blp/DdsTexture.java new file mode 100644 index 0000000..f33529e --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/blp/DdsTexture.java @@ -0,0 +1,38 @@ +package com.etheller.warsmash.viewer5.handlers.blp; + +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.InputStream; + +import javax.imageio.ImageIO; + +import com.etheller.warsmash.viewer5.ModelViewer; +import com.etheller.warsmash.viewer5.PathSolver; +import com.etheller.warsmash.viewer5.RawOpenGLTextureResource; +import com.etheller.warsmash.viewer5.handlers.ResourceHandler; + +public class DdsTexture extends RawOpenGLTextureResource { + + public DdsTexture(final ModelViewer viewer, final ResourceHandler handler, final String extension, + final PathSolver pathSolver, final String fetchUrl) { + super(viewer, extension, pathSolver, fetchUrl, handler); + } + + @Override + protected void lateLoad() { + + } + + @Override + protected void load(final InputStream src, final Object options) { + BufferedImage img; + try { + img = ImageIO.read(src); + update(img, false); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + } + +} \ No newline at end of file diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/AnimatedObject.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/AnimatedObject.java index ce47a6c..96576b9 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/AnimatedObject.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/AnimatedObject.java @@ -3,23 +3,24 @@ package com.etheller.warsmash.viewer5.handlers.mdx; import java.util.HashMap; import java.util.Map; -import com.etheller.warsmash.parsers.mdlx.timeline.FloatArrayTimeline; -import com.etheller.warsmash.parsers.mdlx.timeline.FloatTimeline; -import com.etheller.warsmash.parsers.mdlx.timeline.Timeline; -import com.etheller.warsmash.parsers.mdlx.timeline.UInt32Timeline; import com.etheller.warsmash.util.War3ID; +import com.hiveworkshop.rms.parsers.mdlx.MdlxAnimatedObject; +import com.hiveworkshop.rms.parsers.mdlx.timeline.MdlxFloatArrayTimeline; +import com.hiveworkshop.rms.parsers.mdlx.timeline.MdlxFloatTimeline; +import com.hiveworkshop.rms.parsers.mdlx.timeline.MdlxTimeline; +import com.hiveworkshop.rms.parsers.mdlx.timeline.MdlxUInt32Timeline; public class AnimatedObject { public MdxModel model; public Map> timelines; public Map variants; - public AnimatedObject(final MdxModel model, final com.etheller.warsmash.parsers.mdlx.AnimatedObject object) { + public AnimatedObject(final MdxModel model, final MdlxAnimatedObject object) { this.model = model; this.timelines = new HashMap<>(); this.variants = new HashMap<>(); - for (final Timeline timeline : object.getTimelines()) { + for (final MdlxTimeline timeline : object.getTimelines()) { this.timelines.put(timeline.getName(), createTypedSd(model, timeline)); } } @@ -126,15 +127,15 @@ public class AnimatedObject { return false; } - private Sd createTypedSd(final MdxModel model, final Timeline timeline) { - if (timeline instanceof UInt32Timeline) { - return new UInt32Sd(model, (UInt32Timeline) timeline); + private Sd createTypedSd(final MdxModel model, final MdlxTimeline timeline) { + if (timeline instanceof MdlxUInt32Timeline) { + return new UInt32Sd(model, (MdlxUInt32Timeline) timeline); } - else if (timeline instanceof FloatTimeline) { - return new ScalarSd(model, (FloatTimeline) timeline); + else if (timeline instanceof MdlxFloatTimeline) { + return new ScalarSd(model, (MdlxFloatTimeline) timeline); } - else if (timeline instanceof FloatArrayTimeline) { - final FloatArrayTimeline faTimeline = (FloatArrayTimeline) timeline; + else if (timeline instanceof MdlxFloatArrayTimeline) { + final MdlxFloatArrayTimeline faTimeline = (MdlxFloatArrayTimeline) timeline; final int arraySize = faTimeline.getArraySize(); if (arraySize == 3) { return new VectorSd(model, faTimeline); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Attachment.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Attachment.java index 0ab5731..96f05b4 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Attachment.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Attachment.java @@ -1,6 +1,7 @@ package com.etheller.warsmash.viewer5.handlers.mdx; -import com.etheller.warsmash.parsers.mdlx.AnimationMap; +import com.hiveworkshop.rms.parsers.mdlx.AnimationMap; +import com.hiveworkshop.rms.parsers.mdlx.MdlxAttachment; public class Attachment extends GenericObject { protected String name; @@ -8,8 +9,7 @@ public class Attachment extends GenericObject { protected final int attachmentId; protected MdxModel internalModel; - public Attachment(final MdxModel model, final com.etheller.warsmash.parsers.mdlx.Attachment attachment, - final int index) { + public Attachment(final MdxModel model, final MdlxAttachment attachment, final int index) { super(model, attachment, index); final String path = attachment.getPath().toLowerCase().replace(".mdl", ".mdx"); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Bone.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Bone.java index 37bac35..eab052c 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Bone.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Bone.java @@ -1,10 +1,12 @@ package com.etheller.warsmash.viewer5.handlers.mdx; +import com.hiveworkshop.rms.parsers.mdlx.MdlxBone; + public class Bone extends GenericObject { private final GeosetAnimation geosetAnimation; - public Bone(final MdxModel model, final com.etheller.warsmash.parsers.mdlx.Bone bone, final int index) { + public Bone(final MdxModel model, final MdlxBone bone, final int index) { super(model, bone, index); GeosetAnimation geosetAnimation = null; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Camera.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Camera.java index 697c138..cfcc6af 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Camera.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Camera.java @@ -1,7 +1,8 @@ package com.etheller.warsmash.viewer5.handlers.mdx; -import com.etheller.warsmash.parsers.mdlx.AnimationMap; import com.etheller.warsmash.util.RenderMathUtils; +import com.hiveworkshop.rms.parsers.mdlx.AnimationMap; +import com.hiveworkshop.rms.parsers.mdlx.MdlxCamera; public class Camera extends AnimatedObject { @@ -12,7 +13,7 @@ public class Camera extends AnimatedObject { public final float nearClippingPlane; public final float[] targetPosition; - public Camera(final MdxModel model, final com.etheller.warsmash.parsers.mdlx.Camera camera) { + public Camera(final MdxModel model, final MdlxCamera camera) { super(model, camera); this.name = camera.getName(); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/CollisionShape.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/CollisionShape.java index a6e1a6a..7044eff 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/CollisionShape.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/CollisionShape.java @@ -7,6 +7,7 @@ import com.badlogic.gdx.math.collision.BoundingBox; import com.badlogic.gdx.math.collision.Ray; import com.etheller.warsmash.util.RenderMathUtils; import com.etheller.warsmash.viewer5.GenericNode; +import com.hiveworkshop.rms.parsers.mdlx.MdlxCollisionShape; public class CollisionShape extends GenericObject { private static Vector3 intersectHeap = new Vector3(); @@ -15,8 +16,7 @@ public class CollisionShape extends GenericObject { private static Ray intersectRayHeap = new Ray(); private Intersectable intersectable; - public CollisionShape(final MdxModel model, final com.etheller.warsmash.parsers.mdlx.CollisionShape object, - final int index) { + public CollisionShape(final MdxModel model, final MdlxCollisionShape object, final int index) { super(model, object, index); final float[][] vertices = object.getVertices(); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectEmitterObject.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectEmitterObject.java index e2cdcef..0b47093 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectEmitterObject.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectEmitterObject.java @@ -20,6 +20,8 @@ import com.etheller.warsmash.viewer5.ModelViewer; import com.etheller.warsmash.viewer5.PathSolver; import com.etheller.warsmash.viewer5.Texture; import com.etheller.warsmash.viewer5.handlers.EmitterObject; +import com.hiveworkshop.rms.parsers.mdlx.MdlxEventObject; +import com.hiveworkshop.rms.parsers.mdlx.MdlxParticleEmitter2; public class EventObjectEmitterObject extends GenericObject implements EmitterObject { private static final class LoadGenericSoundCallback implements LoadGenericCallback { @@ -106,8 +108,7 @@ public class EventObjectEmitterObject extends GenericObject implements EmitterOb */ private boolean ok = false; - public EventObjectEmitterObject(final MdxModel model, - final com.etheller.warsmash.parsers.mdlx.EventObject eventObject, final int index) { + public EventObjectEmitterObject(final MdxModel model, final MdlxEventObject eventObject, final int index) { super(model, eventObject, index); final ModelViewer viewer = model.viewer; @@ -256,8 +257,7 @@ public class EventObjectEmitterObject extends GenericObject implements EmitterOb } final int[] blendModes = FilterMode - .emitterFilterMode(com.etheller.warsmash.parsers.mdlx.ParticleEmitter2.FilterMode - .fromId(getInt(row, "BlendMode"))); + .emitterFilterMode(MdlxParticleEmitter2.FilterMode.fromId(getInt(row, "BlendMode"))); this.blendSrc = blendModes[0]; this.blendDst = blendModes[1]; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/FilterMode.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/FilterMode.java index b29c055..aa82c90 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/FilterMode.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/FilterMode.java @@ -1,6 +1,8 @@ package com.etheller.warsmash.viewer5.handlers.mdx; import com.badlogic.gdx.graphics.GL20; +import com.hiveworkshop.rms.parsers.mdlx.MdlxLayer; +import com.hiveworkshop.rms.parsers.mdlx.MdlxParticleEmitter2; public class FilterMode { private static final int[] ERROR_DEFAULT = new int[] { 0, 0 }; @@ -9,7 +11,7 @@ public class FilterMode { private static final int[] ADDITIVE_ALPHA = new int[] { GL20.GL_SRC_ALPHA, GL20.GL_ONE }; private static final int[] BLEND = new int[] { GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA }; - public static int[] layerFilterMode(final com.etheller.warsmash.parsers.mdlx.Layer.FilterMode filterMode) { + public static int[] layerFilterMode(final MdlxLayer.FilterMode filterMode) { switch (filterMode) { case BLEND: return BLEND; // Blend @@ -26,8 +28,7 @@ public class FilterMode { } } - public static int[] emitterFilterMode( - final com.etheller.warsmash.parsers.mdlx.ParticleEmitter2.FilterMode filterMode) { + public static int[] emitterFilterMode(final MdlxParticleEmitter2.FilterMode filterMode) { switch (filterMode) { case BLEND: return BLEND; // Blend diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GenericObject.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GenericObject.java index 849e85c..f71ac28 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GenericObject.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GenericObject.java @@ -1,7 +1,8 @@ package com.etheller.warsmash.viewer5.handlers.mdx; -import com.etheller.warsmash.parsers.mdlx.AnimationMap; import com.etheller.warsmash.util.RenderMathUtils; +import com.hiveworkshop.rms.parsers.mdlx.AnimationMap; +import com.hiveworkshop.rms.parsers.mdlx.MdlxGenericObject; public class GenericObject extends AnimatedObject implements GenericIndexed { @@ -38,8 +39,7 @@ public class GenericObject extends AnimatedObject implements GenericIndexed { public final boolean hasScaleAnim; public final boolean hasGenericAnim; - public GenericObject(final MdxModel model, final com.etheller.warsmash.parsers.mdlx.GenericObject object, - final int index) { + public GenericObject(final MdxModel model, final MdlxGenericObject object, final int index) { super(model, object); this.index = index; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Geoset.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Geoset.java index 3c0d730..9e8bdd8 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Geoset.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Geoset.java @@ -5,6 +5,7 @@ import java.util.Arrays; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.glutils.ShaderProgram; import com.etheller.warsmash.viewer5.gl.ANGLEInstancedArrays; +import com.hiveworkshop.rms.parsers.mdlx.MdlxGeoset; public class Geoset { public MdxModel model; @@ -25,12 +26,12 @@ public class Geoset { private final int skinStride; private final int boneCountOffsetBytes; public final boolean unselectable; - public final com.etheller.warsmash.parsers.mdlx.Geoset mdlxGeoset; + public final MdlxGeoset mdlxGeoset; public Geoset(final MdxModel model, final int index, final int positionOffset, final int normalOffset, final int uvOffset, final int skinOffset, final int faceOffset, final int vertices, final int elements, final int openGLSkinType, final int skinStride, final int boneCountOffsetBytes, final boolean unselectable, - final com.etheller.warsmash.parsers.mdlx.Geoset mdlxGeoset) { + final MdlxGeoset mdlxGeoset) { this.model = model; this.index = index; this.positionOffset = positionOffset; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GeosetAnimation.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GeosetAnimation.java index 4828ee9..4e8a256 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GeosetAnimation.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GeosetAnimation.java @@ -1,6 +1,7 @@ package com.etheller.warsmash.viewer5.handlers.mdx; -import com.etheller.warsmash.parsers.mdlx.AnimationMap; +import com.hiveworkshop.rms.parsers.mdlx.AnimationMap; +import com.hiveworkshop.rms.parsers.mdlx.MdlxGeosetAnimation; public class GeosetAnimation extends AnimatedObject { @@ -8,8 +9,7 @@ public class GeosetAnimation extends AnimatedObject { private final float[] color; public final int geosetId; - public GeosetAnimation(final MdxModel model, - final com.etheller.warsmash.parsers.mdlx.GeosetAnimation geosetAnimation) { + public GeosetAnimation(final MdxModel model, final MdlxGeosetAnimation geosetAnimation) { super(model, geosetAnimation); final float[] color = geosetAnimation.getColor(); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Helper.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Helper.java index 2cd24b3..ba0133b 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Helper.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Helper.java @@ -1,11 +1,12 @@ package com.etheller.warsmash.viewer5.handlers.mdx; +import com.hiveworkshop.rms.parsers.mdlx.MdlxGenericObject; + /** * An MDX helper. */ public class Helper extends GenericObject { - public Helper(final MdxModel model, final com.etheller.warsmash.parsers.mdlx.GenericObject object, - final int index) { + public Helper(final MdxModel model, final MdlxGenericObject object, final int index) { super(model, object, index); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Layer.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Layer.java index b270535..72dfbc8 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Layer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Layer.java @@ -2,7 +2,8 @@ package com.etheller.warsmash.viewer5.handlers.mdx; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.glutils.ShaderProgram; -import com.etheller.warsmash.parsers.mdlx.AnimationMap; +import com.hiveworkshop.rms.parsers.mdlx.AnimationMap; +import com.hiveworkshop.rms.parsers.mdlx.MdlxLayer; /** * An MDX layer. @@ -26,11 +27,10 @@ public class Layer extends AnimatedObject { public boolean blended; public TextureAnimation textureAnimation; - public Layer(final MdxModel model, final com.etheller.warsmash.parsers.mdlx.Layer layer, final int layerId, - final int priorityPlane) { + public Layer(final MdxModel model, final MdlxLayer layer, final int layerId, final int priorityPlane) { super(model, layer); - final com.etheller.warsmash.parsers.mdlx.Layer.FilterMode filterMode = layer.getFilterMode(); + final MdlxLayer.FilterMode filterMode = layer.getFilterMode(); final int textureAnimationId = layer.getTextureAnimationId(); final GL20 gl = model.viewer.gl; @@ -50,8 +50,8 @@ public class Layer extends AnimatedObject { this.noDepthTest = flags & 0x40; this.noDepthSet = flags & 0x80; - this.depthMaskValue = ((filterMode == com.etheller.warsmash.parsers.mdlx.Layer.FilterMode.NONE) - || (filterMode == com.etheller.warsmash.parsers.mdlx.Layer.FilterMode.TRANSPARENT)); + this.depthMaskValue = ((filterMode == MdlxLayer.FilterMode.NONE) + || (filterMode == MdlxLayer.FilterMode.TRANSPARENT)); this.blendSrc = 0; this.blendDst = 0; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Light.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Light.java index cc1d564..2aaeef8 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Light.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Light.java @@ -1,6 +1,7 @@ package com.etheller.warsmash.viewer5.handlers.mdx; -import com.etheller.warsmash.parsers.mdlx.AnimationMap; +import com.hiveworkshop.rms.parsers.mdlx.AnimationMap; +import com.hiveworkshop.rms.parsers.mdlx.MdlxLight; public class Light extends GenericObject { @@ -11,17 +12,17 @@ public class Light extends GenericObject { private final float[] ambientColor; private final float ambientIntensity; - public Light(final MdxModel model, final com.etheller.warsmash.parsers.mdlx.Light light, final int index) { + public Light(final MdxModel model, final MdlxLight light, final int index) { super(model, light, index); switch (light.getType()) { - case 0: + case OMNIDIRECTIONAL: this.type = Type.OMNIDIRECTIONAL; break; - case 1: + case DIRECTIONAL: this.type = Type.DIRECTIONAL; break; - case 2: + case AMBIENT: this.type = Type.AMBIENT; break; default: diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java index e885d0b..46b1e81 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java @@ -10,7 +10,6 @@ import com.badlogic.gdx.math.Matrix4; import com.badlogic.gdx.math.Quaternion; import com.badlogic.gdx.math.Vector3; import com.badlogic.gdx.math.collision.Ray; -import com.etheller.warsmash.parsers.mdlx.Sequence; import com.etheller.warsmash.util.WarsmashConstants; import com.etheller.warsmash.viewer5.GenericNode; import com.etheller.warsmash.viewer5.ModelInstance; @@ -24,6 +23,7 @@ import com.etheller.warsmash.viewer5.TextureMapper; import com.etheller.warsmash.viewer5.UpdatableObject; import com.etheller.warsmash.viewer5.gl.DataTexture; import com.etheller.warsmash.viewer5.handlers.w3x.DynamicShadowManager; +import com.hiveworkshop.rms.parsers.mdlx.MdlxGeoset; public class MdxComplexInstance extends ModelInstance { private static final float[] visibilityHeap = new float[1]; @@ -764,7 +764,7 @@ public class MdxComplexInstance extends ModelInstance { if (!geoset.unselectable) { geoset.getAlpha(alphaHeap, this.sequence, this.frame, this.counter); if (alphaHeap[0] > 0) { - final com.etheller.warsmash.parsers.mdlx.Geoset mdlxGeoset = geoset.mdlxGeoset; + final MdlxGeoset mdlxGeoset = geoset.mdlxGeoset; if (CollisionShape.intersectRayTriangles(ray, this, mdlxGeoset.getVertices(), mdlxGeoset.getFaces(), 3, intersection)) { return true; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxHandler.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxHandler.java index e2b7ee0..5c94309 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxHandler.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxHandler.java @@ -8,6 +8,7 @@ import com.etheller.warsmash.viewer5.ModelViewer; import com.etheller.warsmash.viewer5.handlers.ModelHandler; import com.etheller.warsmash.viewer5.handlers.ResourceHandlerConstructionParams; import com.etheller.warsmash.viewer5.handlers.blp.BlpHandler; +import com.etheller.warsmash.viewer5.handlers.blp.DdsHandler; import com.etheller.warsmash.viewer5.handlers.tga.TgaHandler; public class MdxHandler extends ModelHandler { @@ -22,6 +23,7 @@ public class MdxHandler extends ModelHandler { @Override public boolean load(final ModelViewer viewer) { viewer.addHandler(new BlpHandler()); + viewer.addHandler(new DdsHandler()); viewer.addHandler(new TgaHandler()); Shaders.complex = viewer.webGL.createShaderProgram(MdxShaders.vsComplex, MdxShaders.fsComplex); @@ -32,14 +34,15 @@ public class MdxHandler extends ModelHandler { Shaders.extendedShadowMap = viewer.webGL.createShaderProgram( "#define EXTENDED_BONES\r\n" + MdxShaders.vsComplex, MdxShaders.fsComplexShadowMap); Shaders.particles = viewer.webGL.createShaderProgram(MdxShaders.vsParticles, MdxShaders.fsParticles); - //Shaders.simple = viewer.webGL.createShaderProgram(MdxShaders.vsSimple, MdxShaders.fsSimple); + // Shaders.simple = viewer.webGL.createShaderProgram(MdxShaders.vsSimple, + // MdxShaders.fsSimple); // Shaders.hd = viewer.webGL.createShaderProgram(MdxShaders.vsHd, MdxShaders.fsHd); // TODO HD reforged // If a shader failed to compile, don't allow the handler to be registered, and // send an error instead. return Shaders.complex.isCompiled() && Shaders.extended.isCompiled() && Shaders.particles.isCompiled() - /* && Shaders.simple.isCompiled() && Shaders.hd.isCompiled() */; + /* && Shaders.simple.isCompiled() && Shaders.hd.isCompiled() */; } @Override diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java index eac8ed4..b314d0c 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java @@ -2,17 +2,36 @@ package com.etheller.warsmash.viewer5.handlers.mdx; import java.io.IOException; import java.io.InputStream; +import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; +import org.apache.commons.compress.utils.IOUtils; + import com.badlogic.gdx.graphics.GL20; -import com.etheller.warsmash.parsers.mdlx.Extent; -import com.etheller.warsmash.parsers.mdlx.MdlxModel; -import com.etheller.warsmash.parsers.mdlx.Sequence; import com.etheller.warsmash.viewer5.ModelInstance; import com.etheller.warsmash.viewer5.ModelViewer; import com.etheller.warsmash.viewer5.PathSolver; import com.etheller.warsmash.viewer5.Texture; +import com.hiveworkshop.rms.parsers.mdlx.MdlxAttachment; +import com.hiveworkshop.rms.parsers.mdlx.MdlxBone; +import com.hiveworkshop.rms.parsers.mdlx.MdlxCamera; +import com.hiveworkshop.rms.parsers.mdlx.MdlxCollisionShape; +import com.hiveworkshop.rms.parsers.mdlx.MdlxEventObject; +import com.hiveworkshop.rms.parsers.mdlx.MdlxExtent; +import com.hiveworkshop.rms.parsers.mdlx.MdlxGeosetAnimation; +import com.hiveworkshop.rms.parsers.mdlx.MdlxHelper; +import com.hiveworkshop.rms.parsers.mdlx.MdlxLayer; +import com.hiveworkshop.rms.parsers.mdlx.MdlxLight; +import com.hiveworkshop.rms.parsers.mdlx.MdlxMaterial; +import com.hiveworkshop.rms.parsers.mdlx.MdlxModel; +import com.hiveworkshop.rms.parsers.mdlx.MdlxParticleEmitter; +import com.hiveworkshop.rms.parsers.mdlx.MdlxParticleEmitter2; +import com.hiveworkshop.rms.parsers.mdlx.MdlxRibbonEmitter; +import com.hiveworkshop.rms.parsers.mdlx.MdlxSequence; +import com.hiveworkshop.rms.parsers.mdlx.MdlxTexture; +import com.hiveworkshop.rms.parsers.mdlx.MdlxTexture.WrapMode; +import com.hiveworkshop.rms.parsers.mdlx.MdlxTextureAnimation; public class MdxModel extends com.etheller.warsmash.viewer5.Model { public boolean reforged = false; @@ -73,7 +92,8 @@ public class MdxModel extends com.etheller.warsmash.viewer5.Model { parser = (MdlxModel) bufferOrParser; } else { - parser = new MdlxModel((InputStream) bufferOrParser); + System.err.println("Wasting memory with conversion from InputStream to buffer in MdxModel"); + parser = new MdlxModel(ByteBuffer.wrap(IOUtils.toByteArray((InputStream) bufferOrParser))); } final ModelViewer viewer = this.viewer; @@ -86,7 +106,7 @@ public class MdxModel extends com.etheller.warsmash.viewer5.Model { this.name = parser.getName(); // Initialize the bounds. - final Extent extent = parser.getExtent(); + final MdlxExtent extent = parser.getExtent(); final float[] min = extent.getMin(); final float[] max = extent.getMax(); for (int i = 0; i < 3; i++) { @@ -97,23 +117,24 @@ public class MdxModel extends com.etheller.warsmash.viewer5.Model { this.bounds.fromExtents(min, max); // Sequences - this.sequences.addAll(parser.getSequences()); + for (final MdlxSequence sequence : parser.getSequences()) { + this.sequences.add(new Sequence(sequence)); + } // Global sequences this.globalSequences.addAll(parser.getGlobalSequences()); // Texture animations - for (final com.etheller.warsmash.parsers.mdlx.TextureAnimation textureAnimation : parser - .getTextureAnimations()) { + for (final MdlxTextureAnimation textureAnimation : parser.getTextureAnimations()) { this.textureAnimations.add(new TextureAnimation(this, textureAnimation)); } // Materials int layerId = 0; - for (final com.etheller.warsmash.parsers.mdlx.Material material : parser.getMaterials()) { + for (final MdlxMaterial material : parser.getMaterials()) { final List layers = new ArrayList<>(); - for (final com.etheller.warsmash.parsers.mdlx.Layer layer : material.getLayers()) { + for (final MdlxLayer layer : material.getLayers()) { final Layer vLayer = new Layer(this, layer, layerId++, material.getPriorityPlane()); layers.add(vLayer); @@ -139,10 +160,10 @@ public class MdxModel extends com.etheller.warsmash.viewer5.Model { final GL20 gl = viewer.gl; // Textures. - for (final com.etheller.warsmash.parsers.mdlx.Texture texture : parser.getTextures()) { + for (final MdlxTexture texture : parser.getTextures()) { String path = texture.getPath(); final int replaceableId = texture.getReplaceableId(); - final int flags = texture.getFlags(); + final WrapMode wrapMode = texture.getWrapMode(); if (replaceableId != 0) { // TODO This uses dumb, stupid, terrible, no-good hardcoded replaceable IDs @@ -163,11 +184,11 @@ public class MdxModel extends com.etheller.warsmash.viewer5.Model { final Texture viewerTexture = (Texture) viewer.load(path, pathSolver, solverParams); // When the texture will load, it will apply its wrap modes. - if ((flags & 0x1) != 0) { + if (wrapMode.isWrapWidth()) { viewerTexture.setWrapS(true); } - if ((flags & 0x2) != 0) { + if (wrapMode.isWrapHeight()) { viewerTexture.setWrapT(true); } @@ -176,7 +197,7 @@ public class MdxModel extends com.etheller.warsmash.viewer5.Model { } // Geoset animations - for (final com.etheller.warsmash.parsers.mdlx.GeosetAnimation geosetAnimation : parser.getGeosetAnimations()) { + for (final MdlxGeosetAnimation geosetAnimation : parser.getGeosetAnimations()) { this.geosetAnimations.add(new GeosetAnimation(this, geosetAnimation)); } @@ -189,53 +210,52 @@ public class MdxModel extends com.etheller.warsmash.viewer5.Model { int objectId = 0; // Bones - for (final com.etheller.warsmash.parsers.mdlx.Bone bone : parser.getBones()) { + for (final MdlxBone bone : parser.getBones()) { this.bones.add(new Bone(this, bone, objectId++)); } // Lights - for (final com.etheller.warsmash.parsers.mdlx.Light light : parser.getLights()) { + for (final MdlxLight light : parser.getLights()) { this.lights.add(new Light(this, light, objectId++)); } // Helpers - for (final com.etheller.warsmash.parsers.mdlx.Helper helper : parser.getHelpers()) { + for (final MdlxHelper helper : parser.getHelpers()) { this.helpers.add(new Helper(this, helper, objectId++)); } // Attachments - for (final com.etheller.warsmash.parsers.mdlx.Attachment attachment : parser.getAttachments()) { + for (final MdlxAttachment attachment : parser.getAttachments()) { this.attachments.add(new Attachment(this, attachment, objectId++)); } // Particle Emitters - for (final com.etheller.warsmash.parsers.mdlx.ParticleEmitter particleEmitter : parser.getParticleEmitters()) { + for (final MdlxParticleEmitter particleEmitter : parser.getParticleEmitters()) { this.particleEmitters.add(new ParticleEmitterObject(this, particleEmitter, objectId++)); } // Particle Emitters 2 - for (final com.etheller.warsmash.parsers.mdlx.ParticleEmitter2 particleEmitter2 : parser - .getParticleEmitters2()) { + for (final MdlxParticleEmitter2 particleEmitter2 : parser.getParticleEmitters2()) { this.particleEmitters2.add(new ParticleEmitter2Object(this, particleEmitter2, objectId++)); } // Ribbon emitters - for (final com.etheller.warsmash.parsers.mdlx.RibbonEmitter ribbonEmitter : parser.getRibbonEmitters()) { + for (final MdlxRibbonEmitter ribbonEmitter : parser.getRibbonEmitters()) { this.ribbonEmitters.add(new RibbonEmitterObject(this, ribbonEmitter, objectId++)); } // Camera - for (final com.etheller.warsmash.parsers.mdlx.Camera camera : parser.getCameras()) { + for (final MdlxCamera camera : parser.getCameras()) { this.cameras.add(new Camera(this, camera)); } // Event objects - for (final com.etheller.warsmash.parsers.mdlx.EventObject eventObject : parser.getEventObjects()) { + for (final MdlxEventObject eventObject : parser.getEventObjects()) { this.eventObjects.add(new EventObjectEmitterObject(this, eventObject, objectId++)); } // Collision shapes - for (final com.etheller.warsmash.parsers.mdlx.CollisionShape collisionShape : parser.getCollisionShapes()) { + for (final MdlxCollisionShape collisionShape : parser.getCollisionShapes()) { this.collisionShapes.add(new CollisionShape(this, collisionShape, objectId++)); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ParticleEmitter2Object.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ParticleEmitter2Object.java index faf2085..a771836 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ParticleEmitter2Object.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ParticleEmitter2Object.java @@ -1,9 +1,11 @@ package com.etheller.warsmash.viewer5.handlers.mdx; -import com.etheller.warsmash.parsers.mdlx.AnimationMap; import com.etheller.warsmash.util.WarsmashConstants; import com.etheller.warsmash.viewer5.Texture; import com.etheller.warsmash.viewer5.handlers.EmitterObject; +import com.hiveworkshop.rms.parsers.mdlx.AnimationMap; +import com.hiveworkshop.rms.parsers.mdlx.MdlxParticleEmitter2; +import com.hiveworkshop.rms.parsers.mdlx.MdlxParticleEmitter2.HeadOrTail; public class ParticleEmitter2Object extends GenericObject implements EmitterObject { public float width; @@ -33,8 +35,7 @@ public class ParticleEmitter2Object extends GenericObject implements EmitterObje public int blendDst; public int priorityPlane; - public ParticleEmitter2Object(final MdxModel model, - final com.etheller.warsmash.parsers.mdlx.ParticleEmitter2 emitter, final int index) { + public ParticleEmitter2Object(final MdxModel model, final MdlxParticleEmitter2 emitter, final int index) { super(model, emitter, index); this.width = emitter.getWidth(); @@ -68,10 +69,10 @@ public class ParticleEmitter2Object extends GenericObject implements EmitterObje this.replaceableId = emitter.getReplaceableId(); - final long headOrTail = emitter.getHeadOrTail(); + final HeadOrTail headOrTail = emitter.getHeadOrTail(); - this.head = ((headOrTail == 0) || (headOrTail == 2)); - this.tail = ((headOrTail == 1) || (headOrTail == 2)); + this.head = headOrTail.isIncludesHead(); + this.tail = headOrTail.isIncludesTail(); this.cellWidth = 1f / emitter.getColumns(); this.cellHeight = 1f / emitter.getRows(); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ParticleEmitterObject.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ParticleEmitterObject.java index 78a53be..66822c1 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ParticleEmitterObject.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ParticleEmitterObject.java @@ -2,8 +2,9 @@ package com.etheller.warsmash.viewer5.handlers.mdx; import java.util.Locale; -import com.etheller.warsmash.parsers.mdlx.AnimationMap; import com.etheller.warsmash.viewer5.handlers.EmitterObject; +import com.hiveworkshop.rms.parsers.mdlx.AnimationMap; +import com.hiveworkshop.rms.parsers.mdlx.MdlxParticleEmitter; public class ParticleEmitterObject extends GenericObject implements EmitterObject { public MdxModel internalModel; @@ -22,8 +23,7 @@ public class ParticleEmitterObject extends GenericObject implements EmitterObjec */ public boolean ok = false; - public ParticleEmitterObject(final MdxModel model, final com.etheller.warsmash.parsers.mdlx.ParticleEmitter emitter, - final int index) { + public ParticleEmitterObject(final MdxModel model, final MdlxParticleEmitter emitter, final int index) { super(model, emitter, index); this.internalModel = (MdxModel) model.viewer.load( diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/QuaternionSd.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/QuaternionSd.java index fe9cf8b..a50cf96 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/QuaternionSd.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/QuaternionSd.java @@ -1,12 +1,12 @@ package com.etheller.warsmash.viewer5.handlers.mdx; -import com.etheller.warsmash.parsers.mdlx.timeline.Timeline; import com.etheller.warsmash.util.Interpolator; import com.etheller.warsmash.util.RenderMathUtils; +import com.hiveworkshop.rms.parsers.mdlx.timeline.MdlxTimeline; public class QuaternionSd extends Sd { - public QuaternionSd(final MdxModel model, final Timeline timeline) { + public QuaternionSd(final MdxModel model, final MdlxTimeline timeline) { super(model, timeline, SdArrayDescriptor.FLOAT_ARRAY); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/RibbonEmitterObject.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/RibbonEmitterObject.java index fe07765..ef18c46 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/RibbonEmitterObject.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/RibbonEmitterObject.java @@ -1,7 +1,8 @@ package com.etheller.warsmash.viewer5.handlers.mdx; -import com.etheller.warsmash.parsers.mdlx.AnimationMap; import com.etheller.warsmash.viewer5.handlers.EmitterObject; +import com.hiveworkshop.rms.parsers.mdlx.AnimationMap; +import com.hiveworkshop.rms.parsers.mdlx.MdlxRibbonEmitter; public class RibbonEmitterObject extends GenericObject implements EmitterObject { public Layer layer; @@ -23,8 +24,7 @@ public class RibbonEmitterObject extends GenericObject implements EmitterObject */ public boolean ok = true; - public RibbonEmitterObject(final MdxModel model, final com.etheller.warsmash.parsers.mdlx.RibbonEmitter emitter, - final int index) { + public RibbonEmitterObject(final MdxModel model, final MdlxRibbonEmitter emitter, final int index) { super(model, emitter, index); this.layer = model.getMaterials().get(emitter.getMaterialId()).layers.get(0); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ScalarSd.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ScalarSd.java index d4f7e82..5ea5036 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ScalarSd.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/ScalarSd.java @@ -1,11 +1,11 @@ package com.etheller.warsmash.viewer5.handlers.mdx; -import com.etheller.warsmash.parsers.mdlx.timeline.Timeline; import com.etheller.warsmash.util.RenderMathUtils; +import com.hiveworkshop.rms.parsers.mdlx.timeline.MdlxTimeline; public class ScalarSd extends Sd { - public ScalarSd(final MdxModel model, final Timeline timeline) { + public ScalarSd(final MdxModel model, final MdlxTimeline timeline) { super(model, timeline, SdArrayDescriptor.FLOAT_ARRAY); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Sd.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Sd.java index 2633c09..baf09f6 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Sd.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Sd.java @@ -5,9 +5,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import com.etheller.warsmash.parsers.mdlx.Sequence; -import com.etheller.warsmash.parsers.mdlx.timeline.Timeline; import com.etheller.warsmash.util.War3ID; +import com.hiveworkshop.rms.parsers.mdlx.timeline.MdlxTimeline; public abstract class Sd { public MdxModel model; @@ -85,7 +84,7 @@ public abstract class Sd { } - public Sd(final MdxModel model, final Timeline timeline, final SdArrayDescriptor arrayDescriptor) { + public Sd(final MdxModel model, final MdlxTimeline timeline, final SdArrayDescriptor arrayDescriptor) { final List globalSequences = model.getGlobalSequences(); final int globalSequenceId = timeline.getGlobalSequenceId(); final Integer forcedInterp = forcedInterpMap.get(timeline.getName()); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SdSequence.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SdSequence.java index 04780d0..4708c73 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SdSequence.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SdSequence.java @@ -3,9 +3,9 @@ package com.etheller.warsmash.viewer5.handlers.mdx; import java.util.ArrayList; import java.util.Arrays; -import com.etheller.warsmash.parsers.mdlx.AnimationMap; -import com.etheller.warsmash.parsers.mdlx.timeline.Timeline; import com.etheller.warsmash.util.ParseUtils; +import com.hiveworkshop.rms.parsers.mdlx.AnimationMap; +import com.hiveworkshop.rms.parsers.mdlx.timeline.MdlxTimeline; public final class SdSequence { private static boolean INJECT_FRAMES_GHOSTWOLF_STYLE = false; @@ -19,7 +19,7 @@ public final class SdSequence { public TYPE[] outTans; public boolean constant; - public SdSequence(final Sd sd, final long start, final long end, final Timeline timeline, + public SdSequence(final Sd sd, final long start, final long end, final MdlxTimeline timeline, final boolean isGlobalSequence, final SdArrayDescriptor arrayDescriptor) { this.sd = sd; this.start = start; @@ -136,22 +136,22 @@ public final class SdSequence { this.outTans = outTansBuilder.toArray(arrayDescriptor.create(outTansBuilder.size())); } - private TYPE[] getValues(final Timeline timeline) { + private TYPE[] getValues(final MdlxTimeline timeline) { final TYPE[] values = timeline.getValues(); return fixTimelineArray(timeline, values); } - private TYPE[] getOutTans(final Timeline timeline) { + private TYPE[] getOutTans(final MdlxTimeline timeline) { final TYPE[] outTans = timeline.getOutTans(); return fixTimelineArray(timeline, outTans); } - private TYPE[] getInTans(final Timeline timeline) { + private TYPE[] getInTans(final MdlxTimeline timeline) { final TYPE[] inTans = timeline.getInTans(); return fixTimelineArray(timeline, inTans); } - private TYPE[] fixTimelineArray(final Timeline timeline, final TYPE[] values) { + private TYPE[] fixTimelineArray(final MdlxTimeline timeline, final TYPE[] values) { if (values == null) { return null; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Sequence.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Sequence.java new file mode 100644 index 0000000..1de9dbe --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Sequence.java @@ -0,0 +1,78 @@ +package com.etheller.warsmash.viewer5.handlers.mdx; + +import java.util.EnumSet; + +import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens; +import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; +import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.SecondaryTag; +import com.hiveworkshop.rms.parsers.mdlx.MdlxExtent; +import com.hiveworkshop.rms.parsers.mdlx.MdlxSequence; + +public class Sequence { + private final MdlxSequence sequence; + private final EnumSet primaryTags = EnumSet.noneOf(AnimationTokens.PrimaryTag.class); + private final EnumSet secondaryTags = EnumSet + .noneOf(AnimationTokens.SecondaryTag.class); + + public Sequence(final MdlxSequence sequence) { + this.sequence = sequence; + populateTags(); + } + + private void populateTags() { + this.primaryTags.clear(); + this.secondaryTags.clear(); + TokenLoop: for (final String token : this.sequence.name.split("\\s+")) { + final String upperCaseToken = token.toUpperCase(); + for (final PrimaryTag primaryTag : PrimaryTag.values()) { + if (upperCaseToken.equals(primaryTag.name())) { + this.primaryTags.add(primaryTag); + continue TokenLoop; + } + } + for (final SecondaryTag secondaryTag : SecondaryTag.values()) { + if (upperCaseToken.equals(secondaryTag.name())) { + this.secondaryTags.add(secondaryTag); + continue TokenLoop; + } + } + break; + } + } + + public String getName() { + return this.sequence.getName(); + } + + public long[] getInterval() { + return this.sequence.getInterval(); + } + + public float getMoveSpeed() { + return this.sequence.getMoveSpeed(); + } + + public int getFlags() { + return this.sequence.getFlags(); + } + + public float getRarity() { + return this.sequence.getRarity(); + } + + public long getSyncPoint() { + return this.sequence.getSyncPoint(); + } + + public MdlxExtent getExtent() { + return this.sequence.getExtent(); + } + + public EnumSet getPrimaryTags() { + return this.primaryTags; + } + + public EnumSet getSecondaryTags() { + return this.secondaryTags; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SetupGeosets.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SetupGeosets.java index fe5b4d7..76485b2 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SetupGeosets.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SetupGeosets.java @@ -6,14 +6,14 @@ import java.util.List; import com.badlogic.gdx.graphics.GL20; import com.etheller.warsmash.util.RenderMathUtils; +import com.hiveworkshop.rms.parsers.mdlx.MdlxGeoset; public class SetupGeosets { private static final int NORMAL_BATCH = 0; private static final int EXTENDED_BATCH = 1; private static final int REFORGED_BATCH = 2; - public static void setupGeosets(final MdxModel model, final List geosets, - final boolean bigNodeSpace) { + public static void setupGeosets(final MdxModel model, final List geosets, final boolean bigNodeSpace) { if (geosets.size() > 0) { final GL20 gl = model.viewer.gl; int positionBytes = 0; @@ -30,7 +30,7 @@ public class SetupGeosets { final int extendedBatchBoneCountOffsetBytes = bigNodeSpace ? 32 : 8; for (int i = 0, l = geosets.size(); i < l; i++) { - final com.etheller.warsmash.parsers.mdlx.Geoset geoset = geosets.get(i); + final MdlxGeoset geoset = geosets.get(i); if (true /* geoset.getLod() == 0 */) { final int vertices = geoset.getVertices().length / 3; @@ -84,7 +84,7 @@ public class SetupGeosets { gl.glBufferData(GL20.GL_ELEMENT_ARRAY_BUFFER, faceBytes, null, GL20.GL_STATIC_DRAW); for (int i = 0, l = geosets.size(); i < l; i++) { - final com.etheller.warsmash.parsers.mdlx.Geoset geoset = geosets.get(i); + final MdlxGeoset geoset = geosets.get(i); final int batchType = batchTypes[i]; if (true /* geoset.lod == 0 */) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/TextureAnimation.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/TextureAnimation.java index 0cdafb7..8e6fb2b 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/TextureAnimation.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/TextureAnimation.java @@ -1,12 +1,12 @@ package com.etheller.warsmash.viewer5.handlers.mdx; -import com.etheller.warsmash.parsers.mdlx.AnimationMap; import com.etheller.warsmash.util.RenderMathUtils; +import com.hiveworkshop.rms.parsers.mdlx.AnimationMap; +import com.hiveworkshop.rms.parsers.mdlx.MdlxTextureAnimation; public class TextureAnimation extends AnimatedObject { - public TextureAnimation(final MdxModel model, - final com.etheller.warsmash.parsers.mdlx.TextureAnimation textureAnimation) { + public TextureAnimation(final MdxModel model, final MdlxTextureAnimation textureAnimation) { super(model, textureAnimation); this.addVariants(AnimationMap.KTAT.getWar3id(), "translation"); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/UInt32Sd.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/UInt32Sd.java index a8027eb..d5025f8 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/UInt32Sd.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/UInt32Sd.java @@ -1,11 +1,11 @@ package com.etheller.warsmash.viewer5.handlers.mdx; -import com.etheller.warsmash.parsers.mdlx.timeline.Timeline; import com.etheller.warsmash.util.RenderMathUtils; +import com.hiveworkshop.rms.parsers.mdlx.timeline.MdlxTimeline; public class UInt32Sd extends Sd { - public UInt32Sd(final MdxModel model, final Timeline timeline) { + public UInt32Sd(final MdxModel model, final MdlxTimeline timeline) { super(model, timeline, SdArrayDescriptor.LONG_ARRAY); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/VectorSd.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/VectorSd.java index d34547b..038a25e 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/VectorSd.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/VectorSd.java @@ -1,12 +1,12 @@ package com.etheller.warsmash.viewer5.handlers.mdx; -import com.etheller.warsmash.parsers.mdlx.timeline.Timeline; import com.etheller.warsmash.util.Interpolator; import com.etheller.warsmash.util.RenderMathUtils; +import com.hiveworkshop.rms.parsers.mdlx.timeline.MdlxTimeline; public class VectorSd extends Sd { - public VectorSd(final MdxModel model, final Timeline timeline) { + public VectorSd(final MdxModel model, final MdlxTimeline timeline) { super(model, timeline, SdArrayDescriptor.FLOAT_ARRAY); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/tga/TgaTexture.java b/core/src/com/etheller/warsmash/viewer5/handlers/tga/TgaTexture.java index 293480b..5f3bd58 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/tga/TgaTexture.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/tga/TgaTexture.java @@ -26,7 +26,7 @@ public class TgaTexture extends RawOpenGLTextureResource { BufferedImage img; try { img = TgaFile.readTGA(this.fetchUrl, src); - update(img); + update(img, false); } catch (final IOException e) { throw new RuntimeException(e); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/IndexedSequence.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/IndexedSequence.java index 273a824..f1677e8 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/IndexedSequence.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/IndexedSequence.java @@ -1,6 +1,6 @@ package com.etheller.warsmash.viewer5.handlers.w3x; -import com.etheller.warsmash.parsers.mdlx.Sequence; +import com.etheller.warsmash.viewer5.handlers.mdx.Sequence; public class IndexedSequence { public final Sequence sequence; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SequenceUtils.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SequenceUtils.java index d9f81d5..fd94e7c 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SequenceUtils.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SequenceUtils.java @@ -5,9 +5,9 @@ import java.util.Comparator; import java.util.EnumSet; import java.util.List; -import com.etheller.warsmash.parsers.mdlx.Sequence; import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; +import com.etheller.warsmash.viewer5.handlers.mdx.Sequence; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.SecondaryTag; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitAckSound.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitAckSound.java deleted file mode 100644 index c8f40f8..0000000 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitAckSound.java +++ /dev/null @@ -1,113 +0,0 @@ -package com.etheller.warsmash.viewer5.handlers.w3x; - -import java.util.ArrayList; -import java.util.List; - -import com.badlogic.gdx.Gdx; -import com.badlogic.gdx.audio.Sound; -import com.badlogic.gdx.utils.TimeUtils; -import com.etheller.warsmash.datasources.DataSource; -import com.etheller.warsmash.units.DataTable; -import com.etheller.warsmash.units.Element; -import com.etheller.warsmash.util.DataSourceFileHandle; -import com.etheller.warsmash.viewer5.AudioBufferSource; -import com.etheller.warsmash.viewer5.AudioContext; -import com.etheller.warsmash.viewer5.AudioPanner; -import com.etheller.warsmash.viewer5.gl.Extensions; -import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderUnit; - -public final class UnitAckSound { - private static final UnitAckSound SILENT = new UnitAckSound(0, 0, 0, 0, 0, 0); - - private final List sounds = new ArrayList<>(); - private final float volume; - private final float pitch; - private final float pitchVariance; - private final float minDistance; - private final float maxDistance; - private final float distanceCutoff; - - private Sound lastPlayedSound; - - public static UnitAckSound create(final DataSource dataSource, final DataTable unitAckSounds, - final String soundName, final String soundType) { - final Element row = unitAckSounds.get(soundName + soundType); - if (row == null) { - return SILENT; - } - final String fileNames = row.getField("FileNames"); - String directoryBase = row.getField("DirectoryBase"); - if ((directoryBase.length() > 1) && !directoryBase.endsWith("\\")) { - directoryBase += "\\"; - } - final float volume = row.getFieldFloatValue("Volume") / 127f; - final float pitch = row.getFieldFloatValue("Pitch"); - float pitchVariance = row.getFieldFloatValue("PitchVariance"); - if (pitchVariance == 1.0f) { - pitchVariance = 0.0f; - } - final float minDistance = row.getFieldFloatValue("MinDistance"); - final float maxDistance = row.getFieldFloatValue("MaxDistance"); - final float distanceCutoff = row.getFieldFloatValue("DistanceCutoff"); - final UnitAckSound sound = new UnitAckSound(volume, pitch, pitchVariance, minDistance, maxDistance, - distanceCutoff); - for (final String fileName : fileNames.split(",")) { - String filePath = directoryBase + fileName; - if (!filePath.toLowerCase().endsWith(".wav")) { - filePath += ".wav"; - } - if (dataSource.has(filePath)) { - sound.sounds.add(Gdx.audio.newSound(new DataSourceFileHandle(dataSource, filePath))); - } - } - return sound; - } - - public UnitAckSound(final float volume, final float pitch, final float pitchVariation, final float minDistance, - final float maxDistance, final float distanceCutoff) { - this.volume = volume; - this.pitch = pitch; - this.pitchVariance = pitchVariation; - this.minDistance = minDistance; - this.maxDistance = maxDistance; - this.distanceCutoff = distanceCutoff; - } - - public boolean play(final AudioContext audioContext, final RenderUnit unit) { - return play(audioContext, unit, (int) (Math.random() * this.sounds.size())); - } - - public boolean play(final AudioContext audioContext, final RenderUnit unit, final int index) { - if (this.sounds.isEmpty()) { - return false; - } - final long millisTime = TimeUtils.millis(); - if (millisTime < unit.lastUnitResponseEndTimeMillis) { - return false; - } - - final AudioPanner panner = audioContext.createPanner(); - final AudioBufferSource source = audioContext.createBufferSource(); - - // Panner settings - panner.setPosition(unit.location[0], unit.location[1], unit.location[2]); - panner.setDistances(this.distanceCutoff, this.minDistance); - panner.connect(audioContext.destination); - - // Source. - source.buffer = this.sounds.get(index); - source.connect(panner); - - // Make a sound. - source.start(0, this.volume, - (this.pitch + ((float) Math.random() * this.pitchVariance * 2)) - this.pitchVariance); - this.lastPlayedSound = source.buffer; - final float duration = Extensions.audio.getDuration(this.lastPlayedSound); - unit.lastUnitResponseEndTimeMillis = millisTime + (long) (1000 * duration); - return true; - } - - public int getSoundCount() { - return this.sounds.size(); - } -} \ No newline at end of file diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitSound.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitSound.java index 17ca30b..c62d2e0 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitSound.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitSound.java @@ -52,11 +52,12 @@ public final class UnitSound { final UnitSound sound = new UnitSound(volume, pitch, pitchVariance, minDistance, maxDistance, distanceCutoff); for (final String fileName : fileNames.split(",")) { String filePath = directoryBase + fileName; - if (!filePath.toLowerCase().endsWith(".wav")) { - filePath += ".wav"; + final int lastDotIndex = filePath.lastIndexOf('.'); + if (lastDotIndex != -1) { + filePath = filePath.substring(0, lastDotIndex); } - if (dataSource.has(filePath)) { - sound.sounds.add(Gdx.audio.newSound(new DataSourceFileHandle(dataSource, filePath))); + if (dataSource.has(filePath + ".wav") || dataSource.has(filePath + ".flac")) { + sound.sounds.add(Gdx.audio.newSound(new DataSourceFileHandle(dataSource, filePath + ".wav"))); } } return sound; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/CliffMesh.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/CliffMesh.java index 876e3b3..bebea05 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/CliffMesh.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/CliffMesh.java @@ -1,7 +1,6 @@ package com.etheller.warsmash.viewer5.handlers.w3x.environment; import java.io.IOException; -import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; @@ -10,9 +9,9 @@ import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.GL30; import com.badlogic.gdx.graphics.glutils.ShaderProgram; import com.etheller.warsmash.datasources.DataSource; -import com.etheller.warsmash.parsers.mdlx.Geoset; -import com.etheller.warsmash.parsers.mdlx.MdlxModel; import com.etheller.warsmash.util.RenderMathUtils; +import com.hiveworkshop.rms.parsers.mdlx.MdlxGeoset; +import com.hiveworkshop.rms.parsers.mdlx.MdlxModel; public class CliffMesh { public int vertexBuffer; @@ -28,11 +27,8 @@ public class CliffMesh { public CliffMesh(final String path, final DataSource dataSource, final GL30 gl) throws IOException { this.gl = gl; if (path.endsWith(".mdx") || path.endsWith(".MDX")) { - MdlxModel model; - try (InputStream stream = dataSource.getResourceAsStream(path)) { - model = new MdlxModel(stream); - } - final Geoset geoset = model.getGeosets().get(0); + final MdlxModel model = new MdlxModel(dataSource.read(path)); + final MdlxGeoset geoset = model.getGeosets().get(0); this.vertexBuffer = gl.glGenBuffer(); gl.glBindBuffer(GL20.GL_ARRAY_BUFFER, this.vertexBuffer); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/GroundTexture.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/GroundTexture.java index 5eb7e27..2f7dcf5 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/GroundTexture.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/GroundTexture.java @@ -2,15 +2,12 @@ package com.etheller.warsmash.viewer5.handlers.w3x.environment; import java.awt.image.BufferedImage; import java.io.IOException; -import java.io.InputStream; import java.nio.Buffer; -import javax.imageio.ImageIO; - import com.badlogic.gdx.graphics.GL30; import com.etheller.warsmash.datasources.DataSource; import com.etheller.warsmash.util.ImageUtils; -import com.etheller.warsmash.viewer5.handlers.tga.TgaFile; +import com.etheller.warsmash.util.ImageUtils.AnyExtensionImage; public class GroundTexture { public int id; @@ -18,34 +15,15 @@ public class GroundTexture { public boolean extended; public GroundTexture(final String path, final DataSource dataSource, final GL30 gl) throws IOException { - if (path.toLowerCase().endsWith(".blp")) { - try (InputStream stream = dataSource.getResourceAsStream(path)) { - if (stream == null) { - final String tgaPath = path.substring(0, path.length() - 4) + ".tga"; - try (final InputStream tgaStream = dataSource.getResourceAsStream(tgaPath)) { - if (tgaStream != null) { - final BufferedImage tgaData = TgaFile.readTGA(tgaPath, tgaStream); - loadImage(path, gl, tgaData); - } - else { - throw new IllegalStateException("Missing ground texture: " + path); - } - } - } - else { - final BufferedImage image = ImageIO.read(stream); - loadImage(path, gl, image); - } - } - } - + final AnyExtensionImage imageInfo = ImageUtils.getAnyExtensionImageFixRGB(dataSource, path, "ground texture"); + loadImage(path, gl, imageInfo.getImageData(), imageInfo.isNeedsSRGBFix()); } - private void loadImage(final String path, final GL30 gl, final BufferedImage image) { + private void loadImage(final String path, final GL30 gl, final BufferedImage image, final boolean sRGBFix) { if (image == null) { throw new IllegalStateException("Missing ground texture: " + path); } - final Buffer buffer = ImageUtils.getTextureBuffer(ImageUtils.forceBufferedImagesRGB(image)); + final Buffer buffer = ImageUtils.getTextureBuffer(sRGBFix ? ImageUtils.forceBufferedImagesRGB(image) : image); final int width = image.getWidth(); final int height = image.getHeight(); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java index 3cd4d94..3cf3990 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java @@ -16,8 +16,6 @@ import java.util.Map; import java.util.TreeSet; import java.util.function.Consumer; -import javax.imageio.ImageIO; - import org.apache.commons.compress.utils.IOUtils; import com.badlogic.gdx.Gdx; @@ -36,6 +34,7 @@ import com.etheller.warsmash.parsers.w3x.wpm.War3MapWpm; import com.etheller.warsmash.units.DataTable; import com.etheller.warsmash.units.Element; import com.etheller.warsmash.util.ImageUtils; +import com.etheller.warsmash.util.ImageUtils.AnyExtensionImage; import com.etheller.warsmash.util.RenderMathUtils; import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.util.WorldEditStrings; @@ -46,7 +45,6 @@ import com.etheller.warsmash.viewer5.Texture; import com.etheller.warsmash.viewer5.gl.DataTexture; import com.etheller.warsmash.viewer5.gl.Extensions; import com.etheller.warsmash.viewer5.gl.WebGL; -import com.etheller.warsmash.viewer5.handlers.tga.TgaFile; import com.etheller.warsmash.viewer5.handlers.w3x.DynamicShadowManager; import com.etheller.warsmash.viewer5.handlers.w3x.SplatModel; import com.etheller.warsmash.viewer5.handlers.w3x.SplatModel.SplatMover; @@ -257,31 +255,12 @@ public class Terrain { } final String texDir = cliffInfo.getField("texDir"); final String texFile = cliffInfo.getField("texFile"); - try (InputStream imageStream = dataSource.getResourceAsStream(texDir + "\\" + texFile + texturesExt)) { - final BufferedImage image; - if (imageStream == null) { - final String tgaPath = texDir + "\\" + texFile + ".tga"; - try (final InputStream tgaStream = dataSource.getResourceAsStream(tgaPath)) { - if (tgaStream != null) { - image = TgaFile.readTGA(tgaPath, tgaStream); - } - else { - throw new IllegalStateException( - "Missing cliff texture: " + texDir + "\\" + texFile + texturesExt); - } - } - } - else { - image = ImageIO.read(imageStream); - if (image == null) { - throw new IllegalStateException( - "Missing cliff texture: " + texDir + "\\" + texFile + texturesExt); - } - } - this.cliffTextures.add(new UnloadedTexture(image.getWidth(), image.getHeight(), - ImageUtils.getTextureBuffer(ImageUtils.forceBufferedImagesRGB(image)), - cliffInfo.getField("cliffModelDir"), cliffInfo.getField("rampModelDir"))); - } + final AnyExtensionImage imageInfo = ImageUtils.getAnyExtensionImageFixRGB(dataSource, + texDir + "\\" + texFile + texturesExt, "cliff texture"); + final BufferedImage image = imageInfo.getRGBCorrectImageData(); + this.cliffTextures + .add(new UnloadedTexture(image.getWidth(), image.getHeight(), ImageUtils.getTextureBuffer(image), + cliffInfo.getField("cliffModelDir"), cliffInfo.getField("rampModelDir"))); this.cliffTexturesSize = Math.max(this.cliffTexturesSize, this.cliffTextures.get(this.cliffTextures.size() - 1).width); this.cliffToGroundTexture.add(this.groundTextureToId.get(cliffInfo.getField("groundTile"))); @@ -381,26 +360,32 @@ public class Terrain { // Water textures this.waterTextureArray = gl.glGenTexture(); gl.glBindTexture(GL30.GL_TEXTURE_2D_ARRAY, this.waterTextureArray); - gl.glTexImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, GL30.GL_SRGB8_ALPHA8, 128, 128, this.waterTextureCount, 0, - GL30.GL_RGBA, GL30.GL_UNSIGNED_BYTE, null); + + final String fileName = waterInfo.getField("texFile"); + final List waterTextures = new ArrayList<>(); + boolean anyWaterTextureNeedsSRGB = false; + for (int i = 0; i < this.waterTextureCount; i++) { + final AnyExtensionImage imageInfo = ImageUtils.getAnyExtensionImageFixRGB(dataSource, + fileName + (i < 10 ? "0" : "") + Integer.toString(i) + texturesExt, "water texture"); + final BufferedImage image = imageInfo.getImageData(); + if ((image.getWidth() != 128) || (image.getHeight() != 128)) { + System.err + .println("Odd water texture size detected of " + image.getWidth() + " x " + image.getHeight()); + } + anyWaterTextureNeedsSRGB |= imageInfo.isNeedsSRGBFix(); + waterTextures.add(image); + } + + gl.glTexImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, anyWaterTextureNeedsSRGB ? GL30.GL_SRGB8_ALPHA8 : GL30.GL_RGBA8, + 128, 128, this.waterTextureCount, 0, GL30.GL_RGBA, GL30.GL_UNSIGNED_BYTE, null); gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_WRAP_S, GL30.GL_CLAMP_TO_EDGE); gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_WRAP_T, GL30.GL_CLAMP_TO_EDGE); gl.glTexParameteri(GL30.GL_TEXTURE_2D_ARRAY, GL30.GL_TEXTURE_BASE_LEVEL, 0); - final String fileName = waterInfo.getField("texFile"); - for (int i = 0; i < this.waterTextureCount; i++) { - - try (InputStream imageStream = dataSource - .getResourceAsStream(fileName + (i < 10 ? "0" : "") + Integer.toString(i) + texturesExt)) { - final BufferedImage image = ImageIO.read(imageStream); - if ((image.getWidth() != 128) || (image.getHeight() != 128)) { - System.err.println( - "Odd water texture size detected of " + image.getWidth() + " x " + image.getHeight()); - } - - gl.glTexSubImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, 0, 0, i, image.getWidth(), image.getHeight(), 1, - GL30.GL_RGBA, GL30.GL_UNSIGNED_BYTE, ImageUtils.getTextureBuffer(image)); - } + for (int i = 0; i < waterTextures.size(); i++) { + final BufferedImage image = waterTextures.get(i); + gl.glTexSubImage3D(GL30.GL_TEXTURE_2D_ARRAY, 0, 0, 0, i, image.getWidth(), image.getHeight(), 1, + GL30.GL_RGBA, GL30.GL_UNSIGNED_BYTE, ImageUtils.getTextureBuffer(image)); } gl.glGenerateMipmap(GL30.GL_TEXTURE_2D_ARRAY); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackInstant.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackInstant.java index fe82026..3129690 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackInstant.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackInstant.java @@ -2,9 +2,9 @@ package com.etheller.warsmash.viewer5.handlers.w3x.rendersim; import java.util.List; -import com.etheller.warsmash.parsers.mdlx.Sequence; import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; +import com.etheller.warsmash.viewer5.handlers.mdx.Sequence; import com.etheller.warsmash.viewer5.handlers.mdx.SequenceLoopMode; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; import com.etheller.warsmash.viewer5.handlers.w3x.IndexedSequence; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackProjectile.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackProjectile.java index d3ec6f0..4731aaf 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackProjectile.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderAttackProjectile.java @@ -3,9 +3,9 @@ package com.etheller.warsmash.viewer5.handlers.w3x.rendersim; import java.util.List; import com.badlogic.gdx.math.Quaternion; -import com.etheller.warsmash.parsers.mdlx.Sequence; import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; +import com.etheller.warsmash.viewer5.handlers.mdx.Sequence; import com.etheller.warsmash.viewer5.handlers.mdx.SequenceLoopMode; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; import com.etheller.warsmash.viewer5.handlers.w3x.IndexedSequence; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java index d045d0e..5a90611 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java @@ -6,12 +6,12 @@ import java.util.Queue; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.math.Quaternion; -import com.etheller.warsmash.parsers.mdlx.Sequence; import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; import com.etheller.warsmash.util.RenderMathUtils; import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; +import com.etheller.warsmash.viewer5.handlers.mdx.Sequence; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.SecondaryTag; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java index a877a57..3a615d4 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java @@ -11,8 +11,6 @@ import java.util.List; import java.util.Queue; import java.util.concurrent.TimeUnit; -import javax.imageio.ImageIO; - import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Input; import com.badlogic.gdx.graphics.Color; @@ -45,7 +43,6 @@ import com.etheller.warsmash.parsers.fdf.frames.StringFrame; import com.etheller.warsmash.parsers.fdf.frames.TextureFrame; import com.etheller.warsmash.parsers.fdf.frames.UIFrame; import com.etheller.warsmash.parsers.jass.Jass2.RootFrameListener; -import com.etheller.warsmash.parsers.mdlx.Layer.FilterMode; import com.etheller.warsmash.units.Element; import com.etheller.warsmash.units.manager.MutableObjectData; import com.etheller.warsmash.util.FastNumberFormat; @@ -133,6 +130,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.ClickableActionFram import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.CommandCardCommandListener; import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.CommandErrorListener; import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.QueueIconListener; +import com.hiveworkshop.rms.parsers.mdlx.MdlxLayer.FilterMode; public class MeleeUI implements CUnitStateListener, CommandButtonListener, CommandCardCommandListener, QueueIconListener, CommandErrorListener, CPlayerStateListener { @@ -276,7 +274,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.cameraManager.target.x = startLocation[0]; this.cameraManager.target.y = startLocation[1]; - this.activeButtonTexture = ImageUtils.getBLPTexture(war3MapViewer.mapMpq, + this.activeButtonTexture = ImageUtils.getAnyExtensionTexture(war3MapViewer.mapMpq, "UI\\Widgets\\Console\\Human\\CommandButton\\human-activebutton.blp"); this.activeCommandUnitTargetFilter = new ActiveCommandUnitTargetFilter(); this.widthRatioCorrection = this.uiViewport.getMinWorldWidth() / 1600f; @@ -303,18 +301,11 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } } else if (war3MapViewer.dataSource.has("war3mapMap.blp")) { - try { - minimapTexture = ImageUtils - .getTexture(ImageIO.read(war3MapViewer.dataSource.getResourceAsStream("war3mapMap.blp"))); - } - catch (final IOException e) { - System.err.println("Could not load minimap BLP file"); - e.printStackTrace(); - } + minimapTexture = ImageUtils.getAnyExtensionTexture(war3MapViewer.dataSource, "war3mapMap.blp"); } final Texture[] teamColors = new Texture[WarsmashConstants.MAX_PLAYERS]; for (int i = 0; i < teamColors.length; i++) { - teamColors[i] = ImageUtils.getBLPTexture(war3MapViewer.dataSource, + teamColors[i] = ImageUtils.getAnyExtensionTexture(war3MapViewer.dataSource, "ReplaceableTextures\\" + ReplaceableIds.getPathString(1) + ReplaceableIds.getIdString(i) + ".blp"); } final Rectangle playableMapArea = war3MapViewer.terrain.getPlayableMapArea(); diff --git a/core/src/com/hiveworkshop/ReteraCASCUtils.java b/core/src/com/hiveworkshop/ReteraCASCUtils.java new file mode 100644 index 0000000..8599c37 --- /dev/null +++ b/core/src/com/hiveworkshop/ReteraCASCUtils.java @@ -0,0 +1,53 @@ +package com.hiveworkshop; + +public class ReteraCASCUtils { + + public static boolean arraysEquals(final byte[] a, final int aFromIndex, final int aToIndex, final byte[] b, + final int bFromIndex, final int bToIndex) { + if (a == null) { + if (b == null) { + return true; + } else { + return false; + } + } + if (b == null) { + return false; + } + if ((aToIndex - aFromIndex) != (bToIndex - bFromIndex)) { + return false; + } + int j = bFromIndex; + for (int i = aFromIndex; i < aToIndex; i++) { + if (a[i] != b[j++]) { + return false; + } + } + return true; + } + + public static int arraysCompareUnsigned(final byte[] a, final int aFromIndex, final int aToIndex, final byte[] b, + final int bFromIndex, final int bToIndex) { + final int i = arraysMismatch(a, aFromIndex, aToIndex, b, bFromIndex, bToIndex); + if ((i >= 0) && (i < Math.min(aToIndex - aFromIndex, bToIndex - bFromIndex))) { + return byteCompareUnsigned(a[aFromIndex + i], b[bFromIndex + i]); + } + return (aToIndex - aFromIndex) - (bToIndex - bFromIndex); + } + + private static int byteCompareUnsigned(final byte b, final byte c) { + return Integer.compare(b & 0xFF, c & 0xFF); + } + + private static int arraysMismatch(final byte[] a, final int aFromIndex, final int aToIndex, final byte[] b, + final int bFromIndex, final int bToIndex) { + final int aLength = aToIndex - aFromIndex; + final int bLength = bToIndex - bFromIndex; + for (int i = 0; (i < aLength) && (i < bLength); i++) { + if (a[aFromIndex + i] != b[bFromIndex + i]) { + return i; + } + } + return Math.min(aLength, bLength); + } +} diff --git a/core/src/com/hiveworkshop/blizzard/casc/ConfigurationFile.java b/core/src/com/hiveworkshop/blizzard/casc/ConfigurationFile.java new file mode 100644 index 0000000..a5f56df --- /dev/null +++ b/core/src/com/hiveworkshop/blizzard/casc/ConfigurationFile.java @@ -0,0 +1,118 @@ +package com.hiveworkshop.blizzard.casc; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; +import java.util.Scanner; + +import com.hiveworkshop.blizzard.casc.nio.MalformedCASCStructureException; +import com.hiveworkshop.nio.ByteBufferInputStream; + +/** + * File containing CASC configuration information. This is basically a + * collection of keys with their assigned value. What the values mean depends on + * the purpose of the key. + */ +public class ConfigurationFile { + /** + * The name of the data folder containing the configuration files. + */ + public static final String CONFIGURATION_FOLDER_NAME = "config"; + + /** + * Character encoding used by configuration files. + */ + public static final Charset FILE_ENCODING = Charset.forName("UTF8"); + + /** + * Length of the configuration bucket folder names. + */ + public static final int BUCKET_NAME_LENGTH = 2; + + /** + * Number of configuration bucket folder tiers. + */ + public static final int BUCKET_TIERS = 2; + + /** + * Retrieve a configuration file from the data folder by its key. + * + * @param dataFolder Path of the CASC data folder. + * @param keyHex Key for configuration file as a hexadecimal string. + * @return The requested configuration file. + * @throws IOException If an exception occurs when retrieving the file. + */ + public static ConfigurationFile lookupConfigurationFile(final Path dataFolder, final String keyHex) + throws IOException { + Path file = dataFolder.resolve(CONFIGURATION_FOLDER_NAME); + for (int tier = 0; tier < BUCKET_TIERS; tier += 1) { + final int keyOffset = tier * BUCKET_NAME_LENGTH; + final String bucketFolderName = keyHex.substring(keyOffset, keyOffset + BUCKET_NAME_LENGTH); + file = file.resolve(bucketFolderName); + } + + file = file.resolve(keyHex); + + final ByteBuffer fileBuffer = ByteBuffer.wrap(Files.readAllBytes(file)); + + return new ConfigurationFile(fileBuffer); + } + + /** + * Underlying map holding the configuration data. + */ + private final Map configuration = new HashMap<>(); + + /** + * Construct a configuration file by decoding a file buffer. + * + * @param fileBuffer File buffer to decode from. + * @throws IOException If one or more IO errors occur. + */ + public ConfigurationFile(final ByteBuffer fileBuffer) throws IOException { + try (final ByteBufferInputStream fileStream = new ByteBufferInputStream(fileBuffer); + final Scanner lineScanner = new Scanner(new InputStreamReader(fileStream, FILE_ENCODING))) { + while (lineScanner.hasNextLine()) { + final String line = lineScanner.nextLine().trim(); + final int lineLength = line.indexOf('#'); + final String record; + if (lineLength != -1) { + record = line.substring(0, lineLength); + } else { + record = line; + } + + if (!record.equals("")) { + final int assignmentIndex = record.indexOf('='); + if (assignmentIndex == -1) { + throw new MalformedCASCStructureException( + "configuration file line contains record with no assignment"); + } + + final String key = record.substring(0, assignmentIndex).trim(); + final String value = record.substring(assignmentIndex + 1).trim(); + + if (configuration.putIfAbsent(key, value) != null) { + throw new MalformedCASCStructureException( + "configuration file contains duplicate key declarations"); + } + } + } + } + } + + /** + * Get the configuration defined by the file. + * + * @return Configuration map. + */ + public Map getConfiguration() { + return configuration; + } + +} diff --git a/core/src/com/hiveworkshop/blizzard/casc/Key.java b/core/src/com/hiveworkshop/blizzard/casc/Key.java new file mode 100644 index 0000000..1a3f00c --- /dev/null +++ b/core/src/com/hiveworkshop/blizzard/casc/Key.java @@ -0,0 +1,78 @@ +package com.hiveworkshop.blizzard.casc; + +import com.hiveworkshop.ReteraCASCUtils; +import com.hiveworkshop.lang.Hex; + +/** + * Class representing a CASC related key such as an encoding key. + *

+ * When testing equality and comparing the length of the shortest key is used. + */ +public final class Key implements Comparable { + /** + * Key array. + */ + private final byte[] key; + + /** + * Wraps a byte array into a key. The array is used directly so must not be + * modified. + * + * @param key Key array. + */ + public Key(final byte[] key) { + this.key = key; + } + + /** + * Constructs a key from a hexadecimal key string. Bytes are order in the order + * they appear in the string, which can be considered big endian. + * + * @param keyString hexadecimal key form of key. + */ + public Key(final CharSequence key) { + this.key = Hex.decodeHex(key); + } + + @Override + public int compareTo(final Key o) { + final int commonLength = Math.min(key.length, o.key.length); + return ReteraCASCUtils.arraysCompareUnsigned(key, 0, commonLength, o.key, 0, commonLength); + } + + @Override + public boolean equals(final Object obj) { + if ((obj == null) || !(obj instanceof Key)) { + return false; + } + + final Key otherKey = (Key) obj; + final int commonLength = Math.min(key.length, otherKey.key.length); + if (!ReteraCASCUtils.arraysEquals(key, 0, commonLength, otherKey.key, 0, commonLength)) { + return false; + } + + return true; + } + + /** + * Return the wrapped key array for low level interaction. + * + * @return Key array. + */ + public byte[] getKey() { + return key.clone(); + } + + @Override + public int hashCode() { + throw new UnsupportedOperationException("key hash code not safe to use due to variable sizes between systems"); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder((key.length + 1)); + Hex.stringBufferAppendHex(builder, key); + return builder.toString(); + } +} diff --git a/core/src/com/hiveworkshop/blizzard/casc/StorageReference.java b/core/src/com/hiveworkshop/blizzard/casc/StorageReference.java new file mode 100644 index 0000000..e0c76e6 --- /dev/null +++ b/core/src/com/hiveworkshop/blizzard/casc/StorageReference.java @@ -0,0 +1,80 @@ +package com.hiveworkshop.blizzard.casc; + +import java.util.Map; + +/** + * A reference to a file extracted from a configuration file. + */ +public class StorageReference { + /** + * Suffix for sizes mapping entry in configuration files. + */ + private static final String SIZES_SUFFIX = "-size"; + + private final long storedSize; + private final long size; + private final Key encodingKey; + private final Key contentKey; + + /** + * Decodes a storage reference from a configuration file. + * + * @param name Name of reference. + * @param configuration Map of configuration file content. + */ + public StorageReference(final String name, final Map configuration) { + final String keys = configuration.get(name); + if (keys == null) { + throw new IllegalArgumentException("name does not exist in configuration"); + } + final String sizes = configuration.get(name + SIZES_SUFFIX); + if (sizes == null) { + throw new IllegalArgumentException("size missing in configuration"); + } + + final String[] keyStrings = keys.split(" "); + contentKey = new Key(keyStrings[0]); + encodingKey = new Key(keyStrings[1]); + + final String[] sizeStrings = sizes.split(" "); + size = Long.parseLong(sizeStrings[0]); + storedSize = Long.parseLong(sizeStrings[1]); + } + + /** + * Content key? + * + * @return Content key. + */ + public Key getContentKey() { + return contentKey; + } + + /** + * Encoding key used to lookup the file from CASC storage. + * + * @return Encoding key. + */ + public Key getEncodingKey() { + return encodingKey; + } + + /** + * File size. + * + * @return File size in bytes of the file. + */ + public long getSize() { + return size; + } + + /** + * Size of file content in CASC storage. + * + * @return Approximate byte usage of file in CASC storage. + */ + public long getStoredSize() { + return storedSize; + } + +} diff --git a/core/src/com/hiveworkshop/blizzard/casc/info/FieldDataType.java b/core/src/com/hiveworkshop/blizzard/casc/info/FieldDataType.java new file mode 100644 index 0000000..bd5cb54 --- /dev/null +++ b/core/src/com/hiveworkshop/blizzard/casc/info/FieldDataType.java @@ -0,0 +1,25 @@ +package com.hiveworkshop.blizzard.casc.info; + +/** + * Field data types to help with decoding values. + */ +public enum FieldDataType { + /** + * Field contains textual data. Size ignored. + */ + STRING, + /** + * Field is a decimal number. Size determines number of bytes used to represent + * it. + */ + DEC, + /** + * Field is a hexadecimal string. Size is number of bytes used to represent it + * with every 2 characters representing 1 byte. + */ + HEX, + /** + * This field type is currently not supported. + */ + UNSUPPORTED +} diff --git a/core/src/com/hiveworkshop/blizzard/casc/info/FieldDescriptor.java b/core/src/com/hiveworkshop/blizzard/casc/info/FieldDescriptor.java new file mode 100644 index 0000000..599e20f --- /dev/null +++ b/core/src/com/hiveworkshop/blizzard/casc/info/FieldDescriptor.java @@ -0,0 +1,65 @@ +package com.hiveworkshop.blizzard.casc.info; + +public class FieldDescriptor { + private static final int NAME_TERMINATOR = '!'; + private static final int DATA_TYPE_TERMINATOR = ':'; + + private final String name; + private FieldDataType dataType; + private final int size; + + /** + * Constructs a field descriptor from a field declaration string. + * + * @param encoded Field declaration string. + */ + public FieldDescriptor(final String encoded) { + final int nameEnd = encoded.indexOf(NAME_TERMINATOR); + if (nameEnd == -1) { + throw new IllegalArgumentException("missing name terminator"); + } + final int dataTypeEnd = encoded.indexOf(DATA_TYPE_TERMINATOR, nameEnd + 1); + if (dataTypeEnd == -1) { + throw new IllegalArgumentException("missing data type terminator"); + } + + name = encoded.substring(0, nameEnd); + + try { + dataType = FieldDataType.valueOf(encoded.substring(nameEnd + 1, dataTypeEnd)); + } catch (final IllegalArgumentException e) { + dataType = FieldDataType.UNSUPPORTED; + } + + size = Integer.parseInt(encoded.substring(dataTypeEnd + 1)); + } + + /** + * Get the field name. + * + * @return Name of the field. + */ + public String getName() { + return name; + } + + /** + * Get the field data type. + * + * @return Field data type. + */ + public FieldDataType getDataType() { + return dataType; + } + + /** + * Get the field size. Field size is the number of bytes required to represent + * the field in native form. A value of 0 means the field is variable length. + * + * @return Field size in bytes. + */ + public int getSize() { + return size; + } + +} diff --git a/core/src/com/hiveworkshop/blizzard/casc/info/Info.java b/core/src/com/hiveworkshop/blizzard/casc/info/Info.java new file mode 100644 index 0000000..24bdaab --- /dev/null +++ b/core/src/com/hiveworkshop/blizzard/casc/info/Info.java @@ -0,0 +1,149 @@ +package com.hiveworkshop.blizzard.casc.info; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.NoSuchElementException; +import java.util.Scanner; + +import com.hiveworkshop.blizzard.casc.nio.MalformedCASCStructureException; +import com.hiveworkshop.nio.ByteBufferInputStream; + +/** + * Top level CASC information file containing configuration information and + * entry point references. + */ +public class Info { + /** + * Name of the CASC build info file located in the install root (parent of the + * data folder). + */ + public static final String BUILD_INFO_FILE_NAME = ".build.info"; + + /** + * Character encoding used by info files. + */ + public static final Charset FILE_ENCODING = Charset.forName("UTF8"); + + /** + * Field separator used by CASC info files. + */ + private static final String FIELD_SEPARATOR_REGEX = "\\|"; + + /** + * Helper method to separate a single line of info file into separate field + * strings. + * + * @param encodedLine Line of info file. + * @return Array of separate fields. + */ + private static String[] separateFields(final String encodedLine) { + return encodedLine.split(FIELD_SEPARATOR_REGEX); + } + + private final ArrayList fieldDescriptors = new ArrayList<>(); + + private final ArrayList> records = new ArrayList<>(); + + /** + * Construct an info file from an array of encoded lines. + * + * @param encodedLines Encoded lines. + * @throws IOException + */ + public Info(final ByteBuffer fileBuffer) throws IOException { + try (final ByteBufferInputStream fileStream = new ByteBufferInputStream(fileBuffer); + final Scanner lineScanner = new Scanner(new InputStreamReader(fileStream, FILE_ENCODING))) { + final String[] encodedFieldDescriptors = separateFields(lineScanner.nextLine()); + for (final String encodedFieldDescriptor : encodedFieldDescriptors) { + fieldDescriptors.add(new FieldDescriptor(encodedFieldDescriptor)); + } + + while (lineScanner.hasNextLine()) { + records.add(new ArrayList<>(Arrays.asList(separateFields(lineScanner.nextLine())))); + } + } catch (final NoSuchElementException e) { + throw new MalformedCASCStructureException("missing headers"); + } + } + + /** + * Retrieves a specific field of a record. + * + * @param recordIndex Record index to lookup. + * @param fieldIndex Field index to retrieve of record. + * @return Field value. + * @throws IndexOutOfBoundsException When recordIndex or fieldIndex are out of + * bounds. + */ + public String getField(final int recordIndex, final int fieldIndex) { + return records.get(recordIndex).get(fieldIndex); + } + + /** + * Retrieves a specific field of a record. + * + * @param recordIndex Record index to lookup. + * @param fieldName Field name to retrieve of record. + * @return Field value, or null if field does not exist. + * @throws IndexOutOfBoundsException When recordIndex is out of bounds. + */ + public String getField(final int recordIndex, final String fieldName) { + // resolve field + final int fieldIndex = getFieldIndex(fieldName); + if (fieldIndex == -1) { + // field does not exist + return null; + } + + return getField(recordIndex, fieldIndex); + } + + /** + * Get the number of fields that make up each record. + * + * @return Field count. + */ + public int getFieldCount() { + return fieldDescriptors.size(); + } + + /** + * Retrieve the field descriptor of a field index. + * + * @param fieldIndex Field index to retrieve descriptor from. + * @return Field descriptor for field index. + */ + public FieldDescriptor getFieldDescriptor(final int fieldIndex) { + return fieldDescriptors.get(fieldIndex); + } + + /** + * Lookup the index of a named field. Returns the field index for the field name + * if found, otherwise returns -1. + * + * @param name Name of the field to find. + * @return Field index of field. + */ + public int getFieldIndex(final String name) { + for (int i = 0; i < fieldDescriptors.size(); i += 1) { + if (fieldDescriptors.get(i).getName().equals(name)) { + return i; + } + } + + return -1; + } + + /** + * Get the number of records in this file. + * + * @return Record count. + */ + public int getRecordCount() { + return records.size(); + } +} diff --git a/core/src/com/hiveworkshop/blizzard/casc/io/WarcraftIIICASC.java b/core/src/com/hiveworkshop/blizzard/casc/io/WarcraftIIICASC.java new file mode 100644 index 0000000..2dbd8a5 --- /dev/null +++ b/core/src/com/hiveworkshop/blizzard/casc/io/WarcraftIIICASC.java @@ -0,0 +1,291 @@ +package com.hiveworkshop.blizzard.casc.io; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +import com.hiveworkshop.blizzard.casc.ConfigurationFile; +import com.hiveworkshop.blizzard.casc.info.Info; +import com.hiveworkshop.blizzard.casc.nio.MalformedCASCStructureException; +import com.hiveworkshop.blizzard.casc.storage.Storage; +import com.hiveworkshop.blizzard.casc.vfs.VirtualFileSystem; +import com.hiveworkshop.blizzard.casc.vfs.VirtualFileSystem.PathResult; + +/** + * A convenient access to locally stored Warcraft III data files. Intended for + * use with CASC versions of Warcraft III including classic and Reforged. + */ +public class WarcraftIIICASC implements AutoCloseable { + /** + * File system view for accessing files from file paths. + */ + public class FileSystem { + /** + * Private constructor, currently not used. + */ + private FileSystem() { + + } + + /** + * Enumerate all file paths contained in this file system. + *

+ * This operation might be quite slow. + * + * @return A list containing all file paths contained in this file system. + * @throws IOException In an exception occurs when resolving files. + */ + public List enumerateFiles() throws IOException { + final List pathResults = vfs.getAllFiles(); + final ArrayList filePathStrings = new ArrayList(pathResults.size()); + + for (final PathResult pathResult : pathResults) { + filePathStrings.add(pathResult.getPath()); + } + + return filePathStrings; + } + + /** + * Test if the specified file path is a file. + * + * @param filePath Path of file to test. + * @return True if path represents a file, otherwise false. + * @throws IOException In an exception occurs when resolving files. + */ + public boolean isFile(final String filePath) throws IOException { + final byte[][] pathFragments = VirtualFileSystem.convertFilePath(filePath); + try { + final PathResult resolveResult = vfs.resolvePath(pathFragments); + return resolveResult.isFile(); + } catch (final FileNotFoundException e) { + return false; + } + } + + /** + * Test if the specified file path is available from local storage. + * + * @param filePath Path of file to test. + * @return True if path represents a file inside local storage, otherwise false. + * @throws IOException In an exception occurs when resolving files. + */ + public boolean isFileAvailable(final String filePath) throws IOException { + final byte[][] pathFragments = VirtualFileSystem.convertFilePath(filePath); + final PathResult resolveResult = vfs.resolvePath(pathFragments); + return resolveResult.existsInStorage(); + } + + /** + * Test if the specified file path is a nested file system. + *

+ * If true a file system can be resolved from the file path which files can be + * resolved from more efficiently than from higher up file systems. + *

+ * Support for this feature is not yet implemented. Please resolve everything + * from the root. + * + * @param filePath Path of file to test. + * @return True if file is a nested file system, otherwise false. + * @throws IOException In an exception occurs when resolving files. + */ + public boolean isNestedFileSystem(final String filePath) throws IOException { + final byte[][] pathFragments = VirtualFileSystem.convertFilePath(filePath); + try { + final PathResult resolveResult = vfs.resolvePath(pathFragments); + return resolveResult.isTVFS(); + } catch (final FileNotFoundException e) { + return false; + } + } + + /** + * Fully read the file at the specified file path into memory. + * + * @param filePath File path of file to read. + * @return Buffer containing file data. + * @throws IOException If an error occurs when reading the file. + */ + public ByteBuffer readFileData(final String filePath) throws IOException { + final byte[][] pathFragments = VirtualFileSystem.convertFilePath(filePath); + final PathResult resolveResult = vfs.resolvePath(pathFragments); + + if (!resolveResult.isFile()) { + throw new FileNotFoundException("the specified file path does not resolve to a file"); + } else if (!resolveResult.existsInStorage()) { + throw new FileNotFoundException("the specified file is not in local storage"); + } + + final ByteBuffer fileBuffer = resolveResult.readFile(null); + fileBuffer.flip(); + return fileBuffer; + } + } + + /** + * Name of the CASC data folder used by Warcraft III. + */ + private static final String WC3_DATA_FOLDER_NAME = "Data"; + + /** + * Warcraft III build information. + */ + private final Info buildInfo; + + /** + * Detected active build information record. + */ + private final int activeInfoRecord; + + /** + * Warcraft III build configuration. + */ + private final ConfigurationFile buildConfiguration; + + /** + * Warcraft III CASC data folder path. + */ + private final Path dataPath; + + /** + * Warcraft III local storage. + */ + private final Storage localStorage; + + /** + * TVFS file system to resolve file paths. + */ + private final VirtualFileSystem vfs; + + /** + * Construct an interface to the CASC local storage used by Warcraft III. Can be + * used to read data files from the local storage. + *

+ * The active build record is used for local storage details. + *

+ * Install folder is the Warcraft III installation folder where the + * .build.info file is located. For example + * C:\Program Files (x86)\Warcraft III. + *

+ * Memory mapped IO can be used instead of conventional channel based IO. This + * should improve IO performance considerably by avoiding excessive memory copy + * operations and system calls. However it may place considerable strain on the + * Java VM application virtual memory address space. As such memory mapping + * should only be used with large address aware VMs. + * + * @param installFolder Warcraft III installation folder. + * @param useMemoryMapping If memory mapped IO should be used to read file data. + * @throws IOException If an exception occurs while mounting. + */ + public WarcraftIIICASC(final Path installFolder, final boolean useMemoryMapping) throws IOException { + final Path infoFilePath = installFolder.resolve(Info.BUILD_INFO_FILE_NAME); + buildInfo = new Info(ByteBuffer.wrap(Files.readAllBytes(infoFilePath))); + + final int recordCount = buildInfo.getRecordCount(); + if (recordCount < 1) { + throw new MalformedCASCStructureException("build info contains no records"); + } + + // resolve the active record + final int activeFiledIndex = buildInfo.getFieldIndex("Active"); + if (activeFiledIndex == -1) { + throw new MalformedCASCStructureException("build info contains no active field"); + } + int recordIndex = 0; + for (; recordIndex < recordCount; recordIndex += 1) { + if (Integer.parseInt(buildInfo.getField(recordIndex, activeFiledIndex)) == 1) { + break; + } + } + if (recordIndex == recordCount) { + throw new MalformedCASCStructureException("build info contains no active record"); + } + activeInfoRecord = recordIndex; + + // resolve build configuration file + final int buildKeyFieldIndex = buildInfo.getFieldIndex("Build Key"); + if (buildKeyFieldIndex == -1) { + throw new MalformedCASCStructureException("build info contains no build key field"); + } + final String buildKey = buildInfo.getField(activeInfoRecord, buildKeyFieldIndex); + + // resolve data folder + dataPath = installFolder.resolve(WC3_DATA_FOLDER_NAME); + if (!Files.isDirectory(dataPath)) { + throw new MalformedCASCStructureException("data folder is missing"); + } + + // resolve build configuration file + buildConfiguration = ConfigurationFile.lookupConfigurationFile(dataPath, buildKey); + + // mounting local storage + localStorage = new Storage(dataPath, false, useMemoryMapping); + + // mounting virtual file system + VirtualFileSystem vfs = null; + try { + vfs = new VirtualFileSystem(localStorage, buildConfiguration.getConfiguration()); + } finally { + if (vfs == null) { + // storage must be closed to prevent resource leaks + localStorage.close(); + } + } + this.vfs = vfs; + } + + @Override + public void close() throws IOException { + localStorage.close(); + } + + /** + * Returns the active record index of the build information. This is the index + * of the record that is mounted. + * + * @return Active record index of build information. + */ + public int getActiveRecordIndex() { + return activeInfoRecord; + } + + /** + * Returns the active branch name which is currently mounted. + *

+ * This might reflect the locale that has been cached to local storage. + * + * @return Branch name. + * @throws IOException If no branch information is available. + */ + public String getBranch() throws IOException { + // resolve branch + final int branchFieldIndex = buildInfo.getFieldIndex("Branch"); + if (branchFieldIndex == -1) { + throw new MalformedCASCStructureException("build info contains no branch field"); + } + return buildInfo.getField(activeInfoRecord, branchFieldIndex); + } + + /** + * Returns the build information of the archive. + * + * @return Build information. + */ + public Info getBuildInfo() { + return buildInfo; + } + + /** + * Get the root file system of Warcraft III. From this all locally stored data + * files can be accessed. + * + * @return Root file system containing all files. + */ + public FileSystem getRootFileSystem() { + return new FileSystem(); + } +} diff --git a/core/src/com/hiveworkshop/blizzard/casc/io/package-info.java b/core/src/com/hiveworkshop/blizzard/casc/io/package-info.java new file mode 100644 index 0000000..bbc9c07 --- /dev/null +++ b/core/src/com/hiveworkshop/blizzard/casc/io/package-info.java @@ -0,0 +1,6 @@ +/** + * High level APIs for CASC file system interaction. + *

+ * These are intended for ease of use, dealing away with many of the low level details required to work with CASC. + */ +package com.hiveworkshop.blizzard.casc.io; \ No newline at end of file diff --git a/core/src/com/hiveworkshop/blizzard/casc/nio/HashMismatchException.java b/core/src/com/hiveworkshop/blizzard/casc/nio/HashMismatchException.java new file mode 100644 index 0000000..dbba7c3 --- /dev/null +++ b/core/src/com/hiveworkshop/blizzard/casc/nio/HashMismatchException.java @@ -0,0 +1,15 @@ +package com.hiveworkshop.blizzard.casc.nio; + +import java.io.IOException; + +public class HashMismatchException extends IOException { + private static final long serialVersionUID = -7133950344327038673L; + + public HashMismatchException() { + } + + public HashMismatchException(String message) { + super(message); + } + +} diff --git a/core/src/com/hiveworkshop/blizzard/casc/nio/LittleHashBlockProcessor.java b/core/src/com/hiveworkshop/blizzard/casc/nio/LittleHashBlockProcessor.java new file mode 100644 index 0000000..fbbf295 --- /dev/null +++ b/core/src/com/hiveworkshop/blizzard/casc/nio/LittleHashBlockProcessor.java @@ -0,0 +1,76 @@ +package com.hiveworkshop.blizzard.casc.nio; + +import java.io.IOException; +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +public class LittleHashBlockProcessor { + + /** + * + * @param encoded + * @return The size of the block + * @throws MalformedCASCStructureException If file is malformed. + */ + public int processBlock(final ByteBuffer encoded) throws MalformedCASCStructureException { + encoded.order(ByteOrder.LITTLE_ENDIAN); + final int length; + final int expectedHash; + try { + length = encoded.getInt(); + expectedHash = encoded.getInt(); + } catch (final BufferUnderflowException e) { + throw new MalformedCASCStructureException("little hash block header out of bounds"); + } + + final int actualHash = expectedHash; // TODO generate actual hash + + if (actualHash != expectedHash) { + return -length; + } + + return length; + } + + /** + * Get a little hash guarded block from the source buffer. + * + * @param sourceBuffer Buffer to retrieve block from. + * @return Guarded block. + * @throws MalformedCASCStructureException If the file is malformed. + * @throws HashMismatchException If the block is corrupt. + */ + public ByteBuffer getBlock(final ByteBuffer sourceBuffer) throws IOException { + final ByteBuffer workingBuffer = sourceBuffer.slice(); + + workingBuffer.order(ByteOrder.LITTLE_ENDIAN); + final int length; + final int expectedHash; + try { + length = workingBuffer.getInt(); + expectedHash = workingBuffer.getInt(); + } catch (final BufferUnderflowException e) { + throw new MalformedCASCStructureException("little hash block header out of bounds"); + } + + if (workingBuffer.remaining() < length) { + throw new MalformedCASCStructureException("little hash block out of bounds"); + } + + workingBuffer.limit(workingBuffer.position() + length); + final ByteBuffer blockBuffer = workingBuffer.slice(); + workingBuffer.position(workingBuffer.limit()); + workingBuffer.limit(workingBuffer.capacity()); + + final int actualHash = expectedHash; // TODO generate actual hash + + if (actualHash != expectedHash) { + throw new HashMismatchException("little hash block"); + } + + sourceBuffer.position(sourceBuffer.position() + workingBuffer.position()); + + return blockBuffer; + } +} diff --git a/core/src/com/hiveworkshop/blizzard/casc/nio/MalformedCASCStructureException.java b/core/src/com/hiveworkshop/blizzard/casc/nio/MalformedCASCStructureException.java new file mode 100644 index 0000000..0e0db77 --- /dev/null +++ b/core/src/com/hiveworkshop/blizzard/casc/nio/MalformedCASCStructureException.java @@ -0,0 +1,15 @@ +package com.hiveworkshop.blizzard.casc.nio; + +import java.io.IOException; + +public class MalformedCASCStructureException extends IOException { + private static final long serialVersionUID = -5323382445554597608L; + + public MalformedCASCStructureException(String message) { + super(message); + } + + public MalformedCASCStructureException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/core/src/com/hiveworkshop/blizzard/casc/storage/BLTEContent.java b/core/src/com/hiveworkshop/blizzard/casc/storage/BLTEContent.java new file mode 100644 index 0000000..e2d8426 --- /dev/null +++ b/core/src/com/hiveworkshop/blizzard/casc/storage/BLTEContent.java @@ -0,0 +1,132 @@ +package com.hiveworkshop.blizzard.casc.storage; + +import java.io.IOException; +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import com.hiveworkshop.blizzard.casc.nio.MalformedCASCStructureException; +import com.hiveworkshop.lang.Hex; + +/** + * BLTE content entry, used to decode BLTE file data that follows it. + */ +public class BLTEContent { + /** + * BLTE content identifier. + */ + private static final ByteBuffer IDENTIFIER = ByteBuffer.wrap(new byte[] { 'B', 'L', 'T', 'E' }); + + /** + * Hash length in bytes. Should be fetched from appropriate digest length. + */ + private static final int HASH_LENGTH = 16; + + private final long compressedSize; + private final long decompressedSize; + private final byte[] hash = new byte[HASH_LENGTH]; + + public BLTEContent(final ByteBuffer blteBuffer) { + compressedSize = Integer.toUnsignedLong(blteBuffer.getInt()); + decompressedSize = Integer.toUnsignedLong(blteBuffer.getInt()); + blteBuffer.get(hash); + } + + public static BLTEContent[] decodeContent(final ByteBuffer storageBuffer) throws IOException { + final ByteBuffer contentBuffer = storageBuffer.slice(); + + // check identifier + + if ((contentBuffer.remaining() < IDENTIFIER.remaining()) + || !contentBuffer.limit(IDENTIFIER.remaining()).equals(IDENTIFIER)) { + throw new MalformedCASCStructureException("missing BLTE identifier"); + } + + // decode header + + contentBuffer.limit(contentBuffer.capacity()); + contentBuffer.position(contentBuffer.position() + IDENTIFIER.remaining()); + contentBuffer.order(ByteOrder.BIG_ENDIAN); + + final long headerSize; + try { + headerSize = Integer.toUnsignedLong(contentBuffer.getInt()); + } catch (final BufferUnderflowException e) { + throw new MalformedCASCStructureException("header preamble goes out of bounds"); + } + + if (headerSize == 0L) { + storageBuffer.position(storageBuffer.position() + contentBuffer.position()); + return new BLTEContent[0]; + } else if (headerSize > contentBuffer.capacity()) { + throw new MalformedCASCStructureException("BLTE header extends beyond storage buffer bounds"); + } + + contentBuffer.limit((int) headerSize); + final ByteBuffer blteBuffer = contentBuffer.slice(); + blteBuffer.order(ByteOrder.BIG_ENDIAN); + contentBuffer.position(contentBuffer.limit()); + contentBuffer.limit(contentBuffer.capacity()); + + final byte flags; + final int entryCount; + try { + flags = blteBuffer.get(); + if (flags != 0xF) { + throw new MalformedCASCStructureException("unknown flags"); + } + // BE24 read + final int be24Bytes = 3; + final ByteBuffer be24Buffer = ByteBuffer.allocate(Integer.BYTES); + be24Buffer.order(ByteOrder.BIG_ENDIAN); + blteBuffer.get(be24Buffer.array(), Integer.BYTES - be24Bytes, be24Bytes); + entryCount = be24Buffer.getInt(0); + if (entryCount == 0) { + throw new MalformedCASCStructureException("explicit zero entry count"); + } + } catch (final BufferUnderflowException e) { + throw new MalformedCASCStructureException("header goes out of bounds"); + } + + final BLTEContent[] content = new BLTEContent[entryCount]; + + for (int index = 0; index < content.length; index += 1) { + content[index] = new BLTEContent(blteBuffer); + } + + if (blteBuffer.hasRemaining()) { + throw new MalformedCASCStructureException("unprocessed BLTE bytes"); + } + + storageBuffer.position(storageBuffer.position() + contentBuffer.position()); + + return content; + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("BLTEChunk{compressedSize="); + builder.append(compressedSize); + builder.append(", decompressedSize="); + builder.append(decompressedSize); + builder.append(", hash="); + Hex.stringBufferAppendHex(builder, hash); + builder.append("}"); + + return builder.toString(); + } + + public long getCompressedSize() { + return compressedSize; + } + + public long getDecompressedSize() { + return decompressedSize; + } + + public byte[] getHash() { + return hash.clone(); + } + +} diff --git a/core/src/com/hiveworkshop/blizzard/casc/storage/BankStream.java b/core/src/com/hiveworkshop/blizzard/casc/storage/BankStream.java new file mode 100644 index 0000000..c7b67f8 --- /dev/null +++ b/core/src/com/hiveworkshop/blizzard/casc/storage/BankStream.java @@ -0,0 +1,185 @@ +package com.hiveworkshop.blizzard.casc.storage; + +import java.io.EOFException; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.nio.BufferOverflowException; +import java.nio.ByteBuffer; +import java.util.zip.DataFormatException; +import java.util.zip.Inflater; + +import com.hiveworkshop.blizzard.casc.Key; +import com.hiveworkshop.blizzard.casc.nio.MalformedCASCStructureException; + +/** + * Allows high level access to stored file data banks. These data banks can be + * assembled using higher level logic into a continuous file. + */ +public class BankStream { + private final StorageContainer container; + private final BLTEContent[] content; + private final ByteBuffer streamBuffer; + private int bank = 0; + private boolean hasBanks; + + /** + * Constructs a bank steam from the given buffer. An optional key can be used to + * verify the right file is being processed. If a key is provided it is assumed + * the remaining size of the buffer exactly matches the container size. + * + * @param storageBuffer Storage buffer, as specified by an index file. + * @param key File encoding key to check contents with, or null if no + * such check is required. + * @throws IOException If an exception occurs during decoding of the + * storageBuffer. + */ + public BankStream(final ByteBuffer storageBuffer, final Key encodingKey) throws IOException { + ByteBuffer streamBuffer = storageBuffer.slice(); + container = new StorageContainer(streamBuffer); + if ((encodingKey != null) && !container.getKey().equals(encodingKey)) { + throw new MalformedCASCStructureException("container encoding key mismatch"); + } + + final int storageSize = (int) container.getSize(); + final int storageSizeDiff = Integer.compare(streamBuffer.capacity(), storageSize); + + if (storageSizeDiff < 0) { + throw new MalformedCASCStructureException("container buffer smaller than container"); + } else if ((encodingKey != null) && (storageSizeDiff != 0)) { + throw new MalformedCASCStructureException("container buffer size mismatch"); + } else if (storageSizeDiff > 0) { + // resize buffer to match file + final int streamPos = streamBuffer.position(); + streamBuffer.limit(storageSize); + streamBuffer.position(0); + streamBuffer = streamBuffer.slice(); + streamBuffer.position(streamPos); + } + + if (streamBuffer.hasRemaining()) { + content = BLTEContent.decodeContent(streamBuffer); + hasBanks = true; + } else { + content = null; + hasBanks = false; + } + + this.streamBuffer = streamBuffer; + storageBuffer.position(storageBuffer.position() + streamBuffer.capacity()); + } + + /** + * Get the length of the next bank in bytes. + * + * @return Length of bank in bytes. + * @throws EOFException If there are no more banks in this stream. + */ + public long getNextBankLength() throws EOFException { + if (!hasNextBank()) { + throw new EOFException("no more banks to decode"); + } + + return content.length != 0 ? content[bank].getDecompressedSize() : streamBuffer.remaining(); + } + + /** + * Decode a bank from the stream. The bank buffer must be large enough to + * receive the bank data as specified by getNextBankLength. A null buffer will + * automatically allocate one large enough. The position of the bank buffer will + * be advanced as appropriate, potentially allowing for many banks to be fetched + * in sequence. + * + * @param bankBuffer Buffer to receive bank data. + * @return If null then a new suitable buffer, otherwise bankBuffer. + * @throws IOException If something goes wrong during bank extraction. + * @throws EOFException If there are no more banks in this stream. + */ + public ByteBuffer getBank(ByteBuffer bankBuffer) throws IOException { + if (!hasNextBank()) { + throw new EOFException("no more banks to decode"); + } + + if (content.length != 0) { + final BLTEContent blteEntry = content[bank]; + final long encodedSize = blteEntry.getCompressedSize(); + final long decodedSize = blteEntry.getDecompressedSize(); + + if (streamBuffer.remaining() < encodedSize) { + throw new MalformedCASCStructureException("encoded data beyond end of file"); + } else if (bankBuffer == null) { + if (decodedSize > Integer.MAX_VALUE) { + throw new MalformedCASCStructureException("bank too large for Java to manipulate"); + } + bankBuffer = ByteBuffer.allocate((int) decodedSize); + } else if (bankBuffer.remaining() < decodedSize) { + throw new BufferOverflowException(); + } + + final ByteBuffer encodedBuffer = ((ByteBuffer) streamBuffer.slice().limit((int) encodedSize)).slice(); + final ByteBuffer decodedBuffer = ((ByteBuffer) bankBuffer.slice().limit((int) decodedSize)).slice(); + final byte[] intermediateEncodedCopy = new byte[encodedBuffer.remaining()]; + final byte[] intermediateDecodedCopy = new byte[decodedBuffer.remaining()]; + + final char encodingMode = (char) encodedBuffer.get(); + switch (encodingMode) { + case 'N': + // uncompressed data + if (encodedBuffer.remaining() != decodedSize) { + throw new MalformedCASCStructureException("not enough uncompressed bytes"); + } + decodedBuffer.put(encodedBuffer); + break; + case 'Z': + // zlib compressed data + final Inflater zlib = new Inflater(); + encodedBuffer.get(intermediateEncodedCopy, 0, encodedBuffer.remaining()); + zlib.setInput(intermediateEncodedCopy); + final int resultSize; + try { + resultSize = zlib.inflate(intermediateDecodedCopy); + decodedBuffer.put(intermediateDecodedCopy, 0, resultSize); + } catch (final DataFormatException e) { + throw new MalformedCASCStructureException("zlib inflate exception", e); + } + if (resultSize != decodedSize) { + throw new MalformedCASCStructureException("not enough bytes generated: " + resultSize + "B"); + } else if (!zlib.finished()) { + throw new MalformedCASCStructureException("unfinished inflate operation"); + } + break; + default: + throw new UnsupportedEncodingException("unsupported encoding mode: " + encodingMode); + } + + streamBuffer.position(streamBuffer.position() + encodedBuffer.position()); + bankBuffer.position(bankBuffer.position() + decodedBuffer.position()); + + bank += 1; + if (bank == content.length) { + hasBanks = false; + } + } else { + // this logic is guessed and requires confirmation + if (bankBuffer == null) { + bankBuffer = ByteBuffer.allocate(streamBuffer.remaining()); + } else if (bankBuffer.remaining() < streamBuffer.remaining()) { + throw new MalformedCASCStructureException("bank buffer too small"); + } + + bankBuffer.put(streamBuffer); + hasBanks = false; + } + + return bankBuffer; + } + + /** + * Returns true while one or more banks are remaining to be streamed. Only valid + * if hasBanks returns true. + * + * @return True if another bank can be decoded, otherwise false. + */ + public boolean hasNextBank() { + return hasBanks; + } +} diff --git a/core/src/com/hiveworkshop/blizzard/casc/storage/IndexEntry.java b/core/src/com/hiveworkshop/blizzard/casc/storage/IndexEntry.java new file mode 100644 index 0000000..02ff05c --- /dev/null +++ b/core/src/com/hiveworkshop/blizzard/casc/storage/IndexEntry.java @@ -0,0 +1,60 @@ +package com.hiveworkshop.blizzard.casc.storage; + +import com.hiveworkshop.blizzard.casc.Key; + +public class IndexEntry { + /** + * Index encoding key. + */ + private final Key key; + + /** + * Logical offset of storage container. + */ + private final long dataOffset; + + /** + * Size of storage container. + */ + private final long fileSize; + + public IndexEntry(final byte[] key, final long dataOffset, final long fileSize) { + this.key = new Key(key); + this.dataOffset = dataOffset; + this.fileSize = fileSize; + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("IndexEntry{key="); + builder.append(key); + builder.append(", dataOffset="); + builder.append(dataOffset); + builder.append(", fileSize="); + builder.append(fileSize); + builder.append("}"); + + return builder.toString(); + } + + public long getDataOffset() { + return dataOffset; + } + + public long getFileSize() { + return fileSize; + } + + public String getKeyString() { + return key.toString(); + } + + public Key getKey() { + return key; + } + + public int compareKey(final Key otherKey) { + return otherKey.compareTo(key); + } +} diff --git a/core/src/com/hiveworkshop/blizzard/casc/storage/IndexFile.java b/core/src/com/hiveworkshop/blizzard/casc/storage/IndexFile.java new file mode 100644 index 0000000..e61d9b6 --- /dev/null +++ b/core/src/com/hiveworkshop/blizzard/casc/storage/IndexFile.java @@ -0,0 +1,159 @@ +package com.hiveworkshop.blizzard.casc.storage; + +import java.io.IOException; +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.Collections; + +import com.hiveworkshop.blizzard.casc.Key; +import com.hiveworkshop.blizzard.casc.nio.HashMismatchException; +import com.hiveworkshop.blizzard.casc.nio.LittleHashBlockProcessor; +import com.hiveworkshop.blizzard.casc.nio.MalformedCASCStructureException; + +public class IndexFile { + /** + * Alignment of the index entry block in bytes. + */ + private static final int ENTRY_BLOCK_ALIGNMENT = 16; + + private int bucketIndex; + + private int fileSizeLength; + + private int dataOffsetLength; + + private int encodingKeyLength; + + private int dataFileSizeBits; + + private long dataSizeMaximum; + + private final ArrayList entries = new ArrayList<>(); + + public IndexFile(final ByteBuffer fileBuffer) throws IOException { + decode(fileBuffer); + } + + private void decode(final ByteBuffer fileBuffer) throws IOException { + final ByteBuffer sourceBuffer = fileBuffer.slice(); + + // decode header + + final LittleHashBlockProcessor hashBlockProcessor = new LittleHashBlockProcessor(); + + final ByteBuffer headerBuffer; + try { + headerBuffer = hashBlockProcessor.getBlock(sourceBuffer); + } catch (final HashMismatchException e) { + throw new MalformedCASCStructureException("header block corrupt", e); + } + + headerBuffer.order(ByteOrder.LITTLE_ENDIAN); + + try { + if (headerBuffer.getShort() != 7) { + // possibly malformed + } + bucketIndex = Byte.toUnsignedInt(headerBuffer.get()); + if (headerBuffer.get() != 0) { + // possibly malformed + } + fileSizeLength = Byte.toUnsignedInt(headerBuffer.get()); + dataOffsetLength = Byte.toUnsignedInt(headerBuffer.get()); + encodingKeyLength = Byte.toUnsignedInt(headerBuffer.get()); + dataFileSizeBits = Byte.toUnsignedInt(headerBuffer.get()); + dataSizeMaximum = headerBuffer.getLong(); + } catch (final BufferUnderflowException e) { + throw new MalformedCASCStructureException("header block too small"); + } + + // decode entries + + final int entriesAlignmentMask = ENTRY_BLOCK_ALIGNMENT - 1; + sourceBuffer.position((sourceBuffer.position() + entriesAlignmentMask) & ~entriesAlignmentMask); + + final ByteBuffer entryBuffer; + try { + entryBuffer = hashBlockProcessor.getBlock(sourceBuffer); + } catch (final HashMismatchException e) { + throw new MalformedCASCStructureException("entries block corrupt", e); + } + + final int entryLength = fileSizeLength + dataOffsetLength + encodingKeyLength; + final int entryCount = entryBuffer.remaining() / entryLength; + + entries.ensureCapacity(entryCount); + + final ByteBuffer decodeDataOffsetBuffer = ByteBuffer.allocate(Long.BYTES); + final int decodeDataOffsetOffset = Long.BYTES - dataOffsetLength; + final ByteBuffer decodeFileSizeBuffer = ByteBuffer.allocate(Long.BYTES); + decodeFileSizeBuffer.order(ByteOrder.LITTLE_ENDIAN); + for (int i = 0; i < entryCount; i += 1) { + final byte[] key = new byte[encodingKeyLength]; + entryBuffer.get(key); + + entryBuffer.get(decodeDataOffsetBuffer.array(), decodeDataOffsetOffset, dataOffsetLength); + final long dataOffset = decodeDataOffsetBuffer.getLong(0); + + entryBuffer.get(decodeFileSizeBuffer.array(), 0, fileSizeLength); + final long fileSize = decodeFileSizeBuffer.getLong(0); + + // this can be used to detect special cross linking entries + // if (getIndexNumber(entry.key, entry.key.length) != bucketIndex); + // System.out.println("Bad key index: index=" + i + ", entry=" + entry + ", + // bucket=" + getIndexNumber(entry.key, entry.key.length)); + + entries.add(new IndexEntry(key, dataOffset, fileSize)); + } + + if (entryBuffer.hasRemaining()) { + throw new MalformedCASCStructureException("unable to fully process entries block"); + } + + fileBuffer.position(fileBuffer.position() + sourceBuffer.position()); + } + + public int getBucketIndex() { + return bucketIndex; + } + + public int getStoreIndex(final long dataOffset) { + return (int) (dataOffset >>> dataFileSizeBits); + } + + public long getStoreOffset(final long dataOffset) { + return dataOffset & ((1L << dataFileSizeBits) - 1L); + } + + public long getDataSizeMaximum() { + return dataSizeMaximum; + } + + public IndexEntry getEntry(final Key encodingKey) { + final int index = Collections.binarySearch(entries, encodingKey, (left, right) -> { + if ((left instanceof IndexEntry) && (right instanceof Key)) { + final IndexEntry entry = (IndexEntry) left; + final Key ekey = (Key) right; + return entry.getKey().compareTo(ekey); + } + throw new IllegalArgumentException("binary search comparing in inverted order"); + }); + + return index >= 0 ? entries.get(index) : null; + } + + public IndexEntry getEntry(final int index) { + return entries.get(index); + } + + public int getEntryCount() { + return entries.size(); + } + + public int getEncodingKeyLength() { + return encodingKeyLength; + } + +} diff --git a/core/src/com/hiveworkshop/blizzard/casc/storage/Storage.java b/core/src/com/hiveworkshop/blizzard/casc/storage/Storage.java new file mode 100644 index 0000000..a5b75d2 --- /dev/null +++ b/core/src/com/hiveworkshop/blizzard/casc/storage/Storage.java @@ -0,0 +1,334 @@ +package com.hiveworkshop.blizzard.casc.storage; + +import java.io.EOFException; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.MappedByteBuffer; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.FileChannel; +import java.nio.channels.FileChannel.MapMode; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; + +import com.hiveworkshop.blizzard.casc.Key; +import com.hiveworkshop.blizzard.casc.nio.MalformedCASCStructureException; + +/** + * Main data storage of a CASC archive. It consists of index files which point + * to storage containers in data files. + */ +public class Storage implements AutoCloseable { + /** + * The name of the data folder containing the configuration files. + */ + public static final String DATA_FOLDER_NAME = "data"; + + /** + * Number of index files used by a data store. + */ + private static final int INDEX_COUNT = 16; + + /** + * Usual number of copies of a specific index located in the folder. This is an + * estimate only used to increase search performance and will not effect + * results. + */ + private static final int INDEX_COPIES = 2; + + /** + * File extension used by storage index files. + */ + public static final String INDEX_FILE_EXTENSION = "idx"; + + /** + * File name of data files. 3 character extension is the index. + */ + public static final String DATA_FILE_NAME = "data"; + + /** + * Largest permitted data file index. + */ + public static final int DATA_FILE_INDEX_MAXIMUM = 999; + + /** + * Extension length used by data files. Defined by the length needed to store + * DATA_FILE_INDEX_MAXIMUM as a decimal string. + */ + public static final int DATA_FILE_EXTENSION_LENGTH = 3; + + /** + * Converts an encoding key into an index file number. + * + * @param encodingKey Input encoding key. + * @param keyLength Length of key to be processed. + * @return Index number. + */ + public static int getBucketIndex(final byte[] encodingKey, final int keyLength) { + int accumulator = 0; + for (int i = 0; i < keyLength; i += 1) { + accumulator ^= encodingKey[i]; + } + final int nibbleMask = (1 << 4) - 1; + return (accumulator & nibbleMask) ^ ((accumulator >> 4) & nibbleMask); + } + + private Path folder; + + private final HashMap channelMap = new HashMap<>(); + + private final IndexFile[] indicies = new IndexFile[INDEX_COUNT]; + + /** + * Index file versions loaded. Possibly useful for debugging. + */ + private final long[] idxVersions = new long[INDEX_COUNT]; + + /** + * Used to track closed status of the store. + */ + private boolean closed = false; + + private boolean useMemoryMapping; + + private int encodingKeyLength; + + /** + * Construct a storage object from the provided data folder. + *

+ * Using memory mapping should give the best performance. However some platforms + * or file systems might not support it. + * + * @param dataFolder Path of the CASC data folder. + * @param useOld Use other (old?) version of index files. + * @param useMemoryMapping If IO should be memory mapped. + * @throws IOException If there was a problem loading from the data folder. + */ + public Storage(final Path dataFolder, final boolean useOld, final boolean useMemoryMapping) throws IOException { + folder = dataFolder.resolve(DATA_FOLDER_NAME); + this.useMemoryMapping = useMemoryMapping; + + final ArrayList indexFiles = new ArrayList(INDEX_COUNT * INDEX_COPIES); + try (final DirectoryStream indexFileIterator = Files.newDirectoryStream(folder, + "*." + INDEX_FILE_EXTENSION)) { + for (final Path indexFile : indexFileIterator) { + indexFiles.add(indexFile); + } + } + + class IndexFileNameMeta { + private Path filePath; + private int index; + private long version; + } + + final HashMap> metaMap = new HashMap>( + INDEX_COUNT); + + for (final Path indexFile : indexFiles) { + final String fileName = indexFile.getFileName().toString(); + + final IndexFileNameMeta fileMeta = new IndexFileNameMeta(); + fileMeta.filePath = indexFile; + fileMeta.index = Integer.parseUnsignedInt(fileName.substring(0, 2), 16); + fileMeta.version = Long.parseUnsignedLong(fileName.substring(2, 10), 16); + + ArrayList bucketList = metaMap.get(fileMeta.index); + if (bucketList == null) { + bucketList = new ArrayList<>(); + metaMap.put(fileMeta.index, bucketList); + } + + bucketList.add(fileMeta); + } + + Comparator bucketOrder = (left, right) -> { + return (int) (left.version - right.version); + }; + if (!useOld) { + bucketOrder = Collections.reverseOrder(bucketOrder); + } + + for (int index = 0; index < indicies.length; index += 1) { + final ArrayList bucketList = metaMap.get(index); + if (bucketList == null) { + throw new MalformedCASCStructureException("storage index file missing"); + } + + Collections.sort(bucketList, bucketOrder); + + final IndexFileNameMeta fileMeta = bucketList.get(0); + idxVersions[index] = fileMeta.version; + indicies[index] = new IndexFile(loadFileFully(fileMeta.filePath)); + } + + // resolve index key length being used + int index = 0; + encodingKeyLength = indicies[index++].getEncodingKeyLength(); + for (; index < indicies.length; index += 1) { + if (encodingKeyLength != indicies[index].getEncodingKeyLength()) { + throw new MalformedCASCStructureException("inconsistent encoding key length between index files"); + } + } + } + + @Override + public synchronized void close() throws IOException { + if (closed) { + return; + } + + IOException exception = null; + for (final Map.Entry channelEntry : channelMap.entrySet()) { + try { + channelEntry.getValue().close(); + } catch (final IOException e) { + if (exception != null) { + exception.addSuppressed(e); + } else { + exception = e; + } + } + } + + closed = true; + + if (exception != null) { + throw new IOException("IOExceptions occured during closure", exception); + } + } + + public boolean hasBanks(final Key encodingKey) { + final int bucketIndex = getBucketIndex(encodingKey.getKey(), encodingKeyLength); + final IndexFile index = indicies[bucketIndex]; + final IndexEntry indexEntry = index.getEntry(encodingKey); + + return indexEntry != null; + } + + public BankStream getBanks(final Key encodingKey) throws IOException { + final int bucketIndex = getBucketIndex(encodingKey.getKey(), encodingKeyLength); + final IndexFile index = indicies[bucketIndex]; + final IndexEntry indexEntry = index.getEntry(encodingKey); + + if (indexEntry == null) { + throw new FileNotFoundException("encoding key not in store indicies"); + } + + final long dataOffset = indexEntry.getDataOffset(); + final int storeIndex = index.getStoreIndex(dataOffset); + final long storeOffset = index.getStoreOffset(dataOffset); + + final ByteBuffer storageBuffer = getStorageBuffer(storeIndex, storeOffset, indexEntry.getFileSize()); + + return new BankStream(storageBuffer, indexEntry.getKey()); + } + + private synchronized FileChannel getDataFileChannel(final int index) throws IOException { + if (closed) { + throw new ClosedChannelException(); + } + + FileChannel fileChannel = channelMap.get(index); + if (fileChannel == null) { + if (index > DATA_FILE_INDEX_MAXIMUM) { + throw new MalformedCASCStructureException("storage data file index too large"); + } + + final StringBuilder builder = new StringBuilder(); + builder.append(DATA_FILE_NAME); + builder.append('.'); + final String extensionNumber = Integer.toUnsignedString(index); + final int extensionZeroCount = DATA_FILE_EXTENSION_LENGTH - extensionNumber.length(); + for (int i = 0; i < extensionZeroCount; i += 1) { + builder.append('0'); + } + builder.append(extensionNumber); + + final Path filePath = folder.resolve(builder.toString()); + fileChannel = FileChannel.open(filePath, StandardOpenOption.READ); + channelMap.put(index, fileChannel); + } + + return fileChannel; + } + + /** + * Fetch a buffer from storage. + * + * @param index Data file index. + * @param offset Data file offset. + * @param length Requested buffer length. + * @return Storage buffer. + * @throws IOException If a problem occurs when preparing the storage buffer. + */ + private ByteBuffer getStorageBuffer(final int index, final long offset, final long length) throws IOException { + final FileChannel fileChannel = getDataFileChannel(index); + if (length > Integer.MAX_VALUE) { + throw new MalformedCASCStructureException("data buffer too large to process"); + } + + final ByteBuffer storageBuffer; + if (useMemoryMapping) { + final MappedByteBuffer mappedBuffer = fileChannel.map(MapMode.READ_ONLY, offset, length); + mappedBuffer.load(); + storageBuffer = mappedBuffer; + } else { + storageBuffer = ByteBuffer.allocate((int) length); + while (storageBuffer.hasRemaining() + && (fileChannel.read(storageBuffer, offset + storageBuffer.position()) != -1)) { + ; + } + + if (storageBuffer.hasRemaining()) { + throw new EOFException("unexpected end of file"); + } + storageBuffer.clear(); + } + + return storageBuffer; + } + + /** + * Loads a file fully into memory. Memory mapping is used if allowed. + * + * @param file Path of file to load into memory. + * @return File buffered into memory. + * @throws IOException If an IO exception occurs. + */ + private ByteBuffer loadFileFully(final Path file) throws IOException { + final ByteBuffer fileBuffer; + try (final FileChannel channel = FileChannel.open(file, StandardOpenOption.READ)) { + final long fileLength = channel.size(); + if (fileLength > Integer.MAX_VALUE) { + throw new MalformedCASCStructureException("file too large to process"); + } + + if (useMemoryMapping) { + final MappedByteBuffer mappedBuffer = channel.map(MapMode.READ_ONLY, 0, fileLength); + mappedBuffer.load(); + fileBuffer = mappedBuffer; + } else { + fileBuffer = ByteBuffer.allocate((int) fileLength); + while (fileBuffer.hasRemaining() && (channel.read(fileBuffer, fileBuffer.position()) != -1)) { + ; + } + + if (fileBuffer.hasRemaining()) { + throw new EOFException("unexpected end of file"); + } + fileBuffer.clear(); + } + } + + return fileBuffer; + } + +} diff --git a/core/src/com/hiveworkshop/blizzard/casc/storage/StorageContainer.java b/core/src/com/hiveworkshop/blizzard/casc/storage/StorageContainer.java new file mode 100644 index 0000000..8bd0885 --- /dev/null +++ b/core/src/com/hiveworkshop/blizzard/casc/storage/StorageContainer.java @@ -0,0 +1,92 @@ +package com.hiveworkshop.blizzard.casc.storage; + +import java.io.IOException; +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import com.hiveworkshop.blizzard.casc.Key; +import com.hiveworkshop.blizzard.casc.nio.HashMismatchException; +import com.hiveworkshop.blizzard.casc.nio.MalformedCASCStructureException; + +/** + * High level storage container representing a storage entry. + */ +public class StorageContainer { + /** + * Size of storage encoding key in bytes. + */ + private static final int ENCODING_KEY_SIZE = 16; + + /** + * Container encoding key. + */ + private Key key; + private long size; + private short flags; + + public StorageContainer(final ByteBuffer storageBuffer) throws IOException { + final ByteBuffer containerBuffer = storageBuffer.slice(); + containerBuffer.order(ByteOrder.LITTLE_ENDIAN); + + // key is in reversed byte order + final int checksumA; + final int checksumB; + try { + final byte[] keyArray = new byte[ENCODING_KEY_SIZE]; + final int keyEnd = containerBuffer.position() + keyArray.length; + for (int writeIndex = 0, readIndex = keyEnd + - 1; writeIndex < keyArray.length; writeIndex += 1, readIndex -= 1) { + keyArray[writeIndex] = containerBuffer.get(readIndex); + } + containerBuffer.position(keyEnd); + + key = new Key(keyArray); + size = Integer.toUnsignedLong(containerBuffer.getInt()); + flags = containerBuffer.getShort(); + + checksumA = containerBuffer.getInt(); + checksumB = containerBuffer.getInt(); + } catch (final BufferUnderflowException e) { + throw new MalformedCASCStructureException("storage buffer too small"); + } + + final int computedA = checksumA; // TODO compute this + final int computedB = checksumB; // TODO compute this + if (checksumA != computedA) { + throw new HashMismatchException("container checksum A mismatch"); + } + if (checksumB != computedB) { + throw new HashMismatchException("container checksum B mismatch"); + } + + storageBuffer.position(storageBuffer.position() + containerBuffer.position()); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("FileEntry{key="); + builder.append(key); + builder.append(", size="); + builder.append(size); + builder.append(", flags="); + builder.append(Integer.toBinaryString(flags)); + builder.append("}"); + + return builder.toString(); + } + + public long getSize() { + return size; + } + + public short getFlags() { + return flags; + } + + public Key getKey() { + return key; + } + +} diff --git a/core/src/com/hiveworkshop/blizzard/casc/trash/LocalDataFiles.java b/core/src/com/hiveworkshop/blizzard/casc/trash/LocalDataFiles.java new file mode 100644 index 0000000..51f348c --- /dev/null +++ b/core/src/com/hiveworkshop/blizzard/casc/trash/LocalDataFiles.java @@ -0,0 +1,343 @@ +package com.hiveworkshop.blizzard.casc.trash; + +import java.io.Closeable; +import java.io.EOFException; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.channels.FileChannel; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.zip.DataFormatException; +import java.util.zip.Inflater; + +import com.hiveworkshop.blizzard.casc.nio.HashMismatchException; +import com.hiveworkshop.blizzard.casc.nio.MalformedCASCStructureException; +import com.hiveworkshop.lang.Hex; + +public class LocalDataFiles implements Closeable { + private static final int FRAGMENTATION_SIZE_BITS = 30; + + private static final int FILE_ENTRY_HEADER_SIZE = 30; + + private static final byte[] BLTE_MIME = new byte[] { 'B', 'L', 'T', 'E' }; + + private final HashMap dataFiles = new HashMap(); + + public LocalDataFiles(final Path dataPath) throws IOException { + mountDataFiles(dataPath); + } + + public void mountDataFiles(final Path dataPath) throws IOException { + try (final DirectoryStream dataFileIterator = Files.newDirectoryStream(dataPath, "data.*")) { + for (final Path dataFile : dataFileIterator) { + System.out.println(dataFile); + final String fileName = dataFile.getFileName().toString(); + final int number = Integer.parseInt(fileName.substring(5, 8)); + dataFiles.put(number, FileChannel.open(dataFile, StandardOpenOption.READ)); + } + + } catch (final Exception e) { + close(); + } + } + + @Override + public void close() throws IOException { + IOException exception = null; + for (final Map.Entry dataFileEntry : dataFiles.entrySet()) { + try { + dataFileEntry.getValue().close(); + } catch (final IOException e) { + exception = e; + } + } + + if (exception != null) { + throw new IOException("one or more IOExceptions occured during closure", exception); + } + } + + public FileEntry getFileEntry(final LocalIndexFile.IndexEntry indexEntry) throws IOException { + final ByteBuffer fileHeader = ByteBuffer.allocate(FILE_ENTRY_HEADER_SIZE); + + final long dataOffset = indexEntry.getDataOffset(); + final int dataFile = (int) (dataOffset >>> FRAGMENTATION_SIZE_BITS); + final long fileOffset = dataOffset & (1L << FRAGMENTATION_SIZE_BITS) - 1L; + final FileChannel channel = dataFiles.get(dataFile); + if (channel.read(fileHeader, fileOffset) != fileHeader.limit()) { + throw new EOFException("unexpected incomplete read"); + } + fileHeader.flip(); + + fileHeader.order(ByteOrder.LITTLE_ENDIAN); + final FileEntry fileEntry = new FileEntry(); + fileEntry.dataFile = dataFile; + fileEntry.fileOffset = fileOffset; + + // key is in reversed byte order + final byte[] key = new byte[16]; + int keyPos = fileHeader.position() + key.length; + fileHeader.position(keyPos); + for (int i = 0; i < key.length; i++) { + key[i] = fileHeader.get(--keyPos); + } + if (!indexEntry.compareKey(key)) { + throw new HashMismatchException("file entry does not match index entry"); + } + fileEntry.key = key; + + fileEntry.size = Integer.toUnsignedLong(fileHeader.getInt()); + fileEntry.flags = fileHeader.getShort(); + + // TODO actually check these + final int ChecksumA = fileHeader.getInt(); + final int ChecksumB = fileHeader.getInt(); + + return fileEntry; + } + + public BLTEChunk[] getBLTEChunks(final FileEntry file) throws IOException { + final FileChannel channel = dataFiles.get(file.dataFile); + final ByteBuffer blteDeclareHeader = ByteBuffer.allocate(8); + + final long blteOffset = file.fileOffset + FILE_ENTRY_HEADER_SIZE; + long currentOffset = blteOffset; + final long blteLimit = file.fileOffset + file.size; + if (blteLimit - currentOffset < blteDeclareHeader.capacity()) { + throw new MalformedCASCStructureException("BLTE header extends beyond file limits"); + } + + currentOffset += channel.read(blteDeclareHeader, currentOffset); + if (blteDeclareHeader.hasRemaining()) { + throw new EOFException("unexpected incomplete read"); + } + blteDeclareHeader.flip(); + + blteDeclareHeader.order(ByteOrder.BIG_ENDIAN); + final byte[] mime = new byte[BLTE_MIME.length]; + blteDeclareHeader.get(mime); + if (!Arrays.equals(mime, BLTE_MIME)) { + throw new MalformedCASCStructureException("expected BLTE mime"); + } + final long headerSize = Integer.toUnsignedLong(blteDeclareHeader.getInt()); + + BLTEChunk[] chunks; + if (headerSize > 0) { + final long headerBodySize = headerSize - blteDeclareHeader.capacity(); + + if (headerBodySize > Integer.MAX_VALUE) { + throw new MalformedCASCStructureException("BLTE header too large to process"); + } else if (blteOffset + headerBodySize > blteLimit) { + throw new MalformedCASCStructureException("BLTE header extends beyond file limits"); + } + + final ByteBuffer blteHeaderBody = ByteBuffer.allocate((int) headerBodySize); + + currentOffset += channel.read(blteHeaderBody, currentOffset); + if (blteHeaderBody.hasRemaining()) { + throw new EOFException("unexpected incomplete read"); + } + blteHeaderBody.flip(); + + blteHeaderBody.order(ByteOrder.BIG_ENDIAN); + blteHeaderBody.mark(); + final byte flags = blteHeaderBody.get(); + if (flags != 0xF) { + throw new MalformedCASCStructureException("unknown BLTE flags"); + } + // BE24 read + blteHeaderBody.reset(); + blteHeaderBody.put((byte) 0); + blteHeaderBody.reset(); + + final int chunkCount = blteHeaderBody.getInt(); + if (chunkCount < 0) { + throw new MalformedCASCStructureException("BLTE chunk count too large to process"); + } else if (chunkCount == 0) { + throw new MalformedCASCStructureException("invalid BLTE chunk count"); + } + + chunks = new BLTEChunk[chunkCount]; + long decompressedOffset = 0; + long compressedOffset = currentOffset - file.fileOffset; + for (int i = 0; i < chunks.length; i += 1) { + final long compressedSize = Integer.toUnsignedLong(blteHeaderBody.getInt()); + final long decompressedSize = Integer.toUnsignedLong(blteHeaderBody.getInt()); + final byte[] checksumHash = new byte[16]; + blteHeaderBody.get(checksumHash); + + final BLTEChunk chunk = new BLTEChunk(); + chunk.compressedOffset = compressedOffset; + chunk.compressedSize = compressedSize; + chunk.decompressedOffset = decompressedOffset; + chunk.decompressedSize = decompressedSize; + chunk.checksumHash = checksumHash; + chunks[i] = chunk; + + compressedOffset += compressedSize; + decompressedOffset += decompressedSize; + } + } else { + chunks = new BLTEChunk[1]; + final BLTEChunk chunk = new BLTEChunk(); + chunk.compressedOffset = currentOffset - file.fileOffset; + chunk.compressedSize = blteLimit - currentOffset; + chunk.decompressedOffset = 0; + + // unknown values, needs experimentation + chunk.decompressedSize = chunk.compressedSize; + chunk.checksumHash = null; + chunks[0] = chunk; + } + return chunks; + } + + public ByteBuffer getBLTEData(final FileEntry file, final BLTEChunk chunk, ByteBuffer blteDataBuffer) + throws IOException { + if (chunk.compressedSize + chunk.compressedOffset > file.size) { + throw new MalformedCASCStructureException("BLTE data extends beyond file data"); + } + + if (blteDataBuffer == null || blteDataBuffer.remaining() < chunk.compressedSize) { + blteDataBuffer = ByteBuffer.allocate((int) chunk.compressedSize); + } + + final int blteDataBufferLimit = blteDataBuffer.limit(); + blteDataBuffer.limit(blteDataBuffer.position() + (int) chunk.compressedSize); + try { + final FileChannel channel = dataFiles.get(file.dataFile); + channel.read(blteDataBuffer, file.fileOffset + chunk.compressedOffset); + if (blteDataBuffer.hasRemaining()) { + throw new EOFException("unexpected incomplete read"); + } + } finally { + blteDataBuffer.position(blteDataBuffer.limit()); + blteDataBuffer.limit(blteDataBufferLimit); + } + + return blteDataBuffer; + } + + public ByteBuffer getFileData(final BLTEChunk chunk, final ByteBuffer blteDataBuffer, ByteBuffer fileDataBuffer) + throws IOException { + if (blteDataBuffer.remaining() < chunk.compressedSize) { + throw new MalformedCASCStructureException("BLTE data too small"); + } + + if (fileDataBuffer == null || fileDataBuffer.remaining() < chunk.decompressedSize) { + fileDataBuffer = ByteBuffer.allocate((int) chunk.decompressedSize); + } + + final int blteDataBufferLimit = blteDataBuffer.limit(); + final int fileDataBufferLimit = fileDataBuffer.limit(); + blteDataBuffer.limit(blteDataBuffer.position() + (int) chunk.compressedSize); + fileDataBuffer.limit(fileDataBuffer.position() + (int) chunk.decompressedSize); + try { + final char encodingMode = (char) blteDataBuffer.get(); + switch (encodingMode) { + case 'N': + if (blteDataBuffer.remaining() != chunk.decompressedSize) { + throw new MalformedCASCStructureException("not enough uncompressed bytes"); + } + fileDataBuffer.put(blteDataBuffer); + break; + case 'Z': + final Inflater zlib = new Inflater(); + zlib.setInput(blteDataBuffer.array(), blteDataBuffer.position(), blteDataBuffer.remaining()); + int resultSize; + try { + resultSize = zlib.inflate(fileDataBuffer.array(), fileDataBuffer.position(), + fileDataBuffer.remaining()); + } catch (final DataFormatException e) { + throw new MalformedCASCStructureException("zlib inflate exception", e); + } + if (resultSize != chunk.decompressedSize) { + throw new MalformedCASCStructureException("not enough bytes generated: " + resultSize + "B"); + } else if (!zlib.finished()) { + throw new MalformedCASCStructureException("unfinished inflate operation"); + } + break; + default: + throw new UnsupportedEncodingException("unsupported encoding mode: " + encodingMode); + } + } finally { + blteDataBuffer.position(blteDataBuffer.limit()); + blteDataBuffer.limit(blteDataBufferLimit); + fileDataBuffer.position(fileDataBuffer.limit()); + fileDataBuffer.limit(fileDataBufferLimit); + } + + return fileDataBuffer; + } + + public static class FileEntry { + private byte[] key; + private int dataFile; + private long fileOffset; + private long size; + private short flags; + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("FileEntry{key=0x"); + Hex.stringBufferAppendHex(builder, key); + builder.append(", dataFile="); + builder.append(dataFile); + builder.append(", fileOffset="); + builder.append(fileOffset); + builder.append(", size="); + builder.append(size); + builder.append(", flags="); + builder.append(Integer.toBinaryString(flags)); + builder.append("}"); + + return builder.toString(); + } + + public boolean hasBLTE() { + return size > FILE_ENTRY_HEADER_SIZE; + } + } + + public static class BLTEChunk { + private long compressedOffset; + private long compressedSize; + private long decompressedOffset; + private long decompressedSize; + private byte[] checksumHash; + + public long getSize() { + return decompressedSize; + } + + public long getOffset() { + return decompressedOffset; + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("BLTEChunk{compressedSize="); + builder.append(compressedSize); + builder.append(", decompressedSize="); + builder.append(decompressedSize); + builder.append(", compressedOffset="); + builder.append(compressedOffset); + builder.append(", decompressedOffset="); + builder.append(decompressedOffset); + builder.append(", checksumHash=0x"); + Hex.stringBufferAppendHex(builder, checksumHash); + builder.append("}"); + + return builder.toString(); + } + } +} diff --git a/core/src/com/hiveworkshop/blizzard/casc/trash/LocalIndexFile.java b/core/src/com/hiveworkshop/blizzard/casc/trash/LocalIndexFile.java new file mode 100644 index 0000000..d0ae136 --- /dev/null +++ b/core/src/com/hiveworkshop/blizzard/casc/trash/LocalIndexFile.java @@ -0,0 +1,171 @@ +package com.hiveworkshop.blizzard.casc.trash; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; + +import com.hiveworkshop.ReteraCASCUtils; +import com.hiveworkshop.blizzard.casc.nio.HashMismatchException; +import com.hiveworkshop.blizzard.casc.nio.LittleHashBlockProcessor; +import com.hiveworkshop.lang.Hex; + +public class LocalIndexFile { + private byte bucketIndex; + + private byte entryFileSizeLength; + + private byte entryDataOffsetLength; + + private byte entryKeyLength; + + private byte dataFileSizeBits; + + private long dataSizeMaximum; + + private final ArrayList entries = new ArrayList<>(); + + public static int getIndexNumber(final byte[] key, final int keyLength) { + int accumulator = 0; + for (int i = 0; i < keyLength; i += 1) { + accumulator ^= key[i]; + } + final int nibbleMask = (1 << 4) - 1; + return accumulator & nibbleMask ^ accumulator >> 4 & nibbleMask; + } + + public LocalIndexFile(final ByteBuffer encodedFileBuffer) throws IOException { + decode(encodedFileBuffer); + } + + public void decode(final ByteBuffer encodedFileBuffer) throws IOException { + final LittleHashBlockProcessor hashBlockProcessor = new LittleHashBlockProcessor(); + final int fileLength = encodedFileBuffer.limit(); + + final int headerLength = hashBlockProcessor.processBlock(encodedFileBuffer); + if (headerLength < 0) { + throw new HashMismatchException("index header corrupt"); + } + + encodedFileBuffer.limit(encodedFileBuffer.position() + headerLength); + encodedFileBuffer.order(ByteOrder.LITTLE_ENDIAN); + + if (encodedFileBuffer.getShort() != 7) { + // possibly malformed + } + bucketIndex = encodedFileBuffer.get(); + if (encodedFileBuffer.get() != 0) { + // possibly malformed + } + entryFileSizeLength = encodedFileBuffer.get(); + entryDataOffsetLength = encodedFileBuffer.get(); + entryKeyLength = encodedFileBuffer.get(); + dataFileSizeBits = encodedFileBuffer.get(); + dataSizeMaximum = encodedFileBuffer.getLong(); + + encodedFileBuffer.limit(fileLength); + final int entriesAlignmentMask = (1 << 4) - 1; + encodedFileBuffer.position((encodedFileBuffer.position() + entriesAlignmentMask) & ~entriesAlignmentMask); + + final int entriesLength = hashBlockProcessor.processBlock(encodedFileBuffer); + if (entriesLength < 0) { + throw new HashMismatchException("index entries corrupt"); + } + + encodedFileBuffer.limit(encodedFileBuffer.position() + entriesLength); + final int entryLength = entryFileSizeLength + entryDataOffsetLength + entryKeyLength; + final int entryCount = encodedFileBuffer.remaining() / entryLength; + + entries.ensureCapacity(entryCount); + + final ByteBuffer decodeDataOffsetBuffer = ByteBuffer.allocate(Long.BYTES); + final int decodeDataOffsetOffset = Long.BYTES - entryDataOffsetLength; + final ByteBuffer decodeFileSizeBuffer = ByteBuffer.allocate(Long.BYTES); + decodeFileSizeBuffer.order(ByteOrder.LITTLE_ENDIAN); + for (int i = 0; i < entryCount; i += 1) { + final IndexEntry entry = new IndexEntry(); + + entry.key = new byte[entryKeyLength]; + encodedFileBuffer.get(entry.key); + + encodedFileBuffer.get(decodeDataOffsetBuffer.array(), decodeDataOffsetOffset, entryDataOffsetLength); + entry.dataOffset = decodeDataOffsetBuffer.getLong(0); + + encodedFileBuffer.get(decodeFileSizeBuffer.array(), 0, entryFileSizeLength); + entry.fileSize = decodeFileSizeBuffer.getLong(0); + + // this can be used to detect special cross linking entries + // if (getIndexNumber(entry.key, entry.key.length) != bucketIndex); + // System.out.println("Bad key index: index=" + i + ", entry=" + entry + ", + // bucket=" + getIndexNumber(entry.key, entry.key.length)); + + entries.add(entry); + } + + encodedFileBuffer.limit(fileLength); + } + + public IndexEntry getEntry(final byte[] key) { + for (final LocalIndexFile.IndexEntry indexEntry : entries) { + if (ReteraCASCUtils.arraysEquals(indexEntry.key, 0, entryKeyLength, key, 0, entryKeyLength)) { + return indexEntry; + } + } + + return null; + } + + public IndexEntry getEntry(final int index) { + return entries.get(index); + } + + public int getEntryCount() { + return entries.size(); + } + + public long getDataFileOffset(final long dataOffset) { + return dataOffset & (1L << dataFileSizeBits) - 1L; + } + + public int getDataFileNumber(final long dataOffset) { + return (int) (dataOffset >>> dataFileSizeBits); + } + + public static class IndexEntry { + private byte[] key; + private long dataOffset; + private long fileSize; + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("IndexEntry{key=0x"); + Hex.stringBufferAppendHex(builder, key); + builder.append(", dataOffset="); + builder.append(dataOffset); + builder.append(", fileSize="); + builder.append(fileSize); + builder.append("}"); + + return builder.toString(); + } + + public long getDataOffset() { + return dataOffset; + } + + public long getFileSize() { + return fileSize; + } + + public String getKeyString() { + final StringBuilder builder = new StringBuilder(); + Hex.stringBufferAppendHex(builder, key); + return builder.toString(); + } + + public boolean compareKey(final byte[] otherKey) { + return ReteraCASCUtils.arraysEquals(key, 0, key.length, otherKey, 0, key.length); + } + } +} diff --git a/core/src/com/hiveworkshop/blizzard/casc/trash/VirtualFileSystem.java b/core/src/com/hiveworkshop/blizzard/casc/trash/VirtualFileSystem.java new file mode 100644 index 0000000..c83c48a --- /dev/null +++ b/core/src/com/hiveworkshop/blizzard/casc/trash/VirtualFileSystem.java @@ -0,0 +1,86 @@ +package com.hiveworkshop.blizzard.casc.trash; + +import java.io.IOException; +import java.io.PrintStream; +import java.nio.ByteBuffer; + +import com.hiveworkshop.blizzard.casc.nio.MalformedCASCStructureException; +//import com.hiveworkshop.blizzard.casc.vfs.path.Container; +//import com.hiveworkshop.blizzard.casc.vfs.path.FileFactory; +//import com.hiveworkshop.blizzard.casc.vfs.path.Node; + +public class VirtualFileSystem { + + private static final ByteBuffer IDENTIFIER = ByteBuffer.wrap(new byte[] { 'T', 'V', 'F', 'S' }); + + // private final ArrayList root; + + public VirtualFileSystem(final ByteBuffer fileBuffer) throws IOException { + final ByteBuffer localBuffer = fileBuffer.slice(); + + // check identifier + + if (localBuffer.remaining() < IDENTIFIER.remaining() + || !localBuffer.limit(IDENTIFIER.remaining()).equals(IDENTIFIER)) { + throw new MalformedCASCStructureException("missing TVFS identifier"); + } + + // decode header + + localBuffer.limit(localBuffer.capacity()); + localBuffer.position(IDENTIFIER.remaining()); + + final byte version = localBuffer.get(); + if (version != 1) { + throw new UnsupportedOperationException("unsupported vfs version: " + version); + } + final int headerSize = Byte.toUnsignedInt(localBuffer.get()); + if (headerSize < 0x26) { + throw new MalformedCASCStructureException("vfs header too small"); + } + localBuffer.limit(headerSize); + + final int encodingKeySize = Byte.toUnsignedInt(localBuffer.get()); + final int patchKeySize = Byte.toUnsignedInt(localBuffer.get()); + + final int flags = localBuffer.getInt(); + + final int pathOffset = localBuffer.getInt(); + final int pathSize = localBuffer.getInt(); + final int fileReferenceOffset = localBuffer.getInt(); + final int fileReferenceSize = localBuffer.getInt(); + final int contentOffset = localBuffer.getInt(); + final int contentSize = localBuffer.getInt(); + + final int maximumPathDepth = Short.toUnsignedInt(localBuffer.getShort()); + + final int containerTableOffsetSize = Math.max(1, + Integer.BYTES - Integer.numberOfLeadingZeros(contentSize) / Byte.SIZE); + + localBuffer.limit(pathOffset + pathSize); + localBuffer.position(pathOffset); + final ByteBuffer pathBuffer = localBuffer.slice(); + localBuffer.clear(); + + localBuffer.limit(fileReferenceOffset + fileReferenceSize); + localBuffer.position(fileReferenceOffset); + final ByteBuffer fileReferenceBuffer = localBuffer.slice(); + localBuffer.clear(); + + localBuffer.limit(contentOffset + contentSize); + localBuffer.position(contentOffset); + final ByteBuffer contentBuffer = localBuffer.slice(); + localBuffer.clear(); + + // final var fileFactory = new FileFactory(fileReferenceBuffer, contentBuffer, + // encodingKeySize, containerTableOffsetSize); + + // root = Container.decodeContainer(pathBuffer, fileFactory); + } + + public void printPaths(final PrintStream out) { + /* + * for (final var node : root) { node.printPaths(out, ""); } + */ + } +} diff --git a/core/src/com/hiveworkshop/blizzard/casc/vfs/FileNode.java b/core/src/com/hiveworkshop/blizzard/casc/vfs/FileNode.java new file mode 100644 index 0000000..a7c3fd9 --- /dev/null +++ b/core/src/com/hiveworkshop/blizzard/casc/vfs/FileNode.java @@ -0,0 +1,24 @@ +package com.hiveworkshop.blizzard.casc.vfs; + +import java.util.List; + +/** + * A file system node containing a logical file. + */ +public class FileNode extends PathNode { + private final StorageReference[] references; + + protected FileNode(final List pathFragments, final List references) { + super(pathFragments); + this.references = references.toArray(new StorageReference[0]); + } + + public int getFileReferenceCount() { + return references.length; + } + + public StorageReference getFileReference(final int index) { + return references[index]; + } + +} diff --git a/core/src/com/hiveworkshop/blizzard/casc/vfs/PathNode.java b/core/src/com/hiveworkshop/blizzard/casc/vfs/PathNode.java new file mode 100644 index 0000000..007b9d3 --- /dev/null +++ b/core/src/com/hiveworkshop/blizzard/casc/vfs/PathNode.java @@ -0,0 +1,27 @@ +package com.hiveworkshop.blizzard.casc.vfs; + +import java.util.List; + +/** + * Represents a path node. Path nodes can either be prefix nodes or file nodes. + */ +public abstract class PathNode { + /** + * Array of path fragments. Each fragment represents part of a path string. Due + * to the potential for multi byte encoding, one cannot assume that each + * fragment can be assembled into a valid string. + */ + private final byte[][] pathFragments; + + protected PathNode(final List pathFragments) { + this.pathFragments = pathFragments.toArray(new byte[0][]); + } + + public int getPathFragmentCount() { + return pathFragments.length; + } + + public byte[] getFragment(final int index) { + return pathFragments[index]; + } +} diff --git a/core/src/com/hiveworkshop/blizzard/casc/vfs/PrefixNode.java b/core/src/com/hiveworkshop/blizzard/casc/vfs/PrefixNode.java new file mode 100644 index 0000000..938dd93 --- /dev/null +++ b/core/src/com/hiveworkshop/blizzard/casc/vfs/PrefixNode.java @@ -0,0 +1,26 @@ +package com.hiveworkshop.blizzard.casc.vfs; + +import java.util.List; + +/** + * Prefix nodes generate a path prefix for other nodes. + */ +public class PrefixNode extends PathNode { + /** + * Array of child node that this node forms a prefix of. + */ + private final PathNode[] nodes; + + protected PrefixNode(final List pathFragments, final List nodes) { + super(pathFragments); + this.nodes = nodes.toArray(new PathNode[0]); + } + + public int getNodeCount() { + return nodes.length; + } + + public PathNode getNode(final int index) { + return nodes[index]; + } +} diff --git a/core/src/com/hiveworkshop/blizzard/casc/vfs/StorageReference.java b/core/src/com/hiveworkshop/blizzard/casc/vfs/StorageReference.java new file mode 100644 index 0000000..aeeef6a --- /dev/null +++ b/core/src/com/hiveworkshop/blizzard/casc/vfs/StorageReference.java @@ -0,0 +1,81 @@ +package com.hiveworkshop.blizzard.casc.vfs; + +import com.hiveworkshop.blizzard.casc.Key; + +/** + * A reference to a file in CASC storage. + */ +public class StorageReference { + /** + * Logical offset of this chunk. + */ + private long offset = 0; + + /** + * Logical size of this chunk. + */ + private long size = 0; + + /** + * Encoding key of chunk. + */ + private Key encodingKey = null; + + /** + * Physical size of stored data. + */ + private long physicalSize = 0; + + /** + * Total size of all decompressed data banks. + */ + private long actualSize = 0; + + public StorageReference(final long offset, final long size, final Key encodingKey, final int physicalSize, + final int actualSize) { + this.offset = offset; + this.size = size; + this.encodingKey = encodingKey; + this.physicalSize = physicalSize; + this.actualSize = actualSize; + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("FileReference{encodingKey="); + builder.append(encodingKey); + builder.append(", offset="); + builder.append(offset); + builder.append(", size="); + builder.append(size); + builder.append(", physicalSize="); + builder.append(physicalSize); + builder.append(", actualSize="); + builder.append(actualSize); + builder.append("}"); + + return builder.toString(); + } + + public long getOffset() { + return offset; + } + + public long getSize() { + return size; + } + + public Key getEncodingKey() { + return encodingKey; + } + + public long getPhysicalSize() { + return physicalSize; + } + + public long getActualSize() { + return actualSize; + } + +} diff --git a/core/src/com/hiveworkshop/blizzard/casc/vfs/TVFSDecoder.java b/core/src/com/hiveworkshop/blizzard/casc/vfs/TVFSDecoder.java new file mode 100644 index 0000000..d84c2dd --- /dev/null +++ b/core/src/com/hiveworkshop/blizzard/casc/vfs/TVFSDecoder.java @@ -0,0 +1,253 @@ +package com.hiveworkshop.blizzard.casc.vfs; + +import java.io.IOException; +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import com.hiveworkshop.blizzard.casc.Key; +import com.hiveworkshop.blizzard.casc.nio.MalformedCASCStructureException; + +/** + * Decodes file data for file value nodes. + *

+ * This is done by collating together data. First the file is resolved from the + * file buffer then the data describing its contents is resolved from the + * contents buffer. + */ +public class TVFSDecoder { + /** + * TVFS file identifier located at start of TVFS files. + */ + private static final ByteBuffer IDENTIFIER = ByteBuffer.wrap(new byte[] { 'T', 'V', 'F', 'S' }); + + /** + * Flag for container values. If set inside a value then the value is a + * container of other nodes otherwise it is a file. + */ + private static final int VALUE_CONTAINER_FLAG = 0x80000000; + + /** + * Specifier for path node value. If path string length is this then value + * follows. + */ + private static final int VALUE_PATH_STRING_LENGTH = 0xFF; + + private byte version = 0; + private int flags = 0; + private int encodingKeySize = 0; + private int patchKeySize = 0; + private int pathOffset = 0; + private int pathSize = 0; + private int fileReferenceOffset = 0; + private int fileReferenceSize = 0; + private int cascReferenceOffset = 0; + private int cascReferenceSize = 0; + private int maximumPathDepth = 0; + + private int contentsOffsetSize = 0; + private ByteBuffer pathBuffer = null; + private ByteBuffer logicalBuffer = null; + private ByteBuffer storageBuffer = null; + + /** + * The offset into the content buffer is a special type that uses the minimum + * number of bytes to hold the largest offset. Hence non-standard types such as + * 3 bytes long big-endian integer are possible so a special buffer is needed to + * decode these numbers. + */ + private final ByteBuffer contentsOffsetDecoder; + + public TVFSDecoder() { + contentsOffsetDecoder = ByteBuffer.allocate(Integer.BYTES); + } + + public List decodeContainer() throws MalformedCASCStructureException { + return decodeContainer(pathBuffer); + } + + private List decodeContainer(final ByteBuffer pathBuffer) throws MalformedCASCStructureException { + final ArrayList nodes = new ArrayList(); + + while (pathBuffer.hasRemaining()) { + final PathNode node = decodeNode(pathBuffer); + nodes.add(node); + } + + return nodes; + } + + private PathNode decodeNode(final ByteBuffer pathBuffer) throws MalformedCASCStructureException { + final ArrayList pathFragments = new ArrayList(); + + PathNode node; + try { + int pathStringLength; + while ((pathStringLength = Byte.toUnsignedInt(pathBuffer.get())) != VALUE_PATH_STRING_LENGTH) { + final byte[] pathFragment = new byte[pathStringLength]; + pathBuffer.get(pathFragment); + pathFragments.add(pathFragment); + } + + final int value = pathBuffer.getInt(); + if ((value & VALUE_CONTAINER_FLAG) != 0) { + // prefix node + final int containerSize = value & ~VALUE_CONTAINER_FLAG; + pathBuffer.position(pathBuffer.position() - Integer.BYTES); + + if (containerSize > pathBuffer.remaining()) { + throw new MalformedCASCStructureException("prefix node container extends beyond path container"); + } + + pathBuffer.limit(pathBuffer.position() + containerSize); + + final ByteBuffer containerBuffer = pathBuffer.slice(); + + pathBuffer.position(pathBuffer.limit()); + pathBuffer.limit(pathBuffer.capacity()); + + containerBuffer.position(Integer.BYTES); + + final List nodes = decodeContainer(containerBuffer); + + node = new PrefixNode(pathFragments, nodes); + } else { + // file value + final StorageReference[] fileReferences = getFileReferences(value); + + node = new FileNode(pathFragments, Arrays.asList(fileReferences)); + } + } catch (final BufferUnderflowException e) { + throw new MalformedCASCStructureException("path stream goes beyond path container"); + } + + return node; + } + + private StorageReference[] getFileReferences(final int fileOffset) throws MalformedCASCStructureException { + if (fileOffset > logicalBuffer.limit()) { + throw new MalformedCASCStructureException("logical offset beyond file reference chunk"); + } + logicalBuffer.position(fileOffset); + + StorageReference[] references; + + try { + final int referenceCount = Byte.toUnsignedInt(logicalBuffer.get()); + references = new StorageReference[referenceCount]; + for (int i = 0; i < referenceCount; i += 1) { + final long offset = Integer.toUnsignedLong(logicalBuffer.getInt()); + final long size = Integer.toUnsignedLong(logicalBuffer.getInt()); + + logicalBuffer.get(contentsOffsetDecoder.array(), Integer.BYTES - contentsOffsetSize, + contentsOffsetSize); + final int cascReferenceOffset = contentsOffsetDecoder.getInt(0); + + if (cascReferenceOffset > storageBuffer.limit()) { + throw new MalformedCASCStructureException("storage offset beyond casc reference chunk"); + } + storageBuffer.position(cascReferenceOffset); + + try { + final byte[] encodingKeyDecoder = new byte[encodingKeySize]; + storageBuffer.get(encodingKeyDecoder); + + final int physicalSize = storageBuffer.getInt(); + final int actualSize = storageBuffer.getInt(); + + final StorageReference reference = new StorageReference(offset, size, new Key(encodingKeyDecoder), + physicalSize, actualSize); + references[i] = reference; + } catch (final BufferUnderflowException e) { + throw new MalformedCASCStructureException("storage goes out of bounds"); + } + } + } catch (final BufferUnderflowException e) { + throw new MalformedCASCStructureException("logical reference goes out of bounds"); + } + + return references; + } + + public TVFSFile loadFile(final ByteBuffer fileBuffer) throws IOException { + final ByteBuffer localBuffer = fileBuffer.slice(); + + // check identifier + + if ((localBuffer.remaining() < IDENTIFIER.remaining()) + || !localBuffer.limit(IDENTIFIER.remaining()).equals(IDENTIFIER)) { + throw new MalformedCASCStructureException("missing TVFS identifier"); + } + + // decode header + + localBuffer.limit(localBuffer.capacity()); + localBuffer.position(IDENTIFIER.remaining()); + + try { + version = localBuffer.get(); + if (version != 1) { + throw new UnsupportedOperationException("unsupported TVFS version: " + version); + } + final int headerSize = Byte.toUnsignedInt(localBuffer.get()); + if (headerSize > localBuffer.capacity()) { + throw new MalformedCASCStructureException("TVFS header extends past end of file"); + } + localBuffer.limit(headerSize); + + encodingKeySize = Byte.toUnsignedInt(localBuffer.get()); + patchKeySize = Byte.toUnsignedInt(localBuffer.get()); + flags = localBuffer.getInt(); + + pathOffset = localBuffer.getInt(); + pathSize = localBuffer.getInt(); + if ((Integer.toUnsignedLong(pathOffset) + Integer.toUnsignedLong(pathSize)) > localBuffer.capacity()) { + throw new MalformedCASCStructureException("path stream extends past end of file"); + } + + fileReferenceOffset = localBuffer.getInt(); + fileReferenceSize = localBuffer.getInt(); + if ((Integer.toUnsignedLong(fileReferenceOffset) + Integer.toUnsignedLong(fileReferenceSize)) > localBuffer + .capacity()) { + throw new MalformedCASCStructureException("logical data extends past end of file"); + } + + cascReferenceOffset = localBuffer.getInt(); + cascReferenceSize = localBuffer.getInt(); + if ((Integer.toUnsignedLong(cascReferenceOffset) + Integer.toUnsignedLong(cascReferenceSize)) > localBuffer + .capacity()) { + throw new MalformedCASCStructureException("storage data extends past end of file"); + } + + maximumPathDepth = Short.toUnsignedInt(localBuffer.getShort()); + } catch (final BufferUnderflowException e) { + throw new MalformedCASCStructureException("header goes out of bounds"); + } + + contentsOffsetSize = Math.max(1, Integer.BYTES - (Integer.numberOfLeadingZeros(cascReferenceSize) / Byte.SIZE)); + contentsOffsetDecoder.putInt(0, 0); + + localBuffer.limit(pathOffset + pathSize); + localBuffer.position(pathOffset); + pathBuffer = localBuffer.slice(); + localBuffer.clear(); + + localBuffer.limit(fileReferenceOffset + fileReferenceSize); + localBuffer.position(fileReferenceOffset); + logicalBuffer = localBuffer.slice(); + localBuffer.clear(); + + localBuffer.limit(cascReferenceOffset + cascReferenceSize); + localBuffer.position(cascReferenceOffset); + storageBuffer = localBuffer.slice(); + localBuffer.clear(); + + final List rootNodes = decodeContainer(); + final TVFSFile tvfsFile = new TVFSFile(version, flags, encodingKeySize, patchKeySize, maximumPathDepth, + rootNodes); + + return tvfsFile; + } +} diff --git a/core/src/com/hiveworkshop/blizzard/casc/vfs/TVFSFile.java b/core/src/com/hiveworkshop/blizzard/casc/vfs/TVFSFile.java new file mode 100644 index 0000000..6d76f27 --- /dev/null +++ b/core/src/com/hiveworkshop/blizzard/casc/vfs/TVFSFile.java @@ -0,0 +1,54 @@ +package com.hiveworkshop.blizzard.casc.vfs; + +import java.util.List; + +/** + * TVFS file containing file system path nodes. + */ +public class TVFSFile { + private final byte version; + private final int flags; + private final int encodingKeySize; + private final int patchKeySize; + private final int maximumPathDepth; + + private final PathNode[] rootNodes; + + public TVFSFile(final byte version, final int flags, final int encodingKeySize, final int patchKeySize, final int maximumPathDepth, + final List rootNodeList) { + this.version = version; + this.flags = flags; + this.encodingKeySize = encodingKeySize; + this.patchKeySize = patchKeySize; + this.maximumPathDepth = maximumPathDepth; + this.rootNodes = rootNodeList.toArray(new PathNode[0]); + } + + public int getEncodingKeySize() { + return encodingKeySize; + } + + public int getFlags() { + return flags; + } + + public int getMaximumPathDepth() { + return maximumPathDepth; + } + + public int getPatchKeySize() { + return patchKeySize; + } + + public PathNode getRootNode(final int index) { + return rootNodes[index]; + } + + public int getRootNodeCount() { + return rootNodes.length; + } + + public byte getVersion() { + return version; + } +} diff --git a/core/src/com/hiveworkshop/blizzard/casc/vfs/VirtualFileSystem.java b/core/src/com/hiveworkshop/blizzard/casc/vfs/VirtualFileSystem.java new file mode 100644 index 0000000..037162b --- /dev/null +++ b/core/src/com/hiveworkshop/blizzard/casc/vfs/VirtualFileSystem.java @@ -0,0 +1,681 @@ +package com.hiveworkshop.blizzard.casc.vfs; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.BufferOverflowException; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.CharsetEncoder; +import java.nio.charset.CodingErrorAction; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.TreeMap; + +import com.hiveworkshop.ReteraCASCUtils; +import com.hiveworkshop.blizzard.casc.Key; +import com.hiveworkshop.blizzard.casc.nio.MalformedCASCStructureException; +import com.hiveworkshop.blizzard.casc.storage.BankStream; +import com.hiveworkshop.blizzard.casc.storage.Storage; + +/** + * High level file system API using TVFS directories to extract files from a + * store. + */ +public final class VirtualFileSystem { + /** + * A result of a file path lookup operation in a TVFS file system. + *

+ * Can be used to fetch the data of a file. + */ + public final class PathResult { + private final PathNode node; + private final byte[][] pathFragments; + + /** + * Internal constructor for path results. + * + * @param node Resolved node. + * @param pathFragments Path of resolved node. + */ + private PathResult(final PathNode node, final byte[][] pathFragments) { + this.node = node; + this.pathFragments = pathFragments; + } + + /** + * Returns true if this file completely exists in storage. + *

+ * The virtual file system structure lists all files, even ones that may not be + * in storage. Only files that are in storage can have their file buffer read. + *

+ * If this result is not a file then it exists in storage as it has no storage + * footprint. + * + * @return True if the file exists in storage. + */ + public boolean existsInStorage() { + boolean exists = true; + + if (isFile()) { + final FileNode fileNode = (FileNode) node; + final int fileReferenceCount = fileNode.getFileReferenceCount(); + for (int fileReferenceIndex = 0; fileReferenceIndex < fileReferenceCount; fileReferenceIndex += 1) { + final StorageReference fileReference = fileNode.getFileReference(fileReferenceIndex); + exists = exists && storage.hasBanks(fileReference.getEncodingKey()); + } + } + + return exists; + } + + /** + * Get the size of the file in bytes. + *

+ * If this result is not a file a value of 0 is returned. + * + * @return File size in bytes. + */ + public long getFileSize() { + long size = 0L; + + if (isFile()) { + final FileNode fileNode = (FileNode) node; + final int fileReferenceCount = fileNode.getFileReferenceCount(); + for (int fileReferenceIndex = 0; fileReferenceIndex < fileReferenceCount; fileReferenceIndex += 1) { + final StorageReference fileReference = fileNode.getFileReference(fileReferenceIndex); + size = Math.max(size, fileReference.getOffset() + fileReference.getSize()); + } + } + + return size; + } + + public String getPath() throws CharacterCodingException { + return convertPathFragments(pathFragments); + } + + public byte[][] getPathFragments() { + return pathFragments; + } + + public boolean isFile() { + return node instanceof FileNode; + } + + /** + * Returns if this path result represents a TVFS file node used by this file + * system. + *

+ * Such nodes logically act as folders in the file path but also contain file + * data used by this file system. Such behaviour may be incompatible with + * standard file systems which do not support both a folder and file at the same + * path. + *

+ * Results that are not files cannot be a TVFS file. + * + * @return If this node is a TVFS file used by this file system. + */ + public boolean isTVFS() { + if (!isFile()) { + return false; + } + + final FileNode fileNode = (FileNode) node; + final StorageReference fileReference = fileNode.getFileReference(0); + return tvfsStorageReferences.containsKey(fileReference.getEncodingKey()); + } + + /** + * Fully read this file into the specified destination buffer. If no buffer is + * specified a new one will be allocated. + *

+ * The specified buffer must have at least getFileSize bytes remaining. + * + * @param destBuffer Buffer to be written to. + * @return Buffer that was written to. + * @throws IOException If an error occurs during reading. + * @throws OutOfMemoryError If no buffer is specified and the file is too big + * for a single buffer. + */ + public ByteBuffer readFile(ByteBuffer destBuffer) throws IOException { + if (!isFile()) { + throw new FileNotFoundException("result is not a file"); + } + + final long fileSize = getFileSize(); + if (fileSize > Integer.MAX_VALUE) { + throw new OutOfMemoryError("file too big to process"); + } + + if (destBuffer == null) { + destBuffer = ByteBuffer.allocate((int) fileSize); + } else if (destBuffer.remaining() < fileSize) { + throw new BufferOverflowException(); + } + + final ByteBuffer fileBuffer = destBuffer.slice(); + + final FileNode fileNode = (FileNode) node; + final int fileReferenceCount = fileNode.getFileReferenceCount(); + for (int fileReferenceIndex = 0; fileReferenceIndex < fileReferenceCount; fileReferenceIndex += 1) { + final StorageReference fileReference = fileNode.getFileReference(fileReferenceIndex); + + final long logicalSize = fileReference.getSize(); + if (logicalSize != fileReference.getActualSize()) { + throw new MalformedCASCStructureException("inconsistent size"); + } + final long logicalOffset = fileReference.getOffset(); + + final BankStream bankStream = storage.getBanks(fileReference.getEncodingKey()); + // TODO test if compressed and logical sizes match stored sizes. + + fileBuffer.limit((int) (logicalOffset + logicalSize)); + fileBuffer.position((int) logicalOffset); + while (bankStream.hasNextBank()) { + bankStream.getBank(fileBuffer); + } + } + + destBuffer.position(destBuffer.position() + (int) fileSize); + return destBuffer; + } + } + + /** + * VFS storage reference key prefix. + */ + public static final String CONFIGURATION_KEY_PREFIX = "vfs-"; + + /** + * Root VFS storage reference. + */ + public static final String ROOT_KEY = "root"; + + /** + * Character encoding used internally by file paths. + */ + public static final Charset PATH_ENCODING = Charset.forName("UTF8"); + + /** + * Path separator used by path strings. + */ + public static final String PATH_SEPERATOR = "\\"; + + /** + * Compares the path fragments of a node with a section of file path fragments. + * This is useful for performing a binary search on a node's children. + *

+ * A return value of 0 does not mean that the node is in the path fragments. + * Only that if it were, it would be this node. This is because the children of + * a node all have unique first fragment sequences so only the first fragment is + * tested. + * + * @param pathFragments Path fragments of a file path. + * @param fragmentIndex Index of fragment where to start comparing at. + * @param fragmentOffset Offset into fragment to start comparing at. + * @param node Node which is being compared. + * @return Similar to standard comparator value (see above). + */ + private static int compareNodePathFragments(final byte[][] pathFragments, final int fragmentIndex, + final int fragmentOffset, final PathNode node) { + final int nodeFragmentCount = node.getPathFragmentCount(); + if (nodeFragmentCount == 0) { + // nodes without fragments have no path fragment presence so always match + return 0; + } + + final byte[] nodeFragment = node.getFragment(0); + final byte[] fragment = pathFragments[fragmentIndex]; + if ((nodeFragment.length == 0) && ((fragment.length - fragmentOffset) > 0)) { + // node with termination fragment are always before all other child nodes + return 1; + } + return ReteraCASCUtils.arraysCompareUnsigned(fragment, fragmentOffset, + Math.min(fragmentOffset + nodeFragment.length, fragment.length), nodeFragment, 0, nodeFragment.length); + } + + /** + * Convert a path string into path fragments for resolution in the VFS. + * + * @param filePath Path string to convert. + * @return Path fragments. + * @throws CharacterCodingException If the path string cannot be encoded into + * fragments. + */ + public static byte[][] convertFilePath(final String filePath) throws CharacterCodingException { + final String[] fragmentStrings = filePath.toLowerCase(Locale.ROOT).split("\\" + PATH_SEPERATOR); + final byte[][] pathFragments = new byte[fragmentStrings.length][]; + + final CharsetEncoder encoder = PATH_ENCODING.newEncoder(); + encoder.onMalformedInput(CodingErrorAction.REPORT); + encoder.onUnmappableCharacter(CodingErrorAction.REPORT); + for (int index = 0; index < fragmentStrings.length; index += 1) { + final ByteBuffer fragmentBuffer = encoder.encode(CharBuffer.wrap(fragmentStrings[index])); + if (fragmentBuffer.hasArray() && (fragmentBuffer.limit() == fragmentBuffer.capacity()) + && (fragmentBuffer.position() == 0)) { + // can use underlying array + pathFragments[index] = fragmentBuffer.array(); + } else { + // copy into array + final byte[] pathFragment = new byte[fragmentBuffer.remaining()]; + fragmentBuffer.get(pathFragment); + pathFragments[index] = pathFragment; + } + } + + return pathFragments; + } + + /** + * Convert path fragments used internally by VFS into a path string. + * + * @param pathFragments Path fragments to convert. + * @return Path string. + * @throws CharacterCodingException If the path fragments cannot be decoded into + * a valid String. + */ + public static String convertPathFragments(final byte[][] pathFragments) throws CharacterCodingException { + final String[] fragmentStrings = new String[pathFragments.length]; + + final CharsetDecoder decoder = PATH_ENCODING.newDecoder(); + decoder.onMalformedInput(CodingErrorAction.REPORT); + decoder.onUnmappableCharacter(CodingErrorAction.REPORT); + + for (int index = 0; index < fragmentStrings.length; index += 1) { + fragmentStrings[index] = decoder.decode(ByteBuffer.wrap(pathFragments[index])).toString(); + } + + return String.join(PATH_SEPERATOR, fragmentStrings); + } + + /** + * Test the path fragments of a node form a section of file path fragments. + * + * @param pathFragments Path fragments of a file path. + * @param fragmentIndex Index of fragment where to start testing at. + * @param fragmentOffset Offset into fragment to start testing at. + * @param node Node which is being tested. + * @return True if the node is contained in the path fragments, otherwise false. + */ + private static boolean equalNodePathFragments(final byte[][] pathFragments, final int fragmentIndex, + int fragmentOffset, final PathNode node) { + final int nodeFragmentCount = node.getPathFragmentCount(); + if (nodeFragmentCount == 0) { + // nodes without fragments have no path fragment presence so always match + return true; + } + + if ((nodeFragmentCount == 1) && (node.getFragment(0).length == 0)) { + // node with termination fragment + return fragmentOffset == pathFragments[fragmentIndex].length; + } else if (pathFragments.length < (fragmentIndex + nodeFragmentCount)) { + // fragment too short + return false; + } + + boolean result = true; + int nodeFragmentIndex = 0; + while (result && (nodeFragmentIndex < nodeFragmentCount)) { + final byte[] fragment = pathFragments[fragmentIndex + nodeFragmentIndex]; + final byte[] nodeFragment = node.getFragment(nodeFragmentIndex); + result = result && ReteraCASCUtils.arraysEquals(fragment, fragmentOffset, + Math.min(fragmentOffset + nodeFragment.length, fragment.length), nodeFragment, 0, + nodeFragment.length); + fragmentOffset = 0; + nodeFragmentIndex += 1; + } + + return result; + } + + /** + * Local CASC storage. Used to retrieve file data. + */ + private final Storage storage; + + /** + * Decoder used to load TVFS files in the TVFS tree. + */ + private final TVFSDecoder decoder = new TVFSDecoder(); + + /** + * TVFS file containing the root directory for the file system. + */ + private final TVFSFile tvfsRoot; + + /** + * TVFS file cache. Holds all loaded TVFS files for this file system. This + * allows the TVFS files to be loaded lazily which could potentially reduce + * loading times and memory usage when only some branches of the TVFS file tree + * are accessed. + */ + private final TreeMap tvfsCache = new TreeMap<>(); + + /** + * Map of all TVFS files used by the TVFS file tree. Keys that are not in this + * map are treated as leaf files rather than a nested TVFS file. + */ + private final TreeMap tvfsStorageReferences = new TreeMap<>(); + + /** + * Construct a TVFS file system from a CASC local storage and build + * configuration. + * + * @param storage CASC local storage to source files from. + * @param buildConfiguration Build configuration of CASC archive. + * @throws IOException If an exception occurs when loading the file system. + */ + public VirtualFileSystem(final Storage storage, final Map buildConfiguration) throws IOException { + this.storage = storage; + + int vfsNumber = 0; + String configurationKey; + while (buildConfiguration + .containsKey(configurationKey = CONFIGURATION_KEY_PREFIX + Integer.toUnsignedString(++vfsNumber))) { + final com.hiveworkshop.blizzard.casc.StorageReference storageReference = new com.hiveworkshop.blizzard.casc.StorageReference( + configurationKey, buildConfiguration); + tvfsStorageReferences.put(storageReference.getEncodingKey(), storageReference); + } + + final com.hiveworkshop.blizzard.casc.StorageReference rootReference = new com.hiveworkshop.blizzard.casc.StorageReference( + CONFIGURATION_KEY_PREFIX + ROOT_KEY, buildConfiguration); + final ByteBuffer rootBuffer = fetchStoredBuffer(rootReference); + tvfsRoot = decoder.loadFile(rootBuffer); + + tvfsCache.put(rootReference.getEncodingKey(), tvfsRoot); + } + + /** + * Resolves a TVFS storage reference into a data buffer from the local storage. + * + * @param storageReference TVFS storage reference. + * @return Data buffer containing refered content. + * @throws IOException If an exception occurs when fetching the data buffer. + */ + private ByteBuffer fetchStoredBuffer(final com.hiveworkshop.blizzard.casc.StorageReference storageReference) + throws IOException { + final long size = storageReference.getSize(); + if (size > Integer.MAX_VALUE) { + throw new MalformedCASCStructureException("stored data too large to process"); + } + + final BankStream bankStream = storage.getBanks(storageReference.getEncodingKey()); + final ByteBuffer storedBuffer = ByteBuffer.allocate((int) size); + try { + while (bankStream.hasNextBank()) { + bankStream.getBank(storedBuffer); + } + } catch (final BufferOverflowException e) { + throw new MalformedCASCStructureException("stored data is bigger than expected"); + } + + if (storedBuffer.hasRemaining()) { + throw new MalformedCASCStructureException("stored data is smaller than expected"); + } + + storedBuffer.rewind(); + return storedBuffer; + } + + /** + * Method to get all files in the file system. + * + * @return List of file path results for every file in the file system. + * @throws IOException If an exception is thrown when loading a TVFS file or + * decoding path fragments into a path string. + */ + public List getAllFiles() throws IOException { + final ArrayList pathStringList = new ArrayList(); + + final int rootCount = tvfsRoot.getRootNodeCount(); + for (int rootIndex = 0; rootIndex < rootCount; rootIndex += 1) { + final PathNode root = tvfsRoot.getRootNode(rootIndex); + recursiveFilePathRetrieve(new byte[1][0], pathStringList, root); + } + + return pathStringList; + } + + /** + * Recursive function to traverse the TVFS tree and resolve all files in the + * file system. + * + * @param parentPathFragments Path fragments of parent node. + * @param resultList Result list. + * @param currentNode The child node to process. + * @throws IOException If an exception occurs when processing the node. + */ + private void recursiveFilePathRetrieve(final byte[][] parentPathFragments, final ArrayList resultList, + final PathNode currentNode) throws IOException { + byte[][] currentPathFragments = parentPathFragments; + + // process path fragments + final int fragmentCount = currentNode.getPathFragmentCount(); + if (fragmentCount > 0) { + int fragmentIndex = 0; + final byte[] fragment = currentNode.getFragment(fragmentIndex++); + + // expand path fragment array + int basePathFragmentsIndex = currentPathFragments.length; + if ((fragmentCount > 1) || (fragment.length > 0)) { + // first fragment of the node gets merged with last path fragment + basePathFragmentsIndex -= 1; + } + currentPathFragments = Arrays.copyOf(currentPathFragments, basePathFragmentsIndex + fragmentCount); + + // merge fragment + final byte[] sourceFragment = currentPathFragments[basePathFragmentsIndex]; + byte[] joinedFragment = fragment; + if (sourceFragment != null) { + joinedFragment = sourceFragment; + if (fragment.length != 0) { + final int joinOffset = sourceFragment.length; + joinedFragment = Arrays.copyOf(sourceFragment, joinOffset + fragment.length); + System.arraycopy(fragment, 0, joinedFragment, joinOffset, fragment.length); + } + } + + // append path fragments + currentPathFragments[basePathFragmentsIndex] = joinedFragment; + for (; fragmentIndex < fragmentCount; fragmentIndex += 1) { + currentPathFragments[basePathFragmentsIndex + fragmentIndex] = currentNode.getFragment(fragmentIndex); + } + } + + if (currentNode instanceof PrefixNode) { + final PrefixNode prefixNode = (PrefixNode) currentNode; + + final int childCount = prefixNode.getNodeCount(); + for (int index = 0; index < childCount; index += 1) { + recursiveFilePathRetrieve(currentPathFragments, resultList, prefixNode.getNode(index)); + } + } else if (currentNode instanceof FileNode) { + final FileNode fileNode = (FileNode) currentNode; + + final int fileReferenceCount = fileNode.getFileReferenceCount(); + if (fileReferenceCount == 1) { + // check if nested VFS + final Key encodingKey = fileNode.getFileReference(0).getEncodingKey(); + final TVFSFile tvfsFile = resolveTVFS(encodingKey); + + if (tvfsFile != null) { + // file is also a folder + final byte[][] folderPathFragments = Arrays.copyOf(currentPathFragments, + currentPathFragments.length + 1); + folderPathFragments[currentPathFragments.length] = new byte[0]; + + final int rootCount = tvfsFile.getRootNodeCount(); + for (int rootIndex = 0; rootIndex < rootCount; rootIndex += 1) { + final PathNode root = tvfsFile.getRootNode(rootIndex); + recursiveFilePathRetrieve(folderPathFragments, resultList, root); + } + } + + resultList.add(new PathResult(currentNode, currentPathFragments)); + } + } else { + throw new IllegalArgumentException("unsupported node type"); + } + } + + /** + * Recursive function to resolve a file node in a TVFS tree from path fragments + * representing a file system file path. + * + * @param pathFragments Path fragments of a file path. + * @param fragmentIndex Index of fragment where currently testing. + * @param fragmentOffset Offset into fragment where currently testing. + * @param node Node which is being tested. + * @return Resolved file node. + * @throws IOException If an exception occurs when testing the node. + */ + private FileNode recursiveResolvePathFragments(final byte[][] pathFragments, int fragmentIndex, int fragmentOffset, + final PathNode node) throws IOException { + if (!equalNodePathFragments(pathFragments, fragmentIndex, fragmentOffset, node)) { + // node not on path + return null; + } + + // advance fragment position + final int nodeFragmentCount = node.getPathFragmentCount(); + if (nodeFragmentCount == 1) { + final byte[] nodeFragment = node.getFragment(0); + if (nodeFragment.length == 0) { + // node with termination fragment + fragmentIndex += 1; + fragmentOffset = 0; + } else { + // node with less than a whole fragment + fragmentOffset += nodeFragment.length; + } + + } else if (nodeFragmentCount > 1) { + // node which completes 1 or more fragments. + fragmentIndex += nodeFragmentCount - 1; + fragmentOffset = node.getFragment(nodeFragmentCount - 1).length; + } + + // process node + if (node instanceof PrefixNode) { + // apply binary search to prefix node to find next node + final PrefixNode prefixNode = (PrefixNode) node; + final int childCount = prefixNode.getNodeCount(); + + int low = 0; + int high = childCount - 1; + while (low <= high) { + final int middle = (low + high) / 2; + final PathNode searchNode = prefixNode.getNode(middle); + final int result = compareNodePathFragments(pathFragments, fragmentIndex, fragmentOffset, searchNode); + + if (result == 0) { + // possible match + return recursiveResolvePathFragments(pathFragments, fragmentIndex, fragmentOffset, searchNode); + } else if (result < 0) { + high = middle - 1; + } else { + low = middle + 1; + } + } + + } else if (node instanceof FileNode) { + final FileNode fileNode = (FileNode) node; + + if ((fragmentIndex == (pathFragments.length - 1)) + && (fragmentOffset == pathFragments[pathFragments.length - 1].length)) { + // file found + return fileNode; + } else if (fragmentOffset == pathFragments[fragmentIndex].length) { + // nested TVFS file + final int fileReferenceCount = fileNode.getFileReferenceCount(); + if (fileReferenceCount == 1) { + // check if nested VFS + final Key encodingKey = fileNode.getFileReference(0).getEncodingKey(); + final TVFSFile tvfsFile = resolveTVFS(encodingKey); + + if (tvfsFile != null) { + // TVFS file to recursively resolve + if (tvfsFile.getRootNodeCount() != 1) { + throw new MalformedCASCStructureException("logic only defined for 1 TVFS root node"); + } + + fragmentIndex += 1; + fragmentOffset = 0; + return recursiveResolvePathFragments(pathFragments, fragmentIndex, fragmentOffset, + tvfsFile.getRootNode(0)); + } + } + } + + } else { + throw new IllegalArgumentException("unsupported node type"); + } + + // file not found + return null; + } + + /** + * Resolves a file from the specified path fragments representing a file system + * file path. + * + * @param pathFragments File path fragments. + * @return Path result for a file. + * @throws FileNotFoundException If the file does not exist in the file system. + * @throws IOException If an exception occurs when resolving the path + * fragments. + * + */ + public PathResult resolvePath(final byte[][] pathFragments) throws IOException { + if (pathFragments.length == 0) { + throw new IllegalArgumentException("pathFragments.length must be greater than 0"); + } + + if (tvfsRoot.getRootNodeCount() != 1) { + throw new MalformedCASCStructureException("logic only defined for 1 root node"); + } + + final FileNode result = recursiveResolvePathFragments(pathFragments, 0, 0, tvfsRoot.getRootNode(0)); + if (result == null) { + throw new FileNotFoundException("path not in storage"); + } + + return new PathResult(result, pathFragments); + } + + /** + * Resolves a TVFS file from an encoding key. The key is checked if it is a TVFS + * file in this file system and then resolved in local storage. The resulting + * file is then decoded as a TVFS file and returned. Decoded TVFS files are + * cached for improved performance. This method can be called concurrently. + * + * @param encodingKey Encoding key of TVFS file to resolve. + * @return The resolved TVFS file, or null if the encoding key is not for a TVFS + * file of this file system. + * @throws IOException If an error occurs when resolving the TVFS file. + */ + private TVFSFile resolveTVFS(final Key encodingKey) throws IOException { + TVFSFile tvfsFile = null; + final com.hiveworkshop.blizzard.casc.StorageReference storageReference = tvfsStorageReferences.get(encodingKey); + if (storageReference != null) { + // is a TVFS file of this file system + synchronized (this) { + tvfsFile = tvfsCache.get(encodingKey); + if (tvfsFile == null) { + // decode TVFS from storage + final ByteBuffer rootBuffer = fetchStoredBuffer(storageReference); + tvfsFile = decoder.loadFile(rootBuffer); + + tvfsCache.put(storageReference.getEncodingKey(), tvfsFile); + } + } + } + return tvfsFile; + } +} diff --git a/core/src/com/hiveworkshop/json/JSONArray.java b/core/src/com/hiveworkshop/json/JSONArray.java new file mode 100644 index 0000000..906d7b3 --- /dev/null +++ b/core/src/com/hiveworkshop/json/JSONArray.java @@ -0,0 +1,1458 @@ +package com.hiveworkshop.json; + +/* + Copyright (c) 2002 JSON.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + The Software shall be used for Good, not Evil. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.lang.reflect.Array; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + + +/** + * A JSONArray is an ordered sequence of values. Its external text form is a + * string wrapped in square brackets with commas separating the values. The + * internal form is an object having get and opt + * methods for accessing the values by index, and put methods for + * adding or replacing values. The values can be any of these types: + * Boolean, JSONArray, JSONObject, + * Number, String, or the + * JSONObject.NULL object. + *

+ * The constructor can convert a JSON text into a Java object. The + * toString method converts to JSON text. + *

+ * A get method returns a value if one can be found, and throws an + * exception if one cannot be found. An opt method returns a + * default value instead of throwing an exception, and so is useful for + * obtaining optional values. + *

+ * The generic get() and opt() methods return an + * object which you can cast or query for type. There are also typed + * get and opt methods that do type checking and type + * coercion for you. + *

+ * The texts produced by the toString methods strictly conform to + * JSON syntax rules. The constructors are more forgiving in the texts they will + * accept: + *

    + *
  • An extra , (comma) may appear just + * before the closing bracket.
  • + *
  • The null value will be inserted when there is , + *  (comma) elision.
  • + *
  • Strings may be quoted with ' (single + * quote).
  • + *
  • Strings do not need to be quoted at all if they do not begin with a quote + * or single quote, and if they do not contain leading or trailing spaces, and + * if they do not contain any of these characters: + * { } [ ] / \ : , # and if they do not look like numbers and + * if they are not the reserved words true, false, or + * null.
  • + *
+ * + * @author JSON.org + * @version 2016-08/15 + */ +public class JSONArray implements Iterable { + + /** + * The arrayList where the JSONArray's properties are kept. + */ + private final ArrayList myArrayList; + + /** + * Construct an empty JSONArray. + */ + public JSONArray() { + this.myArrayList = new ArrayList(); + } + + /** + * Construct a JSONArray from a JSONTokener. + * + * @param x + * A JSONTokener + * @throws JSONException + * If there is a syntax error. + */ + public JSONArray(JSONTokener x) throws JSONException { + this(); + if (x.nextClean() != '[') { + throw x.syntaxError("A JSONArray text must start with '['"); + } + + char nextChar = x.nextClean(); + if (nextChar == 0) { + // array is unclosed. No ']' found, instead EOF + throw x.syntaxError("Expected a ',' or ']'"); + } + if (nextChar != ']') { + x.back(); + for (;;) { + if (x.nextClean() == ',') { + x.back(); + this.myArrayList.add(JSONObject.NULL); + } else { + x.back(); + this.myArrayList.add(x.nextValue()); + } + switch (x.nextClean()) { + case 0: + // array is unclosed. No ']' found, instead EOF + throw x.syntaxError("Expected a ',' or ']'"); + case ',': + nextChar = x.nextClean(); + if (nextChar == 0) { + // array is unclosed. No ']' found, instead EOF + throw x.syntaxError("Expected a ',' or ']'"); + } + if (nextChar == ']') { + return; + } + x.back(); + break; + case ']': + return; + default: + throw x.syntaxError("Expected a ',' or ']'"); + } + } + } + } + + /** + * Construct a JSONArray from a source JSON text. + * + * @param source + * A string that begins with [ (left + * bracket) and ends with ] + *  (right bracket). + * @throws JSONException + * If there is a syntax error. + */ + public JSONArray(String source) throws JSONException { + this(new JSONTokener(source)); + } + + /** + * Construct a JSONArray from a Collection. + * + * @param collection + * A Collection. + */ + public JSONArray(Collection collection) { + if (collection == null) { + this.myArrayList = new ArrayList(); + } else { + this.myArrayList = new ArrayList(collection.size()); + for (Object o: collection){ + this.myArrayList.add(JSONObject.wrap(o)); + } + } + } + + /** + * Construct a JSONArray from an array. + * + * @param array + * Array. If the parameter passed is null, or not an array, an + * exception will be thrown. + * + * @throws JSONException + * If not an array or if an array value is non-finite number. + * @throws NullPointerException + * Thrown if the array parameter is null. + */ + public JSONArray(Object array) throws JSONException { + this(); + if (array.getClass().isArray()) { + int length = Array.getLength(array); + this.myArrayList.ensureCapacity(length); + for (int i = 0; i < length; i += 1) { + this.put(JSONObject.wrap(Array.get(array, i))); + } + } else { + throw new JSONException( + "JSONArray initial value should be a string or collection or array."); + } + } + + @Override + public Iterator iterator() { + return this.myArrayList.iterator(); + } + + /** + * Get the object value associated with an index. + * + * @param index + * The index must be between 0 and length() - 1. + * @return An object value. + * @throws JSONException + * If there is no value for the index. + */ + public Object get(int index) throws JSONException { + Object object = this.opt(index); + if (object == null) { + throw new JSONException("JSONArray[" + index + "] not found."); + } + return object; + } + + /** + * Get the boolean value associated with an index. The string values "true" + * and "false" are converted to boolean. + * + * @param index + * The index must be between 0 and length() - 1. + * @return The truth. + * @throws JSONException + * If there is no value for the index or if the value is not + * convertible to boolean. + */ + public boolean getBoolean(int index) throws JSONException { + Object object = this.get(index); + if (object.equals(Boolean.FALSE) + || (object instanceof String && ((String) object) + .equalsIgnoreCase("false"))) { + return false; + } else if (object.equals(Boolean.TRUE) + || (object instanceof String && ((String) object) + .equalsIgnoreCase("true"))) { + return true; + } + throw new JSONException("JSONArray[" + index + "] is not a boolean."); + } + + /** + * Get the double value associated with an index. + * + * @param index + * The index must be between 0 and length() - 1. + * @return The value. + * @throws JSONException + * If the key is not found or if the value cannot be converted + * to a number. + */ + public double getDouble(int index) throws JSONException { + return this.getNumber(index).doubleValue(); + } + + /** + * Get the float value associated with a key. + * + * @param index + * The index must be between 0 and length() - 1. + * @return The numeric value. + * @throws JSONException + * if the key is not found or if the value is not a Number + * object and cannot be converted to a number. + */ + public float getFloat(int index) throws JSONException { + return this.getNumber(index).floatValue(); + } + + /** + * Get the Number value associated with a key. + * + * @param index + * The index must be between 0 and length() - 1. + * @return The numeric value. + * @throws JSONException + * if the key is not found or if the value is not a Number + * object and cannot be converted to a number. + */ + public Number getNumber(int index) throws JSONException { + Object object = this.get(index); + try { + if (object instanceof Number) { + return (Number)object; + } + return JSONObject.stringToNumber(object.toString()); + } catch (Exception e) { + throw new JSONException("JSONArray[" + index + "] is not a number.", e); + } + } + + /** + * Get the enum value associated with an index. + * + * @param + * Enum Type + * @param clazz + * The type of enum to retrieve. + * @param index + * The index must be between 0 and length() - 1. + * @return The enum value at the index location + * @throws JSONException + * if the key is not found or if the value cannot be converted + * to an enum. + */ + public > E getEnum(Class clazz, int index) throws JSONException { + E val = optEnum(clazz, index); + if(val==null) { + // JSONException should really take a throwable argument. + // If it did, I would re-implement this with the Enum.valueOf + // method and place any thrown exception in the JSONException + throw new JSONException("JSONArray[" + index + "] is not an enum of type " + + JSONObject.quote(clazz.getSimpleName()) + "."); + } + return val; + } + + /** + * Get the BigDecimal value associated with an index. If the value is float + * or double, the the {@link BigDecimal#BigDecimal(double)} constructor + * will be used. See notes on the constructor for conversion issues that + * may arise. + * + * @param index + * The index must be between 0 and length() - 1. + * @return The value. + * @throws JSONException + * If the key is not found or if the value cannot be converted + * to a BigDecimal. + */ + public BigDecimal getBigDecimal (int index) throws JSONException { + Object object = this.get(index); + BigDecimal val = JSONObject.objectToBigDecimal(object, null); + if(val == null) { + throw new JSONException("JSONArray[" + index + + "] could not convert to BigDecimal ("+ object + ")."); + } + return val; + } + + /** + * Get the BigInteger value associated with an index. + * + * @param index + * The index must be between 0 and length() - 1. + * @return The value. + * @throws JSONException + * If the key is not found or if the value cannot be converted + * to a BigInteger. + */ + public BigInteger getBigInteger (int index) throws JSONException { + Object object = this.get(index); + BigInteger val = JSONObject.objectToBigInteger(object, null); + if(val == null) { + throw new JSONException("JSONArray[" + index + + "] could not convert to BigDecimal ("+ object + ")."); + } + return val; + } + + /** + * Get the int value associated with an index. + * + * @param index + * The index must be between 0 and length() - 1. + * @return The value. + * @throws JSONException + * If the key is not found or if the value is not a number. + */ + public int getInt(int index) throws JSONException { + return this.getNumber(index).intValue(); + } + + /** + * Get the JSONArray associated with an index. + * + * @param index + * The index must be between 0 and length() - 1. + * @return A JSONArray value. + * @throws JSONException + * If there is no value for the index. or if the value is not a + * JSONArray + */ + public JSONArray getJSONArray(int index) throws JSONException { + Object object = this.get(index); + if (object instanceof JSONArray) { + return (JSONArray) object; + } + throw new JSONException("JSONArray[" + index + "] is not a JSONArray."); + } + + /** + * Get the JSONObject associated with an index. + * + * @param index + * subscript + * @return A JSONObject value. + * @throws JSONException + * If there is no value for the index or if the value is not a + * JSONObject + */ + public JSONObject getJSONObject(int index) throws JSONException { + Object object = this.get(index); + if (object instanceof JSONObject) { + return (JSONObject) object; + } + throw new JSONException("JSONArray[" + index + "] is not a JSONObject."); + } + + /** + * Get the long value associated with an index. + * + * @param index + * The index must be between 0 and length() - 1. + * @return The value. + * @throws JSONException + * If the key is not found or if the value cannot be converted + * to a number. + */ + public long getLong(int index) throws JSONException { + return this.getNumber(index).longValue(); + } + + /** + * Get the string associated with an index. + * + * @param index + * The index must be between 0 and length() - 1. + * @return A string value. + * @throws JSONException + * If there is no string value for the index. + */ + public String getString(int index) throws JSONException { + Object object = this.get(index); + if (object instanceof String) { + return (String) object; + } + throw new JSONException("JSONArray[" + index + "] not a string."); + } + + /** + * Determine if the value is null. + * + * @param index + * The index must be between 0 and length() - 1. + * @return true if the value at the index is null, or if there is no value. + */ + public boolean isNull(int index) { + return JSONObject.NULL.equals(this.opt(index)); + } + + /** + * Make a string from the contents of this JSONArray. The + * separator string is inserted between each element. Warning: + * This method assumes that the data structure is acyclical. + * + * @param separator + * A string that will be inserted between the elements. + * @return a string. + * @throws JSONException + * If the array contains an invalid number. + */ + public String join(String separator) throws JSONException { + int len = this.length(); + if (len == 0) { + return ""; + } + + StringBuilder sb = new StringBuilder( + JSONObject.valueToString(this.myArrayList.get(0))); + + for (int i = 1; i < len; i++) { + sb.append(separator) + .append(JSONObject.valueToString(this.myArrayList.get(i))); + } + return sb.toString(); + } + + /** + * Get the number of elements in the JSONArray, included nulls. + * + * @return The length (or size). + */ + public int length() { + return this.myArrayList.size(); + } + + /** + * Get the optional object value associated with an index. + * + * @param index + * The index must be between 0 and length() - 1. If not, null is returned. + * @return An object value, or null if there is no object at that index. + */ + public Object opt(int index) { + return (index < 0 || index >= this.length()) ? null : this.myArrayList + .get(index); + } + + /** + * Get the optional boolean value associated with an index. It returns false + * if there is no value at that index, or if the value is not Boolean.TRUE + * or the String "true". + * + * @param index + * The index must be between 0 and length() - 1. + * @return The truth. + */ + public boolean optBoolean(int index) { + return this.optBoolean(index, false); + } + + /** + * Get the optional boolean value associated with an index. It returns the + * defaultValue if there is no value at that index or if it is not a Boolean + * or the String "true" or "false" (case insensitive). + * + * @param index + * The index must be between 0 and length() - 1. + * @param defaultValue + * A boolean default. + * @return The truth. + */ + public boolean optBoolean(int index, boolean defaultValue) { + try { + return this.getBoolean(index); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get the optional double value associated with an index. NaN is returned + * if there is no value for the index, or if the value is not a number and + * cannot be converted to a number. + * + * @param index + * The index must be between 0 and length() - 1. + * @return The value. + */ + public double optDouble(int index) { + return this.optDouble(index, Double.NaN); + } + + /** + * Get the optional double value associated with an index. The defaultValue + * is returned if there is no value for the index, or if the value is not a + * number and cannot be converted to a number. + * + * @param index + * subscript + * @param defaultValue + * The default value. + * @return The value. + */ + public double optDouble(int index, double defaultValue) { + final Number val = this.optNumber(index, null); + if (val == null) { + return defaultValue; + } + final double doubleValue = val.doubleValue(); + // if (Double.isNaN(doubleValue) || Double.isInfinite(doubleValue)) { + // return defaultValue; + // } + return doubleValue; + } + + /** + * Get the optional float value associated with an index. NaN is returned + * if there is no value for the index, or if the value is not a number and + * cannot be converted to a number. + * + * @param index + * The index must be between 0 and length() - 1. + * @return The value. + */ + public float optFloat(int index) { + return this.optFloat(index, Float.NaN); + } + + /** + * Get the optional float value associated with an index. The defaultValue + * is returned if there is no value for the index, or if the value is not a + * number and cannot be converted to a number. + * + * @param index + * subscript + * @param defaultValue + * The default value. + * @return The value. + */ + public float optFloat(int index, float defaultValue) { + final Number val = this.optNumber(index, null); + if (val == null) { + return defaultValue; + } + final float floatValue = val.floatValue(); + // if (Float.isNaN(floatValue) || Float.isInfinite(floatValue)) { + // return floatValue; + // } + return floatValue; + } + + /** + * Get the optional int value associated with an index. Zero is returned if + * there is no value for the index, or if the value is not a number and + * cannot be converted to a number. + * + * @param index + * The index must be between 0 and length() - 1. + * @return The value. + */ + public int optInt(int index) { + return this.optInt(index, 0); + } + + /** + * Get the optional int value associated with an index. The defaultValue is + * returned if there is no value for the index, or if the value is not a + * number and cannot be converted to a number. + * + * @param index + * The index must be between 0 and length() - 1. + * @param defaultValue + * The default value. + * @return The value. + */ + public int optInt(int index, int defaultValue) { + final Number val = this.optNumber(index, null); + if (val == null) { + return defaultValue; + } + return val.intValue(); + } + + /** + * Get the enum value associated with a key. + * + * @param + * Enum Type + * @param clazz + * The type of enum to retrieve. + * @param index + * The index must be between 0 and length() - 1. + * @return The enum value at the index location or null if not found + */ + public > E optEnum(Class clazz, int index) { + return this.optEnum(clazz, index, null); + } + + /** + * Get the enum value associated with a key. + * + * @param + * Enum Type + * @param clazz + * The type of enum to retrieve. + * @param index + * The index must be between 0 and length() - 1. + * @param defaultValue + * The default in case the value is not found + * @return The enum value at the index location or defaultValue if + * the value is not found or cannot be assigned to clazz + */ + public > E optEnum(Class clazz, int index, E defaultValue) { + try { + Object val = this.opt(index); + if (JSONObject.NULL.equals(val)) { + return defaultValue; + } + if (clazz.isAssignableFrom(val.getClass())) { + // we just checked it! + @SuppressWarnings("unchecked") + E myE = (E) val; + return myE; + } + return Enum.valueOf(clazz, val.toString()); + } catch (IllegalArgumentException e) { + return defaultValue; + } catch (NullPointerException e) { + return defaultValue; + } + } + + /** + * Get the optional BigInteger value associated with an index. The + * defaultValue is returned if there is no value for the index, or if the + * value is not a number and cannot be converted to a number. + * + * @param index + * The index must be between 0 and length() - 1. + * @param defaultValue + * The default value. + * @return The value. + */ + public BigInteger optBigInteger(int index, BigInteger defaultValue) { + Object val = this.opt(index); + return JSONObject.objectToBigInteger(val, defaultValue); + } + + /** + * Get the optional BigDecimal value associated with an index. The + * defaultValue is returned if there is no value for the index, or if the + * value is not a number and cannot be converted to a number. If the value + * is float or double, the the {@link BigDecimal#BigDecimal(double)} + * constructor will be used. See notes on the constructor for conversion + * issues that may arise. + * + * @param index + * The index must be between 0 and length() - 1. + * @param defaultValue + * The default value. + * @return The value. + */ + public BigDecimal optBigDecimal(int index, BigDecimal defaultValue) { + Object val = this.opt(index); + return JSONObject.objectToBigDecimal(val, defaultValue); + } + + /** + * Get the optional JSONArray associated with an index. + * + * @param index + * subscript + * @return A JSONArray value, or null if the index has no value, or if the + * value is not a JSONArray. + */ + public JSONArray optJSONArray(int index) { + Object o = this.opt(index); + return o instanceof JSONArray ? (JSONArray) o : null; + } + + /** + * Get the optional JSONObject associated with an index. Null is returned if + * the key is not found, or null if the index has no value, or if the value + * is not a JSONObject. + * + * @param index + * The index must be between 0 and length() - 1. + * @return A JSONObject value. + */ + public JSONObject optJSONObject(int index) { + Object o = this.opt(index); + return o instanceof JSONObject ? (JSONObject) o : null; + } + + /** + * Get the optional long value associated with an index. Zero is returned if + * there is no value for the index, or if the value is not a number and + * cannot be converted to a number. + * + * @param index + * The index must be between 0 and length() - 1. + * @return The value. + */ + public long optLong(int index) { + return this.optLong(index, 0); + } + + /** + * Get the optional long value associated with an index. The defaultValue is + * returned if there is no value for the index, or if the value is not a + * number and cannot be converted to a number. + * + * @param index + * The index must be between 0 and length() - 1. + * @param defaultValue + * The default value. + * @return The value. + */ + public long optLong(int index, long defaultValue) { + final Number val = this.optNumber(index, null); + if (val == null) { + return defaultValue; + } + return val.longValue(); + } + + /** + * Get an optional {@link Number} value associated with a key, or null + * if there is no such key or if the value is not a number. If the value is a string, + * an attempt will be made to evaluate it as a number ({@link BigDecimal}). This method + * would be used in cases where type coercion of the number value is unwanted. + * + * @param index + * The index must be between 0 and length() - 1. + * @return An object which is the value. + */ + public Number optNumber(int index) { + return this.optNumber(index, null); + } + + /** + * Get an optional {@link Number} value associated with a key, or the default if there + * is no such key or if the value is not a number. If the value is a string, + * an attempt will be made to evaluate it as a number ({@link BigDecimal}). This method + * would be used in cases where type coercion of the number value is unwanted. + * + * @param index + * The index must be between 0 and length() - 1. + * @param defaultValue + * The default. + * @return An object which is the value. + */ + public Number optNumber(int index, Number defaultValue) { + Object val = this.opt(index); + if (JSONObject.NULL.equals(val)) { + return defaultValue; + } + if (val instanceof Number){ + return (Number) val; + } + + if (val instanceof String) { + try { + return JSONObject.stringToNumber((String) val); + } catch (Exception e) { + return defaultValue; + } + } + return defaultValue; + } + + /** + * Get the optional string value associated with an index. It returns an + * empty string if there is no value at that index. If the value is not a + * string and is not null, then it is converted to a string. + * + * @param index + * The index must be between 0 and length() - 1. + * @return A String value. + */ + public String optString(int index) { + return this.optString(index, ""); + } + + /** + * Get the optional string associated with an index. The defaultValue is + * returned if the key is not found. + * + * @param index + * The index must be between 0 and length() - 1. + * @param defaultValue + * The default value. + * @return A String value. + */ + public String optString(int index, String defaultValue) { + Object object = this.opt(index); + return JSONObject.NULL.equals(object) ? defaultValue : object + .toString(); + } + + /** + * Append a boolean value. This increases the array's length by one. + * + * @param value + * A boolean value. + * @return this. + */ + public JSONArray put(boolean value) { + return this.put(value ? Boolean.TRUE : Boolean.FALSE); + } + + /** + * Put a value in the JSONArray, where the value will be a JSONArray which + * is produced from a Collection. + * + * @param value + * A Collection value. + * @return this. + * @throws JSONException + * If the value is non-finite number. + */ + public JSONArray put(Collection value) { + return this.put(new JSONArray(value)); + } + + /** + * Append a double value. This increases the array's length by one. + * + * @param value + * A double value. + * @return this. + * @throws JSONException + * if the value is not finite. + */ + public JSONArray put(double value) throws JSONException { + return this.put(Double.valueOf(value)); + } + + /** + * Append a float value. This increases the array's length by one. + * + * @param value + * A float value. + * @return this. + * @throws JSONException + * if the value is not finite. + */ + public JSONArray put(float value) throws JSONException { + return this.put(Float.valueOf(value)); + } + + /** + * Append an int value. This increases the array's length by one. + * + * @param value + * An int value. + * @return this. + */ + public JSONArray put(int value) { + return this.put(Integer.valueOf(value)); + } + + /** + * Append an long value. This increases the array's length by one. + * + * @param value + * A long value. + * @return this. + */ + public JSONArray put(long value) { + return this.put(Long.valueOf(value)); + } + + /** + * Put a value in the JSONArray, where the value will be a JSONObject which + * is produced from a Map. + * + * @param value + * A Map value. + * @return this. + * @throws JSONException + * If a value in the map is non-finite number. + * @throws NullPointerException + * If a key in the map is null + */ + public JSONArray put(Map value) { + return this.put(new JSONObject(value)); + } + + /** + * Append an object value. This increases the array's length by one. + * + * @param value + * An object value. The value should be a Boolean, Double, + * Integer, JSONArray, JSONObject, Long, or String, or the + * JSONObject.NULL object. + * @return this. + * @throws JSONException + * If the value is non-finite number. + */ + public JSONArray put(Object value) { + JSONObject.testValidity(value); + this.myArrayList.add(value); + return this; + } + + /** + * Put or replace a boolean value in the JSONArray. If the index is greater + * than the length of the JSONArray, then null elements will be added as + * necessary to pad it out. + * + * @param index + * The subscript. + * @param value + * A boolean value. + * @return this. + * @throws JSONException + * If the index is negative. + */ + public JSONArray put(int index, boolean value) throws JSONException { + return this.put(index, value ? Boolean.TRUE : Boolean.FALSE); + } + + /** + * Put a value in the JSONArray, where the value will be a JSONArray which + * is produced from a Collection. + * + * @param index + * The subscript. + * @param value + * A Collection value. + * @return this. + * @throws JSONException + * If the index is negative or if the value is non-finite. + */ + public JSONArray put(int index, Collection value) throws JSONException { + return this.put(index, new JSONArray(value)); + } + + /** + * Put or replace a double value. If the index is greater than the length of + * the JSONArray, then null elements will be added as necessary to pad it + * out. + * + * @param index + * The subscript. + * @param value + * A double value. + * @return this. + * @throws JSONException + * If the index is negative or if the value is non-finite. + */ + public JSONArray put(int index, double value) throws JSONException { + return this.put(index, Double.valueOf(value)); + } + + /** + * Put or replace a float value. If the index is greater than the length of + * the JSONArray, then null elements will be added as necessary to pad it + * out. + * + * @param index + * The subscript. + * @param value + * A float value. + * @return this. + * @throws JSONException + * If the index is negative or if the value is non-finite. + */ + public JSONArray put(int index, float value) throws JSONException { + return this.put(index, Float.valueOf(value)); + } + + /** + * Put or replace an int value. If the index is greater than the length of + * the JSONArray, then null elements will be added as necessary to pad it + * out. + * + * @param index + * The subscript. + * @param value + * An int value. + * @return this. + * @throws JSONException + * If the index is negative. + */ + public JSONArray put(int index, int value) throws JSONException { + return this.put(index, Integer.valueOf(value)); + } + + /** + * Put or replace a long value. If the index is greater than the length of + * the JSONArray, then null elements will be added as necessary to pad it + * out. + * + * @param index + * The subscript. + * @param value + * A long value. + * @return this. + * @throws JSONException + * If the index is negative. + */ + public JSONArray put(int index, long value) throws JSONException { + return this.put(index, Long.valueOf(value)); + } + + /** + * Put a value in the JSONArray, where the value will be a JSONObject that + * is produced from a Map. + * + * @param index + * The subscript. + * @param value + * The Map value. + * @return this. + * @throws JSONException + * If the index is negative or if the the value is an invalid + * number. + * @throws NullPointerException + * If a key in the map is null + */ + public JSONArray put(int index, Map value) throws JSONException { + this.put(index, new JSONObject(value)); + return this; + } + + /** + * Put or replace an object value in the JSONArray. If the index is greater + * than the length of the JSONArray, then null elements will be added as + * necessary to pad it out. + * + * @param index + * The subscript. + * @param value + * The value to put into the array. The value should be a + * Boolean, Double, Integer, JSONArray, JSONObject, Long, or + * String, or the JSONObject.NULL object. + * @return this. + * @throws JSONException + * If the index is negative or if the the value is an invalid + * number. + */ + public JSONArray put(int index, Object value) throws JSONException { + if (index < 0) { + throw new JSONException("JSONArray[" + index + "] not found."); + } + if (index < this.length()) { + JSONObject.testValidity(value); + this.myArrayList.set(index, value); + return this; + } + if(index == this.length()){ + // simple append + return this.put(value); + } + // if we are inserting past the length, we want to grow the array all at once + // instead of incrementally. + this.myArrayList.ensureCapacity(index + 1); + while (index != this.length()) { + // we don't need to test validity of NULL objects + this.myArrayList.add(JSONObject.NULL); + } + return this.put(value); + } + + /** + * Creates a JSONPointer using an initialization string and tries to + * match it to an item within this JSONArray. For example, given a + * JSONArray initialized with this document: + *
+     * [
+     *     {"b":"c"}
+     * ]
+     * 
+ * and this JSONPointer string: + *
+     * "/0/b"
+     * 
+ * Then this method will return the String "c" + * A JSONPointerException may be thrown from code called by this method. + * + * @param jsonPointer string that can be used to create a JSONPointer + * @return the item matched by the JSONPointer, otherwise null + */ + public Object query(String jsonPointer) { + return query(new JSONPointer(jsonPointer)); + } + + /** + * Uses a user initialized JSONPointer and tries to + * match it to an item within this JSONArray. For example, given a + * JSONArray initialized with this document: + *
+     * [
+     *     {"b":"c"}
+     * ]
+     * 
+ * and this JSONPointer: + *
+     * "/0/b"
+     * 
+ * Then this method will return the String "c" + * A JSONPointerException may be thrown from code called by this method. + * + * @param jsonPointer string that can be used to create a JSONPointer + * @return the item matched by the JSONPointer, otherwise null + */ + public Object query(JSONPointer jsonPointer) { + return jsonPointer.queryFrom(this); + } + + /** + * Queries and returns a value from this object using {@code jsonPointer}, or + * returns null if the query fails due to a missing key. + * + * @param jsonPointer the string representation of the JSON pointer + * @return the queried value or {@code null} + * @throws IllegalArgumentException if {@code jsonPointer} has invalid syntax + */ + public Object optQuery(String jsonPointer) { + return optQuery(new JSONPointer(jsonPointer)); + } + + /** + * Queries and returns a value from this object using {@code jsonPointer}, or + * returns null if the query fails due to a missing key. + * + * @param jsonPointer The JSON pointer + * @return the queried value or {@code null} + * @throws IllegalArgumentException if {@code jsonPointer} has invalid syntax + */ + public Object optQuery(JSONPointer jsonPointer) { + try { + return jsonPointer.queryFrom(this); + } catch (JSONPointerException e) { + return null; + } + } + + /** + * Remove an index and close the hole. + * + * @param index + * The index of the element to be removed. + * @return The value that was associated with the index, or null if there + * was no value. + */ + public Object remove(int index) { + return index >= 0 && index < this.length() + ? this.myArrayList.remove(index) + : null; + } + + /** + * Determine if two JSONArrays are similar. + * They must contain similar sequences. + * + * @param other The other JSONArray + * @return true if they are equal + */ + public boolean similar(Object other) { + if (!(other instanceof JSONArray)) { + return false; + } + int len = this.length(); + if (len != ((JSONArray)other).length()) { + return false; + } + for (int i = 0; i < len; i += 1) { + Object valueThis = this.myArrayList.get(i); + Object valueOther = ((JSONArray)other).myArrayList.get(i); + if(valueThis == valueOther) { + continue; + } + if(valueThis == null) { + return false; + } + if (valueThis instanceof JSONObject) { + if (!((JSONObject)valueThis).similar(valueOther)) { + return false; + } + } else if (valueThis instanceof JSONArray) { + if (!((JSONArray)valueThis).similar(valueOther)) { + return false; + } + } else if (!valueThis.equals(valueOther)) { + return false; + } + } + return true; + } + + /** + * Produce a JSONObject by combining a JSONArray of names with the values of + * this JSONArray. + * + * @param names + * A JSONArray containing a list of key strings. These will be + * paired with the values. + * @return A JSONObject, or null if there are no names or if this JSONArray + * has no values. + * @throws JSONException + * If any of the names are null. + */ + public JSONObject toJSONObject(JSONArray names) throws JSONException { + if (names == null || names.isEmpty() || this.isEmpty()) { + return null; + } + JSONObject jo = new JSONObject(names.length()); + for (int i = 0; i < names.length(); i += 1) { + jo.put(names.getString(i), this.opt(i)); + } + return jo; + } + + /** + * Make a JSON text of this JSONArray. For compactness, no unnecessary + * whitespace is added. If it is not possible to produce a syntactically + * correct JSON text then null will be returned instead. This could occur if + * the array contains an invalid number. + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * + * @return a printable, displayable, transmittable representation of the + * array. + */ + @Override + public String toString() { + try { + return this.toString(0); + } catch (Exception e) { + return null; + } + } + + /** + * Make a pretty-printed JSON text of this JSONArray. + * + *

If indentFactor > 0 and the {@link JSONArray} has only + * one element, then the array will be output on a single line: + *

{@code [1]}
+ * + *

If an array has 2 or more elements, then it will be output across + * multiple lines:

{@code
+     * [
+     * 1,
+     * "value 2",
+     * 3
+     * ]
+     * }
+ *

+ * Warning: This method assumes that the data structure is acyclical. + * + * + * @param indentFactor + * The number of spaces to add to each level of indentation. + * @return a printable, displayable, transmittable representation of the + * object, beginning with [ (left + * bracket) and ending with ] + *  (right bracket). + * @throws JSONException + */ + public String toString(int indentFactor) throws JSONException { + StringWriter sw = new StringWriter(); + synchronized (sw.getBuffer()) { + return this.write(sw, indentFactor, 0).toString(); + } + } + + /** + * Write the contents of the JSONArray as JSON text to a writer. For + * compactness, no whitespace is added. + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * + * @return The writer. + * @throws JSONException + */ + public Writer write(Writer writer) throws JSONException { + return this.write(writer, 0, 0); + } + + /** + * Write the contents of the JSONArray as JSON text to a writer. + * + *

If indentFactor > 0 and the {@link JSONArray} has only + * one element, then the array will be output on a single line: + *

{@code [1]}
+ * + *

If an array has 2 or more elements, then it will be output across + * multiple lines:

{@code
+     * [
+     * 1,
+     * "value 2",
+     * 3
+     * ]
+     * }
+ *

+ * Warning: This method assumes that the data structure is acyclical. + * + * + * @param writer + * Writes the serialized JSON + * @param indentFactor + * The number of spaces to add to each level of indentation. + * @param indent + * The indentation of the top level. + * @return The writer. + * @throws JSONException + */ + public Writer write(Writer writer, int indentFactor, int indent) + throws JSONException { + try { + boolean commanate = false; + int length = this.length(); + writer.write('['); + + if (length == 1) { + try { + JSONObject.writeValue(writer, this.myArrayList.get(0), + indentFactor, indent); + } catch (Exception e) { + throw new JSONException("Unable to write JSONArray value at index: 0", e); + } + } else if (length != 0) { + final int newindent = indent + indentFactor; + + for (int i = 0; i < length; i += 1) { + if (commanate) { + writer.write(','); + } + if (indentFactor > 0) { + writer.write('\n'); + } + JSONObject.indent(writer, newindent); + try { + JSONObject.writeValue(writer, this.myArrayList.get(i), + indentFactor, newindent); + } catch (Exception e) { + throw new JSONException("Unable to write JSONArray value at index: " + i, e); + } + commanate = true; + } + if (indentFactor > 0) { + writer.write('\n'); + } + JSONObject.indent(writer, indent); + } + writer.write(']'); + return writer; + } catch (IOException e) { + throw new JSONException(e); + } + } + + /** + * Returns a java.util.List containing all of the elements in this array. + * If an element in the array is a JSONArray or JSONObject it will also + * be converted. + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * @return a java.util.List containing the elements of this array + */ + public List toList() { + List results = new ArrayList(this.myArrayList.size()); + for (Object element : this.myArrayList) { + if (element == null || JSONObject.NULL.equals(element)) { + results.add(null); + } else if (element instanceof JSONArray) { + results.add(((JSONArray) element).toList()); + } else if (element instanceof JSONObject) { + results.add(((JSONObject) element).toMap()); + } else { + results.add(element); + } + } + return results; + } + + /** + * Check if JSONArray is empty. + * + * @return true if JSONArray is empty, otherwise false. + */ + public boolean isEmpty() { + return this.myArrayList.isEmpty(); + } + +} diff --git a/core/src/com/hiveworkshop/json/JSONException.java b/core/src/com/hiveworkshop/json/JSONException.java new file mode 100644 index 0000000..f924fb5 --- /dev/null +++ b/core/src/com/hiveworkshop/json/JSONException.java @@ -0,0 +1,45 @@ +package com.hiveworkshop.json; + +/** + * The JSONException is thrown by the JSON.org classes when things are amiss. + * + * @author JSON.org + * @version 2015-12-09 + */ +public class JSONException extends RuntimeException { + /** Serialization ID */ + private static final long serialVersionUID = 0; + + /** + * Constructs a JSONException with an explanatory message. + * + * @param message + * Detail about the reason for the exception. + */ + public JSONException(final String message) { + super(message); + } + + /** + * Constructs a JSONException with an explanatory message and cause. + * + * @param message + * Detail about the reason for the exception. + * @param cause + * The cause. + */ + public JSONException(final String message, final Throwable cause) { + super(message, cause); + } + + /** + * Constructs a new JSONException with the specified cause. + * + * @param cause + * The cause. + */ + public JSONException(final Throwable cause) { + super(cause.getMessage(), cause); + } + +} diff --git a/core/src/com/hiveworkshop/json/JSONObject.java b/core/src/com/hiveworkshop/json/JSONObject.java new file mode 100644 index 0000000..3bfd26c --- /dev/null +++ b/core/src/com/hiveworkshop/json/JSONObject.java @@ -0,0 +1,2551 @@ +package com.hiveworkshop.json; + +import java.io.Closeable; + +/* + Copyright (c) 2002 JSON.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + The Software shall be used for Good, not Evil. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Collection; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.ResourceBundle; +import java.util.Set; +import java.util.regex.Pattern; + +/** + * A JSONObject is an unordered collection of name/value pairs. Its external + * form is a string wrapped in curly braces with colons between the names and + * values, and commas between the values and names. The internal form is an + * object having get and opt methods for accessing + * the values by name, and put methods for adding or replacing + * values by name. The values can be any of these types: Boolean, + * JSONArray, JSONObject, Number, + * String, or the JSONObject.NULL object. A + * JSONObject constructor can be used to convert an external form JSON text + * into an internal form whose values can be retrieved with the + * get and opt methods, or to convert values into a + * JSON text using the put and toString methods. A + * get method returns a value if one can be found, and throws an + * exception if one cannot be found. An opt method returns a + * default value instead of throwing an exception, and so is useful for + * obtaining optional values. + *

+ * The generic get() and opt() methods return an + * object, which you can cast or query for type. There are also typed + * get and opt methods that do type checking and type + * coercion for you. The opt methods differ from the get methods in that they + * do not throw. Instead, they return a specified value, such as null. + *

+ * The put methods add or replace values in an object. For + * example, + * + *

+ * myString = new JSONObject()
+ *         .put("JSON", "Hello, World!").toString();
+ * 
+ * + * produces the string {"JSON": "Hello, World"}. + *

+ * The texts produced by the toString methods strictly conform to + * the JSON syntax rules. The constructors are more forgiving in the texts they + * will accept: + *

    + *
  • An extra , (comma) may appear just + * before the closing brace.
  • + *
  • Strings may be quoted with ' (single + * quote).
  • + *
  • Strings do not need to be quoted at all if they do not begin with a + * quote or single quote, and if they do not contain leading or trailing + * spaces, and if they do not contain any of these characters: + * { } [ ] / \ : , # and if they do not look like numbers and + * if they are not the reserved words true, false, + * or null.
  • + *
+ * + * @author JSON.org + * @version 2016-08-15 + */ +public class JSONObject { + /** + * JSONObject.NULL is equivalent to the value that JavaScript calls null, + * whilst Java's null is equivalent to the value that JavaScript calls + * undefined. + */ + private static final class Null { + + /** + * There is only intended to be a single instance of the NULL object, + * so the clone method returns itself. + * + * @return NULL. + */ + @Override + protected final Object clone() { + return this; + } + + /** + * A Null object is equal to the null value and to itself. + * + * @param object + * An object to test for nullness. + * @return true if the object parameter is the JSONObject.NULL object or + * null. + */ + @Override + public boolean equals(Object object) { + return object == null || object == this; + } + /** + * A Null object is equal to the null value and to itself. + * + * @return always returns 0. + */ + @Override + public int hashCode() { + return 0; + } + + /** + * Get the "null" string value. + * + * @return The string "null". + */ + @Override + public String toString() { + return "null"; + } + } + + /** + * Regular Expression Pattern that matches JSON Numbers. This is primarily used for + * output to guarantee that we are always writing valid JSON. + */ + static final Pattern NUMBER_PATTERN = Pattern.compile("-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?"); + + /** + * The map where the JSONObject's properties are kept. + */ + private final Map map; + + /** + * It is sometimes more convenient and less ambiguous to have a + * NULL object than to use Java's null value. + * JSONObject.NULL.equals(null) returns true. + * JSONObject.NULL.toString() returns "null". + */ + public static final Object NULL = new Null(); + + /** + * Construct an empty JSONObject. + */ + public JSONObject() { + // HashMap is used on purpose to ensure that elements are unordered by + // the specification. + // JSON tends to be a portable transfer format to allows the container + // implementations to rearrange their items for a faster element + // retrieval based on associative access. + // Therefore, an implementation mustn't rely on the order of the item. + this.map = new HashMap(); + } + + /** + * Construct a JSONObject from a subset of another JSONObject. An array of + * strings is used to identify the keys that should be copied. Missing keys + * are ignored. + * + * @param jo + * A JSONObject. + * @param names + * An array of strings. + */ + public JSONObject(JSONObject jo, String[] names) { + this(names.length); + for (int i = 0; i < names.length; i += 1) { + try { + this.putOnce(names[i], jo.opt(names[i])); + } catch (Exception ignore) { + } + } + } + + /** + * Construct a JSONObject from a JSONTokener. + * + * @param x + * A JSONTokener object containing the source string. + * @throws JSONException + * If there is a syntax error in the source string or a + * duplicated key. + */ + public JSONObject(JSONTokener x) throws JSONException { + this(); + char c; + String key; + + if (x.nextClean() != '{') { + throw x.syntaxError("A JSONObject text must begin with '{'"); + } + for (;;) { + c = x.nextClean(); + switch (c) { + case 0: + throw x.syntaxError("A JSONObject text must end with '}'"); + case '}': + return; + default: + x.back(); + key = x.nextValue().toString(); + } + + // The key is followed by ':'. + + c = x.nextClean(); + if (c != ':') { + throw x.syntaxError("Expected a ':' after a key"); + } + + // Use syntaxError(..) to include error location + + if (key != null) { + // Check if key exists + if (this.opt(key) != null) { + // key already exists + throw x.syntaxError("Duplicate key \"" + key + "\""); + } + // Only add value if non-null + Object value = x.nextValue(); + if (value!=null) { + this.put(key, value); + } + } + + // Pairs are separated by ','. + + switch (x.nextClean()) { + case ';': + case ',': + if (x.nextClean() == '}') { + return; + } + x.back(); + break; + case '}': + return; + default: + throw x.syntaxError("Expected a ',' or '}'"); + } + } + } + + /** + * Construct a JSONObject from a Map. + * + * @param m + * A map object that can be used to initialize the contents of + * the JSONObject. + * @throws JSONException + * If a value in the map is non-finite number. + * @throws NullPointerException + * If a key in the map is null + */ + public JSONObject(Map m) { + if (m == null) { + this.map = new HashMap(); + } else { + this.map = new HashMap(m.size()); + for (final Entry e : m.entrySet()) { + if(e.getKey() == null) { + throw new NullPointerException("Null key."); + } + final Object value = e.getValue(); + if (value != null) { + this.map.put(String.valueOf(e.getKey()), wrap(value)); + } + } + } + } + + /** + * Construct a JSONObject from an Object using bean getters. It reflects on + * all of the public methods of the object. For each of the methods with no + * parameters and a name starting with "get" or + * "is" followed by an uppercase letter, the method is invoked, + * and a key and the value returned from the getter method are put into the + * new JSONObject. + *

+ * The key is formed by removing the "get" or "is" + * prefix. If the second remaining character is not upper case, then the + * first character is converted to lower case. + *

+ * Methods that are static, return void, + * have parameters, or are "bridge" methods, are ignored. + *

+ * For example, if an object has a method named "getName", and + * if the result of calling object.getName() is + * "Larry Fine", then the JSONObject will contain + * "name": "Larry Fine". + *

+ * The {@link JSONPropertyName} annotation can be used on a bean getter to + * override key name used in the JSONObject. For example, using the object + * above with the getName method, if we annotated it with: + *

+     * @JSONPropertyName("FullName")
+     * public String getName() { return this.name; }
+     * 
+ * The resulting JSON object would contain "FullName": "Larry Fine" + *

+ * Similarly, the {@link JSONPropertyName} annotation can be used on non- + * get and is methods. We can also override key + * name used in the JSONObject as seen below even though the field would normally + * be ignored: + *

+     * @JSONPropertyName("FullName")
+     * public String fullName() { return this.name; }
+     * 
+ * The resulting JSON object would contain "FullName": "Larry Fine" + *

+ * The {@link JSONPropertyIgnore} annotation can be used to force the bean property + * to not be serialized into JSON. If both {@link JSONPropertyIgnore} and + * {@link JSONPropertyName} are defined on the same method, a depth comparison is + * performed and the one closest to the concrete class being serialized is used. + * If both annotations are at the same level, then the {@link JSONPropertyIgnore} + * annotation takes precedent and the field is not serialized. + * For example, the following declaration would prevent the getName + * method from being serialized: + *

+     * @JSONPropertyName("FullName")
+     * @JSONPropertyIgnore 
+     * public String getName() { return this.name; }
+     * 
+ *

+ * + * @param bean + * An object that has getter methods that should be used to make + * a JSONObject. + */ + public JSONObject(Object bean) { + this(); + this.populateMap(bean); + } + + /** + * Construct a JSONObject from an Object, using reflection to find the + * public members. The resulting JSONObject's keys will be the strings from + * the names array, and the values will be the field values associated with + * those keys in the object. If a key is not found or not visible, then it + * will not be copied into the new JSONObject. + * + * @param object + * An object that has fields that should be used to make a + * JSONObject. + * @param names + * An array of strings, the names of the fields to be obtained + * from the object. + */ + public JSONObject(Object object, String names[]) { + this(names.length); + Class c = object.getClass(); + for (int i = 0; i < names.length; i += 1) { + String name = names[i]; + try { + this.putOpt(name, c.getField(name).get(object)); + } catch (Exception ignore) { + } + } + } + + /** + * Construct a JSONObject from a source JSON text string. This is the most + * commonly used JSONObject constructor. + * + * @param source + * A string beginning with { (left + * brace) and ending with } + *  (right brace). + * @exception JSONException + * If there is a syntax error in the source string or a + * duplicated key. + */ + public JSONObject(String source) throws JSONException { + this(new JSONTokener(source)); + } + + /** + * Construct a JSONObject from a ResourceBundle. + * + * @param baseName + * The ResourceBundle base name. + * @param locale + * The Locale to load the ResourceBundle for. + * @throws JSONException + * If any JSONExceptions are detected. + */ + public JSONObject(String baseName, Locale locale) throws JSONException { + this(); + ResourceBundle bundle = ResourceBundle.getBundle(baseName, locale, + Thread.currentThread().getContextClassLoader()); + +// Iterate through the keys in the bundle. + + Enumeration keys = bundle.getKeys(); + while (keys.hasMoreElements()) { + Object key = keys.nextElement(); + if (key != null) { + +// Go through the path, ensuring that there is a nested JSONObject for each +// segment except the last. Add the value using the last segment's name into +// the deepest nested JSONObject. + + String[] path = ((String) key).split("\\."); + int last = path.length - 1; + JSONObject target = this; + for (int i = 0; i < last; i += 1) { + String segment = path[i]; + JSONObject nextTarget = target.optJSONObject(segment); + if (nextTarget == null) { + nextTarget = new JSONObject(); + target.put(segment, nextTarget); + } + target = nextTarget; + } + target.put(path[last], bundle.getString((String) key)); + } + } + } + + /** + * Constructor to specify an initial capacity of the internal map. Useful for library + * internal calls where we know, or at least can best guess, how big this JSONObject + * will be. + * + * @param initialCapacity initial capacity of the internal map. + */ + protected JSONObject(int initialCapacity){ + this.map = new HashMap(initialCapacity); + } + + /** + * Accumulate values under a key. It is similar to the put method except + * that if there is already an object stored under the key then a JSONArray + * is stored under the key to hold all of the accumulated values. If there + * is already a JSONArray, then the new value is appended to it. In + * contrast, the put method replaces the previous value. + * + * If only one value is accumulated that is not a JSONArray, then the result + * will be the same as using put. But if multiple values are accumulated, + * then the result will be like append. + * + * @param key + * A key string. + * @param value + * An object to be accumulated under the key. + * @return this. + * @throws JSONException + * If the value is non-finite number. + * @throws NullPointerException + * If the key is null. + */ + public JSONObject accumulate(String key, Object value) throws JSONException { + testValidity(value); + Object object = this.opt(key); + if (object == null) { + this.put(key, + value instanceof JSONArray ? new JSONArray().put(value) + : value); + } else if (object instanceof JSONArray) { + ((JSONArray) object).put(value); + } else { + this.put(key, new JSONArray().put(object).put(value)); + } + return this; + } + + /** + * Append values to the array under a key. If the key does not exist in the + * JSONObject, then the key is put in the JSONObject with its value being a + * JSONArray containing the value parameter. If the key was already + * associated with a JSONArray, then the value parameter is appended to it. + * + * @param key + * A key string. + * @param value + * An object to be accumulated under the key. + * @return this. + * @throws JSONException + * If the value is non-finite number or if the current value associated with + * the key is not a JSONArray. + * @throws NullPointerException + * If the key is null. + */ + public JSONObject append(String key, Object value) throws JSONException { + testValidity(value); + Object object = this.opt(key); + if (object == null) { + this.put(key, new JSONArray().put(value)); + } else if (object instanceof JSONArray) { + this.put(key, ((JSONArray) object).put(value)); + } else { + throw new JSONException("JSONObject[" + key + + "] is not a JSONArray."); + } + return this; + } + + /** + * Produce a string from a double. The string "null" will be returned if the + * number is not finite. + * + * @param d + * A double. + * @return A String. + */ + public static String doubleToString(double d) { + if (Double.isInfinite(d) || Double.isNaN(d)) { + return "null"; + } + +// Shave off trailing zeros and decimal point, if possible. + + String string = Double.toString(d); + if (string.indexOf('.') > 0 && string.indexOf('e') < 0 + && string.indexOf('E') < 0) { + while (string.endsWith("0")) { + string = string.substring(0, string.length() - 1); + } + if (string.endsWith(".")) { + string = string.substring(0, string.length() - 1); + } + } + return string; + } + + /** + * Get the value object associated with a key. + * + * @param key + * A key string. + * @return The object associated with the key. + * @throws JSONException + * if the key is not found. + */ + public Object get(String key) throws JSONException { + if (key == null) { + throw new JSONException("Null key."); + } + Object object = this.opt(key); + if (object == null) { + throw new JSONException("JSONObject[" + quote(key) + "] not found."); + } + return object; + } + + /** + * Get the enum value associated with a key. + * + * @param + * Enum Type + * @param clazz + * The type of enum to retrieve. + * @param key + * A key string. + * @return The enum value associated with the key + * @throws JSONException + * if the key is not found or if the value cannot be converted + * to an enum. + */ + public > E getEnum(Class clazz, String key) throws JSONException { + E val = optEnum(clazz, key); + if(val==null) { + // JSONException should really take a throwable argument. + // If it did, I would re-implement this with the Enum.valueOf + // method and place any thrown exception in the JSONException + throw new JSONException("JSONObject[" + quote(key) + + "] is not an enum of type " + quote(clazz.getSimpleName()) + + "."); + } + return val; + } + + /** + * Get the boolean value associated with a key. + * + * @param key + * A key string. + * @return The truth. + * @throws JSONException + * if the value is not a Boolean or the String "true" or + * "false". + */ + public boolean getBoolean(String key) throws JSONException { + Object object = this.get(key); + if (object.equals(Boolean.FALSE) + || (object instanceof String && ((String) object) + .equalsIgnoreCase("false"))) { + return false; + } else if (object.equals(Boolean.TRUE) + || (object instanceof String && ((String) object) + .equalsIgnoreCase("true"))) { + return true; + } + throw new JSONException("JSONObject[" + quote(key) + + "] is not a Boolean."); + } + + /** + * Get the BigInteger value associated with a key. + * + * @param key + * A key string. + * @return The numeric value. + * @throws JSONException + * if the key is not found or if the value cannot + * be converted to BigInteger. + */ + public BigInteger getBigInteger(String key) throws JSONException { + Object object = this.get(key); + BigInteger ret = objectToBigInteger(object, null); + if (ret != null) { + return ret; + } + throw new JSONException("JSONObject[" + quote(key) + + "] could not be converted to BigInteger (" + object + ")."); + } + + /** + * Get the BigDecimal value associated with a key. If the value is float or + * double, the the {@link BigDecimal#BigDecimal(double)} constructor will + * be used. See notes on the constructor for conversion issues that may + * arise. + * + * @param key + * A key string. + * @return The numeric value. + * @throws JSONException + * if the key is not found or if the value + * cannot be converted to BigDecimal. + */ + public BigDecimal getBigDecimal(String key) throws JSONException { + Object object = this.get(key); + BigDecimal ret = objectToBigDecimal(object, null); + if (ret != null) { + return ret; + } + throw new JSONException("JSONObject[" + quote(key) + + "] could not be converted to BigDecimal (" + object + ")."); + } + + /** + * Get the double value associated with a key. + * + * @param key + * A key string. + * @return The numeric value. + * @throws JSONException + * if the key is not found or if the value is not a Number + * object and cannot be converted to a number. + */ + public double getDouble(String key) throws JSONException { + return this.getNumber(key).doubleValue(); + } + + /** + * Get the float value associated with a key. + * + * @param key + * A key string. + * @return The numeric value. + * @throws JSONException + * if the key is not found or if the value is not a Number + * object and cannot be converted to a number. + */ + public float getFloat(String key) throws JSONException { + return this.getNumber(key).floatValue(); + } + + /** + * Get the Number value associated with a key. + * + * @param key + * A key string. + * @return The numeric value. + * @throws JSONException + * if the key is not found or if the value is not a Number + * object and cannot be converted to a number. + */ + public Number getNumber(String key) throws JSONException { + Object object = this.get(key); + try { + if (object instanceof Number) { + return (Number)object; + } + return stringToNumber(object.toString()); + } catch (Exception e) { + throw new JSONException("JSONObject[" + quote(key) + + "] is not a number.", e); + } + } + + /** + * Get the int value associated with a key. + * + * @param key + * A key string. + * @return The integer value. + * @throws JSONException + * if the key is not found or if the value cannot be converted + * to an integer. + */ + public int getInt(String key) throws JSONException { + return this.getNumber(key).intValue(); + } + + /** + * Get the JSONArray value associated with a key. + * + * @param key + * A key string. + * @return A JSONArray which is the value. + * @throws JSONException + * if the key is not found or if the value is not a JSONArray. + */ + public JSONArray getJSONArray(String key) throws JSONException { + Object object = this.get(key); + if (object instanceof JSONArray) { + return (JSONArray) object; + } + throw new JSONException("JSONObject[" + quote(key) + + "] is not a JSONArray."); + } + + /** + * Get the JSONObject value associated with a key. + * + * @param key + * A key string. + * @return A JSONObject which is the value. + * @throws JSONException + * if the key is not found or if the value is not a JSONObject. + */ + public JSONObject getJSONObject(String key) throws JSONException { + Object object = this.get(key); + if (object instanceof JSONObject) { + return (JSONObject) object; + } + throw new JSONException("JSONObject[" + quote(key) + + "] is not a JSONObject."); + } + + /** + * Get the long value associated with a key. + * + * @param key + * A key string. + * @return The long value. + * @throws JSONException + * if the key is not found or if the value cannot be converted + * to a long. + */ + public long getLong(String key) throws JSONException { + return this.getNumber(key).longValue(); + } + + /** + * Get an array of field names from a JSONObject. + * + * @param jo + * JSON object + * @return An array of field names, or null if there are no names. + */ + public static String[] getNames(JSONObject jo) { + if (jo.isEmpty()) { + return null; + } + return jo.keySet().toArray(new String[jo.length()]); + } + + /** + * Get an array of public field names from an Object. + * + * @param object + * object to read + * @return An array of field names, or null if there are no names. + */ + public static String[] getNames(Object object) { + if (object == null) { + return null; + } + Class klass = object.getClass(); + Field[] fields = klass.getFields(); + int length = fields.length; + if (length == 0) { + return null; + } + String[] names = new String[length]; + for (int i = 0; i < length; i += 1) { + names[i] = fields[i].getName(); + } + return names; + } + + /** + * Get the string associated with a key. + * + * @param key + * A key string. + * @return A string which is the value. + * @throws JSONException + * if there is no string value for the key. + */ + public String getString(String key) throws JSONException { + Object object = this.get(key); + if (object instanceof String) { + return (String) object; + } + throw new JSONException("JSONObject[" + quote(key) + "] not a string."); + } + + /** + * Determine if the JSONObject contains a specific key. + * + * @param key + * A key string. + * @return true if the key exists in the JSONObject. + */ + public boolean has(String key) { + return this.map.containsKey(key); + } + + /** + * Increment a property of a JSONObject. If there is no such property, + * create one with a value of 1. If there is such a property, and if it is + * an Integer, Long, Double, or Float, then add one to it. + * + * @param key + * A key string. + * @return this. + * @throws JSONException + * If there is already a property with this name that is not an + * Integer, Long, Double, or Float. + */ + public JSONObject increment(String key) throws JSONException { + Object value = this.opt(key); + if (value == null) { + this.put(key, 1); + } else if (value instanceof BigInteger) { + this.put(key, ((BigInteger)value).add(BigInteger.ONE)); + } else if (value instanceof BigDecimal) { + this.put(key, ((BigDecimal)value).add(BigDecimal.ONE)); + } else if (value instanceof Integer) { + this.put(key, ((Integer) value).intValue() + 1); + } else if (value instanceof Long) { + this.put(key, ((Long) value).longValue() + 1L); + } else if (value instanceof Double) { + this.put(key, ((Double) value).doubleValue() + 1.0d); + } else if (value instanceof Float) { + this.put(key, ((Float) value).floatValue() + 1.0f); + } else { + throw new JSONException("Unable to increment [" + quote(key) + "]."); + } + return this; + } + + /** + * Determine if the value associated with the key is null or if there is no + * value. + * + * @param key + * A key string. + * @return true if there is no value associated with the key or if the value + * is the JSONObject.NULL object. + */ + public boolean isNull(String key) { + return JSONObject.NULL.equals(this.opt(key)); + } + + /** + * Get an enumeration of the keys of the JSONObject. Modifying this key Set will also + * modify the JSONObject. Use with caution. + * + * @see Set#iterator() + * + * @return An iterator of the keys. + */ + public Iterator keys() { + return this.keySet().iterator(); + } + + /** + * Get a set of keys of the JSONObject. Modifying this key Set will also modify the + * JSONObject. Use with caution. + * + * @see Map#keySet() + * + * @return A keySet. + */ + public Set keySet() { + return this.map.keySet(); + } + + /** + * Get a set of entries of the JSONObject. These are raw values and may not + * match what is returned by the JSONObject get* and opt* functions. Modifying + * the returned EntrySet or the Entry objects contained therein will modify the + * backing JSONObject. This does not return a clone or a read-only view. + * + * Use with caution. + * + * @see Map#entrySet() + * + * @return An Entry Set + */ + protected Set> entrySet() { + return this.map.entrySet(); + } + + /** + * Get the number of keys stored in the JSONObject. + * + * @return The number of keys in the JSONObject. + */ + public int length() { + return this.map.size(); + } + + /** + * Check if JSONObject is empty. + * + * @return true if JSONObject is empty, otherwise false. + */ + public boolean isEmpty() { + return this.map.isEmpty(); + } + + /** + * Produce a JSONArray containing the names of the elements of this + * JSONObject. + * + * @return A JSONArray containing the key strings, or null if the JSONObject + * is empty. + */ + public JSONArray names() { + if(this.map.isEmpty()) { + return null; + } + return new JSONArray(this.map.keySet()); + } + + /** + * Produce a string from a Number. + * + * @param number + * A Number + * @return A String. + * @throws JSONException + * If n is a non-finite number. + */ + public static String numberToString(Number number) throws JSONException { + if (number == null) { + throw new JSONException("Null pointer"); + } + testValidity(number); + + // Shave off trailing zeros and decimal point, if possible. + + String string = number.toString(); + if (string.indexOf('.') > 0 && string.indexOf('e') < 0 + && string.indexOf('E') < 0) { + while (string.endsWith("0")) { + string = string.substring(0, string.length() - 1); + } + if (string.endsWith(".")) { + string = string.substring(0, string.length() - 1); + } + } + return string; + } + + /** + * Get an optional value associated with a key. + * + * @param key + * A key string. + * @return An object which is the value, or null if there is no value. + */ + public Object opt(String key) { + return key == null ? null : this.map.get(key); + } + + /** + * Get the enum value associated with a key. + * + * @param + * Enum Type + * @param clazz + * The type of enum to retrieve. + * @param key + * A key string. + * @return The enum value associated with the key or null if not found + */ + public > E optEnum(Class clazz, String key) { + return this.optEnum(clazz, key, null); + } + + /** + * Get the enum value associated with a key. + * + * @param + * Enum Type + * @param clazz + * The type of enum to retrieve. + * @param key + * A key string. + * @param defaultValue + * The default in case the value is not found + * @return The enum value associated with the key or defaultValue + * if the value is not found or cannot be assigned to clazz + */ + public > E optEnum(Class clazz, String key, E defaultValue) { + try { + Object val = this.opt(key); + if (NULL.equals(val)) { + return defaultValue; + } + if (clazz.isAssignableFrom(val.getClass())) { + // we just checked it! + @SuppressWarnings("unchecked") + E myE = (E) val; + return myE; + } + return Enum.valueOf(clazz, val.toString()); + } catch (IllegalArgumentException e) { + return defaultValue; + } catch (NullPointerException e) { + return defaultValue; + } + } + + /** + * Get an optional boolean associated with a key. It returns false if there + * is no such key, or if the value is not Boolean.TRUE or the String "true". + * + * @param key + * A key string. + * @return The truth. + */ + public boolean optBoolean(String key) { + return this.optBoolean(key, false); + } + + /** + * Get an optional boolean associated with a key. It returns the + * defaultValue if there is no such key, or if it is not a Boolean or the + * String "true" or "false" (case insensitive). + * + * @param key + * A key string. + * @param defaultValue + * The default. + * @return The truth. + */ + public boolean optBoolean(String key, boolean defaultValue) { + Object val = this.opt(key); + if (NULL.equals(val)) { + return defaultValue; + } + if (val instanceof Boolean){ + return ((Boolean) val).booleanValue(); + } + try { + // we'll use the get anyway because it does string conversion. + return this.getBoolean(key); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get an optional BigDecimal associated with a key, or the defaultValue if + * there is no such key or if its value is not a number. If the value is a + * string, an attempt will be made to evaluate it as a number. If the value + * is float or double, then the {@link BigDecimal#BigDecimal(double)} + * constructor will be used. See notes on the constructor for conversion + * issues that may arise. + * + * @param key + * A key string. + * @param defaultValue + * The default. + * @return An object which is the value. + */ + public BigDecimal optBigDecimal(String key, BigDecimal defaultValue) { + Object val = this.opt(key); + return objectToBigDecimal(val, defaultValue); + } + + /** + * @param val value to convert + * @param defaultValue default value to return is the conversion doesn't work or is null. + * @return BigDecimal conversion of the original value, or the defaultValue if unable + * to convert. + */ + static BigDecimal objectToBigDecimal(Object val, BigDecimal defaultValue) { + if (NULL.equals(val)) { + return defaultValue; + } + if (val instanceof BigDecimal){ + return (BigDecimal) val; + } + if (val instanceof BigInteger){ + return new BigDecimal((BigInteger) val); + } + if (val instanceof Double || val instanceof Float){ + final double d = ((Number) val).doubleValue(); + if(Double.isNaN(d)) { + return defaultValue; + } + return new BigDecimal(((Number) val).doubleValue()); + } + if (val instanceof Long || val instanceof Integer + || val instanceof Short || val instanceof Byte){ + return new BigDecimal(((Number) val).longValue()); + } + // don't check if it's a string in case of unchecked Number subclasses + try { + return new BigDecimal(val.toString()); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get an optional BigInteger associated with a key, or the defaultValue if + * there is no such key or if its value is not a number. If the value is a + * string, an attempt will be made to evaluate it as a number. + * + * @param key + * A key string. + * @param defaultValue + * The default. + * @return An object which is the value. + */ + public BigInteger optBigInteger(String key, BigInteger defaultValue) { + Object val = this.opt(key); + return objectToBigInteger(val, defaultValue); + } + + /** + * @param val value to convert + * @param defaultValue default value to return is the conversion doesn't work or is null. + * @return BigInteger conversion of the original value, or the defaultValue if unable + * to convert. + */ + static BigInteger objectToBigInteger(Object val, BigInteger defaultValue) { + if (NULL.equals(val)) { + return defaultValue; + } + if (val instanceof BigInteger){ + return (BigInteger) val; + } + if (val instanceof BigDecimal){ + return ((BigDecimal) val).toBigInteger(); + } + if (val instanceof Double || val instanceof Float){ + final double d = ((Number) val).doubleValue(); + if(Double.isNaN(d)) { + return defaultValue; + } + return new BigDecimal(d).toBigInteger(); + } + if (val instanceof Long || val instanceof Integer + || val instanceof Short || val instanceof Byte){ + return BigInteger.valueOf(((Number) val).longValue()); + } + // don't check if it's a string in case of unchecked Number subclasses + try { + // the other opt functions handle implicit conversions, i.e. + // jo.put("double",1.1d); + // jo.optInt("double"); -- will return 1, not an error + // this conversion to BigDecimal then to BigInteger is to maintain + // that type cast support that may truncate the decimal. + final String valStr = val.toString(); + if(isDecimalNotation(valStr)) { + return new BigDecimal(valStr).toBigInteger(); + } + return new BigInteger(valStr); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get an optional double associated with a key, or NaN if there is no such + * key or if its value is not a number. If the value is a string, an attempt + * will be made to evaluate it as a number. + * + * @param key + * A string which is the key. + * @return An object which is the value. + */ + public double optDouble(String key) { + return this.optDouble(key, Double.NaN); + } + + /** + * Get an optional double associated with a key, or the defaultValue if + * there is no such key or if its value is not a number. If the value is a + * string, an attempt will be made to evaluate it as a number. + * + * @param key + * A key string. + * @param defaultValue + * The default. + * @return An object which is the value. + */ + public double optDouble(String key, double defaultValue) { + Number val = this.optNumber(key); + if (val == null) { + return defaultValue; + } + final double doubleValue = val.doubleValue(); + // if (Double.isNaN(doubleValue) || Double.isInfinite(doubleValue)) { + // return defaultValue; + // } + return doubleValue; + } + + /** + * Get the optional double value associated with an index. NaN is returned + * if there is no value for the index, or if the value is not a number and + * cannot be converted to a number. + * + * @param key + * A key string. + * @return The value. + */ + public float optFloat(String key) { + return this.optFloat(key, Float.NaN); + } + + /** + * Get the optional double value associated with an index. The defaultValue + * is returned if there is no value for the index, or if the value is not a + * number and cannot be converted to a number. + * + * @param key + * A key string. + * @param defaultValue + * The default value. + * @return The value. + */ + public float optFloat(String key, float defaultValue) { + Number val = this.optNumber(key); + if (val == null) { + return defaultValue; + } + final float floatValue = val.floatValue(); + // if (Float.isNaN(floatValue) || Float.isInfinite(floatValue)) { + // return defaultValue; + // } + return floatValue; + } + + /** + * Get an optional int value associated with a key, or zero if there is no + * such key or if the value is not a number. If the value is a string, an + * attempt will be made to evaluate it as a number. + * + * @param key + * A key string. + * @return An object which is the value. + */ + public int optInt(String key) { + return this.optInt(key, 0); + } + + /** + * Get an optional int value associated with a key, or the default if there + * is no such key or if the value is not a number. If the value is a string, + * an attempt will be made to evaluate it as a number. + * + * @param key + * A key string. + * @param defaultValue + * The default. + * @return An object which is the value. + */ + public int optInt(String key, int defaultValue) { + final Number val = this.optNumber(key, null); + if (val == null) { + return defaultValue; + } + return val.intValue(); + } + + /** + * Get an optional JSONArray associated with a key. It returns null if there + * is no such key, or if its value is not a JSONArray. + * + * @param key + * A key string. + * @return A JSONArray which is the value. + */ + public JSONArray optJSONArray(String key) { + Object o = this.opt(key); + return o instanceof JSONArray ? (JSONArray) o : null; + } + + /** + * Get an optional JSONObject associated with a key. It returns null if + * there is no such key, or if its value is not a JSONObject. + * + * @param key + * A key string. + * @return A JSONObject which is the value. + */ + public JSONObject optJSONObject(String key) { + Object object = this.opt(key); + return object instanceof JSONObject ? (JSONObject) object : null; + } + + /** + * Get an optional long value associated with a key, or zero if there is no + * such key or if the value is not a number. If the value is a string, an + * attempt will be made to evaluate it as a number. + * + * @param key + * A key string. + * @return An object which is the value. + */ + public long optLong(String key) { + return this.optLong(key, 0); + } + + /** + * Get an optional long value associated with a key, or the default if there + * is no such key or if the value is not a number. If the value is a string, + * an attempt will be made to evaluate it as a number. + * + * @param key + * A key string. + * @param defaultValue + * The default. + * @return An object which is the value. + */ + public long optLong(String key, long defaultValue) { + final Number val = this.optNumber(key, null); + if (val == null) { + return defaultValue; + } + + return val.longValue(); + } + + /** + * Get an optional {@link Number} value associated with a key, or null + * if there is no such key or if the value is not a number. If the value is a string, + * an attempt will be made to evaluate it as a number ({@link BigDecimal}). This method + * would be used in cases where type coercion of the number value is unwanted. + * + * @param key + * A key string. + * @return An object which is the value. + */ + public Number optNumber(String key) { + return this.optNumber(key, null); + } + + /** + * Get an optional {@link Number} value associated with a key, or the default if there + * is no such key or if the value is not a number. If the value is a string, + * an attempt will be made to evaluate it as a number. This method + * would be used in cases where type coercion of the number value is unwanted. + * + * @param key + * A key string. + * @param defaultValue + * The default. + * @return An object which is the value. + */ + public Number optNumber(String key, Number defaultValue) { + Object val = this.opt(key); + if (NULL.equals(val)) { + return defaultValue; + } + if (val instanceof Number){ + return (Number) val; + } + + try { + return stringToNumber(val.toString()); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get an optional string associated with a key. It returns an empty string + * if there is no such key. If the value is not a string and is not null, + * then it is converted to a string. + * + * @param key + * A key string. + * @return A string which is the value. + */ + public String optString(String key) { + return this.optString(key, ""); + } + + /** + * Get an optional string associated with a key. It returns the defaultValue + * if there is no such key. + * + * @param key + * A key string. + * @param defaultValue + * The default. + * @return A string which is the value. + */ + public String optString(String key, String defaultValue) { + Object object = this.opt(key); + return NULL.equals(object) ? defaultValue : object.toString(); + } + + /** + * Populates the internal map of the JSONObject with the bean properties. The + * bean can not be recursive. + * + * @see JSONObject#JSONObject(Object) + * + * @param bean + * the bean + */ + private void populateMap(Object bean) { + Class klass = bean.getClass(); + + // If klass is a System class then set includeSuperClass to false. + + boolean includeSuperClass = klass.getClassLoader() != null; + + Method[] methods = includeSuperClass ? klass.getMethods() : klass.getDeclaredMethods(); + for (final Method method : methods) { + final int modifiers = method.getModifiers(); + if (Modifier.isPublic(modifiers) + && !Modifier.isStatic(modifiers) + && method.getParameterTypes().length == 0 + && !method.isBridge() + && method.getReturnType() != Void.TYPE + && isValidMethodName(method.getName())) { + final String key = getKeyNameFromMethod(method); + if (key != null && !key.isEmpty()) { + try { + final Object result = method.invoke(bean); + if (result != null) { + this.map.put(key, wrap(result)); + // we don't use the result anywhere outside of wrap + // if it's a resource we should be sure to close it + // after calling toString + if (result instanceof Closeable) { + try { + ((Closeable) result).close(); + } catch (IOException ignore) { + } + } + } + } catch (IllegalAccessException ignore) { + } catch (IllegalArgumentException ignore) { + } catch (InvocationTargetException ignore) { + } + } + } + } + } + + private boolean isValidMethodName(String name) { + return !"getClass".equals(name) && !"getDeclaringClass".equals(name); + } + + private String getKeyNameFromMethod(Method method) { + final int ignoreDepth = getAnnotationDepth(method, JSONPropertyIgnore.class); + if (ignoreDepth > 0) { + final int forcedNameDepth = getAnnotationDepth(method, JSONPropertyName.class); + if (forcedNameDepth < 0 || ignoreDepth <= forcedNameDepth) { + // the hierarchy asked to ignore, and the nearest name override + // was higher or non-existent + return null; + } + } + JSONPropertyName annotation = getAnnotation(method, JSONPropertyName.class); + if (annotation != null && annotation.value() != null && !annotation.value().isEmpty()) { + return annotation.value(); + } + String key; + final String name = method.getName(); + if (name.startsWith("get") && name.length() > 3) { + key = name.substring(3); + } else if (name.startsWith("is") && name.length() > 2) { + key = name.substring(2); + } else { + return null; + } + // if the first letter in the key is not uppercase, then skip. + // This is to maintain backwards compatibility before PR406 + // (https://github.com/stleary/JSON-java/pull/406/) + if (Character.isLowerCase(key.charAt(0))) { + return null; + } + if (key.length() == 1) { + key = key.toLowerCase(Locale.ROOT); + } else if (!Character.isUpperCase(key.charAt(1))) { + key = key.substring(0, 1).toLowerCase(Locale.ROOT) + key.substring(1); + } + return key; + } + + /** + * Searches the class hierarchy to see if the method or it's super + * implementations and interfaces has the annotation. + * + * @param + * type of the annotation + * + * @param m + * method to check + * @param annotationClass + * annotation to look for + * @return the {@link Annotation} if the annotation exists on the current method + * or one of it's super class definitions + */ + private static A getAnnotation(final Method m, final Class annotationClass) { + // if we have invalid data the result is null + if (m == null || annotationClass == null) { + return null; + } + + if (m.isAnnotationPresent(annotationClass)) { + return m.getAnnotation(annotationClass); + } + + // if we've already reached the Object class, return null; + Class c = m.getDeclaringClass(); + if (c.getSuperclass() == null) { + return null; + } + + // check directly implemented interfaces for the method being checked + for (Class i : c.getInterfaces()) { + try { + Method im = i.getMethod(m.getName(), m.getParameterTypes()); + return getAnnotation(im, annotationClass); + } catch (final SecurityException ex) { + continue; + } catch (final NoSuchMethodException ex) { + continue; + } + } + + try { + return getAnnotation( + c.getSuperclass().getMethod(m.getName(), m.getParameterTypes()), + annotationClass); + } catch (final SecurityException ex) { + return null; + } catch (final NoSuchMethodException ex) { + return null; + } + } + + /** + * Searches the class hierarchy to see if the method or it's super + * implementations and interfaces has the annotation. Returns the depth of the + * annotation in the hierarchy. + * + * @param + * type of the annotation + * + * @param m + * method to check + * @param annotationClass + * annotation to look for + * @return Depth of the annotation or -1 if the annotation is not on the method. + */ + private static int getAnnotationDepth(final Method m, final Class annotationClass) { + // if we have invalid data the result is -1 + if (m == null || annotationClass == null) { + return -1; + } + + if (m.isAnnotationPresent(annotationClass)) { + return 1; + } + + // if we've already reached the Object class, return -1; + Class c = m.getDeclaringClass(); + if (c.getSuperclass() == null) { + return -1; + } + + // check directly implemented interfaces for the method being checked + for (Class i : c.getInterfaces()) { + try { + Method im = i.getMethod(m.getName(), m.getParameterTypes()); + int d = getAnnotationDepth(im, annotationClass); + if (d > 0) { + // since the annotation was on the interface, add 1 + return d + 1; + } + } catch (final SecurityException ex) { + continue; + } catch (final NoSuchMethodException ex) { + continue; + } + } + + try { + int d = getAnnotationDepth( + c.getSuperclass().getMethod(m.getName(), m.getParameterTypes()), + annotationClass); + if (d > 0) { + // since the annotation was on the superclass, add 1 + return d + 1; + } + return -1; + } catch (final SecurityException ex) { + return -1; + } catch (final NoSuchMethodException ex) { + return -1; + } + } + + /** + * Put a key/boolean pair in the JSONObject. + * + * @param key + * A key string. + * @param value + * A boolean which is the value. + * @return this. + * @throws JSONException + * If the value is non-finite number. + * @throws NullPointerException + * If the key is null. + */ + public JSONObject put(String key, boolean value) throws JSONException { + return this.put(key, value ? Boolean.TRUE : Boolean.FALSE); + } + + /** + * Put a key/value pair in the JSONObject, where the value will be a + * JSONArray which is produced from a Collection. + * + * @param key + * A key string. + * @param value + * A Collection value. + * @return this. + * @throws JSONException + * If the value is non-finite number. + * @throws NullPointerException + * If the key is null. + */ + public JSONObject put(String key, Collection value) throws JSONException { + return this.put(key, new JSONArray(value)); + } + + /** + * Put a key/double pair in the JSONObject. + * + * @param key + * A key string. + * @param value + * A double which is the value. + * @return this. + * @throws JSONException + * If the value is non-finite number. + * @throws NullPointerException + * If the key is null. + */ + public JSONObject put(String key, double value) throws JSONException { + return this.put(key, Double.valueOf(value)); + } + + /** + * Put a key/float pair in the JSONObject. + * + * @param key + * A key string. + * @param value + * A float which is the value. + * @return this. + * @throws JSONException + * If the value is non-finite number. + * @throws NullPointerException + * If the key is null. + */ + public JSONObject put(String key, float value) throws JSONException { + return this.put(key, Float.valueOf(value)); + } + + /** + * Put a key/int pair in the JSONObject. + * + * @param key + * A key string. + * @param value + * An int which is the value. + * @return this. + * @throws JSONException + * If the value is non-finite number. + * @throws NullPointerException + * If the key is null. + */ + public JSONObject put(String key, int value) throws JSONException { + return this.put(key, Integer.valueOf(value)); + } + + /** + * Put a key/long pair in the JSONObject. + * + * @param key + * A key string. + * @param value + * A long which is the value. + * @return this. + * @throws JSONException + * If the value is non-finite number. + * @throws NullPointerException + * If the key is null. + */ + public JSONObject put(String key, long value) throws JSONException { + return this.put(key, Long.valueOf(value)); + } + + /** + * Put a key/value pair in the JSONObject, where the value will be a + * JSONObject which is produced from a Map. + * + * @param key + * A key string. + * @param value + * A Map value. + * @return this. + * @throws JSONException + * If the value is non-finite number. + * @throws NullPointerException + * If the key is null. + */ + public JSONObject put(String key, Map value) throws JSONException { + return this.put(key, new JSONObject(value)); + } + + /** + * Put a key/value pair in the JSONObject. If the value is null, then the + * key will be removed from the JSONObject if it is present. + * + * @param key + * A key string. + * @param value + * An object which is the value. It should be of one of these + * types: Boolean, Double, Integer, JSONArray, JSONObject, Long, + * String, or the JSONObject.NULL object. + * @return this. + * @throws JSONException + * If the value is non-finite number. + * @throws NullPointerException + * If the key is null. + */ + public JSONObject put(String key, Object value) throws JSONException { + if (key == null) { + throw new NullPointerException("Null key."); + } + if (value != null) { + testValidity(value); + this.map.put(key, value); + } else { + this.remove(key); + } + return this; + } + + /** + * Put a key/value pair in the JSONObject, but only if the key and the value + * are both non-null, and only if there is not already a member with that + * name. + * + * @param key + * key to insert into + * @param value + * value to insert + * @return this. + * @throws JSONException + * if the key is a duplicate + */ + public JSONObject putOnce(String key, Object value) throws JSONException { + if (key != null && value != null) { + if (this.opt(key) != null) { + throw new JSONException("Duplicate key \"" + key + "\""); + } + return this.put(key, value); + } + return this; + } + + /** + * Put a key/value pair in the JSONObject, but only if the key and the value + * are both non-null. + * + * @param key + * A key string. + * @param value + * An object which is the value. It should be of one of these + * types: Boolean, Double, Integer, JSONArray, JSONObject, Long, + * String, or the JSONObject.NULL object. + * @return this. + * @throws JSONException + * If the value is a non-finite number. + */ + public JSONObject putOpt(String key, Object value) throws JSONException { + if (key != null && value != null) { + return this.put(key, value); + } + return this; + } + + /** + * Creates a JSONPointer using an initialization string and tries to + * match it to an item within this JSONObject. For example, given a + * JSONObject initialized with this document: + *

+     * {
+     *     "a":{"b":"c"}
+     * }
+     * 
+ * and this JSONPointer string: + *
+     * "/a/b"
+     * 
+ * Then this method will return the String "c". + * A JSONPointerException may be thrown from code called by this method. + * + * @param jsonPointer string that can be used to create a JSONPointer + * @return the item matched by the JSONPointer, otherwise null + */ + public Object query(String jsonPointer) { + return query(new JSONPointer(jsonPointer)); + } + /** + * Uses a user initialized JSONPointer and tries to + * match it to an item within this JSONObject. For example, given a + * JSONObject initialized with this document: + *
+     * {
+     *     "a":{"b":"c"}
+     * }
+     * 
+ * and this JSONPointer: + *
+     * "/a/b"
+     * 
+ * Then this method will return the String "c". + * A JSONPointerException may be thrown from code called by this method. + * + * @param jsonPointer string that can be used to create a JSONPointer + * @return the item matched by the JSONPointer, otherwise null + */ + public Object query(JSONPointer jsonPointer) { + return jsonPointer.queryFrom(this); + } + + /** + * Queries and returns a value from this object using {@code jsonPointer}, or + * returns null if the query fails due to a missing key. + * + * @param jsonPointer the string representation of the JSON pointer + * @return the queried value or {@code null} + * @throws IllegalArgumentException if {@code jsonPointer} has invalid syntax + */ + public Object optQuery(String jsonPointer) { + return optQuery(new JSONPointer(jsonPointer)); + } + + /** + * Queries and returns a value from this object using {@code jsonPointer}, or + * returns null if the query fails due to a missing key. + * + * @param jsonPointer The JSON pointer + * @return the queried value or {@code null} + * @throws IllegalArgumentException if {@code jsonPointer} has invalid syntax + */ + public Object optQuery(JSONPointer jsonPointer) { + try { + return jsonPointer.queryFrom(this); + } catch (JSONPointerException e) { + return null; + } + } + + /** + * Produce a string in double quotes with backslash sequences in all the + * right places. A backslash will be inserted within </, producing + * <\/, allowing JSON text to be delivered in HTML. In JSON text, a + * string cannot contain a control character or an unescaped quote or + * backslash. + * + * @param string + * A String + * @return A String correctly formatted for insertion in a JSON text. + */ + public static String quote(String string) { + StringWriter sw = new StringWriter(); + synchronized (sw.getBuffer()) { + try { + return quote(string, sw).toString(); + } catch (IOException ignored) { + // will never happen - we are writing to a string writer + return ""; + } + } + } + + public static Writer quote(String string, Writer w) throws IOException { + if (string == null || string.isEmpty()) { + w.write("\"\""); + return w; + } + + char b; + char c = 0; + String hhhh; + int i; + int len = string.length(); + + w.write('"'); + for (i = 0; i < len; i += 1) { + b = c; + c = string.charAt(i); + switch (c) { + case '\\': + case '"': + w.write('\\'); + w.write(c); + break; + case '/': + if (b == '<') { + w.write('\\'); + } + w.write(c); + break; + case '\b': + w.write("\\b"); + break; + case '\t': + w.write("\\t"); + break; + case '\n': + w.write("\\n"); + break; + case '\f': + w.write("\\f"); + break; + case '\r': + w.write("\\r"); + break; + default: + if (c < ' ' || (c >= '\u0080' && c < '\u00a0') + || (c >= '\u2000' && c < '\u2100')) { + w.write("\\u"); + hhhh = Integer.toHexString(c); + w.write("0000", 0, 4 - hhhh.length()); + w.write(hhhh); + } else { + w.write(c); + } + } + } + w.write('"'); + return w; + } + + /** + * Remove a name and its value, if present. + * + * @param key + * The name to be removed. + * @return The value that was associated with the name, or null if there was + * no value. + */ + public Object remove(String key) { + return this.map.remove(key); + } + + /** + * Determine if two JSONObjects are similar. + * They must contain the same set of names which must be associated with + * similar values. + * + * @param other The other JSONObject + * @return true if they are equal + */ + public boolean similar(Object other) { + try { + if (!(other instanceof JSONObject)) { + return false; + } + if (!this.keySet().equals(((JSONObject)other).keySet())) { + return false; + } + for (final Entry entry : this.entrySet()) { + String name = entry.getKey(); + Object valueThis = entry.getValue(); + Object valueOther = ((JSONObject)other).get(name); + if(valueThis == valueOther) { + continue; + } + if(valueThis == null) { + return false; + } + if (valueThis instanceof JSONObject) { + if (!((JSONObject)valueThis).similar(valueOther)) { + return false; + } + } else if (valueThis instanceof JSONArray) { + if (!((JSONArray)valueThis).similar(valueOther)) { + return false; + } + } else if (!valueThis.equals(valueOther)) { + return false; + } + } + return true; + } catch (Throwable exception) { + return false; + } + } + + /** + * Tests if the value should be tried as a decimal. It makes no test if there are actual digits. + * + * @param val value to test + * @return true if the string is "-0" or if it contains '.', 'e', or 'E', false otherwise. + */ + protected static boolean isDecimalNotation(final String val) { + return val.indexOf('.') > -1 || val.indexOf('e') > -1 + || val.indexOf('E') > -1 || "-0".equals(val); + } + + /** + * Converts a string to a number using the narrowest possible type. Possible + * returns for this function are BigDecimal, Double, BigInteger, Long, and Integer. + * When a Double is returned, it should always be a valid Double and not NaN or +-infinity. + * + * @param val value to convert + * @return Number representation of the value. + * @throws NumberFormatException thrown if the value is not a valid number. A public + * caller should catch this and wrap it in a {@link JSONException} if applicable. + */ + protected static Number stringToNumber(final String val) throws NumberFormatException { + char initial = val.charAt(0); + if ((initial >= '0' && initial <= '9') || initial == '-') { + // decimal representation + if (isDecimalNotation(val)) { + // quick dirty way to see if we need a BigDecimal instead of a Double + // this only handles some cases of overflow or underflow + if (val.length()>14) { + return new BigDecimal(val); + } + final Double d = Double.valueOf(val); + if (d.isInfinite() || d.isNaN()) { + // if we can't parse it as a double, go up to BigDecimal + // this is probably due to underflow like 4.32e-678 + // or overflow like 4.65e5324. The size of the string is small + // but can't be held in a Double. + return new BigDecimal(val); + } + return d; + } + // integer representation. + // This will narrow any values to the smallest reasonable Object representation + // (Integer, Long, or BigInteger) + + // string version + // The compare string length method reduces GC, + // but leads to smaller integers being placed in larger wrappers even though not + // needed. i.e. 1,000,000,000 -> Long even though it's an Integer + // 1,000,000,000,000,000,000 -> BigInteger even though it's a Long + //if(val.length()<=9){ + // return Integer.valueOf(val); + //} + //if(val.length()<=18){ + // return Long.valueOf(val); + //} + //return new BigInteger(val); + + // BigInteger version: We use a similar bitLenth compare as + // BigInteger#intValueExact uses. Increases GC, but objects hold + // only what they need. i.e. Less runtime overhead if the value is + // long lived. Which is the better tradeoff? This is closer to what's + // in stringToValue. + BigInteger bi = new BigInteger(val); + if(bi.bitLength()<=31){ + return Integer.valueOf(bi.intValue()); + } + if(bi.bitLength()<=63){ + return Long.valueOf(bi.longValue()); + } + return bi; + } + throw new NumberFormatException("val ["+val+"] is not a valid number."); + } + + /** + * Try to convert a string into a number, boolean, or null. If the string + * can't be converted, return the string. + * + * @param string + * A String. can not be null. + * @return A simple JSON value. + * @throws NullPointerException + * Thrown if the string is null. + */ + // Changes to this method must be copied to the corresponding method in + // the XML class to keep full support for Android + public static Object stringToValue(String string) { + if ("".equals(string)) { + return string; + } + + // check JSON key words true/false/null + if ("true".equalsIgnoreCase(string)) { + return Boolean.TRUE; + } + if ("false".equalsIgnoreCase(string)) { + return Boolean.FALSE; + } + if ("null".equalsIgnoreCase(string)) { + return JSONObject.NULL; + } + + /* + * If it might be a number, try converting it. If a number cannot be + * produced, then the value will just be a string. + */ + + char initial = string.charAt(0); + if ((initial >= '0' && initial <= '9') || initial == '-') { + try { + // if we want full Big Number support the contents of this + // `try` block can be replaced with: + // return stringToNumber(string); + if (isDecimalNotation(string)) { + Double d = Double.valueOf(string); + if (!d.isInfinite() && !d.isNaN()) { + return d; + } + } else { + Long myLong = Long.valueOf(string); + if (string.equals(myLong.toString())) { + if (myLong.longValue() == myLong.intValue()) { + return Integer.valueOf(myLong.intValue()); + } + return myLong; + } + } + } catch (Exception ignore) { + } + } + return string; + } + + /** + * Throw an exception if the object is a NaN or infinite number. + * + * @param o + * The object to test. + * @throws JSONException + * If o is a non-finite number. + */ + public static void testValidity(Object o) throws JSONException { + if (o != null) { + if (o instanceof Double) { + if (((Double) o).isInfinite() || ((Double) o).isNaN()) { + throw new JSONException( + "JSON does not allow non-finite numbers."); + } + } else if (o instanceof Float) { + if (((Float) o).isInfinite() || ((Float) o).isNaN()) { + throw new JSONException( + "JSON does not allow non-finite numbers."); + } + } + } + } + + /** + * Produce a JSONArray containing the values of the members of this + * JSONObject. + * + * @param names + * A JSONArray containing a list of key strings. This determines + * the sequence of the values in the result. + * @return A JSONArray of values. + * @throws JSONException + * If any of the values are non-finite numbers. + */ + public JSONArray toJSONArray(JSONArray names) throws JSONException { + if (names == null || names.isEmpty()) { + return null; + } + JSONArray ja = new JSONArray(); + for (int i = 0; i < names.length(); i += 1) { + ja.put(this.opt(names.getString(i))); + } + return ja; + } + + /** + * Make a JSON text of this JSONObject. For compactness, no whitespace is + * added. If this would not result in a syntactically correct JSON text, + * then null will be returned instead. + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * + * @return a printable, displayable, portable, transmittable representation + * of the object, beginning with { (left + * brace) and ending with } (right + * brace). + */ + @Override + public String toString() { + try { + return this.toString(0); + } catch (Exception e) { + return null; + } + } + + /** + * Make a pretty-printed JSON text of this JSONObject. + * + *

If indentFactor > 0 and the {@link JSONObject} + * has only one key, then the object will be output on a single line: + *

{@code {"key": 1}}
+ * + *

If an object has 2 or more keys, then it will be output across + * multiple lines:

{
+     *  "key1": 1,
+     *  "key2": "value 2",
+     *  "key3": 3
+     * }
+ *

+ * Warning: This method assumes that the data structure is acyclical. + * + * + * @param indentFactor + * The number of spaces to add to each level of indentation. + * @return a printable, displayable, portable, transmittable representation + * of the object, beginning with { (left + * brace) and ending with } (right + * brace). + * @throws JSONException + * If the object contains an invalid number. + */ + public String toString(int indentFactor) throws JSONException { + StringWriter w = new StringWriter(); + synchronized (w.getBuffer()) { + return this.write(w, indentFactor, 0).toString(); + } + } + + /** + * Make a JSON text of an Object value. If the object has an + * value.toJSONString() method, then that method will be used to produce the + * JSON text. The method is required to produce a strictly conforming text. + * If the object does not contain a toJSONString method (which is the most + * common case), then a text will be produced by other means. If the value + * is an array or Collection, then a JSONArray will be made from it and its + * toJSONString method will be called. If the value is a MAP, then a + * JSONObject will be made from it and its toJSONString method will be + * called. Otherwise, the value's toString method will be called, and the + * result will be quoted. + * + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * @param value + * The value to be serialized. + * @return a printable, displayable, transmittable representation of the + * object, beginning with { (left + * brace) and ending with } (right + * brace). + * @throws JSONException + * If the value is or contains an invalid number. + */ + public static String valueToString(Object value) throws JSONException { + // moves the implementation to JSONWriter as: + // 1. It makes more sense to be part of the writer class + // 2. For Android support this method is not available. By implementing it in the Writer + // Android users can use the writer with the built in Android JSONObject implementation. + return JSONWriter.valueToString(value); + } + + /** + * Wrap an object, if necessary. If the object is null, return the NULL + * object. If it is an array or collection, wrap it in a JSONArray. If it is + * a map, wrap it in a JSONObject. If it is a standard property (Double, + * String, et al) then it is already wrapped. Otherwise, if it comes from + * one of the java packages, turn it into a string. And if it doesn't, try + * to wrap it in a JSONObject. If the wrapping fails, then null is returned. + * + * @param object + * The object to wrap + * @return The wrapped value + */ + public static Object wrap(Object object) { + try { + if (object == null) { + return NULL; + } + if (object instanceof JSONObject || object instanceof JSONArray + || NULL.equals(object) || object instanceof JSONString + || object instanceof Byte || object instanceof Character + || object instanceof Short || object instanceof Integer + || object instanceof Long || object instanceof Boolean + || object instanceof Float || object instanceof Double + || object instanceof String || object instanceof BigInteger + || object instanceof BigDecimal || object instanceof Enum) { + return object; + } + + if (object instanceof Collection) { + Collection coll = (Collection) object; + return new JSONArray(coll); + } + if (object.getClass().isArray()) { + return new JSONArray(object); + } + if (object instanceof Map) { + Map map = (Map) object; + return new JSONObject(map); + } + Package objectPackage = object.getClass().getPackage(); + String objectPackageName = objectPackage != null ? objectPackage + .getName() : ""; + if (objectPackageName.startsWith("java.") + || objectPackageName.startsWith("javax.") + || object.getClass().getClassLoader() == null) { + return object.toString(); + } + return new JSONObject(object); + } catch (Exception exception) { + return null; + } + } + + /** + * Write the contents of the JSONObject as JSON text to a writer. For + * compactness, no whitespace is added. + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * + * @return The writer. + * @throws JSONException + */ + public Writer write(Writer writer) throws JSONException { + return this.write(writer, 0, 0); + } + + static final Writer writeValue(Writer writer, Object value, + int indentFactor, int indent) throws JSONException, IOException { + if (value == null || value.equals(null)) { + writer.write("null"); + } else if (value instanceof JSONString) { + Object o; + try { + o = ((JSONString) value).toJSONString(); + } catch (Exception e) { + throw new JSONException(e); + } + writer.write(o != null ? o.toString() : quote(value.toString())); + } else if (value instanceof Number) { + // not all Numbers may match actual JSON Numbers. i.e. fractions or Imaginary + final String numberAsString = numberToString((Number) value); + if(NUMBER_PATTERN.matcher(numberAsString).matches()) { + writer.write(numberAsString); + } else { + // The Number value is not a valid JSON number. + // Instead we will quote it as a string + quote(numberAsString, writer); + } + } else if (value instanceof Boolean) { + writer.write(value.toString()); + } else if (value instanceof Enum) { + writer.write(quote(((Enum)value).name())); + } else if (value instanceof JSONObject) { + ((JSONObject) value).write(writer, indentFactor, indent); + } else if (value instanceof JSONArray) { + ((JSONArray) value).write(writer, indentFactor, indent); + } else if (value instanceof Map) { + Map map = (Map) value; + new JSONObject(map).write(writer, indentFactor, indent); + } else if (value instanceof Collection) { + Collection coll = (Collection) value; + new JSONArray(coll).write(writer, indentFactor, indent); + } else if (value.getClass().isArray()) { + new JSONArray(value).write(writer, indentFactor, indent); + } else { + quote(value.toString(), writer); + } + return writer; + } + + static final void indent(Writer writer, int indent) throws IOException { + for (int i = 0; i < indent; i += 1) { + writer.write(' '); + } + } + + /** + * Write the contents of the JSONObject as JSON text to a writer. + * + *

If indentFactor > 0 and the {@link JSONObject} + * has only one key, then the object will be output on a single line: + *

{@code {"key": 1}}
+ * + *

If an object has 2 or more keys, then it will be output across + * multiple lines:

{
+     *  "key1": 1,
+     *  "key2": "value 2",
+     *  "key3": 3
+     * }
+ *

+ * Warning: This method assumes that the data structure is acyclical. + * + * + * @param writer + * Writes the serialized JSON + * @param indentFactor + * The number of spaces to add to each level of indentation. + * @param indent + * The indentation of the top level. + * @return The writer. + * @throws JSONException + */ + public Writer write(Writer writer, int indentFactor, int indent) + throws JSONException { + try { + boolean commanate = false; + final int length = this.length(); + writer.write('{'); + + if (length == 1) { + final Entry entry = this.entrySet().iterator().next(); + final String key = entry.getKey(); + writer.write(quote(key)); + writer.write(':'); + if (indentFactor > 0) { + writer.write(' '); + } + try{ + writeValue(writer, entry.getValue(), indentFactor, indent); + } catch (Exception e) { + throw new JSONException("Unable to write JSONObject value for key: " + key, e); + } + } else if (length != 0) { + final int newindent = indent + indentFactor; + for (final Entry entry : this.entrySet()) { + if (commanate) { + writer.write(','); + } + if (indentFactor > 0) { + writer.write('\n'); + } + indent(writer, newindent); + final String key = entry.getKey(); + writer.write(quote(key)); + writer.write(':'); + if (indentFactor > 0) { + writer.write(' '); + } + try { + writeValue(writer, entry.getValue(), indentFactor, newindent); + } catch (Exception e) { + throw new JSONException("Unable to write JSONObject value for key: " + key, e); + } + commanate = true; + } + if (indentFactor > 0) { + writer.write('\n'); + } + indent(writer, indent); + } + writer.write('}'); + return writer; + } catch (IOException exception) { + throw new JSONException(exception); + } + } + + /** + * Returns a java.util.Map containing all of the entries in this object. + * If an entry in the object is a JSONArray or JSONObject it will also + * be converted. + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * @return a java.util.Map containing the entries of this object + */ + public Map toMap() { + Map results = new HashMap(); + for (Entry entry : this.entrySet()) { + Object value; + if (entry.getValue() == null || NULL.equals(entry.getValue())) { + value = null; + } else if (entry.getValue() instanceof JSONObject) { + value = ((JSONObject) entry.getValue()).toMap(); + } else if (entry.getValue() instanceof JSONArray) { + value = ((JSONArray) entry.getValue()).toList(); + } else { + value = entry.getValue(); + } + results.put(entry.getKey(), value); + } + return results; + } +} diff --git a/core/src/com/hiveworkshop/json/JSONPointer.java b/core/src/com/hiveworkshop/json/JSONPointer.java new file mode 100644 index 0000000..fb13323 --- /dev/null +++ b/core/src/com/hiveworkshop/json/JSONPointer.java @@ -0,0 +1,293 @@ +package com.hiveworkshop.json; + +import static java.lang.String.format; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/* +Copyright (c) 2002 JSON.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +The Software shall be used for Good, not Evil. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +/** + * A JSON Pointer is a simple query language defined for JSON documents by + * RFC 6901. + * + * In a nutshell, JSONPointer allows the user to navigate into a JSON document + * using strings, and retrieve targeted objects, like a simple form of XPATH. + * Path segments are separated by the '/' char, which signifies the root of + * the document when it appears as the first char of the string. Array + * elements are navigated using ordinals, counting from 0. JSONPointer strings + * may be extended to any arbitrary number of segments. If the navigation + * is successful, the matched item is returned. A matched item may be a + * JSONObject, a JSONArray, or a JSON value. If the JSONPointer string building + * fails, an appropriate exception is thrown. If the navigation fails to find + * a match, a JSONPointerException is thrown. + * + * @author JSON.org + * @version 2016-05-14 + */ +public class JSONPointer { + + // used for URL encoding and decoding + private static final String ENCODING = "utf-8"; + + /** + * This class allows the user to build a JSONPointer in steps, using + * exactly one segment in each step. + */ + public static class Builder { + + // Segments for the eventual JSONPointer string + private final List refTokens = new ArrayList(); + + /** + * Creates a {@code JSONPointer} instance using the tokens previously set using the + * {@link #append(String)} method calls. + */ + public JSONPointer build() { + return new JSONPointer(this.refTokens); + } + + /** + * Adds an arbitrary token to the list of reference tokens. It can be any non-null value. + * + * Unlike in the case of JSON string or URI fragment representation of JSON pointers, the + * argument of this method MUST NOT be escaped. If you want to query the property called + * {@code "a~b"} then you should simply pass the {@code "a~b"} string as-is, there is no + * need to escape it as {@code "a~0b"}. + * + * @param token the new token to be appended to the list + * @return {@code this} + * @throws NullPointerException if {@code token} is null + */ + public Builder append(String token) { + if (token == null) { + throw new NullPointerException("token cannot be null"); + } + this.refTokens.add(token); + return this; + } + + /** + * Adds an integer to the reference token list. Although not necessarily, mostly this token will + * denote an array index. + * + * @param arrayIndex the array index to be added to the token list + * @return {@code this} + */ + public Builder append(int arrayIndex) { + this.refTokens.add(String.valueOf(arrayIndex)); + return this; + } + } + + /** + * Static factory method for {@link Builder}. Example usage: + * + *


+     * JSONPointer pointer = JSONPointer.builder()
+     *       .append("obj")
+     *       .append("other~key").append("another/key")
+     *       .append("\"")
+     *       .append(0)
+     *       .build();
+     * 
+ * + * @return a builder instance which can be used to construct a {@code JSONPointer} instance by chained + * {@link Builder#append(String)} calls. + */ + public static Builder builder() { + return new Builder(); + } + + // Segments for the JSONPointer string + private final List refTokens; + + /** + * Pre-parses and initializes a new {@code JSONPointer} instance. If you want to + * evaluate the same JSON Pointer on different JSON documents then it is recommended + * to keep the {@code JSONPointer} instances due to performance considerations. + * + * @param pointer the JSON String or URI Fragment representation of the JSON pointer. + * @throws IllegalArgumentException if {@code pointer} is not a valid JSON pointer + */ + public JSONPointer(final String pointer) { + if (pointer == null) { + throw new NullPointerException("pointer cannot be null"); + } + if (pointer.isEmpty() || pointer.equals("#")) { + this.refTokens = Collections.emptyList(); + return; + } + String refs; + if (pointer.startsWith("#/")) { + refs = pointer.substring(2); + try { + refs = URLDecoder.decode(refs, ENCODING); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } else if (pointer.startsWith("/")) { + refs = pointer.substring(1); + } else { + throw new IllegalArgumentException("a JSON pointer should start with '/' or '#/'"); + } + this.refTokens = new ArrayList(); + int slashIdx = -1; + int prevSlashIdx = 0; + do { + prevSlashIdx = slashIdx + 1; + slashIdx = refs.indexOf('/', prevSlashIdx); + if(prevSlashIdx == slashIdx || prevSlashIdx == refs.length()) { + // found 2 slashes in a row ( obj//next ) + // or single slash at the end of a string ( obj/test/ ) + this.refTokens.add(""); + } else if (slashIdx >= 0) { + final String token = refs.substring(prevSlashIdx, slashIdx); + this.refTokens.add(unescape(token)); + } else { + // last item after separator, or no separator at all. + final String token = refs.substring(prevSlashIdx); + this.refTokens.add(unescape(token)); + } + } while (slashIdx >= 0); + // using split does not take into account consecutive separators or "ending nulls" + //for (String token : refs.split("/")) { + // this.refTokens.add(unescape(token)); + //} + } + + public JSONPointer(List refTokens) { + this.refTokens = new ArrayList(refTokens); + } + + private String unescape(String token) { + return token.replace("~1", "/").replace("~0", "~") + .replace("\\\"", "\"") + .replace("\\\\", "\\"); + } + + /** + * Evaluates this JSON Pointer on the given {@code document}. The {@code document} + * is usually a {@link JSONObject} or a {@link JSONArray} instance, but the empty + * JSON Pointer ({@code ""}) can be evaluated on any JSON values and in such case the + * returned value will be {@code document} itself. + * + * @param document the JSON document which should be the subject of querying. + * @return the result of the evaluation + * @throws JSONPointerException if an error occurs during evaluation + */ + public Object queryFrom(Object document) throws JSONPointerException { + if (this.refTokens.isEmpty()) { + return document; + } + Object current = document; + for (String token : this.refTokens) { + if (current instanceof JSONObject) { + current = ((JSONObject) current).opt(unescape(token)); + } else if (current instanceof JSONArray) { + current = readByIndexToken(current, token); + } else { + throw new JSONPointerException(format( + "value [%s] is not an array or object therefore its key %s cannot be resolved", current, + token)); + } + } + return current; + } + + /** + * Matches a JSONArray element by ordinal position + * @param current the JSONArray to be evaluated + * @param indexToken the array index in string form + * @return the matched object. If no matching item is found a + * @throws JSONPointerException is thrown if the index is out of bounds + */ + private Object readByIndexToken(Object current, String indexToken) throws JSONPointerException { + try { + int index = Integer.parseInt(indexToken); + JSONArray currentArr = (JSONArray) current; + if (index >= currentArr.length()) { + throw new JSONPointerException(format("index %s is out of bounds - the array has %d elements", indexToken, + Integer.valueOf(currentArr.length()))); + } + try { + return currentArr.get(index); + } catch (JSONException e) { + throw new JSONPointerException("Error reading value at index position " + index, e); + } + } catch (NumberFormatException e) { + throw new JSONPointerException(format("%s is not an array index", indexToken), e); + } + } + + /** + * Returns a string representing the JSONPointer path value using string + * representation + */ + @Override + public String toString() { + StringBuilder rval = new StringBuilder(""); + for (String token: this.refTokens) { + rval.append('/').append(escape(token)); + } + return rval.toString(); + } + + /** + * Escapes path segment values to an unambiguous form. + * The escape char to be inserted is '~'. The chars to be escaped + * are ~, which maps to ~0, and /, which maps to ~1. Backslashes + * and double quote chars are also escaped. + * @param token the JSONPointer segment value to be escaped + * @return the escaped value for the token + */ + private String escape(String token) { + return token.replace("~", "~0") + .replace("/", "~1") + .replace("\\", "\\\\") + .replace("\"", "\\\""); + } + + /** + * Returns a string representing the JSONPointer path value using URI + * fragment identifier representation + */ + public String toURIFragment() { + try { + StringBuilder rval = new StringBuilder("#"); + for (String token : this.refTokens) { + rval.append('/').append(URLEncoder.encode(token, ENCODING)); + } + return rval.toString(); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/core/src/com/hiveworkshop/json/JSONPointerException.java b/core/src/com/hiveworkshop/json/JSONPointerException.java new file mode 100644 index 0000000..10d746f --- /dev/null +++ b/core/src/com/hiveworkshop/json/JSONPointerException.java @@ -0,0 +1,45 @@ +package com.hiveworkshop.json; + +/* +Copyright (c) 2002 JSON.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +The Software shall be used for Good, not Evil. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +/** + * The JSONPointerException is thrown by {@link JSONPointer} if an error occurs + * during evaluating a pointer. + * + * @author JSON.org + * @version 2016-05-13 + */ +public class JSONPointerException extends JSONException { + private static final long serialVersionUID = 8872944667561856751L; + + public JSONPointerException(String message) { + super(message); + } + + public JSONPointerException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/core/src/com/hiveworkshop/json/JSONPropertyIgnore.java b/core/src/com/hiveworkshop/json/JSONPropertyIgnore.java new file mode 100644 index 0000000..cf0b428 --- /dev/null +++ b/core/src/com/hiveworkshop/json/JSONPropertyIgnore.java @@ -0,0 +1,43 @@ +package com.hiveworkshop.json; + +/* +Copyright (c) 2018 JSON.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +The Software shall be used for Good, not Evil. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@Documented +@Retention(RUNTIME) +@Target({METHOD}) +/** + * Use this annotation on a getter method to override the Bean name + * parser for Bean -> JSONObject mapping. If this annotation is + * present at any level in the class hierarchy, then the method will + * not be serialized from the bean into the JSONObject. + */ +public @interface JSONPropertyIgnore { } diff --git a/core/src/com/hiveworkshop/json/JSONPropertyName.java b/core/src/com/hiveworkshop/json/JSONPropertyName.java new file mode 100644 index 0000000..22d10ad --- /dev/null +++ b/core/src/com/hiveworkshop/json/JSONPropertyName.java @@ -0,0 +1,47 @@ +package com.hiveworkshop.json; + +/* +Copyright (c) 2018 JSON.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +The Software shall be used for Good, not Evil. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@Documented +@Retention(RUNTIME) +@Target({METHOD}) +/** + * Use this annotation on a getter method to override the Bean name + * parser for Bean -> JSONObject mapping. A value set to empty string "" + * will have the Bean parser fall back to the default field name processing. + */ +public @interface JSONPropertyName { + /** + * @return The name of the property as to be used in the JSON Object. + */ + String value(); +} diff --git a/core/src/com/hiveworkshop/json/JSONString.java b/core/src/com/hiveworkshop/json/JSONString.java new file mode 100644 index 0000000..444bc8a --- /dev/null +++ b/core/src/com/hiveworkshop/json/JSONString.java @@ -0,0 +1,18 @@ +package com.hiveworkshop.json; +/** + * The JSONString interface allows a toJSONString() + * method so that a class can change the behavior of + * JSONObject.toString(), JSONArray.toString(), + * and JSONWriter.value(Object). The + * toJSONString method will be used instead of the default behavior + * of using the Object's toString() method and quoting the result. + */ +public interface JSONString { + /** + * The toJSONString method allows a class to produce its own JSON + * serialization. + * + * @return A strictly syntactically correct JSON text. + */ + public String toJSONString(); +} diff --git a/core/src/com/hiveworkshop/json/JSONStringer.java b/core/src/com/hiveworkshop/json/JSONStringer.java new file mode 100644 index 0000000..7330d40 --- /dev/null +++ b/core/src/com/hiveworkshop/json/JSONStringer.java @@ -0,0 +1,79 @@ +package com.hiveworkshop.json; + +/* +Copyright (c) 2006 JSON.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +The Software shall be used for Good, not Evil. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +import java.io.StringWriter; + +/** + * JSONStringer provides a quick and convenient way of producing JSON text. + * The texts produced strictly conform to JSON syntax rules. No whitespace is + * added, so the results are ready for transmission or storage. Each instance of + * JSONStringer can produce one JSON text. + *

+ * A JSONStringer instance provides a value method for appending + * values to the + * text, and a key + * method for adding keys before values in objects. There are array + * and endArray methods that make and bound array values, and + * object and endObject methods which make and bound + * object values. All of these methods return the JSONWriter instance, + * permitting cascade style. For example,

+ * myString = new JSONStringer()
+ *     .object()
+ *         .key("JSON")
+ *         .value("Hello, World!")
+ *     .endObject()
+ *     .toString();
which produces the string
+ * {"JSON":"Hello, World!"}
+ *

+ * The first method called must be array or object. + * There are no methods for adding commas or colons. JSONStringer adds them for + * you. Objects and arrays can be nested up to 20 levels deep. + *

+ * This can sometimes be easier than using a JSONObject to build a string. + * @author JSON.org + * @version 2015-12-09 + */ +public class JSONStringer extends JSONWriter { + /** + * Make a fresh JSONStringer. It can be used to build one JSON text. + */ + public JSONStringer() { + super(new StringWriter()); + } + + /** + * Return the JSON text. This method is used to obtain the product of the + * JSONStringer instance. It will return null if there was a + * problem in the construction of the JSON text (such as the calls to + * array were not properly balanced with calls to + * endArray). + * @return The JSON text. + */ + @Override + public String toString() { + return this.mode == 'd' ? this.writer.toString() : null; + } +} diff --git a/core/src/com/hiveworkshop/json/JSONTokener.java b/core/src/com/hiveworkshop/json/JSONTokener.java new file mode 100644 index 0000000..5d78054 --- /dev/null +++ b/core/src/com/hiveworkshop/json/JSONTokener.java @@ -0,0 +1,531 @@ +package com.hiveworkshop.json; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.StringReader; + +/* +Copyright (c) 2002 JSON.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +The Software shall be used for Good, not Evil. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + */ + +/** + * A JSONTokener takes a source string and extracts characters and tokens from + * it. It is used by the JSONObject and JSONArray constructors to parse + * JSON source strings. + * @author JSON.org + * @version 2014-05-03 + */ +public class JSONTokener { + /** current read character position on the current line. */ + private long character; + /** flag to indicate if the end of the input has been found. */ + private boolean eof; + /** current read index of the input. */ + private long index; + /** current line of the input. */ + private long line; + /** previous character read from the input. */ + private char previous; + /** Reader for the input. */ + private final Reader reader; + /** flag to indicate that a previous character was requested. */ + private boolean usePrevious; + /** the number of characters read in the previous line. */ + private long characterPreviousLine; + + + /** + * Construct a JSONTokener from a Reader. The caller must close the Reader. + * + * @param reader A reader. + */ + public JSONTokener(Reader reader) { + this.reader = reader.markSupported() + ? reader + : new BufferedReader(reader); + this.eof = false; + this.usePrevious = false; + this.previous = 0; + this.index = 0; + this.character = 1; + this.characterPreviousLine = 0; + this.line = 1; + } + + + /** + * Construct a JSONTokener from an InputStream. The caller must close the input stream. + * @param inputStream The source. + */ + public JSONTokener(InputStream inputStream) { + this(new InputStreamReader(inputStream)); + } + + + /** + * Construct a JSONTokener from a string. + * + * @param s A source string. + */ + public JSONTokener(String s) { + this(new StringReader(s)); + } + + + /** + * Back up one character. This provides a sort of lookahead capability, + * so that you can test for a digit or letter before attempting to parse + * the next number or identifier. + * @throws JSONException Thrown if trying to step back more than 1 step + * or if already at the start of the string + */ + public void back() throws JSONException { + if (this.usePrevious || this.index <= 0) { + throw new JSONException("Stepping back two steps is not supported"); + } + this.decrementIndexes(); + this.usePrevious = true; + this.eof = false; + } + + /** + * Decrements the indexes for the {@link #back()} method based on the previous character read. + */ + private void decrementIndexes() { + this.index--; + if(this.previous=='\r' || this.previous == '\n') { + this.line--; + this.character=this.characterPreviousLine ; + } else if(this.character > 0){ + this.character--; + } + } + + /** + * Get the hex value of a character (base16). + * @param c A character between '0' and '9' or between 'A' and 'F' or + * between 'a' and 'f'. + * @return An int between 0 and 15, or -1 if c was not a hex digit. + */ + public static int dehexchar(char c) { + if (c >= '0' && c <= '9') { + return c - '0'; + } + if (c >= 'A' && c <= 'F') { + return c - ('A' - 10); + } + if (c >= 'a' && c <= 'f') { + return c - ('a' - 10); + } + return -1; + } + + /** + * Checks if the end of the input has been reached. + * + * @return true if at the end of the file and we didn't step back + */ + public boolean end() { + return this.eof && !this.usePrevious; + } + + + /** + * Determine if the source string still contains characters that next() + * can consume. + * @return true if not yet at the end of the source. + * @throws JSONException thrown if there is an error stepping forward + * or backward while checking for more data. + */ + public boolean more() throws JSONException { + if(this.usePrevious) { + return true; + } + try { + this.reader.mark(1); + } catch (IOException e) { + throw new JSONException("Unable to preserve stream position", e); + } + try { + // -1 is EOF, but next() can not consume the null character '\0' + if(this.reader.read() <= 0) { + this.eof = true; + return false; + } + this.reader.reset(); + } catch (IOException e) { + throw new JSONException("Unable to read the next character from the stream", e); + } + return true; + } + + + /** + * Get the next character in the source string. + * + * @return The next character, or 0 if past the end of the source string. + * @throws JSONException Thrown if there is an error reading the source string. + */ + public char next() throws JSONException { + int c; + if (this.usePrevious) { + this.usePrevious = false; + c = this.previous; + } else { + try { + c = this.reader.read(); + } catch (IOException exception) { + throw new JSONException(exception); + } + } + if (c <= 0) { // End of stream + this.eof = true; + return 0; + } + this.incrementIndexes(c); + this.previous = (char) c; + return this.previous; + } + + /** + * Increments the internal indexes according to the previous character + * read and the character passed as the current character. + * @param c the current character read. + */ + private void incrementIndexes(int c) { + if(c > 0) { + this.index++; + if(c=='\r') { + this.line++; + this.characterPreviousLine = this.character; + this.character=0; + }else if (c=='\n') { + if(this.previous != '\r') { + this.line++; + this.characterPreviousLine = this.character; + } + this.character=0; + } else { + this.character++; + } + } + } + + /** + * Consume the next character, and check that it matches a specified + * character. + * @param c The character to match. + * @return The character. + * @throws JSONException if the character does not match. + */ + public char next(char c) throws JSONException { + char n = this.next(); + if (n != c) { + if(n > 0) { + throw this.syntaxError("Expected '" + c + "' and instead saw '" + + n + "'"); + } + throw this.syntaxError("Expected '" + c + "' and instead saw ''"); + } + return n; + } + + + /** + * Get the next n characters. + * + * @param n The number of characters to take. + * @return A string of n characters. + * @throws JSONException + * Substring bounds error if there are not + * n characters remaining in the source string. + */ + public String next(int n) throws JSONException { + if (n == 0) { + return ""; + } + + char[] chars = new char[n]; + int pos = 0; + + while (pos < n) { + chars[pos] = this.next(); + if (this.end()) { + throw this.syntaxError("Substring bounds error"); + } + pos += 1; + } + return new String(chars); + } + + + /** + * Get the next char in the string, skipping whitespace. + * @throws JSONException Thrown if there is an error reading the source string. + * @return A character, or 0 if there are no more characters. + */ + public char nextClean() throws JSONException { + for (;;) { + char c = this.next(); + if (c == 0 || c > ' ') { + return c; + } + } + } + + + /** + * Return the characters up to the next close quote character. + * Backslash processing is done. The formal JSON format does not + * allow strings in single quotes, but an implementation is allowed to + * accept them. + * @param quote The quoting character, either + * " (double quote) or + * ' (single quote). + * @return A String. + * @throws JSONException Unterminated string. + */ + public String nextString(char quote) throws JSONException { + char c; + StringBuilder sb = new StringBuilder(); + for (;;) { + c = this.next(); + switch (c) { + case 0: + case '\n': + case '\r': + throw this.syntaxError("Unterminated string"); + case '\\': + c = this.next(); + switch (c) { + case 'b': + sb.append('\b'); + break; + case 't': + sb.append('\t'); + break; + case 'n': + sb.append('\n'); + break; + case 'f': + sb.append('\f'); + break; + case 'r': + sb.append('\r'); + break; + case 'u': + try { + sb.append((char)Integer.parseInt(this.next(4), 16)); + } catch (NumberFormatException e) { + throw this.syntaxError("Illegal escape.", e); + } + break; + case '"': + case '\'': + case '\\': + case '/': + sb.append(c); + break; + default: + throw this.syntaxError("Illegal escape."); + } + break; + default: + if (c == quote) { + return sb.toString(); + } + sb.append(c); + } + } + } + + + /** + * Get the text up but not including the specified character or the + * end of line, whichever comes first. + * @param delimiter A delimiter character. + * @return A string. + * @throws JSONException Thrown if there is an error while searching + * for the delimiter + */ + public String nextTo(char delimiter) throws JSONException { + StringBuilder sb = new StringBuilder(); + for (;;) { + char c = this.next(); + if (c == delimiter || c == 0 || c == '\n' || c == '\r') { + if (c != 0) { + this.back(); + } + return sb.toString().trim(); + } + sb.append(c); + } + } + + + /** + * Get the text up but not including one of the specified delimiter + * characters or the end of line, whichever comes first. + * @param delimiters A set of delimiter characters. + * @return A string, trimmed. + * @throws JSONException Thrown if there is an error while searching + * for the delimiter + */ + public String nextTo(String delimiters) throws JSONException { + char c; + StringBuilder sb = new StringBuilder(); + for (;;) { + c = this.next(); + if (delimiters.indexOf(c) >= 0 || c == 0 || + c == '\n' || c == '\r') { + if (c != 0) { + this.back(); + } + return sb.toString().trim(); + } + sb.append(c); + } + } + + + /** + * Get the next value. The value can be a Boolean, Double, Integer, + * JSONArray, JSONObject, Long, or String, or the JSONObject.NULL object. + * @throws JSONException If syntax error. + * + * @return An object. + */ + public Object nextValue() throws JSONException { + char c = this.nextClean(); + String string; + + switch (c) { + case '"': + case '\'': + return this.nextString(c); + case '{': + this.back(); + return new JSONObject(this); + case '[': + this.back(); + return new JSONArray(this); + } + + /* + * Handle unquoted text. This could be the values true, false, or + * null, or it can be a number. An implementation (such as this one) + * is allowed to also accept non-standard forms. + * + * Accumulate characters until we reach the end of the text or a + * formatting character. + */ + + StringBuilder sb = new StringBuilder(); + while (c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0) { + sb.append(c); + c = this.next(); + } + if (!this.eof) { + this.back(); + } + + string = sb.toString().trim(); + if ("".equals(string)) { + throw this.syntaxError("Missing value"); + } + return JSONObject.stringToValue(string); + } + + + /** + * Skip characters until the next character is the requested character. + * If the requested character is not found, no characters are skipped. + * @param to A character to skip to. + * @return The requested character, or zero if the requested character + * is not found. + * @throws JSONException Thrown if there is an error while searching + * for the to character + */ + public char skipTo(char to) throws JSONException { + char c; + try { + long startIndex = this.index; + long startCharacter = this.character; + long startLine = this.line; + this.reader.mark(1000000); + do { + c = this.next(); + if (c == 0) { + // in some readers, reset() may throw an exception if + // the remaining portion of the input is greater than + // the mark size (1,000,000 above). + this.reader.reset(); + this.index = startIndex; + this.character = startCharacter; + this.line = startLine; + return 0; + } + } while (c != to); + this.reader.mark(1); + } catch (IOException exception) { + throw new JSONException(exception); + } + this.back(); + return c; + } + + /** + * Make a JSONException to signal a syntax error. + * + * @param message The error message. + * @return A JSONException object, suitable for throwing + */ + public JSONException syntaxError(String message) { + return new JSONException(message + this.toString()); + } + + /** + * Make a JSONException to signal a syntax error. + * + * @param message The error message. + * @param causedBy The throwable that caused the error. + * @return A JSONException object, suitable for throwing + */ + public JSONException syntaxError(String message, Throwable causedBy) { + return new JSONException(message + this.toString(), causedBy); + } + + /** + * Make a printable string of this JSONTokener. + * + * @return " at {index} [character {character} line {line}]" + */ + @Override + public String toString() { + return " at " + this.index + " [character " + this.character + " line " + + this.line + "]"; + } +} diff --git a/core/src/com/hiveworkshop/json/JSONWriter.java b/core/src/com/hiveworkshop/json/JSONWriter.java new file mode 100644 index 0000000..44bfdeb --- /dev/null +++ b/core/src/com/hiveworkshop/json/JSONWriter.java @@ -0,0 +1,413 @@ +package com.hiveworkshop.json; + +import java.io.IOException; +import java.util.Collection; +import java.util.Map; + +/* +Copyright (c) 2006 JSON.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +The Software shall be used for Good, not Evil. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +/** + * JSONWriter provides a quick and convenient way of producing JSON text. + * The texts produced strictly conform to JSON syntax rules. No whitespace is + * added, so the results are ready for transmission or storage. Each instance of + * JSONWriter can produce one JSON text. + *

+ * A JSONWriter instance provides a value method for appending + * values to the + * text, and a key + * method for adding keys before values in objects. There are array + * and endArray methods that make and bound array values, and + * object and endObject methods which make and bound + * object values. All of these methods return the JSONWriter instance, + * permitting a cascade style. For example,

+ * new JSONWriter(myWriter)
+ *     .object()
+ *         .key("JSON")
+ *         .value("Hello, World!")
+ *     .endObject();
which writes
+ * {"JSON":"Hello, World!"}
+ *

+ * The first method called must be array or object. + * There are no methods for adding commas or colons. JSONWriter adds them for + * you. Objects and arrays can be nested up to 200 levels deep. + *

+ * This can sometimes be easier than using a JSONObject to build a string. + * @author JSON.org + * @version 2016-08-08 + */ +public class JSONWriter { + private static final int maxdepth = 200; + + /** + * The comma flag determines if a comma should be output before the next + * value. + */ + private boolean comma; + + /** + * The current mode. Values: + * 'a' (array), + * 'd' (done), + * 'i' (initial), + * 'k' (key), + * 'o' (object). + */ + protected char mode; + + /** + * The object/array stack. + */ + private final JSONObject stack[]; + + /** + * The stack top index. A value of 0 indicates that the stack is empty. + */ + private int top; + + /** + * The writer that will receive the output. + */ + protected Appendable writer; + + /** + * Make a fresh JSONWriter. It can be used to build one JSON text. + */ + public JSONWriter(Appendable w) { + this.comma = false; + this.mode = 'i'; + this.stack = new JSONObject[maxdepth]; + this.top = 0; + this.writer = w; + } + + /** + * Append a value. + * @param string A string value. + * @return this + * @throws JSONException If the value is out of sequence. + */ + private JSONWriter append(String string) throws JSONException { + if (string == null) { + throw new JSONException("Null pointer"); + } + if (this.mode == 'o' || this.mode == 'a') { + try { + if (this.comma && this.mode == 'a') { + this.writer.append(','); + } + this.writer.append(string); + } catch (IOException e) { + // Android as of API 25 does not support this exception constructor + // however we won't worry about it. If an exception is happening here + // it will just throw a "Method not found" exception instead. + throw new JSONException(e); + } + if (this.mode == 'o') { + this.mode = 'k'; + } + this.comma = true; + return this; + } + throw new JSONException("Value out of sequence."); + } + + /** + * Begin appending a new array. All values until the balancing + * endArray will be appended to this array. The + * endArray method must be called to mark the array's end. + * @return this + * @throws JSONException If the nesting is too deep, or if the object is + * started in the wrong place (for example as a key or after the end of the + * outermost array or object). + */ + public JSONWriter array() throws JSONException { + if (this.mode == 'i' || this.mode == 'o' || this.mode == 'a') { + this.push(null); + this.append("["); + this.comma = false; + return this; + } + throw new JSONException("Misplaced array."); + } + + /** + * End something. + * @param m Mode + * @param c Closing character + * @return this + * @throws JSONException If unbalanced. + */ + private JSONWriter end(char m, char c) throws JSONException { + if (this.mode != m) { + throw new JSONException(m == 'a' + ? "Misplaced endArray." + : "Misplaced endObject."); + } + this.pop(m); + try { + this.writer.append(c); + } catch (IOException e) { + // Android as of API 25 does not support this exception constructor + // however we won't worry about it. If an exception is happening here + // it will just throw a "Method not found" exception instead. + throw new JSONException(e); + } + this.comma = true; + return this; + } + + /** + * End an array. This method most be called to balance calls to + * array. + * @return this + * @throws JSONException If incorrectly nested. + */ + public JSONWriter endArray() throws JSONException { + return this.end('a', ']'); + } + + /** + * End an object. This method most be called to balance calls to + * object. + * @return this + * @throws JSONException If incorrectly nested. + */ + public JSONWriter endObject() throws JSONException { + return this.end('k', '}'); + } + + /** + * Append a key. The key will be associated with the next value. In an + * object, every value must be preceded by a key. + * @param string A key string. + * @return this + * @throws JSONException If the key is out of place. For example, keys + * do not belong in arrays or if the key is null. + */ + public JSONWriter key(String string) throws JSONException { + if (string == null) { + throw new JSONException("Null key."); + } + if (this.mode == 'k') { + try { + JSONObject topObject = this.stack[this.top - 1]; + // don't use the built in putOnce method to maintain Android support + if(topObject.has(string)) { + throw new JSONException("Duplicate key \"" + string + "\""); + } + topObject.put(string, true); + if (this.comma) { + this.writer.append(','); + } + this.writer.append(JSONObject.quote(string)); + this.writer.append(':'); + this.comma = false; + this.mode = 'o'; + return this; + } catch (IOException e) { + // Android as of API 25 does not support this exception constructor + // however we won't worry about it. If an exception is happening here + // it will just throw a "Method not found" exception instead. + throw new JSONException(e); + } + } + throw new JSONException("Misplaced key."); + } + + + /** + * Begin appending a new object. All keys and values until the balancing + * endObject will be appended to this object. The + * endObject method must be called to mark the object's end. + * @return this + * @throws JSONException If the nesting is too deep, or if the object is + * started in the wrong place (for example as a key or after the end of the + * outermost array or object). + */ + public JSONWriter object() throws JSONException { + if (this.mode == 'i') { + this.mode = 'o'; + } + if (this.mode == 'o' || this.mode == 'a') { + this.append("{"); + this.push(new JSONObject()); + this.comma = false; + return this; + } + throw new JSONException("Misplaced object."); + + } + + + /** + * Pop an array or object scope. + * @param c The scope to close. + * @throws JSONException If nesting is wrong. + */ + private void pop(char c) throws JSONException { + if (this.top <= 0) { + throw new JSONException("Nesting error."); + } + char m = this.stack[this.top - 1] == null ? 'a' : 'k'; + if (m != c) { + throw new JSONException("Nesting error."); + } + this.top -= 1; + this.mode = this.top == 0 + ? 'd' + : this.stack[this.top - 1] == null + ? 'a' + : 'k'; + } + + /** + * Push an array or object scope. + * @param jo The scope to open. + * @throws JSONException If nesting is too deep. + */ + private void push(JSONObject jo) throws JSONException { + if (this.top >= maxdepth) { + throw new JSONException("Nesting too deep."); + } + this.stack[this.top] = jo; + this.mode = jo == null ? 'a' : 'k'; + this.top += 1; + } + + /** + * Make a JSON text of an Object value. If the object has an + * value.toJSONString() method, then that method will be used to produce the + * JSON text. The method is required to produce a strictly conforming text. + * If the object does not contain a toJSONString method (which is the most + * common case), then a text will be produced by other means. If the value + * is an array or Collection, then a JSONArray will be made from it and its + * toJSONString method will be called. If the value is a MAP, then a + * JSONObject will be made from it and its toJSONString method will be + * called. Otherwise, the value's toString method will be called, and the + * result will be quoted. + * + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * @param value + * The value to be serialized. + * @return a printable, displayable, transmittable representation of the + * object, beginning with { (left + * brace) and ending with } (right + * brace). + * @throws JSONException + * If the value is or contains an invalid number. + */ + public static String valueToString(Object value) throws JSONException { + if (value == null || value.equals(null)) { + return "null"; + } + if (value instanceof JSONString) { + String object; + try { + object = ((JSONString) value).toJSONString(); + } catch (Exception e) { + throw new JSONException(e); + } + if (object != null) { + return object; + } + throw new JSONException("Bad value from toJSONString: " + object); + } + if (value instanceof Number) { + // not all Numbers may match actual JSON Numbers. i.e. Fractions or Complex + final String numberAsString = JSONObject.numberToString((Number) value); + if(JSONObject.NUMBER_PATTERN.matcher(numberAsString).matches()) { + // Close enough to a JSON number that we will return it unquoted + return numberAsString; + } + // The Number value is not a valid JSON number. + // Instead we will quote it as a string + return JSONObject.quote(numberAsString); + } + if (value instanceof Boolean || value instanceof JSONObject + || value instanceof JSONArray) { + return value.toString(); + } + if (value instanceof Map) { + Map map = (Map) value; + return new JSONObject(map).toString(); + } + if (value instanceof Collection) { + Collection coll = (Collection) value; + return new JSONArray(coll).toString(); + } + if (value.getClass().isArray()) { + return new JSONArray(value).toString(); + } + if(value instanceof Enum){ + return JSONObject.quote(((Enum)value).name()); + } + return JSONObject.quote(value.toString()); + } + + /** + * Append either the value true or the value + * false. + * @param b A boolean. + * @return this + * @throws JSONException + */ + public JSONWriter value(boolean b) throws JSONException { + return this.append(b ? "true" : "false"); + } + + /** + * Append a double value. + * @param d A double. + * @return this + * @throws JSONException If the number is not finite. + */ + public JSONWriter value(double d) throws JSONException { + return this.value(Double.valueOf(d)); + } + + /** + * Append a long value. + * @param l A long. + * @return this + * @throws JSONException + */ + public JSONWriter value(long l) throws JSONException { + return this.append(Long.toString(l)); + } + + + /** + * Append an object value. + * @param object The object to append. It can be null, or a Boolean, Number, + * String, JSONObject, or JSONArray, or an object that implements JSONString. + * @return this + * @throws JSONException If the value is out of sequence. + */ + public JSONWriter value(Object object) throws JSONException { + return this.append(valueToString(object)); + } +} diff --git a/core/src/com/hiveworkshop/lang/Hex.java b/core/src/com/hiveworkshop/lang/Hex.java new file mode 100644 index 0000000..8dfd024 --- /dev/null +++ b/core/src/com/hiveworkshop/lang/Hex.java @@ -0,0 +1,82 @@ +package com.hiveworkshop.lang; + +import java.util.Arrays; + +public abstract class Hex { + private static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', + 'E', 'F' }; + + public static final int RADIX_SIZE = 4; + + public static final int RADIX = HEX_DIGITS.length; + + private static final byte NO_VALUE = -1; + + private static final int DECIMAL_CHARACTERS = 10; + + private static final byte[] CHAR_VALUES = new byte[1 << Byte.SIZE]; + static { + Arrays.fill(CHAR_VALUES, NO_VALUE); + int value = 0; + for (; value < DECIMAL_CHARACTERS; value += 1) { + CHAR_VALUES['0' + value] = (byte) value; + } + for (; value < RADIX; value += 1) { + CHAR_VALUES[('a' - DECIMAL_CHARACTERS) + value] = (byte) value; + CHAR_VALUES[('A' - DECIMAL_CHARACTERS) + value] = (byte) value; + } + } + + /** + * Hexadecimal prefix string. + */ + public static final String HEX_PREFIX = "0x"; + + private static int NIBBLE_MASK = 0b1111; + + public static byte decodeNibble(final int codePoint) { + if (codePoint > CHAR_VALUES.length) { + return NO_VALUE; + } else { + return CHAR_VALUES[codePoint]; + } + } + + public static byte[] decodeHex(final CharSequence hex) { + final int nibbleCount = hex.length(); + int valueNibbleShift = ((nibbleCount - 1) % (Byte.SIZE / RADIX_SIZE)) * RADIX_SIZE; + final byte[] values = new byte[(nibbleCount + 1) >> 1]; + int valueIndex = 0; + int value = 0; + + for (int nibbleIndex = 0; nibbleIndex < nibbleCount; nibbleIndex += 1) { + final byte nibble = decodeNibble(hex.charAt(nibbleIndex)); + if (nibble == NO_VALUE) { + throw new NumberFormatException("non-hex character"); + } + + value |= nibble << valueNibbleShift; + + if (valueNibbleShift == 0) { + valueNibbleShift = Byte.SIZE; + values[valueIndex++] = (byte) value; + value = 0; + } + + valueNibbleShift -= RADIX_SIZE; + } + + return values; + } + + public static void stringBufferAppendHex(final StringBuilder builder, final byte hex) { + builder.append(HEX_DIGITS[(hex >> 4) & NIBBLE_MASK]); + builder.append(HEX_DIGITS[hex & NIBBLE_MASK]); + } + + public static void stringBufferAppendHex(final StringBuilder builder, final byte[] hex) { + for (int i = 0; i < hex.length; i += 1) { + stringBufferAppendHex(builder, hex[i]); + } + } +} diff --git a/core/src/com/hiveworkshop/nio/ByteBufferInputStream.java b/core/src/com/hiveworkshop/nio/ByteBufferInputStream.java new file mode 100644 index 0000000..f7eff2a --- /dev/null +++ b/core/src/com/hiveworkshop/nio/ByteBufferInputStream.java @@ -0,0 +1,39 @@ +package com.hiveworkshop.nio; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; + +/** + * Simple InputStream wrapper for ByteBuffer. + *

+ * This class is not thread safe. + *

+ * https://stackoverflow.com/questions/4332264/wrapping-a-bytebuffer-with-an-inputstream + */ +public class ByteBufferInputStream extends InputStream { + + ByteBuffer buf; + + public ByteBufferInputStream(ByteBuffer buf) { + this.buf = buf; + } + + public int read() throws IOException { + if (!buf.hasRemaining()) { + return -1; + } + return buf.get() & 0xFF; + } + + public int read(byte[] bytes, int off, int len) + throws IOException { + if (!buf.hasRemaining()) { + return -1; + } + + len = Math.min(len, buf.remaining()); + buf.get(bytes, off, len); + return len; + } +} diff --git a/core/src/com/hiveworkshop/rms/parsers/mdlx/AnimationMap.java b/core/src/com/hiveworkshop/rms/parsers/mdlx/AnimationMap.java new file mode 100644 index 0000000..0fa82d4 --- /dev/null +++ b/core/src/com/hiveworkshop/rms/parsers/mdlx/AnimationMap.java @@ -0,0 +1,266 @@ +package com.hiveworkshop.rms.parsers.mdlx; + +import java.util.HashMap; +import java.util.Map; + +import com.etheller.warsmash.util.War3ID; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlUtils; + +/** + * A map from MDX animation tags to their equivalent MDL tokens, and the + * implementation objects. + * + *

+ * Based on the works of Chananya Freiman. + */ +public enum AnimationMap { + // Layer + /** + * Layer texture ID + */ + KMTF(MdlUtils.TOKEN_TEXTURE_ID, MdlxTimelineDescriptor.UINT32_TIMELINE), + /** + * Layer alpha + */ + KMTA(MdlUtils.TOKEN_ALPHA, MdlxTimelineDescriptor.FLOAT_TIMELINE), + /** + * Layer emissive gain + */ + KMTE("EmissiveGain", MdlxTimelineDescriptor.FLOAT_TIMELINE), + /** + * Layer fresnel color + */ + KFC3("FresnelColor", MdlxTimelineDescriptor.VECTOR3_TIMELINE), + /** + * Layer fresnel opacity + */ + KFCA("FresnelOpacity", MdlxTimelineDescriptor.FLOAT_TIMELINE), + /** + * Layer fresnel team color + */ + KFTC("FresnelTeamColor", MdlxTimelineDescriptor.UINT32_TIMELINE), + // TextureAnimation + /** + * Texture animation translation + */ + KTAT(MdlUtils.TOKEN_TRANSLATION, MdlxTimelineDescriptor.VECTOR3_TIMELINE), + /** + * Texture animation rotation + */ + KTAR(MdlUtils.TOKEN_ROTATION, MdlxTimelineDescriptor.VECTOR4_TIMELINE), + /** + * Texture animation scaling + */ + KTAS(MdlUtils.TOKEN_SCALING, MdlxTimelineDescriptor.VECTOR3_TIMELINE), + // GeosetAnimation + /** + * Geoset animation alpha + */ + KGAO(MdlUtils.TOKEN_ALPHA, MdlxTimelineDescriptor.FLOAT_TIMELINE), + /** + * Geoset animation color + */ + KGAC(MdlUtils.TOKEN_COLOR, MdlxTimelineDescriptor.VECTOR3_TIMELINE), + // Light + /** + * Light attenuation start + */ + KLAS(MdlUtils.TOKEN_ATTENUATION_START, MdlxTimelineDescriptor.FLOAT_TIMELINE), + /** + * Light attenuation end + */ + KLAE(MdlUtils.TOKEN_ATTENUATION_END, MdlxTimelineDescriptor.FLOAT_TIMELINE), + /** + * Light color + */ + KLAC(MdlUtils.TOKEN_COLOR, MdlxTimelineDescriptor.VECTOR3_TIMELINE), + /** + * Light intensity + */ + KLAI(MdlUtils.TOKEN_INTENSITY, MdlxTimelineDescriptor.FLOAT_TIMELINE), + /** + * Light ambient intensity + */ + KLBI(MdlUtils.TOKEN_AMB_INTENSITY, MdlxTimelineDescriptor.FLOAT_TIMELINE), + /** + * Light ambient color + */ + KLBC(MdlUtils.TOKEN_AMB_COLOR, MdlxTimelineDescriptor.VECTOR3_TIMELINE), + /** + * Light visibility + */ + KLAV(MdlUtils.TOKEN_VISIBILITY, MdlxTimelineDescriptor.FLOAT_TIMELINE), + // Attachment + /** + * Attachment visibility + */ + KATV(MdlUtils.TOKEN_VISIBILITY, MdlxTimelineDescriptor.FLOAT_TIMELINE), + // ParticleEmitter + /** + * Particle emitter emission rate + */ + KPEE(MdlUtils.TOKEN_EMISSION_RATE, MdlxTimelineDescriptor.FLOAT_TIMELINE), + /** + * Particle emitter gravity + */ + KPEG(MdlUtils.TOKEN_GRAVITY, MdlxTimelineDescriptor.FLOAT_TIMELINE), + /** + * Particle emitter longitude + */ + KPLN(MdlUtils.TOKEN_LONGITUDE, MdlxTimelineDescriptor.FLOAT_TIMELINE), + /** + * Particle emitter latitude + */ + KPLT(MdlUtils.TOKEN_LATITUDE, MdlxTimelineDescriptor.FLOAT_TIMELINE), + /** + * Particle emitter lifespan + */ + KPEL(MdlUtils.TOKEN_LIFE_SPAN, MdlxTimelineDescriptor.FLOAT_TIMELINE), + /** + * Particle emitter initial velocity + */ + KPES(MdlUtils.TOKEN_INIT_VELOCITY, MdlxTimelineDescriptor.FLOAT_TIMELINE), + /** + * Particle emitter visibility + */ + KPEV(MdlUtils.TOKEN_VISIBILITY, MdlxTimelineDescriptor.FLOAT_TIMELINE), + // ParticleEmitter2 + /** + * Particle emitter 2 speed + */ + KP2S("Speed", MdlxTimelineDescriptor.FLOAT_TIMELINE), + /** + * Particle emitter 2 variation + */ + KP2R("Variation", MdlxTimelineDescriptor.FLOAT_TIMELINE), + /** + * Particle emitter 2 latitude + */ + KP2L("Latitude", MdlxTimelineDescriptor.FLOAT_TIMELINE), + /** + * Particle emitter 2 gravity + */ + KP2G("Gravity", MdlxTimelineDescriptor.FLOAT_TIMELINE), + /** + * Particle emitter 2 emission rate + */ + KP2E("EmissionRate", MdlxTimelineDescriptor.FLOAT_TIMELINE), + /** + * Particle emitter 2 length + */ + KP2N("Length", MdlxTimelineDescriptor.FLOAT_TIMELINE), + /** + * Particle emitter 2 width + */ + KP2W("Width", MdlxTimelineDescriptor.FLOAT_TIMELINE), + /** + * Particle emitter 2 visibility + */ + KP2V("Visibility", MdlxTimelineDescriptor.FLOAT_TIMELINE), + // ParticleEmitterCorn + /** + * Popcorn emitter alpha + */ + KPPA("Alpha", MdlxTimelineDescriptor.FLOAT_TIMELINE), + /** + * Popcorn emitter color + */ + KPPC("Color", MdlxTimelineDescriptor.VECTOR3_TIMELINE), + /** + * Popcorn emitter emission rate + */ + KPPE("EmissionRate", MdlxTimelineDescriptor.FLOAT_TIMELINE), + /** + * Popcorn emitter lifespan + */ + KPPL("LifeSpan", MdlxTimelineDescriptor.FLOAT_TIMELINE), + /** + * Popcorn emitter speed + */ + KPPS("Speed", MdlxTimelineDescriptor.FLOAT_TIMELINE), + /** + * Popcorn emitter visibility + */ + KPPV("Visibility", MdlxTimelineDescriptor.FLOAT_TIMELINE), + // RibbonEmitter + /** + * Ribbon emitter height above + */ + KRHA("HeightAbove", MdlxTimelineDescriptor.FLOAT_TIMELINE), + /** + * Ribbon emitter height below + */ + KRHB("HeightBelow", MdlxTimelineDescriptor.FLOAT_TIMELINE), + /** + * Ribbon emitter alpha + */ + KRAL("Alpha", MdlxTimelineDescriptor.FLOAT_TIMELINE), + /** + * Ribbon emitter color + */ + KRCO("Color", MdlxTimelineDescriptor.VECTOR3_TIMELINE), + /** + * Ribbon emitter texture slot + */ + KRTX("TextureSlot", MdlxTimelineDescriptor.UINT32_TIMELINE), + /** + * Ribbon emitter visibility + */ + KRVS("Visibility", MdlxTimelineDescriptor.FLOAT_TIMELINE), + // Camera + /** + * Camera source translation + */ + KCTR(MdlUtils.TOKEN_TRANSLATION, MdlxTimelineDescriptor.VECTOR3_TIMELINE), + /** + * Camera target translation + */ + KTTR(MdlUtils.TOKEN_TRANSLATION, MdlxTimelineDescriptor.VECTOR3_TIMELINE), + /** + * Camera source rotation + */ + KCRL(MdlUtils.TOKEN_ROTATION, MdlxTimelineDescriptor.UINT32_TIMELINE), + // GenericObject + /** + * Generic object translation + */ + KGTR(MdlUtils.TOKEN_TRANSLATION, MdlxTimelineDescriptor.VECTOR3_TIMELINE), + /** + * Generic object rotation + */ + KGRT(MdlUtils.TOKEN_ROTATION, MdlxTimelineDescriptor.VECTOR4_TIMELINE), + /** + * Generic object scaling + */ + KGSC(MdlUtils.TOKEN_SCALING, MdlxTimelineDescriptor.VECTOR3_TIMELINE); + + private final String mdlToken; + private final MdlxTimelineDescriptor implementation; + private final War3ID war3id; + + AnimationMap(final String mdlToken, final MdlxTimelineDescriptor implementation) { + this.mdlToken = mdlToken; + this.implementation = implementation; + this.war3id = War3ID.fromString(name()); + } + + public String getMdlToken() { + return this.mdlToken; + } + + public MdlxTimelineDescriptor getImplementation() { + return this.implementation; + } + + public War3ID getWar3id() { + return this.war3id; + } + + public static final Map ID_TO_TAG = new HashMap<>(); + + static { + for (final AnimationMap tag : AnimationMap.values()) { + ID_TO_TAG.put(tag.getWar3id(), tag); + } + } +} diff --git a/core/src/com/hiveworkshop/rms/parsers/mdlx/InterpolationType.java b/core/src/com/hiveworkshop/rms/parsers/mdlx/InterpolationType.java new file mode 100644 index 0000000..3856a18 --- /dev/null +++ b/core/src/com/hiveworkshop/rms/parsers/mdlx/InterpolationType.java @@ -0,0 +1,26 @@ +package com.hiveworkshop.rms.parsers.mdlx; + +public enum InterpolationType { + DONT_INTERP("DontInterp"), LINEAR("Linear"), HERMITE("Hermite"), BEZIER("Bezier"); + + public static final InterpolationType[] VALUES = values(); + + private final String token; + + InterpolationType(final String token) { + this.token = token; + } + + public static InterpolationType getType(final int whichValue) { + return VALUES[whichValue]; + } + + public boolean tangential() { + return ordinal() > 1; + } + + @Override + public String toString() { + return token; + } +} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/AnimatedObject.java b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxAnimatedObject.java similarity index 50% rename from core/src/com/etheller/warsmash/parsers/mdlx/AnimatedObject.java rename to core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxAnimatedObject.java index 8d93db5..16e9460 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/AnimatedObject.java +++ b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxAnimatedObject.java @@ -1,37 +1,29 @@ -package com.etheller.warsmash.parsers.mdlx; +package com.hiveworkshop.rms.parsers.mdlx; -import java.io.IOException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; -import com.etheller.warsmash.parsers.mdlx.timeline.Timeline; -import com.etheller.warsmash.util.MdlUtils; import com.etheller.warsmash.util.War3ID; -import com.google.common.io.LittleEndianDataInputStream; -import com.google.common.io.LittleEndianDataOutputStream; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlTokenInputStream; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlTokenOutputStream; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlUtils; +import com.hiveworkshop.rms.parsers.mdlx.timeline.MdlxTimeline; +import com.hiveworkshop.rms.util.BinaryReader; +import com.hiveworkshop.rms.util.BinaryWriter; /** * Based on the works of Chananya Freiman. - * */ -public abstract class AnimatedObject implements Chunk, MdlxBlock { - protected final List> timelines; +public abstract class MdlxAnimatedObject implements MdlxChunk, MdlxBlock { + public final List> timelines = new ArrayList<>(); - public AnimatedObject() { - this.timelines = new ArrayList<>(); - } - - public void readTimelines(final LittleEndianDataInputStream stream, long size) throws IOException { + public void readTimelines(final BinaryReader reader, long size) { while (size > 0) { - final War3ID name = new War3ID(Integer.reverseBytes(stream.readInt())); - final AnimationMap animationMap = AnimationMap.ID_TO_TAG.get(name); - if (animationMap == null) { - throw new IllegalStateException("Unable to parse: '" + name + "'"); - } - final Timeline timeline = animationMap.getImplementation().createTimeline(); + final War3ID name = new War3ID(reader.readTag()); + final MdlxTimeline timeline = AnimationMap.ID_TO_TAG.get(name).getImplementation().createTimeline(); - timeline.readMdx(stream, name); + timeline.readMdx(reader, name); size -= timeline.getByteLength(); @@ -39,9 +31,9 @@ public abstract class AnimatedObject implements Chunk, MdlxBlock { } } - public void writeTimelines(final LittleEndianDataOutputStream stream) throws IOException { - for (final Timeline timeline : this.timelines) { - timeline.writeMdx(stream); + public void writeTimelines(final BinaryWriter writer) { + for (final MdlxTimeline timeline : this.timelines) { + timeline.writeMdx(writer); } } @@ -49,17 +41,17 @@ public abstract class AnimatedObject implements Chunk, MdlxBlock { return new TransformedAnimatedBlockIterator(stream.readBlock().iterator()); } - public void readTimeline(final MdlTokenInputStream stream, final AnimationMap name) throws IOException { - final Timeline timeline = name.getImplementation().createTimeline(); + public void readTimeline(final MdlTokenInputStream stream, final AnimationMap name) { + final MdlxTimeline timeline = name.getImplementation().createTimeline(); timeline.readMdl(stream, name.getWar3id()); this.timelines.add(timeline); } - public boolean writeTimeline(final MdlTokenOutputStream stream, final AnimationMap name) throws IOException { - for (final Timeline timeline : this.timelines) { - if (timeline.getName().equals(name.getWar3id())) { + public boolean writeTimeline(final MdlTokenOutputStream stream, final AnimationMap name) { + for (final MdlxTimeline timeline : this.timelines) { + if (timeline.name.equals(name.getWar3id())) { timeline.writeMdl(stream); return true; } @@ -68,20 +60,23 @@ public abstract class AnimatedObject implements Chunk, MdlxBlock { } @Override - public long getByteLength() { + public long getByteLength(final int version) { long size = 0; - for (final Timeline timeline : this.timelines) { + for (final MdlxTimeline timeline : this.timelines) { size += timeline.getByteLength(); } return size; } + public List> getTimelines() { + return this.timelines; + } + /** * TODO: This code uses StringBuilder implicitly during string concat. This * should be upgraded for performance. * * @author micro - * */ private static final class TransformedAnimatedBlockIterator implements Iterator { private final Iterator delegate; @@ -103,10 +98,5 @@ public abstract class AnimatedObject implements Chunk, MdlxBlock { } return token; } - - } - - public List> getTimelines() { - return this.timelines; } } diff --git a/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxAttachment.java b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxAttachment.java new file mode 100644 index 0000000..28a23fe --- /dev/null +++ b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxAttachment.java @@ -0,0 +1,105 @@ +package com.hiveworkshop.rms.parsers.mdlx; + +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlTokenInputStream; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlTokenOutputStream; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlUtils; +import com.hiveworkshop.rms.util.BinaryReader; +import com.hiveworkshop.rms.util.BinaryWriter; + +public class MdlxAttachment extends MdlxGenericObject { + public String path = ""; + public int attachmentId = 0; + + public MdlxAttachment() { + super(0x800); + } + + @Override + public void readMdx(final BinaryReader reader, final int version) { + final int position = reader.position(); + final long size = reader.readUInt32(); + + super.readMdx(reader, version); + + this.path = reader.read(260); + this.attachmentId = reader.readInt32(); + + readTimelines(reader, size - (reader.position() - position)); + } + + @Override + public void writeMdx(final BinaryWriter writer, final int version) { + writer.writeUInt32(getByteLength(version)); + + super.writeMdx(writer, version); + + writer.writeWithNulls(this.path, 260); + writer.writeInt32(this.attachmentId); + + writeNonGenericAnimationChunks(writer); + } + + @Override + public void readMdl(final MdlTokenInputStream stream, final int version) { + for (final String token : super.readMdlGeneric(stream)) { + if (MdlUtils.TOKEN_ATTACHMENT_ID.equals(token)) { + this.attachmentId = stream.readInt(); + } + else if (MdlUtils.TOKEN_PATH.equals(token)) { + this.path = stream.read(); + } + else if (MdlUtils.TOKEN_VISIBILITY.equals(token)) { + readTimeline(stream, AnimationMap.KATV); + } + else { + throw new RuntimeException("Unknown token in Attachment " + this.name + ": " + token); + } + } + } + + @Override + public void writeMdl(final MdlTokenOutputStream stream, final int version) { + stream.startObjectBlock(MdlUtils.TOKEN_ATTACHMENT, this.name); + writeGenericHeader(stream); + + // flowtsohg asks in his JS: + // Is this needed? MDX supplies it, but MdlxConv does not use it. + // Retera: + // I tried to preserve it when it was shown, but omit it when it was omitted + // for MDL in Matrix Eater. Matrix Eater's MDL -> MDX is generating them + // and discarding what was read from the MDL. The Matrix Eater is notably + // buggy from a cursory read through, and would always omit AttachmentID 0 + // in MDL output. + stream.writeAttrib(MdlUtils.TOKEN_ATTACHMENT_ID, this.attachmentId); + + if ((this.path != null) && (this.path.length() > 0)) { + stream.writeStringAttrib(MdlUtils.TOKEN_PATH, this.path); + } + + writeTimeline(stream, AnimationMap.KATV); + + writeGenericTimelines(stream); + stream.endBlock(); + } + + @Override + public long getByteLength(final int version) { + return 268 + super.getByteLength(version); + } + + public String getPath() { + return this.path; + } + + public int getAttachmentId() { + return this.attachmentId; + } + + public void setPath(final String path) { + this.path = path; + } + + public void setAttachmentId(final int attachmentId) { + this.attachmentId = attachmentId; + } +} diff --git a/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxBlock.java b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxBlock.java new file mode 100644 index 0000000..08d92a7 --- /dev/null +++ b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxBlock.java @@ -0,0 +1,16 @@ +package com.hiveworkshop.rms.parsers.mdlx; + +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlTokenInputStream; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlTokenOutputStream; +import com.hiveworkshop.rms.util.BinaryReader; +import com.hiveworkshop.rms.util.BinaryWriter; + +public interface MdlxBlock { + void readMdx(final BinaryReader reader, final int version); + + void writeMdx(final BinaryWriter writer, final int version); + + void readMdl(final MdlTokenInputStream stream, final int version); + + void writeMdl(final MdlTokenOutputStream stream, final int version); +} diff --git a/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxBlockDescriptor.java b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxBlockDescriptor.java new file mode 100644 index 0000000..a45ae6d --- /dev/null +++ b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxBlockDescriptor.java @@ -0,0 +1,44 @@ +package com.hiveworkshop.rms.parsers.mdlx; + +import com.hiveworkshop.rms.util.Descriptor; + +public interface MdlxBlockDescriptor extends Descriptor { + + MdlxBlockDescriptor ATTACHMENT = MdlxAttachment::new; + + MdlxBlockDescriptor BONE = MdlxBone::new; + + MdlxBlockDescriptor CAMERA = MdlxCamera::new; + + MdlxBlockDescriptor COLLISION_SHAPE = MdlxCollisionShape::new; + + MdlxBlockDescriptor EVENT_OBJECT = MdlxEventObject::new; + + MdlxBlockDescriptor GEOSET = MdlxGeoset::new; + + MdlxBlockDescriptor GEOSET_ANIMATION = MdlxGeosetAnimation::new; + + MdlxBlockDescriptor HELPER = MdlxHelper::new; + + MdlxBlockDescriptor LIGHT = MdlxLight::new; + + MdlxBlockDescriptor LAYER = MdlxLayer::new; + + MdlxBlockDescriptor MATERIAL = MdlxMaterial::new; + + MdlxBlockDescriptor PARTICLE_EMITTER = MdlxParticleEmitter::new; + + MdlxBlockDescriptor PARTICLE_EMITTER2 = MdlxParticleEmitter2::new; + + MdlxBlockDescriptor PARTICLE_EMITTER_POPCORN = MdlxParticleEmitterPopcorn::new; + + MdlxBlockDescriptor RIBBON_EMITTER = MdlxRibbonEmitter::new; + + MdlxBlockDescriptor SEQUENCE = MdlxSequence::new; + + MdlxBlockDescriptor TEXTURE = MdlxTexture::new; + + MdlxBlockDescriptor TEXTURE_ANIMATION = MdlxTextureAnimation::new; + + MdlxBlockDescriptor FACE_EFFECT = MdlxFaceEffect::new; +} diff --git a/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxBone.java b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxBone.java new file mode 100644 index 0000000..3bc443a --- /dev/null +++ b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxBone.java @@ -0,0 +1,104 @@ +package com.hiveworkshop.rms.parsers.mdlx; + +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlTokenInputStream; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlTokenOutputStream; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlUtils; +import com.hiveworkshop.rms.util.BinaryReader; +import com.hiveworkshop.rms.util.BinaryWriter; + +public class MdlxBone extends MdlxGenericObject { + public int geosetId = -1; + public int geosetAnimationId = -1; + + public MdlxBone() { + super(0x100); + } + + @Override + public void readMdx(final BinaryReader reader, final int version) { + super.readMdx(reader, version); + + this.geosetId = reader.readInt32(); + this.geosetAnimationId = reader.readInt32(); + } + + @Override + public void writeMdx(final BinaryWriter writer, final int version) { + super.writeMdx(writer, version); + + writer.writeInt32(this.geosetId); + writer.writeInt32(this.geosetAnimationId); + } + + @Override + public void readMdl(final MdlTokenInputStream stream, final int version) { + for (String token : super.readMdlGeneric(stream)) { + if (MdlUtils.TOKEN_GEOSETID.equals(token)) { + token = stream.read(); + + if (MdlUtils.TOKEN_MULTIPLE.equals(token)) { + this.geosetId = -1; + } + else { + this.geosetId = Integer.parseInt(token); + } + } + else if (MdlUtils.TOKEN_GEOSETANIMID.equals(token)) { + token = stream.read(); + + if (MdlUtils.TOKEN_NONE.equals(token)) { + this.geosetAnimationId = -1; + } + else { + this.geosetAnimationId = Integer.parseInt(token); + } + } + else { + throw new RuntimeException("Unknown token in Bone " + this.name + ": " + token); + } + } + } + + @Override + public void writeMdl(final MdlTokenOutputStream stream, final int version) { + stream.startObjectBlock(MdlUtils.TOKEN_BONE, this.name); + writeGenericHeader(stream); + + if (this.geosetId == -1) { + stream.writeAttrib(MdlUtils.TOKEN_GEOSETID, MdlUtils.TOKEN_MULTIPLE); + } + else { + stream.writeAttrib(MdlUtils.TOKEN_GEOSETID, this.geosetId); + } + if (this.geosetAnimationId == -1) { + stream.writeAttrib(MdlUtils.TOKEN_GEOSETANIMID, MdlUtils.TOKEN_NONE); + } + else { + stream.writeAttrib(MdlUtils.TOKEN_GEOSETANIMID, this.geosetAnimationId); + } + + writeGenericTimelines(stream); + stream.endBlock(); + } + + @Override + public long getByteLength(final int version) { + return 8 + super.getByteLength(version); + } + + public int getGeosetId() { + return this.geosetId; + } + + public int getGeosetAnimationId() { + return this.geosetAnimationId; + } + + public void setGeosetId(final int geosetId) { + this.geosetId = geosetId; + } + + public void setGeosetAnimationId(final int geosetAnimationId) { + this.geosetAnimationId = geosetAnimationId; + } +} diff --git a/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxCamera.java b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxCamera.java new file mode 100644 index 0000000..3d257a0 --- /dev/null +++ b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxCamera.java @@ -0,0 +1,160 @@ +package com.hiveworkshop.rms.parsers.mdlx; + +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlTokenInputStream; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlTokenOutputStream; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlUtils; +import com.hiveworkshop.rms.util.BinaryReader; +import com.hiveworkshop.rms.util.BinaryWriter; + +public class MdlxCamera extends MdlxAnimatedObject { + public String name = ""; + public float[] position = new float[3]; + public float fieldOfView = 0; + public float farClippingPlane = 0; + public float nearClippingPlane = 0; + public float[] targetPosition = new float[3]; + + @Override + public void readMdx(final BinaryReader reader, final int version) { + final long size = reader.readUInt32(); + + this.name = reader.read(80); + reader.readFloat32Array(this.position); + this.fieldOfView = reader.readFloat32(); + this.farClippingPlane = reader.readFloat32(); + this.nearClippingPlane = reader.readFloat32(); + reader.readFloat32Array(this.targetPosition); + + readTimelines(reader, size - 120); + } + + @Override + public void writeMdx(final BinaryWriter writer, final int version) { + writer.writeUInt32(getByteLength(version)); + writer.writeWithNulls(this.name, 80); + writer.writeFloat32Array(this.position); + writer.writeFloat32(this.fieldOfView); + writer.writeFloat32(this.farClippingPlane); + writer.writeFloat32(this.nearClippingPlane); + writer.writeFloat32Array(this.targetPosition); + + writeTimelines(writer); + } + + @Override + public void readMdl(final MdlTokenInputStream stream, final int version) { + this.name = stream.read(); + + for (final String token : stream.readBlock()) { + switch (token) { + case MdlUtils.TOKEN_POSITION: + stream.readFloatArray(this.position); + break; + case MdlUtils.TOKEN_TRANSLATION: + readTimeline(stream, AnimationMap.KCTR); + break; + case MdlUtils.TOKEN_ROTATION: + readTimeline(stream, AnimationMap.KCRL); + break; + case MdlUtils.TOKEN_FIELDOFVIEW: + this.fieldOfView = stream.readFloat(); + break; + case MdlUtils.TOKEN_FARCLIP: + this.farClippingPlane = stream.readFloat(); + break; + case MdlUtils.TOKEN_NEARCLIP: + this.nearClippingPlane = stream.readFloat(); + break; + case MdlUtils.TOKEN_TARGET: + for (final String subToken : stream.readBlock()) { + switch (subToken) { + case MdlUtils.TOKEN_POSITION: + stream.readFloatArray(this.targetPosition); + break; + case MdlUtils.TOKEN_TRANSLATION: + readTimeline(stream, AnimationMap.KTTR); + break; + default: + throw new IllegalStateException( + "Unknown token in Camera " + this.name + "'s Target: " + subToken); + } + } + break; + default: + throw new RuntimeException("Unknown token in Camera " + this.name + ": " + token); + } + } + } + + @Override + public void writeMdl(final MdlTokenOutputStream stream, final int version) { + stream.startObjectBlock(MdlUtils.TOKEN_CAMERA, this.name); + + stream.writeFloatArrayAttrib(MdlUtils.TOKEN_POSITION, this.position); + writeTimeline(stream, AnimationMap.KCTR); + writeTimeline(stream, AnimationMap.KCRL); + stream.writeFloatAttrib(MdlUtils.TOKEN_FIELDOFVIEW, this.fieldOfView); + stream.writeFloatAttrib(MdlUtils.TOKEN_FARCLIP, this.farClippingPlane); + stream.writeFloatAttrib(MdlUtils.TOKEN_NEARCLIP, this.nearClippingPlane); + + stream.startBlock(MdlUtils.TOKEN_TARGET); + stream.writeFloatArrayAttrib(MdlUtils.TOKEN_POSITION, this.targetPosition); + writeTimeline(stream, AnimationMap.KTTR); + stream.endBlock(); + + stream.endBlock(); + } + + @Override + public long getByteLength(final int version) { + return 120 + super.getByteLength(version); + } + + public String getName() { + return this.name; + } + + public float[] getPosition() { + return this.position; + } + + public float getFieldOfView() { + return this.fieldOfView; + } + + public float getFarClippingPlane() { + return this.farClippingPlane; + } + + public float getNearClippingPlane() { + return this.nearClippingPlane; + } + + public float[] getTargetPosition() { + return this.targetPosition; + } + + public void setName(final String name) { + this.name = name; + } + + public void setPosition(final float[] position) { + this.position = position; + } + + public void setFieldOfView(final float fieldOfView) { + this.fieldOfView = fieldOfView; + } + + public void setFarClippingPlane(final float farClippingPlane) { + this.farClippingPlane = farClippingPlane; + } + + public void setNearClippingPlane(final float nearClippingPlane) { + this.nearClippingPlane = nearClippingPlane; + } + + public void setTargetPosition(final float[] targetPosition) { + this.targetPosition = targetPosition; + } +} diff --git a/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxChunk.java b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxChunk.java new file mode 100644 index 0000000..3a2b79b --- /dev/null +++ b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxChunk.java @@ -0,0 +1,5 @@ +package com.hiveworkshop.rms.parsers.mdlx; + +public interface MdlxChunk { + long getByteLength(int version); +} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/CollisionShape.java b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxCollisionShape.java similarity index 58% rename from core/src/com/etheller/warsmash/parsers/mdlx/CollisionShape.java rename to core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxCollisionShape.java index f5fc810..840301f 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/CollisionShape.java +++ b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxCollisionShape.java @@ -1,14 +1,13 @@ -package com.etheller.warsmash.parsers.mdlx; +package com.hiveworkshop.rms.parsers.mdlx; -import java.io.IOException; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlTokenInputStream; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlTokenOutputStream; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlUtils; +import com.hiveworkshop.rms.util.BinaryReader; +import com.hiveworkshop.rms.util.BinaryWriter; -import com.etheller.warsmash.util.MdlUtils; -import com.etheller.warsmash.util.ParseUtils; -import com.google.common.io.LittleEndianDataInputStream; -import com.google.common.io.LittleEndianDataOutputStream; - -public class CollisionShape extends GenericObject { - public static enum Type { +public class MdlxCollisionShape extends MdlxGenericObject { + public enum Type { BOX, PLANE, SPHERE, @@ -18,11 +17,11 @@ public class CollisionShape extends GenericObject { private final boolean boundsRadius; - private Type() { + Type() { this.boundsRadius = false; } - private Type(final boolean boundsRadius) { + Type(final boolean boundsRadius) { this.boundsRadius = boundsRadius; } @@ -35,46 +34,48 @@ public class CollisionShape extends GenericObject { } } - private CollisionShape.Type type; - private final float[][] vertices = { new float[3], new float[3] }; - private float boundsRadius; + public MdlxCollisionShape.Type type = Type.SPHERE; + public final float[][] vertices = { new float[3], new float[3] }; + public float boundsRadius = 0; - public CollisionShape() { + public MdlxCollisionShape() { super(0x2000); } @Override - public void readMdx(final LittleEndianDataInputStream stream) throws IOException { - super.readMdx(stream); + public void readMdx(final BinaryReader reader, final int version) { + super.readMdx(reader, version); - final long typeIndex = ParseUtils.readUInt32(stream); - this.type = CollisionShape.Type.from((int) typeIndex); - ParseUtils.readFloatArray(stream, this.vertices[0]); + this.type = MdlxCollisionShape.Type.from(reader.readInt32()); + reader.readFloat32Array(this.vertices[0]); if (this.type != Type.SPHERE) { - ParseUtils.readFloatArray(stream, this.vertices[1]); + reader.readFloat32Array(this.vertices[1]); } + if ((this.type == Type.SPHERE) || (this.type == Type.CYLINDER)) { - this.boundsRadius = stream.readFloat(); + this.boundsRadius = reader.readFloat32(); } } @Override - public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException { - super.writeMdx(stream); + public void writeMdx(final BinaryWriter writer, final int version) { + super.writeMdx(writer, version); - ParseUtils.writeUInt32(stream, this.type == null ? -1 : this.type.ordinal()); - ParseUtils.writeFloatArray(stream, this.vertices[0]); - if (this.type != CollisionShape.Type.SPHERE) { - ParseUtils.writeFloatArray(stream, this.vertices[1]); + writer.writeInt32(this.type.ordinal()); + writer.writeFloat32Array(this.vertices[0]); + + if (this.type != MdlxCollisionShape.Type.SPHERE) { + writer.writeFloat32Array(this.vertices[1]); } + if ((this.type == Type.SPHERE) || (this.type == Type.CYLINDER)) { - stream.writeFloat(this.boundsRadius); + writer.writeFloat32(this.boundsRadius); } } @Override - public void readMdl(final MdlTokenInputStream stream) { + public void readMdl(final MdlTokenInputStream stream, final int version) { for (final String token : super.readMdlGeneric(stream)) { switch (token) { case MdlUtils.TOKEN_BOX: @@ -89,16 +90,15 @@ public class CollisionShape extends GenericObject { case MdlUtils.TOKEN_CYLINDER: this.type = Type.CYLINDER; break; - case MdlUtils.TOKEN_VERTICES: + case MdlUtils.TOKEN_VERTICES: { final int count = stream.readInt(); stream.read(); // { - stream.readFloatArray(this.vertices[0]); if (count == 2) { stream.readFloatArray(this.vertices[1]); } - stream.read(); // } + } break; case MdlUtils.TOKEN_BOUNDSRADIUS: this.boundsRadius = stream.readFloat(); @@ -110,10 +110,10 @@ public class CollisionShape extends GenericObject { } @Override - public void writeMdl(final MdlTokenOutputStream stream) throws IOException { + public void writeMdl(final MdlTokenOutputStream stream, final int version) { stream.startObjectBlock(MdlUtils.TOKEN_COLLISION_SHAPE, this.name); writeGenericHeader(stream); - String type; + final String type; int vertices = 2; switch (this.type) { case BOX: @@ -122,9 +122,10 @@ public class CollisionShape extends GenericObject { case PLANE: type = MdlUtils.TOKEN_PLANE; break; - case SPHERE: + case SPHERE: { type = MdlUtils.TOKEN_SPHERE; vertices = 1; + } break; case CYLINDER: type = MdlUtils.TOKEN_CYLINDER; @@ -150,8 +151,8 @@ public class CollisionShape extends GenericObject { } @Override - public long getByteLength() { - long size = 16 + super.getByteLength(); + public long getByteLength(final int version) { + long size = 16 + super.getByteLength(version); if (this.type != Type.SPHERE) { size += 12; @@ -164,16 +165,23 @@ public class CollisionShape extends GenericObject { return size; } - public float[][] getVertices() { - return this.vertices; + public MdlxCollisionShape.Type getType() { + return this.type; } - public CollisionShape.Type getType() { - return this.type; + public float[][] getVertices() { + return this.vertices; } public float getBoundsRadius() { return this.boundsRadius; } + public void setType(final MdlxCollisionShape.Type type) { + this.type = type; + } + + public void setBoundsRadius(final float boundsRadius) { + this.boundsRadius = boundsRadius; + } } diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/EventObject.java b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxEventObject.java similarity index 51% rename from core/src/com/etheller/warsmash/parsers/mdlx/EventObject.java rename to core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxEventObject.java index 987d359..fa20996 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/EventObject.java +++ b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxEventObject.java @@ -1,48 +1,54 @@ -package com.etheller.warsmash.parsers.mdlx; +package com.hiveworkshop.rms.parsers.mdlx; -import java.io.IOException; - -import com.etheller.warsmash.util.MdlUtils; -import com.etheller.warsmash.util.ParseUtils; import com.etheller.warsmash.util.War3ID; -import com.google.common.io.LittleEndianDataInputStream; -import com.google.common.io.LittleEndianDataOutputStream; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlTokenInputStream; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlTokenOutputStream; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlUtils; +import com.hiveworkshop.rms.util.BinaryReader; +import com.hiveworkshop.rms.util.BinaryWriter; -public class EventObject extends GenericObject { +public class MdlxEventObject extends MdlxGenericObject { private static final War3ID KEVT = War3ID.fromString("KEVT"); - private int globalSequenceId = -1; - private long[] keyFrames = { 1 }; - public EventObject() { + public int globalSequenceId = -1; + public long[] keyFrames = { 1 }; + + public MdlxEventObject() { super(0x400); } @Override - public void readMdx(final LittleEndianDataInputStream stream) throws IOException { - super.readMdx(stream); - stream.readInt(); // KEVT skipped - final long count = ParseUtils.readUInt32(stream); - this.globalSequenceId = stream.readInt(); + public void readMdx(final BinaryReader reader, final int version) { + super.readMdx(reader, version); + + reader.readInt32(); // KEVT skipped + + final long count = reader.readUInt32(); + + this.globalSequenceId = reader.readInt32(); this.keyFrames = new long[(int) count]; + for (int i = 0; i < count; i++) { - this.keyFrames[i] = stream.readInt(); + this.keyFrames[i] = reader.readInt32(); } } @Override - public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException { - super.writeMdx(stream); - ParseUtils.writeWar3ID(stream, KEVT); - ParseUtils.writeUInt32(stream, this.keyFrames.length); - stream.writeInt(this.globalSequenceId); - for (int i = 0; i < this.keyFrames.length; i++) { - ParseUtils.writeUInt32(stream, this.keyFrames[i]); + public void writeMdx(final BinaryWriter writer, final int version) { + super.writeMdx(writer, version); + + writer.writeTag(KEVT.getValue()); + writer.writeUInt32(this.keyFrames.length); + writer.writeInt32(this.globalSequenceId); + + for (final long keyFrame : this.keyFrames) { + writer.writeUInt32(keyFrame); } } @Override - public void readMdl(final MdlTokenInputStream stream) { + public void readMdl(final MdlTokenInputStream stream, final int version) { for (final String token : super.readMdlGeneric(stream)) { if (MdlUtils.TOKEN_EVENT_TRACK.equals(token)) { this.keyFrames = new long[stream.readInt()]; @@ -55,7 +61,7 @@ public class EventObject extends GenericObject { } @Override - public void writeMdl(final MdlTokenOutputStream stream) throws IOException { + public void writeMdl(final MdlTokenOutputStream stream, final int version) { stream.startObjectBlock(MdlUtils.TOKEN_EVENT_OBJECT, this.name); writeGenericHeader(stream); stream.startBlock(MdlUtils.TOKEN_EVENT_TRACK, this.keyFrames.length); @@ -71,24 +77,23 @@ public class EventObject extends GenericObject { } @Override - public long getByteLength() { - return 12 + (this.keyFrames.length * 4) + super.getByteLength(); + public long getByteLength(final int version) { + return 12 + (this.keyFrames.length * 4) + super.getByteLength(version); } public int getGlobalSequenceId() { return this.globalSequenceId; } - public void setGlobalSequenceId(final int globalSequenceId) { - this.globalSequenceId = globalSequenceId; - } - public long[] getKeyFrames() { return this.keyFrames; } + public void setGlobalSequenceId(final int globalSequenceId) { + this.globalSequenceId = globalSequenceId; + } + public void setKeyFrames(final long[] keyFrames) { this.keyFrames = keyFrames; } - } diff --git a/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxExtent.java b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxExtent.java new file mode 100644 index 0000000..1dd590d --- /dev/null +++ b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxExtent.java @@ -0,0 +1,62 @@ +package com.hiveworkshop.rms.parsers.mdlx; + +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlTokenOutputStream; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlUtils; +import com.hiveworkshop.rms.util.BinaryReader; +import com.hiveworkshop.rms.util.BinaryWriter; + +public class MdlxExtent { + public float boundsRadius = 0; + public float[] min = new float[3]; + public float[] max = new float[3]; + + public void readMdx(final BinaryReader reader) { + this.boundsRadius = reader.readFloat32(); + reader.readFloat32Array(this.min); + reader.readFloat32Array(this.max); + } + + public void writeMdx(final BinaryWriter writer) { + writer.writeFloat32(this.boundsRadius); + writer.writeFloat32Array(this.min); + writer.writeFloat32Array(this.max); + } + + public void writeMdl(final MdlTokenOutputStream stream) { + if ((this.min[0] != 0) || (this.min[1] != 0) || (this.min[2] != 0)) { + stream.writeFloatArrayAttrib(MdlUtils.TOKEN_MINIMUM_EXTENT, this.min); + } + + if ((this.max[0] != 0) || (this.max[1] != 0) || (this.max[2] != 0)) { + stream.writeFloatArrayAttrib(MdlUtils.TOKEN_MAXIMUM_EXTENT, this.max); + } + + if (this.boundsRadius != 0) { + stream.writeFloatAttrib(MdlUtils.TOKEN_BOUNDSRADIUS, this.boundsRadius); + } + } + + public float getBoundsRadius() { + return this.boundsRadius; + } + + public float[] getMin() { + return this.min; + } + + public float[] getMax() { + return this.max; + } + + public void setBoundsRadius(final float boundsRadius) { + this.boundsRadius = boundsRadius; + } + + public void setMin(final float[] min) { + this.min = min; + } + + public void setMax(final float[] max) { + this.max = max; + } +} diff --git a/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxFaceEffect.java b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxFaceEffect.java new file mode 100644 index 0000000..47cb82b --- /dev/null +++ b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxFaceEffect.java @@ -0,0 +1,45 @@ +package com.hiveworkshop.rms.parsers.mdlx; + +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlTokenInputStream; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlTokenOutputStream; +import com.hiveworkshop.rms.util.BinaryReader; +import com.hiveworkshop.rms.util.BinaryWriter; + +public class MdlxFaceEffect implements MdlxBlock { + public String type = ""; + public String path = ""; + + @Override + public void readMdx(final BinaryReader reader, final int version) { + type = reader.read(80); + path = reader.read(260); + } + + @Override + public void writeMdx(final BinaryWriter writer, final int version) { + writer.writeWithNulls(type, 80); + writer.writeWithNulls(path, 260); + } + + @Override + public void readMdl(final MdlTokenInputStream stream, final int version) { + type = stream.read(); + + for (final String token : stream.readBlock()) { + if (token.equals("Path")) { + path = stream.read(); + } else { + throw new IllegalStateException("Unknown token in MdlxFaceEffect: " + token); + } + } + } + + @Override + public void writeMdl(final MdlTokenOutputStream stream, final int version) { + stream.startObjectBlock("FaceFX", type); + + stream.writeStringAttrib("Path", path); + + stream.endBlock(); + } +} diff --git a/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxGenericObject.java b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxGenericObject.java new file mode 100644 index 0000000..12e9ee2 --- /dev/null +++ b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxGenericObject.java @@ -0,0 +1,278 @@ +package com.hiveworkshop.rms.parsers.mdlx; + +import java.util.Iterator; + +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlTokenInputStream; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlTokenOutputStream; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlUtils; +import com.hiveworkshop.rms.parsers.mdlx.timeline.MdlxTimeline; +import com.hiveworkshop.rms.util.BinaryReader; +import com.hiveworkshop.rms.util.BinaryWriter; + +/** + * A generic object. + *

+ * The parent class for all objects that exist in the world, and may contain + * spatial animations. This includes bones, particle emitters, and many other + * things. + *

+ * Based on the works of Chananya Freiman. + */ +public abstract class MdlxGenericObject extends MdlxAnimatedObject { + public String name = ""; + public int objectId = -1; + public int parentId = -1; + public int flags = 0; + + public MdlxGenericObject(final int flags) { + this.flags = flags; + } + + @Override + public void readMdx(final BinaryReader reader, final int version) { + final long size = reader.readUInt32(); + + this.name = reader.read(80); + this.objectId = reader.readInt32(); + this.parentId = reader.readInt32(); + this.flags = reader.readInt32(); + + readTimelines(reader, size - 96); + } + + @Override + public void writeMdx(final BinaryWriter writer, final int version) { + writer.writeUInt32(getGenericByteLength(version)); + writer.writeWithNulls(this.name, 80); + writer.writeInt32(this.objectId); + writer.writeInt32(this.parentId); + writer.writeInt32(this.flags); + + for (final MdlxTimeline timeline : this.timelines) { + if (isGeneric(timeline)) { + timeline.writeMdx(writer); + } + } + } + + public void writeNonGenericAnimationChunks(final BinaryWriter writer) { + for (final MdlxTimeline timeline : this.timelines) { + if (!isGeneric(timeline)) { + timeline.writeMdx(writer); + } + } + } + + protected final Iterable readMdlGeneric(final MdlTokenInputStream stream) { + this.name = stream.read(); + return () -> new WrappedMdlTokenIterator(readAnimatedBlock(stream), MdlxGenericObject.this, stream); + } + + public void writeGenericHeader(final MdlTokenOutputStream stream) { + stream.writeAttrib(MdlUtils.TOKEN_OBJECTID, this.objectId); + + if (this.parentId != -1) { + stream.writeAttrib("Parent", this.parentId); + } + + if ((this.flags & 0x40) != 0) { + stream.writeFlag(MdlUtils.TOKEN_BILLBOARDED_LOCK_Z); + } + + if ((this.flags & 0x20) != 0) { + stream.writeFlag(MdlUtils.TOKEN_BILLBOARDED_LOCK_Y); + } + + if ((this.flags & 0x10) != 0) { + stream.writeFlag(MdlUtils.TOKEN_BILLBOARDED_LOCK_X); + } + + if ((this.flags & 0x8) != 0) { + stream.writeFlag(MdlUtils.TOKEN_BILLBOARDED); + } + + if ((this.flags & 0x80) != 0) { + stream.writeFlag(MdlUtils.TOKEN_CAMERA_ANCHORED); + } + + if ((this.flags & 0x2) != 0) { + stream.writeFlag(MdlUtils.TOKEN_DONT_INHERIT + " { " + MdlUtils.TOKEN_ROTATION + " }"); + } + + if ((this.flags & 0x1) != 0) { + stream.writeFlag(MdlUtils.TOKEN_DONT_INHERIT + " { " + MdlUtils.TOKEN_TRANSLATION + " }"); + } + + if ((this.flags & 0x4) != 0) { + stream.writeFlag(MdlUtils.TOKEN_DONT_INHERIT + " { " + MdlUtils.TOKEN_SCALING + " }"); + } + } + + public void writeGenericTimelines(final MdlTokenOutputStream stream) { + writeTimeline(stream, AnimationMap.KGTR); + writeTimeline(stream, AnimationMap.KGRT); + writeTimeline(stream, AnimationMap.KGSC); + } + + public long getGenericByteLength(final int version) { + long size = 96; + + for (final MdlxTimeline timeline : this.timelines) { + if (isGeneric(timeline)) { + size += timeline.getByteLength(); + } + } + + return size; + } + + public boolean isGeneric(final MdlxTimeline timeline) { + final AnimationMap type = AnimationMap.ID_TO_TAG.get(timeline.name); + + return (type == AnimationMap.KGTR) || (type == AnimationMap.KGRT) || (type == AnimationMap.KGSC); + } + + @Override + public long getByteLength(final int version) { + return 96 + super.getByteLength(version); + } + + public String getName() { + return this.name; + } + + public int getObjectId() { + return this.objectId; + } + + public int getParentId() { + return this.parentId; + } + + public int getFlags() { + return this.flags; + } + + public void setName(final String name) { + this.name = name; + } + + public void setObjectId(final int objectId) { + this.objectId = objectId; + } + + public void setParentId(final int parentId) { + this.parentId = parentId; + } + + public void setFlags(final int flags) { + this.flags = flags; + } + + private static final class WrappedMdlTokenIterator implements Iterator { + private final Iterator delegate; + private final MdlxGenericObject updatingObject; + private final MdlTokenInputStream stream; + private String next; + private boolean hasLoaded = false; + + public WrappedMdlTokenIterator(final Iterator delegate, final MdlxGenericObject updatingObject, + final MdlTokenInputStream stream) { + this.delegate = delegate; + this.updatingObject = updatingObject; + this.stream = stream; + } + + @Override + public boolean hasNext() { + if (this.delegate.hasNext()) { + this.next = read(); + this.hasLoaded = true; + return this.next != null; + } + return false; + } + + @Override + public String next() { + if (!this.hasLoaded) { + this.next = read(); + } + this.hasLoaded = false; + return this.next; + } + + private String read() { + String token; + InteriorParsing: do { + token = this.delegate.next(); + if (token == null) { + break; + } + switch (token) { + case MdlUtils.TOKEN_OBJECTID: + this.updatingObject.objectId = Integer.parseInt(this.delegate.next()); + token = null; + break; + case MdlUtils.TOKEN_PARENT: + this.updatingObject.parentId = Integer.parseInt(this.delegate.next()); + token = null; + break; + case MdlUtils.TOKEN_BILLBOARDED_LOCK_Z: + this.updatingObject.flags |= 0x40; + token = null; + break; + case MdlUtils.TOKEN_BILLBOARDED_LOCK_Y: + this.updatingObject.flags |= 0x20; + token = null; + break; + case MdlUtils.TOKEN_BILLBOARDED_LOCK_X: + this.updatingObject.flags |= 0x10; + token = null; + break; + case MdlUtils.TOKEN_BILLBOARDED: + this.updatingObject.flags |= 0x8; + token = null; + break; + case MdlUtils.TOKEN_CAMERA_ANCHORED: + this.updatingObject.flags |= 0x80; + token = null; + break; + case MdlUtils.TOKEN_DONT_INHERIT: + for (final String subToken : this.stream.readBlock()) { + switch (subToken) { + case MdlUtils.TOKEN_ROTATION: + this.updatingObject.flags |= 0x2; + break; + case MdlUtils.TOKEN_TRANSLATION: + this.updatingObject.flags |= 0x1; + break; + case MdlUtils.TOKEN_SCALING: + this.updatingObject.flags |= 0x0; + break; + } + } + token = null; + break; + case MdlUtils.TOKEN_TRANSLATION: + this.updatingObject.readTimeline(this.stream, AnimationMap.KGTR); + token = null; + break; + case MdlUtils.TOKEN_ROTATION: + this.updatingObject.readTimeline(this.stream, AnimationMap.KGRT); + token = null; + break; + case MdlUtils.TOKEN_SCALING: + this.updatingObject.readTimeline(this.stream, AnimationMap.KGSC); + token = null; + break; + default: + break InteriorParsing; + } + } + while (this.delegate.hasNext()); + return token; + } + + } +} diff --git a/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxGeoset.java b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxGeoset.java new file mode 100644 index 0000000..8a0d198 --- /dev/null +++ b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxGeoset.java @@ -0,0 +1,592 @@ +package com.hiveworkshop.rms.parsers.mdlx; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import com.etheller.warsmash.util.War3ID; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlTokenInputStream; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlTokenOutputStream; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlUtils; +import com.hiveworkshop.rms.util.BinaryReader; +import com.hiveworkshop.rms.util.BinaryWriter; + +public class MdlxGeoset implements MdlxBlock, MdlxChunk { + private static final War3ID VRTX = War3ID.fromString("VRTX"); + private static final War3ID NRMS = War3ID.fromString("NRMS"); + private static final War3ID PTYP = War3ID.fromString("PTYP"); + private static final War3ID PCNT = War3ID.fromString("PCNT"); + private static final War3ID PVTX = War3ID.fromString("PVTX"); + private static final War3ID GNDX = War3ID.fromString("GNDX"); + private static final War3ID MTGC = War3ID.fromString("MTGC"); + private static final War3ID MATS = War3ID.fromString("MATS"); + private static final War3ID TANG = War3ID.fromString("TANG"); + private static final War3ID SKIN = War3ID.fromString("SKIN"); + private static final War3ID UVAS = War3ID.fromString("UVAS"); + private static final War3ID UVBS = War3ID.fromString("UVBS"); + + public float[] vertices; + public float[] normals; + public long[] faceTypeGroups; // unsigned int[] + public long[] faceGroups; // unsigned int[] + public int[] faces; // unsigned short[] + public short[] vertexGroups; // unsigned byte[] + public long[] matrixGroups; // unsigned int[] + public long[] matrixIndices; // unsigned int[] + public long materialId = 0; + public long selectionGroup = 0; + public long selectionFlags = 0; + /** + * @since 900 + */ + public int lod = 0; + /** + * @since 900 + */ + public String lodName = ""; + public MdlxExtent extent = new MdlxExtent(); + public List sequenceExtents = new ArrayList<>(); + /** + * @since 900 + */ + public float[] tangents; + /** + * @since 900 + */ + public short[] skin; + public float[][] uvSets; + + @Override + public void readMdx(final BinaryReader reader, final int version) { + final long size = reader.readUInt32(); + + reader.readInt32(); // skip VRTX + this.vertices = reader.readFloat32Array(reader.readInt32() * 3); + reader.readInt32(); // skip NRMS + this.normals = reader.readFloat32Array(reader.readInt32() * 3); + reader.readInt32(); // skip PTYP + this.faceTypeGroups = reader.readUInt32Array(reader.readInt32()); + reader.readInt32(); // skip PCNT + this.faceGroups = reader.readUInt32Array(reader.readInt32()); + reader.readInt32(); // skip PVTX + this.faces = reader.readUInt16Array(reader.readInt32()); + reader.readInt32(); // skip GNDX + this.vertexGroups = reader.readUInt8Array(reader.readInt32()); + reader.readInt32(); // skip MTGC + this.matrixGroups = reader.readUInt32Array(reader.readInt32()); + reader.readInt32(); // skip MATS + this.matrixIndices = reader.readUInt32Array(reader.readInt32()); + this.materialId = reader.readUInt32(); + this.selectionGroup = reader.readUInt32(); + this.selectionFlags = reader.readUInt32(); + + if (version > 800) { + this.lod = reader.readInt32(); + this.lodName = reader.read(80); + } + + this.extent.readMdx(reader); + + final long numExtents = reader.readUInt32(); + + for (int i = 0; i < numExtents; i++) { + final MdlxExtent extent = new MdlxExtent(); + extent.readMdx(reader); + this.sequenceExtents.add(extent); + } + + int id = reader.readTag(); // TANG or SKIN or UVAS + + if ((version > 800) && (id != UVAS.getValue())) { + if (id == TANG.getValue()) { + this.tangents = reader.readFloat32Array(reader.readInt32() * 4); + + id = reader.readTag(); // SKIN or UVAS + } + + if (id == SKIN.getValue()) { + this.skin = reader.readUInt8Array(reader.readInt32()); + + id = reader.readInt32(); // UVAS + } + } + + final long numUVLayers = reader.readUInt32(); + this.uvSets = new float[(int) numUVLayers][]; + for (int i = 0; i < numUVLayers; i++) { + reader.readInt32(); // skip UVBS + this.uvSets[i] = reader.readFloat32Array(reader.readInt32() * 2); + } + } + + @Override + public void writeMdx(final BinaryWriter writer, final int version) { + writer.writeUInt32(getByteLength(version)); + writer.writeTag(VRTX.getValue()); + writer.writeUInt32(this.vertices.length / 3); + writer.writeFloat32Array(this.vertices); + writer.writeTag(NRMS.getValue()); + writer.writeUInt32(this.normals.length / 3); + writer.writeFloat32Array(this.normals); + writer.writeTag(PTYP.getValue()); + writer.writeUInt32(this.faceTypeGroups.length); + writer.writeUInt32Array(this.faceTypeGroups); + writer.writeTag(PCNT.getValue()); + writer.writeUInt32(this.faceGroups.length); + writer.writeUInt32Array(this.faceGroups); + writer.writeTag(PVTX.getValue()); + writer.writeUInt32(this.faces.length); + writer.writeUInt16Array(this.faces); + writer.writeTag(GNDX.getValue()); + writer.writeUInt32(this.vertexGroups.length); + writer.writeUInt8Array(this.vertexGroups); + writer.writeTag(MTGC.getValue()); + writer.writeUInt32(this.matrixGroups.length); + writer.writeUInt32Array(this.matrixGroups); + writer.writeTag(MATS.getValue()); + writer.writeUInt32(this.matrixIndices.length); + writer.writeUInt32Array(this.matrixIndices); + writer.writeUInt32(this.materialId); + writer.writeUInt32(this.selectionGroup); + writer.writeUInt32(this.selectionFlags); + + if (version > 800) { + writer.writeInt32(this.lod); + writer.writeWithNulls(this.lodName, 80); + } + + this.extent.writeMdx(writer); + writer.writeUInt32(this.sequenceExtents.size()); + + for (final MdlxExtent sequenceExtent : this.sequenceExtents) { + sequenceExtent.writeMdx(writer); + } + + if (version > 800) { + if (this.tangents != null) { + writer.writeTag(TANG.getValue()); + writer.writeUInt32(this.tangents.length / 4); + writer.writeFloat32Array(this.tangents); + } + + if (this.skin != null) { + writer.writeTag(SKIN.getValue()); + writer.writeUInt32(this.skin.length); + writer.writeUInt8Array(this.skin); + } + } + + writer.writeTag(UVAS.getValue()); + writer.writeUInt32(this.uvSets.length); + + for (final float[] uvSet : this.uvSets) { + writer.writeTag(UVBS.getValue()); + writer.writeUInt32(uvSet.length / 2); + writer.writeFloat32Array(uvSet); + } + } + + @Override + public void readMdl(final MdlTokenInputStream stream, final int version) { + this.uvSets = new float[0][]; + + for (final String token : stream.readBlock()) { + // For now hardcoded for triangles, until I see a model with something + // different. + switch (token) { + case MdlUtils.TOKEN_VERTICES: + this.vertices = stream.readVectorArray(new float[stream.readInt() * 3], 3); + break; + case MdlUtils.TOKEN_NORMALS: + this.normals = stream.readVectorArray(new float[stream.readInt() * 3], 3); + break; + case MdlUtils.TOKEN_TVERTICES: { + this.uvSets = Arrays.copyOf(this.uvSets, this.uvSets.length + 1); + this.uvSets[this.uvSets.length - 1] = stream.readVectorArray(new float[stream.readInt() * 2], 2); + } + break; + case MdlUtils.TOKEN_VERTEX_GROUP: { + // Vertex groups are stored in a block with no count, can't allocate the buffer + // yet. + final List vertexGroups = new ArrayList<>(); + for (final String vertexGroup : stream.readBlock()) { + vertexGroups.add(Short.valueOf(vertexGroup)); + } + + this.vertexGroups = new short[vertexGroups.size()]; + int i = 0; + for (final Short vertexGroup : vertexGroups) { + this.vertexGroups[i++] = vertexGroup; + } + } + break; + case "Tangents": { + final int tansCount = (int) stream.readUInt32(); + this.tangents = new float[tansCount * 4]; + stream.readVectorArray(this.tangents, 4); + } + break; + case "SkinWeights": { + final int skinCount = (int) stream.readUInt32(); + this.skin = new short[skinCount * 8]; + stream.readUInt8Array(this.skin); + } + break; + case MdlUtils.TOKEN_FACES: { + this.faceTypeGroups = new long[] { 4L }; + stream.readInt(); // number of groups + final int count = stream.readInt(); + stream.read(); // { + stream.read(); // Triangles + stream.read(); // { + this.faces = stream.readUInt16Array(new int[count]); + this.faceGroups = new long[] { count }; + stream.read(); // } + stream.read(); // } + } + break; + case MdlUtils.TOKEN_GROUPS: { + final List indices = new ArrayList<>(); + final List groups = new ArrayList<>(); + + stream.readInt(); // matrices count + stream.readInt(); // total indices + + // eslint-disable-next-line no-unused-vars + for (final String matrix : stream.readBlock()) { + int size = 0; + + for (final String index : stream.readBlock()) { + indices.add(Integer.valueOf(index)); + size += 1; + } + groups.add(size); + } + + this.matrixIndices = new long[indices.size()]; + int i = 0; + for (final Integer index : indices) { + this.matrixIndices[i++] = index; + } + this.matrixGroups = new long[groups.size()]; + i = 0; + for (final Integer group : groups) { + this.matrixGroups[i++] = group; + } + } + break; + case MdlUtils.TOKEN_MINIMUM_EXTENT: + stream.readFloatArray(this.extent.min); + break; + case MdlUtils.TOKEN_MAXIMUM_EXTENT: + stream.readFloatArray(this.extent.max); + break; + case MdlUtils.TOKEN_BOUNDSRADIUS: + this.extent.boundsRadius = stream.readFloat(); + break; + case MdlUtils.TOKEN_ANIM: { + final MdlxExtent extent = new MdlxExtent(); + for (final String subToken : stream.readBlock()) { + switch (subToken) { + case MdlUtils.TOKEN_MINIMUM_EXTENT: + stream.readFloatArray(extent.min); + break; + case MdlUtils.TOKEN_MAXIMUM_EXTENT: + stream.readFloatArray(extent.max); + break; + case MdlUtils.TOKEN_BOUNDSRADIUS: + extent.boundsRadius = stream.readFloat(); + break; + } + } + this.sequenceExtents.add(extent); + } + break; + case MdlUtils.TOKEN_MATERIAL_ID: + this.materialId = stream.readInt(); + break; + case MdlUtils.TOKEN_SELECTION_GROUP: + this.selectionGroup = stream.readInt(); + break; + case MdlUtils.TOKEN_UNSELECTABLE: + this.selectionFlags = 4; + break; + case "LevelOfDetail": + this.lod = stream.readInt(); + break; + case "Name": + this.lodName = stream.read(); + break; + default: + throw new RuntimeException("Unknown token in Geoset: " + token); + } + } + } + + @Override + public void writeMdl(final MdlTokenOutputStream stream, final int version) { + stream.startBlock(MdlUtils.TOKEN_GEOSET); + + stream.writeVectorArray(MdlUtils.TOKEN_VERTICES, this.vertices, 3); + stream.writeVectorArray(MdlUtils.TOKEN_NORMALS, this.normals, 3); + + for (final float[] uvSet : this.uvSets) { + stream.writeVectorArray(MdlUtils.TOKEN_TVERTICES, uvSet, 2); + } + + if (version <= 800) { + stream.startBlock(MdlUtils.TOKEN_VERTEX_GROUP); + for (final short vertexGroup : this.vertexGroups) { + stream.writeLine(vertexGroup + ","); + } + stream.endBlock(); + } + + if (version > 800) { + + stream.startBlock(MdlUtils.TOKEN_VERTEX_GROUP); + if (this.skin == null) { + for (final short vertexGroup : this.vertexGroups) { + stream.writeLine(vertexGroup + ","); + } + } + stream.endBlock(); + + if (this.tangents != null) { + stream.startBlock("Tangents", this.tangents.length / 4); + + for (int i = 0, l = this.tangents.length; i < l; i += 4) { + stream.writeFloatArray(Arrays.copyOfRange(this.tangents, i, i + 4)); + } + + stream.endBlock(); + } + + if (this.skin != null) { + stream.startBlock("SkinWeights", this.skin.length / 8); + + for (int i = 0, l = this.skin.length; i < l; i += 8) { + stream.writeShortArrayRaw(Arrays.copyOfRange(this.skin, i, i + 8)); + } + + stream.endBlock(); + } + } + + // For now hardcoded for triangles, until I see a model with something + // different. + stream.startBlock(MdlUtils.TOKEN_FACES, 1, this.faces.length); + stream.startBlock(MdlUtils.TOKEN_TRIANGLES); + final StringBuilder facesBuffer = new StringBuilder(); + for (final int faceValue : this.faces) { + if (facesBuffer.length() > 0) { + facesBuffer.append(", "); + } + facesBuffer.append(faceValue); + } + stream.writeLine("{ " + facesBuffer.toString() + " },"); + stream.endBlock(); + stream.endBlock(); + + stream.startBlock(MdlUtils.TOKEN_GROUPS, this.matrixGroups.length, this.matrixIndices.length); + int index = 0; + for (final long groupSize : this.matrixGroups) { + stream.writeLongSubArrayAttrib(MdlUtils.TOKEN_MATRICES, this.matrixIndices, index, + (int) (index + groupSize)); + index += groupSize; + } + stream.endBlock(); + + this.extent.writeMdl(stream); + + for (final MdlxExtent sequenceExtent : this.sequenceExtents) { + stream.startBlock(MdlUtils.TOKEN_ANIM); + sequenceExtent.writeMdl(stream); + stream.endBlock(); + } + + stream.writeAttribUInt32("MaterialID", this.materialId); + stream.writeAttribUInt32("SelectionGroup", this.selectionGroup); + if (this.selectionFlags == 4) { + stream.writeFlag("Unselectable"); + } + + if (version > 800) { + stream.writeAttrib("LevelOfDetail", this.lod); + + if (this.lodName.length() > 0) { + stream.writeStringAttrib("Name", this.lodName); + } + } + + stream.endBlock(); + } + + @Override + public long getByteLength(final int version) { + long size = 120 + (this.vertices.length * 4) + (this.normals.length * 4) + (this.faceTypeGroups.length * 4) + + (this.faceGroups.length * 4) + (this.faces.length * 2) + this.vertexGroups.length + + (this.matrixGroups.length * 4) + (this.matrixIndices.length * 4) + (this.sequenceExtents.size() * 28); + for (final float[] uvSet : this.uvSets) { + size += 8 + (uvSet.length * 4); + } + + if (version > 800) { + size += 84; + + if (this.tangents != null) { + size += 8 + (this.tangents.length * 4); + } + + if (this.skin != null) { + size += 8 + this.skin.length; + } + } + + return size; + } + + public float[] getVertices() { + return this.vertices; + } + + public float[] getNormals() { + return this.normals; + } + + public long[] getFaceTypeGroups() { + return this.faceTypeGroups; + } + + public long[] getFaceGroups() { + return this.faceGroups; + } + + public int[] getFaces() { + return this.faces; + } + + public short[] getVertexGroups() { + return this.vertexGroups; + } + + public long[] getMatrixGroups() { + return this.matrixGroups; + } + + public long[] getMatrixIndices() { + return this.matrixIndices; + } + + public long getMaterialId() { + return this.materialId; + } + + public long getSelectionGroup() { + return this.selectionGroup; + } + + public long getSelectionFlags() { + return this.selectionFlags; + } + + public int getLod() { + return this.lod; + } + + public String getLodName() { + return this.lodName; + } + + public MdlxExtent getExtent() { + return this.extent; + } + + public List getSequenceExtents() { + return this.sequenceExtents; + } + + public float[] getTangents() { + return this.tangents; + } + + public short[] getSkin() { + return this.skin; + } + + public float[][] getUvSets() { + return this.uvSets; + } + + public void setVertices(final float[] vertices) { + this.vertices = vertices; + } + + public void setNormals(final float[] normals) { + this.normals = normals; + } + + public void setFaceTypeGroups(final long[] faceTypeGroups) { + this.faceTypeGroups = faceTypeGroups; + } + + public void setFaceGroups(final long[] faceGroups) { + this.faceGroups = faceGroups; + } + + public void setFaces(final int[] faces) { + this.faces = faces; + } + + public void setVertexGroups(final short[] vertexGroups) { + this.vertexGroups = vertexGroups; + } + + public void setMatrixGroups(final long[] matrixGroups) { + this.matrixGroups = matrixGroups; + } + + public void setMatrixIndices(final long[] matrixIndices) { + this.matrixIndices = matrixIndices; + } + + public void setMaterialId(final long materialId) { + this.materialId = materialId; + } + + public void setSelectionGroup(final long selectionGroup) { + this.selectionGroup = selectionGroup; + } + + public void setSelectionFlags(final long selectionFlags) { + this.selectionFlags = selectionFlags; + } + + public void setLod(final int lod) { + this.lod = lod; + } + + public void setLodName(final String lodName) { + this.lodName = lodName; + } + + public void setExtent(final MdlxExtent extent) { + this.extent = extent; + } + + public void setSequenceExtents(final List sequenceExtents) { + this.sequenceExtents = sequenceExtents; + } + + public void setTangents(final float[] tangents) { + this.tangents = tangents; + } + + public void setSkin(final short[] skin) { + this.skin = skin; + } + + public void setUvSets(final float[][] uvSets) { + this.uvSets = uvSets; + } +} diff --git a/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxGeosetAnimation.java b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxGeosetAnimation.java new file mode 100644 index 0000000..cf18f92 --- /dev/null +++ b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxGeosetAnimation.java @@ -0,0 +1,136 @@ +package com.hiveworkshop.rms.parsers.mdlx; + +import java.util.Iterator; + +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlTokenInputStream; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlTokenOutputStream; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlUtils; +import com.hiveworkshop.rms.util.BinaryReader; +import com.hiveworkshop.rms.util.BinaryWriter; + +public class MdlxGeosetAnimation extends MdlxAnimatedObject { + public float alpha = 1; + public int flags = 0; + public float[] color = { 1, 1, 1 }; + public int geosetId = -1; + + @Override + public void readMdx(final BinaryReader reader, final int version) { + final long size = reader.readUInt32(); + + this.alpha = reader.readFloat32(); + this.flags = reader.readInt32(); + reader.readFloat32Array(this.color); + this.geosetId = reader.readInt32(); + + readTimelines(reader, size - 28); + } + + @Override + public void writeMdx(final BinaryWriter writer, final int version) { + writer.writeUInt32(getByteLength(version)); + writer.writeFloat32(this.alpha); + writer.writeInt32(this.flags); + writer.writeFloat32Array(this.color); + writer.writeInt32(this.geosetId); + + writeTimelines(writer); + } + + @Override + public void readMdl(final MdlTokenInputStream stream, final int version) { + final Iterator blockIterator = readAnimatedBlock(stream); + while (blockIterator.hasNext()) { + final String token = blockIterator.next(); + switch (token) { + case MdlUtils.TOKEN_DROP_SHADOW: + this.flags |= 0x1; + break; + case MdlUtils.TOKEN_STATIC_ALPHA: + this.alpha = stream.readFloat(); + break; + case MdlUtils.TOKEN_ALPHA: + readTimeline(stream, AnimationMap.KGAO); + break; + case MdlUtils.TOKEN_STATIC_COLOR: { + this.flags |= 0x2; + stream.readColor(this.color); + } + break; + case MdlUtils.TOKEN_COLOR: { + this.flags |= 0x2; + readTimeline(stream, AnimationMap.KGAC); + } + break; + case MdlUtils.TOKEN_GEOSETID: + this.geosetId = stream.readInt(); + break; + default: + throw new RuntimeException("Unknown token in GeosetAnimation: " + token); + } + } + } + + @Override + public void writeMdl(final MdlTokenOutputStream stream, final int version) { + stream.startBlock(MdlUtils.TOKEN_GEOSETANIM); + + if ((this.flags & 0x1) != 0) { + stream.writeFlag(MdlUtils.TOKEN_DROP_SHADOW); + } + + if (!writeTimeline(stream, AnimationMap.KGAO)) { + stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_ALPHA, this.alpha); + } + + if ((this.flags & 0x2) != 0) { + if (!writeTimeline(stream, AnimationMap.KGAC) + && ((this.color[0] != 0) || (this.color[1] != 0) || (this.color[2] != 0))) { + stream.writeColor(MdlUtils.TOKEN_STATIC_COLOR, this.color); + } + } + + if (this.geosetId != -1) { // TODO Retera added -1 check here, why wasn't it there before in JS??? + stream.writeAttrib(MdlUtils.TOKEN_GEOSETID, this.geosetId); + } + + stream.endBlock(); + } + + @Override + public long getByteLength(final int version) { + return 28 + super.getByteLength(version); + } + + public float getAlpha() { + return this.alpha; + } + + public int getFlags() { + return this.flags; + } + + public float[] getColor() { + return this.color; + } + + public int getGeosetId() { + return this.geosetId; + } + + public void setAlpha(final float alpha) { + this.alpha = alpha; + } + + public void setFlags(final int flags) { + this.flags = flags; + } + + public void setColor(final float[] color) { + this.color = color; + } + + public void setGeosetId(final int geosetId) { + this.geosetId = geosetId; + } +} diff --git a/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxHelper.java b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxHelper.java new file mode 100644 index 0000000..962b020 --- /dev/null +++ b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxHelper.java @@ -0,0 +1,28 @@ +package com.hiveworkshop.rms.parsers.mdlx; + +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlTokenInputStream; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlTokenOutputStream; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlUtils; + +public class MdlxHelper extends MdlxGenericObject { + public MdlxHelper() { + super(0x0); // NOTE: ghostwolf JS didn't pass the 0x1 flag???? + // ANOTHER NOTE: setting the 0x1 flag causes other fan programs to spam error + // messages + } + + @Override + public void readMdl(final MdlTokenInputStream stream, final int version) { + for (final String token : readMdlGeneric(stream)) { + throw new RuntimeException("Unknown token in Helper: " + token); + } + } + + @Override + public void writeMdl(final MdlTokenOutputStream stream, final int version) { + stream.startObjectBlock(MdlUtils.TOKEN_HELPER, name); + writeGenericHeader(stream); + writeGenericTimelines(stream); + stream.endBlock(); + } +} diff --git a/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxLayer.java b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxLayer.java new file mode 100644 index 0000000..0f54476 --- /dev/null +++ b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxLayer.java @@ -0,0 +1,369 @@ +package com.hiveworkshop.rms.parsers.mdlx; + +import java.util.Iterator; + +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlTokenInputStream; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlTokenOutputStream; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlUtils; +import com.hiveworkshop.rms.util.BinaryReader; +import com.hiveworkshop.rms.util.BinaryWriter; + +public class MdlxLayer extends MdlxAnimatedObject { + public enum FilterMode { + NONE("None"), + TRANSPARENT("Transparent"), + BLEND("Blend"), + ADDITIVE("Additive"), + ADDALPHA("AddAlpha"), + MODULATE("Modulate"), + MODULATE2X("Modulate2x"); + + String token; + + FilterMode(final String token) { + this.token = token; + } + + public static FilterMode fromId(final int id) { + return values()[id]; + } + + public static int nameToId(final String name) { + for (final FilterMode mode : values()) { + if (mode.token.equals(name)) { + return mode.ordinal(); + } + } + return -1; + } + + public static FilterMode nameToFilter(final String name) { + for (final FilterMode mode : values()) { + if (mode.token.equals(name)) { + return mode; + } + } + return null; + } + + @Override + public String toString() { + return this.token; + } + } + + public FilterMode filterMode = FilterMode.NONE; + public int flags = 0; + public int textureId = -1; + public int textureAnimationId = -1; + public long coordId = 0; + public float alpha = 1; + /** + * @since 900 + */ + public float emissiveGain = 1; + /** + * @since 1000 + */ + public float[] fresnelColor = new float[] { 1, 1, 1 }; + /** + * @since 1000 + */ + public float fresnelOpacity = 0; + /** + * @since 1000 + */ + public float fresnelTeamColor = 0; + + @Override + public void readMdx(final BinaryReader reader, final int version) { + final int position = reader.position(); + final long size = reader.readUInt32(); + + this.filterMode = FilterMode.fromId(reader.readInt32()); + this.flags = reader.readInt32(); // UInt32 in JS + this.textureId = reader.readInt32(); + this.textureAnimationId = reader.readInt32(); + this.coordId = reader.readInt32(); + this.alpha = reader.readFloat32(); + + if (version > 800) { + this.emissiveGain = reader.readFloat32(); + + if (version > 900) { + reader.readFloat32Array(this.fresnelColor); + this.fresnelOpacity = reader.readFloat32(); + this.fresnelTeamColor = reader.readFloat32(); + } + } + + readTimelines(reader, size - (reader.position() - position)); + } + + @Override + public void writeMdx(final BinaryWriter writer, final int version) { + writer.writeUInt32(getByteLength(version)); + writer.writeUInt32(this.filterMode.ordinal()); + writer.writeUInt32(this.flags); + writer.writeInt32(this.textureId); + writer.writeInt32(this.textureAnimationId); + writer.writeUInt32(this.coordId); + writer.writeFloat32(this.alpha); + + if (version > 800) { + writer.writeFloat32(this.emissiveGain); + + if (version > 900) { + writer.writeFloat32Array(this.fresnelColor); + writer.writeFloat32(this.fresnelOpacity); + writer.writeFloat32(this.fresnelTeamColor); + } + } + + writeTimelines(writer); + } + + @Override + public void readMdl(final MdlTokenInputStream stream, final int version) { + final Iterator iterator = readAnimatedBlock(stream); + while (iterator.hasNext()) { + final String token = iterator.next(); + switch (token) { + case MdlUtils.TOKEN_FILTER_MODE: + this.filterMode = FilterMode.fromId(FilterMode.nameToId(stream.read())); + break; + case MdlUtils.TOKEN_UNSHADED: + this.flags |= 0x1; + break; + case MdlUtils.TOKEN_SPHERE_ENV_MAP: + this.flags |= 0x2; + break; + case MdlUtils.TOKEN_TWO_SIDED: + this.flags |= 0x10; + break; + case MdlUtils.TOKEN_UNFOGGED: + this.flags |= 0x20; + break; + case MdlUtils.TOKEN_NO_DEPTH_TEST: + this.flags |= 0x40; + break; + case MdlUtils.TOKEN_NO_DEPTH_SET: + this.flags |= 0x80; + break; + case "Unlit": + this.flags |= 0x100; + case MdlUtils.TOKEN_STATIC_TEXTURE_ID: + this.textureId = stream.readInt(); + break; + case MdlUtils.TOKEN_TEXTURE_ID: + readTimeline(stream, AnimationMap.KMTF); + break; + case MdlUtils.TOKEN_TVERTEX_ANIM_ID: + this.textureAnimationId = stream.readInt(); + break; + case MdlUtils.TOKEN_COORD_ID: + this.coordId = stream.readInt(); + break; + case MdlUtils.TOKEN_STATIC_ALPHA: + this.alpha = stream.readFloat(); + break; + case MdlUtils.TOKEN_ALPHA: + readTimeline(stream, AnimationMap.KMTA); + break; + case "static EmissiveGain": + this.emissiveGain = stream.readFloat(); + break; + case "EmissiveGain": + readTimeline(stream, AnimationMap.KMTE); + break; + case "static FresnelColor": + stream.readColor(this.fresnelColor); + break; + case "FresnelColor": + readTimeline(stream, AnimationMap.KFC3); + break; + case "static FresnelOpacity": + this.fresnelOpacity = stream.readFloat(); + break; + case "FresnelOpacity": + readTimeline(stream, AnimationMap.KFCA); + break; + case "static FresnelTeamColor": + this.fresnelTeamColor = stream.readFloat(); + break; + case "FresnelTeamColor": + readTimeline(stream, AnimationMap.KFTC); + break; + default: + throw new RuntimeException("Unknown token in Layer: " + token); + } + } + } + + @Override + public void writeMdl(final MdlTokenOutputStream stream, final int version) { + stream.startBlock(MdlUtils.TOKEN_LAYER); + + stream.writeAttrib(MdlUtils.TOKEN_FILTER_MODE, this.filterMode.toString()); + + if ((this.flags & 0x1) != 0) { + stream.writeFlag(MdlUtils.TOKEN_UNSHADED); + } + + if ((this.flags & 0x2) != 0) { + stream.writeFlag(MdlUtils.TOKEN_SPHERE_ENV_MAP); + } + + if ((this.flags & 0x10) != 0) { + stream.writeFlag(MdlUtils.TOKEN_TWO_SIDED); + } + + if ((this.flags & 0x20) != 0) { + stream.writeFlag(MdlUtils.TOKEN_UNFOGGED); + } + + if ((this.flags & 0x40) != 0) { + stream.writeFlag(MdlUtils.TOKEN_NO_DEPTH_TEST); + } + + if ((this.flags & 0x100) != 0) { + stream.writeFlag(MdlUtils.TOKEN_NO_DEPTH_SET); + } + + if ((version > 800) && ((this.flags & 0x100) != 0)) { + stream.writeFlag("Unlit"); + } + + if (!writeTimeline(stream, AnimationMap.KMTF)) { + stream.writeAttrib(MdlUtils.TOKEN_STATIC_TEXTURE_ID, this.textureId); + } + + if (this.textureAnimationId != -1) { + stream.writeAttrib(MdlUtils.TOKEN_TVERTEX_ANIM_ID, this.textureAnimationId); + } + + if (this.coordId != 0) { + stream.writeAttribUInt32(MdlUtils.TOKEN_COORD_ID, this.coordId); + } + + if (!writeTimeline(stream, AnimationMap.KMTA) && (this.alpha != 1)) { + stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_ALPHA, this.alpha); + } + + if (version > 800) { + if (!writeTimeline(stream, AnimationMap.KMTE) && (this.emissiveGain != 1)) { + stream.writeFloatAttrib("static EmissiveGain", this.emissiveGain); + } + + if (!writeTimeline(stream, AnimationMap.KFC3) + && ((this.fresnelColor[0] != 1) || (this.fresnelColor[1] != 1) || (this.fresnelColor[2] != 1))) { + stream.writeFloatArrayAttrib("static FresnelColor", this.fresnelColor); + } + + if (!writeTimeline(stream, AnimationMap.KFCA) && (this.fresnelOpacity != 0)) { + stream.writeFloatAttrib("static FresnelOpacity", this.fresnelOpacity); + } + + if (!writeTimeline(stream, AnimationMap.KFTC) && (this.fresnelTeamColor != 0)) { + stream.writeFloatAttrib("static FresnelTeamColor", this.fresnelTeamColor); + } + } + + stream.endBlock(); + } + + @Override + public long getByteLength(final int version) { + long byteLength = 28 + super.getByteLength(version); + + if (version > 800) { + byteLength += 4; + + if (version > 900) { + byteLength += 20; + } + } + + return byteLength; + } + + public FilterMode getFilterMode() { + return this.filterMode; + } + + public int getFlags() { + return this.flags; + } + + public int getTextureId() { + return this.textureId; + } + + public int getTextureAnimationId() { + return this.textureAnimationId; + } + + public long getCoordId() { + return this.coordId; + } + + public float getAlpha() { + return this.alpha; + } + + public float getEmissiveGain() { + return this.emissiveGain; + } + + public float[] getFresnelColor() { + return this.fresnelColor; + } + + public float getFresnelOpacity() { + return this.fresnelOpacity; + } + + public float getFresnelTeamColor() { + return this.fresnelTeamColor; + } + + public void setFilterMode(final FilterMode filterMode) { + this.filterMode = filterMode; + } + + public void setFlags(final int flags) { + this.flags = flags; + } + + public void setTextureId(final int textureId) { + this.textureId = textureId; + } + + public void setTextureAnimationId(final int textureAnimationId) { + this.textureAnimationId = textureAnimationId; + } + + public void setCoordId(final long coordId) { + this.coordId = coordId; + } + + public void setAlpha(final float alpha) { + this.alpha = alpha; + } + + public void setEmissiveGain(final float emissiveGain) { + this.emissiveGain = emissiveGain; + } + + public void setFresnelColor(final float[] fresnelColor) { + this.fresnelColor = fresnelColor; + } + + public void setFresnelOpacity(final float fresnelOpacity) { + this.fresnelOpacity = fresnelOpacity; + } + + public void setFresnelTeamColor(final float fresnelTeamColor) { + this.fresnelTeamColor = fresnelTeamColor; + } +} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/Light.java b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxLight.java similarity index 50% rename from core/src/com/etheller/warsmash/parsers/mdlx/Light.java rename to core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxLight.java index 19f8dd5..1a65685 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/Light.java +++ b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxLight.java @@ -1,69 +1,89 @@ -package com.etheller.warsmash.parsers.mdlx; +package com.hiveworkshop.rms.parsers.mdlx; -import java.io.IOException; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlTokenInputStream; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlTokenOutputStream; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlUtils; +import com.hiveworkshop.rms.util.BinaryReader; +import com.hiveworkshop.rms.util.BinaryWriter; -import com.etheller.warsmash.util.MdlUtils; -import com.etheller.warsmash.util.ParseUtils; -import com.google.common.io.LittleEndianDataInputStream; -import com.google.common.io.LittleEndianDataOutputStream; +public class MdlxLight extends MdlxGenericObject { + public enum Type { + OMNIDIRECTIONAL("Omnidirectional"), + DIRECTIONAL("Directional"), + AMBIENT("Ambient"); -public class Light extends GenericObject { + String token; - private int type = -1; - private final float[] attenuation = new float[2]; - private final float[] color = new float[3]; - private float intensity = 0; - private final float[] ambientColor = new float[3]; - private float ambientIntensity = 0; + Type(final String token) { + this.token = token; + } - public Light() { + public static Type fromId(final int id) { + return values()[id]; + } + + @Override + public String toString() { + return this.token; + } + } + + public Type type = Type.OMNIDIRECTIONAL; + public float[] attenuation = new float[2]; + public float[] color = new float[3]; + public float intensity = 0; + public float[] ambientColor = new float[3]; + public float ambientIntensity = 0; + + public MdlxLight() { super(0x200); } @Override - public void readMdx(final LittleEndianDataInputStream stream) throws IOException { - final long size = ParseUtils.readUInt32(stream); + public void readMdx(final BinaryReader reader, final int version) { + final int position = reader.position(); + final long size = reader.readUInt32(); - super.readMdx(stream); + super.readMdx(reader, version); - this.type = stream.readInt(); // UInt32 in JS - ParseUtils.readFloatArray(stream, this.attenuation); - ParseUtils.readFloatArray(stream, this.color); - this.intensity = stream.readFloat(); - ParseUtils.readFloatArray(stream, this.ambientColor); - this.ambientIntensity = stream.readFloat(); + this.type = Type.fromId(reader.readInt32()); + reader.readFloat32Array(this.attenuation); + reader.readFloat32Array(this.color); + this.intensity = reader.readFloat32(); + reader.readFloat32Array(this.ambientColor); + this.ambientIntensity = reader.readFloat32(); - readTimelines(stream, size - this.getByteLength()); + readTimelines(reader, size - (reader.position() - position)); } @Override - public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException { - ParseUtils.writeUInt32(stream, getByteLength()); + public void writeMdx(final BinaryWriter writer, final int version) { + writer.writeUInt32(getByteLength(version)); - super.writeMdx(stream); + super.writeMdx(writer, version); - ParseUtils.writeUInt32(stream, this.type); - ParseUtils.writeFloatArray(stream, this.attenuation); - ParseUtils.writeFloatArray(stream, this.color); - stream.writeFloat(this.intensity); - ParseUtils.writeFloatArray(stream, this.ambientColor); - stream.writeFloat(this.ambientIntensity); + writer.writeUInt32(this.type.ordinal()); + writer.writeFloat32Array(this.attenuation); + writer.writeFloat32Array(this.color); + writer.writeFloat32(this.intensity); + writer.writeFloat32Array(this.ambientColor); + writer.writeFloat32(this.ambientIntensity); - writeNonGenericAnimationChunks(stream); + writeNonGenericAnimationChunks(writer); } @Override - public void readMdl(final MdlTokenInputStream stream) throws IOException { + public void readMdl(final MdlTokenInputStream stream, final int version) { for (final String token : super.readMdlGeneric(stream)) { switch (token) { case MdlUtils.TOKEN_OMNIDIRECTIONAL: - this.type = 0; + this.type = Type.OMNIDIRECTIONAL; break; case MdlUtils.TOKEN_DIRECTIONAL: - this.type = 1; + this.type = Type.DIRECTIONAL; break; case MdlUtils.TOKEN_AMBIENT: - this.type = 2; + this.type = Type.AMBIENT; break; case MdlUtils.TOKEN_STATIC_ATTENUATION_START: this.attenuation[0] = stream.readFloat(); @@ -105,29 +125,17 @@ public class Light extends GenericObject { readTimeline(stream, AnimationMap.KLAV); break; default: - throw new IllegalStateException("Unknown token in Light: " + token); + throw new RuntimeException("Unknown token in Light: " + token); } } } @Override - public void writeMdl(final MdlTokenOutputStream stream) throws IOException { + public void writeMdl(final MdlTokenOutputStream stream, final int version) { stream.startObjectBlock(MdlUtils.TOKEN_LIGHT, this.name); writeGenericHeader(stream); - switch (this.type) { - case 0: - stream.writeFlag(MdlUtils.TOKEN_OMNIDIRECTIONAL); - break; - case 1: - stream.writeFlag(MdlUtils.TOKEN_DIRECTIONAL); - break; - case 2: - stream.writeFlag(MdlUtils.TOKEN_AMBIENT); - break; - default: - throw new IllegalStateException("Unable to save Light of type: " + this.type); - } + stream.writeFlag(this.type.toString()); if (!writeTimeline(stream, AnimationMap.KLAS)) { stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_ATTENUATION_START, this.attenuation[0]); @@ -160,11 +168,11 @@ public class Light extends GenericObject { } @Override - public long getByteLength() { - return 48 + super.getByteLength(); + public long getByteLength(final int version) { + return 48 + super.getByteLength(version); } - public int getType() { + public Type getType() { return this.type; } @@ -188,4 +196,27 @@ public class Light extends GenericObject { return this.ambientIntensity; } + public void setType(final Type type) { + this.type = type; + } + + public void setAttenuation(final float[] attenuation) { + this.attenuation = attenuation; + } + + public void setColor(final float[] color) { + this.color = color; + } + + public void setIntensity(final float intensity) { + this.intensity = intensity; + } + + public void setAmbientColor(final float[] ambientColor) { + this.ambientColor = ambientColor; + } + + public void setAmbientIntensity(final float ambientIntensity) { + this.ambientIntensity = ambientIntensity; + } } diff --git a/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxMaterial.java b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxMaterial.java new file mode 100644 index 0000000..e7e013b --- /dev/null +++ b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxMaterial.java @@ -0,0 +1,173 @@ +package com.hiveworkshop.rms.parsers.mdlx; + +import java.util.ArrayList; +import java.util.List; + +import com.etheller.warsmash.util.War3ID; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlTokenInputStream; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlTokenOutputStream; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlUtils; +import com.hiveworkshop.rms.util.BinaryReader; +import com.hiveworkshop.rms.util.BinaryWriter; + +public class MdlxMaterial implements MdlxBlock, MdlxChunk { + public static final War3ID LAYS = War3ID.fromString("LAYS"); + public int priorityPlane = 0; + public int flags; + /** + * @since 900 + */ + public String shader = ""; + public final List layers = new ArrayList<>(); + + @Override + public void readMdx(final BinaryReader reader, final int version) { + reader.readUInt32(); // Don't care about the size + + this.priorityPlane = reader.readInt32(); + this.flags = reader.readInt32(); + + if (version > 800) { + this.shader = reader.read(80); + } + + reader.readInt32(); // skip LAYS + + final long layerCount = reader.readUInt32(); + for (int i = 0; i < layerCount; i++) { + final MdlxLayer layer = new MdlxLayer(); + layer.readMdx(reader, version); + this.layers.add(layer); + } + } + + @Override + public void writeMdx(final BinaryWriter writer, final int version) { + writer.writeUInt32(getByteLength(version)); + writer.writeInt32(this.priorityPlane); + writer.writeInt32(this.flags); + + if (version > 800) { + writer.writeWithNulls(this.shader, 80); + } + + writer.writeTag(LAYS.getValue()); + writer.writeUInt32(this.layers.size()); + + for (final MdlxLayer layer : this.layers) { + layer.writeMdx(writer, version); + } + } + + @Override + public void readMdl(final MdlTokenInputStream stream, final int version) { + for (final String token : stream.readBlock()) { + switch (token) { + case MdlUtils.TOKEN_CONSTANT_COLOR: + this.flags |= 0x1; + break; + case MdlUtils.TOKEN_SORT_PRIMS_NEAR_Z: + this.flags |= 0x8; + break; + case MdlUtils.TOKEN_SORT_PRIMS_FAR_Z: + this.flags |= 0x10; + break; + case MdlUtils.TOKEN_FULL_RESOLUTION: + this.flags |= 0x20; + break; + case MdlUtils.TOKEN_PRIORITY_PLANE: + this.priorityPlane = stream.readInt(); + break; + case "Shader": + this.shader = stream.read(); + break; + case MdlUtils.TOKEN_LAYER: { + final MdlxLayer layer = new MdlxLayer(); + layer.readMdl(stream, version); + this.layers.add(layer); + } + break; + default: + throw new RuntimeException("Unknown token in Material: " + token); + } + } + } + + @Override + public void writeMdl(final MdlTokenOutputStream stream, final int version) { + stream.startBlock(MdlUtils.TOKEN_MATERIAL); + + if ((this.flags & 0x1) != 0) { + stream.writeFlag(MdlUtils.TOKEN_CONSTANT_COLOR); + } + + if ((this.flags & 0x8) != 0) { + stream.writeFlag(MdlUtils.TOKEN_SORT_PRIMS_NEAR_Z); + } + + if ((this.flags & 0x10) != 0) { + stream.writeFlag(MdlUtils.TOKEN_SORT_PRIMS_FAR_Z); + } + + if ((this.flags & 0x20) != 0) { + stream.writeFlag(MdlUtils.TOKEN_FULL_RESOLUTION); + } + + if (this.priorityPlane != 0) { + stream.writeAttrib(MdlUtils.TOKEN_PRIORITY_PLANE, this.priorityPlane); + } + + if (version > 800) { + stream.writeStringAttrib("Shader", this.shader); + } + + for (final MdlxLayer layer : this.layers) { + layer.writeMdl(stream, version); + } + + stream.endBlock(); + } + + @Override + public long getByteLength(final int version) { + long size = 20; + + if (version > 800) { + size += 80; + } + + for (final MdlxLayer layer : this.layers) { + size += layer.getByteLength(version); + } + + return size; + } + + public int getPriorityPlane() { + return this.priorityPlane; + } + + public int getFlags() { + return this.flags; + } + + public String getShader() { + return this.shader; + } + + public List getLayers() { + return this.layers; + } + + public void setPriorityPlane(final int priorityPlane) { + this.priorityPlane = priorityPlane; + } + + public void setFlags(final int flags) { + this.flags = flags; + } + + public void setShader(final String shader) { + this.shader = shader; + } +} diff --git a/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxModel.java b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxModel.java new file mode 100644 index 0000000..6929b1b --- /dev/null +++ b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxModel.java @@ -0,0 +1,967 @@ +package com.hiveworkshop.rms.parsers.mdlx; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +import com.etheller.warsmash.util.War3ID; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlTokenInputStream; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlTokenOutputStream; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlUtils; +import com.hiveworkshop.rms.util.BinaryReader; +import com.hiveworkshop.rms.util.BinaryWriter; + +/** + * A Warcraft 3 model. Supports loading from and saving to both the binary MDX + * and text MDL file formats. + */ +public class MdlxModel { + // Below, these can't call a function on a string to make their value + // because + // switch/case statements require the value to be compile-time defined in + // order + // to be legal, and it appears to only allow basic binary operators for + // that. + // I would love a clearer way to just type 'MDLX' in a character constant in + // Java for this + private static final int MDLX = ('M' << 24) | ('D' << 16) | ('L' << 8) | ('X');// War3ID.fromString("MDLX").getValue(); + private static final int VERS = ('V' << 24) | ('E' << 16) | ('R' << 8) | ('S');// War3ID.fromString("VERS").getValue(); + private static final int MODL = ('M' << 24) | ('O' << 16) | ('D' << 8) | ('L');// War3ID.fromString("MODL").getValue(); + private static final int SEQS = ('S' << 24) | ('E' << 16) | ('Q' << 8) | ('S');// War3ID.fromString("SEQS").getValue(); + private static final int GLBS = ('G' << 24) | ('L' << 16) | ('B' << 8) | ('S');// War3ID.fromString("GLBS").getValue(); + private static final int MTLS = ('M' << 24) | ('T' << 16) | ('L' << 8) | ('S');// War3ID.fromString("MTLS").getValue(); + private static final int TEXS = ('T' << 24) | ('E' << 16) | ('X' << 8) | ('S');// War3ID.fromString("TEXS").getValue(); + private static final int TXAN = ('T' << 24) | ('X' << 16) | ('A' << 8) | ('N');// War3ID.fromString("TXAN").getValue(); + private static final int GEOS = ('G' << 24) | ('E' << 16) | ('O' << 8) | ('S');// War3ID.fromString("GEOS").getValue(); + private static final int GEOA = ('G' << 24) | ('E' << 16) | ('O' << 8) | ('A');// War3ID.fromString("GEOA").getValue(); + private static final int BONE = ('B' << 24) | ('O' << 16) | ('N' << 8) | ('E');// War3ID.fromString("BONE").getValue(); + private static final int LITE = ('L' << 24) | ('I' << 16) | ('T' << 8) | ('E');// War3ID.fromString("LITE").getValue(); + private static final int HELP = ('H' << 24) | ('E' << 16) | ('L' << 8) | ('P');// War3ID.fromString("HELP").getValue(); + private static final int ATCH = ('A' << 24) | ('T' << 16) | ('C' << 8) | ('H');// War3ID.fromString("ATCH").getValue(); + private static final int PIVT = ('P' << 24) | ('I' << 16) | ('V' << 8) | ('T');// War3ID.fromString("PIVT").getValue(); + private static final int PREM = ('P' << 24) | ('R' << 16) | ('E' << 8) | ('M');// War3ID.fromString("PREM").getValue(); + private static final int PRE2 = ('P' << 24) | ('R' << 16) | ('E' << 8) | ('2');// War3ID.fromString("PRE2").getValue(); + private static final int CORN = ('C' << 24) | ('O' << 16) | ('R' << 8) | ('N');// War3ID.fromString("CORN").getValue(); + private static final int RIBB = ('R' << 24) | ('I' << 16) | ('B' << 8) | ('B');// War3ID.fromString("RIBB").getValue(); + private static final int CAMS = ('C' << 24) | ('A' << 16) | ('M' << 8) | ('S');// War3ID.fromString("CAMS").getValue(); + private static final int EVTS = ('E' << 24) | ('V' << 16) | ('T' << 8) | ('S');// War3ID.fromString("EVTS").getValue(); + private static final int CLID = ('C' << 24) | ('L' << 16) | ('I' << 8) | ('D');// War3ID.fromString("CLID").getValue(); + private static final int FAFX = ('F' << 24) | ('A' << 16) | ('F' << 8) | ('X');// War3ID.fromString("FAFX").getValue(); + private static final int BPOS = ('B' << 24) | ('P' << 16) | ('O' << 8) | ('S');// War3ID.fromString("BPOS").getValue(); + + public int version = 800; + public String name = ""; + /** + * (Comment copied from Ghostwolf JS) To the best of my knowledge, this should + * always be left empty. This is probably a leftover from the Warcraft 3 beta. + * (WS game note: No, I never saw any animation files in the RoC 2001-2002 Beta. + * So it must be from the Alpha) + * + * @member {string} + */ + public String animationFile = ""; + public MdlxExtent extent = new MdlxExtent(); + public long blendTime = 0; + public List sequences = new ArrayList<>(); + public List globalSequences = new ArrayList<>(); + public List materials = new ArrayList<>(); + public List textures = new ArrayList<>(); + public List textureAnimations = new ArrayList<>(); + public List geosets = new ArrayList<>(); + public List geosetAnimations = new ArrayList<>(); + public List bones = new ArrayList<>(); + public List lights = new ArrayList<>(); + public List helpers = new ArrayList<>(); + public List attachments = new ArrayList<>(); + public List pivotPoints = new ArrayList<>(); + public List particleEmitters = new ArrayList<>(); + public List particleEmitters2 = new ArrayList<>(); + public List particleEmittersPopcorn = new ArrayList<>(); + public List ribbonEmitters = new ArrayList<>(); + public List cameras = new ArrayList<>(); + public List eventObjects = new ArrayList<>(); + public List collisionShapes = new ArrayList<>(); + /** + * @since 900 + */ + public List faceEffects = new ArrayList<>(); + /** + * @since 900 + */ + public List bindPose = new ArrayList<>(); + public List unknownChunks = new ArrayList<>(); + + public MdlxModel() { + + } + + public MdlxModel(final ByteBuffer buffer) { + load(buffer); + } + + public void load(final ByteBuffer buffer) { + // MDX files start with "MDLX". + if ((buffer.get(0) == 77) && (buffer.get(1) == 68) && (buffer.get(2) == 76) && (buffer.get(3) == 88)) { + loadMdx(buffer); + } + else { + loadMdl(buffer); + } + } + + public void loadMdx(final ByteBuffer buffer) { + final BinaryReader reader = new BinaryReader(buffer); + + if (reader.readTag() != MDLX) { + throw new IllegalStateException("WrongMagicNumber"); + } + + while (reader.remaining() > 0) { + final int tag = reader.readTag(); + final int size = reader.readInt32(); + + switch (tag) { + case VERS: + loadVersionChunk(reader); + break; + case MODL: + loadModelChunk(reader); + break; + case SEQS: + loadStaticObjects(this.sequences, MdlxBlockDescriptor.SEQUENCE, reader, size / 132); + break; + case GLBS: + loadGlobalSequenceChunk(reader, size); + break; + case MTLS: + loadDynamicObjects(this.materials, MdlxBlockDescriptor.MATERIAL, reader, size); + break; + case TEXS: + loadStaticObjects(this.textures, MdlxBlockDescriptor.TEXTURE, reader, size / 268); + break; + case TXAN: + loadDynamicObjects(this.textureAnimations, MdlxBlockDescriptor.TEXTURE_ANIMATION, reader, size); + break; + case GEOS: + loadDynamicObjects(this.geosets, MdlxBlockDescriptor.GEOSET, reader, size); + break; + case GEOA: + loadDynamicObjects(this.geosetAnimations, MdlxBlockDescriptor.GEOSET_ANIMATION, reader, size); + break; + case BONE: + loadDynamicObjects(this.bones, MdlxBlockDescriptor.BONE, reader, size); + break; + case LITE: + loadDynamicObjects(this.lights, MdlxBlockDescriptor.LIGHT, reader, size); + break; + case HELP: + loadDynamicObjects(this.helpers, MdlxBlockDescriptor.HELPER, reader, size); + break; + case ATCH: + loadDynamicObjects(this.attachments, MdlxBlockDescriptor.ATTACHMENT, reader, size); + break; + case PIVT: + loadPivotPointChunk(reader, size); + break; + case PREM: + loadDynamicObjects(this.particleEmitters, MdlxBlockDescriptor.PARTICLE_EMITTER, reader, size); + break; + case PRE2: + loadDynamicObjects(this.particleEmitters2, MdlxBlockDescriptor.PARTICLE_EMITTER2, reader, size); + break; + case CORN: + loadDynamicObjects(this.particleEmittersPopcorn, MdlxBlockDescriptor.PARTICLE_EMITTER_POPCORN, reader, + size); + break; + case RIBB: + loadDynamicObjects(this.ribbonEmitters, MdlxBlockDescriptor.RIBBON_EMITTER, reader, size); + break; + case CAMS: + loadDynamicObjects(this.cameras, MdlxBlockDescriptor.CAMERA, reader, size); + break; + case EVTS: + loadDynamicObjects(this.eventObjects, MdlxBlockDescriptor.EVENT_OBJECT, reader, size); + break; + case CLID: + loadDynamicObjects(this.collisionShapes, MdlxBlockDescriptor.COLLISION_SHAPE, reader, size); + break; + case FAFX: + loadStaticObjects(this.faceEffects, MdlxBlockDescriptor.FACE_EFFECT, reader, size / 340); + break; + case BPOS: + loadBindPoseChunk(reader, size); + break; + default: + this.unknownChunks.add(new MdlxUnknownChunk(reader, size, new War3ID(tag))); + break; + } + } + } + + private void loadVersionChunk(final BinaryReader reader) { + this.version = reader.readInt32(); + } + + private void loadModelChunk(final BinaryReader reader) { + this.name = reader.read(80); + this.animationFile = reader.read(260); + this.extent.readMdx(reader); + this.blendTime = reader.readInt32(); + } + + private void loadStaticObjects(final List out, final MdlxBlockDescriptor constructor, + final BinaryReader reader, final long count) { + for (int i = 0; i < count; i++) { + final E object = constructor.create(); + + object.readMdx(reader, this.version); + + out.add(object); + } + } + + private void loadGlobalSequenceChunk(final BinaryReader reader, final long size) { + for (long i = 0, l = size / 4; i < l; i++) { + this.globalSequences.add(reader.readUInt32()); + } + } + + private void loadDynamicObjects(final List out, + final MdlxBlockDescriptor constructor, final BinaryReader reader, final long size) { + long totalSize = 0; + while (totalSize < size) { + final E object = constructor.create(); + + object.readMdx(reader, this.version); + + totalSize += object.getByteLength(this.version); + + out.add(object); + } + } + + private void loadPivotPointChunk(final BinaryReader reader, final long size) { + for (long i = 0, l = size / 12; i < l; i++) { + this.pivotPoints.add(reader.readFloat32Array(3)); + } + } + + private void loadBindPoseChunk(final BinaryReader reader, final long size) { + for (int i = 0, l = reader.readInt32(); i < l; i++) { + this.bindPose.add(reader.readFloat32Array(12)); + } + } + + public ByteBuffer saveMdx() { + final BinaryWriter writer = new BinaryWriter(getByteLength()); + + writer.writeTag(MDLX); + saveVersionChunk(writer); + saveModelChunk(writer); + saveStaticObjectChunk(writer, SEQS, this.sequences, 132); + saveGlobalSequenceChunk(writer); + saveDynamicObjectChunk(writer, MTLS, this.materials); + saveStaticObjectChunk(writer, TEXS, this.textures, 268); + saveDynamicObjectChunk(writer, TXAN, this.textureAnimations); + saveDynamicObjectChunk(writer, GEOS, this.geosets); + saveDynamicObjectChunk(writer, GEOA, this.geosetAnimations); + saveDynamicObjectChunk(writer, BONE, this.bones); + saveDynamicObjectChunk(writer, LITE, this.lights); + saveDynamicObjectChunk(writer, HELP, this.helpers); + saveDynamicObjectChunk(writer, ATCH, this.attachments); + savePivotPointChunk(writer); + saveDynamicObjectChunk(writer, PREM, this.particleEmitters); + saveDynamicObjectChunk(writer, PRE2, this.particleEmitters2); + + if (this.version > 800) { + saveDynamicObjectChunk(writer, CORN, this.particleEmittersPopcorn); + } + + saveDynamicObjectChunk(writer, RIBB, this.ribbonEmitters); + saveDynamicObjectChunk(writer, CAMS, this.cameras); + saveDynamicObjectChunk(writer, EVTS, this.eventObjects); + saveDynamicObjectChunk(writer, CLID, this.collisionShapes); + + if (this.version > 800) { + saveStaticObjectChunk(writer, FAFX, this.faceEffects, 340); + saveBindPoseChunk(writer); + } + + for (final MdlxUnknownChunk chunk : this.unknownChunks) { + chunk.writeMdx(writer, this.version); + } + + return writer.buffer; + } + + private void saveVersionChunk(final BinaryWriter writer) { + writer.writeTag(VERS); + writer.writeUInt32(4); + writer.writeUInt32(this.version); + } + + private void saveModelChunk(final BinaryWriter writer) { + writer.writeTag(MODL); + writer.writeUInt32(372); + writer.writeWithNulls(this.name, 80); + writer.writeWithNulls(this.animationFile, 260); + this.extent.writeMdx(writer); + writer.writeUInt32(this.blendTime); + } + + private void saveStaticObjectChunk(final BinaryWriter writer, final int name, + final List objects, final long size) { + if (!objects.isEmpty()) { + writer.writeTag(name); + writer.writeUInt32(objects.size() * size); + + for (final E object : objects) { + object.writeMdx(writer, this.version); + } + } + } + + private void saveGlobalSequenceChunk(final BinaryWriter writer) { + if (!this.globalSequences.isEmpty()) { + writer.writeTag(GLBS); + writer.writeUInt32(this.globalSequences.size() * 4); + + for (final Long globalSequence : this.globalSequences) { + writer.writeUInt32(globalSequence); + } + } + } + + private void saveDynamicObjectChunk(final BinaryWriter writer, final int name, + final List objects) { + if (!objects.isEmpty()) { + writer.writeTag(name); + writer.writeUInt32(getObjectsByteLength(objects)); + + for (final E object : objects) { + object.writeMdx(writer, this.version); + } + } + } + + private void savePivotPointChunk(final BinaryWriter writer) { + if (this.pivotPoints.size() > 0) { + writer.writeTag(PIVT); + writer.writeUInt32(this.pivotPoints.size() * 12); + + for (final float[] pivotPoint : this.pivotPoints) { + writer.writeFloat32Array(pivotPoint); + } + } + } + + private void saveBindPoseChunk(final BinaryWriter writer) { + if (this.bindPose.size() > 0) { + writer.writeTag(BPOS); + writer.writeUInt32(4 + (this.bindPose.size() * 48)); + writer.writeUInt32(this.bindPose.size()); + + for (final float[] matrix : this.bindPose) { + writer.writeFloat32Array(matrix); + } + } + } + + public void loadMdl(final ByteBuffer buffer) { + String token; + final MdlTokenInputStream stream = new MdlTokenInputStream(buffer); + + while ((token = stream.read()) != null) { + switch (token) { + case MdlUtils.TOKEN_VERSION: + loadVersionBlock(stream); + break; + case MdlUtils.TOKEN_MODEL: + loadModelBlock(stream); + break; + case MdlUtils.TOKEN_SEQUENCES: + loadNumberedObjectBlock(this.sequences, MdlxBlockDescriptor.SEQUENCE, MdlUtils.TOKEN_ANIM, stream); + break; + case MdlUtils.TOKEN_GLOBAL_SEQUENCES: + loadGlobalSequenceBlock(stream); + break; + case MdlUtils.TOKEN_TEXTURES: + loadNumberedObjectBlock(this.textures, MdlxBlockDescriptor.TEXTURE, MdlUtils.TOKEN_BITMAP, stream); + break; + case MdlUtils.TOKEN_MATERIALS: + loadNumberedObjectBlock(this.materials, MdlxBlockDescriptor.MATERIAL, MdlUtils.TOKEN_MATERIAL, stream); + break; + case MdlUtils.TOKEN_TEXTURE_ANIMS: + loadNumberedObjectBlock(this.textureAnimations, MdlxBlockDescriptor.TEXTURE_ANIMATION, + MdlUtils.TOKEN_TEXTURE_ANIM, stream); + break; + case MdlUtils.TOKEN_GEOSET: + loadObject(this.geosets, MdlxBlockDescriptor.GEOSET, stream); + break; + case MdlUtils.TOKEN_GEOSETANIM: + loadObject(this.geosetAnimations, MdlxBlockDescriptor.GEOSET_ANIMATION, stream); + break; + case MdlUtils.TOKEN_BONE: + loadObject(this.bones, MdlxBlockDescriptor.BONE, stream); + break; + case MdlUtils.TOKEN_LIGHT: + loadObject(this.lights, MdlxBlockDescriptor.LIGHT, stream); + break; + case MdlUtils.TOKEN_HELPER: + loadObject(this.helpers, MdlxBlockDescriptor.HELPER, stream); + break; + case MdlUtils.TOKEN_ATTACHMENT: + loadObject(this.attachments, MdlxBlockDescriptor.ATTACHMENT, stream); + break; + case MdlUtils.TOKEN_PIVOT_POINTS: + loadPivotPointBlock(stream); + break; + case MdlUtils.TOKEN_PARTICLE_EMITTER: + loadObject(this.particleEmitters, MdlxBlockDescriptor.PARTICLE_EMITTER, stream); + break; + case MdlUtils.TOKEN_PARTICLE_EMITTER2: + loadObject(this.particleEmitters2, MdlxBlockDescriptor.PARTICLE_EMITTER2, stream); + break; + case "ParticleEmitterPopcorn": + loadObject(this.particleEmittersPopcorn, MdlxBlockDescriptor.PARTICLE_EMITTER_POPCORN, stream); + break; + case MdlUtils.TOKEN_RIBBON_EMITTER: + loadObject(this.ribbonEmitters, MdlxBlockDescriptor.RIBBON_EMITTER, stream); + break; + case MdlUtils.TOKEN_CAMERA: + loadObject(this.cameras, MdlxBlockDescriptor.CAMERA, stream); + break; + case MdlUtils.TOKEN_EVENT_OBJECT: + loadObject(this.eventObjects, MdlxBlockDescriptor.EVENT_OBJECT, stream); + break; + case MdlUtils.TOKEN_COLLISION_SHAPE: + loadObject(this.collisionShapes, MdlxBlockDescriptor.COLLISION_SHAPE, stream); + break; + case "FaceFX": + loadObject(this.faceEffects, MdlxBlockDescriptor.FACE_EFFECT, stream); + break; + case "BindPose": + loadBindPoseBlock(stream); + break; + default: + throw new IllegalStateException("Unsupported block: " + token); + } + } + } + + private void loadVersionBlock(final MdlTokenInputStream stream) { + for (final String token : stream.readBlock()) { + if (MdlUtils.TOKEN_FORMAT_VERSION.equals(token)) { + this.version = stream.readInt(); + } + else { + throw new IllegalStateException("Unknown token in Version: " + token); + } + } + } + + private void loadModelBlock(final MdlTokenInputStream stream) { + this.name = stream.read(); + for (final String token : stream.readBlock()) { + if (token.startsWith("Num")) { + /*- + * Don't care about the number of things, the arrays will grow as they wish. + * This includes: + * NumGeosets + * NumGeosetAnims + * NumHelpers + * NumLights + * NumBones + * NumAttachments + * NumParticleEmitters + * NumParticleEmitters2 + * NumRibbonEmitters + * NumEvents + */ + stream.read(); + } + else { + switch (token) { + case MdlUtils.TOKEN_BLEND_TIME: + this.blendTime = stream.readUInt32(); + break; + case MdlUtils.TOKEN_MINIMUM_EXTENT: + stream.readFloatArray(this.extent.min); + break; + case MdlUtils.TOKEN_MAXIMUM_EXTENT: + stream.readFloatArray(this.extent.max); + break; + case MdlUtils.TOKEN_BOUNDSRADIUS: + this.extent.boundsRadius = stream.readFloat(); + break; + default: + throw new IllegalStateException("Unknown token in Model: " + token); + } + } + } + } + + private void loadNumberedObjectBlock(final List out, + final MdlxBlockDescriptor constructor, final String name, final MdlTokenInputStream stream) { + stream.read(); // Don't care about the number, the array will grow + + for (final String token : stream.readBlock()) { + if (token.equals(name)) { + final E object = constructor.create(); + + object.readMdl(stream, this.version); + + out.add(object); + } + else { + throw new IllegalStateException("Unknown token in " + name + ": " + token); + } + } + } + + private void loadGlobalSequenceBlock(final MdlTokenInputStream stream) { + stream.read(); // Don't care about the number, the array will grow + + for (final String token : stream.readBlock()) { + if (token.equals(MdlUtils.TOKEN_DURATION)) { + this.globalSequences.add(stream.readUInt32()); + } + else { + throw new IllegalStateException("Unknown token in GlobalSequences: " + token); + } + } + } + + private void loadObject(final List out, final MdlxBlockDescriptor descriptor, + final MdlTokenInputStream stream) { + final E object = descriptor.create(); + + object.readMdl(stream, this.version); + + out.add(object); + } + + private void loadPivotPointBlock(final MdlTokenInputStream stream) { + final int count = stream.readInt(); + + stream.read(); // { + + for (int i = 0; i < count; i++) { + this.pivotPoints.add(stream.readFloatArray(new float[3])); + } + + stream.read(); // } + } + + private void loadBindPoseBlock(final MdlTokenInputStream stream) { + for (final String token : stream.readBlock()) { + if (token.equals("Matrices")) { + final int matrices = stream.readInt(); + + stream.read(); // { + + for (int i = 0; i < matrices; i++) { + this.bindPose.add(stream.readFloatArray(new float[12])); + } + + stream.read(); // } + } + else { + throw new IllegalStateException("Unknown token in BindPose: " + token); + } + } + } + + public ByteBuffer saveMdl() { + final MdlTokenOutputStream stream = new MdlTokenOutputStream(); + + saveVersionBlock(stream); + saveModelBlock(stream); + saveStaticObjectsBlock(stream, MdlUtils.TOKEN_SEQUENCES, this.sequences); + saveGlobalSequenceBlock(stream); + saveStaticObjectsBlock(stream, MdlUtils.TOKEN_TEXTURES, this.textures); + saveStaticObjectsBlock(stream, MdlUtils.TOKEN_MATERIALS, this.materials); + saveStaticObjectsBlock(stream, MdlUtils.TOKEN_TEXTURE_ANIMS, this.textureAnimations); + saveObjects(stream, this.geosets); + saveObjects(stream, this.geosetAnimations); + saveObjects(stream, this.bones); + saveObjects(stream, this.lights); + saveObjects(stream, this.helpers); + saveObjects(stream, this.attachments); + savePivotPointBlock(stream); + saveObjects(stream, this.particleEmitters); + saveObjects(stream, this.particleEmitters2); + + if (this.version > 800) { + saveObjects(stream, this.particleEmittersPopcorn); + } + + saveObjects(stream, this.ribbonEmitters); + saveObjects(stream, this.cameras); + saveObjects(stream, this.eventObjects); + saveObjects(stream, this.collisionShapes); + + if (this.version > 800) { + saveObjects(stream, this.faceEffects); + saveBindPoseBlock(stream); + } + + return ByteBuffer.wrap(stream.buffer.toString().getBytes()); + } + + private void saveVersionBlock(final MdlTokenOutputStream stream) { + stream.startBlock(MdlUtils.TOKEN_VERSION); + stream.writeAttrib(MdlUtils.TOKEN_FORMAT_VERSION, this.version); + stream.endBlock(); + } + + private void saveModelBlock(final MdlTokenOutputStream stream) { + stream.startObjectBlock(MdlUtils.TOKEN_MODEL, this.name); + stream.writeAttribUInt32(MdlUtils.TOKEN_BLEND_TIME, this.blendTime); + this.extent.writeMdl(stream); + stream.endBlock(); + } + + private void saveStaticObjectsBlock(final MdlTokenOutputStream stream, final String name, + final List objects) { + if (!objects.isEmpty()) { + stream.startBlock(name, objects.size()); + + for (final MdlxBlock object : objects) { + object.writeMdl(stream, this.version); + } + + stream.endBlock(); + } + } + + private void saveGlobalSequenceBlock(final MdlTokenOutputStream stream) { + if (!this.globalSequences.isEmpty()) { + stream.startBlock(MdlUtils.TOKEN_GLOBAL_SEQUENCES, this.globalSequences.size()); + + for (final Long globalSequence : this.globalSequences) { + stream.writeAttribUInt32(MdlUtils.TOKEN_DURATION, globalSequence); + } + + stream.endBlock(); + } + } + + private void saveObjects(final MdlTokenOutputStream stream, final List objects) { + for (final MdlxBlock object : objects) { + object.writeMdl(stream, this.version); + } + } + + private void savePivotPointBlock(final MdlTokenOutputStream stream) { + if (!this.pivotPoints.isEmpty()) { + stream.startBlock(MdlUtils.TOKEN_PIVOT_POINTS, this.pivotPoints.size()); + + for (final float[] pivotPoint : this.pivotPoints) { + stream.writeFloatArray(pivotPoint); + } + + stream.endBlock(); + } + } + + private void saveBindPoseBlock(final MdlTokenOutputStream stream) { + if (!this.bindPose.isEmpty()) { + stream.startBlock("BindPose"); + + stream.startBlock("Matrices", this.bindPose.size()); + + for (final float[] matrix : this.bindPose) { + stream.writeFloatArray(matrix); + } + + stream.endBlock(); + + stream.endBlock(); + } + } + + public int getByteLength() { + int size = 396; + + size += getStaticObjectsChunkByteLength(this.sequences, 132); + size += getStaticObjectsChunkByteLength(this.globalSequences, 4); + size += getDynamicObjectsChunkByteLength(this.materials); + size += getStaticObjectsChunkByteLength(this.textures, 268); + size += getDynamicObjectsChunkByteLength(this.textureAnimations); + size += getDynamicObjectsChunkByteLength(this.geosets); + size += getDynamicObjectsChunkByteLength(this.geosetAnimations); + size += getDynamicObjectsChunkByteLength(this.bones); + size += getDynamicObjectsChunkByteLength(this.lights); + size += getDynamicObjectsChunkByteLength(this.helpers); + size += getDynamicObjectsChunkByteLength(this.attachments); + size += getStaticObjectsChunkByteLength(this.pivotPoints, 12); + size += getDynamicObjectsChunkByteLength(this.particleEmitters); + size += getDynamicObjectsChunkByteLength(this.particleEmitters2); + + if (this.version > 800) { + size += getDynamicObjectsChunkByteLength(this.particleEmittersPopcorn); + } + + size += getDynamicObjectsChunkByteLength(this.ribbonEmitters); + size += getDynamicObjectsChunkByteLength(this.cameras); + size += getDynamicObjectsChunkByteLength(this.eventObjects); + size += getDynamicObjectsChunkByteLength(this.collisionShapes); + size += getObjectsByteLength(this.unknownChunks); + + if (this.version > 800) { + size += getStaticObjectsChunkByteLength(this.faceEffects, 340); + size += getBindPoseChunkByteLength(); + } + + return size; + } + + private long getObjectsByteLength(final List objects) { + long size = 0; + for (final E object : objects) { + size += object.getByteLength(this.version); + } + return size; + } + + private long getDynamicObjectsChunkByteLength(final List objects) { + if (!objects.isEmpty()) { + return 8 + getObjectsByteLength(objects); + } + + return 0; + } + + private long getStaticObjectsChunkByteLength(final List objects, final long size) { + if (!objects.isEmpty()) { + return 8 + (objects.size() * size); + } + + return 0; + } + + private long getBindPoseChunkByteLength() { + if (this.bindPose.size() > 0) { + return 12 + (this.bindPose.size() * 48); + } + + return 0; + } + + public List getGeosets() { + return this.geosets; + } + + public int getVersion() { + return this.version; + } + + public String getName() { + return this.name; + } + + public String getAnimationFile() { + return this.animationFile; + } + + public MdlxExtent getExtent() { + return this.extent; + } + + public long getBlendTime() { + return this.blendTime; + } + + public List getSequences() { + return this.sequences; + } + + public List getGlobalSequences() { + return this.globalSequences; + } + + public List getMaterials() { + return this.materials; + } + + public List getTextures() { + return this.textures; + } + + public List getTextureAnimations() { + return this.textureAnimations; + } + + public List getGeosetAnimations() { + return this.geosetAnimations; + } + + public List getBones() { + return this.bones; + } + + public List getLights() { + return this.lights; + } + + public List getHelpers() { + return this.helpers; + } + + public List getAttachments() { + return this.attachments; + } + + public List getPivotPoints() { + return this.pivotPoints; + } + + public List getParticleEmitters() { + return this.particleEmitters; + } + + public List getParticleEmitters2() { + return this.particleEmitters2; + } + + public List getParticleEmittersPopcorn() { + return this.particleEmittersPopcorn; + } + + public List getRibbonEmitters() { + return this.ribbonEmitters; + } + + public List getCameras() { + return this.cameras; + } + + public List getEventObjects() { + return this.eventObjects; + } + + public List getCollisionShapes() { + return this.collisionShapes; + } + + public List getFaceEffects() { + return this.faceEffects; + } + + public List getBindPose() { + return this.bindPose; + } + + public List getUnknownChunks() { + return this.unknownChunks; + } + + public void setVersion(final int version) { + this.version = version; + } + + public void setName(final String name) { + this.name = name; + } + + public void setAnimationFile(final String animationFile) { + this.animationFile = animationFile; + } + + public void setExtent(final MdlxExtent extent) { + this.extent = extent; + } + + public void setBlendTime(final long blendTime) { + this.blendTime = blendTime; + } + + public void setSequences(final List sequences) { + this.sequences = sequences; + } + + public void setGlobalSequences(final List globalSequences) { + this.globalSequences = globalSequences; + } + + public void setMaterials(final List materials) { + this.materials = materials; + } + + public void setTextures(final List textures) { + this.textures = textures; + } + + public void setTextureAnimations(final List textureAnimations) { + this.textureAnimations = textureAnimations; + } + + public void setGeosets(final List geosets) { + this.geosets = geosets; + } + + public void setGeosetAnimations(final List geosetAnimations) { + this.geosetAnimations = geosetAnimations; + } + + public void setBones(final List bones) { + this.bones = bones; + } + + public void setLights(final List lights) { + this.lights = lights; + } + + public void setHelpers(final List helpers) { + this.helpers = helpers; + } + + public void setAttachments(final List attachments) { + this.attachments = attachments; + } + + public void setPivotPoints(final List pivotPoints) { + this.pivotPoints = pivotPoints; + } + + public void setParticleEmitters(final List particleEmitters) { + this.particleEmitters = particleEmitters; + } + + public void setParticleEmitters2(final List particleEmitters2) { + this.particleEmitters2 = particleEmitters2; + } + + public void setParticleEmittersPopcorn(final List particleEmittersPopcorn) { + this.particleEmittersPopcorn = particleEmittersPopcorn; + } + + public void setRibbonEmitters(final List ribbonEmitters) { + this.ribbonEmitters = ribbonEmitters; + } + + public void setCameras(final List cameras) { + this.cameras = cameras; + } + + public void setEventObjects(final List eventObjects) { + this.eventObjects = eventObjects; + } + + public void setCollisionShapes(final List collisionShapes) { + this.collisionShapes = collisionShapes; + } + + public void setFaceEffects(final List faceEffects) { + this.faceEffects = faceEffects; + } + + public void setBindPose(final List bindPose) { + this.bindPose = bindPose; + } + + public void setUnknownChunks(final List unknownChunks) { + this.unknownChunks = unknownChunks; + } +} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/ParticleEmitter.java b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxParticleEmitter.java similarity index 53% rename from core/src/com/etheller/warsmash/parsers/mdlx/ParticleEmitter.java rename to core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxParticleEmitter.java index fb8a3d1..d4d4ca5 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/ParticleEmitter.java +++ b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxParticleEmitter.java @@ -1,72 +1,63 @@ -package com.etheller.warsmash.parsers.mdlx; +package com.hiveworkshop.rms.parsers.mdlx; -import java.io.IOException; import java.util.Iterator; -import com.etheller.warsmash.util.MdlUtils; -import com.etheller.warsmash.util.ParseUtils; -import com.google.common.io.LittleEndianDataInputStream; -import com.google.common.io.LittleEndianDataOutputStream; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlTokenInputStream; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlTokenOutputStream; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlUtils; +import com.hiveworkshop.rms.util.BinaryReader; +import com.hiveworkshop.rms.util.BinaryWriter; -public class ParticleEmitter extends GenericObject { - private float emissionRate = 0; - private float gravity = 0; - private float longitude = 0; - private float latitude = 0; - private String path = ""; - private float lifeSpan = 0; - private float speed = 0; +public class MdlxParticleEmitter extends MdlxGenericObject { + public float emissionRate = 0; + public float gravity = 0; + public float longitude = 0; + public float latitude = 0; + public String path = ""; + public float lifeSpan = 0; + public float speed = 0; - public ParticleEmitter() { + public MdlxParticleEmitter() { super(0x1000); } - /** - * Restricts us to only be able to parse models on one thread at a time, in - * return for high performance. - */ - private static final byte[] PATH_BYTES_HEAP = new byte[260]; - @Override - public void readMdx(final LittleEndianDataInputStream stream) throws IOException { - final long size = ParseUtils.readUInt32(stream); + public void readMdx(final BinaryReader reader, final int version) { + final int position = reader.position(); + final long size = reader.readUInt32(); - super.readMdx(stream); + super.readMdx(reader, version); - this.emissionRate = stream.readFloat(); - this.gravity = stream.readFloat(); - this.longitude = stream.readFloat(); - this.latitude = stream.readFloat(); - this.path = ParseUtils.readString(stream, PATH_BYTES_HEAP); - this.lifeSpan = stream.readFloat(); - this.speed = stream.readFloat(); + this.emissionRate = reader.readFloat32(); + this.gravity = reader.readFloat32(); + this.longitude = reader.readFloat32(); + this.latitude = reader.readFloat32(); + this.path = reader.read(260); + this.lifeSpan = reader.readFloat32(); + this.speed = reader.readFloat32(); - readTimelines(stream, size - this.getByteLength()); + readTimelines(reader, size - (reader.position() - position)); } @Override - public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException { - ParseUtils.writeUInt32(stream, getByteLength()); + public void writeMdx(final BinaryWriter writer, final int version) { + writer.writeUInt32(getByteLength(version)); - super.writeMdx(stream); + super.writeMdx(writer, version); - stream.writeFloat(this.emissionRate); - stream.writeFloat(this.gravity); - stream.writeFloat(this.longitude); - stream.writeFloat(this.latitude); - final byte[] bytes = this.path.getBytes(ParseUtils.UTF8); - stream.write(bytes); - for (int i = 0; i < (PATH_BYTES_HEAP.length - bytes.length); i++) { - stream.write((byte) 0); - } - stream.writeFloat(this.lifeSpan); - stream.writeFloat(this.speed); + writer.writeFloat32(this.emissionRate); + writer.writeFloat32(this.gravity); + writer.writeFloat32(this.longitude); + writer.writeFloat32(this.latitude); + writer.writeWithNulls(this.path, 260); + writer.writeFloat32(this.lifeSpan); + writer.writeFloat32(this.speed); - writeNonGenericAnimationChunks(stream); + writeNonGenericAnimationChunks(writer); } @Override - public void readMdl(final MdlTokenInputStream stream) throws IOException { + public void readMdl(final MdlTokenInputStream stream, final int version) { for (final String token : super.readMdlGeneric(stream)) { switch (token) { case MdlUtils.TOKEN_EMITTER_USES_MDL: @@ -102,7 +93,7 @@ public class ParticleEmitter extends GenericObject { case MdlUtils.TOKEN_VISIBILITY: readTimeline(stream, AnimationMap.KPEV); break; - case MdlUtils.TOKEN_PARTICLE: + case MdlUtils.TOKEN_PARTICLE: { final Iterator iterator = readAnimatedBlock(stream); while (iterator.hasNext()) { final String subToken = iterator.next(); @@ -123,19 +114,20 @@ public class ParticleEmitter extends GenericObject { this.path = stream.read(); break; default: - throw new IllegalStateException( + throw new RuntimeException( "Unknown token in ParticleEmitter " + this.name + "'s Particle: " + subToken); } } + } break; default: - throw new IllegalStateException("Unknown token in ParticleEmitter " + this.name + ": " + token); + throw new RuntimeException("Unknown token in ParticleEmitter " + this.name + ": " + token); } } } @Override - public void writeMdl(final MdlTokenOutputStream stream) throws IOException { + public void writeMdl(final MdlTokenOutputStream stream, final int version) { stream.startObjectBlock(MdlUtils.TOKEN_PARTICLE_EMITTER, this.name); writeGenericHeader(stream); @@ -147,31 +139,31 @@ public class ParticleEmitter extends GenericObject { stream.writeFlag(MdlUtils.TOKEN_EMITTER_USES_TGA); } - if (!this.writeTimeline(stream, AnimationMap.KPEE)) { + if (!writeTimeline(stream, AnimationMap.KPEE)) { stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_EMISSION_RATE, this.emissionRate); } - if (!this.writeTimeline(stream, AnimationMap.KPEG)) { + if (!writeTimeline(stream, AnimationMap.KPEG)) { stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_GRAVITY, this.gravity); } - if (!this.writeTimeline(stream, AnimationMap.KPLN)) { + if (!writeTimeline(stream, AnimationMap.KPLN)) { stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_LONGITUDE, this.longitude); } - if (!this.writeTimeline(stream, AnimationMap.KPLT)) { + if (!writeTimeline(stream, AnimationMap.KPLT)) { stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_LATITUDE, this.latitude); } - this.writeTimeline(stream, AnimationMap.KPEV); + writeTimeline(stream, AnimationMap.KPEV); stream.startBlock(MdlUtils.TOKEN_PARTICLE); - if (!this.writeTimeline(stream, AnimationMap.KPEL)) { + if (!writeTimeline(stream, AnimationMap.KPEL)) { stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_LIFE_SPAN, this.lifeSpan); } - if (!this.writeTimeline(stream, AnimationMap.KPES)) { + if (!writeTimeline(stream, AnimationMap.KPES)) { stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_INIT_VELOCITY, this.speed); } @@ -187,8 +179,8 @@ public class ParticleEmitter extends GenericObject { } @Override - public long getByteLength() { - return 288 + super.getByteLength(); + public long getByteLength(final int version) { + return 288 + super.getByteLength(version); } public float getEmissionRate() { @@ -219,4 +211,31 @@ public class ParticleEmitter extends GenericObject { return this.speed; } + public void setEmissionRate(final float emissionRate) { + this.emissionRate = emissionRate; + } + + public void setGravity(final float gravity) { + this.gravity = gravity; + } + + public void setLongitude(final float longitude) { + this.longitude = longitude; + } + + public void setLatitude(final float latitude) { + this.latitude = latitude; + } + + public void setPath(final String path) { + this.path = path; + } + + public void setLifeSpan(final float lifeSpan) { + this.lifeSpan = lifeSpan; + } + + public void setSpeed(final float speed) { + this.speed = speed; + } } diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/ParticleEmitter2.java b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxParticleEmitter2.java similarity index 52% rename from core/src/com/etheller/warsmash/parsers/mdlx/ParticleEmitter2.java rename to core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxParticleEmitter2.java index 07423fb..aceb85e 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/ParticleEmitter2.java +++ b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxParticleEmitter2.java @@ -1,33 +1,23 @@ -package com.etheller.warsmash.parsers.mdlx; +package com.hiveworkshop.rms.parsers.mdlx; -import java.io.IOException; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlTokenInputStream; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlTokenOutputStream; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlUtils; +import com.hiveworkshop.rms.util.BinaryReader; +import com.hiveworkshop.rms.util.BinaryWriter; -import com.etheller.warsmash.util.MdlUtils; -import com.etheller.warsmash.util.ParseUtils; -import com.google.common.io.LittleEndianDataInputStream; -import com.google.common.io.LittleEndianDataOutputStream; - -public class ParticleEmitter2 extends GenericObject { - // 0: blend - // 1: additive - // 2: modulate - // 3: modulate 2x - // 4: alphakey - public static enum FilterMode { +public class MdlxParticleEmitter2 extends MdlxGenericObject { + public enum FilterMode { BLEND("Blend"), ADDITIVE("Additive"), MODULATE("Modulate"), MODULATE2X("Modulate2x"), ALPHAKEY("AlphaKey"); - String mdlText; + String token; - FilterMode(final String str) { - this.mdlText = str; - } - - public String getMdlText() { - return this.mdlText; + FilterMode(final String token) { + this.token = token; } public static FilterMode fromId(final int id) { @@ -36,7 +26,7 @@ public class ParticleEmitter2 extends GenericObject { public static int nameToId(final String name) { for (final FilterMode mode : values()) { - if (mode.getMdlText().equals(name)) { + if (mode.token.equals(name)) { return mode.ordinal(); } } @@ -45,114 +35,158 @@ public class ParticleEmitter2 extends GenericObject { @Override public String toString() { - return getMdlText(); + return this.token; } } - private float speed = 0; - private float variation = 0; - private float latitude = 0; - private float gravity = 0; - private float lifeSpan = 0; - private float emissionRate = 0; - private float length; - private float width; - private FilterMode filterMode = FilterMode.BLEND; - private long rows = 0; - private long columns = 0; - private long headOrTail = 0; - private float tailLength = 0; - private float timeMiddle = 0; - private final float[][] segmentColors = new float[3][3]; - private final short[] segmentAlphas = new short[3]; // unsigned byte[] - private final float[] segmentScaling = new float[3]; - private final long[][] headIntervals = new long[2][3]; - private final long[][] tailIntervals = new long[2][3]; - private int textureId = -1; - private long squirt = 0; - private int priorityPlane = 0; - private long replaceableId = 0; + public enum HeadOrTail { + HEAD("Head", true, false), + TAIL("Tail", false, true), + BOTH("Both", true, true); - public ParticleEmitter2() { + String token; + boolean includesHead; + boolean includesTail; + + private HeadOrTail(final String token, final boolean includesHead, final boolean includesTail) { + this.token = token; + this.includesHead = includesHead; + this.includesTail = includesTail; + } + + public static HeadOrTail fromId(final int id) { + return values()[id]; + } + + public static int nameToId(final String name) { + for (final HeadOrTail mode : values()) { + if (mode.token.equals(name)) { + return mode.ordinal(); + } + } + + return -1; + } + + @Override + public String toString() { + return this.token; + } + + public boolean isIncludesHead() { + return this.includesHead; + } + + public boolean isIncludesTail() { + return this.includesTail; + } + } + + public float speed = 0; + public float variation = 0; + public float latitude = 0; + public float gravity = 0; + public float lifeSpan = 0; + public float emissionRate = 0; + public float length = 0; + public float width = 0; + public FilterMode filterMode = FilterMode.BLEND; + public long rows = 0; + public long columns = 0; + public HeadOrTail headOrTail = HeadOrTail.HEAD; + public float tailLength = 0; + public float timeMiddle = 0; + public final float[][] segmentColors = new float[3][3]; + public short[] segmentAlphas = new short[3]; // unsigned byte[] + public float[] segmentScaling = new float[3]; + public long[][] headIntervals = new long[2][3]; + public long[][] tailIntervals = new long[2][3]; + public int textureId = -1; + public long squirt = 0; + public int priorityPlane = 0; + public long replaceableId = 0; + + public MdlxParticleEmitter2() { super(0x1000); } @Override - public void readMdx(final LittleEndianDataInputStream stream) throws IOException { - final long size = ParseUtils.readUInt32(stream); + public void readMdx(final BinaryReader reader, final int version) { + final int position = reader.position(); + final long size = reader.readUInt32(); - super.readMdx(stream); + super.readMdx(reader, version); - this.speed = stream.readFloat(); - this.variation = stream.readFloat(); - this.latitude = stream.readFloat(); - this.gravity = stream.readFloat(); - this.lifeSpan = stream.readFloat(); - this.emissionRate = stream.readFloat(); - this.length = stream.readFloat(); - this.width = stream.readFloat(); - this.filterMode = FilterMode.fromId((int) (ParseUtils.readUInt32(stream))); - this.rows = ParseUtils.readUInt32(stream); - this.columns = ParseUtils.readUInt32(stream); - this.headOrTail = ParseUtils.readUInt32(stream); - this.tailLength = stream.readFloat(); - this.timeMiddle = stream.readFloat(); - ParseUtils.readFloatArray(stream, this.segmentColors[0]); - ParseUtils.readFloatArray(stream, this.segmentColors[1]); - ParseUtils.readFloatArray(stream, this.segmentColors[2]); - ParseUtils.readUInt8Array(stream, this.segmentAlphas); - ParseUtils.readFloatArray(stream, this.segmentScaling); - ParseUtils.readUInt32Array(stream, this.headIntervals[0]); - ParseUtils.readUInt32Array(stream, this.headIntervals[1]); - ParseUtils.readUInt32Array(stream, this.tailIntervals[0]); - ParseUtils.readUInt32Array(stream, this.tailIntervals[1]); - this.textureId = stream.readInt(); - this.squirt = ParseUtils.readUInt32(stream); - this.priorityPlane = stream.readInt(); - this.replaceableId = ParseUtils.readUInt32(stream); + this.speed = reader.readFloat32(); + this.variation = reader.readFloat32(); + this.latitude = reader.readFloat32(); + this.gravity = reader.readFloat32(); + this.lifeSpan = reader.readFloat32(); + this.emissionRate = reader.readFloat32(); + this.length = reader.readFloat32(); + this.width = reader.readFloat32(); + this.filterMode = FilterMode.fromId(reader.readInt32()); + this.rows = reader.readUInt32(); + this.columns = reader.readUInt32(); + this.headOrTail = HeadOrTail.fromId(reader.readInt32()); + this.tailLength = reader.readFloat32(); + this.timeMiddle = reader.readFloat32(); + reader.readFloat32Array(this.segmentColors[0]); + reader.readFloat32Array(this.segmentColors[1]); + reader.readFloat32Array(this.segmentColors[2]); + reader.readUInt8Array(this.segmentAlphas); + reader.readFloat32Array(this.segmentScaling); + reader.readUInt32Array(this.headIntervals[0]); + reader.readUInt32Array(this.headIntervals[1]); + reader.readUInt32Array(this.tailIntervals[0]); + reader.readUInt32Array(this.tailIntervals[1]); + this.textureId = reader.readInt32(); + this.squirt = reader.readUInt32(); + this.priorityPlane = reader.readInt32(); + this.replaceableId = reader.readUInt32(); - readTimelines(stream, size - this.getByteLength()); + readTimelines(reader, size - (reader.position() - position)); } @Override - public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException { - ParseUtils.writeUInt32(stream, getByteLength()); + public void writeMdx(final BinaryWriter writer, final int version) { + writer.writeUInt32(getByteLength(version)); - super.writeMdx(stream); + super.writeMdx(writer, version); - stream.writeFloat(this.speed); - stream.writeFloat(this.variation); - stream.writeFloat(this.latitude); - stream.writeFloat(this.gravity); - stream.writeFloat(this.lifeSpan); - stream.writeFloat(this.emissionRate); - stream.writeFloat(this.length); - stream.writeFloat(this.width); - ParseUtils.writeUInt32(stream, this.filterMode.ordinal()); - ParseUtils.writeUInt32(stream, this.rows); - ParseUtils.writeUInt32(stream, this.columns); - ParseUtils.writeUInt32(stream, this.headOrTail); - stream.writeFloat(this.tailLength); - stream.writeFloat(this.timeMiddle); - ParseUtils.writeFloatArray(stream, this.segmentColors[0]); - ParseUtils.writeFloatArray(stream, this.segmentColors[1]); - ParseUtils.writeFloatArray(stream, this.segmentColors[2]); - ParseUtils.writeUInt8Array(stream, this.segmentAlphas); - ParseUtils.writeFloatArray(stream, this.segmentScaling); - ParseUtils.writeUInt32Array(stream, this.headIntervals[0]); - ParseUtils.writeUInt32Array(stream, this.headIntervals[1]); - ParseUtils.writeUInt32Array(stream, this.tailIntervals[0]); - ParseUtils.writeUInt32Array(stream, this.tailIntervals[1]); - stream.writeInt(this.textureId); - ParseUtils.writeUInt32(stream, this.squirt); - stream.writeInt(this.priorityPlane); - ParseUtils.writeUInt32(stream, this.replaceableId); + writer.writeFloat32(this.speed); + writer.writeFloat32(this.variation); + writer.writeFloat32(this.latitude); + writer.writeFloat32(this.gravity); + writer.writeFloat32(this.lifeSpan); + writer.writeFloat32(this.emissionRate); + writer.writeFloat32(this.length); + writer.writeFloat32(this.width); + writer.writeInt32(this.filterMode.ordinal()); + writer.writeUInt32(this.rows); + writer.writeUInt32(this.columns); + writer.writeInt32(this.headOrTail.ordinal()); + writer.writeFloat32(this.tailLength); + writer.writeFloat32(this.timeMiddle); + writer.writeFloat32Array(this.segmentColors[0]); + writer.writeFloat32Array(this.segmentColors[1]); + writer.writeFloat32Array(this.segmentColors[2]); + writer.writeUInt8Array(this.segmentAlphas); + writer.writeFloat32Array(this.segmentScaling); + writer.writeUInt32Array(this.headIntervals[0]); + writer.writeUInt32Array(this.headIntervals[1]); + writer.writeUInt32Array(this.tailIntervals[0]); + writer.writeUInt32Array(this.tailIntervals[1]); + writer.writeInt32(this.textureId); + writer.writeUInt32(this.squirt); + writer.writeInt32(this.priorityPlane); + writer.writeUInt32(this.replaceableId); - writeNonGenericAnimationChunks(stream); + writeNonGenericAnimationChunks(writer); } @Override - public void readMdl(final MdlTokenInputStream stream) throws IOException { + public void readMdl(final MdlTokenInputStream stream, final int version) { for (final String token : super.readMdlGeneric(stream)) { switch (token) { case MdlUtils.TOKEN_SORT_PRIMS_FAR_Z: @@ -246,13 +280,13 @@ public class ParticleEmitter2 extends GenericObject { this.columns = stream.readUInt32(); break; case MdlUtils.TOKEN_HEAD: - this.headOrTail = 0; + this.headOrTail = HeadOrTail.HEAD; break; case MdlUtils.TOKEN_TAIL: - this.headOrTail = 1; + this.headOrTail = HeadOrTail.TAIL; break; case MdlUtils.TOKEN_BOTH: - this.headOrTail = 2; + this.headOrTail = HeadOrTail.BOTH; break; case MdlUtils.TOKEN_TAIL_LENGTH: this.tailLength = stream.readFloat(); @@ -260,15 +294,14 @@ public class ParticleEmitter2 extends GenericObject { case MdlUtils.TOKEN_TIME: this.timeMiddle = stream.readFloat(); break; - case MdlUtils.TOKEN_SEGMENT_COLOR: + case MdlUtils.TOKEN_SEGMENT_COLOR: { stream.read(); // { - for (int i = 0; i < 3; i++) { stream.read(); // Color stream.readColor(this.segmentColors[i]); } - stream.read(); // } + } break; case MdlUtils.TOKEN_ALPHA: stream.readUInt8Array(this.segmentAlphas); @@ -297,15 +330,14 @@ public class ParticleEmitter2 extends GenericObject { case MdlUtils.TOKEN_PRIORITY_PLANE: this.priorityPlane = stream.readInt(); break; - default: - throw new IllegalStateException("Unknown token in ParticleEmitter2 " + this.name + ": " + token); + throw new RuntimeException("Unknown token in ParticleEmitter2 " + this.name + ": " + token); } } } @Override - public void writeMdl(final MdlTokenOutputStream stream) throws IOException { + public void writeMdl(final MdlTokenOutputStream stream, final int version) { stream.startObjectBlock(MdlUtils.TOKEN_PARTICLE_EMITTER2, this.name); writeGenericHeader(stream); @@ -333,19 +365,19 @@ public class ParticleEmitter2 extends GenericObject { stream.writeFlag(MdlUtils.TOKEN_XY_QUAD); } - if (!this.writeTimeline(stream, AnimationMap.KP2S)) { + if (!writeTimeline(stream, AnimationMap.KP2S)) { stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_SPEED, this.speed); } - if (!this.writeTimeline(stream, AnimationMap.KP2R)) { + if (!writeTimeline(stream, AnimationMap.KP2R)) { stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_VARIATION, this.variation); } - if (!this.writeTimeline(stream, AnimationMap.KP2L)) { + if (!writeTimeline(stream, AnimationMap.KP2L)) { stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_LATITUDE, this.latitude); } - if (!this.writeTimeline(stream, AnimationMap.KP2G)) { + if (!writeTimeline(stream, AnimationMap.KP2G)) { stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_GRAVITY, this.gravity); } @@ -357,37 +389,22 @@ public class ParticleEmitter2 extends GenericObject { stream.writeFloatAttrib(MdlUtils.TOKEN_LIFE_SPAN, this.lifeSpan); - if (!this.writeTimeline(stream, AnimationMap.KP2E)) { + if (!writeTimeline(stream, AnimationMap.KP2E)) { stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_EMISSION_RATE, this.emissionRate); } - if (!this.writeTimeline(stream, AnimationMap.KP2W)) { + if (!writeTimeline(stream, AnimationMap.KP2W)) { stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_WIDTH, this.width); } - if (!this.writeTimeline(stream, AnimationMap.KP2N)) { + if (!writeTimeline(stream, AnimationMap.KP2N)) { stream.writeFloatAttrib(MdlUtils.TOKEN_STATIC_LENGTH, this.length); } - stream.writeFlag(this.filterMode.getMdlText()); - + stream.writeFlag(this.filterMode.toString()); stream.writeAttribUInt32(MdlUtils.TOKEN_ROWS, this.rows); stream.writeAttribUInt32(MdlUtils.TOKEN_COLUMNS, this.columns); - - switch ((int) this.headOrTail) { - case 0: - stream.writeFlag(MdlUtils.TOKEN_HEAD); - break; - case 1: - stream.writeFlag(MdlUtils.TOKEN_TAIL); - break; - case 2: - stream.writeFlag(MdlUtils.TOKEN_BOTH); - break; - default: - throw new IllegalStateException("Bad headOrTail value when saving MDL: " + this.headOrTail); - } - + stream.writeFlag(this.headOrTail.toString()); stream.writeFloatAttrib(MdlUtils.TOKEN_TAIL_LENGTH, this.tailLength); stream.writeFloatAttrib(MdlUtils.TOKEN_TIME, this.timeMiddle); @@ -419,8 +436,8 @@ public class ParticleEmitter2 extends GenericObject { } @Override - public long getByteLength() { - return 175 + super.getByteLength(); + public long getByteLength(final int version) { + return 175 + super.getByteLength(version); } public float getSpeed() { @@ -467,7 +484,7 @@ public class ParticleEmitter2 extends GenericObject { return this.columns; } - public long getHeadOrTail() { + public HeadOrTail getHeadOrTail() { return this.headOrTail; } @@ -515,4 +532,91 @@ public class ParticleEmitter2 extends GenericObject { return this.replaceableId; } + public void setSpeed(final float speed) { + this.speed = speed; + } + + public void setVariation(final float variation) { + this.variation = variation; + } + + public void setLatitude(final float latitude) { + this.latitude = latitude; + } + + public void setGravity(final float gravity) { + this.gravity = gravity; + } + + public void setLifeSpan(final float lifeSpan) { + this.lifeSpan = lifeSpan; + } + + public void setEmissionRate(final float emissionRate) { + this.emissionRate = emissionRate; + } + + public void setLength(final float length) { + this.length = length; + } + + public void setWidth(final float width) { + this.width = width; + } + + public void setFilterMode(final FilterMode filterMode) { + this.filterMode = filterMode; + } + + public void setRows(final long rows) { + this.rows = rows; + } + + public void setColumns(final long columns) { + this.columns = columns; + } + + public void setHeadOrTail(final HeadOrTail headOrTail) { + this.headOrTail = headOrTail; + } + + public void setTailLength(final float tailLength) { + this.tailLength = tailLength; + } + + public void setTimeMiddle(final float timeMiddle) { + this.timeMiddle = timeMiddle; + } + + public void setSegmentAlphas(final short[] segmentAlphas) { + this.segmentAlphas = segmentAlphas; + } + + public void setSegmentScaling(final float[] segmentScaling) { + this.segmentScaling = segmentScaling; + } + + public void setHeadIntervals(final long[][] headIntervals) { + this.headIntervals = headIntervals; + } + + public void setTailIntervals(final long[][] tailIntervals) { + this.tailIntervals = tailIntervals; + } + + public void setTextureId(final int textureId) { + this.textureId = textureId; + } + + public void setSquirt(final long squirt) { + this.squirt = squirt; + } + + public void setPriorityPlane(final int priorityPlane) { + this.priorityPlane = priorityPlane; + } + + public void setReplaceableId(final long replaceableId) { + this.replaceableId = replaceableId; + } } diff --git a/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxParticleEmitterPopcorn.java b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxParticleEmitterPopcorn.java new file mode 100644 index 0000000..f48f4ff --- /dev/null +++ b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxParticleEmitterPopcorn.java @@ -0,0 +1,180 @@ +package com.hiveworkshop.rms.parsers.mdlx; + +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlTokenInputStream; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlTokenOutputStream; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlUtils; +import com.hiveworkshop.rms.util.BinaryReader; +import com.hiveworkshop.rms.util.BinaryWriter; + +public class MdlxParticleEmitterPopcorn extends MdlxGenericObject { + public float lifeSpan = 0; + public float emissionRate = 0; + public float speed = 0; + public float[] color = new float[] { 1, 1, 1 }; + public float alpha = 0; + public int replaceableId = 0; + public String path = ""; + public String animationVisiblityGuide = ""; + + public MdlxParticleEmitterPopcorn() { + super(0); + } + + @Override + public void readMdx(final BinaryReader reader, final int version) { + final int position = reader.position(); + final long size = reader.readUInt32(); + + super.readMdx(reader, version); + + this.lifeSpan = reader.readFloat32(); + this.emissionRate = reader.readFloat32(); + this.speed = reader.readFloat32(); + reader.readFloat32Array(this.color); + this.alpha = reader.readFloat32(); + this.replaceableId = reader.readInt32(); + this.path = reader.read(260); + this.animationVisiblityGuide = reader.read(260); + + readTimelines(reader, size - (reader.position() - position)); + } + + @Override + public void writeMdx(final BinaryWriter writer, final int version) { + writer.writeUInt32(getByteLength(version)); + + super.writeMdx(writer, version); + + writer.writeFloat32(this.lifeSpan); + writer.writeFloat32(this.emissionRate); + writer.writeFloat32(this.speed); + writer.writeFloat32Array(this.color); + writer.writeFloat32(this.alpha); + writer.writeInt32(this.replaceableId); + writer.writeWithNulls(this.path, 260); + writer.writeWithNulls(this.animationVisiblityGuide, 260); + + writeNonGenericAnimationChunks(writer); + } + + @Override + public void readMdl(final MdlTokenInputStream stream, final int version) { + for (final String token : super.readMdlGeneric(stream)) { + switch (token) { + case "SortPrimsFarZ": + this.flags |= 0x10000; + break; + case "Unshaded": + this.flags |= 0x8000; + break; + case "Unfogged": + this.flags |= 0x40000; + break; + case "static LifeSpan": + this.lifeSpan = stream.readFloat(); + break; + case "LifeSpan": + readTimeline(stream, AnimationMap.KPPL); + break; + case "static EmissionRate": + this.emissionRate = stream.readFloat(); + break; + case "EmissionRate": + readTimeline(stream, AnimationMap.KPPE); + break; + case "static Speed": + this.speed = stream.readFloat(); + break; + case "Speed": + readTimeline(stream, AnimationMap.KPPS); + break; + case "static Color": + stream.readColor(this.color); + break; + case "Color": + readTimeline(stream, AnimationMap.KPPC); + break; + case "static Alpha": + this.alpha = stream.readFloat(); + break; + case "Alpha": + readTimeline(stream, AnimationMap.KPPA); + break; + case "Visibility": + readTimeline(stream, AnimationMap.KPPV); + break; + case "ReplaceableId": + this.replaceableId = stream.readInt(); + break; + case "Path": + this.path = stream.read(); + break; + case "AnimVisibilityGuide": + this.animationVisiblityGuide = stream.read(); + break; + default: + throw new RuntimeException("Unknown token in MdlxParticleEmitterPopcorn " + this.name + ": " + token); + } + } + } + + @Override + public void writeMdl(final MdlTokenOutputStream stream, final int version) { + stream.startObjectBlock(MdlUtils.TOKEN_PARTICLE_EMITTER2, this.name); + writeGenericHeader(stream); + + if ((this.flags & 0x10000) != 0) { + stream.writeFlag("SortPrimsFarZ"); + } + + if ((this.flags & 0x8000) != 0) { + stream.writeFlag("Unshaded"); + } + + if ((this.flags & 0x40000) != 0) { + stream.writeFlag("Unfogged"); + } + + if (!writeTimeline(stream, AnimationMap.KPPL)) { + stream.writeFloatAttrib("static LifeSpan", this.lifeSpan); + } + + if (!writeTimeline(stream, AnimationMap.KPPE)) { + stream.writeFloatAttrib("static EmissionRate", this.emissionRate); + } + + if (!writeTimeline(stream, AnimationMap.KPPS)) { + stream.writeFloatAttrib("static Speed", this.speed); + } + + if (!writeTimeline(stream, AnimationMap.KPPC)) { + stream.writeFloatArrayAttrib("static Color", this.color); + } + + if (!writeTimeline(stream, AnimationMap.KPPA)) { + stream.writeFloatAttrib("static Alpha", this.alpha); + } + + writeTimeline(stream, AnimationMap.KPPV); + + if (this.replaceableId != 0) { + stream.writeAttrib("ReplaceableId", this.replaceableId); + } + + if (this.path.length() != 0) { + stream.writeStringAttrib("Path", this.path); + } + + if (this.animationVisiblityGuide.length() != 0) { + stream.writeStringAttrib("AnimVisibilityGuide", this.animationVisiblityGuide); + } + + writeGenericTimelines(stream); + stream.endBlock(); + } + + @Override + public long getByteLength(final int version) { + return 556 + super.getByteLength(version); + } +} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/RibbonEmitter.java b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxRibbonEmitter.java similarity index 52% rename from core/src/com/etheller/warsmash/parsers/mdlx/RibbonEmitter.java rename to core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxRibbonEmitter.java index 4b0e53b..c504621 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/RibbonEmitter.java +++ b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxRibbonEmitter.java @@ -1,73 +1,73 @@ -package com.etheller.warsmash.parsers.mdlx; +package com.hiveworkshop.rms.parsers.mdlx; -import java.io.IOException; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlTokenInputStream; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlTokenOutputStream; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlUtils; +import com.hiveworkshop.rms.util.BinaryReader; +import com.hiveworkshop.rms.util.BinaryWriter; -import com.etheller.warsmash.util.MdlUtils; -import com.etheller.warsmash.util.ParseUtils; -import com.google.common.io.LittleEndianDataInputStream; -import com.google.common.io.LittleEndianDataOutputStream; +public class MdlxRibbonEmitter extends MdlxGenericObject { + public float heightAbove = 0; + public float heightBelow = 0; + public float alpha = 0; + public float[] color = new float[3]; + public float lifeSpan = 0; + public long textureSlot = 0; + public long emissionRate = 0; + public long rows = 0; + public long columns = 0; + public int materialId = 0; + public float gravity = 0; -public class RibbonEmitter extends GenericObject { - private float heightAbove = 0; - private float heightBelow = 0; - private float alpha = 0; - private final float[] color = new float[3]; - private float lifeSpan = 0; - private long textureSlot = 0; - private long emissionRate = 0; - private long rows = 0; - private long columns = 0; - private int materialId = 0; - private float gravity = 0; - - public RibbonEmitter() { + public MdlxRibbonEmitter() { super(0x4000); } @Override - public void readMdx(final LittleEndianDataInputStream stream) throws IOException { - final long size = ParseUtils.readUInt32(stream); + public void readMdx(final BinaryReader reader, final int version) { + final int position = reader.position(); + final long size = reader.readUInt32(); - super.readMdx(stream); + super.readMdx(reader, version); - this.heightAbove = stream.readFloat(); - this.heightBelow = stream.readFloat(); - this.alpha = stream.readFloat(); - ParseUtils.readFloatArray(stream, this.color); - this.lifeSpan = stream.readFloat(); - this.textureSlot = ParseUtils.readUInt32(stream); - this.emissionRate = ParseUtils.readUInt32(stream); - this.rows = ParseUtils.readUInt32(stream); - this.columns = ParseUtils.readUInt32(stream); - this.materialId = stream.readInt(); - this.gravity = stream.readFloat(); + this.heightAbove = reader.readFloat32(); + this.heightBelow = reader.readFloat32(); + this.alpha = reader.readFloat32(); + reader.readFloat32Array(this.color); + this.lifeSpan = reader.readFloat32(); + this.textureSlot = reader.readUInt32(); + this.emissionRate = reader.readUInt32(); + this.rows = reader.readUInt32(); + this.columns = reader.readUInt32(); + this.materialId = reader.readInt32(); + this.gravity = reader.readFloat32(); - readTimelines(stream, size - getByteLength()); + readTimelines(reader, size - (reader.position() - position)); } @Override - public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException { - ParseUtils.writeUInt32(stream, getByteLength()); + public void writeMdx(final BinaryWriter writer, final int version) { + writer.writeUInt32(getByteLength(version)); - super.writeMdx(stream); + super.writeMdx(writer, version); - stream.writeFloat(this.heightAbove); - stream.writeFloat(this.heightBelow); - stream.writeFloat(this.alpha); - ParseUtils.writeFloatArray(stream, this.color); - stream.writeFloat(this.lifeSpan); - ParseUtils.writeUInt32(stream, this.textureSlot); - ParseUtils.writeUInt32(stream, this.emissionRate); - ParseUtils.writeUInt32(stream, this.rows); - ParseUtils.writeUInt32(stream, this.columns); - stream.writeInt(this.materialId); - stream.writeFloat(this.gravity); + writer.writeFloat32(this.heightAbove); + writer.writeFloat32(this.heightBelow); + writer.writeFloat32(this.alpha); + writer.writeFloat32Array(this.color); + writer.writeFloat32(this.lifeSpan); + writer.writeUInt32(this.textureSlot); + writer.writeUInt32(this.emissionRate); + writer.writeUInt32(this.rows); + writer.writeUInt32(this.columns); + writer.writeInt32(this.materialId); + writer.writeFloat32(this.gravity); - writeNonGenericAnimationChunks(stream); + writeNonGenericAnimationChunks(writer); } @Override - public void readMdl(final MdlTokenInputStream stream) throws IOException { + public void readMdl(final MdlTokenInputStream stream, final int version) { for (final String token : super.readMdlGeneric(stream)) { switch (token) { case MdlUtils.TOKEN_STATIC_HEIGHT_ABOVE: @@ -122,13 +122,13 @@ public class RibbonEmitter extends GenericObject { this.materialId = stream.readInt(); break; default: - throw new IllegalStateException("Unknown token in RibbonEmitter " + this.name + ": " + token); + throw new RuntimeException("Unknown token in RibbonEmitter " + this.name + ": " + token); } } } @Override - public void writeMdl(final MdlTokenOutputStream stream) throws IOException { + public void writeMdl(final MdlTokenOutputStream stream, final int version) { stream.startObjectBlock(MdlUtils.TOKEN_RIBBON_EMITTER, this.name); writeGenericHeader(stream); @@ -170,8 +170,8 @@ public class RibbonEmitter extends GenericObject { } @Override - public long getByteLength() { - return 56 + super.getByteLength(); + public long getByteLength(final int version) { + return 56 + super.getByteLength(version); } public float getHeightAbove() { @@ -218,4 +218,47 @@ public class RibbonEmitter extends GenericObject { return this.gravity; } + public void setHeightAbove(final float heightAbove) { + this.heightAbove = heightAbove; + } + + public void setHeightBelow(final float heightBelow) { + this.heightBelow = heightBelow; + } + + public void setAlpha(final float alpha) { + this.alpha = alpha; + } + + public void setColor(final float[] color) { + this.color = color; + } + + public void setLifeSpan(final float lifeSpan) { + this.lifeSpan = lifeSpan; + } + + public void setTextureSlot(final long textureSlot) { + this.textureSlot = textureSlot; + } + + public void setEmissionRate(final long emissionRate) { + this.emissionRate = emissionRate; + } + + public void setRows(final long rows) { + this.rows = rows; + } + + public void setColumns(final long columns) { + this.columns = columns; + } + + public void setMaterialId(final int materialId) { + this.materialId = materialId; + } + + public void setGravity(final float gravity) { + this.gravity = gravity; + } } diff --git a/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxSequence.java b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxSequence.java new file mode 100644 index 0000000..9f0b3c8 --- /dev/null +++ b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxSequence.java @@ -0,0 +1,121 @@ +package com.hiveworkshop.rms.parsers.mdlx; + +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlTokenInputStream; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlTokenOutputStream; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlUtils; +import com.hiveworkshop.rms.util.BinaryReader; +import com.hiveworkshop.rms.util.BinaryWriter; + +public class MdlxSequence implements MdlxBlock { + public String name = ""; + public long[] interval = new long[2]; + public float moveSpeed = 0; + public int flags = 0; + public float rarity = 0; + public long syncPoint = 0; + public MdlxExtent extent = new MdlxExtent(); + + @Override + public void readMdx(final BinaryReader reader, final int version) { + this.name = reader.read(80); + reader.readUInt32Array(this.interval); + this.moveSpeed = reader.readFloat32(); + this.flags = reader.readInt32(); + this.rarity = reader.readFloat32(); + this.syncPoint = reader.readUInt32(); + this.extent.readMdx(reader); + } + + @Override + public void writeMdx(final BinaryWriter writer, final int version) { + writer.writeWithNulls(this.name, 80); + writer.writeUInt32Array(this.interval); + writer.writeFloat32(this.moveSpeed); + writer.writeUInt32(this.flags); + writer.writeFloat32(this.rarity); + writer.writeUInt32(this.syncPoint); + this.extent.writeMdx(writer); + } + + @Override + public void readMdl(final MdlTokenInputStream stream, final int version) { + this.name = stream.read(); + + for (final String token : stream.readBlock()) { + switch (token) { + case MdlUtils.TOKEN_INTERVAL: + stream.readIntArray(this.interval); + break; + case MdlUtils.TOKEN_NONLOOPING: + this.flags = 1; + break; + case MdlUtils.TOKEN_MOVESPEED: + this.moveSpeed = stream.readFloat(); + break; + case MdlUtils.TOKEN_RARITY: + this.rarity = stream.readFloat(); + break; + case MdlUtils.TOKEN_MINIMUM_EXTENT: + stream.readFloatArray(this.extent.min); + break; + case MdlUtils.TOKEN_MAXIMUM_EXTENT: + stream.readFloatArray(this.extent.max); + break; + case MdlUtils.TOKEN_BOUNDSRADIUS: + this.extent.boundsRadius = stream.readFloat(); + break; + default: + throw new IllegalStateException("Unknown token in Sequence \"" + this.name + "\": " + token); + } + } + } + + @Override + public void writeMdl(final MdlTokenOutputStream stream, final int version) { + stream.startObjectBlock(MdlUtils.TOKEN_ANIM, this.name); + stream.writeArrayAttrib(MdlUtils.TOKEN_INTERVAL, this.interval); + + if (this.flags == 1) { + stream.writeFlag(MdlUtils.TOKEN_NONLOOPING); + } + + if (this.moveSpeed != 0) { + stream.writeFloatAttrib(MdlUtils.TOKEN_MOVESPEED, this.moveSpeed); + } + + if (this.rarity != 0) { + stream.writeFloatAttrib(MdlUtils.TOKEN_RARITY, this.rarity); + } + + this.extent.writeMdl(stream); + stream.endBlock(); + } + + public String getName() { + return this.name; + } + + public long[] getInterval() { + return this.interval; + } + + public float getMoveSpeed() { + return this.moveSpeed; + } + + public int getFlags() { + return this.flags; + } + + public float getRarity() { + return this.rarity; + } + + public long getSyncPoint() { + return this.syncPoint; + } + + public MdlxExtent getExtent() { + return this.extent; + } +} diff --git a/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxTexture.java b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxTexture.java new file mode 100644 index 0000000..cc15b8c --- /dev/null +++ b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxTexture.java @@ -0,0 +1,120 @@ +package com.hiveworkshop.rms.parsers.mdlx; + +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlTokenInputStream; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlTokenOutputStream; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlUtils; +import com.hiveworkshop.rms.util.BinaryReader; +import com.hiveworkshop.rms.util.BinaryWriter; + +public class MdlxTexture implements MdlxBlock { + public enum WrapMode { + REPEAT_BOTH(true, true), + WRAP_WIDTH(true, false), + WRAP_HEIGHT(false, true), + WRAP_BOTH(false, false); + + private final boolean wrapWidth; + private final boolean wrapHeight; + + public static WrapMode fromId(final int id) { + return values()[id]; + } + + private WrapMode(final boolean wrapWidth, final boolean wrapHeight) { + this.wrapWidth = wrapWidth; + this.wrapHeight = wrapHeight; + } + + public boolean isWrapWidth() { + return this.wrapWidth; + } + + public boolean isWrapHeight() { + return this.wrapHeight; + } + } + + public int replaceableId = 0; + public String path = ""; + public WrapMode wrapMode = WrapMode.REPEAT_BOTH; + + @Override + public void readMdx(final BinaryReader reader, final int version) { + this.replaceableId = reader.readInt32(); + this.path = reader.read(260); + this.wrapMode = WrapMode.fromId(reader.readInt32()); + } + + @Override + public void writeMdx(final BinaryWriter writer, final int version) { + writer.writeInt32(this.replaceableId); + writer.writeWithNulls(this.path, 260); + writer.writeInt32(this.wrapMode.ordinal()); + } + + @Override + public void readMdl(final MdlTokenInputStream stream, final int version) { + for (final String token : stream.readBlock()) { + switch (token) { + case MdlUtils.TOKEN_IMAGE: + this.path = stream.read(); + break; + case MdlUtils.TOKEN_REPLACEABLE_ID: + this.replaceableId = stream.readInt(); + break; + case MdlUtils.TOKEN_WRAP_WIDTH: + this.wrapMode = WrapMode.fromId(this.wrapMode.ordinal() + 0x1); + break; + case MdlUtils.TOKEN_WRAP_HEIGHT: + this.wrapMode = WrapMode.fromId(this.wrapMode.ordinal() + 0x2); + break; + default: + throw new RuntimeException("Unknown token in Texture: " + token); + } + } + } + + @Override + public void writeMdl(final MdlTokenOutputStream stream, final int version) { + stream.startBlock(MdlUtils.TOKEN_BITMAP); + stream.writeStringAttrib(MdlUtils.TOKEN_IMAGE, this.path); + + if (this.replaceableId != 0) { + stream.writeAttrib(MdlUtils.TOKEN_REPLACEABLE_ID, this.replaceableId); + } + + if ((this.wrapMode == WrapMode.WRAP_WIDTH) || (this.wrapMode == WrapMode.WRAP_BOTH)) { + stream.writeFlag(MdlUtils.TOKEN_WRAP_WIDTH); + } + + if ((this.wrapMode == WrapMode.WRAP_HEIGHT) || (this.wrapMode == WrapMode.WRAP_BOTH)) { + stream.writeFlag(MdlUtils.TOKEN_WRAP_HEIGHT); + } + + stream.endBlock(); + } + + public int getReplaceableId() { + return this.replaceableId; + } + + public String getPath() { + return this.path; + } + + public WrapMode getWrapMode() { + return this.wrapMode; + } + + public void setReplaceableId(final int replaceableId) { + this.replaceableId = replaceableId; + } + + public void setPath(final String path) { + this.path = path; + } + + public void setWrapMode(final WrapMode wrapMode) { + this.wrapMode = wrapMode; + } +} diff --git a/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxTextureAnimation.java b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxTextureAnimation.java new file mode 100644 index 0000000..a61c04f --- /dev/null +++ b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxTextureAnimation.java @@ -0,0 +1,56 @@ +package com.hiveworkshop.rms.parsers.mdlx; + +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlTokenInputStream; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlTokenOutputStream; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlUtils; +import com.hiveworkshop.rms.util.BinaryReader; +import com.hiveworkshop.rms.util.BinaryWriter; + +public class MdlxTextureAnimation extends MdlxAnimatedObject { + @Override + public void readMdx(final BinaryReader reader, final int version) { + final long size = reader.readUInt32(); + + readTimelines(reader, size - 4); + } + + @Override + public void writeMdx(final BinaryWriter writer, final int version) { + writer.writeUInt32(getByteLength(version)); + + writeTimelines(writer); + } + + @Override + public void readMdl(final MdlTokenInputStream stream, final int version) { + for (final String token : stream.readBlock()) { + switch (token) { + case MdlUtils.TOKEN_TRANSLATION: + readTimeline(stream, AnimationMap.KTAT); + break; + case MdlUtils.TOKEN_ROTATION: + readTimeline(stream, AnimationMap.KTAR); + break; + case MdlUtils.TOKEN_SCALING: + readTimeline(stream, AnimationMap.KTAS); + break; + default: + throw new RuntimeException("Unknown token in TextureAnimation: " + token); + } + } + } + + @Override + public void writeMdl(final MdlTokenOutputStream stream, final int version) { + stream.startBlock(MdlUtils.TOKEN_TVERTEX_ANIM_SPACE); + writeTimeline(stream, AnimationMap.KTAT); + writeTimeline(stream, AnimationMap.KTAR); + writeTimeline(stream, AnimationMap.KTAS); + stream.endBlock(); + } + + @Override + public long getByteLength(final int version) { + return 4 + super.getByteLength(version); + } +} diff --git a/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxTimelineDescriptor.java b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxTimelineDescriptor.java new file mode 100644 index 0000000..b413cfc --- /dev/null +++ b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxTimelineDescriptor.java @@ -0,0 +1,18 @@ +package com.hiveworkshop.rms.parsers.mdlx; + +import com.hiveworkshop.rms.parsers.mdlx.timeline.MdlxFloatArrayTimeline; +import com.hiveworkshop.rms.parsers.mdlx.timeline.MdlxFloatTimeline; +import com.hiveworkshop.rms.parsers.mdlx.timeline.MdlxTimeline; +import com.hiveworkshop.rms.parsers.mdlx.timeline.MdlxUInt32Timeline; + +public interface MdlxTimelineDescriptor { + MdlxTimeline createTimeline(); + + MdlxTimelineDescriptor UINT32_TIMELINE = MdlxUInt32Timeline::new; + + MdlxTimelineDescriptor FLOAT_TIMELINE = MdlxFloatTimeline::new; + + MdlxTimelineDescriptor VECTOR3_TIMELINE = () -> new MdlxFloatArrayTimeline(3); + + MdlxTimelineDescriptor VECTOR4_TIMELINE = () -> new MdlxFloatArrayTimeline(4); +} diff --git a/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxUnknownChunk.java b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxUnknownChunk.java new file mode 100644 index 0000000..e478787 --- /dev/null +++ b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxUnknownChunk.java @@ -0,0 +1,31 @@ +package com.hiveworkshop.rms.parsers.mdlx; + +import com.etheller.warsmash.util.War3ID; +import com.hiveworkshop.rms.util.BinaryReader; +import com.hiveworkshop.rms.util.BinaryWriter; + +public class MdlxUnknownChunk implements MdlxChunk { + public final short[] chunk; + public final War3ID tag; + + public MdlxUnknownChunk(final BinaryReader reader, final long size, final War3ID tag) { + System.err.println("Loading unknown chunk: " + tag); + this.chunk = reader.readUInt8Array((int) size); + this.tag = tag; + } + + public void writeMdx(final BinaryWriter writer, final int version) { + writer.writeTag(this.tag.getValue()); + // Below: Byte.BYTES used because it's mean as a UInt8 array. This is + // not using Short.BYTES, deliberately, despite using a short[] as the + // type for the array. This is a Java problem that did not exist in the original + // JavaScript implementation by Ghostwolf + writer.writeUInt32(this.chunk.length * Byte.BYTES); + writer.writeUInt8Array(this.chunk); + } + + @Override + public long getByteLength(final int version) { + return 8 + (this.chunk.length * Byte.BYTES); + } +} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/mdl/GhostwolfTokenInputStream.java b/core/src/com/hiveworkshop/rms/parsers/mdlx/mdl/MdlTokenInputStream.java similarity index 59% rename from core/src/com/etheller/warsmash/parsers/mdlx/mdl/GhostwolfTokenInputStream.java rename to core/src/com/hiveworkshop/rms/parsers/mdlx/mdl/MdlTokenInputStream.java index 21076c5..bdba276 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/mdl/GhostwolfTokenInputStream.java +++ b/core/src/com/hiveworkshop/rms/parsers/mdlx/mdl/MdlTokenInputStream.java @@ -1,24 +1,17 @@ -package com.etheller.warsmash.parsers.mdlx.mdl; +package com.hiveworkshop.rms.parsers.mdlx.mdl; import java.nio.ByteBuffer; import java.util.Iterator; -import com.etheller.warsmash.parsers.mdlx.MdlTokenInputStream; - -public class GhostwolfTokenInputStream implements MdlTokenInputStream { +public class MdlTokenInputStream { private final ByteBuffer buffer; private int index; - private final int ident; - private final int fractionDigits; - public GhostwolfTokenInputStream(final ByteBuffer buffer) { + public MdlTokenInputStream(final ByteBuffer buffer) { this.buffer = buffer; this.index = 0; - this.ident = 0; // Used for writing blocks nicely. - this.fractionDigits = 6; // The number of fraction digits when writing floats. } - @Override public String read() { boolean inComment = false; boolean inString = false; @@ -82,50 +75,44 @@ public class GhostwolfTokenInputStream implements MdlTokenInputStream { return null; } - @Override public String peek() { final int index = this.index; - final String value = this.read(); + final String value = read(); this.index = index; return value; } - @Override public long readUInt32() { - return Long.parseLong(this.read()); + return Long.parseLong(read()); } - @Override public int readInt() { - return Integer.parseInt(this.read()); + return Integer.parseInt(read()); } - @Override public float readFloat() { - return Float.parseFloat(this.read()); + return Float.parseFloat(read()); } - @Override public void readIntArray(final long[] values) { - this.read(); // { + read(); // { for (int i = 0, l = values.length; i < l; i++) { - values[i] = this.readInt(); + values[i] = readInt(); } - this.read(); // } + read(); // } } - @Override public float[] readFloatArray(final float[] values) { - this.read(); // { + read(); // { for (int i = 0, l = values.length; i < l; i++) { - values[i] = this.readFloat(); + values[i] = readFloat(); } - this.read(); // } + read(); // } return values; } @@ -133,100 +120,89 @@ public class GhostwolfTokenInputStream implements MdlTokenInputStream { * Read an MDL keyframe value. If the value is a scalar, it is just the number. * If the value is a vector, it is enclosed with curly braces. * - * @param {Float32Array|Uint32Array} value + * @param values {Float32Array|Uint32Array} */ - @Override public void readKeyframe(final float[] values) { if (values.length == 1) { - values[0] = this.readFloat(); + values[0] = readFloat(); } else { - this.readFloatArray(values); + readFloatArray(values); } } - @Override public float[] readVectorArray(final float[] array, final int vectorLength) { - this.read(); // { + read(); // { for (int i = 0, l = array.length / vectorLength; i < l; i++) { - this.read(); // { + read(); // { for (int j = 0; j < vectorLength; j++) { - array[(i * vectorLength) + j] = this.readFloat(); + array[(i * vectorLength) + j] = readFloat(); } - this.read(); // } + read(); // } } - this.read(); // } + read(); // } return array; } - @Override public Iterable readBlock() { - this.read(); // { - return new Iterable() { + read(); // { + return () -> new Iterator() { + String current; + private boolean hasLoaded = false; + @Override - public Iterator iterator() { - return new Iterator() { - String current; - private boolean hasLoaded = false; + public String next() { + if (!this.hasLoaded) { + hasNext(); + } + this.hasLoaded = false; + return this.current; + } - @Override - public String next() { - if (!this.hasLoaded) { - hasNext(); - } - this.hasLoaded = false; - return this.current; - } - - @Override - public boolean hasNext() { - this.current = read(); - this.hasLoaded = true; - return (this.current != null) && !this.current.equals("}"); - } - }; + @Override + public boolean hasNext() { + this.current = read(); + this.hasLoaded = true; + return (this.current != null) && !this.current.equals("}"); } }; } - @Override public int[] readUInt16Array(final int[] values) { - this.read(); // { + read(); // { for (int i = 0, l = values.length; i < l; i++) { - values[i] = this.readInt(); + values[i] = readInt(); } - this.read(); // } + read(); // } return values; } - @Override public short[] readUInt8Array(final short[] values) { - this.read(); // { + read(); // { for (int i = 0, l = values.length; i < l; i++) { - values[i] = Short.parseShort(this.read()); + values[i] = Short.parseShort(read()); } - this.read(); // } + read(); // } return values; } - @Override public void readColor(final float[] color) { - this.read(); // { + read(); // { - color[2] = this.readFloat(); - color[1] = this.readFloat(); - color[0] = this.readFloat(); + color[2] = readFloat(); + color[1] = readFloat(); + color[0] = readFloat(); - this.read(); // } + read(); // } } } diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/mdl/GhostwolfTokenOutputStream.java b/core/src/com/hiveworkshop/rms/parsers/mdlx/mdl/MdlTokenOutputStream.java similarity index 64% rename from core/src/com/etheller/warsmash/parsers/mdlx/mdl/GhostwolfTokenOutputStream.java rename to core/src/com/hiveworkshop/rms/parsers/mdlx/mdl/MdlTokenOutputStream.java index d154b20..8d35bfc 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/mdl/GhostwolfTokenOutputStream.java +++ b/core/src/com/hiveworkshop/rms/parsers/mdlx/mdl/MdlTokenOutputStream.java @@ -1,183 +1,143 @@ -package com.etheller.warsmash.parsers.mdlx.mdl; +package com.hiveworkshop.rms.parsers.mdlx.mdl; -import java.io.IOException; +public class MdlTokenOutputStream { + public final StringBuilder buffer = new StringBuilder(); + public int ident = 0; + public int fractionDigits = 6; -import com.etheller.warsmash.parsers.mdlx.MdlTokenOutputStream; - -public class GhostwolfTokenOutputStream implements MdlTokenOutputStream { - private final Appendable buffer; - private final int index; - private int ident; - private final int fractionDigits; - - public GhostwolfTokenOutputStream(final Appendable appendable) { - this.buffer = appendable; - this.index = 0; - this.ident = 0; // Used for writing blocks nicely. - this.fractionDigits = 6; // The number of fraction digits when writing floats. - } - - @Override public void writeKeyframe(final String prefix, final long uInt32Value) { writeAttribUInt32(prefix, uInt32Value); } - @Override public void writeKeyframe(final String prefix, final float floatValue) { writeFloatAttrib(prefix, floatValue); } - @Override public void writeKeyframe(final String prefix, final float[] floatArrayValues) { writeFloatArrayAttrib(prefix, floatArrayValues); } - @Override public void indent() { this.ident += 1; } - @Override public void unindent() { this.ident -= 1; } - @Override public void startObjectBlock(final String name, final String objectName) { - this.writeLine(name + " \"" + objectName + "\" {"); + writeLine(name + " \"" + objectName + "\" {"); this.ident += 1; } - @Override public void startBlock(final String name, final int blockSize) { - this.writeLine(name + " " + blockSize + " {" + ""); + writeLine(name + " " + blockSize + " {" + ""); this.ident += 1; } - @Override public void startBlock(final String name) { - this.writeLine(name + " {" + ""); + writeLine(name + " {" + ""); this.ident += 1; } - @Override public void writeFlag(final String token) { - this.writeLine(token + ","); + writeLine(token + ","); } - @Override public void writeFlagUInt32(final long flag) { - this.writeLine(flag + ","); + writeLine(flag + ","); } - @Override public void writeAttrib(final String string, final int globalSequenceId) { writeLine(string + " " + globalSequenceId + ","); } - @Override public void writeAttribUInt32(final String attribName, final long uInt) { writeLine(attribName + " " + uInt + ","); } - @Override public void writeAttrib(final String string, final String value) { writeLine(string + " " + value + ","); } - @Override public void writeFloatAttrib(final String attribName, final float value) { writeLine(attribName + " " + value + ","); } - @Override public void writeStringAttrib(final String attribName, final String value) { writeLine(attribName + " \"" + value + "\","); } - @Override public void writeFloatArrayAttrib(final String attribName, final float[] floatArray) { - this.writeLine(attribName + " { " + formatFloatArray(floatArray) + " },"); + writeLine(attribName + " { " + formatFloatArray(floatArray) + " },"); } - @Override public void writeLongSubArrayAttrib(final String attribName, final long[] array, final int startIndexInclusive, final int endIndexExclusive) { - this.writeLine(attribName + " { " + formatLongSubArray(array, startIndexInclusive, endIndexExclusive) + " },"); + writeLine(attribName + " { " + formatLongSubArray(array, startIndexInclusive, endIndexExclusive) + " },"); } - @Override public void writeFloatArray(final float[] floatArray) { - this.writeLine("{ " + formatFloatArray(floatArray) + " },"); + writeLine("{ " + formatFloatArray(floatArray) + " },"); + } + + public void writeShortArrayRaw(final short[] shortArray) { + writeLine(formatShortArray(shortArray) + ","); } public void writeFloatSubArray(final float[] floatArray, final int startIndexInclusive, final int endIndexExclusive) { - this.writeLine("{ " + formatFloatSubArray(floatArray, startIndexInclusive, endIndexExclusive) + " },"); + writeLine("{ " + formatFloatSubArray(floatArray, startIndexInclusive, endIndexExclusive) + " },"); } - @Override public void writeVectorArray(final String token, final float[] vectors, final int vectorLength) { - this.startBlock(token, vectors.length / vectorLength); + startBlock(token, vectors.length / vectorLength); for (int i = 0, l = vectors.length; i < l; i += vectorLength) { - this.writeFloatSubArray(vectors, i, i + vectorLength); + writeFloatSubArray(vectors, i, i + vectorLength); } - this.endBlock(); + endBlock(); } - @Override public void endBlock() { this.ident -= 1; - this.writeLine("}"); + writeLine("}"); } - @Override public void endBlockComma() { this.ident -= 1; - this.writeLine("},"); + writeLine("},"); } - @Override public void writeLine(final String string) { - try { - for (int i = 0; i < this.ident; i++) { - this.buffer.append('\t'); - } - this.buffer.append(string); - this.buffer.append('\n'); - } - catch (final IOException e) { - throw new RuntimeException(e); + for (int i = 0; i < this.ident; i++) { + this.buffer.append("\t"); } + this.buffer.append(string); + this.buffer.append('\n'); } - @Override public void startBlock(final String tokenFaces, final int sizeNumberProbably, final int length) { - this.writeLine(tokenFaces + " " + sizeNumberProbably + " " + length + " {" + ""); + writeLine(tokenFaces + " " + sizeNumberProbably + " " + length + " {" + ""); this.ident += 1; } - @Override public void writeColor(final String tokenStaticColor, final float[] color) { - this.writeLine(tokenStaticColor + " { " + color[2] + ", " + color[1] + ", " + color[0] + " },"); + writeLine(tokenStaticColor + " { " + color[2] + ", " + color[1] + ", " + color[0] + " },"); } - @Override public void writeArrayAttrib(final String tokenAlpha, final short[] uint8Array) { - this.writeLine(tokenAlpha + " { " + formatShortArray(uint8Array) + " },"); + writeLine(tokenAlpha + " { " + formatShortArray(uint8Array) + " },"); } - @Override public void writeArrayAttrib(final String tokenAlpha, final int[] uint16Array) { - this.writeLine(tokenAlpha + " { " + formatIntArray(uint16Array) + " },"); + writeLine(tokenAlpha + " { " + formatIntArray(uint16Array) + " },"); } - @Override public void writeArrayAttrib(final String tokenAlpha, final long[] uint32Array) { - this.writeLine(tokenAlpha + " { " + formatLongArray(uint32Array) + " },"); + writeLine(tokenAlpha + " { " + formatLongArray(uint32Array) + " },"); } private String formatFloat(final float value) { @@ -193,44 +153,44 @@ public class GhostwolfTokenOutputStream implements MdlTokenOutputStream { private String formatFloatArray(final float[] value) { final StringBuilder stringBuilder = new StringBuilder(); - for (int i = 0, l = value.length; i < l; i++) { + for (final float v : value) { if (stringBuilder.length() > 0) { stringBuilder.append(", "); } - stringBuilder.append(formatFloat(value[i])); + stringBuilder.append(formatFloat(v)); } return stringBuilder.toString(); } private String formatLongArray(final long[] value) { final StringBuilder stringBuilder = new StringBuilder(); - for (int i = 0, l = value.length; i < l; i++) { + for (final long item : value) { if (stringBuilder.length() > 0) { stringBuilder.append(", "); } - stringBuilder.append(value[i]); + stringBuilder.append(item); } return stringBuilder.toString(); } private String formatShortArray(final short[] value) { final StringBuilder stringBuilder = new StringBuilder(); - for (int i = 0, l = value.length; i < l; i++) { + for (final short item : value) { if (stringBuilder.length() > 0) { stringBuilder.append(", "); } - stringBuilder.append(value[i]); + stringBuilder.append(item); } return stringBuilder.toString(); } private String formatIntArray(final int[] value) { final StringBuilder stringBuilder = new StringBuilder(); - for (int i = 0, l = value.length; i < l; i++) { + for (final int j : value) { if (stringBuilder.length() > 0) { stringBuilder.append(", "); } - stringBuilder.append(value[i]); + stringBuilder.append(j); } return stringBuilder.toString(); } @@ -257,5 +217,4 @@ public class GhostwolfTokenOutputStream implements MdlTokenOutputStream { } return stringBuilder.toString(); } - } diff --git a/core/src/com/hiveworkshop/rms/parsers/mdlx/mdl/MdlUtils.java b/core/src/com/hiveworkshop/rms/parsers/mdlx/mdl/MdlUtils.java new file mode 100644 index 0000000..b53f567 --- /dev/null +++ b/core/src/com/hiveworkshop/rms/parsers/mdlx/mdl/MdlUtils.java @@ -0,0 +1,204 @@ +package com.hiveworkshop.rms.parsers.mdlx.mdl; + +/** + * Constants for the tokens were used to prevent typos in token literals. It + * would be very easy for me to type "Interval" in one place and "Intreval" in + * another by mistake. With this paradigm, that mistake causes a compile error, + * since TOKEN_INTREVAL does not exist. + */ +public class MdlUtils { + public static final String TOKEN_VERSION = "Version"; + + public static final String TOKEN_MODEL = "Model"; + + public static final String TOKEN_SEQUENCES = "Sequences"; + + public static final String TOKEN_GLOBAL_SEQUENCES = "GlobalSequences"; + + public static final String TOKEN_INTERVAL = "Interval"; + public static final String TOKEN_NONLOOPING = "NonLooping"; + public static final String TOKEN_MOVESPEED = "MoveSpeed"; + public static final String TOKEN_RARITY = "Rarity"; + + public static final String TOKEN_FORMAT_VERSION = "FormatVersion"; + public static final String TOKEN_BLEND_TIME = "BlendTime"; + public static final String TOKEN_DURATION = "Duration"; + + public static final String TOKEN_IMAGE = "Image"; + public static final String TOKEN_WRAP_WIDTH = "WrapWidth"; + public static final String TOKEN_WRAP_HEIGHT = "WrapHeight"; + public static final String TOKEN_BITMAP = "Bitmap"; + + public static final String TOKEN_TVERTEX_ANIM_SPACE = "TVertexAnim"; + + public static final String TOKEN_DONT_INTERP = "DontInterp"; + public static final String TOKEN_LINEAR = "Linear"; + public static final String TOKEN_HERMITE = "Hermite"; + public static final String TOKEN_BEZIER = "Bezier"; + public static final String TOKEN_GLOBAL_SEQ_ID = "GlobalSeqId"; + + public static final String TOKEN_PLANE = "Plane"; + public static final String TOKEN_BOX = "Box"; + public static final String TOKEN_SPHERE = "Sphere"; + public static final String TOKEN_CYLINDER = "Cylinder"; + + public static final String TOKEN_GEOSETID = "GeosetId"; + public static final String TOKEN_MULTIPLE = "Multiple"; + public static final String TOKEN_GEOSETANIMID = "GeosetAnimId"; + public static final String TOKEN_NONE = "None"; + public static final String TOKEN_OBJECTID = "ObjectId"; + public static final String TOKEN_PARENT = "Parent"; + public static final String TOKEN_BILLBOARDED_LOCK_Z = "BillboardedLockZ"; + public static final String TOKEN_BILLBOARDED_LOCK_Y = "BillboardedLockY"; + public static final String TOKEN_BILLBOARDED_LOCK_X = "BillboardedLockX"; + public static final String TOKEN_BILLBOARDED = "Billboarded"; + public static final String TOKEN_CAMERA_ANCHORED = "CameraAnchored"; + public static final String TOKEN_DONT_INHERIT = "DontInherit"; + public static final String TOKEN_ROTATION = "Rotation"; + public static final String TOKEN_TRANSLATION = "Translation"; + public static final String TOKEN_SCALING = "Scaling"; + public static final String TOKEN_STATIC = "static"; + public static final String TOKEN_ATTACHMENT_ID = "AttachmentID"; + public static final String TOKEN_PATH = "Path"; + public static final String TOKEN_VISIBILITY = "Visibility"; + public static final String TOKEN_POSITION = "Position"; + public static final String TOKEN_FIELDOFVIEW = "FieldOfView"; + public static final String TOKEN_FARCLIP = "FarClip"; + public static final String TOKEN_NEARCLIP = "NearClip"; + public static final String TOKEN_TARGET = "Target"; + public static final String TOKEN_VERTICES = "Vertices"; + public static final String TOKEN_BOUNDSRADIUS = "BoundsRadius"; + public static final String TOKEN_EVENT_TRACK = "EventTrack"; + public static final String TOKEN_MAXIMUM_EXTENT = "MaximumExtent"; + public static final String TOKEN_MINIMUM_EXTENT = "MinimumExtent"; + public static final String TOKEN_NORMALS = "Normals"; + public static final String TOKEN_TVERTICES = "TVertices"; + public static final String TOKEN_VERTEX_GROUP = "VertexGroup"; + public static final String TOKEN_FACES = "Faces"; + public static final String TOKEN_GROUPS = "Groups"; + public static final String TOKEN_ANIM = "Anim"; + public static final String TOKEN_MATERIAL_ID = "MaterialID"; + public static final String TOKEN_SELECTION_GROUP = "SelectionGroup"; + public static final String TOKEN_UNSELECTABLE = "Unselectable"; + public static final String TOKEN_TRIANGLES = "Triangles"; + public static final String TOKEN_MATRICES = "Matrices"; + public static final String TOKEN_DROP_SHADOW = "DropShadow"; + public static final String TOKEN_ALPHA = "Alpha"; + public static final String TOKEN_COLOR = "Color"; + public static final String TOKEN_STATIC_ALPHA = TOKEN_STATIC + " " + TOKEN_ALPHA; + public static final String TOKEN_STATIC_COLOR = TOKEN_STATIC + " " + TOKEN_COLOR; + public static final String TOKEN_FILTER_MODE = "FilterMode"; + public static final String TOKEN_UNSHADED = "Unshaded"; + public static final String TOKEN_SPHERE_ENV_MAP = "SphereEnvMap"; + public static final String TOKEN_TWO_SIDED = "TwoSided"; + public static final String TOKEN_UNFOGGED = "Unfogged"; + public static final String TOKEN_NO_DEPTH_TEST = "NoDepthTest"; + public static final String TOKEN_NO_DEPTH_SET = "NoDepthSet"; + public static final String TOKEN_TEXTURE_ID = "TextureID"; + public static final String TOKEN_STATIC_TEXTURE_ID = TOKEN_STATIC + " " + TOKEN_TEXTURE_ID; + public static final String TOKEN_TVERTEX_ANIM_ID = "TVertexAnimId"; + public static final String TOKEN_COORD_ID = "CoordId"; + + public static final String TOKEN_OMNIDIRECTIONAL = "Omnidirectional"; + public static final String TOKEN_DIRECTIONAL = "Directional"; + public static final String TOKEN_AMBIENT = "Ambient"; + public static final String TOKEN_ATTENUATION_START = "AttenuationStart"; + public static final String TOKEN_STATIC_ATTENUATION_START = TOKEN_STATIC + " " + TOKEN_ATTENUATION_START; + public static final String TOKEN_ATTENUATION_END = "AttenuationEnd"; + public static final String TOKEN_STATIC_ATTENUATION_END = TOKEN_STATIC + " " + TOKEN_ATTENUATION_END; + public static final String TOKEN_INTENSITY = "Intensity"; + public static final String TOKEN_STATIC_INTENSITY = TOKEN_STATIC + " " + TOKEN_INTENSITY; + public static final String TOKEN_AMB_INTENSITY = "AmbIntensity"; + public static final String TOKEN_STATIC_AMB_INTENSITY = TOKEN_STATIC + " " + TOKEN_AMB_INTENSITY; + public static final String TOKEN_AMB_COLOR = "AmbColor"; + public static final String TOKEN_STATIC_AMB_COLOR = TOKEN_STATIC + " " + TOKEN_AMB_COLOR; + + public static final String TOKEN_CONSTANT_COLOR = "ConstantColor"; + public static final String TOKEN_SORT_PRIMS_NEAR_Z = "SortPrimsNearZ"; + public static final String TOKEN_SORT_PRIMS_FAR_Z = "SortPrimsFarZ"; + public static final String TOKEN_FULL_RESOLUTION = "FullResolution"; + public static final String TOKEN_PRIORITY_PLANE = "PriorityPlane"; + + public static final String TOKEN_EMITTER_USES_MDL = "EmitterUsesMDL"; + public static final String TOKEN_EMITTER_USES_TGA = "EmitterUsesTGA"; + public static final String TOKEN_EMISSION_RATE = "EmissionRate"; + public static final String TOKEN_STATIC_EMISSION_RATE = TOKEN_STATIC + " " + TOKEN_EMISSION_RATE; + public static final String TOKEN_GRAVITY = "Gravity"; + public static final String TOKEN_STATIC_GRAVITY = TOKEN_STATIC + " " + TOKEN_GRAVITY; + public static final String TOKEN_LONGITUDE = "Longitude"; + public static final String TOKEN_STATIC_LONGITUDE = TOKEN_STATIC + " " + TOKEN_LONGITUDE; + public static final String TOKEN_LATITUDE = "Latitude"; + public static final String TOKEN_STATIC_LATITUDE = TOKEN_STATIC + " " + TOKEN_LATITUDE; + public static final String TOKEN_PARTICLE = "Particle"; + public static final String TOKEN_LIFE_SPAN = "LifeSpan"; + public static final String TOKEN_STATIC_LIFE_SPAN = TOKEN_STATIC + " " + TOKEN_LIFE_SPAN; + public static final String TOKEN_INIT_VELOCITY = "InitVelocity"; + public static final String TOKEN_STATIC_INIT_VELOCITY = TOKEN_STATIC + " " + TOKEN_INIT_VELOCITY; + + public static final String TOKEN_LINE_EMITTER = "LineEmitter"; + public static final String TOKEN_MODEL_SPACE = "ModelSpace"; + public static final String TOKEN_XY_QUAD = "XYQuad"; + public static final String TOKEN_SPEED = "Speed"; + public static final String TOKEN_STATIC_SPEED = TOKEN_STATIC + " " + TOKEN_SPEED; + public static final String TOKEN_VARIATION = "Variation"; + public static final String TOKEN_STATIC_VARIATION = TOKEN_STATIC + " " + TOKEN_VARIATION; + public static final String TOKEN_SQUIRT = "Squirt"; + public static final String TOKEN_WIDTH = "Width"; + public static final String TOKEN_STATIC_WIDTH = TOKEN_STATIC + " " + TOKEN_WIDTH; + public static final String TOKEN_LENGTH = "Length"; + public static final String TOKEN_STATIC_LENGTH = TOKEN_STATIC + " " + TOKEN_LENGTH; + public static final String TOKEN_ROWS = "Rows"; + public static final String TOKEN_COLUMNS = "Columns"; + public static final String TOKEN_HEAD = "Head"; + public static final String TOKEN_TAIL = "Tail"; + public static final String TOKEN_BOTH = "Both"; + public static final String TOKEN_TAIL_LENGTH = "TailLength"; + public static final String TOKEN_TIME = "Time"; + public static final String TOKEN_SEGMENT_COLOR = "SegmentColor"; + public static final String TOKEN_PARTICLE_SCALING = "ParticleScaling"; + public static final String TOKEN_LIFE_SPAN_UV_ANIM = "LifeSpanUVAnim"; + public static final String TOKEN_DECAY_UV_ANIM = "DecayUVAnim"; + public static final String TOKEN_TAIL_UV_ANIM = "TailUVAnim"; + public static final String TOKEN_TAIL_DECAY_UV_ANIM = "TailDecayUVAnim"; + public static final String TOKEN_REPLACEABLE_ID = "ReplaceableId"; + public static final String TOKEN_BLEND = "Blend";// ParticleEmitter2.FilterMode.BLEND.getMdlText(); + public static final String TOKEN_ADDITIVE = "Additive";// ParticleEmitter2.FilterMode.ADDITIVE.getMdlText(); + public static final String TOKEN_MODULATE = "Modulate";// ParticleEmitter2.FilterMode.MODULATE.getMdlText(); + public static final String TOKEN_MODULATE2X = "Modulate2x";// ParticleEmitter2.FilterMode.MODULATE2X.getMdlText(); + public static final String TOKEN_ALPHAKEY = "AlphaKey";// ParticleEmitter2.FilterMode.ALPHAKEY.getMdlText(); + + public static final String TOKEN_HEIGHT_ABOVE = "HeightAbove"; + public static final String TOKEN_STATIC_HEIGHT_ABOVE = TOKEN_STATIC + " " + TOKEN_HEIGHT_ABOVE; + public static final String TOKEN_HEIGHT_BELOW = "HeightBelow"; + public static final String TOKEN_STATIC_HEIGHT_BELOW = TOKEN_STATIC + " " + TOKEN_HEIGHT_BELOW; + public static final String TOKEN_TEXTURE_SLOT = "TextureSlot"; + public static final String TOKEN_STATIC_TEXTURE_SLOT = TOKEN_STATIC + " " + TOKEN_TEXTURE_SLOT; + + public static final String TOKEN_TEXTURES = "Textures"; + public static final String TOKEN_MATERIALS = "Materials"; + public static final String TOKEN_TEXTURE_ANIMS = "TextureAnims"; + public static final String TOKEN_TEXTURE_ANIM = "TextureAnim"; + public static final String TOKEN_PIVOT_POINTS = "PivotPoints"; + + public static final String TOKEN_ATTACHMENT = "Attachment"; + public static final String TOKEN_BONE = "Bone"; + public static final String TOKEN_CAMERA = "Camera"; + public static final String TOKEN_COLLISION_SHAPE = "CollisionShape"; + public static final String TOKEN_EVENT_OBJECT = "EventObject"; + public static final String TOKEN_GEOSET = "Geoset"; + public static final String TOKEN_GEOSETANIM = "GeosetAnim"; + public static final String TOKEN_HELPER = "Helper"; + public static final String TOKEN_LAYER = "Layer"; + public static final String TOKEN_LIGHT = "Light"; + public static final String TOKEN_MATERIAL = "Material"; + public static final String TOKEN_PARTICLE_EMITTER = "ParticleEmitter"; + public static final String TOKEN_PARTICLE_EMITTER2 = "ParticleEmitter2"; + public static final String TOKEN_RIBBON_EMITTER = "RibbonEmitter"; + + // > 800 + + public static final String TOKEN_EMISSIVE_GAIN = "EmissiveGain"; + public static final String TOKEN_FRESNEL_COLOR = "FresnelColor"; + public static final String TOKEN_FRESNEL_OPACITY = "FresnelOpacity"; + public static final String TOKEN_FRESNEL_TEAM_COLOR = "FresnelTeamColor"; +} diff --git a/core/src/com/hiveworkshop/rms/parsers/mdlx/timeline/MdlxFloatArrayTimeline.java b/core/src/com/hiveworkshop/rms/parsers/mdlx/timeline/MdlxFloatArrayTimeline.java new file mode 100644 index 0000000..d877dd4 --- /dev/null +++ b/core/src/com/hiveworkshop/rms/parsers/mdlx/timeline/MdlxFloatArrayTimeline.java @@ -0,0 +1,45 @@ +package com.hiveworkshop.rms.parsers.mdlx.timeline; + +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlTokenInputStream; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlTokenOutputStream; +import com.hiveworkshop.rms.util.BinaryReader; +import com.hiveworkshop.rms.util.BinaryWriter; + +public final class MdlxFloatArrayTimeline extends MdlxTimeline { + private final int arraySize; + + public MdlxFloatArrayTimeline(final int arraySize) { + this.arraySize = arraySize; + } + + @Override + protected int size() { + return arraySize; + } + + @Override + protected float[] readMdxValue(final BinaryReader reader) { + return reader.readFloat32Array(arraySize); + } + + @Override + protected float[] readMdlValue(final MdlTokenInputStream stream) { + final float[] output = new float[arraySize]; + stream.readKeyframe(output); + return output; + } + + @Override + protected void writeMdxValue(final BinaryWriter writer, final float[] value) { + writer.writeFloat32Array(value); + } + + @Override + protected void writeMdlValue(final MdlTokenOutputStream stream, final String prefix, final float[] value) { + stream.writeKeyframe(prefix, value); + } + + public int getArraySize() { + return arraySize; + } +} diff --git a/core/src/com/hiveworkshop/rms/parsers/mdlx/timeline/MdlxFloatTimeline.java b/core/src/com/hiveworkshop/rms/parsers/mdlx/timeline/MdlxFloatTimeline.java new file mode 100644 index 0000000..ad5ac52 --- /dev/null +++ b/core/src/com/hiveworkshop/rms/parsers/mdlx/timeline/MdlxFloatTimeline.java @@ -0,0 +1,33 @@ +package com.hiveworkshop.rms.parsers.mdlx.timeline; + +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlTokenInputStream; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlTokenOutputStream; +import com.hiveworkshop.rms.util.BinaryReader; +import com.hiveworkshop.rms.util.BinaryWriter; + +public final class MdlxFloatTimeline extends MdlxTimeline { + @Override + protected int size() { + return 1; + } + + @Override + protected float[] readMdxValue(final BinaryReader reader) { + return new float[] { reader.readFloat32() }; + } + + @Override + protected float[] readMdlValue(final MdlTokenInputStream stream) { + return new float[] { stream.readFloat() }; + } + + @Override + protected void writeMdxValue(final BinaryWriter writer, final float[] value) { + writer.writeFloat32(value[0]); + } + + @Override + protected void writeMdlValue(final MdlTokenOutputStream stream, final String prefix, final float[] value) { + stream.writeKeyframe(prefix, value[0]); + } +} diff --git a/core/src/com/etheller/warsmash/parsers/mdlx/timeline/Timeline.java b/core/src/com/hiveworkshop/rms/parsers/mdlx/timeline/MdlxTimeline.java similarity index 55% rename from core/src/com/etheller/warsmash/parsers/mdlx/timeline/Timeline.java rename to core/src/com/hiveworkshop/rms/parsers/mdlx/timeline/MdlxTimeline.java index 0c06b87..a677791 100644 --- a/core/src/com/etheller/warsmash/parsers/mdlx/timeline/Timeline.java +++ b/core/src/com/hiveworkshop/rms/parsers/mdlx/timeline/MdlxTimeline.java @@ -1,48 +1,41 @@ -package com.etheller.warsmash.parsers.mdlx.timeline; +package com.hiveworkshop.rms.parsers.mdlx.timeline; -import java.io.IOException; - -import com.etheller.warsmash.parsers.mdlx.AnimationMap; -import com.etheller.warsmash.parsers.mdlx.Chunk; -import com.etheller.warsmash.parsers.mdlx.InterpolationType; -import com.etheller.warsmash.parsers.mdlx.MdlTokenInputStream; -import com.etheller.warsmash.parsers.mdlx.MdlTokenOutputStream; -import com.etheller.warsmash.util.MdlUtils; -import com.etheller.warsmash.util.ParseUtils; import com.etheller.warsmash.util.War3ID; -import com.google.common.io.LittleEndianDataInputStream; -import com.google.common.io.LittleEndianDataOutputStream; +import com.hiveworkshop.rms.parsers.mdlx.AnimationMap; +import com.hiveworkshop.rms.parsers.mdlx.InterpolationType; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlTokenInputStream; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlTokenOutputStream; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlUtils; +import com.hiveworkshop.rms.util.BinaryReader; +import com.hiveworkshop.rms.util.BinaryWriter; -public abstract class Timeline implements Chunk { - private War3ID name; - private InterpolationType interpolationType; - private int globalSequenceId = -1; +public abstract class MdlxTimeline { + public War3ID name; + public InterpolationType interpolationType; + public int globalSequenceId = -1; - private long[] frames; - private TYPE[] values; - private TYPE[] inTans; - private TYPE[] outTans; + public long[] frames; + public TYPE[] values; + public TYPE[] inTans; + public TYPE[] outTans; /** * Restricts us to only be able to parse models on one thread at a time, in * return for high performance. */ - private static StringBuffer STRING_BUFFER_HEAP = new StringBuffer(); + private static final StringBuffer STRING_BUFFER_HEAP = new StringBuffer(); + + public MdlxTimeline() { - public War3ID getName() { - return this.name; } - public Timeline() { - } - - public void readMdx(final LittleEndianDataInputStream stream, final War3ID name) throws IOException { + public void readMdx(final BinaryReader reader, final War3ID name) { this.name = name; - final long keyFrameCount = ParseUtils.readUInt32(stream); + final long keyFrameCount = reader.readUInt32(); - this.interpolationType = InterpolationType.VALUES[stream.readInt()]; - this.globalSequenceId = stream.readInt(); + this.interpolationType = InterpolationType.getType(reader.readInt32()); + this.globalSequenceId = reader.readInt32(); this.frames = new long[(int) keyFrameCount]; this.values = (TYPE[]) new Object[(int) keyFrameCount]; @@ -52,34 +45,37 @@ public abstract class Timeline implements Chunk { } for (int i = 0; i < keyFrameCount; i++) { - this.frames[i] = (stream.readInt()); // TODO autoboxing is slow - this.values[i] = (this.readMdxValue(stream)); + this.frames[i] = reader.readInt32(); + this.values[i] = (readMdxValue(reader)); if (this.interpolationType.tangential()) { - this.inTans[i] = (this.readMdxValue(stream)); - this.outTans[i] = (this.readMdxValue(stream)); + this.inTans[i] = (readMdxValue(reader)); + this.outTans[i] = (readMdxValue(reader)); } } } - public void writeMdx(final LittleEndianDataOutputStream stream) throws IOException { - stream.writeInt(Integer.reverseBytes(this.name.getValue())); + public void writeMdx(final BinaryWriter writer) { + writer.writeTag(this.name.getValue()); + final int keyframeCount = this.frames.length; - stream.writeInt(keyframeCount); - stream.writeInt(this.interpolationType.ordinal()); - stream.writeInt(this.globalSequenceId); + + writer.writeInt32(keyframeCount); + writer.writeInt32(this.interpolationType.ordinal()); + writer.writeInt32(this.globalSequenceId); for (int i = 0; i < keyframeCount; i++) { - stream.writeInt((int) this.frames[i]); - writeMdxValue(stream, this.values[i]); + writer.writeInt32((int) this.frames[i]); + writeMdxValue(writer, this.values[i]); + if (this.interpolationType.tangential()) { - writeMdxValue(stream, this.inTans[i]); - writeMdxValue(stream, this.outTans[i]); + writeMdxValue(writer, this.inTans[i]); + writeMdxValue(writer, this.outTans[i]); } } } - public void readMdl(final MdlTokenInputStream stream, final War3ID name) throws IOException { + public void readMdl(final MdlTokenInputStream stream, final War3ID name) { this.name = name; final int keyFrameCount = stream.readInt(); @@ -105,6 +101,7 @@ public abstract class Timeline implements Chunk { interpolationType = InterpolationType.DONT_INTERP; break; } + ; this.interpolationType = interpolationType; @@ -124,42 +121,23 @@ public abstract class Timeline implements Chunk { } for (int i = 0; i < keyFrameCount; i++) { this.frames[i] = (stream.readInt()); - this.values[i] = (this.readMdlValue(stream)); + this.values[i] = (readMdlValue(stream)); if (interpolationType.tangential()) { stream.read(); // InTan - this.inTans[i] = (this.readMdlValue(stream)); + this.inTans[i] = (readMdlValue(stream)); stream.read(); // OutTan - this.outTans[i] = (this.readMdlValue(stream)); + this.outTans[i] = (readMdlValue(stream)); } } stream.read(); // } } - public void writeMdl(final MdlTokenOutputStream stream) throws IOException { + public void writeMdl(final MdlTokenOutputStream stream) { final int tracksCount = this.frames.length; stream.startBlock(AnimationMap.ID_TO_TAG.get(this.name).getMdlToken(), tracksCount); - String token; - switch (this.interpolationType) { - case DONT_INTERP: - token = MdlUtils.TOKEN_DONT_INTERP; - break; - case LINEAR: - token = MdlUtils.TOKEN_LINEAR; - break; - case HERMITE: - token = MdlUtils.TOKEN_HERMITE; - break; - case BEZIER: - token = MdlUtils.TOKEN_BEZIER; - break; - default: - token = MdlUtils.TOKEN_DONT_INTERP; - break; - } - - stream.writeFlag(token); + stream.writeFlag(this.interpolationType.toString()); if (this.globalSequenceId != -1) { stream.writeAttrib(MdlUtils.TOKEN_GLOBAL_SEQ_ID, this.globalSequenceId); @@ -169,11 +147,11 @@ public abstract class Timeline implements Chunk { STRING_BUFFER_HEAP.setLength(0); STRING_BUFFER_HEAP.append(this.frames[i]); STRING_BUFFER_HEAP.append(':'); - this.writeMdlValue(stream, STRING_BUFFER_HEAP.toString(), this.values[i]); + writeMdlValue(stream, STRING_BUFFER_HEAP.toString(), this.values[i]); if (this.interpolationType.tangential()) { stream.indent(); - this.writeMdlValue(stream, "InTan", this.inTans[i]); - this.writeMdlValue(stream, "OutTan", this.outTans[i]); + writeMdlValue(stream, "InTan", this.inTans[i]); + writeMdlValue(stream, "OutTan", this.outTans[i]); stream.unindent(); } } @@ -181,7 +159,6 @@ public abstract class Timeline implements Chunk { stream.endBlock(); } - @Override public long getByteLength() { final int tracksCount = this.frames.length; int size = 16; @@ -200,22 +177,26 @@ public abstract class Timeline implements Chunk { protected abstract int size(); - protected abstract TYPE readMdxValue(LittleEndianDataInputStream stream) throws IOException; + protected abstract TYPE readMdxValue(BinaryReader reader); protected abstract TYPE readMdlValue(MdlTokenInputStream stream); - protected abstract void writeMdxValue(LittleEndianDataOutputStream stream, TYPE value) throws IOException; + protected abstract void writeMdxValue(BinaryWriter writer, TYPE value); protected abstract void writeMdlValue(MdlTokenOutputStream stream, String prefix, TYPE value); - public int getGlobalSequenceId() { - return this.globalSequenceId; + public War3ID getName() { + return this.name; } public InterpolationType getInterpolationType() { return this.interpolationType; } + public int getGlobalSequenceId() { + return this.globalSequenceId; + } + public long[] getFrames() { return this.frames; } @@ -231,4 +212,5 @@ public abstract class Timeline implements Chunk { public TYPE[] getOutTans() { return this.outTans; } + } diff --git a/core/src/com/hiveworkshop/rms/parsers/mdlx/timeline/MdlxUInt32Timeline.java b/core/src/com/hiveworkshop/rms/parsers/mdlx/timeline/MdlxUInt32Timeline.java new file mode 100644 index 0000000..a80c197 --- /dev/null +++ b/core/src/com/hiveworkshop/rms/parsers/mdlx/timeline/MdlxUInt32Timeline.java @@ -0,0 +1,34 @@ +package com.hiveworkshop.rms.parsers.mdlx.timeline; + +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlTokenInputStream; +import com.hiveworkshop.rms.parsers.mdlx.mdl.MdlTokenOutputStream; +import com.hiveworkshop.rms.util.BinaryReader; +import com.hiveworkshop.rms.util.BinaryWriter; + +public final class MdlxUInt32Timeline extends MdlxTimeline { + @Override + protected int size() { + return 1; + } + + @Override + protected long[] readMdxValue(final BinaryReader reader) { + return new long[]{reader.readUInt32()}; + } + + @Override + protected long[] readMdlValue(final MdlTokenInputStream stream) { + return new long[]{stream.readUInt32()}; + } + + @Override + protected void writeMdxValue(final BinaryWriter writer, final long[] uint32) { + writer.writeUInt32(uint32[0]); + } + + @Override + protected void writeMdlValue(final MdlTokenOutputStream stream, final String prefix, final long[] uint32) { + stream.writeKeyframe(prefix, uint32[0]); + } + +} diff --git a/core/src/com/hiveworkshop/rms/parsers/mdlx/util/MdxUtils.java b/core/src/com/hiveworkshop/rms/parsers/mdlx/util/MdxUtils.java new file mode 100644 index 0000000..514d43e --- /dev/null +++ b/core/src/com/hiveworkshop/rms/parsers/mdlx/util/MdxUtils.java @@ -0,0 +1,32 @@ +package com.hiveworkshop.rms.parsers.mdlx.util; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; + +import org.apache.commons.compress.utils.IOUtils; + +import com.hiveworkshop.rms.parsers.mdlx.MdlxModel; + +public class MdxUtils { + public static MdlxModel loadMdlx(final InputStream inputStream) throws IOException { + + return new MdlxModel(ByteBuffer.wrap(IOUtils.toByteArray(inputStream))); + } + + public static void saveMdx(final MdlxModel model, final OutputStream outputStream) throws IOException { + outputStream.write(model.saveMdx().array()); + } + + public static void saveMdl(final MdlxModel model, final OutputStream outputStream) throws IOException { + outputStream.write(model.saveMdl().array()); + } + + public static void saveMdl(final MdlxModel model, final File file) throws IOException { + saveMdl(model, new FileOutputStream(file)); + } + +} diff --git a/core/src/com/hiveworkshop/rms/util/BinaryReader.java b/core/src/com/hiveworkshop/rms/util/BinaryReader.java new file mode 100644 index 0000000..eb6affd --- /dev/null +++ b/core/src/com/hiveworkshop/rms/util/BinaryReader.java @@ -0,0 +1,216 @@ +package com.hiveworkshop.rms.util; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +public class BinaryReader { + ByteBuffer buffer; + + public BinaryReader(final ByteBuffer buffer) { + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.position(0); + + this.buffer = buffer; + } + + public int remaining() { + return this.buffer.remaining(); + } + + public int position() { + return this.buffer.position(); + } + + public void position(final int newPosition) { + this.buffer.position(newPosition); + } + + public void move(final int offset) { + this.buffer.position(this.buffer.position() + offset); + } + + public String read(final int count) { + final StringBuilder value = new StringBuilder(); + + for (int i = 0; i < count; i++) { + final byte b = this.buffer.get(); + + if (b != 0) { + value.append((char) (b & 0xFF)); + } + } + + return value.toString(); + } + + public String readBytes(final int count) { + final StringBuilder value = new StringBuilder(); + + for (int i = 0; i < count; i++) { + value.append((char) (this.buffer.get() & 0xFF)); + } + + return value.toString(); + } + + public String readUntilNull() { + final StringBuilder value = new StringBuilder(); + byte b = this.buffer.get(); + + while (b != 0) { + value.append((char) (b & 0xFF)); + + b = this.buffer.get(); + } + + return value.toString(); + } + + public byte readInt8() { + return this.buffer.get(); + } + + public short readInt16() { + return this.buffer.getShort(); + } + + public int readInt32() { + return this.buffer.getInt(); + } + + public long readInt64() { + return this.buffer.getLong(); + } + + public short readUInt8() { + return (short) Byte.toUnsignedInt(this.buffer.get()); + } + + public int readUInt16() { + return Short.toUnsignedInt(this.buffer.getShort()); + } + + public long readUInt32() { + return Integer.toUnsignedLong(this.buffer.getInt()); + } + + public float readFloat32() { + return this.buffer.getFloat(); + } + + public double readFloat64() { + return this.buffer.getDouble(); + } + + public byte[] readInt8Array(final byte[] out) { + for (int i = 0, l = out.length; i < l; i++) { + out[i] = readInt8(); + } + + return out; + } + + public byte[] readInt8Array(final int count) { + return readInt8Array(new byte[count]); + } + + public short[] readInt16Array(final short[] out) { + for (int i = 0, l = out.length; i < l; i++) { + out[i] = readInt16(); + } + + return out; + } + + public short[] readInt16Array(final int count) { + return readInt16Array(new short[count]); + } + + public int[] readInt32Array(final int[] out) { + for (int i = 0, l = out.length; i < l; i++) { + out[i] = readInt32(); + } + + return out; + } + + public int[] readInt32Array(final int count) { + return readInt32Array(new int[count]); + } + + public long[] readInt64Array(final long[] out) { + for (int i = 0, l = out.length; i < l; i++) { + out[i] = readInt64(); + } + + return out; + } + + public long[] readInt64Array(final int count) { + return readInt64Array(new long[count]); + } + + public short[] readUInt8Array(final short[] out) { + for (int i = 0, l = out.length; i < l; i++) { + out[i] = readUInt8(); + } + + return out; + } + + public short[] readUInt8Array(final int count) { + return readUInt8Array(new short[count]); + } + + public int[] readUInt16Array(final int[] out) { + for (int i = 0, l = out.length; i < l; i++) { + out[i] = readUInt16(); + } + + return out; + } + + public int[] readUInt16Array(final int count) { + return readUInt16Array(new int[count]); + } + + public long[] readUInt32Array(final long[] out) { + for (int i = 0, l = out.length; i < l; i++) { + out[i] = readUInt32(); + } + + return out; + } + + public long[] readUInt32Array(final int count) { + return readUInt32Array(new long[count]); + } + + public float[] readFloat32Array(final float[] out) { + for (int i = 0, l = out.length; i < l; i++) { + out[i] = readFloat32(); + } + + return out; + } + + public float[] readFloat32Array(final int count) { + return readFloat32Array(new float[count]); + } + + public double[] readFloat64Array(final double[] out) { + for (int i = 0, l = out.length; i < l; i++) { + out[i] = readFloat64(); + } + + return out; + } + + public double[] readFloat64Array(final int count) { + return readFloat64Array(new double[count]); + } + + public int readTag() { + return Integer.reverseBytes(readInt32()); + } +} diff --git a/core/src/com/hiveworkshop/rms/util/BinaryWriter.java b/core/src/com/hiveworkshop/rms/util/BinaryWriter.java new file mode 100644 index 0000000..a56277c --- /dev/null +++ b/core/src/com/hiveworkshop/rms/util/BinaryWriter.java @@ -0,0 +1,140 @@ +package com.hiveworkshop.rms.util; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +public class BinaryWriter { + public ByteBuffer buffer; + + public BinaryWriter(final int capacity) { + this.buffer = ByteBuffer.allocate(capacity); + this.buffer.order(ByteOrder.LITTLE_ENDIAN); + } + + public int remaining() { + return this.buffer.remaining(); + } + + public int position() { + return this.buffer.position(); + } + + public void position(final int newPosition) { + this.buffer.position(newPosition); + } + + public void move(final int offset) { + this.buffer.position(this.buffer.position() + offset); + } + + public void write(final String value) { + writeInt8Array(value.getBytes()); + } + + public void writeWithNulls(final String value, final int length) { + final byte[] bytes = value.getBytes(); + final int nulls = length - bytes.length; + + writeInt8Array(bytes); + + if (nulls > 0) { + for (int i = 0; i < nulls; i++) { + writeInt8((byte) 0); + } + } + } + + public void writeInt8(final byte value) { + this.buffer.put(value); + } + + public void writeInt16(final short value) { + this.buffer.putShort(value); + } + + public void writeInt32(final int value) { + this.buffer.putInt(value); + } + + public void writeInt64(final long value) { + this.buffer.putLong(value); + } + + public void writeUInt8(final short value) { + this.buffer.put((byte) value); + } + + public void writeUInt16(final int value) { + this.buffer.putShort((short) value); + } + + public void writeUInt32(final long value) { + this.buffer.putInt((int) value); + } + + public void writeFloat32(final float value) { + this.buffer.putFloat(value); + } + + public void writeFloat64(final double value) { + this.buffer.putDouble(value); + } + + public void writeInt8Array(final byte[] values) { + for (final byte value : values) { + writeInt8(value); + } + } + + public void writeInt16Array(final short[] values) { + for (final short value : values) { + writeInt16(value); + } + } + + public void writeInt32Array(final int[] values) { + for (final int value : values) { + writeInt32(value); + } + } + + public void writeInt64Array(final long[] values) { + for (final long value : values) { + writeInt64(value); + } + } + + public void writeUInt8Array(final short[] values) { + for (final short value : values) { + writeUInt8(value); + } + } + + public void writeUInt16Array(final int[] values) { + for (final int value : values) { + writeUInt16(value); + } + } + + public void writeUInt32Array(final long[] values) { + for (final long value : values) { + writeUInt32(value); + } + } + + public void writeFloat32Array(final float[] values) { + for (final float value : values) { + writeFloat32(value); + } + } + + public void writeFloat64Array(final double[] values) { + for (final double value : values) { + writeFloat64(value); + } + } + + public void writeTag(final int tag) { + writeInt32(Integer.reverseBytes(tag)); + } +} diff --git a/core/src/com/hiveworkshop/rms/util/Descriptor.java b/core/src/com/hiveworkshop/rms/util/Descriptor.java new file mode 100644 index 0000000..654533c --- /dev/null +++ b/core/src/com/hiveworkshop/rms/util/Descriptor.java @@ -0,0 +1,6 @@ +package com.hiveworkshop.rms.util; + +public interface Descriptor { + E create(); + +} diff --git a/desktop/src/com/etheller/warsmash/audio/Flac.java b/desktop/src/com/etheller/warsmash/audio/Flac.java new file mode 100644 index 0000000..945bb06 --- /dev/null +++ b/desktop/src/com/etheller/warsmash/audio/Flac.java @@ -0,0 +1,186 @@ +/******************************************************************************* + * Copyright 2011 See AUTHORS file. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************/ + +package com.etheller.warsmash.audio; + +import java.io.BufferedOutputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.Arrays; + +import com.badlogic.gdx.files.FileHandle; +import com.badlogic.gdx.utils.GdxRuntimeException; +import com.badlogic.gdx.utils.StreamUtils; +import com.etheller.warsmash.audio.Wav.WavInputStream; + +import io.nayuki.flac.common.StreamInfo; +import io.nayuki.flac.decode.DataFormatException; +import io.nayuki.flac.decode.FlacDecoder; + +public class Flac { + static public class Music extends OpenALMusic { + private WavInputStream input; + + public Music(final OpenALAudio audio, final FileHandle file) { + super(audio, file); + try { + this.input = new WavInputStream(new ByteArrayInputStream(makeItWav(file)), file); + } + catch (final IOException ex) { + throw new GdxRuntimeException("Error reading FLAC file: " + this.file, ex); + } + if (audio.noDevice) { + return; + } + setup(this.input.channels, this.input.sampleRate); + } + + @Override + public int read(final byte[] buffer) { + if (this.input == null) { + try { + this.input = new WavInputStream(new ByteArrayInputStream(makeItWav(this.file)), this.file); + } + catch (final IOException ex) { + throw new GdxRuntimeException("Error reading FLAC file: " + this.file, ex); + } + setup(this.input.channels, this.input.sampleRate); + } + try { + return this.input.read(buffer); + } + catch (final IOException ex) { + throw new GdxRuntimeException("Error reading FLAC file: " + this.file, ex); + } + } + + @Override + public void reset() { + StreamUtils.closeQuietly(this.input); + this.input = null; + } + } + + static public class Sound extends OpenALSound { + public Sound(final OpenALAudio audio, final FileHandle file) { + super(audio); + if (audio.noDevice) { + return; + } + + WavInputStream input = null; + try { + input = new WavInputStream(new ByteArrayInputStream(makeItWav(file)), file); + setup(StreamUtils.copyStreamToByteArray(input, input.dataRemaining), input.channels, input.sampleRate); + } + catch (final IOException ex) { + throw new GdxRuntimeException("Error reading FLAC file: " + file, ex); + } + finally { + StreamUtils.closeQuietly(input); + } + } + } + + private static byte[] makeItWav(final FileHandle file) throws IOException { + // Decode input FLAC file + StreamInfo streamInfo; + int[][] samples; + try (FlacDecoder dec = new FlacDecoder(file.readBytes())) { + + // Handle metadata header blocks + while (dec.readAndHandleMetadataBlock() != null) { + ; + } + streamInfo = dec.streamInfo; + if ((streamInfo.sampleDepth % 8) != 0) { + throw new UnsupportedOperationException("Only whole-byte sample depth supported"); + } + + // Decode every block + samples = new int[streamInfo.numChannels][(int) streamInfo.numSamples]; + for (int off = 0;;) { + final int len = dec.readAudioBlock(samples, off); + if (len == 0) { + break; + } + off += len; + } + } + + // Check audio MD5 hash + final byte[] expectHash = streamInfo.md5Hash; + if (Arrays.equals(expectHash, new byte[16])) { + System.err.println("Warning: MD5 hash field was blank"); + } + else if (!Arrays.equals(StreamInfo.getMd5Hash(samples, streamInfo.sampleDepth), expectHash)) { + throw new DataFormatException("MD5 hash check failed"); + // Else the hash check passed + } + + // Start writing WAV output file + final int bytesPerSample = streamInfo.sampleDepth / 8; + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (DataOutputStream out = new DataOutputStream(new BufferedOutputStream(baos))) { + // Header chunk + final int sampleDataLen = samples[0].length * streamInfo.numChannels * bytesPerSample; + out.writeInt(0x52494646); // "RIFF" + writeLittleInt32(out, sampleDataLen + 36); + out.writeInt(0x57415645); // "WAVE" + + // Metadata chunk + out.writeInt(0x666D7420); // "fmt " + writeLittleInt32(out, 16); + writeLittleInt16(out, 0x0001); + writeLittleInt16(out, streamInfo.numChannels); + writeLittleInt32(out, streamInfo.sampleRate); + writeLittleInt32(out, streamInfo.sampleRate * streamInfo.numChannels * bytesPerSample); + writeLittleInt16(out, streamInfo.numChannels * bytesPerSample); + writeLittleInt16(out, streamInfo.sampleDepth); + + // Audio data chunk ("data") + out.writeInt(0x64617461); // "data" + writeLittleInt32(out, sampleDataLen); + for (int i = 0; i < samples[0].length; i++) { + for (int j = 0; j < samples.length; j++) { + final int val = samples[j][i]; + if (bytesPerSample == 1) { + out.write(val + 128); // Convert to unsigned, as per WAV PCM conventions + } + else { // 2 <= bytesPerSample <= 4 + for (int k = 0; k < bytesPerSample; k++) { + out.write(val >>> (k * 8)); // Little endian + } + } + } + } + return baos.toByteArray(); + } + } + + // Helper members for writing WAV files + + private static void writeLittleInt16(final DataOutputStream out, final int x) throws IOException { + out.writeShort(Integer.reverseBytes(x) >>> 16); + } + + private static void writeLittleInt32(final DataOutputStream out, final int x) throws IOException { + out.writeInt(Integer.reverseBytes(x)); + } + +} diff --git a/desktop/src/com/etheller/warsmash/audio/OpenALAudio.java b/desktop/src/com/etheller/warsmash/audio/OpenALAudio.java index ad3077a..ac16260 100644 --- a/desktop/src/com/etheller/warsmash/audio/OpenALAudio.java +++ b/desktop/src/com/etheller/warsmash/audio/OpenALAudio.java @@ -84,6 +84,8 @@ public class OpenALAudio implements Audio { registerMusic("wav", Wav.Music.class); registerSound("mp3", Mp3.Sound.class); registerMusic("mp3", Mp3.Music.class); + registerSound("flac", Flac.Sound.class); + registerMusic("flac", Flac.Music.class); try { AL.create(); diff --git a/desktop/src/com/etheller/warsmash/audio/Wav.java b/desktop/src/com/etheller/warsmash/audio/Wav.java index eaa493e..7c8b6e3 100644 --- a/desktop/src/com/etheller/warsmash/audio/Wav.java +++ b/desktop/src/com/etheller/warsmash/audio/Wav.java @@ -19,6 +19,7 @@ package com.etheller.warsmash.audio; import java.io.EOFException; import java.io.FilterInputStream; import java.io.IOException; +import java.io.InputStream; import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.utils.GdxRuntimeException; @@ -85,16 +86,20 @@ public class Wav { public int channels, sampleRate, dataRemaining; public WavInputStream(final FileHandle file) { - super(file.read()); + this(file.read(), file); + } + + public WavInputStream(final InputStream stream, final Object loggableFile) { + super(stream); try { if ((read() != 'R') || (read() != 'I') || (read() != 'F') || (read() != 'F')) { - throw new GdxRuntimeException("RIFF header not found: " + file); + throw new GdxRuntimeException("RIFF header not found: " + loggableFile); } skipFully(4); if ((read() != 'W') || (read() != 'A') || (read() != 'V') || (read() != 'E')) { - throw new GdxRuntimeException("Invalid wave file header: " + file); + throw new GdxRuntimeException("Invalid wave file header: " + loggableFile); } final int fmtChunkLength = seekToChunk('f', 'm', 't', ' '); @@ -148,7 +153,7 @@ public class Wav { } catch (final Throwable ex) { StreamUtils.closeQuietly(this); - throw new GdxRuntimeException("Error reading WAV file: " + file, ex); + throw new GdxRuntimeException("Error reading WAV file: " + loggableFile, ex); } } diff --git a/desktop/src/com/etheller/warsmash/desktop/editor/mdx/listeners/YseraGUIListener.java b/desktop/src/com/etheller/warsmash/desktop/editor/mdx/listeners/YseraGUIListener.java index df9f784..7255d28 100644 --- a/desktop/src/com/etheller/warsmash/desktop/editor/mdx/listeners/YseraGUIListener.java +++ b/desktop/src/com/etheller/warsmash/desktop/editor/mdx/listeners/YseraGUIListener.java @@ -1,7 +1,7 @@ package com.etheller.warsmash.desktop.editor.mdx.listeners; -import com.etheller.warsmash.parsers.mdlx.MdlxModel; import com.etheller.warsmash.util.SubscriberSetNotifier; +import com.hiveworkshop.rms.parsers.mdlx.MdlxModel; public interface YseraGUIListener { void openModel(MdlxModel model); diff --git a/desktop/src/com/etheller/warsmash/desktop/editor/mdx/ui/AnimationControllerFrame.java b/desktop/src/com/etheller/warsmash/desktop/editor/mdx/ui/AnimationControllerFrame.java index 1e84df4..87dba1f 100644 --- a/desktop/src/com/etheller/warsmash/desktop/editor/mdx/ui/AnimationControllerFrame.java +++ b/desktop/src/com/etheller/warsmash/desktop/editor/mdx/ui/AnimationControllerFrame.java @@ -5,7 +5,7 @@ import javax.swing.WindowConstants; import com.etheller.warsmash.WarsmashPreviewApplication; import com.etheller.warsmash.desktop.editor.mdx.listeners.YseraGUIListener; -import com.etheller.warsmash.parsers.mdlx.MdlxModel; +import com.hiveworkshop.rms.parsers.mdlx.MdlxModel; public class AnimationControllerFrame extends JFrame implements YseraGUIListener { private final AnimationControllerPanel animationControllerPanel; diff --git a/desktop/src/com/etheller/warsmash/desktop/editor/mdx/ui/AnimationControllerPanel.java b/desktop/src/com/etheller/warsmash/desktop/editor/mdx/ui/AnimationControllerPanel.java index b865e9e..abdd053 100644 --- a/desktop/src/com/etheller/warsmash/desktop/editor/mdx/ui/AnimationControllerPanel.java +++ b/desktop/src/com/etheller/warsmash/desktop/editor/mdx/ui/AnimationControllerPanel.java @@ -25,15 +25,15 @@ import javax.swing.plaf.basic.BasicComboBoxRenderer; import com.etheller.warsmash.WarsmashPreviewApplication; import com.etheller.warsmash.desktop.editor.mdx.listeners.YseraGUIListener; -import com.etheller.warsmash.parsers.mdlx.MdlxModel; -import com.etheller.warsmash.parsers.mdlx.Sequence; import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; import com.etheller.warsmash.viewer5.handlers.mdx.SequenceLoopMode; +import com.hiveworkshop.rms.parsers.mdlx.MdlxModel; +import com.hiveworkshop.rms.parsers.mdlx.MdlxSequence; public class AnimationControllerPanel extends JPanel implements YseraGUIListener { private final WarsmashPreviewApplication previewApplication; - private final DefaultComboBoxModel animations; - private final JComboBox animationBox; + private final DefaultComboBoxModel animations; + private final JComboBox animationBox; private MdlxModel model; private JRadioButton defaultLoopButton; private JRadioButton alwaysLoopButton; @@ -51,7 +51,7 @@ public class AnimationControllerPanel extends JPanel implements YseraGUIListener @Override public Component getListCellRendererComponent(final JList list, final Object value, final int index, final boolean isSelected, final boolean cellHasFocus) { - Object display = value == null ? "(Unanimated)" : ((Sequence) value).getName(); + Object display = value == null ? "(Unanimated)" : ((MdlxSequence) value).getName(); if ((value != null) && (AnimationControllerPanel.this.model != null)) { display = "(" + AnimationControllerPanel.this.model.getSequences().indexOf(value) + ") " + display; } @@ -197,7 +197,7 @@ public class AnimationControllerPanel extends JPanel implements YseraGUIListener this.animations.removeAllElements(); this.animations.addElement(null); if (this.model != null) { - for (final Sequence animation : this.model.getSequences()) { + for (final MdlxSequence animation : this.model.getSequences()) { this.animations.addElement(animation); } } diff --git a/desktop/src/com/etheller/warsmash/desktop/editor/mdx/ui/YseraPanel.java b/desktop/src/com/etheller/warsmash/desktop/editor/mdx/ui/YseraPanel.java index 51a40bf..340bf02 100644 --- a/desktop/src/com/etheller/warsmash/desktop/editor/mdx/ui/YseraPanel.java +++ b/desktop/src/com/etheller/warsmash/desktop/editor/mdx/ui/YseraPanel.java @@ -25,8 +25,8 @@ import com.badlogic.gdx.math.Vector3; import com.etheller.warsmash.WarsmashPreviewApplication; import com.etheller.warsmash.desktop.editor.mdx.listeners.YseraGUIListener; import com.etheller.warsmash.desktop.editor.util.ExceptionPopup; -import com.etheller.warsmash.parsers.mdlx.MdlxModel; import com.etheller.warsmash.viewer5.handlers.w3x.camera.PortraitCameraManager; +import com.hiveworkshop.rms.parsers.mdlx.MdlxModel; public class YseraPanel extends JPanel { private static final Quaternion IDENTITY = new Quaternion(); diff --git a/desktop/src/io/nayuki/flac/app/DecodeFlacToWav.java b/desktop/src/io/nayuki/flac/app/DecodeFlacToWav.java new file mode 100644 index 0000000..bf47e3f --- /dev/null +++ b/desktop/src/io/nayuki/flac/app/DecodeFlacToWav.java @@ -0,0 +1,141 @@ +/* + * FLAC library (Java) + * + * Copyright (c) Project Nayuki + * https://www.nayuki.io/page/flac-library-java + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program (see COPYING.txt and COPYING.LESSER.txt). + * If not, see . + */ + +package io.nayuki.flac.app; + +import java.io.BufferedOutputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Arrays; +import io.nayuki.flac.common.StreamInfo; +import io.nayuki.flac.decode.DataFormatException; +import io.nayuki.flac.decode.FlacDecoder; + + +/** + * Decodes a FLAC file to an uncompressed PCM WAV file. Overwrites output file if already exists. + * Runs silently if successful, otherwise prints error messages to standard error. + *

Usage: java DecodeFlacToWav InFile.flac OutFile.wav

+ *

Requirements on the FLAC file:

+ *
    + *
  • Sample depth is 8, 16, 24, or 32 bits (not 4, 17, 23, etc.)
  • + *
  • Contains no ID3v1 or ID3v2 tags, or other data unrecognized by the FLAC format
  • + *
  • Correct total number of samples (not zero) is stored in stream info block
  • + *
  • Every frame has a correct header, subframes do not overflow the sample depth, + * and other strict checks enforced by this decoder library
  • + *
+ */ +public final class DecodeFlacToWav { + + public static void main(String[] args) throws IOException { + // Handle command line arguments + if (args.length != 2) { + System.err.println("Usage: java DecodeFlacToWav InFile.flac OutFile.wav"); + System.exit(1); + return; + } + File inFile = new File(args[0]); + File outFile = new File(args[1]); + + // Decode input FLAC file + StreamInfo streamInfo; + int[][] samples; + try (FlacDecoder dec = new FlacDecoder(inFile)) { + + // Handle metadata header blocks + while (dec.readAndHandleMetadataBlock() != null); + streamInfo = dec.streamInfo; + if (streamInfo.sampleDepth % 8 != 0) + throw new UnsupportedOperationException("Only whole-byte sample depth supported"); + + // Decode every block + samples = new int[streamInfo.numChannels][(int)streamInfo.numSamples]; + for (int off = 0; ;) { + int len = dec.readAudioBlock(samples, off); + if (len == 0) + break; + off += len; + } + } + + // Check audio MD5 hash + byte[] expectHash = streamInfo.md5Hash; + if (Arrays.equals(expectHash, new byte[16])) + System.err.println("Warning: MD5 hash field was blank"); + else if (!Arrays.equals(StreamInfo.getMd5Hash(samples, streamInfo.sampleDepth), expectHash)) + throw new DataFormatException("MD5 hash check failed"); + // Else the hash check passed + + // Start writing WAV output file + int bytesPerSample = streamInfo.sampleDepth / 8; + try (DataOutputStream out = new DataOutputStream( + new BufferedOutputStream(new FileOutputStream(outFile)))) { + DecodeFlacToWav.out = out; + + // Header chunk + int sampleDataLen = samples[0].length * streamInfo.numChannels * bytesPerSample; + out.writeInt(0x52494646); // "RIFF" + writeLittleInt32(sampleDataLen + 36); + out.writeInt(0x57415645); // "WAVE" + + // Metadata chunk + out.writeInt(0x666D7420); // "fmt " + writeLittleInt32(16); + writeLittleInt16(0x0001); + writeLittleInt16(streamInfo.numChannels); + writeLittleInt32(streamInfo.sampleRate); + writeLittleInt32(streamInfo.sampleRate * streamInfo.numChannels * bytesPerSample); + writeLittleInt16(streamInfo.numChannels * bytesPerSample); + writeLittleInt16(streamInfo.sampleDepth); + + // Audio data chunk ("data") + out.writeInt(0x64617461); // "data" + writeLittleInt32(sampleDataLen); + for (int i = 0; i < samples[0].length; i++) { + for (int j = 0; j < samples.length; j++) { + int val = samples[j][i]; + if (bytesPerSample == 1) + out.write(val + 128); // Convert to unsigned, as per WAV PCM conventions + else { // 2 <= bytesPerSample <= 4 + for (int k = 0; k < bytesPerSample; k++) + out.write(val >>> (k * 8)); // Little endian + } + } + } + } + } + + + // Helper members for writing WAV files + + private static DataOutputStream out; + + private static void writeLittleInt16(int x) throws IOException { + out.writeShort(Integer.reverseBytes(x) >>> 16); + } + + private static void writeLittleInt32(int x) throws IOException { + out.writeInt(Integer.reverseBytes(x)); + } + +} diff --git a/desktop/src/io/nayuki/flac/app/EncodeWavToFlac.java b/desktop/src/io/nayuki/flac/app/EncodeWavToFlac.java new file mode 100644 index 0000000..64fbd95 --- /dev/null +++ b/desktop/src/io/nayuki/flac/app/EncodeWavToFlac.java @@ -0,0 +1,176 @@ +/* + * FLAC library (Java) + * + * Copyright (c) Project Nayuki + * https://www.nayuki.io/page/flac-library-java + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program (see COPYING.txt and COPYING.LESSER.txt). + * If not, see . + */ + +package io.nayuki.flac.app; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.EOFException; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.RandomAccessFile; +import java.nio.charset.StandardCharsets; +import io.nayuki.flac.common.StreamInfo; +import io.nayuki.flac.decode.DataFormatException; +import io.nayuki.flac.encode.BitOutputStream; +import io.nayuki.flac.encode.FlacEncoder; +import io.nayuki.flac.encode.RandomAccessFileOutputStream; +import io.nayuki.flac.encode.SubframeEncoder; + + +/** + * Encodes an uncompressed PCM WAV file to a FLAC file. + * Overwrites the output file if it already exists. + *

Usage: java EncodeWavToFlac InFile.wav OutFile.flac

+ *

Requirements on the WAV file:

+ *
    + *
  • Sample depth is 8, 16, 24, or 32 bits (not 4, 17, 23, etc.)
  • + *
  • Number of channels is between 1 to 8 inclusive
  • + *
  • Sample rate is less than 220 hertz
  • + *
+ */ +public final class EncodeWavToFlac { + + public static void main(String[] args) throws IOException { + // Handle command line arguments + if (args.length != 2) { + System.err.println("Usage: java EncodeWavToFlac InFile.wav OutFile.flac"); + System.exit(1); + return; + } + File inFile = new File(args[0]); + File outFile = new File(args[1]); + + // Read WAV file headers and audio sample data + int[][] samples; + int sampleRate; + int sampleDepth; + try (InputStream in = new BufferedInputStream(new FileInputStream(inFile))) { + // Parse and check WAV header + if (!readString(in, 4).equals("RIFF")) + throw new DataFormatException("Invalid RIFF file header"); + readLittleUint(in, 4); // Remaining data length + if (!readString(in, 4).equals("WAVE")) + throw new DataFormatException("Invalid WAV file header"); + + // Handle the format chunk + if (!readString(in, 4).equals("fmt ")) + throw new DataFormatException("Unrecognized WAV file chunk"); + if (readLittleUint(in, 4) != 16) + throw new DataFormatException("Unsupported WAV file type"); + if (readLittleUint(in, 2) != 0x0001) + throw new DataFormatException("Unsupported WAV file codec"); + int numChannels = readLittleUint(in, 2); + if (numChannels < 0 || numChannels > 8) + throw new RuntimeException("Too many (or few) audio channels"); + sampleRate = readLittleUint(in, 4); + if (sampleRate <= 0 || sampleRate >= (1 << 20)) + throw new RuntimeException("Sample rate too large or invalid"); + int byteRate = readLittleUint(in, 4); + int blockAlign = readLittleUint(in, 2); + sampleDepth = readLittleUint(in, 2); + if (sampleDepth == 0 || sampleDepth > 32 || sampleDepth % 8 != 0) + throw new RuntimeException("Unsupported sample depth"); + int bytesPerSample = sampleDepth / 8; + if (bytesPerSample * numChannels != blockAlign) + throw new RuntimeException("Invalid block align value"); + if (bytesPerSample * numChannels * sampleRate != byteRate) + throw new RuntimeException("Invalid byte rate value"); + + // Handle the data chunk + if (!readString(in, 4).equals("data")) + throw new DataFormatException("Unrecognized WAV file chunk"); + int sampleDataLen = readLittleUint(in, 4); + if (sampleDataLen <= 0 || sampleDataLen % (numChannels * bytesPerSample) != 0) + throw new DataFormatException("Invalid length of audio sample data"); + int numSamples = sampleDataLen / (numChannels * bytesPerSample); + samples = new int[numChannels][numSamples]; + for (int i = 0; i < numSamples; i++) { + for (int ch = 0; ch < numChannels; ch++) { + int val = readLittleUint(in, bytesPerSample); + if (sampleDepth == 8) + val -= 128; + else + val = (val << (32 - sampleDepth)) >> (32 - sampleDepth); + samples[ch][i] = val; + } + } + // Note: There might be chunks after "data", but they can be ignored + } + + // Open output file and encode samples to FLAC + try (RandomAccessFile raf = new RandomAccessFile(outFile, "rw")) { + raf.setLength(0); // Truncate an existing file + BitOutputStream out = new BitOutputStream( + new BufferedOutputStream(new RandomAccessFileOutputStream(raf))); + out.writeInt(32, 0x664C6143); + + // Populate and write the stream info structure + StreamInfo info = new StreamInfo(); + info.sampleRate = sampleRate; + info.numChannels = samples.length; + info.sampleDepth = sampleDepth; + info.numSamples = samples[0].length; + info.md5Hash = StreamInfo.getMd5Hash(samples, sampleDepth); + info.write(true, out); + + // Encode all frames + new FlacEncoder(info, samples, 4096, SubframeEncoder.SearchOptions.SUBSET_BEST, out); + out.flush(); + + // Rewrite the stream info metadata block, which is + // located at a fixed offset in the file by definition + raf.seek(4); + info.write(true, out); + out.flush(); + } + } + + + // Reads len bytes from the given stream and interprets them as a UTF-8 string. + private static String readString(InputStream in, int len) throws IOException { + byte[] temp = new byte[len]; + for (int i = 0; i < temp.length; i++) { + int b = in.read(); + if (b == -1) + throw new EOFException(); + temp[i] = (byte)b; + } + return new String(temp, StandardCharsets.UTF_8); + } + + + // Reads n bytes (0 <= n <= 4) from the given stream, interpreting + // them as an unsigned integer encoded in little endian. + private static int readLittleUint(InputStream in, int n) throws IOException { + int result = 0; + for (int i = 0; i < n; i++) { + int b = in.read(); + if (b == -1) + throw new EOFException(); + result |= b << (i * 8); + } + return result; + } + +} diff --git a/desktop/src/io/nayuki/flac/app/SeekableFlacPlayerGui.java b/desktop/src/io/nayuki/flac/app/SeekableFlacPlayerGui.java new file mode 100644 index 0000000..418b1f4 --- /dev/null +++ b/desktop/src/io/nayuki/flac/app/SeekableFlacPlayerGui.java @@ -0,0 +1,236 @@ +/* + * FLAC library (Java) + * + * Copyright (c) Project Nayuki + * https://www.nayuki.io/page/flac-library-java + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program (see COPYING.txt and COPYING.LESSER.txt). + * If not, see . + */ + +package io.nayuki.flac.app; + +import java.awt.Dimension; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseMotionAdapter; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.File; +import java.io.IOException; +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.DataLine; +import javax.sound.sampled.LineUnavailableException; +import javax.sound.sampled.SourceDataLine; +import javax.swing.JFrame; +import javax.swing.JSlider; +import javax.swing.SwingConstants; +import javax.swing.SwingUtilities; +import javax.swing.plaf.basic.BasicSliderUI; +import javax.swing.plaf.metal.MetalSliderUI; +import io.nayuki.flac.common.StreamInfo; +import io.nayuki.flac.decode.FlacDecoder; + + +/** + * Plays a single FLAC file to the system audio output, showing a GUI window with a seek bar. + * The file to play is specified as a command line argument. The seek bar is responsible for both + * displaying the current playback position, and allowing the user to click to seek to new positions. + *

Usage: java SeekableFlacPlayerGui InFile.flac

+ */ +public final class SeekableFlacPlayerGui { + + public static void main(String[] args) throws + LineUnavailableException, IOException, InterruptedException { + + /*-- Initialization code --*/ + + // Handle command line arguments + if (args.length != 1) { + System.err.println("Usage: java SeekableFlacPlayerGui InFile.flac"); + System.exit(1); + return; + } + File inFile = new File(args[0]); + + // Process header metadata blocks + FlacDecoder decoder = new FlacDecoder(inFile); + while (decoder.readAndHandleMetadataBlock() != null); + StreamInfo streamInfo = decoder.streamInfo; + if (streamInfo.numSamples == 0) + throw new IllegalArgumentException("Unknown audio length"); + + // Start Java sound output API + AudioFormat format = new AudioFormat(streamInfo.sampleRate, + streamInfo.sampleDepth, streamInfo.numChannels, true, false); + DataLine.Info info = new DataLine.Info(SourceDataLine.class, format); + SourceDataLine line = (SourceDataLine)AudioSystem.getLine(info); + line.open(format); + line.start(); + + // Create GUI object, event handler, communication object + final double[] seekRequest = {-1}; + AudioPlayerGui gui = new AudioPlayerGui("FLAC Player"); + gui.listener = new AudioPlayerGui.Listener() { + public void seekRequested(double t) { + synchronized(seekRequest) { + seekRequest[0] = t; + seekRequest.notify(); + } + } + public void windowClosing() { + System.exit(0); + } + }; + + /*-- Audio player loop --*/ + + // Decode and write audio data, handle seek requests, wait for seek when end of stream reached + int bytesPerSample = streamInfo.sampleDepth / 8; + long startTime = line.getMicrosecondPosition(); + + // Buffers for data created and discarded within each loop iteration, but allocated outside the loop + int[][] samples = new int[streamInfo.numChannels][65536]; + byte[] sampleBytes = new byte[65536 * streamInfo.numChannels * bytesPerSample]; + while (true) { + + // Get and clear seek request, if any + double seekReq; + synchronized(seekRequest) { + seekReq = seekRequest[0]; + seekRequest[0] = -1; + } + + // Decode next audio block, or seek and decode + int blockSamples; + if (seekReq == -1) + blockSamples = decoder.readAudioBlock(samples, 0); + else { + long samplePos = Math.round(seekReq * streamInfo.numSamples); + seekReq = -1; + blockSamples = decoder.seekAndReadAudioBlock(samplePos, samples, 0); + line.flush(); + startTime = line.getMicrosecondPosition() - Math.round(samplePos * 1e6 / streamInfo.sampleRate); + } + + // Set display position + double timePos = (line.getMicrosecondPosition() - startTime) / 1e6; + gui.setPosition(timePos * streamInfo.sampleRate / streamInfo.numSamples); + + // Wait when end of stream reached + if (blockSamples == 0) { + synchronized(seekRequest) { + while (seekRequest[0] == -1) + seekRequest.wait(); + } + continue; + } + + // Convert samples to channel-interleaved bytes in little endian + int sampleBytesLen = 0; + for (int i = 0; i < blockSamples; i++) { + for (int ch = 0; ch < streamInfo.numChannels; ch++) { + int val = samples[ch][i]; + for (int j = 0; j < bytesPerSample; j++, sampleBytesLen++) + sampleBytes[sampleBytesLen] = (byte)(val >>> (j << 3)); + } + } + line.write(sampleBytes, 0, sampleBytesLen); + } + } + + + + /*---- User interface classes ----*/ + + private static final class AudioPlayerGui { + + /*-- Fields --*/ + + public Listener listener; + private JSlider slider; + private BasicSliderUI sliderUi; + + + /*-- Constructor --*/ + + public AudioPlayerGui(String windowTitle) { + // Create and configure slider + slider = new JSlider(SwingConstants.HORIZONTAL, 0, 10000, 0); + sliderUi = new MetalSliderUI(); + slider.setUI(sliderUi); + slider.setPreferredSize(new Dimension(800, 50)); + slider.addMouseListener(new MouseAdapter() { + public void mousePressed(MouseEvent ev) { + moveSlider(ev); + } + public void mouseReleased(MouseEvent ev) { + moveSlider(ev); + listener.seekRequested((double)slider.getValue() / slider.getMaximum()); + } + }); + slider.addMouseMotionListener(new MouseMotionAdapter() { + public void mouseDragged(MouseEvent ev) { + moveSlider(ev); + } + }); + + // Create and configure frame (window) + JFrame frame = new JFrame(windowTitle); + frame.add(slider); + frame.pack(); + frame.addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent ev) { + listener.windowClosing(); + } + }); + frame.setResizable(false); + frame.setVisible(true); + } + + + /*-- Methods --*/ + + public void setPosition(double t) { + if (Double.isNaN(t)) + return; + final double val = Math.max(Math.min(t, 1), 0); + SwingUtilities.invokeLater(new Runnable() { + public void run() { + if (!slider.getValueIsAdjusting()) + slider.setValue((int)Math.round(val * slider.getMaximum())); + } + }); + } + + + private void moveSlider(MouseEvent ev) { + slider.setValue(sliderUi.valueForXPosition(ev.getX())); + } + + + /*-- Helper interface --*/ + + public interface Listener { + + public void seekRequested(double t); // 0.0 <= t <= 1.0 + + public void windowClosing(); + + } + + } + +} diff --git a/desktop/src/io/nayuki/flac/app/ShowFlacFileStats.java b/desktop/src/io/nayuki/flac/app/ShowFlacFileStats.java new file mode 100644 index 0000000..21f4216 --- /dev/null +++ b/desktop/src/io/nayuki/flac/app/ShowFlacFileStats.java @@ -0,0 +1,277 @@ +/* + * FLAC library (Java) + * + * Copyright (c) Project Nayuki + * https://www.nayuki.io/page/flac-library-java + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program (see COPYING.txt and COPYING.LESSER.txt). + * If not, see . + */ + +package io.nayuki.flac.app; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.SortedMap; +import java.util.TreeMap; +import io.nayuki.flac.common.FrameInfo; +import io.nayuki.flac.common.StreamInfo; +import io.nayuki.flac.decode.DataFormatException; +import io.nayuki.flac.decode.FlacLowLevelInput; +import io.nayuki.flac.decode.FrameDecoder; +import io.nayuki.flac.decode.SeekableFileFlacInput; + + +/** + * Reads a FLAC file, collects various statistics, and + * prints human-formatted information to standard output. + *

Usage: java ShowFlacFileStats InFile.flac

+ *

Example output from this program (abbreviated):

+ *
===== Block sizes (samples) =====
+ * 4096: * (11)
+ * 5120: ***** (56)
+ * 6144: *********** (116)
+ * 7168: ************* (134)
+ * 8192: ***************** (177)
+ * 9216: ***************** (182)
+ *10240: ***************** (179)
+ *11264: ****************************** (318)
+ *12288: ****************** (194)
+ *
+ *===== Frame sizes (bytes) =====
+ *12000: ****** (20)
+ *13000: ******* (24)
+ *14000: ********** (34)
+ *15000: **************** (51)
+ *16000: ********************* (68)
+ *17000: ******************* (63)
+ *18000: ******************* (63)
+ *19000: ************************ (77)
+ *20000: ********************* (70)
+ *21000: ****************** (60)
+ *22000: ************************* (82)
+ *23000: ********************* (69)
+ *24000: *************************** (87)
+ *25000: *************************** (88)
+ *26000: ********************** (73)
+ *27000: ************************** (84)
+ *28000: ****************************** (98)
+ *29000: ********************** (73)
+ *30000: *********************** (75)
+ *31000: ************ (39)
+ *
+ *===== Average compression ratio at block sizes =====
+ * 4096: ********************** (0.7470)
+ * 5120: ******************** (0.6815)
+ * 6144: ******************** (0.6695)
+ * 7168: ******************* (0.6438)
+ * 8192: ******************* (0.6379)
+ * 9216: ****************** (0.6107)
+ *10240: ****************** (0.6022)
+ *11264: ***************** (0.5628)
+ *12288: ***************** (0.5724)
+ *
+ *===== Stereo coding modes =====
+ *Independent: **** (83)
+ *Left-side  :  (3)
+ *Right-side : ************************ (574)
+ *Mid-side   : ****************************** (708)
+ */ +public final class ShowFlacFileStats { + + /*---- Main application function ----*/ + + public static void main(String[] args) throws IOException { + // Handle command line arguments + if (args.length != 1) { + System.err.println("Usage: java ShowFlacFileStats InFile.flac"); + System.exit(1); + return; + } + File inFile = new File(args[0]); + + // Data structures to hold statistics + List blockSizes = new ArrayList<>(); + List frameSizes = new ArrayList<>(); + List channelAssignments = new ArrayList<>(); + + // Read input file + StreamInfo streamInfo = null; + try (FlacLowLevelInput input = new SeekableFileFlacInput(inFile)) { + // Magic string "fLaC" + if (input.readUint(32) != 0x664C6143) + throw new DataFormatException("Invalid magic string"); + + // Handle metadata blocks + for (boolean last = false; !last; ) { + last = input.readUint(1) != 0; + int type = input.readUint(7); + int length = input.readUint(24); + byte[] data = new byte[length]; + input.readFully(data); + if (type == 0) + streamInfo = new StreamInfo(data); + } + + // Decode every frame + FrameDecoder dec = new FrameDecoder(input, streamInfo.sampleDepth); + int[][] blockSamples = new int[8][65536]; + while (true) { + FrameInfo meta = dec.readFrame(blockSamples, 0); + if (meta == null) + break; + blockSizes.add(meta.blockSize); + frameSizes.add(meta.frameSize); + channelAssignments.add(meta.channelAssignment); + } + } + + // Build and print graphs + printBlockSizeHistogram(blockSizes); + printFrameSizeHistogram(frameSizes); + printCompressionRatioGraph(streamInfo, blockSizes, frameSizes); + if (streamInfo.numChannels == 2) + printStereoModeGraph(channelAssignments); + } + + + + /*---- Statistics-processing functions ----*/ + + private static void printBlockSizeHistogram(List blockSizes) { + Map blockSizeCounts = new TreeMap<>(); + for (int bs : blockSizes) { + if (!blockSizeCounts.containsKey(bs)) + blockSizeCounts.put(bs, 0); + int count = blockSizeCounts.get(bs) + 1; + blockSizeCounts.put(bs, count); + } + List blockSizeLabels = new ArrayList<>(); + List blockSizeValues = new ArrayList<>(); + for (Map.Entry entry : blockSizeCounts.entrySet()) { + blockSizeLabels.add(String.format("%5d", entry.getKey())); + blockSizeValues.add((double)entry.getValue()); + } + printNormalizedBarGraph("Block sizes (samples)", blockSizeLabels, blockSizeValues); + } + + + private static void printFrameSizeHistogram(List frameSizes) { + final int step = 1000; + SortedMap frameSizeCounts = new TreeMap<>(); + int maxKeyLen = 0; + for (int fs : frameSizes) { + int key = (int)Math.round((double)fs / step); + maxKeyLen = Math.max(Integer.toString(key * step).length(), maxKeyLen); + if (!frameSizeCounts.containsKey(key)) + frameSizeCounts.put(key, 0); + frameSizeCounts.put(key, frameSizeCounts.get(key) + 1); + } + for (int i = frameSizeCounts.firstKey(); i < frameSizeCounts.lastKey(); i++) { + if (!frameSizeCounts.containsKey(i)) + frameSizeCounts.put(i, 0); + } + List frameSizeLabels = new ArrayList<>(); + List frameSizeValues = new ArrayList<>(); + for (Map.Entry entry : frameSizeCounts.entrySet()) { + frameSizeLabels.add(String.format("%" + maxKeyLen + "d", entry.getKey() * step)); + frameSizeValues.add((double)entry.getValue()); + } + printNormalizedBarGraph("Frame sizes (bytes)", frameSizeLabels, frameSizeValues); + } + + + private static void printCompressionRatioGraph(StreamInfo streamInfo, List blockSizes, List frameSizes) { + Map blockSizeCounts = new TreeMap<>(); + Map blockSizeBytes = new TreeMap<>(); + for (int i = 0; i < blockSizes.size(); i++) { + int bs = blockSizes.get(i); + if (!blockSizeCounts.containsKey(bs)) { + blockSizeCounts.put(bs, 0); + blockSizeBytes.put(bs, 0L); + } + blockSizeCounts.put(bs, blockSizeCounts.get(bs) + 1); + blockSizeBytes.put(bs, blockSizeBytes.get(bs) + frameSizes.get(i)); + } + List blockRatioLabels = new ArrayList<>(); + List blockRatioValues = new ArrayList<>(); + for (Map.Entry entry : blockSizeCounts.entrySet()) { + blockRatioLabels.add(String.format("%5d", entry.getKey())); + blockRatioValues.add(blockSizeBytes.get(entry.getKey()) / ((double)entry.getValue() * entry.getKey() * streamInfo.numChannels * streamInfo.sampleDepth / 8)); + } + printNormalizedBarGraph("Average compression ratio at block sizes", blockRatioLabels, blockRatioValues); + } + + + private static void printStereoModeGraph(List channelAssignments) { + List stereoModeLabels = Arrays.asList("Independent", "Left-side", "Right-side", "Mid-side"); + List stereoModeValues = new ArrayList<>(); + for (int i = 0; i < 4; i++) + stereoModeValues.add(0.0); + for (int mode : channelAssignments) { + int index; + switch (mode) { + case 1: index = 0; break; + case 8: index = 1; break; + case 9: index = 2; break; + case 10: index = 3; break; + default: throw new DataFormatException("Invalid mode in stereo stream"); + } + stereoModeValues.set(index, stereoModeValues.get(index) + 1); + } + printNormalizedBarGraph("Stereo coding modes", stereoModeLabels, stereoModeValues); + } + + + + /*---- Utility functions ----*/ + + private static void printNormalizedBarGraph(String heading, List labels, List values) { + Objects.requireNonNull(heading); + Objects.requireNonNull(labels); + Objects.requireNonNull(values); + if (labels.size() != values.size()) + throw new IllegalArgumentException(); + + final int maxBarWidth = 100; + System.out.printf("==================== %s ====================%n", heading); + System.out.println(); + + int maxLabelLen = 0; + for (String s : labels) + maxLabelLen = Math.max(s.length(), maxLabelLen); + String spaces = new String(new char[maxLabelLen]).replace((char)0, ' '); + + double maxValue = 1; // This avoids division by zero + for (double val : values) + maxValue = Math.max(val, maxValue); + + for (int i = 0; i < labels.size(); i++) { + String label = labels.get(i); + double value = values.get(i); + int barWidth = (int)Math.round(value / maxValue * maxBarWidth); + String bar = new String(new char[barWidth]).replace((char)0, '*'); + System.out.printf("%s%s: %s (%s)%n", label, spaces.substring(label.length()), + bar, (long)value == value ? Long.toString((long)value) : Double.toString(value)); + } + System.out.println(); + System.out.println(); + } + +} diff --git a/desktop/src/io/nayuki/flac/common/FrameInfo.java b/desktop/src/io/nayuki/flac/common/FrameInfo.java new file mode 100644 index 0000000..a4c1562 --- /dev/null +++ b/desktop/src/io/nayuki/flac/common/FrameInfo.java @@ -0,0 +1,456 @@ +/* + * FLAC library (Java) + * + * Copyright (c) Project Nayuki + * https://www.nayuki.io/page/flac-library-java + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program (see COPYING.txt and COPYING.LESSER.txt). + * If not, see . + */ + +package io.nayuki.flac.common; + +import java.io.IOException; +import java.util.Objects; +import io.nayuki.flac.decode.DataFormatException; +import io.nayuki.flac.decode.FlacLowLevelInput; +import io.nayuki.flac.decode.FrameDecoder; +import io.nayuki.flac.encode.BitOutputStream; + + +/** + * Represents most fields in a frame header, in decoded (not raw) form. Mutable structure, + * not thread safe. Also has methods for parsing and serializing this structure to/from bytes. + * All fields can be modified freely when no method call is active. + * @see FrameDecoder + * @see StreamInfo#checkFrame(FrameInfo) + */ +public final class FrameInfo { + + /*---- Fields ----*/ + + // Exactly one of these following two fields equals -1. + + /** + * The index of this frame, where the foremost frame has index 0 and each subsequent frame + * increments it. This is either a uint31 value or −1 if unused. Exactly one of the fields + * frameIndex and sampleOffse is equal to −1 (not both nor neither). This value can only + * be used if the stream info's minBlockSize = maxBlockSize (constant block size encoding style). + */ + public int frameIndex; + + /** + * The offset of the first sample in this frame with respect to the beginning of the + * audio stream. This is either a uint36 value or −1 if unused. Exactly one of + * the fields frameIndex and sampleOffse is equal to −1 (not both nor neither). + */ + public long sampleOffset; + + + /** + * The number of audio channels in this frame, in the range 1 to 8 inclusive. + * This value is fully determined by the channelAssignment field. + */ + public int numChannels; + + /** + * The raw channel assignment value of this frame, which is a uint4 value. + * This indicates the number of channels, but also tells the stereo coding mode. + */ + public int channelAssignment; + + /** + * The number of samples per channel in this frame, in the range 1 to 65536 inclusive. + */ + public int blockSize; + + /** + * The sample rate of this frame in hertz (Hz), in the range 1 to 655360 inclusive, + * or −1 if unavailable (i.e. the stream info should be consulted). + */ + public int sampleRate; + + /** + * The sample depth of this frame in bits, in the range 8 to 24 inclusive, + * or −1 if unavailable (i.e. the stream info should be consulted). + */ + public int sampleDepth; + + /** + * The size of this frame in bytes, from the start of the sync sequence to the end + * of the trailing CRC-16 checksum. A valid value is at least 10, or −1 + * if unavailable (e.g. the frame header was parsed but not the entire frame). + */ + public int frameSize; + + + + /*---- Constructors ----*/ + + /** + * Constructs a blank frame metadata structure, setting all fields to unknown or invalid values. + */ + public FrameInfo() { + frameIndex = -1; + sampleOffset = -1; + numChannels = -1; + channelAssignment = -1; + blockSize = -1; + sampleRate = -1; + sampleDepth = -1; + frameSize = -1; + } + + + + /*---- Functions to read FrameInfo from stream ----*/ + + /** + * Reads the next FLAC frame header from the specified input stream, either returning + * a new frame info object or {@code null}. The stream must be aligned to a byte + * boundary and start at a sync sequence. If EOF is immediately encountered before + * any bytes were read, then this returns {@code null}. + *

Otherwise this reads between 6 to 16 bytes from the stream – starting + * from the sync code, and ending after the CRC-8 value is read (but before reading + * any subframes). It tries to parse the frame header data. After the values are + * successfully decoded, a new frame info object is created, almost all fields are + * set to the parsed values, and it is returned. (This doesn't read to the end + * of the frame, so the frameSize field is set to -1.)

+ * @param in the input stream to read from (not {@code null}) + * @return a new frame info object or {@code null} + * @throws NullPointerException if the input stream is {@code null} + * @throws DataFormatException if the input data contains invalid values + * @throws IOException if an I/O exception occurred + */ + public static FrameInfo readFrame(FlacLowLevelInput in) throws IOException { + // Preliminaries + in.resetCrcs(); + int temp = in.readByte(); + if (temp == -1) + return null; + FrameInfo result = new FrameInfo(); + result.frameSize = -1; + + // Read sync bits + int sync = temp << 6 | in.readUint(6); // Uint14 + if (sync != 0x3FFE) + throw new DataFormatException("Sync code expected"); + + // Read various simple fields + if (in.readUint(1) != 0) + throw new DataFormatException("Reserved bit"); + int blockStrategy = in.readUint(1); + int blockSizeCode = in.readUint(4); + int sampleRateCode = in.readUint(4); + int chanAsgn = in.readUint(4); + result.channelAssignment = chanAsgn; + if (chanAsgn < 8) + result.numChannels = chanAsgn + 1; + else if (8 <= chanAsgn && chanAsgn <= 10) + result.numChannels = 2; + else + throw new DataFormatException("Reserved channel assignment"); + result.sampleDepth = decodeSampleDepth(in.readUint(3)); + if (in.readUint(1) != 0) + throw new DataFormatException("Reserved bit"); + + // Read and check the frame/sample position field + long position = readUtf8Integer(in); // Reads 1 to 7 bytes + if (blockStrategy == 0) { + if ((position >>> 31) != 0) + throw new DataFormatException("Frame index too large"); + result.frameIndex = (int)position; + result.sampleOffset = -1; + } else if (blockStrategy == 1) { + result.sampleOffset = position; + result.frameIndex = -1; + } else + throw new AssertionError(); + + // Read variable-length data for some fields + result.blockSize = decodeBlockSize(blockSizeCode, in); // Reads 0 to 2 bytes + result.sampleRate = decodeSampleRate(sampleRateCode, in); // Reads 0 to 2 bytes + int computedCrc8 = in.getCrc8(); + if (in.readUint(8) != computedCrc8) + throw new DataFormatException("CRC-8 mismatch"); + return result; + } + + + // Reads 1 to 7 whole bytes from the input stream. Return value is a uint36. + // See: https://hydrogenaud.io/index.php/topic,112831.msg929128.html#msg929128 + private static long readUtf8Integer(FlacLowLevelInput in) throws IOException { + int head = in.readUint(8); + int n = Integer.numberOfLeadingZeros(~(head << 24)); // Number of leading 1s in the byte + assert 0 <= n && n <= 8; + if (n == 0) + return head; + else if (n == 1 || n == 8) + throw new DataFormatException("Invalid UTF-8 coded number"); + else { + long result = head & (0x7F >>> n); + for (int i = 0; i < n - 1; i++) { + int temp = in.readUint(8); + if ((temp & 0xC0) != 0x80) + throw new DataFormatException("Invalid UTF-8 coded number"); + result = (result << 6) | (temp & 0x3F); + } + if ((result >>> 36) != 0) + throw new AssertionError(); + return result; + } + } + + + // Argument is a uint4 value. Reads 0 to 2 bytes from the input stream. + // Return value is in the range [1, 65536]. + private static int decodeBlockSize(int code, FlacLowLevelInput in) throws IOException { + if ((code >>> 4) != 0) + throw new IllegalArgumentException(); + switch (code) { + case 0: throw new DataFormatException("Reserved block size"); + case 6: return in.readUint(8) + 1; + case 7: return in.readUint(16) + 1; + default: + int result = searchSecond(BLOCK_SIZE_CODES, code); + if (result < 1 || result > 65536) + throw new AssertionError(); + return result; + } + } + + + // Argument is a uint4 value. Reads 0 to 2 bytes from the input stream. + // Return value is in the range [-1, 655350]. + private static int decodeSampleRate(int code, FlacLowLevelInput in) throws IOException { + if ((code >>> 4) != 0) + throw new IllegalArgumentException(); + switch (code) { + case 0: return -1; // Caller should obtain value from stream info metadata block + case 12: return in.readUint(8); + case 13: return in.readUint(16); + case 14: return in.readUint(16) * 10; + case 15: throw new DataFormatException("Invalid sample rate"); + default: + int result = searchSecond(SAMPLE_RATE_CODES, code); + if (result < 1 || result > 655350) + throw new AssertionError(); + return result; + } + } + + + // Argument is a uint3 value. Pure function and performs no I/O. Return value is in the range [-1, 24]. + private static int decodeSampleDepth(int code) { + if ((code >>> 3) != 0) + throw new IllegalArgumentException(); + else if (code == 0) + return -1; // Caller should obtain value from stream info metadata block + else { + int result = searchSecond(SAMPLE_DEPTH_CODES, code); + if (result == -1) + throw new DataFormatException("Reserved bit depth"); + if (result < 1 || result > 32) + throw new AssertionError(); + return result; + } + } + + + + /*---- Functions to write FrameInfo to stream ----*/ + + /** + * Writes the current state of this object as a frame header to the specified + * output stream, from the sync field through to the CRC-8 field (inclusive). + * This does not write the data of subframes, the bit padding, nor the CRC-16 field.

+ *

The stream must be byte-aligned before this method is called, and will be aligned + * upon returning (i.e. it writes a whole number of bytes). This method initially resets + * the stream's CRC computations, which is useful behavior for the caller because + * it will need to write the CRC-16 at the end of the frame.

+ * @param out the output stream to write to (not {@code null}) + * @throws NullPointerException if the output stream is {@code null} + * @throws IOException if an I/O exception occurred + */ + public void writeHeader(BitOutputStream out) throws IOException { + Objects.requireNonNull(out); + out.resetCrcs(); + out.writeInt(14, 0x3FFE); // Sync + out.writeInt(1, 0); // Reserved + out.writeInt(1, 1); // Blocking strategy + + int blockSizeCode = getBlockSizeCode(blockSize); + out.writeInt(4, blockSizeCode); + int sampleRateCode = getSampleRateCode(sampleRate); + out.writeInt(4, sampleRateCode); + + out.writeInt(4, channelAssignment); + out.writeInt(3, getSampleDepthCode(sampleDepth)); + out.writeInt(1, 0); // Reserved + + // Variable-length: 1 to 7 bytes + if (frameIndex != -1 && sampleOffset == -1) + writeUtf8Integer(sampleOffset, out); + else if (sampleOffset != -1 && frameIndex == -1) + writeUtf8Integer(sampleOffset, out); + else + throw new IllegalStateException(); + + // Variable-length: 0 to 2 bytes + if (blockSizeCode == 6) + out.writeInt(8, blockSize - 1); + else if (blockSizeCode == 7) + out.writeInt(16, blockSize - 1); + + // Variable-length: 0 to 2 bytes + if (sampleRateCode == 12) + out.writeInt(8, sampleRate); + else if (sampleRateCode == 13) + out.writeInt(16, sampleRate); + else if (sampleRateCode == 14) + out.writeInt(16, sampleRate / 10); + + out.writeInt(8, out.getCrc8()); + } + + + // Given a uint36 value, this writes 1 to 7 whole bytes to the given output stream. + private static void writeUtf8Integer(long val, BitOutputStream out) throws IOException { + if ((val >>> 36) != 0) + throw new IllegalArgumentException(); + int bitLen = 64 - Long.numberOfLeadingZeros(val); + if (bitLen <= 7) + out.writeInt(8, (int)val); + else { + int n = (bitLen - 2) / 5; + out.writeInt(8, (0xFF80 >>> n) | (int)(val >>> (n * 6))); + for (int i = n - 1; i >= 0; i--) + out.writeInt(8, 0x80 | ((int)(val >>> (i * 6)) & 0x3F)); + } + } + + + // Returns a uint4 value representing the given block size. Pure function. + private static int getBlockSizeCode(int blockSize) { + int result = searchFirst(BLOCK_SIZE_CODES, blockSize); + if (result != -1); // Already done + else if (1 <= blockSize && blockSize <= 256) + result = 6; + else if (1 <= blockSize && blockSize <= 65536) + result = 7; + else // blockSize < 1 || blockSize > 65536 + throw new IllegalArgumentException(); + + if ((result >>> 4) != 0) + throw new AssertionError(); + return result; + } + + + // Returns a uint4 value representing the given sample rate. Pure function. + private static int getSampleRateCode(int sampleRate) { + if (sampleRate == 0 || sampleRate < -1) + throw new IllegalArgumentException(); + int result = searchFirst(SAMPLE_RATE_CODES, sampleRate); + if (result != -1); // Already done + else if (0 <= sampleRate && sampleRate < 256) + result = 12; + else if (0 <= sampleRate && sampleRate < 65536) + result = 13; + else if (0 <= sampleRate && sampleRate < 655360 && sampleRate % 10 == 0) + result = 14; + else + result = 0; + + if ((result >>> 4) != 0) + throw new AssertionError(); + return result; + } + + + // Returns a uint3 value representing the given sample depth. Pure function. + private static int getSampleDepthCode(int sampleDepth) { + if (sampleDepth != -1 && (sampleDepth < 1 || sampleDepth > 32)) + throw new IllegalArgumentException(); + int result = searchFirst(SAMPLE_DEPTH_CODES, sampleDepth); + if (result == -1) + result = 0; + if ((result >>> 3) != 0) + throw new AssertionError(); + return result; + } + + + + /*---- Tables of constants and search functions ----*/ + + private static final int searchFirst(int[][] table, int key) { + for (int[] pair : table) { + if (pair[0] == key) + return pair[1]; + } + return -1; + } + + + private static final int searchSecond(int[][] table, int key) { + for (int[] pair : table) { + if (pair[1] == key) + return pair[0]; + } + return -1; + } + + + private static final int[][] BLOCK_SIZE_CODES = { + { 192, 1}, + { 576, 2}, + { 1152, 3}, + { 2304, 4}, + { 4608, 5}, + { 256, 8}, + { 512, 9}, + { 1024, 10}, + { 2048, 11}, + { 4096, 12}, + { 8192, 13}, + {16384, 14}, + {32768, 15}, + }; + + + private static final int[][] SAMPLE_DEPTH_CODES = { + { 8, 1}, + {12, 2}, + {16, 4}, + {20, 5}, + {24, 6}, + }; + + + private static final int[][] SAMPLE_RATE_CODES = { + { 88200, 1}, + {176400, 2}, + {192000, 3}, + { 8000, 4}, + { 16000, 5}, + { 22050, 6}, + { 24000, 7}, + { 32000, 8}, + { 44100, 9}, + { 48000, 10}, + { 96000, 11}, + }; + +} diff --git a/desktop/src/io/nayuki/flac/common/SeekTable.java b/desktop/src/io/nayuki/flac/common/SeekTable.java new file mode 100644 index 0000000..04b433b --- /dev/null +++ b/desktop/src/io/nayuki/flac/common/SeekTable.java @@ -0,0 +1,204 @@ +/* + * FLAC library (Java) + * + * Copyright (c) Project Nayuki + * https://www.nayuki.io/page/flac-library-java + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program (see COPYING.txt and COPYING.LESSER.txt). + * If not, see . + */ + +package io.nayuki.flac.common; + +import java.io.ByteArrayInputStream; +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import io.nayuki.flac.decode.FlacDecoder; +import io.nayuki.flac.encode.BitOutputStream; + + +/** + * Represents precisely all the fields of a seek table metadata block. Mutable structure, + * not thread-safe. Also has methods for parsing and serializing this structure to/from bytes. + * All fields and objects can be modified freely when no method call is active. + * @see FlacDecoder + */ +public final class SeekTable { + + /*---- Fields ----*/ + + /** + * The list of seek points in this seek table. It is okay to replace this + * list as needed (the initially constructed list object is not special). + */ + public List points; + + + + /*---- Constructors ----*/ + + /** + * Constructs a blank seek table with an initially empty + * list of seek points. (Note that the empty state is legal.) + */ + public SeekTable() { + points = new ArrayList<>(); + } + + + /** + * Constructs a seek table by parsing the given byte array representing the metadata block. + * (The array must contain only the metadata payload, without the type or length fields.) + *

This constructor does not check the validity of the seek points, namely the ordering + * of seek point offsets, so calling {@link#checkValues()} on the freshly constructed object + * can fail. However, this does guarantee that every point's frameSamples field is a uint16.

+ * @param b the metadata block's payload data to parse (not {@code null}) + * @throws NullPointerException if the array is {@code null} + * @throws IllegalArgumentException if the array length + * is not a multiple of 18 (size of each seek point) + */ + public SeekTable(byte[] b) { + this(); + Objects.requireNonNull(b); + if (b.length % 18 != 0) + throw new IllegalArgumentException("Data contains a partial seek point"); + try { + DataInput in = new DataInputStream(new ByteArrayInputStream(b)); + for (int i = 0; i < b.length; i += 18) { + SeekPoint p = new SeekPoint(); + p.sampleOffset = in.readLong(); + p.fileOffset = in.readLong(); + p.frameSamples = in.readUnsignedShort(); + points.add(p); + } + // Skip closing the in-memory streams + } catch (IOException e) { + throw new AssertionError(e); + } + } + + + + /*---- Methods ----*/ + + /** + * Checks the state of this object and returns silently if all these criteria pass: + *
    + *
  • No object is {@code null}
  • + *
  • The frameSamples field of each point is a uint16 value
  • + *
  • All points with sampleOffset = −1 (i.e. 0xFFF...FFF) are at the end of the list
  • + *
  • All points with sampleOffset ≠ −1 have strictly increasing + * values of sampleOffset and non-decreasing values of fileOffset
  • + *
+ * @throws NullPointerException if the list or an element is {@code null} + * @throws IllegalStateException if the current list of seek points is contains invalid data + */ + public void checkValues() { + // Check list and each point + Objects.requireNonNull(points); + for (SeekPoint p : points) { + Objects.requireNonNull(p); + if ((p.frameSamples & 0xFFFF) != p.frameSamples) + throw new IllegalStateException("Frame samples outside uint16 range"); + } + + // Check ordering of points + for (int i = 1; i < points.size(); i++) { + SeekPoint p = points.get(i); + if (p.sampleOffset != -1) { + SeekPoint q = points.get(i - 1); + if (p.sampleOffset <= q.sampleOffset) + throw new IllegalStateException("Sample offsets out of order"); + if (p.fileOffset < q.fileOffset) + throw new IllegalStateException("File offsets out of order"); + } + } + } + + + /** + * Writes all the points of this seek table as a metadata block to the specified output stream, + * also indicating whether it is the last metadata block. (This does write the type and length + * fields for the metadata block, unlike the constructor which takes an array without those fields.) + * @param last whether the metadata block is the final one in the FLAC file + * @param out the output stream to write to (not {@code null}) + * @throws NullPointerException if the output stream is {@code null} + * @throws IllegalStateException if there are too many + * @throws IOException if an I/O exception occurred + * seek points (> 932067) or {@link#checkValues()} fails + */ + public void write(boolean last, BitOutputStream out) throws IOException { + // Check arguments and state + Objects.requireNonNull(out); + Objects.requireNonNull(points); + if (points.size() > ((1 << 24) - 1) / 18) + throw new IllegalStateException("Too many seek points"); + checkValues(); + + // Write metadata block header + out.writeInt(1, last ? 1 : 0); + out.writeInt(7, 3); + out.writeInt(24, points.size() * 18); + + // Write each seek point + for (SeekPoint p : points) { + out.writeInt(32, (int)(p.sampleOffset >>> 32)); + out.writeInt(32, (int)(p.sampleOffset >>> 0)); + out.writeInt(32, (int)(p.fileOffset >>> 32)); + out.writeInt(32, (int)(p.fileOffset >>> 0)); + out.writeInt(16, p.frameSamples); + } + } + + + + /*---- Helper structure ----*/ + + /** + * Represents a seek point entry in a seek table. Mutable structure, not thread-safe. + * This class itself does not check the correctness of data, but other classes might. + *

A seek point with data (sampleOffset = x, fileOffset = y, frameSamples = z) means + * that at byte position (y + (byte offset of foremost audio frame)) in the file, + * a FLAC frame begins (with the sync sequence), that frame has sample offset x + * (where sample 0 is defined as the start of the audio stream), + * and the frame contains z samples per channel. + * @see SeekTable + */ + public static final class SeekPoint { + + /** + * The sample offset in the audio stream, a uint64 value. + * A value of -1 (i.e. 0xFFF...FFF) means this is a placeholder point. + */ + public long sampleOffset; + + /** + * The byte offset relative to the start of the foremost frame, a uint64 value. + * If sampleOffset is -1, then this value is ignored. + */ + public long fileOffset; + + /** + * The number of audio samples in the target block/frame, a uint16 value. + * If sampleOffset is -1, then this value is ignored. + */ + public int frameSamples; + + } + +} diff --git a/desktop/src/io/nayuki/flac/common/StreamInfo.java b/desktop/src/io/nayuki/flac/common/StreamInfo.java new file mode 100644 index 0000000..d8ab3e1 --- /dev/null +++ b/desktop/src/io/nayuki/flac/common/StreamInfo.java @@ -0,0 +1,310 @@ +/* + * FLAC library (Java) + * + * Copyright (c) Project Nayuki + * https://www.nayuki.io/page/flac-library-java + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program (see COPYING.txt and COPYING.LESSER.txt). + * If not, see . + */ + +package io.nayuki.flac.common; + +import java.io.IOException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Objects; +import io.nayuki.flac.decode.ByteArrayFlacInput; +import io.nayuki.flac.decode.DataFormatException; +import io.nayuki.flac.decode.FlacDecoder; +import io.nayuki.flac.decode.FlacLowLevelInput; +import io.nayuki.flac.encode.BitOutputStream; + + +/** + * Represents precisely all the fields of a stream info metadata block. Mutable structure, + * not thread-safe. Also has methods for parsing and serializing this structure to/from bytes. + * All fields can be modified freely when no method call is active. + * @see FrameInfo + * @see FlacDecoder + */ +public final class StreamInfo { + + /*---- Fields about block and frame sizes ----*/ + + /** + * Minimum block size (in samples per channel) among the whole stream, a uint16 value. + * However when minBlockSize = maxBlockSize (constant block size encoding style), + * the final block is allowed to be smaller than minBlockSize. + */ + public int minBlockSize; + + /** + * Maximum block size (in samples per channel) among the whole stream, a uint16 value. + */ + public int maxBlockSize; + + /** + * Minimum frame size (in bytes) among the whole stream, a uint24 value. + * However, a value of 0 signifies that the value is unknown. + */ + public int minFrameSize; + + /** + * Maximum frame size (in bytes) among the whole stream, a uint24 value. + * However, a value of 0 signifies that the value is unknown. + */ + public int maxFrameSize; + + + /*---- Fields about stream properties ----*/ + + /** + * The sample rate of the audio stream (in hertz (Hz)), a positive uint20 value. + * Note that 0 is an invalid value. + */ + public int sampleRate; + + /** + * The number of channels in the audio stream, between 1 and 8 inclusive. + * 1 means mono, 2 means stereo, et cetera. + */ + public int numChannels; + + /** + * The bits per sample in the audio stream, in the range 4 to 32 inclusive. + */ + public int sampleDepth; + + /** + * The total number of samples per channel in the whole stream, a uint36 value. + * The special value of 0 signifies that the value is unknown (not empty zero-length stream). + */ + public long numSamples; + + /** + * The 16-byte MD5 hash of the raw uncompressed audio data serialized in little endian with + * channel interleaving (not planar). It can be all zeros to signify that the hash was not computed. + * It is okay to replace this array as needed (the initially constructed array object is not special). + */ + public byte[] md5Hash; + + + + /*---- Constructors ----*/ + + /** + * Constructs a blank stream info structure with certain default values. + */ + public StreamInfo() { + // Set these fields to legal unknown values + minFrameSize = 0; + maxFrameSize = 0; + numSamples = 0; + md5Hash = new byte[16]; + + // Set these fields to invalid (not reserved) values + minBlockSize = 0; + maxBlockSize = 0; + sampleRate = 0; + } + + + /** + * Constructs a stream info structure by parsing the specified 34-byte metadata block. + * (The array must contain only the metadata payload, without the type or length fields.) + * @param b the metadata block's payload data to parse (not {@code null}) + * @throws NullPointerException if the array is {@code null} + * @throws IllegalArgumentException if the array length is not 34 + * @throws DataFormatException if the data contains invalid values + */ + public StreamInfo(byte[] b) { + Objects.requireNonNull(b); + if (b.length != 34) + throw new IllegalArgumentException("Invalid data length"); + try { + FlacLowLevelInput in = new ByteArrayFlacInput(b); + minBlockSize = in.readUint(16); + maxBlockSize = in.readUint(16); + minFrameSize = in.readUint(24); + maxFrameSize = in.readUint(24); + if (minBlockSize < 16) + throw new DataFormatException("Minimum block size less than 16"); + if (maxBlockSize > 65535) + throw new DataFormatException("Maximum block size greater than 65535"); + if (maxBlockSize < minBlockSize) + throw new DataFormatException("Maximum block size less than minimum block size"); + if (minFrameSize != 0 && maxFrameSize != 0 && maxFrameSize < minFrameSize) + throw new DataFormatException("Maximum frame size less than minimum frame size"); + sampleRate = in.readUint(20); + if (sampleRate == 0 || sampleRate > 655350) + throw new DataFormatException("Invalid sample rate"); + numChannels = in.readUint(3) + 1; + sampleDepth = in.readUint(5) + 1; + numSamples = (long)in.readUint(18) << 18 | in.readUint(18); // uint36 + md5Hash = new byte[16]; + in.readFully(md5Hash); + // Skip closing the in-memory stream + } catch (IOException e) { + throw new AssertionError(e); + } + } + + + + /*---- Methods ----*/ + + /** + * Checks the state of this object, and either returns silently or throws an exception. + * @throws NullPointerException if the MD5 hash array is {@code null} + * @throws IllegalStateException if any field has an invalid value + */ + public void checkValues() { + if ((minBlockSize >>> 16) != 0) + throw new IllegalStateException("Invalid minimum block size"); + if ((maxBlockSize >>> 16) != 0) + throw new IllegalStateException("Invalid maximum block size"); + if ((minFrameSize >>> 24) != 0) + throw new IllegalStateException("Invalid minimum frame size"); + if ((maxFrameSize >>> 24) != 0) + throw new IllegalStateException("Invalid maximum frame size"); + if (sampleRate == 0 || (sampleRate >>> 20) != 0) + throw new IllegalStateException("Invalid sample rate"); + if (numChannels < 1 || numChannels > 8) + throw new IllegalStateException("Invalid number of channels"); + if (sampleDepth < 4 || sampleDepth > 32) + throw new IllegalStateException("Invalid sample depth"); + if ((numSamples >>> 36) != 0) + throw new IllegalStateException("Invalid number of samples"); + Objects.requireNonNull(md5Hash); + if (md5Hash.length != 16) + throw new IllegalStateException("Invalid MD5 hash length"); + } + + + /** + * Checks whether the specified frame information is consistent with values in + * this stream info object, either returning silently or throwing an exception. + * @param meta the frame info object to check (not {@code null}) + * @throws NullPointerException if the frame info is {@code null} + * @throws DataFormatException if the frame info contains bad values + */ + public void checkFrame(FrameInfo meta) { + if (meta.numChannels != numChannels) + throw new DataFormatException("Channel count mismatch"); + if (meta.sampleRate != -1 && meta.sampleRate != sampleRate) + throw new DataFormatException("Sample rate mismatch"); + if (meta.sampleDepth != -1 && meta.sampleDepth != sampleDepth) + throw new DataFormatException("Sample depth mismatch"); + if (numSamples != 0 && meta.blockSize > numSamples) + throw new DataFormatException("Block size exceeds total number of samples"); + + if (meta.blockSize > maxBlockSize) + throw new DataFormatException("Block size exceeds maximum"); + // Note: If minBlockSize == maxBlockSize, then the final block + // in the stream is allowed to be smaller than minBlockSize + + if (minFrameSize != 0 && meta.frameSize < minFrameSize) + throw new DataFormatException("Frame size less than minimum"); + if (maxFrameSize != 0 && meta.frameSize > maxFrameSize) + throw new DataFormatException("Frame size exceeds maximum"); + } + + + /** + * Writes this stream info metadata block to the specified output stream, including the + * metadata block header, writing exactly 38 bytes. (This is unlike the constructor, + * which takes an array without the type and length fields.) The output stream must + * initially be aligned to a byte boundary, and will finish at a byte boundary. + * @param last whether the metadata block is the final one in the FLAC file + * @param out the output stream to write to (not {@code null}) + * @throws NullPointerException if the output stream is {@code null} + * @throws IOException if an I/O exception occurred + */ + public void write(boolean last, BitOutputStream out) throws IOException { + // Check arguments and state + Objects.requireNonNull(out); + checkValues(); + + // Write metadata block header + out.writeInt(1, last ? 1 : 0); + out.writeInt(7, 0); // Type + out.writeInt(24, 34); // Length + + // Write stream info block fields + out.writeInt(16, minBlockSize); + out.writeInt(16, maxBlockSize); + out.writeInt(24, minFrameSize); + out.writeInt(24, maxFrameSize); + out.writeInt(20, sampleRate); + out.writeInt(3, numChannels - 1); + out.writeInt(5, sampleDepth - 1); + out.writeInt(18, (int)(numSamples >>> 18)); + out.writeInt(18, (int)(numSamples >>> 0)); + for (byte b : md5Hash) + out.writeInt(8, b); + } + + + + /*---- Static functions ----*/ + + /** + * Computes and returns the MD5 hash of the specified raw audio sample data at the specified + * bit depth. Currently, the bit depth must be a multiple of 8, between 8 and 32 inclusive. + * The returned array is a new object of length 16. + * @param samples the audio samples to hash, where + * each subarray is a channel (all not {@code null}) + * @param depth the bit depth of the audio samples + * (i.e. each sample value is a signed 'depth'-bit integer) + * @return a new 16-byte array representing the MD5 hash of the audio data + * @throws NullPointerException if the array or any subarray is {@code null} + * @throws IllegalArgumentException if the bit depth is unsupported + */ + public static byte[] getMd5Hash(int[][] samples, int depth) { + // Check arguments + Objects.requireNonNull(samples); + for (int[] chanSamples : samples) + Objects.requireNonNull(chanSamples); + if (depth < 0 || depth > 32 || depth % 8 != 0) + throw new IllegalArgumentException("Unsupported bit depth"); + + // Create hasher + MessageDigest hasher; + try { // Guaranteed available by the Java Cryptography Architecture + hasher = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e) { + throw new AssertionError(e); + } + + // Convert samples to a stream of bytes, compute hash + int numChannels = samples.length; + int numSamples = samples[0].length; + int numBytes = depth / 8; + byte[] buf = new byte[numChannels * numBytes * Math.min(numSamples, 2048)]; + for (int i = 0, l = 0; i < numSamples; i++) { + for (int j = 0; j < numChannels; j++) { + int val = samples[j][i]; + for (int k = 0; k < numBytes; k++, l++) + buf[l] = (byte)(val >>> (k << 3)); + } + if (l == buf.length || i == numSamples - 1) { + hasher.update(buf, 0, l); + l = 0; + } + } + return hasher.digest(); + } + +} diff --git a/desktop/src/io/nayuki/flac/decode/AbstractFlacLowLevelInput.java b/desktop/src/io/nayuki/flac/decode/AbstractFlacLowLevelInput.java new file mode 100644 index 0000000..bca3b88 --- /dev/null +++ b/desktop/src/io/nayuki/flac/decode/AbstractFlacLowLevelInput.java @@ -0,0 +1,354 @@ +/* + * FLAC library (Java) + * + * Copyright (c) Project Nayuki + * https://www.nayuki.io/page/flac-library-java + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program (see COPYING.txt and COPYING.LESSER.txt). + * If not, see . + */ + +package io.nayuki.flac.decode; + +import java.io.EOFException; +import java.io.IOException; +import java.util.Arrays; +import java.util.Objects; + + +/** + * A basic implementation of most functionality required by FlacLowLevelInpuut. + */ +public abstract class AbstractFlacLowLevelInput implements FlacLowLevelInput { + + /*---- Fields ----*/ + + // Data from the underlying stream is first stored into this byte buffer before further processing. + private long byteBufferStartPos; + private byte[] byteBuffer; + private int byteBufferLen; + private int byteBufferIndex; + + // The buffer of next bits to return to a reader. Note that byteBufferIndex is incremented when byte + // values are put into the bit buffer, but they might not have been consumed by the ultimate reader yet. + private long bitBuffer; // Only the bottom bitBufferLen bits are valid; the top bits are garbage. + private int bitBufferLen; // Always in the range [0, 64]. + + // Current state of the CRC calculations. + private int crc8; // Always a uint8 value. + private int crc16; // Always a uint16 value. + private int crcStartIndex; // In the range [0, byteBufferLen], unless byteBufferLen = -1. + + + + /*---- Constructors ----*/ + + public AbstractFlacLowLevelInput() { + byteBuffer = new byte[4096]; + positionChanged(0); + } + + + + /*---- Methods ----*/ + + /*-- Stream position --*/ + + public long getPosition() { + return byteBufferStartPos + byteBufferIndex - (bitBufferLen + 7) / 8; + } + + + public int getBitPosition() { + return (-bitBufferLen) & 7; + } + + + // When a subclass handles seekTo() and didn't throw UnsupportedOperationException, + // it must call this method to flush the buffers of upcoming data. + protected void positionChanged(long pos) { + byteBufferStartPos = pos; + Arrays.fill(byteBuffer, (byte)0); // Defensive clearing, should have no visible effect outside of debugging + byteBufferLen = 0; + byteBufferIndex = 0; + bitBuffer = 0; // Defensive clearing, should have no visible effect outside of debugging + bitBufferLen = 0; + resetCrcs(); + } + + + // Either returns silently or throws an exception. + private void checkByteAligned() { + if (bitBufferLen % 8 != 0) + throw new IllegalStateException("Not at a byte boundary"); + } + + + /*-- Reading bitwise integers --*/ + + public int readUint(int n) throws IOException { + if (n < 0 || n > 32) + throw new IllegalArgumentException(); + while (bitBufferLen < n) { + int b = readUnderlying(); + if (b == -1) + throw new EOFException(); + bitBuffer = (bitBuffer << 8) | b; + bitBufferLen += 8; + assert 0 <= bitBufferLen && bitBufferLen <= 64; + } + int result = (int)(bitBuffer >>> (bitBufferLen - n)); + if (n != 32) { + result &= (1 << n) - 1; + assert (result >>> n) == 0; + } + bitBufferLen -= n; + assert 0 <= bitBufferLen && bitBufferLen <= 64; + return result; + } + + + public int readSignedInt(int n) throws IOException { + int shift = 32 - n; + return (readUint(n) << shift) >> shift; + } + + + public void readRiceSignedInts(int param, long[] result, int start, int end) throws IOException { + if (param < 0 || param > 31) + throw new IllegalArgumentException(); + long unaryLimit = 1L << (53 - param); + + byte[] consumeTable = RICE_DECODING_CONSUMED_TABLES[param]; + int[] valueTable = RICE_DECODING_VALUE_TABLES[param]; + while (true) { + middle: + while (start <= end - RICE_DECODING_CHUNK) { + if (bitBufferLen < RICE_DECODING_CHUNK * RICE_DECODING_TABLE_BITS) { + if (byteBufferIndex <= byteBufferLen - 8) { + fillBitBuffer(); + } else + break; + } + for (int i = 0; i < RICE_DECODING_CHUNK; i++, start++) { + // Fast decoder + int extractedBits = (int)(bitBuffer >>> (bitBufferLen - RICE_DECODING_TABLE_BITS)) & RICE_DECODING_TABLE_MASK; + int consumed = consumeTable[extractedBits]; + if (consumed == 0) + break middle; + bitBufferLen -= consumed; + result[start] = valueTable[extractedBits]; + } + } + + // Slow decoder + if (start >= end) + break; + long val = 0; + while (readUint(1) == 0) { + if (val >= unaryLimit) { + // At this point, the final decoded value would be so large that the result of the + // downstream restoreLpc() calculation would not fit in the output sample's bit depth - + // hence why we stop early and throw an exception. However, this check is conservative + // and doesn't catch all the cases where the post-LPC result wouldn't fit. + throw new DataFormatException("Residual value too large"); + } + val++; + } + val = (val << param) | readUint(param); // Note: Long masking unnecessary because param <= 31 + assert (val >>> 53) == 0; // Must fit a uint53 by design due to unaryLimit + val = (val >>> 1) ^ -(val & 1); // Transform uint53 to int53 according to Rice coding of signed numbers + assert (val >> 52) == 0 || (val >> 52) == -1; // Must fit a signed int53 by design + result[start] = val; + start++; + } + } + + + // Appends at least 8 bits to the bit buffer, or throws EOFException. + private void fillBitBuffer() throws IOException { + int i = byteBufferIndex; + int n = Math.min((64 - bitBufferLen) >>> 3, byteBufferLen - i); + byte[] b = byteBuffer; + if (n > 0) { + for (int j = 0; j < n; j++, i++) + bitBuffer = (bitBuffer << 8) | (b[i] & 0xFF); + bitBufferLen += n << 3; + } else if (bitBufferLen <= 56) { + int temp = readUnderlying(); + if (temp == -1) + throw new EOFException(); + bitBuffer = (bitBuffer << 8) | temp; + bitBufferLen += 8; + } + assert 8 <= bitBufferLen && bitBufferLen <= 64; + byteBufferIndex += n; + } + + + /*-- Reading bytes --*/ + + public int readByte() throws IOException { + checkByteAligned(); + if (bitBufferLen >= 8) + return readUint(8); + else { + assert bitBufferLen == 0; + return readUnderlying(); + } + } + + + public void readFully(byte[] b) throws IOException { + Objects.requireNonNull(b); + checkByteAligned(); + for (int i = 0; i < b.length; i++) + b[i] = (byte)readUint(8); + } + + + // Reads a byte from the byte buffer (if available) or from the underlying stream, returning either a uint8 or -1. + private int readUnderlying() throws IOException { + if (byteBufferIndex >= byteBufferLen) { + if (byteBufferLen == -1) + return -1; + byteBufferStartPos += byteBufferLen; + updateCrcs(0); + byteBufferLen = readUnderlying(byteBuffer, 0, byteBuffer.length); + crcStartIndex = 0; + if (byteBufferLen <= 0) + return -1; + byteBufferIndex = 0; + } + assert byteBufferIndex < byteBufferLen; + int temp = byteBuffer[byteBufferIndex] & 0xFF; + byteBufferIndex++; + return temp; + } + + + // Reads up to 'len' bytes from the underlying byte-based input stream into the given array subrange. + // Returns a value in the range [0, len] for a successful read, or -1 if the end of stream was reached. + protected abstract int readUnderlying(byte[] buf, int off, int len) throws IOException; + + + /*-- CRC calculations --*/ + + public void resetCrcs() { + checkByteAligned(); + crcStartIndex = byteBufferIndex - bitBufferLen / 8; + crc8 = 0; + crc16 = 0; + } + + + public int getCrc8() { + checkByteAligned(); + updateCrcs(bitBufferLen / 8); + if ((crc8 >>> 8) != 0) + throw new AssertionError(); + return crc8; + } + + + public int getCrc16() { + checkByteAligned(); + updateCrcs(bitBufferLen / 8); + if ((crc16 >>> 16) != 0) + throw new AssertionError(); + return crc16; + } + + + // Updates the two CRC values with data in byteBuffer[crcStartIndex : byteBufferIndex - unusedTrailingBytes]. + private void updateCrcs(int unusedTrailingBytes) { + int end = byteBufferIndex - unusedTrailingBytes; + for (int i = crcStartIndex; i < end; i++) { + int b = byteBuffer[i] & 0xFF; + crc8 = CRC8_TABLE[crc8 ^ b] & 0xFF; + crc16 = CRC16_TABLE[(crc16 >>> 8) ^ b] ^ ((crc16 & 0xFF) << 8); + assert (crc8 >>> 8) == 0; + assert (crc16 >>> 16) == 0; + } + crcStartIndex = end; + } + + + /*-- Miscellaneous --*/ + + // Note: This class only uses memory and has no native resources. It's not strictly necessary to + // call the implementation of AbstractFlacLowLevelInput.close() here, but it's a good habit anyway. + public void close() throws IOException { + byteBuffer = null; + byteBufferLen = -1; + byteBufferIndex = -1; + bitBuffer = 0; + bitBufferLen = -1; + crc8 = -1; + crc16 = -1; + crcStartIndex = -1; + } + + + + /*---- Tables of constants ----*/ + + // For Rice decoding + + private static final int RICE_DECODING_TABLE_BITS = 13; // Configurable, must be positive + private static final int RICE_DECODING_TABLE_MASK = (1 << RICE_DECODING_TABLE_BITS) - 1; + private static final byte[][] RICE_DECODING_CONSUMED_TABLES = new byte[31][1 << RICE_DECODING_TABLE_BITS]; + private static final int[][] RICE_DECODING_VALUE_TABLES = new int[31][1 << RICE_DECODING_TABLE_BITS]; + private static final int RICE_DECODING_CHUNK = 4; // Configurable, must be positive, and RICE_DECODING_CHUNK * RICE_DECODING_TABLE_BITS <= 64 + + static { + for (int param = 0; param < RICE_DECODING_CONSUMED_TABLES.length; param++) { + byte[] consumed = RICE_DECODING_CONSUMED_TABLES[param]; + int[] values = RICE_DECODING_VALUE_TABLES[param]; + for (int i = 0; ; i++) { + int numBits = (i >>> param) + 1 + param; + if (numBits > RICE_DECODING_TABLE_BITS) + break; + int bits = ((1 << param) | (i & ((1 << param) - 1))); + int shift = RICE_DECODING_TABLE_BITS - numBits; + for (int j = 0; j < (1 << shift); j++) { + consumed[(bits << shift) | j] = (byte)numBits; + values[(bits << shift) | j] = (i >>> 1) ^ -(i & 1); + } + } + if (consumed[0] != 0) + throw new AssertionError(); + } + } + + + // For CRC calculations + + private static byte[] CRC8_TABLE = new byte[256]; + private static char[] CRC16_TABLE = new char[256]; + + static { + for (int i = 0; i < CRC8_TABLE.length; i++) { + int temp8 = i; + int temp16 = i << 8; + for (int j = 0; j < 8; j++) { + temp8 = (temp8 << 1) ^ ((temp8 >>> 7) * 0x107); + temp16 = (temp16 << 1) ^ ((temp16 >>> 15) * 0x18005); + } + CRC8_TABLE[i] = (byte)temp8; + CRC16_TABLE[i] = (char)temp16; + } + } + +} diff --git a/desktop/src/io/nayuki/flac/decode/ByteArrayFlacInput.java b/desktop/src/io/nayuki/flac/decode/ByteArrayFlacInput.java new file mode 100644 index 0000000..2e99809 --- /dev/null +++ b/desktop/src/io/nayuki/flac/decode/ByteArrayFlacInput.java @@ -0,0 +1,86 @@ +/* + * FLAC library (Java) + * + * Copyright (c) Project Nayuki + * https://www.nayuki.io/page/flac-library-java + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program (see COPYING.txt and COPYING.LESSER.txt). + * If not, see . + */ + +package io.nayuki.flac.decode; + +import java.io.IOException; +import java.util.Objects; + + +/** + * A FLAC input stream based on a fixed byte array. + */ +public final class ByteArrayFlacInput extends AbstractFlacLowLevelInput { + + /*---- Fields ----*/ + + // The underlying byte array to read from. + private byte[] data; + private int offset; + + + + /*---- Constructors ----*/ + + public ByteArrayFlacInput(byte[] b) { + super(); + data = Objects.requireNonNull(b); + offset = 0; + } + + + + /*---- Methods ----*/ + + public long getLength() { + return data.length; + } + + + public void seekTo(long pos) { + offset = (int)pos; + positionChanged(pos); + } + + + protected int readUnderlying(byte[] buf, int off, int len) { + if (off < 0 || off > buf.length || len < 0 || len > buf.length - off) + throw new ArrayIndexOutOfBoundsException(); + int n = Math.min(data.length - offset, len); + if (n == 0) + return -1; + System.arraycopy(data, offset, buf, off, n); + offset += n; + return n; + } + + + // Discards data buffers and invalidates this stream. Because this class and its superclass + // only use memory and have no native resources, it's okay to simply let a ByteArrayFlacInput + // be garbage-collected without calling close(). + public void close() throws IOException { + if (data != null) { + data = null; + super.close(); + } + } + +} diff --git a/desktop/src/io/nayuki/flac/decode/DataFormatException.java b/desktop/src/io/nayuki/flac/decode/DataFormatException.java new file mode 100644 index 0000000..80e1f51 --- /dev/null +++ b/desktop/src/io/nayuki/flac/decode/DataFormatException.java @@ -0,0 +1,47 @@ +/* + * FLAC library (Java) + * + * Copyright (c) Project Nayuki + * https://www.nayuki.io/page/flac-library-java + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program (see COPYING.txt and COPYING.LESSER.txt). + * If not, see . + */ + +package io.nayuki.flac.decode; + + +/** + * Thrown when data being read violates the FLAC file format. + */ +@SuppressWarnings("serial") +public class DataFormatException extends RuntimeException { + + /*---- Constructors ----*/ + + public DataFormatException() { + super(); + } + + + public DataFormatException(String msg) { + super(msg); + } + + + public DataFormatException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/desktop/src/io/nayuki/flac/decode/FlacDecoder.java b/desktop/src/io/nayuki/flac/decode/FlacDecoder.java new file mode 100644 index 0000000..9a2d7cf --- /dev/null +++ b/desktop/src/io/nayuki/flac/decode/FlacDecoder.java @@ -0,0 +1,337 @@ +/* + * FLAC library (Java) + * + * Copyright (c) Project Nayuki + * https://www.nayuki.io/page/flac-library-java + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program (see COPYING.txt and COPYING.LESSER.txt). + * If not, see . + */ + +package io.nayuki.flac.decode; + +import java.io.File; +import java.io.IOException; +import java.util.Objects; + +import io.nayuki.flac.common.FrameInfo; +import io.nayuki.flac.common.SeekTable; +import io.nayuki.flac.common.StreamInfo; + +/** + * Handles high-level decoding and seeking in FLAC files. Also returns metadata + * blocks. Every object is stateful, not thread-safe, and needs to be closed. + * Sample usage: + * + *

+ * // Create a decoder
+ *FlacDecoder dec = new FlacDecoder(...);
+ *
+ *// Make the decoder process all metadata blocks internally.
+ *// We could capture the returned data for extra processing.
+ *// We must read all metadata before reading audio data.
+ *while (dec.readAndHandleMetadataBlock() != null);
+ *
+ *// Read audio samples starting from beginning
+ *int[][] samples = (...);
+ *dec.readAudioBlock(samples, ...);
+ *dec.readAudioBlock(samples, ...);
+ *dec.readAudioBlock(samples, ...);
+ *
+ *// Seek to some position and continue reading
+ *dec.seekAndReadAudioBlock(..., samples, ...);
+ *dec.readAudioBlock(samples, ...);
+ *dec.readAudioBlock(samples, ...);
+ *
+ *// Close underlying file stream
+ *dec.close();
+ * 
+ * + * @see FrameDecoder + * @see FlacLowLevelInput + */ +public final class FlacDecoder implements AutoCloseable { + + /*---- Fields ----*/ + + public StreamInfo streamInfo; + public SeekTable seekTable; + + private FlacLowLevelInput input; + + private long metadataEndPos; + + private FrameDecoder frameDec; + + /*---- Constructors ----*/ + + // Constructs a new FLAC decoder to read the given file. + // This immediately reads the basic header but not metadata blocks. + public FlacDecoder(final File file) throws IOException { + // Initialize streams + Objects.requireNonNull(file); + this.input = new SeekableFileFlacInput(file); + + // Read basic header + if (this.input.readUint(32) != 0x664C6143) { + throw new DataFormatException("Invalid magic string"); + } + this.metadataEndPos = -1; + } + + // Constructs a new FLAC decoder to read the given file. + // This immediately reads the basic header but not metadata blocks. + public FlacDecoder(final byte[] file) throws IOException { + // Initialize streams + Objects.requireNonNull(file); + this.input = new ByteArrayFlacInput(file); + + // Read basic header + if (this.input.readUint(32) != 0x664C6143) { + throw new DataFormatException("Invalid magic string"); + } + this.metadataEndPos = -1; + } + + /*---- Methods ----*/ + + // Reads, handles, and returns the next metadata block. Returns a pair (Integer + // type, byte[] data) if the + // next metadata block exists, otherwise returns null if the final metadata + // block was previously read. + // In addition to reading and returning data, this method also updates the + // internal state + // of this object to reflect the new data seen, and throws exceptions for + // situations such as + // not starting with a stream info metadata block or encountering duplicates of + // certain blocks. + public Object[] readAndHandleMetadataBlock() throws IOException { + if (this.metadataEndPos != -1) { + return null; // All metadata already consumed + } + + // Read entire block + final boolean last = this.input.readUint(1) != 0; + final int type = this.input.readUint(7); + final int length = this.input.readUint(24); + final byte[] data = new byte[length]; + this.input.readFully(data); + + // Handle recognized block + if (type == 0) { + if (this.streamInfo != null) { + throw new DataFormatException("Duplicate stream info metadata block"); + } + this.streamInfo = new StreamInfo(data); + } + else { + if (this.streamInfo == null) { + throw new DataFormatException("Expected stream info metadata block"); + } + if (type == 3) { + if (this.seekTable != null) { + throw new DataFormatException("Duplicate seek table metadata block"); + } + this.seekTable = new SeekTable(data); + } + } + + if (last) { + this.metadataEndPos = this.input.getPosition(); + this.frameDec = new FrameDecoder(this.input, this.streamInfo.sampleDepth); + } + return new Object[] { type, data }; + } + + // Reads and decodes the next block of audio samples into the given buffer, + // returning the number of samples in the block. The return value is 0 if the + // read + // started at the end of stream, or a number in the range [1, 65536] for a valid + // block. + // All metadata blocks must be read before starting to read audio blocks. + public int readAudioBlock(final int[][] samples, final int off) throws IOException { + if (this.frameDec == null) { + throw new IllegalStateException("Metadata blocks not fully consumed yet"); + } + final FrameInfo frame = this.frameDec.readFrame(samples, off); + if (frame == null) { + return 0; + } + else { + return frame.blockSize; // In the range [1, 65536] + } + } + + // Seeks to the given sample position and reads audio samples into the given + // buffer, + // returning the number of samples filled. If audio data is available then the + // return value + // is at least 1; otherwise 0 is returned to indicate the end of stream. Note + // that the + // sample position can land in the middle of a FLAC block and will still behave + // correctly. + // In theory this method subsumes the functionality of readAudioBlock(), but + // seeking can be + // an expensive operation so readAudioBlock() should be used for ordinary + // contiguous streaming. + public int seekAndReadAudioBlock(final long pos, final int[][] samples, final int off) throws IOException { + if (this.frameDec == null) { + throw new IllegalStateException("Metadata blocks not fully consumed yet"); + } + + long[] sampleAndFilePos = getBestSeekPoint(pos); + if ((pos - sampleAndFilePos[0]) > 300000) { + sampleAndFilePos = seekBySyncAndDecode(pos); + sampleAndFilePos[1] -= this.metadataEndPos; + } + this.input.seekTo(sampleAndFilePos[1] + this.metadataEndPos); + + long curPos = sampleAndFilePos[0]; + final int[][] smpl = new int[this.streamInfo.numChannels][65536]; + while (true) { + final FrameInfo frame = this.frameDec.readFrame(smpl, 0); + if (frame == null) { + return 0; + } + final long nextPos = curPos + frame.blockSize; + if (nextPos > pos) { + for (int ch = 0; ch < smpl.length; ch++) { + System.arraycopy(smpl[ch], (int) (pos - curPos), samples[ch], off, (int) (nextPos - pos)); + } + return (int) (nextPos - pos); + } + curPos = nextPos; + } + } + + private long[] getBestSeekPoint(final long pos) { + long samplePos = 0; + long filePos = 0; + if (this.seekTable != null) { + for (final SeekTable.SeekPoint p : this.seekTable.points) { + if (p.sampleOffset <= pos) { + samplePos = p.sampleOffset; + filePos = p.fileOffset; + } + else { + break; + } + } + } + return new long[] { samplePos, filePos }; + } + + // Returns a pair (sample offset, file position) such sampleOffset <= pos and + // abs(sampleOffset - pos) + // is a relatively small number compared to the total number of samples in the + // audio file. + // This method works by skipping to arbitrary places in the file, finding a sync + // sequence, + // decoding the frame header, examining the audio position stored in the frame, + // and possibly deciding + // to skip to other places and retrying. This changes the state of the input + // streams as a side effect. + // There is a small chance of finding a valid-looking frame header but causing + // erroneous decoding later. + private long[] seekBySyncAndDecode(final long pos) throws IOException { + long start = this.metadataEndPos; + long end = this.input.getLength(); + while ((end - start) > 100000) { // Binary search + final long mid = (start + end) >>> 1; + final long[] offsets = getNextFrameOffsets(mid); + if ((offsets == null) || (offsets[0] > pos)) { + end = mid; + } + else { + start = offsets[1]; + } + } + return getNextFrameOffsets(start); + } + + // Returns a pair (sample offset, file position) describing the next frame found + // starting + // at the given file offset, or null if no frame is found before the end of + // stream. + // This changes the state of the input streams as a side effect. + private long[] getNextFrameOffsets(long filePos) throws IOException { + if ((filePos < this.metadataEndPos) || (filePos > this.input.getLength())) { + throw new IllegalArgumentException("File position out of bounds"); + } + + // Repeatedly search for a sync + while (true) { + this.input.seekTo(filePos); + + // Finite state machine to match the 2-byte sync sequence + int state = 0; + while (true) { + final int b = this.input.readByte(); + if (b == -1) { + return null; + } + else if (b == 0xFF) { + state = 1; + } + else if ((state == 1) && ((b & 0xFE) == 0xF8)) { + break; + } + else { + state = 0; + } + } + + // Sync found, rewind 2 bytes, try to decode frame header + filePos = this.input.getPosition() - 2; + this.input.seekTo(filePos); + try { + final FrameInfo frame = FrameInfo.readFrame(this.input); + return new long[] { getSampleOffset(frame), filePos }; + } + catch (final DataFormatException e) { + // Advance past the sync and search again + filePos += 2; + } + } + } + + // Calculates the sample offset of the given frame, automatically handling the + // constant-block-size case. + private long getSampleOffset(final FrameInfo frame) { + Objects.requireNonNull(frame); + if (frame.sampleOffset != -1) { + return frame.sampleOffset; + } + else if (frame.frameIndex != -1) { + return frame.frameIndex * this.streamInfo.maxBlockSize; + } + else { + throw new AssertionError(); + } + } + + // Closes the underlying input streams and discards object data. + // This decoder object becomes invalid for any method calls or field usages. + @Override + public void close() throws IOException { + if (this.input != null) { + this.streamInfo = null; + this.seekTable = null; + this.frameDec = null; + this.input.close(); + this.input = null; + } + } + +} diff --git a/desktop/src/io/nayuki/flac/decode/FlacLowLevelInput.java b/desktop/src/io/nayuki/flac/decode/FlacLowLevelInput.java new file mode 100644 index 0000000..24c2a80 --- /dev/null +++ b/desktop/src/io/nayuki/flac/decode/FlacLowLevelInput.java @@ -0,0 +1,129 @@ +/* + * FLAC library (Java) + * + * Copyright (c) Project Nayuki + * https://www.nayuki.io/page/flac-library-java + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program (see COPYING.txt and COPYING.LESSER.txt). + * If not, see . + */ + +package io.nayuki.flac.decode; + +import java.io.IOException; + + +/** + * A low-level input stream tailored to the needs of FLAC decoding. An overview of methods includes + * bit reading, CRC calculation, Rice decoding, and positioning and seeking (partly optional). + * @see SeekableFileFlacInput + * @see FrameDecoder + */ +public interface FlacLowLevelInput extends AutoCloseable { + + /*---- Stream position ----*/ + + // Returns the total number of bytes in the FLAC file represented by this input stream. + // This number should not change for the lifetime of this object. Implementing this is optional; + // it's intended to support blind seeking without the use of seek tables, such as binary searching + // the whole file. A class may choose to throw UnsupportedOperationException instead, + // such as for a non-seekable network input stream of unknown length. + public long getLength(); + + + // Returns the current byte position in the stream, a non-negative value. + // This increments after every 8 bits read, and a partially read byte is treated as unread. + // This value is 0 initially, is set directly by seekTo(), and potentially increases + // after every call to a read*() method. Other methods do not affect this value. + public long getPosition(); + + + // Returns the current number of consumed bits in the current byte. This starts at 0, + // increments for each bit consumed, maxes out at 7, then resets to 0 and repeats. + public int getBitPosition(); + + + // Changes the position of the next read to the given byte offset from the start of the stream. + // This also resets CRCs and sets the bit position to 0. + // Implementing this is optional; it is intended to support playback seeking. + // A class may choose to throw UnsupportedOperationException instead. + public void seekTo(long pos) throws IOException; + + + + /*---- Reading bitwise integers ----*/ + + // Reads the next given number of bits (0 <= n <= 32) as an unsigned integer (i.e. zero-extended to int32). + // However in the case of n = 32, the result will be a signed integer that represents a uint32. + public int readUint(int n) throws IOException; + + + // Reads the next given number of bits (0 <= n <= 32) as an signed integer (i.e. sign-extended to int32). + public int readSignedInt(int n) throws IOException; + + + // Reads and decodes the next batch of Rice-coded signed integers. Note that any Rice-coded integer might read a large + // number of bits from the underlying stream (but not in practice because it would be a very inefficient encoding). + // Every new value stored into the array is guaranteed to fit into a signed int53 - see FrameDecoder.restoreLpc() + // for an explanation of why this is a necessary (but not sufficient) bound on the range of decoded values. + public void readRiceSignedInts(int param, long[] result, int start, int end) throws IOException; + + + + /*---- Reading bytes ----*/ + + // Returns the next unsigned byte value (in the range [0, 255]) or -1 for EOF. + // Must be called at a byte boundary (i.e. getBitPosition() == 0), otherwise IllegalStateException is thrown. + public int readByte() throws IOException; + + + // Discards any partial bits, then reads the given array fully or throws EOFException. + // Must be called at a byte boundary (i.e. getBitPosition() == 0), otherwise IllegalStateException is thrown. + public void readFully(byte[] b) throws IOException; + + + + /*---- CRC calculations ----*/ + + // Marks the current byte position as the start of both CRC calculations. + // The effect of resetCrcs() is implied at the beginning of stream and when seekTo() is called. + // Must be called at a byte boundary (i.e. getBitPosition() == 0), otherwise IllegalStateException is thrown. + public void resetCrcs(); + + + // Returns the CRC-8 hash of all the bytes read since the most recent time one of these + // events occurred: a call to resetCrcs(), a call to seekTo(), the beginning of stream. + // Must be called at a byte boundary (i.e. getBitPosition() == 0), otherwise IllegalStateException is thrown. + public int getCrc8(); + + + // Returns the CRC-16 hash of all the bytes read since the most recent time one of these + // events occurred: a call to resetCrcs(), a call to seekTo(), the beginning of stream. + // Must be called at a byte boundary (i.e. getBitPosition() == 0), otherwise IllegalStateException is thrown. + public int getCrc16(); + + + + /*---- Miscellaneous ----*/ + + // Closes underlying objects / native resources, and possibly discards memory buffers. + // Generally speaking, this operation invalidates this input stream, so calling methods + // (other than close()) or accessing fields thereafter should be forbidden. + // The close() method must be idempotent and safe when called more than once. + // If an implementation does not have native or time-sensitive resources, it is okay for the class user + // to skip calling close() and simply let the object be garbage-collected. But out of good habit, it is + // recommended to always close a FlacLowLevelInput stream so that the logic works correctly on all types. + public void close() throws IOException; + +} diff --git a/desktop/src/io/nayuki/flac/decode/FrameDecoder.java b/desktop/src/io/nayuki/flac/decode/FrameDecoder.java new file mode 100644 index 0000000..7d1d12d --- /dev/null +++ b/desktop/src/io/nayuki/flac/decode/FrameDecoder.java @@ -0,0 +1,387 @@ +/* + * FLAC library (Java) + * + * Copyright (c) Project Nayuki + * https://www.nayuki.io/page/flac-library-java + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program (see COPYING.txt and COPYING.LESSER.txt). + * If not, see . + */ + +package io.nayuki.flac.decode; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Objects; +import io.nayuki.flac.common.FrameInfo; + + +/** + * Decodes a FLAC frame from an input stream into raw audio samples. Note that these objects are + * stateful and not thread-safe, due to the bit input stream field, private temporary arrays, etc. + *

This class only uses memory and has no native resources; however, the + * code that uses this class is responsible for cleaning up the input stream.

+ * @see FlacDecoder + * @see FlacLowLevelInput + */ +public final class FrameDecoder { + + /*---- Fields ----*/ + + // Can be changed when there is no active call of readFrame(). + // Must be not null when readFrame() is called. + public FlacLowLevelInput in; + + // Can be changed when there is no active call of readFrame(). + // Must be in the range [4, 32]. + public int expectedSampleDepth; + + // Temporary arrays to hold two decoded audio channels (a.k.a. subframes). They have int64 range + // because the worst case of 32-bit audio encoded in stereo side mode uses signed 33 bits. + // The maximum possible block size is either 65536 samples per channel from the + // frame header logic, or 65535 from a strict reading of the FLAC specification. + // Two buffers are needed for stereo coding modes, but not more than two because + // all other multi-channel audio is processed independently per channel. + private long[] temp0; + private long[] temp1; + + // The number of samples (per channel) in the current block/frame being processed. + // This value is only valid while the method readFrame() is on the call stack. + // When readFrame() is active, this value is in the range [1, 65536]. + private int currentBlockSize; + + + + /*---- Constructors ----*/ + + // Constructs a frame decoder that initially uses the given stream. + // The caller is responsible for cleaning up the input stream. + public FrameDecoder(FlacLowLevelInput in, int expectDepth) { + this.in = in; + expectedSampleDepth = expectDepth; + temp0 = new long[65536]; + temp1 = new long[65536]; + currentBlockSize = -1; + } + + + + /*---- Methods ----*/ + + // Reads the next frame of FLAC data from the current bit input stream, decodes it, + // and stores output samples into the given array, and returns a new metadata object. + // The bit input stream must be initially aligned at a byte boundary. If EOF is encountered before + // any actual bytes were read, then this returns null. Otherwise this function either successfully + // decodes a frame and returns a new metadata object, or throws an appropriate exception. A frame + // may have up to 8 channels and 65536 samples, so the output arrays need to be sized appropriately. + public FrameInfo readFrame(int[][] outSamples, int outOffset) throws IOException { + // Check field states + Objects.requireNonNull(in); + if (currentBlockSize != -1) + throw new IllegalStateException("Concurrent call"); + + // Parse the frame header to see if one is available + long startByte = in.getPosition(); + FrameInfo meta = FrameInfo.readFrame(in); + if (meta == null) // EOF occurred cleanly + return null; + if (meta.sampleDepth != -1 && meta.sampleDepth != expectedSampleDepth) + throw new DataFormatException("Sample depth mismatch"); + + // Check arguments and read frame header + currentBlockSize = meta.blockSize; + Objects.requireNonNull(outSamples); + if (outOffset < 0 || outOffset > outSamples[0].length) + throw new IndexOutOfBoundsException(); + if (outSamples.length < meta.numChannels) + throw new IllegalArgumentException("Output array too small for number of channels"); + if (outOffset > outSamples[0].length - currentBlockSize) + throw new IndexOutOfBoundsException(); + + // Do the hard work + decodeSubframes(expectedSampleDepth, meta.channelAssignment, outSamples, outOffset); + + // Read padding and footer + if (in.readUint((8 - in.getBitPosition()) % 8) != 0) + throw new DataFormatException("Invalid padding bits"); + int computedCrc16 = in.getCrc16(); + if (in.readUint(16) != computedCrc16) + throw new DataFormatException("CRC-16 mismatch"); + + // Handle frame size and miscellaneous + long frameSize = in.getPosition() - startByte; + if (frameSize < 10) + throw new AssertionError(); + if ((int)frameSize != frameSize) + throw new DataFormatException("Frame size too large"); + meta.frameSize = (int)frameSize; + currentBlockSize = -1; + return meta; + } + + + // Based on the current bit input stream and the two given arguments, this method reads and decodes + // each subframe, performs stereo decoding if applicable, and writes the final uncompressed audio data + // to the array range outSamples[0 : numChannels][outOffset : outOffset + currentBlockSize]. + // Note that this method uses the private temporary arrays and passes them into sub-method calls. + private void decodeSubframes(int sampleDepth, int chanAsgn, int[][] outSamples, int outOffset) throws IOException { + // Check arguments + if (sampleDepth < 1 || sampleDepth > 32) + throw new IllegalArgumentException(); + if ((chanAsgn >>> 4) != 0) + throw new IllegalArgumentException(); + + if (0 <= chanAsgn && chanAsgn <= 7) { + // Handle 1 to 8 independently coded channels + int numChannels = chanAsgn + 1; + for (int ch = 0; ch < numChannels; ch++) { + decodeSubframe(sampleDepth, temp0); + int[] outChan = outSamples[ch]; + for (int i = 0; i < currentBlockSize; i++) + outChan[outOffset + i] = checkBitDepth(temp0[i], sampleDepth); + } + + } else if (8 <= chanAsgn && chanAsgn <= 10) { + // Handle one of the side-coded stereo methods + decodeSubframe(sampleDepth + (chanAsgn == 9 ? 1 : 0), temp0); + decodeSubframe(sampleDepth + (chanAsgn == 9 ? 0 : 1), temp1); + + if (chanAsgn == 8) { // Left-side stereo + for (int i = 0; i < currentBlockSize; i++) + temp1[i] = temp0[i] - temp1[i]; + } else if (chanAsgn == 9) { // Side-right stereo + for (int i = 0; i < currentBlockSize; i++) + temp0[i] += temp1[i]; + } else if (chanAsgn == 10) { // Mid-side stereo + for (int i = 0; i < currentBlockSize; i++) { + long side = temp1[i]; + long right = temp0[i] - (side >> 1); + temp1[i] = right; + temp0[i] = right + side; + } + } else + throw new AssertionError(); + + // Copy data from temporary to output arrays, and convert from long to int + int[] outLeft = outSamples[0]; + int[] outRight = outSamples[1]; + for (int i = 0; i < currentBlockSize; i++) { + outLeft [outOffset + i] = checkBitDepth(temp0[i], sampleDepth); + outRight[outOffset + i] = checkBitDepth(temp1[i], sampleDepth); + } + } else // 11 <= channelAssignment <= 15 + throw new DataFormatException("Reserved channel assignment"); + } + + + // Checks that 'val' is a signed 'depth'-bit integer, and either returns the + // value downcasted to an int or throws an exception if it's out of range. + // Note that depth must be in the range [1, 32] because the return value is an int. + // For example when depth = 16, the range of valid values is [-32768, 32767]. + private static int checkBitDepth(long val, int depth) { + assert 1 <= depth && depth <= 32; + // Equivalent check: (val >> (depth - 1)) == 0 || (val >> (depth - 1)) == -1 + if (val >> (depth - 1) == val >> depth) + return (int)val; + else + throw new IllegalArgumentException(val + " is not a signed " + depth + "-bit value"); + } + + + // Reads one subframe from the bit input stream, decodes it, and writes to result[0 : currentBlockSize]. + private void decodeSubframe(int sampleDepth, long[] result) throws IOException { + // Check arguments + Objects.requireNonNull(result); + if (sampleDepth < 1 || sampleDepth > 33) + throw new IllegalArgumentException(); + if (result.length < currentBlockSize) + throw new IllegalArgumentException(); + + // Read header fields + if (in.readUint(1) != 0) + throw new DataFormatException("Invalid padding bit"); + int type = in.readUint(6); + int shift = in.readUint(1); // Also known as "wasted bits-per-sample" + if (shift == 1) { + while (in.readUint(1) == 0) { // Unary coding + if (shift >= sampleDepth) + throw new DataFormatException("Waste-bits-per-sample exceeds sample depth"); + shift++; + } + } + if (!(0 <= shift && shift <= sampleDepth)) + throw new AssertionError(); + sampleDepth -= shift; + + // Read sample data based on type + if (type == 0) // Constant coding + Arrays.fill(result, 0, currentBlockSize, in.readSignedInt(sampleDepth)); + else if (type == 1) { // Verbatim coding + for (int i = 0; i < currentBlockSize; i++) + result[i] = in.readSignedInt(sampleDepth); + } else if (8 <= type && type <= 12) + decodeFixedPredictionSubframe(type - 8, sampleDepth, result); + else if (32 <= type && type <= 63) + decodeLinearPredictiveCodingSubframe(type - 31, sampleDepth, result); + else + throw new DataFormatException("Reserved subframe type"); + + // Add trailing zeros to each sample + if (shift > 0) { + for (int i = 0; i < currentBlockSize; i++) + result[i] <<= shift; + } + } + + + // Reads from the input stream, performs computation, and writes to result[0 : currentBlockSize]. + private void decodeFixedPredictionSubframe(int predOrder, int sampleDepth, long[] result) throws IOException { + // Check arguments + Objects.requireNonNull(result); + if (sampleDepth < 1 || sampleDepth > 33) + throw new IllegalArgumentException(); + if (predOrder < 0 || predOrder >= FIXED_PREDICTION_COEFFICIENTS.length) + throw new IllegalArgumentException(); + if (predOrder > currentBlockSize) + throw new DataFormatException("Fixed prediction order exceeds block size"); + if (result.length < currentBlockSize) + throw new IllegalArgumentException(); + + // Read and compute various values + for (int i = 0; i < predOrder; i++) // Non-Rice-coded warm-up samples + result[i] = in.readSignedInt(sampleDepth); + readResiduals(predOrder, result); + restoreLpc(result, FIXED_PREDICTION_COEFFICIENTS[predOrder], sampleDepth, 0); + } + + private static final int[][] FIXED_PREDICTION_COEFFICIENTS = { + {}, + {1}, + {2, -1}, + {3, -3, 1}, + {4, -6, 4, -1}, + }; + + + // Reads from the input stream, performs computation, and writes to result[0 : currentBlockSize]. + private void decodeLinearPredictiveCodingSubframe(int lpcOrder, int sampleDepth, long[] result) throws IOException { + // Check arguments + Objects.requireNonNull(result); + if (sampleDepth < 1 || sampleDepth > 33) + throw new IllegalArgumentException(); + if (lpcOrder < 1 || lpcOrder > 32) + throw new IllegalArgumentException(); + if (lpcOrder > currentBlockSize) + throw new DataFormatException("LPC order exceeds block size"); + if (result.length < currentBlockSize) + throw new IllegalArgumentException(); + + // Read non-Rice-coded warm-up samples + for (int i = 0; i < lpcOrder; i++) + result[i] = in.readSignedInt(sampleDepth); + + // Read parameters for the LPC coefficients + int precision = in.readUint(4) + 1; + if (precision == 16) + throw new DataFormatException("Invalid LPC precision"); + int shift = in.readSignedInt(5); + if (shift < 0) + throw new DataFormatException("Invalid LPC shift"); + + // Read the coefficients themselves + int[] coefs = new int[lpcOrder]; + for (int i = 0; i < coefs.length; i++) + coefs[i] = in.readSignedInt(precision); + + // Perform the main LPC decoding + readResiduals(lpcOrder, result); + restoreLpc(result, coefs, sampleDepth, shift); + } + + + // Updates the values of result[coefs.length : currentBlockSize] according to linear predictive coding. + // This method reads all the arguments and the field currentBlockSize, only writes to result, and has no other side effects. + // After this method returns, every value in result must fit in a signed sampleDepth-bit integer. + // The largest allowed sample depth is 33, hence the largest absolute value allowed in the result is 2^32. + // During the LPC restoration process, the prefix of result before index i consists of entirely int33 values. + // Because coefs.length <= 32 and each coefficient fits in a signed int15 (both according to the FLAC specification), + // the maximum (worst-case) absolute value of 'sum' is 2^32 * 2^14 * 32 = 2^51, which fits in a signed int53. + // And because of this, the maximum possible absolute value of a residual before LPC restoration is applied, + // such that the post-LPC result fits in a signed int33, is 2^51 + 2^32 which also fits in a signed int53. + // Therefore a residue that is larger than a signed int53 will necessarily not fit in the int33 result and is wrong. + private void restoreLpc(long[] result, int[] coefs, int sampleDepth, int shift) { + // Check and handle arguments + Objects.requireNonNull(result); + Objects.requireNonNull(coefs); + if (result.length < currentBlockSize) + throw new IllegalArgumentException(); + if (sampleDepth < 1 || sampleDepth > 33) + throw new IllegalArgumentException(); + if (shift < 0 || shift > 63) + throw new IllegalArgumentException(); + long lowerBound = (-1) << (sampleDepth - 1); + long upperBound = -(lowerBound + 1); + + for (int i = coefs.length; i < currentBlockSize; i++) { + long sum = 0; + for (int j = 0; j < coefs.length; j++) + sum += result[i - 1 - j] * coefs[j]; + assert (sum >> 53) == 0 || (sum >> 53) == -1; // Fits in signed int54 + sum = result[i] + (sum >> shift); + // Check that sum fits in a sampleDepth-bit signed integer, + // i.e. -(2^(sampleDepth-1)) <= sum < 2^(sampleDepth-1) + if (sum < lowerBound || sum > upperBound) + throw new DataFormatException("Post-LPC result exceeds bit depth"); + result[i] = sum; + } + } + + + // Reads metadata and Rice-coded numbers from the input stream, storing them in result[warmup : currentBlockSize]. + // The stored numbers are guaranteed to fit in a signed int53 - see the explanation in restoreLpc(). + private void readResiduals(int warmup, long[] result) throws IOException { + // Check and handle arguments + Objects.requireNonNull(result); + if (warmup < 0 || warmup > currentBlockSize) + throw new IllegalArgumentException(); + if (result.length < currentBlockSize) + throw new IllegalArgumentException(); + + int method = in.readUint(2); + if (method >= 2) + throw new DataFormatException("Reserved residual coding method"); + assert method == 0 || method == 1; + int paramBits = method == 0 ? 4 : 5; + int escapeParam = method == 0 ? 0xF : 0x1F; + + int partitionOrder = in.readUint(4); + int numPartitions = 1 << partitionOrder; + if (currentBlockSize % numPartitions != 0) + throw new DataFormatException("Block size not divisible by number of Rice partitions"); + for (int inc = currentBlockSize >>> partitionOrder, partEnd = inc, resultIndex = warmup; + partEnd <= currentBlockSize; partEnd += inc) { + + int param = in.readUint(paramBits); + if (param == escapeParam) { + int numBits = in.readUint(5); + for (; resultIndex < partEnd; resultIndex++) + result[resultIndex] = in.readSignedInt(numBits); + } else { + in.readRiceSignedInts(param, result, resultIndex, partEnd); + resultIndex = partEnd; + } + } + } + +} diff --git a/desktop/src/io/nayuki/flac/decode/SeekableFileFlacInput.java b/desktop/src/io/nayuki/flac/decode/SeekableFileFlacInput.java new file mode 100644 index 0000000..f446ff0 --- /dev/null +++ b/desktop/src/io/nayuki/flac/decode/SeekableFileFlacInput.java @@ -0,0 +1,83 @@ +/* + * FLAC library (Java) + * + * Copyright (c) Project Nayuki + * https://www.nayuki.io/page/flac-library-java + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program (see COPYING.txt and COPYING.LESSER.txt). + * If not, see . + */ + +package io.nayuki.flac.decode; + +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.util.Objects; + + +/** + * A FLAC input stream based on a {@link RandomAccessFile}. + */ +public final class SeekableFileFlacInput extends AbstractFlacLowLevelInput { + + /*---- Fields ----*/ + + // The underlying byte-based input stream to read from. + private RandomAccessFile raf; + + + + /*---- Constructors ----*/ + + public SeekableFileFlacInput(File file) throws IOException { + super(); + Objects.requireNonNull(file); + this.raf = new RandomAccessFile(file, "r"); + } + + + + /*---- Methods ----*/ + + public long getLength() { + try { + return raf.length(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + + public void seekTo(long pos) throws IOException { + raf.seek(pos); + positionChanged(pos); + } + + + protected int readUnderlying(byte[] buf, int off, int len) throws IOException { + return raf.read(buf, off, len); + } + + + // Closes the underlying RandomAccessFile stream (very important). + public void close() throws IOException { + if (raf != null) { + raf.close(); + raf = null; + super.close(); + } + } + +} diff --git a/desktop/src/io/nayuki/flac/encode/AdvancedFlacEncoder.java b/desktop/src/io/nayuki/flac/encode/AdvancedFlacEncoder.java new file mode 100644 index 0000000..17004ec --- /dev/null +++ b/desktop/src/io/nayuki/flac/encode/AdvancedFlacEncoder.java @@ -0,0 +1,115 @@ +/* + * FLAC library (Java) + * + * Copyright (c) Project Nayuki + * https://www.nayuki.io/page/flac-library-java + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program (see COPYING.txt and COPYING.LESSER.txt). + * If not, see . + */ + +package io.nayuki.flac.encode; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import io.nayuki.flac.common.StreamInfo; + + +public final class AdvancedFlacEncoder { + + public AdvancedFlacEncoder(StreamInfo info, int[][] samples, int baseSize, int[] sizeMultiples, SubframeEncoder.SearchOptions opts, BitOutputStream out) throws IOException { + int numSamples = samples[0].length; + + // Calculate compressed sizes for many block positions and sizes + @SuppressWarnings("unchecked") + SizeEstimate[][] encoderInfo = new SizeEstimate[sizeMultiples.length][(numSamples + baseSize - 1) / baseSize]; + long startTime = System.currentTimeMillis(); + for (int i = 0; i < encoderInfo[0].length; i++) { + double progress = (double)i / encoderInfo[0].length; + double timeRemain = (System.currentTimeMillis() - startTime) / 1000.0 / progress * (1 - progress); + System.err.printf("\rprogress=%.2f%% timeRemain=%ds", progress * 100, Math.round(timeRemain)); + + int pos = i * baseSize; + for (int j = 0; j < encoderInfo.length; j++) { + int n = Math.min(sizeMultiples[j] * baseSize, numSamples - pos); + long[][] subsamples = getRange(samples, pos, n); + encoderInfo[j][i] = FrameEncoder.computeBest(pos, subsamples, info.sampleDepth, info.sampleRate, opts); + } + } + System.err.println(); + + // Initialize arrays to prepare for dynamic programming + FrameEncoder[] bestEncoders = new FrameEncoder[encoderInfo[0].length]; + long[] bestSizes = new long[bestEncoders.length]; + Arrays.fill(bestSizes, Long.MAX_VALUE); + + // Use dynamic programming to calculate optimum block size switching + for (int i = 0; i < encoderInfo.length; i++) { + for (int j = bestSizes.length - 1; j >= 0; j--) { + long size = encoderInfo[i][j].sizeEstimate; + if (j + sizeMultiples[i] < bestSizes.length) + size += bestSizes[j + sizeMultiples[i]]; + if (size < bestSizes[j]) { + bestSizes[j] = size; + bestEncoders[j] = encoderInfo[i][j].encoder; + } + } + } + + // Do the actual encoding and writing + info.minBlockSize = 0; + info.maxBlockSize = 0; + info.minFrameSize = 0; + info.maxFrameSize = 0; + List blockSizes = new ArrayList<>(); + for (int i = 0; i < bestEncoders.length; ) { + FrameEncoder enc = bestEncoders[i]; + int pos = i * baseSize; + int n = Math.min(enc.metadata.blockSize, numSamples - pos); + blockSizes.add(n); + if (info.minBlockSize == 0 || n < info.minBlockSize) + info.minBlockSize = Math.max(n, 16); + info.maxBlockSize = Math.max(n, info.maxBlockSize); + + long[][] subsamples = getRange(samples, pos, n); + long startByte = out.getByteCount(); + bestEncoders[i].encode(subsamples, out); + i += (n + baseSize - 1) / baseSize; + + long frameSize = out.getByteCount() - startByte; + if (frameSize < 0 || (int)frameSize != frameSize) + throw new AssertionError(); + if (info.minFrameSize == 0 || frameSize < info.minFrameSize) + info.minFrameSize = (int)frameSize; + if (frameSize > info.maxFrameSize) + info.maxFrameSize = (int)frameSize; + } + } + + + // Returns the subrange array[ : ][off : off + len] upcasted to long. + private static long[][] getRange(int[][] array, int off, int len) { + long[][] result = new long[array.length][len]; + for (int i = 0; i < array.length; i++) { + int[] src = array[i]; + long[] dest = result[i]; + for (int j = 0; j < len; j++) + dest[j] = src[off + j]; + } + return result; + } + +} diff --git a/desktop/src/io/nayuki/flac/encode/BitOutputStream.java b/desktop/src/io/nayuki/flac/encode/BitOutputStream.java new file mode 100644 index 0000000..b7a2dc5 --- /dev/null +++ b/desktop/src/io/nayuki/flac/encode/BitOutputStream.java @@ -0,0 +1,174 @@ +/* + * FLAC library (Java) + * + * Copyright (c) Project Nayuki + * https://www.nayuki.io/page/flac-library-java + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program (see COPYING.txt and COPYING.LESSER.txt). + * If not, see . + */ + +package io.nayuki.flac.encode; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Objects; + + +/* + * A bit-oriented output stream, with other methods tailored for FLAC usage (such as CRC calculation). + */ +public final class BitOutputStream implements AutoCloseable { + + /*---- Fields ----*/ + + private OutputStream out; // The underlying byte-based output stream to write to. + private long bitBuffer; // Only the bottom bitBufferLen bits are valid; the top bits are garbage. + private int bitBufferLen; // Always in the range [0, 64]. + private long byteCount; // Number of bytes written since the start of stream. + + // Current state of the CRC calculations. + private int crc8; // Always a uint8 value. + private int crc16; // Always a uint16 value. + + + + /*---- Constructors ----*/ + + // Constructs a FLAC-oriented bit output stream from the given byte-based output stream. + public BitOutputStream(OutputStream out) throws IOException { + this.out = Objects.requireNonNull(out); + bitBuffer = 0; + bitBufferLen = 0; + byteCount = 0; + resetCrcs(); + } + + + + /*---- Methods ----*/ + + /*-- Bit position --*/ + + // Writes between 0 to 7 zero bits, to align the current bit position to a byte boundary. + public void alignToByte() throws IOException { + writeInt((64 - bitBufferLen) % 8, 0); + } + + + // Either returns silently or throws an exception. + private void checkByteAligned() { + if (bitBufferLen % 8 != 0) + throw new IllegalStateException("Not at a byte boundary"); + } + + + /*-- Writing bitwise integers --*/ + + // Writes the lowest n bits of the given value to this bit output stream. + // This doesn't care whether val represents a signed or unsigned integer. + public void writeInt(int n, int val) throws IOException { + if (n < 0 || n > 32) + throw new IllegalArgumentException(); + + if (bitBufferLen + n > 64) { + flush(); + assert bitBufferLen + n <= 64; + } + bitBuffer <<= n; + bitBuffer |= val & ((1L << n) - 1); + bitBufferLen += n; + assert 0 <= bitBufferLen && bitBufferLen <= 64; + } + + + // Writes out whole bytes from the bit buffer to the underlying stream. After this is done, + // only 0 to 7 bits remain in the bit buffer. Also updates the CRCs on each byte written. + public void flush() throws IOException { + while (bitBufferLen >= 8) { + bitBufferLen -= 8; + int b = (int)(bitBuffer >>> bitBufferLen) & 0xFF; + out.write(b); + byteCount++; + crc8 ^= b; + crc16 ^= b << 8; + for (int i = 0; i < 8; i++) { + crc8 <<= 1; + crc16 <<= 1; + crc8 ^= (crc8 >>> 8) * 0x107; + crc16 ^= (crc16 >>> 16) * 0x18005; + assert (crc8 >>> 8) == 0; + assert (crc16 >>> 16) == 0; + } + } + assert 0 <= bitBufferLen && bitBufferLen <= 64; + out.flush(); + } + + + /*-- CRC calculations --*/ + + // Marks the current position (which must be byte-aligned) as the start of both CRC calculations. + public void resetCrcs() throws IOException { + flush(); + crc8 = 0; + crc16 = 0; + } + + + // Returns the CRC-8 hash of all the bytes written since the last call to resetCrcs() + // (or from the beginning of stream if reset was never called). + public int getCrc8() throws IOException { + checkByteAligned(); + flush(); + if ((crc8 >>> 8) != 0) + throw new AssertionError(); + return crc8; + } + + + // Returns the CRC-16 hash of all the bytes written since the last call to resetCrcs() + // (or from the beginning of stream if reset was never called). + public int getCrc16() throws IOException { + checkByteAligned(); + flush(); + if ((crc16 >>> 16) != 0) + throw new AssertionError(); + return crc16; + } + + + /*-- Miscellaneous --*/ + + // Returns the number of bytes written since the start of the stream. + public long getByteCount() { + return byteCount + bitBufferLen / 8; + } + + + // Writes out any internally buffered bit data, closes the underlying output stream, and invalidates this + // bit output stream object for any future operation. Note that a BitOutputStream only uses memory but + // does not have native resources. It is okay to flush() the pending data and simply let a BitOutputStream + // be garbage collected without calling close(), but the parent is still responsible for calling close() + // on the underlying output stream if it uses native resources (such as FileOutputStream or SocketOutputStream). + public void close() throws IOException { + if (out != null) { + checkByteAligned(); + flush(); + out.close(); + out = null; + } + } + +} diff --git a/desktop/src/io/nayuki/flac/encode/ConstantEncoder.java b/desktop/src/io/nayuki/flac/encode/ConstantEncoder.java new file mode 100644 index 0000000..ddaa707 --- /dev/null +++ b/desktop/src/io/nayuki/flac/encode/ConstantEncoder.java @@ -0,0 +1,79 @@ +/* + * FLAC library (Java) + * + * Copyright (c) Project Nayuki + * https://www.nayuki.io/page/flac-library-java + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program (see COPYING.txt and COPYING.LESSER.txt). + * If not, see . + */ + +package io.nayuki.flac.encode; + +import java.io.IOException; + + +/* + * Under the constant coding mode, this provides size calculations on and bitstream encoding of audio sample data. + * Note that unlike the other subframe encoders which are fully general, not all data can be encoded using constant mode. + * The encoded size depends on the shift and bit depth, but not on the data length or contents. + */ +final class ConstantEncoder extends SubframeEncoder { + + // Computes the best way to encode the given values under the constant coding mode, + // returning an exact size plus a new encoder object associated with the input arguments. + // However if the sample data is non-constant then null is returned instead, + // to indicate that the data is impossible to represent in this mode. + public static SizeEstimate computeBest(long[] samples, int shift, int depth) { + if (!isConstant(samples)) + return null; + ConstantEncoder enc = new ConstantEncoder(samples, shift, depth); + long size = 1 + 6 + 1 + shift + depth; + return new SizeEstimate(size, enc); + } + + + // Constructs a constant encoder for the given data, right shift, and sample depth. + public ConstantEncoder(long[] samples, int shift, int depth) { + super(shift, depth); + } + + + // Encodes the given vector of audio sample data to the given bit output stream using + // the this encoding method (and the superclass fields sampleShift and sampleDepth). + // This requires the data array to have the same values (but not necessarily + // the same object reference) as the array that was passed to the constructor. + public void encode(long[] samples, BitOutputStream out) throws IOException { + if (!isConstant(samples)) + throw new IllegalArgumentException("Data is not constant-valued"); + if ((samples[0] >> sampleShift) << sampleShift != samples[0]) + throw new IllegalArgumentException("Invalid shift value for data"); + writeTypeAndShift(0, out); + writeRawSample(samples[0] >> sampleShift, out); + } + + + // Returns true iff the set of unique values in the array has size exactly 1. Pure function. + private static boolean isConstant(long[] data) { + if (data.length == 0) + return false; + long val = data[0]; + for (long x : data) { + if (x != val) + return false; + } + return true; + } + +} diff --git a/desktop/src/io/nayuki/flac/encode/FastDotProduct.java b/desktop/src/io/nayuki/flac/encode/FastDotProduct.java new file mode 100644 index 0000000..aaa4305 --- /dev/null +++ b/desktop/src/io/nayuki/flac/encode/FastDotProduct.java @@ -0,0 +1,97 @@ +/* + * FLAC library (Java) + * + * Copyright (c) Project Nayuki + * https://www.nayuki.io/page/flac-library-java + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program (see COPYING.txt and COPYING.LESSER.txt). + * If not, see . + */ + +package io.nayuki.flac.encode; + +import java.util.Objects; + + +/* + * Speeds up computations of a signal vector's autocorrelation by avoiding redundant + * arithmetic operations. Acts as a helper class for LinearPredictiveEncoder. + * Objects of this class are intended to be immutable, but can't enforce it because + * they store a reference to a caller-controlled array without making a private copy. + */ +final class FastDotProduct { + + /*---- Fields ----*/ + + // Not null, and precomputed.length <= data.length. + private long[] data; + + // precomputed[i] = dotProduct(0, i, data.length - i). In other words, it is the sum + // the of products of all unordered pairs of elements whose indices differ by i. + private double[] precomputed; + + + + /*---- Constructors ----*/ + + // Constructs a fast dot product calculator over the given array, with the given maximum difference in indexes. + // This pre-calculates some dot products and caches them so that future queries can be answered faster. + // To avoid the cost of copying the entire vector, a reference to the array is saved into this object. + // The values from data array are still needed when dotProduct() is called, thus no other code is allowed to modify the values. + public FastDotProduct(long[] data, int maxDelta) { + // Check arguments + this.data = Objects.requireNonNull(data); + if (maxDelta < 0 || maxDelta >= data.length) + throw new IllegalArgumentException(); + + // Precompute some dot products + precomputed = new double[maxDelta + 1]; + for (int i = 0; i < precomputed.length; i++) { + double sum = 0; + for (int j = 0; i + j < data.length; j++) + sum += (double)data[j] * data[i + j]; + precomputed[i] = sum; + } + } + + + + /*---- Methods ----*/ + + // Returns the dot product of data[off0 : off0 + len] with data[off1 : off1 + len], + // i.e. data[off0]*data[off1] + data[off0+1]*data[off1+1] + ... + data[off0+len-1]*data[off1+len-1], + // with potential rounding error. Note that all the endpoints must lie within the bounds + // of the data array. Also, this method requires abs(off0 - off1) <= maxDelta. + public double dotProduct(int off0, int off1, int len) { + if (off0 > off1) // Symmetric case + return dotProduct(off1, off0, len); + + // Check arguments + if (off0 < 0 || off1 < 0 || len < 0 || data.length - len < off1) + throw new IndexOutOfBoundsException(); + assert off0 <= off1; + int delta = off1 - off0; + if (delta > precomputed.length) + throw new IllegalArgumentException(); + + // Add up a small number of products to remove from the precomputed sum + double removal = 0; + for (int i = 0; i < off0; i++) + removal += (double)data[i] * data[i + delta]; + for (int i = off1 + len; i < data.length; i++) + removal += (double)data[i] * data[i - delta]; + return precomputed[delta] - removal; + } + +} diff --git a/desktop/src/io/nayuki/flac/encode/FixedPredictionEncoder.java b/desktop/src/io/nayuki/flac/encode/FixedPredictionEncoder.java new file mode 100644 index 0000000..498ad39 --- /dev/null +++ b/desktop/src/io/nayuki/flac/encode/FixedPredictionEncoder.java @@ -0,0 +1,85 @@ +/* + * FLAC library (Java) + * + * Copyright (c) Project Nayuki + * https://www.nayuki.io/page/flac-library-java + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program (see COPYING.txt and COPYING.LESSER.txt). + * If not, see . + */ + +package io.nayuki.flac.encode; + +import java.io.IOException; +import java.util.Objects; + + +/* + * Under the fixed prediction coding mode of some order, this provides size calculations on and bitstream encoding of audio sample data. + */ +final class FixedPredictionEncoder extends SubframeEncoder { + + // Computes the best way to encode the given values under the fixed prediction coding mode of the given order, + // returning a size plus a new encoder object associated with the input arguments. The maxRiceOrder argument + // is used by the Rice encoder to estimate the size of coding the residual signal. + public static SizeEstimate computeBest(long[] samples, int shift, int depth, int order, int maxRiceOrder) { + FixedPredictionEncoder enc = new FixedPredictionEncoder(samples, shift, depth, order); + samples = LinearPredictiveEncoder.shiftRight(samples, shift); + LinearPredictiveEncoder.applyLpc(samples, COEFFICIENTS[order], 0); + long temp = RiceEncoder.computeBestSizeAndOrder(samples, order, maxRiceOrder); + enc.riceOrder = (int)(temp & 0xF); + long size = 1 + 6 + 1 + shift + order * depth + (temp >>> 4); + return new SizeEstimate(size, enc); + } + + + + private final int order; + public int riceOrder; + + + public FixedPredictionEncoder(long[] samples, int shift, int depth, int order) { + super(shift, depth); + if (order < 0 || order >= COEFFICIENTS.length || samples.length < order) + throw new IllegalArgumentException(); + this.order = order; + } + + + public void encode(long[] samples, BitOutputStream out) throws IOException { + Objects.requireNonNull(samples); + Objects.requireNonNull(out); + if (samples.length < order) + throw new IllegalArgumentException(); + + writeTypeAndShift(8 + order, out); + samples = LinearPredictiveEncoder.shiftRight(samples, sampleShift); + + for (int i = 0; i < order; i++) // Warmup + writeRawSample(samples[i], out); + LinearPredictiveEncoder.applyLpc(samples, COEFFICIENTS[order], 0); + RiceEncoder.encode(samples, order, riceOrder, out); + } + + + // The linear predictive coding (LPC) coefficients for fixed prediction of orders 0 to 4 (inclusive). + private static final int[][] COEFFICIENTS = { + {}, + {1}, + {2, -1}, + {3, -3, 1}, + {4, -6, 4, -1}, + }; + +} diff --git a/desktop/src/io/nayuki/flac/encode/FlacEncoder.java b/desktop/src/io/nayuki/flac/encode/FlacEncoder.java new file mode 100644 index 0000000..d9e9c66 --- /dev/null +++ b/desktop/src/io/nayuki/flac/encode/FlacEncoder.java @@ -0,0 +1,67 @@ +/* + * FLAC library (Java) + * + * Copyright (c) Project Nayuki + * https://www.nayuki.io/page/flac-library-java + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program (see COPYING.txt and COPYING.LESSER.txt). + * If not, see . + */ + +package io.nayuki.flac.encode; + +import java.io.IOException; +import io.nayuki.flac.common.StreamInfo; + + +public final class FlacEncoder { + + public FlacEncoder(StreamInfo info, int[][] samples, int blockSize, SubframeEncoder.SearchOptions opt, BitOutputStream out) throws IOException { + info.minBlockSize = blockSize; + info.maxBlockSize = blockSize; + info.minFrameSize = 0; + info.maxFrameSize = 0; + + for (int i = 0, pos = 0; pos < samples[0].length; i++) { + System.err.printf("frame=%d position=%d %.2f%%%n", i, pos, 100.0 * pos / samples[0].length); + int n = Math.min(samples[0].length - pos, blockSize); + long[][] subsamples = getRange(samples, pos, n); + FrameEncoder enc = FrameEncoder.computeBest(pos, subsamples, info.sampleDepth, info.sampleRate, opt).encoder; + long startByte = out.getByteCount(); + enc.encode(subsamples, out); + long frameSize = out.getByteCount() - startByte; + if (frameSize < 0 || (int)frameSize != frameSize) + throw new AssertionError(); + if (info.minFrameSize == 0 || frameSize < info.minFrameSize) + info.minFrameSize = (int)frameSize; + if (frameSize > info.maxFrameSize) + info.maxFrameSize = (int)frameSize; + pos += n; + } + } + + + // Returns the subrange array[ : ][off : off + len] upcasted to long. + private static long[][] getRange(int[][] array, int off, int len) { + long[][] result = new long[array.length][len]; + for (int i = 0; i < array.length; i++) { + int[] src = array[i]; + long[] dest = result[i]; + for (int j = 0; j < len; j++) + dest[j] = src[off + j]; + } + return result; + } + +} diff --git a/desktop/src/io/nayuki/flac/encode/FrameEncoder.java b/desktop/src/io/nayuki/flac/encode/FrameEncoder.java new file mode 100644 index 0000000..6d1cf18 --- /dev/null +++ b/desktop/src/io/nayuki/flac/encode/FrameEncoder.java @@ -0,0 +1,174 @@ +/* + * FLAC library (Java) + * + * Copyright (c) Project Nayuki + * https://www.nayuki.io/page/flac-library-java + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program (see COPYING.txt and COPYING.LESSER.txt). + * If not, see . + */ + +package io.nayuki.flac.encode; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Objects; +import io.nayuki.flac.common.FrameInfo; + + +/* + * Calculates/estimates the encoded size of a frame of audio sample data + * (including the frame header), and also performs the encoding to an output stream. + */ +final class FrameEncoder { + + /*---- Static functions ----*/ + + public static SizeEstimate computeBest(int sampleOffset, long[][] samples, int sampleDepth, int sampleRate, SubframeEncoder.SearchOptions opt) { + FrameEncoder enc = new FrameEncoder(sampleOffset, samples, sampleDepth, sampleRate); + int numChannels = samples.length; + @SuppressWarnings("unchecked") + SizeEstimate[] encoderInfo = new SizeEstimate[numChannels]; + if (numChannels != 2) { + enc.metadata.channelAssignment = numChannels - 1; + for (int i = 0; i < encoderInfo.length; i++) + encoderInfo[i] = SubframeEncoder.computeBest(samples[i], sampleDepth, opt); + } else { // Explore the 4 stereo encoding modes + long[] left = samples[0]; + long[] right = samples[1]; + long[] mid = new long[samples[0].length]; + long[] side = new long[mid.length]; + for (int i = 0; i < mid.length; i++) { + mid[i] = (left[i] + right[i]) >> 1; + side[i] = left[i] - right[i]; + } + SizeEstimate leftInfo = SubframeEncoder.computeBest(left , sampleDepth, opt); + SizeEstimate rightInfo = SubframeEncoder.computeBest(right, sampleDepth, opt); + SizeEstimate midInfo = SubframeEncoder.computeBest(mid , sampleDepth, opt); + SizeEstimate sideInfo = SubframeEncoder.computeBest(side , sampleDepth + 1, opt); + long mode1Size = leftInfo.sizeEstimate + rightInfo.sizeEstimate; + long mode8Size = leftInfo.sizeEstimate + sideInfo.sizeEstimate; + long mode9Size = rightInfo.sizeEstimate + sideInfo.sizeEstimate; + long mode10Size = midInfo.sizeEstimate + sideInfo.sizeEstimate; + long minimum = Math.min(Math.min(mode1Size, mode8Size), Math.min(mode9Size, mode10Size)); + if (mode1Size == minimum) { + enc.metadata.channelAssignment = 1; + encoderInfo[0] = leftInfo; + encoderInfo[1] = rightInfo; + } else if (mode8Size == minimum) { + enc.metadata.channelAssignment = 8; + encoderInfo[0] = leftInfo; + encoderInfo[1] = sideInfo; + } else if (mode9Size == minimum) { + enc.metadata.channelAssignment = 9; + encoderInfo[0] = sideInfo; + encoderInfo[1] = rightInfo; + } else if (mode10Size == minimum) { + enc.metadata.channelAssignment = 10; + encoderInfo[0] = midInfo; + encoderInfo[1] = sideInfo; + } else + throw new AssertionError(); + } + + // Add up subframe sizes + long size = 0; + enc.subEncoders = new SubframeEncoder[encoderInfo.length]; + for (int i = 0; i < enc.subEncoders.length; i++) { + size += encoderInfo[i].sizeEstimate; + enc.subEncoders[i] = encoderInfo[i].encoder; + } + + // Count length of header (always in whole bytes) + try { + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + try (BitOutputStream bitout = new BitOutputStream(bout)) { + enc.metadata.writeHeader(bitout); + } + bout.close(); + size += bout.toByteArray().length * 8; + } catch (IOException e) { + throw new AssertionError(e); + } + + // Count padding and footer + size = (size + 7) / 8; // Round up to nearest byte + size += 2; // CRC-16 + return new SizeEstimate<>(size, enc); + } + + + + /*---- Fields ----*/ + + public FrameInfo metadata; + private SubframeEncoder[] subEncoders; + + + + /*---- Constructors ----*/ + + public FrameEncoder(int sampleOffset, long[][] samples, int sampleDepth, int sampleRate) { + metadata = new FrameInfo(); + metadata.sampleOffset = sampleOffset; + metadata.sampleDepth = sampleDepth; + metadata.sampleRate = sampleRate; + metadata.blockSize = samples[0].length; + metadata.channelAssignment = samples.length - 1; + } + + + + /*---- Public methods ----*/ + + public void encode(long[][] samples, BitOutputStream out) throws IOException { + // Check arguments + Objects.requireNonNull(samples); + Objects.requireNonNull(out); + if (samples[0].length != metadata.blockSize) + throw new IllegalArgumentException(); + + metadata.writeHeader(out); + + int chanAsgn = metadata.channelAssignment; + if (0 <= chanAsgn && chanAsgn <= 7) { + for (int i = 0; i < samples.length; i++) + subEncoders[i].encode(samples[i], out); + } else if (8 <= chanAsgn || chanAsgn <= 10) { + long[] left = samples[0]; + long[] right = samples[1]; + long[] mid = new long[metadata.blockSize]; + long[] side = new long[metadata.blockSize]; + for (int i = 0; i < metadata.blockSize; i++) { + mid[i] = (left[i] + right[i]) >> 1; + side[i] = left[i] - right[i]; + } + if (chanAsgn == 8) { + subEncoders[0].encode(left, out); + subEncoders[1].encode(side, out); + } else if (chanAsgn == 9) { + subEncoders[0].encode(side, out); + subEncoders[1].encode(right, out); + } else if (chanAsgn == 10) { + subEncoders[0].encode(mid, out); + subEncoders[1].encode(side, out); + } else + throw new AssertionError(); + } else + throw new AssertionError(); + out.alignToByte(); + out.writeInt(16, out.getCrc16()); + } + +} diff --git a/desktop/src/io/nayuki/flac/encode/LinearPredictiveEncoder.java b/desktop/src/io/nayuki/flac/encode/LinearPredictiveEncoder.java new file mode 100644 index 0000000..9ac1130 --- /dev/null +++ b/desktop/src/io/nayuki/flac/encode/LinearPredictiveEncoder.java @@ -0,0 +1,277 @@ +/* + * FLAC library (Java) + * + * Copyright (c) Project Nayuki + * https://www.nayuki.io/page/flac-library-java + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program (see COPYING.txt and COPYING.LESSER.txt). + * If not, see . + */ + +package io.nayuki.flac.encode; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Objects; + + +/* + * Under the linear predictive coding (LPC) mode of some order, this provides size estimates on and bitstream encoding of audio sample data. + */ +final class LinearPredictiveEncoder extends SubframeEncoder { + + // Computes a good way to encode the given values under the linear predictive coding (LPC) mode of the given order, + // returning a size plus a new encoder object associated with the input arguments. This process of minimizing the size + // has an enormous search space, and it is impossible to guarantee the absolute optimal solution. The maxRiceOrder argument + // is used by the Rice encoder to estimate the size of coding the residual signal. The roundVars argument controls + // how many different coefficients are tested rounding both up and down, resulting in exponential time behavior. + public static SizeEstimate computeBest(long[] samples, int shift, int depth, int order, int roundVars, FastDotProduct fdp, int maxRiceOrder) { + // Check arguments + if (order < 1 || order > 32) + throw new IllegalArgumentException(); + if (roundVars < 0 || roundVars > order || roundVars > 30) + throw new IllegalArgumentException(); + + LinearPredictiveEncoder enc = new LinearPredictiveEncoder(samples, shift, depth, order, fdp); + samples = shiftRight(samples, shift); + + final double[] residues; + Integer[] indices = null; + int scaler = 1 << enc.coefShift; + if (roundVars > 0) { + residues = new double[order]; + indices = new Integer[order]; + for (int i = 0; i < order; i++) { + residues[i] = Math.abs(Math.round(enc.realCoefs[i] * scaler) - enc.realCoefs[i] * scaler); + indices[i] = i; + } + Arrays.sort(indices, new Comparator() { + public int compare(Integer x, Integer y) { + return Double.compare(residues[y], residues[x]); + } + }); + } else + residues = null; + + long bestSize = Long.MAX_VALUE; + int[] bestCoefs = enc.coefficients.clone(); + for (int i = 0; i < (1 << roundVars); i++) { + for (int j = 0; j < roundVars; j++) { + int k = indices[j]; + double coef = enc.realCoefs[k]; + int val; + if (((i >>> j) & 1) == 0) + val = (int)Math.floor(coef * scaler); + else + val = (int)Math.ceil(coef * scaler); + enc.coefficients[order - 1 - k] = Math.max(Math.min(val, (1 << (enc.coefDepth - 1)) - 1), -(1 << (enc.coefDepth - 1))); + } + + long[] newData = roundVars > 0 ? samples.clone() : samples; + applyLpc(newData, enc.coefficients, enc.coefShift); + long temp = RiceEncoder.computeBestSizeAndOrder(newData, order, maxRiceOrder); + long size = 1 + 6 + 1 + shift + order * depth + (temp >>> 4); + if (size < bestSize) { + bestSize = size; + bestCoefs = enc.coefficients.clone(); + enc.riceOrder = (int)(temp & 0xF); + } + } + enc.coefficients = bestCoefs; + return new SizeEstimate(bestSize, enc); + } + + + + private final int order; + private final double[] realCoefs; + private int[] coefficients; + private final int coefDepth; + private final int coefShift; + public int riceOrder; + + + public LinearPredictiveEncoder(long[] samples, int shift, int depth, int order, FastDotProduct fdp) { + super(shift, depth); + int numSamples = samples.length; + if (order < 1 || order > 32 || numSamples < order) + throw new IllegalArgumentException(); + this.order = order; + + // Set up matrix to solve linear least squares problem + double[][] matrix = new double[order][order + 1]; + for (int r = 0; r < matrix.length; r++) { + for (int c = 0; c < matrix[r].length; c++) { + double val; + if (c >= r) + val = fdp.dotProduct(r, c, samples.length - order); + else + val = matrix[c][r]; + matrix[r][c] = val; + } + } + + // Solve matrix, then examine range of coefficients + realCoefs = solveMatrix(matrix); + double maxCoef = 0; + for (double x : realCoefs) + maxCoef = Math.max(Math.abs(x), maxCoef); + int wholeBits = maxCoef >= 1 ? (int)(Math.log(maxCoef) / Math.log(2)) + 1 : 0; + + // Quantize and store the coefficients + coefficients = new int[order]; + coefDepth = 15; // The maximum possible + coefShift = coefDepth - 1 - wholeBits; + for (int i = 0; i < realCoefs.length; i++) { + double coef = realCoefs[realCoefs.length - 1 - i]; + int val = (int)Math.round(coef * (1 << coefShift)); + coefficients[i] = Math.max(Math.min(val, (1 << (coefDepth - 1)) - 1), -(1 << (coefDepth - 1))); + } + } + + + // Solves an n * (n+1) augmented matrix (which modifies its values as a side effect), + // returning a new solution vector of length n. + private static double[] solveMatrix(double[][] mat) { + // Gauss-Jordan elimination algorithm + int rows = mat.length; + int cols = mat[0].length; + if (rows + 1 != cols) + throw new IllegalArgumentException(); + + // Forward elimination + int numPivots = 0; + for (int j = 0; j < rows && numPivots < rows; j++) { + int pivotRow = rows; + double pivotMag = 0; + for (int i = numPivots; i < rows; i++) { + if (Math.abs(mat[i][j]) > pivotMag) { + pivotMag = Math.abs(mat[i][j]); + pivotRow = i; + } + } + if (pivotRow == rows) + continue; + + double[] temp = mat[numPivots]; + mat[numPivots] = mat[pivotRow]; + mat[pivotRow] = temp; + pivotRow = numPivots; + numPivots++; + + double factor = mat[pivotRow][j]; + for (int k = 0; k < cols; k++) + mat[pivotRow][k] /= factor; + mat[pivotRow][j] = 1; + + for (int i = pivotRow + 1; i < rows; i++) { + factor = mat[i][j]; + for (int k = 0; k < cols; k++) + mat[i][k] -= mat[pivotRow][k] * factor; + mat[i][j] = 0; + } + } + + // Back substitution + double[] result = new double[rows]; + for (int i = numPivots - 1; i >= 0; i--) { + int pivotCol = 0; + while (pivotCol < cols && mat[i][pivotCol] == 0) + pivotCol++; + if (pivotCol == cols) + continue; + result[pivotCol] = mat[i][cols - 1]; + + for (int j = i - 1; j >= 0; j--) { + double factor = mat[j][pivotCol]; + for (int k = 0; k < cols; k++) + mat[j][k] -= mat[i][k] * factor; + mat[j][pivotCol] = 0; + } + } + return result; + } + + + public void encode(long[] samples, BitOutputStream out) throws IOException { + Objects.requireNonNull(samples); + Objects.requireNonNull(out); + if (samples.length < order) + throw new IllegalArgumentException(); + + writeTypeAndShift(32 + order - 1, out); + samples = shiftRight(samples, sampleShift); + + for (int i = 0; i < order; i++) // Warmup + writeRawSample(samples[i], out); + out.writeInt(4, coefDepth - 1); + out.writeInt(5, coefShift); + for (int x : coefficients) + out.writeInt(coefDepth, x); + applyLpc(samples, coefficients, coefShift); + RiceEncoder.encode(samples, order, riceOrder, out); + } + + + + /*---- Static helper functions ----*/ + + // Applies linear prediction to data[coefs.length : data.length] so that newdata[i] = + // data[i] - ((data[i-1]*coefs[0] + data[i-2]*coefs[1] + ... + data[i-coefs.length]*coefs[coefs.length]) >> shift). + // By FLAC parameters, each data[i] must fit in a signed 33-bit integer, each coef must fit in signed int15, and coefs.length <= 32. + // When these preconditions are met, they guarantee the lack of arithmetic overflow in the computation and results, + // and each value written back to the data array fits in a signed int53. + static void applyLpc(long[] data, int[] coefs, int shift) { + // Check arguments and arrays strictly + Objects.requireNonNull(data); + Objects.requireNonNull(coefs); + if (coefs.length > 32 || shift < 0 || shift > 63) + throw new IllegalArgumentException(); + for (long x : data) { + x >>= 32; + if (x != 0 && x != -1) // Check if it fits in signed int33 + throw new IllegalArgumentException(); + } + for (int x : coefs) { + x >>= 14; + if (x != 0 && x != -1) // Check if it fits in signed int15 + throw new IllegalArgumentException(); + } + + // Perform the LPC convolution/FIR + for (int i = data.length - 1; i >= coefs.length; i--) { + long sum = 0; + for (int j = 0; j < coefs.length; j++) + sum += data[i - 1 - j] * coefs[j]; + long val = data[i] - (sum >> shift); + if ((val >> 52) != 0 && (val >> 52) != -1) // Check if it fits in signed int53 + throw new AssertionError(); + data[i] = val; + } + } + + + // Returns a new array where each result[i] = data[i] >> shift. + static long[] shiftRight(long[] data, int shift) { + Objects.requireNonNull(data); + if (shift < 0 || shift > 63) + throw new IllegalArgumentException(); + long[] result = new long[data.length]; + for (int i = 0; i < data.length; i++) + result[i] = data[i] >> shift; + return result; + } + +} diff --git a/desktop/src/io/nayuki/flac/encode/RandomAccessFileOutputStream.java b/desktop/src/io/nayuki/flac/encode/RandomAccessFileOutputStream.java new file mode 100644 index 0000000..ac54c64 --- /dev/null +++ b/desktop/src/io/nayuki/flac/encode/RandomAccessFileOutputStream.java @@ -0,0 +1,81 @@ +/* + * FLAC library (Java) + * + * Copyright (c) Project Nayuki + * https://www.nayuki.io/page/flac-library-java + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program (see COPYING.txt and COPYING.LESSER.txt). + * If not, see . + */ + +package io.nayuki.flac.encode; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.RandomAccessFile; +import java.util.Objects; + + +/* + * An adapter from RandomAccessFile to OutputStream. These objects have no buffer, so seek() + * and write() can be safely interleaved. Also, objects of this class have no direct + * native resources - so it is safe to discard a RandomAccessFileOutputStream object without + * closing it, as long as other code will close() the underlying RandomAccessFile object. + */ +public final class RandomAccessFileOutputStream extends OutputStream { + + /*---- Fields ----*/ + + private RandomAccessFile out; + + + + /*---- Constructors ----*/ + + public RandomAccessFileOutputStream(RandomAccessFile raf) { + this.out = Objects.requireNonNull(raf); + } + + + + /*---- Methods ----*/ + + public long getPosition() throws IOException { + return out.getFilePointer(); + } + + + public void seek(long pos) throws IOException { + out.seek(pos); + } + + + public void write(int b) throws IOException { + out.write(b); + } + + + public void write(byte[] b, int off, int len) throws IOException { + out.write(b, off, len); + } + + + public void close() throws IOException { + if (out != null) { + out.close(); + out = null; + } + } + +} diff --git a/desktop/src/io/nayuki/flac/encode/RiceEncoder.java b/desktop/src/io/nayuki/flac/encode/RiceEncoder.java new file mode 100644 index 0000000..be6a291 --- /dev/null +++ b/desktop/src/io/nayuki/flac/encode/RiceEncoder.java @@ -0,0 +1,216 @@ +/* + * FLAC library (Java) + * + * Copyright (c) Project Nayuki + * https://www.nayuki.io/page/flac-library-java + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program (see COPYING.txt and COPYING.LESSER.txt). + * If not, see . + */ + +package io.nayuki.flac.encode; + +import java.io.IOException; +import java.util.Objects; + + +/* + * Calculates/estimates the encoded size of a vector of residuals, and also performs the encoding to an output stream. + */ +final class RiceEncoder { + + /*---- Functions for size calculation ---*/ + + // Calculates the best number of bits and partition order needed to encode the values data[warmup : data.length]. + // Each value in that subrange of data must fit in a signed 53-bit integer. The result is packed in the form + // ((bestSize << 4) | bestOrder), where bestSize is an unsigned integer and bestOrder is a uint4. + // Note that the partition orders searched, and hence the resulting bestOrder, are in the range [0, maxPartOrder]. + public static long computeBestSizeAndOrder(long[] data, int warmup, int maxPartOrder) { + // Check arguments strictly + Objects.requireNonNull(data); + if (warmup < 0 || warmup > data.length) + throw new IllegalArgumentException(); + if (maxPartOrder < 0 || maxPartOrder > 15) + throw new IllegalArgumentException(); + for (long x : data) { + x >>= 52; + if (x != 0 && x != -1) // Check that it fits in a signed int53 + throw new IllegalArgumentException(); + } + + long bestSize = Integer.MAX_VALUE; + int bestOrder = -1; + + int[] escapeBits = null; + int[] bitsAtParam = null; + for (int order = maxPartOrder; order >= 0; order--) { + int partSize = data.length >>> order; + if ((partSize << order) != data.length || partSize < warmup) + continue; + int numPartitions = 1 << order; + + if (escapeBits == null) { // And bitsAtParam == null + escapeBits = new int[numPartitions]; + bitsAtParam = new int[numPartitions * 16]; + for (int i = warmup; i < data.length; i++) { + int j = i / partSize; + long val = data[i]; + escapeBits[j] = Math.max(65 - Long.numberOfLeadingZeros(val ^ (val >> 63)), escapeBits[j]); + val = (val >= 0) ? (val << 1) : (((-val) << 1) - 1); + for (int param = 0; param < 15; param++, val >>>= 1) + bitsAtParam[param + j * 16] += val + 1 + param; + } + } else { // Both arrays are non-null + // Logically halve the size of both arrays (but without reallocating to the true new size) + for (int i = 0; i < numPartitions; i++) { + int j = i << 1; + escapeBits[i] = Math.max(escapeBits[j], escapeBits[j + 1]); + for (int param = 0; param < 15; param++) + bitsAtParam[param + i * 16] = bitsAtParam[param + j * 16] + bitsAtParam[param + (j + 1) * 16]; + } + } + + long size = 4 + (4 << order); + for (int i = 0; i < numPartitions; i++) { + int min = Integer.MAX_VALUE; + if (escapeBits[i] <= 31) + min = 5 + escapeBits[i] * (partSize - (i == 0 ? warmup : 0)); + for (int param = 0; param < 15; param++) + min = Math.min(bitsAtParam[param + i * 16], min); + size += min; + } + if (size < bestSize) { + bestSize = size; + bestOrder = order; + } + } + + if (bestSize == Integer.MAX_VALUE || (bestOrder >>> 4) != 0) + throw new AssertionError(); + return bestSize << 4 | bestOrder; + } + + + // Calculates the number of bits needed to encode the sequence of values + // data[start : end] with an optimally chosen Rice parameter. + private static long computeBestSizeAndParam(long[] data, int start, int end) { + assert data != null && 0 <= start && start <= end && end <= data.length; + + // Use escape code + int bestParam; + long bestSize; + { + long accumulator = 0; + for (int i = start; i < end; i++) { + long val = data[i]; + accumulator |= val ^ (val >> 63); + } + int numBits = 65 - Long.numberOfLeadingZeros(accumulator); + assert 1 <= numBits && numBits <= 65; + if (numBits <= 31) { + bestSize = 4 + 5 + (end - start) * numBits; + bestParam = 16 + numBits; + if ((bestParam >>> 6) != 0) + throw new AssertionError(); + } else { + bestSize = Long.MAX_VALUE; + bestParam = 0; + } + } + + // Use Rice coding + for (int param = 0; param <= 14; param++) { + long size = 4; + for (int i = start; i < end; i++) { + long val = data[i]; + if (val >= 0) + val <<= 1; + else + val = ((-val) << 1) - 1; + size += (val >>> param) + 1 + param; + } + if (size < bestSize) { + bestSize = size; + bestParam = param; + } + } + return bestSize << 6 | bestParam; + } + + + + /*---- Functions for encoding data ---*/ + + // Encodes the sequence of values data[warmup : data.length] with an appropriately chosen order and Rice parameters. + // Each value in data must fit in a signed 53-bit integer. + public static void encode(long[] data, int warmup, int order, BitOutputStream out) throws IOException { + // Check arguments strictly + Objects.requireNonNull(data); + Objects.requireNonNull(out); + if (warmup < 0 || warmup > data.length) + throw new IllegalArgumentException(); + if (order < 0 || order > 15) + throw new IllegalArgumentException(); + for (long x : data) { + x >>= 52; + if (x != 0 && x != -1) // Check that it fits in a signed int53 + throw new IllegalArgumentException(); + } + + out.writeInt(2, 0); + out.writeInt(4, order); + int numPartitions = 1 << order; + int start = warmup; + int end = data.length >>> order; + for (int i = 0; i < numPartitions; i++) { + int param = (int)computeBestSizeAndParam(data, start, end) & 0x3F; + encode(data, start, end, param, out); + start = end; + end += data.length >>> order; + } + } + + + // Encodes the sequence of values data[start : end] with the given Rice parameter. + private static void encode(long[] data, int start, int end, int param, BitOutputStream out) throws IOException { + assert 0 <= param && param <= 31 && data != null && out != null; + assert 0 <= start && start <= end && end <= data.length; + + if (param < 15) { + out.writeInt(4, param); + for (int j = start; j < end; j++) + writeRiceSignedInt(data[j], param, out); + } else { + out.writeInt(4, 15); + int numBits = param - 16; + out.writeInt(5, numBits); + for (int j = start; j < end; j++) + out.writeInt(numBits, (int)data[j]); + } + } + + + private static void writeRiceSignedInt(long val, int param, BitOutputStream out) throws IOException { + assert 0 <= param && param <= 31 && out != null; + assert (val >> 52) == 0 || (val >> 52) == -1; // Fits in a signed int53 + + long unsigned = val >= 0 ? val << 1 : ((-val) << 1) - 1; + int unary = (int)(unsigned >>> param); + for (int i = 0; i < unary; i++) + out.writeInt(1, 0); + out.writeInt(1, 1); + out.writeInt(param, (int)unsigned); + } + +} diff --git a/desktop/src/io/nayuki/flac/encode/SizeEstimate.java b/desktop/src/io/nayuki/flac/encode/SizeEstimate.java new file mode 100644 index 0000000..80258d2 --- /dev/null +++ b/desktop/src/io/nayuki/flac/encode/SizeEstimate.java @@ -0,0 +1,61 @@ +/* + * FLAC library (Java) + * + * Copyright (c) Project Nayuki + * https://www.nayuki.io/page/flac-library-java + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program (see COPYING.txt and COPYING.LESSER.txt). + * If not, see . + */ + +package io.nayuki.flac.encode; + +import java.util.Objects; + + +/* + * Pairs an integer with an arbitrary object. Immutable structure. + */ +final class SizeEstimate { + + /*---- Fields ----*/ + + public final long sizeEstimate; // Non-negative + public final E encoder; // Not null + + + + /*---- Constructors ----*/ + + public SizeEstimate(long size, E enc) { + if (size < 0) + throw new IllegalArgumentException(); + sizeEstimate = size; + encoder = Objects.requireNonNull(enc); + } + + + + /*---- Methods ----*/ + + // Returns this object if the size is less than or equal to the other object, otherwise returns other. + public SizeEstimate minimum(SizeEstimate other) { + Objects.requireNonNull(other); + if (sizeEstimate <= other.sizeEstimate) + return this; + else + return other; + } + +} diff --git a/desktop/src/io/nayuki/flac/encode/SubframeEncoder.java b/desktop/src/io/nayuki/flac/encode/SubframeEncoder.java new file mode 100644 index 0000000..09ae8f0 --- /dev/null +++ b/desktop/src/io/nayuki/flac/encode/SubframeEncoder.java @@ -0,0 +1,247 @@ +/* + * FLAC library (Java) + * + * Copyright (c) Project Nayuki + * https://www.nayuki.io/page/flac-library-java + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program (see COPYING.txt and COPYING.LESSER.txt). + * If not, see . + */ + +package io.nayuki.flac.encode; + +import java.io.IOException; +import java.util.Objects; + + +/* + * Calculates/estimates the encoded size of a subframe of audio sample data, and also performs the encoding to an output stream. + */ +public abstract class SubframeEncoder { + + /*---- Static functions ----*/ + + // Computes/estimates the best way to encode the given vector of audio sample data at the given sample depth under + // the given search criteria, returning a size estimate plus a new encoder object associated with that size. + public static SizeEstimate computeBest(long[] samples, int sampleDepth, SearchOptions opt) { + // Check arguments + Objects.requireNonNull(samples); + if (sampleDepth < 1 || sampleDepth > 33) + throw new IllegalArgumentException(); + Objects.requireNonNull(opt); + for (long x : samples) { + x >>= sampleDepth - 1; + if (x != 0 && x != -1) // Check that the input actually fits the indicated sample depth + throw new IllegalArgumentException(); + } + + // Encode with constant if possible + SizeEstimate result = ConstantEncoder.computeBest(samples, 0, sampleDepth); + if (result != null) + return result; + + // Detect number of trailing zero bits + int shift = computeWastedBits(samples); + + // Start with verbatim as fallback + result = VerbatimEncoder.computeBest(samples, shift, sampleDepth); + + // Try fixed prediction encoding + for (int order = opt.minFixedOrder; 0 <= order && order <= opt.maxFixedOrder; order++) { + SizeEstimate temp = FixedPredictionEncoder.computeBest( + samples, shift, sampleDepth, order, opt.maxRiceOrder); + result = result.minimum(temp); + } + + // Try linear predictive coding + FastDotProduct fdp = new FastDotProduct(samples, Math.max(opt.maxLpcOrder, 0)); + for (int order = opt.minLpcOrder; 0 <= order && order <= opt.maxLpcOrder; order++) { + SizeEstimate temp = LinearPredictiveEncoder.computeBest( + samples, shift, sampleDepth, order, Math.min(opt.lpcRoundVariables, order), fdp, opt.maxRiceOrder); + result = result.minimum(temp); + } + + // Return the encoder found with the lowest bit length + return result; + } + + + // Looks at each value in the array and computes the minimum number of trailing binary zeros + // among all the elements. For example, computedwastedBits({0b10, 0b10010, 0b1100}) = 1. + // If there are no elements or every value is zero (the former actually implies the latter), then + // the return value is 0. This is because every zero value has an infinite number of trailing zeros. + private static int computeWastedBits(long[] data) { + Objects.requireNonNull(data); + long accumulator = 0; + for (long x : data) + accumulator |= x; + if (accumulator == 0) + return 0; + else { + int result = Long.numberOfTrailingZeros(accumulator); + assert 0 <= result && result <= 63; + return result; + } + } + + + + /*---- Instance members ----*/ + + protected final int sampleShift; // Number of bits to shift each sample right by. In the range [0, sampleDepth]. + protected final int sampleDepth; // Stipulate that each audio sample fits in a signed integer of this width. In the range [1, 33]. + + + // Constructs a subframe encoder on some data array with the given right shift (wasted bits) and sample depth. + // Note that every element of the array must fit in a signed depth-bit integer and have at least 'shift' trailing binary zeros. + // After the encoder object is created and when encode() is called, it must receive the same array length and values (but the object reference can be different). + // Subframe encoders should not retain a reference to the sample data array because the higher-level encoder may request and + // keep many size estimates coupled with encoder objects, but only utilize a small number of encoder objects in the end. + protected SubframeEncoder(int shift, int depth) { + if (depth < 1 || depth > 33 || shift < 0 || shift > depth) + throw new IllegalArgumentException(); + sampleShift = shift; + sampleDepth = depth; + } + + + // Encodes the given vector of audio sample data to the given bit output stream + // using the current encoding method (dictated by subclasses and field values). + // This requires the data array to have the same values (but not necessarily be the same object reference) + // as the array that was passed to the constructor when this encoder object was created. + public abstract void encode(long[] samples, BitOutputStream out) throws IOException; + + + // Writes the subframe header to the given output stream, based on the given + // type code (uint6) and this object's sampleShift field (a.k.a. wasted bits per sample). + protected final void writeTypeAndShift(int type, BitOutputStream out) throws IOException { + // Check arguments + if ((type >>> 6) != 0) + throw new IllegalArgumentException(); + Objects.requireNonNull(out); + + // Write some fields + out.writeInt(1, 0); + out.writeInt(6, type); + + // Write shift value in quasi-unary + if (sampleShift == 0) + out.writeInt(1, 0); + else { + out.writeInt(1, 1); + for (int i = 0; i < sampleShift - 1; i++) + out.writeInt(1, 0); + out.writeInt(1, 1); + } + } + + + // Writes the given value to the output stream as a signed (sampleDepth-sampleShift) bit integer. + // Note that the value to being written is equal to the raw sample value shifted right by sampleShift. + protected final void writeRawSample(long val, BitOutputStream out) throws IOException { + int width = sampleDepth - sampleShift; + if (width < 1 || width > 33) + throw new IllegalStateException(); + long temp = val >> (width - 1); + if (temp != 0 && temp != -1) + throw new IllegalArgumentException(); + if (width <= 32) + out.writeInt(width, (int)val); + else { // width == 33 + out.writeInt(1, (int)(val >>> 32)); + out.writeInt(32, (int)val); + } + } + + + + /*---- Helper structure ----*/ + + // Represents options for how to search the encoding parameters for a subframe. It is used directly by + // SubframeEncoder.computeBest() and indirectly by its sub-calls. Objects of this class are immutable. + public static final class SearchOptions { + + /*-- Fields --*/ + + // The range of orders to test for fixed prediction mode, possibly none. + // The values satisfy (minFixedOrder = maxFixedOrder = -1) || (0 <= minFixedOrder <= maxFixedOrder <= 4). + public final int minFixedOrder; + public final int maxFixedOrder; + + // The range of orders to test for linear predictive coding (LPC) mode, possibly none. + // The values satisfy (minLpcOrder = maxLpcOrder = -1) || (1 <= minLpcOrder <= maxLpcOrder <= 32). + // Note that the FLAC subset format requires maxLpcOrder <= 12 when sampleRate <= 48000. + public final int minLpcOrder; + public final int maxLpcOrder; + + // How many LPC coefficient variables to try rounding both up and down. + // In the range [0, 30]. Note that each increase by one will double the search time! + public final int lpcRoundVariables; + + // The maximum partition order used in Rice coding. The minimum is not configurable and always 0. + // In the range [0, 15]. Note that the FLAC subset format requires maxRiceOrder <= 8. + public final int maxRiceOrder; + + + /*-- Constructors --*/ + + // Constructs a search options object based on the given values, + // throwing an IllegalArgumentException if and only if they are nonsensical. + public SearchOptions(int minFixedOrder, int maxFixedOrder, int minLpcOrder, int maxLpcOrder, int lpcRoundVars, int maxRiceOrder) { + // Check argument ranges + if ((minFixedOrder != -1 || maxFixedOrder != -1) && + !(0 <= minFixedOrder && minFixedOrder <= maxFixedOrder && maxFixedOrder <= 4)) + throw new IllegalArgumentException(); + if ((minLpcOrder != -1 || maxLpcOrder != -1) && + !(1 <= minLpcOrder && minLpcOrder <= maxLpcOrder && maxLpcOrder <= 32)) + throw new IllegalArgumentException(); + if (lpcRoundVars < 0 || lpcRoundVars > 30) + throw new IllegalArgumentException(); + if (maxRiceOrder < 0 || maxRiceOrder > 15) + throw new IllegalArgumentException(); + + // Copy arguments to fields + this.minFixedOrder = minFixedOrder; + this.maxFixedOrder = maxFixedOrder; + this.minLpcOrder = minLpcOrder; + this.maxLpcOrder = maxLpcOrder; + this.lpcRoundVariables = lpcRoundVars; + this.maxRiceOrder = maxRiceOrder; + } + + + /*-- Constants for recommended defaults --*/ + + // Note that these constants are for convenience only, and offer little promises in terms of API stability. + // For example, there is no expectation that the set of search option names as a whole, + // or the values of each search option will remain the same from version to version. + // Even if a search option retains the same value across code versions, the underlying encoder implementation + // can change in such a way that the encoded output is not bit-identical or size-identical across versions. + // Therefore, treat these search options as suggestions that strongly influence the encoded FLAC output, + // but *not* as firm guarantees that the same audio data with the same options will forever produce the same result. + + // These search ranges conform to the FLAC subset format. + public static final SearchOptions SUBSET_ONLY_FIXED = new SearchOptions(0, 4, -1, -1, 0, 8); + public static final SearchOptions SUBSET_MEDIUM = new SearchOptions(0, 1, 2, 8, 0, 5); + public static final SearchOptions SUBSET_BEST = new SearchOptions(0, 1, 2, 12, 0, 8); + public static final SearchOptions SUBSET_INSANE = new SearchOptions(0, 4, 1, 12, 4, 8); + + // These cannot guarantee that an encoded file conforms to the FLAC subset (i.e. they are lax). + public static final SearchOptions LAX_MEDIUM = new SearchOptions(0, 1, 2, 22, 0, 15); + public static final SearchOptions LAX_BEST = new SearchOptions(0, 1, 2, 32, 0, 15); + public static final SearchOptions LAX_INSANE = new SearchOptions(0, 1, 2, 32, 4, 15); + + } + +} diff --git a/desktop/src/io/nayuki/flac/encode/VerbatimEncoder.java b/desktop/src/io/nayuki/flac/encode/VerbatimEncoder.java new file mode 100644 index 0000000..0257743 --- /dev/null +++ b/desktop/src/io/nayuki/flac/encode/VerbatimEncoder.java @@ -0,0 +1,58 @@ +/* + * FLAC library (Java) + * + * Copyright (c) Project Nayuki + * https://www.nayuki.io/page/flac-library-java + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program (see COPYING.txt and COPYING.LESSER.txt). + * If not, see . + */ + +package io.nayuki.flac.encode; + +import java.io.IOException; + + +/* + * Under the verbatim coding mode, this provides size calculations on and bitstream encoding of audio sample data. + * Note that the size depends on the data length, shift, and bit depth, but not on the data contents. + */ +final class VerbatimEncoder extends SubframeEncoder { + + // Computes the best way to encode the given values under the verbatim coding mode, + // returning an exact size plus a new encoder object associated with the input arguments. + public static SizeEstimate computeBest(long[] samples, int shift, int depth) { + VerbatimEncoder enc = new VerbatimEncoder(samples, shift, depth); + long size = 1 + 6 + 1 + shift + samples.length * depth; + return new SizeEstimate(size, enc); + } + + + // Constructs a constant encoder for the given data, right shift, and sample depth. + public VerbatimEncoder(long[] samples, int shift, int depth) { + super(shift, depth); + } + + + // Encodes the given vector of audio sample data to the given bit output stream using + // the this encoding method (and the superclass fields sampleShift and sampleDepth). + // This requires the data array to have the same values (but not necessarily + // the same object reference) as the array that was passed to the constructor. + public void encode(long[] samples, BitOutputStream out) throws IOException { + writeTypeAndShift(1, out); + for (long val : samples) + writeRawSample(val >> sampleShift, out); + } + +} From 581f51ef65ef7d5772c6064c4c81026bc61ac2f1 Mon Sep 17 00:00:00 2001 From: Retera Date: Mon, 1 Feb 2021 08:38:04 -0500 Subject: [PATCH 099/116] Update to have lumber harvest, still bugged --- core/assets/warsmash.ini | 2 +- .../com/etheller/warsmash/viewer5/Camera.java | 6 +- .../viewer5/handlers/w3x/SequenceUtils.java | 1 + .../viewer5/handlers/w3x/TextTag.java | 36 +++++ .../viewer5/handlers/w3x/War3MapViewer.java | 43 +++++- .../w3x/rendersim/RenderDestructable.java | 44 +++++- .../handlers/w3x/rendersim/RenderUnit.java | 117 --------------- .../handlers/w3x/rendersim/RenderWidget.java | 134 ++++++++++++++++++ .../w3x/simulation/CDestructable.java | 6 + .../handlers/w3x/simulation/CSimulation.java | 14 +- .../handlers/w3x/simulation/CUnit.java | 23 +-- .../handlers/w3x/simulation/CWidget.java | 8 ++ .../simulation/abilities/CAbilityAttack.java | 11 +- .../abilities/combat/CAbilityColdArrows.java | 4 +- .../abilities/harvest/CAbilityHarvest.java | 52 ++++++- .../abilities/mine/CAbilityGoldMine.java | 3 +- .../impl/AbstractCAbilityTypeDefinition.java | 2 + .../impl/CAbilityTypeDefinitionHarvest.java | 5 +- .../types/impl/CAbilityTypeHarvest.java | 2 +- .../impl/CAbilityTypeHarvestLevelData.java | 14 +- .../behaviors/CAbstractRangedBehavior.java | 14 +- .../simulation/behaviors/CBehaviorAttack.java | 18 ++- .../behaviors/CBehaviorAttackListener.java | 35 +++++ .../simulation/behaviors/CBehaviorFollow.java | 7 +- .../behaviors/build/CBehaviorOrcBuild.java | 7 +- .../behaviors/harvest/CBehaviorHarvest.java | 89 ++++++++++-- .../harvest/CBehaviorReturnResources.java | 72 +++++++--- .../combat/attacks/CUnitAttack.java | 3 +- .../combat/attacks/CUnitAttackInstant.java | 5 +- .../combat/attacks/CUnitAttackListener.java | 9 ++ .../combat/attacks/CUnitAttackMissile.java | 10 +- .../attacks/CUnitAttackMissileBounce.java | 13 +- .../attacks/CUnitAttackMissileSplash.java | 16 ++- .../combat/attacks/CUnitAttackNormal.java | 5 +- .../combat/projectile/CAttackProjectile.java | 8 +- .../util/SimulationRenderController.java | 6 +- .../viewer5/handlers/w3x/ui/MeleeUI.java | 16 +++ 37 files changed, 661 insertions(+), 199 deletions(-) create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/TextTag.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttackListener.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackListener.java diff --git a/core/assets/warsmash.ini b/core/assets/warsmash.ini index 137f96a..f33461d 100644 --- a/core/assets/warsmash.ini +++ b/core/assets/warsmash.ini @@ -20,7 +20,7 @@ Path07="." [Map] //FilePath="CombatUnitTests.w3x" //FilePath="PitchRoll.w3x" -FilePath="PeonStartingBase.w3x" +FilePath="PeonStartingBase_Simple.w3x" //FilePath="MyStromguarde.w3m" //FilePath="ColdArrows.w3m" //FilePath="DungeonGoldMine.w3m" diff --git a/core/src/com/etheller/warsmash/viewer5/Camera.java b/core/src/com/etheller/warsmash/viewer5/Camera.java index 840359f..1a97cd6 100644 --- a/core/src/com/etheller/warsmash/viewer5/Camera.java +++ b/core/src/com/etheller/warsmash/viewer5/Camera.java @@ -1,5 +1,6 @@ package com.etheller.warsmash.viewer5; +import com.badlogic.gdx.Gdx; import com.badlogic.gdx.math.Matrix4; import com.badlogic.gdx.math.Quaternion; import com.badlogic.gdx.math.Rectangle; @@ -310,10 +311,11 @@ public class Camera { final Rectangle viewport = this.rect; vectorHeap.set(v); - vectorHeap.prj(this.inverseViewMatrix); + vectorHeap.prj(this.viewProjectionMatrix); out.x = Math.round(((vectorHeap.x + 1) / 2) * viewport.width); - out.y = Math.round(((vectorHeap.y + 1) / 2) * viewport.height); + out.y = ((Gdx.graphics.getHeight() - viewport.y - viewport.height) + (viewport.height)) + - Math.round(((vectorHeap.y + 1) / 2) * viewport.height); return out; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SequenceUtils.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SequenceUtils.java index fd94e7c..edeb0d3 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SequenceUtils.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SequenceUtils.java @@ -17,6 +17,7 @@ public class SequenceUtils { public static final EnumSet FLESH = EnumSet.of(SecondaryTag.FLESH); public static final EnumSet TALK = EnumSet.of(SecondaryTag.TALK); public static final EnumSet BONE = EnumSet.of(SecondaryTag.BONE); + public static final EnumSet HIT = EnumSet.of(SecondaryTag.HIT); private static final StandSequenceComparator STAND_SEQUENCE_COMPARATOR = new StandSequenceComparator(); private static final SecondaryTagSequenceComparator SECONDARY_TAG_SEQUENCE_COMPARATOR = new SecondaryTagSequenceComparator( diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/TextTag.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/TextTag.java new file mode 100644 index 0000000..9965389 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/TextTag.java @@ -0,0 +1,36 @@ +package com.etheller.warsmash.viewer5.handlers.w3x; + +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.math.Vector3; + +public class TextTag { + private final Vector3 position; + private final String text; + private final Color color; + private int lifetime = 0; + + public TextTag(final Vector3 position, final String text, final Color color) { + this.position = position; + this.text = text; + this.color = color; + position.z += 64f; + } + + public boolean update() { + this.position.z += 1.0f; + this.lifetime++; + return this.lifetime > 196; + } + + public Vector3 getPosition() { + return this.position; + } + + public Color getColor() { + return this.color; + } + + public String getText() { + return this.text; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index eaa471d..3a9f082 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -23,6 +23,7 @@ import org.apache.commons.compress.utils.IOUtils; import org.apache.commons.compress.utils.SeekableInMemoryByteChannel; import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.math.Vector3; @@ -94,8 +95,10 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidgetFilterFunction; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackInstant; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackListener; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackMissile; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.projectile.CAttackProjectile; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.ResourceType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.SimulationRenderController; import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.CommandErrorListener; import com.etheller.warsmash.viewer5.handlers.w3x.ui.sound.KeyedSounds; @@ -104,6 +107,8 @@ import mpq.MPQArchive; import mpq.MPQException; public class War3MapViewer extends AbstractMdxModelViewer { + private static final Color PLACEHOLDER_LUMBER_COLOR = new Color(0.0f, 200f / 255f, 80f / 255f, 1.0f); + private static final Color PLACEHOLDER_GOLD_COLOR = new Color(1.0f, 220f / 255f, 0f, 1.0f); private static final War3ID UNIT_FILE = War3ID.fromString("umdl"); private static final War3ID UNIT_SPECIAL = War3ID.fromString("uspa"); private static final War3ID UBER_SPLAT = War3ID.fromString("uubs"); @@ -203,6 +208,8 @@ public class War3MapViewer extends AbstractMdxModelViewer { private int localPlayerIndex; private final CommandErrorListener commandErrorListener; + public final List textTags = new ArrayList<>(); + public War3MapViewer(final DataSource dataSource, final CanvasProvider canvas, final CommandErrorListener errorListener) { super(dataSource, canvas); @@ -449,7 +456,7 @@ public class War3MapViewer extends AbstractMdxModelViewer { public CAttackProjectile createAttackProjectile(final CSimulation simulation, final float launchX, final float launchY, final float launchFacing, final CUnit source, final CUnitAttackMissile unitAttack, final AbilityTarget target, final float damage, - final int bounceIndex) { + final int bounceIndex, final CUnitAttackListener attackListener) { final War3ID typeId = source.getTypeId(); final int projectileSpeed = unitAttack.getProjectileSpeed(); final float projectileArc = unitAttack.getProjectileArc(); @@ -468,7 +475,7 @@ public class War3MapViewer extends AbstractMdxModelViewer { final float height = War3MapViewer.this.terrain.getGroundHeight(x, y) + source.getFlyHeight() + projectileLaunchZ; final CAttackProjectile simulationAttackProjectile = new CAttackProjectile(x, y, - projectileSpeed, target, source, damage, unitAttack, bounceIndex); + projectileSpeed, target, source, damage, unitAttack, bounceIndex, attackListener); final MdxModel model = (MdxModel) load(missileArt, War3MapViewer.this.mapPathSolver, War3MapViewer.this.solverParams); @@ -563,7 +570,6 @@ public class War3MapViewer extends AbstractMdxModelViewer { @Override public void removeDestructable(final CDestructable dest) { final RenderDestructable renderPeer = War3MapViewer.this.destructableToRenderPeer.remove(dest); - War3MapViewer.this.doodads.remove(renderPeer); War3MapViewer.this.worldScene.removeInstance(renderPeer.instance); if (renderPeer.walkableBounds != null) { War3MapViewer.this.walkableObjectsTree.remove((MdxComplexInstance) renderPeer.instance, @@ -634,6 +640,24 @@ public class War3MapViewer extends AbstractMdxModelViewer { final RenderUnit renderPeer = War3MapViewer.this.unitToRenderPeer.get(cUnit); renderPeer.repositioned(War3MapViewer.this); } + + @Override + public void spawnGainResourceTextTag(final CUnit gainingUnit, final ResourceType resourceType, + final int amount) { + final RenderUnit renderPeer = War3MapViewer.this.unitToRenderPeer.get(gainingUnit); + switch (resourceType) { + case FOOD: + throw new IllegalArgumentException(); + case GOLD: + War3MapViewer.this.textTags.add(new TextTag(new Vector3(renderPeer.location), "+" + amount, + PLACEHOLDER_GOLD_COLOR)); + break; + case LUMBER: + War3MapViewer.this.textTags.add(new TextTag(new Vector3(renderPeer.location), "+" + amount, + PLACEHOLDER_LUMBER_COLOR)); + break; + } + } }, this.terrain.pathingGrid, this.terrain.getEntireMap(), this.seededRandom, w3iFile.getPlayers(), this.commandErrorListener); @@ -784,6 +808,8 @@ public class War3MapViewer extends AbstractMdxModelViewer { final float y = doodad.getLocation()[1]; final CDestructable simulationDestructable = this.simulation.createDestructable(row.getAlias(), x, y, destructablePathing, destructablePathingDeath); + simulationDestructable.setLife(this.simulation, + simulationDestructable.getLife() * (doodad.getLife() / 100f)); final RenderDestructable renderDestructable = new RenderDestructable(this, model, row, doodad, type, maxPitch, maxRoll, doodad.getLife(), destructableShadow, simulationDestructable); if (row.readSLKTagBoolean("walkable")) { @@ -796,7 +822,6 @@ public class War3MapViewer extends AbstractMdxModelViewer { renderDestructableBounds); renderDestructable.walkableBounds = renderDestructableBounds; } - this.doodads.add(renderDestructable); this.widgets.add(renderDestructable); } else { @@ -1199,6 +1224,12 @@ public class War3MapViewer extends AbstractMdxModelViewer { super.update(); + final Iterator textTagIterator = this.textTags.iterator(); + while (textTagIterator.hasNext()) { + if (textTagIterator.next().update()) { + textTagIterator.remove(); + } + } for (final RenderWidget unit : this.widgets) { unit.updateAnimations(this); } @@ -1683,4 +1714,8 @@ public class War3MapViewer extends AbstractMdxModelViewer { return false; } } + + public void add(final TextTag textTag) { + this.textTags.add(textTag); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderDestructable.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderDestructable.java index efef1e2..7642f39 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderDestructable.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderDestructable.java @@ -1,5 +1,6 @@ package com.etheller.warsmash.viewer5.handlers.w3x.rendersim; +import com.badlogic.gdx.Gdx; import com.badlogic.gdx.math.Rectangle; import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; import com.etheller.warsmash.units.manager.MutableObjectData.WorldEditorDataType; @@ -7,6 +8,7 @@ import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; +import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; import com.etheller.warsmash.viewer5.handlers.w3x.SplatModel.SplatMover; import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; import com.etheller.warsmash.viewer5.handlers.w3x.environment.BuildingShadow; @@ -18,17 +20,21 @@ public class RenderDestructable extends RenderDoodad implements RenderWidget { private static final War3ID TEX_ID = War3ID.fromString("btxi"); private static final War3ID SEL_CIRCLE_SIZE = War3ID.fromString("bgsc"); - private final float life; + private float life; public Rectangle walkableBounds; private final CDestructable simulationDestructable; private SplatMover selectionCircle; + private final UnitAnimationListenerImpl unitAnimationListenerImpl; + private boolean dead; + private BuildingShadow destructableShadow; public RenderDestructable(final War3MapViewer map, final MdxModel model, final MutableGameObject row, final com.etheller.warsmash.parsers.w3x.doo.Doodad doodad, final WorldEditorDataType type, final float maxPitch, final float maxRoll, final float life, final BuildingShadow destructableShadow, final CDestructable simulationDestructable) { super(map, model, row, doodad, type, maxPitch, maxRoll); - this.life = life; + this.life = simulationDestructable.getLife(); + this.destructableShadow = destructableShadow; this.simulationDestructable = simulationDestructable; String replaceableTextureFile = row.getFieldAsString(TEX_FILE, 0); final int replaceableTextureId = row.getFieldAsInteger(TEX_ID, 0); @@ -41,6 +47,9 @@ public class RenderDestructable extends RenderDoodad implements RenderWidget { this.instance.setReplaceableTexture(replaceableTextureId, replaceableTextureFile); } this.selectionScale *= row.getFieldAsFloat(SEL_CIRCLE_SIZE, 0); + this.unitAnimationListenerImpl = new UnitAnimationListenerImpl((MdxComplexInstance) this.instance); + simulationDestructable.setUnitAnimationListener(this.unitAnimationListenerImpl); + this.unitAnimationListenerImpl.playAnimation(true, getAnimation(), SequenceUtils.EMPTY, 1.0f, true); } @Override @@ -65,6 +74,37 @@ public class RenderDestructable extends RenderDoodad implements RenderWidget { public void updateAnimations(final War3MapViewer war3MapViewer) { // TODO maybe move getAnimation behaviors to here and make this thing not a // doodad + + final boolean dead = this.simulationDestructable.isDead(); + if (dead && !this.dead) { + this.unitAnimationListenerImpl.playAnimation(true, PrimaryTag.DEATH, SequenceUtils.EMPTY, 1.0f, true); + if (this.destructableShadow != null) { + this.destructableShadow.remove(); + this.destructableShadow = null; + } + if (this.selectionCircle != null) { + this.selectionCircle.destroy(Gdx.gl30, war3MapViewer.terrain.centerOffset); + this.selectionCircle = null; + } + } + else if (!dead) { + if (this.dead) { + this.unitAnimationListenerImpl.playAnimation(true, PrimaryTag.BIRTH, SequenceUtils.EMPTY, 1.0f, true); + // TODO add back shadow here + + } + else { + if (Math.abs(this.life - this.simulationDestructable.getLife()) > 0.003f) { + if (this.life > this.simulationDestructable.getLife()) { + this.unitAnimationListenerImpl.playAnimation(true, PrimaryTag.STAND, SequenceUtils.HIT, 1.0f, + true); + } + this.life = this.simulationDestructable.getLife(); + } + } + } + this.dead = dead; + this.unitAnimationListenerImpl.update(); } @Override diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java index 5a90611..36d98d8 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java @@ -1,8 +1,6 @@ package com.etheller.warsmash.viewer5.handlers.w3x.rendersim; import java.util.EnumSet; -import java.util.LinkedList; -import java.util.Queue; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.math.Quaternion; @@ -11,7 +9,6 @@ import com.etheller.warsmash.util.RenderMathUtils; import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; -import com.etheller.warsmash.viewer5.handlers.mdx.Sequence; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.SecondaryTag; @@ -27,7 +24,6 @@ import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.commandbuttons.Comma import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.commandbuttons.CommandCardPopulatingAbilityVisitor; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitAnimationListener; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; @@ -436,119 +432,6 @@ public class RenderUnit implements RenderWidget { return this.unitAnimationListenerImpl.secondaryAnimationTags; } - private static final class UnitAnimationListenerImpl implements CUnitAnimationListener { - private final MdxComplexInstance instance; - private final EnumSet secondaryAnimationTags = EnumSet - .noneOf(AnimationTokens.SecondaryTag.class); - private final EnumSet recycleSet = EnumSet - .noneOf(AnimationTokens.SecondaryTag.class); - private PrimaryTag currentAnimation; - private EnumSet currentAnimationSecondaryTags; - private float currentSpeedRatio; - private boolean currentlyAllowingRarityVariations; - private final Queue animationQueue = new LinkedList<>(); - - public UnitAnimationListenerImpl(final MdxComplexInstance instance) { - this.instance = instance; - } - - @Override - public void addSecondaryTag(final AnimationTokens.SecondaryTag tag) { - this.secondaryAnimationTags.add(tag); - playAnimation(true, this.currentAnimation, this.currentAnimationSecondaryTags, this.currentSpeedRatio, - this.currentlyAllowingRarityVariations); - } - - @Override - public void removeSecondaryTag(final AnimationTokens.SecondaryTag tag) { - this.secondaryAnimationTags.remove(tag); - playAnimation(true, this.currentAnimation, this.currentAnimationSecondaryTags, this.currentSpeedRatio, - this.currentlyAllowingRarityVariations); - } - - @Override - public void playAnimation(final boolean force, final PrimaryTag animationName, - final EnumSet secondaryAnimationTags, final float speedRatio, - final boolean allowRarityVariations) { - this.animationQueue.clear(); - if (force || (animationName != this.currentAnimation) - || !secondaryAnimationTags.equals(this.currentAnimationSecondaryTags)) { - this.currentSpeedRatio = speedRatio; - this.recycleSet.clear(); - this.recycleSet.addAll(this.secondaryAnimationTags); - this.recycleSet.addAll(secondaryAnimationTags); - this.instance.setAnimationSpeed(speedRatio); - if (SequenceUtils.randomSequence(this.instance, animationName, this.recycleSet, - allowRarityVariations) != null) { - this.currentAnimation = animationName; - this.currentAnimationSecondaryTags = secondaryAnimationTags; - this.currentlyAllowingRarityVariations = allowRarityVariations; - } - } - } - - public void playAnimationWithDuration(final boolean force, final PrimaryTag animationName, - final EnumSet secondaryAnimationTags, final float duration, - final boolean allowRarityVariations) { - this.animationQueue.clear(); - if (force || (animationName != this.currentAnimation) - || !secondaryAnimationTags.equals(this.currentAnimationSecondaryTags)) { - this.recycleSet.clear(); - this.recycleSet.addAll(this.secondaryAnimationTags); - this.recycleSet.addAll(secondaryAnimationTags); - final Sequence sequence = SequenceUtils.randomSequence(this.instance, animationName, this.recycleSet, - allowRarityVariations); - if (sequence != null) { - this.currentAnimation = animationName; - this.currentAnimationSecondaryTags = secondaryAnimationTags; - this.currentlyAllowingRarityVariations = allowRarityVariations; - this.currentSpeedRatio = ((sequence.getInterval()[1] - sequence.getInterval()[0]) / 1000.0f) - / duration; - this.instance.setAnimationSpeed(this.currentSpeedRatio); - } - } - } - - @Override - public void queueAnimation(final PrimaryTag animationName, final EnumSet secondaryAnimationTags, - final boolean allowRarityVariations) { - this.animationQueue.add(new QueuedAnimation(animationName, secondaryAnimationTags, allowRarityVariations)); - } - - public void update() { - if (this.instance.sequenceEnded || (this.instance.sequence == -1)) { - // animation done - if ((this.instance.sequence != -1) && (((MdxModel) this.instance.model).getSequences() - .get(this.instance.sequence).getFlags() == 0)) { - // animation is a looping animation - playAnimation(true, this.currentAnimation, this.currentAnimationSecondaryTags, - this.currentSpeedRatio, this.currentlyAllowingRarityVariations); - } - else { - final QueuedAnimation nextAnimation = this.animationQueue.poll(); - if (nextAnimation != null) { - playAnimation(true, nextAnimation.animationName, nextAnimation.secondaryAnimationTags, 1.0f, - nextAnimation.allowRarityVariations); - } - } - } - } - - } - - private static final class QueuedAnimation { - private final PrimaryTag animationName; - private final EnumSet secondaryAnimationTags; - private final boolean allowRarityVariations; - - public QueuedAnimation(final PrimaryTag animationName, final EnumSet secondaryAnimationTags, - final boolean allowRarityVariations) { - this.animationName = animationName; - this.secondaryAnimationTags = secondaryAnimationTags; - this.allowRarityVariations = allowRarityVariations; - } - } - public void repositioned(final War3MapViewer map) { final float prevX = this.location[0]; final float prevY = this.location[1]; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderWidget.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderWidget.java index 4016abe..64df533 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderWidget.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderWidget.java @@ -1,8 +1,19 @@ package com.etheller.warsmash.viewer5.handlers.w3x.rendersim; +import java.util.EnumSet; +import java.util.LinkedList; +import java.util.Queue; + import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; +import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; +import com.etheller.warsmash.viewer5.handlers.mdx.Sequence; +import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens; +import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; +import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.SecondaryTag; +import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; import com.etheller.warsmash.viewer5.handlers.w3x.SplatModel.SplatMover; import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitAnimationListener; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; public interface RenderWidget { @@ -23,4 +34,127 @@ public interface RenderWidget { void unassignSelectionCircle(); void assignSelectionCircle(SplatMover t); + + public static final class UnitAnimationListenerImpl implements CUnitAnimationListener { + private final MdxComplexInstance instance; + protected final EnumSet secondaryAnimationTags = EnumSet + .noneOf(AnimationTokens.SecondaryTag.class); + private final EnumSet recycleSet = EnumSet + .noneOf(AnimationTokens.SecondaryTag.class); + private PrimaryTag currentAnimation; + private EnumSet currentAnimationSecondaryTags; + private float currentSpeedRatio; + private boolean currentlyAllowingRarityVariations; + private final Queue animationQueue = new LinkedList<>(); + + public UnitAnimationListenerImpl(final MdxComplexInstance instance) { + this.instance = instance; + } + + @Override + public void addSecondaryTag(final AnimationTokens.SecondaryTag tag) { + if (!secondaryAnimationTags.contains(tag)) { + this.secondaryAnimationTags.add(tag); + if (!animationQueue.isEmpty()) { + final QueuedAnimation nextAnimation = animationQueue.poll(); + playAnimation(true, nextAnimation.animationName, nextAnimation.secondaryAnimationTags, 1.0f, + nextAnimation.allowRarityVariations); + } + else { + playAnimation(true, this.currentAnimation, this.currentAnimationSecondaryTags, + this.currentSpeedRatio, this.currentlyAllowingRarityVariations); + } + } + } + + @Override + public void removeSecondaryTag(final AnimationTokens.SecondaryTag tag) { + if (secondaryAnimationTags.contains(tag)) { + this.secondaryAnimationTags.remove(tag); + playAnimation(true, this.currentAnimation, this.currentAnimationSecondaryTags, this.currentSpeedRatio, + this.currentlyAllowingRarityVariations); + } + } + + @Override + public void playAnimation(final boolean force, final PrimaryTag animationName, + final EnumSet secondaryAnimationTags, final float speedRatio, + final boolean allowRarityVariations) { + this.animationQueue.clear(); + if (force || (animationName != this.currentAnimation) + || !secondaryAnimationTags.equals(this.currentAnimationSecondaryTags)) { + this.currentSpeedRatio = speedRatio; + this.recycleSet.clear(); + this.recycleSet.addAll(this.secondaryAnimationTags); + this.recycleSet.addAll(secondaryAnimationTags); + this.instance.setAnimationSpeed(speedRatio); + if (SequenceUtils.randomSequence(this.instance, animationName, this.recycleSet, + allowRarityVariations) != null) { + this.currentAnimation = animationName; + this.currentAnimationSecondaryTags = secondaryAnimationTags; + this.currentlyAllowingRarityVariations = allowRarityVariations; + } + } + } + + public void playAnimationWithDuration(final boolean force, final PrimaryTag animationName, + final EnumSet secondaryAnimationTags, final float duration, + final boolean allowRarityVariations) { + this.animationQueue.clear(); + if (force || (animationName != this.currentAnimation) + || !secondaryAnimationTags.equals(this.currentAnimationSecondaryTags)) { + this.recycleSet.clear(); + this.recycleSet.addAll(this.secondaryAnimationTags); + this.recycleSet.addAll(secondaryAnimationTags); + final Sequence sequence = SequenceUtils.randomSequence(this.instance, animationName, this.recycleSet, + allowRarityVariations); + if (sequence != null) { + this.currentAnimation = animationName; + this.currentAnimationSecondaryTags = secondaryAnimationTags; + this.currentlyAllowingRarityVariations = allowRarityVariations; + this.currentSpeedRatio = ((sequence.getInterval()[1] - sequence.getInterval()[0]) / 1000.0f) + / duration; + this.instance.setAnimationSpeed(this.currentSpeedRatio); + } + } + } + + @Override + public void queueAnimation(final PrimaryTag animationName, final EnumSet secondaryAnimationTags, + final boolean allowRarityVariations) { + this.animationQueue.add(new QueuedAnimation(animationName, secondaryAnimationTags, allowRarityVariations)); + } + + public void update() { + if (this.instance.sequenceEnded || (this.instance.sequence == -1)) { + // animation done + if ((this.instance.sequence != -1) && (((MdxModel) this.instance.model).getSequences() + .get(this.instance.sequence).getFlags() == 0)) { + // animation is a looping animation + playAnimation(true, this.currentAnimation, this.currentAnimationSecondaryTags, + this.currentSpeedRatio, this.currentlyAllowingRarityVariations); + } + else { + final QueuedAnimation nextAnimation = this.animationQueue.poll(); + if (nextAnimation != null) { + playAnimation(true, nextAnimation.animationName, nextAnimation.secondaryAnimationTags, 1.0f, + nextAnimation.allowRarityVariations); + } + } + } + } + } + + public static final class QueuedAnimation { + private final PrimaryTag animationName; + private final EnumSet secondaryAnimationTags; + private final boolean allowRarityVariations; + + public QueuedAnimation(final PrimaryTag animationName, final EnumSet secondaryAnimationTags, + final boolean allowRarityVariations) { + this.animationName = animationName; + this.secondaryAnimationTags = secondaryAnimationTags; + this.allowRarityVariations = allowRarityVariations; + } + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CDestructable.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CDestructable.java index e59224d..e4b2ab6 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CDestructable.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CDestructable.java @@ -3,6 +3,7 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation; import java.util.EnumSet; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.RemovablePathingMapInstance; +import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderWidget.UnitAnimationListenerImpl; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTargetVisitor; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; @@ -12,6 +13,7 @@ public class CDestructable extends CWidget { private final CDestructableType destType; private final RemovablePathingMapInstance pathingInstance; private final RemovablePathingMapInstance pathingInstanceDeath; + private UnitAnimationListenerImpl unitAnimationListenerImpl; public CDestructable(final int handleId, final float x, final float y, final float life, final CDestructableType destTypeInstance, final RemovablePathingMapInstance pathingInstance, @@ -65,4 +67,8 @@ public class CDestructable extends CWidget { public CDestructableType getDestType() { return this.destType; } + + public void setUnitAnimationListener(final UnitAnimationListenerImpl unitAnimationListenerImpl) { + this.unitAnimationListenerImpl = unitAnimationListenerImpl; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java index 5bb923f..6f9da3f 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java @@ -21,6 +21,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorMove; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackInstant; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackListener; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackMissile; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.projectile.CAttackProjectile; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.data.CAbilityData; @@ -31,6 +32,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CAllianceTy import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CMapControl; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayer; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CRace; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.ResourceType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.SimulationRenderController; import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.CommandErrorListener; @@ -125,6 +127,10 @@ public class CSimulation { return this.units; } + public List getDestructables() { + return this.destructables; + } + public CUnit createUnit(final War3ID typeId, final int playerIndex, final float x, final float y, final float facing, final BufferedImage buildingPathingPixelMap, final RemovablePathingMapInstance pathingInstance) { @@ -164,9 +170,9 @@ public class CSimulation { public CAttackProjectile createProjectile(final CUnit source, final float launchX, final float launchY, final float launchFacing, final CUnitAttackMissile attack, final AbilityTarget target, final float damage, - final int bounceIndex) { + final int bounceIndex, final CUnitAttackListener attackListener) { final CAttackProjectile projectile = this.simulationRenderController.createAttackProjectile(this, launchX, - launchY, launchFacing, source, attack, target, damage, bounceIndex); + launchY, launchFacing, source, attack, target, damage, bounceIndex, attackListener); this.newProjectiles.add(projectile); return projectile; } @@ -292,6 +298,10 @@ public class CSimulation { this.simulationRenderController.unitRepositioned(cUnit); } + public void unitGainResourceEvent(final CUnit unit, final ResourceType resourceType, final int amount) { + this.simulationRenderController.spawnGainResourceTextTag(unit, resourceType, amount); + } + public void unitsLoaded() { // called on startup after the system loads the map's units layer, but not any // custom scripts yet diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java index 97895c8..80998ca 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java @@ -25,6 +25,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTargetVisitor; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorAttack; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorAttackListener; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorFollow; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorHoldPosition; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorMove; @@ -591,12 +592,6 @@ public class CUnit extends CWidget { return groundDistance; } - public double distanceSquaredNoCollision(final AbilityTarget target) { - final double dx = Math.abs(target.getX() - getX()); - final double dy = Math.abs(target.getY() - getY()); - return (dx * dx) + (dy * dy); - } - public double distance(final float x, final float y) { double dx = Math.abs(x - getX()); double dy = Math.abs(y - getY()); @@ -645,7 +640,8 @@ public class CUnit extends CWidget { CAllianceType.PASSIVE)) { for (final CUnitAttack attack : this.unitType.getAttacks()) { if (source.canBeTargetedBy(simulation, this, attack.getTargetsAllowed())) { - this.currentBehavior = getAttackBehavior().reset(OrderIds.attack, attack, source, false); + this.currentBehavior = getAttackBehavior().reset(OrderIds.attack, attack, source, false, + CBehaviorAttackListener.DO_NOTHING); this.currentBehavior.begin(simulation); break; } @@ -695,6 +691,17 @@ public class CUnit extends CWidget { } } } + else if (target instanceof CDestructable) { + final CDestructable targetDest = (CDestructable) target; + final CDestructableType targetDestType = targetDest.getDestType(); + final BufferedImage pathingPixelMap = targetDest.isDead() ? targetDestType.getPathingDeathPixelMap() + : targetDestType.getPathingPixelMap(); + final float targetX = target.getX(); + final float targetY = target.getY(); + if ((pathingPixelMap != null) && canReachToPathing(range, 270, pathingPixelMap, targetX, targetY)) { + return true; + } + } return distance <= range; } @@ -853,7 +860,7 @@ public class CUnit extends CWidget { this.source.currentBehavior.end(this.game); } this.source.currentBehavior = this.source.getAttackBehavior().reset(OrderIds.attack, attack, - unit, this.disableMove); + unit, this.disableMove, CBehaviorAttackListener.DO_NOTHING); this.source.currentBehavior.begin(this.game); this.foundAnyTarget = true; return true; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWidget.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWidget.java index e59c433..02989ac 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWidget.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWidget.java @@ -23,10 +23,12 @@ public abstract class CWidget implements AbilityTarget { return this.handleId; } + @Override public float getX() { return this.x; } + @Override public float getY() { return this.y; } @@ -60,4 +62,10 @@ public abstract class CWidget implements AbilityTarget { public abstract boolean canBeTargetedBy(CSimulation simulation, CUnit source, final EnumSet targetsAllowed); + + public double distanceSquaredNoCollision(final AbilityTarget target) { + final double dx = Math.abs(target.getX() - getX()); + final double dy = Math.abs(target.getY() - getY()); + return (dx * dx) + (dy * dy); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java index d72a024..8dcbe0d 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java @@ -6,6 +6,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorAttack; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorAttackListener; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CAllianceType; @@ -36,6 +37,10 @@ public class CAbilityAttack extends AbstractCAbility { return; } } + else { + receiver.orderIdNotAccepted(); + return; + } } if ((orderId == OrderIds.smart) || (orderId == OrderIds.attack)) { boolean canTarget = false; @@ -115,7 +120,8 @@ public class CAbilityAttack extends AbstractCAbility { CBehavior behavior = null; for (final CUnitAttack attack : caster.getUnitType().getAttacks()) { if (target.canBeTargetedBy(game, caster, attack.getTargetsAllowed())) { - behavior = caster.getAttackBehavior().reset(OrderIds.attack, attack, target, false); + behavior = caster.getAttackBehavior().reset(OrderIds.attack, attack, target, false, + CBehaviorAttackListener.DO_NOTHING); break; } } @@ -138,7 +144,8 @@ public class CAbilityAttack extends AbstractCAbility { CBehavior behavior = null; for (final CUnitAttack attack : caster.getUnitType().getAttacks()) { if (attack.getWeaponType().isAttackGroundSupported()) { - behavior = caster.getAttackBehavior().reset(OrderIds.attackground, attack, point, false); + behavior = caster.getAttackBehavior().reset(OrderIds.attackground, attack, point, false, + CBehaviorAttackListener.DO_NOTHING); break; } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/combat/CAbilityColdArrows.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/combat/CAbilityColdArrows.java index a878277..b68f899 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/combat/CAbilityColdArrows.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/combat/CAbilityColdArrows.java @@ -8,6 +8,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.AbstractC import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorAttackListener; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; @@ -106,7 +107,8 @@ public class CAbilityColdArrows extends AbstractCAbility { CBehavior behavior = null; for (final CUnitAttack attack : caster.getUnitType().getAttacks()) { if (target.canBeTargetedBy(game, caster, attack.getTargetsAllowed())) { - behavior = caster.getAttackBehavior().reset(OrderIds.coldarrowstarg, attack, target, false); + behavior = caster.getAttackBehavior().reset(OrderIds.coldarrowstarg, attack, target, false, + CBehaviorAttackListener.DO_NOTHING); break; } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityHarvest.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityHarvest.java index ffc1f40..5a08e2e 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityHarvest.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityHarvest.java @@ -1,5 +1,8 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.harvest; +import java.util.EnumSet; +import java.util.List; + import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CDestructable; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; @@ -12,6 +15,11 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.harvest.CBehaviorHarvest; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.harvest.CBehaviorReturnResources; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackNormal; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; @@ -21,23 +29,46 @@ public class CAbilityHarvest extends AbstractGenericSingleIconActiveAbility { private final int damageToTree; private final int goldCapacity; private final int lumberCapacity; + private final float castRange; + private final float duration; private CBehaviorHarvest behaviorHarvest; private CBehaviorReturnResources behaviorReturnResources; private int carriedResourceAmount; private ResourceType carriedResourceType; + private CUnitAttack treeAttack; + private CWidget lastHarvestTarget; public CAbilityHarvest(final int handleId, final War3ID alias, final int damageToTree, final int goldCapacity, - final int lumberCapacity) { + final int lumberCapacity, final float castRange, final float duration) { super(handleId, alias); this.damageToTree = damageToTree; this.goldCapacity = goldCapacity; this.lumberCapacity = lumberCapacity; + this.castRange = castRange; + this.duration = duration; } @Override public void onAdd(final CSimulation game, final CUnit unit) { this.behaviorHarvest = new CBehaviorHarvest(unit, this); this.behaviorReturnResources = new CBehaviorReturnResources(unit, this); + + final List unitAttacks = unit.getUnitType().getAttacks(); + CUnitAttack bestFitTreeAttack = null; + for (final CUnitAttack attack : unitAttacks) { + if (attack.getTargetsAllowed().contains(CTargetType.TREE)) { + bestFitTreeAttack = attack; + } + } + this.treeAttack = new CUnitAttackNormal( + bestFitTreeAttack == null ? 0.433f : bestFitTreeAttack.getAnimationBackswingPoint(), + bestFitTreeAttack == null ? 0.433f : bestFitTreeAttack.getAnimationDamagePoint(), CAttackType.NORMAL, + this.duration, 0, 1, this.damageToTree * 2, 0, (int) this.castRange, + bestFitTreeAttack == null ? 250 : bestFitTreeAttack.getRangeMotionBuffer(), + bestFitTreeAttack == null ? false : bestFitTreeAttack.isShowUI(), + bestFitTreeAttack == null ? EnumSet.of(CTargetType.TREE) : bestFitTreeAttack.getTargetsAllowed(), + bestFitTreeAttack == null ? "AxeMediumChop" : bestFitTreeAttack.getWeaponSound(), + bestFitTreeAttack == null ? CWeaponType.NORMAL : bestFitTreeAttack.getWeaponType()); } @Override @@ -104,7 +135,12 @@ public class CAbilityHarvest extends AbstractGenericSingleIconActiveAbility { receiver.mustTargetResources(); } else if (target instanceof CDestructable) { - receiver.mustTargetResources(); + if (target.canBeTargetedBy(game, unit, this.treeAttack.getTargetsAllowed())) { + receiver.targetOk(target); + } + else { + receiver.mustTargetResources(); + } } else { receiver.mustTargetResources(); @@ -173,4 +209,16 @@ public class CAbilityHarvest extends AbstractGenericSingleIconActiveAbility { return this.behaviorReturnResources; } + public CUnitAttack getTreeAttack() { + return this.treeAttack; + } + + public void setLastHarvestTarget(final CWidget lastHarvestTarget) { + this.lastHarvestTarget = lastHarvestTarget; + } + + public CWidget getLastHarvestTarget() { + return this.lastHarvestTarget; + } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/mine/CAbilityGoldMine.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/mine/CAbilityGoldMine.java index 6cf532a..b798673 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/mine/CAbilityGoldMine.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/mine/CAbilityGoldMine.java @@ -20,7 +20,7 @@ public class CAbilityGoldMine extends AbstractGenericNoIconAbility { private final float miningDuration; private final int miningCapacity; private final List activeMiners; - private final boolean wasEmpty; + private boolean wasEmpty; public CAbilityGoldMine(final int handleId, final War3ID alias, final int maxGold, final float miningDuration, final int miningCapacity) { @@ -52,6 +52,7 @@ public class CAbilityGoldMine extends AbstractGenericNoIconAbility { else { unit.getUnitAnimationListener().addSecondaryTag(SecondaryTag.WORK); } + this.wasEmpty = empty; } for (int i = this.activeMiners.size() - 1; i >= 0; i--) { final CBehaviorHarvest activeMiner = this.activeMiners.get(i); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/definitions/impl/AbstractCAbilityTypeDefinition.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/definitions/impl/AbstractCAbilityTypeDefinition.java index 4bb6718..cf19827 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/definitions/impl/AbstractCAbilityTypeDefinition.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/definitions/impl/AbstractCAbilityTypeDefinition.java @@ -13,6 +13,8 @@ public abstract class AbstractCAbilityTypeDefinition createAbilityType(final War3ID alias, final MutableGameObject abilityEditorData) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/definitions/impl/CAbilityTypeDefinitionHarvest.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/definitions/impl/CAbilityTypeDefinitionHarvest.java index dd7f98e..4998ab9 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/definitions/impl/CAbilityTypeDefinitionHarvest.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/definitions/impl/CAbilityTypeDefinitionHarvest.java @@ -24,7 +24,10 @@ public class CAbilityTypeDefinitionHarvest extends AbstractCAbilityTypeDefinitio final int damageToTree = abilityEditorData.getFieldAsInteger(DAMAGE_TO_TREE, level); final int goldCapacity = abilityEditorData.getFieldAsInteger(GOLD_CAPACITY, level); final int lumberCapacity = abilityEditorData.getFieldAsInteger(LUMBER_CAPACITY, level); - return new CAbilityTypeHarvestLevelData(targetsAllowedAtLevel, damageToTree, goldCapacity, lumberCapacity); + final float castRange = abilityEditorData.getFieldAsFloat(CAST_RANGE, level); + final float duration = abilityEditorData.getFieldAsFloat(DURATION, level); + return new CAbilityTypeHarvestLevelData(targetsAllowedAtLevel, damageToTree, goldCapacity, lumberCapacity, + castRange, duration); } @Override diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeHarvest.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeHarvest.java index 29ffbac..1e8ec15 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeHarvest.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeHarvest.java @@ -18,7 +18,7 @@ public class CAbilityTypeHarvest extends CAbilityType targetsAllowed, final int damageToTree, - final int goldCapacity, final int lumberCapacity) { + final int goldCapacity, final int lumberCapacity, final float castRange, final float duration) { super(targetsAllowed); this.damageToTree = damageToTree; this.goldCapacity = goldCapacity; this.lumberCapacity = lumberCapacity; + this.castRange = castRange; + this.duration = duration; } public int getDamageToTree() { @@ -30,4 +34,12 @@ public class CAbilityTypeHarvestLevelData extends CAbilityTypeLevelData { return this.lumberCapacity; } + public float getCastRange() { + return this.castRange; + } + + public float getDuration() { + return this.duration; + } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CAbstractRangedBehavior.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CAbstractRangedBehavior.java index bd2653f..651f23c 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CAbstractRangedBehavior.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CAbstractRangedBehavior.java @@ -6,11 +6,9 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting public abstract class CAbstractRangedBehavior implements CRangedBehavior { protected final CUnit unit; - private final boolean disableCollision; - public CAbstractRangedBehavior(final CUnit unit, final boolean disableCollision) { + public CAbstractRangedBehavior(final CUnit unit) { this.unit = unit; - this.disableCollision = disableCollision; } protected AbilityTarget target; @@ -20,12 +18,16 @@ public abstract class CAbstractRangedBehavior implements CRangedBehavior { private CBehaviorMove moveBehavior; protected final CAbstractRangedBehavior innerReset(final AbilityTarget target) { + return innerReset(target, false); + } + + protected final CAbstractRangedBehavior innerReset(final AbilityTarget target, final boolean disableCollision) { this.target = target; this.wasWithinPropWindow = false; this.wasInRange = false; CBehaviorMove moveBehavior; if (!this.unit.isMovementDisabled()) { - moveBehavior = this.unit.getMoveBehavior().reset(this.target, this, this.disableCollision); + moveBehavior = this.unit.getMoveBehavior().reset(this.target, this, disableCollision); } else { moveBehavior = null; @@ -36,6 +38,8 @@ public abstract class CAbstractRangedBehavior implements CRangedBehavior { protected abstract CBehavior update(CSimulation simulation, boolean withinRange); + protected abstract CBehavior updateOnInvalidTarget(CSimulation simulation); + protected abstract boolean checkTargetStillValid(CSimulation simulation); protected abstract void resetBeforeMoving(CSimulation simulation); @@ -43,7 +47,7 @@ public abstract class CAbstractRangedBehavior implements CRangedBehavior { @Override public final CBehavior update(final CSimulation simulation) { if (!checkTargetStillValid(simulation)) { - return this.unit.pollNextOrderBehavior(simulation); + return updateOnInvalidTarget(simulation); } if (!isWithinRange(simulation)) { if ((this.moveBehavior == null) || this.disableMove) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java index 6dab933..a0e27a8 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java @@ -15,7 +15,7 @@ public class CBehaviorAttack extends CAbstractRangedBehavior { private final AbilityTargetStillAliveAndTargetableVisitor abilityTargetStillAliveVisitor; public CBehaviorAttack(final CUnit unit) { - super(unit, false); + super(unit); this.abilityTargetStillAliveVisitor = new AbilityTargetStillAliveAndTargetableVisitor(); } @@ -23,10 +23,12 @@ public class CBehaviorAttack extends CAbstractRangedBehavior { private int damagePointLaunchTime; private int backSwingTime; private int thisOrderCooldownEndTime; + private CBehaviorAttackListener attackListener; public CBehaviorAttack reset(final int highlightOrderId, final CUnitAttack unitAttack, final AbilityTarget target, - final boolean disableMove) { + final boolean disableMove, final CBehaviorAttackListener attackListener) { this.highlightOrderId = highlightOrderId; + this.attackListener = attackListener; super.innerReset(target); this.unitAttack = unitAttack; this.damagePointLaunchTime = 0; @@ -63,6 +65,11 @@ public class CBehaviorAttack extends CAbstractRangedBehavior { this.thisOrderCooldownEndTime = 0; } + @Override + protected CBehavior updateOnInvalidTarget(final CSimulation simulation) { + return this.attackListener.onFinish(simulation, this.unit); + } + @Override public CBehavior update(final CSimulation simulation, final boolean withinRange) { final int cooldownEndTime = this.unit.getCooldownEndTime(); @@ -85,7 +92,7 @@ public class CBehaviorAttack extends CAbstractRangedBehavior { else { damage = simulation.getSeededRandom().nextInt(maxDamage - minDamage) + minDamage; } - this.unitAttack.launch(simulation, this.unit, this.target, damage); + this.unitAttack.launch(simulation, this.unit, this.target, damage, this.attackListener); this.damagePointLaunchTime = 0; } } @@ -114,6 +121,11 @@ public class CBehaviorAttack extends CAbstractRangedBehavior { false); } + if ((this.backSwingTime != 0) && (currentTurnTick >= this.backSwingTime)) { + this.backSwingTime = 0; + System.out.println("INTERRUPT AFTER BACKSWING"); + return this.attackListener.onFirstUpdateAfterBackswing(this); + } return this; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttackListener.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttackListener.java new file mode 100644 index 0000000..2c51f47 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttackListener.java @@ -0,0 +1,35 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackListener; + +public interface CBehaviorAttackListener extends CUnitAttackListener { + + // For this function, return the current attack behavior to keep attacking, or + // else return something else to interrupt it + CBehavior onFirstUpdateAfterBackswing(CBehaviorAttack currentAttackBehavior); + + CBehavior onFinish(CSimulation game, final CUnit finishingUnit); + + CBehaviorAttackListener DO_NOTHING = new CBehaviorAttackListener() { + @Override + public void onHit(final AbilityTarget target, final float damage) { + } + + @Override + public void onLaunch() { + } + + @Override + public CBehavior onFirstUpdateAfterBackswing(final CBehaviorAttack currentAttackBehavior) { + return currentAttackBehavior; + } + + @Override + public CBehavior onFinish(final CSimulation game, final CUnit finishingUnit) { + return finishingUnit.pollNextOrderBehavior(game); + } + }; +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorFollow.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorFollow.java index 5b4aba8..5b128e5 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorFollow.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorFollow.java @@ -11,7 +11,7 @@ public class CBehaviorFollow extends CAbstractRangedBehavior { private int higlightOrderId; public CBehaviorFollow(final CUnit unit) { - super(unit, false); + super(unit); } public CBehavior reset(final int higlightOrderId, final CUnit target) { @@ -35,6 +35,11 @@ public class CBehaviorFollow extends CAbstractRangedBehavior { return this; } + @Override + protected CBehavior updateOnInvalidTarget(final CSimulation simulation) { + return this.unit.pollNextOrderBehavior(simulation); + } + @Override protected boolean checkTargetStillValid(final CSimulation simulation) { return this.target.visit(AbilityTargetStillAliveVisitor.INSTANCE); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java index d127711..b596e0d 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java @@ -21,7 +21,7 @@ public class CBehaviorOrcBuild extends CAbstractRangedBehavior { private War3ID orderId; public CBehaviorOrcBuild(final CUnit unit) { - super(unit, false); + super(unit); } public CBehavior reset(final AbilityPointTarget target, final int orderId, final int highlightOrderId) { @@ -88,6 +88,11 @@ public class CBehaviorOrcBuild extends CAbstractRangedBehavior { return true; } + @Override + protected CBehavior updateOnInvalidTarget(final CSimulation simulation) { + return this.unit.pollNextOrderBehavior(simulation); + } + @Override protected void resetBeforeMoving(final CSimulation simulation) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/harvest/CBehaviorHarvest.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/harvest/CBehaviorHarvest.java index 864a63a..2b12a28 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/harvest/CBehaviorHarvest.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/harvest/CBehaviorHarvest.java @@ -13,27 +13,30 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.harvest.CAbilityHarvest; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.mine.CAbilityGoldMine; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTargetStillAliveVisitor; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTargetVisitor; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CAbstractRangedBehavior; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorAttack; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorAttackListener; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.ResourceType; -public class CBehaviorHarvest extends CAbstractRangedBehavior implements AbilityTargetVisitor { +public class CBehaviorHarvest extends CAbstractRangedBehavior + implements AbilityTargetVisitor, CBehaviorAttackListener { private final CAbilityHarvest abilityHarvest; private CSimulation simulation; private int popoutFromMineTurnTick = 0; - private CAbilityGoldMine abilityGoldMine; public CBehaviorHarvest(final CUnit unit, final CAbilityHarvest abilityHarvest) { - super(unit, true); + super(unit); this.abilityHarvest = abilityHarvest; } public CBehaviorHarvest reset(final CWidget target) { - innerReset(target); - this.abilityGoldMine = null; + innerReset(target, target instanceof CUnit); + this.abilityHarvest.setLastHarvestTarget(target); if (this.popoutFromMineTurnTick != 0) { // TODO this check is probably only for debug and should be removed after // extensive testing @@ -76,9 +79,9 @@ public class CBehaviorHarvest extends CAbstractRangedBehavior implements Ability this.unit.setHidden(true); this.unit.setInvulnerable(true); this.unit.setPaused(true); + this.unit.setAcceptingOrders(false); this.popoutFromMineTurnTick = this.simulation.getGameTurnTick() + (int) (abilityGoldMine.getMiningDuration() / WarsmashConstants.SIMULATION_STEP_TIME); - this.abilityGoldMine = abilityGoldMine; } else { // we are stuck waiting to mine, let's make sure we play stand animation @@ -103,6 +106,8 @@ public class CBehaviorHarvest extends CAbstractRangedBehavior implements Ability this.unit.setHidden(false); this.unit.setInvulnerable(false); this.unit.setPaused(false); + this.unit.setAcceptingOrders(true); + dropResources(); this.abilityHarvest.setCarriedResources(ResourceType.GOLD, goldMined); this.unit.getUnitAnimationListener().addSecondaryTag(SecondaryTag.GOLD); this.simulation.unitRepositioned(this.unit); @@ -110,15 +115,49 @@ public class CBehaviorHarvest extends CAbstractRangedBehavior implements Ability @Override public CBehavior accept(final CDestructable target) { - // TODO cut trees! - if (String.valueOf(target).length() > 5) { - return this.unit.pollNextOrderBehavior(this.simulation); + if ((this.abilityHarvest.getCarriedResourceType() != ResourceType.LUMBER) + || (this.abilityHarvest.getCarriedResourceAmount() < this.abilityHarvest.getLumberCapacity())) { + return this.unit.getAttackBehavior().reset(getHighlightOrderId(), this.abilityHarvest.getTreeAttack(), + target, false, this); } else { - return null; + // we have some LUMBER and we can't carry any more, time to return resources + return this.abilityHarvest.getBehaviorReturnResources().reset(this.simulation); } } + @Override + public void onHit(final AbilityTarget target, final float damage) { + if (this.abilityHarvest.getCarriedResourceType() != ResourceType.LUMBER) { + dropResources(); + } + this.abilityHarvest.setCarriedResources(ResourceType.LUMBER, + Math.min(this.abilityHarvest.getCarriedResourceAmount() + this.abilityHarvest.getDamageToTree(), + this.abilityHarvest.getLumberCapacity())); + this.unit.getUnitAnimationListener().addSecondaryTag(SecondaryTag.LUMBER); + } + + @Override + public void onLaunch() { + + } + + @Override + public CBehavior onFirstUpdateAfterBackswing(final CBehaviorAttack currentAttackBehavior) { + if (this.abilityHarvest.getCarriedResourceAmount() >= this.abilityHarvest.getLumberCapacity()) { + return this.abilityHarvest.getBehaviorReturnResources().reset(this.simulation); + } + return currentAttackBehavior; + } + + @Override + public CBehavior onFinish(final CSimulation game, final CUnit finishingUnit) { + if (this.abilityHarvest.getCarriedResourceAmount() >= this.abilityHarvest.getLumberCapacity()) { + return this.abilityHarvest.getBehaviorReturnResources().reset(this.simulation); + } + return updateOnInvalidTarget(game); + } + @Override public CBehavior accept(final CItem target) { return this.unit.pollNextOrderBehavior(this.simulation); @@ -129,6 +168,20 @@ public class CBehaviorHarvest extends CAbstractRangedBehavior implements Ability return this.target.visit(AbilityTargetStillAliveVisitor.INSTANCE); } + @Override + protected CBehavior updateOnInvalidTarget(final CSimulation simulation) { + if (this.target instanceof CDestructable) { + // wood + final CDestructable nearestTree = CBehaviorReturnResources.findNearestTree(this.unit, this.abilityHarvest, + simulation, (CDestructable) this.target); + if (nearestTree != null) { + this.target = nearestTree; + return this; + } + } + return this.unit.pollNextOrderBehavior(simulation); + } + @Override protected void resetBeforeMoving(final CSimulation simulation) { @@ -152,4 +205,20 @@ public class CBehaviorHarvest extends CAbstractRangedBehavior implements Ability return this.abilityHarvest.getGoldCapacity(); } + private void dropResources() { + if (this.abilityHarvest.getCarriedResourceType() != null) { + switch (this.abilityHarvest.getCarriedResourceType()) { + case FOOD: + throw new IllegalStateException("Unit used Harvest skill to carry FOOD resource!"); + case GOLD: + this.unit.getUnitAnimationListener().removeSecondaryTag(SecondaryTag.GOLD); + break; + case LUMBER: + this.unit.getUnitAnimationListener().removeSecondaryTag(SecondaryTag.LUMBER); + break; + } + } + this.abilityHarvest.setCarriedResources(null, 0); + } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/harvest/CBehaviorReturnResources.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/harvest/CBehaviorReturnResources.java index 31cef51..ed229c1 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/harvest/CBehaviorReturnResources.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/harvest/CBehaviorReturnResources.java @@ -5,6 +5,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CDestructable; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CItem; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.harvest.CAbilityHarvest; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.harvest.CAbilityReturnResources; @@ -22,7 +23,7 @@ public class CBehaviorReturnResources extends CAbstractRangedBehavior implements private CSimulation simulation; public CBehaviorReturnResources(final CUnit unit, final CAbilityHarvest abilityHarvest) { - super(unit, true); + super(unit); this.abilityHarvest = abilityHarvest; } @@ -32,7 +33,7 @@ public class CBehaviorReturnResources extends CAbstractRangedBehavior implements // TODO it is unconventional not to return self here return this.unit.pollNextOrderBehavior(simulation); } - innerReset(nearestDropoffPoint); + innerReset(nearestDropoffPoint, true); return this; } @@ -65,22 +66,44 @@ public class CBehaviorReturnResources extends CAbstractRangedBehavior implements final CAbilityReturnResources abilityReturnResources = (CAbilityReturnResources) ability; if (abilityReturnResources.accepts(this.abilityHarvest.getCarriedResourceType())) { final CPlayer player = this.simulation.getPlayer(this.unit.getPlayerIndex()); + CWidget nextTarget = null; switch (this.abilityHarvest.getCarriedResourceType()) { case FOOD: throw new IllegalStateException("Unit used Harvest skill to carry FOOD resource!"); case GOLD: player.setGold(player.getGold() + this.abilityHarvest.getCarriedResourceAmount()); this.unit.getUnitAnimationListener().removeSecondaryTag(SecondaryTag.GOLD); + if ((this.abilityHarvest.getLastHarvestTarget() != null) && this.abilityHarvest + .getLastHarvestTarget().visit(AbilityTargetStillAliveVisitor.INSTANCE)) { + nextTarget = this.abilityHarvest.getLastHarvestTarget(); + } + else { + nextTarget = findNearestMine(this.unit, this.simulation); + } break; case LUMBER: player.setLumber(player.getLumber() + this.abilityHarvest.getCarriedResourceAmount()); this.unit.getUnitAnimationListener().removeSecondaryTag(SecondaryTag.LUMBER); + if (this.abilityHarvest.getLastHarvestTarget() != null) { + if (this.abilityHarvest.getLastHarvestTarget() + .visit(AbilityTargetStillAliveVisitor.INSTANCE)) { + nextTarget = this.abilityHarvest.getLastHarvestTarget(); + } + else { + nextTarget = findNearestTree(this.unit, this.abilityHarvest, this.simulation, + this.abilityHarvest.getLastHarvestTarget()); + } + } + else { + nextTarget = findNearestTree(this.unit, this.abilityHarvest, this.simulation, this.unit); + } break; } + this.simulation.unitGainResourceEvent(this.unit, this.abilityHarvest.getCarriedResourceType(), + this.abilityHarvest.getCarriedResourceAmount()); this.abilityHarvest.setCarriedResources(null, 0); - final CUnit nearestMine = findNearestMine(this.unit, this.simulation); - if (nearestMine != null) { - return this.abilityHarvest.getBehaviorHarvest().reset(nearestMine); + if (nextTarget != null) { + return this.abilityHarvest.getBehaviorHarvest().reset(nextTarget); } return this.unit.pollNextOrderBehavior(this.simulation); } @@ -102,18 +125,17 @@ public class CBehaviorReturnResources extends CAbstractRangedBehavior implements @Override protected boolean checkTargetStillValid(final CSimulation simulation) { - final boolean aliveCheck = this.target.visit(AbilityTargetStillAliveVisitor.INSTANCE); - if (!aliveCheck) { - final CUnit nearestDropoff = findNearestDropoffPoint(simulation); - if (nearestDropoff == null) { - return false; - } - else { - this.target = nearestDropoff; - return true; - } + return this.target.visit(AbilityTargetStillAliveVisitor.INSTANCE); + } + + @Override + protected CBehavior updateOnInvalidTarget(final CSimulation simulation) { + final CUnit nearestDropoff = findNearestDropoffPoint(simulation); + if (nearestDropoff != null) { + this.target = nearestDropoff; + return this; } - return true; + return this.unit.pollNextOrderBehavior(simulation); } @Override @@ -176,6 +198,24 @@ public class CBehaviorReturnResources extends CAbstractRangedBehavior implements return nearestMine; } + public static CDestructable findNearestTree(final CUnit worker, final CAbilityHarvest abilityHarvest, + final CSimulation simulation, final CWidget toObject) { + CDestructable nearestMine = null; + double nearestMineDistance = Float.MAX_VALUE; + for (final CDestructable unit : simulation.getDestructables()) { + if (unit.canBeTargetedBy(simulation, worker, abilityHarvest.getTreeAttack().getTargetsAllowed())) { + // TODO maybe use distance squared, problem is that we're using this + // inefficient more complex distance function on unit + final double distance = unit.distanceSquaredNoCollision(toObject); + if (distance < nearestMineDistance) { + nearestMineDistance = distance; + nearestMine = unit; + } + } + } + return nearestMine; + } + @Override public void begin(final CSimulation game) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttack.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttack.java index b463192..989f9f0 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttack.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttack.java @@ -198,5 +198,6 @@ public abstract class CUnitAttack { return this.maxDamage; } - public abstract void launch(CSimulation simulation, CUnit unit, AbilityTarget target, float damage); + public abstract void launch(CSimulation simulation, CUnit unit, AbilityTarget target, float damage, + CUnitAttackListener attackListener); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackInstant.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackInstant.java index 131d8f7..934940f 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackInstant.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackInstant.java @@ -34,11 +34,14 @@ public class CUnitAttackInstant extends CUnitAttack { } @Override - public void launch(final CSimulation simulation, final CUnit unit, final AbilityTarget target, final float damage) { + public void launch(final CSimulation simulation, final CUnit unit, final AbilityTarget target, final float damage, + final CUnitAttackListener attackListener) { + attackListener.onLaunch(); final CWidget widget = target.visit(AbilityTargetWidgetVisitor.INSTANCE); if (widget != null) { simulation.createInstantAttackEffect(unit, this, widget); widget.damage(simulation, unit, getAttackType(), getWeaponSound(), damage); + attackListener.onHit(target, damage); } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackListener.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackListener.java new file mode 100644 index 0000000..e833fc7 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackListener.java @@ -0,0 +1,9 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget; + +public interface CUnitAttackListener { + void onLaunch(); + + void onHit(AbilityTarget target, float damage); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissile.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissile.java index bddceff..3b503d2 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissile.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissile.java @@ -65,16 +65,20 @@ public class CUnitAttackMissile extends CUnitAttack { } @Override - public void launch(final CSimulation simulation, final CUnit unit, final AbilityTarget target, final float damage) { + public void launch(final CSimulation simulation, final CUnit unit, final AbilityTarget target, final float damage, + final CUnitAttackListener attackListener) { + attackListener.onLaunch(); simulation.createProjectile(unit, unit.getX(), unit.getY(), (float) Math.toRadians(unit.getFacing()), this, - target, damage, 0); + target, damage, 0, attackListener); } public void doDamage(final CSimulation cSimulation, final CUnit source, final AbilityTarget target, - final float damage, final float x, final float y, final int bounceIndex) { + final float damage, final float x, final float y, final int bounceIndex, + final CUnitAttackListener attackListener) { final CWidget widget = target.visit(AbilityTargetWidgetVisitor.INSTANCE); if (widget != null) { widget.damage(cSimulation, source, getAttackType(), getWeaponSound(), damage); + attackListener.onHit(target, damage); } } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileBounce.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileBounce.java index c973afd..4fcb82f 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileBounce.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileBounce.java @@ -54,14 +54,15 @@ public class CUnitAttackMissileBounce extends CUnitAttackMissile { @Override public void doDamage(final CSimulation cSimulation, final CUnit source, final AbilityTarget target, - final float damage, final float x, final float y, final int bounceIndex) { - super.doDamage(cSimulation, source, target, damage, x, y, bounceIndex); + final float damage, final float x, final float y, final int bounceIndex, + final CUnitAttackListener attackListener) { + super.doDamage(cSimulation, source, target, damage, x, y, bounceIndex, attackListener); final CWidget widget = target.visit(AbilityTargetWidgetVisitor.INSTANCE); if (widget != null) { final int nextBounceIndex = bounceIndex + 1; if (nextBounceIndex != this.maximumNumberOfTargets) { BounceMissileConsumer.INSTANCE.nextBounce(cSimulation, source, widget, this, x, y, damage, - nextBounceIndex); + nextBounceIndex, attackListener); } } } @@ -77,11 +78,12 @@ public class CUnitAttackMissileBounce extends CUnitAttackMissile { private float y; private float damage; private int bounceIndex; + private CUnitAttackListener attackListener; private boolean launched = false; public void nextBounce(final CSimulation simulation, final CUnit source, final CWidget target, final CUnitAttackMissileBounce attack, final float x, final float y, final float damage, - final int bounceIndex) { + final int bounceIndex, final CUnitAttackListener attackListener) { this.simulation = simulation; this.source = source; this.target = target; @@ -90,6 +92,7 @@ public class CUnitAttackMissileBounce extends CUnitAttackMissile { this.y = y; this.damage = damage; this.bounceIndex = bounceIndex; + this.attackListener = attackListener; this.launched = false; final float doubleMaxArea = attack.areaOfEffectFullDamage + (this.simulation.getGameplayConstants().getCloseEnoughRange() * 2); @@ -112,7 +115,7 @@ public class CUnitAttackMissileBounce extends CUnitAttackMissile { final float dy = enumUnit.getY() - this.y; final float angle = (float) Math.atan2(dy, dx); this.simulation.createProjectile(this.source, this.x, this.y, angle, this.attack, enumUnit, - this.damage * (1.0f - this.attack.damageLossFactor), this.bounceIndex); + this.damage * (1.0f - this.attack.damageLossFactor), this.bounceIndex, this.attackListener); this.launched = true; return true; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileSplash.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileSplash.java index 5f0ae91..017cbda 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileSplash.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileSplash.java @@ -94,10 +94,12 @@ public class CUnitAttackMissileSplash extends CUnitAttackMissile { @Override public void doDamage(final CSimulation cSimulation, final CUnit source, final AbilityTarget target, - final float damage, final float x, final float y, final int bounceIndex) { - SplashDamageConsumer.INSTANCE.doDamage(cSimulation, source, target, this, x, y, damage); + final float damage, final float x, final float y, final int bounceIndex, + final CUnitAttackListener attackListener) { + SplashDamageConsumer.INSTANCE.doDamage(cSimulation, source, target, this, x, y, damage, attackListener); if ((getWeaponType() != CWeaponType.ARTILLERY) && !SplashDamageConsumer.INSTANCE.hitTarget) { - super.doDamage(cSimulation, source, target, damage * this.damageFactorSmall, x, y, bounceIndex); + super.doDamage(cSimulation, source, target, damage * this.damageFactorSmall, x, y, bounceIndex, + attackListener); } } @@ -111,10 +113,12 @@ public class CUnitAttackMissileSplash extends CUnitAttackMissile { private float x; private float y; private float damage; + private CUnitAttackListener attackListener; private boolean hitTarget; public void doDamage(final CSimulation simulation, final CUnit source, final AbilityTarget target, - final CUnitAttackMissileSplash attack, final float x, final float y, final float damage) { + final CUnitAttackMissileSplash attack, final float x, final float y, final float damage, + final CUnitAttackListener attackListener) { this.simulation = simulation; this.source = source; this.target = target; @@ -122,6 +126,7 @@ public class CUnitAttackMissileSplash extends CUnitAttackMissile { this.x = x; this.y = y; this.damage = damage; + this.attackListener = attackListener; this.hitTarget = false; final float doubleMaxArea = (attack.areaOfEffectSmallDamage) * 2; final float maxArea = doubleMaxArea / 2; @@ -136,14 +141,17 @@ public class CUnitAttackMissileSplash extends CUnitAttackMissile { if (distance <= (this.attack.areaOfEffectFullDamage)) { enumUnit.damage(this.simulation, this.source, this.attack.getAttackType(), this.attack.getWeaponSound(), this.damage); + this.attackListener.onHit(enumUnit, this.damage); } else if (distance <= (this.attack.areaOfEffectMediumDamage)) { enumUnit.damage(this.simulation, this.source, this.attack.getAttackType(), this.attack.getWeaponSound(), this.damage * this.attack.damageFactorMedium); + this.attackListener.onHit(enumUnit, this.damage); } else if (distance <= (this.attack.areaOfEffectSmallDamage)) { enumUnit.damage(this.simulation, this.source, this.attack.getAttackType(), this.attack.getWeaponSound(), this.damage * this.attack.damageFactorSmall); + this.attackListener.onHit(enumUnit, this.damage); } if (enumUnit == this.target) { this.hitTarget = true; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackNormal.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackNormal.java index 60d8375..2ce888e 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackNormal.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackNormal.java @@ -24,10 +24,13 @@ public class CUnitAttackNormal extends CUnitAttack { } @Override - public void launch(final CSimulation simulation, final CUnit unit, final AbilityTarget target, final float damage) { + public void launch(final CSimulation simulation, final CUnit unit, final AbilityTarget target, final float damage, + final CUnitAttackListener attackListener) { + attackListener.onLaunch(); final CWidget widget = target.visit(AbilityTargetWidgetVisitor.INSTANCE); if (widget != null) { widget.damage(simulation, unit, getAttackType(), getWeaponSound(), damage); + attackListener.onHit(target, damage); } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/projectile/CAttackProjectile.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/projectile/CAttackProjectile.java index 23df874..58013db 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/projectile/CAttackProjectile.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/projectile/CAttackProjectile.java @@ -5,6 +5,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackListener; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackMissile; public class CAttackProjectile { @@ -19,9 +20,11 @@ public class CAttackProjectile { private final float damage; private final CUnitAttackMissile unitAttack; private final int bounceIndex; + private final CUnitAttackListener attackListener; public CAttackProjectile(final float x, final float y, final float speed, final AbilityTarget target, - final CUnit source, final float damage, final CUnitAttackMissile unitAttack, final int bounceIndex) { + final CUnit source, final float damage, final CUnitAttackMissile unitAttack, final int bounceIndex, + final CUnitAttackListener attackListener) { this.x = x; this.y = y; this.speed = speed; @@ -30,6 +33,7 @@ public class CAttackProjectile { this.damage = damage; this.unitAttack = unitAttack; this.bounceIndex = bounceIndex; + this.attackListener = attackListener; this.initialTargetX = target.getX(); this.initialTargetY = target.getY(); } @@ -60,7 +64,7 @@ public class CAttackProjectile { if (done && !this.done) { this.unitAttack.doDamage(cSimulation, this.source, this.target, this.damage, this.x, this.y, - this.bounceIndex); + this.bounceIndex, this.attackListener); this.done = true; } return this.done; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java index 2ebed45..b1eb8bc 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java @@ -9,12 +9,14 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackInstant; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackListener; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackMissile; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.projectile.CAttackProjectile; public interface SimulationRenderController { CAttackProjectile createAttackProjectile(CSimulation simulation, float launchX, float launchY, float launchFacing, - CUnit source, CUnitAttackMissile attack, AbilityTarget target, float damage, int bounceIndex); + CUnit source, CUnitAttackMissile attack, AbilityTarget target, float damage, int bounceIndex, + CUnitAttackListener attackListener); CUnit createUnit(CSimulation simulation, final War3ID typeId, final int playerIndex, final float x, final float y, final float facing); @@ -42,4 +44,6 @@ public interface SimulationRenderController { void spawnUnitReadySound(CUnit trainedUnit); void unitRepositioned(CUnit cUnit); + + void spawnGainResourceTextTag(CUnit gainingUnit, ResourceType resourceType, int amount); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java index 3a615d4..3aa3f32 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java @@ -22,6 +22,7 @@ import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.GlyphLayout; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator; +import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator.FreeTypeFontParameter; import com.badlogic.gdx.graphics.glutils.PixmapTextureData; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Vector2; @@ -65,6 +66,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.SecondaryTag; import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; import com.etheller.warsmash.viewer5.handlers.w3x.SplatModel; import com.etheller.warsmash.viewer5.handlers.w3x.SplatModel.SplatMover; +import com.etheller.warsmash.viewer5.handlers.w3x.TextTag; import com.etheller.warsmash.viewer5.handlers.w3x.UnitSound; import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; import com.etheller.warsmash.viewer5.handlers.w3x.camera.CameraPreset; @@ -252,6 +254,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma private MdxModel waypointModel; private final List waypointModelInstances = new ArrayList<>(); private List selectedUnits; + private BitmapFont textTagFont; public MeleeUI(final DataSource dataSource, final ExtendViewport uiViewport, final FreeTypeFontGenerator fontGenerator, final Scene uiScene, final Scene portraitScene, @@ -634,9 +637,14 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma War3MapViewer.mdx(this.rootFrame.getSkinField("WaypointIndicator")), this.war3MapViewer.mapPathSolver, this.war3MapViewer.solverParams); + final FreeTypeFontParameter fontParam = new FreeTypeFontParameter(); + fontParam.size = (int) GameUI.convertY(this.uiViewport, 0.012f); + this.textTagFont = this.fontGenerator.generateFont(fontParam); + this.rootFrame.positionBounds(this.rootFrame, this.uiViewport); selectUnit(null); + } @Override @@ -834,6 +842,14 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.meleeUIMinimap.render(batch, this.war3MapViewer.units); this.timeIndicator.setFrameByRatio(this.war3MapViewer.simulation.getGameTimeOfDay() / this.war3MapViewer.simulation.getGameplayConstants().getGameDayHours()); + for (final TextTag textTag : this.war3MapViewer.textTags) { + this.war3MapViewer.worldScene.camera.worldToScreen(screenCoordsVector, textTag.getPosition()); + final Vector2 unprojected = this.uiViewport.unproject(screenCoordsVector); + this.textTagFont.setColor(textTag.getColor()); + glyphLayout.setText(this.textTagFont, textTag.getText()); + this.textTagFont.draw(batch, textTag.getText(), unprojected.x - (glyphLayout.width / 2), + (unprojected.y - (glyphLayout.height / 2))); + } } public void portraitTalk() { From 7cf7f30907f4b69c40402f8ea8f71438b7019883 Mon Sep 17 00:00:00 2001 From: Retera Date: Sat, 6 Feb 2021 12:27:47 -0500 Subject: [PATCH 100/116] Checkpoint after Furbolg battle --- core/assets/warsmash.ini | 2 + .../etheller/warsmash/WarsmashGdxMapGame.java | 4 +- .../viewer5/handlers/w3x/SequenceUtils.java | 17 +++- .../viewer5/handlers/w3x/TextTag.java | 7 +- .../viewer5/handlers/w3x/War3MapViewer.java | 23 +++++ .../w3x/simulation/CDestructable.java | 9 ++ .../w3x/simulation/CDestructableType.java | 6 +- .../handlers/w3x/simulation/CSimulation.java | 4 + .../handlers/w3x/simulation/CUnit.java | 20 ++-- .../w3x/simulation/abilities/CAbility.java | 2 + .../simulation/abilities/CAbilityAttack.java | 4 + .../simulation/abilities/CAbilityGeneric.java | 4 + .../simulation/abilities/CAbilityMove.java | 4 + .../build/CAbilityBuildInProgress.java | 6 ++ .../abilities/build/CAbilityHumanBuild.java | 9 +- .../abilities/build/CAbilityNagaBuild.java | 9 +- .../abilities/build/CAbilityNeutralBuild.java | 9 +- .../build/CAbilityNightElfBuild.java | 9 +- .../abilities/build/CAbilityOrcBuild.java | 11 +++ .../abilities/build/CAbilityUndeadBuild.java | 9 +- .../abilities/combat/CAbilityColdArrows.java | 4 + .../abilities/harvest/CAbilityHarvest.java | 4 + .../harvest/CAbilityReturnResources.java | 4 + .../abilities/mine/CAbilityGoldMine.java | 4 + .../abilities/queue/CAbilityQueue.java | 4 + .../abilities/queue/CAbilityRally.java | 4 + .../abilities/test/CAbilityChannelTest.java | 91 +++++++++++++++++++ .../CAbilityTypeDefinitionChannelTest.java | 33 +++++++ .../types/impl/CAbilityTypeChannelTest.java | 23 +++++ .../CAbilityTypeChannelTestLevelData.java | 20 ++++ .../simulation/behaviors/CBehaviorAttack.java | 1 - .../simulation/behaviors/CBehaviorMove.java | 15 ++- .../behaviors/build/CBehaviorOrcBuild.java | 78 +++++++++------- .../behaviors/harvest/CBehaviorHarvest.java | 5 +- .../harvest/CBehaviorReturnResources.java | 3 +- .../behaviors/test/CBehaviorChannelTest.java | 49 ++++++++++ .../w3x/simulation/data/CAbilityData.java | 2 + .../simulation/data/CDestructableData.java | 4 +- .../util/SimulationRenderController.java | 2 + .../viewer5/handlers/w3x/ui/MeleeUI.java | 16 ++-- resources/UI/FrameDef/SmashUI/ToolTip.fdf | 5 +- 41 files changed, 468 insertions(+), 71 deletions(-) create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/test/CAbilityChannelTest.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/definitions/impl/CAbilityTypeDefinitionChannelTest.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeChannelTest.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeChannelTestLevelData.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/test/CBehaviorChannelTest.java diff --git a/core/assets/warsmash.ini b/core/assets/warsmash.ini index f33461d..1861dae 100644 --- a/core/assets/warsmash.ini +++ b/core/assets/warsmash.ini @@ -44,3 +44,5 @@ FilePath="PeonStartingBase_Simple.w3x" //FilePath="FarseerHoldPositionTest.w3x" //FilePath="Ramps.w3m" //FilePath="V1\Farm.w3x" +//FilePath="PenguinWorld.w3x" +//FilePath="Maps\FrozenThrone\Campaign\UndeadX09.w3x" diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java index e5963d0..43734ac 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java @@ -58,7 +58,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.SettableCommandErro public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProvider, InputProcessor { private static final boolean ENABLE_AUDIO = true; - private static final boolean ENABLE_MUSIC = true; + private static final boolean ENABLE_MUSIC = false; private DataSource codebase; private War3MapViewer viewer; private final Rectangle tempRect = new Rectangle(); @@ -217,7 +217,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv final String musicField = rootFrame.getSkinField("Music_V1"); final String[] musics = musicField.split(";"); String musicPath = musics[(int) (Math.random() * musics.length)]; - if (false) { + if (true) { musicPath = "Sound\\Music\\mp3Music\\OrcTheme.mp3"; } final Music music = Gdx.audio.newMusic( diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SequenceUtils.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SequenceUtils.java index edeb0d3..b6f1111 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SequenceUtils.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SequenceUtils.java @@ -18,6 +18,7 @@ public class SequenceUtils { public static final EnumSet TALK = EnumSet.of(SecondaryTag.TALK); public static final EnumSet BONE = EnumSet.of(SecondaryTag.BONE); public static final EnumSet HIT = EnumSet.of(SecondaryTag.HIT); + public static final EnumSet SPELL = EnumSet.of(SecondaryTag.SPELL); private static final StandSequenceComparator STAND_SEQUENCE_COMPARATOR = new StandSequenceComparator(); private static final SecondaryTagSequenceComparator SECONDARY_TAG_SEQUENCE_COMPARATOR = new SecondaryTagSequenceComparator( @@ -44,8 +45,9 @@ public class SequenceUtils { for (int i = 0, l = sequences.size(); i < l; i++) { final Sequence sequence = sequences.get(i); - if (sequence.getPrimaryTags().contains(type) && (sequence.getSecondaryTags().containsAll(tags) - && tags.containsAll(sequence.getSecondaryTags()))) { + if ((sequence.getPrimaryTags().contains(type) || (type == null)) + && (sequence.getSecondaryTags().containsAll(tags) + && tags.containsAll(sequence.getSecondaryTags()))) { filtered.add(new IndexedSequence(sequence, i)); } } @@ -109,7 +111,7 @@ public class SequenceUtils { int fallbackTagsMatchCount = 0; for (int i = 0, l = sequences.size(); i < l; i++) { final Sequence sequence = sequences.get(i); - if (sequence.getPrimaryTags().contains(type)) { + if (sequence.getPrimaryTags().contains(type) || (type == null)) { final int matchCount = matchCount(tags, sequence.getSecondaryTags()); if (matchCount > fallbackTagsMatchCount) { fallbackTags = sequence.getSecondaryTags(); @@ -120,7 +122,7 @@ public class SequenceUtils { if (fallbackTags == null) { for (int i = 0, l = sequences.size(); i < l; i++) { final Sequence sequence = sequences.get(i); - if (sequence.getPrimaryTags().contains(type)) { + if (sequence.getPrimaryTags().contains(type) || (type == null)) { if ((fallbackTags == null) || (sequence.getSecondaryTags().size() < fallbackTags.size()) || ((sequence.getSecondaryTags().size() == fallbackTags.size()) && (SecondaryTagSequenceComparator.getTagsOrdinal(sequence.getSecondaryTags(), @@ -281,7 +283,12 @@ public class SequenceUtils { return sequence.sequence; } else { - return null; + if (animationName == null) { + return null; + } + else { + return randomSequence(target, null, secondaryAnimationTags, allowRarityVariations); + } } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/TextTag.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/TextTag.java index 9965389..4533d28 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/TextTag.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/TextTag.java @@ -5,6 +5,7 @@ import com.badlogic.gdx.math.Vector3; public class TextTag { private final Vector3 position; + private float screenCoordsZHeight; private final String text; private final Color color; private int lifetime = 0; @@ -17,7 +18,7 @@ public class TextTag { } public boolean update() { - this.position.z += 1.0f; + this.screenCoordsZHeight += 1.0f; this.lifetime++; return this.lifetime > 196; } @@ -33,4 +34,8 @@ public class TextTag { public String getText() { return this.text; } + + public float getScreenCoordsZHeight() { + return this.screenCoordsZHeight; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index 3a9f082..900c6b8 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -628,6 +628,24 @@ public class War3MapViewer extends AbstractMdxModelViewer { } } + @Override + public void spawnEffectOnUnit(final CUnit unit, final String effectPath) { + final RenderUnit renderUnit = War3MapViewer.this.unitToRenderPeer.get(unit); + final MdxModel spawnedEffectModel = (MdxModel) load(mdx(effectPath), PathSolver.DEFAULT, null); + if (spawnedEffectModel != null) { + final MdxComplexInstance modelInstance = (MdxComplexInstance) spawnedEffectModel + .addInstance(); + modelInstance.setTeamColor(unit.getPlayerIndex()); + modelInstance.setLocation(renderUnit.location); + modelInstance.setScene(War3MapViewer.this.worldScene); + SequenceUtils.randomBirthSequence(modelInstance); + War3MapViewer.this.projectiles + .add(new RenderAttackInstant(modelInstance, War3MapViewer.this, + (float) Math.toRadians(renderUnit.getSimulationUnit().getFacing()))); + } + + } + @Override public void spawnUnitReadySound(final CUnit trainedUnit) { final RenderUnit renderPeer = War3MapViewer.this.unitToRenderPeer.get(trainedUnit); @@ -823,6 +841,7 @@ public class War3MapViewer extends AbstractMdxModelViewer { renderDestructable.walkableBounds = renderDestructableBounds; } this.widgets.add(renderDestructable); + this.destructableToRenderPeer.put(simulationDestructable, renderDestructable); } else { this.doodads.add(new RenderDoodad(this, model, row, doodad, type, maxPitch, maxRoll)); @@ -1647,6 +1666,10 @@ public class War3MapViewer extends AbstractMdxModelViewer { return this.unitToRenderPeer.get(unit); } + public RenderDestructable getRenderPeer(final CDestructable dest) { + return this.destructableToRenderPeer.get(dest); + } + private static final class QuadtreeIntersectorFindsWalkableRenderHeight implements QuadtreeIntersector { private float z; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CDestructable.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CDestructable.java index e4b2ab6..d5f1e8f 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CDestructable.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CDestructable.java @@ -37,7 +37,16 @@ public class CDestructable extends CWidget { @Override public void damage(final CSimulation simulation, final CUnit source, final CAttackType attackType, final String weaponType, final float damage) { + final boolean wasDead = isDead(); this.life -= damage; + if (!wasDead && isDead()) { + if (this.pathingInstance != null) { + this.pathingInstance.remove(); + } + if (this.pathingInstanceDeath != null) { + this.pathingInstanceDeath.add(); + } + } simulation.destructableDamageEvent(this, weaponType, this.destType.getArmorType()); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CDestructableType.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CDestructableType.java index 4cb7182..c13bea6 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CDestructableType.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CDestructableType.java @@ -8,14 +8,14 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; public class CDestructableType { private final String name; - private final int life; + private final float life; private final EnumSet targetedAs; private final String armorType; private final int buildTime; private final BufferedImage pathingPixelMap; private final BufferedImage pathingDeathPixelMap; - public CDestructableType(final String name, final int life, final EnumSet targetedAs, + public CDestructableType(final String name, final float life, final EnumSet targetedAs, final String armorType, final int buildTime, final BufferedImage pathingPixelMap, final BufferedImage pathingDeathPixelMap) { this.name = name; @@ -31,7 +31,7 @@ public class CDestructableType { return this.name; } - public int getLife() { + public float getLife() { return this.life; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java index 6f9da3f..58cc63d 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java @@ -324,4 +324,8 @@ public class CSimulation { } return null; } + + public void createEffectOnUnit(final CUnit unit, final String effectPath) { + this.simulationRenderController.spawnEffectOnUnit(unit, effectPath); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java index 80998ca..e579099 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java @@ -405,6 +405,11 @@ public class CUnit extends CWidget { if (this.currentBehavior != null) { this.currentBehavior.begin(game); } + for (final COrder queuedOrder : this.orderQueue) { + final int abilityHandleId = queuedOrder.getAbilityHandleId(); + final CAbility ability = game.getAbility(abilityHandleId); + ability.onCancelFromQueue(game, this, queuedOrder.getOrderId()); + } this.orderQueue.clear(); this.stateNotifier.ordersChanged(); this.stateNotifier.waypointsChanged(); @@ -1130,6 +1135,7 @@ public class CUnit extends CWidget { @Override public Void accept(final AbilityPointTarget target) { + CAbility abilityToUse = null; for (final CAbility ability : this.trainedUnit.getAbilities()) { ability.checkCanUse(this.game, this.trainedUnit, this.rallyOrderId, BooleanAbilityActivationReceiver.INSTANCE); @@ -1138,12 +1144,12 @@ public class CUnit extends CWidget { .getInstance().reset(); ability.checkCanTarget(this.game, this.trainedUnit, this.rallyOrderId, target, targetCheckReceiver); if (targetCheckReceiver.isTargetable()) { - this.trainedUnit.order(this.game, - new COrderTargetPoint(ability.getHandleId(), this.rallyOrderId, target, false), false); - return null; + abilityToUse = ability; } } } + this.trainedUnit.order(this.game, + new COrderTargetPoint(abilityToUse.getHandleId(), this.rallyOrderId, target, false), false); return null; } @@ -1154,6 +1160,7 @@ public class CUnit extends CWidget { private Void acceptWidget(final CSimulation game, final CUnit trainedUnit, final int rallyOrderId, final CWidget target) { + CAbility abilityToUse = null; for (final CAbility ability : trainedUnit.getAbilities()) { ability.checkCanUse(game, trainedUnit, rallyOrderId, BooleanAbilityActivationReceiver.INSTANCE); if (BooleanAbilityActivationReceiver.INSTANCE.isOk()) { @@ -1161,12 +1168,13 @@ public class CUnit extends CWidget { .getInstance().reset(); ability.checkCanTarget(game, trainedUnit, rallyOrderId, target, targetCheckReceiver); if (targetCheckReceiver.isTargetable()) { - trainedUnit.order(game, new COrderTargetWidget(ability.getHandleId(), rallyOrderId, - target.getHandleId(), false), false); - return null; + abilityToUse = ability; } } } + trainedUnit.order(game, + new COrderTargetWidget(abilityToUse.getHandleId(), rallyOrderId, target.getHandleId(), false), + false); return null; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbility.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbility.java index a566f7c..612c221 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbility.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbility.java @@ -15,6 +15,8 @@ public interface CAbility extends CAbilityView { void onTick(CSimulation game, CUnit unit); + void onCancelFromQueue(CSimulation game, CUnit unit, int orderId); + /* return false to not do anything, such as for toggling autocast */ boolean checkBeforeQueue(CSimulation game, CUnit caster, int orderId); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java index 8dcbe0d..304f933 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java @@ -168,4 +168,8 @@ public class CAbilityAttack extends AbstractCAbility { return visitor.accept(this); } + @Override + public void onCancelFromQueue(final CSimulation game, final CUnit unit, final int orderId) { + } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityGeneric.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityGeneric.java index 65945b3..2debd14 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityGeneric.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityGeneric.java @@ -85,4 +85,8 @@ public class CAbilityGeneric extends AbstractCAbility { public CBehavior beginNoTarget(final CSimulation game, final CUnit caster, final int orderId) { return caster.pollNextOrderBehavior(game); } + + @Override + public void onCancelFromQueue(final CSimulation game, final CUnit unit, final int orderId) { + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityMove.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityMove.java index 52cb431..1f2d1b7 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityMove.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityMove.java @@ -126,4 +126,8 @@ public class CAbilityMove extends AbstractCAbility { return visitor.accept(this); } + @Override + public void onCancelFromQueue(final CSimulation game, final CUnit unit, final int orderId) { + } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityBuildInProgress.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityBuildInProgress.java index e161795..4b38b76 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityBuildInProgress.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityBuildInProgress.java @@ -90,4 +90,10 @@ public class CAbilityBuildInProgress extends AbstractCAbility { return visitor.accept(this); } + @Override + public void onCancelFromQueue(final CSimulation game, final CUnit unit, final int orderId) { + // TODO Auto-generated method stub + + } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityHumanBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityHumanBuild.java index 5646b88..b6f420f 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityHumanBuild.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityHumanBuild.java @@ -42,7 +42,8 @@ public class CAbilityHumanBuild extends AbstractCAbilityBuild { } @Override - public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final AbilityPointTarget point) { + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, + final AbilityPointTarget point) { // caster.getMoveBehavior().reset(point.x, point.y, ) return null; } @@ -58,4 +59,10 @@ public class CAbilityHumanBuild extends AbstractCAbilityBuild { return visitor.accept(this); } + @Override + public void onCancelFromQueue(final CSimulation game, final CUnit unit, final int orderId) { + // TODO Auto-generated method stub + + } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityNagaBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityNagaBuild.java index 1c353fc..57587a6 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityNagaBuild.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityNagaBuild.java @@ -36,7 +36,8 @@ public class CAbilityNagaBuild extends AbstractCAbilityBuild { } @Override - public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final AbilityPointTarget point) { + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, + final AbilityPointTarget point) { // TODO Auto-generated method stub return null; } @@ -50,4 +51,10 @@ public class CAbilityNagaBuild extends AbstractCAbilityBuild { public int getBaseOrderId() { return OrderIds.nagabuild; } + + @Override + public void onCancelFromQueue(final CSimulation game, final CUnit unit, final int orderId) { + // TODO Auto-generated method stub + + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityNeutralBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityNeutralBuild.java index 1f26807..bbfb9de 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityNeutralBuild.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityNeutralBuild.java @@ -36,7 +36,8 @@ public class CAbilityNeutralBuild extends AbstractCAbilityBuild { } @Override - public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final AbilityPointTarget point) { + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, + final AbilityPointTarget point) { // TODO Auto-generated method stub return null; } @@ -50,4 +51,10 @@ public class CAbilityNeutralBuild extends AbstractCAbilityBuild { public int getBaseOrderId() { return OrderIds.buildmenu; } + + @Override + public void onCancelFromQueue(final CSimulation game, final CUnit unit, final int orderId) { + // TODO Auto-generated method stub + + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityNightElfBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityNightElfBuild.java index 5d837e0..a33a558 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityNightElfBuild.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityNightElfBuild.java @@ -42,7 +42,8 @@ public class CAbilityNightElfBuild extends AbstractCAbilityBuild { } @Override - public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final AbilityPointTarget point) { + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, + final AbilityPointTarget point) { // TODO Auto-generated method stub return null; } @@ -58,4 +59,10 @@ public class CAbilityNightElfBuild extends AbstractCAbilityBuild { return visitor.accept(this); } + @Override + public void onCancelFromQueue(final CSimulation game, final CUnit unit, final int orderId) { + // TODO Auto-generated method stub + + } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityOrcBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityOrcBuild.java index 37a9f57..9b4d474 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityOrcBuild.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityOrcBuild.java @@ -65,6 +65,17 @@ public class CAbilityOrcBuild extends AbstractCAbilityBuild { return this.buildBehavior.reset(point, orderId, getBaseOrderId()); } + @Override + public void onCancelFromQueue(final CSimulation game, final CUnit unit, final int orderId) { + final CPlayer player = game.getPlayer(unit.getPlayerIndex()); + final War3ID orderIdAsRawtype = new War3ID(orderId); + final CUnitType unitType = game.getUnitData().getUnitType(orderIdAsRawtype); + player.refundFor(unitType); + if (unitType.getFoodUsed() != 0) { + player.setFoodUsed(player.getFoodUsed() - unitType.getFoodUsed()); + } + } + @Override public CBehavior beginNoTarget(final CSimulation game, final CUnit caster, final int orderId) { return caster.pollNextOrderBehavior(game); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityUndeadBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityUndeadBuild.java index d49e55f..b5a6276 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityUndeadBuild.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityUndeadBuild.java @@ -42,7 +42,8 @@ public class CAbilityUndeadBuild extends AbstractCAbilityBuild { } @Override - public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final AbilityPointTarget point) { + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, + final AbilityPointTarget point) { // TODO Auto-generated method stub return null; } @@ -58,4 +59,10 @@ public class CAbilityUndeadBuild extends AbstractCAbilityBuild { return visitor.accept(this); } + @Override + public void onCancelFromQueue(final CSimulation game, final CUnit unit, final int orderId) { + // TODO Auto-generated method stub + + } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/combat/CAbilityColdArrows.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/combat/CAbilityColdArrows.java index b68f899..08da353 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/combat/CAbilityColdArrows.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/combat/CAbilityColdArrows.java @@ -128,4 +128,8 @@ public class CAbilityColdArrows extends AbstractCAbility { public CBehavior beginNoTarget(final CSimulation game, final CUnit caster, final int orderId) { return caster.pollNextOrderBehavior(game); } + + @Override + public void onCancelFromQueue(final CSimulation game, final CUnit unit, final int orderId) { + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityHarvest.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityHarvest.java index 5a08e2e..c0d8620 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityHarvest.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityHarvest.java @@ -221,4 +221,8 @@ public class CAbilityHarvest extends AbstractGenericSingleIconActiveAbility { return this.lastHarvestTarget; } + @Override + public void onCancelFromQueue(final CSimulation game, final CUnit unit, final int orderId) { + } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityReturnResources.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityReturnResources.java index c069acc..1c7b08b 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityReturnResources.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityReturnResources.java @@ -79,6 +79,10 @@ public class CAbilityReturnResources extends AbstractGenericNoIconAbility { receiver.notAnActiveAbility(); } + @Override + public void onCancelFromQueue(final CSimulation game, final CUnit unit, final int orderId) { + } + public boolean accepts(final ResourceType resourceType) { return this.acceptedResourceTypes.contains(resourceType); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/mine/CAbilityGoldMine.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/mine/CAbilityGoldMine.java index b798673..3a28cc6 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/mine/CAbilityGoldMine.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/mine/CAbilityGoldMine.java @@ -109,6 +109,10 @@ public class CAbilityGoldMine extends AbstractGenericNoIconAbility { receiver.notAnActiveAbility(); } + @Override + public void onCancelFromQueue(final CSimulation game, final CUnit unit, final int orderId) { + } + public int getGold() { return this.gold; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityQueue.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityQueue.java index e868633..daae13f 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityQueue.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityQueue.java @@ -150,4 +150,8 @@ public final class CAbilityQueue extends AbstractCAbility { public T visit(final CAbilityVisitor visitor) { return visitor.accept(this); } + + @Override + public void onCancelFromQueue(final CSimulation game, final CUnit unit, final int orderId) { + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityRally.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityRally.java index 3609354..2ea18ab 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityRally.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityRally.java @@ -103,4 +103,8 @@ public class CAbilityRally extends AbstractCAbility { return OrderIds.setrally; } + @Override + public void onCancelFromQueue(final CSimulation game, final CUnit unit, final int orderId) { + } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/test/CAbilityChannelTest.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/test/CAbilityChannelTest.java new file mode 100644 index 0000000..db50488 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/test/CAbilityChannelTest.java @@ -0,0 +1,91 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.test; + +import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic.AbstractGenericSingleIconNoSmartActiveAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.test.CBehaviorChannelTest; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; + +public class CAbilityChannelTest extends AbstractGenericSingleIconNoSmartActiveAbility { + private CBehaviorChannelTest behaviorChannelTest; + private final float artDuration; + + public CAbilityChannelTest(final int handleId, final War3ID alias, final float artDuration) { + super(handleId, alias); + this.artDuration = artDuration; + } + + @Override + public int getBaseOrderId() { + return OrderIds.channel; + } + + @Override + public boolean isToggleOn() { + return false; + } + + @Override + public void onAdd(final CSimulation game, final CUnit unit) { + this.behaviorChannelTest = new CBehaviorChannelTest(unit, this.artDuration); + } + + @Override + public void onRemove(final CSimulation game, final CUnit unit) { + } + + @Override + public void onTick(final CSimulation game, final CUnit unit) { + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final CWidget target) { + return this.behaviorChannelTest; + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, + final AbilityPointTarget point) { + return this.behaviorChannelTest; + } + + @Override + public CBehavior beginNoTarget(final CSimulation game, final CUnit caster, final int orderId) { + return this.behaviorChannelTest; + } + + @Override + protected void innerCheckCanTarget(final CSimulation game, final CUnit unit, final int orderId, + final CWidget target, final AbilityTargetCheckReceiver receiver) { + receiver.orderIdNotAccepted(); + } + + @Override + protected void innerCheckCanTarget(final CSimulation game, final CUnit unit, final int orderId, + final AbilityPointTarget target, final AbilityTargetCheckReceiver receiver) { + receiver.orderIdNotAccepted(); + } + + @Override + protected void innerCheckCanTargetNoTarget(final CSimulation game, final CUnit unit, final int orderId, + final AbilityTargetCheckReceiver receiver) { + receiver.targetOk(null); + } + + @Override + protected void innerCheckCanUse(final CSimulation game, final CUnit unit, final int orderId, + final AbilityActivationReceiver receiver) { + receiver.useOk(); + } + + @Override + public void onCancelFromQueue(final CSimulation game, final CUnit unit, final int orderId) { + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/definitions/impl/CAbilityTypeDefinitionChannelTest.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/definitions/impl/CAbilityTypeDefinitionChannelTest.java new file mode 100644 index 0000000..6d4808a --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/definitions/impl/CAbilityTypeDefinitionChannelTest.java @@ -0,0 +1,33 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.definitions.impl; + +import java.util.EnumSet; +import java.util.List; + +import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; +import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.CAbilityType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.definitions.CAbilityTypeDefinition; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.impl.CAbilityTypeChannelTest; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.impl.CAbilityTypeChannelTestLevelData; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; + +public class CAbilityTypeDefinitionChannelTest extends AbstractCAbilityTypeDefinition + implements CAbilityTypeDefinition { + protected static final War3ID ART_DURATION = War3ID.fromString("Ncl4"); + + @Override + protected CAbilityTypeChannelTestLevelData createLevelData(final MutableGameObject abilityEditorData, + final int level) { + final String targetsAllowedAtLevelString = abilityEditorData.getFieldAsString(TARGETS_ALLOWED, level); + final float artDuration = abilityEditorData.getFieldAsFloat(ART_DURATION, level); + final EnumSet targetsAllowedAtLevel = CTargetType.parseTargetTypeSet(targetsAllowedAtLevelString); + return new CAbilityTypeChannelTestLevelData(targetsAllowedAtLevel, artDuration); + } + + @Override + protected CAbilityType innerCreateAbilityType(final War3ID alias, final MutableGameObject abilityEditorData, + final List levelData) { + return new CAbilityTypeChannelTest(alias, abilityEditorData.getCode(), levelData); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeChannelTest.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeChannelTest.java new file mode 100644 index 0000000..3359527 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeChannelTest.java @@ -0,0 +1,23 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.impl; + +import java.util.List; + +import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.test.CAbilityChannelTest; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.CAbilityType; + +public class CAbilityTypeChannelTest extends CAbilityType { + + public CAbilityTypeChannelTest(final War3ID alias, final War3ID code, + final List levelData) { + super(alias, code, levelData); + } + + @Override + public CAbility createAbility(final int handleId) { + final CAbilityTypeChannelTestLevelData levelData = getLevelData(0); + return new CAbilityChannelTest(handleId, getAlias(), levelData.getArtDuration()); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeChannelTestLevelData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeChannelTestLevelData.java new file mode 100644 index 0000000..0c30097 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeChannelTestLevelData.java @@ -0,0 +1,20 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.impl; + +import java.util.EnumSet; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.CAbilityTypeLevelData; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; + +public class CAbilityTypeChannelTestLevelData extends CAbilityTypeLevelData { + private final float artDuration; + + public CAbilityTypeChannelTestLevelData(final EnumSet targetsAllowed, final float artDuration) { + super(targetsAllowed); + this.artDuration = artDuration; + } + + public float getArtDuration() { + return this.artDuration; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java index a0e27a8..8e9d655 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java @@ -123,7 +123,6 @@ public class CBehaviorAttack extends CAbstractRangedBehavior { if ((this.backSwingTime != 0) && (currentTurnTick >= this.backSwingTime)) { this.backSwingTime = 0; - System.out.println("INTERRUPT AFTER BACKSWING"); return this.attackListener.onFirstUpdateAfterBackswing(this); } return this; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorMove.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorMove.java index 3255241..4c772ef 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorMove.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorMove.java @@ -45,6 +45,7 @@ public class CBehaviorMove implements CBehavior { private boolean pathfindingActive = false; private boolean firstPathfindJob = false; private boolean pathfindingFailedGiveUp; + private int giveUpUntilTurnTick; public CBehaviorMove reset(final int highlightOrderId, final AbilityTarget target) { target.visit(this.targetVisitingResetter.reset(highlightOrderId)); @@ -74,6 +75,7 @@ public class CBehaviorMove implements CBehavior { this.followUnit = null; this.firstUpdate = true; this.pathfindingFailedGiveUp = false; + this.giveUpUntilTurnTick = 0; } private void internalResetMove(final int highlightOrderId, final CUnit followUnit) { @@ -88,6 +90,7 @@ public class CBehaviorMove implements CBehavior { this.followUnit = followUnit; this.firstUpdate = true; this.pathfindingFailedGiveUp = false; + this.giveUpUntilTurnTick = 0; } @Override @@ -203,7 +206,9 @@ public class CBehaviorMove implements CBehavior { facing += angleToAdd; this.unit.setFacing(facing); } - if ((this.path != null) && !this.pathfindingActive && (absDelta < propulsionWindow)) { + final boolean blockedByGiveUpUntilTickDelay = simulation.getGameTurnTick() < this.giveUpUntilTurnTick; + if (!blockedByGiveUpUntilTickDelay && (this.path != null) && !this.pathfindingActive + && (absDelta < propulsionWindow)) { final float speedTick = speed * WarsmashConstants.SIMULATION_STEP_TIME; double continueDistance = speedTick; do { @@ -455,7 +460,13 @@ public class CBehaviorMove implements CBehavior { } } else if (this.path.isEmpty() || (this.searchCycles > 6)) { - this.pathfindingFailedGiveUp = true; + if (this.searchCycles > 9) { + this.pathfindingFailedGiveUp = true; + } + else { + this.giveUpUntilTurnTick = simulation.getGameTurnTick() + + (int) (5 / WarsmashConstants.SIMULATION_STEP_TIME); + } } } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java index b596e0d..20025df 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java @@ -19,6 +19,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayer; public class CBehaviorOrcBuild extends CAbstractRangedBehavior { private int highlightOrderId; private War3ID orderId; + private boolean unitCreated = false; public CBehaviorOrcBuild(final CUnit unit) { super(unit); @@ -27,6 +28,7 @@ public class CBehaviorOrcBuild extends CAbstractRangedBehavior { public CBehavior reset(final AbilityPointTarget target, final int orderId, final int highlightOrderId) { this.highlightOrderId = highlightOrderId; this.orderId = new War3ID(orderId); + this.unitCreated = false; return innerReset(target); } @@ -44,41 +46,44 @@ public class CBehaviorOrcBuild extends CAbstractRangedBehavior { @Override protected CBehavior update(final CSimulation simulation, final boolean withinRange) { - final CUnitType unitTypeToCreate = simulation.getUnitData().getUnitType(this.orderId); - final BufferedImage buildingPathingPixelMap = unitTypeToCreate.getBuildingPathingPixelMap(); - boolean buildLocationObstructed = false; - if (buildingPathingPixelMap != null) { - final EnumSet preventedPathingTypes = unitTypeToCreate.getPreventedPathingTypes(); - final EnumSet requiredPathingTypes = unitTypeToCreate.getRequiredPathingTypes(); + if (!this.unitCreated) { + this.unitCreated = true; + final CUnitType unitTypeToCreate = simulation.getUnitData().getUnitType(this.orderId); + final BufferedImage buildingPathingPixelMap = unitTypeToCreate.getBuildingPathingPixelMap(); + boolean buildLocationObstructed = false; + if (buildingPathingPixelMap != null) { + final EnumSet preventedPathingTypes = unitTypeToCreate.getPreventedPathingTypes(); + final EnumSet requiredPathingTypes = unitTypeToCreate.getRequiredPathingTypes(); - if (!simulation.getPathingGrid().checkPathingTexture(this.target.getX(), this.target.getY(), - (int) simulation.getGameplayConstants().getBuildingAngle(), buildingPathingPixelMap, - preventedPathingTypes, requiredPathingTypes, simulation.getWorldCollision(), this.unit)) { - buildLocationObstructed = true; + if (!simulation.getPathingGrid().checkPathingTexture(this.target.getX(), this.target.getY(), + (int) simulation.getGameplayConstants().getBuildingAngle(), buildingPathingPixelMap, + preventedPathingTypes, requiredPathingTypes, simulation.getWorldCollision(), this.unit)) { + buildLocationObstructed = true; + } } - } - if (!buildLocationObstructed) { - final CUnit constructedStructure = simulation.createUnit(this.orderId, this.unit.getPlayerIndex(), - this.target.getX(), this.target.getY(), simulation.getGameplayConstants().getBuildingAngle()); - constructedStructure.setConstructing(true); - constructedStructure.setWorkerInside(this.unit); - constructedStructure.setLife(simulation, - constructedStructure.getMaximumLife() * WarsmashConstants.BUILDING_CONSTRUCT_START_LIFE); - constructedStructure.setFoodUsed(unitTypeToCreate.getFoodUsed()); - constructedStructure.add(simulation, - new CAbilityBuildInProgress(simulation.getHandleIdAllocator().createId())); - for (final CAbility ability : constructedStructure.getAbilities()) { - ability.visit(AbilityDisableWhileUnderConstructionVisitor.INSTANCE); + if (!buildLocationObstructed) { + final CUnit constructedStructure = simulation.createUnit(this.orderId, this.unit.getPlayerIndex(), + this.target.getX(), this.target.getY(), simulation.getGameplayConstants().getBuildingAngle()); + constructedStructure.setConstructing(true); + constructedStructure.setWorkerInside(this.unit); + constructedStructure.setLife(simulation, + constructedStructure.getMaximumLife() * WarsmashConstants.BUILDING_CONSTRUCT_START_LIFE); + constructedStructure.setFoodUsed(unitTypeToCreate.getFoodUsed()); + constructedStructure.add(simulation, + new CAbilityBuildInProgress(simulation.getHandleIdAllocator().createId())); + for (final CAbility ability : constructedStructure.getAbilities()) { + ability.visit(AbilityDisableWhileUnderConstructionVisitor.INSTANCE); + } + this.unit.setHidden(true); + this.unit.setPaused(true); + this.unit.setInvulnerable(true); + simulation.unitConstructedEvent(this.unit, constructedStructure); + } + else { + final CPlayer player = simulation.getPlayer(this.unit.getPlayerIndex()); + refund(player, unitTypeToCreate); + simulation.getCommandErrorListener().showCantPlaceError(); } - this.unit.setHidden(true); - this.unit.setPaused(true); - this.unit.setInvulnerable(true); - simulation.unitConstructedEvent(this.unit, constructedStructure); - } - else { - final CPlayer player = simulation.getPlayer(this.unit.getPlayerIndex()); - player.setFoodUsed(player.getFoodUsed() - unitTypeToCreate.getFoodUsed()); - simulation.getCommandErrorListener().showCantPlaceError(); } return this.unit.pollNextOrderBehavior(simulation); } @@ -105,7 +110,16 @@ public class CBehaviorOrcBuild extends CAbstractRangedBehavior { @Override public void end(final CSimulation game) { + if (!this.unitCreated) { + final CPlayer player = game.getPlayer(this.unit.getPlayerIndex()); + final CUnitType unitTypeToCreate = game.getUnitData().getUnitType(this.orderId); + refund(player, unitTypeToCreate); + } + } + private void refund(final CPlayer player, final CUnitType unitTypeToCreate) { + player.setFoodUsed(player.getFoodUsed() - unitTypeToCreate.getFoodUsed()); + player.refundFor(unitTypeToCreate); } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/harvest/CBehaviorHarvest.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/harvest/CBehaviorHarvest.java index 2b12a28..05ad0ba 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/harvest/CBehaviorHarvest.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/harvest/CBehaviorHarvest.java @@ -173,10 +173,9 @@ public class CBehaviorHarvest extends CAbstractRangedBehavior if (this.target instanceof CDestructable) { // wood final CDestructable nearestTree = CBehaviorReturnResources.findNearestTree(this.unit, this.abilityHarvest, - simulation, (CDestructable) this.target); + simulation, this.unit); if (nearestTree != null) { - this.target = nearestTree; - return this; + return reset(nearestTree); } } return this.unit.pollNextOrderBehavior(simulation); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/harvest/CBehaviorReturnResources.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/harvest/CBehaviorReturnResources.java index ed229c1..834b0b4 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/harvest/CBehaviorReturnResources.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/harvest/CBehaviorReturnResources.java @@ -203,7 +203,8 @@ public class CBehaviorReturnResources extends CAbstractRangedBehavior implements CDestructable nearestMine = null; double nearestMineDistance = Float.MAX_VALUE; for (final CDestructable unit : simulation.getDestructables()) { - if (unit.canBeTargetedBy(simulation, worker, abilityHarvest.getTreeAttack().getTargetsAllowed())) { + if (!unit.isDead() + && unit.canBeTargetedBy(simulation, worker, abilityHarvest.getTreeAttack().getTargetsAllowed())) { // TODO maybe use distance squared, problem is that we're using this // inefficient more complex distance function on unit final double distance = unit.distanceSquaredNoCollision(toObject); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/test/CBehaviorChannelTest.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/test/CBehaviorChannelTest.java new file mode 100644 index 0000000..52615b5 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/test/CBehaviorChannelTest.java @@ -0,0 +1,49 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.test; + +import com.etheller.warsmash.util.WarsmashConstants; +import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; + +public class CBehaviorChannelTest implements CBehavior { + private final CUnit unit; + private final float artDuration; + private int nextArtTick; + + public CBehaviorChannelTest(final CUnit unit, final float artDuration) { + this.unit = unit; + this.artDuration = artDuration; + } + + public CBehaviorChannelTest reset() { + this.nextArtTick = 0; + return this; + } + + @Override + public CBehavior update(final CSimulation game) { + this.unit.getUnitAnimationListener().playAnimation(false, null, SequenceUtils.SPELL, 1.0f, true); + final int gameTurnTick = game.getGameTurnTick(); + if (gameTurnTick >= this.nextArtTick) { + game.createEffectOnUnit(this.unit, "Abilities\\Spells\\Undead\\DeathPact\\DeathPactTarget.mdl"); + this.nextArtTick = gameTurnTick + (int) (this.artDuration / WarsmashConstants.SIMULATION_STEP_TIME); + } + return this; + } + + @Override + public void begin(final CSimulation game) { + } + + @Override + public void end(final CSimulation game) { + } + + @Override + public int getHighlightOrderId() { + return OrderIds.channel; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CAbilityData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CAbilityData.java index c496c93..c5541b0 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CAbilityData.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CAbilityData.java @@ -10,6 +10,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityGeneric; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.CAbilityType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.definitions.CAbilityTypeDefinition; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.definitions.impl.CAbilityTypeDefinitionChannelTest; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.definitions.impl.CAbilityTypeDefinitionColdArrows; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.definitions.impl.CAbilityTypeDefinitionGoldMine; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.definitions.impl.CAbilityTypeDefinitionHarvest; @@ -32,6 +33,7 @@ public class CAbilityData { this.codeToAbilityTypeDefinition.put(War3ID.fromString("Agld"), new CAbilityTypeDefinitionGoldMine()); this.codeToAbilityTypeDefinition.put(War3ID.fromString("Artn"), new CAbilityTypeDefinitionReturnResources()); this.codeToAbilityTypeDefinition.put(War3ID.fromString("Ahar"), new CAbilityTypeDefinitionHarvest()); + this.codeToAbilityTypeDefinition.put(War3ID.fromString("ANcl"), new CAbilityTypeDefinitionChannelTest()); } public CAbilityType getAbilityType(final War3ID alias) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CDestructableData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CDestructableData.java index 62e7713..e26ff00 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CDestructableData.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CDestructableData.java @@ -45,7 +45,7 @@ public class CDestructableData { final CDestructableType unitTypeInstance = getUnitTypeInstance(typeId, unitType); - final int life = unitTypeInstance.getLife(); + final float life = unitTypeInstance.getLife(); final CDestructable destructable = new CDestructable(handleId, x, y, life, unitTypeInstance, pathingInstance, pathingInstanceDeath); @@ -60,7 +60,7 @@ public class CDestructableData { final BufferedImage buildingPathingDeathPixelMap = this.simulationRenderController .getDestructablePathingDeathPixelMap(typeId); final String name = unitType.getFieldAsString(NAME, 0); - final int life = unitType.getFieldAsInteger(HIT_POINT_MAXIMUM, 0); + final float life = unitType.getFieldAsFloat(HIT_POINT_MAXIMUM, 0); final EnumSet targetedAs = CTargetType .parseTargetTypeSet(unitType.getFieldAsString(TARGETED_AS, 0)); final String armorType = unitType.getFieldAsString(ARMOR_TYPE, 0); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java index b1eb8bc..89c16dd 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java @@ -46,4 +46,6 @@ public interface SimulationRenderController { void unitRepositioned(CUnit cUnit); void spawnGainResourceTextTag(CUnit gainingUnit, ResourceType resourceType, int amount); + + void spawnEffectOnUnit(CUnit unit, String effectPath); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java index 3aa3f32..2302d9d 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java @@ -844,11 +844,14 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma / this.war3MapViewer.simulation.getGameplayConstants().getGameDayHours()); for (final TextTag textTag : this.war3MapViewer.textTags) { this.war3MapViewer.worldScene.camera.worldToScreen(screenCoordsVector, textTag.getPosition()); - final Vector2 unprojected = this.uiViewport.unproject(screenCoordsVector); - this.textTagFont.setColor(textTag.getColor()); - glyphLayout.setText(this.textTagFont, textTag.getText()); - this.textTagFont.draw(batch, textTag.getText(), unprojected.x - (glyphLayout.width / 2), - (unprojected.y - (glyphLayout.height / 2))); + if (this.war3MapViewer.worldScene.camera.rect.contains(screenCoordsVector.x, + (Gdx.graphics.getHeight() - screenCoordsVector.y) + textTag.getScreenCoordsZHeight())) { + final Vector2 unprojected = this.uiViewport.unproject(screenCoordsVector); + this.textTagFont.setColor(textTag.getColor()); + glyphLayout.setText(this.textTagFont, textTag.getText()); + this.textTagFont.draw(batch, textTag.getText(), unprojected.x - (glyphLayout.width / 2), + (unprojected.y - (glyphLayout.height / 2)) + textTag.getScreenCoordsZHeight()); + } } } @@ -1151,7 +1154,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma final float rallyPointX = target.getX(); final float rallyPointY = target.getY(); this.rallyPointInstance.setLocation(rallyPointX, rallyPointY, - MeleeUI.this.war3MapViewer.terrain.getGroundHeight(rallyPointX, rallyPointY)); + MeleeUI.this.war3MapViewer.terrain.getGroundHeight(rallyPointX, rallyPointY) + 192); return null; } @@ -1431,6 +1434,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.attack1Icon.setVisible(false); this.attack2Icon.setVisible(false); this.armorIcon.setVisible(false); + this.selectWorkerInsideFrame.setVisible(false); } else { for (final QueueIcon queueIconFrame : this.queueIconFrames) { diff --git a/resources/UI/FrameDef/SmashUI/ToolTip.fdf b/resources/UI/FrameDef/SmashUI/ToolTip.fdf index bb64ccd..958c852 100644 --- a/resources/UI/FrameDef/SmashUI/ToolTip.fdf +++ b/resources/UI/FrameDef/SmashUI/ToolTip.fdf @@ -29,11 +29,12 @@ Frame "FRAME" "SmashToolTip" { Frame "TEXT" "SmashToolTipText" { SetAllPoints, DecorateFileNames, - FrameFont "MasterFont", 0.013, "", + FrameFont "MasterFont", 0.008, "", FontJustificationH JUSTIFYLEFT, FontJustificationV JUSTIFYTOP, FontFlags "FIXEDSIZE", - FontColor 0.99 0.827 0.0705 1.0, + //FontColor 0.99 0.827 0.0705 1.0, + FontColor 1.0 1.0 1.0 1.0, FontHighlightColor 1.0 1.0 1.0 1.0, FontDisabledColor 0.2 0.2 0.2 1.0, FontShadowColor 0.0 0.0 0.0 0.9, From 780ed7b60bcf0d49c24ef1991f0d82f83f9c06cd Mon Sep 17 00:00:00 2001 From: Retera Date: Sat, 13 Feb 2021 19:56:11 -0500 Subject: [PATCH 101/116] Checkpoint after adding tooltips with prices --- core/assets/warsmash.ini | 3 +- .../etheller/warsmash/WarsmashGdxMapGame.java | 4 +- .../etheller/warsmash/parsers/fdf/GameUI.java | 51 ++- .../fdf/frames/AbstractRenderableFrame.java | 12 +- .../parsers/fdf/frames/AbstractUIFrame.java | 7 + .../parsers/fdf/frames/SingleStringFrame.java | 102 +++++ .../parsers/fdf/frames/StringFrame.java | 390 ++++++++++++++++-- .../etheller/warsmash/parsers/jass/Jass2.java | 4 +- .../objectdata/Warcraft3MapObjectData.java | 17 +- .../etheller/warsmash/units/DataTable.java | 12 +- .../warsmash/units/HashedGameObject.java | 10 + .../etheller/warsmash/units/custom/WTS.java | 7 + .../com/etheller/warsmash/viewer5/Bounds.java | 6 +- .../warsmash/viewer5/ModelInstance.java | 81 +++- .../viewer5/handlers/mdx/MdxModel.java | 2 +- .../viewer5/handlers/w3x/SequenceUtils.java | 4 +- .../viewer5/handlers/w3x/UnitSound.java | 10 +- .../viewer5/handlers/w3x/War3MapViewer.java | 9 +- .../handlers/w3x/environment/Terrain.java | 23 +- .../w3x/rendersim/RenderDestructable.java | 5 + .../handlers/w3x/rendersim/RenderUnit.java | 5 + .../handlers/w3x/rendersim/RenderWidget.java | 2 + .../w3x/rendersim/ability/AbilityDataUI.java | 39 +- .../w3x/rendersim/ability/IconUI.java | 16 +- .../commandbuttons/CommandButtonListener.java | 3 +- .../CommandCardPopulatingAbilityVisitor.java | 25 +- .../handlers/w3x/simulation/CUnit.java | 64 ++- .../build/AbstractCAbilityBuild.java | 14 + .../abilities/build/CAbilityOrcBuild.java | 11 - .../abilities/build/CAbilityUndeadBuild.java | 89 ++-- .../harvest/CAbilityReturnResources.java | 3 + .../abilities/hero/CAbilityHero.java | 5 + .../w3x/simulation/behaviors/CBehavior.java | 2 +- .../simulation/behaviors/CBehaviorAttack.java | 6 +- .../simulation/behaviors/CBehaviorFollow.java | 7 +- .../behaviors/CBehaviorHoldPosition.java | 2 +- .../simulation/behaviors/CBehaviorMove.java | 14 +- .../simulation/behaviors/CBehaviorPatrol.java | 7 +- .../simulation/behaviors/CBehaviorStop.java | 2 +- .../simulation/behaviors/CRangedBehavior.java | 2 + ...yDisableWhileUnderConstructionVisitor.java | 2 +- .../behaviors/build/CBehaviorOrcBuild.java | 13 +- .../behaviors/build/CBehaviorUndeadBuild.java | 152 +++++++ .../behaviors/harvest/CBehaviorHarvest.java | 7 +- .../harvest/CBehaviorReturnResources.java | 7 +- .../behaviors/test/CBehaviorChannelTest.java | 2 +- .../w3x/simulation/util/ResourceType.java | 1 + .../handlers/w3x/ui/CommandCardIcon.java | 35 +- .../viewer5/handlers/w3x/ui/MeleeUI.java | 257 ++++++++---- .../viewer5/handlers/w3x/ui/MenuUI.java | 3 +- .../viewer5/handlers/w3x/ui/QueueIcon.java | 20 + .../w3x/ui/command/ClickableActionFrame.java | 8 + .../w3x/ui/command/CommandErrorListener.java | 2 + .../command/SettableCommandErrorListener.java | 5 + resources/UI/FrameDef/SmashUI/ToolTip.fdf | 40 +- 55 files changed, 1362 insertions(+), 269 deletions(-) create mode 100644 core/src/com/etheller/warsmash/parsers/fdf/frames/SingleStringFrame.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/hero/CAbilityHero.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorUndeadBuild.java diff --git a/core/assets/warsmash.ini b/core/assets/warsmash.ini index 1861dae..3ac7324 100644 --- a/core/assets/warsmash.ini +++ b/core/assets/warsmash.ini @@ -20,7 +20,7 @@ Path07="." [Map] //FilePath="CombatUnitTests.w3x" //FilePath="PitchRoll.w3x" -FilePath="PeonStartingBase_Simple.w3x" +//FilePath="PeonStartingBase_Simple.w3x" //FilePath="MyStromguarde.w3m" //FilePath="ColdArrows.w3m" //FilePath="DungeonGoldMine.w3m" @@ -46,3 +46,4 @@ FilePath="PeonStartingBase_Simple.w3x" //FilePath="V1\Farm.w3x" //FilePath="PenguinWorld.w3x" //FilePath="Maps\FrozenThrone\Campaign\UndeadX09.w3x" +FilePath="LavellaLagoon.w3x" diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java index 43734ac..17bfcbf 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java @@ -58,7 +58,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.SettableCommandErro public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProvider, InputProcessor { private static final boolean ENABLE_AUDIO = true; - private static final boolean ENABLE_MUSIC = false; + private static final boolean ENABLE_MUSIC = true; private DataSource codebase; private War3MapViewer viewer; private final Rectangle tempRect = new Rectangle(); @@ -218,7 +218,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv final String[] musics = musicField.split(";"); String musicPath = musics[(int) (Math.random() * musics.length)]; if (true) { - musicPath = "Sound\\Music\\mp3Music\\OrcTheme.mp3"; + musicPath = "Sound\\Music\\mp3Music\\DarkAgents.mp3"; } final Music music = Gdx.audio.newMusic( new DataSourceFileHandle(WarsmashGdxMapGame.this.viewer.dataSource, musicPath)); diff --git a/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java index 97766d0..419d276 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java @@ -43,6 +43,7 @@ import com.etheller.warsmash.parsers.fdf.frames.TextureFrame; import com.etheller.warsmash.parsers.fdf.frames.UIFrame; import com.etheller.warsmash.units.DataTable; import com.etheller.warsmash.units.Element; +import com.etheller.warsmash.units.custom.WTS; import com.etheller.warsmash.util.ImageUtils; import com.etheller.warsmash.util.StringBundle; import com.etheller.warsmash.viewer5.Scene; @@ -66,10 +67,12 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { private final Viewport fdfCoordinateResolutionDummyViewport; private final DataTable skinData; private final Element errorStrings; + private final GlyphLayout glyphLayout; + private final WTS mapStrings; public GameUI(final DataSource dataSource, final Element skin, final Viewport viewport, final FreeTypeFontGenerator fontGenerator, final Scene uiScene, final AbstractMdxModelViewer modelViewer, - final int racialCommandIndex) { + final int racialCommandIndex, final WTS mapStrings) { super("GameUI", null); this.dataSource = dataSource; this.skin = skin; @@ -106,6 +109,8 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { throw new RuntimeException(e); } this.errorStrings = this.skinData.get("Errors"); + this.glyphLayout = new GlyphLayout(); + this.mapStrings = mapStrings; } public static Element loadSkin(final DataSource dataSource, final String skin) { @@ -275,7 +280,7 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { this.fontParam.size = 128; } final BitmapFont frameFont = this.fontGenerator.generateFont(this.fontParam); - final StringFrame stringFrame = new StringFrame(name, parent, color, justifyH, justifyV, frameFont); + final StringFrame stringFrame = new StringFrame(name, parent, color, justifyH, justifyV, frameFont, name); this.nameToFrame.put(name, stringFrame); add(stringFrame); return stringFrame; @@ -389,8 +394,17 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { this.fontParam.size = 24; } frameFont = this.fontGenerator.generateFont(this.fontParam); + String textString = frameDefinition.getName(); + String text = frameDefinition.getString("Text"); + if (text != null) { + final String decoratedString = this.templates.getDecoratedString(text); + if (decoratedString != text) { + text = decoratedString; + } + textString = text; + } final StringFrame stringFrame = new StringFrame(frameDefinition.getName(), parent, fontColor, justifyH, - justifyV, frameFont); + justifyV, frameFont, textString); if (fontShadowColor != null) { final Vector2Definition shadowOffset = frameDefinition.getVector2("FontShadowOffset"); stringFrame.setFontShadowColor(fontShadowColor); @@ -398,14 +412,6 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { stringFrame.setFontShadowOffsetY(convertY(viewport2, shadowOffset.getY())); } inflatedFrame = stringFrame; - String text = frameDefinition.getString("Text"); - if (text != null) { - final String decoratedString = this.templates.getDecoratedString(text); - if (decoratedString != text) { - text = decoratedString; - } - stringFrame.setText(text); - } } else if ("GLUETEXTBUTTON".equals(frameDefinition.getFrameType())) { // ButtonText & ControlBackdrop @@ -529,17 +535,18 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { this.fontParam.size = 24; } frameFont = this.fontGenerator.generateFont(this.fontParam); - final StringFrame stringFrame = new StringFrame(frameDefinition.getName(), parent, fontColor, justifyH, - justifyV, frameFont); - inflatedFrame = stringFrame; + String textString = frameDefinition.getName(); String text = frameDefinition.getString("Text"); if (text != null) { final String decoratedString = this.templates.getDecoratedString(text); if (decoratedString != text) { text = decoratedString; } - stringFrame.setText(text); + textString = text; } + final StringFrame stringFrame = new StringFrame(frameDefinition.getName(), parent, fontColor, justifyH, + justifyV, frameFont, textString); + inflatedFrame = stringFrame; break; case Texture: final String file = frameDefinition.getString("File"); @@ -693,6 +700,18 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { } public String getErrorString(final String key) { - return this.errorStrings.getField(key, this.racialCommandIndex); + String errorString = this.errorStrings.getField(key, this.racialCommandIndex); + if (errorString.startsWith("TRIGSTR_")) { + errorString = this.mapStrings.get(Integer.parseInt(errorString.substring(8))); + } + return errorString; + } + + public GlyphLayout getGlyphLayout() { + return this.glyphLayout; + } + + public void setText(final StringFrame stringFrame, final String text) { + stringFrame.setText(text, this, this.viewport); } } diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java index 9eadfc4..94daafd 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java @@ -30,6 +30,8 @@ public abstract class AbstractRenderableFrame implements UIFrame { protected int level; protected final Rectangle renderBounds = new Rectangle(0, 0, 0, 0); // in libgdx rendering space private final EnumMap framePointToAssignment = new EnumMap<>(FramePoint.class); + protected float assignedHeight; + protected float assignedWidth; public AbstractRenderableFrame(final String name, final UIFrame parent) { this.name = name; @@ -47,11 +49,13 @@ public abstract class AbstractRenderableFrame implements UIFrame { @Override public void setWidth(final float width) { + this.assignedWidth = width; this.renderBounds.width = width; } @Override public void setHeight(final float height) { + this.assignedHeight = height; this.renderBounds.height = height; } @@ -65,6 +69,10 @@ public abstract class AbstractRenderableFrame implements UIFrame { return null; } + public void clearFramePointAssignments() { + this.framePointToAssignment.clear(); + } + private FramePointAssignment getLeftAnchor() { return getByPriority(LEFT_ANCHOR_PRIORITY); } @@ -238,7 +246,7 @@ public abstract class AbstractRenderableFrame implements UIFrame { final FramePointAssignment centerVerticalAnchor = getCenterVerticalAnchor(); if (leftAnchor != null) { this.renderBounds.x = leftAnchor.getX(gameUI, viewport); - if (this.renderBounds.width == 0) { + if (this.assignedWidth == 0) { if (rightAnchor != null) { this.renderBounds.width = rightAnchor.getX(gameUI, viewport) - this.renderBounds.x; } @@ -259,7 +267,7 @@ public abstract class AbstractRenderableFrame implements UIFrame { } if (bottomAnchor != null) { this.renderBounds.y = bottomAnchor.getY(gameUI, viewport); - if (this.renderBounds.height == 0) { + if (this.assignedHeight == 0) { if (topAnchor != null) { this.renderBounds.height = topAnchor.getY(gameUI, viewport) - this.renderBounds.y; } diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractUIFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractUIFrame.java index df60e18..425a267 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractUIFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractUIFrame.java @@ -19,6 +19,13 @@ public abstract class AbstractUIFrame extends AbstractRenderableFrame implements this.childFrames.add(childFrame); } + public void remove(final UIFrame childFrame) { + if (childFrame == null) { + return; + } + this.childFrames.remove(childFrame); + } + public AbstractUIFrame(final String name, final UIFrame parent) { super(name, parent); } diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/SingleStringFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/SingleStringFrame.java new file mode 100644 index 0000000..4ea4756 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/SingleStringFrame.java @@ -0,0 +1,102 @@ +package com.etheller.warsmash.parsers.fdf.frames; + +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.g2d.GlyphLayout; +import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.badlogic.gdx.utils.viewport.Viewport; +import com.etheller.warsmash.parsers.fdf.GameUI; +import com.etheller.warsmash.parsers.fdf.datamodel.TextJustify; + +public class SingleStringFrame extends AbstractRenderableFrame { + private Color color; + private String text = "Default string"; + private final TextJustify justifyH; + private final TextJustify justifyV; + private final BitmapFont frameFont; + private Color fontShadowColor; + private float fontShadowOffsetX; + private float fontShadowOffsetY; + private float alpha = 1.0f; + + public SingleStringFrame(final String name, final UIFrame parent, final Color color, final TextJustify justifyH, + final TextJustify justifyV, final BitmapFont frameFont) { + super(name, parent); + this.color = color; + this.justifyH = justifyH; + this.justifyV = justifyV; + this.frameFont = frameFont; + this.text = name; + } + + public void setText(final String text) { + if (text == null) { + throw new IllegalArgumentException(); + } + this.text = text; + } + + public void setColor(final Color color) { + this.color = color; + } + + public void setFontShadowColor(final Color fontShadowColor) { + this.fontShadowColor = fontShadowColor; + } + + public void setFontShadowOffsetX(final float fontShadowOffsetX) { + this.fontShadowOffsetX = fontShadowOffsetX; + } + + public void setFontShadowOffsetY(final float fontShadowOffsetY) { + this.fontShadowOffsetY = fontShadowOffsetY; + } + + @Override + protected void internalRender(final SpriteBatch batch, final BitmapFont baseFont, final GlyphLayout glyphLayout) { + glyphLayout.setText(this.frameFont, this.text); + final float x; + switch (this.justifyH) { + case CENTER: + x = this.renderBounds.x + ((this.renderBounds.width - glyphLayout.width) / 2); + break; + case RIGHT: + x = (this.renderBounds.x + this.renderBounds.width) - glyphLayout.width; + break; + case LEFT: + default: + x = this.renderBounds.x; + break; + } + final float y; + switch (this.justifyV) { + case MIDDLE: + y = this.renderBounds.y + ((this.renderBounds.height + this.frameFont.getLineHeight()) / 2); + break; + case TOP: + y = (this.renderBounds.y + this.renderBounds.height); + break; + case BOTTOM: + default: + y = this.renderBounds.y + this.frameFont.getLineHeight(); + break; + } + if (this.fontShadowColor != null) { + this.frameFont.setColor(this.fontShadowColor.r, this.fontShadowColor.g, this.fontShadowColor.b, + this.fontShadowColor.a * this.alpha); + this.frameFont.draw(batch, this.text, x + this.fontShadowOffsetX, y + this.fontShadowOffsetY); + } + this.frameFont.setColor(this.color.r, this.color.g, this.color.b, this.color.a * this.alpha); + this.frameFont.draw(batch, this.text, x, y); + } + + @Override + protected void innerPositionBounds(final GameUI gameUI, final Viewport viewport) { + } + + public void setAlpha(final float alpha) { + this.alpha = alpha; + + } + +} diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/StringFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/StringFrame.java index 0d64fab..5375fbc 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/StringFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/StringFrame.java @@ -1,14 +1,20 @@ package com.etheller.warsmash.parsers.fdf.frames; +import java.util.ArrayList; +import java.util.List; + import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.GlyphLayout; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.utils.viewport.Viewport; import com.etheller.warsmash.parsers.fdf.GameUI; +import com.etheller.warsmash.parsers.fdf.datamodel.AnchorDefinition; +import com.etheller.warsmash.parsers.fdf.datamodel.FramePoint; import com.etheller.warsmash.parsers.fdf.datamodel.TextJustify; public class StringFrame extends AbstractRenderableFrame { + private final List internalFrames = new ArrayList<>(); private Color color; private String text = "Default string"; private final TextJustify justifyH; @@ -18,22 +24,26 @@ public class StringFrame extends AbstractRenderableFrame { private float fontShadowOffsetX; private float fontShadowOffsetY; private float alpha = 1.0f; + private final SimpleFrame internalFramesContainer; + private float predictedViewportHeight; public StringFrame(final String name, final UIFrame parent, final Color color, final TextJustify justifyH, - final TextJustify justifyV, final BitmapFont frameFont) { + final TextJustify justifyV, final BitmapFont frameFont, final String text) { super(name, parent); this.color = color; this.justifyH = justifyH; this.justifyV = justifyV; this.frameFont = frameFont; - this.text = name; + this.text = text; + this.internalFramesContainer = new SimpleFrame(null, this); } - public void setText(final String text) { + public void setText(final String text, final GameUI gameUI, final Viewport viewport) { if (text == null) { throw new IllegalArgumentException(); } this.text = text; + positionBounds(gameUI, viewport); } public void setColor(final Color color) { @@ -54,44 +64,346 @@ public class StringFrame extends AbstractRenderableFrame { @Override protected void internalRender(final SpriteBatch batch, final BitmapFont baseFont, final GlyphLayout glyphLayout) { - glyphLayout.setText(this.frameFont, this.text); - final float x; - switch (this.justifyH) { - case CENTER: - x = this.renderBounds.x + ((this.renderBounds.width - glyphLayout.width) / 2); - break; - case RIGHT: - x = (this.renderBounds.x + this.renderBounds.width) - glyphLayout.width; - break; - case LEFT: - default: - x = this.renderBounds.x; - break; - } - final float y; - switch (this.justifyV) { - case MIDDLE: - y = this.renderBounds.y + ((this.renderBounds.height + this.frameFont.getLineHeight()) / 2); - break; - case TOP: - y = (this.renderBounds.y + this.renderBounds.height); - break; - case BOTTOM: - default: - y = this.renderBounds.y + this.frameFont.getLineHeight(); - break; - } - if (this.fontShadowColor != null) { - this.frameFont.setColor(this.fontShadowColor.r, this.fontShadowColor.g, this.fontShadowColor.b, - this.fontShadowColor.a * this.alpha); - this.frameFont.draw(batch, this.text, x + this.fontShadowOffsetX, y + this.fontShadowOffsetY); - } - this.frameFont.setColor(this.color.r, this.color.g, this.color.b, this.color.a * this.alpha); - this.frameFont.draw(batch, this.text, x, y); + this.internalFramesContainer.render(batch, baseFont, glyphLayout); } @Override protected void innerPositionBounds(final GameUI gameUI, final Viewport viewport) { + createInternalFrames(gameUI.getGlyphLayout()); + this.internalFramesContainer.positionBounds(gameUI, viewport); + } + + private void createInternalFrames(final GlyphLayout glyphLayout) { + for (final SingleStringFrame internalFrame : this.internalFrames) { + this.internalFramesContainer.remove(internalFrame); + } + this.internalFrames.clear(); + final StringBuilder currentLine = new StringBuilder(); + final StringBuilder currentWord = new StringBuilder(); + float currentXCoordForWord = 0; + float currentXCoordForFrames = 0; + final float usedWidth = 0; + float usedHeight = 0; + float usedWidthMax = 0; + final float startingBoundsWidth = this.renderBounds.width; + final boolean firstInLine = false; + Color currentColor = this.color; + for (int i = 0; i < this.text.length(); i++) { + final char c = this.text.charAt(i); + switch (c) { + case '|': { + // special control character + if ((i + 1) < this.text.length()) { + final char escapedCharacter = this.text.charAt(i + 1); + switch (escapedCharacter) { + case 'c': + if ((i + 9) < this.text.length()) { + int colorInt; + try { + final String upperCase = this.text.substring(i + 2, i + 10).toUpperCase(); + colorInt = (int) Long.parseLong(upperCase, 16); + } + catch (final NumberFormatException exc) { + currentWord.append(c); + break; + } + i += 9; + { + final String wordString = currentWord.toString(); + currentWord.setLength(0); + glyphLayout.setText(this.frameFont, wordString); + final float wordWidth = glyphLayout.width; + if ((startingBoundsWidth > 0) + && ((currentXCoordForWord + wordWidth) >= startingBoundsWidth)) { + final String currentLineString = currentLine.toString(); + currentLine.setLength(0); + glyphLayout.setText(this.frameFont, currentLineString); + usedWidthMax = Math.max(currentXCoordForFrames + glyphLayout.width, usedWidthMax); + final SingleStringFrame singleStringFrame = new SingleStringFrame(currentLineString, + this.internalFramesContainer, currentColor, TextJustify.LEFT, + TextJustify.TOP, this.frameFont); + singleStringFrame.setHeight(this.frameFont.getLineHeight()); + singleStringFrame.setWidth(glyphLayout.width); + singleStringFrame.setAlpha(this.alpha); + singleStringFrame.setFontShadowColor(this.fontShadowColor); + singleStringFrame.setFontShadowOffsetX(this.fontShadowOffsetX); + singleStringFrame.setFontShadowOffsetY(this.fontShadowOffsetY); + singleStringFrame.addAnchor(new AnchorDefinition(FramePoint.TOPLEFT, + currentXCoordForFrames, usedHeight)); + this.internalFrames.add(singleStringFrame); + usedHeight += this.frameFont.getLineHeight(); + currentXCoordForWord = 0; + currentXCoordForFrames = 0; + } + currentXCoordForWord += wordWidth; + currentLine.append(wordString); + + final String currentLineString = currentLine.toString(); + currentLine.setLength(0); + glyphLayout.setText(this.frameFont, currentLineString); + usedWidthMax = Math.max(currentXCoordForFrames + glyphLayout.width, usedWidthMax); + final SingleStringFrame singleStringFrame = new SingleStringFrame(currentLineString, + this.internalFramesContainer, currentColor, TextJustify.LEFT, TextJustify.TOP, + this.frameFont); + singleStringFrame.setHeight(this.frameFont.getLineHeight()); + singleStringFrame.setWidth(glyphLayout.width); + singleStringFrame.setAlpha(this.alpha); + singleStringFrame.setFontShadowColor(this.fontShadowColor); + singleStringFrame.setFontShadowOffsetX(this.fontShadowOffsetX); + singleStringFrame.setFontShadowOffsetY(this.fontShadowOffsetY); + singleStringFrame.addAnchor( + new AnchorDefinition(FramePoint.TOPLEFT, currentXCoordForFrames, -usedHeight)); + this.internalFrames.add(singleStringFrame); + currentXCoordForFrames = currentXCoordForWord; + + currentColor = new Color((colorInt << 8) | (colorInt >>> 24)); + } + } + break; + case 'r': + i++; { + final String wordString = currentWord.toString(); + currentWord.setLength(0); + glyphLayout.setText(this.frameFont, wordString); + final float wordWidth = glyphLayout.width; + if ((startingBoundsWidth > 0) && ((currentXCoordForWord + wordWidth) >= startingBoundsWidth)) { + final String currentLineString = currentLine.toString(); + currentLine.setLength(0); + glyphLayout.setText(this.frameFont, currentLineString); + usedWidthMax = Math.max(currentXCoordForFrames + glyphLayout.width, usedWidthMax); + final SingleStringFrame singleStringFrame = new SingleStringFrame(currentLineString, + this.internalFramesContainer, currentColor, TextJustify.LEFT, TextJustify.TOP, + this.frameFont); + singleStringFrame.setHeight(this.frameFont.getLineHeight()); + singleStringFrame.setWidth(glyphLayout.width); + singleStringFrame.setAlpha(this.alpha); + singleStringFrame.setFontShadowColor(this.fontShadowColor); + singleStringFrame.setFontShadowOffsetX(this.fontShadowOffsetX); + singleStringFrame.setFontShadowOffsetY(this.fontShadowOffsetY); + singleStringFrame.addAnchor( + new AnchorDefinition(FramePoint.TOPLEFT, currentXCoordForFrames, -usedHeight)); + this.internalFrames.add(singleStringFrame); + usedHeight += this.frameFont.getLineHeight(); + currentXCoordForWord = 0; + currentXCoordForFrames = 0; + } + currentXCoordForWord += wordWidth; + currentLine.append(wordString); + + final String currentLineString = currentLine.toString(); + currentLine.setLength(0); + glyphLayout.setText(this.frameFont, currentLineString); + usedWidthMax = Math.max(currentXCoordForFrames + glyphLayout.width, usedWidthMax); + final SingleStringFrame singleStringFrame = new SingleStringFrame(currentLineString, + this.internalFramesContainer, currentColor, TextJustify.LEFT, TextJustify.TOP, + this.frameFont); + singleStringFrame.setHeight(this.frameFont.getLineHeight()); + singleStringFrame.setWidth(glyphLayout.width); + singleStringFrame.setAlpha(this.alpha); + singleStringFrame.setFontShadowColor(this.fontShadowColor); + singleStringFrame.setFontShadowOffsetX(this.fontShadowOffsetX); + singleStringFrame.setFontShadowOffsetY(this.fontShadowOffsetY); + singleStringFrame.addAnchor( + new AnchorDefinition(FramePoint.TOPLEFT, currentXCoordForFrames, -usedHeight)); + this.internalFrames.add(singleStringFrame); + currentXCoordForFrames = currentXCoordForWord; + } + currentColor = this.color; + break; + case 'n': { + + final String wordString = currentWord.toString(); + currentWord.setLength(0); + glyphLayout.setText(this.frameFont, wordString); + final float wordWidth = glyphLayout.width; + if ((startingBoundsWidth > 0) && ((currentXCoordForWord + wordWidth) >= startingBoundsWidth)) { + final String currentLineString = currentLine.toString(); + currentLine.setLength(0); + glyphLayout.setText(this.frameFont, currentLineString); + usedWidthMax = Math.max(currentXCoordForFrames + glyphLayout.width, usedWidthMax); + final SingleStringFrame singleStringFrame = new SingleStringFrame(currentLineString, + this.internalFramesContainer, currentColor, TextJustify.LEFT, TextJustify.TOP, + this.frameFont); + singleStringFrame.setHeight(this.frameFont.getLineHeight()); + singleStringFrame.setWidth(glyphLayout.width); + singleStringFrame.setAlpha(this.alpha); + singleStringFrame.setFontShadowColor(this.fontShadowColor); + singleStringFrame.setFontShadowOffsetX(this.fontShadowOffsetX); + singleStringFrame.setFontShadowOffsetY(this.fontShadowOffsetY); + singleStringFrame.addAnchor( + new AnchorDefinition(FramePoint.TOPLEFT, currentXCoordForFrames, -usedHeight)); + this.internalFrames.add(singleStringFrame); + usedHeight += this.frameFont.getLineHeight(); + currentXCoordForWord = 0; + currentXCoordForFrames = 0; + } + currentXCoordForWord += wordWidth; + currentLine.append(wordString); + + final String currentLineString = currentLine.toString(); + currentLine.setLength(0); + glyphLayout.setText(this.frameFont, currentLineString); + usedWidthMax = Math.max(currentXCoordForFrames + glyphLayout.width, usedWidthMax); + final SingleStringFrame singleStringFrame = new SingleStringFrame(currentLineString, + this.internalFramesContainer, currentColor, TextJustify.LEFT, TextJustify.TOP, + this.frameFont); + singleStringFrame.setHeight(this.frameFont.getLineHeight()); + singleStringFrame.setWidth(glyphLayout.width); + singleStringFrame.setAlpha(this.alpha); + singleStringFrame.setFontShadowColor(this.fontShadowColor); + singleStringFrame.setFontShadowOffsetX(this.fontShadowOffsetX); + singleStringFrame.setFontShadowOffsetY(this.fontShadowOffsetY); + singleStringFrame.addAnchor( + new AnchorDefinition(FramePoint.TOPLEFT, currentXCoordForFrames, -usedHeight)); + this.internalFrames.add(singleStringFrame); + usedHeight += this.frameFont.getLineHeight(); + currentXCoordForWord = 0; + currentXCoordForFrames = 0; + + } + i++; + break; + default: + currentWord.append(c); + break; + } + } + } + break; + case ' ': + currentWord.append(' '); + final String wordString = currentWord.toString(); + currentWord.setLength(0); + glyphLayout.setText(this.frameFont, wordString); + final float wordWidth = glyphLayout.width; + if ((startingBoundsWidth > 0) && ((currentXCoordForWord + wordWidth) >= startingBoundsWidth)) { + final String currentLineString = currentLine.toString(); + currentLine.setLength(0); + glyphLayout.setText(this.frameFont, currentLineString); + usedWidthMax = Math.max(currentXCoordForFrames + glyphLayout.width, usedWidthMax); + final SingleStringFrame singleStringFrame = new SingleStringFrame(currentLineString, + this.internalFramesContainer, currentColor, TextJustify.LEFT, TextJustify.TOP, + this.frameFont); + singleStringFrame.setHeight(this.frameFont.getLineHeight()); + singleStringFrame.setWidth(glyphLayout.width); + singleStringFrame.setAlpha(this.alpha); + singleStringFrame.setFontShadowColor(this.fontShadowColor); + singleStringFrame.setFontShadowOffsetX(this.fontShadowOffsetX); + singleStringFrame.setFontShadowOffsetY(this.fontShadowOffsetY); + singleStringFrame + .addAnchor(new AnchorDefinition(FramePoint.TOPLEFT, currentXCoordForFrames, -usedHeight)); + this.internalFrames.add(singleStringFrame); + usedHeight += this.frameFont.getLineHeight(); + currentXCoordForWord = 0; + currentXCoordForFrames = 0; + } + currentXCoordForWord += wordWidth; + currentLine.append(wordString); + break; + default: + currentWord.append(c); + break; + } + } + + { + + final String wordString = currentWord.toString(); + currentWord.setLength(0); + glyphLayout.setText(this.frameFont, wordString); + final float wordWidth = glyphLayout.width; + if ((startingBoundsWidth > 0) && ((currentXCoordForWord + wordWidth) >= startingBoundsWidth)) { + final String currentLineString = currentLine.toString(); + currentLine.setLength(0); + glyphLayout.setText(this.frameFont, currentLineString); + usedWidthMax = Math.max(currentXCoordForFrames + glyphLayout.width, usedWidthMax); + final SingleStringFrame singleStringFrame = new SingleStringFrame(currentLineString, + this.internalFramesContainer, currentColor, TextJustify.LEFT, TextJustify.TOP, this.frameFont); + singleStringFrame.setHeight(this.frameFont.getLineHeight()); + singleStringFrame.setWidth(glyphLayout.width); + singleStringFrame.setAlpha(this.alpha); + singleStringFrame.setFontShadowColor(this.fontShadowColor); + singleStringFrame.setFontShadowOffsetX(this.fontShadowOffsetX); + singleStringFrame.setFontShadowOffsetY(this.fontShadowOffsetY); + singleStringFrame + .addAnchor(new AnchorDefinition(FramePoint.TOPLEFT, currentXCoordForFrames, -usedHeight)); + this.internalFrames.add(singleStringFrame); + usedHeight += this.frameFont.getLineHeight(); + currentXCoordForWord = 0; + currentXCoordForFrames = 0; + } + currentXCoordForWord += wordWidth; + currentLine.append(wordString); + + final String currentLineString = currentLine.toString(); + currentLine.setLength(0); + glyphLayout.setText(this.frameFont, currentLineString); + usedWidthMax = Math.max(currentXCoordForFrames + glyphLayout.width, usedWidthMax); + final SingleStringFrame singleStringFrame = new SingleStringFrame(currentLineString, + this.internalFramesContainer, currentColor, TextJustify.LEFT, TextJustify.TOP, this.frameFont); + singleStringFrame.setHeight(this.frameFont.getLineHeight()); + singleStringFrame.setWidth(glyphLayout.width); + singleStringFrame.setAlpha(this.alpha); + singleStringFrame.addAnchor(new AnchorDefinition(FramePoint.TOPLEFT, currentXCoordForFrames, -usedHeight)); + this.internalFrames.add(singleStringFrame); + currentXCoordForFrames = currentXCoordForWord; + usedHeight += this.frameFont.getCapHeight(); + } + + this.internalFramesContainer.setWidth(usedWidthMax); + this.internalFramesContainer.setHeight(usedHeight); + this.predictedViewportHeight = (usedHeight - this.frameFont.getCapHeight()) + this.frameFont.getLineHeight(); + + this.internalFramesContainer.clearFramePointAssignments(); + switch (this.justifyH) { + case CENTER: + switch (this.justifyV) { + case MIDDLE: + this.internalFramesContainer.addAnchor(new AnchorDefinition(FramePoint.CENTER, 0, 0)); + break; + case BOTTOM: + this.internalFramesContainer.addAnchor(new AnchorDefinition(FramePoint.BOTTOM, 0, 0)); + break; + case TOP: + default: + this.internalFramesContainer.addAnchor(new AnchorDefinition(FramePoint.TOP, 0, 0)); + break; + } + break; + case RIGHT: + switch (this.justifyV) { + case MIDDLE: + this.internalFramesContainer.addAnchor(new AnchorDefinition(FramePoint.RIGHT, 0, 0)); + break; + case BOTTOM: + this.internalFramesContainer.addAnchor(new AnchorDefinition(FramePoint.BOTTOMRIGHT, 0, 0)); + break; + case TOP: + default: + this.internalFramesContainer.addAnchor(new AnchorDefinition(FramePoint.TOPRIGHT, 0, 0)); + break; + } + break; + case LEFT: + default: + switch (this.justifyV) { + case MIDDLE: + this.internalFramesContainer.addAnchor(new AnchorDefinition(FramePoint.LEFT, 0, 0)); + break; + case BOTTOM: + this.internalFramesContainer.addAnchor(new AnchorDefinition(FramePoint.BOTTOMLEFT, 0, 0)); + break; + case TOP: + default: + this.internalFramesContainer.addAnchor(new AnchorDefinition(FramePoint.TOPLEFT, 0, 0)); + break; + } + break; + } + + for (final SingleStringFrame internalFrame : this.internalFrames) { + this.internalFramesContainer.add(internalFrame); + } } public void setAlpha(final float alpha) { @@ -99,4 +411,8 @@ public class StringFrame extends AbstractRenderableFrame { } + public float getPredictedViewportHeight() { + return this.predictedViewportHeight; + } + } diff --git a/core/src/com/etheller/warsmash/parsers/jass/Jass2.java b/core/src/com/etheller/warsmash/parsers/jass/Jass2.java index 15c6690..c537a98 100644 --- a/core/src/com/etheller/warsmash/parsers/jass/Jass2.java +++ b/core/src/com/etheller/warsmash/parsers/jass/Jass2.java @@ -138,7 +138,7 @@ public class Jass2 { final String skinArg = arguments.get(0).visit(StringJassValueVisitor.getInstance()); final Element skin = GameUI.loadSkin(dataSource, skinArg); final GameUI gameUI = new GameUI(dataSource, skin, uiViewport, fontGenerator, uiScene, - war3MapViewer, 0); + war3MapViewer, 0, war3MapViewer.getAllObjectData().getWts()); JUIEnvironment.this.gameUI = gameUI; JUIEnvironment.this.skin = skin; rootFrameListener.onCreate(gameUI); @@ -254,7 +254,7 @@ public class Jass2 { final StringFrame frame = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); final String text = arguments.get(1).visit(StringJassValueVisitor.getInstance()); - frame.setText(text); + JUIEnvironment.this.gameUI.setText(frame, text); return null; } }); diff --git a/core/src/com/etheller/warsmash/parsers/w3x/objectdata/Warcraft3MapObjectData.java b/core/src/com/etheller/warsmash/parsers/w3x/objectdata/Warcraft3MapObjectData.java index a02449a..adc5708 100644 --- a/core/src/com/etheller/warsmash/parsers/w3x/objectdata/Warcraft3MapObjectData.java +++ b/core/src/com/etheller/warsmash/parsers/w3x/objectdata/Warcraft3MapObjectData.java @@ -28,10 +28,11 @@ public final class Warcraft3MapObjectData { private final MutableObjectData upgrades; private final List datas; private transient Map typeToData = new HashMap<>(); + private final WTS wts; public Warcraft3MapObjectData(final MutableObjectData units, final MutableObjectData items, final MutableObjectData destructibles, final MutableObjectData doodads, final MutableObjectData abilities, - final MutableObjectData buffs, final MutableObjectData upgrades) { + final MutableObjectData buffs, final MutableObjectData upgrades, final WTS wts) { this.units = units; this.items = items; this.destructibles = destructibles; @@ -50,6 +51,7 @@ public final class Warcraft3MapObjectData { for (final MutableObjectData data : this.datas) { this.typeToData.put(data.getWorldEditorDataType(), data); } + this.wts = wts; } public MutableObjectData getDataByType(final WorldEditorDataType type) { @@ -88,6 +90,10 @@ public final class Warcraft3MapObjectData { return this.datas; } + public WTS getWts() { + return this.wts; + } + public static Warcraft3MapObjectData load(final DataSource dataSource, final boolean inlineWTS) throws IOException { final StandardObjectData standardObjectData = new StandardObjectData(dataSource); @@ -115,12 +121,7 @@ public final class Warcraft3MapObjectData { final War3ObjectDataChangeset upgradeChangeset = new War3ObjectDataChangeset('q'); final WTS wts = dataSource.has("war3map.wts") ? new WTSFile(dataSource.getResourceAsStream("war3map.wts")) - : new WTS() { - @Override - public String get(final int key) { - return "TRIGSTR_" + key; - } - }; + : WTS.DO_NOTHING; if (dataSource.has("war3map.w3u")) { unitChangeset.load(new LittleEndianDataInputStream(dataSource.getResourceAsStream("war3map.w3u")), wts, inlineWTS); @@ -168,6 +169,6 @@ public final class Warcraft3MapObjectData { standardUpgrades, standardUpgradeMeta, upgradeChangeset); return new Warcraft3MapObjectData(unitData, itemData, destructableData, doodadData, abilityData, buffData, - upgradeData); + upgradeData, wts); } } \ No newline at end of file diff --git a/core/src/com/etheller/warsmash/units/DataTable.java b/core/src/com/etheller/warsmash/units/DataTable.java index a2c2ebb..b82337a 100644 --- a/core/src/com/etheller/warsmash/units/DataTable.java +++ b/core/src/com/etheller/warsmash/units/DataTable.java @@ -6,9 +6,11 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Set; @@ -109,11 +111,11 @@ public class DataTable implements ObjectData { else if (input.contains("=")) { final int eIndex = input.indexOf("="); final String fieldValue = input.substring(eIndex + 1); - int fieldIndex = 0; final StringBuilder builder = new StringBuilder(); boolean withinQuotedString = false; final String fieldName = input.substring(0, eIndex); boolean wasSlash = false; + final List values = new ArrayList<>(); for (int i = 0; i < fieldValue.length(); i++) { final char c = fieldValue.charAt(i); final boolean isSlash = c == '/'; @@ -125,7 +127,7 @@ public class DataTable implements ObjectData { withinQuotedString = !withinQuotedString; } else if (!withinQuotedString && (c == ',')) { - currentUnit.setField(fieldName, builder.toString().trim(), fieldIndex++); + values.add(builder.toString().trim()); builder.setLength(0); // empty buffer } else { @@ -137,8 +139,12 @@ public class DataTable implements ObjectData { if (currentUnit == null) { System.out.println("null for " + input); } - currentUnit.setField(fieldName, builder.toString().trim(), fieldIndex++); + if ("Nofood".equals(fieldName)) { + System.out.println(builder.toString().trim()); + } + values.add(builder.toString().trim()); } + currentUnit.setField(fieldName, values); } } diff --git a/core/src/com/etheller/warsmash/units/HashedGameObject.java b/core/src/com/etheller/warsmash/units/HashedGameObject.java index 3e9f109..0768c93 100644 --- a/core/src/com/etheller/warsmash/units/HashedGameObject.java +++ b/core/src/com/etheller/warsmash/units/HashedGameObject.java @@ -32,6 +32,16 @@ public abstract class HashedGameObject implements GameObject { } } + public void setField(final String field, final List value) { + final StringKey key = new StringKey(field); + if (value.isEmpty()) { + this.fields.remove(key); + } + else { + this.fields.put(key, value); + } + } + @Override public String getField(final String field) { final String value = ""; diff --git a/core/src/com/etheller/warsmash/units/custom/WTS.java b/core/src/com/etheller/warsmash/units/custom/WTS.java index 2ad60bd..52cc2c0 100644 --- a/core/src/com/etheller/warsmash/units/custom/WTS.java +++ b/core/src/com/etheller/warsmash/units/custom/WTS.java @@ -2,4 +2,11 @@ package com.etheller.warsmash.units.custom; public interface WTS { String get(int key); + + WTS DO_NOTHING = new WTS() { + @Override + public String get(final int key) { + return "TRIGSTR_" + key; + } + }; } diff --git a/core/src/com/etheller/warsmash/viewer5/Bounds.java b/core/src/com/etheller/warsmash/viewer5/Bounds.java index 153d483..e00e044 100644 --- a/core/src/com/etheller/warsmash/viewer5/Bounds.java +++ b/core/src/com/etheller/warsmash/viewer5/Bounds.java @@ -9,7 +9,7 @@ public class Bounds { public float x, y, z, r; private BoundingBox boundingBox; - public void fromExtents(final float[] min, final float[] max) { + public void fromExtents(final float[] min, final float[] max, final float boundsRadius) { final float x = min[0]; final float y = min[1]; final float z = min[2]; @@ -20,7 +20,7 @@ public class Bounds { this.x = x + (w / 2f); this.y = y + (d / 2f); this.z = z + (h / 2f); - this.r = (float) (Math.max(Math.max(w, d), h) / 2.); + this.r = boundsRadius > 0 ? boundsRadius : (float) (Math.max(Math.max(w, d), h) / 2.); this.boundingBox = new BoundingBox(new Vector3(min), new Vector3(max)); } @@ -33,6 +33,6 @@ public class Bounds { } public BoundingBox getBoundingBox() { - return boundingBox; + return this.boundingBox; } } diff --git a/core/src/com/etheller/warsmash/viewer5/ModelInstance.java b/core/src/com/etheller/warsmash/viewer5/ModelInstance.java index 9d75fae..c145516 100644 --- a/core/src/com/etheller/warsmash/viewer5/ModelInstance.java +++ b/core/src/com/etheller/warsmash/viewer5/ModelInstance.java @@ -1,6 +1,7 @@ package com.etheller.warsmash.viewer5; import com.badlogic.gdx.math.Matrix4; +import com.badlogic.gdx.math.Vector3; import com.etheller.warsmash.util.RenderMathUtils; import com.etheller.warsmash.util.Vector4; @@ -88,6 +89,64 @@ public abstract class ModelInstance extends Node { return scene.addInstance(this); } + @Override + public Node move(final float[] offset) { + final Node result = super.move(offset); + updateSceneGridLocationInfo(); + return result; + } + + @Override + public Node moveTo(final float[] offset) { + final Node result = super.moveTo(offset); + updateSceneGridLocationInfo(); + return result; + } + + @Override + public Node setLocation(final float x, final float y, final float z) { + final Node result = super.setLocation(x, y, z); + updateSceneGridLocationInfo(); + return result; + } + + @Override + public Node setLocation(final float[] location) { + final Node result = super.setLocation(location); + updateSceneGridLocationInfo(); + return result; + } + + @Override + public Node setLocation(final Vector3 location) { + final Node result = super.setLocation(location); + updateSceneGridLocationInfo(); + return result; + } + + private void updateSceneGridLocationInfo() { + if (this.scene != null) { + // can't just use world location if it moves + float x, y; + if (this.dirty) { + // TODO this is an incorrect, predicted location for dirty case + if ((this.parent != null) && !this.dontInheritTranslation) { + x = this.parent.localLocation.x + this.localLocation.x; + y = this.parent.localLocation.y + this.localLocation.y; + } + else { + x = this.localLocation.x; + y = this.localLocation.y; + } + } + else { + x = this.worldLocation.x; + y = this.worldLocation.y; + } + this.scene.instanceMoved(this, x, y); + } + } + @Override public void recalculateTransformation() { super.recalculateTransformation(); @@ -100,28 +159,48 @@ public abstract class ModelInstance extends Node { public boolean isVisible(final Camera camera) { // can't just use world location if it moves float x, y, z; + float sx, sy, sz; if (this.dirty) { // TODO this is an incorrect, predicted location for dirty case if ((this.parent != null) && !this.dontInheritTranslation) { x = this.parent.localLocation.x + this.localLocation.x; y = this.parent.localLocation.y + this.localLocation.y; z = this.parent.localLocation.z + this.localLocation.z; + sx = this.parent.localScale.x * this.localScale.x; + sy = this.parent.localScale.y * this.localScale.y; + sz = this.parent.localScale.z * this.localScale.z; } else { x = this.localLocation.x; y = this.localLocation.y; z = this.localLocation.z; + sx = this.localScale.x; + sy = this.localScale.y; + sz = this.localScale.z; } } else { x = this.worldLocation.x; y = this.worldLocation.y; z = this.worldLocation.z; + sx = this.worldScale.x; + sy = this.worldScale.y; + sz = this.worldScale.z; } + // Get the biggest scaling dimension. + if (sy > sx) { + sx = sy; + } + + if (sz > sx) { + sx = sz; + } + final Bounds bounds = this.model.bounds; final Vector4[] planes = camera.planes; - this.plane = RenderMathUtils.testSphere(planes, x + bounds.x, y + bounds.y, z, bounds.r, this.plane); + this.plane = RenderMathUtils.testSphere(planes, x + bounds.x, y + bounds.y, z + bounds.z, bounds.r * sx, + this.plane); if (this.plane == -1) { this.depth = RenderMathUtils.distanceToPlane3(planes[4], x, y, z); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java index b314d0c..953aa6b 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxModel.java @@ -114,7 +114,7 @@ public class MdxModel extends com.etheller.warsmash.viewer5.Model { min[i] = max[i] = 0; } } - this.bounds.fromExtents(min, max); + this.bounds.fromExtents(min, max, extent.getBoundsRadius()); // Sequences for (final MdlxSequence sequence : parser.getSequences()) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SequenceUtils.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SequenceUtils.java index b6f1111..c9e0583 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SequenceUtils.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SequenceUtils.java @@ -19,6 +19,7 @@ public class SequenceUtils { public static final EnumSet BONE = EnumSet.of(SecondaryTag.BONE); public static final EnumSet HIT = EnumSet.of(SecondaryTag.HIT); public static final EnumSet SPELL = EnumSet.of(SecondaryTag.SPELL); + public static final EnumSet WORK = EnumSet.of(SecondaryTag.WORK); private static final StandSequenceComparator STAND_SEQUENCE_COMPARATOR = new StandSequenceComparator(); private static final SecondaryTagSequenceComparator SECONDARY_TAG_SEQUENCE_COMPARATOR = new SecondaryTagSequenceComparator( @@ -283,7 +284,8 @@ public class SequenceUtils { return sequence.sequence; } else { - if (animationName == null) { + if ((animationName == null) || (secondaryAnimationTags.size() != 1) + || !secondaryAnimationTags.contains(SecondaryTag.SPELL)) { return null; } else { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitSound.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitSound.java index c62d2e0..e87b503 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitSound.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitSound.java @@ -82,7 +82,7 @@ public final class UnitSound { if (millisTime < unit.lastUnitResponseEndTimeMillis) { return false; } - if (play(audioContext, unit.location[0], unit.location[1])) { + if (play(audioContext, unit.location[0], unit.location[1], unit.location[2])) { final float duration = Extensions.audio.getDuration(this.lastPlayedSound); unit.lastUnitResponseEndTimeMillis = millisTime + (long) (1000 * duration); return true; @@ -90,11 +90,11 @@ public final class UnitSound { return false; } - public boolean play(final AudioContext audioContext, final float x, final float y) { - return play(audioContext, x, y, (int) (Math.random() * this.sounds.size())); + public boolean play(final AudioContext audioContext, final float x, final float y, final float z) { + return play(audioContext, x, y, z, (int) (Math.random() * this.sounds.size())); } - public boolean play(final AudioContext audioContext, final float x, final float y, final int index) { + public boolean play(final AudioContext audioContext, final float x, final float y, final float z, final int index) { if (this.sounds.isEmpty()) { return false; } @@ -106,7 +106,7 @@ public final class UnitSound { final AudioBufferSource source = audioContext.createBufferSource(); // Panner settings - panner.setPosition(x, y, 0); + panner.setPosition(x, y, z); panner.setDistances(this.distanceCutoff, this.minDistance); panner.connect(audioContext.destination); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index 900c6b8..9721338 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -537,6 +537,10 @@ public class War3MapViewer extends AbstractMdxModelViewer { @Override public void spawnDamageSound(final CWidget damagedDestructable, final String weaponSound, final String armorType) { + RenderWidget damagedWidget = War3MapViewer.this.unitToRenderPeer.get(damagedDestructable); + if (damagedWidget == null) { + damagedWidget = War3MapViewer.this.destructableToRenderPeer.get(damagedDestructable); + } final String key = weaponSound + armorType; UnitSound combatSound = this.keyToCombatSound.get(key); if (combatSound == null) { @@ -545,7 +549,7 @@ public class War3MapViewer extends AbstractMdxModelViewer { this.keyToCombatSound.put(key, combatSound); } combatSound.play(War3MapViewer.this.worldScene.audioContext, damagedDestructable.getX(), - damagedDestructable.getY()); + damagedDestructable.getY(), damagedWidget.getZ()); } @Override @@ -599,9 +603,10 @@ public class War3MapViewer extends AbstractMdxModelViewer { public void spawnUnitConstructionFinishSound(final CUnit constructedStructure) { final UnitSound constructingBuilding = War3MapViewer.this.uiSounds .getSound(War3MapViewer.this.gameUI.getSkinField("JobDoneSound")); + final RenderUnit renderUnit = War3MapViewer.this.unitToRenderPeer.get(constructedStructure); if (constructingBuilding != null) { constructingBuilding.play(War3MapViewer.this.worldScene.audioContext, - constructedStructure.getX(), constructedStructure.getY()); + constructedStructure.getX(), constructedStructure.getY(), renderUnit.getZ()); } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java index 3cf3990..fe868ce 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java @@ -603,25 +603,25 @@ public class Terrain { final int bottomLeftHeight = bottomLeft.getLayerHeight() - base; boolean invalidRamp = false; if (DISALLOW_HEIGHT_3_RAMPS) { - if (topLeftHeight > 1) { + if (rampBlockedByCliff) { + invalidRamp = true; + } + else if (topLeftHeight > 1) { invalidRamp = true; topLeft.setRamp(0); } - if (topRightHeight > 1) { + else if (topRightHeight > 1) { invalidRamp = true; topRight.setRamp(0); } - if (bottomRightHeight > 1) { + else if (bottomRightHeight > 1) { invalidRamp = true; bottomRight.setRamp(0); } - if (bottomLeftHeight > 1) { + else if (bottomLeftHeight > 1) { invalidRamp = true; bottomLeft.setRamp(0); } - if (rampBlockedByCliff) { - invalidRamp = true; - } } if (!invalidRamp) { String fileName = "" + getRampLetter(topLeftHeight, topLeft.isRamp()) @@ -702,6 +702,15 @@ public class Terrain { } } } + for (int i = (int) rampArea.getX(); i < xLimit; i++) { + final int yLimit = (int) ((rampArea.getY() + rampArea.getHeight()) - 1); + for (int j = (int) rampArea.getY(); j < yLimit; j++) { + final RenderCorner bottomLeft = this.corners[i][j]; + if (bottomLeft.isRamp() && !bottomLeft.romp) { + bottomLeft.hideCliff = true; + } + } + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderDestructable.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderDestructable.java index 7642f39..6da29d4 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderDestructable.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderDestructable.java @@ -127,6 +127,11 @@ public class RenderDestructable extends RenderDoodad implements RenderWidget { return this.y; } + @Override + public float getZ() { + return this.instance.localLocation.z; + } + @Override public void unassignSelectionCircle() { this.selectionCircle = null; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java index 36d98d8..08bec1c 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java @@ -479,6 +479,11 @@ public class RenderUnit implements RenderWidget { return this.location[1]; } + @Override + public float getZ() { + return this.location[2]; + } + @Override public void unassignSelectionCircle() { this.selectionCircle = null; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderWidget.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderWidget.java index 64df533..4aa2e50 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderWidget.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderWidget.java @@ -31,6 +31,8 @@ public interface RenderWidget { float getY(); + float getZ(); + void unassignSelectionCircle(); void assignSelectionCircle(SplatMover t); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityDataUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityDataUI.java index 7b3a57d..d83ba7a 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityDataUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityDataUI.java @@ -23,15 +23,25 @@ public class AbilityDataUI { private static final War3ID ICON_RESEARCH = War3ID.fromString("arar"); private static final War3ID ICON_RESEARCH_X = War3ID.fromString("arpx"); private static final War3ID ICON_RESEARCH_Y = War3ID.fromString("arpy"); + private static final War3ID ABILITY_TIP = War3ID.fromString("atp1"); + private static final War3ID ABILITY_UBER_TIP = War3ID.fromString("aub1"); + private static final War3ID ABILITY_UN_TIP = War3ID.fromString("aut1"); + private static final War3ID ABILITY_UN_UBER_TIP = War3ID.fromString("auu1"); + private static final War3ID ABILITY_RESEARCH_TIP = War3ID.fromString("aret"); + private static final War3ID ABILITY_RESEARCH_UBER_TIP = War3ID.fromString("arut"); private static final War3ID UNIT_ICON_NORMAL_X = War3ID.fromString("ubpx"); private static final War3ID UNIT_ICON_NORMAL_Y = War3ID.fromString("ubpy"); private static final War3ID UNIT_ICON_NORMAL = War3ID.fromString("uico"); + private static final War3ID UNIT_TIP = War3ID.fromString("utip"); + private static final War3ID UNIT_UBER_TIP = War3ID.fromString("utub"); private static final War3ID UPGRADE_ICON_NORMAL_X = War3ID.fromString("gbpx"); private static final War3ID UPGRADE_ICON_NORMAL_Y = War3ID.fromString("gbpy"); private static final War3ID UPGRADE_ICON_NORMAL = War3ID.fromString("gar1"); private static final War3ID UPGRADE_LEVELS = War3ID.fromString("glvl"); + private static final War3ID UPGRADE_TIP = War3ID.fromString("gtp1"); + private static final War3ID UPGRADE_UBER_TIP = War3ID.fromString("gub1"); private final Map rawcodeToUI = new HashMap<>(); private final Map rawcodeToUnitUI = new HashMap<>(); @@ -61,6 +71,12 @@ public class AbilityDataUI { final String iconResearchPath = gameUI.trySkinField(abilityTypeData.getFieldAsString(ICON_RESEARCH, 0)); final String iconNormalPath = gameUI.trySkinField(abilityTypeData.getFieldAsString(ICON_NORMAL, 0)); final String iconTurnOffPath = gameUI.trySkinField(abilityTypeData.getFieldAsString(ICON_TURN_OFF, 0)); + final String iconTip = abilityTypeData.getFieldAsString(ABILITY_TIP, 0); + final String iconUberTip = abilityTypeData.getFieldAsString(ABILITY_UBER_TIP, 0); + final String iconTurnOffTip = abilityTypeData.getFieldAsString(ABILITY_UN_TIP, 0); + final String iconTurnOffUberTip = abilityTypeData.getFieldAsString(ABILITY_UN_UBER_TIP, 0); + final String iconResearchTip = abilityTypeData.getFieldAsString(ABILITY_RESEARCH_TIP, 0); + final String iconResearchUberTip = abilityTypeData.getFieldAsString(ABILITY_RESEARCH_UBER_TIP, 0); final int iconResearchX = abilityTypeData.getFieldAsInteger(ICON_RESEARCH_X, 0); final int iconResearchY = abilityTypeData.getFieldAsInteger(ICON_RESEARCH_Y, 0); final int iconNormalX = abilityTypeData.getFieldAsInteger(ICON_NORMAL_X, 0); @@ -74,18 +90,24 @@ public class AbilityDataUI { final Texture iconTurnOff = gameUI.loadTexture(iconTurnOffPath); final Texture iconTurnOffDisabled = gameUI.loadTexture(disable(iconTurnOffPath, disabledPrefix)); this.rawcodeToUI.put(alias, - new AbilityIconUI(new IconUI(iconResearch, iconResearchDisabled, iconResearchX, iconResearchY), - new IconUI(iconNormal, iconNormalDisabled, iconNormalX, iconNormalY), - new IconUI(iconTurnOff, iconTurnOffDisabled, iconTurnOffX, iconTurnOffY))); + new AbilityIconUI( + new IconUI(iconResearch, iconResearchDisabled, iconResearchX, iconResearchY, + iconResearchTip, iconResearchUberTip), + new IconUI(iconNormal, iconNormalDisabled, iconNormalX, iconNormalY, iconTip, iconUberTip), + new IconUI(iconTurnOff, iconTurnOffDisabled, iconTurnOffX, iconTurnOffY, iconTurnOffTip, + iconTurnOffUberTip))); } for (final War3ID alias : unitData.keySet()) { final MutableGameObject abilityTypeData = unitData.get(alias); final String iconNormalPath = gameUI.trySkinField(abilityTypeData.getFieldAsString(UNIT_ICON_NORMAL, 0)); final int iconNormalX = abilityTypeData.getFieldAsInteger(UNIT_ICON_NORMAL_X, 0); final int iconNormalY = abilityTypeData.getFieldAsInteger(UNIT_ICON_NORMAL_Y, 0); + final String iconTip = abilityTypeData.getFieldAsString(UNIT_TIP, 0); + final String iconUberTip = abilityTypeData.getFieldAsString(UNIT_UBER_TIP, 0); final Texture iconNormal = gameUI.loadTexture(iconNormalPath); final Texture iconNormalDisabled = gameUI.loadTexture(disable(iconNormalPath, disabledPrefix)); - this.rawcodeToUnitUI.put(alias, new IconUI(iconNormal, iconNormalDisabled, iconNormalX, iconNormalY)); + this.rawcodeToUnitUI.put(alias, + new IconUI(iconNormal, iconNormalDisabled, iconNormalX, iconNormalY, iconTip, iconUberTip)); } for (final War3ID alias : upgradeData.keySet()) { final MutableGameObject upgradeTypeData = upgradeData.get(alias); @@ -94,11 +116,14 @@ public class AbilityDataUI { final int iconNormalY = upgradeTypeData.getFieldAsInteger(UPGRADE_ICON_NORMAL_Y, 0); final List upgradeIconsByLevel = new ArrayList<>(); for (int i = 0; i < upgradeLevels; i++) { + final String iconTip = upgradeTypeData.getFieldAsString(UPGRADE_TIP, 0); + final String iconUberTip = upgradeTypeData.getFieldAsString(UPGRADE_UBER_TIP, 0); final String iconNormalPath = gameUI .trySkinField(upgradeTypeData.getFieldAsString(UPGRADE_ICON_NORMAL, i)); final Texture iconNormal = gameUI.loadTexture(iconNormalPath); final Texture iconNormalDisabled = gameUI.loadTexture(disable(iconNormalPath, disabledPrefix)); - upgradeIconsByLevel.add(new IconUI(iconNormal, iconNormalDisabled, iconNormalX, iconNormalY)); + upgradeIconsByLevel.add( + new IconUI(iconNormal, iconNormalDisabled, iconNormalX, iconNormalY, iconTip, iconUberTip)); } this.rawcodeToUpgradeUI.put(alias, upgradeIconsByLevel); } @@ -127,7 +152,9 @@ public class AbilityDataUI { final Texture iconDisabled = gameUI.loadTexture(disable(iconPath, disabledPrefix)); final int buttonPositionX = builtInAbility.getFieldValue("Buttonpos", 0); final int buttonPositionY = builtInAbility.getFieldValue("Buttonpos", 1); - return new IconUI(icon, iconDisabled, buttonPositionX, buttonPositionY); + final String tip = builtInAbility.getField("Tip"); + final String uberTip = builtInAbility.getField("UberTip"); + return new IconUI(icon, iconDisabled, buttonPositionX, buttonPositionY, tip, uberTip); } public AbilityIconUI getUI(final War3ID rawcode) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/IconUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/IconUI.java index 1cfbdb3..8d56183 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/IconUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/IconUI.java @@ -7,13 +7,17 @@ public class IconUI { private final Texture iconDisabled; private final int buttonPositionX; private final int buttonPositionY; + private final String toolTip; + private final String uberTip; - public IconUI(final Texture icon, final Texture iconDisabled, final int buttonPositionX, - final int buttonPositionY) { + public IconUI(final Texture icon, final Texture iconDisabled, final int buttonPositionX, final int buttonPositionY, + final String toolTip, final String uberTip) { this.icon = icon; this.iconDisabled = iconDisabled; this.buttonPositionX = buttonPositionX; this.buttonPositionY = buttonPositionY; + this.toolTip = toolTip; + this.uberTip = uberTip; } public Texture getIcon() { @@ -31,4 +35,12 @@ public class IconUI { public int getButtonPositionY() { return this.buttonPositionY; } + + public String getToolTip() { + return this.toolTip; + } + + public String getUberTip() { + return this.uberTip; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandButtonListener.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandButtonListener.java index 0840520..ad00601 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandButtonListener.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandButtonListener.java @@ -35,5 +35,6 @@ public interface CommandButtonListener { // // int getOrderId(); void commandButton(int buttonPositionX, int buttonPositionY, Texture icon, int abilityHandleId, int orderId, - int autoCastOrderId, boolean active, boolean autoCastActive, boolean menuButton); + int autoCastOrderId, boolean active, boolean autoCastActive, boolean menuButton, String tip, String uberTip, + int goldCost, int lumberCost, int foodCost); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java index b789909..14f14ea 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java @@ -6,6 +6,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.AbilityIconU import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.IconUI; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityAttack; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityGeneric; @@ -134,8 +135,9 @@ public class CommandCardPopulatingAbilityVisitor implements CAbilityVisitor= trainedUnitType.getBuildTime()) { @@ -314,7 +333,7 @@ public class CUnit extends CWidget { final int lastBehaviorHighlightOrderId = lastBehavior.getHighlightOrderId(); this.currentBehavior = this.currentBehavior.update(game); if (lastBehavior != this.currentBehavior) { - lastBehavior.end(game); + lastBehavior.end(game, false); this.currentBehavior.begin(game); } if (this.currentBehavior.getHighlightOrderId() != lastBehaviorHighlightOrderId) { @@ -328,6 +347,7 @@ public class CUnit extends CWidget { } } return false; + } private void popoutWorker(final CSimulation game) { @@ -399,7 +419,7 @@ public class CUnit extends CWidget { else { setDefaultBehavior(this.stopBehavior); if (this.currentBehavior != null) { - this.currentBehavior.end(game); + this.currentBehavior.end(game, true); } this.currentBehavior = beginOrder(game, order); if (this.currentBehavior != null) { @@ -658,7 +678,7 @@ public class CUnit extends CWidget { private void kill(final CSimulation simulation) { if (this.currentBehavior != null) { - this.currentBehavior.end(simulation); + this.currentBehavior.end(simulation, true); } this.currentBehavior = null; this.orderQueue.clear(); @@ -862,7 +882,7 @@ public class CUnit extends CWidget { && unit.canBeTargetedBy(this.game, this.source, attack.getTargetsAllowed()) && (this.source.distance(unit) >= this.source.getUnitType().getMinimumAttackRange())) { if (this.source.currentBehavior != null) { - this.source.currentBehavior.end(this.game); + this.source.currentBehavior.end(this.game, false); } this.source.currentBehavior = this.source.getAttackBehavior().reset(OrderIds.attack, attack, unit, this.disableMove, CBehaviorAttackListener.DO_NOTHING); @@ -1073,11 +1093,21 @@ public class CUnit extends CWidget { final QueueItemType queueItemType) { this.buildQueue[index] = rawcode; this.buildQueueTypes[index] = queueItemType; - if ((index == 0) && (rawcode != null) && (queueItemType == QueueItemType.UNIT)) { - final CPlayer player = game.getPlayer(this.playerIndex); - final CUnitType unitType = game.getUnitData().getUnitType(this.buildQueue[index]); - if (unitType.getFoodUsed() != 0) { - player.setFoodUsed(player.getFoodUsed() + unitType.getFoodUsed()); + if (index == 0) { + this.queuedUnitFoodPaid = true; + if ((rawcode != null) && (queueItemType == QueueItemType.UNIT)) { + final CPlayer player = game.getPlayer(this.playerIndex); + final CUnitType unitType = game.getUnitData().getUnitType(this.buildQueue[index]); + if (unitType.getFoodUsed() != 0) { + final int newFoodUsed = player.getFoodUsed() + unitType.getFoodUsed(); + if (newFoodUsed <= player.getFoodCap()) { + player.setFoodUsed(newFoodUsed); + } + else { + this.queuedUnitFoodPaid = false; + game.getCommandErrorListener().showNoFoodError(); + } + } } } } @@ -1148,8 +1178,10 @@ public class CUnit extends CWidget { } } } - this.trainedUnit.order(this.game, - new COrderTargetPoint(abilityToUse.getHandleId(), this.rallyOrderId, target, false), false); + if (abilityToUse != null) { + this.trainedUnit.order(this.game, + new COrderTargetPoint(abilityToUse.getHandleId(), this.rallyOrderId, target, false), false); + } return null; } @@ -1172,9 +1204,11 @@ public class CUnit extends CWidget { } } } - trainedUnit.order(game, - new COrderTargetWidget(abilityToUse.getHandleId(), rallyOrderId, target.getHandleId(), false), - false); + if (abilityToUse != null) { + trainedUnit.order(game, + new COrderTargetWidget(abilityToUse.getHandleId(), rallyOrderId, target.getHandleId(), false), + false); + } return null; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java index 78db5ac..21a284b 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java @@ -19,6 +19,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetC import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.ResourceType; public abstract class AbstractCAbilityBuild extends AbstractCAbility implements CAbilityMenu { + private static boolean REFUND_ON_ORDER_CANCEL = false; private final Set structuresBuilt; public AbstractCAbilityBuild(final int handleId, final List structuresBuilt) { @@ -94,6 +95,19 @@ public abstract class AbstractCAbilityBuild extends AbstractCAbility implements return true; } + @Override + public void onCancelFromQueue(final CSimulation game, final CUnit unit, final int orderId) { + if (REFUND_ON_ORDER_CANCEL) { + final CPlayer player = game.getPlayer(unit.getPlayerIndex()); + final War3ID orderIdAsRawtype = new War3ID(orderId); + final CUnitType unitType = game.getUnitData().getUnitType(orderIdAsRawtype); + player.refundFor(unitType); + if (unitType.getFoodUsed() != 0) { + player.setFoodUsed(player.getFoodUsed() - unitType.getFoodUsed()); + } + } + } + @Override public void onTick(final CSimulation game, final CUnit unit) { } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityOrcBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityOrcBuild.java index 9b4d474..37a9f57 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityOrcBuild.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityOrcBuild.java @@ -65,17 +65,6 @@ public class CAbilityOrcBuild extends AbstractCAbilityBuild { return this.buildBehavior.reset(point, orderId, getBaseOrderId()); } - @Override - public void onCancelFromQueue(final CSimulation game, final CUnit unit, final int orderId) { - final CPlayer player = game.getPlayer(unit.getPlayerIndex()); - final War3ID orderIdAsRawtype = new War3ID(orderId); - final CUnitType unitType = game.getUnitData().getUnitType(orderIdAsRawtype); - player.refundFor(unitType); - if (unitType.getFoodUsed() != 0) { - player.setFoodUsed(player.getFoodUsed() - unitType.getFoodUsed()); - } - } - @Override public CBehavior beginNoTarget(final CSimulation game, final CUnit caster, final int orderId) { return caster.pollNextOrderBehavior(game); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityUndeadBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityUndeadBuild.java index b5a6276..8d5a8f7 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityUndeadBuild.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityUndeadBuild.java @@ -1,57 +1,25 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build; +import java.awt.image.BufferedImage; import java.util.List; import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.build.CBehaviorUndeadBuild; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayer; public class CAbilityUndeadBuild extends AbstractCAbilityBuild { + private CBehaviorUndeadBuild buildBehavior; public CAbilityUndeadBuild(final int handleId, final List structuresBuilt) { super(handleId, structuresBuilt); - // TODO Auto-generated constructor stub - } - - @Override - public int getBaseOrderId() { - return OrderIds.undeadbuild; - } - - @Override - public void onAdd(final CSimulation game, final CUnit unit) { - // TODO Auto-generated method stub - - } - - @Override - public void onRemove(final CSimulation game, final CUnit unit) { - // TODO Auto-generated method stub - - } - - @Override - public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final CWidget target) { - // TODO Auto-generated method stub - return null; - } - - @Override - public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, - final AbilityPointTarget point) { - // TODO Auto-generated method stub - return null; - } - - @Override - public CBehavior beginNoTarget(final CSimulation game, final CUnit caster, final int orderId) { - // TODO Auto-generated method stub - return null; } @Override @@ -60,9 +28,50 @@ public class CAbilityUndeadBuild extends AbstractCAbilityBuild { } @Override - public void onCancelFromQueue(final CSimulation game, final CUnit unit, final int orderId) { - // TODO Auto-generated method stub - + public void onAdd(final CSimulation game, final CUnit unit) { + this.buildBehavior = new CBehaviorUndeadBuild(unit); } + @Override + public void onRemove(final CSimulation game, final CUnit unit) { + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final CWidget target) { + return caster.pollNextOrderBehavior(game); + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, + final AbilityPointTarget point) { + final War3ID orderIdAsRawtype = new War3ID(orderId); + final CUnitType unitType = game.getUnitData().getUnitType(orderIdAsRawtype); + final BufferedImage buildingPathingPixelMap = unitType.getBuildingPathingPixelMap(); + if (buildingPathingPixelMap != null) { + point.x = (float) Math.floor(point.x / 64f) * 64f; + point.y = (float) Math.floor(point.y / 64f) * 64f; + if (((buildingPathingPixelMap.getWidth() / 2) % 2) == 1) { + point.x += 32f; + } + if (((buildingPathingPixelMap.getHeight() / 2) % 2) == 1) { + point.y += 32f; + } + } + final CPlayer player = game.getPlayer(caster.getPlayerIndex()); + player.chargeFor(unitType); + if (unitType.getFoodUsed() != 0) { + player.setFoodUsed(player.getFoodUsed() + unitType.getFoodUsed()); + } + return this.buildBehavior.reset(point, orderId, getBaseOrderId()); + } + + @Override + public CBehavior beginNoTarget(final CSimulation game, final CUnit caster, final int orderId) { + return caster.pollNextOrderBehavior(game); + } + + @Override + public int getBaseOrderId() { + return OrderIds.undeadbuild; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityReturnResources.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityReturnResources.java index 1c7b08b..c3d1051 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityReturnResources.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityReturnResources.java @@ -84,6 +84,9 @@ public class CAbilityReturnResources extends AbstractGenericNoIconAbility { } public boolean accepts(final ResourceType resourceType) { + if (isDisabled()) { + return false; + } return this.acceptedResourceTypes.contains(resourceType); } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/hero/CAbilityHero.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/hero/CAbilityHero.java new file mode 100644 index 0000000..c844845 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/hero/CAbilityHero.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.hero; + +public class CAbilityHero { + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehavior.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehavior.java index 7bfc44d..beb10a0 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehavior.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehavior.java @@ -13,7 +13,7 @@ public interface CBehavior { void begin(CSimulation game); - void end(CSimulation game); + void end(CSimulation game, boolean interrupted); int getHighlightOrderId(); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java index 8e9d655..1750c82 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java @@ -134,7 +134,11 @@ public class CBehaviorAttack extends CAbstractRangedBehavior { } @Override - public void end(final CSimulation game) { + public void end(final CSimulation game, final boolean interrupted) { + } + + @Override + public void endMove(final CSimulation game, final boolean interrupted) { } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorFollow.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorFollow.java index 5b128e5..2134745 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorFollow.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorFollow.java @@ -55,7 +55,12 @@ public class CBehaviorFollow extends CAbstractRangedBehavior { } @Override - public void end(final CSimulation game) { + public void end(final CSimulation game, final boolean interrupted) { + + } + + @Override + public void endMove(final CSimulation game, final boolean interrupted) { } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorHoldPosition.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorHoldPosition.java index ada8684..91cbd53 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorHoldPosition.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorHoldPosition.java @@ -35,7 +35,7 @@ public class CBehaviorHoldPosition implements CBehavior { } @Override - public void end(final CSimulation game) { + public void end(final CSimulation game, boolean interrupted) { } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorMove.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorMove.java index 4c772ef..4cc32fa 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorMove.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorMove.java @@ -110,6 +110,7 @@ public class CBehaviorMove implements CBehavior { this.firstUpdate = false; } if (this.pathfindingFailedGiveUp) { + onMoveGiveUp(simulation); return this.unit.pollNextOrderBehavior(simulation); } final float prevX = this.unit.getX(); @@ -247,6 +248,7 @@ public class CBehaviorMove implements CBehavior { this.searchCycles = 0; } if (this.path.isEmpty()) { + onMoveGiveUp(simulation); return this.unit.pollNextOrderBehavior(simulation); } else { @@ -279,6 +281,7 @@ public class CBehaviorMove implements CBehavior { deltaY = currentTargetY - nextY; deltaX = currentTargetX - nextX; if ((deltaX == 0.000f) && (deltaY == 0.000f) && this.path.isEmpty()) { + onMoveGiveUp(simulation); return this.unit.pollNextOrderBehavior(simulation); } System.out.println("new target: " + currentTargetX + "," + currentTargetY); @@ -341,6 +344,12 @@ public class CBehaviorMove implements CBehavior { return this; } + private void onMoveGiveUp(final CSimulation simulation) { + if (this.rangedBehavior != null) { + this.rangedBehavior.endMove(simulation, true); + } + } + private final class TargetVisitingResetter implements AbilityTargetVisitor { private int highlightOrderId; @@ -380,11 +389,14 @@ public class CBehaviorMove implements CBehavior { } @Override - public void end(final CSimulation game) { + public void end(final CSimulation game, final boolean interrupted) { if (ALWAYS_INTERRUPT_MOVE) { game.removeFromPathfindingQueue(this); this.pathfindingActive = false; } + if (this.rangedBehavior != null) { + this.rangedBehavior.endMove(game, interrupted); + } } public CUnit getUnit() { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorPatrol.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorPatrol.java index fa4c097..b1edb7b 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorPatrol.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorPatrol.java @@ -54,7 +54,12 @@ public class CBehaviorPatrol implements CRangedBehavior { } @Override - public void end(final CSimulation game) { + public void end(final CSimulation game, final boolean interrupted) { + + } + + @Override + public void endMove(final CSimulation game, final boolean interrupted) { } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorStop.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorStop.java index cea203f..e45bc02 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorStop.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorStop.java @@ -34,7 +34,7 @@ public class CBehaviorStop implements CBehavior { } @Override - public void end(final CSimulation game) { + public void end(final CSimulation game, boolean interrupted) { } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CRangedBehavior.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CRangedBehavior.java index 5c1f8a1..6b8ce91 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CRangedBehavior.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CRangedBehavior.java @@ -4,4 +4,6 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; public interface CRangedBehavior extends CBehavior { boolean isWithinRange(final CSimulation simulation); + + void endMove(CSimulation game, boolean interrupted); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/AbilityDisableWhileUnderConstructionVisitor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/AbilityDisableWhileUnderConstructionVisitor.java index 9280afb..5c9a76a 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/AbilityDisableWhileUnderConstructionVisitor.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/AbilityDisableWhileUnderConstructionVisitor.java @@ -120,7 +120,7 @@ public class AbilityDisableWhileUnderConstructionVisitor implements CAbilityVisi @Override public Void accept(final GenericNoIconAbility ability) { - ability.setDisabled(false); + ability.setDisabled(true); ability.setIconShowing(false); return null; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java index 20025df..78a72e7 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java @@ -109,8 +109,8 @@ public class CBehaviorOrcBuild extends CAbstractRangedBehavior { } @Override - public void end(final CSimulation game) { - if (!this.unitCreated) { + public void end(final CSimulation game, final boolean interrupted) { + if (!this.unitCreated && interrupted) { final CPlayer player = game.getPlayer(this.unit.getPlayerIndex()); final CUnitType unitTypeToCreate = game.getUnitData().getUnitType(this.orderId); refund(player, unitTypeToCreate); @@ -122,4 +122,13 @@ public class CBehaviorOrcBuild extends CAbstractRangedBehavior { player.refundFor(unitTypeToCreate); } + @Override + public void endMove(final CSimulation game, final boolean interrupted) { + if (!this.unitCreated && interrupted) { + final CPlayer player = game.getPlayer(this.unit.getPlayerIndex()); + final CUnitType unitTypeToCreate = game.getUnitData().getUnitType(this.orderId); + refund(player, unitTypeToCreate); + } + } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorUndeadBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorUndeadBuild.java new file mode 100644 index 0000000..a0f6733 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorUndeadBuild.java @@ -0,0 +1,152 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.build; + +import java.awt.image.BufferedImage; +import java.util.EnumSet; + +import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.util.WarsmashConstants; +import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; +import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityBuildInProgress; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CAbstractRangedBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.pathing.CBuildingPathingType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayer; + +public class CBehaviorUndeadBuild extends CAbstractRangedBehavior { + private static int delayAnimationTicks = (int) (2.267f / WarsmashConstants.SIMULATION_STEP_TIME); + private int highlightOrderId; + private War3ID orderId; + private boolean unitCreated = false; + private int doneTick = 0; + + public CBehaviorUndeadBuild(final CUnit unit) { + super(unit); + } + + public CBehavior reset(final AbilityPointTarget target, final int orderId, final int highlightOrderId) { + this.highlightOrderId = highlightOrderId; + this.orderId = new War3ID(orderId); + this.unitCreated = false; + return innerReset(target); + } + + @Override + public boolean isWithinRange(final CSimulation simulation) { + if (this.doneTick != 0) { + return true; + } + final CUnitType unitType = simulation.getUnitData().getUnitType(this.orderId); + return this.unit.canReachToPathing(0, simulation.getGameplayConstants().getBuildingAngle(), + unitType.getBuildingPathingPixelMap(), this.target.getX(), this.target.getY()); + } + + @Override + public int getHighlightOrderId() { + return this.highlightOrderId; + } + + @Override + protected CBehavior update(final CSimulation simulation, final boolean withinRange) { + if (this.doneTick != 0) { + if (simulation.getGameTurnTick() > this.doneTick) { + return this.unit.pollNextOrderBehavior(simulation); + } + } + else if (!this.unitCreated) { + this.unitCreated = true; + final CUnitType unitTypeToCreate = simulation.getUnitData().getUnitType(this.orderId); + final BufferedImage buildingPathingPixelMap = unitTypeToCreate.getBuildingPathingPixelMap(); + boolean buildLocationObstructed = false; + if (buildingPathingPixelMap != null) { + final EnumSet preventedPathingTypes = unitTypeToCreate.getPreventedPathingTypes(); + final EnumSet requiredPathingTypes = unitTypeToCreate.getRequiredPathingTypes(); + + if (!simulation.getPathingGrid().checkPathingTexture(this.target.getX(), this.target.getY(), + (int) simulation.getGameplayConstants().getBuildingAngle(), buildingPathingPixelMap, + preventedPathingTypes, requiredPathingTypes, simulation.getWorldCollision(), this.unit)) { + buildLocationObstructed = true; + } + } + if (!buildLocationObstructed) { + final CUnit constructedStructure = simulation.createUnit(this.orderId, this.unit.getPlayerIndex(), + this.target.getX(), this.target.getY(), simulation.getGameplayConstants().getBuildingAngle()); + constructedStructure.setConstructing(true); + constructedStructure.setLife(simulation, + constructedStructure.getMaximumLife() * WarsmashConstants.BUILDING_CONSTRUCT_START_LIFE); + constructedStructure.setFoodUsed(unitTypeToCreate.getFoodUsed()); + constructedStructure.add(simulation, + new CAbilityBuildInProgress(simulation.getHandleIdAllocator().createId())); + for (final CAbility ability : constructedStructure.getAbilities()) { + ability.visit(AbilityDisableWhileUnderConstructionVisitor.INSTANCE); + } + final float deltaX = this.unit.getX() - this.target.getX(); + final float deltaY = this.unit.getY() - this.target.getY(); + final float delta = (float) Math.sqrt((deltaX * deltaX) + (deltaY * deltaY)); + this.unit.setPoint(this.target.getX() + ((deltaX / delta) * unitTypeToCreate.getCollisionSize()), + this.target.getY() + ((deltaY / delta) * unitTypeToCreate.getCollisionSize()), + simulation.getWorldCollision()); + simulation.unitRepositioned(this.unit); + simulation.unitConstructedEvent(this.unit, constructedStructure); + this.doneTick = simulation.getGameTurnTick() + delayAnimationTicks; + } + else { + final CPlayer player = simulation.getPlayer(this.unit.getPlayerIndex()); + refund(player, unitTypeToCreate); + simulation.getCommandErrorListener().showCantPlaceError(); + return this.unit.pollNextOrderBehavior(simulation); + } + } + this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.STAND, SequenceUtils.WORK, 1.0f, true); + return this; + } + + @Override + protected boolean checkTargetStillValid(final CSimulation simulation) { + return true; + } + + @Override + protected CBehavior updateOnInvalidTarget(final CSimulation simulation) { + return this.unit.pollNextOrderBehavior(simulation); + } + + @Override + protected void resetBeforeMoving(final CSimulation simulation) { + + } + + @Override + public void begin(final CSimulation game) { + + } + + @Override + public void end(final CSimulation game, final boolean interrupted) { + if (!this.unitCreated && interrupted) { + final CPlayer player = game.getPlayer(this.unit.getPlayerIndex()); + final CUnitType unitTypeToCreate = game.getUnitData().getUnitType(this.orderId); + refund(player, unitTypeToCreate); + } + } + + private void refund(final CPlayer player, final CUnitType unitTypeToCreate) { + player.setFoodUsed(player.getFoodUsed() - unitTypeToCreate.getFoodUsed()); + player.refundFor(unitTypeToCreate); + } + + @Override + public void endMove(final CSimulation game, final boolean interrupted) { + if (!this.unitCreated && interrupted) { + final CPlayer player = game.getPlayer(this.unit.getPlayerIndex()); + final CUnitType unitTypeToCreate = game.getUnitData().getUnitType(this.orderId); + refund(player, unitTypeToCreate); + } + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/harvest/CBehaviorHarvest.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/harvest/CBehaviorHarvest.java index 05ad0ba..ab50413 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/harvest/CBehaviorHarvest.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/harvest/CBehaviorHarvest.java @@ -192,7 +192,7 @@ public class CBehaviorHarvest extends CAbstractRangedBehavior } @Override - public void end(final CSimulation game) { + public void end(final CSimulation game, final boolean interrupted) { } @@ -220,4 +220,9 @@ public class CBehaviorHarvest extends CAbstractRangedBehavior this.abilityHarvest.setCarriedResources(null, 0); } + @Override + public void endMove(final CSimulation game, final boolean interrupted) { + + } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/harvest/CBehaviorReturnResources.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/harvest/CBehaviorReturnResources.java index 834b0b4..f6b43cd 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/harvest/CBehaviorReturnResources.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/harvest/CBehaviorReturnResources.java @@ -223,7 +223,12 @@ public class CBehaviorReturnResources extends CAbstractRangedBehavior implements } @Override - public void end(final CSimulation game) { + public void end(final CSimulation game, final boolean interrupted) { + + } + + @Override + public void endMove(final CSimulation game, final boolean interrupted) { } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/test/CBehaviorChannelTest.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/test/CBehaviorChannelTest.java index 52615b5..d64ecd9 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/test/CBehaviorChannelTest.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/test/CBehaviorChannelTest.java @@ -38,7 +38,7 @@ public class CBehaviorChannelTest implements CBehavior { } @Override - public void end(final CSimulation game) { + public void end(final CSimulation game, boolean interrupted) { } @Override diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/ResourceType.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/ResourceType.java index a375252..6e7977c 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/ResourceType.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/ResourceType.java @@ -5,4 +5,5 @@ public enum ResourceType { LUMBER, FOOD; + public static final ResourceType[] VALUES = values(); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java index 351e4d3..a3235ba 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java @@ -29,6 +29,11 @@ public class CommandCardIcon extends AbstractRenderableFrame implements Clickabl private boolean autoCastActive; private final CommandCardCommandListener commandCardCommandListener; private boolean menuButton; + private String tip; + private String uberTip; + private int tipGoldCost; + private int tipLumberCost; + private int tipFoodCost; public CommandCardIcon(final String name, final UIFrame parent, final CommandCardCommandListener commandCardCommandListener) { @@ -74,7 +79,8 @@ public class CommandCardIcon extends AbstractRenderableFrame implements Clickabl } public void setCommandButtonData(final Texture texture, final int abilityHandleId, final int orderId, - final int autoCastOrderId, final boolean active, final boolean autoCastActive, final boolean menuButton) { + final int autoCastOrderId, final boolean active, final boolean autoCastActive, final boolean menuButton, + final String tip, final String uberTip, final int goldCost, final int lumberCost, final int foodCost) { this.menuButton = menuButton; setVisible(true); this.iconFrame.setVisible(true); @@ -96,6 +102,11 @@ public class CommandCardIcon extends AbstractRenderableFrame implements Clickabl this.abilityHandleId = abilityHandleId; this.orderId = orderId; this.autoCastOrderId = autoCastOrderId; + this.tip = tip; + this.uberTip = uberTip; + this.tipGoldCost = goldCost; + this.tipLumberCost = lumberCost; + this.tipFoodCost = foodCost; } @Override @@ -175,6 +186,26 @@ public class CommandCardIcon extends AbstractRenderableFrame implements Clickabl @Override public String getToolTip() { - return "CommandCardIcon w/ OID=" + this.orderId; + return this.tip; + } + + @Override + public String getUberTip() { + return this.uberTip; + } + + @Override + public int getToolTipGoldCost() { + return this.tipGoldCost; + } + + @Override + public int getToolTipLumberCost() { + return this.tipLumberCost; + } + + @Override + public int getToolTipFoodCost() { + return this.tipFoodCost; } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java index 2302d9d..4eb17b5 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java @@ -35,6 +35,7 @@ import com.etheller.warsmash.parsers.fdf.datamodel.AnchorDefinition; import com.etheller.warsmash.parsers.fdf.datamodel.FramePoint; import com.etheller.warsmash.parsers.fdf.datamodel.TextJustify; import com.etheller.warsmash.parsers.fdf.datamodel.Vector4Definition; +import com.etheller.warsmash.parsers.fdf.frames.AbstractUIFrame; import com.etheller.warsmash.parsers.fdf.frames.FilterModeTextureFrame; import com.etheller.warsmash.parsers.fdf.frames.SetPoint; import com.etheller.warsmash.parsers.fdf.frames.SimpleFrame; @@ -73,6 +74,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.camera.CameraPreset; import com.etheller.warsmash.viewer5.handlers.w3x.camera.CameraRates; import com.etheller.warsmash.viewer5.handlers.w3x.camera.GameCameraManager; import com.etheller.warsmash.viewer5.handlers.w3x.camera.PortraitCameraManager; +import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.PathingFlags; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderUnit; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderWidget; @@ -128,6 +130,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.BooleanAbility import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.CWidgetAbilityTargetCheckReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.MeleeUIAbilityActivationReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.PointAbilityTargetCheckReceiver; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.ResourceType; import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.ClickableActionFrame; import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.CommandCardCommandListener; import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.CommandErrorListener; @@ -176,6 +179,10 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma // tooltip private UIFrame tooltipFrame; private StringFrame tooltipText; + private StringFrame tooltipUberTipText; + private UIFrame[] tooltipResourceFrames; + private TextureFrame[] tooltipResourceIconFrames; + private StringFrame[] tooltipResourceTextFrames; private UIFrame simpleInfoPanelUnitDetail; private StringFrame simpleNameValue; @@ -255,6 +262,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma private final List waypointModelInstances = new ArrayList<>(); private List selectedUnits; private BitmapFont textTagFont; + private SetPoint uberTipNoResourcesSetPoint; + private SetPoint uberTipWithResourcesSetPoint; public MeleeUI(final DataSource dataSource, final ExtendViewport uiViewport, final FreeTypeFontGenerator fontGenerator, final Scene uiScene, final Scene portraitScene, @@ -357,7 +366,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } } this.rootFrame = new GameUI(this.dataSource, GameUI.loadSkin(this.dataSource, racialSkinIndex), this.uiViewport, - this.fontGenerator, this.uiScene, this.war3MapViewer, racialCommandIndex); + this.fontGenerator, this.uiScene, this.war3MapViewer, racialCommandIndex, + this.war3MapViewer.getAllObjectData().getWts()); this.rootFrameListener.onCreate(this.rootFrame); try { this.rootFrame.loadTOCFile("UI\\FrameDef\\FrameDef.toc"); @@ -601,9 +611,36 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.tooltipFrame = this.rootFrame.createFrame("SmashToolTip", this.rootFrame, 0, 0); this.tooltipFrame.addAnchor(new AnchorDefinition(FramePoint.BOTTOMRIGHT, GameUI.convertX(this.uiViewport, 0.f), GameUI.convertY(this.uiViewport, 0.176f))); - this.tooltipFrame.setWidth(GameUI.convertX(this.uiViewport, 0.176f)); + this.tooltipFrame.setWidth(GameUI.convertX(this.uiViewport, 0.280f)); this.tooltipText = (StringFrame) this.rootFrame.getFrameByName("SmashToolTipText", 0); + this.tooltipText.setWidth(GameUI.convertX(this.uiViewport, 0.274f)); + this.tooltipText.addAnchor(new AnchorDefinition(FramePoint.TOPLEFT, GameUI.convertX(this.uiViewport, 0.003f), + GameUI.convertY(this.uiViewport, -0.003f))); this.tooltipFrame.setVisible(false); + this.tooltipUberTipText = (StringFrame) this.rootFrame.getFrameByName("SmashUberTipText", 0); + this.tooltipUberTipText.setWidth(GameUI.convertX(this.uiViewport, 0.274f)); + this.uberTipNoResourcesSetPoint = new SetPoint(FramePoint.TOPLEFT, this.tooltipText, FramePoint.BOTTOMLEFT, 0, + GameUI.convertY(this.uiViewport, -0.014f)); + this.uberTipWithResourcesSetPoint = new SetPoint(FramePoint.TOPLEFT, this.tooltipText, FramePoint.BOTTOMLEFT, 0, + GameUI.convertY(this.uiViewport, -0.024f)); + this.tooltipUberTipText.addSetPoint(this.uberTipNoResourcesSetPoint); + this.tooltipResourceFrames = new UIFrame[ResourceType.VALUES.length]; + this.tooltipResourceIconFrames = new TextureFrame[ResourceType.VALUES.length]; + this.tooltipResourceTextFrames = new StringFrame[ResourceType.VALUES.length]; + for (int i = 0; i < this.tooltipResourceFrames.length; i++) { + this.tooltipResourceFrames[i] = this.rootFrame.createFrame("SmashToolTipIconResource", this.tooltipFrame, 0, + i); + this.tooltipResourceIconFrames[i] = (TextureFrame) this.rootFrame + .getFrameByName("SmashToolTipIconResourceBackdrop", i); + this.tooltipResourceTextFrames[i] = (StringFrame) this.rootFrame + .getFrameByName("SmashToolTipIconResourceLabel", i); + this.tooltipResourceFrames[i].addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.tooltipText, + FramePoint.BOTTOMLEFT, GameUI.convertX(this.uiViewport, 0.004f + (0.032f * i)), + GameUI.convertY(this.uiViewport, -0.011f))); + // have we really no better API than the below??? + ((AbstractUIFrame) this.tooltipFrame).add(this.tooltipResourceFrames[i]); + this.rootFrame.remove(this.tooltipResourceFrames[i]); + } // this.tooltipFrame = this.rootFrame.createFrameByType("BACKDROP", "SmashToolTipBackdrop", this.rootFrame, "", 0); this.cursorFrame = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", "SmashCursorFrame", this.rootFrame, @@ -689,7 +726,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma abilityHandleId, orderId, isShiftDown()); } if (rightClick) { - this.war3MapViewer.getUiSounds().getSound("AutoCastButtonClick").play(this.uiScene.audioContext, 0, 0); + this.war3MapViewer.getUiSounds().getSound("AutoCastButtonClick").play(this.uiScene.audioContext, 0, 0, 0); } } @@ -709,7 +746,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma @Override public void showCommandError(final String message) { - this.errorMessageFrame.setText(message); + this.rootFrame.setText(this.errorMessageFrame, message); this.errorMessageFrame.setVisible(true); this.lastErrorMessageExpireTime = TimeUtils.millis() + WORLD_FRAME_MESSAGE_EXPIRE_MILLIS; this.lastErrorMessageFadeTime = TimeUtils.millis() + WORLD_FRAME_MESSAGE_FADEOUT_MILLIS; @@ -720,7 +757,14 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma public void showCantPlaceError() { showCommandError(this.rootFrame.getErrorString("Cantplace")); this.war3MapViewer.getUiSounds().getSound(this.rootFrame.getSkinField("CantPlaceSound")) - .play(this.uiScene.audioContext, 0, 0); + .play(this.uiScene.audioContext, 0, 0, 0); + } + + @Override + public void showNoFoodError() { + showCommandError(this.rootFrame.getErrorString("NoFood")); + this.war3MapViewer.getUiSounds().getSound(this.rootFrame.getSkinField("NoFoodSound")) + .play(this.uiScene.audioContext, 0, 0, 0); } public void update(final float deltaTime) { @@ -1034,29 +1078,53 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } clickLocationTemp.z = viewer.terrain.getGroundHeight(clickLocationTemp.x, clickLocationTemp.y); - final float halfRenderWidth = MeleeUI.this.cursorModelPathing.getWidth() * 16; - final float halfRenderHeight = MeleeUI.this.cursorModelPathing.getHeight() * 16; - for (int i = 0; i < MeleeUI.this.cursorModelUnderneathPathingRedGreenPixmap.getWidth(); i++) { - for (int j = 0; j < MeleeUI.this.cursorModelUnderneathPathingRedGreenPixmap.getHeight(); j++) { - boolean blocked = false; - final short pathing = viewer.simulation.getPathingGrid().getPathing( - (clickLocationTemp.x + (i * 32)) - halfRenderWidth, - (clickLocationTemp.y + (j * 32)) - halfRenderHeight); - for (final CBuildingPathingType preventedType : MeleeUI.this.cursorBuildingUnitType - .getPreventedPathingTypes()) { - if (PathingFlags.isPathingFlag(pathing, preventedType)) { - blocked = true; - } + final int cursorWidthCells = MeleeUI.this.cursorModelPathing.getWidth(); + final int halfCursorWidthCells = cursorWidthCells / 2; + final float halfRenderWidth = cursorWidthCells * 16; + final int cursorHeightCells = MeleeUI.this.cursorModelPathing.getHeight(); + final int halfCursorHeightCells = cursorHeightCells / 2; + final float halfRenderHeight = cursorHeightCells * 16; + final PathingGrid pathingGrid = viewer.simulation.getPathingGrid(); + boolean blockAll = false; + final int cellX = pathingGrid.getCellX(clickLocationTemp.x); + final int cellY = pathingGrid.getCellY(clickLocationTemp.y); + if ((cellX < halfCursorWidthCells) || (cellX > (pathingGrid.getWidth() - halfCursorWidthCells)) + || (cellY < halfCursorHeightCells) + || (cellY > (pathingGrid.getHeight() - halfCursorHeightCells))) { + blockAll = true; + } + if (blockAll) { + for (int i = 0; i < MeleeUI.this.cursorModelUnderneathPathingRedGreenPixmap.getWidth(); i++) { + for (int j = 0; j < MeleeUI.this.cursorModelUnderneathPathingRedGreenPixmap.getHeight(); j++) { + MeleeUI.this.cursorModelUnderneathPathingRedGreenPixmap.drawPixel(i, + MeleeUI.this.cursorModelUnderneathPathingRedGreenPixmap.getHeight() - 1 - j, + Color.rgba8888(1, 0, 0, 1.0f)); } - for (final CBuildingPathingType requiredType : MeleeUI.this.cursorBuildingUnitType - .getRequiredPathingTypes()) { - if (!PathingFlags.isPathingFlag(pathing, requiredType)) { - blocked = true; + } + } + else { + for (int i = 0; i < MeleeUI.this.cursorModelUnderneathPathingRedGreenPixmap.getWidth(); i++) { + for (int j = 0; j < MeleeUI.this.cursorModelUnderneathPathingRedGreenPixmap.getHeight(); j++) { + boolean blocked = false; + final short pathing = pathingGrid.getPathing( + (clickLocationTemp.x + (i * 32)) - halfRenderWidth, + (clickLocationTemp.y + (j * 32)) - halfRenderHeight); + for (final CBuildingPathingType preventedType : MeleeUI.this.cursorBuildingUnitType + .getPreventedPathingTypes()) { + if (PathingFlags.isPathingFlag(pathing, preventedType)) { + blocked = true; + } } + for (final CBuildingPathingType requiredType : MeleeUI.this.cursorBuildingUnitType + .getRequiredPathingTypes()) { + if (!PathingFlags.isPathingFlag(pathing, requiredType)) { + blocked = true; + } + } + final int color = blocked ? Color.rgba8888(1, 0, 0, 1.0f) : Color.rgba8888(0, 1, 0, 1.0f); + MeleeUI.this.cursorModelUnderneathPathingRedGreenPixmap.drawPixel(i, + MeleeUI.this.cursorModelUnderneathPathingRedGreenPixmap.getHeight() - 1 - j, color); } - final int color = blocked ? Color.rgba8888(1, 0, 0, 1.0f) : Color.rgba8888(0, 1, 0, 1.0f); - MeleeUI.this.cursorModelUnderneathPathingRedGreenPixmap.drawPixel(i, - MeleeUI.this.cursorModelUnderneathPathingRedGreenPixmap.getHeight() - 1 - j, color); } } MeleeUI.this.cursorModelUnderneathPathingRedGreenPixmapTexture @@ -1251,19 +1319,19 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.selectedUnit = unit; if (unit == null) { clearCommandCard(); - this.simpleNameValue.setText(""); - this.unitLifeText.setText(""); - this.unitManaText.setText(""); - this.simpleClassValue.setText(""); - this.simpleBuildingActionLabel.setText(""); + this.rootFrame.setText(this.simpleNameValue, ""); + this.rootFrame.setText(this.unitLifeText, ""); + this.rootFrame.setText(this.unitManaText, ""); + this.rootFrame.setText(this.simpleClassValue, ""); + this.rootFrame.setText(this.simpleBuildingActionLabel, ""); this.attack1Icon.setVisible(false); this.attack2Icon.setVisible(false); - this.attack1InfoPanelIconLevel.setText(""); - this.attack2InfoPanelIconLevel.setText(""); - this.simpleBuildingBuildingActionLabel.setText(""); - this.simpleBuildingNameValue.setText(""); + this.rootFrame.setText(this.attack1InfoPanelIconLevel, ""); + this.rootFrame.setText(this.attack2InfoPanelIconLevel, ""); + this.rootFrame.setText(this.simpleBuildingBuildingActionLabel, ""); + this.rootFrame.setText(this.simpleBuildingNameValue, ""); this.armorIcon.setVisible(false); - this.armorInfoPanelIconLevel.setText(""); + this.rootFrame.setText(this.armorInfoPanelIconLevel, ""); this.simpleBuildTimeIndicator.setVisible(false); this.simpleBuildingBuildTimeIndicator.setVisible(false); this.simpleInfoPanelBuildingDetail.setVisible(false); @@ -1381,15 +1449,15 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma private void reloadSelectedUnitUI(final RenderUnit unit) { final CUnit simulationUnit = unit.getSimulationUnit(); - this.unitLifeText.setText( + this.rootFrame.setText(this.unitLifeText, FastNumberFormat.formatWholeNumber(simulationUnit.getLife()) + " / " + simulationUnit.getMaximumLife()); final int maximumMana = simulationUnit.getMaximumMana(); if (maximumMana > 0) { - this.unitManaText - .setText(FastNumberFormat.formatWholeNumber(simulationUnit.getMana()) + " / " + maximumMana); + this.rootFrame.setText(this.unitManaText, + FastNumberFormat.formatWholeNumber(simulationUnit.getMana()) + " / " + maximumMana); } else { - this.unitManaText.setText(""); + this.rootFrame.setText(this.unitManaText, ""); } repositionRallyPoint(simulationUnit); repositionWaypointFlags(simulationUnit); @@ -1418,18 +1486,18 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } this.simpleInfoPanelBuildingDetail.setVisible(true); this.simpleInfoPanelUnitDetail.setVisible(false); - this.simpleBuildingNameValue.setText(simulationUnit.getUnitType().getName()); - this.simpleBuildingDescriptionValue.setText(""); + this.rootFrame.setText(this.simpleBuildingNameValue, simulationUnit.getUnitType().getName()); + this.rootFrame.setText(this.simpleBuildingDescriptionValue, ""); this.simpleBuildingBuildTimeIndicator.setVisible(true); this.simpleBuildTimeIndicator.setVisible(false); if (simulationUnit.getBuildQueueTypes()[0] == QueueItemType.UNIT) { - this.simpleBuildingBuildingActionLabel - .setText(this.rootFrame.getTemplates().getDecoratedString("TRAINING")); + this.rootFrame.setText(this.simpleBuildingBuildingActionLabel, + this.rootFrame.getTemplates().getDecoratedString("TRAINING")); } else { - this.simpleBuildingBuildingActionLabel - .setText(this.rootFrame.getTemplates().getDecoratedString("RESEARCHING")); + this.rootFrame.setText(this.simpleBuildingBuildingActionLabel, + this.rootFrame.getTemplates().getDecoratedString("RESEARCHING")); } this.attack1Icon.setVisible(false); this.attack2Icon.setVisible(false); @@ -1442,7 +1510,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } this.simpleInfoPanelBuildingDetail.setVisible(false); this.simpleInfoPanelUnitDetail.setVisible(true); - this.simpleNameValue.setText(simulationUnit.getUnitType().getName()); + this.rootFrame.setText(this.simpleNameValue, simulationUnit.getUnitType().getName()); String classText = null; for (final CUnitClassification classification : simulationUnit.getClassifications()) { if ((classification == CUnitClassification.MECHANICAL) && simulationUnit.getUnitType().isBuilding()) { @@ -1454,10 +1522,10 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } } if (classText != null) { - this.simpleClassValue.setText(classText); + this.rootFrame.setText(this.simpleClassValue, classText); } else { - this.simpleClassValue.setText(""); + this.rootFrame.setText(this.simpleClassValue, ""); } final boolean anyAttacks = simulationUnit.getUnitType().getAttacks().size() > 0; @@ -1469,12 +1537,14 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma final CUnitAttack attackOne = simulationUnit.getUnitType().getAttacks().get(0); this.attack1Icon.setVisible(attackOne.isShowUI()); this.attack1IconBackdrop.setTexture(this.damageBackdrops.getTexture(attackOne.getAttackType())); - this.attack1InfoPanelIconValue.setText(attackOne.getMinDamage() + " - " + attackOne.getMaxDamage()); + this.rootFrame.setText(this.attack1InfoPanelIconValue, + attackOne.getMinDamage() + " - " + attackOne.getMaxDamage()); if (simulationUnit.getUnitType().getAttacks().size() > 1) { final CUnitAttack attackTwo = simulationUnit.getUnitType().getAttacks().get(1); this.attack2Icon.setVisible(attackTwo.isShowUI()); this.attack2IconBackdrop.setTexture(this.damageBackdrops.getTexture(attackTwo.getAttackType())); - this.attack2InfoPanelIconValue.setText(attackTwo.getMinDamage() + " - " + attackTwo.getMaxDamage()); + this.rootFrame.setText(this.attack2InfoPanelIconValue, + attackTwo.getMinDamage() + " - " + attackTwo.getMaxDamage()); } else { this.attack2Icon.setVisible(false); @@ -1501,8 +1571,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.simpleBuildTimeIndicator.setVisible(constructing); this.simpleBuildingBuildTimeIndicator.setVisible(false); if (constructing) { - this.simpleBuildingActionLabel - .setText(this.rootFrame.getTemplates().getDecoratedString("CONSTRUCTING")); + this.rootFrame.setText(this.simpleBuildingActionLabel, + this.rootFrame.getTemplates().getDecoratedString("CONSTRUCTING")); this.queueIconFrames[0].setVisible(true); this.queueIconFrames[0].setTexture(this.war3MapViewer.getAbilityDataUI() .getUnitUI(this.selectedUnit.getSimulationUnit().getTypeId()).getIcon()); @@ -1517,7 +1587,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } } else { - this.simpleBuildingActionLabel.setText(""); + this.rootFrame.setText(this.simpleBuildingActionLabel, ""); this.selectWorkerInsideFrame.setVisible(false); } final Texture defenseTexture = this.defenseBackdrops @@ -1526,7 +1596,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma throw new RuntimeException(simulationUnit.getUnitType().getDefenseType() + " can't find texture!"); } localArmorIconBackdrop.setTexture(defenseTexture); - localArmorInfoPanelIconValue.setText(Integer.toString(simulationUnit.getDefense())); + this.rootFrame.setText(localArmorInfoPanelIconValue, Integer.toString(simulationUnit.getDefense())); } clearAndRepopulateCommandCard(); } @@ -1542,7 +1612,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma @Override public void commandButton(final int buttonPositionX, final int buttonPositionY, final Texture icon, final int abilityHandleId, final int orderId, final int autoCastId, final boolean active, - final boolean autoCastActive, final boolean menuButton) { + final boolean autoCastActive, final boolean menuButton, final String tip, final String uberTip, + final int goldCost, final int lumberCost, final int foodCost) { int x = Math.max(0, Math.min(COMMAND_CARD_WIDTH - 1, buttonPositionX)); int y = Math.max(0, Math.min(COMMAND_CARD_HEIGHT - 1, buttonPositionY)); while ((x < COMMAND_CARD_WIDTH) && (y < COMMAND_CARD_HEIGHT) && this.commandCard[y][x].isVisible()) { @@ -1554,7 +1625,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } if ((x < COMMAND_CARD_WIDTH) && (y < COMMAND_CARD_HEIGHT)) { this.commandCard[y][x].setCommandButtonData(icon, abilityHandleId, orderId, autoCastId, active, - autoCastActive, menuButton); + autoCastActive, menuButton, tip, uberTip, goldCost, lumberCost, foodCost); } } @@ -1639,38 +1710,38 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma selectUnit(null); } else { - this.unitLifeText - .setText(FastNumberFormat.formatWholeNumber(this.selectedUnit.getSimulationUnit().getLife()) + " / " + this.rootFrame.setText(this.unitLifeText, + FastNumberFormat.formatWholeNumber(this.selectedUnit.getSimulationUnit().getLife()) + " / " + this.selectedUnit.getSimulationUnit().getMaximumLife()); } } @Override public void goldChanged() { - this.resourceBarGoldText.setText(Integer.toString(this.localPlayer.getGold())); + this.rootFrame.setText(this.resourceBarGoldText, Integer.toString(this.localPlayer.getGold())); } @Override public void lumberChanged() { - this.resourceBarLumberText.setText(Integer.toString(this.localPlayer.getLumber())); + this.rootFrame.setText(this.resourceBarLumberText, Integer.toString(this.localPlayer.getLumber())); } @Override public void foodChanged() { final int foodCap = this.localPlayer.getFoodCap(); if (foodCap == 0) { - this.resourceBarSupplyText.setText(Integer.toString(this.localPlayer.getFoodUsed())); + this.rootFrame.setText(this.resourceBarSupplyText, Integer.toString(this.localPlayer.getFoodUsed())); this.resourceBarSupplyText.setColor(Color.WHITE); } else { - this.resourceBarSupplyText.setText(this.localPlayer.getFoodUsed() + "/" + foodCap); + this.rootFrame.setText(this.resourceBarSupplyText, this.localPlayer.getFoodUsed() + "/" + foodCap); this.resourceBarSupplyText.setColor(this.localPlayer.getFoodUsed() > foodCap ? Color.RED : Color.WHITE); } } @Override public void upkeepChanged() { - this.resourceBarUpkeepText.setText("Upkeep NYI"); + this.rootFrame.setText(this.resourceBarUpkeepText, "Upkeep NYI"); this.resourceBarUpkeepText.setColor(Color.CYAN); } @@ -1691,7 +1762,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma if (this.activeCommand != null) { final IconUI cancelUI = abilityDataUI.getCancelUI(); this.commandButton(cancelUI.getButtonPositionX(), cancelUI.getButtonPositionY(), cancelUI.getIcon(), 0, - menuOrderId, 0, false, false, true); + menuOrderId, 0, false, false, true, cancelUI.getToolTip(), cancelUI.getUberTip(), 0, 0, 0); } else { if (menuOrderId != 0) { @@ -1700,7 +1771,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma : 0; final IconUI cancelUI = abilityDataUI.getCancelUI(); this.commandButton(cancelUI.getButtonPositionX(), cancelUI.getButtonPositionY(), cancelUI.getIcon(), 0, - exitOrderId, 0, false, false, true); + exitOrderId, 0, false, false, true, cancelUI.getToolTip(), cancelUI.getUberTip(), 0, 0, 0); } this.selectedUnit.populateCommandCard(this.war3MapViewer.simulation, this, abilityDataUI, menuOrderId); } @@ -1765,7 +1836,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.selectedSoundCount = 0; if (this.activeCommand instanceof CAbilityRally) { this.war3MapViewer.getUiSounds().getSound("RallyPointPlace").play(this.uiScene.audioContext, - 0, 0); + 0, 0, 0); } if (!shiftDown) { this.subMenuOrderIdStack.clear(); @@ -1802,11 +1873,11 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.selectedSoundCount = 0; if (this.activeCommand instanceof AbstractCAbilityBuild) { this.war3MapViewer.getUiSounds().getSound("PlaceBuildingDefault") - .play(this.uiScene.audioContext, 0, 0); + .play(this.uiScene.audioContext, 0, 0, 0); } else if (this.activeCommand instanceof CAbilityRally) { this.war3MapViewer.getUiSounds().getSound("RallyPointPlace") - .play(this.uiScene.audioContext, 0, 0); + .play(this.uiScene.audioContext, 0, 0, 0); } if (!shiftDown) { this.subMenuOrderIdStack.clear(); @@ -1854,7 +1925,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } if (rallied) { this.war3MapViewer.getUiSounds().getSound("RallyPointPlace") - .play(this.uiScene.audioContext, 0, 0); + .play(this.uiScene.audioContext, 0, 0, 0); } this.selectedSoundCount = 0; } @@ -1916,7 +1987,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma portraitTalk(); } if (rallied) { - this.war3MapViewer.getUiSounds().getSound("RallyPointPlace").play(this.uiScene.audioContext, 0, 0); + this.war3MapViewer.getUiSounds().getSound("RallyPointPlace").play(this.uiScene.audioContext, 0, 0, 0); } this.selectedSoundCount = 0; } @@ -1987,7 +2058,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma if (this.mouseDownUIFrame != null) { if (clickedUIFrame == this.mouseDownUIFrame) { this.mouseDownUIFrame.onClick(button); - this.war3MapViewer.getUiSounds().getSound("InterfaceClick").play(this.uiScene.audioContext, 0, 0); + this.war3MapViewer.getUiSounds().getSound("InterfaceClick").play(this.uiScene.audioContext, 0, 0, 0); } this.mouseDownUIFrame.mouseUp(this.rootFrame, this.uiViewport); } @@ -2020,10 +2091,48 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma if (mousedUIFrame != this.mouseOverUIFrame) { if (mousedUIFrame instanceof ClickableActionFrame) { this.mouseOverUIFrame = (ClickableActionFrame) mousedUIFrame; + final int goldCost = this.mouseOverUIFrame.getToolTipGoldCost(); + final int lumberCost = this.mouseOverUIFrame.getToolTipLumberCost(); + final int foodCost = this.mouseOverUIFrame.getToolTipFoodCost(); final String toolTip = this.mouseOverUIFrame.getToolTip(); - this.tooltipText.setText(toolTip); - System.out.println("tooltip text assign to " + toolTip); - this.tooltipFrame.setHeight(GameUI.convertY(this.uiViewport, 0.020f)); + this.rootFrame.setText(this.tooltipUberTipText, this.mouseOverUIFrame.getUberTip()); + int resourceIndex = 0; + if (goldCost != 0) { + this.tooltipResourceFrames[resourceIndex].setVisible(true); + this.tooltipResourceIconFrames[resourceIndex].setTexture("ToolTipGoldIcon", this.rootFrame); + this.rootFrame.setText(this.tooltipResourceTextFrames[resourceIndex], Integer.toString(goldCost)); + resourceIndex++; + } + if (lumberCost != 0) { + this.tooltipResourceFrames[resourceIndex].setVisible(true); + this.tooltipResourceIconFrames[resourceIndex].setTexture("ToolTipLumberIcon", this.rootFrame); + this.rootFrame.setText(this.tooltipResourceTextFrames[resourceIndex], Integer.toString(lumberCost)); + resourceIndex++; + } + if (foodCost != 0) { + this.tooltipResourceFrames[resourceIndex].setVisible(true); + this.tooltipResourceIconFrames[resourceIndex].setTexture("ToolTipSupplyIcon", this.rootFrame); + this.rootFrame.setText(this.tooltipResourceTextFrames[resourceIndex], Integer.toString(foodCost)); + resourceIndex++; + } + for (int i = resourceIndex; i < this.tooltipResourceFrames.length; i++) { + this.tooltipResourceFrames[i].setVisible(false); + } + float resourcesHeight; + if (resourceIndex != 0) { + this.tooltipUberTipText.addSetPoint(this.uberTipWithResourcesSetPoint); + resourcesHeight = 0.014f; + } + else { + this.tooltipUberTipText.addSetPoint(this.uberTipNoResourcesSetPoint); + resourcesHeight = 0.004f; + } + this.rootFrame.setText(this.tooltipText, toolTip); + final float predictedViewportHeight = this.tooltipText.getPredictedViewportHeight() + + GameUI.convertY(this.uiViewport, resourcesHeight) + + this.tooltipUberTipText.getPredictedViewportHeight() + + GameUI.convertY(this.uiViewport, 0.003f); + this.tooltipFrame.setHeight(predictedViewportHeight); this.tooltipFrame.positionBounds(this.rootFrame, this.uiViewport); this.tooltipFrame.setVisible(true); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MenuUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MenuUI.java index 270693a..c3d5113 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MenuUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MenuUI.java @@ -15,6 +15,7 @@ import com.etheller.warsmash.parsers.fdf.datamodel.FramePoint; import com.etheller.warsmash.parsers.fdf.frames.SpriteFrame; import com.etheller.warsmash.parsers.fdf.frames.UIFrame; import com.etheller.warsmash.parsers.jass.Jass2.RootFrameListener; +import com.etheller.warsmash.units.custom.WTS; import com.etheller.warsmash.viewer5.Scene; import com.etheller.warsmash.viewer5.handlers.mdx.MdxViewer; @@ -65,7 +66,7 @@ public class MenuUI { // Load skins and templates // ================================= this.rootFrame = new GameUI(this.dataSource, GameUI.loadSkin(this.dataSource, 1), this.uiViewport, - this.fontGenerator, this.uiScene, this.viewer, 0); + this.fontGenerator, this.uiScene, this.viewer, 0, WTS.DO_NOTHING); this.rootFrameListener.onCreate(this.rootFrame); try { this.rootFrame.loadTOCFile("UI\\FrameDef\\FrameDef.toc"); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/QueueIcon.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/QueueIcon.java index 9fc52a6..43a8f39 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/QueueIcon.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/QueueIcon.java @@ -109,4 +109,24 @@ public class QueueIcon extends AbstractRenderableFrame implements ClickableActio public String getToolTip() { return "QueueIcon " + this.queueIconIndexId; } + + @Override + public String getUberTip() { + return "The |cffffcc00QueueIcon|r is a hardcoded Warsmash engine component that is not yet loading its |cffffaa88description|r."; + } + + @Override + public int getToolTipFoodCost() { + return 0; + } + + @Override + public int getToolTipGoldCost() { + return 0; + } + + @Override + public int getToolTipLumberCost() { + return 0; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/ClickableActionFrame.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/ClickableActionFrame.java index e1ccfb9..46721d6 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/ClickableActionFrame.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/ClickableActionFrame.java @@ -12,4 +12,12 @@ public interface ClickableActionFrame extends UIFrame { void onClick(int button); String getToolTip(); + + String getUberTip(); + + int getToolTipGoldCost(); + + int getToolTipLumberCost(); + + int getToolTipFoodCost(); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/CommandErrorListener.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/CommandErrorListener.java index 3a8f77c..ed33944 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/CommandErrorListener.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/CommandErrorListener.java @@ -4,4 +4,6 @@ public interface CommandErrorListener { void showCommandError(String message); void showCantPlaceError(); + + void showNoFoodError(); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/SettableCommandErrorListener.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/SettableCommandErrorListener.java index 5b43c62..2cd025c 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/SettableCommandErrorListener.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/SettableCommandErrorListener.java @@ -13,6 +13,11 @@ public class SettableCommandErrorListener implements CommandErrorListener { this.delegate.showCantPlaceError(); } + @Override + public void showNoFoodError() { + this.delegate.showNoFoodError(); + } + public void setDelegate(final CommandErrorListener delegate) { this.delegate = delegate; } diff --git a/resources/UI/FrameDef/SmashUI/ToolTip.fdf b/resources/UI/FrameDef/SmashUI/ToolTip.fdf index 958c852..bb30920 100644 --- a/resources/UI/FrameDef/SmashUI/ToolTip.fdf +++ b/resources/UI/FrameDef/SmashUI/ToolTip.fdf @@ -22,22 +22,46 @@ Frame "FRAME" "SmashToolTip" { BackdropCornerFlags "UL|UR|BL|BR|T|L|B|R", BackdropCornerSize 0.008, BackdropBackgroundSize 0.036, - BackdropBackgroundInsets 0.0035 0.0035 0.0035 0.0035, + BackdropBackgroundInsets 0.0025 0.0025 0.0025 0.0025, BackdropEdgeFile "ToolTipBorder", BackdropBlendAll, } Frame "TEXT" "SmashToolTipText" { - SetAllPoints, DecorateFileNames, - FrameFont "MasterFont", 0.008, "", + FrameFont "MasterFont", 0.010, "", FontJustificationH JUSTIFYLEFT, FontJustificationV JUSTIFYTOP, FontFlags "FIXEDSIZE", - //FontColor 0.99 0.827 0.0705 1.0, FontColor 1.0 1.0 1.0 1.0, - FontHighlightColor 1.0 1.0 1.0 1.0, - FontDisabledColor 0.2 0.2 0.2 1.0, - FontShadowColor 0.0 0.0 0.0 0.9, - FontShadowOffset 0.001 -0.001, } + Frame "TEXT" "SmashUberTipText" { + DecorateFileNames, + FrameFont "MasterFont", 0.010, "", + FontJustificationH JUSTIFYLEFT, + FontJustificationV JUSTIFYTOP, + FontFlags "FIXEDSIZE", + FontColor 1.0 1.0 1.0 1.0, + } +} +Frame "SIMPLEFRAME" "SmashToolTipIconResource" { + DecorateFileNames, + Height 0.010, + + // --- icon ------------------------------------------------------------- + Texture "SmashToolTipIconResourceBackdrop" { + Anchor LEFT, 0.0, 0.0 + Width 0.008, + Height 0.008, + File "ToolTipStonesIcon", + } + + // --- label ------------------------------------------------------------ + String "SmashToolTipIconResourceLabel" { + SetPoint LEFT, "SmashToolTipIconResourceBackdrop", RIGHT, 0.001, 0.000, + FontJustificationH JUSTIFYLEFT, + FontJustificationV JUSTIFYMIDDLE, + FontColor 0.99 0.827 0.0705 1.0, + Font "InfoPanelTextFont",0.0085, + Text "275", + } } \ No newline at end of file From 2c5c00d4eafafd26b237a503e2345df7801f47fc Mon Sep 17 00:00:00 2001 From: Retera Date: Mon, 15 Feb 2021 01:40:57 -0500 Subject: [PATCH 102/116] Update build and train to show requirements, also sRGB filter change for fun --- core/assets/warsmash131.ini | 3 +- .../etheller/warsmash/WarsmashGdxMapGame.java | 2 +- .../datasources/FolderDataSource.java | 2 +- .../parsers/fdf/frames/StringFrame.java | 8 +- .../etheller/warsmash/util/ImageUtils.java | 5 +- .../viewer5/RawOpenGLTextureResource.java | 3 +- .../etheller/warsmash/viewer5/WorldScene.java | 2 +- .../handlers/w3x/rendersim/RenderUnit.java | 8 +- .../handlers/w3x/rendersim/RenderWidget.java | 2 +- .../CommandCardPopulatingAbilityVisitor.java | 94 +++++++++++++++++-- .../handlers/w3x/simulation/CSimulation.java | 3 + .../handlers/w3x/simulation/CUnit.java | 8 +- .../handlers/w3x/simulation/CUnitType.java | 9 +- .../w3x/simulation/CUnitTypeRequirement.java | 21 +++++ .../build/AbstractCAbilityBuild.java | 32 +++++-- .../abilities/queue/CAbilityQueue.java | 31 ++++-- .../abilities/queue/CAbilityRally.java | 2 +- .../behaviors/harvest/CBehaviorHarvest.java | 7 +- .../w3x/simulation/data/CUnitData.java | 40 +++++++- .../w3x/simulation/players/CPlayer.java | 20 ++++ .../util/AbilityActivationReceiver.java | 4 +- .../BooleanAbilityActivationReceiver.java | 4 +- .../MeleeUIAbilityActivationReceiver.java | 3 +- .../StringMsgAbilityActivationReceiver.java | 6 +- .../viewer5/handlers/w3x/ui/MeleeUI.java | 11 ++- .../UI/FrameDef/SmashUI/UnitPortrait.fdf | 4 +- 26 files changed, 284 insertions(+), 50 deletions(-) create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitTypeRequirement.java diff --git a/core/assets/warsmash131.ini b/core/assets/warsmash131.ini index 8b7bf66..2d0db59 100644 --- a/core/assets/warsmash131.ini +++ b/core/assets/warsmash131.ini @@ -18,7 +18,8 @@ Path04="." //FilePath="PrivateDontShare/Cult 8.w3x" //FilePath="TorchLight2.w3x" //FilePath="OrcAssault.w3x" -FilePath="PeonStartingBase.w3x" +//FilePath="PeonStartingBase.w3x" //FilePath="PhoenixAttack.w3x" //FilePath="OperationReforged.w3x" //FilePath="AzerothRoleplay1.909t03DecoratedV2.w3x" +FilePath="American Colo EX 1.0 unpro.w3x" diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java index 17bfcbf..1d57f1c 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java @@ -218,7 +218,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv final String[] musics = musicField.split(";"); String musicPath = musics[(int) (Math.random() * musics.length)]; if (true) { - musicPath = "Sound\\Music\\mp3Music\\DarkAgents.mp3"; + musicPath = "Sound\\Music\\mp3Music\\PH1.mp3"; } final Music music = Gdx.audio.newMusic( new DataSourceFileHandle(WarsmashGdxMapGame.this.viewer.dataSource, musicPath)); diff --git a/core/src/com/etheller/warsmash/datasources/FolderDataSource.java b/core/src/com/etheller/warsmash/datasources/FolderDataSource.java index d429cdd..356eb7e 100644 --- a/core/src/com/etheller/warsmash/datasources/FolderDataSource.java +++ b/core/src/com/etheller/warsmash/datasources/FolderDataSource.java @@ -58,7 +58,7 @@ public class FolderDataSource implements DataSource { if (!has(path)) { return null; } - return ByteBuffer.wrap(Files.readAllBytes(Paths.get(path))); + return ByteBuffer.wrap(Files.readAllBytes(Paths.get(this.folderPath.toString(), path))); } @Override diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/StringFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/StringFrame.java index 5375fbc..eef0852 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/StringFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/StringFrame.java @@ -97,6 +97,7 @@ public class StringFrame extends AbstractRenderableFrame { final char escapedCharacter = this.text.charAt(i + 1); switch (escapedCharacter) { case 'c': + case 'C': if ((i + 9) < this.text.length()) { int colorInt; try { @@ -161,6 +162,7 @@ public class StringFrame extends AbstractRenderableFrame { } break; case 'r': + case 'R': i++; { final String wordString = currentWord.toString(); currentWord.setLength(0); @@ -210,7 +212,8 @@ public class StringFrame extends AbstractRenderableFrame { } currentColor = this.color; break; - case 'n': { + case 'n': + case 'N': { final String wordString = currentWord.toString(); currentWord.setLength(0); @@ -344,6 +347,9 @@ public class StringFrame extends AbstractRenderableFrame { singleStringFrame.setHeight(this.frameFont.getLineHeight()); singleStringFrame.setWidth(glyphLayout.width); singleStringFrame.setAlpha(this.alpha); + singleStringFrame.setFontShadowColor(this.fontShadowColor); + singleStringFrame.setFontShadowOffsetX(this.fontShadowOffsetX); + singleStringFrame.setFontShadowOffsetY(this.fontShadowOffsetY); singleStringFrame.addAnchor(new AnchorDefinition(FramePoint.TOPLEFT, currentXCoordForFrames, -usedHeight)); this.internalFrames.add(singleStringFrame); currentXCoordForFrames = currentXCoordForWord; diff --git a/core/src/com/etheller/warsmash/util/ImageUtils.java b/core/src/com/etheller/warsmash/util/ImageUtils.java index c9c6844..6991a8a 100644 --- a/core/src/com/etheller/warsmash/util/ImageUtils.java +++ b/core/src/com/etheller/warsmash/util/ImageUtils.java @@ -98,7 +98,7 @@ public final class ImageUtils { } public boolean isNeedsSRGBFix() { - return this.needsSRGBFix; + return false; } } @@ -217,6 +217,9 @@ public final class ImageUtils { * @return Resulting sRGB image. */ public static BufferedImage forceBufferedImagesRGB(final BufferedImage in) { + if (true) { + return in; + } // Resolve input ColorSpace. final ColorSpace inCS = in.getColorModel().getColorSpace(); final ColorSpace sRGBCS = ColorSpace.getInstance(ColorSpace.CS_sRGB); diff --git a/core/src/com/etheller/warsmash/viewer5/RawOpenGLTextureResource.java b/core/src/com/etheller/warsmash/viewer5/RawOpenGLTextureResource.java index 0d09915..7c0fb8c 100644 --- a/core/src/com/etheller/warsmash/viewer5/RawOpenGLTextureResource.java +++ b/core/src/com/etheller/warsmash/viewer5/RawOpenGLTextureResource.java @@ -91,7 +91,8 @@ public abstract class RawOpenGLTextureResource extends Texture { final GL20 gl = this.viewer.gl; } - public void update(final BufferedImage image, final boolean sRGBFix) { + public void update(final BufferedImage image, boolean sRGBFix) { + sRGBFix = false; final GL20 gl = this.viewer.gl; final int imageWidth = image.getWidth(); diff --git a/core/src/com/etheller/warsmash/viewer5/WorldScene.java b/core/src/com/etheller/warsmash/viewer5/WorldScene.java index e00d9b0..f05a550 100644 --- a/core/src/com/etheller/warsmash/viewer5/WorldScene.java +++ b/core/src/com/etheller/warsmash/viewer5/WorldScene.java @@ -57,7 +57,7 @@ public class WorldScene extends Scene { // Update and collect all of the visible instances. for (final GridCell cell : this.grid.cells) { - if (cell.isVisible(this.camera)) { + if (cell.isVisible(this.camera) || true) { this.visibleCells += 1; for (final ModelInstance instance : new ArrayList<>(cell.instances)) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java index 08bec1c..346ee7a 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java @@ -4,6 +4,7 @@ import java.util.EnumSet; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.math.Quaternion; +import com.etheller.warsmash.parsers.fdf.GameUI; import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; import com.etheller.warsmash.util.RenderMathUtils; import com.etheller.warsmash.util.War3ID; @@ -142,10 +143,11 @@ public class RenderUnit implements RenderWidget { } - public void populateCommandCard(final CSimulation game, final CommandButtonListener commandButtonListener, - final AbilityDataUI abilityDataUI, final int subMenuOrderId) { + public void populateCommandCard(final CSimulation game, final GameUI gameUI, + final CommandButtonListener commandButtonListener, final AbilityDataUI abilityDataUI, + final int subMenuOrderId) { final CommandCardPopulatingAbilityVisitor commandCardPopulatingVisitor = CommandCardPopulatingAbilityVisitor.INSTANCE - .reset(game, this.simulationUnit, commandButtonListener, abilityDataUI, subMenuOrderId); + .reset(game, gameUI, this.simulationUnit, commandButtonListener, abilityDataUI, subMenuOrderId); for (final CAbility ability : this.simulationUnit.getAbilities()) { ability.visit(commandCardPopulatingVisitor); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderWidget.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderWidget.java index 4aa2e50..3eeabd0 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderWidget.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderWidget.java @@ -44,7 +44,7 @@ public interface RenderWidget { private final EnumSet recycleSet = EnumSet .noneOf(AnimationTokens.SecondaryTag.class); private PrimaryTag currentAnimation; - private EnumSet currentAnimationSecondaryTags; + private EnumSet currentAnimationSecondaryTags = SequenceUtils.EMPTY; private float currentSpeedRatio; private boolean currentlyAllowingRarityVariations; private final Queue animationQueue = new LinkedList<>(); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java index 14f14ea..0293fc3 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java @@ -1,5 +1,6 @@ package com.etheller.warsmash.viewer5.handlers.w3x.rendersim.commandbuttons; +import com.etheller.warsmash.parsers.fdf.GameUI; import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.AbilityDataUI; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.AbilityIconUI; @@ -28,6 +29,8 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAb import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayer; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.ResourceType; public class CommandCardPopulatingAbilityVisitor implements CAbilityVisitor { public static final CommandCardPopulatingAbilityVisitor INSTANCE = new CommandCardPopulatingAbilityVisitor(); @@ -38,11 +41,14 @@ public class CommandCardPopulatingAbilityVisitor implements CAbilityVisitor requiredPathingTypes; private final float propWindow; private final float turnRate; + private final List requirements; public CUnitType(final String name, final int life, final int manaInitial, final int manaMaximum, final int speed, final int defense, final String abilityList, final boolean isBldg, final MovementType movementType, @@ -68,7 +69,8 @@ public class CUnitType { final List unitsTrained, final List researchesAvailable, final CUnitRace unitRace, final int goldCost, final int lumberCost, final int foodUsed, final int foodMade, final int buildTime, final EnumSet preventedPathingTypes, - final EnumSet requiredPathingTypes, final float propWindow, final float turnRate) { + final EnumSet requiredPathingTypes, final float propWindow, final float turnRate, + final List requirements) { this.name = name; this.life = life; this.manaInitial = manaInitial; @@ -105,6 +107,7 @@ public class CUnitType { this.requiredPathingTypes = requiredPathingTypes; this.propWindow = propWindow; this.turnRate = turnRate; + this.requirements = requirements; } public String getName() { @@ -250,4 +253,8 @@ public class CUnitType { public float getTurnRate() { return this.turnRate; } + + public List getRequirements() { + return this.requirements; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitTypeRequirement.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitTypeRequirement.java new file mode 100644 index 0000000..63b810d --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitTypeRequirement.java @@ -0,0 +1,21 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation; + +import com.etheller.warsmash.util.War3ID; + +public class CUnitTypeRequirement { + private final War3ID requirement; + private final int requiredLevel; + + public CUnitTypeRequirement(final War3ID requirement, final int requiredLevel) { + this.requirement = requirement; + this.requiredLevel = requiredLevel; + } + + public War3ID getRequirement() { + return this.requirement; + } + + public int getRequiredLevel() { + return this.requiredLevel; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java index 21a284b..df29658 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java @@ -9,6 +9,7 @@ import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitTypeRequirement; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.AbstractCAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.menu.CAbilityMenu; @@ -39,22 +40,37 @@ public abstract class AbstractCAbilityBuild extends AbstractCAbility implements final CUnitType unitType = game.getUnitData().getUnitType(orderIdAsRawtype); if (unitType != null) { final CPlayer player = game.getPlayer(unit.getPlayerIndex()); - if (player.getGold() >= unitType.getGoldCost()) { - if (player.getLumber() >= unitType.getLumberCost()) { - if ((unitType.getFoodUsed() == 0) - || ((player.getFoodUsed() + unitType.getFoodUsed()) <= player.getFoodCap())) { - receiver.useOk(); + final List requirements = unitType.getRequirements(); + boolean requirementsMet = true; + for (final CUnitTypeRequirement requirement : requirements) { + if (player.getTechtreeUnlocked(requirement.getRequirement()) < requirement.getRequiredLevel()) { + requirementsMet = false; + } + } + if (requirementsMet) { + if (player.getGold() >= unitType.getGoldCost()) { + if (player.getLumber() >= unitType.getLumberCost()) { + if ((unitType.getFoodUsed() == 0) + || ((player.getFoodUsed() + unitType.getFoodUsed()) <= player.getFoodCap())) { + + receiver.useOk(); + } + else { + receiver.notEnoughResources(ResourceType.FOOD); + } } else { - receiver.notEnoughResources(ResourceType.FOOD); + receiver.notEnoughResources(ResourceType.LUMBER); } } else { - receiver.notEnoughResources(ResourceType.LUMBER); + receiver.notEnoughResources(ResourceType.GOLD); } } else { - receiver.notEnoughResources(ResourceType.GOLD); + for (final CUnitTypeRequirement requirement : requirements) { + receiver.missingRequirement(requirement.getRequirement(), requirement.getRequiredLevel()); + } } } else { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityQueue.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityQueue.java index daae13f..fbd44d8 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityQueue.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityQueue.java @@ -8,6 +8,7 @@ import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitTypeRequirement; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.AbstractCAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; @@ -45,22 +46,36 @@ public final class CAbilityQueue extends AbstractCAbility { final CUnitType unitType = game.getUnitData().getUnitType(orderIdAsRawtype); if (unitType != null) { final CPlayer player = game.getPlayer(unit.getPlayerIndex()); - if (player.getGold() >= unitType.getGoldCost()) { - if (player.getLumber() >= unitType.getLumberCost()) { - if ((unitType.getFoodUsed() == 0) - || ((player.getFoodUsed() + unitType.getFoodUsed()) <= player.getFoodCap())) { - receiver.useOk(); + final List requirements = unitType.getRequirements(); + boolean requirementsMet = true; + for (final CUnitTypeRequirement requirement : requirements) { + if (player.getTechtreeUnlocked(requirement.getRequirement()) < requirement.getRequiredLevel()) { + requirementsMet = false; + } + } + if (requirementsMet) { + if (player.getGold() >= unitType.getGoldCost()) { + if (player.getLumber() >= unitType.getLumberCost()) { + if ((unitType.getFoodUsed() == 0) + || ((player.getFoodUsed() + unitType.getFoodUsed()) <= player.getFoodCap())) { + receiver.useOk(); + } + else { + receiver.notEnoughResources(ResourceType.FOOD); + } } else { - receiver.notEnoughResources(ResourceType.FOOD); + receiver.notEnoughResources(ResourceType.LUMBER); } } else { - receiver.notEnoughResources(ResourceType.LUMBER); + receiver.notEnoughResources(ResourceType.GOLD); } } else { - receiver.notEnoughResources(ResourceType.GOLD); + for (final CUnitTypeRequirement requirement : requirements) { + receiver.missingRequirement(requirement.getRequirement(), requirement.getRequiredLevel()); + } } } else { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityRally.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityRally.java index 2ea18ab..f1b7283 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityRally.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityRally.java @@ -19,7 +19,7 @@ public class CAbilityRally extends AbstractCAbility { @Override public void onAdd(final CSimulation game, final CUnit unit) { - + unit.setRallyPoint(unit); } @Override diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/harvest/CBehaviorHarvest.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/harvest/CBehaviorHarvest.java index ab50413..3833f81 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/harvest/CBehaviorHarvest.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/harvest/CBehaviorHarvest.java @@ -92,7 +92,12 @@ public class CBehaviorHarvest extends CAbstractRangedBehavior } } // weird invalid target and we have no resources, consider harvesting done - return this.unit.pollNextOrderBehavior(this.simulation); + if (this.abilityHarvest.getCarriedResourceAmount() == 0) { + return this.unit.pollNextOrderBehavior(this.simulation); + } + else { + return this.abilityHarvest.getBehaviorReturnResources().reset(this.simulation); + } } else { // we have some GOLD and we're not in a mine (?) lets do a return resources diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java index 13e5bc7..f76ee68 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java @@ -16,6 +16,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitClassification; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitTypeRequirement; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.HandleIdAllocator; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityAttack; @@ -137,6 +138,9 @@ public class CUnitData { private static final War3ID RESEARCHES_AVAILABLE = War3ID.fromString("ures"); private static final War3ID UNIT_RACE = War3ID.fromString("urac"); + private static final War3ID REQUIRES = War3ID.fromString("ureq"); + private static final War3ID REQUIRES_AMOUNT = War3ID.fromString("urqc"); + private static final War3ID GOLD_COST = War3ID.fromString("ugol"); private static final War3ID LUMBER_COST = War3ID.fromString("ulum"); private static final War3ID BUILD_TIME = War3ID.fromString("ubld"); @@ -416,6 +420,40 @@ public class CUnitData { } } + final String requirementsString = unitType.getFieldAsString(REQUIRES, 0); + final String requirementsLevelsString = unitType.getFieldAsString(REQUIRES_AMOUNT, 0); + final String[] requirementsStringItems = requirementsString.split(","); + final String[] requirementsLevelsStringItems = requirementsLevelsString.split(","); + final List requirements = new ArrayList<>(); + for (int i = 0; i < requirementsStringItems.length; i++) { + final String item = requirementsStringItems[i]; + if (!item.isEmpty()) { + int level; + if (i < requirementsLevelsStringItems.length) { + if (requirementsLevelsStringItems[i].isEmpty()) { + level = 1; + } + else { + level = Integer.parseInt(requirementsLevelsStringItems[i]); + } + } + else if (requirementsLevelsStringItems.length > 0) { + final String requirementLevel = requirementsLevelsStringItems[requirementsLevelsStringItems.length + - 1]; + if (requirementLevel.isEmpty()) { + level = 1; + } + else { + level = Integer.parseInt(requirementLevel); + } + } + else { + level = 1; + } + requirements.add(new CUnitTypeRequirement(War3ID.fromString(item), level)); + } + } + final EnumSet preventedPathingTypes = CBuildingPathingType .parsePathingTypeListSet(unitType.getFieldAsString(PREVENT_PLACE, 0)); final EnumSet requiredPathingTypes = CBuildingPathingType @@ -429,7 +467,7 @@ public class CUnitData { defenseType, impactZ, buildingPathingPixelMap, deathTime, targetedAs, acquisitionRange, minimumAttackRange, structuresBuilt, unitsTrained, researchesAvailable, unitRace, goldCost, lumberCost, foodUsed, foodMade, buildTime, preventedPathingTypes, requiredPathingTypes, propWindow, - turnRate); + turnRate, requirements); this.unitIdToUnitType.put(typeId, unitTypeInstance); } return unitTypeInstance; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayer.java index 078c8f2..e00070f 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayer.java @@ -150,6 +150,26 @@ public class CPlayer { return techtreeUnlocked; } + public void addTechtreeUnlocked(final War3ID rawcode) { + final Integer techtreeUnlocked = this.rawcodeToTechtreeUnlocked.get(rawcode); + if (techtreeUnlocked == null) { + this.rawcodeToTechtreeUnlocked.put(rawcode, 1); + } + else { + this.rawcodeToTechtreeUnlocked.put(rawcode, techtreeUnlocked + 1); + } + } + + public void removeTechtreeUnlocked(final War3ID rawcode) { + final Integer techtreeUnlocked = this.rawcodeToTechtreeUnlocked.get(rawcode); + if (techtreeUnlocked == null) { + this.rawcodeToTechtreeUnlocked.put(rawcode, -1); + } + else { + this.rawcodeToTechtreeUnlocked.put(rawcode, techtreeUnlocked - 1); + } + } + public void addStateListener(final CPlayerStateListener listener) { this.stateNotifier.subscribe(listener); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/AbilityActivationReceiver.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/AbilityActivationReceiver.java index ff76f71..09d79a4 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/AbilityActivationReceiver.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/AbilityActivationReceiver.java @@ -1,5 +1,7 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.util; +import com.etheller.warsmash.util.War3ID; + public interface AbilityActivationReceiver { void useOk(); @@ -7,7 +9,7 @@ public interface AbilityActivationReceiver { void notAnActiveAbility(); - void missingRequirement(String name); + void missingRequirement(War3ID type, int level); void casterMovementDisabled(); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/BooleanAbilityActivationReceiver.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/BooleanAbilityActivationReceiver.java index aaec289..b6ae3b7 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/BooleanAbilityActivationReceiver.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/BooleanAbilityActivationReceiver.java @@ -1,5 +1,7 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.util; +import com.etheller.warsmash.util.War3ID; + public class BooleanAbilityActivationReceiver implements AbilityActivationReceiver { public static final BooleanAbilityActivationReceiver INSTANCE = new BooleanAbilityActivationReceiver(); private boolean ok; @@ -20,7 +22,7 @@ public class BooleanAbilityActivationReceiver implements AbilityActivationReceiv } @Override - public void missingRequirement(final String name) { + public void missingRequirement(final War3ID type, final int level) { this.ok = false; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/MeleeUIAbilityActivationReceiver.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/MeleeUIAbilityActivationReceiver.java index 7012c8b..fe5420d 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/MeleeUIAbilityActivationReceiver.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/MeleeUIAbilityActivationReceiver.java @@ -1,5 +1,6 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.util; +import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.AudioContext; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderUnit; import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.CommandErrorListener; @@ -59,7 +60,7 @@ public class MeleeUIAbilityActivationReceiver implements AbilityActivationReceiv } @Override - public void missingRequirement(final String name) { + public void missingRequirement(final War3ID type, final int level) { this.genericError.onClick(this.commandErrorListener, this.worldSceneAudioContext, this.commandedUnit); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/StringMsgAbilityActivationReceiver.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/StringMsgAbilityActivationReceiver.java index 5ec8516..5f8697c 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/StringMsgAbilityActivationReceiver.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/StringMsgAbilityActivationReceiver.java @@ -1,5 +1,7 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.util; +import com.etheller.warsmash.util.War3ID; + public class StringMsgAbilityActivationReceiver implements AbilityActivationReceiver { private String message; private boolean useOk = false; @@ -34,8 +36,8 @@ public class StringMsgAbilityActivationReceiver implements AbilityActivationRece } @Override - public void missingRequirement(final String name) { - this.message = "NOTEXTERN: Requires " + name; + public void missingRequirement(final War3ID type, final int level) { + this.message = "NOTEXTERN: Requires " + type; } @Override diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java index 4eb17b5..11e6086 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java @@ -418,7 +418,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.unitManaText = (StringFrame) this.rootFrame.getFrameByName("UnitPortraitManaPointText", 0); final float infoPanelUnitDetailWidth = GameUI.convertY(this.uiViewport, 0.180f); - final float infoPanelUnitDetailHeight = GameUI.convertY(this.uiViewport, 0.105f); + final float infoPanelUnitDetailHeight = GameUI.convertY(this.uiViewport, 0.110f); this.smashSimpleInfoPanel = this.rootFrame.createSimpleFrame("SmashSimpleInfoPanel", this.rootFrame, 0); this.smashSimpleInfoPanel .addAnchor(new AnchorDefinition(FramePoint.BOTTOM, 0, GameUI.convertY(this.uiViewport, 0.0f))); @@ -556,7 +556,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma new Color(0xFFCC00FF), TextJustify.LEFT, TextJustify.MIDDLE, worldFrameMessageFontHeight); this.errorMessageFrame.addAnchor(new AnchorDefinition(FramePoint.BOTTOMLEFT, GameUI.convertX(this.uiViewport, 0.212f), GameUI.convertY(this.uiViewport, 0.182f))); - this.errorMessageFrame.setWidth(GameUI.convertX(this.uiViewport, 0.25f)); + this.errorMessageFrame.setWidth(GameUI.convertX(this.uiViewport, 0.35f)); this.errorMessageFrame.setHeight(GameUI.convertY(this.uiViewport, worldFrameMessageFontHeight)); this.errorMessageFrame.setFontShadowColor(new Color(0f, 0f, 0f, 0.9f)); @@ -1773,7 +1773,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.commandButton(cancelUI.getButtonPositionX(), cancelUI.getButtonPositionY(), cancelUI.getIcon(), 0, exitOrderId, 0, false, false, true, cancelUI.getToolTip(), cancelUI.getUberTip(), 0, 0, 0); } - this.selectedUnit.populateCommandCard(this.war3MapViewer.simulation, this, abilityDataUI, menuOrderId); + this.selectedUnit.populateCommandCard(this.war3MapViewer.simulation, this.rootFrame, this, abilityDataUI, + menuOrderId); } } @@ -1941,7 +1942,9 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma else { final List selectedUnits = this.war3MapViewer.selectUnit(screenX, worldScreenY, false); - selectWidgets(selectedUnits); + if (!selectedUnits.isEmpty()) { + selectWidgets(selectedUnits); + } } } } diff --git a/resources/UI/FrameDef/SmashUI/UnitPortrait.fdf b/resources/UI/FrameDef/SmashUI/UnitPortrait.fdf index ce7f91b..eb3aa55 100644 --- a/resources/UI/FrameDef/SmashUI/UnitPortrait.fdf +++ b/resources/UI/FrameDef/SmashUI/UnitPortrait.fdf @@ -31,13 +31,13 @@ Frame "SIMPLEFRAME" "UnitPortrait" { } String "UnitPortraitHitPointText" INHERITS "UnitPortraitTextTemplate" { - Anchor BOTTOM, 0, 0.0115, + Anchor BOTTOM, 0, 0.014, FontJustificationH JUSTIFYCENTER, FontColor 0.0 1.0 0.0 1.0, } String "UnitPortraitManaPointText" INHERITS "UnitPortraitTextTemplate" { - Anchor BOTTOM, 0, -0.0030, + Anchor BOTTOM, 0, -0.0005, FontJustificationH JUSTIFYCENTER, FontColor 1.0 1.0 1.0 1.0, } From e99b44222c7a5eaacc91dc86312b59a3bc8b7bf7 Mon Sep 17 00:00:00 2001 From: Retera Date: Mon, 15 Feb 2021 23:41:36 -0500 Subject: [PATCH 103/116] Update with small fixes, data ui load level 1 tooltip, night elf build --- core/assets/warsmash.ini | 3 +- .../etheller/warsmash/WarsmashGdxMapGame.java | 2 +- .../etheller/warsmash/units/DataTable.java | 3 - .../w3x/rendersim/ability/AbilityDataUI.java | 12 +-- .../build/CAbilityNightElfBuild.java | 89 +++++++++-------- .../viewer5/handlers/w3x/ui/MeleeUI.java | 96 ++++++++++--------- 6 files changed, 109 insertions(+), 96 deletions(-) diff --git a/core/assets/warsmash.ini b/core/assets/warsmash.ini index 3ac7324..92ef020 100644 --- a/core/assets/warsmash.ini +++ b/core/assets/warsmash.ini @@ -46,4 +46,5 @@ Path07="." //FilePath="V1\Farm.w3x" //FilePath="PenguinWorld.w3x" //FilePath="Maps\FrozenThrone\Campaign\UndeadX09.w3x" -FilePath="LavellaLagoon.w3x" +//FilePath="LavellaLagoon.w3x" +FilePath="WiceOrc.w3x" diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java index 1d57f1c..2e00545 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java @@ -58,7 +58,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.SettableCommandErro public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProvider, InputProcessor { private static final boolean ENABLE_AUDIO = true; - private static final boolean ENABLE_MUSIC = true; + private static final boolean ENABLE_MUSIC = false; private DataSource codebase; private War3MapViewer viewer; private final Rectangle tempRect = new Rectangle(); diff --git a/core/src/com/etheller/warsmash/units/DataTable.java b/core/src/com/etheller/warsmash/units/DataTable.java index b82337a..d9c33e3 100644 --- a/core/src/com/etheller/warsmash/units/DataTable.java +++ b/core/src/com/etheller/warsmash/units/DataTable.java @@ -139,9 +139,6 @@ public class DataTable implements ObjectData { if (currentUnit == null) { System.out.println("null for " + input); } - if ("Nofood".equals(fieldName)) { - System.out.println(builder.toString().trim()); - } values.add(builder.toString().trim()); } currentUnit.setField(fieldName, values); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityDataUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityDataUI.java index d83ba7a..f071393 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityDataUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityDataUI.java @@ -71,12 +71,12 @@ public class AbilityDataUI { final String iconResearchPath = gameUI.trySkinField(abilityTypeData.getFieldAsString(ICON_RESEARCH, 0)); final String iconNormalPath = gameUI.trySkinField(abilityTypeData.getFieldAsString(ICON_NORMAL, 0)); final String iconTurnOffPath = gameUI.trySkinField(abilityTypeData.getFieldAsString(ICON_TURN_OFF, 0)); - final String iconTip = abilityTypeData.getFieldAsString(ABILITY_TIP, 0); - final String iconUberTip = abilityTypeData.getFieldAsString(ABILITY_UBER_TIP, 0); - final String iconTurnOffTip = abilityTypeData.getFieldAsString(ABILITY_UN_TIP, 0); - final String iconTurnOffUberTip = abilityTypeData.getFieldAsString(ABILITY_UN_UBER_TIP, 0); - final String iconResearchTip = abilityTypeData.getFieldAsString(ABILITY_RESEARCH_TIP, 0); - final String iconResearchUberTip = abilityTypeData.getFieldAsString(ABILITY_RESEARCH_UBER_TIP, 0); + final String iconTip = abilityTypeData.getFieldAsString(ABILITY_TIP, 1); + final String iconUberTip = abilityTypeData.getFieldAsString(ABILITY_UBER_TIP, 1); + final String iconTurnOffTip = abilityTypeData.getFieldAsString(ABILITY_UN_TIP, 1); + final String iconTurnOffUberTip = abilityTypeData.getFieldAsString(ABILITY_UN_UBER_TIP, 1); + final String iconResearchTip = abilityTypeData.getFieldAsString(ABILITY_RESEARCH_TIP, 1); + final String iconResearchUberTip = abilityTypeData.getFieldAsString(ABILITY_RESEARCH_UBER_TIP, 1); final int iconResearchX = abilityTypeData.getFieldAsInteger(ICON_RESEARCH_X, 0); final int iconResearchY = abilityTypeData.getFieldAsInteger(ICON_RESEARCH_Y, 0); final int iconNormalX = abilityTypeData.getFieldAsInteger(ICON_NORMAL_X, 0); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityNightElfBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityNightElfBuild.java index a33a558..9fd0a2a 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityNightElfBuild.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityNightElfBuild.java @@ -1,57 +1,25 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build; +import java.awt.image.BufferedImage; import java.util.List; import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.build.CBehaviorOrcBuild; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayer; public class CAbilityNightElfBuild extends AbstractCAbilityBuild { + private CBehaviorOrcBuild buildBehavior; public CAbilityNightElfBuild(final int handleId, final List structuresBuilt) { super(handleId, structuresBuilt); - // TODO Auto-generated constructor stub - } - - @Override - public int getBaseOrderId() { - return OrderIds.nightelfbuild; - } - - @Override - public void onAdd(final CSimulation game, final CUnit unit) { - // TODO Auto-generated method stub - - } - - @Override - public void onRemove(final CSimulation game, final CUnit unit) { - // TODO Auto-generated method stub - - } - - @Override - public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final CWidget target) { - // TODO Auto-generated method stub - return null; - } - - @Override - public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, - final AbilityPointTarget point) { - // TODO Auto-generated method stub - return null; - } - - @Override - public CBehavior beginNoTarget(final CSimulation game, final CUnit caster, final int orderId) { - // TODO Auto-generated method stub - return null; } @Override @@ -60,9 +28,50 @@ public class CAbilityNightElfBuild extends AbstractCAbilityBuild { } @Override - public void onCancelFromQueue(final CSimulation game, final CUnit unit, final int orderId) { - // TODO Auto-generated method stub - + public void onAdd(final CSimulation game, final CUnit unit) { + this.buildBehavior = new CBehaviorOrcBuild(unit); } + @Override + public void onRemove(final CSimulation game, final CUnit unit) { + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final CWidget target) { + return caster.pollNextOrderBehavior(game); + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, + final AbilityPointTarget point) { + final War3ID orderIdAsRawtype = new War3ID(orderId); + final CUnitType unitType = game.getUnitData().getUnitType(orderIdAsRawtype); + final BufferedImage buildingPathingPixelMap = unitType.getBuildingPathingPixelMap(); + if (buildingPathingPixelMap != null) { + point.x = (float) Math.floor(point.x / 64f) * 64f; + point.y = (float) Math.floor(point.y / 64f) * 64f; + if (((buildingPathingPixelMap.getWidth() / 2) % 2) == 1) { + point.x += 32f; + } + if (((buildingPathingPixelMap.getHeight() / 2) % 2) == 1) { + point.y += 32f; + } + } + final CPlayer player = game.getPlayer(caster.getPlayerIndex()); + player.chargeFor(unitType); + if (unitType.getFoodUsed() != 0) { + player.setFoodUsed(player.getFoodUsed() + unitType.getFoodUsed()); + } + return this.buildBehavior.reset(point, orderId, getBaseOrderId()); + } + + @Override + public CBehavior beginNoTarget(final CSimulation game, final CUnit caster, final int orderId) { + return caster.pollNextOrderBehavior(game); + } + + @Override + public int getBaseOrderId() { + return OrderIds.nightelfbuild; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java index 11e6086..69f94e5 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java @@ -146,7 +146,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma private static final String BUILDING_PATHING_PREVIEW_KEY = "buildingPathingPreview"; public static final float DEFAULT_COMMAND_CARD_ICON_WIDTH = 0.039f; public static final float DEFAULT_COMMAND_CARD_ICON_PRESSED_WIDTH = 0.037f; - private static final int COMMAND_CARD_WIDTH = 4; + private static final int COMMAND_CARD_WIDTH = 5; private static final int COMMAND_CARD_HEIGHT = 3; private static final Vector2 screenCoordsVector = new Vector2(); @@ -1748,6 +1748,9 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma @Override public void ordersChanged() { reloadSelectedUnitUI(this.selectedUnit); + if (this.mouseOverUIFrame instanceof ClickableActionFrame) { + loadTooltip(); + } } @Override @@ -2094,50 +2097,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma if (mousedUIFrame != this.mouseOverUIFrame) { if (mousedUIFrame instanceof ClickableActionFrame) { this.mouseOverUIFrame = (ClickableActionFrame) mousedUIFrame; - final int goldCost = this.mouseOverUIFrame.getToolTipGoldCost(); - final int lumberCost = this.mouseOverUIFrame.getToolTipLumberCost(); - final int foodCost = this.mouseOverUIFrame.getToolTipFoodCost(); - final String toolTip = this.mouseOverUIFrame.getToolTip(); - this.rootFrame.setText(this.tooltipUberTipText, this.mouseOverUIFrame.getUberTip()); - int resourceIndex = 0; - if (goldCost != 0) { - this.tooltipResourceFrames[resourceIndex].setVisible(true); - this.tooltipResourceIconFrames[resourceIndex].setTexture("ToolTipGoldIcon", this.rootFrame); - this.rootFrame.setText(this.tooltipResourceTextFrames[resourceIndex], Integer.toString(goldCost)); - resourceIndex++; - } - if (lumberCost != 0) { - this.tooltipResourceFrames[resourceIndex].setVisible(true); - this.tooltipResourceIconFrames[resourceIndex].setTexture("ToolTipLumberIcon", this.rootFrame); - this.rootFrame.setText(this.tooltipResourceTextFrames[resourceIndex], Integer.toString(lumberCost)); - resourceIndex++; - } - if (foodCost != 0) { - this.tooltipResourceFrames[resourceIndex].setVisible(true); - this.tooltipResourceIconFrames[resourceIndex].setTexture("ToolTipSupplyIcon", this.rootFrame); - this.rootFrame.setText(this.tooltipResourceTextFrames[resourceIndex], Integer.toString(foodCost)); - resourceIndex++; - } - for (int i = resourceIndex; i < this.tooltipResourceFrames.length; i++) { - this.tooltipResourceFrames[i].setVisible(false); - } - float resourcesHeight; - if (resourceIndex != 0) { - this.tooltipUberTipText.addSetPoint(this.uberTipWithResourcesSetPoint); - resourcesHeight = 0.014f; - } - else { - this.tooltipUberTipText.addSetPoint(this.uberTipNoResourcesSetPoint); - resourcesHeight = 0.004f; - } - this.rootFrame.setText(this.tooltipText, toolTip); - final float predictedViewportHeight = this.tooltipText.getPredictedViewportHeight() - + GameUI.convertY(this.uiViewport, resourcesHeight) - + this.tooltipUberTipText.getPredictedViewportHeight() - + GameUI.convertY(this.uiViewport, 0.003f); - this.tooltipFrame.setHeight(predictedViewportHeight); - this.tooltipFrame.positionBounds(this.rootFrame, this.uiViewport); - this.tooltipFrame.setVisible(true); + loadTooltip(); } else { this.mouseOverUIFrame = null; @@ -2147,6 +2107,52 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma return false; } + private void loadTooltip() { + final int goldCost = this.mouseOverUIFrame.getToolTipGoldCost(); + final int lumberCost = this.mouseOverUIFrame.getToolTipLumberCost(); + final int foodCost = this.mouseOverUIFrame.getToolTipFoodCost(); + final String toolTip = this.mouseOverUIFrame.getToolTip(); + this.rootFrame.setText(this.tooltipUberTipText, this.mouseOverUIFrame.getUberTip()); + int resourceIndex = 0; + if (goldCost != 0) { + this.tooltipResourceFrames[resourceIndex].setVisible(true); + this.tooltipResourceIconFrames[resourceIndex].setTexture("ToolTipGoldIcon", this.rootFrame); + this.rootFrame.setText(this.tooltipResourceTextFrames[resourceIndex], Integer.toString(goldCost)); + resourceIndex++; + } + if (lumberCost != 0) { + this.tooltipResourceFrames[resourceIndex].setVisible(true); + this.tooltipResourceIconFrames[resourceIndex].setTexture("ToolTipLumberIcon", this.rootFrame); + this.rootFrame.setText(this.tooltipResourceTextFrames[resourceIndex], Integer.toString(lumberCost)); + resourceIndex++; + } + if (foodCost != 0) { + this.tooltipResourceFrames[resourceIndex].setVisible(true); + this.tooltipResourceIconFrames[resourceIndex].setTexture("ToolTipSupplyIcon", this.rootFrame); + this.rootFrame.setText(this.tooltipResourceTextFrames[resourceIndex], Integer.toString(foodCost)); + resourceIndex++; + } + for (int i = resourceIndex; i < this.tooltipResourceFrames.length; i++) { + this.tooltipResourceFrames[i].setVisible(false); + } + float resourcesHeight; + if (resourceIndex != 0) { + this.tooltipUberTipText.addSetPoint(this.uberTipWithResourcesSetPoint); + resourcesHeight = 0.014f; + } + else { + this.tooltipUberTipText.addSetPoint(this.uberTipNoResourcesSetPoint); + resourcesHeight = 0.004f; + } + this.rootFrame.setText(this.tooltipText, toolTip); + final float predictedViewportHeight = this.tooltipText.getPredictedViewportHeight() + + GameUI.convertY(this.uiViewport, resourcesHeight) + + this.tooltipUberTipText.getPredictedViewportHeight() + GameUI.convertY(this.uiViewport, 0.003f); + this.tooltipFrame.setHeight(predictedViewportHeight); + this.tooltipFrame.positionBounds(this.rootFrame, this.uiViewport); + this.tooltipFrame.setVisible(true); + } + public float getHeightRatioCorrection() { return this.heightRatioCorrection; } From 27ee2345744985a156993d30aeca84630a5c0204 Mon Sep 17 00:00:00 2001 From: Retera Date: Sat, 20 Feb 2021 10:42:27 -0500 Subject: [PATCH 104/116] Small updates --- core/assets/warsmash.ini | 60 ++++++------------- core/assets/warsmash122.ini | 51 ++++++++++++++++ core/assets/warsmash131.ini | 25 -------- core/assets/warsmash131notworking.ini | 42 +++++++++++++ .../warsmash/util/WarsmashConstants.java | 2 +- .../viewer5/handlers/w3x/War3MapViewer.java | 3 + .../impl/AbstractCAbilityTypeDefinition.java | 2 +- .../behaviors/build/CBehaviorUndeadBuild.java | 6 +- .../w3x/simulation/data/CUnitData.java | 6 +- .../viewer5/handlers/w3x/ui/MeleeUI.java | 2 +- 10 files changed, 127 insertions(+), 72 deletions(-) create mode 100644 core/assets/warsmash122.ini delete mode 100644 core/assets/warsmash131.ini create mode 100644 core/assets/warsmash131notworking.ini diff --git a/core/assets/warsmash.ini b/core/assets/warsmash.ini index 92ef020..ad07661 100644 --- a/core/assets/warsmash.ini +++ b/core/assets/warsmash.ini @@ -1,50 +1,26 @@ [DataSources] -Count=8 -Type00=MPQ -Path00="D:\Games\Warcraft III Patch 1.22\war3.mpq" -Type01=MPQ -Path01="D:\Games\Warcraft III Patch 1.22\War3x.mpq" -Type02=MPQ -Path02="D:\Games\Warcraft III Patch 1.22\War3xlocal.mpq" -Type03=MPQ -Path03="D:\Games\Warcraft III Patch 1.22\War3Patch.mpq" +Count=5 +Type00=Folder +Path00="D:\Games\Warcraft III CASC 1.31\war3.w3mod" +Type01=Folder +Path01="D:\Games\Warcraft III CASC 1.31\war3.w3mod\_locales\enus.w3mod" +Type02=Folder +Path02="..\..\resources" +Type03=Folder +Path03="D:\Backups\Warsmash\Data" Type04=Folder -Path04="..\..\resources" -Type05=Folder -Path05="D:\Backups\Warsmash\Data" -Type06=Folder -Path06="D:\Games\Warcraft III Patch 1.22\Maps" -Type07=Folder -Path07="." +Path04="." [Map] -//FilePath="CombatUnitTests.w3x" //FilePath="PitchRoll.w3x" -//FilePath="PeonStartingBase_Simple.w3x" -//FilePath="MyStromguarde.w3m" -//FilePath="ColdArrows.w3m" -//FilePath="DungeonGoldMine.w3m" -//FilePath="PlayerPeasants.w3m" -//FilePath="FireLord.w3x" +//FilePath="ReforgedGeorgeVacation.w3x" //FilePath="Maps\Campaign\NightElf03.w3m" -//FilePath="PhoenixAttack.w3x" -//FilePath="LightEnvironmentTest.w3x" +//FilePath="PrivateDontShare/Cult 8.w3x" //FilePath="TorchLight2.w3x" //FilePath="OrcAssault.w3x" -//FilePath="FrostyVsFarm.w3m" -//FilePath="ModelTest.w3x" -//FilePath="SpinningSample.w3x" -//FilePath="Maps\Campaign\Prologue02.w3m" -//FilePath="Pathing.w3x" -//FilePath="ItemFacing.w3x" -//FilePath=SomeParticleTests.w3x -//FilePath="PeonMiningMultiHall.w3x" -//FilePath="QuadtreeBugs.w3x" -//FilePath="test2.w3x" -//FilePath="FarseerHoldPositionTest.w3x" -//FilePath="Ramps.w3m" -//FilePath="V1\Farm.w3x" -//FilePath="PenguinWorld.w3x" -//FilePath="Maps\FrozenThrone\Campaign\UndeadX09.w3x" -//FilePath="LavellaLagoon.w3x" -FilePath="WiceOrc.w3x" +//FilePath="PeonStartingBase.w3x" +//FilePath="PhoenixAttack.w3x" +//FilePath="OperationReforged.w3x" +//FilePath="AzerothRoleplay1.909t03DecoratedV2.w3x" +//FilePath="American Colo EX 1.0 unpro.w3x" +FilePath="TheSheepAttack.w3x" diff --git a/core/assets/warsmash122.ini b/core/assets/warsmash122.ini new file mode 100644 index 0000000..d761aa6 --- /dev/null +++ b/core/assets/warsmash122.ini @@ -0,0 +1,51 @@ +[DataSources] +Count=8 +Type00=MPQ +Path00="D:\Games\Warcraft III Patch 1.22\war3.mpq" +Type01=MPQ +Path01="D:\Games\Warcraft III Patch 1.22\War3x.mpq" +Type02=MPQ +Path02="D:\Games\Warcraft III Patch 1.22\War3xlocal.mpq" +Type03=MPQ +Path03="D:\Games\Warcraft III Patch 1.22\War3Patch.mpq" +Type04=Folder +Path04="..\..\resources" +Type05=Folder +Path05="D:\Backups\Warsmash\Data" +Type06=Folder +Path06="D:\Games\Warcraft III Patch 1.22\Maps" +Type07=Folder +Path07="." + +[Map] +//FilePath="CombatUnitTests.w3x" +//FilePath="PitchRoll.w3x" +//FilePath="PeonStartingBase_Simple.w3x" +//FilePath="MyStromguarde.w3m" +//FilePath="ColdArrows.w3m" +//FilePath="DungeonGoldMine.w3m" +//FilePath="PlayerPeasants.w3m" +//FilePath="FireLord.w3x" +//FilePath="Maps\Campaign\NightElf03.w3m" +//FilePath="PhoenixAttack.w3x" +//FilePath="LightEnvironmentTest.w3x" +//FilePath="TorchLight2.w3x" +//FilePath="OrcAssault.w3x" +//FilePath="FrostyVsFarm.w3m" +//FilePath="ModelTest.w3x" +//FilePath="SpinningSample.w3x" +//FilePath="Maps\Campaign\Prologue02.w3m" +//FilePath="Pathing.w3x" +//FilePath="ItemFacing.w3x" +//FilePath=SomeParticleTests.w3x +//FilePath="PeonMiningMultiHall.w3x" +//FilePath="QuadtreeBugs.w3x" +//FilePath="test2.w3x" +//FilePath="FarseerHoldPositionTest.w3x" +//FilePath="Ramps.w3m" +//FilePath="V1\Farm.w3x" +//FilePath="PenguinWorld.w3x" +//FilePath="Maps\FrozenThrone\Campaign\UndeadX09.w3x" +//FilePath="LavellaLagoon.w3x" +//FilePath="WiceOrc.w3x" +FilePath="TheSheepAttack.w3x" diff --git a/core/assets/warsmash131.ini b/core/assets/warsmash131.ini deleted file mode 100644 index 2d0db59..0000000 --- a/core/assets/warsmash131.ini +++ /dev/null @@ -1,25 +0,0 @@ -[DataSources] -Count=5 -Type00=Folder -Path00="D:\Games\Warcraft III CASC 1.31\war3.w3mod" -Type01=Folder -Path01="D:\Games\Warcraft III CASC 1.31\war3.w3mod\_locales\enus.w3mod" -Type02=Folder -Path02="..\..\resources" -Type03=Folder -Path03="D:\Backups\Warsmash\Data" -Type04=Folder -Path04="." - -[Map] -//FilePath="PitchRoll.w3x" -//FilePath="ReforgedGeorgeVacation.w3x" -//FilePath="Maps\Campaign\NightElf03.w3m" -//FilePath="PrivateDontShare/Cult 8.w3x" -//FilePath="TorchLight2.w3x" -//FilePath="OrcAssault.w3x" -//FilePath="PeonStartingBase.w3x" -//FilePath="PhoenixAttack.w3x" -//FilePath="OperationReforged.w3x" -//FilePath="AzerothRoleplay1.909t03DecoratedV2.w3x" -FilePath="American Colo EX 1.0 unpro.w3x" diff --git a/core/assets/warsmash131notworking.ini b/core/assets/warsmash131notworking.ini new file mode 100644 index 0000000..140c51a --- /dev/null +++ b/core/assets/warsmash131notworking.ini @@ -0,0 +1,42 @@ +[DataSources] +Count=5 +Type00=CASC +Path00="D:\Games\Warcraft III Patch 1.31\" +Prefixes00="war3.w3mod","war3.w3mod\_deprecated.w3mod","war3.w3mod\_locales\enus.w3mod" +Type01=Folder +Path01="..\..\resources" +Type02=Folder +Path02="D:\Backups\Warsmash\Data" +Type03=Folder +Path03="D:\Games\Warcraft III Patch 1.22\Maps" +Type04=Folder +Path04="." + +[Map] +//FilePath="CombatUnitTests.w3x" +//FilePath="PitchRoll.w3x" +//FilePath="PeonStartingBase.w3x" +//FilePath="MyStromguarde.w3m" +//FilePath="ColdArrows.w3m" +//FilePath="DungeonGoldMine.w3m" +//FilePath="PlayerPeasants.w3m" +//FilePath="FireLord.w3x" +//FilePath="Maps\Campaign\NightElf03.w3m" +//FilePath="PhoenixAttack.w3x" +//FilePath="LightEnvironmentTest.w3x" +//FilePath="TorchLight2.w3x" +FilePath="OrcAssault.w3x" +//FilePath="FrostyVsFarm.w3m" +//FilePath="ModelTest.w3x" +//FilePath="SpinningSample.w3x" +//FilePath="Maps\Campaign\Prologue02.w3m" +//FilePath="Pathing.w3x" +//FilePath="ItemFacing.w3x" +//FilePath=SomeParticleTests.w3x +//FilePath="PeonMiningMultiHall.w3x" +//FilePath="QuadtreeBugs.w3x" +//FilePath="test2.w3x" +//FilePath="FarseerHoldPositionTest.w3x" +//FilePath="Ramps.w3m" +//FilePath="V1\Farm.w3x" +//FilePath="TheSheepAttack.w3x" \ No newline at end of file diff --git a/core/src/com/etheller/warsmash/util/WarsmashConstants.java b/core/src/com/etheller/warsmash/util/WarsmashConstants.java index d1876cc..5fc0f6b 100644 --- a/core/src/com/etheller/warsmash/util/WarsmashConstants.java +++ b/core/src/com/etheller/warsmash/util/WarsmashConstants.java @@ -1,7 +1,7 @@ package com.etheller.warsmash.util; public class WarsmashConstants { - public static final int MAX_PLAYERS = 16; + public static final int MAX_PLAYERS = 28; public static final int REPLACEABLE_TEXTURE_LIMIT = 64; public static final float SIMULATION_STEP_TIME = 1 / 20f; public static final int PORT_NUMBER = 6115; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index 9721338..3fcd3ab 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -541,6 +541,9 @@ public class War3MapViewer extends AbstractMdxModelViewer { if (damagedWidget == null) { damagedWidget = War3MapViewer.this.destructableToRenderPeer.get(damagedDestructable); } + if (damagedWidget == null) { + return; + } final String key = weaponSound + armorType; UnitSound combatSound = this.keyToCombatSound.get(key); if (combatSound == null) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/definitions/impl/AbstractCAbilityTypeDefinition.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/definitions/impl/AbstractCAbilityTypeDefinition.java index cf19827..52386a4 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/definitions/impl/AbstractCAbilityTypeDefinition.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/definitions/impl/AbstractCAbilityTypeDefinition.java @@ -20,7 +20,7 @@ public abstract class AbstractCAbilityTypeDefinition createAbilityType(final War3ID alias, final MutableGameObject abilityEditorData) { final int levels = abilityEditorData.getFieldAsInteger(LEVELS, 0); final List levelData = new ArrayList<>(); - for (int level = 0; level < levels; level++) { + for (int level = 1; level <= levels; level++) { levelData.add(createLevelData(abilityEditorData, level)); } return innerCreateAbilityType(alias, abilityEditorData, levelData); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorUndeadBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorUndeadBuild.java index a0f6733..f6491fc 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorUndeadBuild.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorUndeadBuild.java @@ -42,8 +42,12 @@ public class CBehaviorUndeadBuild extends CAbstractRangedBehavior { return true; } final CUnitType unitType = simulation.getUnitData().getUnitType(this.orderId); + final BufferedImage buildingPathingPixelMap = unitType.getBuildingPathingPixelMap(); + if (buildingPathingPixelMap == null) { + return this.unit.canReach(this.target.getX(), this.target.getY(), unitType.getCollisionSize()); + } return this.unit.canReachToPathing(0, simulation.getGameplayConstants().getBuildingAngle(), - unitType.getBuildingPathingPixelMap(), this.target.getX(), this.target.getY()); + buildingPathingPixelMap, this.target.getX(), this.target.getY()); } @Override diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java index f76ee68..a22a4c9 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java @@ -11,6 +11,7 @@ import com.etheller.warsmash.units.manager.MutableObjectData; import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid; +import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.MovementType; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.RemovablePathingMapInstance; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; @@ -246,7 +247,10 @@ public class CUnitData { final float turnRate = unitType.getFieldAsFloat(TURN_RATE, 0); final boolean isBldg = unitType.getFieldAsBoolean(IS_BLDG, 0); - final PathingGrid.MovementType movementType = PathingGrid.getMovementType(movetp); + PathingGrid.MovementType movementType = PathingGrid.getMovementType(movetp); + if (movementType == null) { + movementType = MovementType.DISABLED; + } final String unitName = unitType.getFieldAsString(NAME, 0); final float acquisitionRange = unitType.getFieldAsFloat(ACQUISITION_RANGE, 0); final float minimumAttackRange = unitType.getFieldAsFloat(MINIMUM_ATTACK_RANGE, 0); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java index 69f94e5..66e5524 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java @@ -146,7 +146,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma private static final String BUILDING_PATHING_PREVIEW_KEY = "buildingPathingPreview"; public static final float DEFAULT_COMMAND_CARD_ICON_WIDTH = 0.039f; public static final float DEFAULT_COMMAND_CARD_ICON_PRESSED_WIDTH = 0.037f; - private static final int COMMAND_CARD_WIDTH = 5; + private static final int COMMAND_CARD_WIDTH = 4; private static final int COMMAND_CARD_HEIGHT = 3; private static final Vector2 screenCoordsVector = new Vector2(); From 22e07de19a8f9a2e7016d508708fa652bc5178a7 Mon Sep 17 00:00:00 2001 From: Retera Date: Sat, 6 Mar 2021 12:46:51 -0500 Subject: [PATCH 105/116] Fun with some menu button support and hero stats and levels --- core/assets/warsmash.ini | 63 ++- core/assets/warsmash122.ini | 51 -- core/assets/warsmash131.ini | 26 + core/assets/warsmashPRSCMOD.ini | 16 +- core/assets/warsmashRF.ini | 2 +- core/assets/warsmashTTOR.ini | 22 + core/assets/warsmashUF.ini | 22 + core/src/com/etheller/warsmash/TestMain.java | 8 + .../etheller/warsmash/WarsmashGdxGame.java | 2 +- ...MapGame.java => WarsmashGdxMapScreen.java} | 67 +-- ...stGame.java => WarsmashGdxMenuScreen.java} | 139 +++--- .../warsmash/WarsmashGdxMultiScreenGame.java | 11 + .../warsmash/WarsmashPreviewApplication.java | 2 +- .../fdf/DynamicFontGeneratorHolder.java | 44 ++ .../etheller/warsmash/parsers/fdf/GameUI.java | 168 ++++++- .../fdf/frames/AbstractRenderableFrame.java | 4 +- .../parsers/fdf/frames/AbstractUIFrame.java | 30 +- .../parsers/fdf/frames/GlueButtonFrame.java | 151 ++++++ .../fdf/frames/GlueTextButtonFrame.java | 35 ++ .../parsers/fdf/frames/TextureFrame.java | 12 + .../etheller/warsmash/parsers/jass/Jass2.java | 18 +- .../etheller/warsmash/util/ImageUtils.java | 5 +- .../warsmash/util/WarsmashConstants.java | 11 +- .../warsmash/viewer5/AudioBufferSource.java | 5 +- .../viewer5/RawOpenGLTextureResource.java | 3 +- .../warsmash/viewer5/SkeletalNode.java | 24 + .../warsmash/viewer5/gl/AudioExtension.java | 2 +- .../handlers/mdx/AttachmentInstance.java | 1 + .../viewer5/handlers/mdx/EventObjectSnd.java | 3 +- .../handlers/mdx/MdxComplexInstance.java | 14 + .../viewer5/handlers/w3x/TextTag.java | 14 +- .../viewer5/handlers/w3x/UnitSound.java | 24 +- .../viewer5/handlers/w3x/War3MapViewer.java | 48 +- .../w3x/rendersim/ability/AbilityDataUI.java | 35 +- .../w3x/rendersim/ability/AbilityIconUI.java | 25 - .../w3x/rendersim/ability/AbilityUI.java | 72 +++ .../commandbuttons/AbilityCommandButton.java | 6 +- .../CommandCardPopulatingAbilityVisitor.java | 22 +- .../w3x/simulation/CGameplayConstants.java | 218 +++++++++ .../handlers/w3x/simulation/CItemType.java | 5 + .../handlers/w3x/simulation/CSimulation.java | 22 +- .../handlers/w3x/simulation/CUnit.java | 143 +++++- .../w3x/simulation/CUnitStateListener.java | 9 + .../handlers/w3x/simulation/CUnitType.java | 78 ++- .../simulation/abilities/CAbilityAttack.java | 8 +- .../simulation/abilities/CAbilityVisitor.java | 3 + .../abilities/combat/CAbilityColdArrows.java | 2 +- .../abilities/harvest/CAbilityHarvest.java | 2 +- .../abilities/hero/CAbilityHero.java | 272 ++++++++++- .../abilities/hero/CPrimaryAttribute.java | 23 + ...yDisableWhileUnderConstructionVisitor.java | 8 + .../combat/attacks/CUnitAttack.java | 80 +++- .../combat/attacks/CUnitAttackInstant.java | 8 + .../combat/attacks/CUnitAttackMissile.java | 8 + .../attacks/CUnitAttackMissileBounce.java | 10 + .../attacks/CUnitAttackMissileLine.java | 9 + .../attacks/CUnitAttackMissileSplash.java | 10 + .../combat/attacks/CUnitAttackNormal.java | 7 + .../w3x/simulation/data/CAbilityData.java | 3 + .../w3x/simulation/data/CItemData.java | 8 + .../w3x/simulation/data/CUnitData.java | 90 +++- .../util/SimulationRenderController.java | 2 + .../handlers/w3x/ui/CommandCardIcon.java | 8 + .../viewer5/handlers/w3x/ui/MeleeUI.java | 238 +++++++--- .../viewer5/handlers/w3x/ui/MenuUI.java | 331 ++++++++++++- .../viewer5/handlers/w3x/ui/QueueIcon.java | 8 + .../w3x/ui/command/ClickableActionFrame.java | 6 +- .../w3x/ui/command/ClickableFrame.java | 17 + .../rms/parsers/mdlx/MdlxTexture.java | 4 +- .../src/com/etheller/warsmash/audio/Flac.java | 15 +- .../etheller/warsmash/audio/OpenALSound.java | 4 +- .../warsmash/desktop/DesktopLauncher.java | 47 +- .../warsmash/desktop/util/TerrainView.java | 4 +- .../FrameDefinitionFieldVisitor.java | 13 +- .../parsers/fdf/datamodel/ControlStyle.java | 12 + resources/UI/FrameDef/UI/SimpleInfoPanel.fdf | 447 ------------------ 76 files changed, 2518 insertions(+), 871 deletions(-) delete mode 100644 core/assets/warsmash122.ini create mode 100644 core/assets/warsmash131.ini create mode 100644 core/assets/warsmashTTOR.ini create mode 100644 core/assets/warsmashUF.ini rename core/src/com/etheller/warsmash/{WarsmashGdxMapGame.java => WarsmashGdxMapScreen.java} (89%) rename core/src/com/etheller/warsmash/{WarsmashGdxMenuTestGame.java => WarsmashGdxMenuScreen.java} (87%) create mode 100644 core/src/com/etheller/warsmash/WarsmashGdxMultiScreenGame.java create mode 100644 core/src/com/etheller/warsmash/parsers/fdf/DynamicFontGeneratorHolder.java create mode 100644 core/src/com/etheller/warsmash/parsers/fdf/frames/GlueButtonFrame.java create mode 100644 core/src/com/etheller/warsmash/parsers/fdf/frames/GlueTextButtonFrame.java delete mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityIconUI.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityUI.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CItemType.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/hero/CPrimaryAttribute.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CItemData.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/ClickableFrame.java delete mode 100644 resources/UI/FrameDef/UI/SimpleInfoPanel.fdf diff --git a/core/assets/warsmash.ini b/core/assets/warsmash.ini index ad07661..2f07845 100644 --- a/core/assets/warsmash.ini +++ b/core/assets/warsmash.ini @@ -1,26 +1,53 @@ [DataSources] -Count=5 -Type00=Folder -Path00="D:\Games\Warcraft III CASC 1.31\war3.w3mod" -Type01=Folder -Path01="D:\Games\Warcraft III CASC 1.31\war3.w3mod\_locales\enus.w3mod" -Type02=Folder -Path02="..\..\resources" -Type03=Folder -Path03="D:\Backups\Warsmash\Data" +Count=8 +Type00=MPQ +Path00="D:\Games\Warcraft III Patch 1.22\war3.mpq" +Type01=MPQ +Path01="D:\Games\Warcraft III Patch 1.22\War3x.mpq" +Type02=MPQ +Path02="D:\Games\Warcraft III Patch 1.22\War3xlocal.mpq" +Type03=MPQ +Path03="D:\Games\Warcraft III Patch 1.22\War3Patch.mpq" Type04=Folder -Path04="." +Path04="..\..\resources" +Type05=Folder +Path05="D:\Backups\Warsmash\Data" +Type06=Folder +Path06="D:\Games\Warcraft III Patch 1.22\Maps" +Type07=Folder +Path07="." [Map] +//FilePath="CombatUnitTests.w3x" //FilePath="PitchRoll.w3x" -//FilePath="ReforgedGeorgeVacation.w3x" +//FilePath="PeonStartingBase_Simple.w3x" +FilePath="PeonStartingBase_Scythe.w3x" +//FilePath="MyStromguarde.w3m" +//FilePath="ColdArrows.w3m" +//FilePath="DungeonGoldMine.w3m" +//FilePath="PlayerPeasants.w3m" +//FilePath="FireLord.w3x" //FilePath="Maps\Campaign\NightElf03.w3m" -//FilePath="PrivateDontShare/Cult 8.w3x" +//FilePath="PhoenixAttack.w3x" +//FilePath="LightEnvironmentTest.w3x" //FilePath="TorchLight2.w3x" //FilePath="OrcAssault.w3x" -//FilePath="PeonStartingBase.w3x" -//FilePath="PhoenixAttack.w3x" -//FilePath="OperationReforged.w3x" -//FilePath="AzerothRoleplay1.909t03DecoratedV2.w3x" -//FilePath="American Colo EX 1.0 unpro.w3x" -FilePath="TheSheepAttack.w3x" +//FilePath="FrostyVsFarm.w3m" +//FilePath="ModelTest.w3x" +//FilePath="SpinningSample.w3x" +//FilePath="Maps\Campaign\Prologue02.w3m" +//FilePath="Pathing.w3x" +//FilePath="ItemFacing.w3x" +//FilePath=SomeParticleTests.w3x +//FilePath="PeonMiningMultiHall.w3x" +//FilePath="QuadtreeBugs.w3x" +//FilePath="test2.w3x" +//FilePath="FarseerHoldPositionTest.w3x" +//FilePath="Ramps.w3m" +//FilePath="V1\Farm.w3x" +//FilePath="PenguinWorld.w3x" +//FilePath="Maps\FrozenThrone\Campaign\UndeadX09.w3x" +//FilePath="LavellaLagoon.w3x" +//FilePath="WiceOrc.w3x" +//FilePath="NorthrendPathingDoodle.w3x" +//FilePath="Maps\Campaign\Prologue01.w3m" diff --git a/core/assets/warsmash122.ini b/core/assets/warsmash122.ini deleted file mode 100644 index d761aa6..0000000 --- a/core/assets/warsmash122.ini +++ /dev/null @@ -1,51 +0,0 @@ -[DataSources] -Count=8 -Type00=MPQ -Path00="D:\Games\Warcraft III Patch 1.22\war3.mpq" -Type01=MPQ -Path01="D:\Games\Warcraft III Patch 1.22\War3x.mpq" -Type02=MPQ -Path02="D:\Games\Warcraft III Patch 1.22\War3xlocal.mpq" -Type03=MPQ -Path03="D:\Games\Warcraft III Patch 1.22\War3Patch.mpq" -Type04=Folder -Path04="..\..\resources" -Type05=Folder -Path05="D:\Backups\Warsmash\Data" -Type06=Folder -Path06="D:\Games\Warcraft III Patch 1.22\Maps" -Type07=Folder -Path07="." - -[Map] -//FilePath="CombatUnitTests.w3x" -//FilePath="PitchRoll.w3x" -//FilePath="PeonStartingBase_Simple.w3x" -//FilePath="MyStromguarde.w3m" -//FilePath="ColdArrows.w3m" -//FilePath="DungeonGoldMine.w3m" -//FilePath="PlayerPeasants.w3m" -//FilePath="FireLord.w3x" -//FilePath="Maps\Campaign\NightElf03.w3m" -//FilePath="PhoenixAttack.w3x" -//FilePath="LightEnvironmentTest.w3x" -//FilePath="TorchLight2.w3x" -//FilePath="OrcAssault.w3x" -//FilePath="FrostyVsFarm.w3m" -//FilePath="ModelTest.w3x" -//FilePath="SpinningSample.w3x" -//FilePath="Maps\Campaign\Prologue02.w3m" -//FilePath="Pathing.w3x" -//FilePath="ItemFacing.w3x" -//FilePath=SomeParticleTests.w3x -//FilePath="PeonMiningMultiHall.w3x" -//FilePath="QuadtreeBugs.w3x" -//FilePath="test2.w3x" -//FilePath="FarseerHoldPositionTest.w3x" -//FilePath="Ramps.w3m" -//FilePath="V1\Farm.w3x" -//FilePath="PenguinWorld.w3x" -//FilePath="Maps\FrozenThrone\Campaign\UndeadX09.w3x" -//FilePath="LavellaLagoon.w3x" -//FilePath="WiceOrc.w3x" -FilePath="TheSheepAttack.w3x" diff --git a/core/assets/warsmash131.ini b/core/assets/warsmash131.ini new file mode 100644 index 0000000..ad07661 --- /dev/null +++ b/core/assets/warsmash131.ini @@ -0,0 +1,26 @@ +[DataSources] +Count=5 +Type00=Folder +Path00="D:\Games\Warcraft III CASC 1.31\war3.w3mod" +Type01=Folder +Path01="D:\Games\Warcraft III CASC 1.31\war3.w3mod\_locales\enus.w3mod" +Type02=Folder +Path02="..\..\resources" +Type03=Folder +Path03="D:\Backups\Warsmash\Data" +Type04=Folder +Path04="." + +[Map] +//FilePath="PitchRoll.w3x" +//FilePath="ReforgedGeorgeVacation.w3x" +//FilePath="Maps\Campaign\NightElf03.w3m" +//FilePath="PrivateDontShare/Cult 8.w3x" +//FilePath="TorchLight2.w3x" +//FilePath="OrcAssault.w3x" +//FilePath="PeonStartingBase.w3x" +//FilePath="PhoenixAttack.w3x" +//FilePath="OperationReforged.w3x" +//FilePath="AzerothRoleplay1.909t03DecoratedV2.w3x" +//FilePath="American Colo EX 1.0 unpro.w3x" +FilePath="TheSheepAttack.w3x" diff --git a/core/assets/warsmashPRSCMOD.ini b/core/assets/warsmashPRSCMOD.ini index e20aad9..0c75d66 100644 --- a/core/assets/warsmashPRSCMOD.ini +++ b/core/assets/warsmashPRSCMOD.ini @@ -4,23 +4,23 @@ [DataSources] Count=9 Type00=MPQ -Path00="E:\Games\Warcraft III Project Revolution\War3\The Sheep Attack\war3.mpq" +Path00="D:\Games\Warcraft III Project Revolution\War3\The Sheep Attack\war3.mpq" Type01=MPQ -Path01="E:\Games\Warcraft III Project Revolution\War3\The Sheep Attack\War3x.mpq" +Path01="D:\Games\Warcraft III Project Revolution\War3\The Sheep Attack\War3x.mpq" Type02=MPQ -Path02="E:\Games\Warcraft III Project Revolution\War3\The Sheep Attack\War3xlocal.mpq" +Path02="D:\Games\Warcraft III Project Revolution\War3\The Sheep Attack\War3xlocal.mpq" Type03=MPQ -Path03="E:\Games\Warcraft III Project Revolution\War3\The Sheep Attack\war3patch.mpq" +Path03="D:\Games\Warcraft III Project Revolution\War3\The Sheep Attack\war3patch.mpq" Type04=MPQ -Path04="E:\Games\Warcraft III Project Revolution\PRSCMOD\Revolution.mpq" +Path04="D:\Games\Warcraft III Project Revolution\PRSCMOD\Revolution.mpq" Type05=MPQ -Path05="E:\Games\Warcraft III Project Revolution\PRSCMOD\Sound.mpq" +Path05="D:\Games\Warcraft III Project Revolution\PRSCMOD\Sound.mpq" Type06=Folder -Path06="E:\Games\Warcraft III Project Revolution\ProjectRevolusmash" +Path06="D:\Games\Warcraft III Project Revolution\ProjectRevolusmash" Type07=Folder Path07="..\..\resources" Type08=Folder -Path08="E:\Games\Warcraft III Project Revolution\PRSCMOD\PR-Maps" +Path08="D:\Games\Warcraft III Project Revolution\PRSCMOD\PR-Maps" [Map] FilePath="ProjectRevolusmash.w3x" \ No newline at end of file diff --git a/core/assets/warsmashRF.ini b/core/assets/warsmashRF.ini index c736fbc..9279a95 100644 --- a/core/assets/warsmashRF.ini +++ b/core/assets/warsmashRF.ini @@ -2,7 +2,7 @@ Count=5 Type00=CASC Path00="C:\Program Files\Warcraft III" -Prefixes00=war3.w3mod,war3.w3mod\_deprecated.w3mod,war3.w3mod\_locales\enus.w3mod +Prefixes00=war3.w3mod,war3.w3mod\_deprecated.w3mod,war3.w3mod\_locales\enus.w3mod,war3.w3mod\_hd.w3mod,war3.w3mod\_hd.w3mod\_locales\enus.w3mod Type01=Folder Path01="..\..\resources" Type02=Folder diff --git a/core/assets/warsmashTTOR.ini b/core/assets/warsmashTTOR.ini new file mode 100644 index 0000000..b1b9c90 --- /dev/null +++ b/core/assets/warsmashTTOR.ini @@ -0,0 +1,22 @@ +// This is the Warsmash INI file for Project Revolution +// PRSCMOD + +[DataSources] +Count=7 +Type00=MPQ +Path00="D:\Games\Warcraft III Project Revolution\War3\The Sheep Attack\war3.mpq" +Type01=MPQ +Path01="D:\Games\Warcraft III Project Revolution\War3\The Sheep Attack\War3x.mpq" +Type02=MPQ +Path02="D:\Games\Warcraft III Project Revolution\War3\The Sheep Attack\War3xlocal.mpq" +Type03=MPQ +Path03="D:\Games\Warcraft III Project Revolution\War3\The Sheep Attack\war3patch_TTOR.mpq" +Type04=Folder +Path04="D:\Games\Warcraft III Project Revolution\ProjectRevolusmash" +Type05=Folder +Path05="..\..\resources" +Type06=Folder +Path06="D:\Games\Warcraft III Project Revolution\War3\The Sheep Attack\Maps" + +[Map] +FilePath="(2)BootyBay.w3m" \ No newline at end of file diff --git a/core/assets/warsmashUF.ini b/core/assets/warsmashUF.ini new file mode 100644 index 0000000..eac283a --- /dev/null +++ b/core/assets/warsmashUF.ini @@ -0,0 +1,22 @@ +// This is the Warsmash INI file for Project Revolution +// PRSCMOD + +[DataSources] +Count=7 +Type00=MPQ +Path00="D:\Games\Warcraft III Patch 1.27 Redownload\Warcraft III\war3.mpq" +Type01=MPQ +Path01="D:\Games\Warcraft III Patch 1.27 Redownload\Warcraft III\War3x.mpq" +Type02=MPQ +Path02="D:\Games\Warcraft III Patch 1.27 Redownload\Warcraft III\War3xlocal.mpq" +Type03=MPQ +Path03="D:\Games\Warcraft III Patch 1.27 Redownload\Warcraft III\war3patch.mpq" +Type04=MPQ +Path04="D:\Games\Warcraft III Patch 1.27 Redownload\Warcraft III\War3Mod.mpq" +Type05=Folder +Path05="..\..\resources" +Type06=Folder +Path06="D:\Games\Warcraft III Patch 1.27 Redownload\Warcraft III\Maps" + +[Map] +FilePath="Maps\Campaign\Prologue01.w3m" \ No newline at end of file diff --git a/core/src/com/etheller/warsmash/TestMain.java b/core/src/com/etheller/warsmash/TestMain.java index 6ab5d22..bc30512 100644 --- a/core/src/com/etheller/warsmash/TestMain.java +++ b/core/src/com/etheller/warsmash/TestMain.java @@ -1,7 +1,15 @@ package com.etheller.warsmash; +import com.etheller.warsmash.util.War3ID; + public class TestMain { public static void main(final String[] args) { + if (true) { + System.out.println(War3ID.fromString("hwat").getValue()); + System.out.println(Integer.toHexString(War3ID.fromString("hwat").getValue())); + System.out.println(new War3ID(0x68776174)); + return; + } // System.out.println(Integer.parseInt("4294967295")); for (int i = 1; i <= 30; i++) { // System.out.println(a(i)); diff --git a/core/src/com/etheller/warsmash/WarsmashGdxGame.java b/core/src/com/etheller/warsmash/WarsmashGdxGame.java index c5bb2f9..e8f9c1e 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxGame.java @@ -62,7 +62,7 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide final String renderer = Gdx.gl.glGetString(GL20.GL_RENDERER); System.err.println("Renderer: " + renderer); - this.codebase = WarsmashGdxMapGame.parseDataSources(this.warsmashIni); + this.codebase = WarsmashGdxMapScreen.parseDataSources(this.warsmashIni); this.viewer = new MdxViewer(this.codebase, this); this.viewer.addHandler(new MdxHandler()); diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java b/core/src/com/etheller/warsmash/WarsmashGdxMapScreen.java similarity index 89% rename from core/src/com/etheller/warsmash/WarsmashGdxMapGame.java rename to core/src/com/etheller/warsmash/WarsmashGdxMapScreen.java index 2e00545..9d7b9c6 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMapScreen.java @@ -9,20 +9,16 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import com.badlogic.gdx.ApplicationAdapter; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.InputProcessor; +import com.badlogic.gdx.Screen; import com.badlogic.gdx.audio.Music; -import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.GL30; import com.badlogic.gdx.graphics.OrthographicCamera; import com.badlogic.gdx.graphics.Texture; -import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.GlyphLayout; import com.badlogic.gdx.graphics.g2d.SpriteBatch; -import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator; -import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator.FreeTypeFontParameter; import com.badlogic.gdx.graphics.glutils.ShapeRenderer; import com.badlogic.gdx.math.Matrix4; import com.badlogic.gdx.math.Rectangle; @@ -56,7 +52,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayerUnit import com.etheller.warsmash.viewer5.handlers.w3x.ui.MeleeUI; import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.SettableCommandErrorListener; -public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProvider, InputProcessor { +public class WarsmashGdxMapScreen implements CanvasProvider, InputProcessor, Screen { private static final boolean ENABLE_AUDIO = true; private static final boolean ENABLE_MUSIC = false; private DataSource codebase; @@ -65,8 +61,6 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv // libGDX stuff private OrthographicCamera uiCamera; - private BitmapFont font; - private BitmapFont font20; private SpriteBatch batch; private ExtendViewport uiViewport; private GlyphLayout glyphLayout; @@ -80,10 +74,13 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv private Scene uiScene; private MeleeUI meleeUI; - private FreeTypeFontGenerator fontGenerator; - public WarsmashGdxMapGame(final DataTable warsmashIni) { + private Music currentMusic; + private final String fileToLoad; + + public WarsmashGdxMapScreen(final DataTable warsmashIni, final String fileToLoad) { this.warsmashIni = warsmashIni; + this.fileToLoad = fileToLoad; } /* @@ -92,7 +89,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv * @see com.badlogic.gdx.ApplicationAdapter#create() */ @Override - public void create() { + public void show() { final ByteBuffer tempByteBuffer = ByteBuffer.allocateDirect(4); tempByteBuffer.order(ByteOrder.LITTLE_ENDIAN); @@ -115,7 +112,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.viewer.enableAudio(); } try { - this.viewer.loadMap(this.warsmashIni.get("Map").getField("FilePath"), 0); + this.viewer.loadMap(this.fileToLoad, 0); } catch (final IOException e) { throw new RuntimeException(e); @@ -155,13 +152,6 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv final int width = Gdx.graphics.getWidth(); final int height = Gdx.graphics.getHeight(); - this.fontGenerator = new FreeTypeFontGenerator( - new DataSourceFileHandle(this.viewer.dataSource, "fonts\\FRIZQT__.TTF")); - final FreeTypeFontParameter fontParam = new FreeTypeFontParameter(); - fontParam.size = 32; - this.font = this.fontGenerator.generateFont(fontParam); - fontParam.size = 20; - this.font20 = this.fontGenerator.generateFont(fontParam); this.glyphLayout = new GlyphLayout(); // Constructs a new OrthographicCamera, using the given viewport width and @@ -207,24 +197,25 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv cameraRatesElement.getFieldFloatValue("FOV"), cameraRatesElement.getFieldFloatValue("Rotation"), cameraRatesElement.getFieldFloatValue("Distance"), cameraRatesElement.getFieldFloatValue("Forward"), cameraRatesElement.getFieldFloatValue("Strafe")); - this.meleeUI = new MeleeUI(this.viewer.mapMpq, this.uiViewport, this.fontGenerator, this.uiScene, portraitScene, - cameraPresets, cameraRates, this.viewer, new RootFrameListener() { + this.meleeUI = new MeleeUI(this.viewer.mapMpq, this.uiViewport, this.uiScene, portraitScene, cameraPresets, + cameraRates, this.viewer, new RootFrameListener() { @Override public void onCreate(final GameUI rootFrame) { - WarsmashGdxMapGame.this.viewer.setGameUI(rootFrame); + WarsmashGdxMapScreen.this.viewer.setGameUI(rootFrame); if (ENABLE_MUSIC) { final String musicField = rootFrame.getSkinField("Music_V1"); final String[] musics = musicField.split(";"); String musicPath = musics[(int) (Math.random() * musics.length)]; - if (true) { + if (false) { musicPath = "Sound\\Music\\mp3Music\\PH1.mp3"; } final Music music = Gdx.audio.newMusic( - new DataSourceFileHandle(WarsmashGdxMapGame.this.viewer.dataSource, musicPath)); + new DataSourceFileHandle(WarsmashGdxMapScreen.this.viewer.dataSource, musicPath)); music.setVolume(0.2f); music.setLooping(true); music.play(); + WarsmashGdxMapScreen.this.currentMusic = music; } } }, new CPlayerUnitOrderExecutor(this.viewer.simulation, commandErrorListener)); @@ -292,7 +283,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv } @Override - public void render() { + public void render(final float delta) { this.uiCamera.update(); Gdx.gl30.glEnable(GL30.GL_SCISSOR_TEST); final float deltaTime = Gdx.graphics.getDeltaTime(); @@ -322,12 +313,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.uiViewport.apply(); this.batch.setProjectionMatrix(this.uiCamera.combined); this.batch.begin(); - this.meleeUI.render(this.batch, this.font20, this.glyphLayout); - this.font.setColor(Color.YELLOW); - final String fpsString = "FPS: " + Gdx.graphics.getFramesPerSecond(); - this.glyphLayout.setText(this.font, fpsString); - this.font.draw(this.batch, fpsString, (this.uiViewport.getMinWorldWidth() - this.glyphLayout.width) / 2, - 1100 * this.meleeUI.getHeightRatioCorrection()); + this.meleeUI.render(this.batch, this.glyphLayout); this.batch.end(); Gdx.gl30.glEnable(GL30.GL_SCISSOR_TEST); @@ -336,7 +322,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv @Override public void dispose() { - this.fontGenerator.dispose(); + this.meleeUI.dispose(); } @Override @@ -351,7 +337,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv @Override public void resize(final int width, final int height) { - super.resize(width, height); +// super.resize(width, height); this.uiViewport.update(width, height); this.uiCamera.position.set(this.uiViewport.getMinWorldWidth() / 2, this.uiViewport.getMinWorldHeight() / 2, 0); @@ -529,4 +515,19 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv } } + + @Override + public void pause() { + } + + @Override + public void resume() { + } + + @Override + public void hide() { + if (this.currentMusic != null) { + this.currentMusic.stop(); + } + } } diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMenuTestGame.java b/core/src/com/etheller/warsmash/WarsmashGdxMenuScreen.java similarity index 87% rename from core/src/com/etheller/warsmash/WarsmashGdxMenuTestGame.java rename to core/src/com/etheller/warsmash/WarsmashGdxMenuScreen.java index 064f9ee..8721dd9 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMenuTestGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMenuScreen.java @@ -5,20 +5,16 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.IntBuffer; -import com.badlogic.gdx.ApplicationAdapter; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.InputProcessor; +import com.badlogic.gdx.Screen; import com.badlogic.gdx.audio.Music; -import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.GL30; import com.badlogic.gdx.graphics.OrthographicCamera; import com.badlogic.gdx.graphics.Texture; -import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.GlyphLayout; import com.badlogic.gdx.graphics.g2d.SpriteBatch; -import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator; -import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator.FreeTypeFontParameter; import com.badlogic.gdx.math.Matrix4; import com.badlogic.gdx.math.Quaternion; import com.badlogic.gdx.math.Rectangle; @@ -30,6 +26,7 @@ import com.etheller.warsmash.parsers.jass.Jass2.RootFrameListener; import com.etheller.warsmash.units.DataTable; import com.etheller.warsmash.util.DataSourceFileHandle; import com.etheller.warsmash.util.ImageUtils; +import com.etheller.warsmash.util.WarsmashConstants; import com.etheller.warsmash.viewer5.Camera; import com.etheller.warsmash.viewer5.CanvasProvider; import com.etheller.warsmash.viewer5.Model; @@ -50,7 +47,7 @@ import com.etheller.warsmash.viewer5.handlers.mdx.SequenceLoopMode; import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; import com.etheller.warsmash.viewer5.handlers.w3x.ui.MenuUI; -public class WarsmashGdxMenuTestGame extends ApplicationAdapter implements CanvasProvider, InputProcessor { +public class WarsmashGdxMenuScreen implements CanvasProvider, InputProcessor, Screen { private static final boolean ENABLE_AUDIO = true; private static final boolean ENABLE_MUSIC = true; private DataSource codebase; @@ -61,8 +58,6 @@ public class WarsmashGdxMenuTestGame extends ApplicationAdapter implements Canva // libGDX stuff private OrthographicCamera uiCamera; - private BitmapFont font; - private BitmapFont font20; private SpriteBatch batch; private ExtendViewport uiViewport; private GlyphLayout glyphLayout; @@ -71,14 +66,16 @@ public class WarsmashGdxMenuTestGame extends ApplicationAdapter implements Canva private Scene uiScene; private Texture solidGreenTexture; private MenuUI menuUI; + private final WarsmashGdxMultiScreenGame game; + private Music currentMusic; - public WarsmashGdxMenuTestGame(final DataTable warsmashIni) { + public WarsmashGdxMenuScreen(final DataTable warsmashIni, final WarsmashGdxMultiScreenGame game) { this.warsmashIni = warsmashIni; + this.game = game; } @Override - public void create() { - + public void show() { final ByteBuffer tempByteBuffer = ByteBuffer.allocateDirect(4); tempByteBuffer.order(ByteOrder.LITTLE_ENDIAN); final IntBuffer temp = tempByteBuffer.asIntBuffer(); @@ -91,7 +88,7 @@ public class WarsmashGdxMenuTestGame extends ApplicationAdapter implements Canva final String renderer = Gdx.gl.glGetString(GL20.GL_RENDERER); System.err.println("Renderer: " + renderer); - this.codebase = WarsmashGdxMapGame.parseDataSources(this.warsmashIni); + this.codebase = WarsmashGdxMapScreen.parseDataSources(this.warsmashIni); this.viewer = new MdxViewer(this.codebase, this); this.viewer.addHandler(new MdxHandler()); @@ -108,13 +105,6 @@ public class WarsmashGdxMenuTestGame extends ApplicationAdapter implements Canva final int width = Gdx.graphics.getWidth(); final int height = Gdx.graphics.getHeight(); - final FreeTypeFontGenerator fontGenerator = new FreeTypeFontGenerator( - new DataSourceFileHandle(this.viewer.dataSource, "fonts\\FRIZQT__.TTF")); - final FreeTypeFontParameter fontParam = new FreeTypeFontParameter(); - fontParam.size = 32; - this.font = fontGenerator.generateFont(fontParam); - fontParam.size = 20; - this.font20 = fontGenerator.generateFont(fontParam); this.glyphLayout = new GlyphLayout(); // Constructs a new OrthographicCamera, using the given viewport width and @@ -184,27 +174,30 @@ public class WarsmashGdxMenuTestGame extends ApplicationAdapter implements Canva System.out.println("Loaded"); Gdx.gl30.glClearColor(0.0f, 0.0f, 0.0f, 1); - this.menuUI = new MenuUI(this.viewer.dataSource, this.uiViewport, fontGenerator, this.uiScene, this.viewer, - new RootFrameListener() { + this.menuUI = new MenuUI(this.viewer.dataSource, this.uiViewport, this.uiScene, this.viewer, this.game, + this.warsmashIni, new RootFrameListener() { @Override public void onCreate(final GameUI rootFrame) { // WarsmashGdxMapGame.this.viewer.setGameUI(rootFrame); if (ENABLE_MUSIC) { - final String musicField = rootFrame.getSkinField("GlueMusic_V1"); + final String musicField = rootFrame + .getSkinField("GlueMusic_V" + WarsmashConstants.GAME_VERSION); final String[] musics = musicField.split(";"); final String musicPath = musics[(int) (Math.random() * musics.length)]; - final Music music = Gdx.audio.newMusic(new DataSourceFileHandle( - WarsmashGdxMenuTestGame.this.viewer.dataSource, musicPath)); + final Music music = Gdx.audio.newMusic( + new DataSourceFileHandle(WarsmashGdxMenuScreen.this.viewer.dataSource, musicPath)); music.setVolume(0.2f); music.setLooping(true); music.play(); + WarsmashGdxMenuScreen.this.currentMusic = music; } singleModelScene(scene, - War3MapViewer.mdx(rootFrame.getSkinField("GlueSpriteLayerBackground_V1")), "Stand"); - WarsmashGdxMenuTestGame.this.modelCamera = WarsmashGdxMenuTestGame.this.mainModel.cameras - .get(0); + War3MapViewer.mdx(rootFrame + .getSkinField("GlueSpriteLayerBackground_V" + WarsmashConstants.GAME_VERSION)), + "Stand"); + WarsmashGdxMenuScreen.this.modelCamera = WarsmashGdxMenuScreen.this.mainModel.cameras.get(0); } }); @@ -213,7 +206,6 @@ public class WarsmashGdxMenuTestGame extends ApplicationAdapter implements Canva libgdxContentInstance.setLocation(0f, 0f, -0.5f); libgdxContentInstance.setScene(this.uiScene); this.menuUI.main(); - fontGenerator.dispose(); updateUIScene(); @@ -487,7 +479,7 @@ public class WarsmashGdxMenuTestGame extends ApplicationAdapter implements Canva private final boolean firstFrame = true; @Override - public void render() { + public void render(final float delta) { Gdx.gl30.glEnable(GL30.GL_SCISSOR_TEST); final float deltaTime = Gdx.graphics.getDeltaTime(); @@ -507,6 +499,7 @@ public class WarsmashGdxMenuTestGame extends ApplicationAdapter implements Canva @Override public void dispose() { + this.menuUI.dispose(); } @Override @@ -525,7 +518,7 @@ public class WarsmashGdxMenuTestGame extends ApplicationAdapter implements Canva this.tempRect.height = height; this.cameraManager.camera.viewport(this.tempRect); - super.resize(width, height); +// super.resize(width, height); this.uiViewport.update(width, height); this.uiCamera.position.set(this.uiViewport.getMinWorldWidth() / 2, this.uiViewport.getMinWorldHeight() / 2, 0); @@ -595,20 +588,16 @@ public class WarsmashGdxMenuTestGame extends ApplicationAdapter implements Canva this.quatHeap.transform(this.position); this.position.scl(this.distance); this.position = this.position.add(this.target); - if (WarsmashGdxMenuTestGame.this.modelCamera != null) { - WarsmashGdxMenuTestGame.this.modelCamera.getPositionTranslation( - WarsmashGdxMenuTestGame.this.cameraPositionTemp, - WarsmashGdxMenuTestGame.this.mainInstance.sequence, - WarsmashGdxMenuTestGame.this.mainInstance.frame, - WarsmashGdxMenuTestGame.this.mainInstance.counter); - WarsmashGdxMenuTestGame.this.modelCamera.getTargetTranslation( - WarsmashGdxMenuTestGame.this.cameraTargetTemp, - WarsmashGdxMenuTestGame.this.mainInstance.sequence, - WarsmashGdxMenuTestGame.this.mainInstance.frame, - WarsmashGdxMenuTestGame.this.mainInstance.counter); + if (WarsmashGdxMenuScreen.this.modelCamera != null) { + WarsmashGdxMenuScreen.this.modelCamera.getPositionTranslation( + WarsmashGdxMenuScreen.this.cameraPositionTemp, WarsmashGdxMenuScreen.this.mainInstance.sequence, + WarsmashGdxMenuScreen.this.mainInstance.frame, WarsmashGdxMenuScreen.this.mainInstance.counter); + WarsmashGdxMenuScreen.this.modelCamera.getTargetTranslation(WarsmashGdxMenuScreen.this.cameraTargetTemp, + WarsmashGdxMenuScreen.this.mainInstance.sequence, WarsmashGdxMenuScreen.this.mainInstance.frame, + WarsmashGdxMenuScreen.this.mainInstance.counter); - this.position.set(WarsmashGdxMenuTestGame.this.modelCamera.position); - this.target.set(WarsmashGdxMenuTestGame.this.modelCamera.targetPosition); + this.position.set(WarsmashGdxMenuScreen.this.modelCamera.position); + this.target.set(WarsmashGdxMenuScreen.this.modelCamera.targetPosition); // this.vecHeap2.set(this.target); // this.vecHeap2.sub(this.position); // this.vecHeap.set(this.vecHeap2); @@ -618,16 +607,15 @@ public class WarsmashGdxMenuTestGame extends ApplicationAdapter implements Canva // this.vecHeap.scl(this.camera.rect.height / 2f); // this.position.add(this.vecHeap); - this.position.add(WarsmashGdxMenuTestGame.this.cameraPositionTemp[0], - WarsmashGdxMenuTestGame.this.cameraPositionTemp[1], - WarsmashGdxMenuTestGame.this.cameraPositionTemp[2]); - this.target.add(WarsmashGdxMenuTestGame.this.cameraTargetTemp[0], - WarsmashGdxMenuTestGame.this.cameraTargetTemp[1], - WarsmashGdxMenuTestGame.this.cameraTargetTemp[2]); - this.camera.perspective(WarsmashGdxMenuTestGame.this.modelCamera.fieldOfView * 0.75f, + this.position.add(WarsmashGdxMenuScreen.this.cameraPositionTemp[0], + WarsmashGdxMenuScreen.this.cameraPositionTemp[1], + WarsmashGdxMenuScreen.this.cameraPositionTemp[2]); + this.target.add(WarsmashGdxMenuScreen.this.cameraTargetTemp[0], + WarsmashGdxMenuScreen.this.cameraTargetTemp[1], WarsmashGdxMenuScreen.this.cameraTargetTemp[2]); + this.camera.perspective(WarsmashGdxMenuScreen.this.modelCamera.fieldOfView * 0.75f, Gdx.graphics.getWidth() / (float) Gdx.graphics.getHeight(), - WarsmashGdxMenuTestGame.this.modelCamera.nearClippingPlane, - WarsmashGdxMenuTestGame.this.modelCamera.farClippingPlane); + WarsmashGdxMenuScreen.this.modelCamera.nearClippingPlane, + WarsmashGdxMenuScreen.this.modelCamera.farClippingPlane); } else { this.camera.perspective(70, this.camera.getAspect(), 100, 5000); @@ -665,25 +653,39 @@ public class WarsmashGdxMenuTestGame extends ApplicationAdapter implements Canva @Override public boolean touchDown(final int screenX, final int screenY, final int pointer, final int button) { - // TODO Auto-generated method stub + final float worldScreenY = getHeight() - screenY; + + if (this.menuUI.touchDown(screenX, screenY, worldScreenY, button)) { + return false; + } return false; } @Override public boolean touchUp(final int screenX, final int screenY, final int pointer, final int button) { - // TODO Auto-generated method stub + final float worldScreenY = getHeight() - screenY; + + if (this.menuUI.touchUp(screenX, screenY, worldScreenY, button)) { + return false; + } return false; } @Override public boolean touchDragged(final int screenX, final int screenY, final int pointer) { - // TODO Auto-generated method stub + final float worldScreenY = getHeight() - screenY; + if (this.menuUI.touchDragged(screenX, screenY, worldScreenY, pointer)) { + return false; + } return false; } @Override public boolean mouseMoved(final int screenX, final int screenY) { - // TODO Auto-generated method stub + final float worldScreenY = getHeight() - screenY; + if (this.menuUI.mouseMoved(screenX, screenY, worldScreenY)) { + return false; + } return false; } @@ -706,14 +708,7 @@ public class WarsmashGdxMenuTestGame extends ApplicationAdapter implements Canva this.uiViewport.apply(); this.batch.setProjectionMatrix(this.uiCamera.combined); this.batch.begin(); - this.menuUI.render(this.batch, this.font20, this.glyphLayout); - this.font.setColor(Color.YELLOW); - final String fpsString = "FPS: " + Gdx.graphics.getFramesPerSecond(); - this.glyphLayout.setText(this.font, fpsString); - if (false) { - this.font.draw(this.batch, fpsString, (this.uiViewport.getMinWorldWidth() - this.glyphLayout.width) / 2, - 1100 * this.menuUI.getHeightRatioCorrection()); - } + this.menuUI.render(this.batch, this.glyphLayout); this.batch.end(); Gdx.gl30.glEnable(GL30.GL_SCISSOR_TEST); @@ -804,4 +799,20 @@ public class WarsmashGdxMenuTestGame extends ApplicationAdapter implements Canva } } + + @Override + public void hide() { + if (this.currentMusic != null) { + this.currentMusic.stop(); + } + this.menuUI.hide(); + } + + @Override + public void pause() { + } + + @Override + public void resume() { + } } diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMultiScreenGame.java b/core/src/com/etheller/warsmash/WarsmashGdxMultiScreenGame.java new file mode 100644 index 0000000..b6234c8 --- /dev/null +++ b/core/src/com/etheller/warsmash/WarsmashGdxMultiScreenGame.java @@ -0,0 +1,11 @@ +package com.etheller.warsmash; + +import com.badlogic.gdx.Game; + +public class WarsmashGdxMultiScreenGame extends Game { + + @Override + public void create() { + } + +} diff --git a/core/src/com/etheller/warsmash/WarsmashPreviewApplication.java b/core/src/com/etheller/warsmash/WarsmashPreviewApplication.java index 253cb39..14cd086 100644 --- a/core/src/com/etheller/warsmash/WarsmashPreviewApplication.java +++ b/core/src/com/etheller/warsmash/WarsmashPreviewApplication.java @@ -59,7 +59,7 @@ public class WarsmashPreviewApplication extends ApplicationAdapter implements Ca final String renderer = Gdx.gl.glGetString(GL20.GL_RENDERER); System.err.println("Renderer: " + renderer); - this.codebase = WarsmashGdxMapGame.parseDataSources(this.warsmashIni); + this.codebase = WarsmashGdxMapScreen.parseDataSources(this.warsmashIni); this.viewer = new MdxViewer(this.codebase, this); this.mdxHandler = new MdxHandler(); diff --git a/core/src/com/etheller/warsmash/parsers/fdf/DynamicFontGeneratorHolder.java b/core/src/com/etheller/warsmash/parsers/fdf/DynamicFontGeneratorHolder.java new file mode 100644 index 0000000..feb4a4a --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/fdf/DynamicFontGeneratorHolder.java @@ -0,0 +1,44 @@ +package com.etheller.warsmash.parsers.fdf; + +import java.util.HashMap; +import java.util.Map; + +import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator; +import com.etheller.warsmash.datasources.DataSource; +import com.etheller.warsmash.units.Element; +import com.etheller.warsmash.util.DataSourceFileHandle; + +public class DynamicFontGeneratorHolder { + private final DataSource dataSource; + private final Element skin; + private final Map fontNameToGenerator; + + public DynamicFontGeneratorHolder(final DataSource dataSource, final Element skin) { + this.dataSource = dataSource; + this.skin = skin; + this.fontNameToGenerator = new HashMap<>(); + } + + public FreeTypeFontGenerator getFontGenerator(final String font) { + FreeTypeFontGenerator fontGenerator = this.fontNameToGenerator.get(font); + if (fontGenerator == null) { + final String fontName = this.skin.getField(font); + if (fontName == null) { + throw new IllegalStateException("No such font: " + font); + } + if (!this.dataSource.has(fontName)) { + throw new IllegalStateException("No such font file: " + fontName + " (for \"" + font + "\")"); + } + fontGenerator = new FreeTypeFontGenerator(new DataSourceFileHandle(this.dataSource, fontName)); + this.fontNameToGenerator.put(font, fontGenerator); + } + return fontGenerator; + } + + public void dispose() { + for (final FreeTypeFontGenerator generator : this.fontNameToGenerator.values()) { + generator.dispose(); + } + this.fontNameToGenerator.clear(); + } +} diff --git a/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java index 419d276..404020e 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java @@ -23,6 +23,7 @@ import com.etheller.warsmash.fdfparser.FDFParser; import com.etheller.warsmash.fdfparser.FrameDefinitionVisitor; import com.etheller.warsmash.parsers.fdf.datamodel.AnchorDefinition; import com.etheller.warsmash.parsers.fdf.datamodel.BackdropCornerFlags; +import com.etheller.warsmash.parsers.fdf.datamodel.ControlStyle; import com.etheller.warsmash.parsers.fdf.datamodel.FontDefinition; import com.etheller.warsmash.parsers.fdf.datamodel.FrameClass; import com.etheller.warsmash.parsers.fdf.datamodel.FrameDefinition; @@ -34,6 +35,8 @@ import com.etheller.warsmash.parsers.fdf.datamodel.Vector4Definition; import com.etheller.warsmash.parsers.fdf.frames.AbstractUIFrame; import com.etheller.warsmash.parsers.fdf.frames.BackdropFrame; import com.etheller.warsmash.parsers.fdf.frames.FilterModeTextureFrame; +import com.etheller.warsmash.parsers.fdf.frames.GlueButtonFrame; +import com.etheller.warsmash.parsers.fdf.frames.GlueTextButtonFrame; import com.etheller.warsmash.parsers.fdf.frames.SetPoint; import com.etheller.warsmash.parsers.fdf.frames.SimpleFrame; import com.etheller.warsmash.parsers.fdf.frames.SimpleStatusBarFrame; @@ -52,6 +55,7 @@ import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; import com.hiveworkshop.rms.parsers.mdlx.MdlxLayer.FilterMode; public final class GameUI extends AbstractUIFrame implements UIFrame { + private static final boolean PIN_FAIL_IS_FATAL = false; private final DataSource dataSource; private final Element skin; private final Viewport viewport; @@ -69,10 +73,12 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { private final Element errorStrings; private final GlyphLayout glyphLayout; private final WTS mapStrings; + private final BitmapFont font; + private final BitmapFont font20; + private final DynamicFontGeneratorHolder dynamicFontGeneratorHolder; - public GameUI(final DataSource dataSource, final Element skin, final Viewport viewport, - final FreeTypeFontGenerator fontGenerator, final Scene uiScene, final AbstractMdxModelViewer modelViewer, - final int racialCommandIndex, final WTS mapStrings) { + public GameUI(final DataSource dataSource, final Element skin, final Viewport viewport, final Scene uiScene, + final AbstractMdxModelViewer modelViewer, final int racialCommandIndex, final WTS mapStrings) { super("GameUI", null); this.dataSource = dataSource; this.skin = skin; @@ -88,7 +94,14 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { this.renderBounds.set(0, 0, viewport.getWorldWidth(), viewport.getWorldHeight()); } this.templates = new FrameTemplateEnvironment(); - this.fontGenerator = fontGenerator; + + this.dynamicFontGeneratorHolder = new DynamicFontGeneratorHolder(this.modelViewer.dataSource, skin); + this.fontGenerator = this.dynamicFontGeneratorHolder.getFontGenerator("MasterFont"); + final FreeTypeFontParameter fontParam = new FreeTypeFontParameter(); + fontParam.size = 32; + this.font = this.fontGenerator.generateFont(fontParam); + fontParam.size = 20; + this.font20 = this.fontGenerator.generateFont(fontParam); this.fontParam = new FreeTypeFontParameter(); this.fdfCoordinateResolutionDummyViewport = new FitViewport(0.8f, 0.6f); this.skinData = new DataTable(modelViewer.getWorldEditStrings()); @@ -393,7 +406,8 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { if (this.fontParam.size == 0) { this.fontParam.size = 24; } - frameFont = this.fontGenerator.generateFont(this.fontParam); + frameFont = this.dynamicFontGeneratorHolder.getFontGenerator(font.getFontName()) + .generateFont(this.fontParam); String textString = frameDefinition.getName(); String text = frameDefinition.getString("Text"); if (text != null) { @@ -415,41 +429,116 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { } else if ("GLUETEXTBUTTON".equals(frameDefinition.getFrameType())) { // ButtonText & ControlBackdrop - final SimpleFrame simpleFrame = new SimpleFrame(frameDefinition.getName(), parent); + final GlueTextButtonFrame glueButtonFrame = new GlueTextButtonFrame(frameDefinition.getName(), parent); // TODO: we should not need to put ourselves in this map 2x, but we do // since there are nested inflate calls happening before the general case // mapping - this.nameToFrame.put(frameDefinition.getName(), simpleFrame); - final String buttonTextKey = frameDefinition.getString("ButtonText"); + this.nameToFrame.put(frameDefinition.getName(), glueButtonFrame); final String controlBackdropKey = frameDefinition.getString("ControlBackdrop"); + final String controlPushedBackdropKey = frameDefinition.getString("ControlPushedBackdrop"); + final String controlDisabledBackdropKey = frameDefinition.getString("ControlDisabledBackdrop"); + final String controlMouseOverHighlightKey = frameDefinition.getString("ControlMouseOverHighlight"); + final String buttonTextKey = frameDefinition.getString("ButtonText"); for (final FrameDefinition childDefinition : frameDefinition.getInnerFrames()) { - if (childDefinition.getName().equals(buttonTextKey) - || childDefinition.getName().equals(controlBackdropKey)) { - final UIFrame inflatedChild = inflate(childDefinition, simpleFrame, frameDefinition, + if (childDefinition.getName().equals(controlBackdropKey)) { + final UIFrame inflatedChild = inflate(childDefinition, glueButtonFrame, frameDefinition, inDecorateFileNames || childDefinition.has("DecorateFileNames")); inflatedChild.setSetAllPoints(true); - simpleFrame.add(inflatedChild); + glueButtonFrame.setControlBackdrop(inflatedChild); + } + else if (childDefinition.getName().equals(controlPushedBackdropKey)) { + final UIFrame inflatedChild = inflate(childDefinition, glueButtonFrame, frameDefinition, + inDecorateFileNames || childDefinition.has("DecorateFileNames")); + inflatedChild.setSetAllPoints(true); + glueButtonFrame.setControlPushedBackdrop(inflatedChild); + } + else if (childDefinition.getName().equals(controlDisabledBackdropKey)) { + final UIFrame inflatedChild = inflate(childDefinition, glueButtonFrame, frameDefinition, + inDecorateFileNames || childDefinition.has("DecorateFileNames")); + inflatedChild.setSetAllPoints(true); + glueButtonFrame.setControlDisabledBackdrop(inflatedChild); + } + else if (childDefinition.getName().equals(controlMouseOverHighlightKey)) { + final UIFrame inflatedChild = inflate(childDefinition, glueButtonFrame, frameDefinition, + inDecorateFileNames || childDefinition.has("DecorateFileNames")); + inflatedChild.setSetAllPoints(true); + glueButtonFrame.setControlMouseOverHighlight(inflatedChild); + } + else if (childDefinition.getName().equals(buttonTextKey)) { + final UIFrame inflatedChild = inflate(childDefinition, glueButtonFrame, frameDefinition, + inDecorateFileNames || childDefinition.has("DecorateFileNames")); + inflatedChild.setSetAllPoints(true); + glueButtonFrame.setButtonText(inflatedChild); } } - inflatedFrame = simpleFrame; + final EnumSet controlStyle = ControlStyle + .parseControlStyle(frameDefinition.getString("ControlStyle")); + if (controlStyle.contains(ControlStyle.AUTOTRACK) + && controlStyle.contains(ControlStyle.HIGHLIGHTONMOUSEOVER)) { + glueButtonFrame.setHighlightOnMouseOver(true); + } + inflatedFrame = glueButtonFrame; } else if ("GLUEBUTTON".equals(frameDefinition.getFrameType())) { // ButtonText & ControlBackdrop - final SimpleFrame simpleFrame = new SimpleFrame(frameDefinition.getName(), parent); + final GlueButtonFrame glueButtonFrame = new GlueButtonFrame(frameDefinition.getName(), parent); // TODO: we should not need to put ourselves in this map 2x, but we do // since there are nested inflate calls happening before the general case // mapping - this.nameToFrame.put(frameDefinition.getName(), simpleFrame); + this.nameToFrame.put(frameDefinition.getName(), glueButtonFrame); final String controlBackdropKey = frameDefinition.getString("ControlBackdrop"); + final String controlPushedBackdropKey = frameDefinition.getString("ControlPushedBackdrop"); + final String controlDisabledBackdropKey = frameDefinition.getString("ControlDisabledBackdrop"); + final String controlMouseOverHighlightKey = frameDefinition.getString("ControlMouseOverHighlight"); for (final FrameDefinition childDefinition : frameDefinition.getInnerFrames()) { if (childDefinition.getName().equals(controlBackdropKey)) { - final UIFrame inflatedChild = inflate(childDefinition, simpleFrame, frameDefinition, + final UIFrame inflatedChild = inflate(childDefinition, glueButtonFrame, frameDefinition, inDecorateFileNames || childDefinition.has("DecorateFileNames")); inflatedChild.setSetAllPoints(true); - simpleFrame.add(inflatedChild); + glueButtonFrame.setControlBackdrop(inflatedChild); + } + else if (childDefinition.getName().equals(controlPushedBackdropKey)) { + final UIFrame inflatedChild = inflate(childDefinition, glueButtonFrame, frameDefinition, + inDecorateFileNames || childDefinition.has("DecorateFileNames")); + inflatedChild.setSetAllPoints(true); + glueButtonFrame.setControlPushedBackdrop(inflatedChild); + } + else if (childDefinition.getName().equals(controlDisabledBackdropKey)) { + final UIFrame inflatedChild = inflate(childDefinition, glueButtonFrame, frameDefinition, + inDecorateFileNames || childDefinition.has("DecorateFileNames")); + inflatedChild.setSetAllPoints(true); + glueButtonFrame.setControlDisabledBackdrop(inflatedChild); + } + else if (childDefinition.getName().equals(controlMouseOverHighlightKey)) { + final UIFrame inflatedChild = inflate(childDefinition, glueButtonFrame, frameDefinition, + inDecorateFileNames || childDefinition.has("DecorateFileNames")); + inflatedChild.setSetAllPoints(true); + glueButtonFrame.setControlMouseOverHighlight(inflatedChild); } } - inflatedFrame = simpleFrame; + final EnumSet controlStyle = ControlStyle + .parseControlStyle(frameDefinition.getString("ControlStyle")); + if (controlStyle.contains(ControlStyle.AUTOTRACK) + && controlStyle.contains(ControlStyle.HIGHLIGHTONMOUSEOVER)) { + glueButtonFrame.setHighlightOnMouseOver(true); + } + inflatedFrame = glueButtonFrame; + } + else if ("HIGHLIGHT".equals(frameDefinition.getFrameType())) { + final String highlightType = frameDefinition.getString("HighlightType"); + if (!"FILETEXTURE".equals(highlightType)) { + throw new IllegalStateException( + "Our engine does not know how to handle a non-FILETEXTURE highlight"); + } + final String highlightAlphaFile = frameDefinition.getString("HighlightAlphaFile"); + final String highlightAlphaMode = frameDefinition.getString("HighlightAlphaMode"); + final FilterModeTextureFrame textureFrame = new FilterModeTextureFrame(frameDefinition.getName(), + parent, false, null); + textureFrame.setTexture(highlightAlphaFile, this); + if ("ADD".equals(highlightAlphaMode)) { + textureFrame.setFilterMode(FilterMode.ADDALPHA); + } + return textureFrame; } else if ("BACKDROP".equals(frameDefinition.getFrameType())) { final boolean tileBackground = frameDefinition.has("BackdropTileBackground"); @@ -480,10 +569,10 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { String edgeFileString = frameDefinition.getString("BackdropEdgeFile"); System.out.println(frameDefinition.getName() + " wants edge file: " + edgeFileString); if (decorateFileNames && (edgeFileString != null)) { - edgeFileString = getSkinField(edgeFileString); + edgeFileString = trySkinField(edgeFileString); } if (decorateFileNames && (edgeFileString != null)) { - backgroundString = getSkinField(backgroundString); + backgroundString = trySkinField(backgroundString); } final Texture background = backgroundString == null ? null : loadTexture(backgroundString); final Texture edgeFile = edgeFileString == null ? null : loadTexture(edgeFileString); @@ -534,7 +623,8 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { if (this.fontParam.size == 0) { this.fontParam.size = 24; } - frameFont = this.fontGenerator.generateFont(this.fontParam); + frameFont = this.dynamicFontGeneratorHolder.getFontGenerator(font.getFontName()) + .generateFont(this.fontParam); String textString = frameDefinition.getName(); String text = frameDefinition.getString("Text"); if (text != null) { @@ -591,12 +681,18 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { final UIFrame otherFrameByName = getFrameByName(setPointDefinition.getOther(), 0 /* TODO: createContext */); if (otherFrameByName == null) { - throw new IllegalStateException("Failing to pin " + frameDefinition.getName() + " to " + System.err.println("Failing to pin " + frameDefinition.getName() + " to " + setPointDefinition.getOther() + " because it was null!"); + if (PIN_FAIL_IS_FATAL) { + throw new IllegalStateException("Failing to pin " + frameDefinition.getName() + " to " + + setPointDefinition.getOther() + " because it was null!"); + } + } + else { + inflatedFrame.addSetPoint(new SetPoint(setPointDefinition.getMyPoint(), otherFrameByName, + setPointDefinition.getOtherPoint(), convertX(this.viewport, setPointDefinition.getX()), + convertY(this.viewport, setPointDefinition.getY()))); } - inflatedFrame.addSetPoint(new SetPoint(setPointDefinition.getMyPoint(), otherFrameByName, - setPointDefinition.getOtherPoint(), convertX(this.viewport, setPointDefinition.getX()), - convertY(this.viewport, setPointDefinition.getY()))); } this.nameToFrame.put(frameDefinition.getName(), inflatedFrame); } @@ -659,9 +755,13 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { } public Texture loadTexture(String path) { - if (!path.contains(".")) { + final int lastDotIndex = path.lastIndexOf('.'); + if (lastDotIndex == -1) { path = path + ".blp"; } + else { + path = path.substring(0, lastDotIndex) + ".blp"; + } Texture texture = this.pathToTexture.get(path); if (texture == null) { try { @@ -714,4 +814,20 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { public void setText(final StringFrame stringFrame, final String text) { stringFrame.setText(text, this, this.viewport); } + + public BitmapFont getFont() { + return this.font; + } + + public BitmapFont getFont20() { + return this.font20; + } + + public FreeTypeFontGenerator getFontGenerator() { + return this.fontGenerator; + } + + public void dispose() { + this.dynamicFontGeneratorHolder.dispose(); + } } diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java index 94daafd..ddd3c15 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java @@ -314,9 +314,9 @@ public abstract class AbstractRenderableFrame implements UIFrame { } @Override - public final void render(final SpriteBatch batch, final BitmapFont baseFont, final GlyphLayout glyphLayout) { + public final void render(final SpriteBatch batch, final BitmapFont font20, final GlyphLayout glyphLayout) { if (this.visible) { - internalRender(batch, baseFont, glyphLayout); + internalRender(batch, font20, glyphLayout); } } diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractUIFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractUIFrame.java index 425a267..ff8756f 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractUIFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractUIFrame.java @@ -46,10 +46,12 @@ public abstract class AbstractUIFrame extends AbstractRenderableFrame implements @Override public UIFrame touchDown(final float screenX, final float screenY, final int button) { - for (final UIFrame childFrame : this.childFrames) { - final UIFrame clickedChild = childFrame.touchDown(screenX, screenY, button); - if (clickedChild != null) { - return clickedChild; + if (isVisible()) { + for (final UIFrame childFrame : this.childFrames) { + final UIFrame clickedChild = childFrame.touchDown(screenX, screenY, button); + if (clickedChild != null) { + return clickedChild; + } } } return super.touchDown(screenX, screenY, button); @@ -57,10 +59,12 @@ public abstract class AbstractUIFrame extends AbstractRenderableFrame implements @Override public UIFrame touchUp(final float screenX, final float screenY, final int button) { - for (final UIFrame childFrame : this.childFrames) { - final UIFrame clickedChild = childFrame.touchUp(screenX, screenY, button); - if (clickedChild != null) { - return clickedChild; + if (isVisible()) { + for (final UIFrame childFrame : this.childFrames) { + final UIFrame clickedChild = childFrame.touchUp(screenX, screenY, button); + if (clickedChild != null) { + return clickedChild; + } } } return super.touchUp(screenX, screenY, button); @@ -68,10 +72,12 @@ public abstract class AbstractUIFrame extends AbstractRenderableFrame implements @Override public UIFrame getFrameChildUnderMouse(final float screenX, final float screenY) { - for (final UIFrame childFrame : this.childFrames) { - final UIFrame clickedChild = childFrame.getFrameChildUnderMouse(screenX, screenY); - if (clickedChild != null) { - return clickedChild; + if (isVisible()) { + for (final UIFrame childFrame : this.childFrames) { + final UIFrame clickedChild = childFrame.getFrameChildUnderMouse(screenX, screenY); + if (clickedChild != null) { + return clickedChild; + } } } return super.getFrameChildUnderMouse(screenX, screenY); diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/GlueButtonFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/GlueButtonFrame.java new file mode 100644 index 0000000..baed87c --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/GlueButtonFrame.java @@ -0,0 +1,151 @@ +package com.etheller.warsmash.parsers.fdf.frames; + +import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.g2d.GlyphLayout; +import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.badlogic.gdx.utils.viewport.Viewport; +import com.etheller.warsmash.parsers.fdf.GameUI; +import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.ClickableFrame; + +public class GlueButtonFrame extends AbstractRenderableFrame implements ClickableFrame { + private UIFrame controlBackdrop; + private UIFrame controlPushedBackdrop; + private UIFrame controlDisabledBackdrop; + private UIFrame controlMouseOverHighlight; + + private boolean enabled = true; + private boolean highlightOnMouseOver; + private boolean mouseOver = false; + + private UIFrame activeChild; + + private Runnable onClick; + + public GlueButtonFrame(final String name, final UIFrame parent) { + super(name, parent); + } + + public void setControlBackdrop(final UIFrame controlBackdrop) { + this.controlBackdrop = controlBackdrop; + } + + public void setControlPushedBackdrop(final UIFrame controlPushedBackdrop) { + this.controlPushedBackdrop = controlPushedBackdrop; + } + + public void setControlDisabledBackdrop(final UIFrame controlDisabledBackdrop) { + this.controlDisabledBackdrop = controlDisabledBackdrop; + } + + public void setControlMouseOverHighlight(final UIFrame controlMouseOverHighlight) { + this.controlMouseOverHighlight = controlMouseOverHighlight; + } + + public void setEnabled(final boolean enabled) { + this.enabled = enabled; + if (this.enabled) { + this.activeChild = this.controlBackdrop; + } + else { + this.activeChild = this.controlDisabledBackdrop; + } + } + + public void setHighlightOnMouseOver(final boolean highlightOnMouseOver) { + this.highlightOnMouseOver = highlightOnMouseOver; + } + + public void setOnClick(final Runnable onClick) { + this.onClick = onClick; + } + + @Override + protected void innerPositionBounds(final GameUI gameUI, final Viewport viewport) { + if (this.controlBackdrop != null) { + this.controlBackdrop.positionBounds(gameUI, viewport); + } + if (this.controlPushedBackdrop != null) { + this.controlPushedBackdrop.positionBounds(gameUI, viewport); + } + if (this.controlDisabledBackdrop != null) { + this.controlDisabledBackdrop.positionBounds(gameUI, viewport); + } + if (this.controlMouseOverHighlight != null) { + this.controlMouseOverHighlight.positionBounds(gameUI, viewport); + } + if (this.enabled) { + this.activeChild = this.controlBackdrop; + } + else { + this.activeChild = this.controlDisabledBackdrop; + } + } + + @Override + protected void internalRender(final SpriteBatch batch, final BitmapFont baseFont, final GlyphLayout glyphLayout) { + if (this.activeChild != null) { + this.activeChild.render(batch, baseFont, glyphLayout); + } + if (this.mouseOver) { + this.controlMouseOverHighlight.render(batch, baseFont, glyphLayout); + } + } + + @Override + public void mouseDown(final GameUI gameUI, final Viewport uiViewport) { + if (this.enabled) { + this.activeChild = this.controlPushedBackdrop; + } + } + + @Override + public void mouseUp(final GameUI gameUI, final Viewport uiViewport) { + if (this.enabled) { + this.activeChild = this.controlBackdrop; + } + } + + @Override + public void mouseEnter(final GameUI gameUI, final Viewport uiViewport) { + if (this.highlightOnMouseOver) { + this.mouseOver = true; + } + } + + @Override + public void mouseExit(final GameUI gameUI, final Viewport uiViewport) { + this.mouseOver = false; + } + + @Override + public void onClick(final int button) { + if (this.onClick != null) { + this.onClick.run(); + } + } + + @Override + public UIFrame touchUp(final float screenX, final float screenY, final int button) { + if (isVisible() && this.enabled && this.renderBounds.contains(screenX, screenY)) { + return this; + } + return super.touchUp(screenX, screenY, button); + } + + @Override + public UIFrame touchDown(final float screenX, final float screenY, final int button) { + if (isVisible() && this.enabled && this.renderBounds.contains(screenX, screenY)) { + return this; + } + return super.touchDown(screenX, screenY, button); + } + + @Override + public UIFrame getFrameChildUnderMouse(final float screenX, final float screenY) { + if (isVisible() && this.enabled && this.renderBounds.contains(screenX, screenY)) { + return this; + } + return super.getFrameChildUnderMouse(screenX, screenY); + } + +} diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/GlueTextButtonFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/GlueTextButtonFrame.java new file mode 100644 index 0000000..a64db3c --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/GlueTextButtonFrame.java @@ -0,0 +1,35 @@ +package com.etheller.warsmash.parsers.fdf.frames; + +import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.g2d.GlyphLayout; +import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.badlogic.gdx.utils.viewport.Viewport; +import com.etheller.warsmash.parsers.fdf.GameUI; + +public class GlueTextButtonFrame extends GlueButtonFrame { + private UIFrame buttonText; + + public GlueTextButtonFrame(final String name, final UIFrame parent) { + super(name, parent); + } + + public void setButtonText(final UIFrame buttonText) { + this.buttonText = buttonText; + } + + @Override + protected void innerPositionBounds(final GameUI gameUI, final Viewport viewport) { + super.innerPositionBounds(gameUI, viewport); + if (this.buttonText != null) { + this.buttonText.positionBounds(gameUI, viewport); + } + } + + @Override + protected void internalRender(final SpriteBatch batch, final BitmapFont baseFont, final GlyphLayout glyphLayout) { + super.internalRender(batch, baseFont, glyphLayout); + if (this.buttonText != null) { + this.buttonText.render(batch, baseFont, glyphLayout); + } + } +} diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/TextureFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/TextureFrame.java index c8f1412..96ee8ca 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/TextureFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/TextureFrame.java @@ -1,5 +1,6 @@ package com.etheller.warsmash.parsers.fdf.frames; +import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.GlyphLayout; @@ -13,6 +14,7 @@ public class TextureFrame extends AbstractRenderableFrame { private TextureRegion texture; private final boolean decorateFileNames; private final Vector4Definition texCoord; + private Color color; public TextureFrame(final String name, final UIFrame parent, final boolean decorateFileNames, final Vector4Definition texCoord) { @@ -26,14 +28,24 @@ public class TextureFrame extends AbstractRenderableFrame { if (this.texture == null) { return; } + if (this.color != null) { + batch.setColor(this.color); + } batch.draw(this.texture, this.renderBounds.x, this.renderBounds.y, this.renderBounds.width, this.renderBounds.height); + if (this.color != null) { + batch.setColor(1f, 1f, 1f, 1f); + } } @Override protected void innerPositionBounds(final GameUI gameUI, final Viewport viewport) { } + public void setColor(final Color color) { + this.color = color; + } + public void setTexture(String file, final GameUI gameUI) { if (this.decorateFileNames) { file = gameUI.getSkinField(file); diff --git a/core/src/com/etheller/warsmash/parsers/jass/Jass2.java b/core/src/com/etheller/warsmash/parsers/jass/Jass2.java index c537a98..fd8fc9b 100644 --- a/core/src/com/etheller/warsmash/parsers/jass/Jass2.java +++ b/core/src/com/etheller/warsmash/parsers/jass/Jass2.java @@ -10,7 +10,6 @@ import org.antlr.v4.runtime.RecognitionException; import org.antlr.v4.runtime.Recognizer; import com.badlogic.gdx.graphics.Color; -import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator; import com.badlogic.gdx.utils.viewport.Viewport; import com.etheller.interpreter.JassLexer; import com.etheller.interpreter.JassParser; @@ -52,13 +51,12 @@ import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; public class Jass2 { public static final boolean REPORT_SYNTAX_ERRORS = true; - public static JUIEnvironment loadJUI(final DataSource dataSource, final Viewport uiViewport, - final FreeTypeFontGenerator fontGenerator, final Scene uiScene, final War3MapViewer war3MapViewer, - final RootFrameListener rootFrameListener, final String... files) { + public static JUIEnvironment loadJUI(final DataSource dataSource, final Viewport uiViewport, final Scene uiScene, + final War3MapViewer war3MapViewer, final RootFrameListener rootFrameListener, final String... files) { final JassProgramVisitor jassProgramVisitor = new JassProgramVisitor(); - final JUIEnvironment environment = new JUIEnvironment(jassProgramVisitor, dataSource, uiViewport, fontGenerator, - uiScene, war3MapViewer, rootFrameListener); + final JUIEnvironment environment = new JUIEnvironment(jassProgramVisitor, dataSource, uiViewport, uiScene, + war3MapViewer, rootFrameListener); for (final String jassFile : files) { try { JassLexer lexer; @@ -103,8 +101,8 @@ public class Jass2 { private Element skin; public JUIEnvironment(final JassProgramVisitor jassProgramVisitor, final DataSource dataSource, - final Viewport uiViewport, final FreeTypeFontGenerator fontGenerator, final Scene uiScene, - final War3MapViewer war3MapViewer, final RootFrameListener rootFrameListener) { + final Viewport uiViewport, final Scene uiScene, final War3MapViewer war3MapViewer, + final RootFrameListener rootFrameListener) { final GlobalScope globals = jassProgramVisitor.getGlobals(); final HandleJassType frameHandleType = globals.registerHandleType("framehandle"); final HandleJassType framePointType = globals.registerHandleType("framepointtype"); @@ -137,8 +135,8 @@ public class Jass2 { final TriggerExecutionScope triggerScope) { final String skinArg = arguments.get(0).visit(StringJassValueVisitor.getInstance()); final Element skin = GameUI.loadSkin(dataSource, skinArg); - final GameUI gameUI = new GameUI(dataSource, skin, uiViewport, fontGenerator, uiScene, - war3MapViewer, 0, war3MapViewer.getAllObjectData().getWts()); + final GameUI gameUI = new GameUI(dataSource, skin, uiViewport, uiScene, war3MapViewer, 0, + war3MapViewer.getAllObjectData().getWts()); JUIEnvironment.this.gameUI = gameUI; JUIEnvironment.this.skin = skin; rootFrameListener.onCreate(gameUI); diff --git a/core/src/com/etheller/warsmash/util/ImageUtils.java b/core/src/com/etheller/warsmash/util/ImageUtils.java index 6991a8a..c9c6844 100644 --- a/core/src/com/etheller/warsmash/util/ImageUtils.java +++ b/core/src/com/etheller/warsmash/util/ImageUtils.java @@ -98,7 +98,7 @@ public final class ImageUtils { } public boolean isNeedsSRGBFix() { - return false; + return this.needsSRGBFix; } } @@ -217,9 +217,6 @@ public final class ImageUtils { * @return Resulting sRGB image. */ public static BufferedImage forceBufferedImagesRGB(final BufferedImage in) { - if (true) { - return in; - } // Resolve input ColorSpace. final ColorSpace inCS = in.getColorModel().getColorSpace(); final ColorSpace sRGBCS = ColorSpace.getInstance(ColorSpace.CS_sRGB); diff --git a/core/src/com/etheller/warsmash/util/WarsmashConstants.java b/core/src/com/etheller/warsmash/util/WarsmashConstants.java index 5fc0f6b..694d5b2 100644 --- a/core/src/com/etheller/warsmash/util/WarsmashConstants.java +++ b/core/src/com/etheller/warsmash/util/WarsmashConstants.java @@ -1,7 +1,12 @@ package com.etheller.warsmash.util; public class WarsmashConstants { - public static final int MAX_PLAYERS = 28; + public static final int MAX_PLAYERS = 16; + /* + * With version, we use 0 for RoC, 1 for TFT emulation, and probably 2+ or + * whatever for custom mods and other stuff + */ + public static int GAME_VERSION = 0; public static final int REPLACEABLE_TEXTURE_LIMIT = 64; public static final float SIMULATION_STEP_TIME = 1 / 20f; public static final int PORT_NUMBER = 6115; @@ -14,4 +19,8 @@ public class WarsmashConstants { // High - 1.5f public static final float MODEL_DETAIL_PARTICLE_FACTOR = 1.5f; public static final float MODEL_DETAIL_PARTICLE_FACTOR_INVERSE = 1f / MODEL_DETAIL_PARTICLE_FACTOR; + + // I know this default string is from somewhere, maybe a language file? Didn't + // find it yet so I used this + public static final String DEFAULT_STRING = "Default string"; } diff --git a/core/src/com/etheller/warsmash/viewer5/AudioBufferSource.java b/core/src/com/etheller/warsmash/viewer5/AudioBufferSource.java index 5e55119..245b844 100644 --- a/core/src/com/etheller/warsmash/viewer5/AudioBufferSource.java +++ b/core/src/com/etheller/warsmash/viewer5/AudioBufferSource.java @@ -11,11 +11,12 @@ public class AudioBufferSource { this.panner = panner; } - public void start(final int value, final float volume, final float pitch) { + public void start(final int value, final float volume, final float pitch, final boolean looping) { if (this.buffer != null) { if (!this.panner.listener.is3DSupported() || this.panner.isWithinListenerDistance()) { Extensions.audio.play(this.buffer, volume, pitch, this.panner.x, this.panner.y, this.panner.z, - this.panner.listener.is3DSupported(), this.panner.maxDistance, this.panner.refDistance); + this.panner.listener.is3DSupported(), this.panner.maxDistance, this.panner.refDistance, + looping); } } } diff --git a/core/src/com/etheller/warsmash/viewer5/RawOpenGLTextureResource.java b/core/src/com/etheller/warsmash/viewer5/RawOpenGLTextureResource.java index 7c0fb8c..0d09915 100644 --- a/core/src/com/etheller/warsmash/viewer5/RawOpenGLTextureResource.java +++ b/core/src/com/etheller/warsmash/viewer5/RawOpenGLTextureResource.java @@ -91,8 +91,7 @@ public abstract class RawOpenGLTextureResource extends Texture { final GL20 gl = this.viewer.gl; } - public void update(final BufferedImage image, boolean sRGBFix) { - sRGBFix = false; + public void update(final BufferedImage image, final boolean sRGBFix) { final GL20 gl = this.viewer.gl; final int imageWidth = image.getWidth(); diff --git a/core/src/com/etheller/warsmash/viewer5/SkeletalNode.java b/core/src/com/etheller/warsmash/viewer5/SkeletalNode.java index 0c6359b..28ab23e 100644 --- a/core/src/com/etheller/warsmash/viewer5/SkeletalNode.java +++ b/core/src/com/etheller/warsmash/viewer5/SkeletalNode.java @@ -12,6 +12,7 @@ public abstract class SkeletalNode extends GenericNode { protected static final Vector3 billboardAxisHeap = new Vector3(); protected static final Quaternion rotationHeap = new Quaternion(); protected static final Quaternion rotationHeap2 = new Quaternion(); + protected static final Quaternion rotationHeap3 = new Quaternion(); protected static final Vector3 scalingHeap = new Vector3(); protected static final Vector3 blendLocationHeap = new Vector3(); protected static final Vector3 blendHeap = new Vector3(); @@ -133,10 +134,33 @@ public abstract class SkeletalNode extends GenericNode { computedRotation = rotationHeap; cameraRayHeap.set(camera.billboardedVectors[6]); computedRotation.set(this.parent.inverseWorldRotation); + + // Compute local rotation + if (!Float.isNaN(blendTimeRatio) && (blendTimeRatio > 0)) { + rotationHeap2.set(this.localRotation).slerp(this.localBlendRotation, blendTimeRatio); + } + else { + rotationHeap2.set(this.localRotation); + } + // Inverse that local rotation + rotationHeap2.x = -rotationHeap2.x; + rotationHeap2.y = -rotationHeap2.y; + rotationHeap2.z = -rotationHeap2.z; + rotationHeap3.set(computedRotation); + RenderMathUtils.mul(computedRotation, rotationHeap2, rotationHeap3); + computedRotation.transform(cameraRayHeap); + billboardAxisHeap.set(0, 1, 0); final float angle = (float) Math.atan2(cameraRayHeap.z, -cameraRayHeap.x); computedRotation.setFromAxisRad(billboardAxisHeap, angle); + + // Inverse that local rotation back to what it was + rotationHeap2.x = -rotationHeap2.x; + rotationHeap2.y = -rotationHeap2.y; + rotationHeap2.z = -rotationHeap2.z; + rotationHeap3.set(computedRotation); + RenderMathUtils.mul(computedRotation, rotationHeap2, rotationHeap3); } else if (this.billboardedZ) { final Camera camera = scene.camera; diff --git a/core/src/com/etheller/warsmash/viewer5/gl/AudioExtension.java b/core/src/com/etheller/warsmash/viewer5/gl/AudioExtension.java index 619633c..8af22f0 100644 --- a/core/src/com/etheller/warsmash/viewer5/gl/AudioExtension.java +++ b/core/src/com/etheller/warsmash/viewer5/gl/AudioExtension.java @@ -9,5 +9,5 @@ public interface AudioExtension { float getDuration(Sound sound); void play(Sound buffer, final float volume, final float pitch, final float x, final float y, final float z, - final boolean is3DSound, float maxDistance, float refDistance); + final boolean is3DSound, float maxDistance, float refDistance, boolean looping); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/AttachmentInstance.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/AttachmentInstance.java index 5b31ca5..c898223 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/AttachmentInstance.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/AttachmentInstance.java @@ -17,6 +17,7 @@ public class AttachmentInstance implements UpdatableObject { internalInstance.dontInheritScaling = false; internalInstance.hide(); internalInstance.setParent(instance.nodes[attachment.objectId]); + internalInstance.setAnimationSpeed(instance.getAnimationSpeed()); this.instance = instance; this.attachment = attachment; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectSnd.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectSnd.java index 75248a5..4afebca 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectSnd.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EventObjectSnd.java @@ -48,7 +48,8 @@ public class EventObjectSnd extends EmittedObject 196; + public boolean update(final float deltaTime) { + this.screenCoordsZHeight += 60.0f * deltaTime; + this.lifetime += deltaTime; + return this.lifetime > 3.5f; } public Vector3 getPosition() { return this.position; } + public float getRemainingLife() { + return 3.5f - this.lifetime; + } + public Color getColor() { return this.color; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitSound.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitSound.java index e87b503..0df2734 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitSound.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/UnitSound.java @@ -17,7 +17,7 @@ import com.etheller.warsmash.viewer5.gl.Extensions; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderUnit; public final class UnitSound { - private static final UnitSound SILENT = new UnitSound(0, 0, 0, 0, 0, 0); + private static final UnitSound SILENT = new UnitSound(0, 0, 0, 0, 0, 0, false); private final List sounds = new ArrayList<>(); private final float volume; @@ -26,6 +26,7 @@ public final class UnitSound { private final float minDistance; private final float maxDistance; private final float distanceCutoff; + private final boolean looping; private Sound lastPlayedSound; @@ -49,7 +50,15 @@ public final class UnitSound { final float minDistance = row.getFieldFloatValue("MinDistance"); final float maxDistance = row.getFieldFloatValue("MaxDistance"); final float distanceCutoff = row.getFieldFloatValue("DistanceCutoff"); - final UnitSound sound = new UnitSound(volume, pitch, pitchVariance, minDistance, maxDistance, distanceCutoff); + final String[] flags = row.getField("Flags").split(","); + boolean looping = false; + for (final String flag : flags) { + if ("LOOPING".equals(flag)) { + looping = true; + } + } + final UnitSound sound = new UnitSound(volume, pitch, pitchVariance, minDistance, maxDistance, distanceCutoff, + looping); for (final String fileName : fileNames.split(",")) { String filePath = directoryBase + fileName; final int lastDotIndex = filePath.lastIndexOf('.'); @@ -64,13 +73,14 @@ public final class UnitSound { } public UnitSound(final float volume, final float pitch, final float pitchVariation, final float minDistance, - final float maxDistance, final float distanceCutoff) { + final float maxDistance, final float distanceCutoff, final boolean looping) { this.volume = volume; this.pitch = pitch; this.pitchVariance = pitchVariation; this.minDistance = minDistance; this.maxDistance = maxDistance; this.distanceCutoff = distanceCutoff; + this.looping = looping; } public boolean playUnitResponse(final AudioContext audioContext, final RenderUnit unit) { @@ -116,7 +126,7 @@ public final class UnitSound { // Make a sound. source.start(0, this.volume, - (this.pitch + ((float) Math.random() * this.pitchVariance * 2)) - this.pitchVariance); + (this.pitch + ((float) Math.random() * this.pitchVariance * 2)) - this.pitchVariance, this.looping); this.lastPlayedSound = source.buffer; return true; } @@ -124,4 +134,10 @@ public final class UnitSound { public int getSoundCount() { return this.sounds.size(); } + + public void stop() { + for (final Sound sound : this.sounds) { + sound.stop(); + } + } } \ No newline at end of file diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index 3fcd3ab..38324f2 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -67,9 +67,11 @@ import com.etheller.warsmash.viewer5.Texture; import com.etheller.warsmash.viewer5.WorldScene; import com.etheller.warsmash.viewer5.gl.WebGL; import com.etheller.warsmash.viewer5.handlers.AbstractMdxModelViewer; +import com.etheller.warsmash.viewer5.handlers.mdx.Attachment; import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxHandler; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; +import com.etheller.warsmash.viewer5.handlers.mdx.MdxNode; import com.etheller.warsmash.viewer5.handlers.mdx.SequenceLoopMode; import com.etheller.warsmash.viewer5.handlers.tga.TgaFile; import com.etheller.warsmash.viewer5.handlers.w3x.SplatModel.SplatMover; @@ -87,6 +89,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderUnit; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderUnitTypeData; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderWidget; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.AbilityDataUI; +import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.AbilityUI; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CDestructable; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; @@ -107,6 +110,7 @@ import mpq.MPQArchive; import mpq.MPQException; public class War3MapViewer extends AbstractMdxModelViewer { + private static final War3ID ABILITY_HERO_RAWCODE = War3ID.fromString("AHer"); private static final Color PLACEHOLDER_LUMBER_COLOR = new Color(0.0f, 200f / 255f, 80f / 255f, 1.0f); private static final Color PLACEHOLDER_GOLD_COLOR = new Color(1.0f, 220f / 255f, 0f, 1.0f); private static final War3ID UNIT_FILE = War3ID.fromString("umdl"); @@ -636,6 +640,42 @@ public class War3MapViewer extends AbstractMdxModelViewer { } } + @Override + public void spawnGainLevelEffect(final CUnit source) { + final AbilityUI heroUI = War3MapViewer.this.abilityDataUI.getUI(ABILITY_HERO_RAWCODE); + final RenderUnit renderUnit = War3MapViewer.this.unitToRenderPeer.get(source); + final String heroLevelUpArt = heroUI.getCasterArt(0); + final MdxModel heroLevelUpModel = loadModel(heroLevelUpArt); + if (heroLevelUpModel != null) { + final MdxComplexInstance modelInstance = (MdxComplexInstance) heroLevelUpModel + .addInstance(); + modelInstance.setTeamColor(source.getPlayerIndex()); + + final MdxModel model = (MdxModel) renderUnit.instance.model; + int index = -1; + for (int i = 0; i < model.attachments.size(); i++) { + final Attachment attachment = model.attachments.get(i); + if (attachment.getName().startsWith("origin ref")) { + index = i; + break; + } + } + if (index != -1) { + final MdxNode attachment = renderUnit.instance.getAttachment(index); + modelInstance.setParent(attachment); + } + else { + modelInstance.setLocation(renderUnit.location); + } + + modelInstance.setScene(War3MapViewer.this.worldScene); + SequenceUtils.randomBirthSequence(modelInstance); + War3MapViewer.this.projectiles + .add(new RenderAttackInstant(modelInstance, War3MapViewer.this, + (float) Math.toRadians(renderUnit.getSimulationUnit().getFacing()))); + } + } + @Override public void spawnEffectOnUnit(final CUnit unit, final String effectPath) { final RenderUnit renderUnit = War3MapViewer.this.unitToRenderPeer.get(unit); @@ -1253,7 +1293,7 @@ public class War3MapViewer extends AbstractMdxModelViewer { final Iterator textTagIterator = this.textTags.iterator(); while (textTagIterator.hasNext()) { - if (textTagIterator.next().update()) { + if (textTagIterator.next().update(Gdx.graphics.getDeltaTime())) { textTagIterator.remove(); } } @@ -1619,6 +1659,10 @@ public class War3MapViewer extends AbstractMdxModelViewer { return mdxPath; } + public MdxModel loadModel(final String path) { + return (MdxModel) load(mdx(path), PathSolver.DEFAULT, null); + } + @Override public SceneLightManager createLightManager(final boolean simple) { if (simple) { @@ -1637,7 +1681,7 @@ public class War3MapViewer extends AbstractMdxModelViewer { public void setGameUI(final GameUI gameUI) { this.gameUI = gameUI; this.abilityDataUI = new AbilityDataUI(this.allObjectData.getAbilities(), this.allObjectData.getUnits(), - this.allObjectData.getUpgrades(), gameUI); + this.allObjectData.getUpgrades(), gameUI, this); } public GameUI getGameUI() { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityDataUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityDataUI.java index f071393..37a893a 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityDataUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityDataUI.java @@ -1,6 +1,7 @@ package com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -11,6 +12,7 @@ import com.etheller.warsmash.units.Element; import com.etheller.warsmash.units.manager.MutableObjectData; import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; public class AbilityDataUI { // Standard ability icon fields @@ -30,6 +32,13 @@ public class AbilityDataUI { private static final War3ID ABILITY_RESEARCH_TIP = War3ID.fromString("aret"); private static final War3ID ABILITY_RESEARCH_UBER_TIP = War3ID.fromString("arut"); + private static final War3ID CASTER_ART = War3ID.fromString("acat"); + private static final War3ID TARGET_ART = War3ID.fromString("atat"); + private static final War3ID SPECIAL_ART = War3ID.fromString("asat"); + private static final War3ID EFFECT_ART = War3ID.fromString("aeat"); + private static final War3ID AREA_EFFECT_ART = War3ID.fromString("aaea"); + private static final War3ID MISSILE_ART = War3ID.fromString("amat"); + private static final War3ID UNIT_ICON_NORMAL_X = War3ID.fromString("ubpx"); private static final War3ID UNIT_ICON_NORMAL_Y = War3ID.fromString("ubpy"); private static final War3ID UNIT_ICON_NORMAL = War3ID.fromString("uico"); @@ -43,7 +52,7 @@ public class AbilityDataUI { private static final War3ID UPGRADE_TIP = War3ID.fromString("gtp1"); private static final War3ID UPGRADE_UBER_TIP = War3ID.fromString("gub1"); - private final Map rawcodeToUI = new HashMap<>(); + private final Map rawcodeToUI = new HashMap<>(); private final Map rawcodeToUnitUI = new HashMap<>(); private final Map> rawcodeToUpgradeUI = new HashMap<>(); private final IconUI moveUI; @@ -62,9 +71,10 @@ public class AbilityDataUI { private final IconUI cancelBuildUI; private final IconUI cancelTrainUI; private final IconUI rallyUI; + private final IconUI selectSkillUI; public AbilityDataUI(final MutableObjectData abilityData, final MutableObjectData unitData, - final MutableObjectData upgradeData, final GameUI gameUI) { + final MutableObjectData upgradeData, final GameUI gameUI, final War3MapViewer viewer) { final String disabledPrefix = gameUI.getSkinField("CommandButtonDisabledArtPath"); for (final War3ID alias : abilityData.keySet()) { final MutableGameObject abilityTypeData = abilityData.get(alias); @@ -89,13 +99,23 @@ public class AbilityDataUI { final Texture iconNormalDisabled = gameUI.loadTexture(disable(iconNormalPath, disabledPrefix)); final Texture iconTurnOff = gameUI.loadTexture(iconTurnOffPath); final Texture iconTurnOffDisabled = gameUI.loadTexture(disable(iconTurnOffPath, disabledPrefix)); + + final List casterArt = Arrays.asList(abilityTypeData.getFieldAsString(CASTER_ART, 0).split(",")); + final List targetArt = Arrays.asList(abilityTypeData.getFieldAsString(TARGET_ART, 0).split(",")); + final List specialArt = Arrays.asList(abilityTypeData.getFieldAsString(SPECIAL_ART, 0).split(",")); + final List effectArt = Arrays.asList(abilityTypeData.getFieldAsString(EFFECT_ART, 0).split(",")); + final List areaEffectArt = Arrays + .asList(abilityTypeData.getFieldAsString(AREA_EFFECT_ART, 0).split(",")); + final List missileArt = Arrays.asList(abilityTypeData.getFieldAsString(MISSILE_ART, 0).split(",")); + this.rawcodeToUI.put(alias, - new AbilityIconUI( + new AbilityUI( new IconUI(iconResearch, iconResearchDisabled, iconResearchX, iconResearchY, iconResearchTip, iconResearchUberTip), new IconUI(iconNormal, iconNormalDisabled, iconNormalX, iconNormalY, iconTip, iconUberTip), new IconUI(iconTurnOff, iconTurnOffDisabled, iconTurnOffX, iconTurnOffY, iconTurnOffTip, - iconTurnOffUberTip))); + iconTurnOffUberTip), + casterArt, targetArt, specialArt, effectArt, areaEffectArt, missileArt)); } for (final War3ID alias : unitData.keySet()) { final MutableGameObject abilityTypeData = unitData.get(alias); @@ -143,6 +163,7 @@ public class AbilityDataUI { this.cancelBuildUI = createBuiltInIconUI(gameUI, "CmdCancelBuild", disabledPrefix); this.cancelTrainUI = createBuiltInIconUI(gameUI, "CmdCancelTrain", disabledPrefix); this.rallyUI = createBuiltInIconUI(gameUI, "CmdRally", disabledPrefix); + this.selectSkillUI = createBuiltInIconUI(gameUI, "CmdSelectSkill", disabledPrefix); } private IconUI createBuiltInIconUI(final GameUI gameUI, final String key, final String disabledPrefix) { @@ -157,7 +178,7 @@ public class AbilityDataUI { return new IconUI(icon, iconDisabled, buttonPositionX, buttonPositionY, tip, uberTip); } - public AbilityIconUI getUI(final War3ID rawcode) { + public AbilityUI getUI(final War3ID rawcode) { return this.rawcodeToUI.get(rawcode); } @@ -251,4 +272,8 @@ public class AbilityDataUI { return this.rallyUI; } + public IconUI getSelectSkillUI() { + return this.selectSkillUI; + } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityIconUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityIconUI.java deleted file mode 100644 index d75e395..0000000 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityIconUI.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability; - -public class AbilityIconUI { - private final IconUI learnIconUI; - private final IconUI onIconUI; - private final IconUI offIconUI; - - public AbilityIconUI(final IconUI learnIconUI, final IconUI onIconUI, final IconUI offIconUI) { - this.learnIconUI = learnIconUI; - this.onIconUI = onIconUI; - this.offIconUI = offIconUI; - } - - public IconUI getLearnIconUI() { - return this.learnIconUI; - } - - public IconUI getOnIconUI() { - return this.onIconUI; - } - - public IconUI getOffIconUI() { - return this.offIconUI; - } -} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityUI.java new file mode 100644 index 0000000..2be75c1 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityUI.java @@ -0,0 +1,72 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability; + +import java.util.List; + +public class AbilityUI { + private final IconUI learnIconUI; + private final IconUI onIconUI; + private final IconUI offIconUI; + private final List casterArt; + private final List targetArt; + private final List specialArt; + private final List effectArt; + private final List areaEffectArt; + private final List missileArt; + + public AbilityUI(final IconUI learnIconUI, final IconUI onIconUI, final IconUI offIconUI, + final List casterArt, final List targetArt, final List specialArt, + final List effectArt, final List areaEffectArt, final List missileArt) { + this.learnIconUI = learnIconUI; + this.onIconUI = onIconUI; + this.offIconUI = offIconUI; + this.casterArt = casterArt; + this.targetArt = targetArt; + this.specialArt = specialArt; + this.effectArt = effectArt; + this.areaEffectArt = areaEffectArt; + this.missileArt = missileArt; + } + + public IconUI getLearnIconUI() { + return this.learnIconUI; + } + + public IconUI getOnIconUI() { + return this.onIconUI; + } + + public IconUI getOffIconUI() { + return this.offIconUI; + } + + public String getCasterArt(final int index) { + return tryGet(this.casterArt, index); + } + + public String getTargetArt(final int index) { + return tryGet(this.targetArt, index); + } + + public String getSpecialArt(final int index) { + return tryGet(this.specialArt, index); + } + + public String getEffectArt(final int index) { + return tryGet(this.effectArt, index); + } + + public String getAreaEffectArt(final int index) { + return tryGet(this.areaEffectArt, index); + } + + public String getMissileArt(final int index) { + return tryGet(this.missileArt, index); + } + + private static String tryGet(final List items, final int index) { + if (index < items.size()) { + return items.get(index); + } + return items.get(items.size() - 1); + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/AbilityCommandButton.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/AbilityCommandButton.java index 82b19aa..5f3a73d 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/AbilityCommandButton.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/AbilityCommandButton.java @@ -1,13 +1,13 @@ package com.etheller.warsmash.viewer5.handlers.w3x.rendersim.commandbuttons; import com.badlogic.gdx.graphics.Texture; -import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.AbilityIconUI; +import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.AbilityUI; public class AbilityCommandButton implements CommandButton { - private final AbilityIconUI abilityIconUI; + private final AbilityUI abilityIconUI; private final int orderId; - public AbilityCommandButton(final AbilityIconUI abilityIconUI, final int orderId) { + public AbilityCommandButton(final AbilityUI abilityIconUI, final int orderId) { this.abilityIconUI = abilityIconUI; this.orderId = orderId; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java index 0293fc3..fd181d2 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java @@ -3,7 +3,7 @@ package com.etheller.warsmash.viewer5.handlers.w3x.rendersim.commandbuttons; import com.etheller.warsmash.parsers.fdf.GameUI; import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.AbilityDataUI; -import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.AbilityIconUI; +import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.AbilityUI; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.IconUI; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; @@ -24,6 +24,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAb import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.combat.CAbilityColdArrows; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic.GenericNoIconAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic.GenericSingleIconActiveAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.hero.CAbilityHero; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityQueue; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityRally; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; @@ -63,7 +64,7 @@ public class CommandCardPopulatingAbilityVisitor implements CAbilityVisitor table.length) { + level = table.length; + } + return table[level - 1]; + } + + /* + * This incorporates the function "f(x)" documented both on + * http://classic.battle.net/war3/basics/heroes.shtml and also on MiscGame.txt. + */ + private static int[] parseTable(final String txt, final int formulaA, final int formulaB, final int formulaC, + final int tableSize) { + final String[] splitTxt = txt.split(","); + final int[] result = new int[tableSize]; + for (int i = 0; i < tableSize; i++) { + if (i < splitTxt.length) { + result[i] = Integer.parseInt(splitTxt[i]); + } + else { + result[i] = (formulaA * result[i - 1]) + (formulaB * i) + formulaC; + } + } + return result; + } + + private static int[] parseIntArray(final String txt) { + final String[] splitTxt = txt.split(","); + final int[] result = new int[splitTxt.length]; + for (int i = 0; i < splitTxt.length; i++) { + result[i] = Integer.parseInt(splitTxt[i]); + } + return result; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CItemType.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CItemType.java new file mode 100644 index 0000000..8f40ce1 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CItemType.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation; + +public class CItemType { + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java index 7711da0..47874be 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java @@ -52,6 +52,7 @@ public class CSimulation { private final PathingGrid pathingGrid; private final CWorldCollision worldCollision; private final CPathfindingProcessor[] pathfindingProcessors; + private final List[] playerHeroes; private final CGameplayConstants gameplayConstants; private final Random seededRandom; private float currentGameDayTimeElapsed; @@ -69,7 +70,8 @@ public class CSimulation { this.simulationRenderController = simulationRenderController; this.pathingGrid = pathingGrid; this.abilityData = new CAbilityData(parsedAbilityData); - this.unitData = new CUnitData(parsedUnitData, this.abilityData, this.simulationRenderController); + this.unitData = new CUnitData(this.gameplayConstants, parsedUnitData, this.abilityData, + this.simulationRenderController); this.destructableData = new CDestructableData(parsedDestructableData, simulationRenderController); this.units = new ArrayList<>(); this.newUnits = new ArrayList<>(); @@ -79,8 +81,10 @@ public class CSimulation { this.handleIdAllocator = new HandleIdAllocator(); this.worldCollision = new CWorldCollision(entireMapBounds, this.gameplayConstants.getMaxCollisionRadius()); this.pathfindingProcessors = new CPathfindingProcessor[WarsmashConstants.MAX_PLAYERS]; - for (int i = 0; i < this.pathfindingProcessors.length; i++) { + this.playerHeroes = new ArrayList[WarsmashConstants.MAX_PLAYERS]; + for (int i = 0; i < WarsmashConstants.MAX_PLAYERS; i++) { this.pathfindingProcessors[i] = new CPathfindingProcessor(pathingGrid, this.worldCollision); + this.playerHeroes[i] = new ArrayList<>(); } this.seededRandom = seededRandom; this.players = new ArrayList<>(); @@ -211,6 +215,7 @@ public class CSimulation { } this.handleIdToUnit.remove(unit.getHandleId()); this.simulationRenderController.removeUnit(unit); + this.playerHeroes[unit.getPlayerIndex()].remove(unit); } } finishAddingNewUnits(); @@ -304,6 +309,18 @@ public class CSimulation { this.simulationRenderController.spawnGainResourceTextTag(unit, resourceType, amount); } + public void unitGainLevelEvent(final CUnit unit) { + this.simulationRenderController.spawnGainLevelEffect(unit); + } + + public void heroCreateEvent(final CUnit hero) { + this.playerHeroes[hero.getPlayerIndex()].add(hero); + } + + public List getPlayerHeroes(final int playerIndex) { + return this.playerHeroes[playerIndex]; + } + public void unitsLoaded() { // called on startup after the system loads the map's units layer, but not any // custom scripts yet @@ -331,4 +348,5 @@ public class CSimulation { public void createEffectOnUnit(final CUnit unit, final String effectPath) { this.simulationRenderController.spawnEffectOnUnit(unit, effectPath); } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java index fe40b17..557db7d 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java @@ -19,6 +19,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitStateListener. import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityBuildInProgress; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.hero.CAbilityHero; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.mine.CAbilityGoldMine; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget; @@ -51,7 +52,13 @@ public class CUnit extends CWidget { private int maximumLife; private int maximumMana; private int speed; - private final int defense; + private int agilityDefenseBonus; + private int permanentDefenseBonus; + private int temporaryDefenseBonus; + + private int currentDefenseDisplay; + private int currentDefense; + private int cooldownEndTime = 0; private float flyHeight; private int playerIndex; @@ -103,10 +110,11 @@ public class CUnit extends CWidget { private int foodMade; private int foodUsed; + private List unitSpecificAttacks; + public CUnit(final int handleId, final int playerIndex, final float x, final float y, final float life, final War3ID typeId, final float facing, final float mana, final int maximumLife, final int maximumMana, - final int speed, final int defense, final CUnitType unitType, - final RemovablePathingMapInstance pathingInstance) { + final int speed, final CUnitType unitType, final RemovablePathingMapInstance pathingInstance) { super(handleId, x, y, life); this.playerIndex = playerIndex; this.typeId = typeId; @@ -115,7 +123,6 @@ public class CUnit extends CWidget { this.maximumLife = maximumLife; this.maximumMana = maximumMana; this.speed = speed; - this.defense = defense; this.pathingInstance = pathingInstance; this.flyHeight = unitType.getDefaultFlyingHeight(); this.unitType = unitType; @@ -126,6 +133,34 @@ public class CUnit extends CWidget { this.currentBehavior = this.defaultBehavior; } + private void computeDerivedFields() { + this.currentDefenseDisplay = this.unitType.getDefense() + this.agilityDefenseBonus + this.permanentDefenseBonus; + this.currentDefense = this.currentDefenseDisplay + this.temporaryDefenseBonus; + } + + public void setAgilityDefenseBonus(final int agilityDefenseBonus) { + this.agilityDefenseBonus = agilityDefenseBonus; + computeDerivedFields(); + } + + public void setPermanentDefenseBonus(final int permanentDefenseBonus) { + this.permanentDefenseBonus = permanentDefenseBonus; + computeDerivedFields(); + } + + public void setTemporaryDefenseBonus(final int temporaryDefenseBonus) { + this.temporaryDefenseBonus = temporaryDefenseBonus; + computeDerivedFields(); + } + + public int getTemporaryDefenseBonus() { + return this.temporaryDefenseBonus; + } + + public int getCurrentDefenseDisplay() { + return this.currentDefenseDisplay; + } + public void setUnitAnimationListener(final CUnitAnimationListener unitAnimationListener) { this.unitAnimationListener = unitAnimationListener; this.unitAnimationListener.playAnimation(true, PrimaryTag.STAND, SequenceUtils.EMPTY, 1.0f, true); @@ -365,8 +400,7 @@ public class CUnit extends CWidget { } public boolean autoAcquireAttackTargets(final CSimulation game, final boolean disableMove) { - if (!this.unitType.getAttacks().isEmpty() - && !this.unitType.getClassifications().contains(CUnitClassification.PEON)) { + if (!this.getAttacks().isEmpty() && !this.unitType.getClassifications().contains(CUnitClassification.PEON)) { if (this.collisionRectangle != null) { tempRect.set(this.collisionRectangle); } @@ -587,7 +621,7 @@ public class CUnit extends CWidget { } public int getDefense() { - return this.defense; + return this.currentDefense; } @Override @@ -647,11 +681,13 @@ public class CUnit extends CWidget { final float damageRatioFromArmorClass = simulation.getGameplayConstants().getDamageRatioAgainst(attackType, this.unitType.getDefenseType()); final float damageRatioFromDefense; - if (this.defense >= 0) { - damageRatioFromDefense = 1f - (float) (((this.defense) * 0.06) / (1 + (0.06 * this.defense))); + final int defense = this.currentDefense; + if (defense >= 0) { + damageRatioFromDefense = 1f - (((defense) * simulation.getGameplayConstants().getDefenseArmor()) + / (1 + (simulation.getGameplayConstants().getDefenseArmor() * defense))); } else { - damageRatioFromDefense = 2f - (float) StrictMath.pow(0.94, -this.defense); + damageRatioFromDefense = 2f - (float) StrictMath.pow(0.94, -defense); } final float trueDamage = damageRatioFromArmorClass * damageRatioFromDefense * damage; this.life -= trueDamage; @@ -660,14 +696,14 @@ public class CUnit extends CWidget { simulation.unitDamageEvent(this, weaponType, this.unitType.getArmorType()); if (!this.invulnerable && isDead()) { if (!wasDead) { - kill(simulation); + kill(simulation, source); } } else { if ((this.currentBehavior == null) || (this.currentBehavior == this.defaultBehavior)) { if (!simulation.getPlayer(getPlayerIndex()).hasAlliance(source.getPlayerIndex(), CAllianceType.PASSIVE)) { - for (final CUnitAttack attack : this.unitType.getAttacks()) { + for (final CUnitAttack attack : this.getAttacks()) { if (source.canBeTargetedBy(simulation, this, attack.getTargetsAllowed())) { this.currentBehavior = getAttackBehavior().reset(OrderIds.attack, attack, source, false, CBehaviorAttackListener.DO_NOTHING); @@ -680,7 +716,7 @@ public class CUnit extends CWidget { } } - private void kill(final CSimulation simulation) { + private void kill(final CSimulation simulation, final CUnit source) { if (this.currentBehavior != null) { this.currentBehavior.end(simulation, true); } @@ -704,6 +740,55 @@ public class CUnit extends CWidget { if (this.foodUsed != 0) { player.setUnitFoodUsed(this, 0); } + + // Award hero experience + if (source != null) { + final CPlayer sourcePlayer = simulation.getPlayer(source.getPlayerIndex()); + if (!sourcePlayer.hasAlliance(this.playerIndex, CAllianceType.PASSIVE)) { + final CGameplayConstants gameplayConstants = simulation.getGameplayConstants(); + if (gameplayConstants.isBuildingKillsGiveExp() || !source.getUnitType().isBuilding()) { + final CUnit killedUnit = this; + final CAbilityHero killedUnitHeroData = getHeroData(); + final boolean killedUnitIsAHero = killedUnitHeroData != null; + int availableAwardXp; + if (killedUnitIsAHero) { + availableAwardXp = gameplayConstants.getGrantHeroXP(killedUnitHeroData.getHeroLevel()); + } + else { + availableAwardXp = gameplayConstants.getGrantNormalXP(this.unitType.getLevel()); + } + final List xpReceivingHeroes = new ArrayList<>(); + final int heroExpRange = gameplayConstants.getHeroExpRange(); + simulation.getWorldCollision().enumUnitsInRect(new Rectangle(this.getX() - heroExpRange, + this.getY() - heroExpRange, heroExpRange * 2, heroExpRange * 2), new CUnitEnumFunction() { + @Override + public boolean call(final CUnit unit) { + if ((unit.distance(killedUnit) <= heroExpRange) + && sourcePlayer.hasAlliance(unit.getPlayerIndex(), CAllianceType.SHARED_XP) + && (unit.getHeroData() != null)) { + xpReceivingHeroes.add(unit); + } + return false; + } + }); + if (xpReceivingHeroes.isEmpty()) { + if (gameplayConstants.isGlobalExperience()) { + for (int i = 0; i < WarsmashConstants.MAX_PLAYERS; i++) { + if (sourcePlayer.hasAlliance(i, CAllianceType.SHARED_XP)) { + xpReceivingHeroes.addAll(simulation.getPlayerHeroes(i)); + } + } + } + } + for (final CUnit receivingHero : xpReceivingHeroes) { + final CAbilityHero heroData = receivingHero.getHeroData(); + heroData.addXp(simulation, receivingHero, + (int) (availableAwardXp * (1f / xpReceivingHeroes.size()) + * gameplayConstants.getHeroFactorXp(heroData.getHeroLevel()))); + } + } + } + } } public boolean canReach(final AbilityTarget target, final float range) { @@ -881,7 +966,7 @@ public class CUnit extends CWidget { public boolean call(final CUnit unit) { if (!this.game.getPlayer(this.source.getPlayerIndex()).hasAlliance(unit.getPlayerIndex(), CAllianceType.PASSIVE)) { - for (final CUnitAttack attack : this.source.unitType.getAttacks()) { + for (final CUnitAttack attack : this.source.getAttacks()) { if (this.source.canReach(unit, this.source.acquisitionRange) && unit.canBeTargetedBy(this.game, this.source, attack.getTargetsAllowed()) && (this.source.distance(unit) >= this.source.getUnitType().getMinimumAttackRange())) { @@ -1020,7 +1105,7 @@ public class CUnit extends CWidget { final boolean wasDead = isDead(); super.setLife(simulation, life); if (isDead() && !wasDead) { - kill(simulation); + kill(simulation, null); } this.stateNotifier.lifeChanged(); } @@ -1138,6 +1223,10 @@ public class CUnit extends CWidget { this.stateNotifier.rallyPointChanged(); } + public void internalPublishHeroStatsChanged() { + this.stateNotifier.heroStatsChanged(); + } + public AbilityTarget getRallyPoint() { return this.rallyPoint; } @@ -1275,4 +1364,28 @@ public class CUnit extends CWidget { public COrder getCurrentOrder() { return this.lastStartedOrder; } + + public CAbilityHero getHeroData() { + for (final CAbility ability : this.abilities) { + if (ability instanceof CAbilityHero) { + return (CAbilityHero) ability; + } + } + return null; + } + + public void setUnitSpecificAttacks(final List unitSpecificAttacks) { + this.unitSpecificAttacks = unitSpecificAttacks; + } + + public List getUnitSpecificAttacks() { + return this.unitSpecificAttacks; + } + + public List getAttacks() { + if (this.unitSpecificAttacks != null) { + return this.unitSpecificAttacks; + } + return this.unitType.getAttacks(); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitStateListener.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitStateListener.java index 2ec7c85..5613e73 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitStateListener.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitStateListener.java @@ -13,6 +13,8 @@ public interface CUnitStateListener { void waypointsChanged(); + void heroStatsChanged(); + public static final class CUnitStateNotifier extends SubscriberSetNotifier implements CUnitStateListener { @Override @@ -49,5 +51,12 @@ public interface CUnitStateListener { listener.waypointsChanged(); } } + + @Override + public void heroStatsChanged() { + for (final CUnitStateListener listener : set) { + listener.heroStatsChanged(); + } + } } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java index 6a364f8..aaf26f6 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java @@ -7,6 +7,7 @@ import java.util.List; import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.MovementType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.hero.CPrimaryAttribute; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CDefenseType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; @@ -58,6 +59,18 @@ public class CUnitType { private final float propWindow; private final float turnRate; private final List requirements; + private final int level; + private final boolean hero; + private final int startingStrength; + private final float strengthPerLevel; + private final int startingAgility; + private final float agilityPerLevel; + private final int startingIntelligence; + private final float intelligencePerLevel; + private final CPrimaryAttribute primaryAttribute; + private final List heroAbilityList; + private final List heroProperNames; + private final int properNamesCount; public CUnitType(final String name, final int life, final int manaInitial, final int manaMaximum, final int speed, final int defense, final String abilityList, final boolean isBldg, final MovementType movementType, @@ -70,7 +83,10 @@ public class CUnitType { final int goldCost, final int lumberCost, final int foodUsed, final int foodMade, final int buildTime, final EnumSet preventedPathingTypes, final EnumSet requiredPathingTypes, final float propWindow, final float turnRate, - final List requirements) { + final List requirements, final int level, final boolean hero, final int strength, + final float strengthPerLevel, final int agility, final float agilityPerLevel, final int intelligence, + final float intelligencePerLevel, final CPrimaryAttribute primaryAttribute, + final List heroAbilityList, final List heroProperNames, final int properNamesCount) { this.name = name; this.life = life; this.manaInitial = manaInitial; @@ -108,6 +124,18 @@ public class CUnitType { this.propWindow = propWindow; this.turnRate = turnRate; this.requirements = requirements; + this.level = level; + this.hero = hero; + this.startingStrength = strength; + this.strengthPerLevel = strengthPerLevel; + this.startingAgility = agility; + this.agilityPerLevel = agilityPerLevel; + this.startingIntelligence = intelligence; + this.intelligencePerLevel = intelligencePerLevel; + this.primaryAttribute = primaryAttribute; + this.heroAbilityList = heroAbilityList; + this.heroProperNames = heroProperNames; + this.properNamesCount = properNamesCount; } public String getName() { @@ -257,4 +285,52 @@ public class CUnitType { public List getRequirements() { return this.requirements; } + + public int getLevel() { + return this.level; + } + + public boolean isHero() { + return this.hero; + } + + public int getStartingStrength() { + return this.startingStrength; + } + + public float getStrengthPerLevel() { + return this.strengthPerLevel; + } + + public int getStartingAgility() { + return this.startingAgility; + } + + public float getAgilityPerLevel() { + return this.agilityPerLevel; + } + + public int getStartingIntelligence() { + return this.startingIntelligence; + } + + public float getIntelligencePerLevel() { + return this.intelligencePerLevel; + } + + public CPrimaryAttribute getPrimaryAttribute() { + return this.primaryAttribute; + } + + public List getHeroAbilityList() { + return this.heroAbilityList; + } + + public List getHeroProperNames() { + return this.heroProperNames; + } + + public int getProperNamesCount() { + return this.properNamesCount; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java index 304f933..a6f4dd6 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java @@ -44,7 +44,7 @@ public class CAbilityAttack extends AbstractCAbility { } if ((orderId == OrderIds.smart) || (orderId == OrderIds.attack)) { boolean canTarget = false; - for (final CUnitAttack attack : unit.getUnitType().getAttacks()) { + for (final CUnitAttack attack : unit.getAttacks()) { if (target.canBeTargetedBy(game, unit, attack.getTargetsAllowed())) { canTarget = true; break; @@ -77,7 +77,7 @@ public class CAbilityAttack extends AbstractCAbility { break; case OrderIds.attackground: boolean allowAttackGround = false; - for (final CUnitAttack attack : unit.getUnitType().getAttacks()) { + for (final CUnitAttack attack : unit.getAttacks()) { if (attack.getWeaponType().isAttackGroundSupported()) { allowAttackGround = true; break; @@ -118,7 +118,7 @@ public class CAbilityAttack extends AbstractCAbility { @Override public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final CWidget target) { CBehavior behavior = null; - for (final CUnitAttack attack : caster.getUnitType().getAttacks()) { + for (final CUnitAttack attack : caster.getAttacks()) { if (target.canBeTargetedBy(game, caster, attack.getTargetsAllowed())) { behavior = caster.getAttackBehavior().reset(OrderIds.attack, attack, target, false, CBehaviorAttackListener.DO_NOTHING); @@ -142,7 +142,7 @@ public class CAbilityAttack extends AbstractCAbility { return caster.getMoveBehavior().reset(OrderIds.attack, point); case OrderIds.attackground: CBehavior behavior = null; - for (final CUnitAttack attack : caster.getUnitType().getAttacks()) { + for (final CUnitAttack attack : caster.getAttacks()) { if (attack.getWeaponType().isAttackGroundSupported()) { behavior = caster.getAttackBehavior().reset(OrderIds.attackground, attack, point, false, CBehaviorAttackListener.DO_NOTHING); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityVisitor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityVisitor.java index a6e6911..b2ec6e3 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityVisitor.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityVisitor.java @@ -10,6 +10,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAb import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.combat.CAbilityColdArrows; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic.GenericNoIconAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic.GenericSingleIconActiveAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.hero.CAbilityHero; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityQueue; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityRally; @@ -51,4 +52,6 @@ public interface CAbilityVisitor { T accept(CAbilityRally ability); T accept(GenericNoIconAbility ability); + + T accept(CAbilityHero ability); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/combat/CAbilityColdArrows.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/combat/CAbilityColdArrows.java index 08da353..a965a4a 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/combat/CAbilityColdArrows.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/combat/CAbilityColdArrows.java @@ -105,7 +105,7 @@ public class CAbilityColdArrows extends AbstractCAbility { @Override public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final CWidget target) { CBehavior behavior = null; - for (final CUnitAttack attack : caster.getUnitType().getAttacks()) { + for (final CUnitAttack attack : caster.getAttacks()) { if (target.canBeTargetedBy(game, caster, attack.getTargetsAllowed())) { behavior = caster.getAttackBehavior().reset(OrderIds.coldarrowstarg, attack, target, false, CBehaviorAttackListener.DO_NOTHING); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityHarvest.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityHarvest.java index c0d8620..92bf056 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityHarvest.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityHarvest.java @@ -53,7 +53,7 @@ public class CAbilityHarvest extends AbstractGenericSingleIconActiveAbility { this.behaviorHarvest = new CBehaviorHarvest(unit, this); this.behaviorReturnResources = new CBehaviorReturnResources(unit, this); - final List unitAttacks = unit.getUnitType().getAttacks(); + final List unitAttacks = unit.getAttacks(); CUnitAttack bestFitTreeAttack = null; for (final CUnitAttack attack : unitAttacks) { if (attack.getTargetsAllowed().contains(CTargetType.TREE)) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/hero/CAbilityHero.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/hero/CAbilityHero.java index c844845..c9aefd6 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/hero/CAbilityHero.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/hero/CAbilityHero.java @@ -1,5 +1,275 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.hero; -public class CAbilityHero { +import java.util.List; +import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.util.WarsmashConstants; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CGameplayConstants; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.AbstractCAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; + +public class CAbilityHero extends AbstractCAbility { + private final List skillsAvailable; + private int xp; + private int heroLevel; + private int skillPoints; + + private HeroStatValue strength; + private HeroStatValue agility; + private HeroStatValue intelligence; + private String properName; + + public CAbilityHero(final int handleId, final List skillsAvailable) { + super(handleId); + this.skillsAvailable = skillsAvailable; + } + + @Override + public void onAdd(final CSimulation game, final CUnit unit) { + this.heroLevel = 1; + this.xp = 0; + final CUnitType unitType = unit.getUnitType(); + this.strength = new HeroStatValue(unitType.getStartingStrength(), unitType.getStrengthPerLevel()); + this.agility = new HeroStatValue(unitType.getStartingAgility(), unitType.getAgilityPerLevel()); + this.intelligence = new HeroStatValue(unitType.getStartingIntelligence(), unitType.getIntelligencePerLevel()); + calculateDerivatedFields(game, unit); + + final int nameIndex = game.getSeededRandom().nextInt(unitType.getProperNamesCount()); + + String properName; + final List heroProperNames = unitType.getHeroProperNames(); + if (heroProperNames.size() > 0) { + if (nameIndex < heroProperNames.size()) { + properName = heroProperNames.get(nameIndex); + } + else { + properName = heroProperNames.get(heroProperNames.size() - 1); + } + } + else { + properName = WarsmashConstants.DEFAULT_STRING; + } + this.properName = properName; + } + + @Override + public void onRemove(final CSimulation game, final CUnit unit) { + + } + + @Override + public void onTick(final CSimulation game, final CUnit unit) { + + } + + @Override + public void onCancelFromQueue(final CSimulation game, final CUnit unit, final int orderId) { + + } + + @Override + public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId) { + return true; + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final CWidget target) { + return null; + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, + final AbilityPointTarget point) { + return null; + } + + @Override + public CBehavior beginNoTarget(final CSimulation game, final CUnit caster, final int orderId) { + return null; + } + + @Override + public void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, final CWidget target, + final AbilityTargetCheckReceiver receiver) { + receiver.orderIdNotAccepted(); + } + + @Override + public void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, + final AbilityPointTarget target, final AbilityTargetCheckReceiver receiver) { + receiver.orderIdNotAccepted(); + } + + @Override + public void checkCanTargetNoTarget(final CSimulation game, final CUnit unit, final int orderId, + final AbilityTargetCheckReceiver receiver) { + receiver.orderIdNotAccepted(); + } + + @Override + public T visit(final CAbilityVisitor visitor) { + return visitor.accept(this); + } + + @Override + protected void innerCheckCanUse(final CSimulation game, final CUnit unit, final int orderId, + final AbilityActivationReceiver receiver) { + receiver.useOk(); + } + + public int getSkillPoints() { + return this.skillPoints; + } + + public void setSkillPoints(final int skillPoints) { + this.skillPoints = skillPoints; + } + + public int getXp() { + return this.xp; + } + + public void setXp(final int xp) { + this.xp = xp; + } + + public int getHeroLevel() { + return this.heroLevel; + } + + public void setHeroLevel(final int level) { + this.heroLevel = level; + } + + public HeroStatValue getStrength() { + return this.strength; + } + + public HeroStatValue getAgility() { + return this.agility; + } + + public HeroStatValue getIntelligence() { + return this.intelligence; + } + + public String getProperName() { + return this.properName; + } + + public void addXp(final CSimulation simulation, final CUnit unit, final int xp) { + this.xp += xp; + final CGameplayConstants gameplayConstants = simulation.getGameplayConstants(); + while ((this.heroLevel < gameplayConstants.getMaxHeroLevel()) + && (this.xp >= gameplayConstants.getNeedHeroXPSum(this.heroLevel))) { + this.heroLevel++; + this.skillPoints++; + calculateDerivatedFields(simulation, unit); + simulation.unitGainLevelEvent(unit); + } + unit.internalPublishHeroStatsChanged(); + } + + private HeroStatValue getStat(final CPrimaryAttribute attribute) { + switch (attribute) { + case AGILITY: + return this.agility; + case INTELLIGENCE: + return this.intelligence; + default: + case STRENGTH: + return this.strength; + } + } + + private void calculateDerivatedFields(final CSimulation game, final CUnit unit) { + final CGameplayConstants gameplayConstants = game.getGameplayConstants(); + final int prevStrength = this.strength.getCurrent(); + final int prevAgility = this.agility.getCurrent(); + final int prevIntelligence = this.intelligence.getCurrent(); + this.strength.calculate(this.heroLevel); + this.agility.calculate(this.heroLevel); + this.intelligence.calculate(this.heroLevel); + final int deltaStrength = this.strength.getCurrent() - prevStrength; + final int deltaIntelligence = this.intelligence.getCurrent() - prevIntelligence; + final int currentAgility = this.agility.getCurrent(); + final int deltaAgility = currentAgility - prevAgility; + + final int primaryAttribute = getStat(unit.getUnitType().getPrimaryAttribute()).getCurrent(); + for (final CUnitAttack attack : unit.getUnitSpecificAttacks()) { + attack.setPrimaryAttributeDamageBonus((int) (primaryAttribute * gameplayConstants.getStrAttackBonus())); + } + + final float hitPointIncrease = gameplayConstants.getStrHitPointBonus() * deltaStrength; + final int oldMaximumLife = unit.getMaximumLife(); + final float oldLife = unit.getLife(); + final int newMaximumLife = Math.round(oldMaximumLife + hitPointIncrease); + final float newLife = (oldLife * (newMaximumLife)) / oldMaximumLife; + unit.setMaximumLife(newMaximumLife); + unit.setLife(game, newLife); + + final float manaPointIncrease = gameplayConstants.getIntManaBonus() * deltaIntelligence; + final int oldMaximumMana = unit.getMaximumMana(); + final float oldMana = unit.getMana(); + final int newMaximumMana = Math.round(oldMaximumMana + manaPointIncrease); + final float newMana = (oldMana * (newMaximumMana)) / oldMaximumMana; + unit.setMaximumMana(newMaximumMana); + unit.setMana(newMana); + + final int agilityDefenseBonus = Math.round( + gameplayConstants.getAgiDefenseBase() + (gameplayConstants.getAgiDefenseBonus() * currentAgility)); + unit.setAgilityDefenseBonus(agilityDefenseBonus); + } + + public static final class HeroStatValue { + private final float perLevelFactor; + private int base; + private int bonus; + private int currentBase; + private int current; + + private HeroStatValue(final int base, final float perLevelFactor) { + this.base = base; + this.perLevelFactor = perLevelFactor; + } + + public void calculate(final int level) { + this.currentBase = this.base + (int) ((level - 1) * this.perLevelFactor); + this.current = this.currentBase + this.bonus; + } + + public void setBase(final int base) { + this.base = base; + } + + public void setBonus(final int bonus) { + this.bonus = bonus; + } + + public int getCurrent() { + return this.current; + } + + public String getDisplayText() { + String text = Integer.toString(this.currentBase); + if (this.bonus != 0) { + if (this.bonus > 0) { + text += "|cFF00FF00 (+" + this.bonus + ")"; + } + else { + text += "|cFFFF0000 (+" + this.bonus + ")"; + } + } + return text; + } + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/hero/CPrimaryAttribute.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/hero/CPrimaryAttribute.java new file mode 100644 index 0000000..f00d4d7 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/hero/CPrimaryAttribute.java @@ -0,0 +1,23 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.hero; + +public enum CPrimaryAttribute { + STRENGTH, + INTELLIGENCE, + AGILITY; + + public static CPrimaryAttribute parsePrimaryAttribute(final String targetTypeString) { + if (targetTypeString == null) { + return STRENGTH; + } + switch (targetTypeString.toUpperCase()) { + case "STR": + return STRENGTH; + case "INT": + return INTELLIGENCE; + case "AGI": + return AGILITY; + default: + return STRENGTH; + } + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/AbilityDisableWhileUnderConstructionVisitor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/AbilityDisableWhileUnderConstructionVisitor.java index 5c9a76a..06bc41d 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/AbilityDisableWhileUnderConstructionVisitor.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/AbilityDisableWhileUnderConstructionVisitor.java @@ -14,6 +14,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAb import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.combat.CAbilityColdArrows; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic.GenericNoIconAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic.GenericSingleIconActiveAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.hero.CAbilityHero; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityQueue; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityRally; @@ -125,4 +126,11 @@ public class AbilityDisableWhileUnderConstructionVisitor implements CAbilityVisi return null; } + @Override + public Void accept(final CAbilityHero ability) { + ability.setDisabled(true); + ability.setIconShowing(false); + return null; + } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttack.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttack.java index 989f9f0..0b885f5 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttack.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttack.java @@ -41,6 +41,12 @@ public abstract class CUnitAttack { // calculate private int minDamage; private int maxDamage; + private int minDamageDisplay; + private int maxDamageDisplay; + + private int primaryAttributeDamageBonus; + private int permanentDamageBonus; + private int temporaryDamageBonus; public CUnitAttack(final float animationBackswingPoint, final float animationDamagePoint, final CAttackType attackType, final float cooldownTime, final int damageBase, final int damageDice, @@ -64,15 +70,42 @@ public abstract class CUnitAttack { computeDerivedFields(); } + public CUnitAttack(final CUnitAttack other) { + this.animationBackswingPoint = other.animationBackswingPoint; + this.animationDamagePoint = other.animationDamagePoint; + this.attackType = other.attackType; + this.cooldownTime = other.cooldownTime; + this.damageBase = other.damageBase; + this.damageDice = other.damageDice; + this.damageSidesPerDie = other.damageSidesPerDie; + this.damageUpgradeAmount = other.damageUpgradeAmount; + this.range = other.range; + this.rangeMotionBuffer = other.rangeMotionBuffer; + this.showUI = other.showUI; + this.targetsAllowed = other.targetsAllowed; + this.weaponSound = other.weaponSound; + this.weaponType = other.weaponType; + + this.primaryAttributeDamageBonus = other.primaryAttributeDamageBonus; + this.permanentDamageBonus = other.permanentDamageBonus; + this.temporaryDamageBonus = other.temporaryDamageBonus; + computeDerivedFields(); + } + + public abstract CUnitAttack copy(); + private void computeDerivedFields() { - this.minDamage = this.damageBase + this.damageDice; - this.maxDamage = this.damageBase + (this.damageDice * this.damageSidesPerDie); - if (this.minDamage < 0) { - this.minDamage = 0; + final int baseDamage = this.damageBase + this.primaryAttributeDamageBonus + this.permanentDamageBonus; + this.minDamageDisplay = baseDamage + this.damageDice; + this.maxDamageDisplay = baseDamage + (this.damageDice * this.damageSidesPerDie); + if (this.minDamageDisplay < 0) { + this.minDamageDisplay = 0; } - if (this.maxDamage < 0) { - this.maxDamage = 0; + if (this.maxDamageDisplay < 0) { + this.maxDamageDisplay = 0; } + this.minDamage = this.minDamageDisplay + this.temporaryDamageBonus; + this.maxDamage = this.maxDamageDisplay + this.temporaryDamageBonus; } public float getAnimationBackswingPoint() { @@ -198,6 +231,41 @@ public abstract class CUnitAttack { return this.maxDamage; } + public int getMinDamageDisplay() { + return this.minDamageDisplay; + } + + public int getMaxDamageDisplay() { + return this.maxDamageDisplay; + } + + public void setPrimaryAttributeDamageBonus(final int primaryAttributeDamageBonus) { + this.primaryAttributeDamageBonus = primaryAttributeDamageBonus; + computeDerivedFields(); + } + + public void setPermanentDamageBonus(final int permanentDamageBonus) { + this.permanentDamageBonus = permanentDamageBonus; + computeDerivedFields(); + } + + public void setTemporaryDamageBonus(final int temporaryDamageBonus) { + this.temporaryDamageBonus = temporaryDamageBonus; + computeDerivedFields(); + } + + public int getPrimaryAttributeDamageBonus() { + return this.primaryAttributeDamageBonus; + } + + public int getPermanentDamageBonus() { + return this.permanentDamageBonus; + } + + public int getTemporaryDamageBonus() { + return this.temporaryDamageBonus; + } + public abstract void launch(CSimulation simulation, CUnit unit, AbilityTarget target, float damage, CUnitAttackListener attackListener); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackInstant.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackInstant.java index 934940f..33bbec4 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackInstant.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackInstant.java @@ -25,6 +25,14 @@ public class CUnitAttackInstant extends CUnitAttack { this.projectileArt = projectileArt; } + @Override + public CUnitAttack copy() { + return new CUnitAttackInstant(getAnimationBackswingPoint(), getAnimationDamagePoint(), getAttackType(), + getCooldownTime(), getDamageBase(), getDamageDice(), getDamageSidesPerDie(), getDamageUpgradeAmount(), + getRange(), getRangeMotionBuffer(), isShowUI(), getTargetsAllowed(), getWeaponSound(), getWeaponType(), + this.projectileArt); + } + public String getProjectileArt() { return this.projectileArt; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissile.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissile.java index 3b503d2..f3017d8 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissile.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissile.java @@ -32,6 +32,14 @@ public class CUnitAttackMissile extends CUnitAttack { this.projectileSpeed = projectileSpeed; } + @Override + public CUnitAttack copy() { + return new CUnitAttackMissile(this.getAnimationBackswingPoint(), getAnimationDamagePoint(), getAttackType(), + getCooldownTime(), getDamageBase(), getDamageDice(), getDamageSidesPerDie(), getDamageUpgradeAmount(), + getRange(), getRangeMotionBuffer(), isShowUI(), getTargetsAllowed(), getWeaponSound(), getWeaponType(), + this.projectileArc, this.projectileArt, this.projectileHomingEnabled, this.projectileSpeed); + } + public float getProjectileArc() { return this.projectileArc; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileBounce.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileBounce.java index 4fcb82f..b56f99b 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileBounce.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileBounce.java @@ -36,6 +36,16 @@ public class CUnitAttackMissileBounce extends CUnitAttackMissile { this.areaOfEffectTargets = areaOfEffectTargets; } + @Override + public CUnitAttack copy() { + return new CUnitAttackMissileBounce(getAnimationBackswingPoint(), getAnimationDamagePoint(), getAttackType(), + getCooldownTime(), getDamageBase(), getDamageDice(), getDamageSidesPerDie(), getDamageUpgradeAmount(), + getRange(), getRangeMotionBuffer(), isShowUI(), getTargetsAllowed(), getWeaponSound(), getWeaponType(), + getProjectileArc(), getProjectileArt(), isProjectileHomingEnabled(), getProjectileSpeed(), + this.damageLossFactor, this.maximumNumberOfTargets, this.areaOfEffectFullDamage, + this.areaOfEffectTargets); + } + public float getDamageLossFactor() { return this.damageLossFactor; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileLine.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileLine.java index 6d4dbeb..bd95e8a 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileLine.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileLine.java @@ -24,6 +24,15 @@ public class CUnitAttackMissileLine extends CUnitAttackMissile { this.damageSpillRadius = damageSpillRadius; } + @Override + public CUnitAttack copy() { + return new CUnitAttackMissileLine(getAnimationBackswingPoint(), getAnimationDamagePoint(), getAttackType(), + getCooldownTime(), getDamageBase(), getDamageDice(), getDamageSidesPerDie(), getDamageUpgradeAmount(), + getRange(), getRangeMotionBuffer(), isShowUI(), getTargetsAllowed(), getWeaponSound(), getWeaponType(), + getProjectileArc(), getProjectileArt(), isProjectileHomingEnabled(), getProjectileSpeed(), + this.damageSpillDistance, this.damageSpillRadius); + } + public float getDamageSpillDistance() { return this.damageSpillDistance; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileSplash.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileSplash.java index 017cbda..525efaf 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileSplash.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackMissileSplash.java @@ -39,6 +39,16 @@ public class CUnitAttackMissileSplash extends CUnitAttackMissile { this.damageFactorSmall = damageFactorSmall; } + @Override + public CUnitAttack copy() { + return new CUnitAttackMissileSplash(getAnimationBackswingPoint(), getAnimationDamagePoint(), getAttackType(), + getCooldownTime(), getDamageBase(), getDamageDice(), getDamageSidesPerDie(), getDamageUpgradeAmount(), + getRange(), getRangeMotionBuffer(), isShowUI(), getTargetsAllowed(), getWeaponSound(), getWeaponType(), + getProjectileArc(), getProjectileArt(), isProjectileHomingEnabled(), getProjectileSpeed(), + this.areaOfEffectFullDamage, this.areaOfEffectMediumDamage, this.areaOfEffectSmallDamage, + this.areaOfEffectTargets, this.damageFactorMedium, this.damageFactorSmall); + } + @Override public int getRange() { return super.getRange(); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackNormal.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackNormal.java index 2ce888e..301070c 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackNormal.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/combat/attacks/CUnitAttackNormal.java @@ -23,6 +23,13 @@ public class CUnitAttackNormal extends CUnitAttack { weaponType); } + @Override + public CUnitAttack copy() { + return new CUnitAttackNormal(getAnimationBackswingPoint(), getAnimationDamagePoint(), getAttackType(), + getCooldownTime(), getDamageBase(), getDamageDice(), getDamageSidesPerDie(), getDamageUpgradeAmount(), + getRange(), getRangeMotionBuffer(), isShowUI(), getTargetsAllowed(), getWeaponSound(), getWeaponType()); + } + @Override public void launch(final CSimulation simulation, final CUnit unit, final AbilityTarget target, final float damage, final CUnitAttackListener attackListener) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CAbilityData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CAbilityData.java index c5541b0..6a92634 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CAbilityData.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CAbilityData.java @@ -40,6 +40,9 @@ public class CAbilityData { CAbilityType abilityType = this.aliasToAbilityType.get(alias); if (abilityType == null) { final MutableGameObject mutableGameObject = this.abilityData.get(alias); + if (mutableGameObject == null) { + return null; + } final War3ID code = mutableGameObject.getCode(); final CAbilityTypeDefinition abilityTypeDefinition = this.codeToAbilityTypeDefinition.get(code); if (abilityTypeDefinition != null) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CItemData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CItemData.java new file mode 100644 index 0000000..9c740c5 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CItemData.java @@ -0,0 +1,8 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.data; + +import com.etheller.warsmash.units.manager.MutableObjectData; + +public class CItemData { + public CItemData(final MutableObjectData itemData) { + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java index a22a4c9..b566693 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java @@ -2,6 +2,7 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.data; import java.awt.image.BufferedImage; import java.util.ArrayList; +import java.util.Arrays; import java.util.EnumSet; import java.util.HashMap; import java.util.List; @@ -13,6 +14,7 @@ import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.MovementType; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.RemovablePathingMapInstance; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CGameplayConstants; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitClassification; @@ -28,6 +30,8 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAb import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityNightElfBuild; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityOrcBuild; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityUndeadBuild; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.hero.CAbilityHero; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.hero.CPrimaryAttribute; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityQueue; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityRally; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; @@ -53,6 +57,8 @@ public class CUnitData { private static final War3ID TURN_RATE = War3ID.fromString("umvr"); private static final War3ID IS_BLDG = War3ID.fromString("ubdg"); private static final War3ID NAME = War3ID.fromString("unam"); + private static final War3ID PROPER_NAMES = War3ID.fromString("upro"); + private static final War3ID PROPER_NAMES_COUNT = War3ID.fromString("upru"); private static final War3ID PROJECTILE_LAUNCH_X = War3ID.fromString("ulpx"); private static final War3ID PROJECTILE_LAUNCH_Y = War3ID.fromString("ulpy"); private static final War3ID PROJECTILE_LAUNCH_Z = War3ID.fromString("ulpz"); @@ -133,6 +139,7 @@ public class CUnitData { private static final War3ID TARGETED_AS = War3ID.fromString("utar"); private static final War3ID ABILITIES_NORMAL = War3ID.fromString("uabi"); + private static final War3ID ABILITIES_HERO = War3ID.fromString("uhab"); private static final War3ID STRUCTURES_BUILT = War3ID.fromString("ubui"); private static final War3ID UNITS_TRAINED = War3ID.fromString("utra"); @@ -151,13 +158,25 @@ public class CUnitData { private static final War3ID REQUIRE_PLACE = War3ID.fromString("upar"); private static final War3ID PREVENT_PLACE = War3ID.fromString("upap"); + private static final War3ID UNIT_LEVEL = War3ID.fromString("ulev"); + + private static final War3ID STR = War3ID.fromString("ustr"); + private static final War3ID STR_PLUS = War3ID.fromString("ustp"); + private static final War3ID AGI = War3ID.fromString("uagi"); + private static final War3ID AGI_PLUS = War3ID.fromString("uagp"); + private static final War3ID INT = War3ID.fromString("uint"); + private static final War3ID INT_PLUS = War3ID.fromString("uinp"); + private static final War3ID PRIMARY_ATTRIBUTE = War3ID.fromString("upra"); + + private final CGameplayConstants gameplayConstants; private final MutableObjectData unitData; private final Map unitIdToUnitType = new HashMap<>(); private final CAbilityData abilityData; private final SimulationRenderController simulationRenderController; - public CUnitData(final MutableObjectData unitData, final CAbilityData abilityData, - final SimulationRenderController simulationRenderController) { + public CUnitData(final CGameplayConstants gameplayConstants, final MutableObjectData unitData, + final CAbilityData abilityData, final SimulationRenderController simulationRenderController) { + this.gameplayConstants = gameplayConstants; this.unitData = unitData; this.abilityData = abilityData; this.simulationRenderController = simulationRenderController; @@ -174,14 +193,20 @@ public class CUnitData { final int manaInitial = unitTypeInstance.getManaInitial(); final int manaMaximum = unitTypeInstance.getManaMaximum(); final int speed = unitTypeInstance.getSpeed(); - final int defense = unitTypeInstance.getDefense(); final CUnit unit = new CUnit(handleId, playerIndex, x, y, life, typeId, facing, manaInitial, life, manaMaximum, - speed, defense, unitTypeInstance, pathingInstance); + speed, unitTypeInstance, pathingInstance); if (speed > 0) { unit.add(simulation, new CAbilityMove(handleIdAllocator.createId())); } - if (!unitTypeInstance.getAttacks().isEmpty()) { + if (unitTypeInstance.isHero()) { + final List heroAttacks = new ArrayList<>(); + for (final CUnitAttack attack : unitTypeInstance.getAttacks()) { + heroAttacks.add(attack.copy()); + } + unit.setUnitSpecificAttacks(heroAttacks); + } + if (!unit.getAttacks().isEmpty()) { unit.add(simulation, new CAbilityAttack(handleIdAllocator.createId())); } final List structuresBuilt = unitTypeInstance.getStructuresBuilt(); @@ -218,6 +243,12 @@ public class CUnitData { if (!unitsTrained.isEmpty()) { unit.add(simulation, new CAbilityRally(handleIdAllocator.createId())); } + if (unitTypeInstance.isHero()) { + final List heroAbilityList = unitTypeInstance.getHeroAbilityList(); + unit.add(simulation, new CAbilityHero(handleIdAllocator.createId(), heroAbilityList)); + // reset initial mana after the value is adjusted for hero data + unit.setMana(manaInitial); + } for (final String ability : unitTypeInstance.getAbilityList().split(",")) { if ((ability.length() > 0) && !"_".equals(ability)) { final CAbility createAbility = this.abilityData.createAbility(ability, handleIdAllocator.createId()); @@ -239,6 +270,8 @@ public class CUnitData { final int speed = unitType.getFieldAsInteger(MOVEMENT_SPEED_BASE, 0); final int defense = unitType.getFieldAsInteger(DEFENSE, 0); final String abilityList = unitType.getFieldAsString(ABILITIES_NORMAL, 0); + final String heroAbilityListString = unitType.getFieldAsString(ABILITIES_HERO, 0); + final int unitLevel = unitType.getFieldAsInteger(UNIT_LEVEL, 0); final float moveHeight = unitType.getFieldAsFloat(MOVE_HEIGHT, 0); final String movetp = unitType.getFieldAsString(MOVE_TYPE, 0); @@ -246,6 +279,22 @@ public class CUnitData { final float propWindow = unitType.getFieldAsFloat(PROPULSION_WINDOW, 0); final float turnRate = unitType.getFieldAsFloat(TURN_RATE, 0); + final float strPlus = unitType.getFieldAsFloat(STR_PLUS, 0); + final float agiPlus = unitType.getFieldAsFloat(AGI_PLUS, 0); + final float intPlus = unitType.getFieldAsFloat(INT_PLUS, 0); + + final int strength = unitType.getFieldAsInteger(STR, 0); + final int agility = unitType.getFieldAsInteger(AGI, 0); + final int intelligence = unitType.getFieldAsInteger(INT, 0); + if (typeId.toString().equals("Hjai")) { + System.out.println("bp"); + } + final CPrimaryAttribute primaryAttribute = CPrimaryAttribute + .parsePrimaryAttribute(unitType.getFieldAsString(PRIMARY_ATTRIBUTE, 0)); + + final String properNames = unitType.getFieldAsString(PROPER_NAMES, 0); + final int properNamesCount = unitType.getFieldAsInteger(PROPER_NAMES_COUNT, 0); + final boolean isBldg = unitType.getFieldAsBoolean(IS_BLDG, 0); PathingGrid.MovementType movementType = PathingGrid.getMovementType(movetp); if (movementType == null) { @@ -424,6 +473,14 @@ public class CUnitData { } } + final String[] heroAbilityListStringItems = heroAbilityListString.split(","); + final List heroAbilityList = new ArrayList<>(); + for (final String heroAbilityItem : heroAbilityListStringItems) { + if (heroAbilityItem.length() == 4) { + heroAbilityList.add(War3ID.fromString(heroAbilityItem)); + } + } + final String requirementsString = unitType.getFieldAsString(REQUIRES, 0); final String requirementsLevelsString = unitType.getFieldAsString(REQUIRES_AMOUNT, 0); final String[] requirementsStringItems = requirementsString.split(","); @@ -466,17 +523,38 @@ public class CUnitData { final String raceString = unitType.getFieldAsString(UNIT_RACE, 0); final CUnitRace unitRace = CUnitRace.parseRace(raceString); + final boolean hero = Character.isUpperCase(typeId.charAt(0)); + + final List heroProperNames = Arrays.asList(properNames.split(",")); + unitTypeInstance = new CUnitType(unitName, life, manaInitial, manaMaximum, speed, defense, abilityList, isBldg, movementType, moveHeight, collisionSize, classifications, attacks, armorType, raise, decay, defenseType, impactZ, buildingPathingPixelMap, deathTime, targetedAs, acquisitionRange, minimumAttackRange, structuresBuilt, unitsTrained, researchesAvailable, unitRace, goldCost, lumberCost, foodUsed, foodMade, buildTime, preventedPathingTypes, requiredPathingTypes, propWindow, - turnRate, requirements); + turnRate, requirements, unitLevel, hero, strength, strPlus, agility, agiPlus, intelligence, intPlus, + primaryAttribute, heroAbilityList, heroProperNames, properNamesCount); this.unitIdToUnitType.put(typeId, unitTypeInstance); } return unitTypeInstance; } + private static int[] populateHeroStatTable(final int maxHeroLevel, final float statPerLevel) { + final int[] table = new int[maxHeroLevel]; + float sumBonusAtLevel = 0f; + for (int i = 0; i < table.length; i++) { + final float newSumBonusAtLevel = sumBonusAtLevel + statPerLevel; + if (i == 0) { + table[i] = (int) newSumBonusAtLevel; + } + else { + table[i] = (int) newSumBonusAtLevel - table[i - 1]; + } + sumBonusAtLevel = newSumBonusAtLevel; + } + return table; + } + private CUnitAttack createAttack(final float animationBackswingPoint, final float animationDamagePoint, final int areaOfEffectFullDamage, final int areaOfEffectMediumDamage, final int areaOfEffectSmallDamage, final EnumSet areaOfEffectTargets, final CAttackType attackType, final float cooldownTime, diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java index 89c16dd..051df71 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java @@ -41,6 +41,8 @@ public interface SimulationRenderController { void spawnBuildingDeathEffect(CUnit cUnit); + void spawnGainLevelEffect(CUnit cUnit); + void spawnUnitReadySound(CUnit trainedUnit); void unitRepositioned(CUnit cUnit); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java index a3235ba..5f3948d 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java @@ -176,6 +176,14 @@ public class CommandCardIcon extends AbstractRenderableFrame implements Clickabl positionBounds(gameUI, uiViewport); } + @Override + public void mouseEnter(final GameUI gameUI, final Viewport uiViewport) { + } + + @Override + public void mouseExit(final GameUI gameUI, final Viewport uiViewport) { + } + @Override public UIFrame getFrameChildUnderMouse(final float screenX, final float screenY) { if (isVisible() && this.renderBounds.contains(screenX, screenY)) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java index 66e5524..04d5f1b 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java @@ -21,7 +21,6 @@ import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.GlyphLayout; import com.badlogic.gdx.graphics.g2d.SpriteBatch; -import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator; import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator.FreeTypeFontParameter; import com.badlogic.gdx.graphics.glutils.PixmapTextureData; import com.badlogic.gdx.math.Rectangle; @@ -82,6 +81,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.AbilityDataU import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.IconUI; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.commandbuttons.CommandButtonListener; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CDestructable; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CGameplayConstants; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CItem; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CPlayerStateListener; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; @@ -108,6 +108,8 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAb import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.combat.CAbilityColdArrows; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic.GenericNoIconAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic.GenericSingleIconActiveAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.hero.CAbilityHero; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.hero.CPrimaryAttribute; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityQueue; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityRally; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; @@ -154,7 +156,6 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma private static final AbilityPointTarget clickLocationTemp2 = new AbilityPointTarget(); private final DataSource dataSource; private final ExtendViewport uiViewport; - private final FreeTypeFontGenerator fontGenerator; private final Scene uiScene; private final Scene portraitScene; private final GameCameraManager cameraManager; @@ -189,6 +190,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma private StringFrame simpleClassValue; private StringFrame simpleBuildingActionLabel; private SimpleStatusBarFrame simpleBuildTimeIndicator; + private SimpleStatusBarFrame simpleHeroLevelBar; private UIFrame simpleInfoPanelBuildingDetail; private StringFrame simpleBuildingNameValue; @@ -213,6 +215,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma private InfoPanelIconBackdrops damageBackdrops; private InfoPanelIconBackdrops defenseBackdrops; + private UIFrame heroInfoPanel; + private final CommandCardIcon[][] commandCard = new CommandCardIcon[COMMAND_CARD_HEIGHT][COMMAND_CARD_WIDTH]; private RenderUnit selectedUnit; @@ -264,14 +268,18 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma private BitmapFont textTagFont; private SetPoint uberTipNoResourcesSetPoint; private SetPoint uberTipWithResourcesSetPoint; + private TextureFrame primaryAttributeIcon; + private StringFrame strengthValue; + private StringFrame agilityValue; + private StringFrame intelligenceValue; + private SimpleFrame smashHeroInfoPanelWrapper; - public MeleeUI(final DataSource dataSource, final ExtendViewport uiViewport, - final FreeTypeFontGenerator fontGenerator, final Scene uiScene, final Scene portraitScene, - final CameraPreset[] cameraPresets, final CameraRates cameraRates, final War3MapViewer war3MapViewer, - final RootFrameListener rootFrameListener, final CPlayerUnitOrderListener unitOrderListener) { + public MeleeUI(final DataSource dataSource, final ExtendViewport uiViewport, final Scene uiScene, + final Scene portraitScene, final CameraPreset[] cameraPresets, final CameraRates cameraRates, + final War3MapViewer war3MapViewer, final RootFrameListener rootFrameListener, + final CPlayerUnitOrderListener unitOrderListener) { this.dataSource = dataSource; this.uiViewport = uiViewport; - this.fontGenerator = fontGenerator; this.uiScene = uiScene; this.portraitScene = portraitScene; this.war3MapViewer = war3MapViewer; @@ -333,41 +341,40 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma // Load skins and templates // ================================= final CRace race = this.localPlayer.getRace(); - final int racialSkinIndex; + final String racialSkinKey; int racialCommandIndex; if (race == null) { - racialSkinIndex = 1; + racialSkinKey = "Human"; racialCommandIndex = 0; } else { switch (race) { case HUMAN: - racialSkinIndex = 1; + racialSkinKey = "Human"; racialCommandIndex = 0; break; case ORC: - racialSkinIndex = 0; + racialSkinKey = "Orc"; racialCommandIndex = 1; break; case NIGHTELF: - racialSkinIndex = 2; + racialSkinKey = "NightElf"; racialCommandIndex = 3; break; case UNDEAD: - racialSkinIndex = 3; + racialSkinKey = "Undead"; racialCommandIndex = 2; break; case DEMON: case OTHER: default: - racialSkinIndex = -1; + racialSkinKey = "Human"; racialCommandIndex = 0; break; } } - this.rootFrame = new GameUI(this.dataSource, GameUI.loadSkin(this.dataSource, racialSkinIndex), this.uiViewport, - this.fontGenerator, this.uiScene, this.war3MapViewer, racialCommandIndex, - this.war3MapViewer.getAllObjectData().getWts()); + this.rootFrame = new GameUI(this.dataSource, GameUI.loadSkin(this.dataSource, racialSkinKey), this.uiViewport, + this.uiScene, this.war3MapViewer, racialCommandIndex, this.war3MapViewer.getAllObjectData().getWts()); this.rootFrameListener.onCreate(this.rootFrame); try { this.rootFrame.loadTOCFile("UI\\FrameDef\\FrameDef.toc"); @@ -418,7 +425,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.unitManaText = (StringFrame) this.rootFrame.getFrameByName("UnitPortraitManaPointText", 0); final float infoPanelUnitDetailWidth = GameUI.convertY(this.uiViewport, 0.180f); - final float infoPanelUnitDetailHeight = GameUI.convertY(this.uiViewport, 0.110f); + final float infoPanelUnitDetailHeight = GameUI.convertY(this.uiViewport, 0.112f); this.smashSimpleInfoPanel = this.rootFrame.createSimpleFrame("SmashSimpleInfoPanel", this.rootFrame, 0); this.smashSimpleInfoPanel .addAnchor(new AnchorDefinition(FramePoint.BOTTOM, 0, GameUI.convertY(this.uiViewport, 0.0f))); @@ -442,6 +449,14 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.simpleBuildTimeIndicator.setWidth(buildTimeIndicatorWidth); this.simpleBuildTimeIndicator.setHeight(buildTimeIndicatorHeight); + this.simpleHeroLevelBar = (SimpleStatusBarFrame) this.rootFrame.getFrameByName("SimpleHeroLevelBar", 0); + final TextureFrame simpleHeroLevelBarBar = this.simpleHeroLevelBar.getBarFrame(); + simpleHeroLevelBarBar.setTexture("SimpleXpBarConsole", this.rootFrame); + simpleHeroLevelBarBar.setColor(new Color(138f / 255f, 0, 131f / 255f, 1f)); + final TextureFrame simpleHeroLevelBarBorder = this.simpleHeroLevelBar.getBorderFrame(); + simpleHeroLevelBarBorder.setTexture("SimpleXpBarBorder", this.rootFrame); + this.simpleHeroLevelBar.setWidth(infoPanelUnitDetailWidth); + // Create Simple Info Panel Building Detail this.simpleInfoPanelBuildingDetail = this.rootFrame.createSimpleFrame("SimpleInfoPanelBuildingDetail", this.smashSimpleInfoPanel, 0); @@ -515,7 +530,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.smashAttack1IconWrapper = (SimpleFrame) this.rootFrame.createSimpleFrame("SmashSimpleInfoPanelIconDamage", this.simpleInfoPanelUnitDetail, 0); this.smashAttack1IconWrapper.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, - FramePoint.TOPLEFT, 0, GameUI.convertY(this.uiViewport, -0.030125f))); + FramePoint.TOPLEFT, 0, GameUI.convertY(this.uiViewport, -0.032f))); this.smashAttack1IconWrapper.setWidth(GameUI.convertX(this.uiViewport, 0.1f)); this.smashAttack1IconWrapper.setHeight(GameUI.convertY(this.uiViewport, 0.030125f)); this.attack1Icon = this.rootFrame.createSimpleFrame("SimpleInfoPanelIconDamage", this.smashAttack1IconWrapper, @@ -528,7 +543,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.simpleInfoPanelUnitDetail, 0); this.smashAttack2IconWrapper .addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, FramePoint.TOPLEFT, - GameUI.convertX(this.uiViewport, 0.1f), GameUI.convertY(this.uiViewport, -0.030125f))); + GameUI.convertX(this.uiViewport, 0.1f), GameUI.convertY(this.uiViewport, -0.03125f))); this.smashAttack2IconWrapper.setWidth(GameUI.convertX(this.uiViewport, 0.1f)); this.smashAttack2IconWrapper.setHeight(GameUI.convertY(this.uiViewport, 0.030125f)); this.attack2Icon = this.rootFrame.createSimpleFrame("SimpleInfoPanelIconDamage", this.smashAttack2IconWrapper, @@ -540,7 +555,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.smashArmorIconWrapper = (SimpleFrame) this.rootFrame.createSimpleFrame("SmashSimpleInfoPanelIconArmor", this.simpleInfoPanelUnitDetail, 0); this.smashArmorIconWrapper.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, - FramePoint.TOPLEFT, GameUI.convertX(this.uiViewport, 0f), GameUI.convertY(this.uiViewport, -0.06025f))); + FramePoint.TOPLEFT, GameUI.convertX(this.uiViewport, 0f), GameUI.convertY(this.uiViewport, -0.0625f))); this.smashArmorIconWrapper.setWidth(GameUI.convertX(this.uiViewport, 0.1f)); this.smashArmorIconWrapper.setHeight(GameUI.convertY(this.uiViewport, 0.030125f)); this.armorIcon = this.rootFrame.createSimpleFrame("SimpleInfoPanelIconArmor", this.smashArmorIconWrapper, 0); @@ -548,6 +563,19 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.armorInfoPanelIconValue = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconValue", 0); this.armorInfoPanelIconLevel = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconLevel", 0); + this.smashHeroInfoPanelWrapper = (SimpleFrame) this.rootFrame.createSimpleFrame("SmashSimpleInfoPanelIconHero", + this.simpleInfoPanelUnitDetail, 0); + this.smashHeroInfoPanelWrapper.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, + FramePoint.TOPLEFT, GameUI.convertX(this.uiViewport, 0.1f), GameUI.convertY(this.uiViewport, -0.029f))); + this.smashHeroInfoPanelWrapper.setWidth(GameUI.convertX(this.uiViewport, 0.1f)); + this.smashHeroInfoPanelWrapper.setHeight(GameUI.convertY(this.uiViewport, 0.0625f)); + this.heroInfoPanel = this.rootFrame.createSimpleFrame("SimpleInfoPanelIconHero", this.smashHeroInfoPanelWrapper, + 0); + this.primaryAttributeIcon = (TextureFrame) this.rootFrame.getFrameByName("InfoPanelIconHeroIcon", 0); + this.strengthValue = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconHeroStrengthValue", 0); + this.agilityValue = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconHeroAgilityValue", 0); + this.intelligenceValue = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconHeroIntellectValue", 0); + this.inventoryCover = this.rootFrame.createSimpleFrame("SmashConsoleInventoryCover", this.rootFrame, 0); final Element fontHeights = this.war3MapViewer.miscData.get("FontHeights"); @@ -676,7 +704,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma final FreeTypeFontParameter fontParam = new FreeTypeFontParameter(); fontParam.size = (int) GameUI.convertY(this.uiViewport, 0.012f); - this.textTagFont = this.fontGenerator.generateFont(fontParam); + this.textTagFont = this.rootFrame.getFontGenerator().generateFont(fontParam); this.rootFrame.positionBounds(this.rootFrame, this.uiViewport); @@ -876,10 +904,16 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } } - public void render(final SpriteBatch batch, final BitmapFont font20, final GlyphLayout glyphLayout) { - this.rootFrame.render(batch, font20, glyphLayout); + public void render(final SpriteBatch batch, final GlyphLayout glyphLayout) { + final BitmapFont font = this.rootFrame.getFont(); + font.setColor(Color.YELLOW); + final String fpsString = "FPS: " + Gdx.graphics.getFramesPerSecond(); + glyphLayout.setText(font, fpsString); + font.draw(batch, fpsString, (this.uiViewport.getMinWorldWidth() - glyphLayout.width) / 2, + 1100 * this.heightRatioCorrection); + this.rootFrame.render(batch, this.rootFrame.getFont20(), glyphLayout); if (this.selectedUnit != null) { - font20.setColor(Color.WHITE); + this.rootFrame.getFont20().setColor(Color.WHITE); } @@ -891,7 +925,10 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma if (this.war3MapViewer.worldScene.camera.rect.contains(screenCoordsVector.x, (Gdx.graphics.getHeight() - screenCoordsVector.y) + textTag.getScreenCoordsZHeight())) { final Vector2 unprojected = this.uiViewport.unproject(screenCoordsVector); - this.textTagFont.setColor(textTag.getColor()); + final float remainingLife = textTag.getRemainingLife(); + final float alpha = (remainingLife > 1.0f ? 1.0f : remainingLife); + this.textTagFont.setColor(textTag.getColor().r, textTag.getColor().g, textTag.getColor().b, + textTag.getColor().a * alpha); glyphLayout.setText(this.textTagFont, textTag.getText()); this.textTagFont.draw(batch, textTag.getText(), unprojected.x - (glyphLayout.width / 2), (unprojected.y - (glyphLayout.height / 2)) + textTag.getScreenCoordsZHeight()); @@ -917,8 +954,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma public Void accept(final CAbilityAttack ability) { if (MeleeUI.this.activeCommandOrderId == OrderIds.attackground) { float radius = 0; - for (final CUnitAttack attack : MeleeUI.this.activeCommandUnit.getSimulationUnit().getUnitType() - .getAttacks()) { + for (final CUnitAttack attack : MeleeUI.this.activeCommandUnit.getSimulationUnit().getAttacks()) { if (attack.getWeaponType().isAttackGroundSupported()) { if (attack instanceof CUnitAttackMissileSplash) { final int areaOfEffectSmallDamage = ((CUnitAttackMissileSplash) attack) @@ -1020,6 +1056,12 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma return null; } + @Override + public Void accept(final CAbilityHero ability) { + handleTargetCursor(ability); + return null; + } + private void handleTargetCursor(final CAbility ability) { if (MeleeUI.this.cursorModelInstance != null) { MeleeUI.this.cursorModelInstance.detach(); @@ -1333,6 +1375,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.armorIcon.setVisible(false); this.rootFrame.setText(this.armorInfoPanelIconLevel, ""); this.simpleBuildTimeIndicator.setVisible(false); + this.simpleHeroLevelBar.setVisible(false); this.simpleBuildingBuildTimeIndicator.setVisible(false); this.simpleInfoPanelBuildingDetail.setVisible(false); this.simpleInfoPanelUnitDetail.setVisible(false); @@ -1340,6 +1383,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma queueIconFrame.setVisible(false); } this.selectWorkerInsideFrame.setVisible(false); + this.heroInfoPanel.setVisible(false); this.rallyPointInstance.hide(); this.rallyPointInstance.detach(); repositionWaypointFlags(null); @@ -1491,6 +1535,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.simpleBuildingBuildTimeIndicator.setVisible(true); this.simpleBuildTimeIndicator.setVisible(false); + this.simpleHeroLevelBar.setVisible(false); if (simulationUnit.getBuildQueueTypes()[0] == QueueItemType.UNIT) { this.rootFrame.setText(this.simpleBuildingBuildingActionLabel, this.rootFrame.getTemplates().getDecoratedString("TRAINING")); @@ -1502,6 +1547,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.attack1Icon.setVisible(false); this.attack2Icon.setVisible(false); this.armorIcon.setVisible(false); + this.heroInfoPanel.setVisible(false); this.selectWorkerInsideFrame.setVisible(false); } else { @@ -1510,41 +1556,35 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } this.simpleInfoPanelBuildingDetail.setVisible(false); this.simpleInfoPanelUnitDetail.setVisible(true); - this.rootFrame.setText(this.simpleNameValue, simulationUnit.getUnitType().getName()); - String classText = null; - for (final CUnitClassification classification : simulationUnit.getClassifications()) { - if ((classification == CUnitClassification.MECHANICAL) && simulationUnit.getUnitType().isBuilding()) { - // buildings dont display MECHANICAL - continue; - } - if (classification.getDisplayName() != null) { - classText = classification.getDisplayName(); - } - } - if (classText != null) { - this.rootFrame.setText(this.simpleClassValue, classText); - } - else { - this.rootFrame.setText(this.simpleClassValue, ""); - } + final String unitTypeName = simulationUnit.getUnitType().getName(); - final boolean anyAttacks = simulationUnit.getUnitType().getAttacks().size() > 0; + final boolean anyAttacks = simulationUnit.getAttacks().size() > 0; final boolean constructing = simulationUnit.isConstructing(); final UIFrame localArmorIcon = this.armorIcon; final TextureFrame localArmorIconBackdrop = this.armorIconBackdrop; final StringFrame localArmorInfoPanelIconValue = this.armorInfoPanelIconValue; if (anyAttacks && !constructing) { - final CUnitAttack attackOne = simulationUnit.getUnitType().getAttacks().get(0); + final CUnitAttack attackOne = simulationUnit.getAttacks().get(0); this.attack1Icon.setVisible(attackOne.isShowUI()); this.attack1IconBackdrop.setTexture(this.damageBackdrops.getTexture(attackOne.getAttackType())); - this.rootFrame.setText(this.attack1InfoPanelIconValue, - attackOne.getMinDamage() + " - " + attackOne.getMaxDamage()); - if (simulationUnit.getUnitType().getAttacks().size() > 1) { - final CUnitAttack attackTwo = simulationUnit.getUnitType().getAttacks().get(1); + String attackOneDmgText = attackOne.getMinDamageDisplay() + " - " + attackOne.getMaxDamageDisplay(); + final int attackOneTemporaryDamageBonus = attackOne.getTemporaryDamageBonus(); + if (attackOneTemporaryDamageBonus != 0) { + attackOneDmgText += (attackOneTemporaryDamageBonus > 0 ? "|cFF00FF00 (+" : "|cFFFF0000 (+") + + attackOneTemporaryDamageBonus + ")"; + } + this.rootFrame.setText(this.attack1InfoPanelIconValue, attackOneDmgText); + if (simulationUnit.getAttacks().size() > 1) { + final CUnitAttack attackTwo = simulationUnit.getAttacks().get(1); this.attack2Icon.setVisible(attackTwo.isShowUI()); this.attack2IconBackdrop.setTexture(this.damageBackdrops.getTexture(attackTwo.getAttackType())); - this.rootFrame.setText(this.attack2InfoPanelIconValue, - attackTwo.getMinDamage() + " - " + attackTwo.getMaxDamage()); + String attackTwoDmgText = attackTwo.getMinDamage() + " - " + attackTwo.getMaxDamage(); + final int attackTwoTemporaryDamageBonus = attackTwo.getTemporaryDamageBonus(); + if (attackTwoTemporaryDamageBonus != 0) { + attackTwoDmgText += (attackTwoTemporaryDamageBonus > 0 ? "|cFF00FF00 (+" : "|cFFFF0000 (+") + + attackTwoTemporaryDamageBonus + ")"; + } + this.rootFrame.setText(this.attack2InfoPanelIconValue, attackTwoDmgText); } else { this.attack2Icon.setVisible(false); @@ -1552,7 +1592,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.smashArmorIconWrapper.addSetPoint( new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, FramePoint.TOPLEFT, - GameUI.convertX(this.uiViewport, 0f), GameUI.convertY(this.uiViewport, -0.06025f))); + GameUI.convertX(this.uiViewport, 0f), GameUI.convertY(this.uiViewport, -0.0625f))); this.smashArmorIconWrapper.positionBounds(this.rootFrame, this.uiViewport); this.armorIcon.positionBounds(this.rootFrame, this.uiViewport); } @@ -1562,11 +1602,67 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.smashArmorIconWrapper.addSetPoint( new SetPoint(FramePoint.TOPLEFT, this.simpleInfoPanelUnitDetail, FramePoint.TOPLEFT, - GameUI.convertX(this.uiViewport, 0f), GameUI.convertY(this.uiViewport, -0.030125f))); + GameUI.convertX(this.uiViewport, 0f), GameUI.convertY(this.uiViewport, -0.032f))); this.smashArmorIconWrapper.positionBounds(this.rootFrame, this.uiViewport); this.armorIcon.positionBounds(this.rootFrame, this.uiViewport); } + final CAbilityHero heroData = simulationUnit.getHeroData(); + final boolean hero = heroData != null; + this.heroInfoPanel.setVisible(hero); + if (hero) { + final CPrimaryAttribute primaryAttribute = simulationUnit.getUnitType().getPrimaryAttribute(); + String iconKey; + switch (primaryAttribute) { + case AGILITY: + iconKey = "InfoPanelIconHeroIconAGI"; + break; + case INTELLIGENCE: + iconKey = "InfoPanelIconHeroIconINT"; + break; + default: + case STRENGTH: + iconKey = "InfoPanelIconHeroIconSTR"; + break; + } + this.primaryAttributeIcon.setTexture(iconKey, this.rootFrame); + + this.rootFrame.setText(this.strengthValue, heroData.getStrength().getDisplayText()); + this.rootFrame.setText(this.agilityValue, heroData.getAgility().getDisplayText()); + this.rootFrame.setText(this.intelligenceValue, heroData.getIntelligence().getDisplayText()); + final String infopanelLevelClass = this.rootFrame.getTemplates() + .getDecoratedString("INFOPANEL_LEVEL_CLASS").replace("%u", "%d"); // :( + final int heroLevel = heroData.getHeroLevel(); + this.rootFrame.setText(this.simpleClassValue, + String.format(infopanelLevelClass, heroLevel, unitTypeName)); + this.rootFrame.setText(this.simpleNameValue, heroData.getProperName()); + this.simpleHeroLevelBar.setVisible(true); + final CGameplayConstants gameplayConstants = this.war3MapViewer.simulation.getGameplayConstants(); + this.simpleHeroLevelBar.setValue((heroData.getXp() - gameplayConstants.getNeedHeroXPSum(heroLevel - 1)) + / (float) gameplayConstants.getNeedHeroXP(heroLevel)); + } + else { + this.rootFrame.setText(this.simpleNameValue, unitTypeName); + String classText = null; + for (final CUnitClassification classification : simulationUnit.getClassifications()) { + if ((classification == CUnitClassification.MECHANICAL) + && simulationUnit.getUnitType().isBuilding()) { + // buildings dont display MECHANICAL + continue; + } + if (classification.getDisplayName() != null) { + classText = classification.getDisplayName(); + } + } + if (classText != null) { + this.rootFrame.setText(this.simpleClassValue, classText); + } + else { + this.rootFrame.setText(this.simpleClassValue, ""); + } + this.simpleHeroLevelBar.setVisible(false); + } + localArmorIcon.setVisible(!constructing); this.simpleBuildTimeIndicator.setVisible(constructing); this.simpleBuildingBuildTimeIndicator.setVisible(false); @@ -1574,13 +1670,13 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.rootFrame.setText(this.simpleBuildingActionLabel, this.rootFrame.getTemplates().getDecoratedString("CONSTRUCTING")); this.queueIconFrames[0].setVisible(true); - this.queueIconFrames[0].setTexture(this.war3MapViewer.getAbilityDataUI() - .getUnitUI(this.selectedUnit.getSimulationUnit().getTypeId()).getIcon()); + this.queueIconFrames[0].setTexture( + this.war3MapViewer.getAbilityDataUI().getUnitUI(simulationUnit.getTypeId()).getIcon()); - if (this.selectedUnit.getSimulationUnit().getWorkerInside() != null) { + if (simulationUnit.getWorkerInside() != null) { this.selectWorkerInsideFrame.setVisible(true); this.selectWorkerInsideFrame.setTexture(this.war3MapViewer.getAbilityDataUI() - .getUnitUI(this.selectedUnit.getSimulationUnit().getWorkerInside().getTypeId()).getIcon()); + .getUnitUI(simulationUnit.getWorkerInside().getTypeId()).getIcon()); } else { this.selectWorkerInsideFrame.setVisible(false); @@ -1596,7 +1692,18 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma throw new RuntimeException(simulationUnit.getUnitType().getDefenseType() + " can't find texture!"); } localArmorIconBackdrop.setTexture(defenseTexture); - this.rootFrame.setText(localArmorInfoPanelIconValue, Integer.toString(simulationUnit.getDefense())); + + String defenseDisplayString = Integer.toString(simulationUnit.getCurrentDefenseDisplay()); + final int temporaryDefenseBonus = simulationUnit.getTemporaryDefenseBonus(); + if (temporaryDefenseBonus != 0) { + if (temporaryDefenseBonus > 0) { + defenseDisplayString += "|cFF00FF00 (+" + temporaryDefenseBonus + ")"; + } + else { + defenseDisplayString += "|cFFFF0000 (+" + temporaryDefenseBonus + ")"; + } + } + this.rootFrame.setText(localArmorInfoPanelIconValue, defenseDisplayString); } clearAndRepopulateCommandCard(); } @@ -1753,6 +1860,11 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } } + @Override + public void heroStatsChanged() { + reloadSelectedUnitUI(this.selectedUnit); + } + @Override public void queueChanged() { reloadSelectedUnitUI(this.selectedUnit); @@ -2191,4 +2303,10 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.unitOrderListener.unitCancelTrainingItem(simulationUnit.getHandleId(), index); } } + + public void dispose() { + if (this.rootFrame != null) { + this.rootFrame.dispose(); + } + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MenuUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MenuUI.java index c3d5113..58ad74f 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MenuUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MenuUI.java @@ -1,23 +1,37 @@ package com.etheller.warsmash.viewer5.handlers.w3x.ui; import java.io.IOException; +import java.io.InputStream; import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.GlyphLayout; import com.badlogic.gdx.graphics.g2d.SpriteBatch; -import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.utils.viewport.ExtendViewport; +import com.etheller.warsmash.WarsmashGdxMapScreen; +import com.etheller.warsmash.WarsmashGdxMenuScreen; +import com.etheller.warsmash.WarsmashGdxMultiScreenGame; import com.etheller.warsmash.datasources.DataSource; import com.etheller.warsmash.parsers.fdf.GameUI; import com.etheller.warsmash.parsers.fdf.datamodel.FramePoint; +import com.etheller.warsmash.parsers.fdf.frames.GlueButtonFrame; +import com.etheller.warsmash.parsers.fdf.frames.GlueTextButtonFrame; +import com.etheller.warsmash.parsers.fdf.frames.SetPoint; import com.etheller.warsmash.parsers.fdf.frames.SpriteFrame; +import com.etheller.warsmash.parsers.fdf.frames.StringFrame; import com.etheller.warsmash.parsers.fdf.frames.UIFrame; import com.etheller.warsmash.parsers.jass.Jass2.RootFrameListener; +import com.etheller.warsmash.units.DataTable; import com.etheller.warsmash.units.custom.WTS; +import com.etheller.warsmash.util.WarsmashConstants; +import com.etheller.warsmash.util.WorldEditStrings; import com.etheller.warsmash.viewer5.Scene; import com.etheller.warsmash.viewer5.handlers.mdx.MdxViewer; +import com.etheller.warsmash.viewer5.handlers.w3x.UnitSound; +import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.ClickableFrame; +import com.etheller.warsmash.viewer5.handlers.w3x.ui.sound.KeyedSounds; public class MenuUI { private static final Vector2 screenCoordsVector = new Vector2(); @@ -25,7 +39,6 @@ public class MenuUI { private final DataSource dataSource; private final Scene uiScene; private final ExtendViewport uiViewport; - private final FreeTypeFontGenerator fontGenerator; private final MdxViewer viewer; private final RootFrameListener rootFrameListener; private final float widthRatioCorrection; @@ -33,20 +46,61 @@ public class MenuUI { private GameUI rootFrame; private SpriteFrame cursorFrame; + private ClickableFrame mouseDownUIFrame; + private ClickableFrame mouseOverUIFrame; + private UIFrame mainMenuFrame; private SpriteFrame glueSpriteLayerTopRight; private SpriteFrame glueSpriteLayerTopLeft; - public MenuUI(final DataSource dataSource, final ExtendViewport uiViewport, - final FreeTypeFontGenerator fontGenerator, final Scene uiScene, final MdxViewer viewer, + private WorldEditStrings worldEditStrings; + + private DataTable uiSoundsTable; + + private KeyedSounds uiSounds; + + private GlueTextButtonFrame singlePlayerButton; + private GlueTextButtonFrame battleNetButton; + private GlueTextButtonFrame localAreaNetworkButton; + private GlueTextButtonFrame optionsButton; + private GlueTextButtonFrame creditsButton; + private GlueButtonFrame realmButton; + private GlueTextButtonFrame exitButton; + + private final boolean quitting = false; + + private MenuState menuState; + + private UIFrame singlePlayerMenu; + + private UIFrame profilePanel; + + private GlueButtonFrame profileButton; + private GlueTextButtonFrame campaignButton; + private GlueTextButtonFrame loadSavedButton; + private GlueTextButtonFrame viewReplayButton; + private GlueTextButtonFrame customCampaignButton; + private GlueTextButtonFrame skirmishButton; + private GlueTextButtonFrame cancelButton; + private GlueButtonFrame editionButton; + + private final WarsmashGdxMultiScreenGame screenManager; + + private final DataTable warsmashIni; + + private UnitSound glueScreenLoop; + + public MenuUI(final DataSource dataSource, final ExtendViewport uiViewport, final Scene uiScene, + final MdxViewer viewer, final WarsmashGdxMultiScreenGame screenManager, final DataTable warsmashIni, final RootFrameListener rootFrameListener) { this.dataSource = dataSource; this.uiViewport = uiViewport; - this.fontGenerator = fontGenerator; this.uiScene = uiScene; this.viewer = viewer; + this.screenManager = screenManager; + this.warsmashIni = warsmashIni; this.rootFrameListener = rootFrameListener; this.widthRatioCorrection = this.uiViewport.getMinWorldWidth() / 1600f; @@ -65,8 +119,9 @@ public class MenuUI { // ================================= // Load skins and templates // ================================= - this.rootFrame = new GameUI(this.dataSource, GameUI.loadSkin(this.dataSource, 1), this.uiViewport, - this.fontGenerator, this.uiScene, this.viewer, 0, WTS.DO_NOTHING); + this.rootFrame = new GameUI(this.dataSource, GameUI.loadSkin(this.dataSource, WarsmashConstants.GAME_VERSION), + this.uiViewport, this.uiScene, this.viewer, 0, WTS.DO_NOTHING); + this.rootFrameListener.onCreate(this.rootFrame); try { this.rootFrame.loadTOCFile("UI\\FrameDef\\FrameDef.toc"); @@ -80,23 +135,31 @@ public class MenuUI { catch (final IOException exc) { throw new IllegalStateException("Unable to load SmashFrameDef.toc", exc); } + + // Create main menu this.mainMenuFrame = this.rootFrame.createFrame("MainMenuFrame", this.rootFrame, 0, 0); + this.mainMenuFrame.setVisible(false); final SpriteFrame warcraftIIILogo = (SpriteFrame) this.rootFrame.getFrameByName("WarCraftIIILogo", 0); - this.rootFrame.setSpriteFrameModel(warcraftIIILogo, this.rootFrame.getSkinField("MainMenuLogo_V1")); + this.rootFrame.setSpriteFrameModel(warcraftIIILogo, + this.rootFrame.getSkinField("MainMenuLogo_V" + WarsmashConstants.GAME_VERSION)); + warcraftIIILogo.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.mainMenuFrame, FramePoint.TOPLEFT, + GameUI.convertX(this.uiViewport, 0.13f), GameUI.convertY(this.uiViewport, -0.08f))); this.rootFrame.getFrameByName("RealmSelect", 0).setVisible(false); this.glueSpriteLayerTopRight = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", "SmashGlueSpriteLayerTopRight", this.rootFrame, "", 0); this.glueSpriteLayerTopRight.setSetAllPoints(true); - final String topRightModel = this.rootFrame.getSkinField("GlueSpriteLayerTopRight_V1"); + final String topRightModel = this.rootFrame + .getSkinField("GlueSpriteLayerTopRight_V" + WarsmashConstants.GAME_VERSION); this.rootFrame.setSpriteFrameModel(this.glueSpriteLayerTopRight, topRightModel); this.glueSpriteLayerTopRight.setSequence("MainMenu Birth"); this.glueSpriteLayerTopLeft = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", "SmashGlueSpriteLayerTopLeft", this.rootFrame, "", 0); this.glueSpriteLayerTopLeft.setSetAllPoints(true); - final String topLeftModel = this.rootFrame.getSkinField("GlueSpriteLayerTopLeft_V1"); + final String topLeftModel = this.rootFrame + .getSkinField("GlueSpriteLayerTopLeft_V" + WarsmashConstants.GAME_VERSION); this.rootFrame.setSpriteFrameModel(this.glueSpriteLayerTopLeft, topLeftModel); this.glueSpriteLayerTopLeft.setSequence("MainMenu Birth"); @@ -107,15 +170,126 @@ public class MenuUI { this.cursorFrame.setZDepth(-1.0f); Gdx.input.setCursorCatched(true); + // Create single player + this.singlePlayerMenu = this.rootFrame.createFrame("SinglePlayerMenu", this.rootFrame, 0, 0); + this.singlePlayerMenu.setVisible(false); + + this.profilePanel = this.rootFrame.getFrameByName("ProfilePanel", 0); + this.profilePanel.setVisible(false); + + // position all this.rootFrame.positionBounds(this.rootFrame, this.uiViewport); + // Main Menu + this.singlePlayerButton = (GlueTextButtonFrame) this.rootFrame.getFrameByName("SinglePlayerButton", 0); + this.battleNetButton = (GlueTextButtonFrame) this.rootFrame.getFrameByName("BattleNetButton", 0); + this.realmButton = (GlueButtonFrame) this.rootFrame.getFrameByName("RealmButton", 0); + this.localAreaNetworkButton = (GlueTextButtonFrame) this.rootFrame.getFrameByName("LocalAreaNetworkButton", 0); + this.optionsButton = (GlueTextButtonFrame) this.rootFrame.getFrameByName("OptionsButton", 0); + this.creditsButton = (GlueTextButtonFrame) this.rootFrame.getFrameByName("CreditsButton", 0); + this.exitButton = (GlueTextButtonFrame) this.rootFrame.getFrameByName("ExitButton", 0); + this.editionButton = (GlueButtonFrame) this.rootFrame.getFrameByName("EditionButton", 0); + + if (this.editionButton != null) { + this.editionButton.setOnClick(new Runnable() { + @Override + public void run() { + WarsmashConstants.GAME_VERSION = (WarsmashConstants.GAME_VERSION == 1 ? 0 : 1); + MenuUI.this.glueSpriteLayerTopLeft.setSequence("MainMenu Death"); + MenuUI.this.glueSpriteLayerTopRight.setSequence("MainMenu Death"); + MenuUI.this.mainMenuFrame.setVisible(false); + MenuUI.this.menuState = MenuState.RESTARTING; + } + }); + } + + this.battleNetButton.setEnabled(false); + this.realmButton.setEnabled(false); + this.localAreaNetworkButton.setEnabled(false); + this.optionsButton.setEnabled(false); + this.creditsButton.setEnabled(false); + + this.exitButton.setOnClick(new Runnable() { + @Override + public void run() { + MenuUI.this.glueSpriteLayerTopLeft.setSequence("MainMenu Death"); + MenuUI.this.glueSpriteLayerTopRight.setSequence("MainMenu Death"); + MenuUI.this.mainMenuFrame.setVisible(false); + MenuUI.this.menuState = MenuState.QUITTING; + } + }); + + this.singlePlayerButton.setOnClick(new Runnable() { + @Override + public void run() { + MenuUI.this.glueSpriteLayerTopLeft.setSequence("MainMenu Death"); + MenuUI.this.glueSpriteLayerTopRight.setSequence("MainMenu Death"); + MenuUI.this.mainMenuFrame.setVisible(false); + MenuUI.this.menuState = MenuState.GOING_TO_SINGLE_PLAYER; + } + }); + + // Single Player + this.profileButton = (GlueButtonFrame) this.rootFrame.getFrameByName("ProfileButton", 0); + this.campaignButton = (GlueTextButtonFrame) this.rootFrame.getFrameByName("CampaignButton", 0); + this.loadSavedButton = (GlueTextButtonFrame) this.rootFrame.getFrameByName("LoadSavedButton", 0); + this.viewReplayButton = (GlueTextButtonFrame) this.rootFrame.getFrameByName("ViewReplayButton", 0); + this.customCampaignButton = (GlueTextButtonFrame) this.rootFrame.getFrameByName("CustomCampaignButton", 0); + this.skirmishButton = (GlueTextButtonFrame) this.rootFrame.getFrameByName("SkirmishButton", 0); + + this.cancelButton = (GlueTextButtonFrame) this.rootFrame.getFrameByName("CancelButton", 0); + + final StringFrame profileNameText = (StringFrame) this.rootFrame.getFrameByName("ProfileNameText", 0); + this.rootFrame.setText(profileNameText, "WorldEdit"); + + this.profileButton.setEnabled(false); + this.loadSavedButton.setEnabled(false); + this.viewReplayButton.setEnabled(false); + this.customCampaignButton.setEnabled(false); + this.skirmishButton.setEnabled(false); + + this.campaignButton.setOnClick(new Runnable() { + @Override + public void run() { + MenuUI.this.glueSpriteLayerTopLeft.setSequence("SinglePlayer Death"); + MenuUI.this.glueSpriteLayerTopRight.setSequence("SinglePlayer Death"); + MenuUI.this.singlePlayerMenu.setVisible(false); + MenuUI.this.menuState = MenuState.GOING_TO_CAMPAIGN; + } + }); + + this.cancelButton.setOnClick(new Runnable() { + @Override + public void run() { + MenuUI.this.glueSpriteLayerTopLeft.setSequence("SinglePlayer Death"); + MenuUI.this.glueSpriteLayerTopRight.setSequence("SinglePlayer Death"); + MenuUI.this.singlePlayerMenu.setVisible(false); + MenuUI.this.menuState = MenuState.GOING_TO_MAIN_MENU; + + } + }); + + this.menuState = MenuState.MAIN_MENU; + + loadSounds(); + + final String glueLoopField = this.rootFrame.getSkinField("GlueScreenLoop_V" + WarsmashConstants.GAME_VERSION); + this.glueScreenLoop = this.uiSounds.getSound(glueLoopField); + this.glueScreenLoop.play(this.uiScene.audioContext, 0f, 0f, 0f); } public void resize() { } - public void render(final SpriteBatch batch, final BitmapFont font20, final GlyphLayout glyphLayout) { + public void render(final SpriteBatch batch, final GlyphLayout glyphLayout) { + final BitmapFont font = this.rootFrame.getFont(); + final BitmapFont font20 = this.rootFrame.getFont20(); + font.setColor(Color.YELLOW); + final String fpsString = "FPS: " + Gdx.graphics.getFramesPerSecond(); + glyphLayout.setText(font, fpsString); + font.draw(batch, fpsString, (this.uiViewport.getMinWorldWidth() - glyphLayout.width) / 2, + 1100 * this.heightRatioCorrection); this.rootFrame.render(batch, font20, glyphLayout); } @@ -143,7 +317,140 @@ public class MenuUI { this.cursorFrame.setSequence("Normal"); if (this.glueSpriteLayerTopRight.isSequenceEnded()) { - this.glueSpriteLayerTopRight.setSequence("MainMenu Stand"); + switch (this.menuState) { + case GOING_TO_MAIN_MENU: + this.glueSpriteLayerTopLeft.setSequence("MainMenu Birth"); + this.glueSpriteLayerTopRight.setSequence("MainMenu Birth"); + this.menuState = MenuState.MAIN_MENU; + break; + case MAIN_MENU: + this.mainMenuFrame.setVisible(true); + this.glueSpriteLayerTopLeft.setSequence("MainMenu Stand"); + this.glueSpriteLayerTopRight.setSequence("MainMenu Stand"); + break; + case GOING_TO_SINGLE_PLAYER: + this.glueSpriteLayerTopLeft.setSequence("SinglePlayer Birth"); + this.glueSpriteLayerTopRight.setSequence("SinglePlayer Birth"); + this.menuState = MenuState.SINGLE_PLAYER; + break; + case SINGLE_PLAYER: + this.singlePlayerMenu.setVisible(true); + this.glueSpriteLayerTopLeft.setSequence("SinglePlayer Stand"); + this.glueSpriteLayerTopRight.setSequence("SinglePlayer Stand"); + break; + case GOING_TO_CAMPAIGN: + MenuUI.this.screenManager.setScreen(new WarsmashGdxMapScreen(MenuUI.this.warsmashIni, + this.warsmashIni.get("Map").getField("FilePath"))); + break; + case QUITTING: + Gdx.app.exit(); + break; + case RESTARTING: + MenuUI.this.screenManager + .setScreen(new WarsmashGdxMenuScreen(MenuUI.this.warsmashIni, this.screenManager)); + break; + default: + break; + } + } + + } + + public boolean touchDown(final int screenX, final int screenY, final float worldScreenY, final int button) { + screenCoordsVector.set(screenX, screenY); + this.uiViewport.unproject(screenCoordsVector); + final UIFrame clickedUIFrame = this.rootFrame.touchDown(screenCoordsVector.x, screenCoordsVector.y, button); + if (clickedUIFrame != null) { + if (clickedUIFrame instanceof ClickableFrame) { + this.mouseDownUIFrame = (ClickableFrame) clickedUIFrame; + this.mouseDownUIFrame.mouseDown(this.rootFrame, this.uiViewport); + } + } + return false; + } + + public boolean touchUp(final int screenX, final int screenY, final float worldScreenY, final int button) { + screenCoordsVector.set(screenX, screenY); + this.uiViewport.unproject(screenCoordsVector); + final UIFrame clickedUIFrame = this.rootFrame.touchUp(screenCoordsVector.x, screenCoordsVector.y, button); + if (this.mouseDownUIFrame != null) { + if (clickedUIFrame == this.mouseDownUIFrame) { + this.mouseDownUIFrame.onClick(button); + this.uiSounds.getSound("GlueScreenClick").play(this.uiScene.audioContext, 0, 0, 0); + } + this.mouseDownUIFrame.mouseUp(this.rootFrame, this.uiViewport); + } + this.mouseDownUIFrame = null; + return false; + } + + public boolean touchDragged(final int screenX, final int screenY, final float worldScreenY, final int pointer) { + mouseMoved(screenX, screenY, worldScreenY); + return false; + } + + public boolean mouseMoved(final int screenX, final int screenY, final float worldScreenY) { + screenCoordsVector.set(screenX, screenY); + this.uiViewport.unproject(screenCoordsVector); + final UIFrame mousedUIFrame = this.rootFrame.getFrameChildUnderMouse(screenCoordsVector.x, + screenCoordsVector.y); + if (mousedUIFrame != this.mouseOverUIFrame) { + if (this.mouseOverUIFrame != null) { + this.mouseOverUIFrame.mouseExit(this.rootFrame, this.uiViewport); + } + if (mousedUIFrame instanceof ClickableFrame) { + this.mouseOverUIFrame = (ClickableFrame) mousedUIFrame; + if (this.mouseOverUIFrame != null) { + this.mouseOverUIFrame.mouseEnter(this.rootFrame, this.uiViewport); + } + } + else { + this.mouseOverUIFrame = null; + } + } + return false; + } + + private void loadSounds() { + this.worldEditStrings = new WorldEditStrings(this.dataSource); + this.uiSoundsTable = new DataTable(this.worldEditStrings); + try { + try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("UI\\SoundInfo\\UISounds.slk")) { + this.uiSoundsTable.readSLK(miscDataTxtStream); + } + try (InputStream miscDataTxtStream = this.dataSource + .getResourceAsStream("UI\\SoundInfo\\AmbienceSounds.slk")) { + this.uiSoundsTable.readSLK(miscDataTxtStream); + } + } + catch (final IOException e) { + e.printStackTrace(); + } + this.uiSounds = new KeyedSounds(this.uiSoundsTable, this.dataSource); + } + + public KeyedSounds getUiSounds() { + return this.uiSounds; + } + + private static enum MenuState { + GOING_TO_MAIN_MENU, + MAIN_MENU, + GOING_TO_SINGLE_PLAYER, + SINGLE_PLAYER, + GOING_TO_CAMPAIGN, + CAMPAIGN, + QUITTING, + RESTARTING; + } + + public void hide() { + this.glueScreenLoop.stop(); + } + + public void dispose() { + if (this.rootFrame != null) { + this.rootFrame.dispose(); } } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/QueueIcon.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/QueueIcon.java index 43a8f39..64d01df 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/QueueIcon.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/QueueIcon.java @@ -97,6 +97,14 @@ public class QueueIcon extends AbstractRenderableFrame implements ClickableActio positionBounds(gameUI, uiViewport); } + @Override + public void mouseEnter(final GameUI gameUI, final Viewport uiViewport) { + } + + @Override + public void mouseExit(final GameUI gameUI, final Viewport uiViewport) { + } + @Override public UIFrame getFrameChildUnderMouse(final float screenX, final float screenY) { if (isVisible() && this.renderBounds.contains(screenX, screenY)) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/ClickableActionFrame.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/ClickableActionFrame.java index 46721d6..25f1110 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/ClickableActionFrame.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/ClickableActionFrame.java @@ -2,13 +2,15 @@ package com.etheller.warsmash.viewer5.handlers.w3x.ui.command; import com.badlogic.gdx.utils.viewport.Viewport; import com.etheller.warsmash.parsers.fdf.GameUI; -import com.etheller.warsmash.parsers.fdf.frames.UIFrame; -public interface ClickableActionFrame extends UIFrame { +public interface ClickableActionFrame extends ClickableFrame { + @Override void mouseDown(final GameUI gameUI, final Viewport uiViewport); + @Override void mouseUp(final GameUI gameUI, final Viewport uiViewport); + @Override void onClick(int button); String getToolTip(); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/ClickableFrame.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/ClickableFrame.java new file mode 100644 index 0000000..15e90e9 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/ClickableFrame.java @@ -0,0 +1,17 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.ui.command; + +import com.badlogic.gdx.utils.viewport.Viewport; +import com.etheller.warsmash.parsers.fdf.GameUI; +import com.etheller.warsmash.parsers.fdf.frames.UIFrame; + +public interface ClickableFrame extends UIFrame { + void mouseDown(final GameUI gameUI, final Viewport uiViewport); + + void mouseUp(final GameUI gameUI, final Viewport uiViewport); + + void mouseEnter(final GameUI gameUI, final Viewport uiViewport); + + void mouseExit(final GameUI gameUI, final Viewport uiViewport); + + void onClick(int button); +} diff --git a/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxTexture.java b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxTexture.java index cc15b8c..657eb5f 100644 --- a/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxTexture.java +++ b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxTexture.java @@ -8,10 +8,10 @@ import com.hiveworkshop.rms.util.BinaryWriter; public class MdlxTexture implements MdlxBlock { public enum WrapMode { - REPEAT_BOTH(true, true), + REPEAT_BOTH(false, false), WRAP_WIDTH(true, false), WRAP_HEIGHT(false, true), - WRAP_BOTH(false, false); + WRAP_BOTH(true, true); private final boolean wrapWidth; private final boolean wrapHeight; diff --git a/desktop/src/com/etheller/warsmash/audio/Flac.java b/desktop/src/com/etheller/warsmash/audio/Flac.java index 945bb06..3a99bf9 100644 --- a/desktop/src/com/etheller/warsmash/audio/Flac.java +++ b/desktop/src/com/etheller/warsmash/audio/Flac.java @@ -134,7 +134,13 @@ public class Flac { } // Start writing WAV output file - final int bytesPerSample = streamInfo.sampleDepth / 8; + int bytesPerSample = streamInfo.sampleDepth / 8; + final boolean needsDownscaleForLibgdx = bytesPerSample >= 3; + int downsampleBytes = 0; + if (needsDownscaleForLibgdx) { + downsampleBytes = bytesPerSample - 2; + bytesPerSample = 2; + } final ByteArrayOutputStream baos = new ByteArrayOutputStream(); try (DataOutputStream out = new DataOutputStream(new BufferedOutputStream(baos))) { // Header chunk @@ -151,14 +157,17 @@ public class Flac { writeLittleInt32(out, streamInfo.sampleRate); writeLittleInt32(out, streamInfo.sampleRate * streamInfo.numChannels * bytesPerSample); writeLittleInt16(out, streamInfo.numChannels * bytesPerSample); - writeLittleInt16(out, streamInfo.sampleDepth); + writeLittleInt16(out, needsDownscaleForLibgdx ? 16 : streamInfo.sampleDepth); // Audio data chunk ("data") out.writeInt(0x64617461); // "data" writeLittleInt32(out, sampleDataLen); for (int i = 0; i < samples[0].length; i++) { for (int j = 0; j < samples.length; j++) { - final int val = samples[j][i]; + int val = samples[j][i]; + for (int k = 0; k < downsampleBytes; k++) { + val = val >>> 8; + } if (bytesPerSample == 1) { out.write(val + 128); // Convert to unsigned, as per WAV PCM conventions } diff --git a/desktop/src/com/etheller/warsmash/audio/OpenALSound.java b/desktop/src/com/etheller/warsmash/audio/OpenALSound.java index b7f086d..0a27681 100644 --- a/desktop/src/com/etheller/warsmash/audio/OpenALSound.java +++ b/desktop/src/com/etheller/warsmash/audio/OpenALSound.java @@ -226,8 +226,8 @@ public class OpenALSound implements Sound { } public long play(final float volume, final float pitch, final float x, final float y, final float z, - final boolean is3DSound, final float maxDistance, final float refDistance) { - final long id = play(); + final boolean is3DSound, final float maxDistance, final float refDistance, final boolean looping) { + final long id = looping ? loop() : play(); setPitch(id, pitch); setVolume(id, volume); setPosition(id, x, y, z, is3DSound, maxDistance, refDistance); diff --git a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java index e17725c..98f99d7 100644 --- a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java +++ b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java @@ -15,12 +15,15 @@ import org.lwjgl.opengl.GL31; import org.lwjgl.opengl.GL32; import org.lwjgl.opengl.GL33; +import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Graphics.DisplayMode; import com.badlogic.gdx.audio.Sound; import com.badlogic.gdx.backends.lwjgl.LwjglApplication; import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration; import com.badlogic.gdx.backends.lwjgl.LwjglNativesLoader; -import com.etheller.warsmash.WarsmashGdxMapGame; +import com.etheller.warsmash.WarsmashGdxMapScreen; +import com.etheller.warsmash.WarsmashGdxMenuScreen; +import com.etheller.warsmash.WarsmashGdxMultiScreenGame; import com.etheller.warsmash.audio.OpenALSound; import com.etheller.warsmash.units.DataTable; import com.etheller.warsmash.util.StringBundle; @@ -40,21 +43,40 @@ public class DesktopLauncher { config.gles30ContextMajorVersion = 3; config.gles30ContextMinorVersion = 3; // config.samples = 16; -// config.vSyncEnabled = false; -// config.foregroundFPS = 0; -// config.backgroundFPS = 0; + config.vSyncEnabled = false; + config.foregroundFPS = 0; + config.backgroundFPS = 0; final DisplayMode desktopDisplayMode = LwjglApplicationConfiguration.getDesktopDisplayMode(); config.width = desktopDisplayMode.width; config.height = desktopDisplayMode.height; - if ((arg.length > 0) && "-windowed".equals(arg[0])) { - config.fullscreen = false; - } - else { - config.fullscreen = true; + String fileToLoad = null; + config.fullscreen = true; + for (int argIndex = 0; argIndex < arg.length; argIndex++) { + if ("-windowed".equals(arg[argIndex])) { + config.fullscreen = false; + } + else if ((arg.length > (argIndex + 1)) && "-loadfile".equals(arg[argIndex])) { + argIndex++; + fileToLoad = arg[argIndex]; + } } loadExtensions(); final DataTable warsmashIni = loadWarsmashIni(); - new LwjglApplication(new WarsmashGdxMapGame(warsmashIni), config); + final WarsmashGdxMultiScreenGame warsmashGdxMultiScreenGame = new WarsmashGdxMultiScreenGame(); + new LwjglApplication(warsmashGdxMultiScreenGame, config); + final String finalFileToLoad = fileToLoad; + Gdx.app.postRunnable(new Runnable() { + @Override + public void run() { + if (finalFileToLoad != null) { + warsmashGdxMultiScreenGame.setScreen(new WarsmashGdxMapScreen(warsmashIni, finalFileToLoad)); + } + else { + warsmashGdxMultiScreenGame + .setScreen(new WarsmashGdxMenuScreen(warsmashIni, warsmashGdxMultiScreenGame)); + } + } + }); } public static DataTable loadWarsmashIni() { @@ -123,8 +145,9 @@ public class DesktopLauncher { @Override public void play(final Sound buffer, final float volume, final float pitch, final float x, final float y, - final float z, final boolean is3dSound, final float maxDistance, final float refDistance) { - ((OpenALSound) buffer).play(volume, pitch, x, y, z, is3dSound, maxDistance, refDistance); + final float z, final boolean is3dSound, final float maxDistance, final float refDistance, + final boolean looping) { + ((OpenALSound) buffer).play(volume, pitch, x, y, z, is3dSound, maxDistance, refDistance, looping); } @Override diff --git a/desktop/src/com/etheller/warsmash/desktop/util/TerrainView.java b/desktop/src/com/etheller/warsmash/desktop/util/TerrainView.java index 3ef9214..08f50e8 100644 --- a/desktop/src/com/etheller/warsmash/desktop/util/TerrainView.java +++ b/desktop/src/com/etheller/warsmash/desktop/util/TerrainView.java @@ -6,7 +6,7 @@ import javax.swing.JFrame; import javax.swing.JScrollPane; import javax.swing.WindowConstants; -import com.etheller.warsmash.WarsmashGdxMapGame; +import com.etheller.warsmash.WarsmashGdxMapScreen; import com.etheller.warsmash.datasources.DataSource; import com.etheller.warsmash.desktop.DesktopLauncher; import com.etheller.warsmash.parsers.w3x.War3Map; @@ -16,7 +16,7 @@ import com.etheller.warsmash.units.DataTable; public class TerrainView { public static void main(final String[] args) { final DataTable warsmashIni = DesktopLauncher.loadWarsmashIni(); - final DataSource dataSources = WarsmashGdxMapGame.parseDataSources(warsmashIni); + final DataSource dataSources = WarsmashGdxMapScreen.parseDataSources(warsmashIni); final War3Map war3Map = new War3Map(dataSources, warsmashIni.get("Map").getField("FilePath")); try { final War3MapW3e environmentFile = war3Map.readEnvironment(); diff --git a/fdfparser/src/com/etheller/warsmash/fdfparser/FrameDefinitionFieldVisitor.java b/fdfparser/src/com/etheller/warsmash/fdfparser/FrameDefinitionFieldVisitor.java index e366a1d..00a8d42 100644 --- a/fdfparser/src/com/etheller/warsmash/fdfparser/FrameDefinitionFieldVisitor.java +++ b/fdfparser/src/com/etheller/warsmash/fdfparser/FrameDefinitionFieldVisitor.java @@ -135,16 +135,19 @@ public class FrameDefinitionFieldVisitor extends FDFBaseVisitor { @Override public Void visitFontElement(final FontElementContext ctx) { - this.frameDefinition.set(ctx.ID().getText(), - new FontFrameDefinitionField(new FontDefinition(ctx.STRING_LITERAL(0).getText(), - Float.parseFloat(ctx.FLOAT().getText()), ctx.STRING_LITERAL(1).getText()))); + String text = ctx.STRING_LITERAL(0).getText(); + text = text.substring(1, text.length() - 1); + this.frameDefinition.set(ctx.ID().getText(), new FontFrameDefinitionField( + new FontDefinition(text, Float.parseFloat(ctx.FLOAT().getText()), ctx.STRING_LITERAL(1).getText()))); return null; } @Override public Void visitSimpleFontElement(final SimpleFontElementContext ctx) { - this.frameDefinition.set(ctx.ID().getText(), new FontFrameDefinitionField( - new FontDefinition(ctx.STRING_LITERAL().getText(), Float.parseFloat(ctx.FLOAT().getText()), null))); + String text = ctx.STRING_LITERAL().getText(); + text = text.substring(1, text.length() - 1); + this.frameDefinition.set(ctx.ID().getText(), + new FontFrameDefinitionField(new FontDefinition(text, Float.parseFloat(ctx.FLOAT().getText()), null))); return null; } diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/ControlStyle.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/ControlStyle.java index 4243b8d..8762a24 100644 --- a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/ControlStyle.java +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/ControlStyle.java @@ -1,7 +1,19 @@ package com.etheller.warsmash.parsers.fdf.datamodel; +import java.util.EnumSet; + public enum ControlStyle { AUTOTRACK, HIGHLIGHTONFOCUS, HIGHLIGHTONMOUSEOVER; + + public static EnumSet parseControlStyle(final String controlStyles) { + final EnumSet set = EnumSet.noneOf(ControlStyle.class); + for (final String flag : controlStyles.split("\\|")) { + if (!"".equals(flag)) { + set.add(ControlStyle.valueOf(flag)); + } + } + return set; + } } diff --git a/resources/UI/FrameDef/UI/SimpleInfoPanel.fdf b/resources/UI/FrameDef/UI/SimpleInfoPanel.fdf deleted file mode 100644 index de6fa8d..0000000 --- a/resources/UI/FrameDef/UI/SimpleInfoPanel.fdf +++ /dev/null @@ -1,447 +0,0 @@ - -// --- TEXT ----------------------------------------------------------------------- - - -String "SimpleInfoPanelTitleTextTemplate" { - FontColor 1.0 1.0 1.0 1.0, - FontShadowColor 0.0 0.0 0.0 0.9, - FontShadowOffset 0.002 -0.002, - Font "InfoPanelTextFont",0.013, -} -String "SimpleInfoPanelTitleTextDisabledTemplate" INHERITS "SimpleInfoPanelTitleTextTemplate" { - FontColor 0.2 0.2 0.2 1.0, -} - -String "SimpleInfoPanelDescriptionTextTemplate" { - FontColor 0.99 0.827 0.0705 1.0, - FontShadowColor 0.0 0.0 0.0 0.9, - FontShadowOffset 0.001 -0.001, - Font "InfoPanelTextFont",0.01, -} -String "SimpleInfoPanelDescriptionHighlightTextTemplate" INHERITS "SimpleInfoPanelDescriptionTextTemplate" { - FontColor 1.0 1.0 1.0 1.0, -} -String "SimpleInfoPanelDescriptionDisabledTextTemplate" INHERITS "SimpleInfoPanelDescriptionTextTemplate" { - FontColor 0.2 0.2 0.2 1.0, -} - -String "SimpleInfoPanelLabelTextTemplate" { - FontJustificationH JUSTIFYLEFT, - FontJustificationV JUSTIFYTOP, - FontColor 0.99 0.827 0.0705 1.0, - FontShadowColor 0.0 0.0 0.0 0.9, - FontShadowOffset 0.001 -0.001, - Font "InfoPanelTextFont",0.0085, -} -String "SimpleInfoPanelLabelHighlightTextTemplate" INHERITS "SimpleInfoPanelLabelTextTemplate" { - FontColor 1.0 1.0 1.0 1.0, -} -String "SimpleInfoPanelLabelDisabledTextTemplate" INHERITS "SimpleInfoPanelLabelTextTemplate" { - FontColor 0.2 0.2 0.2 1.0, -} - -String "SimpleInfoPanelValueTextTemplate" INHERITS "SimpleInfoPanelLabelTextTemplate" { - FontColor 1.0 1.0 1.0 1.0, -} - -String "SimpleInfoPanelAttributeTextTemplate" { - FontColor 1.0 1.0 1.0 1.0, - FontShadowColor 0.0 0.0 0.0 0.9, - FontShadowOffset 0.001 -0.001, - Font "InfoPanelTextFont",0.009, -} -String "SimpleInfoPanelAttributeDisabledTextTemplate" INHERITS "SimpleInfoPanelAttributeTextTemplate" { - FontColor 0.2 0.2 0.2 1.0, -} - -Texture "InfoPanelIconTemplate" { - Width 0.032, - Height 0.032, - Anchor TOPLEFT, 0.004, -0.001, -} - -Texture "ResourceIconTemplate" { - Width 0.014, - Height 0.014, -} - -String "ResourceTextTemplate" INHERITS "SimpleInfoPanelValueTextTemplate" { - Font "InfoPanelTextFont", 0.0085, -} - -// -- FRAMES ---------------------------------------------------------------- - -Frame "SIMPLEFRAME" "SimpleInfoPanelUnitDetail" { - UseActiveContext, - SetAllPoints, - DecorateFileNames, - - // --- unit name frame -------------------------------------------------- - String "SimpleNameValue" INHERITS "SimpleInfoPanelTitleTextTemplate" { - Anchor TOP,0,0, - } - - // --- hero level bar --------------------------------------------------- - Frame "SIMPLESTATUSBAR" "SimpleHeroLevelBar" { - UseActiveContext, - SetPoint TOP, "SimpleNameValue", BOTTOM, 0.0, -0.0015, - Height 0.015625, - } - - // --- timed life bar ---------------------------------------------------- - Frame "SIMPLESTATUSBAR" "SimpleProgressIndicator" { - UseActiveContext, - SetPoint TOP, "SimpleNameValue", BOTTOM, 0.0, -0.0015, - Height 0.015625, - } - - // --- building build queue panel ------------------------------------------------- - Frame "SIMPLESTATUSBAR" "SimpleBuildTimeIndicator" { - UseActiveContext, - SetPoint TOPLEFT, "SimpleInfoPanelUnitDetail", TOPLEFT, 0.061250, -0.038125, - } - - String "SimpleBuildingActionLabel" INHERITS "SimpleInfoPanelDescriptionTextTemplate" { - SetPoint CENTER, "SimpleInfoPanelUnitDetail", TOPLEFT, 0.11375, -0.029875, - Text "Retarded text", - } - - // --- unit stats panel ------------------------------------------------- - // This is required to make sure the class text appears above the status bars. - Frame "SIMPLEFRAME" "SimpleUnitStatsPanel" { - UseActiveContext, - SetAllPoints, - DecorateFileNames, - - // --- class ------------------------------------------------------------ - String "SimpleClassValue" INHERITS "SimpleInfoPanelValueTextTemplate" { - SetPoint TOP, "SimpleNameValue", BOTTOM, 0.0, -0.0055, - FontJustificationH JUSTIFYCENTER, - } - } -} - -Frame "SIMPLEFRAME" "SimpleInfoPanelCargoDetail" { - UseActiveContext, - SetAllPoints, - DecorateFileNames, - - // --- unit name frame -------------------------------------------------- - String "SimpleHoldNameValue" INHERITS "SimpleInfoPanelTitleTextTemplate" { - Anchor TOP,0,0, - } - - String "SimpleHoldDescriptionValue" INHERITS "SimpleInfoPanelDescriptionTextTemplate" { - SetPoint TOP, "SimpleHoldNameValue", BOTTOM, 0.0, -0.007, - Width 0.188, - } -} - -Frame "SIMPLEFRAME" "SimpleInfoPanelBuildingDetail" { - UseActiveContext, - SetAllPoints, - DecorateFileNames, - - String "SimpleBuildingNameValue" INHERITS "SimpleInfoPanelTitleTextTemplate" { - Anchor TOP,0,0, - } - - String "SimpleBuildingDescriptionValue" INHERITS "SimpleInfoPanelDescriptionTextTemplate" { - SetPoint TOP, "SimpleBuildingNameValue", BOTTOM, 0.0, -0.007, - Width 0.188, - } - - // --- building build queue panel ------------------------------------------------- - Frame "SIMPLESTATUSBAR" "SimpleBuildTimeIndicator" { - UseActiveContext, - SetPoint TOPLEFT, "SimpleInfoPanelBuildingDetail", TOPLEFT, 0.061250, -0.038125, - } - - String "SimpleBuildingActionLabel" INHERITS "SimpleInfoPanelDescriptionTextTemplate" { - SetPoint CENTER, "SimpleInfoPanelBuildingDetail", TOPLEFT, 0.11375, -0.029875, - Text "Retarded text", - } - - Layer "ARTWORK" { - Texture "SimpleBuildQueueBackdrop" { - SetPoint BOTTOMLEFT, "SimpleInfoPanelBuildingDetail", BOTTOMLEFT, 0.0, 0.0, - SetPoint BOTTOMRIGHT, "SimpleInfoPanelBuildingDetail", BOTTOMRIGHT, 0.0, 0.0, - Height 0.1, - File "BuildQueueBackdrop", - } - } -} - -Frame "SIMPLEFRAME" "SimpleInfoPanelItemDetail" { - UseActiveContext, - SetAllPoints, - DecorateFileNames, - - // --- item name frame -------------------------------------------------- - String "SimpleItemNameValue" INHERITS "SimpleInfoPanelTitleTextTemplate" { - Anchor TOP,0,0, - } - - // --- item description frame ------------------------------------------- - String "SimpleItemDescriptionValue" INHERITS "SimpleInfoPanelDescriptionTextTemplate" { - Width 0.188, - SetPoint TOP, "SimpleItemNameValue", BOTTOM, 0.0, -0.008, - } -} - -Frame "SIMPLEFRAME" "SimpleInfoPanelDestructableDetail" { - UseActiveContext, - SetAllPoints, - DecorateFileNames, - - // --- destructable name frame -------------------------------------------------- - String "SimpleDestructableNameValue" INHERITS "SimpleInfoPanelTitleTextTemplate" { - Anchor TOP,0,0, - } - - // --- destructable description frame ------------------------------------------- - //String "SimpleDestructableDescriptionValue" INHERITS "SimpleInfoPanelDescriptionTextTemplate" { - // Width 0.188, - // SetPoint TOP, "SimpleDestructableNameValue", BOTTOM, 0.0, -0.008, - //} -} - -Frame "SIMPLEFRAME" "SimpleInfoPanelIconDamage" { - UseActiveContext, - SetAllPoints, - DecorateFileNames, - Height 0.03125, - - // --- icon ------------------------------------------------------------- - Texture "InfoPanelIconBackdrop" INHERITS "InfoPanelIconTemplate" { - File "HeroStrengthIcon", - } - - // --- icon # ----------------------------------------------------------- - String "InfoPanelIconLevel" INHERITS "SimpleInfoPanelAttributeTextTemplate" { - SetPoint CENTER, "InfoPanelIconBackdrop", BOTTOMRIGHT, -0.007625, 0.006875, - } - - // --- label ------------------------------------------------------------ - String "InfoPanelIconLabel" INHERITS "SimpleInfoPanelLabelTextTemplate" { - SetPoint TOPLEFT, "InfoPanelIconBackdrop", TOPRIGHT, 0.0, -0.003, - Text "COLON_DAMAGE", - } - - // --- value ------------------------------------------------------------ - String "InfoPanelIconValue" INHERITS "SimpleInfoPanelValueTextTemplate" { - SetPoint TOPLEFT, "InfoPanelIconLabel", BOTTOMLEFT, 0.005, -0.003, - } -} - -Frame "SIMPLEFRAME" "SimpleInfoPanelIconArmor" { - UseActiveContext, - SetAllPoints, - DecorateFileNames, - Height 0.03125, - - // --- icon ------------------------------------------------------------- - Texture "InfoPanelIconBackdrop" INHERITS "InfoPanelIconTemplate" { - File "HeroStrengthIcon", - } - - // --- icon # ----------------------------------------------------------- - String "InfoPanelIconLevel" INHERITS "SimpleInfoPanelAttributeTextTemplate" { - SetPoint CENTER, "InfoPanelIconBackdrop", BOTTOMRIGHT, -0.007625, 0.006875, - } - - // --- label ------------------------------------------------------------ - String "InfoPanelIconLabel" INHERITS "SimpleInfoPanelLabelTextTemplate" { - SetPoint TOPLEFT, "InfoPanelIconBackdrop", TOPRIGHT, 0.0, -0.003, - Text "COLON_ARMOR", - } - - // --- value ------------------------------------------------------------ - String "InfoPanelIconValue" INHERITS "SimpleInfoPanelValueTextTemplate" { - SetPoint TOPLEFT, "InfoPanelIconLabel", BOTTOMLEFT, 0.005, -0.003, - } -} - -Frame "SIMPLEFRAME" "SimpleInfoPanelIconRank" { - UseActiveContext, - SetAllPoints, - DecorateFileNames, - Height 0.03125, - - // --- icon ------------------------------------------------------------- - Texture "InfoPanelIconBackdrop" INHERITS "InfoPanelIconTemplate" { - File "HeroStrengthIcon", - } - - // --- icon # ----------------------------------------------------------- - String "InfoPanelIconLevel" INHERITS "SimpleInfoPanelAttributeTextTemplate" { - SetPoint CENTER, "InfoPanelIconBackdrop", BOTTOMRIGHT, -0.007625, 0.006875, - } - - // --- label ------------------------------------------------------------ - String "InfoPanelIconLabel" INHERITS "SimpleInfoPanelLabelTextTemplate" { - SetPoint TOPLEFT, "InfoPanelIconBackdrop", TOPRIGHT, 0.0, -0.003, - Text "COLON_RANK", - } - - // --- value ------------------------------------------------------------ - String "InfoPanelIconValue" INHERITS "SimpleInfoPanelValueTextTemplate" { - SetPoint TOPLEFT, "InfoPanelIconLabel", BOTTOMLEFT, 0.005, -0.003, - } -} - -Frame "SIMPLEFRAME" "SimpleInfoPanelIconFood" { - UseActiveContext, - SetAllPoints, - DecorateFileNames, - Height 0.03125, - - // --- icon ------------------------------------------------------------- - Texture "InfoPanelIconBackdrop" INHERITS "InfoPanelIconTemplate" { - File "InfoPanelIconFood", - } - - // --- icon # ----------------------------------------------------------- - String "InfoPanelIconLevel" INHERITS "SimpleInfoPanelAttributeTextTemplate" { - SetPoint CENTER, "InfoPanelIconBackdrop", BOTTOMRIGHT, -0.007625, 0.006875, - } - - // --- label ------------------------------------------------------------ - String "InfoPanelIconLabel" INHERITS "SimpleInfoPanelLabelTextTemplate" { - SetPoint TOPLEFT, "InfoPanelIconBackdrop", TOPRIGHT, 0.0, -0.003, - Text "COLON_FOOD", - } - - // --- value ------------------------------------------------------------ - String "InfoPanelIconValue" INHERITS "SimpleInfoPanelValueTextTemplate" { - SetPoint TOPLEFT, "InfoPanelIconLabel", BOTTOMLEFT, 0.005, -0.003, - } -} - -Frame "SIMPLEFRAME" "SimpleInfoPanelIconGold" { - UseActiveContext, - SetAllPoints, - DecorateFileNames, - Height 0.03125, - - // --- icon ------------------------------------------------------------- - Texture "InfoPanelIconBackdrop" INHERITS "InfoPanelIconTemplate" { - File "InfoPanelIconGold", - } - - // --- icon # ----------------------------------------------------------- - String "InfoPanelIconLevel" INHERITS "SimpleInfoPanelAttributeTextTemplate" { - SetPoint CENTER, "InfoPanelIconBackdrop", BOTTOMRIGHT, -0.007625, 0.006875, - } - - // --- label ------------------------------------------------------------ - String "InfoPanelIconLabel" INHERITS "SimpleInfoPanelLabelTextTemplate" { - SetPoint TOPLEFT, "InfoPanelIconBackdrop", TOPRIGHT, 0.0, -0.003, - Text "COLON_GOLD", - } - - // --- value ------------------------------------------------------------ - String "InfoPanelIconValue" INHERITS "SimpleInfoPanelValueTextTemplate" { - SetPoint TOPLEFT, "InfoPanelIconLabel", BOTTOMLEFT, 0.005, -0.003, - } -} - -Frame "SIMPLEFRAME" "SimpleInfoPanelIconHero" { - UseActiveContext, - SetAllPoints, - DecorateFileNames, - Height 0.0625, - - // --- icon ------------------------------------------------------------- - Texture "InfoPanelIconHeroIcon" INHERITS "InfoPanelIconTemplate" { - File "HeroStrengthIcon", - Anchor LEFT, 0.004, 0.0, - } - - Frame "SIMPLEFRAME" "SimpleInfoPanelIconHeroText" { - UseActiveContext, - DecorateFileNames, - SetPoint LEFT, "InfoPanelIconHeroIcon", RIGHT, 0.0, 0.0, - SetPoint RIGHT, "SimpleInfoPanelIconHero", RIGHT, 0.0, 0.0, - SetPoint TOP, "SimpleInfoPanelIconHero", TOP, 0.0, 0.0, - SetPoint BOTTOM, "SimpleInfoPanelIconHero", BOTTOM, 0.0, 0.0, - - // --- strength --------------------------------------------------------- - String "InfoPanelIconHeroStrengthLabel" INHERITS "SimpleInfoPanelLabelTextTemplate" { - Anchor TOPLEFT, 0.0, -0.003, - Text "COLON_STRENGTH", - } - - String "InfoPanelIconHeroStrengthValue" INHERITS "SimpleInfoPanelValueTextTemplate" { - SetPoint TOPLEFT, "InfoPanelIconHeroStrengthLabel", BOTTOMLEFT, 0.005, 0.0, - } - - // --- agility ---------------------------------------------------------- - String "InfoPanelIconHeroAgilityLabel" INHERITS "SimpleInfoPanelLabelTextTemplate" { - SetPoint TOPLEFT, "InfoPanelIconHeroStrengthValue", BOTTOMLEFT, -0.005, -0.004, - Text "COLON_AGILITY", - } - - String "InfoPanelIconHeroAgilityValue" INHERITS "SimpleInfoPanelValueTextTemplate" { - SetPoint TOPLEFT, "InfoPanelIconHeroAgilityLabel", BOTTOMLEFT, 0.005, 0.0, - } - - // --- intellect -------------------------------------------------------- - String "InfoPanelIconHeroIntellectLabel" INHERITS "SimpleInfoPanelLabelTextTemplate" { - SetPoint TOPLEFT, "InfoPanelIconHeroAgilityValue", BOTTOMLEFT, -0.005, -0.004, - Text "COLON_INTELLECT", - } - - String "InfoPanelIconHeroIntellectValue" INHERITS "SimpleInfoPanelValueTextTemplate" { - SetPoint TOPLEFT, "InfoPanelIconHeroIntellectLabel", BOTTOMLEFT, 0.005, 0.0, - } - } -} - -Frame "SIMPLEFRAME" "SimpleInfoPanelIconAlly" { - UseActiveContext, - DecorateFileNames, - Height 0.0625, - - // --- title ------------------------------------------------------------ - String "InfoPanelIconAllyTitle" INHERITS "SimpleInfoPanelLabelTextTemplate" { - SetPoint TOPLEFT, "SimpleInfoPanelIconAlly", TOPLEFT, 0.0, 0.0, - Text "ALLY_RESOURCES", - } - - // --- gold ------------------------------------------------------------- - Texture "InfoPanelIconAllyGoldIcon" INHERITS "ResourceIconTemplate" { - SetPoint TOPLEFT, "SimpleInfoPanelIconAlly", TOPLEFT, 0.0, -0.009, - File "GoldIcon", - } - - String "InfoPanelIconAllyGoldValue" INHERITS "ResourceTextTemplate" { - SetPoint LEFT, "InfoPanelIconAllyGoldIcon", RIGHT, 0.005, 0.0, - } - - // --- wood ------------------------------------------------------------- - Texture "InfoPanelIconAllyWoodIcon" INHERITS "ResourceIconTemplate" { - SetPoint TOPLEFT, "InfoPanelIconAllyGoldIcon", BOTTOMLEFT, 0.0, 0.0, - File "LumberIcon", - } - - String "InfoPanelIconAllyWoodValue" INHERITS "ResourceTextTemplate" { - SetPoint LEFT, "InfoPanelIconAllyWoodIcon", RIGHT, 0.005, 0.0, - } - - // --- food ------------------------------------------------------------- - Texture "InfoPanelIconAllyFoodIcon" INHERITS "ResourceIconTemplate" { - SetPoint TOPLEFT, "InfoPanelIconAllyWoodIcon", BOTTOMLEFT, 0.0, 0.0, - File "SupplyIcon", - } - - String "InfoPanelIconAllyFoodValue" INHERITS "ResourceTextTemplate" { - SetPoint LEFT, "InfoPanelIconAllyFoodIcon", RIGHT, 0.005, 0.0, - } - - // --- upkeep ----------------------------------------------------------- - String "InfoPanelIconAllyUpkeep" INHERITS "SimpleInfoPanelValueTextTemplate" { - SetPoint TOPLEFT, "InfoPanelIconAllyFoodValue", BOTTOMLEFT, 0.0, -0.005, - Text "Upkeep", - } -} \ No newline at end of file From 9249c8e6df3005396d68470419134aeb6069782f Mon Sep 17 00:00:00 2001 From: Retera Date: Sat, 6 Mar 2021 12:57:32 -0500 Subject: [PATCH 106/116] Reworking how billboard locks work --- .../warsmash/viewer5/SkeletalNode.java | 117 ++++++++++-------- 1 file changed, 64 insertions(+), 53 deletions(-) diff --git a/core/src/com/etheller/warsmash/viewer5/SkeletalNode.java b/core/src/com/etheller/warsmash/viewer5/SkeletalNode.java index 28ab23e..9afb7c8 100644 --- a/core/src/com/etheller/warsmash/viewer5/SkeletalNode.java +++ b/core/src/com/etheller/warsmash/viewer5/SkeletalNode.java @@ -119,59 +119,6 @@ public abstract class SkeletalNode extends GenericNode { this.convertBasis(computedRotation); } - else if (this.billboardedX) { - final Camera camera = scene.camera; - computedRotation = rotationHeap; - cameraRayHeap.set(camera.billboardedVectors[6]); - computedRotation.set(this.parent.inverseWorldRotation); - computedRotation.transform(cameraRayHeap); - billboardAxisHeap.set(1, 0, 0); - final float angle = (float) Math.atan2(cameraRayHeap.z, cameraRayHeap.y); - computedRotation.setFromAxisRad(billboardAxisHeap, angle); - } - else if (this.billboardedY) { - final Camera camera = scene.camera; - computedRotation = rotationHeap; - cameraRayHeap.set(camera.billboardedVectors[6]); - computedRotation.set(this.parent.inverseWorldRotation); - - // Compute local rotation - if (!Float.isNaN(blendTimeRatio) && (blendTimeRatio > 0)) { - rotationHeap2.set(this.localRotation).slerp(this.localBlendRotation, blendTimeRatio); - } - else { - rotationHeap2.set(this.localRotation); - } - // Inverse that local rotation - rotationHeap2.x = -rotationHeap2.x; - rotationHeap2.y = -rotationHeap2.y; - rotationHeap2.z = -rotationHeap2.z; - rotationHeap3.set(computedRotation); - RenderMathUtils.mul(computedRotation, rotationHeap2, rotationHeap3); - - computedRotation.transform(cameraRayHeap); - - billboardAxisHeap.set(0, 1, 0); - final float angle = (float) Math.atan2(cameraRayHeap.z, -cameraRayHeap.x); - computedRotation.setFromAxisRad(billboardAxisHeap, angle); - - // Inverse that local rotation back to what it was - rotationHeap2.x = -rotationHeap2.x; - rotationHeap2.y = -rotationHeap2.y; - rotationHeap2.z = -rotationHeap2.z; - rotationHeap3.set(computedRotation); - RenderMathUtils.mul(computedRotation, rotationHeap2, rotationHeap3); - } - else if (this.billboardedZ) { - final Camera camera = scene.camera; - computedRotation = rotationHeap; - cameraRayHeap.set(camera.billboardedVectors[6]); - computedRotation.set(this.parent.inverseWorldRotation); - computedRotation.transform(cameraRayHeap); - billboardAxisHeap.set(0, 0, 1); - final float angle = (float) Math.atan2(cameraRayHeap.y, cameraRayHeap.x); - computedRotation.setFromAxisRad(billboardAxisHeap, angle); - } else { if (!Float.isNaN(blendTimeRatio) && (blendTimeRatio > 0)) { rotationHeap.set(this.localRotation).slerp(this.localBlendRotation, blendTimeRatio); @@ -180,6 +127,70 @@ public abstract class SkeletalNode extends GenericNode { else { computedRotation = this.localRotation; } + + if (this.billboardedX) { + final Camera camera = scene.camera; + cameraRayHeap.set(camera.billboardedVectors[6]); + rotationHeap2.set(this.parent.inverseWorldRotation); + + rotationHeap3.set(computedRotation); + // Inverse that local rotation + rotationHeap3.x = -rotationHeap3.x; + rotationHeap3.y = -rotationHeap3.y; + rotationHeap3.z = -rotationHeap3.z; + + rotationHeap3.mul(rotationHeap2); + + rotationHeap3.transform(cameraRayHeap); + + billboardAxisHeap.set(1, 0, 0); + final float angle = (float) Math.atan2(cameraRayHeap.z, cameraRayHeap.y); + rotationHeap3.setFromAxisRad(billboardAxisHeap, angle); + + RenderMathUtils.mul(computedRotation, computedRotation, rotationHeap3); + } + else if (this.billboardedY) { + final Camera camera = scene.camera; + cameraRayHeap.set(camera.billboardedVectors[6]); + rotationHeap2.set(this.parent.inverseWorldRotation); + + rotationHeap3.set(computedRotation); + // Inverse that local rotation + rotationHeap3.x = -rotationHeap3.x; + rotationHeap3.y = -rotationHeap3.y; + rotationHeap3.z = -rotationHeap3.z; + + rotationHeap3.mul(rotationHeap2); + + rotationHeap3.transform(cameraRayHeap); + + billboardAxisHeap.set(0, 1, 0); + final float angle = (float) Math.atan2(cameraRayHeap.z, -cameraRayHeap.x); + rotationHeap3.setFromAxisRad(billboardAxisHeap, angle); + + RenderMathUtils.mul(computedRotation, computedRotation, rotationHeap3); + } + else if (this.billboardedZ) { + final Camera camera = scene.camera; + cameraRayHeap.set(camera.billboardedVectors[6]); + rotationHeap2.set(this.parent.inverseWorldRotation); + + rotationHeap3.set(computedRotation); + // Inverse that local rotation + rotationHeap3.x = -rotationHeap3.x; + rotationHeap3.y = -rotationHeap3.y; + rotationHeap3.z = -rotationHeap3.z; + + rotationHeap3.mul(rotationHeap2); + + rotationHeap3.transform(cameraRayHeap); + + billboardAxisHeap.set(0, 0, 1); + final float angle = (float) Math.atan2(cameraRayHeap.y, cameraRayHeap.x); + rotationHeap3.setFromAxisRad(billboardAxisHeap, angle); + + RenderMathUtils.mul(computedRotation, computedRotation, rotationHeap3); + } } if (!Float.isNaN(blendTimeRatio) && (blendTimeRatio > 0)) { From 53c9e20c3f020a71c6d6613177185b915300e654 Mon Sep 17 00:00:00 2001 From: Retera Date: Mon, 8 Mar 2021 08:30:36 -0500 Subject: [PATCH 107/116] Fix issues with billboard lock X and Y --- core/assets/warsmash.ini | 61 +++++------------ core/assets/warsmash122.ini | 53 +++++++++++++++ core/assets/warsmashPRSCMOD.ini | 26 ------- .../warsmash/viewer5/SkeletalNode.java | 68 +++++++++---------- .../viewer5/handlers/mdx/BatchGroup.java | 7 +- 5 files changed, 110 insertions(+), 105 deletions(-) create mode 100644 core/assets/warsmash122.ini delete mode 100644 core/assets/warsmashPRSCMOD.ini diff --git a/core/assets/warsmash.ini b/core/assets/warsmash.ini index 2f07845..0c75d66 100644 --- a/core/assets/warsmash.ini +++ b/core/assets/warsmash.ini @@ -1,53 +1,26 @@ +// This is the Warsmash INI file for Project Revolution +// PRSCMOD + [DataSources] -Count=8 +Count=9 Type00=MPQ -Path00="D:\Games\Warcraft III Patch 1.22\war3.mpq" +Path00="D:\Games\Warcraft III Project Revolution\War3\The Sheep Attack\war3.mpq" Type01=MPQ -Path01="D:\Games\Warcraft III Patch 1.22\War3x.mpq" +Path01="D:\Games\Warcraft III Project Revolution\War3\The Sheep Attack\War3x.mpq" Type02=MPQ -Path02="D:\Games\Warcraft III Patch 1.22\War3xlocal.mpq" +Path02="D:\Games\Warcraft III Project Revolution\War3\The Sheep Attack\War3xlocal.mpq" Type03=MPQ -Path03="D:\Games\Warcraft III Patch 1.22\War3Patch.mpq" -Type04=Folder -Path04="..\..\resources" -Type05=Folder -Path05="D:\Backups\Warsmash\Data" +Path03="D:\Games\Warcraft III Project Revolution\War3\The Sheep Attack\war3patch.mpq" +Type04=MPQ +Path04="D:\Games\Warcraft III Project Revolution\PRSCMOD\Revolution.mpq" +Type05=MPQ +Path05="D:\Games\Warcraft III Project Revolution\PRSCMOD\Sound.mpq" Type06=Folder -Path06="D:\Games\Warcraft III Patch 1.22\Maps" +Path06="D:\Games\Warcraft III Project Revolution\ProjectRevolusmash" Type07=Folder -Path07="." +Path07="..\..\resources" +Type08=Folder +Path08="D:\Games\Warcraft III Project Revolution\PRSCMOD\PR-Maps" [Map] -//FilePath="CombatUnitTests.w3x" -//FilePath="PitchRoll.w3x" -//FilePath="PeonStartingBase_Simple.w3x" -FilePath="PeonStartingBase_Scythe.w3x" -//FilePath="MyStromguarde.w3m" -//FilePath="ColdArrows.w3m" -//FilePath="DungeonGoldMine.w3m" -//FilePath="PlayerPeasants.w3m" -//FilePath="FireLord.w3x" -//FilePath="Maps\Campaign\NightElf03.w3m" -//FilePath="PhoenixAttack.w3x" -//FilePath="LightEnvironmentTest.w3x" -//FilePath="TorchLight2.w3x" -//FilePath="OrcAssault.w3x" -//FilePath="FrostyVsFarm.w3m" -//FilePath="ModelTest.w3x" -//FilePath="SpinningSample.w3x" -//FilePath="Maps\Campaign\Prologue02.w3m" -//FilePath="Pathing.w3x" -//FilePath="ItemFacing.w3x" -//FilePath=SomeParticleTests.w3x -//FilePath="PeonMiningMultiHall.w3x" -//FilePath="QuadtreeBugs.w3x" -//FilePath="test2.w3x" -//FilePath="FarseerHoldPositionTest.w3x" -//FilePath="Ramps.w3m" -//FilePath="V1\Farm.w3x" -//FilePath="PenguinWorld.w3x" -//FilePath="Maps\FrozenThrone\Campaign\UndeadX09.w3x" -//FilePath="LavellaLagoon.w3x" -//FilePath="WiceOrc.w3x" -//FilePath="NorthrendPathingDoodle.w3x" -//FilePath="Maps\Campaign\Prologue01.w3m" +FilePath="ProjectRevolusmash.w3x" \ No newline at end of file diff --git a/core/assets/warsmash122.ini b/core/assets/warsmash122.ini new file mode 100644 index 0000000..2f07845 --- /dev/null +++ b/core/assets/warsmash122.ini @@ -0,0 +1,53 @@ +[DataSources] +Count=8 +Type00=MPQ +Path00="D:\Games\Warcraft III Patch 1.22\war3.mpq" +Type01=MPQ +Path01="D:\Games\Warcraft III Patch 1.22\War3x.mpq" +Type02=MPQ +Path02="D:\Games\Warcraft III Patch 1.22\War3xlocal.mpq" +Type03=MPQ +Path03="D:\Games\Warcraft III Patch 1.22\War3Patch.mpq" +Type04=Folder +Path04="..\..\resources" +Type05=Folder +Path05="D:\Backups\Warsmash\Data" +Type06=Folder +Path06="D:\Games\Warcraft III Patch 1.22\Maps" +Type07=Folder +Path07="." + +[Map] +//FilePath="CombatUnitTests.w3x" +//FilePath="PitchRoll.w3x" +//FilePath="PeonStartingBase_Simple.w3x" +FilePath="PeonStartingBase_Scythe.w3x" +//FilePath="MyStromguarde.w3m" +//FilePath="ColdArrows.w3m" +//FilePath="DungeonGoldMine.w3m" +//FilePath="PlayerPeasants.w3m" +//FilePath="FireLord.w3x" +//FilePath="Maps\Campaign\NightElf03.w3m" +//FilePath="PhoenixAttack.w3x" +//FilePath="LightEnvironmentTest.w3x" +//FilePath="TorchLight2.w3x" +//FilePath="OrcAssault.w3x" +//FilePath="FrostyVsFarm.w3m" +//FilePath="ModelTest.w3x" +//FilePath="SpinningSample.w3x" +//FilePath="Maps\Campaign\Prologue02.w3m" +//FilePath="Pathing.w3x" +//FilePath="ItemFacing.w3x" +//FilePath=SomeParticleTests.w3x +//FilePath="PeonMiningMultiHall.w3x" +//FilePath="QuadtreeBugs.w3x" +//FilePath="test2.w3x" +//FilePath="FarseerHoldPositionTest.w3x" +//FilePath="Ramps.w3m" +//FilePath="V1\Farm.w3x" +//FilePath="PenguinWorld.w3x" +//FilePath="Maps\FrozenThrone\Campaign\UndeadX09.w3x" +//FilePath="LavellaLagoon.w3x" +//FilePath="WiceOrc.w3x" +//FilePath="NorthrendPathingDoodle.w3x" +//FilePath="Maps\Campaign\Prologue01.w3m" diff --git a/core/assets/warsmashPRSCMOD.ini b/core/assets/warsmashPRSCMOD.ini deleted file mode 100644 index 0c75d66..0000000 --- a/core/assets/warsmashPRSCMOD.ini +++ /dev/null @@ -1,26 +0,0 @@ -// This is the Warsmash INI file for Project Revolution -// PRSCMOD - -[DataSources] -Count=9 -Type00=MPQ -Path00="D:\Games\Warcraft III Project Revolution\War3\The Sheep Attack\war3.mpq" -Type01=MPQ -Path01="D:\Games\Warcraft III Project Revolution\War3\The Sheep Attack\War3x.mpq" -Type02=MPQ -Path02="D:\Games\Warcraft III Project Revolution\War3\The Sheep Attack\War3xlocal.mpq" -Type03=MPQ -Path03="D:\Games\Warcraft III Project Revolution\War3\The Sheep Attack\war3patch.mpq" -Type04=MPQ -Path04="D:\Games\Warcraft III Project Revolution\PRSCMOD\Revolution.mpq" -Type05=MPQ -Path05="D:\Games\Warcraft III Project Revolution\PRSCMOD\Sound.mpq" -Type06=Folder -Path06="D:\Games\Warcraft III Project Revolution\ProjectRevolusmash" -Type07=Folder -Path07="..\..\resources" -Type08=Folder -Path08="D:\Games\Warcraft III Project Revolution\PRSCMOD\PR-Maps" - -[Map] -FilePath="ProjectRevolusmash.w3x" \ No newline at end of file diff --git a/core/src/com/etheller/warsmash/viewer5/SkeletalNode.java b/core/src/com/etheller/warsmash/viewer5/SkeletalNode.java index 9afb7c8..16fb9e7 100644 --- a/core/src/com/etheller/warsmash/viewer5/SkeletalNode.java +++ b/core/src/com/etheller/warsmash/viewer5/SkeletalNode.java @@ -12,7 +12,6 @@ public abstract class SkeletalNode extends GenericNode { protected static final Vector3 billboardAxisHeap = new Vector3(); protected static final Quaternion rotationHeap = new Quaternion(); protected static final Quaternion rotationHeap2 = new Quaternion(); - protected static final Quaternion rotationHeap3 = new Quaternion(); protected static final Vector3 scalingHeap = new Vector3(); protected static final Vector3 blendLocationHeap = new Vector3(); protected static final Vector3 blendHeap = new Vector3(); @@ -120,76 +119,77 @@ public abstract class SkeletalNode extends GenericNode { this.convertBasis(computedRotation); } else { + computedRotation = rotationHeap.set(this.localRotation); if (!Float.isNaN(blendTimeRatio) && (blendTimeRatio > 0)) { - rotationHeap.set(this.localRotation).slerp(this.localBlendRotation, blendTimeRatio); - computedRotation = rotationHeap; - } - else { - computedRotation = this.localRotation; + computedRotation.slerp(this.localBlendRotation, blendTimeRatio); } if (this.billboardedX) { + if (computedScaling == this.localScale) { + computedScaling = scalingHeap.set(computedScaling); + } + // It took me many hours to deduce from playing around that this negative one + // multiplier should be here. I suggest a lot of testing before you remove it. + computedScaling.z *= -1; + final Camera camera = scene.camera; cameraRayHeap.set(camera.billboardedVectors[6]); - rotationHeap2.set(this.parent.inverseWorldRotation); - rotationHeap3.set(computedRotation); + rotationHeap2.set(computedRotation); // Inverse that local rotation - rotationHeap3.x = -rotationHeap3.x; - rotationHeap3.y = -rotationHeap3.y; - rotationHeap3.z = -rotationHeap3.z; + rotationHeap2.x = -rotationHeap2.x; + rotationHeap2.y = -rotationHeap2.y; + rotationHeap2.z = -rotationHeap2.z; - rotationHeap3.mul(rotationHeap2); + rotationHeap2.mul(this.parent.inverseWorldRotation); - rotationHeap3.transform(cameraRayHeap); + rotationHeap2.transform(cameraRayHeap); billboardAxisHeap.set(1, 0, 0); final float angle = (float) Math.atan2(cameraRayHeap.z, cameraRayHeap.y); - rotationHeap3.setFromAxisRad(billboardAxisHeap, angle); + rotationHeap2.setFromAxisRad(billboardAxisHeap, angle); - RenderMathUtils.mul(computedRotation, computedRotation, rotationHeap3); + RenderMathUtils.mul(computedRotation, computedRotation, rotationHeap2); } else if (this.billboardedY) { final Camera camera = scene.camera; cameraRayHeap.set(camera.billboardedVectors[6]); - rotationHeap2.set(this.parent.inverseWorldRotation); - rotationHeap3.set(computedRotation); + rotationHeap2.set(computedRotation); // Inverse that local rotation - rotationHeap3.x = -rotationHeap3.x; - rotationHeap3.y = -rotationHeap3.y; - rotationHeap3.z = -rotationHeap3.z; + rotationHeap2.x = -rotationHeap2.x; + rotationHeap2.y = -rotationHeap2.y; + rotationHeap2.z = -rotationHeap2.z; - rotationHeap3.mul(rotationHeap2); + rotationHeap2.mul(this.parent.inverseWorldRotation); - rotationHeap3.transform(cameraRayHeap); + rotationHeap2.transform(cameraRayHeap); billboardAxisHeap.set(0, 1, 0); - final float angle = (float) Math.atan2(cameraRayHeap.z, -cameraRayHeap.x); - rotationHeap3.setFromAxisRad(billboardAxisHeap, angle); + final float angle = (float) Math.atan2(-cameraRayHeap.z, cameraRayHeap.x); + rotationHeap2.setFromAxisRad(billboardAxisHeap, angle); - RenderMathUtils.mul(computedRotation, computedRotation, rotationHeap3); + RenderMathUtils.mul(computedRotation, computedRotation, rotationHeap2); } else if (this.billboardedZ) { final Camera camera = scene.camera; cameraRayHeap.set(camera.billboardedVectors[6]); - rotationHeap2.set(this.parent.inverseWorldRotation); - rotationHeap3.set(computedRotation); + rotationHeap2.set(computedRotation); // Inverse that local rotation - rotationHeap3.x = -rotationHeap3.x; - rotationHeap3.y = -rotationHeap3.y; - rotationHeap3.z = -rotationHeap3.z; + rotationHeap2.x = -rotationHeap2.x; + rotationHeap2.y = -rotationHeap2.y; + rotationHeap2.z = -rotationHeap2.z; - rotationHeap3.mul(rotationHeap2); + rotationHeap2.mul(this.parent.inverseWorldRotation); - rotationHeap3.transform(cameraRayHeap); + rotationHeap2.transform(cameraRayHeap); billboardAxisHeap.set(0, 0, 1); final float angle = (float) Math.atan2(cameraRayHeap.y, cameraRayHeap.x); - rotationHeap3.setFromAxisRad(billboardAxisHeap, angle); + rotationHeap2.setFromAxisRad(billboardAxisHeap, angle); - RenderMathUtils.mul(computedRotation, computedRotation, rotationHeap3); + RenderMathUtils.mul(computedRotation, computedRotation, rotationHeap2); } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/BatchGroup.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/BatchGroup.java index 3fe6d19..0295e3c 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/BatchGroup.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/BatchGroup.java @@ -98,7 +98,12 @@ public class BatchGroup extends GenericGroup { final float layerAlpha = instance.layerAlphas[layerIndex]; if ((geosetColor[3] > 0) && (layerAlpha > 0)) { - final int layerTexture = instance.layerTextures[layerIndex]; + // BELOW: I updated it to "Math.max(0," because MDL and MDX parser for PRSCMOD + // menu screen behaved differently, + // the MDL case was getting "no data" for default value when unanimated, and "no + // data" resolved to -1, + // whereas MDX binary contained an "unused" 0 value. + final int layerTexture = Math.max(0, instance.layerTextures[layerIndex]); final float[] uvAnim = instance.uvAnims[layerIndex]; shader.setUniform4fv("u_geosetColor", geosetColor, 0, geosetColor.length); From 876b26e42fd889ec6767ff07673f1f0fc1d3dd3a Mon Sep 17 00:00:00 2001 From: Retera Date: Mon, 8 Mar 2021 08:31:23 -0500 Subject: [PATCH 108/116] Rename sample file --- core/assets/warsmash.ini | 61 ++++++++++++++++++++++++--------- core/assets/warsmash122.ini | 53 ---------------------------- core/assets/warsmashPRSCMOD.ini | 26 ++++++++++++++ 3 files changed, 70 insertions(+), 70 deletions(-) delete mode 100644 core/assets/warsmash122.ini create mode 100644 core/assets/warsmashPRSCMOD.ini diff --git a/core/assets/warsmash.ini b/core/assets/warsmash.ini index 0c75d66..2f07845 100644 --- a/core/assets/warsmash.ini +++ b/core/assets/warsmash.ini @@ -1,26 +1,53 @@ -// This is the Warsmash INI file for Project Revolution -// PRSCMOD - [DataSources] -Count=9 +Count=8 Type00=MPQ -Path00="D:\Games\Warcraft III Project Revolution\War3\The Sheep Attack\war3.mpq" +Path00="D:\Games\Warcraft III Patch 1.22\war3.mpq" Type01=MPQ -Path01="D:\Games\Warcraft III Project Revolution\War3\The Sheep Attack\War3x.mpq" +Path01="D:\Games\Warcraft III Patch 1.22\War3x.mpq" Type02=MPQ -Path02="D:\Games\Warcraft III Project Revolution\War3\The Sheep Attack\War3xlocal.mpq" +Path02="D:\Games\Warcraft III Patch 1.22\War3xlocal.mpq" Type03=MPQ -Path03="D:\Games\Warcraft III Project Revolution\War3\The Sheep Attack\war3patch.mpq" -Type04=MPQ -Path04="D:\Games\Warcraft III Project Revolution\PRSCMOD\Revolution.mpq" -Type05=MPQ -Path05="D:\Games\Warcraft III Project Revolution\PRSCMOD\Sound.mpq" +Path03="D:\Games\Warcraft III Patch 1.22\War3Patch.mpq" +Type04=Folder +Path04="..\..\resources" +Type05=Folder +Path05="D:\Backups\Warsmash\Data" Type06=Folder -Path06="D:\Games\Warcraft III Project Revolution\ProjectRevolusmash" +Path06="D:\Games\Warcraft III Patch 1.22\Maps" Type07=Folder -Path07="..\..\resources" -Type08=Folder -Path08="D:\Games\Warcraft III Project Revolution\PRSCMOD\PR-Maps" +Path07="." [Map] -FilePath="ProjectRevolusmash.w3x" \ No newline at end of file +//FilePath="CombatUnitTests.w3x" +//FilePath="PitchRoll.w3x" +//FilePath="PeonStartingBase_Simple.w3x" +FilePath="PeonStartingBase_Scythe.w3x" +//FilePath="MyStromguarde.w3m" +//FilePath="ColdArrows.w3m" +//FilePath="DungeonGoldMine.w3m" +//FilePath="PlayerPeasants.w3m" +//FilePath="FireLord.w3x" +//FilePath="Maps\Campaign\NightElf03.w3m" +//FilePath="PhoenixAttack.w3x" +//FilePath="LightEnvironmentTest.w3x" +//FilePath="TorchLight2.w3x" +//FilePath="OrcAssault.w3x" +//FilePath="FrostyVsFarm.w3m" +//FilePath="ModelTest.w3x" +//FilePath="SpinningSample.w3x" +//FilePath="Maps\Campaign\Prologue02.w3m" +//FilePath="Pathing.w3x" +//FilePath="ItemFacing.w3x" +//FilePath=SomeParticleTests.w3x +//FilePath="PeonMiningMultiHall.w3x" +//FilePath="QuadtreeBugs.w3x" +//FilePath="test2.w3x" +//FilePath="FarseerHoldPositionTest.w3x" +//FilePath="Ramps.w3m" +//FilePath="V1\Farm.w3x" +//FilePath="PenguinWorld.w3x" +//FilePath="Maps\FrozenThrone\Campaign\UndeadX09.w3x" +//FilePath="LavellaLagoon.w3x" +//FilePath="WiceOrc.w3x" +//FilePath="NorthrendPathingDoodle.w3x" +//FilePath="Maps\Campaign\Prologue01.w3m" diff --git a/core/assets/warsmash122.ini b/core/assets/warsmash122.ini deleted file mode 100644 index 2f07845..0000000 --- a/core/assets/warsmash122.ini +++ /dev/null @@ -1,53 +0,0 @@ -[DataSources] -Count=8 -Type00=MPQ -Path00="D:\Games\Warcraft III Patch 1.22\war3.mpq" -Type01=MPQ -Path01="D:\Games\Warcraft III Patch 1.22\War3x.mpq" -Type02=MPQ -Path02="D:\Games\Warcraft III Patch 1.22\War3xlocal.mpq" -Type03=MPQ -Path03="D:\Games\Warcraft III Patch 1.22\War3Patch.mpq" -Type04=Folder -Path04="..\..\resources" -Type05=Folder -Path05="D:\Backups\Warsmash\Data" -Type06=Folder -Path06="D:\Games\Warcraft III Patch 1.22\Maps" -Type07=Folder -Path07="." - -[Map] -//FilePath="CombatUnitTests.w3x" -//FilePath="PitchRoll.w3x" -//FilePath="PeonStartingBase_Simple.w3x" -FilePath="PeonStartingBase_Scythe.w3x" -//FilePath="MyStromguarde.w3m" -//FilePath="ColdArrows.w3m" -//FilePath="DungeonGoldMine.w3m" -//FilePath="PlayerPeasants.w3m" -//FilePath="FireLord.w3x" -//FilePath="Maps\Campaign\NightElf03.w3m" -//FilePath="PhoenixAttack.w3x" -//FilePath="LightEnvironmentTest.w3x" -//FilePath="TorchLight2.w3x" -//FilePath="OrcAssault.w3x" -//FilePath="FrostyVsFarm.w3m" -//FilePath="ModelTest.w3x" -//FilePath="SpinningSample.w3x" -//FilePath="Maps\Campaign\Prologue02.w3m" -//FilePath="Pathing.w3x" -//FilePath="ItemFacing.w3x" -//FilePath=SomeParticleTests.w3x -//FilePath="PeonMiningMultiHall.w3x" -//FilePath="QuadtreeBugs.w3x" -//FilePath="test2.w3x" -//FilePath="FarseerHoldPositionTest.w3x" -//FilePath="Ramps.w3m" -//FilePath="V1\Farm.w3x" -//FilePath="PenguinWorld.w3x" -//FilePath="Maps\FrozenThrone\Campaign\UndeadX09.w3x" -//FilePath="LavellaLagoon.w3x" -//FilePath="WiceOrc.w3x" -//FilePath="NorthrendPathingDoodle.w3x" -//FilePath="Maps\Campaign\Prologue01.w3m" diff --git a/core/assets/warsmashPRSCMOD.ini b/core/assets/warsmashPRSCMOD.ini new file mode 100644 index 0000000..0c75d66 --- /dev/null +++ b/core/assets/warsmashPRSCMOD.ini @@ -0,0 +1,26 @@ +// This is the Warsmash INI file for Project Revolution +// PRSCMOD + +[DataSources] +Count=9 +Type00=MPQ +Path00="D:\Games\Warcraft III Project Revolution\War3\The Sheep Attack\war3.mpq" +Type01=MPQ +Path01="D:\Games\Warcraft III Project Revolution\War3\The Sheep Attack\War3x.mpq" +Type02=MPQ +Path02="D:\Games\Warcraft III Project Revolution\War3\The Sheep Attack\War3xlocal.mpq" +Type03=MPQ +Path03="D:\Games\Warcraft III Project Revolution\War3\The Sheep Attack\war3patch.mpq" +Type04=MPQ +Path04="D:\Games\Warcraft III Project Revolution\PRSCMOD\Revolution.mpq" +Type05=MPQ +Path05="D:\Games\Warcraft III Project Revolution\PRSCMOD\Sound.mpq" +Type06=Folder +Path06="D:\Games\Warcraft III Project Revolution\ProjectRevolusmash" +Type07=Folder +Path07="..\..\resources" +Type08=Folder +Path08="D:\Games\Warcraft III Project Revolution\PRSCMOD\PR-Maps" + +[Map] +FilePath="ProjectRevolusmash.w3x" \ No newline at end of file From 5e3150124a63b7a67a57605652bc74f61c523124 Mon Sep 17 00:00:00 2001 From: Retera Date: Tue, 16 Mar 2021 01:03:24 -0400 Subject: [PATCH 109/116] Inventory system for basic item pickup and drop but no item behaviors yet --- .../parsers/fdf/frames/SpriteFrame.java | 7 + .../parsers/fdf/frames/StringFrame.java | 15 + .../warsmash/util/WarsmashConstants.java | 2 +- .../viewer5/handlers/mdx/EmitterGroup.java | 2 +- .../handlers/mdx/GeometryEmitterFuncs.java | 2 +- .../handlers/mdx/MdxComplexInstance.java | 12 + .../viewer5/handlers/w3x/TextTag.java | 4 +- .../viewer5/handlers/w3x/War3MapViewer.java | 114 +++- .../handlers/w3x/environment/PathingGrid.java | 5 +- .../handlers/w3x/environment/Terrain.java | 6 +- .../handlers/w3x/rendersim/RenderItem.java | 127 ++++- .../w3x/rendersim/RenderUnitTypeData.java | 15 +- .../w3x/rendersim/ability/AbilityDataUI.java | 30 +- .../w3x/rendersim/ability/ItemUI.java | 32 ++ .../w3x/simulation/CGameplayConstants.java | 43 ++ .../handlers/w3x/simulation/CItem.java | 66 ++- .../handlers/w3x/simulation/CItemType.java | 151 ++++++ .../handlers/w3x/simulation/CSimulation.java | 39 +- .../handlers/w3x/simulation/CUnit.java | 54 +- .../w3x/simulation/CUnitStateListener.java | 9 + .../handlers/w3x/simulation/CUnitType.java | 9 +- .../handlers/w3x/simulation/CWidget.java | 2 + .../w3x/simulation/abilities/CAbility.java | 3 +- .../simulation/abilities/CAbilityAttack.java | 3 +- .../simulation/abilities/CAbilityGeneric.java | 3 +- .../simulation/abilities/CAbilityMove.java | 3 +- .../build/AbstractCAbilityBuild.java | 3 +- .../build/CAbilityBuildInProgress.java | 3 +- .../abilities/combat/CAbilityColdArrows.java | 3 +- .../generic/AbstractGenericNoIconAbility.java | 4 +- ...bstractGenericSingleIconActiveAbility.java | 3 +- .../abilities/hero/CAbilityHero.java | 3 +- .../inventory/CAbilityInventory.java | 240 +++++++++ .../abilities/queue/CAbilityQueue.java | 3 +- .../abilities/queue/CAbilityRally.java | 3 +- .../targeting/AbilityTargetItemVisitor.java | 30 ++ .../AbilityTargetStillAliveVisitor.java | 2 +- .../impl/CAbilityTypeDefinitionInventory.java | 42 ++ .../types/impl/CAbilityTypeInventory.java | 24 + .../impl/CAbilityTypeInventoryLevelData.java | 47 ++ .../behaviors/build/CBehaviorOrcBuild.java | 9 +- .../behaviors/build/CBehaviorUndeadBuild.java | 9 +- .../inventory/CBehaviorDropItem.java | 71 +++ .../behaviors/inventory/CBehaviorGetItem.java | 70 +++ .../w3x/simulation/data/CAbilityData.java | 2 + .../w3x/simulation/data/CItemData.java | 109 ++++ .../w3x/simulation/data/CUnitData.java | 6 +- .../orders/COrderDropItemAtPoint.java | 111 ++++ .../w3x/simulation/orders/COrderNoTarget.java | 5 +- .../simulation/orders/COrderTargetPoint.java | 5 +- .../simulation/orders/COrderTargetWidget.java | 5 +- .../players/CPlayerUnitOrderExecutor.java | 9 + .../players/CPlayerUnitOrderListener.java | 5 +- .../util/SimulationRenderController.java | 5 + .../handlers/w3x/ui/CommandCardIcon.java | 102 ++-- .../viewer5/handlers/w3x/ui/MeleeUI.java | 410 +++++++++++--- .../handlers/w3x/ui/MeleeUIMinimap.java | 2 +- .../viewer5/handlers/w3x/ui/QueueIcon.java | 15 +- .../command/CommandCardCommandListener.java | 2 +- .../w3x/ui/command/CommandErrorListener.java | 2 + .../command/SettableCommandErrorListener.java | 5 + .../rms/parsers/mdlx/MdlxModel.java | 2 +- .../parsers/mdlx/MdlxTextureAnimation.java | 2 +- .../rms/parsers/mdlx/mdl/MdlUtils.java | 5 +- .../gdx/backends/lwjgl/LwjglCanvas.java | 501 ++++++++++++++++++ 65 files changed, 2414 insertions(+), 223 deletions(-) create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/ItemUI.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/inventory/CAbilityInventory.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/targeting/AbilityTargetItemVisitor.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/definitions/impl/CAbilityTypeDefinitionInventory.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeInventory.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeInventoryLevelData.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/inventory/CBehaviorDropItem.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/inventory/CBehaviorGetItem.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderDropItemAtPoint.java create mode 100644 desktop/src/com/badlogic/gdx/backends/lwjgl/LwjglCanvas.java diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/SpriteFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/SpriteFrame.java index e8167dc..ba042ba 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/SpriteFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/SpriteFrame.java @@ -127,4 +127,11 @@ public class SpriteFrame extends AbstractRenderableFrame { return this.instance.sequenceEnded; } + public void setReplaceableId(final int replaceableId, final String blpPath) { + if (this.instance != null) { + this.instance.setReplaceableTexture(replaceableId, blpPath); + } + + } + } diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/StringFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/StringFrame.java index eef0852..24cef0f 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/StringFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/StringFrame.java @@ -48,18 +48,30 @@ public class StringFrame extends AbstractRenderableFrame { public void setColor(final Color color) { this.color = color; + for (final SingleStringFrame internalFrame : this.internalFrames) { + internalFrame.setColor(color); + } } public void setFontShadowColor(final Color fontShadowColor) { this.fontShadowColor = fontShadowColor; + for (final SingleStringFrame internalFrame : this.internalFrames) { + internalFrame.setFontShadowColor(fontShadowColor); + } } public void setFontShadowOffsetX(final float fontShadowOffsetX) { this.fontShadowOffsetX = fontShadowOffsetX; + for (final SingleStringFrame internalFrame : this.internalFrames) { + internalFrame.setFontShadowOffsetX(fontShadowOffsetX); + } } public void setFontShadowOffsetY(final float fontShadowOffsetY) { this.fontShadowOffsetY = fontShadowOffsetY; + for (final SingleStringFrame internalFrame : this.internalFrames) { + internalFrame.setFontShadowOffsetY(fontShadowOffsetY); + } } @Override @@ -414,6 +426,9 @@ public class StringFrame extends AbstractRenderableFrame { public void setAlpha(final float alpha) { this.alpha = alpha; + for (final SingleStringFrame internalFrame : this.internalFrames) { + internalFrame.setAlpha(alpha); + } } diff --git a/core/src/com/etheller/warsmash/util/WarsmashConstants.java b/core/src/com/etheller/warsmash/util/WarsmashConstants.java index 694d5b2..6b2e4ab 100644 --- a/core/src/com/etheller/warsmash/util/WarsmashConstants.java +++ b/core/src/com/etheller/warsmash/util/WarsmashConstants.java @@ -6,7 +6,7 @@ public class WarsmashConstants { * With version, we use 0 for RoC, 1 for TFT emulation, and probably 2+ or * whatever for custom mods and other stuff */ - public static int GAME_VERSION = 0; + public static int GAME_VERSION = 1; public static final int REPLACEABLE_TEXTURE_LIMIT = 64; public static final float SIMULATION_STEP_TIME = 1 / 20f; public static final int PORT_NUMBER = 6115; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EmitterGroup.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EmitterGroup.java index b4ed813..7fed7a8 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EmitterGroup.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/EmitterGroup.java @@ -39,7 +39,7 @@ public class EmitterGroup extends GenericGroup { viewer.webGL.useShaderProgram(shader); shader.setUniformMatrix("u_mvp", mvp); - shader.setUniformf("u_texture", 0); + shader.setUniformi("u_texture", 0); final int a_position = shader.getAttributeLocation("a_position"); instancedArrays.glVertexAttribDivisorANGLE(a_position, 0); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GeometryEmitterFuncs.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GeometryEmitterFuncs.java index 1cf61cd..378e5c7 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GeometryEmitterFuncs.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/GeometryEmitterFuncs.java @@ -433,7 +433,7 @@ public class GeometryEmitterFuncs { buffer.bindAndUpdate(size); - shader.setUniformi("u_emitter", emitterType); + shader.setUniformf("u_emitter", emitterType); shader.setVertexAttribute("a_p0", 3, GL20.GL_FLOAT, false, BYTES_PER_OBJECT, BYTE_OFFSET_P0); shader.setVertexAttribute("a_p1", 3, GL20.GL_FLOAT, false, BYTES_PER_OBJECT, BYTE_OFFSET_P1); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java index 61b203e..67ba901 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java @@ -6,6 +6,8 @@ import java.nio.FloatBuffer; import java.util.ArrayList; import java.util.List; +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.math.Matrix4; import com.badlogic.gdx.math.Quaternion; import com.badlogic.gdx.math.Vector3; @@ -510,6 +512,11 @@ public class MdxComplexInstance extends ModelInstance { for (final GenericGroup group : model.opaqueGroups) { group.render(this, mvp); } + + final int glGetError = Gdx.gl.glGetError(); + if (glGetError != GL20.GL_NO_ERROR) { + throw new IllegalStateException("GL ERROR: " + glGetError + " ON " + model.name + " (Opaque)"); + } } @Override @@ -521,6 +528,11 @@ public class MdxComplexInstance extends ModelInstance { for (final GenericGroup group : model.translucentGroups) { group.render(this, this.scene.camera.viewProjectionMatrix); + + final int glGetError = Gdx.gl.glGetError(); + if (glGetError != GL20.GL_NO_ERROR) { + throw new IllegalStateException("GL ERROR: " + glGetError + " ON " + model.name + " (Translucent)"); + } } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/TextTag.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/TextTag.java index af3bb82..921995b 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/TextTag.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/TextTag.java @@ -20,7 +20,7 @@ public class TextTag { public boolean update(final float deltaTime) { this.screenCoordsZHeight += 60.0f * deltaTime; this.lifetime += deltaTime; - return this.lifetime > 3.5f; + return this.lifetime > 2.5f; } public Vector3 getPosition() { @@ -28,7 +28,7 @@ public class TextTag { } public float getRemainingLife() { - return 3.5f - this.lifetime; + return 2.5f - this.lifetime; } public Color getColor() { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index 38324f2..e5f2409 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -24,6 +24,7 @@ import org.apache.commons.compress.utils.SeekableInMemoryByteChannel; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.math.Vector3; @@ -91,6 +92,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderWidget; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.AbilityDataUI; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.AbilityUI; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CDestructable; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CItem; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitClassification; @@ -130,6 +132,8 @@ public class War3MapViewer extends AbstractMdxModelViewer { private static final War3ID DESTRUCTABLE_PATHING_DEATH = War3ID.fromString("bptd"); private static final War3ID ELEVATION_SAMPLE_RADIUS = War3ID.fromString("uerd"); private static final War3ID MAX_PITCH = War3ID.fromString("umxp"); + private static final War3ID ALLOW_CUSTOM_TEAM_COLOR = War3ID.fromString("utcc"); + private static final War3ID TEAM_COLOR = War3ID.fromString("utco"); private static final War3ID MAX_ROLL = War3ID.fromString("umxr"); private static final War3ID sloc = War3ID.fromString("sloc"); private static final LoadGenericCallback stringDataCallback = new StringDataCallbackImplementation(); @@ -160,7 +164,6 @@ public class War3MapViewer extends AbstractMdxModelViewer { public MappedData unitMetaData = new MappedData(); public List widgets = new ArrayList<>(); public List units = new ArrayList<>(); - public List items = new ArrayList<>(); public List projectiles = new ArrayList<>(); public boolean unitsReady; public War3Map mapMpq; @@ -199,6 +202,7 @@ public class War3MapViewer extends AbstractMdxModelViewer { private final Map unitToRenderPeer = new HashMap<>(); private final Map destructableToRenderPeer = new HashMap<>(); + private final Map itemToRenderPeer = new HashMap<>(); private final Map unitIdToTypeData = new HashMap<>(); private GameUI gameUI; private Vector3 lightDirection; @@ -451,7 +455,7 @@ public class War3MapViewer extends AbstractMdxModelViewer { this.confirmationInstance.setScene(this.worldScene); this.allObjectData = this.mapMpq.readModifications(); - this.simulation = new CSimulation(this.miscData, this.allObjectData.getUnits(), + this.simulation = new CSimulation(this.miscData, this.allObjectData.getUnits(), this.allObjectData.getItems(), this.allObjectData.getDestructibles(), this.allObjectData.getAbilities(), new SimulationRenderController() { private final Map keyToCombatSound = new HashMap<>(); @@ -541,10 +545,7 @@ public class War3MapViewer extends AbstractMdxModelViewer { @Override public void spawnDamageSound(final CWidget damagedDestructable, final String weaponSound, final String armorType) { - RenderWidget damagedWidget = War3MapViewer.this.unitToRenderPeer.get(damagedDestructable); - if (damagedWidget == null) { - damagedWidget = War3MapViewer.this.destructableToRenderPeer.get(damagedDestructable); - } + final RenderWidget damagedWidget = getRenderPeer(damagedDestructable); if (damagedWidget == null) { return; } @@ -621,7 +622,7 @@ public class War3MapViewer extends AbstractMdxModelViewer { public CUnit createUnit(final CSimulation simulation, final War3ID typeId, final int playerIndex, final float x, final float y, final float facing) { return createNewUnit(War3MapViewer.this.allObjectData, typeId, x, y, 0f, playerIndex, - (float) Math.toRadians(facing)); + playerIndex, (float) Math.toRadians(facing)); } @Override @@ -724,6 +725,26 @@ public class War3MapViewer extends AbstractMdxModelViewer { break; } } + + @Override + public void spawnUIUnitGetItemSound(final CUnit cUnit, final CItem item) { + final RenderUnit renderPeer = War3MapViewer.this.unitToRenderPeer.get(cUnit); + if (localPlayerIndex == renderPeer.getSimulationUnit().getPlayerIndex()) { + War3MapViewer.this.uiSounds.getSound("ItemGet").play( + War3MapViewer.this.worldScene.audioContext, renderPeer.getX(), renderPeer.getY(), + renderPeer.getZ()); + } + } + + @Override + public void spawnUIUnitDropItemSound(final CUnit cUnit, final CItem item) { + final RenderUnit renderPeer = War3MapViewer.this.unitToRenderPeer.get(cUnit); + if (localPlayerIndex == renderPeer.getSimulationUnit().getPlayerIndex()) { + War3MapViewer.this.uiSounds.getSound("ItemDrop").play( + War3MapViewer.this.worldScene.audioContext, renderPeer.getX(), renderPeer.getY(), + renderPeer.getZ()); + } + } }, this.terrain.pathingGrid, this.terrain.getEntireMap(), this.seededRandom, w3iFile.getPlayers(), this.commandErrorListener); @@ -983,10 +1004,11 @@ public class War3MapViewer extends AbstractMdxModelViewer { final float unitY = unit.getLocation()[1]; final float unitZ = unit.getLocation()[2]; final int playerIndex = unit.getPlayer(); + final int customTeamColor = unit.getCustomTeamColor(); final float unitAngle = unit.getAngle(); final CUnit unitCreated = createNewUnit(modifications, unitId, unitX, unitY, unitZ, playerIndex, - unitAngle); + customTeamColor, unitAngle); if (unit.getGoldAmount() != 0) { unitCreated.setGold(unit.getGoldAmount()); } @@ -1001,7 +1023,7 @@ public class War3MapViewer extends AbstractMdxModelViewer { } private CUnit createNewUnit(final Warcraft3MapObjectData modifications, final War3ID unitId, float unitX, - float unitY, final float unitZ, final int playerIndex, final float unitAngle) { + float unitY, final float unitZ, final int playerIndex, int customTeamColor, final float unitAngle) { UnitSoundset soundset = null; MutableGameObject row = null; String path = null; @@ -1174,7 +1196,15 @@ public class War3MapViewer extends AbstractMdxModelViewer { final CUnit simulationUnit = this.simulation.createUnit(row.getAlias(), playerIndex, unitX, unitY, angle, buildingPathingPixelMap, pathingInstance); final RenderUnitTypeData typeData = getUnitTypeData(unitId, row); - final RenderUnit renderUnit = new RenderUnit(this, model, row, unitX, unitY, unitZ, playerIndex, + if (!typeData.isAllowCustomTeamColor() || (customTeamColor == -1)) { + if (typeData.getTeamColor() != -1) { + customTeamColor = typeData.getTeamColor(); + } + else { + customTeamColor = playerIndex; + } + } + final RenderUnit renderUnit = new RenderUnit(this, model, row, unitX, unitY, unitZ, customTeamColor, soundset, portraitModel, simulationUnit, typeData, specialArtModel, buildingShadowInstance, this.selectionCircleScaleFactor); this.unitToRenderPeer.put(simulationUnit, renderUnit); @@ -1205,19 +1235,22 @@ public class War3MapViewer extends AbstractMdxModelViewer { return simulationUnit; } else { - this.items - .add(new RenderItem(this, model, row, unitX, unitY, unitZ, unitAngle, soundset, portraitModel)); // TODO - // store - // somewhere + final CItem simulationItem = this.simulation.createItem(row.getAlias(), unitX, unitY); + final RenderItem renderItem = new RenderItem(this, model, row, unitX, unitY, unitZ, unitAngle, soundset, + portraitModel, simulationItem); + this.widgets.add(renderItem); + this.itemToRenderPeer.put(simulationItem, renderItem); + if (unitShadowSplat != null) { unitShadowSplat.unitMapping.add(new Consumer() { @Override public void accept(final SplatMover t) { - + renderItem.shadow = t; } }); } if (unitShadowSplatDynamicIngame != null) { + renderItem.shadow = unitShadowSplatDynamicIngame; } } } @@ -1278,7 +1311,8 @@ public class War3MapViewer extends AbstractMdxModelViewer { RenderUnitTypeData unitTypeData = this.unitIdToTypeData.get(key); if (unitTypeData == null) { unitTypeData = new RenderUnitTypeData(row.getFieldAsFloat(MAX_PITCH, 0), row.getFieldAsFloat(MAX_ROLL, 0), - row.getFieldAsFloat(ELEVATION_SAMPLE_RADIUS, 0)); + row.getFieldAsFloat(ELEVATION_SAMPLE_RADIUS, 0), row.getFieldAsBoolean(ALLOW_CUSTOM_TEAM_COLOR, 0), + row.getFieldAsInteger(TEAM_COLOR, 0)); this.unitIdToTypeData.put(key, unitTypeData); } return unitTypeData; @@ -1287,13 +1321,14 @@ public class War3MapViewer extends AbstractMdxModelViewer { @Override public void update() { if (this.anyReady) { - this.terrain.update(); + final float deltaTime = Gdx.graphics.getDeltaTime(); + this.terrain.update(deltaTime); super.update(); final Iterator textTagIterator = this.textTags.iterator(); while (textTagIterator.hasNext()) { - if (textTagIterator.next().update(Gdx.graphics.getDeltaTime())) { + if (textTagIterator.next().update(deltaTime)) { textTagIterator.remove(); } } @@ -1307,13 +1342,6 @@ public class War3MapViewer extends AbstractMdxModelViewer { projectileIterator.remove(); } } - for (final RenderItem item : this.items) { - final MdxComplexInstance instance = item.instance; - final MdxComplexInstance mdxComplexInstance = instance; - if (mdxComplexInstance.sequenceEnded || (mdxComplexInstance.sequence == -1)) { - SequenceUtils.randomStandSequence(mdxComplexInstance); - } - } for (final RenderDoodad item : this.doodads) { final ModelInstance instance = item.instance; if (instance instanceof MdxComplexInstance) { @@ -1373,6 +1401,11 @@ public class War3MapViewer extends AbstractMdxModelViewer { scene.renderTranslucent(); } } + + final int glGetError = Gdx.gl.glGetError(); + if (glGetError != GL20.GL_NO_ERROR) { + throw new IllegalStateException("GL ERROR: " + glGetError); + } } } @@ -1492,11 +1525,11 @@ public class War3MapViewer extends AbstractMdxModelViewer { else { sel = Arrays.asList(entity); } + this.doSelectUnit(sel); } else { sel = Collections.emptyList(); } - this.doSelectUnit(sel); return sel; } @@ -1659,6 +1692,17 @@ public class War3MapViewer extends AbstractMdxModelViewer { return mdxPath; } + public String blp(String iconPath) { + final int lastDotIndex = iconPath.lastIndexOf('.'); + if (lastDotIndex != -1) { + iconPath = iconPath.substring(0, lastDotIndex); + } + if (!iconPath.toLowerCase().endsWith(".blp")) { + iconPath += ".blp"; + } + return iconPath; + } + public MdxModel loadModel(final String path) { return (MdxModel) load(mdx(path), PathSolver.DEFAULT, null); } @@ -1681,7 +1725,7 @@ public class War3MapViewer extends AbstractMdxModelViewer { public void setGameUI(final GameUI gameUI) { this.gameUI = gameUI; this.abilityDataUI = new AbilityDataUI(this.allObjectData.getAbilities(), this.allObjectData.getUnits(), - this.allObjectData.getUpgrades(), gameUI, this); + this.allObjectData.getItems(), this.allObjectData.getUpgrades(), gameUI, this); } public GameUI getGameUI() { @@ -1722,6 +1766,21 @@ public class War3MapViewer extends AbstractMdxModelViewer { return this.destructableToRenderPeer.get(dest); } + public RenderItem getRenderPeer(final CItem item) { + return this.itemToRenderPeer.get(item); + } + + private RenderWidget getRenderPeer(final CWidget damagedDestructable) { + RenderWidget damagedWidget = War3MapViewer.this.unitToRenderPeer.get(damagedDestructable); + if (damagedWidget == null) { + damagedWidget = War3MapViewer.this.destructableToRenderPeer.get(damagedDestructable); + } + if (damagedWidget == null) { + damagedWidget = War3MapViewer.this.itemToRenderPeer.get(damagedDestructable); + } + return damagedWidget; + } + private static final class QuadtreeIntersectorFindsWalkableRenderHeight implements QuadtreeIntersector { private float z; @@ -1793,4 +1852,5 @@ public class War3MapViewer extends AbstractMdxModelViewer { public void add(final TextTag textTag) { this.textTags.add(textTag); } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java index 10eac37..7f1b4ba 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/PathingGrid.java @@ -83,7 +83,7 @@ public class PathingGrid { data |= PathingFlags.UNFLYABLE; } if (((rgb & 0xFF0000) >>> 16) > 127) { - data |= PathingFlags.UNWALKABLE; + data |= PathingFlags.UNWALKABLE | PathingFlags.UNSWIMABLE; } this.dynamicPathingOverlay[(yy * this.pathingGridSizes[0]) + xx] |= data; } @@ -188,8 +188,7 @@ public class PathingGrid { public RemovablePathingMapInstance createRemovablePathingOverlayTexture(final float positionX, final float positionY, final int rotationInput, final BufferedImage pathingTextureTga) { - return new RemovablePathingMapInstance(positionX, - positionY, rotationInput, pathingTextureTga); + return new RemovablePathingMapInstance(positionX, positionY, rotationInput, pathingTextureTga); } public int getWidth() { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java index fe868ce..3cdb5a7 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/environment/Terrain.java @@ -188,7 +188,7 @@ public class Terrain { if (waterInfo != null) { this.waterHeightOffset = waterInfo.getFieldFloatValue("height"); this.waterTextureCount = waterInfo.getFieldValue("numTex"); - this.waterIncreasePerFrame = waterInfo.getFieldValue("texRate") / 60f; + this.waterIncreasePerFrame = waterInfo.getFieldValue("texRate"); } else { this.waterHeightOffset = 0; @@ -896,8 +896,8 @@ public class Terrain { } } - public void update() { - this.waterIndex += this.waterIncreasePerFrame; + public void update(final float deltaTime) { + this.waterIndex += this.waterIncreasePerFrame * deltaTime; if (this.waterIndex >= this.waterTextureCount) { this.waterIndex = 0; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderItem.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderItem.java index ac7169c..2dc82bd 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderItem.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderItem.java @@ -6,25 +6,35 @@ import com.etheller.warsmash.util.RenderMathUtils; import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; +import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; +import com.etheller.warsmash.viewer5.handlers.w3x.SplatModel.SplatMover; import com.etheller.warsmash.viewer5.handlers.w3x.UnitSoundset; import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CItem; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; -public class RenderItem { +public class RenderItem implements RenderWidget { private static final War3ID ITEM_MODEL_SCALE = War3ID.fromString("isca"); private static final War3ID ITEM_RED = War3ID.fromString("iclr"); private static final War3ID ITEM_GREEN = War3ID.fromString("iclg"); private static final War3ID ITEM_BLUE = War3ID.fromString("iclb"); + private final CItem simulationItem; public final MdxComplexInstance instance; public final MutableGameObject row; public final float[] location = new float[3]; public float radius; public UnitSoundset soundset; public final MdxModel portraitModel; + public SplatMover shadow; + public SplatMover selectionCircle; + private boolean hidden; + private boolean dead; public RenderItem(final War3MapViewer map, final MdxModel model, final MutableGameObject row, final float x, - final float y, final float z, final float angle, final UnitSoundset soundset, - final MdxModel portraitModel) { + final float y, final float z, final float angle, final UnitSoundset soundset, final MdxModel portraitModel, + final CItem simulationItem) { this.portraitModel = portraitModel; + this.simulationItem = simulationItem; final MdxComplexInstance instance = (MdxComplexInstance) model.addInstance(); this.location[0] = x; @@ -55,4 +65,115 @@ public class RenderItem { this.row = row; this.soundset = soundset; } + + @Override + public MdxComplexInstance getInstance() { + return this.instance; + } + + @Override + public CWidget getSimulationWidget() { + return this.simulationItem; + } + + @Override + public void updateAnimations(final War3MapViewer map) { + final boolean hidden = this.simulationItem.isHidden(); + if (hidden != this.hidden) { + this.hidden = hidden; + if (hidden) { + this.instance.hide(); + if (this.shadow != null) { + this.shadow.hide(); + } + } + else { + this.instance.show(); + if (this.shadow != null) { + this.shadow.show(map.terrain.centerOffset); + } + } + } + final boolean dead = this.simulationItem.isDead(); + final MdxComplexInstance mdxComplexInstance = this.instance; + if (dead) { + if (!this.dead) { + this.dead = dead; + SequenceUtils.randomDeathSequence(mdxComplexInstance); + } + } + else if (mdxComplexInstance.sequenceEnded || (mdxComplexInstance.sequence == -1)) { + SequenceUtils.randomStandSequence(mdxComplexInstance); + } + + final float prevX = this.location[0]; + final float prevY = this.location[1]; + final float simulationX = this.simulationItem.getX(); + final float simulationY = this.simulationItem.getY(); + final float simDx = simulationX - this.location[0]; + final float simDy = simulationY - this.location[1]; + this.location[0] = simulationX; + this.location[1] = simulationY; + final float dx = this.location[0] - prevX; + final float dy = this.location[1] - prevY; + final float groundHeight; + // land units will have their feet pass under the surface of the water, so items + // here are in the same place + final float groundHeightTerrainAndWater = map.terrain.getGroundHeight(this.location[0], this.location[1]); + MdxComplexInstance currentWalkableUnder; + currentWalkableUnder = map.getHighestWalkableUnder(this.location[0], this.location[1]); + War3MapViewer.gdxRayHeap.set(this.location[0], this.location[1], 4096, 0, 0, -8192); + if ((currentWalkableUnder != null) + && currentWalkableUnder.intersectRayWithCollision(War3MapViewer.gdxRayHeap, + War3MapViewer.intersectionHeap, true, true) + && (War3MapViewer.intersectionHeap.z > groundHeightTerrainAndWater)) { + groundHeight = War3MapViewer.intersectionHeap.z; + } + else { + groundHeight = groundHeightTerrainAndWater; + currentWalkableUnder = null; + } + this.location[2] = this.simulationItem.getFlyHeight() + groundHeight; + + this.instance.moveTo(this.location); + if (this.shadow != null) { + this.shadow.move(dx, dy, map.terrain.centerOffset); + this.shadow.setHeightAbsolute(currentWalkableUnder != null, groundHeight + map.imageWalkableZOffset); + } + } + + @Override + public boolean isIntersectedOnMeshAlways() { + return false; + } + + @Override + public float getSelectionScale() { + return 1.0f; + } + + @Override + public float getX() { + return this.location[0]; + } + + @Override + public float getY() { + return this.location[1]; + } + + @Override + public float getZ() { + return this.location[2]; + } + + @Override + public void unassignSelectionCircle() { + this.selectionCircle = null; + } + + @Override + public void assignSelectionCircle(final SplatMover t) { + this.selectionCircle = t; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnitTypeData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnitTypeData.java index 649ae3b..e6c1c6b 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnitTypeData.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnitTypeData.java @@ -4,11 +4,16 @@ public class RenderUnitTypeData { private final float maxPitch; private final float maxRoll; private final float sampleRadius; + private final boolean allowCustomTeamColor; + private final int teamColor; - public RenderUnitTypeData(final float maxPitch, final float maxRoll, final float sampleRadius) { + public RenderUnitTypeData(final float maxPitch, final float maxRoll, final float sampleRadius, + final boolean allowCustomTeamColor, final int teamColor) { this.maxPitch = maxPitch; this.maxRoll = maxRoll; this.sampleRadius = sampleRadius; + this.allowCustomTeamColor = allowCustomTeamColor; + this.teamColor = teamColor; } public float getMaxPitch() { @@ -22,4 +27,12 @@ public class RenderUnitTypeData { public float getElevationSampleRadius() { return this.sampleRadius; } + + public boolean isAllowCustomTeamColor() { + return this.allowCustomTeamColor; + } + + public int getTeamColor() { + return this.teamColor; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityDataUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityDataUI.java index 37a893a..42585ef 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityDataUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/AbilityDataUI.java @@ -45,6 +45,13 @@ public class AbilityDataUI { private static final War3ID UNIT_TIP = War3ID.fromString("utip"); private static final War3ID UNIT_UBER_TIP = War3ID.fromString("utub"); + private static final War3ID ITEM_ICON_NORMAL_X = War3ID.fromString("ubpx"); + private static final War3ID ITEM_ICON_NORMAL_Y = War3ID.fromString("ubpy"); + private static final War3ID ITEM_ICON_NORMAL = War3ID.fromString("iico"); + private static final War3ID ITEM_TIP = War3ID.fromString("utip"); + private static final War3ID ITEM_UBER_TIP = War3ID.fromString("utub"); + private static final War3ID ITEM_DESCRIPTION = War3ID.fromString("ides"); + private static final War3ID UPGRADE_ICON_NORMAL_X = War3ID.fromString("gbpx"); private static final War3ID UPGRADE_ICON_NORMAL_Y = War3ID.fromString("gbpy"); private static final War3ID UPGRADE_ICON_NORMAL = War3ID.fromString("gar1"); @@ -54,6 +61,7 @@ public class AbilityDataUI { private final Map rawcodeToUI = new HashMap<>(); private final Map rawcodeToUnitUI = new HashMap<>(); + private final Map rawcodeToItemUI = new HashMap<>(); private final Map> rawcodeToUpgradeUI = new HashMap<>(); private final IconUI moveUI; private final IconUI stopUI; @@ -74,7 +82,8 @@ public class AbilityDataUI { private final IconUI selectSkillUI; public AbilityDataUI(final MutableObjectData abilityData, final MutableObjectData unitData, - final MutableObjectData upgradeData, final GameUI gameUI, final War3MapViewer viewer) { + final MutableObjectData itemData, final MutableObjectData upgradeData, final GameUI gameUI, + final War3MapViewer viewer) { final String disabledPrefix = gameUI.getSkinField("CommandButtonDisabledArtPath"); for (final War3ID alias : abilityData.keySet()) { final MutableGameObject abilityTypeData = abilityData.get(alias); @@ -129,6 +138,21 @@ public class AbilityDataUI { this.rawcodeToUnitUI.put(alias, new IconUI(iconNormal, iconNormalDisabled, iconNormalX, iconNormalY, iconTip, iconUberTip)); } + for (final War3ID alias : itemData.keySet()) { + final MutableGameObject abilityTypeData = itemData.get(alias); + final String iconNormalPath = gameUI.trySkinField(abilityTypeData.getFieldAsString(ITEM_ICON_NORMAL, 0)); + final int iconNormalX = abilityTypeData.getFieldAsInteger(ITEM_ICON_NORMAL_X, 0); + final int iconNormalY = abilityTypeData.getFieldAsInteger(ITEM_ICON_NORMAL_Y, 0); + final String iconTip = abilityTypeData.getFieldAsString(ITEM_TIP, 0); + final String iconUberTip = abilityTypeData.getFieldAsString(ITEM_UBER_TIP, 0); + final String iconDescription = abilityTypeData.getFieldAsString(ITEM_DESCRIPTION, 0); + final Texture iconNormal = gameUI.loadTexture(iconNormalPath); + final Texture iconNormalDisabled = gameUI.loadTexture(disable(iconNormalPath, disabledPrefix)); + this.rawcodeToItemUI.put(alias, + new ItemUI( + new IconUI(iconNormal, iconNormalDisabled, iconNormalX, iconNormalY, iconTip, iconUberTip), + abilityTypeData.getName(), iconDescription, iconNormalPath)); + } for (final War3ID alias : upgradeData.keySet()) { final MutableGameObject upgradeTypeData = upgradeData.get(alias); final int upgradeLevels = upgradeTypeData.getFieldAsInteger(UPGRADE_LEVELS, 0); @@ -186,6 +210,10 @@ public class AbilityDataUI { return this.rawcodeToUnitUI.get(rawcode); } + public ItemUI getItemUI(final War3ID rawcode) { + return this.rawcodeToItemUI.get(rawcode); + } + public IconUI getUpgradeUI(final War3ID rawcode, final int level) { final List upgradeUI = this.rawcodeToUpgradeUI.get(rawcode); if (upgradeUI != null) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/ItemUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/ItemUI.java new file mode 100644 index 0000000..b1a6a4d --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/ability/ItemUI.java @@ -0,0 +1,32 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability; + +public class ItemUI { + private final IconUI iconUI; + private final String name; + private final String description; + private final String itemIconPathForDragging; + + public ItemUI(final IconUI iconUI, final String name, final String description, + final String itemIconPathForDragging) { + this.iconUI = iconUI; + this.name = name; + this.description = description; + this.itemIconPathForDragging = itemIconPathForDragging; + } + + public IconUI getIconUI() { + return this.iconUI; + } + + public String getName() { + return this.name; + } + + public String getDescription() { + return this.description; + } + + public String getItemIconPathForDragging() { + return this.itemIconPathForDragging; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CGameplayConstants.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CGameplayConstants.java index f885d54..2cba007 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CGameplayConstants.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CGameplayConstants.java @@ -65,6 +65,17 @@ public class CGameplayConstants { private final boolean maxLevelHeroesDrainExp; private final boolean buildingKillsGiveExp; + private final float dropItemRange; + private final float giveItemRange; + private final float pickupItemRange; + private final float pawnItemRange; + private final float pawnItemRate; + + private final float followRange; + private final float structureFollowRange; + private final float followItemRange; + private final float spellCastRangeBuffer; + public CGameplayConstants(final DataTable parsedDataTable) { final Element miscData = parsedDataTable.get("Misc"); // TODO use radians for half angle @@ -155,6 +166,18 @@ public class CGameplayConstants { this.agiAttackSpeedBonus = miscData.getFieldFloatValue("AgiAttackSpeedBonus"); this.heroAbilityLevelSkip = miscData.getFieldValue("HeroAbilityLevelSkip"); + + this.dropItemRange = miscData.getFieldFloatValue("DropItemRange"); + this.giveItemRange = miscData.getFieldFloatValue("GiveItemRange"); + this.pickupItemRange = miscData.getFieldFloatValue("PickupItemRange"); + this.pawnItemRange = miscData.getFieldFloatValue("PawnItemRange"); + this.pawnItemRate = miscData.getFieldFloatValue("PawnItemRate"); + + this.followRange = miscData.getFieldFloatValue("FollowRange"); + this.structureFollowRange = miscData.getFieldFloatValue("StructureFollowRange"); + this.followItemRange = miscData.getFieldFloatValue("FollowItemRange"); + + this.spellCastRangeBuffer = miscData.getFieldFloatValue("SpellCastRangeBuffer"); } public float getAttackHalfAngle() { @@ -305,6 +328,26 @@ public class CGameplayConstants { return getTableValue(this.grantNormalXp, level); } + public float getDropItemRange() { + return this.dropItemRange; + } + + public float getPickupItemRange() { + return this.pickupItemRange; + } + + public float getGiveItemRange() { + return this.giveItemRange; + } + + public float getPawnItemRange() { + return this.pawnItemRange; + } + + public float getPawnItemRate() { + return this.pawnItemRate; + } + private static int getTableValue(final int[] table, int level) { if (level <= 0) { return 0; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CItem.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CItem.java index db6e5c1..2548e4d 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CItem.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CItem.java @@ -3,17 +3,22 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation; import java.util.EnumSet; import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid; +import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.MovementType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTargetVisitor; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; public class CItem extends CWidget { + private final War3ID typeId; + private final CItemType itemType; + private boolean hidden; - private final War3ID itemType; - - public CItem(final int handleId, final float x, final float y, final float life, final War3ID itemType) { + public CItem(final int handleId, final float x, final float y, final float life, final War3ID typeId, + final CItemType itemTypeInstance) { super(handleId, x, y, life); - this.itemType = itemType; + this.typeId = typeId; + this.itemType = itemTypeInstance; } @Override @@ -30,6 +35,7 @@ public class CItem extends CWidget { public void damage(final CSimulation simulation, final CUnit source, final CAttackType attackType, final String weaponType, final float damage) { this.life -= damage; + simulation.itemDamageEvent(this, weaponType, this.itemType.getArmorType()); } @Override @@ -38,8 +44,60 @@ public class CItem extends CWidget { return targetsAllowed.contains(CTargetType.ITEM); } + public void setX(final float x, final CWorldCollision collision) { + super.setX(x); + } + + public void setY(final float y, final CWorldCollision collision) { + super.setY(y); + } + @Override public T visit(final AbilityTargetVisitor visitor) { return visitor.accept(this); } + + public War3ID getTypeId() { + return this.typeId; + } + + public CItemType getItemType() { + return this.itemType; + } + + public void setHidden(final boolean hidden) { + this.hidden = hidden; + } + + public boolean isHidden() { + return this.hidden; + } + + public void setPointAndCheckUnstuck(final float newX, final float newY, final CSimulation game) { + final CWorldCollision collision = game.getWorldCollision(); + final PathingGrid pathingGrid = game.getPathingGrid(); + ; + float outputX = newX, outputY = newY; + int checkX = 0; + int checkY = 0; + float collisionSize; + tempRect.setSize(16, 16); + collisionSize = 16; + for (int i = 0; i < 300; i++) { + final float centerX = newX + (checkX * 64); + final float centerY = newY + (checkY * 64); + tempRect.setCenter(centerX, centerY); + if (pathingGrid.isPathable(centerX, centerY, MovementType.FOOT, collisionSize)) { + outputX = centerX; + outputY = centerY; + break; + } + final double angle = ((((int) Math.floor(Math.sqrt((4 * i) + 1))) % 4) * Math.PI) / 2; + checkX -= (int) Math.cos(angle); + checkY -= (int) Math.sin(angle); + } + setX(outputX); + setY(outputY); + } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CItemType.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CItemType.java index 8f40ce1..47de954 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CItemType.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CItemType.java @@ -1,5 +1,156 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation; +import java.util.List; + +import com.etheller.warsmash.util.War3ID; + public class CItemType { + private final List abilityList; + private final War3ID cooldownGroup; + private final boolean ignoreCooldown; + private final int numberOfCharges; + private final boolean activelyUsed; + private final boolean perishable; + private final boolean useAutomaticallyWhenAcquired; + private final int goldCost; + private final int lumberCost; + private final int stockMax; + private final int stockReplenishInterval; + private final int stockStartDelay; + private final int hitPoints; + private final String armorType; + private final int level; + private final int levelUnclassified; + private final int priority; + private final boolean sellable; + private final boolean pawnable; + private final boolean droppedWhenCarrierDies; + private final boolean canBeDropped; + private final boolean validTargetForTransformation; + private final boolean includeAsRandomChoice; + + public CItemType(final List abilityList, final War3ID cooldownGroup, final boolean ignoreCooldown, + final int numberOfCharges, final boolean activelyUsed, final boolean perishable, + final boolean useAutomaticallyWhenAcquired, final int goldCost, final int lumberCost, final int stockMax, + final int stockReplenishInterval, final int stockStartDelay, final int hitPoints, final String armorType, + final int level, final int levelUnclassified, final int priority, final boolean sellable, + final boolean pawnable, final boolean droppedWhenCarrierDies, final boolean canBeDropped, + final boolean validTargetForTransformation, final boolean includeAsRandomChoice) { + this.abilityList = abilityList; + this.cooldownGroup = cooldownGroup; + this.ignoreCooldown = ignoreCooldown; + this.numberOfCharges = numberOfCharges; + this.activelyUsed = activelyUsed; + this.perishable = perishable; + this.useAutomaticallyWhenAcquired = useAutomaticallyWhenAcquired; + this.goldCost = goldCost; + this.lumberCost = lumberCost; + this.stockMax = stockMax; + this.stockReplenishInterval = stockReplenishInterval; + this.stockStartDelay = stockStartDelay; + this.hitPoints = hitPoints; + this.armorType = armorType; + this.level = level; + this.levelUnclassified = levelUnclassified; + this.priority = priority; + this.sellable = sellable; + this.pawnable = pawnable; + this.droppedWhenCarrierDies = droppedWhenCarrierDies; + this.canBeDropped = canBeDropped; + this.validTargetForTransformation = validTargetForTransformation; + this.includeAsRandomChoice = includeAsRandomChoice; + } + + public List getAbilityList() { + return this.abilityList; + } + + public War3ID getCooldownGroup() { + return this.cooldownGroup; + } + + public boolean isIgnoreCooldown() { + return this.ignoreCooldown; + } + + public int getNumberOfCharges() { + return this.numberOfCharges; + } + + public boolean isActivelyUsed() { + return this.activelyUsed; + } + + public boolean isPerishable() { + return this.perishable; + } + + public boolean isUseAutomaticallyWhenAcquired() { + return this.useAutomaticallyWhenAcquired; + } + + public int getGoldCost() { + return this.goldCost; + } + + public int getLumberCost() { + return this.lumberCost; + } + + public int getStockMax() { + return this.stockMax; + } + + public int getStockReplenishInterval() { + return this.stockReplenishInterval; + } + + public int getStockStartDelay() { + return this.stockStartDelay; + } + + public int getHitPoints() { + return this.hitPoints; + } + + public String getArmorType() { + return this.armorType; + } + + public int getLevel() { + return this.level; + } + + public int getLevelUnclassified() { + return this.levelUnclassified; + } + + public int getPriority() { + return this.priority; + } + + public boolean isSellable() { + return this.sellable; + } + + public boolean isPawnable() { + return this.pawnable; + } + + public boolean isDroppedWhenCarrierDies() { + return this.droppedWhenCarrierDies; + } + + public boolean isCanBeDropped() { + return this.canBeDropped; + } + + public boolean isValidTargetForTransformation() { + return this.validTargetForTransformation; + } + + public boolean isIncludeAsRandomChoice() { + return this.includeAsRandomChoice; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java index 47874be..dc82645 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java @@ -26,6 +26,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUni import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.projectile.CAttackProjectile; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.data.CAbilityData; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.data.CDestructableData; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.data.CItemData; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.data.CUnitData; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.pathing.CPathfindingProcessor; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CAllianceType; @@ -40,9 +41,11 @@ public class CSimulation { private final CAbilityData abilityData; private final CUnitData unitData; private final CDestructableData destructableData; + private final CItemData itemData; private final List units; private final List newUnits; private final List destructables; + private final List items; private final List players; private final List projectiles; private final List newProjectiles; @@ -58,14 +61,15 @@ public class CSimulation { private float currentGameDayTimeElapsed; private final Map handleIdToUnit = new HashMap<>(); private final Map handleIdToDestructable = new HashMap<>(); + private final Map handleIdToItem = new HashMap<>(); private final Map handleIdToAbility = new HashMap<>(); private transient CommandErrorListener commandErrorListener; public CSimulation(final DataTable miscData, final MutableObjectData parsedUnitData, - final MutableObjectData parsedDestructableData, final MutableObjectData parsedAbilityData, - final SimulationRenderController simulationRenderController, final PathingGrid pathingGrid, - final Rectangle entireMapBounds, final Random seededRandom, final List playerInfos, - final CommandErrorListener commandErrorListener) { + final MutableObjectData parsedItemData, final MutableObjectData parsedDestructableData, + final MutableObjectData parsedAbilityData, final SimulationRenderController simulationRenderController, + final PathingGrid pathingGrid, final Rectangle entireMapBounds, final Random seededRandom, + final List playerInfos, final CommandErrorListener commandErrorListener) { this.gameplayConstants = new CGameplayConstants(miscData); this.simulationRenderController = simulationRenderController; this.pathingGrid = pathingGrid; @@ -73,9 +77,11 @@ public class CSimulation { this.unitData = new CUnitData(this.gameplayConstants, parsedUnitData, this.abilityData, this.simulationRenderController); this.destructableData = new CDestructableData(parsedDestructableData, simulationRenderController); + this.itemData = new CItemData(parsedItemData); this.units = new ArrayList<>(); this.newUnits = new ArrayList<>(); this.destructables = new ArrayList<>(); + this.items = new ArrayList<>(); this.projectiles = new ArrayList<>(); this.newProjectiles = new ArrayList<>(); this.handleIdAllocator = new HandleIdAllocator(); @@ -155,6 +161,13 @@ public class CSimulation { return dest; } + public CItem createItem(final War3ID alias, final float unitX, final float unitY) { + final CItem item = this.itemData.create(this, alias, unitX, unitY, this.handleIdAllocator.createId()); + this.handleIdToItem.put(item.getHandleId(), item); + this.items.add(item); + return item; + } + public CUnit createUnit(final War3ID typeId, final int playerIndex, final float x, final float y, final float facing) { return this.simulationRenderController.createUnit(this, typeId, playerIndex, x, y, facing); @@ -271,6 +284,10 @@ public class CSimulation { this.simulationRenderController.spawnDamageSound(damagedDestructable, weaponSound, armorType); } + public void itemDamageEvent(final CItem damageItem, final String weaponSound, final String armorType) { + this.simulationRenderController.spawnDamageSound(damageItem, weaponSound, armorType); + } + public void unitConstructedEvent(final CUnit constructingUnit, final CUnit constructedStructure) { this.simulationRenderController.spawnUnitConstructionSound(constructingUnit, constructedStructure); } @@ -279,7 +296,7 @@ public class CSimulation { return this.players.get(index); } - public CommandErrorListener getCommandErrorListener() { + public CommandErrorListener getCommandErrorListener(final int playerIndex) { return this.commandErrorListener; } @@ -317,6 +334,14 @@ public class CSimulation { this.playerHeroes[hero.getPlayerIndex()].add(hero); } + public void unitPickUpItemEvent(final CUnit cUnit, final CItem item) { + this.simulationRenderController.spawnUIUnitGetItemSound(cUnit, item); + } + + public void unitDropItemEvent(final CUnit cUnit, final CItem item) { + this.simulationRenderController.spawnUIUnitDropItemSound(cUnit, item); + } + public List getPlayerHeroes(final int playerIndex) { return this.playerHeroes[playerIndex]; } @@ -342,6 +367,10 @@ public class CSimulation { if (destructable != null) { return destructable; } + final CItem item = this.handleIdToItem.get(handleId); + if (item != null) { + return item; + } return null; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java index 557db7d..c64acc5 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java @@ -20,6 +20,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityBuildInProgress; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.hero.CAbilityHero; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.inventory.CAbilityInventory; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.mine.CAbilityGoldMine; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget; @@ -45,7 +46,6 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.BooleanAbility import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.BooleanAbilityTargetCheckReceiver; public class CUnit extends CWidget { - private static final Rectangle tempRect = new Rectangle(); private War3ID typeId; private float facing; // degrees private float mana; @@ -436,7 +436,7 @@ public class CUnit extends CWidget { if (ability != null) { // Allow the ability to response to the order without actually placing itself in // the queue, nor modifying (interrupting) the queue. - if (!ability.checkBeforeQueue(game, this, order.getOrderId())) { + if (!ability.checkBeforeQueue(game, this, order.getOrderId(), order.getTarget(game))) { this.stateNotifier.ordersChanged(); return; } @@ -629,9 +629,15 @@ public class CUnit extends CWidget { return this.unitType.getImpactZ(); } + public double angleTo(final AbilityTarget target) { + final double dx = target.getX() - getX(); + final double dy = target.getY() - getY(); + return StrictMath.atan2(dy, dx); + } + public double distance(final AbilityTarget target) { - double dx = Math.abs(target.getX() - getX()); - double dy = Math.abs(target.getY() - getY()); + double dx = StrictMath.abs(target.getX() - getX()); + double dy = StrictMath.abs(target.getY() - getY()); final float thisCollisionSize = this.unitType.getCollisionSize(); float targetCollisionSize; if (target instanceof CUnit) { @@ -701,17 +707,28 @@ public class CUnit extends CWidget { } else { if ((this.currentBehavior == null) || (this.currentBehavior == this.defaultBehavior)) { - if (!simulation.getPlayer(getPlayerIndex()).hasAlliance(source.getPlayerIndex(), - CAllianceType.PASSIVE)) { + boolean foundMatchingReturnFireAttack = false; + if (!simulation.getPlayer(getPlayerIndex()).hasAlliance(source.getPlayerIndex(), CAllianceType.PASSIVE) + && !this.unitType.getClassifications().contains(CUnitClassification.PEON)) { for (final CUnitAttack attack : this.getAttacks()) { if (source.canBeTargetedBy(simulation, this, attack.getTargetsAllowed())) { this.currentBehavior = getAttackBehavior().reset(OrderIds.attack, attack, source, false, CBehaviorAttackListener.DO_NOTHING); this.currentBehavior.begin(simulation); + foundMatchingReturnFireAttack = true; break; } } } + if (!foundMatchingReturnFireAttack && this.unitType.isCanFlee() && !isMovementDisabled() + && (this.moveBehavior != null)) { + final double angleTo = source.angleTo(this); + final int distanceToFlee = getSpeed(); + this.currentBehavior = this.moveBehavior.reset(OrderIds.move, + new AbilityPointTarget((float) (getX() + (distanceToFlee * StrictMath.cos(angleTo))), + (float) (getY() + (distanceToFlee * StrictMath.sin(angleTo))))); + this.currentBehavior.begin(simulation); + } } } } @@ -1194,7 +1211,7 @@ public class CUnit extends CWidget { } else { this.queuedUnitFoodPaid = false; - game.getCommandErrorListener().showNoFoodError(); + game.getCommandErrorListener(this.playerIndex).showNoFoodError(); } } } @@ -1374,6 +1391,15 @@ public class CUnit extends CWidget { return null; } + public CAbilityInventory getInventoryData() { + for (final CAbility ability : this.abilities) { + if (ability instanceof CAbilityInventory) { + return (CAbilityInventory) ability; + } + } + return null; + } + public void setUnitSpecificAttacks(final List unitSpecificAttacks) { this.unitSpecificAttacks = unitSpecificAttacks; } @@ -1388,4 +1414,18 @@ public class CUnit extends CWidget { } return this.unitType.getAttacks(); } + + public void onPickUpItem(final CSimulation game, final CItem item, final boolean playUserUISounds) { + this.stateNotifier.inventoryChanged(); + if (playUserUISounds) { + game.unitPickUpItemEvent(this, item); + } + } + + public void onDropItem(final CSimulation game, final CItem droppedItem, final boolean playUserUISounds) { + this.stateNotifier.inventoryChanged(); + if (playUserUISounds) { + game.unitDropItemEvent(this, droppedItem); + } + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitStateListener.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitStateListener.java index 5613e73..2f1356a 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitStateListener.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitStateListener.java @@ -15,6 +15,8 @@ public interface CUnitStateListener { void heroStatsChanged(); + void inventoryChanged(); + public static final class CUnitStateNotifier extends SubscriberSetNotifier implements CUnitStateListener { @Override @@ -58,5 +60,12 @@ public interface CUnitStateListener { listener.heroStatsChanged(); } } + + @Override + public void inventoryChanged() { + for (final CUnitStateListener listener : set) { + listener.inventoryChanged(); + } + } } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java index aaf26f6..9146721 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java @@ -71,6 +71,7 @@ public class CUnitType { private final List heroAbilityList; private final List heroProperNames; private final int properNamesCount; + private final boolean canFlee; public CUnitType(final String name, final int life, final int manaInitial, final int manaMaximum, final int speed, final int defense, final String abilityList, final boolean isBldg, final MovementType movementType, @@ -86,7 +87,8 @@ public class CUnitType { final List requirements, final int level, final boolean hero, final int strength, final float strengthPerLevel, final int agility, final float agilityPerLevel, final int intelligence, final float intelligencePerLevel, final CPrimaryAttribute primaryAttribute, - final List heroAbilityList, final List heroProperNames, final int properNamesCount) { + final List heroAbilityList, final List heroProperNames, final int properNamesCount, + final boolean canFlee) { this.name = name; this.life = life; this.manaInitial = manaInitial; @@ -136,6 +138,7 @@ public class CUnitType { this.heroAbilityList = heroAbilityList; this.heroProperNames = heroProperNames; this.properNamesCount = properNamesCount; + this.canFlee = canFlee; } public String getName() { @@ -333,4 +336,8 @@ public class CUnitType { public int getProperNamesCount() { return this.properNamesCount; } + + public boolean isCanFlee() { + return this.canFlee; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWidget.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWidget.java index 02989ac..1ae17c3 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWidget.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWidget.java @@ -2,11 +2,13 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation; import java.util.EnumSet; +import com.badlogic.gdx.math.Rectangle; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; public abstract class CWidget implements AbilityTarget { + protected static final Rectangle tempRect = new Rectangle(); private final int handleId; private float x; private float y; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbility.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbility.java index 612c221..7664f44 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbility.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbility.java @@ -4,6 +4,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; public interface CAbility extends CAbilityView { @@ -18,7 +19,7 @@ public interface CAbility extends CAbilityView { void onCancelFromQueue(CSimulation game, CUnit unit, int orderId); /* return false to not do anything, such as for toggling autocast */ - boolean checkBeforeQueue(CSimulation game, CUnit caster, int orderId); + boolean checkBeforeQueue(CSimulation game, CUnit caster, int orderId, AbilityTarget target); CBehavior begin(CSimulation game, CUnit caster, int orderId, CWidget target); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java index a6f4dd6..89599aa 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java @@ -4,6 +4,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorAttack; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorAttackListener; @@ -64,7 +65,7 @@ public class CAbilityAttack extends AbstractCAbility { } @Override - public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId) { + public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId, AbilityTarget target) { return true; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityGeneric.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityGeneric.java index 2debd14..df4e170 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityGeneric.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityGeneric.java @@ -5,6 +5,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; @@ -66,7 +67,7 @@ public class CAbilityGeneric extends AbstractCAbility { } @Override - public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId) { + public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId, AbilityTarget target) { return false; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityMove.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityMove.java index 1f2d1b7..232287d 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityMove.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityMove.java @@ -4,6 +4,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorFollow; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorHoldPosition; @@ -91,7 +92,7 @@ public class CAbilityMove extends AbstractCAbility { } @Override - public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId) { + public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId, AbilityTarget target) { return true; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java index df29658..811ca64 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java @@ -14,6 +14,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.AbstractCAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.menu.CAbilityMenu; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayer; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; @@ -107,7 +108,7 @@ public abstract class AbstractCAbilityBuild extends AbstractCAbility implements } @Override - public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId) { + public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId, AbilityTarget target) { return true; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityBuildInProgress.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityBuildInProgress.java index 4b38b76..7c9c085 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityBuildInProgress.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityBuildInProgress.java @@ -6,6 +6,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.AbstractCAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayer; @@ -33,7 +34,7 @@ public class CAbilityBuildInProgress extends AbstractCAbility { } @Override - public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId) { + public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId, AbilityTarget target) { final CPlayer player = game.getPlayer(caster.getPlayerIndex()); player.refundFor(caster.getUnitType()); caster.setLife(game, 0); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/combat/CAbilityColdArrows.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/combat/CAbilityColdArrows.java index a965a4a..14dd9a0 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/combat/CAbilityColdArrows.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/combat/CAbilityColdArrows.java @@ -7,6 +7,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.AbstractCAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorAttackListener; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; @@ -91,7 +92,7 @@ public class CAbilityColdArrows extends AbstractCAbility { } @Override - public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId) { + public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId, AbilityTarget target) { switch (orderId) { case OrderIds.coldarrows: case OrderIds.uncoldarrows: diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/AbstractGenericNoIconAbility.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/AbstractGenericNoIconAbility.java index f215c1c..8abdcae 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/AbstractGenericNoIconAbility.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/AbstractGenericNoIconAbility.java @@ -5,6 +5,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.AbstractCAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget; public abstract class AbstractGenericNoIconAbility extends AbstractCAbility implements GenericNoIconAbility { private final War3ID alias; @@ -15,7 +16,8 @@ public abstract class AbstractGenericNoIconAbility extends AbstractCAbility impl } @Override - public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId) { + public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId, + final AbilityTarget target) { return true; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/AbstractGenericSingleIconActiveAbility.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/AbstractGenericSingleIconActiveAbility.java index 155f253..169f4ce 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/AbstractGenericSingleIconActiveAbility.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/AbstractGenericSingleIconActiveAbility.java @@ -7,6 +7,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.AbstractCAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; @@ -20,7 +21,7 @@ public abstract class AbstractGenericSingleIconActiveAbility extends AbstractCAb } @Override - public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId) { + public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId, AbilityTarget target) { return true; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/hero/CAbilityHero.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/hero/CAbilityHero.java index c9aefd6..28e2e40 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/hero/CAbilityHero.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/hero/CAbilityHero.java @@ -12,6 +12,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.AbstractCAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; @@ -77,7 +78,7 @@ public class CAbilityHero extends AbstractCAbility { } @Override - public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId) { + public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId, AbilityTarget target) { return true; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/inventory/CAbilityInventory.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/inventory/CAbilityInventory.java new file mode 100644 index 0000000..08da64d --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/inventory/CAbilityInventory.java @@ -0,0 +1,240 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.inventory; + +import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CItem; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CItemType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic.AbstractGenericNoIconAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.inventory.CBehaviorDropItem; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.inventory.CBehaviorGetItem; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; + +public class CAbilityInventory extends AbstractGenericNoIconAbility { + private final boolean canDropItems; + private final boolean canGetItems; + private final boolean canUseItems; + private final boolean dropItemsOnDeath; + private final CItem[] itemsHeld; + private CBehaviorGetItem behaviorGetItem; + private CBehaviorDropItem behaviorDropItem; + + public CAbilityInventory(final int handleId, final War3ID alias, final boolean canDropItems, + final boolean canGetItems, final boolean canUseItems, final boolean dropItemsOnDeath, + final int itemCapacity) { + super(handleId, alias); + this.canDropItems = canDropItems; + this.canGetItems = canGetItems; + this.canUseItems = canUseItems; + this.dropItemsOnDeath = dropItemsOnDeath; + this.itemsHeld = new CItem[itemCapacity]; + } + + @Override + public void onAdd(final CSimulation game, final CUnit unit) { + this.behaviorGetItem = new CBehaviorGetItem(unit, this); + this.behaviorDropItem = new CBehaviorDropItem(unit, this); + } + + @Override + public void onRemove(final CSimulation game, final CUnit unit) { + + } + + @Override + public void onTick(final CSimulation game, final CUnit unit) { + + } + + @Override + public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId, + final AbilityTarget target) { + if ((orderId >= OrderIds.itemdrag00) && (orderId <= OrderIds.itemdrag05)) { + for (int i = 0; i < this.itemsHeld.length; i++) { + if (this.itemsHeld[i] == target) { + final CItem temp = this.itemsHeld[i]; + final int dragDropDestinationIndex = orderId - OrderIds.itemdrag00; + this.itemsHeld[i] = this.itemsHeld[dragDropDestinationIndex]; + this.itemsHeld[dragDropDestinationIndex] = temp; + return false; + } + } + } + return super.checkBeforeQueue(game, caster, orderId, target); + } + + @Override + public void onCancelFromQueue(final CSimulation game, final CUnit unit, final int orderId) { + + } + + public int getItemCapacity() { + return this.itemsHeld.length; + } + + public CItem getItemInSlot(final int slotIndex) { + if ((slotIndex < 0) || (slotIndex >= this.itemsHeld.length)) { + return null; + } + return this.itemsHeld[slotIndex]; + } + + public boolean isDropItemsOnDeath() { + return this.dropItemsOnDeath; + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final CWidget target) { + return this.behaviorGetItem.reset((CItem) target); + } + + public CBehavior beginDropItem(final CSimulation game, final CUnit caster, final int orderId, + final CItem itemToDrop, final AbilityPointTarget target) { + return this.behaviorDropItem.reset(itemToDrop, target); + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, + final AbilityPointTarget point) { + // TODO Auto-generated method stub + return null; + } + + @Override + public CBehavior beginNoTarget(final CSimulation game, final CUnit caster, final int orderId) { + // TODO Auto-generated method stub + return null; + } + + @Override + public void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, final CWidget target, + final AbilityTargetCheckReceiver receiver) { + if (((orderId == OrderIds.getitem) || (orderId == OrderIds.smart)) && !target.isDead()) { + if (target instanceof CItem) { + final CItem targetItem = (CItem) target; + if (!targetItem.isHidden()) { + receiver.targetOk(target); + } + else { + receiver.orderIdNotAccepted(); + } + } + else { + receiver.orderIdNotAccepted(); + } + } + else { + if ((orderId >= OrderIds.itemdrag00) && (orderId <= OrderIds.itemdrag05)) { + if (target instanceof CItem) { + final int slot = getSlot((CItem) target); + if (slot != -1) { + receiver.targetOk(target); + } + else { + receiver.orderIdNotAccepted(); + } + } + else { + receiver.orderIdNotAccepted(); + } + } + receiver.orderIdNotAccepted(); + } + } + + public int getSlot(final CItem target) { + int slot = -1; + for (int i = 0; i < this.itemsHeld.length; i++) { + if (this.itemsHeld[i] == target) { + slot = i; + } + } + return slot; + } + + @Override + public void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, + final AbilityPointTarget target, final AbilityTargetCheckReceiver receiver) { + if (orderId == OrderIds.dropitem) { + receiver.orderIdNotAccepted(); + } + } + + @Override + public void checkCanTargetNoTarget(final CSimulation game, final CUnit unit, final int orderId, + final AbilityTargetCheckReceiver receiver) { + receiver.orderIdNotAccepted(); + } + + @Override + protected void innerCheckCanUse(final CSimulation game, final CUnit unit, final int orderId, + final AbilityActivationReceiver receiver) { + receiver.useOk(); + } + + /** + * Attempts to give the hero the specified item, returning the item slot to + * which the item is added or -1 if no available slot is found + * + * @param item + * @return + */ + public int giveItem(final CSimulation simulation, final CUnit hero, final CItem item, + final boolean playUserUISounds) { + if ((item != null) && !item.isDead() && !item.isHidden()) { + final CItemType itemType = item.getItemType(); + if (itemType.isUseAutomaticallyWhenAcquired()) { + if (itemType.isActivelyUsed()) { + item.setLife(simulation, 0); + // TODO when we give unit ability here, then use ability + } + } + else { + for (int i = 0; i < this.itemsHeld.length; i++) { + if (this.itemsHeld[i] == null) { + this.itemsHeld[i] = item; + item.setHidden(true); + hero.onPickUpItem(simulation, item, true); + return i; + } + } + if (playUserUISounds) { + simulation.getCommandErrorListener(hero.getPlayerIndex()).showInventoryFullError(); + } + } + } + return -1; + } + + public void dropItem(final CSimulation simulation, final CUnit hero, final int slotIndex, final float x, + final float y, final boolean playUserUISounds) { + final CItem droppedItem = this.itemsHeld[slotIndex]; + hero.onDropItem(simulation, droppedItem, true); + this.itemsHeld[slotIndex] = null; + droppedItem.setHidden(false); + droppedItem.setPointAndCheckUnstuck(x, y, simulation); + } + + public void dropItem(final CSimulation simulation, final CUnit hero, final CItem itemToDrop, final float x, + final float y, final boolean playUserUISounds) { + boolean foundItem = false; + for (int i = 0; i < this.itemsHeld.length; i++) { + if (this.itemsHeld[i] == itemToDrop) { + this.itemsHeld[i] = null; + foundItem = true; + } + } + if (foundItem) { + hero.onDropItem(simulation, itemToDrop, true); + itemToDrop.setHidden(false); + itemToDrop.setPointAndCheckUnstuck(x, y, simulation); + } + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityQueue.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityQueue.java index fbd44d8..7250841 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityQueue.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityQueue.java @@ -13,6 +13,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.AbstractCAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayer; @@ -115,7 +116,7 @@ public final class CAbilityQueue extends AbstractCAbility { } @Override - public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId) { + public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId, AbilityTarget target) { return true; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityRally.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityRally.java index f1b7283..4f2fecc 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityRally.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityRally.java @@ -6,6 +6,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.AbstractCAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; @@ -72,7 +73,7 @@ public class CAbilityRally extends AbstractCAbility { } @Override - public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId) { + public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId, AbilityTarget target) { return true; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/targeting/AbilityTargetItemVisitor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/targeting/AbilityTargetItemVisitor.java new file mode 100644 index 0000000..b1276ab --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/targeting/AbilityTargetItemVisitor.java @@ -0,0 +1,30 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CDestructable; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CItem; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; + +public class AbilityTargetItemVisitor implements AbilityTargetVisitor { + public static final AbilityTargetItemVisitor INSTANCE = new AbilityTargetItemVisitor(); + + @Override + public CItem accept(final AbilityPointTarget target) { + return null; + } + + @Override + public CItem accept(final CUnit target) { + return null; + } + + @Override + public CItem accept(final CDestructable target) { + return null; + } + + @Override + public CItem accept(final CItem target) { + return target; + } + +} \ No newline at end of file diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/targeting/AbilityTargetStillAliveVisitor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/targeting/AbilityTargetStillAliveVisitor.java index bd70964..d6a7917 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/targeting/AbilityTargetStillAliveVisitor.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/targeting/AbilityTargetStillAliveVisitor.java @@ -24,7 +24,7 @@ public class AbilityTargetStillAliveVisitor implements AbilityTargetVisitor + implements CAbilityTypeDefinition { + protected static final War3ID ITEM_CAPACITY = War3ID.fromString("inv1"); + protected static final War3ID DROP_ITEMS_ON_DEATH = War3ID.fromString("inv2"); + protected static final War3ID CAN_USE_ITEMS = War3ID.fromString("inv3"); + protected static final War3ID CAN_GET_ITEMS = War3ID.fromString("inv4"); + protected static final War3ID CAN_DROP_ITEMS = War3ID.fromString("inv5"); + + @Override + protected CAbilityTypeInventoryLevelData createLevelData(final MutableGameObject abilityEditorData, + final int level) { + final String targetsAllowedAtLevelString = abilityEditorData.getFieldAsString(TARGETS_ALLOWED, level); + final EnumSet targetsAllowedAtLevel = CTargetType.parseTargetTypeSet(targetsAllowedAtLevelString); + final int itemCapacity = abilityEditorData.getFieldAsInteger(ITEM_CAPACITY, level); + final boolean dropItemsOnDeath = abilityEditorData.getFieldAsBoolean(DROP_ITEMS_ON_DEATH, level); + final boolean canUseItems = abilityEditorData.getFieldAsBoolean(CAN_USE_ITEMS, level); + final boolean canGetItems = abilityEditorData.getFieldAsBoolean(CAN_GET_ITEMS, level); + final boolean canDropItems = abilityEditorData.getFieldAsBoolean(CAN_DROP_ITEMS, level); + return new CAbilityTypeInventoryLevelData(targetsAllowedAtLevel, canDropItems, canGetItems, canUseItems, + dropItemsOnDeath, itemCapacity); + } + + @Override + protected CAbilityType innerCreateAbilityType(final War3ID alias, final MutableGameObject abilityEditorData, + final List levelData) { + return new CAbilityTypeInventory(alias, abilityEditorData.getCode(), levelData); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeInventory.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeInventory.java new file mode 100644 index 0000000..66b06f2 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeInventory.java @@ -0,0 +1,24 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.impl; + +import java.util.List; + +import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.inventory.CAbilityInventory; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.CAbilityType; + +public class CAbilityTypeInventory extends CAbilityType { + + public CAbilityTypeInventory(final War3ID alias, final War3ID code, + final List levelData) { + super(alias, code, levelData); + } + + @Override + public CAbility createAbility(final int handleId) { + final CAbilityTypeInventoryLevelData levelData = getLevelData(0); + return new CAbilityInventory(handleId, getAlias(), levelData.isCanDropItems(), levelData.isCanGetItems(), + levelData.isCanUseItems(), levelData.isDropItemsOnDeath(), levelData.getItemCapacity()); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeInventoryLevelData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeInventoryLevelData.java new file mode 100644 index 0000000..518b95f --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeInventoryLevelData.java @@ -0,0 +1,47 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.impl; + +import java.util.EnumSet; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.CAbilityTypeLevelData; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; + +public class CAbilityTypeInventoryLevelData extends CAbilityTypeLevelData { + + private final boolean canDropItems; + private final boolean canGetItems; + private final boolean canUseItems; + private final boolean dropItemsOnDeath; + private final int itemCapacity; + + public CAbilityTypeInventoryLevelData(final EnumSet targetsAllowed, final boolean canDropItems, + final boolean canGetItems, final boolean canUseItems, final boolean dropItemsOnDeath, + final int itemCapacity) { + super(targetsAllowed); + this.canDropItems = canDropItems; + this.canGetItems = canGetItems; + this.canUseItems = canUseItems; + this.dropItemsOnDeath = dropItemsOnDeath; + this.itemCapacity = itemCapacity; + } + + public boolean isCanDropItems() { + return this.canDropItems; + } + + public boolean isCanGetItems() { + return this.canGetItems; + } + + public boolean isCanUseItems() { + return this.canUseItems; + } + + public boolean isDropItemsOnDeath() { + return this.dropItemsOnDeath; + } + + public int getItemCapacity() { + return this.itemCapacity; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java index 78a72e7..1b91123 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java @@ -61,9 +61,10 @@ public class CBehaviorOrcBuild extends CAbstractRangedBehavior { buildLocationObstructed = true; } } + final int playerIndex = this.unit.getPlayerIndex(); if (!buildLocationObstructed) { - final CUnit constructedStructure = simulation.createUnit(this.orderId, this.unit.getPlayerIndex(), - this.target.getX(), this.target.getY(), simulation.getGameplayConstants().getBuildingAngle()); + final CUnit constructedStructure = simulation.createUnit(this.orderId, playerIndex, this.target.getX(), + this.target.getY(), simulation.getGameplayConstants().getBuildingAngle()); constructedStructure.setConstructing(true); constructedStructure.setWorkerInside(this.unit); constructedStructure.setLife(simulation, @@ -80,9 +81,9 @@ public class CBehaviorOrcBuild extends CAbstractRangedBehavior { simulation.unitConstructedEvent(this.unit, constructedStructure); } else { - final CPlayer player = simulation.getPlayer(this.unit.getPlayerIndex()); + final CPlayer player = simulation.getPlayer(playerIndex); refund(player, unitTypeToCreate); - simulation.getCommandErrorListener().showCantPlaceError(); + simulation.getCommandErrorListener(playerIndex).showCantPlaceError(); } } return this.unit.pollNextOrderBehavior(simulation); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorUndeadBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorUndeadBuild.java index f6491fc..6fd5b1c 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorUndeadBuild.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorUndeadBuild.java @@ -77,9 +77,10 @@ public class CBehaviorUndeadBuild extends CAbstractRangedBehavior { buildLocationObstructed = true; } } + final int playerIndex = this.unit.getPlayerIndex(); if (!buildLocationObstructed) { - final CUnit constructedStructure = simulation.createUnit(this.orderId, this.unit.getPlayerIndex(), - this.target.getX(), this.target.getY(), simulation.getGameplayConstants().getBuildingAngle()); + final CUnit constructedStructure = simulation.createUnit(this.orderId, playerIndex, this.target.getX(), + this.target.getY(), simulation.getGameplayConstants().getBuildingAngle()); constructedStructure.setConstructing(true); constructedStructure.setLife(simulation, constructedStructure.getMaximumLife() * WarsmashConstants.BUILDING_CONSTRUCT_START_LIFE); @@ -100,9 +101,9 @@ public class CBehaviorUndeadBuild extends CAbstractRangedBehavior { this.doneTick = simulation.getGameTurnTick() + delayAnimationTicks; } else { - final CPlayer player = simulation.getPlayer(this.unit.getPlayerIndex()); + final CPlayer player = simulation.getPlayer(playerIndex); refund(player, unitTypeToCreate); - simulation.getCommandErrorListener().showCantPlaceError(); + simulation.getCommandErrorListener(playerIndex).showCantPlaceError(); return this.unit.pollNextOrderBehavior(simulation); } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/inventory/CBehaviorDropItem.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/inventory/CBehaviorDropItem.java new file mode 100644 index 0000000..a74bd3d --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/inventory/CBehaviorDropItem.java @@ -0,0 +1,71 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.inventory; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CItem; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.inventory.CAbilityInventory; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTargetStillAliveVisitor; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CAbstractRangedBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; + +public class CBehaviorDropItem extends CAbstractRangedBehavior { + private final CAbilityInventory inventory; + private CItem targetItem; + + public CBehaviorDropItem(final CUnit unit, final CAbilityInventory inventory) { + super(unit); + this.inventory = inventory; + } + + public CBehaviorDropItem reset(final CItem targetItem, final AbilityPointTarget targetPoint) { + innerReset(targetPoint); + this.targetItem = targetItem; + return this; + } + + @Override + public boolean isWithinRange(final CSimulation simulation) { + return this.unit.canReach(this.target, simulation.getGameplayConstants().getDropItemRange()); + } + + @Override + public void endMove(final CSimulation game, final boolean interrupted) { + } + + @Override + public void begin(final CSimulation game) { + } + + @Override + public void end(final CSimulation game, final boolean interrupted) { + } + + @Override + public int getHighlightOrderId() { + return OrderIds.dropitem; + } + + @Override + protected CBehavior update(final CSimulation simulation, final boolean withinRange) { + this.inventory.dropItem(simulation, this.unit, this.targetItem, this.target.getX(), this.target.getY(), true); + return this.unit.pollNextOrderBehavior(simulation); + } + + @Override + protected CBehavior updateOnInvalidTarget(final CSimulation simulation) { + return this.unit.pollNextOrderBehavior(simulation); + } + + @Override + protected boolean checkTargetStillValid(final CSimulation simulation) { + return this.target.visit(AbilityTargetStillAliveVisitor.INSTANCE); + } + + @Override + protected void resetBeforeMoving(final CSimulation simulation) { + + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/inventory/CBehaviorGetItem.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/inventory/CBehaviorGetItem.java new file mode 100644 index 0000000..6568c68 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/inventory/CBehaviorGetItem.java @@ -0,0 +1,70 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.inventory; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CItem; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.inventory.CAbilityInventory; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTargetItemVisitor; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTargetStillAliveVisitor; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CAbstractRangedBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; + +public class CBehaviorGetItem extends CAbstractRangedBehavior { + private final CAbilityInventory inventory; + + public CBehaviorGetItem(final CUnit unit, final CAbilityInventory inventory) { + super(unit); + this.inventory = inventory; + } + + public CBehaviorGetItem reset(final CItem targetItem) { + innerReset(targetItem); + return this; + } + + @Override + public boolean isWithinRange(final CSimulation simulation) { + return this.unit.canReach(this.target, simulation.getGameplayConstants().getPickupItemRange()); + } + + @Override + public void endMove(final CSimulation game, final boolean interrupted) { + } + + @Override + public void begin(final CSimulation game) { + } + + @Override + public void end(final CSimulation game, final boolean interrupted) { + } + + @Override + public int getHighlightOrderId() { + return OrderIds.getitem; + } + + @Override + protected CBehavior update(final CSimulation simulation, final boolean withinRange) { + final CItem targetItem = this.target.visit(AbilityTargetItemVisitor.INSTANCE); + this.inventory.giveItem(simulation, this.unit, targetItem, true); + return this.unit.pollNextOrderBehavior(simulation); + } + + @Override + protected CBehavior updateOnInvalidTarget(final CSimulation simulation) { + return this.unit.pollNextOrderBehavior(simulation); + } + + @Override + protected boolean checkTargetStillValid(final CSimulation simulation) { + return this.target.visit(AbilityTargetStillAliveVisitor.INSTANCE); + } + + @Override + protected void resetBeforeMoving(final CSimulation simulation) { + + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CAbilityData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CAbilityData.java index 6a92634..d0cdcb5 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CAbilityData.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CAbilityData.java @@ -14,6 +14,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.def import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.definitions.impl.CAbilityTypeDefinitionColdArrows; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.definitions.impl.CAbilityTypeDefinitionGoldMine; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.definitions.impl.CAbilityTypeDefinitionHarvest; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.definitions.impl.CAbilityTypeDefinitionInventory; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.definitions.impl.CAbilityTypeDefinitionReturnResources; public class CAbilityData { @@ -34,6 +35,7 @@ public class CAbilityData { this.codeToAbilityTypeDefinition.put(War3ID.fromString("Artn"), new CAbilityTypeDefinitionReturnResources()); this.codeToAbilityTypeDefinition.put(War3ID.fromString("Ahar"), new CAbilityTypeDefinitionHarvest()); this.codeToAbilityTypeDefinition.put(War3ID.fromString("ANcl"), new CAbilityTypeDefinitionChannelTest()); + this.codeToAbilityTypeDefinition.put(War3ID.fromString("AInv"), new CAbilityTypeDefinitionInventory()); } public CAbilityType getAbilityType(final War3ID alias) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CItemData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CItemData.java index 9c740c5..bfc8305 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CItemData.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CItemData.java @@ -1,8 +1,117 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.data; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + import com.etheller.warsmash.units.manager.MutableObjectData; +import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; +import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CItem; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CItemType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; public class CItemData { + private static final War3ID ABILITY_LIST = War3ID.fromString("iabi"); + private static final War3ID COOLDOWN_GROUP = War3ID.fromString("icid"); + private static final War3ID IGNORE_COOLDOWN = War3ID.fromString("iicd"); + private static final War3ID NUMBER_OF_CHARGES = War3ID.fromString("iuse"); + private static final War3ID ACTIVELY_USED = War3ID.fromString("iusa"); + private static final War3ID PERISHABLE = War3ID.fromString("iper"); + private static final War3ID USE_AUTOMATICALLY_WHEN_ACQUIRED = War3ID.fromString("ipow"); + + private static final War3ID GOLD_COST = War3ID.fromString("igol"); + private static final War3ID LUMBER_COST = War3ID.fromString("ilum"); + private static final War3ID STOCK_MAX = War3ID.fromString("isto"); + private static final War3ID STOCK_REPLENISH_INTERVAL = War3ID.fromString("istr"); + private static final War3ID STOCK_START_DELAY = War3ID.fromString("isst"); + + private static final War3ID HIT_POINTS = War3ID.fromString("ihtp"); + private static final War3ID ARMOR_TYPE = War3ID.fromString("iarm"); + + private static final War3ID LEVEL = War3ID.fromString("ilev"); + private static final War3ID LEVEL_UNCLASSIFIED = War3ID.fromString("ilvo"); + private static final War3ID PRIORITY = War3ID.fromString("ipri"); + + private static final War3ID SELLABLE = War3ID.fromString("isel"); + private static final War3ID PAWNABLE = War3ID.fromString("ipaw"); + + private static final War3ID DROPPED_WHEN_CARRIER_DIES = War3ID.fromString("idrp"); + private static final War3ID CAN_BE_DROPPED = War3ID.fromString("idro"); + + private static final War3ID VALID_TARGET_FOR_TRANSFORMATION = War3ID.fromString("imor"); + private static final War3ID INCLUDE_AS_RANDOM_CHOICE = War3ID.fromString("iprn"); + + private final Map itemIdToItemType = new HashMap<>(); + private final MutableObjectData itemData; + public CItemData(final MutableObjectData itemData) { + this.itemData = itemData; + } + + public CItem create(final CSimulation simulation, final War3ID typeId, final float x, final float y, + final int handleId) { + final MutableGameObject itemType = this.itemData.get(typeId); + final CItemType itemTypeInstance = getItemTypeInstance(typeId, itemType); + + return new CItem(handleId, x, y, itemTypeInstance.getHitPoints(), typeId, itemTypeInstance); + } + + private CItemType getItemTypeInstance(final War3ID typeId, final MutableGameObject itemType) { + CItemType itemTypeInstance = this.itemIdToItemType.get(typeId); + if (itemTypeInstance == null) { + final String abilityListString = itemType.getFieldAsString(ABILITY_LIST, 0); + final String[] abilityListStringItems = abilityListString.split(","); + final List abilityList = new ArrayList<>(); + for (final String abilityListStringItem : abilityListStringItems) { + if (abilityListStringItem.length() == 4) { + abilityList.add(War3ID.fromString(abilityListStringItem)); + } + } + + final War3ID cooldownGroup; + final String cooldownGroupString = itemType.getFieldAsString(COOLDOWN_GROUP, 0); + if ((cooldownGroupString != null) && (cooldownGroupString.length() == 4)) { + cooldownGroup = War3ID.fromString(cooldownGroupString); + } + else { + cooldownGroup = null; + } + final boolean ignoreCooldown = itemType.getFieldAsBoolean(IGNORE_COOLDOWN, 0); + final int numberOfCharges = itemType.getFieldAsInteger(NUMBER_OF_CHARGES, 0); + final boolean activelyUsed = itemType.getFieldAsBoolean(ACTIVELY_USED, 0); + final boolean perishable = itemType.getFieldAsBoolean(PERISHABLE, 0); + final boolean useAutomaticallyWhenAcquired = itemType.getFieldAsBoolean(USE_AUTOMATICALLY_WHEN_ACQUIRED, 0); + + final int goldCost = itemType.getFieldAsInteger(GOLD_COST, 0); + final int lumberCost = itemType.getFieldAsInteger(LUMBER_COST, 0); + final int stockMax = itemType.getFieldAsInteger(STOCK_MAX, 0); + final int stockReplenishInterval = itemType.getFieldAsInteger(STOCK_REPLENISH_INTERVAL, 0); + final int stockStartDelay = itemType.getFieldAsInteger(STOCK_START_DELAY, 0); + + final int hitPoints = itemType.getFieldAsInteger(HIT_POINTS, 0); + final String armorType = itemType.getFieldAsString(ARMOR_TYPE, 0); + + final int level = itemType.getFieldAsInteger(LEVEL, 0); + final int levelUnclassified = itemType.getFieldAsInteger(LEVEL_UNCLASSIFIED, 0); + final int priority = itemType.getFieldAsInteger(PRIORITY, 0); + + final boolean sellable = itemType.getFieldAsBoolean(SELLABLE, 0); + final boolean pawnable = itemType.getFieldAsBoolean(PAWNABLE, 0); + + final boolean droppedWhenCarrierDies = itemType.getFieldAsBoolean(DROPPED_WHEN_CARRIER_DIES, 0); + final boolean canBeDropped = itemType.getFieldAsBoolean(CAN_BE_DROPPED, 0); + + final boolean validTargetForTransformation = itemType.getFieldAsBoolean(VALID_TARGET_FOR_TRANSFORMATION, 0); + final boolean includeAsRandomChoice = itemType.getFieldAsBoolean(INCLUDE_AS_RANDOM_CHOICE, 0); + + itemTypeInstance = new CItemType(abilityList, cooldownGroup, ignoreCooldown, numberOfCharges, activelyUsed, + perishable, useAutomaticallyWhenAcquired, goldCost, lumberCost, stockMax, stockReplenishInterval, + stockStartDelay, hitPoints, armorType, level, levelUnclassified, priority, sellable, pawnable, + droppedWhenCarrierDies, canBeDropped, validTargetForTransformation, includeAsRandomChoice); + this.itemIdToItemType.put(typeId, itemTypeInstance); + } + return itemTypeInstance; } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java index b566693..283b160 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java @@ -168,6 +168,8 @@ public class CUnitData { private static final War3ID INT_PLUS = War3ID.fromString("uinp"); private static final War3ID PRIMARY_ATTRIBUTE = War3ID.fromString("upra"); + private static final War3ID CAN_FLEE = War3ID.fromString("ufle"); + private final CGameplayConstants gameplayConstants; private final MutableObjectData unitData; private final Map unitIdToUnitType = new HashMap<>(); @@ -279,6 +281,8 @@ public class CUnitData { final float propWindow = unitType.getFieldAsFloat(PROPULSION_WINDOW, 0); final float turnRate = unitType.getFieldAsFloat(TURN_RATE, 0); + final boolean canFlee = unitType.getFieldAsBoolean(CAN_FLEE, 0); + final float strPlus = unitType.getFieldAsFloat(STR_PLUS, 0); final float agiPlus = unitType.getFieldAsFloat(AGI_PLUS, 0); final float intPlus = unitType.getFieldAsFloat(INT_PLUS, 0); @@ -533,7 +537,7 @@ public class CUnitData { minimumAttackRange, structuresBuilt, unitsTrained, researchesAvailable, unitRace, goldCost, lumberCost, foodUsed, foodMade, buildTime, preventedPathingTypes, requiredPathingTypes, propWindow, turnRate, requirements, unitLevel, hero, strength, strPlus, agility, agiPlus, intelligence, intPlus, - primaryAttribute, heroAbilityList, heroProperNames, properNamesCount); + primaryAttribute, heroAbilityList, heroProperNames, properNamesCount, canFlee); this.unitIdToUnitType.put(typeId, unitTypeInstance); } return unitTypeInstance; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderDropItemAtPoint.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderDropItemAtPoint.java new file mode 100644 index 0000000..0084a25 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderDropItemAtPoint.java @@ -0,0 +1,111 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CItem; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.inventory.CAbilityInventory; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.StringMsgTargetCheckReceiver; + +public class COrderDropItemAtPoint implements COrder { + private final int abilityHandleId; + private final int orderId; + private final int itemHandleId; + private final AbilityPointTarget target; + private final boolean queued; + + public COrderDropItemAtPoint(final int abilityHandleId, final int orderId, final int itemHandleId, + final AbilityPointTarget target, final boolean queued) { + this.abilityHandleId = abilityHandleId; + this.orderId = orderId; + this.itemHandleId = itemHandleId; + this.target = target; + this.queued = queued; + } + + @Override + public int getAbilityHandleId() { + return this.abilityHandleId; + } + + @Override + public int getOrderId() { + return this.orderId; + } + + @Override + public AbilityPointTarget getTarget(final CSimulation game) { + return this.target; + } + + @Override + public boolean isQueued() { + return this.queued; + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster) { + final CAbilityInventory ability = (CAbilityInventory) game.getAbility(this.abilityHandleId); + ability.checkCanUse(game, caster, this.orderId, this.abilityActivationReceiver.reset()); + if (this.abilityActivationReceiver.isUseOk()) { + final StringMsgTargetCheckReceiver targetReceiver = (StringMsgTargetCheckReceiver) targetCheckReceiver; + final CItem itemToDrop = (CItem) game.getWidget(this.itemHandleId); + return ability.beginDropItem(game, caster, this.orderId, itemToDrop, this.target); + } + else { + game.getCommandErrorListener(caster.getPlayerIndex()) + .showCommandError(this.abilityActivationReceiver.getMessage()); + return caster.pollNextOrderBehavior(game); + } + + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = (prime * result) + this.abilityHandleId; + result = (prime * result) + this.itemHandleId; + result = (prime * result) + this.orderId; + result = (prime * result) + (this.queued ? 1231 : 1237); + result = (prime * result) + ((this.target == null) ? 0 : this.target.hashCode()); + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final COrderDropItemAtPoint other = (COrderDropItemAtPoint) obj; + if (this.abilityHandleId != other.abilityHandleId) { + return false; + } + if (this.itemHandleId != other.itemHandleId) { + return false; + } + if (this.orderId != other.orderId) { + return false; + } + if (this.queued != other.queued) { + return false; + } + if (this.target == null) { + if (other.target != null) { + return false; + } + } + else if (!this.target.equals(other.target)) { + return false; + } + return true; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderNoTarget.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderNoTarget.java index a1537b3..6b08cd9 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderNoTarget.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderNoTarget.java @@ -44,12 +44,13 @@ public class COrderNoTarget implements COrder { return ability.beginNoTarget(game, caster, this.orderId); } else { - game.getCommandErrorListener().showCommandError(targetReceiver.getMessage()); + game.getCommandErrorListener(caster.getPlayerIndex()).showCommandError(targetReceiver.getMessage()); return caster.pollNextOrderBehavior(game); } } else { - game.getCommandErrorListener().showCommandError(this.abilityActivationReceiver.getMessage()); + game.getCommandErrorListener(caster.getPlayerIndex()) + .showCommandError(this.abilityActivationReceiver.getMessage()); return caster.pollNextOrderBehavior(game); } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetPoint.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetPoint.java index d3948db..89e25c8 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetPoint.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetPoint.java @@ -52,12 +52,13 @@ public class COrderTargetPoint implements COrder { return ability.begin(game, caster, this.orderId, this.target); } else { - game.getCommandErrorListener().showCommandError(targetReceiver.getMessage()); + game.getCommandErrorListener(caster.getPlayerIndex()).showCommandError(targetReceiver.getMessage()); return caster.pollNextOrderBehavior(game); } } else { - game.getCommandErrorListener().showCommandError(this.abilityActivationReceiver.getMessage()); + game.getCommandErrorListener(caster.getPlayerIndex()) + .showCommandError(this.abilityActivationReceiver.getMessage()); return caster.pollNextOrderBehavior(game); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetWidget.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetWidget.java index d9c4c01..0262d05 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetWidget.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetWidget.java @@ -55,12 +55,13 @@ public class COrderTargetWidget implements COrder { return ability.begin(game, caster, this.orderId, targetReceiver.getTarget()); } else { - game.getCommandErrorListener().showCommandError(targetReceiver.getMessage()); + game.getCommandErrorListener(caster.getPlayerIndex()).showCommandError(targetReceiver.getMessage()); return caster.pollNextOrderBehavior(game); } } else { - game.getCommandErrorListener().showCommandError(this.abilityActivationReceiver.getMessage()); + game.getCommandErrorListener(caster.getPlayerIndex()) + .showCommandError(this.abilityActivationReceiver.getMessage()); return caster.pollNextOrderBehavior(game); } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderExecutor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderExecutor.java index 104ee22..96c2458 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderExecutor.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderExecutor.java @@ -4,6 +4,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorHoldPosition; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.COrderDropItemAtPoint; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.COrderNoTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.COrderTargetPoint; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.COrderTargetWidget; @@ -26,6 +27,14 @@ public class CPlayerUnitOrderExecutor implements CPlayerUnitOrderListener { unit.order(this.game, new COrderTargetWidget(abilityHandleId, orderId, targetHandleId, queue), queue); } + @Override + public void issueDropItemAtPointOrder(final int unitHandleId, final int abilityHandleId, final int orderId, + final int targetHandleId, final float x, final float y, final boolean queue) { + final CUnit unit = this.game.getUnit(unitHandleId); + unit.order(this.game, new COrderDropItemAtPoint(abilityHandleId, orderId, targetHandleId, + new AbilityPointTarget(x, y), queue), queue); + } + @Override public void issuePointOrder(final int unitHandleId, final int abilityHandleId, final int orderId, final float x, final float y, final boolean queue) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderListener.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderListener.java index 187dc11..af64694 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderListener.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderListener.java @@ -5,8 +5,9 @@ public interface CPlayerUnitOrderListener { void issuePointOrder(int unitHandleId, int abilityHandleId, int orderId, float x, float y, boolean queue); - // Below: used for "DROP ITEM AT POINT" ???? -// boolean issueTargetAndPointOrder(int unitHandleId, int orderId, int targetHandleId, float x, float y); + // Below: used for "DROP ITEM AT POINT" + void issueDropItemAtPointOrder(int unitHandleId, int abilityHandleId, int orderId, int targetHandleId, float x, + float y, final boolean queue); void issueImmediateOrder(int unitHandleId, int abilityHandleId, int orderId, boolean queue); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java index 051df71..05b9350 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java @@ -4,6 +4,7 @@ import java.awt.image.BufferedImage; import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CDestructable; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CItem; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; @@ -50,4 +51,8 @@ public interface SimulationRenderController { void spawnGainResourceTextTag(CUnit gainingUnit, ResourceType resourceType, int amount); void spawnEffectOnUnit(CUnit unit, String effectPath); + + void spawnUIUnitGetItemSound(CUnit cUnit, CItem item); + + void spawnUIUnitDropItemSound(CUnit cUnit, CItem item); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java index 5f3948d..15bf620 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java @@ -12,7 +12,6 @@ import com.etheller.warsmash.parsers.fdf.frames.SpriteFrame; import com.etheller.warsmash.parsers.fdf.frames.TextureFrame; import com.etheller.warsmash.parsers.fdf.frames.UIFrame; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; -import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.commandbuttons.CommandButton; import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.ClickableActionFrame; import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.CommandCardCommandListener; @@ -22,7 +21,8 @@ public class CommandCardIcon extends AbstractRenderableFrame implements Clickabl private TextureFrame activeHighlightFrame; private SpriteFrame cooldownFrame; private SpriteFrame autocastFrame; - private CommandButton commandButton; + private float defaultWidth; + private float defaultHeight; private int abilityHandleId; private int orderId; private int autoCastOrderId; @@ -49,33 +49,16 @@ public class CommandCardIcon extends AbstractRenderableFrame implements Clickabl this.autocastFrame = autocastFrame; } - public void setCommandButton(final CommandButton commandButton) { - this.commandButton = commandButton; - if (commandButton == null) { - this.iconFrame.setVisible(false); + public void clear() { + this.iconFrame.setVisible(false); + if (this.activeHighlightFrame != null) { this.activeHighlightFrame.setVisible(false); - this.cooldownFrame.setVisible(false); + } + this.cooldownFrame.setVisible(false); + if (this.autocastFrame != null) { this.autocastFrame.setVisible(false); - setVisible(false); - } - else { - if (commandButton.isEnabled()) { - this.iconFrame.setTexture(commandButton.getIcon()); - } - else { - this.iconFrame.setTexture(commandButton.getDisabledIcon()); - } - if (commandButton.getCooldownRemaining() <= 0) { - this.cooldownFrame.setVisible(false); - } - else { - this.cooldownFrame.setVisible(true); - this.cooldownFrame.setSequence(PrimaryTag.STAND); - this.cooldownFrame.setAnimationSpeed(commandButton.getCooldown()); - this.cooldownFrame - .setFrameByRatio(1 - (commandButton.getCooldownRemaining() / commandButton.getCooldown())); - } } + setVisible(false); } public void setCommandButtonData(final Texture texture, final int abilityHandleId, final int orderId, @@ -84,19 +67,23 @@ public class CommandCardIcon extends AbstractRenderableFrame implements Clickabl this.menuButton = menuButton; setVisible(true); this.iconFrame.setVisible(true); - this.activeHighlightFrame.setVisible(active); + if (this.activeHighlightFrame != null) { + this.activeHighlightFrame.setVisible(active); + } this.cooldownFrame.setVisible(false); - this.autocastFrame.setVisible(autoCastOrderId != 0); - if (autoCastOrderId != 0) { - if (this.autoCastActive != autoCastActive) { - if (autoCastActive) { - this.autocastFrame.setSequence(PrimaryTag.STAND); - } - else { - this.autocastFrame.setSequence(-1); + if (this.autocastFrame != null) { + this.autocastFrame.setVisible(autoCastOrderId != 0); + if (autoCastOrderId != 0) { + if (this.autoCastActive != autoCastActive) { + if (autoCastActive) { + this.autocastFrame.setSequence(PrimaryTag.STAND); + } + else { + this.autocastFrame.setSequence(-1); + } } + this.autoCastActive = autoCastActive; } - this.autoCastActive = autoCastActive; } this.iconFrame.setTexture(texture); this.abilityHandleId = abilityHandleId; @@ -112,23 +99,32 @@ public class CommandCardIcon extends AbstractRenderableFrame implements Clickabl @Override protected void innerPositionBounds(final GameUI gameUI, final Viewport viewport) { this.iconFrame.positionBounds(gameUI, viewport); - this.activeHighlightFrame.positionBounds(gameUI, viewport); + if (this.activeHighlightFrame != null) { + this.activeHighlightFrame.positionBounds(gameUI, viewport); + } this.cooldownFrame.positionBounds(gameUI, viewport); - this.autocastFrame.positionBounds(gameUI, viewport); + if (this.autocastFrame != null) { + this.autocastFrame.positionBounds(gameUI, viewport); + } } @Override protected void internalRender(final SpriteBatch batch, final BitmapFont baseFont, final GlyphLayout glyphLayout) { this.iconFrame.render(batch, baseFont, glyphLayout); - this.activeHighlightFrame.render(batch, baseFont, glyphLayout); + if (this.activeHighlightFrame != null) { + this.activeHighlightFrame.render(batch, baseFont, glyphLayout); + } this.cooldownFrame.render(batch, baseFont, glyphLayout); - this.autocastFrame.render(batch, baseFont, glyphLayout); + if (this.autocastFrame != null) { + this.autocastFrame.render(batch, baseFont, glyphLayout); + } } @Override public UIFrame touchDown(final float screenX, final float screenY, final int button) { if (isVisible() && this.renderBounds.contains(screenX, screenY)) { - if ((this.orderId != 0) || this.menuButton) { + if (((button == Input.Buttons.LEFT) && (this.orderId != 0)) + || ((button == Input.Buttons.RIGHT) && (this.autoCastOrderId != 0)) || this.menuButton) { return this; } } @@ -154,28 +150,40 @@ public class CommandCardIcon extends AbstractRenderableFrame implements Clickabl this.commandCardCommandListener.openMenu(this.orderId); } else { - this.commandCardCommandListener.startUsingAbility(this.abilityHandleId, this.orderId, false); + this.commandCardCommandListener.onClick(this.abilityHandleId, this.orderId, false); } } else if (button == Input.Buttons.RIGHT) { - this.commandCardCommandListener.startUsingAbility(this.abilityHandleId, this.autoCastOrderId, true); + this.commandCardCommandListener.onClick(this.abilityHandleId, this.autoCastOrderId, true); } } @Override public void mouseDown(final GameUI gameUI, final Viewport uiViewport) { - this.iconFrame.setWidth(GameUI.convertX(uiViewport, MeleeUI.DEFAULT_COMMAND_CARD_ICON_PRESSED_WIDTH)); - this.iconFrame.setHeight(GameUI.convertY(uiViewport, MeleeUI.DEFAULT_COMMAND_CARD_ICON_PRESSED_WIDTH)); + this.iconFrame.setWidth(this.defaultWidth * 0.95f); + this.iconFrame.setHeight(this.defaultHeight * 0.95f); positionBounds(gameUI, uiViewport); } @Override public void mouseUp(final GameUI gameUI, final Viewport uiViewport) { - this.iconFrame.setWidth(GameUI.convertX(uiViewport, MeleeUI.DEFAULT_COMMAND_CARD_ICON_WIDTH)); - this.iconFrame.setHeight(GameUI.convertY(uiViewport, MeleeUI.DEFAULT_COMMAND_CARD_ICON_WIDTH)); + this.iconFrame.setWidth(this.defaultWidth); + this.iconFrame.setHeight(this.defaultHeight); positionBounds(gameUI, uiViewport); } + @Override + public void setWidth(final float width) { + this.defaultWidth = width; + super.setWidth(width); + } + + @Override + public void setHeight(final float height) { + this.defaultHeight = height; + super.setHeight(height); + } + @Override public void mouseEnter(final GameUI gameUI, final Viewport uiViewport) { } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java index 04d5f1b..290c4c9 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java @@ -79,10 +79,12 @@ import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderUnit; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderWidget; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.AbilityDataUI; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.IconUI; +import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.ItemUI; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.commandbuttons.CommandButtonListener; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CDestructable; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CGameplayConstants; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CItem; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CItemType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CPlayerStateListener; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit.QueueItemType; @@ -110,6 +112,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic.G import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic.GenericSingleIconActiveAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.hero.CAbilityHero; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.hero.CPrimaryAttribute; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.inventory.CAbilityInventory; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityQueue; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityRally; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; @@ -147,9 +150,11 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma - WORLD_FRAME_MESSAGE_FADEOUT_MILLIS; private static final String BUILDING_PATHING_PREVIEW_KEY = "buildingPathingPreview"; public static final float DEFAULT_COMMAND_CARD_ICON_WIDTH = 0.039f; - public static final float DEFAULT_COMMAND_CARD_ICON_PRESSED_WIDTH = 0.037f; + public static final float DEFAULT_INVENTORY_ICON_WIDTH = 0.03125f; private static final int COMMAND_CARD_WIDTH = 4; private static final int COMMAND_CARD_HEIGHT = 3; + private static final int INVENTORY_WIDTH = 2; + private static final int INVENTORY_HEIGHT = 3; private static final Vector2 screenCoordsVector = new Vector2(); private static final Vector3 clickLocationTemp = new Vector3(); @@ -217,6 +222,11 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma private UIFrame heroInfoPanel; + private SimpleFrame inventoryBarFrame; + private StringFrame inventoryTitleFrame; + private final CommandCardIcon[][] inventoryIcons = new CommandCardIcon[INVENTORY_HEIGHT][INVENTORY_WIDTH]; + private Texture consoleInventoryNoCapacityTexture; + private final CommandCardIcon[][] commandCard = new CommandCardIcon[COMMAND_CARD_HEIGHT][COMMAND_CARD_WIDTH]; private RenderUnit selectedUnit; @@ -274,6 +284,10 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma private StringFrame intelligenceValue; private SimpleFrame smashHeroInfoPanelWrapper; + private final StringBuilder recycleStringBuilder = new StringBuilder(); + private CItem draggingItem; + private final ItemCommandCardCommandListener itemCommandCardCommandListener; + public MeleeUI(final DataSource dataSource, final ExtendViewport uiViewport, final Scene uiScene, final Scene portraitScene, final CameraPreset[] cameraPresets, final CameraRates cameraRates, final War3MapViewer war3MapViewer, final RootFrameListener rootFrameListener, @@ -303,6 +317,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.cursorTargetSetupVisitor = new CursorTargetSetupVisitor(); this.localPlayer.addStateListener(this); + + this.itemCommandCardCommandListener = new ItemCommandCardCommandListener(); } private MeleeUIMinimap createMinimap(final War3MapViewer war3MapViewer) { @@ -576,6 +592,57 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.agilityValue = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconHeroAgilityValue", 0); this.intelligenceValue = (StringFrame) this.rootFrame.getFrameByName("InfoPanelIconHeroIntellectValue", 0); + this.inventoryBarFrame = (SimpleFrame) this.rootFrame.createSimpleFrame("SmashSimpleInventoryBar", + this.rootFrame, 0); + this.inventoryBarFrame.setWidth(GameUI.convertX(this.uiViewport, 0.064f)); + this.inventoryBarFrame.setHeight(GameUI.convertY(this.uiViewport, 0.115f)); + this.inventoryBarFrame.addSetPoint(new SetPoint(FramePoint.BOTTOMRIGHT, this.consoleUI, FramePoint.BOTTOMLEFT, + GameUI.convertX(this.uiViewport, 0.496f), GameUI.convertY(this.uiViewport, 0.0f))); + + int commandButtonIndex = 0; + for (int j = 0; j < INVENTORY_HEIGHT; j++) { + for (int i = 0; i < INVENTORY_WIDTH; i++) { + final CommandCardIcon commandCardIcon = new CommandCardIcon( + "SmashInventoryButton_" + commandButtonIndex, this.inventoryBarFrame, + this.itemCommandCardCommandListener); + this.inventoryBarFrame.add(commandCardIcon); + final TextureFrame iconFrame = new TextureFrame( + "SmashInventoryButton_" + (commandButtonIndex) + "_Icon", this.rootFrame, false, null); + final SpriteFrame cooldownFrame = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", + "SmashInventoryButton_" + (commandButtonIndex) + "_Cooldown", this.rootFrame, "", 0); + commandCardIcon.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.inventoryBarFrame, + FramePoint.TOPRIGHT, GameUI.convertX(this.uiViewport, 0.0187f + (0.04f * i)), + GameUI.convertY(this.uiViewport, -0.0021f - (0.03815f * j)))); + commandCardIcon.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_INVENTORY_ICON_WIDTH)); + commandCardIcon.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_INVENTORY_ICON_WIDTH)); + iconFrame.addSetPoint(new SetPoint(FramePoint.CENTER, commandCardIcon, FramePoint.CENTER, 0, 0)); + iconFrame.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_INVENTORY_ICON_WIDTH)); + iconFrame.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_INVENTORY_ICON_WIDTH)); + iconFrame.setTexture(ImageUtils.DEFAULT_ICON_PATH, this.rootFrame); + cooldownFrame.addSetPoint(new SetPoint(FramePoint.CENTER, commandCardIcon, FramePoint.CENTER, 0, 0)); + this.rootFrame.setSpriteFrameModel(cooldownFrame, this.rootFrame.getSkinField("CommandButtonCooldown")); + cooldownFrame.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_INVENTORY_ICON_WIDTH)); + cooldownFrame.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_INVENTORY_ICON_WIDTH)); + commandCardIcon.set(iconFrame, null, cooldownFrame, null); + this.inventoryIcons[j][i] = commandCardIcon; + commandCardIcon.clear(); + commandButtonIndex++; + } + } + this.inventoryTitleFrame = this.rootFrame.createStringFrame("SmashInventoryText", this.inventoryBarFrame, + new Color(0xFCDE12FF), TextJustify.LEFT, TextJustify.MIDDLE, 0.0109f); + this.rootFrame.setText(this.inventoryTitleFrame, this.rootFrame.getTemplates().getDecoratedString("INVENTORY")); + this.inventoryTitleFrame + .addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.inventoryBarFrame, FramePoint.TOPRIGHT, + GameUI.convertX(this.uiViewport, 0.029f), GameUI.convertY(this.uiViewport, 0.0165625f))); + this.inventoryTitleFrame.setWidth(GameUI.convertX(this.uiViewport, 0.076875f)); + this.inventoryTitleFrame.setHeight(GameUI.convertX(this.uiViewport, 0.01125f)); + this.inventoryTitleFrame.setFontShadowColor(new Color(0f, 0f, 0f, 0.9f)); + this.inventoryTitleFrame.setFontShadowOffsetX(GameUI.convertX(this.uiViewport, 0.001f)); + this.inventoryTitleFrame.setFontShadowOffsetY(GameUI.convertY(this.uiViewport, -0.001f)); + this.consoleInventoryNoCapacityTexture = ImageUtils.getAnyExtensionTexture(this.dataSource, + this.rootFrame.getSkinField("ConsoleInventoryNoCapacity")); + this.inventoryCover = this.rootFrame.createSimpleFrame("SmashConsoleInventoryCover", this.rootFrame, 0); final Element fontHeights = this.war3MapViewer.miscData.get("FontHeights"); @@ -592,7 +659,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.errorMessageFrame.setFontShadowOffsetY(GameUI.convertY(this.uiViewport, -0.001f)); this.errorMessageFrame.setVisible(false); - int commandButtonIndex = 0; + commandButtonIndex = 0; for (int j = 0; j < COMMAND_CARD_HEIGHT; j++) { for (int i = 0; i < COMMAND_CARD_WIDTH; i++) { final CommandCardIcon commandCardIcon = new CommandCardIcon("SmashCommandButton_" + commandButtonIndex, @@ -631,7 +698,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma autocastFrame.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); commandCardIcon.set(iconFrame, activeHighlightFrame, cooldownFrame, autocastFrame); this.commandCard[j][i] = commandCardIcon; - commandCardIcon.setCommandButton(null); + commandCardIcon.clear(); commandButtonIndex++; } } @@ -713,7 +780,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } @Override - public void startUsingAbility(final int abilityHandleId, final int orderId, final boolean rightClick) { + public void onClick(final int abilityHandleId, final int orderId, final boolean rightClick) { // TODO not O(N) if (this.selectedUnit == null) { return; @@ -776,8 +843,9 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma public void showCommandError(final String message) { this.rootFrame.setText(this.errorMessageFrame, message); this.errorMessageFrame.setVisible(true); - this.lastErrorMessageExpireTime = TimeUtils.millis() + WORLD_FRAME_MESSAGE_EXPIRE_MILLIS; - this.lastErrorMessageFadeTime = TimeUtils.millis() + WORLD_FRAME_MESSAGE_FADEOUT_MILLIS; + final long millis = TimeUtils.millis(); + this.lastErrorMessageExpireTime = millis + WORLD_FRAME_MESSAGE_EXPIRE_MILLIS; + this.lastErrorMessageFadeTime = millis + WORLD_FRAME_MESSAGE_FADEOUT_MILLIS; this.errorMessageFrame.setAlpha(1.0f); } @@ -795,6 +863,13 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma .play(this.uiScene.audioContext, 0, 0, 0); } + @Override + public void showInventoryFullError() { + showCommandError(this.rootFrame.getErrorString("InventoryFull")); + this.war3MapViewer.getUiSounds().getSound(this.rootFrame.getSkinField("InventoryFullSound")) + .play(this.uiScene.audioContext, 0, 0, 0); + } + public void update(final float deltaTime) { this.portrait.update(); @@ -824,7 +899,12 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.cursorFrame.setFramePointY(FramePoint.BOTTOM, screenCoordsVector.y); if (this.activeCommand != null) { - this.activeCommand.visit(this.cursorTargetSetupVisitor.reset(baseMouseX, baseMouseY)); + if (this.draggingItem != null) { + this.cursorFrame.setSequence("HoldItem"); + } + else { + this.activeCommand.visit(this.cursorTargetSetupVisitor.reset(baseMouseX, baseMouseY)); + } } else { if (this.cursorModelInstance != null) { @@ -1349,6 +1429,52 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } } + public void setDraggingItem(final CItem itemInSlot) { + this.draggingItem = itemInSlot; + if (itemInSlot != null) { + final String iconPath = this.war3MapViewer.getAbilityDataUI().getItemUI(itemInSlot.getTypeId()) + .getItemIconPathForDragging(); + this.cursorFrame.setReplaceableId(21, this.war3MapViewer.blp(iconPath)); + + int index = 0; + final CAbilityInventory inventory = this.selectedUnit.getSimulationUnit().getInventoryData(); + for (int i = 0; i < INVENTORY_HEIGHT; i++) { + for (int j = 0; j < INVENTORY_WIDTH; j++) { + final CommandCardIcon inventoryIcon = this.inventoryIcons[i][j]; + final CItem item = inventory.getItemInSlot(index); + if (item == null) { + if (index < inventory.getItemCapacity()) { + inventoryIcon.setCommandButtonData(null, 0, 0, index + 1, true, false, false, null, null, 0, + 0, 0); + } + } + index++; + } + } + } + else { + if (this.selectedUnit != null) { + final CAbilityInventory inventory = this.selectedUnit.getSimulationUnit().getInventoryData(); + if (inventory != null) { + int index = 0; + for (int i = 0; i < INVENTORY_HEIGHT; i++) { + for (int j = 0; j < INVENTORY_WIDTH; j++) { + final CommandCardIcon inventoryIcon = this.inventoryIcons[i][j]; + final CItem item = inventory.getItemInSlot(index); + if (item == null) { + if (index < inventory.getItemCapacity()) { + inventoryIcon.clear(); + } + } + index++; + } + } + } + } + + } + } + public void selectUnit(RenderUnit unit) { this.subMenuOrderIdStack.clear(); if ((unit != null) && unit.getSimulationUnit().isDead()) { @@ -1359,6 +1485,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } this.portrait.setSelectedUnit(unit); this.selectedUnit = unit; + setDraggingItem(null); if (unit == null) { clearCommandCard(); this.rootFrame.setText(this.simpleNameValue, ""); @@ -1386,6 +1513,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.heroInfoPanel.setVisible(false); this.rallyPointInstance.hide(); this.rallyPointInstance.detach(); + this.inventoryCover.setVisible(true); + this.inventoryBarFrame.setVisible(false); repositionWaypointFlags(null); } else { @@ -1518,12 +1647,16 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma final IconUI upgradeUI = this.war3MapViewer.getAbilityDataUI() .getUpgradeUI(simulationUnit.getBuildQueue()[i], 0); this.queueIconFrames[i].setTexture(upgradeUI.getIcon()); + this.queueIconFrames[i].setToolTip(upgradeUI.getToolTip()); + this.queueIconFrames[i].setUberTip(upgradeUI.getUberTip()); break; case UNIT: default: final IconUI unitUI = this.war3MapViewer.getAbilityDataUI() .getUnitUI(simulationUnit.getBuildQueue()[i]); this.queueIconFrames[i].setTexture(unitUI.getIcon()); + this.queueIconFrames[i].setToolTip(unitUI.getToolTip()); + this.queueIconFrames[i].setUberTip(unitUI.getUberTip()); break; } } @@ -1662,6 +1795,59 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } this.simpleHeroLevelBar.setVisible(false); } + final CAbilityInventory inventory = simulationUnit.getInventoryData(); + this.inventoryCover.setVisible(inventory == null); + if (inventory != null) { + this.inventoryBarFrame.setVisible(true); + int index = 0; + for (int i = 0; i < INVENTORY_HEIGHT; i++) { + for (int j = 0; j < INVENTORY_WIDTH; j++) { + final CommandCardIcon inventoryIcon = this.inventoryIcons[i][j]; + final CItem item = inventory.getItemInSlot(index); + if (item != null) { + final ItemUI itemUI = this.war3MapViewer.getAbilityDataUI().getItemUI(item.getTypeId()); + final IconUI iconUI = itemUI.getIconUI(); + final CItemType itemType = item.getItemType(); + // TODO: below we set menu=false, this is bad, item should be based on item abil + final boolean activelyUsed = itemType.isActivelyUsed(); + final boolean pawnable = itemType.isPawnable(); + final String uberTip = iconUI.getUberTip(); + this.recycleStringBuilder.setLength(0); + if (pawnable) { + this.recycleStringBuilder + .append(this.rootFrame.getTemplates().getDecoratedString("ITEM_PAWN_TOOLTIP")); + this.recycleStringBuilder.append("|n"); + } + if (activelyUsed) { + this.recycleStringBuilder + .append(this.rootFrame.getTemplates().getDecoratedString("ITEM_USE_TOOLTIP")); + this.recycleStringBuilder.append("|n"); + } + this.recycleStringBuilder.append(uberTip); + inventoryIcon.setCommandButtonData(iconUI.getIcon(), 0, + activelyUsed ? itemType.getCooldownGroup().getValue() : 0, index + 1, activelyUsed, + false, false, itemUI.getName(), this.recycleStringBuilder.toString(), + itemType.getGoldCost(), itemType.getLumberCost(), 0); + } + else { + if (index >= inventory.getItemCapacity()) { + inventoryIcon.setCommandButtonData(this.consoleInventoryNoCapacityTexture, 0, 0, 0, + false, false, false, null, null, 0, 0, 0); + } + else { + if (this.draggingItem != null) { + inventoryIcon.setCommandButtonData(null, 0, 0, index + 1, true, false, false, null, + null, 0, 0, 0); + } + else { + inventoryIcon.clear(); + } + } + } + index++; + } + } + } localArmorIcon.setVisible(!constructing); this.simpleBuildTimeIndicator.setVisible(constructing); @@ -1711,7 +1897,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma private void clearCommandCard() { for (int j = 0; j < COMMAND_CARD_HEIGHT; j++) { for (int i = 0; i < COMMAND_CARD_WIDTH; i++) { - this.commandCard[j][i].setCommandButton(null); + this.commandCard[j][i].clear(); } } } @@ -1865,6 +2051,11 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma reloadSelectedUnitUI(this.selectedUnit); } + @Override + public void inventoryChanged() { + reloadSelectedUnitUI(this.selectedUnit); + } + @Override public void queueChanged() { reloadSelectedUnitUI(this.selectedUnit); @@ -1874,7 +2065,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma clearCommandCard(); final AbilityDataUI abilityDataUI = this.war3MapViewer.getAbilityDataUI(); final int menuOrderId = getSubMenuOrderId(); - if (this.activeCommand != null) { + if ((this.activeCommand != null) && (this.draggingItem == null)) { final IconUI cancelUI = abilityDataUI.getCancelUI(); this.commandButton(cancelUI.getButtonPositionX(), cancelUI.getButtonPositionY(), cancelUI.getIcon(), 0, menuOrderId, 0, false, false, true, cancelUI.getToolTip(), cancelUI.getUberTip(), 0, 0, 0); @@ -1932,6 +2123,9 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.activeCommandUnit = null; this.activeCommand = null; this.activeCommandOrderId = -1; + if (this.draggingItem != null) { + setDraggingItem(null); + } clearAndRepopulateCommandCard(); } else { @@ -1966,44 +2160,65 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.war3MapViewer.getClickLocation(clickLocationTemp, screenX, (int) worldScreenY); clickLocationTemp2.set(clickLocationTemp.x, clickLocationTemp.y); - this.activeCommand.checkCanTarget(this.war3MapViewer.simulation, - this.activeCommandUnit.getSimulationUnit(), this.activeCommandOrderId, - clickLocationTemp2, PointAbilityTargetCheckReceiver.INSTANCE); - final Vector2 target = PointAbilityTargetCheckReceiver.INSTANCE.getTarget(); - if (target != null) { - if ((this.activeCommand instanceof CAbilityAttack) - && (this.activeCommandOrderId == OrderIds.attack)) { - this.war3MapViewer.showConfirmation(clickLocationTemp, 1, 0, 0); - } - else { - this.war3MapViewer.showConfirmation(clickLocationTemp, 0, 1, 0); - } - this.unitOrderListener.issuePointOrder( + if (this.draggingItem != null) { + this.war3MapViewer.showConfirmation(clickLocationTemp, 0, 1, 0); + + this.unitOrderListener.issueDropItemAtPointOrder( this.activeCommandUnit.getSimulationUnit().getHandleId(), - this.activeCommand.getHandleId(), this.activeCommandOrderId, clickLocationTemp2.x, - clickLocationTemp2.y, shiftDown); + this.activeCommand.getHandleId(), this.activeCommandOrderId, + this.draggingItem.getHandleId(), clickLocationTemp2.x, clickLocationTemp2.y, + shiftDown); if (getSelectedUnit().soundset.yes .playUnitResponse(this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { portraitTalk(); } - this.selectedSoundCount = 0; - if (this.activeCommand instanceof AbstractCAbilityBuild) { - this.war3MapViewer.getUiSounds().getSound("PlaceBuildingDefault") - .play(this.uiScene.audioContext, 0, 0, 0); - } - else if (this.activeCommand instanceof CAbilityRally) { - this.war3MapViewer.getUiSounds().getSound("RallyPointPlace") - .play(this.uiScene.audioContext, 0, 0, 0); - } - if (!shiftDown) { - this.subMenuOrderIdStack.clear(); - this.activeCommandUnit = null; - this.activeCommand = null; - this.activeCommandOrderId = -1; - clearAndRepopulateCommandCard(); - } - + this.activeCommandUnit = null; + this.activeCommand = null; + this.activeCommandOrderId = -1; + setDraggingItem(null); + clearAndRepopulateCommandCard(); } + else { + this.activeCommand.checkCanTarget(this.war3MapViewer.simulation, + this.activeCommandUnit.getSimulationUnit(), this.activeCommandOrderId, + clickLocationTemp2, PointAbilityTargetCheckReceiver.INSTANCE); + final Vector2 target = PointAbilityTargetCheckReceiver.INSTANCE.getTarget(); + if (target != null) { + if ((this.activeCommand instanceof CAbilityAttack) + && (this.activeCommandOrderId == OrderIds.attack)) { + this.war3MapViewer.showConfirmation(clickLocationTemp, 1, 0, 0); + } + else { + this.war3MapViewer.showConfirmation(clickLocationTemp, 0, 1, 0); + } + this.unitOrderListener.issuePointOrder( + this.activeCommandUnit.getSimulationUnit().getHandleId(), + this.activeCommand.getHandleId(), this.activeCommandOrderId, + clickLocationTemp2.x, clickLocationTemp2.y, shiftDown); + if (getSelectedUnit().soundset.yes.playUnitResponse( + this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { + portraitTalk(); + } + this.selectedSoundCount = 0; + if (this.activeCommand instanceof AbstractCAbilityBuild) { + this.war3MapViewer.getUiSounds().getSound("PlaceBuildingDefault") + .play(this.uiScene.audioContext, 0, 0, 0); + } + else if (this.activeCommand instanceof CAbilityRally) { + this.war3MapViewer.getUiSounds().getSound("RallyPointPlace") + .play(this.uiScene.audioContext, 0, 0, 0); + } + if (!shiftDown) { + this.subMenuOrderIdStack.clear(); + this.activeCommandUnit = null; + this.activeCommand = null; + this.activeCommandOrderId = -1; + clearAndRepopulateCommandCard(); + } + + } + } + } } } @@ -2224,45 +2439,51 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma final int lumberCost = this.mouseOverUIFrame.getToolTipLumberCost(); final int foodCost = this.mouseOverUIFrame.getToolTipFoodCost(); final String toolTip = this.mouseOverUIFrame.getToolTip(); - this.rootFrame.setText(this.tooltipUberTipText, this.mouseOverUIFrame.getUberTip()); - int resourceIndex = 0; - if (goldCost != 0) { - this.tooltipResourceFrames[resourceIndex].setVisible(true); - this.tooltipResourceIconFrames[resourceIndex].setTexture("ToolTipGoldIcon", this.rootFrame); - this.rootFrame.setText(this.tooltipResourceTextFrames[resourceIndex], Integer.toString(goldCost)); - resourceIndex++; - } - if (lumberCost != 0) { - this.tooltipResourceFrames[resourceIndex].setVisible(true); - this.tooltipResourceIconFrames[resourceIndex].setTexture("ToolTipLumberIcon", this.rootFrame); - this.rootFrame.setText(this.tooltipResourceTextFrames[resourceIndex], Integer.toString(lumberCost)); - resourceIndex++; - } - if (foodCost != 0) { - this.tooltipResourceFrames[resourceIndex].setVisible(true); - this.tooltipResourceIconFrames[resourceIndex].setTexture("ToolTipSupplyIcon", this.rootFrame); - this.rootFrame.setText(this.tooltipResourceTextFrames[resourceIndex], Integer.toString(foodCost)); - resourceIndex++; - } - for (int i = resourceIndex; i < this.tooltipResourceFrames.length; i++) { - this.tooltipResourceFrames[i].setVisible(false); - } - float resourcesHeight; - if (resourceIndex != 0) { - this.tooltipUberTipText.addSetPoint(this.uberTipWithResourcesSetPoint); - resourcesHeight = 0.014f; + final String uberTip = this.mouseOverUIFrame.getUberTip(); + if ((toolTip == null) || (uberTip == null)) { + this.tooltipFrame.setVisible(false); } else { - this.tooltipUberTipText.addSetPoint(this.uberTipNoResourcesSetPoint); - resourcesHeight = 0.004f; + this.rootFrame.setText(this.tooltipUberTipText, uberTip); + int resourceIndex = 0; + if (goldCost != 0) { + this.tooltipResourceFrames[resourceIndex].setVisible(true); + this.tooltipResourceIconFrames[resourceIndex].setTexture("ToolTipGoldIcon", this.rootFrame); + this.rootFrame.setText(this.tooltipResourceTextFrames[resourceIndex], Integer.toString(goldCost)); + resourceIndex++; + } + if (lumberCost != 0) { + this.tooltipResourceFrames[resourceIndex].setVisible(true); + this.tooltipResourceIconFrames[resourceIndex].setTexture("ToolTipLumberIcon", this.rootFrame); + this.rootFrame.setText(this.tooltipResourceTextFrames[resourceIndex], Integer.toString(lumberCost)); + resourceIndex++; + } + if (foodCost != 0) { + this.tooltipResourceFrames[resourceIndex].setVisible(true); + this.tooltipResourceIconFrames[resourceIndex].setTexture("ToolTipSupplyIcon", this.rootFrame); + this.rootFrame.setText(this.tooltipResourceTextFrames[resourceIndex], Integer.toString(foodCost)); + resourceIndex++; + } + for (int i = resourceIndex; i < this.tooltipResourceFrames.length; i++) { + this.tooltipResourceFrames[i].setVisible(false); + } + float resourcesHeight; + if (resourceIndex != 0) { + this.tooltipUberTipText.addSetPoint(this.uberTipWithResourcesSetPoint); + resourcesHeight = 0.014f; + } + else { + this.tooltipUberTipText.addSetPoint(this.uberTipNoResourcesSetPoint); + resourcesHeight = 0.004f; + } + this.rootFrame.setText(this.tooltipText, toolTip); + final float predictedViewportHeight = this.tooltipText.getPredictedViewportHeight() + + GameUI.convertY(this.uiViewport, resourcesHeight) + + this.tooltipUberTipText.getPredictedViewportHeight() + GameUI.convertY(this.uiViewport, 0.003f); + this.tooltipFrame.setHeight(predictedViewportHeight); + this.tooltipFrame.positionBounds(this.rootFrame, this.uiViewport); + this.tooltipFrame.setVisible(true); } - this.rootFrame.setText(this.tooltipText, toolTip); - final float predictedViewportHeight = this.tooltipText.getPredictedViewportHeight() - + GameUI.convertY(this.uiViewport, resourcesHeight) - + this.tooltipUberTipText.getPredictedViewportHeight() + GameUI.convertY(this.uiViewport, 0.003f); - this.tooltipFrame.setHeight(predictedViewportHeight); - this.tooltipFrame.positionBounds(this.rootFrame, this.uiViewport); - this.tooltipFrame.setVisible(true); } public float getHeightRatioCorrection() { @@ -2309,4 +2530,39 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.rootFrame.dispose(); } } + + private class ItemCommandCardCommandListener implements CommandCardCommandListener { + @Override + public void onClick(final int abilityHandleId, final int orderId, final boolean rightClick) { + if (rightClick) { + final RenderUnit selectedUnit2 = MeleeUI.this.selectedUnit; + final CUnit simulationUnit = selectedUnit2.getSimulationUnit(); + final CAbilityInventory inventoryData = simulationUnit.getInventoryData(); + final int slot = orderId - 1; + final CItem itemInSlot = inventoryData.getItemInSlot(slot); + if (MeleeUI.this.draggingItem != null) { + final CUnit activeCmdSimUnit = MeleeUI.this.activeCommandUnit.getSimulationUnit(); + MeleeUI.this.unitOrderListener.issueTargetOrder(activeCmdSimUnit.getHandleId(), + activeCmdSimUnit.getInventoryData().getHandleId(), OrderIds.itemdrag00 + slot, + MeleeUI.this.draggingItem.getHandleId(), false); + setDraggingItem(null); + MeleeUI.this.activeCommand = null; + MeleeUI.this.activeCommandUnit = null; + } + else { + if (itemInSlot != null) { + setDraggingItem(itemInSlot); + MeleeUI.this.activeCommand = inventoryData; + MeleeUI.this.activeCommandUnit = selectedUnit2; + } + } + } + } + + @Override + public void openMenu(final int orderId) { + MeleeUI.this.openMenu(orderId); + } + + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUIMinimap.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUIMinimap.java index fcaf00f..c3af169 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUIMinimap.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUIMinimap.java @@ -34,7 +34,7 @@ public class MeleeUIMinimap { batch.draw(this.minimapTexture, this.minimap.x, this.minimap.y, this.minimap.width, this.minimap.height); for (final RenderUnit unit : units) { - final Texture minimapIcon = this.teamColors[unit.playerIndex]; + final Texture minimapIcon = this.teamColors[unit.getSimulationUnit().getPlayerIndex()]; batch.draw(minimapIcon, this.minimapFilledArea.x + (((unit.location[0] - this.playableMapArea.getX()) / (this.playableMapArea.getWidth())) diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/QueueIcon.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/QueueIcon.java index 64d01df..fe090d2 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/QueueIcon.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/QueueIcon.java @@ -20,6 +20,9 @@ public class QueueIcon extends AbstractRenderableFrame implements ClickableActio private float defaultHeight; private final int queueIconIndexId; + private String toolTip; + private String uberTip; + public QueueIcon(final String name, final UIFrame parent, final QueueIconListener clickListener, final int queueIconIndexId) { super(name, parent); @@ -113,14 +116,22 @@ public class QueueIcon extends AbstractRenderableFrame implements ClickableActio return null; } + public void setToolTip(final String toolTip) { + this.toolTip = toolTip; + } + + public void setUberTip(final String uberTip) { + this.uberTip = uberTip; + } + @Override public String getToolTip() { - return "QueueIcon " + this.queueIconIndexId; + return this.toolTip; } @Override public String getUberTip() { - return "The |cffffcc00QueueIcon|r is a hardcoded Warsmash engine component that is not yet loading its |cffffaa88description|r."; + return this.uberTip; } @Override diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/CommandCardCommandListener.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/CommandCardCommandListener.java index 36dc718..fe003ab 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/CommandCardCommandListener.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/CommandCardCommandListener.java @@ -1,7 +1,7 @@ package com.etheller.warsmash.viewer5.handlers.w3x.ui.command; public interface CommandCardCommandListener { - void startUsingAbility(int abilityHandleId, int orderId, boolean rightClick); + void onClick(int abilityHandleId, int orderId, boolean rightClick); void openMenu(int orderId); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/CommandErrorListener.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/CommandErrorListener.java index ed33944..70fa4f3 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/CommandErrorListener.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/CommandErrorListener.java @@ -6,4 +6,6 @@ public interface CommandErrorListener { void showCantPlaceError(); void showNoFoodError(); + + void showInventoryFullError(); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/SettableCommandErrorListener.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/SettableCommandErrorListener.java index 2cd025c..7bed78b 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/SettableCommandErrorListener.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/SettableCommandErrorListener.java @@ -18,6 +18,11 @@ public class SettableCommandErrorListener implements CommandErrorListener { this.delegate.showNoFoodError(); } + @Override + public void showInventoryFullError() { + this.delegate.showInventoryFullError(); + } + public void setDelegate(final CommandErrorListener delegate) { this.delegate = delegate; } diff --git a/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxModel.java b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxModel.java index 6929b1b..01511eb 100644 --- a/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxModel.java +++ b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxModel.java @@ -393,7 +393,7 @@ public class MdlxModel { break; case MdlUtils.TOKEN_TEXTURE_ANIMS: loadNumberedObjectBlock(this.textureAnimations, MdlxBlockDescriptor.TEXTURE_ANIMATION, - MdlUtils.TOKEN_TEXTURE_ANIM, stream); + MdlUtils.TOKEN_TVERTEX_ANIM, stream); break; case MdlUtils.TOKEN_GEOSET: loadObject(this.geosets, MdlxBlockDescriptor.GEOSET, stream); diff --git a/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxTextureAnimation.java b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxTextureAnimation.java index a61c04f..acbc889 100644 --- a/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxTextureAnimation.java +++ b/core/src/com/hiveworkshop/rms/parsers/mdlx/MdlxTextureAnimation.java @@ -42,7 +42,7 @@ public class MdlxTextureAnimation extends MdlxAnimatedObject { @Override public void writeMdl(final MdlTokenOutputStream stream, final int version) { - stream.startBlock(MdlUtils.TOKEN_TVERTEX_ANIM_SPACE); + stream.startBlock(MdlUtils.TOKEN_TVERTEX_ANIM); writeTimeline(stream, AnimationMap.KTAT); writeTimeline(stream, AnimationMap.KTAR); writeTimeline(stream, AnimationMap.KTAS); diff --git a/core/src/com/hiveworkshop/rms/parsers/mdlx/mdl/MdlUtils.java b/core/src/com/hiveworkshop/rms/parsers/mdlx/mdl/MdlUtils.java index b53f567..5f3f4fa 100644 --- a/core/src/com/hiveworkshop/rms/parsers/mdlx/mdl/MdlUtils.java +++ b/core/src/com/hiveworkshop/rms/parsers/mdlx/mdl/MdlUtils.java @@ -29,7 +29,7 @@ public class MdlUtils { public static final String TOKEN_WRAP_HEIGHT = "WrapHeight"; public static final String TOKEN_BITMAP = "Bitmap"; - public static final String TOKEN_TVERTEX_ANIM_SPACE = "TVertexAnim"; + public static final String TOKEN_TVERTEX_ANIM = "TVertexAnim"; public static final String TOKEN_DONT_INTERP = "DontInterp"; public static final String TOKEN_LINEAR = "Linear"; @@ -177,7 +177,6 @@ public class MdlUtils { public static final String TOKEN_TEXTURES = "Textures"; public static final String TOKEN_MATERIALS = "Materials"; public static final String TOKEN_TEXTURE_ANIMS = "TextureAnims"; - public static final String TOKEN_TEXTURE_ANIM = "TextureAnim"; public static final String TOKEN_PIVOT_POINTS = "PivotPoints"; public static final String TOKEN_ATTACHMENT = "Attachment"; @@ -198,7 +197,7 @@ public class MdlUtils { // > 800 public static final String TOKEN_EMISSIVE_GAIN = "EmissiveGain"; - public static final String TOKEN_FRESNEL_COLOR = "FresnelColor"; + public static final String TOKEN_FRESNEL_COLOR = "FresnelColor"; public static final String TOKEN_FRESNEL_OPACITY = "FresnelOpacity"; public static final String TOKEN_FRESNEL_TEAM_COLOR = "FresnelTeamColor"; } diff --git a/desktop/src/com/badlogic/gdx/backends/lwjgl/LwjglCanvas.java b/desktop/src/com/badlogic/gdx/backends/lwjgl/LwjglCanvas.java new file mode 100644 index 0000000..a1c915e --- /dev/null +++ b/desktop/src/com/badlogic/gdx/backends/lwjgl/LwjglCanvas.java @@ -0,0 +1,501 @@ +/******************************************************************************* + * Copyright 2011 See AUTHORS file. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************/ + +package com.badlogic.gdx.backends.lwjgl; + +import java.awt.Canvas; +import java.awt.Cursor; +import java.awt.Dimension; +import java.awt.EventQueue; +import java.util.HashMap; +import java.util.Map; + +import org.lwjgl.opengl.AWTGLCanvas; +import org.lwjgl.opengl.Display; + +import com.badlogic.gdx.Application; +import com.badlogic.gdx.ApplicationListener; +import com.badlogic.gdx.ApplicationLogger; +import com.badlogic.gdx.Audio; +import com.badlogic.gdx.Files; +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.Graphics; +import com.badlogic.gdx.Input; +import com.badlogic.gdx.LifecycleListener; +import com.badlogic.gdx.Net; +import com.badlogic.gdx.Preferences; +import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.Clipboard; +import com.badlogic.gdx.utils.SharedLibraryLoader; +import com.etheller.warsmash.audio.OpenALAudio; + +/** + * An OpenGL surface on an AWT Canvas, allowing OpenGL to be embedded in a Swing + * application. This uses {@link Display#setParent(Canvas)}, which is preferred + * over {@link AWTGLCanvas} but is limited to a single LwjglCanvas in an + * application. All OpenGL calls are done on the EDT. Note that you may need to + * call {@link #stop()} or a Swing application may deadlock on System.exit due + * to how LWJGL and/or Swing deal with shutdown hooks. + * + * @author Nathan Sweet + */ +public class LwjglCanvas implements Application { + static boolean isWindows = System.getProperty("os.name").contains("Windows"); + + LwjglGraphics graphics; + OpenALAudio audio; + LwjglFiles files; + LwjglInput input; + LwjglNet net; + ApplicationListener listener; + Canvas canvas; + final Array runnables = new Array(); + final Array executedRunnables = new Array(); + final Array lifecycleListeners = new Array(); + boolean running = true; + int logLevel = LOG_INFO; + ApplicationLogger applicationLogger; + Cursor cursor; + + public LwjglCanvas(final ApplicationListener listener) { + final LwjglApplicationConfiguration config = new LwjglApplicationConfiguration(); + initialize(listener, config); + } + + public LwjglCanvas(final ApplicationListener listener, final LwjglApplicationConfiguration config) { + initialize(listener, config); + } + + private void initialize(final ApplicationListener listener, final LwjglApplicationConfiguration config) { + LwjglNativesLoader.load(); + setApplicationLogger(new LwjglApplicationLogger()); + this.canvas = new Canvas() { + private final Dimension minSize = new Dimension(1, 1); + + @Override + public final void addNotify() { + super.addNotify(); + if (SharedLibraryLoader.isMac) { + EventQueue.invokeLater(new Runnable() { + @Override + public void run() { + create(); + } + }); + } + else { + create(); + } + } + + @Override + public final void removeNotify() { + stop(); + super.removeNotify(); + } + + @Override + public Dimension getMinimumSize() { + return this.minSize; + } + }; + this.canvas.setSize(1, 1); + this.canvas.setIgnoreRepaint(true); + + this.graphics = new LwjglGraphics(this.canvas, config) { + @Override + public void setTitle(final String title) { + super.setTitle(title); + LwjglCanvas.this.setTitle(title); + } + + public boolean setWindowedMode(final int width, final int height, final boolean fullscreen) { + if (!super.setWindowedMode(width, height)) { + return false; + } + if (!fullscreen) { + LwjglCanvas.this.setDisplayMode(width, height); + } + return true; + } + + @Override + public boolean setFullscreenMode(final DisplayMode displayMode) { + if (!super.setFullscreenMode(displayMode)) { + return false; + } + LwjglCanvas.this.setDisplayMode(displayMode.width, displayMode.height); + return true; + } + }; + this.graphics.setVSync(config.vSyncEnabled); + if (!LwjglApplicationConfiguration.disableAudio) { + this.audio = new OpenALAudio(); + } + this.files = new LwjglFiles(); + this.input = new LwjglInput(); + this.net = new LwjglNet(); + this.listener = listener; + + Gdx.app = this; + Gdx.graphics = this.graphics; + Gdx.audio = this.audio; + Gdx.files = this.files; + Gdx.input = this.input; + Gdx.net = this.net; + } + + protected void setDisplayMode(final int width, final int height) { + } + + protected void setTitle(final String title) { + } + + @Override + public ApplicationListener getApplicationListener() { + return this.listener; + } + + public Canvas getCanvas() { + return this.canvas; + } + + @Override + public Audio getAudio() { + return this.audio; + } + + @Override + public Files getFiles() { + return this.files; + } + + @Override + public Graphics getGraphics() { + return this.graphics; + } + + @Override + public Input getInput() { + return this.input; + } + + @Override + public Net getNet() { + return this.net; + } + + @Override + public ApplicationType getType() { + return ApplicationType.Desktop; + } + + @Override + public int getVersion() { + return 0; + } + + void create() { + try { + this.graphics.setupDisplay(); + + this.listener.create(); + this.listener.resize(Math.max(1, this.graphics.getWidth()), Math.max(1, this.graphics.getHeight())); + + start(); + } + catch (final Exception ex) { + stopped(); + exception(ex); + return; + } + + EventQueue.invokeLater(new Runnable() { + int lastWidth = Math.max(1, LwjglCanvas.this.graphics.getWidth()); + int lastHeight = Math.max(1, LwjglCanvas.this.graphics.getHeight()); + + @Override + public void run() { + if (!LwjglCanvas.this.running || Display.isCloseRequested()) { + LwjglCanvas.this.running = false; + stopped(); + return; + } + try { + Display.processMessages(); + if ((LwjglCanvas.this.cursor != null) || !isWindows) { + LwjglCanvas.this.canvas.setCursor(LwjglCanvas.this.cursor); + } + + boolean shouldRender = false; + + final int width = Math.max(1, LwjglCanvas.this.graphics.getWidth()); + final int height = Math.max(1, LwjglCanvas.this.graphics.getHeight()); + if ((this.lastWidth != width) || (this.lastHeight != height)) { + this.lastWidth = width; + this.lastHeight = height; + Gdx.gl.glViewport(0, 0, this.lastWidth, this.lastHeight); + resize(width, height); + LwjglCanvas.this.listener.resize(width, height); + shouldRender = true; + } + + if (executeRunnables()) { + shouldRender = true; + } + + // If one of the runnables set running to false, for example after an exit(). + if (!LwjglCanvas.this.running) { + return; + } + + LwjglCanvas.this.input.update(); + shouldRender |= LwjglCanvas.this.graphics.shouldRender(); + LwjglCanvas.this.input.processEvents(); + if (LwjglCanvas.this.audio != null) { + LwjglCanvas.this.audio.update(); + } + + if (shouldRender) { + LwjglCanvas.this.graphics.updateTime(); + LwjglCanvas.this.graphics.frameId++; + LwjglCanvas.this.listener.render(); + Display.update(false); + } + + Display.sync(getFrameRate()); + } + catch (final Throwable ex) { + exception(ex); + } + EventQueue.invokeLater(this); + } + }); + } + + public boolean executeRunnables() { + synchronized (this.runnables) { + for (int i = this.runnables.size - 1; i >= 0; i--) { + this.executedRunnables.addAll(this.runnables.get(i)); + } + this.runnables.clear(); + } + if (this.executedRunnables.size == 0) { + return false; + } + do { + this.executedRunnables.pop().run(); + } + while (this.executedRunnables.size > 0); + return true; + } + + protected int getFrameRate() { + int frameRate = Display.isActive() ? this.graphics.config.foregroundFPS : this.graphics.config.backgroundFPS; + if (frameRate == -1) { + frameRate = 10; + } + if (frameRate == 0) { + frameRate = this.graphics.config.backgroundFPS; + } + if (frameRate == 0) { + frameRate = 30; + } + return frameRate; + } + + protected void exception(final Throwable ex) { + ex.printStackTrace(); + stop(); + } + + /** + * Called after {@link ApplicationListener} create and resize, but before the + * game loop iteration. + */ + protected void start() { + } + + /** Called when the canvas size changes. */ + protected void resize(final int width, final int height) { + } + + /** Called when the game loop has stopped. */ + protected void stopped() { + } + + public void stop() { + EventQueue.invokeLater(new Runnable() { + @Override + public void run() { + if (!LwjglCanvas.this.running) { + return; + } + LwjglCanvas.this.running = false; + final Array listeners = LwjglCanvas.this.lifecycleListeners; + synchronized (listeners) { + for (final LifecycleListener listener : listeners) { + listener.pause(); + listener.dispose(); + } + } + LwjglCanvas.this.listener.pause(); + LwjglCanvas.this.listener.dispose(); + try { + Display.destroy(); + if (LwjglCanvas.this.audio != null) { + LwjglCanvas.this.audio.dispose(); + } + } + catch (final Throwable ignored) { + } + } + }); + } + + @Override + public long getJavaHeap() { + return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); + } + + @Override + public long getNativeHeap() { + return getJavaHeap(); + } + + Map preferences = new HashMap(); + + @Override + public Preferences getPreferences(final String name) { + if (this.preferences.containsKey(name)) { + return this.preferences.get(name); + } + else { + final Preferences prefs = new LwjglPreferences(name, ".prefs/"); + this.preferences.put(name, prefs); + return prefs; + } + } + + @Override + public Clipboard getClipboard() { + return new LwjglClipboard(); + } + + @Override + public void postRunnable(final Runnable runnable) { + synchronized (this.runnables) { + this.runnables.add(runnable); + Gdx.graphics.requestRendering(); + } + } + + @Override + public void debug(final String tag, final String message) { + if (this.logLevel >= LOG_DEBUG) { + getApplicationLogger().debug(tag, message); + } + } + + @Override + public void debug(final String tag, final String message, final Throwable exception) { + if (this.logLevel >= LOG_DEBUG) { + getApplicationLogger().debug(tag, message, exception); + } + } + + @Override + public void log(final String tag, final String message) { + if (this.logLevel >= LOG_INFO) { + getApplicationLogger().log(tag, message); + } + } + + @Override + public void log(final String tag, final String message, final Throwable exception) { + if (this.logLevel >= LOG_INFO) { + getApplicationLogger().log(tag, message, exception); + } + } + + @Override + public void error(final String tag, final String message) { + if (this.logLevel >= LOG_ERROR) { + getApplicationLogger().error(tag, message); + } + } + + @Override + public void error(final String tag, final String message, final Throwable exception) { + if (this.logLevel >= LOG_ERROR) { + getApplicationLogger().error(tag, message, exception); + } + } + + @Override + public void setLogLevel(final int logLevel) { + this.logLevel = logLevel; + } + + @Override + public int getLogLevel() { + return this.logLevel; + } + + @Override + public void setApplicationLogger(final ApplicationLogger applicationLogger) { + this.applicationLogger = applicationLogger; + } + + @Override + public ApplicationLogger getApplicationLogger() { + return this.applicationLogger; + } + + @Override + public void exit() { + postRunnable(new Runnable() { + @Override + public void run() { + LwjglCanvas.this.listener.pause(); + LwjglCanvas.this.listener.dispose(); + if (LwjglCanvas.this.audio != null) { + LwjglCanvas.this.audio.dispose(); + } + System.exit(-1); + } + }); + } + + /** @param cursor May be null. */ + public void setCursor(final Cursor cursor) { + this.cursor = cursor; + } + + @Override + public void addLifecycleListener(final LifecycleListener listener) { + synchronized (this.lifecycleListeners) { + this.lifecycleListeners.add(listener); + } + } + + @Override + public void removeLifecycleListener(final LifecycleListener listener) { + synchronized (this.lifecycleListeners) { + this.lifecycleListeners.removeValue(listener, true); + } + } +} From 55bdfcb9fc8c6a42a075225340e1f44a2b5e300e Mon Sep 17 00:00:00 2001 From: Retera Date: Sat, 20 Mar 2021 14:54:38 -0400 Subject: [PATCH 110/116] Update with some beginning on a jass API that does not run and is not usable yet --- .../warsmash/WarsmashGdxMapScreen.java | 8 +- .../warsmash/WarsmashGdxMenuScreen.java | 2 +- .../etheller/warsmash/parsers/jass/Jass2.java | 1451 +++++++++++++++++ .../warsmash/parsers/jass/Tmpgen.java | 22 + .../warsmash/parsers/jass/Tmpgen2.java | 29 + .../warsmash/util/WarsmashConstants.java | 2 +- .../com/etheller/warsmash/viewer5/Model.java | 2 +- .../handlers/mdx/MdxComplexInstance.java | 18 +- .../viewer5/handlers/mdx/Sequence.java | 9 + .../viewer5/handlers/w3x/War3MapViewer.java | 32 +- .../w3x/simulation/CGameplayConstants.java | 16 + .../handlers/w3x/simulation/CSimulation.java | 75 +- .../handlers/w3x/simulation/CUnit.java | 2 +- .../handlers/w3x/simulation/CUnitType.java | 31 +- .../w3x/simulation/ai/AIDifficulty.java | 9 + .../w3x/simulation/config/CBasePlayer.java | 185 +++ .../w3x/simulation/config/CPlayerAPI.java | 5 + .../w3x/simulation/config/War3MapConfig.java | 153 ++ .../config/War3MapConfigStartLoc.java | 44 + .../w3x/simulation/data/CItemData.java | 8 + .../w3x/simulation/data/CUnitData.java | 49 +- .../w3x/simulation/item/CItemTypeJass.java | 15 + .../w3x/simulation/orders/OrderIdUtils.java | 38 + .../w3x/simulation/players/CAllianceType.java | 2 + .../w3x/simulation/players/CMapControl.java | 2 + .../w3x/simulation/players/CMapFlag.java | 44 + .../w3x/simulation/players/CMapPlacement.java | 10 + .../w3x/simulation/players/CPlayer.java | 76 +- .../w3x/simulation/players/CPlayerColor.java | 25 + .../simulation/players/CPlayerGameResult.java | 10 + .../w3x/simulation/players/CPlayerJass.java | 47 + .../w3x/simulation/players/CPlayerScore.java | 31 + .../w3x/simulation/players/CPlayerState.java | 42 + .../players/CPlayerUnitOrderExecutor.java | 51 +- .../w3x/simulation/players/CRace.java | 2 + .../simulation/players/CRacePreference.java | 15 + .../w3x/simulation/players/CStartLocPrio.java | 9 + .../w3x/simulation/state/CGameState.java | 9 + .../w3x/simulation/state/CUnitState.java | 10 + .../w3x/simulation/timers/CTimer.java | 89 + .../w3x/simulation/timers/CTimerJass.java | 25 + .../trigger/JassGameEventsWar3.java | 248 +++ .../trigger/enumtypes/CAttackTypeJass.java | 9 + .../trigger/enumtypes/CBlendMode.java | 12 + .../trigger/enumtypes/CCameraField.java | 13 + .../trigger/enumtypes/CDamageType.java | 32 + .../trigger/enumtypes/CEffectType.java | 13 + .../trigger/enumtypes/CFogState.java | 22 + .../trigger/enumtypes/CGameSpeed.java | 11 + .../trigger/enumtypes/CGameType.java | 27 + .../trigger/enumtypes/CLimitOp.java | 12 + .../trigger/enumtypes/CMapDensity.java | 10 + .../trigger/enumtypes/CMapDifficulty.java | 10 + .../trigger/enumtypes/CPathingTypeJass.java | 14 + .../trigger/enumtypes/CPlayerSlotState.java | 9 + .../trigger/enumtypes/CRarityControl.java | 8 + .../trigger/enumtypes/CSoundType.java | 8 + .../trigger/enumtypes/CSoundVolumeGroup.java | 14 + .../trigger/enumtypes/CTexMapFlags.java | 10 + .../trigger/enumtypes/CVersion.java | 8 + .../enumtypes/CWeaponSoundTypeJass.java | 40 + .../w3x/simulation/unit/CUnitTypeJass.java | 39 + .../viewer5/handlers/w3x/ui/MeleeUI.java | 122 +- .../ast/scope/TriggerExecutionScope.java | 2 + .../ast/value/StringJassValue.java | 8 +- 65 files changed, 3208 insertions(+), 197 deletions(-) create mode 100644 core/src/com/etheller/warsmash/parsers/jass/Tmpgen.java create mode 100644 core/src/com/etheller/warsmash/parsers/jass/Tmpgen2.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/ai/AIDifficulty.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/config/CBasePlayer.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/config/CPlayerAPI.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/config/War3MapConfig.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/config/War3MapConfigStartLoc.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/item/CItemTypeJass.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/OrderIdUtils.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CMapFlag.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CMapPlacement.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerColor.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerGameResult.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerJass.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerScore.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerState.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CStartLocPrio.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/state/CGameState.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/state/CUnitState.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/timers/CTimer.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/timers/CTimerJass.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/JassGameEventsWar3.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CAttackTypeJass.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CBlendMode.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CCameraField.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CDamageType.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CEffectType.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CFogState.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CGameSpeed.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CGameType.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CLimitOp.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CMapDensity.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CMapDifficulty.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CPathingTypeJass.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CPlayerSlotState.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CRarityControl.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CSoundType.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CSoundVolumeGroup.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CTexMapFlags.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CVersion.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CWeaponSoundTypeJass.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/unit/CUnitTypeJass.java diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMapScreen.java b/core/src/com/etheller/warsmash/WarsmashGdxMapScreen.java index 9d7b9c6..086b7dc 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMapScreen.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMapScreen.java @@ -35,6 +35,7 @@ import com.etheller.warsmash.units.DataTable; import com.etheller.warsmash.units.Element; import com.etheller.warsmash.util.DataSourceFileHandle; import com.etheller.warsmash.util.ImageUtils; +import com.etheller.warsmash.util.WarsmashConstants; import com.etheller.warsmash.viewer5.CanvasProvider; import com.etheller.warsmash.viewer5.Model; import com.etheller.warsmash.viewer5.ModelInstance; @@ -48,6 +49,7 @@ import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; import com.etheller.warsmash.viewer5.handlers.w3x.camera.CameraPreset; import com.etheller.warsmash.viewer5.handlers.w3x.camera.CameraRates; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.config.War3MapConfig; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayerUnitOrderExecutor; import com.etheller.warsmash.viewer5.handlers.w3x.ui.MeleeUI; import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.SettableCommandErrorListener; @@ -105,7 +107,8 @@ public class WarsmashGdxMapScreen implements CanvasProvider, InputProcessor, Scr final SettableCommandErrorListener commandErrorListener = new SettableCommandErrorListener(); this.codebase = parseDataSources(this.warsmashIni); - this.viewer = new War3MapViewer(this.codebase, this, commandErrorListener); + this.viewer = new War3MapViewer(this.codebase, this, commandErrorListener, + new War3MapConfig(WarsmashConstants.MAX_PLAYERS)); if (ENABLE_AUDIO) { this.viewer.worldScene.enableAudio(); @@ -218,7 +221,8 @@ public class WarsmashGdxMapScreen implements CanvasProvider, InputProcessor, Scr WarsmashGdxMapScreen.this.currentMusic = music; } } - }, new CPlayerUnitOrderExecutor(this.viewer.simulation, commandErrorListener)); + }, new CPlayerUnitOrderExecutor(this.viewer.simulation, this.viewer.getLocalPlayerIndex(), + commandErrorListener)); commandErrorListener.setDelegate(this.meleeUI); final ModelInstance libgdxContentInstance = new LibGDXContentLayerModel(null, this.viewer, "", this.viewer.mapPathSolver, "").addInstance(); diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMenuScreen.java b/core/src/com/etheller/warsmash/WarsmashGdxMenuScreen.java index 8721dd9..4788545 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMenuScreen.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMenuScreen.java @@ -187,7 +187,7 @@ public class WarsmashGdxMenuScreen implements CanvasProvider, InputProcessor, Sc final String musicPath = musics[(int) (Math.random() * musics.length)]; final Music music = Gdx.audio.newMusic( new DataSourceFileHandle(WarsmashGdxMenuScreen.this.viewer.dataSource, musicPath)); - music.setVolume(0.2f); +// music.setVolume(0.2f); music.setLooping(true); music.play(); WarsmashGdxMenuScreen.this.currentMusic = music; diff --git a/core/src/com/etheller/warsmash/parsers/jass/Jass2.java b/core/src/com/etheller/warsmash/parsers/jass/Jass2.java index fd8fc9b..5d1fe78 100644 --- a/core/src/com/etheller/warsmash/parsers/jass/Jass2.java +++ b/core/src/com/etheller/warsmash/parsers/jass/Jass2.java @@ -1,7 +1,9 @@ package com.etheller.warsmash.parsers.jass; +import java.awt.geom.Point2D; import java.io.IOException; import java.util.List; +import java.util.Locale; import org.antlr.v4.runtime.BaseErrorListener; import org.antlr.v4.runtime.CharStreams; @@ -23,7 +25,9 @@ import com.etheller.interpreter.ast.value.HandleJassType; import com.etheller.interpreter.ast.value.HandleJassValue; import com.etheller.interpreter.ast.value.IntegerJassValue; import com.etheller.interpreter.ast.value.JassValue; +import com.etheller.interpreter.ast.value.RealJassValue; import com.etheller.interpreter.ast.value.StringJassValue; +import com.etheller.interpreter.ast.value.visitor.BooleanJassValueVisitor; import com.etheller.interpreter.ast.value.visitor.IntegerJassValueVisitor; import com.etheller.interpreter.ast.value.visitor.JassFunctionJassValueVisitor; import com.etheller.interpreter.ast.value.visitor.ObjectJassValueVisitor; @@ -45,12 +49,101 @@ import com.etheller.warsmash.parsers.jass.triggers.BoolExprOr; import com.etheller.warsmash.parsers.jass.triggers.TriggerAction; import com.etheller.warsmash.parsers.jass.triggers.TriggerCondition; import com.etheller.warsmash.units.Element; +import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; +import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.Scene; import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; +import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.ItemUI; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CDestructableType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.ai.AIDifficulty; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.config.CPlayerAPI; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.config.War3MapConfig; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.item.CItemTypeJass; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIdUtils; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CAllianceType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CMapControl; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CMapFlag; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CMapPlacement; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayerColor; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayerGameResult; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayerJass; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayerScore; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayerState; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CRace; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CRacePreference; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CStartLocPrio; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.state.CGameState; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.state.CUnitState; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.timers.CTimerJass; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.JassGameEventsWar3; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.enumtypes.CAttackTypeJass; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.enumtypes.CBlendMode; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.enumtypes.CCameraField; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.enumtypes.CDamageType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.enumtypes.CEffectType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.enumtypes.CFogState; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.enumtypes.CGameSpeed; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.enumtypes.CGameType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.enumtypes.CLimitOp; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.enumtypes.CMapDensity; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.enumtypes.CMapDifficulty; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.enumtypes.CPathingTypeJass; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.enumtypes.CPlayerSlotState; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.enumtypes.CRarityControl; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.enumtypes.CSoundType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.enumtypes.CSoundVolumeGroup; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.enumtypes.CTexMapFlags; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.enumtypes.CVersion; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.enumtypes.CWeaponSoundTypeJass; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.unit.CUnitTypeJass; public class Jass2 { public static final boolean REPORT_SYNTAX_ERRORS = true; + public static CommonEnvironment loadCommon(final DataSource dataSource, final Viewport uiViewport, + final Scene uiScene, final War3MapViewer war3MapViewer, final RootFrameListener rootFrameListener, + final String... files) { + + final JassProgramVisitor jassProgramVisitor = new JassProgramVisitor(); + final CommonEnvironment environment = new CommonEnvironment(jassProgramVisitor, dataSource, uiViewport, uiScene, + war3MapViewer, rootFrameListener); + for (final String jassFile : files) { + try { + JassLexer lexer; + try { + lexer = new JassLexer(CharStreams.fromStream(dataSource.getResourceAsStream(jassFile))); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + final JassParser parser = new JassParser(new CommonTokenStream(lexer)); +// parser.removeErrorListener(ConsoleErrorListener.INSTANCE); + parser.addErrorListener(new BaseErrorListener() { + @Override + public void syntaxError(final Recognizer recognizer, final Object offendingSymbol, + final int line, final int charPositionInLine, final String msg, + final RecognitionException e) { + if (!REPORT_SYNTAX_ERRORS) { + return; + } + + final String sourceName = String.format("%s:%d:%d: ", jassFile, line, charPositionInLine); + + System.err.println(sourceName + "line " + line + ":" + charPositionInLine + " " + msg); + } + }); + jassProgramVisitor.visit(parser.program()); + } + catch (final Exception e) { + e.printStackTrace(); + } + } + jassProgramVisitor.getJassNativeManager().checkUnregisteredNatives(); + return environment; + } + public static JUIEnvironment loadJUI(final DataSource dataSource, final Viewport uiViewport, final Scene uiScene, final War3MapViewer war3MapViewer, final RootFrameListener rootFrameListener, final String... files) { @@ -455,4 +548,1362 @@ public class Jass2 { }); } } + + private static final class CommonEnvironment { + private GameUI gameUI; + private Element skin; + + public CommonEnvironment(final JassProgramVisitor jassProgramVisitor, final DataSource dataSource, + final Viewport uiViewport, final Scene uiScene, final War3MapViewer war3MapViewer, + final RootFrameListener rootFrameListener) { + final GlobalScope globals = jassProgramVisitor.getGlobals(); + final HandleJassType agentType = globals.registerHandleType("agent"); + final HandleJassType eventType = globals.registerHandleType("event"); + final HandleJassType playerType = globals.registerHandleType("player"); + final HandleJassType widgetType = globals.registerHandleType("widget"); + final HandleJassType unitType = globals.registerHandleType("unit"); + final HandleJassType destructableType = globals.registerHandleType("destructable"); + final HandleJassType itemType = globals.registerHandleType("item"); + final HandleJassType abilityType = globals.registerHandleType("ability"); + final HandleJassType buffType = globals.registerHandleType("buff"); + final HandleJassType forceType = globals.registerHandleType("force"); + final HandleJassType groupType = globals.registerHandleType("group"); + final HandleJassType triggerType = globals.registerHandleType("trigger"); + final HandleJassType triggerconditionType = globals.registerHandleType("triggercondition"); + final HandleJassType triggeractionType = globals.registerHandleType("triggeraction"); + final HandleJassType timerType = globals.registerHandleType("timer"); + final HandleJassType locationType = globals.registerHandleType("location"); + final HandleJassType regionType = globals.registerHandleType("region"); + final HandleJassType rectType = globals.registerHandleType("rect"); + final HandleJassType boolexprType = globals.registerHandleType("boolexpr"); + final HandleJassType soundType = globals.registerHandleType("sound"); + final HandleJassType conditionfuncType = globals.registerHandleType("conditionfunc"); + final HandleJassType filterfuncType = globals.registerHandleType("filterfunc"); + final HandleJassType unitpoolType = globals.registerHandleType("unitpool"); + final HandleJassType itempoolType = globals.registerHandleType("itempool"); + final HandleJassType raceType = globals.registerHandleType("race"); + final HandleJassType alliancetypeType = globals.registerHandleType("alliancetype"); + final HandleJassType racepreferenceType = globals.registerHandleType("racepreference"); + final HandleJassType gamestateType = globals.registerHandleType("gamestate"); + final HandleJassType igamestateType = globals.registerHandleType("igamestate"); + final HandleJassType fgamestateType = globals.registerHandleType("fgamestate"); + final HandleJassType playerstateType = globals.registerHandleType("playerstate"); + final HandleJassType playerscoreType = globals.registerHandleType("playerscore"); + final HandleJassType playergameresultType = globals.registerHandleType("playergameresult"); + final HandleJassType unitstateType = globals.registerHandleType("unitstate"); + final HandleJassType aidifficultyType = globals.registerHandleType("aidifficulty"); + final HandleJassType eventidType = globals.registerHandleType("eventid"); + final HandleJassType gameeventType = globals.registerHandleType("gameevent"); + final HandleJassType playereventType = globals.registerHandleType("playerevent"); + final HandleJassType playeruniteventType = globals.registerHandleType("playerunitevent"); + final HandleJassType uniteventType = globals.registerHandleType("unitevent"); + final HandleJassType limitopType = globals.registerHandleType("limitop"); + final HandleJassType widgeteventType = globals.registerHandleType("widgetevent"); + final HandleJassType dialogeventType = globals.registerHandleType("dialogevent"); + final HandleJassType unittypeType = globals.registerHandleType("unittype"); + final HandleJassType gamespeedType = globals.registerHandleType("gamespeed"); + final HandleJassType gamedifficultyType = globals.registerHandleType("gamedifficulty"); + final HandleJassType gametypeType = globals.registerHandleType("gametype"); + final HandleJassType mapflagType = globals.registerHandleType("mapflag"); + final HandleJassType mapvisibilityType = globals.registerHandleType("mapvisibility"); + final HandleJassType mapsettingType = globals.registerHandleType("mapsetting"); + final HandleJassType mapdensityType = globals.registerHandleType("mapdensity"); + final HandleJassType mapcontrolType = globals.registerHandleType("mapcontrol"); + final HandleJassType playerslotstateType = globals.registerHandleType("playerslotstate"); + final HandleJassType volumegroupType = globals.registerHandleType("volumegroup"); + final HandleJassType camerafieldType = globals.registerHandleType("camerafield"); + final HandleJassType camerasetupType = globals.registerHandleType("camerasetup"); + final HandleJassType playercolorType = globals.registerHandleType("playercolor"); + final HandleJassType placementType = globals.registerHandleType("placement"); + final HandleJassType startlocprioType = globals.registerHandleType("startlocprio"); + final HandleJassType raritycontrolType = globals.registerHandleType("raritycontrol"); + final HandleJassType blendmodeType = globals.registerHandleType("blendmode"); + final HandleJassType texmapflagsType = globals.registerHandleType("texmapflags"); + final HandleJassType effectType = globals.registerHandleType("effect"); + final HandleJassType effecttypeType = globals.registerHandleType("effecttype"); + final HandleJassType weathereffectType = globals.registerHandleType("weathereffect"); + final HandleJassType terraindeformationType = globals.registerHandleType("terraindeformation"); + final HandleJassType fogstateType = globals.registerHandleType("fogstate"); + final HandleJassType fogmodifierType = globals.registerHandleType("fogmodifier"); + final HandleJassType dialogType = globals.registerHandleType("dialog"); + final HandleJassType buttonType = globals.registerHandleType("button"); + final HandleJassType questType = globals.registerHandleType("quest"); + final HandleJassType questitemType = globals.registerHandleType("questitem"); + final HandleJassType defeatconditionType = globals.registerHandleType("defeatcondition"); + final HandleJassType timerdialogType = globals.registerHandleType("timerdialog"); + final HandleJassType leaderboardType = globals.registerHandleType("leaderboard"); + final HandleJassType multiboardType = globals.registerHandleType("multiboard"); + final HandleJassType multiboarditemType = globals.registerHandleType("multiboarditem"); + final HandleJassType trackableType = globals.registerHandleType("trackable"); + final HandleJassType gamecacheType = globals.registerHandleType("gamecache"); + final HandleJassType versionType = globals.registerHandleType("version"); + final HandleJassType itemtypeType = globals.registerHandleType("itemtype"); + final HandleJassType texttagType = globals.registerHandleType("texttag"); + final HandleJassType attacktypeType = globals.registerHandleType("attacktype"); + final HandleJassType damagetypeType = globals.registerHandleType("damagetype"); + final HandleJassType weapontypeType = globals.registerHandleType("weapontype"); + final HandleJassType soundtypeType = globals.registerHandleType("soundtype"); + final HandleJassType lightningType = globals.registerHandleType("lightning"); + final HandleJassType pathingtypeType = globals.registerHandleType("pathingtype"); + final HandleJassType imageType = globals.registerHandleType("image"); + final HandleJassType ubersplatType = globals.registerHandleType("ubersplat"); + final HandleJassType hashtableType = globals.registerHandleType("hashtable"); + + jassProgramVisitor.getJassNativeManager().createNative("ConvertRace", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final int i = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + return new HandleJassValue(raceType, CRace.VALUES[i]); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ConvertAllianceType", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final int i = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + return new HandleJassValue(alliancetypeType, CAllianceType.VALUES[i]); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ConvertRacePref", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final int i = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + return new HandleJassValue(racepreferenceType, CRacePreference.getById(i)); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ConvertIGameState", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final int i = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + return new HandleJassValue(igamestateType, CGameState.VALUES[i]); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ConvertFGameState", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final int i = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + return new HandleJassValue(fgamestateType, CGameState.VALUES[i]); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ConvertPlayerState", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final int i = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + return new HandleJassValue(playerstateType, CPlayerState.VALUES[i]); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ConvertPlayerScore", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final int i = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + return new HandleJassValue(playerscoreType, CPlayerScore.VALUES[i]); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ConvertGameResult", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final int i = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + return new HandleJassValue(playergameresultType, CPlayerGameResult.VALUES[i]); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ConvertUnitState", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final int i = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + return new HandleJassValue(unitstateType, CUnitState.VALUES[i]); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ConvertAIDifficulty", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final int i = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + return new HandleJassValue(aidifficultyType, AIDifficulty.VALUES[i]); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ConvertGameEvent", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final int i = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + return new HandleJassValue(gameeventType, JassGameEventsWar3.VALUES[i]); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ConvertPlayerEvent", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final int i = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + return new HandleJassValue(playereventType, JassGameEventsWar3.VALUES[i]); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ConvertPlayerUnitEvent", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final int i = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + return new HandleJassValue(playeruniteventType, JassGameEventsWar3.VALUES[i]); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ConvertWidgetEvent", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final int i = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + return new HandleJassValue(widgeteventType, JassGameEventsWar3.VALUES[i]); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ConvertDialogEvent", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final int i = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + return new HandleJassValue(dialogeventType, JassGameEventsWar3.VALUES[i]); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ConvertUnitEvent", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final int i = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + return new HandleJassValue(uniteventType, JassGameEventsWar3.VALUES[i]); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ConvertLimitOp", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final int i = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + return new HandleJassValue(limitopType, CLimitOp.VALUES[i]); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ConvertUnitType", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final int i = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + return new HandleJassValue(unittypeType, CUnitTypeJass.VALUES[i]); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ConvertGameSpeed", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final int i = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + return new HandleJassValue(gamespeedType, CGameSpeed.VALUES[i]); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ConvertPlacement", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final int i = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + return new HandleJassValue(placementType, CMapPlacement.VALUES[i]); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ConvertStartLocPrio", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final int i = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + return new HandleJassValue(startlocprioType, CStartLocPrio.VALUES[i]); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ConvertGameDifficulty", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final int i = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + return new HandleJassValue(gamedifficultyType, CMapDifficulty.VALUES[i]); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ConvertGameType", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final int i = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + return new HandleJassValue(gametypeType, CGameType.getById(i)); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ConvertMapFlag", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final int i = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + return new HandleJassValue(gametypeType, CMapFlag.getById(i)); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ConvertMapVisibility", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + return new HandleJassValue(mapvisibilityType, null); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ConvertMapSetting", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + return new HandleJassValue(mapsettingType, null); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ConvertMapDensity", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final int i = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + return new HandleJassValue(mapdensityType, CMapDensity.VALUES[i]); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ConvertMapControl", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final int i = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + return new HandleJassValue(mapcontrolType, CMapControl.VALUES[i]); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ConvertPlayerColor", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final int i = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + return new HandleJassValue(playercolorType, CMapControl.VALUES[i]); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ConvertPlayerSlotState", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final int i = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + return new HandleJassValue(playerslotstateType, CPlayerSlotState.VALUES[i]); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ConvertVolumeGroup", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final int i = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + return new HandleJassValue(volumegroupType, CSoundVolumeGroup.VALUES[i]); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ConvertCameraField", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final int i = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + return new HandleJassValue(camerafieldType, CCameraField.VALUES[i]); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ConvertBlendMode", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final int i = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + return new HandleJassValue(blendmodeType, CBlendMode.VALUES[i]); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ConvertRarityControl", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final int i = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + return new HandleJassValue(raritycontrolType, CRarityControl.VALUES[i]); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ConvertTexMapFlags", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final int i = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + return new HandleJassValue(texmapflagsType, CTexMapFlags.VALUES[i]); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ConvertFogState", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final int i = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + return new HandleJassValue(fogstateType, CFogState.VALUES[i]); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ConvertEffectType", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final int i = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + return new HandleJassValue(effecttypeType, CEffectType.VALUES[i]); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ConvertVersion", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final int i = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + return new HandleJassValue(versionType, CVersion.VALUES[i]); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ConvertItemType", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final int i = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + return new HandleJassValue(itemtypeType, CItemTypeJass.VALUES[i]); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ConvertAttackType", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final int i = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + return new HandleJassValue(attacktypeType, CAttackTypeJass.VALUES[i]); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ConvertDamageType", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final int i = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + return new HandleJassValue(attacktypeType, CDamageType.VALUES[i]); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ConvertWeaponType", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final int i = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + return new HandleJassValue(weapontypeType, CWeaponSoundTypeJass.VALUES[i]); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ConvertSoundType", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final int i = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + return new HandleJassValue(soundtypeType, CSoundType.VALUES[i]); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ConvertPathingType", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final int i = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + return new HandleJassValue(pathingtypeType, CPathingTypeJass.VALUES[i]); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("OrderId", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final String idString = arguments.get(0).visit(StringJassValueVisitor.getInstance()); + final int orderId = OrderIdUtils.getOrderId(idString); + return new IntegerJassValue(orderId); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("OrderId2String", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Integer id = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + return new StringJassValue(OrderIdUtils.getStringFromOrderId(id)); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("UnitId", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final String idString = arguments.get(0).visit(StringJassValueVisitor.getInstance()); + final CUnitType unitType = war3MapViewer.simulation.getUnitData() + .getUnitTypeByJassLegacyName(idString); + if (unitType == null) { + return new IntegerJassValue(0); + } + return new IntegerJassValue(unitType.getTypeId().getValue()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("UnitId2String", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Integer id = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + final War3ID war3id = new War3ID(id); + return new StringJassValue( + war3MapViewer.simulation.getUnitData().getUnitType(war3id).getLegacyName()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("AbilityId", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + return new IntegerJassValue(0); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("AbilityId2String", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + return new StringJassValue(""); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetObjectName", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Integer id = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + final War3ID war3id = new War3ID(id); + final CSimulation simulation = war3MapViewer.simulation; + final CUnitType unitType = simulation.getUnitData().getUnitType(war3id); + if (unitType != null) { + return new StringJassValue(unitType.getName()); + } + // TODO for now this looks in the ability editor data, not the fast symbol table + // layer on top, because the layer on top forgot to have a name value... + final MutableGameObject abilityEditorData = war3MapViewer.getAllObjectData().getAbilities() + .get(war3id); + if (abilityEditorData != null) { + return new StringJassValue(abilityEditorData.getName()); + } + final ItemUI itemUI = war3MapViewer.getAbilityDataUI().getItemUI(war3id); + if (itemUI != null) { + return new StringJassValue(itemUI.getName()); + } + final CDestructableType destructableType = simulation.getDestructableData().getUnitType(war3id); + if (destructableType != null) { + return new StringJassValue(destructableType.getName()); + } + return new StringJassValue(""); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("Deg2Rad", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Double value = arguments.get(0).visit(RealJassValueVisitor.getInstance()); + return new RealJassValue(StrictMath.toRadians(value)); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("Rad2Deg", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Double value = arguments.get(0).visit(RealJassValueVisitor.getInstance()); + return new RealJassValue(StrictMath.toDegrees(value)); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("Sin", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Double value = arguments.get(0).visit(RealJassValueVisitor.getInstance()); + return new RealJassValue(StrictMath.sin(value)); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("Cos", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Double value = arguments.get(0).visit(RealJassValueVisitor.getInstance()); + return new RealJassValue(StrictMath.cos(value)); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("Tan", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Double value = arguments.get(0).visit(RealJassValueVisitor.getInstance()); + return new RealJassValue(StrictMath.tan(value)); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("Asin", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Double value = arguments.get(0).visit(RealJassValueVisitor.getInstance()); + final double result = StrictMath.asin(value); + if (Double.isNaN(result)) { + return new RealJassValue(0); + } + return new RealJassValue(result); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("Acos", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Double value = arguments.get(0).visit(RealJassValueVisitor.getInstance()); + final double result = StrictMath.acos(value); + if (Double.isNaN(result)) { + return new RealJassValue(0); + } + return new RealJassValue(result); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("Atan", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Double value = arguments.get(0).visit(RealJassValueVisitor.getInstance()); + final double result = StrictMath.atan(value); + if (Double.isNaN(result)) { + return new RealJassValue(0); + } + return new RealJassValue(result); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("Atan2", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Double y = arguments.get(0).visit(RealJassValueVisitor.getInstance()); + final Double x = arguments.get(1).visit(RealJassValueVisitor.getInstance()); + final double result = StrictMath.atan2(y, x); + if (Double.isNaN(result)) { + return new RealJassValue(0); + } + return new RealJassValue(result); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("SquareRoot", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Double value = arguments.get(0).visit(RealJassValueVisitor.getInstance()); + final double result = StrictMath.sqrt(value); + return new RealJassValue(result); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("Pow", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Double y = arguments.get(0).visit(RealJassValueVisitor.getInstance()); + final Double x = arguments.get(1).visit(RealJassValueVisitor.getInstance()); + final double result = StrictMath.pow(y, x); + if (Double.isNaN(result)) { + return new RealJassValue(0); + } + return new RealJassValue(result); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("I2R", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Integer i = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + return new RealJassValue(i.doubleValue()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("R2I", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Double r = arguments.get(0).visit(RealJassValueVisitor.getInstance()); + return new IntegerJassValue(r.intValue()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("I2S", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Integer i = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + return new StringJassValue(i.toString()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("R2S", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Double r = arguments.get(0).visit(RealJassValueVisitor.getInstance()); + return new StringJassValue(r.toString()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("R2SW", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Double r = arguments.get(0).visit(RealJassValueVisitor.getInstance()); + final int width = arguments.get(1).visit(IntegerJassValueVisitor.getInstance()); + final int precision = arguments.get(2).visit(IntegerJassValueVisitor.getInstance()); + return new StringJassValue(String.format("%" + precision + "." + width + "f", r)); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("S2I", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final String s = arguments.get(0).visit(StringJassValueVisitor.getInstance()); + try { + final int intValue = Integer.parseInt(s); + return new IntegerJassValue(intValue); + } + catch (final Exception exc) { + return new IntegerJassValue(0); + } + } + }); + jassProgramVisitor.getJassNativeManager().createNative("S2R", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final String s = arguments.get(0).visit(StringJassValueVisitor.getInstance()); + try { + final double parsedValue = Double.parseDouble(s); + return new RealJassValue(parsedValue); + } + catch (final Exception exc) { + return new RealJassValue(0); + } + } + }); + jassProgramVisitor.getJassNativeManager().createNative("SubString", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final String s = arguments.get(0).visit(StringJassValueVisitor.getInstance()); + final int start = arguments.get(1).visit(IntegerJassValueVisitor.getInstance()); + final int end = arguments.get(2).visit(IntegerJassValueVisitor.getInstance()); + return new StringJassValue(s.substring(start, end)); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("StringLength", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final String s = arguments.get(0).visit(StringJassValueVisitor.getInstance()); + return new IntegerJassValue(s.length()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("StringCase", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final String s = arguments.get(0).visit(StringJassValueVisitor.getInstance()); + final boolean upper = arguments.get(1).visit(BooleanJassValueVisitor.getInstance()); + return new StringJassValue(upper ? s.toUpperCase(Locale.US) : s.toLowerCase(Locale.US)); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("StringHash", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final String s = arguments.get(0).visit(StringJassValueVisitor.getInstance()); + return new IntegerJassValue(s.hashCode()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetLocalizedString", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final String key = arguments.get(0).visit(StringJassValueVisitor.getInstance()); + // TODO this might be wrong, or a subset of the needed return values + final String decoratedString = war3MapViewer.getGameUI().getTemplates().getDecoratedString(key); + if (key.equals(decoratedString)) { + System.err.println("GetLocalizedString: NOT FOUND: " + key); + } + return new StringJassValue(decoratedString); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetLocalizedHotkey", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final String key = arguments.get(0).visit(StringJassValueVisitor.getInstance()); + // TODO this might be wrong, or a subset of the needed return values + final String decoratedString = war3MapViewer.getGameUI().getTemplates().getDecoratedString(key); + if (key.equals(decoratedString)) { + System.err.println("GetLocalizedHotkey: NOT FOUND: " + key); + } + return new IntegerJassValue(decoratedString.charAt(0)); + } + }); + final War3MapConfig mapConfig = war3MapViewer.getMapConfig(); + registerConfigNatives(jassProgramVisitor, mapConfig, startlocprioType, gametypeType, placementType, + gamespeedType, gamedifficultyType, mapdensityType, locationType, playerType, playercolorType, + mapcontrolType, playerslotstateType, war3MapViewer.simulation); + + jassProgramVisitor.getJassNativeManager().createNative("CreateTimer", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + return new HandleJassValue(timerType, new CTimerJass(globalScope)); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("DestroyTimer", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CTimerJass timer = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + war3MapViewer.simulation.unregisterTimer(timer); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("TimerStart", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CTimerJass timer = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final Double timeout = arguments.get(1).visit(RealJassValueVisitor.getInstance()); + final boolean periodic = arguments.get(2).visit(BooleanJassValueVisitor.getInstance()); + final JassFunction handlerFunc = arguments.get(3).visit(JassFunctionJassValueVisitor.getInstance()); + if (!timer.isRunning()) { + timer.setTimeoutTime(timeout.floatValue()); + timer.setRepeats(periodic); + timer.setHandlerFunc(handlerFunc); + timer.start(war3MapViewer.simulation); + } + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("TimerGetElapsed", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CTimerJass timer = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + return new RealJassValue(timer.getElapsed(war3MapViewer.simulation)); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("TimerGetRemaining", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CTimerJass timer = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + return new RealJassValue(timer.getRemaining(war3MapViewer.simulation)); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("TimerGetTimeout", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CTimerJass timer = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + return new RealJassValue(timer.getTimeoutTime()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("PauseTimer", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CTimerJass timer = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + timer.pause(war3MapViewer.simulation); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ResumeTimer", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CTimerJass timer = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + timer.resume(war3MapViewer.simulation); + return null; + } + }); + } + + private void registerConfigNatives(final JassProgramVisitor jassProgramVisitor, final War3MapConfig mapConfig, + final HandleJassType startlocprioType, final HandleJassType gametypeType, + final HandleJassType placementType, final HandleJassType gamespeedType, + final HandleJassType gamedifficultyType, final HandleJassType mapdensityType, + final HandleJassType locationType, final HandleJassType playerType, + final HandleJassType playercolorType, final HandleJassType mapcontrolType, + final HandleJassType playerslotstateType, final CPlayerAPI playerAPI) { + jassProgramVisitor.getJassNativeManager().createNative("SetMapName", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final String name = arguments.get(0).visit(StringJassValueVisitor.getInstance()); + mapConfig.setMapName(name); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("SetMapDescription", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final String name = arguments.get(0).visit(StringJassValueVisitor.getInstance()); + mapConfig.setMapDescription(name); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("SetTeams", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Integer teamCount = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + mapConfig.setTeamCount(teamCount); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("SetPlayers", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Integer playerCount = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + mapConfig.setPlayerCount(playerCount); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("DefineStartLocation", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Integer whichStartLoc = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + final Double x = arguments.get(1).visit(RealJassValueVisitor.getInstance()); + final Double y = arguments.get(2).visit(RealJassValueVisitor.getInstance()); + mapConfig.getStartLoc(whichStartLoc).setX(x.floatValue()); + mapConfig.getStartLoc(whichStartLoc).setY(y.floatValue()); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("DefineStartLocationLoc", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Integer whichStartLoc = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + final Point2D.Double whichLocation = arguments.get(1) + .visit(ObjectJassValueVisitor.getInstance()); + mapConfig.getStartLoc(whichStartLoc).setX((float) whichLocation.x); + mapConfig.getStartLoc(whichStartLoc).setY((float) whichLocation.y); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("SetStartLocPrioCount", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Integer whichStartLoc = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + final Integer prioSlotCount = arguments.get(1).visit(IntegerJassValueVisitor.getInstance()); + mapConfig.getStartLoc(whichStartLoc).setStartLocPrioCount(prioSlotCount); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("SetStartLocPrio", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Integer whichStartLoc = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + final Integer prioSlotIndex = arguments.get(1).visit(IntegerJassValueVisitor.getInstance()); + final Integer otherStartLocIndex = arguments.get(2).visit(IntegerJassValueVisitor.getInstance()); + final CStartLocPrio priority = arguments.get(1) + .visit(ObjectJassValueVisitor.getInstance()); + mapConfig.getStartLoc(whichStartLoc).setStartLocPrio(prioSlotIndex, otherStartLocIndex, priority); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetStartLocPrioSlot", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Integer whichStartLoc = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + final Integer prioSlotIndex = arguments.get(1).visit(IntegerJassValueVisitor.getInstance()); + return new IntegerJassValue( + mapConfig.getStartLoc(whichStartLoc).getOtherStartIndices()[prioSlotIndex]); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetStartLocPrio", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Integer whichStartLoc = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + final Integer prioSlotIndex = arguments.get(1).visit(IntegerJassValueVisitor.getInstance()); + return new HandleJassValue(startlocprioType, + mapConfig.getStartLoc(whichStartLoc).getOtherStartLocPriorities()[prioSlotIndex]); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("SetGameTypeSupported", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CGameType gameType = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final Boolean value = arguments.get(1).visit(BooleanJassValueVisitor.getInstance()); + mapConfig.setGameTypeSupported(gameType, value); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("SetMapFlag", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CMapFlag mapFlag = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final Boolean value = arguments.get(1).visit(BooleanJassValueVisitor.getInstance()); + mapConfig.setMapFlag(mapFlag, value); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("SetGamePlacement", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CMapPlacement placement = arguments.get(0) + .visit(ObjectJassValueVisitor.getInstance()); + mapConfig.setPlacement(placement); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("SetGameSpeed", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CGameSpeed gameSpeed = arguments.get(0) + .visit(ObjectJassValueVisitor.getInstance()); + mapConfig.setGameSpeed(gameSpeed); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("SetGameDifficulty", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CMapDifficulty gameDifficulty = arguments.get(0) + .visit(ObjectJassValueVisitor.getInstance()); + mapConfig.setGameDifficulty(gameDifficulty); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("SetResourceDensity", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CMapDensity resourceDensity = arguments.get(0) + .visit(ObjectJassValueVisitor.getInstance()); + mapConfig.setResourceDensity(resourceDensity); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("SetCreatureDensity", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CMapDensity creatureDensity = arguments.get(0) + .visit(ObjectJassValueVisitor.getInstance()); + mapConfig.setCreatureDensity(creatureDensity); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetTeams", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + return new IntegerJassValue(mapConfig.getTeamCount()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetPlayers", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + return new IntegerJassValue(mapConfig.getPlayerCount()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("IsGameTypeSupported", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CGameType gameType = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + return new BooleanJassValue(mapConfig.isGameTypeSupported(gameType)); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetGameTypeSelected", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + return new HandleJassValue(gametypeType, mapConfig.getGameTypeSelected()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("IsMapFlagSet", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CMapFlag mapFlag = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + return new BooleanJassValue(mapConfig.isMapFlagSet(mapFlag)); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetGamePlacement", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + return new HandleJassValue(placementType, mapConfig.getPlacement()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetGameSpeed", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + return new HandleJassValue(gamespeedType, mapConfig.getGameSpeed()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetGameDifficulty", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + return new HandleJassValue(gamedifficultyType, mapConfig.getGameDifficulty()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetResourceDensity", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + return new HandleJassValue(mapdensityType, mapConfig.getResourceDensity()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetCreatureDensity", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + return new HandleJassValue(mapdensityType, mapConfig.getCreatureDensity()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetStartLocationX", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Integer whichStartLoc = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + return new RealJassValue(mapConfig.getStartLoc(whichStartLoc).getX()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetStartLocationY", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Integer whichStartLoc = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + return new RealJassValue(mapConfig.getStartLoc(whichStartLoc).getY()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetStartLocationLoc", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Integer whichStartLoc = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); + return new HandleJassValue(locationType, new Point2D.Double( + mapConfig.getStartLoc(whichStartLoc).getX(), mapConfig.getStartLoc(whichStartLoc).getY())); + } + }); + // PlayerAPI + + jassProgramVisitor.getJassNativeManager().createNative("SetPlayerTeam", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CPlayerJass player = arguments.get(0) + .visit(ObjectJassValueVisitor.getInstance()); + final Integer whichTeam = arguments.get(1).visit(IntegerJassValueVisitor.getInstance()); + player.setTeam(whichTeam); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("SetPlayerStartLocation", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CPlayerJass player = arguments.get(0) + .visit(ObjectJassValueVisitor.getInstance()); + final Integer startLocIndex = arguments.get(1).visit(IntegerJassValueVisitor.getInstance()); + player.setStartLocationIndex(startLocIndex); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ForcePlayerStartLocation", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CPlayerJass player = arguments.get(0) + .visit(ObjectJassValueVisitor.getInstance()); + final Integer startLocIndex = arguments.get(1).visit(IntegerJassValueVisitor.getInstance()); + player.forceStartLocation(startLocIndex); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("SetPlayerColor", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CPlayerJass player = arguments.get(0) + .visit(ObjectJassValueVisitor.getInstance()); + final CPlayerColor playerColor = arguments.get(1) + .visit(ObjectJassValueVisitor.getInstance()); + player.setColor(playerColor.ordinal()); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("SetPlayerAlliance", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CPlayerJass player = arguments.get(0) + .visit(ObjectJassValueVisitor.getInstance()); + final CPlayerJass otherPlayer = arguments.get(1) + .visit(ObjectJassValueVisitor.getInstance()); + final CAllianceType whichAllianceSetting = arguments.get(2) + .visit(ObjectJassValueVisitor.getInstance()); + final Boolean value = arguments.get(3).visit(BooleanJassValueVisitor.getInstance()); + player.setAlliance(otherPlayer.getId(), whichAllianceSetting, value); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("SetPlayerTaxRate", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CPlayerJass player = arguments.get(0) + .visit(ObjectJassValueVisitor.getInstance()); + final CPlayerJass otherPlayer = arguments.get(1) + .visit(ObjectJassValueVisitor.getInstance()); + final CPlayerState whichResource = arguments.get(2) + .visit(ObjectJassValueVisitor.getInstance()); + final int taxRate = arguments.get(3).visit(IntegerJassValueVisitor.getInstance()); + player.setTaxRate(otherPlayer.getId(), whichResource, taxRate); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("SetPlayerRacePreference", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CPlayerJass player = arguments.get(0) + .visit(ObjectJassValueVisitor.getInstance()); + final CRacePreference whichRacePreference = arguments.get(1) + .visit(ObjectJassValueVisitor.getInstance()); + player.setRacePref(whichRacePreference); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("SetPlayerRaceSelectable", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CPlayerJass player = arguments.get(0) + .visit(ObjectJassValueVisitor.getInstance()); + final Boolean value = arguments.get(1).visit(BooleanJassValueVisitor.getInstance()); + player.setRaceSelectable(value); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("SetPlayerController", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CPlayerJass player = arguments.get(0) + .visit(ObjectJassValueVisitor.getInstance()); + final CMapControl controlType = arguments.get(1) + .visit(ObjectJassValueVisitor.getInstance()); + player.setController(controlType); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("SetPlayerName", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CPlayerJass player = arguments.get(0) + .visit(ObjectJassValueVisitor.getInstance()); + final String name = arguments.get(1).visit(StringJassValueVisitor.getInstance()); + player.setName(name); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("SetPlayerOnScoreScreen", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CPlayerJass player = arguments.get(0) + .visit(ObjectJassValueVisitor.getInstance()); + final Boolean value = arguments.get(1).visit(BooleanJassValueVisitor.getInstance()); + player.setOnScoreScreen(value); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetPlayerTeam", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CPlayerJass player = arguments.get(0) + .visit(ObjectJassValueVisitor.getInstance()); + return new IntegerJassValue(player.getTeam()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetPlayerStartLocation", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CPlayerJass player = arguments.get(0) + .visit(ObjectJassValueVisitor.getInstance()); + return new IntegerJassValue(player.getStartLocationIndex()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetPlayerColor", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CPlayerJass player = arguments.get(0) + .visit(ObjectJassValueVisitor.getInstance()); + return new HandleJassValue(playercolorType, CPlayerColor.getColorByIndex(player.getColor())); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetPlayerSelectable", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CPlayerJass player = arguments.get(0) + .visit(ObjectJassValueVisitor.getInstance()); + return new BooleanJassValue(player.isSelectable()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetPlayerController", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CPlayerJass player = arguments.get(0) + .visit(ObjectJassValueVisitor.getInstance()); + return new HandleJassValue(mapcontrolType, player.getController()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetPlayerSlotState", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CPlayerJass player = arguments.get(0) + .visit(ObjectJassValueVisitor.getInstance()); + return new HandleJassValue(playerslotstateType, player.getController()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetPlayerTaxRate", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CPlayerJass player = arguments.get(0) + .visit(ObjectJassValueVisitor.getInstance()); + final CPlayerJass otherPlayer = arguments.get(1) + .visit(ObjectJassValueVisitor.getInstance()); + final CPlayerState whichResource = arguments.get(2) + .visit(ObjectJassValueVisitor.getInstance()); + return new IntegerJassValue(player.getTaxRate(otherPlayer.getId(), whichResource)); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("IsPlayerRacePrefSet", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CPlayerJass player = arguments.get(0) + .visit(ObjectJassValueVisitor.getInstance()); + final CRacePreference racePref = arguments.get(1) + .visit(ObjectJassValueVisitor.getInstance()); + return new BooleanJassValue(player.isRacePrefSet(racePref)); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetPlayerName", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CPlayerJass player = arguments.get(0) + .visit(ObjectJassValueVisitor.getInstance()); + return new StringJassValue(player.getName()); + } + }); + } + } } diff --git a/core/src/com/etheller/warsmash/parsers/jass/Tmpgen.java b/core/src/com/etheller/warsmash/parsers/jass/Tmpgen.java new file mode 100644 index 0000000..7d32790 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/jass/Tmpgen.java @@ -0,0 +1,22 @@ +package com.etheller.warsmash.parsers.jass; + +import java.util.Scanner; + +public class Tmpgen { + + public static void main(final String[] args) { + // final HandleJassType eventType = globals.registerHandleType("event"); + + final Scanner scanner = new Scanner(System.in); + while (scanner.hasNextLine()) { + final String line = scanner.nextLine(); + if (line.startsWith("type ")) { + final String[] splitLine = line.split("\\s+"); + System.out.println("final HandleJassType " + splitLine[1] + "Type = globals.registerHandleType(\"" + + splitLine[1] + "\");"); + } + } + + } + +} diff --git a/core/src/com/etheller/warsmash/parsers/jass/Tmpgen2.java b/core/src/com/etheller/warsmash/parsers/jass/Tmpgen2.java new file mode 100644 index 0000000..b9124c1 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/jass/Tmpgen2.java @@ -0,0 +1,29 @@ +package com.etheller.warsmash.parsers.jass; + +import java.util.Scanner; + +public class Tmpgen2 { + + public static void main(final String[] args) { + // final HandleJassType eventType = globals.registerHandleType("event"); + + final Scanner scanner = new Scanner(System.in); + while (scanner.hasNextLine()) { + final String line = scanner.nextLine(); + final String[] splitLine = line.trim().split("\\s+"); + if (line.trim().startsWith("//")) { + System.out.println(line); + } + else { + if (splitLine.length > 3) { + System.out.println(splitLine[2] + ","); + } + else { + System.out.println(); + } + } + } + + } + +} diff --git a/core/src/com/etheller/warsmash/util/WarsmashConstants.java b/core/src/com/etheller/warsmash/util/WarsmashConstants.java index 6b2e4ab..694d5b2 100644 --- a/core/src/com/etheller/warsmash/util/WarsmashConstants.java +++ b/core/src/com/etheller/warsmash/util/WarsmashConstants.java @@ -6,7 +6,7 @@ public class WarsmashConstants { * With version, we use 0 for RoC, 1 for TFT emulation, and probably 2+ or * whatever for custom mods and other stuff */ - public static int GAME_VERSION = 1; + public static int GAME_VERSION = 0; public static final int REPLACEABLE_TEXTURE_LIMIT = 64; public static final float SIMULATION_STEP_TIME = 1 / 20f; public static final int PORT_NUMBER = 6115; diff --git a/core/src/com/etheller/warsmash/viewer5/Model.java b/core/src/com/etheller/warsmash/viewer5/Model.java index 379781d..fbedd8f 100644 --- a/core/src/com/etheller/warsmash/viewer5/Model.java +++ b/core/src/com/etheller/warsmash/viewer5/Model.java @@ -6,7 +6,7 @@ import java.util.List; import com.etheller.warsmash.viewer5.handlers.ModelHandler; public abstract class Model extends HandlerResource { - public Bounds bounds; + public final Bounds bounds; public List preloadedInstances; public Model(final HANDLER handler, final ModelViewer viewer, final String extension, final PathSolver pathSolver, diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java index 67ba901..7336f62 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java @@ -13,6 +13,7 @@ import com.badlogic.gdx.math.Quaternion; import com.badlogic.gdx.math.Vector3; import com.badlogic.gdx.math.collision.Ray; import com.etheller.warsmash.util.WarsmashConstants; +import com.etheller.warsmash.viewer5.Bounds; import com.etheller.warsmash.viewer5.GenericNode; import com.etheller.warsmash.viewer5.ModelInstance; import com.etheller.warsmash.viewer5.Node; @@ -748,8 +749,23 @@ public class MdxComplexInstance extends ModelInstance { throw new UnsupportedOperationException("NOT API"); } + public Bounds getBounds() { + if (this.sequence == -1) { + return this.model.bounds; + } + else { + final Bounds sequenceBounds = ((MdxModel) this.model).sequences.get(this.sequence).getBounds(); + if (sequenceBounds.r == 0) { + return this.model.bounds; + } + else { + return sequenceBounds; + } + } + } + public void intersectRayBounds(final Ray ray, final Vector3 intersection) { - this.model.bounds.intersectRay(ray, intersection); + getBounds().intersectRay(ray, intersection); } /** diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Sequence.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Sequence.java index 1de9dbe..d3b916a 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Sequence.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/Sequence.java @@ -2,6 +2,7 @@ package com.etheller.warsmash.viewer5.handlers.mdx; import java.util.EnumSet; +import com.etheller.warsmash.viewer5.Bounds; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.SecondaryTag; @@ -10,12 +11,16 @@ import com.hiveworkshop.rms.parsers.mdlx.MdlxSequence; public class Sequence { private final MdlxSequence sequence; + private final Bounds bounds; private final EnumSet primaryTags = EnumSet.noneOf(AnimationTokens.PrimaryTag.class); private final EnumSet secondaryTags = EnumSet .noneOf(AnimationTokens.SecondaryTag.class); public Sequence(final MdlxSequence sequence) { this.sequence = sequence; + this.bounds = new Bounds(); + final MdlxExtent sequenceExtent = sequence.getExtent(); + this.bounds.fromExtents(sequenceExtent.getMin(), sequenceExtent.getMax(), sequenceExtent.getBoundsRadius()); populateTags(); } @@ -64,6 +69,10 @@ public class Sequence { return this.sequence.getSyncPoint(); } + public Bounds getBounds() { + return this.bounds; + } + public MdlxExtent getExtent() { return this.sequence.getExtent(); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index e5f2409..4b4c67b 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -42,6 +42,7 @@ import com.etheller.warsmash.parsers.w3x.doo.War3MapDoo; import com.etheller.warsmash.parsers.w3x.objectdata.Warcraft3MapObjectData; import com.etheller.warsmash.parsers.w3x.unitsdoo.War3MapUnitsDoo; import com.etheller.warsmash.parsers.w3x.w3e.War3MapW3e; +import com.etheller.warsmash.parsers.w3x.w3i.Player; import com.etheller.warsmash.parsers.w3x.w3i.War3MapW3i; import com.etheller.warsmash.parsers.w3x.wpm.War3MapWpm; import com.etheller.warsmash.units.DataTable; @@ -103,6 +104,10 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUni import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackListener; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackMissile; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.projectile.CAttackProjectile; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.config.CBasePlayer; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.config.War3MapConfig; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CMapControl; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CRacePreference; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.ResourceType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.SimulationRenderController; import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.CommandErrorListener; @@ -144,6 +149,7 @@ public class War3MapViewer extends AbstractMdxModelViewer { public static final Vector3 intersectionHeap = new Vector3(); private static final Rectangle rectangleHeap = new Rectangle(); public static final StreamDataCallbackImplementation streamDataCallback = new StreamDataCallbackImplementation(); + private static final boolean ENABLE_WORLDEDIT_AS_GAMEPLAY_DATA_HACK = true; public WorldScene worldScene; public boolean anyReady; @@ -218,8 +224,10 @@ public class War3MapViewer extends AbstractMdxModelViewer { public final List textTags = new ArrayList<>(); + private final War3MapConfig mapConfig; + public War3MapViewer(final DataSource dataSource, final CanvasProvider canvas, - final CommandErrorListener errorListener) { + final CommandErrorListener errorListener, final War3MapConfig mapConfig) { super(dataSource, canvas); this.gameDataSource = dataSource; @@ -236,6 +244,7 @@ public class War3MapViewer extends AbstractMdxModelViewer { } this.commandErrorListener = errorListener; + this.mapConfig = mapConfig; } public void loadSLKs(final WorldEditStrings worldEditStrings) throws IOException { @@ -387,6 +396,17 @@ public class War3MapViewer extends AbstractMdxModelViewer { final War3MapW3i w3iFile = this.mapMpq.readMapInformation(); + if (ENABLE_WORLDEDIT_AS_GAMEPLAY_DATA_HACK) { + int playerIndex = 0; + for (final Player player : w3iFile.getPlayers()) { + final CBasePlayer cfgPlayer = this.mapConfig.getPlayer(playerIndex); + cfgPlayer.setName(player.getName()); + cfgPlayer.setRacePref(CRacePreference.VALUES[player.getRace()]); + cfgPlayer.setController(CMapControl.VALUES[player.getType()]); + playerIndex++; + } + } + tileset = w3iFile.getTileset(); DataSource tilesetSource; @@ -455,8 +475,8 @@ public class War3MapViewer extends AbstractMdxModelViewer { this.confirmationInstance.setScene(this.worldScene); this.allObjectData = this.mapMpq.readModifications(); - this.simulation = new CSimulation(this.miscData, this.allObjectData.getUnits(), this.allObjectData.getItems(), - this.allObjectData.getDestructibles(), this.allObjectData.getAbilities(), + this.simulation = new CSimulation(this.mapConfig, this.miscData, this.allObjectData.getUnits(), + this.allObjectData.getItems(), this.allObjectData.getDestructibles(), this.allObjectData.getAbilities(), new SimulationRenderController() { private final Map keyToCombatSound = new HashMap<>(); @@ -745,8 +765,7 @@ public class War3MapViewer extends AbstractMdxModelViewer { renderPeer.getZ()); } } - }, this.terrain.pathingGrid, this.terrain.getEntireMap(), this.seededRandom, w3iFile.getPlayers(), - this.commandErrorListener); + }, this.terrain.pathingGrid, this.terrain.getEntireMap(), this.seededRandom, this.commandErrorListener); this.walkableObjectsTree = new Quadtree<>(this.terrain.getEntireMap()); if (this.doodadsAndDestructiblesLoaded) { @@ -1853,4 +1872,7 @@ public class War3MapViewer extends AbstractMdxModelViewer { this.textTags.add(textTag); } + public War3MapConfig getMapConfig() { + return this.mapConfig; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CGameplayConstants.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CGameplayConstants.java index 2cba007..303fd7d 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CGameplayConstants.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CGameplayConstants.java @@ -348,6 +348,22 @@ public class CGameplayConstants { return this.pawnItemRate; } + public float getFollowRange() { + return this.followRange; + } + + public float getStructureFollowRange() { + return this.structureFollowRange; + } + + public float getFollowItemRange() { + return this.followItemRange; + } + + public float getSpellCastRangeBuffer() { + return this.spellCastRangeBuffer; + } + private static int getTableValue(final int[] table, int level) { if (level <= 0) { return 0; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java index dc82645..24e23c3 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java @@ -5,12 +5,13 @@ import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; +import java.util.LinkedList; import java.util.List; +import java.util.ListIterator; import java.util.Map; import java.util.Random; import com.badlogic.gdx.math.Rectangle; -import com.etheller.warsmash.parsers.w3x.w3i.Player; import com.etheller.warsmash.units.DataTable; import com.etheller.warsmash.units.manager.MutableObjectData; import com.etheller.warsmash.util.War3ID; @@ -24,20 +25,24 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUni import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackListener; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackMissile; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.projectile.CAttackProjectile; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.config.CBasePlayer; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.config.CPlayerAPI; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.config.War3MapConfig; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.config.War3MapConfigStartLoc; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.data.CAbilityData; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.data.CDestructableData; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.data.CItemData; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.data.CUnitData; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.pathing.CPathfindingProcessor; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CAllianceType; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CMapControl; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayer; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CRace; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.timers.CTimer; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.ResourceType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.SimulationRenderController; import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.CommandErrorListener; -public class CSimulation { +public class CSimulation implements CPlayerAPI { private final CAbilityData abilityData; private final CUnitData unitData; private final CDestructableData destructableData; @@ -63,13 +68,14 @@ public class CSimulation { private final Map handleIdToDestructable = new HashMap<>(); private final Map handleIdToItem = new HashMap<>(); private final Map handleIdToAbility = new HashMap<>(); + private final LinkedList activeTimers = new LinkedList<>(); private transient CommandErrorListener commandErrorListener; - public CSimulation(final DataTable miscData, final MutableObjectData parsedUnitData, + public CSimulation(final War3MapConfig config, final DataTable miscData, final MutableObjectData parsedUnitData, final MutableObjectData parsedItemData, final MutableObjectData parsedDestructableData, final MutableObjectData parsedAbilityData, final SimulationRenderController simulationRenderController, final PathingGrid pathingGrid, final Rectangle entireMapBounds, final Random seededRandom, - final List playerInfos, final CommandErrorListener commandErrorListener) { + final CommandErrorListener commandErrorListener) { this.gameplayConstants = new CGameplayConstants(miscData); this.simulationRenderController = simulationRenderController; this.pathingGrid = pathingGrid; @@ -94,27 +100,19 @@ public class CSimulation { } this.seededRandom = seededRandom; this.players = new ArrayList<>(); - for (int i = 0; i < (WarsmashConstants.MAX_PLAYERS - 4); i++) { - CPlayer newPlayer; - if (i < playerInfos.size()) { - final Player playerInfo = playerInfos.get(i); - newPlayer = new CPlayer(playerInfo.getId(), CMapControl.values()[playerInfo.getType()], - playerInfo.getName(), CRace.parseRace(playerInfo.getRace()), playerInfo.getStartLocation()); - } - else { - newPlayer = new CPlayer(i, CMapControl.NONE, "Default string", CRace.OTHER, new float[] { 0, 0 }); - } + for (int i = 0; i < WarsmashConstants.MAX_PLAYERS; i++) { + final CBasePlayer configPlayer = config.getPlayer(i); + final War3MapConfigStartLoc startLoc = config.getStartLoc(configPlayer.getStartLocationIndex()); + final CPlayer newPlayer = new CPlayer(CRace.OTHER, new float[] { startLoc.getX(), startLoc.getY() }, + configPlayer); this.players.add(newPlayer); } - this.players.add(new CPlayer(this.players.size(), CMapControl.NEUTRAL, - miscData.getLocalizedString("WESTRING_PLAYER_NA"), CRace.OTHER, new float[] { 0, 0 })); - this.players.add(new CPlayer(this.players.size(), CMapControl.NEUTRAL, - miscData.getLocalizedString("WESTRING_PLAYER_NV"), CRace.OTHER, new float[] { 0, 0 })); - this.players.add(new CPlayer(this.players.size(), CMapControl.NEUTRAL, - miscData.getLocalizedString("WESTRING_PLAYER_NE"), CRace.OTHER, new float[] { 0, 0 })); - final CPlayer neutralPassive = new CPlayer(this.players.size(), CMapControl.NEUTRAL, - miscData.getLocalizedString("WESTRING_PLAYER_NP"), CRace.OTHER, new float[] { 0, 0 }); - this.players.add(neutralPassive); + this.players.get(this.players.size() - 4).setName(miscData.getLocalizedString("WESTRING_PLAYER_NA")); + this.players.get(this.players.size() - 3).setName(miscData.getLocalizedString("WESTRING_PLAYER_NV")); + this.players.get(this.players.size() - 2).setName(miscData.getLocalizedString("WESTRING_PLAYER_NE")); + final CPlayer neutralPassive = this.players.get(this.players.size() - 1); + neutralPassive.setName(miscData.getLocalizedString("WESTRING_PLAYER_NP")); + for (int i = 0; i < WarsmashConstants.MAX_PLAYERS; i++) { final CPlayer cPlayer = this.players.get(i); cPlayer.setAlliance(neutralPassive, CAllianceType.PASSIVE, true); @@ -133,6 +131,14 @@ public class CSimulation { return this.abilityData; } + public CDestructableData getDestructableData() { + return this.destructableData; + } + + public CItemData getItemData() { + return this.itemData; + } + public List getUnits() { return this.units; } @@ -141,6 +147,22 @@ public class CSimulation { return this.destructables; } + public void registerTimer(final CTimer timer) { + final ListIterator listIterator = this.activeTimers.listIterator(); + while (listIterator.hasNext()) { + final CTimer nextTimer = listIterator.next(); + if (nextTimer.getEngineFireTick() > timer.getEngineFireTick()) { + listIterator.previous(); + listIterator.add(timer); + } + } + this.activeTimers.add(timer); + } + + public void unregisterTimer(final CTimer time) { + this.activeTimers.remove(time); + } + public CUnit createUnit(final War3ID typeId, final int playerIndex, final float x, final float y, final float facing, final BufferedImage buildingPathingPixelMap, final RemovablePathingMapInstance pathingInstance) { @@ -247,6 +269,10 @@ public class CSimulation { this.gameTurnTick++; this.currentGameDayTimeElapsed = (this.currentGameDayTimeElapsed + WarsmashConstants.SIMULATION_STEP_TIME) % this.gameplayConstants.getGameDayLength(); + while (!this.activeTimers.isEmpty() && (this.activeTimers.peek().getEngineFireTick() <= this.gameTurnTick)) { + this.activeTimers.pop().fire(this); + } + } private void finishAddingNewUnits() { @@ -292,6 +318,7 @@ public class CSimulation { this.simulationRenderController.spawnUnitConstructionSound(constructingUnit, constructedStructure); } + @Override public CPlayer getPlayer(final int index) { return this.players.get(index); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java index c64acc5..442321b 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java @@ -721,7 +721,7 @@ public class CUnit extends CWidget { } } if (!foundMatchingReturnFireAttack && this.unitType.isCanFlee() && !isMovementDisabled() - && (this.moveBehavior != null)) { + && (this.moveBehavior != null) && (this.playerIndex != source.getPlayerIndex())) { final double angleTo = source.angleTo(this); final int distanceToFlee = getSpeed(); this.currentBehavior = this.moveBehavior.reset(OrderIds.move, diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java index 9146721..50b27f9 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java @@ -20,6 +20,8 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.pathing.CBuildingPa */ public class CUnitType { private final String name; + private final String legacyName; + private final War3ID typeId; private final int life; private final int manaInitial; private final int manaMaximum; @@ -73,15 +75,16 @@ public class CUnitType { private final int properNamesCount; private final boolean canFlee; - public CUnitType(final String name, final int life, final int manaInitial, final int manaMaximum, final int speed, - final int defense, final String abilityList, final boolean isBldg, final MovementType movementType, - final float defaultFlyingHeight, final float collisionSize, - final EnumSet classifications, final List attacks, final String armorType, - final boolean raise, final boolean decay, final CDefenseType defenseType, final float impactZ, - final BufferedImage buildingPathingPixelMap, final float deathTime, final EnumSet targetedAs, - final float defaultAcquisitionRange, final float minimumAttackRange, final List structuresBuilt, - final List unitsTrained, final List researchesAvailable, final CUnitRace unitRace, - final int goldCost, final int lumberCost, final int foodUsed, final int foodMade, final int buildTime, + public CUnitType(final String name, final String legacyName, final War3ID typeId, final int life, + final int manaInitial, final int manaMaximum, final int speed, final int defense, final String abilityList, + final boolean isBldg, final MovementType movementType, final float defaultFlyingHeight, + final float collisionSize, final EnumSet classifications, + final List attacks, final String armorType, final boolean raise, final boolean decay, + final CDefenseType defenseType, final float impactZ, final BufferedImage buildingPathingPixelMap, + final float deathTime, final EnumSet targetedAs, final float defaultAcquisitionRange, + final float minimumAttackRange, final List structuresBuilt, final List unitsTrained, + final List researchesAvailable, final CUnitRace unitRace, final int goldCost, final int lumberCost, + final int foodUsed, final int foodMade, final int buildTime, final EnumSet preventedPathingTypes, final EnumSet requiredPathingTypes, final float propWindow, final float turnRate, final List requirements, final int level, final boolean hero, final int strength, @@ -90,6 +93,8 @@ public class CUnitType { final List heroAbilityList, final List heroProperNames, final int properNamesCount, final boolean canFlee) { this.name = name; + this.legacyName = legacyName; + this.typeId = typeId; this.life = life; this.manaInitial = manaInitial; this.manaMaximum = manaMaximum; @@ -145,6 +150,14 @@ public class CUnitType { return this.name; } + public String getLegacyName() { + return this.legacyName; + } + + public War3ID getTypeId() { + return this.typeId; + } + public int getLife() { return this.life; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/ai/AIDifficulty.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/ai/AIDifficulty.java new file mode 100644 index 0000000..681f710 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/ai/AIDifficulty.java @@ -0,0 +1,9 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.ai; + +public enum AIDifficulty { + NEWBIE, + NORMAL, + INSANE; + + public static AIDifficulty[] VALUES = values(); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/config/CBasePlayer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/config/CBasePlayer.java new file mode 100644 index 0000000..931d19a --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/config/CBasePlayer.java @@ -0,0 +1,185 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.config; + +import java.util.EnumMap; +import java.util.EnumSet; + +import com.etheller.warsmash.util.WarsmashConstants; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CAllianceType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CMapControl; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayerJass; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayerState; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CRacePreference; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.enumtypes.CPlayerSlotState; + +public class CBasePlayer implements CPlayerJass { + private final int id; + private String name; + private int team; + private int startLocationIndex; + private int forcedStartLocationIndex = -1; + private int color; + private final EnumSet racePrefs; + private final EnumSet[] alliances; + private final EnumMap[] taxRates; + private boolean onScoreScreen; + private boolean raceSelectable; + private CMapControl mapControl = CMapControl.NEUTRAL; + private CPlayerSlotState slotState = CPlayerSlotState.EMPTY; + + public CBasePlayer(final CBasePlayer other) { + this.id = other.id; + this.name = other.name; + this.team = other.team; + this.startLocationIndex = other.startLocationIndex; + this.forcedStartLocationIndex = other.forcedStartLocationIndex; + this.color = other.color; + this.racePrefs = other.racePrefs; + this.alliances = other.alliances; + this.taxRates = other.taxRates; + this.onScoreScreen = other.onScoreScreen; + this.raceSelectable = other.raceSelectable; + this.mapControl = other.mapControl; + this.slotState = other.slotState; + } + + public CBasePlayer(final int id) { + this.id = id; + this.alliances = new EnumSet[WarsmashConstants.MAX_PLAYERS]; + this.taxRates = new EnumMap[WarsmashConstants.MAX_PLAYERS]; + this.racePrefs = EnumSet.noneOf(CRacePreference.class); + for (int i = 0; i < this.alliances.length; i++) { + if (i == id) { + // player is fully allied with self + this.alliances[i] = EnumSet.allOf(CAllianceType.class); + } + else { + this.alliances[i] = EnumSet.noneOf(CAllianceType.class); + } + this.taxRates[i] = new EnumMap<>(CPlayerState.class); + } + } + + public int getId() { + return this.id; + } + + @Override + public void setName(final String name) { + this.name = name; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public void setOnScoreScreen(final boolean onScoreScreen) { + this.onScoreScreen = onScoreScreen; + } + + public boolean isOnScoreScreen() { + return this.onScoreScreen; + } + + @Override + public void setRaceSelectable(final boolean raceSelectable) { + this.raceSelectable = raceSelectable; + } + + @Override + public void setTeam(final int team) { + this.team = team; + } + + @Override + public int getTeam() { + return this.team; + } + + @Override + public int getStartLocationIndex() { + return this.startLocationIndex; + } + + @Override + public void setStartLocationIndex(final int startLocationIndex) { + this.startLocationIndex = startLocationIndex; + } + + @Override + public void setColor(final int color) { + this.color = color; + } + + @Override + public int getColor() { + return this.color; + } + + @Override + public boolean isRacePrefSet(final CRacePreference racePref) { + return this.racePrefs.contains(racePref); + } + + @Override + public void setRacePref(final CRacePreference racePref) { + this.racePrefs.add(racePref); + } + + @Override + public void setAlliance(final int otherPlayerIndex, final CAllianceType allianceType, final boolean value) { + final EnumSet alliancesWithOtherPlayer = this.alliances[otherPlayerIndex]; + if (value) { + alliancesWithOtherPlayer.add(allianceType); + } + else { + alliancesWithOtherPlayer.remove(allianceType); + } + } + + public boolean hasAlliance(final int otherPlayerIndex, final CAllianceType allianceType) { + final EnumSet alliancesWithOtherPlayer = this.alliances[otherPlayerIndex]; + return alliancesWithOtherPlayer.contains(allianceType); + } + + @Override + public void forceStartLocation(final int startLocIndex) { + this.forcedStartLocationIndex = startLocIndex; + } + + @Override + public void setTaxRate(final int otherPlayerIndex, final CPlayerState whichResource, final int rate) { + this.taxRates[otherPlayerIndex].put(whichResource, rate); + } + + @Override + public void setController(final CMapControl mapControl) { + this.mapControl = mapControl; + + } + + @Override + public boolean isSelectable() { + return this.raceSelectable; + } + + @Override + public CMapControl getController() { + return this.mapControl; + } + + @Override + public CPlayerSlotState getSlotState() { + return this.slotState; + } + + @Override + public int getTaxRate(final int otherPlayerIndex, final CPlayerState whichResource) { + final Integer taxRate = this.taxRates[otherPlayerIndex].get(whichResource); + if (taxRate == null) { + return 0; + } + return taxRate; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/config/CPlayerAPI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/config/CPlayerAPI.java new file mode 100644 index 0000000..61baa4f --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/config/CPlayerAPI.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.config; + +public interface CPlayerAPI { + CBasePlayer getPlayer(int index); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/config/War3MapConfig.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/config/War3MapConfig.java new file mode 100644 index 0000000..dec0f41 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/config/War3MapConfig.java @@ -0,0 +1,153 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.config; + +import java.util.EnumMap; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CMapFlag; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CMapPlacement; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.enumtypes.CGameSpeed; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.enumtypes.CGameType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.enumtypes.CMapDensity; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.enumtypes.CMapDifficulty; + +public class War3MapConfig implements CPlayerAPI { + private String mapName; + private String mapDescription; + private int teamCount; + private int playerCount; + private final War3MapConfigStartLoc[] startLocations; + private final CBasePlayer[] players; + private final EnumMap gameTypeToSupported = new EnumMap<>(CGameType.class); + private final EnumMap mapFlagToEnabled = new EnumMap<>(CMapFlag.class); + private CMapPlacement placement; + private CGameSpeed gameSpeed; + private CMapDifficulty gameDifficulty; + private CMapDensity resourceDensity; + private CMapDensity creatureDensity; + private CGameType gameTypeSelected; + + public War3MapConfig(final int maxPlayers) { + this.startLocations = new War3MapConfigStartLoc[maxPlayers]; + this.players = new CBasePlayer[maxPlayers]; + for (int i = 0; i < maxPlayers; i++) { + this.startLocations[i] = new War3MapConfigStartLoc(); + this.players[i] = new CBasePlayer(i); + } + } + + public void setMapName(final String mapName) { + this.mapName = mapName; + } + + public void setMapDescription(final String mapDescription) { + this.mapDescription = mapDescription; + } + + public String getMapName() { + return this.mapName; + } + + public String getMapDescription() { + return this.mapDescription; + } + + public void setTeamCount(final int teamCount) { + this.teamCount = teamCount; + } + + public void setPlayerCount(final int playerCount) { + this.playerCount = playerCount; + } + + public void defineStartLocation(final int whichStartLoc, final float x, final float y) { + final War3MapConfigStartLoc startLoc = this.startLocations[whichStartLoc]; + startLoc.setX(x); + startLoc.setY(y); + } + + public War3MapConfigStartLoc getStartLoc(final int whichStartLoc) { + return this.startLocations[whichStartLoc]; + } + + public void setGameTypeSupported(final CGameType gameType, final boolean supported) { + this.gameTypeToSupported.put(gameType, supported); + } + + public void setMapFlag(final CMapFlag mapFlag, final boolean set) { + this.mapFlagToEnabled.put(mapFlag, set); + } + + public void setPlacement(final CMapPlacement placement) { + this.placement = placement; + } + + public void setGameSpeed(final CGameSpeed gameSpeed) { + this.gameSpeed = gameSpeed; + } + + public void setGameDifficulty(final CMapDifficulty gameDifficulty) { + this.gameDifficulty = gameDifficulty; + } + + public void setResourceDensity(final CMapDensity resourceDensity) { + this.resourceDensity = resourceDensity; + } + + public void setCreatureDensity(final CMapDensity creatureDensity) { + this.creatureDensity = creatureDensity; + } + + public int getTeamCount() { + return this.teamCount; + } + + public int getPlayerCount() { + return this.playerCount; + } + + public boolean isGameTypeSupported(final CGameType gameType) { + final Boolean supported = this.gameTypeToSupported.get(gameType); + return (supported != null) && supported; + } + + public CGameType getGameTypeSelected() { + return this.gameTypeSelected; + } + + public boolean isMapFlagSet(final CMapFlag mapFlag) { + final Boolean flag = this.mapFlagToEnabled.get(mapFlag); + return (flag != null) && flag; + } + + public CMapPlacement getPlacement() { + return this.placement; + } + + public CGameSpeed getGameSpeed() { + return this.gameSpeed; + } + + public CMapDifficulty getGameDifficulty() { + return this.gameDifficulty; + } + + public CMapDensity getResourceDensity() { + return this.resourceDensity; + } + + public CMapDensity getCreatureDensity() { + return this.creatureDensity; + } + + public float getStartLocationX(final int startLocIndex) { + return this.startLocations[startLocIndex].getX(); + } + + public float getStartLocationY(final int startLocIndex) { + return this.startLocations[startLocIndex].getY(); + } + + @Override + public CBasePlayer getPlayer(final int index) { + return this.players[index]; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/config/War3MapConfigStartLoc.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/config/War3MapConfigStartLoc.java new file mode 100644 index 0000000..281eb76 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/config/War3MapConfigStartLoc.java @@ -0,0 +1,44 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.config; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CStartLocPrio; + +public class War3MapConfigStartLoc { + private float x; + private float y; + private int[] otherStartIndices; + private CStartLocPrio[] otherStartLocPriorities; + + public void setX(final float x) { + this.x = x; + } + + public void setY(final float y) { + this.y = y; + } + + public float getX() { + return this.x; + } + + public float getY() { + return this.y; + } + + public int[] getOtherStartIndices() { + return this.otherStartIndices; + } + + public CStartLocPrio[] getOtherStartLocPriorities() { + return this.otherStartLocPriorities; + } + + public void setStartLocPrioCount(final int startLocPrioCount) { + this.otherStartIndices = new int[startLocPrioCount]; + this.otherStartLocPriorities = new CStartLocPrio[startLocPrioCount]; + } + + public void setStartLocPrio(final int prioSlotIndex, final int otherStartLocIndex, final CStartLocPrio priority) { + this.otherStartIndices[prioSlotIndex] = otherStartLocIndex; + this.otherStartLocPriorities[prioSlotIndex] = priority; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CItemData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CItemData.java index bfc8305..24ea711 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CItemData.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CItemData.java @@ -58,6 +58,14 @@ public class CItemData { return new CItem(handleId, x, y, itemTypeInstance.getHitPoints(), typeId, itemTypeInstance); } + public CItemType getItemType(final War3ID typeId) { + final MutableGameObject itemType = this.itemData.get(typeId); + if (itemType == null) { + return null; + } + return getItemTypeInstance(typeId, itemType); + } + private CItemType getItemTypeInstance(final War3ID typeId, final MutableGameObject itemType) { CItemType itemTypeInstance = this.itemIdToItemType.get(typeId); if (itemTypeInstance == null) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java index 283b160..7df7d6a 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java @@ -173,6 +173,7 @@ public class CUnitData { private final CGameplayConstants gameplayConstants; private final MutableObjectData unitData; private final Map unitIdToUnitType = new HashMap<>(); + private final Map jassLegacyNameToUnitId = new HashMap<>(); private final CAbilityData abilityData; private final SimulationRenderController simulationRenderController; @@ -266,6 +267,7 @@ public class CUnitData { final MutableGameObject unitType) { CUnitType unitTypeInstance = this.unitIdToUnitType.get(typeId); if (unitTypeInstance == null) { + final String legacyName = getLegacyName(unitType); final int life = unitType.getFieldAsInteger(HIT_POINT_MAXIMUM, 0); final int manaInitial = unitType.getFieldAsInteger(MANA_INITIAL_AMOUNT, 0); final int manaMaximum = unitType.getFieldAsInteger(MANA_MAXIMUM, 0); @@ -531,18 +533,33 @@ public class CUnitData { final List heroProperNames = Arrays.asList(properNames.split(",")); - unitTypeInstance = new CUnitType(unitName, life, manaInitial, manaMaximum, speed, defense, abilityList, - isBldg, movementType, moveHeight, collisionSize, classifications, attacks, armorType, raise, decay, - defenseType, impactZ, buildingPathingPixelMap, deathTime, targetedAs, acquisitionRange, - minimumAttackRange, structuresBuilt, unitsTrained, researchesAvailable, unitRace, goldCost, - lumberCost, foodUsed, foodMade, buildTime, preventedPathingTypes, requiredPathingTypes, propWindow, - turnRate, requirements, unitLevel, hero, strength, strPlus, agility, agiPlus, intelligence, intPlus, - primaryAttribute, heroAbilityList, heroProperNames, properNamesCount, canFlee); + unitTypeInstance = new CUnitType(unitName, legacyName, typeId, life, manaInitial, manaMaximum, speed, + defense, abilityList, isBldg, movementType, moveHeight, collisionSize, classifications, attacks, + armorType, raise, decay, defenseType, impactZ, buildingPathingPixelMap, deathTime, targetedAs, + acquisitionRange, minimumAttackRange, structuresBuilt, unitsTrained, researchesAvailable, unitRace, + goldCost, lumberCost, foodUsed, foodMade, buildTime, preventedPathingTypes, requiredPathingTypes, + propWindow, turnRate, requirements, unitLevel, hero, strength, strPlus, agility, agiPlus, + intelligence, intPlus, primaryAttribute, heroAbilityList, heroProperNames, properNamesCount, + canFlee); this.unitIdToUnitType.put(typeId, unitTypeInstance); + this.jassLegacyNameToUnitId.put(legacyName, typeId); } return unitTypeInstance; } + private String getLegacyName(final MutableGameObject unitType) { + String legacyName; + if (unitType.isCustom()) { + legacyName = "custom_" + unitType.getAlias(); + } + else { + // ?? this might be correct here, not sure, legacy name is mostly only used + // for spawning hidden units in campaign secrets + legacyName = unitType.readSLKTag("name"); + } + return legacyName; + } + private static int[] populateHeroStatTable(final int maxHeroLevel, final float statPerLevel) { final int[] table = new int[maxHeroLevel]; float sumBonusAtLevel = 0f; @@ -713,4 +730,22 @@ public class CUnitData { .getBuildingPathingPixelMap(rawcode); return getUnitTypeInstance(rawcode, buildingPathingPixelMap, unitType); } + + public CUnitType getUnitTypeByJassLegacyName(final String jassLegacyName) { + final War3ID typeId = this.jassLegacyNameToUnitId.get(jassLegacyName); + if (typeId == null) { + // VERY inefficient, but this is a crazy system anyway, they should not be using + // this! + System.err.println( + "We are doing a highly inefficient lookup for a non-cached unit type based on its legacy string ID that I am pretty sure is not used by modding community: " + + jassLegacyName); + for (final War3ID key : this.unitData.keySet()) { + final MutableGameObject mutableGameObject = this.unitData.get(key); + if (jassLegacyName.equals(getLegacyName(mutableGameObject))) { + return getUnitType(mutableGameObject.getAlias()); + } + } + } + return getUnitType(typeId); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/item/CItemTypeJass.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/item/CItemTypeJass.java new file mode 100644 index 0000000..bde3174 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/item/CItemTypeJass.java @@ -0,0 +1,15 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.item; + +public enum CItemTypeJass { + PERMANENT, + CHARGED, + POWERUP, + ARTIFACT, + PURCHASABLE, + CAMPAIGN, + MISCELLANEOUS, + UNKNOWN, + ANY; + + public static CItemTypeJass[] VALUES = values(); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/OrderIdUtils.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/OrderIdUtils.java new file mode 100644 index 0000000..90375e3 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/OrderIdUtils.java @@ -0,0 +1,38 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; + +public class OrderIdUtils { + private static Map orderIdToString = new HashMap<>(); + private static Map stringToOrderId = new HashMap<>(); + + static { + for (final Field field : OrderIds.class.getDeclaredFields()) { + final String name = field.getName(); + try { + final Object value = field.get(null); + if (value instanceof Integer) { + final Integer orderId = (Integer) value; + orderIdToString.put(orderId, name); + stringToOrderId.put(name, orderId); + } + } + catch (final IllegalArgumentException e) { + e.printStackTrace(); + } + catch (final IllegalAccessException e) { + e.printStackTrace(); + } + } + } + + public static int getOrderId(final String orderIdString) { + return stringToOrderId.get(orderIdString); + } + + public static String getStringFromOrderId(final Integer orderId) { + return orderIdToString.get(orderId); + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CAllianceType.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CAllianceType.java index 8587028..ea1f28a 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CAllianceType.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CAllianceType.java @@ -11,4 +11,6 @@ public enum CAllianceType { SHARED_ADVANCED_CONTROL, RESCUABLE, SHARED_VISION_FORCED; + + public static CAllianceType[] VALUES = values(); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CMapControl.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CMapControl.java index 0d7f4a1..423e458 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CMapControl.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CMapControl.java @@ -7,4 +7,6 @@ public enum CMapControl { NEUTRAL, CREEP, NONE; + + public static CMapControl[] VALUES = values(); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CMapFlag.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CMapFlag.java new file mode 100644 index 0000000..f001bc8 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CMapFlag.java @@ -0,0 +1,44 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.players; + +public enum CMapFlag { + MAP_FOG_HIDE_TERRAIN, + MAP_FOG_MAP_EXPLORED, + MAP_FOG_ALWAYS_VISIBLE, + + MAP_USE_HANDICAPS, + MAP_OBSERVERS, + MAP_OBSERVERS_ON_DEATH, + + MAP_FIXED_COLORS, + + MAP_LOCK_RESOURCE_TRADING, + MAP_RESOURCE_TRADING_ALLIES_ONLY, + + MAP_LOCK_ALLIANCE_CHANGES, + MAP_ALLIANCE_CHANGES_HIDDEN, + + MAP_CHEATS, + MAP_CHEATS_HIDDEN, + + MAP_LOCK_SPEED, + MAP_LOCK_RANDOM_SEED, + MAP_SHARED_ADVANCED_CONTROL, + MAP_RANDOM_HERO, + MAP_RANDOM_RACES, + MAP_RELOADED; + + public static CMapFlag[] VALUES = values(); + + public static CMapFlag getById(final int id) { + for (final CMapFlag type : VALUES) { + if ((type.getId()) == id) { + return type; + } + } + return null; + } + + public int getId() { + return 1 << ordinal(); + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CMapPlacement.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CMapPlacement.java new file mode 100644 index 0000000..29eb00e --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CMapPlacement.java @@ -0,0 +1,10 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.players; + +public enum CMapPlacement { + RANDOM, + FIXED, + USE_MAP_SETTINGS, + TEAMS_TOGETHER; + + public static CMapPlacement[] VALUES = values(); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayer.java index e00070f..956a396 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayer.java @@ -1,29 +1,22 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.players; -import java.util.EnumSet; import java.util.HashMap; import java.util.Map; import com.etheller.warsmash.util.War3ID; -import com.etheller.warsmash.util.WarsmashConstants; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CPlayerStateListener; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CPlayerStateListener.CPlayerStateNotifier; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.config.CBasePlayer; -public class CPlayer { - private final int id; - private int colorIndex; - private final CMapControl controlType; - private String name; +public class CPlayer extends CBasePlayer { private final CRace race; private final float[] startLocation; - private final EnumSet racePrefs; private int gold = 500; private int lumber = 150; private int foodCap; private int foodUsed; - private final EnumSet[] alliances = new EnumSet[WarsmashConstants.MAX_PLAYERS]; private final Map rawcodeToTechtreeUnlocked = new HashMap<>(); // if you use triggers for this then the transient tag here becomes really @@ -31,73 +24,20 @@ public class CPlayer { // which fields shouldn't be persisted if we do game state save later private transient CPlayerStateNotifier stateNotifier = new CPlayerStateNotifier(); - public CPlayer(final int id, final CMapControl controlType, final String name, final CRace race, - final float[] startLocation) { - this.id = id; - this.colorIndex = id; - this.controlType = controlType; - this.name = name; + public CPlayer(final CRace race, final float[] startLocation, final CBasePlayer configPlayer) { + super(configPlayer); this.race = race; this.startLocation = startLocation; - this.racePrefs = EnumSet.noneOf(CRacePreference.class); - for (int i = 0; i < this.alliances.length; i++) { - if (i == this.id) { - // player is fully allied with self - this.alliances[i] = EnumSet.allOf(CAllianceType.class); - } - else { - this.alliances[i] = EnumSet.noneOf(CAllianceType.class); - } - } } - public int getId() { - return this.id; - } - - public int getColorIndex() { - return this.colorIndex; - } - - public CMapControl getControlType() { - return this.controlType; - } - - public String getName() { - return this.name; - } - - public void setName(final String name) { - this.name = name; + public void setAlliance(final CPlayer other, final CAllianceType alliance, final boolean flag) { + setAlliance(other.getId(), alliance, flag); } public CRace getRace() { return this.race; } - public boolean isRacePrefSet(final CRacePreference racePref) { - return this.racePrefs.contains(racePref); - } - - public void setAlliance(final CPlayer otherPlayer, final CAllianceType allianceType, final boolean value) { - final EnumSet alliancesWithOtherPlayer = this.alliances[otherPlayer.getId()]; - if (value) { - alliancesWithOtherPlayer.add(allianceType); - } - else { - alliancesWithOtherPlayer.remove(allianceType); - } - } - - public boolean hasAlliance(final CPlayer otherPlayer, final CAllianceType allianceType) { - return hasAlliance(otherPlayer.getId(), allianceType); - } - - public boolean hasAlliance(final int otherPlayerIndex, final CAllianceType allianceType) { - final EnumSet alliancesWithOtherPlayer = this.alliances[otherPlayerIndex]; - return alliancesWithOtherPlayer.contains(allianceType); - } - public int getGold() { return this.gold; } @@ -138,10 +78,6 @@ public class CPlayer { this.stateNotifier.foodChanged(); } - public void setColorIndex(final int colorIndex) { - this.colorIndex = colorIndex; - } - public int getTechtreeUnlocked(final War3ID rawcode) { final Integer techtreeUnlocked = this.rawcodeToTechtreeUnlocked.get(rawcode); if (techtreeUnlocked == null) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerColor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerColor.java new file mode 100644 index 0000000..20cfcf6 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerColor.java @@ -0,0 +1,25 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.players; + +public enum CPlayerColor { + RED, + BLUE, + CYAN, + PURPLE, + YELLOW, + ORANGE, + GREEN, + PINK, + LIGHT_GRAY, + LIGHT_BLUE, + AQUA, + BROWN; + + public static CPlayerColor[] VALUES = values(); + + public static CPlayerColor getColorByIndex(final int index) { + if ((index >= 0) && (index < VALUES.length)) { + return VALUES[index]; + } + return null; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerGameResult.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerGameResult.java new file mode 100644 index 0000000..e713d5c --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerGameResult.java @@ -0,0 +1,10 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.players; + +public enum CPlayerGameResult { + VICTORY, + DEFEAT, + TIE, + NEUTRAL; + + public static CPlayerGameResult[] VALUES = values(); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerJass.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerJass.java new file mode 100644 index 0000000..57cc14d --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerJass.java @@ -0,0 +1,47 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.players; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.enumtypes.CPlayerSlotState; + +public interface CPlayerJass { + int getId(); + + void setTeam(int team); + + void setStartLocationIndex(int startLocIndex); + + void forceStartLocation(int startLocIndex); + + void setColor(int colorIndex); + + void setAlliance(int otherPlayerIndex, CAllianceType whichAllianceSetting, boolean value); + + void setTaxRate(int otherPlayerIndex, CPlayerState whichResource, int rate); + + void setRacePref(CRacePreference whichRacePreference); + + void setRaceSelectable(boolean selectable); + + void setController(CMapControl mapControl); + + void setName(String name); + + void setOnScoreScreen(boolean flag); + + int getTeam(); + + int getStartLocationIndex(); + + int getColor(); + + boolean isSelectable(); + + CMapControl getController(); + + CPlayerSlotState getSlotState(); + + int getTaxRate(int otherPlayerIndex, CPlayerState whichResource); + + boolean isRacePrefSet(CRacePreference pref); + + String getName(); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerScore.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerScore.java new file mode 100644 index 0000000..f0950d8 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerScore.java @@ -0,0 +1,31 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.players; + +public enum CPlayerScore { + UNITS_TRAINED, + UNITS_KILLED, + STRUCT_BUILT, + STRUCT_RAZED, + TECH_PERCENT, + FOOD_MAXPROD, + FOOD_MAXUSED, + HEROES_KILLED, + ITEMS_GAINED, + MERCS_HIRED, + GOLD_MINED_TOTAL, + GOLD_MINED_UPKEEP, + GOLD_LOST_UPKEEP, + GOLD_LOST_TAX, + GOLD_GIVEN, + GOLD_RECEIVED, + LUMBER_TOTAL, + LUMBER_LOST_UPKEEP, + LUMBER_LOST_TAX, + LUMBER_GIVEN, + LUMBER_RECEIVED, + UNIT_TOTAL, + HERO_TOTAL, + RESOURCE_TOTAL, + TOTAL; + + public static CPlayerScore[] VALUES = values(); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerState.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerState.java new file mode 100644 index 0000000..9965b4f --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerState.java @@ -0,0 +1,42 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.players; + +public enum CPlayerState { + // current resource levels + // + RESOURCE_GOLD, + RESOURCE_LUMBER, + RESOURCE_HERO_TOKENS, + RESOURCE_FOOD_CAP, + RESOURCE_FOOD_USED, + FOOD_CAP_CEILING, + + GIVES_BOUNTY, + ALLIED_VICTORY, + PLACED, + OBSERVER_ON_DEATH, + OBSERVER, + UNFOLLOWABLE, + + // taxation rate for each resource + // + GOLD_UPKEEP_RATE, + LUMBER_UPKEEP_RATE, + + // cumulative resources collected by the player during the mission + // + GOLD_GATHERED, + LUMBER_GATHERED, + + UNKNOWN_STATE_17, + UNKNOWN_STATE_18, + UNKNOWN_STATE_19, + UNKNOWN_STATE_20, + UNKNOWN_STATE_21, + UNKNOWN_STATE_22, + UNKNOWN_STATE_23, + UNKNOWN_STATE_24, + + NO_CREEP_SLEEP; + + public static CPlayerState[] VALUES = values(); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderExecutor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderExecutor.java index 96c2458..5620203 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderExecutor.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderExecutor.java @@ -13,10 +13,13 @@ import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.CommandErrorListene public class CPlayerUnitOrderExecutor implements CPlayerUnitOrderListener { private final CSimulation game; + private final int playerIndex; private final CommandErrorListener errorListener; - public CPlayerUnitOrderExecutor(final CSimulation game, final CommandErrorListener errorListener) { + public CPlayerUnitOrderExecutor(final CSimulation game, final int playerIndex, + final CommandErrorListener errorListener) { this.game = game; + this.playerIndex = playerIndex; this.errorListener = errorListener; } @@ -24,50 +27,60 @@ public class CPlayerUnitOrderExecutor implements CPlayerUnitOrderListener { public void issueTargetOrder(final int unitHandleId, final int abilityHandleId, final int orderId, final int targetHandleId, final boolean queue) { final CUnit unit = this.game.getUnit(unitHandleId); - unit.order(this.game, new COrderTargetWidget(abilityHandleId, orderId, targetHandleId, queue), queue); + if (this.playerIndex == unit.getPlayerIndex()) { + unit.order(this.game, new COrderTargetWidget(abilityHandleId, orderId, targetHandleId, queue), queue); + } } @Override public void issueDropItemAtPointOrder(final int unitHandleId, final int abilityHandleId, final int orderId, final int targetHandleId, final float x, final float y, final boolean queue) { final CUnit unit = this.game.getUnit(unitHandleId); - unit.order(this.game, new COrderDropItemAtPoint(abilityHandleId, orderId, targetHandleId, - new AbilityPointTarget(x, y), queue), queue); + if (this.playerIndex == unit.getPlayerIndex()) { + unit.order(this.game, new COrderDropItemAtPoint(abilityHandleId, orderId, targetHandleId, + new AbilityPointTarget(x, y), queue), queue); + } } @Override public void issuePointOrder(final int unitHandleId, final int abilityHandleId, final int orderId, final float x, final float y, final boolean queue) { final CUnit unit = this.game.getUnit(unitHandleId); - unit.order(this.game, new COrderTargetPoint(abilityHandleId, orderId, new AbilityPointTarget(x, y), queue), - queue); + if (this.playerIndex == unit.getPlayerIndex()) { + unit.order(this.game, new COrderTargetPoint(abilityHandleId, orderId, new AbilityPointTarget(x, y), queue), + queue); + } } @Override public void issueImmediateOrder(final int unitHandleId, final int abilityHandleId, final int orderId, final boolean queue) { final CUnit unit = this.game.getUnit(unitHandleId); - if (abilityHandleId == 0) { - if (orderId == OrderIds.stop) { - unit.order(this.game, null, queue); - } - else if (orderId == OrderIds.holdposition) { - unit.order(this.game, null, queue); - final CBehaviorHoldPosition holdPositionBehavior = unit.getHoldPositionBehavior(); - if (holdPositionBehavior != null) { - unit.setDefaultBehavior(holdPositionBehavior); + if (this.playerIndex == unit.getPlayerIndex()) { + if (abilityHandleId == 0) { + if (orderId == OrderIds.stop) { + unit.order(this.game, null, queue); + } + else if (orderId == OrderIds.holdposition) { + unit.order(this.game, null, queue); + final CBehaviorHoldPosition holdPositionBehavior = unit.getHoldPositionBehavior(); + if (holdPositionBehavior != null) { + unit.setDefaultBehavior(holdPositionBehavior); + } } } - } - else { - unit.order(this.game, new COrderNoTarget(abilityHandleId, orderId, queue), queue); + else { + unit.order(this.game, new COrderNoTarget(abilityHandleId, orderId, queue), queue); + } } } @Override public void unitCancelTrainingItem(final int unitHandleId, final int cancelIndex) { final CUnit unit = this.game.getUnit(unitHandleId); - unit.cancelBuildQueueItem(this.game, cancelIndex); + if (this.playerIndex == unit.getPlayerIndex()) { + unit.cancelBuildQueueItem(this.game, cancelIndex); + } } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CRace.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CRace.java index 26bb76a..9043d91 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CRace.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CRace.java @@ -14,6 +14,8 @@ public enum CRace { this.id = id; } + public static CRace[] VALUES = values(); + public int getId() { return this.id; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CRacePreference.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CRacePreference.java index 7086929..24ff6ac 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CRacePreference.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CRacePreference.java @@ -8,4 +8,19 @@ public enum CRacePreference { DEMON, RANDOM, USER_SELECTABLE; + + public static CRacePreference[] VALUES = values(); + + public static CRacePreference getById(final int id) { + for (final CRacePreference type : VALUES) { + if ((type.getId()) == id) { + return type; + } + } + return null; + } + + public int getId() { + return 1 << ordinal(); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CStartLocPrio.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CStartLocPrio.java new file mode 100644 index 0000000..99d7008 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CStartLocPrio.java @@ -0,0 +1,9 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.players; + +public enum CStartLocPrio { + LOW, + HIGH, + NOT; + + public static CStartLocPrio[] VALUES = values(); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/state/CGameState.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/state/CGameState.java new file mode 100644 index 0000000..0cad618 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/state/CGameState.java @@ -0,0 +1,9 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.state; + +public enum CGameState { + DIVINE_INTERVENTION, + DISCONNECTED, + TIME_OF_DAY; + + public static CGameState[] VALUES = values(); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/state/CUnitState.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/state/CUnitState.java new file mode 100644 index 0000000..2272ad4 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/state/CUnitState.java @@ -0,0 +1,10 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.state; + +public enum CUnitState { + LIFE, + MAX_LIFE, + MANA, + MAX_MANA; + + public static CUnitState[] VALUES = values(); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/timers/CTimer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/timers/CTimer.java new file mode 100644 index 0000000..917f44e --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/timers/CTimer.java @@ -0,0 +1,89 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.timers; + +import com.etheller.warsmash.util.WarsmashConstants; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; + +public abstract class CTimer { + private int engineFireTick; + private int scheduleTick; + private float timeoutTime; + private float remainingTimeAfterPause; + private boolean running = false; + private boolean repeats; + + public void setTimeoutTime(final float timeoutTime) { + this.timeoutTime = timeoutTime; + } + + public boolean isRepeats() { + return this.repeats; + } + + public boolean isRunning() { + return this.running; + } + + public float getTimeoutTime() { + return this.timeoutTime; + } + + /** + * @param simulation + */ + public void start(final CSimulation simulation) { + this.running = true; + final int currentTick = simulation.getGameTurnTick(); + this.scheduleTick = currentTick; + innerStart(this.timeoutTime, simulation, currentTick); + } + + private void innerStart(final float timeoutTime, final CSimulation simulation, final int currentTick) { + final int ticks = (int) (timeoutTime / WarsmashConstants.SIMULATION_STEP_TIME); + this.engineFireTick = currentTick + ticks; + simulation.registerTimer(this); + } + + public void pause(final CSimulation simulation) { + this.remainingTimeAfterPause = getRemaining(simulation); + simulation.unregisterTimer(this); + } + + public void resume(final CSimulation simulation) { + if (this.remainingTimeAfterPause == 0) { + start(simulation); + } + final int currentTick = simulation.getGameTurnTick(); + innerStart(this.remainingTimeAfterPause, simulation, currentTick); + this.remainingTimeAfterPause = 0; + } + + public float getElapsed(final CSimulation simulation) { + final int currentTick = simulation.getGameTurnTick(); + final int elapsedTicks = currentTick - this.scheduleTick; + return Math.max(elapsedTicks * WarsmashConstants.SIMULATION_STEP_TIME, this.timeoutTime); + + } + + public float getRemaining(final CSimulation simulation) { + return this.timeoutTime - getElapsed(simulation); + } + + public void setRepeats(final boolean repeats) { + this.repeats = repeats; + } + + public int getEngineFireTick() { + return this.engineFireTick; + } + + public abstract void onFire(); + + public void fire(final CSimulation simulation) { + // its implied that we will have "unregisterTimer" happen automatically + // before this is called + this.running = false; + if (this.repeats) { + start(simulation); + } + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/timers/CTimerJass.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/timers/CTimerJass.java new file mode 100644 index 0000000..33ba52e --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/timers/CTimerJass.java @@ -0,0 +1,25 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.timers; + +import java.util.Collections; + +import com.etheller.interpreter.ast.function.JassFunction; +import com.etheller.interpreter.ast.scope.GlobalScope; +import com.etheller.interpreter.ast.scope.TriggerExecutionScope; + +public class CTimerJass extends CTimer { + private JassFunction handlerFunc; + private final GlobalScope jassGlobalScope; + + public CTimerJass(final GlobalScope jassGlobalScope) { + this.jassGlobalScope = jassGlobalScope; + } + + public void setHandlerFunc(final JassFunction handlerFunc) { + this.handlerFunc = handlerFunc; + } + + @Override + public void onFire() { + this.handlerFunc.call(Collections.emptyList(), this.jassGlobalScope, TriggerExecutionScope.EMPTY); + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/JassGameEventsWar3.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/JassGameEventsWar3.java new file mode 100644 index 0000000..7d78f30 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/JassGameEventsWar3.java @@ -0,0 +1,248 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger; + +//=================================================== +//Game, Player and Unit Events +// +//When an event causes a trigger to fire these +//values allow the action code to determine which +//event was dispatched and therefore which set of +//native functions should be used to get information +//about the event. +// +//Do NOT change the order or value of these constants +//without insuring that the JASS_GAME_EVENTS_WAR3 enum +//is changed to match. +// +//=================================================== + +public enum JassGameEventsWar3 { + // =================================================== + // For use with TriggerRegisterGameEvent + // =================================================== + EVENT_GAME_VICTORY, + EVENT_GAME_END_LEVEL, + + EVENT_GAME_VARIABLE_LIMIT, + EVENT_GAME_STATE_LIMIT, + + EVENT_GAME_TIMER_EXPIRED, + + EVENT_GAME_ENTER_REGION, + EVENT_GAME_LEAVE_REGION, + + EVENT_GAME_TRACKABLE_HIT, + EVENT_GAME_TRACKABLE_TRACK, + + EVENT_GAME_SHOW_SKILL, + EVENT_GAME_BUILD_SUBMENU, + + // =================================================== + // For use with TriggerRegisterPlayerEvent + // =================================================== + EVENT_PLAYER_STATE_LIMIT, + EVENT_PLAYER_ALLIANCE_CHANGED, + + EVENT_PLAYER_DEFEAT, + EVENT_PLAYER_VICTORY, + EVENT_PLAYER_LEAVE, + EVENT_PLAYER_CHAT, + EVENT_PLAYER_END_CINEMATIC, + + // =================================================== + // For use with TriggerRegisterPlayerUnitEvent + // =================================================== + EVENT_PLAYER_UNIT_ATTACKED, + EVENT_PLAYER_UNIT_RESCUED, + + EVENT_PLAYER_UNIT_DEATH, + EVENT_PLAYER_UNIT_DECAY, + + EVENT_PLAYER_UNIT_DETECTED, + EVENT_PLAYER_UNIT_HIDDEN, + + EVENT_PLAYER_UNIT_SELECTED, + EVENT_PLAYER_UNIT_DESELECTED, + + EVENT_PLAYER_UNIT_CONSTRUCT_START, + EVENT_PLAYER_UNIT_CONSTRUCT_CANCEL, + EVENT_PLAYER_UNIT_CONSTRUCT_FINISH, + + EVENT_PLAYER_UNIT_UPGRADE_START, + EVENT_PLAYER_UNIT_UPGRADE_CANCEL, + EVENT_PLAYER_UNIT_UPGRADE_FINISH, + + EVENT_PLAYER_UNIT_TRAIN_START, + EVENT_PLAYER_UNIT_TRAIN_CANCEL, + EVENT_PLAYER_UNIT_TRAIN_FINISH, + + EVENT_PLAYER_UNIT_RESEARCH_START, + EVENT_PLAYER_UNIT_RESEARCH_CANCEL, + EVENT_PLAYER_UNIT_RESEARCH_FINISH, + EVENT_PLAYER_UNIT_ISSUED_ORDER, + EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER, + EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER, + + EVENT_PLAYER_HERO_LEVEL, + EVENT_PLAYER_HERO_SKILL, + + EVENT_PLAYER_HERO_REVIVABLE, + + EVENT_PLAYER_HERO_REVIVE_START, + EVENT_PLAYER_HERO_REVIVE_CANCEL, + EVENT_PLAYER_HERO_REVIVE_FINISH, + EVENT_PLAYER_UNIT_SUMMON, + EVENT_PLAYER_UNIT_DROP_ITEM, + EVENT_PLAYER_UNIT_PICKUP_ITEM, + EVENT_PLAYER_UNIT_USE_ITEM, + EVENT_PLAYER_UNIT_LOADED, + + // =================================================== + // For use with TriggerRegisterUnitEvent + // =================================================== + + EVENT_UNIT_DAMAGED, + EVENT_UNIT_DEATH, + EVENT_UNIT_DECAY, + EVENT_UNIT_DETECTED, + EVENT_UNIT_HIDDEN, + EVENT_UNIT_SELECTED, + EVENT_UNIT_DESELECTED, + + EVENT_UNIT_STATE_LIMIT, + + // Events which may have a filter for the "other unit" + // + EVENT_UNIT_ACQUIRED_TARGET, + EVENT_UNIT_TARGET_IN_RANGE, + EVENT_UNIT_ATTACKED, + EVENT_UNIT_RESCUED, + + EVENT_UNIT_CONSTRUCT_CANCEL, + EVENT_UNIT_CONSTRUCT_FINISH, + + EVENT_UNIT_UPGRADE_START, + EVENT_UNIT_UPGRADE_CANCEL, + EVENT_UNIT_UPGRADE_FINISH, + +// Events which involve the specified unit performing +// training of other units +// + EVENT_UNIT_TRAIN_START, + EVENT_UNIT_TRAIN_CANCEL, + EVENT_UNIT_TRAIN_FINISH, + + EVENT_UNIT_RESEARCH_START, + EVENT_UNIT_RESEARCH_CANCEL, + EVENT_UNIT_RESEARCH_FINISH, + + EVENT_UNIT_ISSUED_ORDER, + EVENT_UNIT_ISSUED_POINT_ORDER, + EVENT_UNIT_ISSUED_TARGET_ORDER, + + EVENT_UNIT_HERO_LEVEL, + EVENT_UNIT_HERO_SKILL, + + EVENT_UNIT_HERO_REVIVABLE, + EVENT_UNIT_HERO_REVIVE_START, + EVENT_UNIT_HERO_REVIVE_CANCEL, + EVENT_UNIT_HERO_REVIVE_FINISH, + + EVENT_UNIT_SUMMON, + + EVENT_UNIT_DROP_ITEM, + EVENT_UNIT_PICKUP_ITEM, + EVENT_UNIT_USE_ITEM, + + EVENT_UNIT_LOADED, + + EVENT_WIDGET_DEATH, + + EVENT_DIALOG_BUTTON_CLICK, + EVENT_DIALOG_CLICK, + + // =================================================== + // Frozen Throne Expansion Events + // Need to be added here to preserve compat + // =================================================== + + // =================================================== + // For use with TriggerRegisterGameEvent + // =================================================== + + EVENT_GAME_LOADED, + EVENT_GAME_TOURNAMENT_FINISH_SOON, + EVENT_GAME_TOURNAMENT_FINISH_NOW, + EVENT_GAME_SAVE, + + EVENT_UNKNOWN_TFT_CODE_260, + + // =================================================== + // For use with TriggerRegisterPlayerEvent + // =================================================== + + EVENT_PLAYER_ARROW_LEFT_DOWN, + EVENT_PLAYER_ARROW_LEFT_UP, + EVENT_PLAYER_ARROW_RIGHT_DOWN, + EVENT_PLAYER_ARROW_RIGHT_UP, + EVENT_PLAYER_ARROW_DOWN_DOWN, + EVENT_PLAYER_ARROW_DOWN_UP, + EVENT_PLAYER_ARROW_UP_DOWN, + EVENT_PLAYER_ARROW_UP_UP, + + // =================================================== + // For use with TriggerRegisterPlayerUnitEvent + // =================================================== + + EVENT_PLAYER_UNIT_SELL, + EVENT_PLAYER_UNIT_CHANGE_OWNER, + EVENT_PLAYER_UNIT_SELL_ITEM, + EVENT_PLAYER_UNIT_SPELL_CHANNEL, + EVENT_PLAYER_UNIT_SPELL_CAST, + EVENT_PLAYER_UNIT_SPELL_EFFECT, + EVENT_PLAYER_UNIT_SPELL_FINISH, + EVENT_PLAYER_UNIT_SPELL_ENDCAST, + EVENT_PLAYER_UNIT_PAWN_ITEM, + + EVENT_UNKNOWN_TFT_CODE_278, + EVENT_UNKNOWN_TFT_CODE_279, + EVENT_UNKNOWN_TFT_CODE_280, + EVENT_UNKNOWN_TFT_CODE_281, + EVENT_UNKNOWN_TFT_CODE_282, + EVENT_UNKNOWN_TFT_CODE_283, + EVENT_UNKNOWN_TFT_CODE_284, + EVENT_UNKNOWN_TFT_CODE_285, + + // =================================================== + // For use with TriggerRegisterUnitEvent + // =================================================== + + EVENT_UNIT_SELL, + EVENT_UNIT_CHANGE_OWNER, + EVENT_UNIT_SELL_ITEM, + EVENT_UNIT_SPELL_CHANNEL, + EVENT_UNIT_SPELL_CAST, + EVENT_UNIT_SPELL_EFFECT, + EVENT_UNIT_SPELL_FINISH, + EVENT_UNIT_SPELL_ENDCAST, + EVENT_UNIT_PAWN_ITEM,; + + private static final int TFT_CUTOFF = EVENT_GAME_LOADED.ordinal(); + + public static JassGameEventsWar3[] VALUES; + static { + final JassGameEventsWar3[] localValuesArray = values(); + final JassGameEventsWar3 endValue = localValuesArray[localValuesArray.length]; + VALUES = new JassGameEventsWar3[endValue.getEventId() + 1]; + for (final JassGameEventsWar3 event : localValuesArray) { + VALUES[event.getEventId()] = event; + } + } + + public int getEventId() { + final int ordinal = ordinal(); + if (ordinal >= TFT_CUTOFF) { + return (ordinal - TFT_CUTOFF) + 256; + } + return ordinal; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CAttackTypeJass.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CAttackTypeJass.java new file mode 100644 index 0000000..8fbd348 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CAttackTypeJass.java @@ -0,0 +1,9 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.enumtypes; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; + +public enum CAttackTypeJass { + ; + public static CAttackType[] VALUES = { CAttackType.SPELLS, CAttackType.NORMAL, CAttackType.PIERCE, + CAttackType.SIEGE, CAttackType.MAGIC, CAttackType.CHAOS, CAttackType.HERO }; +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CBlendMode.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CBlendMode.java new file mode 100644 index 0000000..8ea22de --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CBlendMode.java @@ -0,0 +1,12 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.enumtypes; + +public enum CBlendMode { + NONE, + KEYALPHA, + BLEND, + ADDITIVE, + MODULATE, + MODULATE_2X; + + public static CBlendMode[] VALUES = values(); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CCameraField.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CCameraField.java new file mode 100644 index 0000000..b51f0a4 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CCameraField.java @@ -0,0 +1,13 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.enumtypes; + +public enum CCameraField { + TARGET_DISTANCE, + FARZ, + ANGLE_OF_ATTACK, + FIELD_OF_VIEW, + ROLL, + ROTATION, + ZOFFSET; + + public static CCameraField[] VALUES = values(); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CDamageType.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CDamageType.java new file mode 100644 index 0000000..2269db5 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CDamageType.java @@ -0,0 +1,32 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.enumtypes; + +public enum CDamageType { + UNKNOWN, + UNKNOWN_CODE_1, + UNKNOWN_CODE_2, + UNKNOWN_CODE_3, + NORMAL, + ENHANCED, + UNKNOWN_CODE_6, + UNKNOWN_CODE_7, + FIRE, + COLD, + LIGHTNING, + POISON, + DISEASE, + DIVINE, + MAGIC, + SONIC, + ACID, + FORCE, + DEATH, + MIND, + PLANT, + DEFENSIVE, + DEMOLITION, + SLOW_POISON, + SPIRIT_LINK, + SHADOW_STRIKE; + + public static CDamageType[] VALUES = values(); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CEffectType.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CEffectType.java new file mode 100644 index 0000000..1d02a97 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CEffectType.java @@ -0,0 +1,13 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.enumtypes; + +public enum CEffectType { + EFFECT, + TARGET, + CASTER, + SPECIAL, + AREA_EFFECT, + MISSILE, + LIGHTNING; + + public static CEffectType[] VALUES = values(); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CFogState.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CFogState.java new file mode 100644 index 0000000..4e849f1 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CFogState.java @@ -0,0 +1,22 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.enumtypes; + +public enum CFogState { + MASKED, + FOGGED, + VISIBLE; + + public static CFogState[] VALUES = values(); + + public static CFogState getById(final int id) { + for (final CFogState type : VALUES) { + if ((type.getId()) == id) { + return type; + } + } + return null; + } + + public int getId() { + return 1 << ordinal(); + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CGameSpeed.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CGameSpeed.java new file mode 100644 index 0000000..ee44d83 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CGameSpeed.java @@ -0,0 +1,11 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.enumtypes; + +public enum CGameSpeed { + SLOWEST, + SLOW, + NORMAL, + FAST, + FASTEST; + + public static CGameSpeed[] VALUES = values(); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CGameType.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CGameType.java new file mode 100644 index 0000000..6c068a3 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CGameType.java @@ -0,0 +1,27 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.enumtypes; + +public enum CGameType { + MELEE, + FFA, + USE_MAP_SETTINGS, + BLIZ, + ONE_ON_ONE, + TWO_TEAM_PLAY, + THREE_TEAM_PLAY, + FOUR_TEAM_PLAY; + + public static CGameType[] VALUES = values(); + + public static CGameType getById(final int id) { + for (final CGameType type : VALUES) { + if ((type.getId()) == id) { + return type; + } + } + return null; + } + + public int getId() { + return 1 << ordinal(); + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CLimitOp.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CLimitOp.java new file mode 100644 index 0000000..1a36421 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CLimitOp.java @@ -0,0 +1,12 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.enumtypes; + +public enum CLimitOp { + LESS_THAN, + LESS_THAN_OR_EQUAL, + EQUAL, + GREATER_THAN_OR_EQUAL, + GREATER_THAN, + NOT_EQUAL; + + public static CLimitOp[] VALUES = values(); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CMapDensity.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CMapDensity.java new file mode 100644 index 0000000..0e27016 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CMapDensity.java @@ -0,0 +1,10 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.enumtypes; + +public enum CMapDensity { + NONE, + LIGHT, + MEDIUM, + HEAVY; + + public static CMapDensity[] VALUES = values(); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CMapDifficulty.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CMapDifficulty.java new file mode 100644 index 0000000..30b1e87 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CMapDifficulty.java @@ -0,0 +1,10 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.enumtypes; + +public enum CMapDifficulty { + EASY, + NORMAL, + HARD, + INSANE; + + public static CMapDifficulty[] VALUES = values(); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CPathingTypeJass.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CPathingTypeJass.java new file mode 100644 index 0000000..2e82cbe --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CPathingTypeJass.java @@ -0,0 +1,14 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.enumtypes; + +public enum CPathingTypeJass { + ANY, + WALKABILITY, + FLYABILITY, + BUILDABILITY, + PEONHARVESTPATHING, + BLIGHTPATHING, + FLOATABILITY, + AMPHIBIOUSPATHING; + + public static CPathingTypeJass[] VALUES = values(); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CPlayerSlotState.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CPlayerSlotState.java new file mode 100644 index 0000000..e8c219f --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CPlayerSlotState.java @@ -0,0 +1,9 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.enumtypes; + +public enum CPlayerSlotState { + EMPTY, + PLAYING, + LEFT; + + public static CPlayerSlotState[] VALUES = values(); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CRarityControl.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CRarityControl.java new file mode 100644 index 0000000..d67015d --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CRarityControl.java @@ -0,0 +1,8 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.enumtypes; + +public enum CRarityControl { + FREQUENT, + RARE; + + public static CRarityControl[] VALUES = values(); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CSoundType.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CSoundType.java new file mode 100644 index 0000000..14db526 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CSoundType.java @@ -0,0 +1,8 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.enumtypes; + +public enum CSoundType { + EFFECT, + EFFECT_LOOPED; + + public static CSoundType[] VALUES = values(); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CSoundVolumeGroup.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CSoundVolumeGroup.java new file mode 100644 index 0000000..3f22881 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CSoundVolumeGroup.java @@ -0,0 +1,14 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.enumtypes; + +public enum CSoundVolumeGroup { + UNITMOVEMENT, + UNITSOUNDS, + COMBAT, + SPELLS, + UI, + MUSIC, + AMBIENTSOUNDS, + FIRE; + + public static CSoundVolumeGroup[] VALUES = values(); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CTexMapFlags.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CTexMapFlags.java new file mode 100644 index 0000000..10dee18 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CTexMapFlags.java @@ -0,0 +1,10 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.enumtypes; + +public enum CTexMapFlags { + NONE, + WRAP_U, + WRAP_V, + WRAP_UV; + + public static CTexMapFlags[] VALUES = values(); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CVersion.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CVersion.java new file mode 100644 index 0000000..6e8cf22 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CVersion.java @@ -0,0 +1,8 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.enumtypes; + +public enum CVersion { + REIGN_OF_CHAOS, + FROZEN_THRONE; + + public static CVersion[] VALUES = values(); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CWeaponSoundTypeJass.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CWeaponSoundTypeJass.java new file mode 100644 index 0000000..f3a6b8f --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/trigger/enumtypes/CWeaponSoundTypeJass.java @@ -0,0 +1,40 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.enumtypes; + +public enum CWeaponSoundTypeJass { + WHOKNOWS(null), + METAL_LIGHT_CHOP("MetalLightChop"), + METAL_MEDIUM_CHOP("MetalMediumChop"), + METAL_HEAVY_CHOP("MetalHeavyChop"), + METAL_LIGHT_SLICE("MetalLightSlice"), + METAL_MEDIUM_SLICE("MetalMediumSlice"), + METAL_HEAVY_SLICE("MetalHeavySlice"), + METAL_MEDIUM_BASH("MetalMediumBash"), + METAL_HEAVY_BASH("MetalHeavyBash"), + METAL_MEDIUM_STAB("MetalMediumStab"), + METAL_HEAVY_STAB("MetalHeavyStab"), + WOOD_LIGHT_SLICE("WoodLightSlice"), + WOOD_MEDIUM_SLICE("WoodMediumSlice"), + WOOD_HEAVY_SLICE("WoodHeavySlice"), + WOOD_LIGHT_BASH("WoodLightBash"), + WOOD_MEDIUM_BASH("WoodMediumBash"), + WOOD_HEAVY_BASH("WoodHeavyBash"), + WOOD_LIGHT_STAB("WoodLightStab"), + WOOD_MEDIUM_STAB("WoodMediumStab"), + CLAW_LIGHT_SLICE("ClawLightSlice"), + CLAW_MEDIUM_SLICE("ClawMediumSlice"), + CLAW_HEAVY_SLICE("ClawHeavySlice"), + AXE_MEDIUM_CHOP("AxeMediumChop"), + ROCK_HEAVY_BASH("RockHeavyBash"); + + private final String soundKey; + + CWeaponSoundTypeJass(final String soundKey) { + this.soundKey = soundKey; + } + + public String getSoundKey() { + return this.soundKey; + } + + public static CWeaponSoundTypeJass[] VALUES = values(); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/unit/CUnitTypeJass.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/unit/CUnitTypeJass.java new file mode 100644 index 0000000..3092f37 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/unit/CUnitTypeJass.java @@ -0,0 +1,39 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.unit; + +public enum CUnitTypeJass { + HERO, + DEAD, + STRUCTURE, + + FLYING, + GROUND, + + ATTACKS_FLYING, + ATTACKS_GROUND, + + MELEE_ATTACKER, + RANGED_ATTACKER, + + GIANT, + SUMMONED, + STUNNED, + PLAGUED, + SNARED, + + UNDEAD, + MECHANICAL, + PEON, + SAPPER, + TOWNHALL, + ANCIENT, + + TAUREN, + POISONED, + POLYMORPHED, + SLEEPING, + RESISTANT, + ETHEREAL, + MAGIC_IMMUNE; + + public static CUnitTypeJass[] VALUES = values(); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java index 290c4c9..20020e4 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java @@ -1163,7 +1163,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma MeleeUI.this.cursorModelInstance = (MdxComplexInstance) model.addInstance(); // MeleeUI.this.cursorModelInstance.setVertexColor(new float[] { 1, 1, 1, 0.5f }); final int playerColorIndex = viewer.simulation - .getPlayer(MeleeUI.this.activeCommandUnit.getSimulationUnit().getPlayerIndex()).getColorIndex(); + .getPlayer(MeleeUI.this.activeCommandUnit.getSimulationUnit().getPlayerIndex()).getColor(); MeleeUI.this.cursorModelInstance.setTeamColor(playerColorIndex); MeleeUI.this.cursorModelInstance.rotate(RenderUnit.tempQuat.setFromAxis(RenderMathUtils.VEC3_UNIT_Z, viewer.simulation.getGameplayConstants().getBuildingAngle())); @@ -1534,8 +1534,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma private void repositionRallyPoint(final CUnit simulationUnit) { final AbilityTarget rallyPoint = simulationUnit.getRallyPoint(); if (rallyPoint != null) { - this.rallyPointInstance.setTeamColor( - this.war3MapViewer.simulation.getPlayer(simulationUnit.getPlayerIndex()).getColorIndex()); + this.rallyPointInstance + .setTeamColor(this.war3MapViewer.simulation.getPlayer(simulationUnit.getPlayerIndex()).getColor()); this.rallyPointInstance.show(); this.rallyPointInstance.detach(); rallyPoint.visit(this.rallyPositioningVisitor.reset(this.rallyPointInstance)); @@ -1634,7 +1634,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } repositionRallyPoint(simulationUnit); repositionWaypointFlags(simulationUnit); - if (simulationUnit.getBuildQueue()[0] != null) { + if ((simulationUnit.getBuildQueue()[0] != null) + && (simulationUnit.getPlayerIndex() == this.war3MapViewer.getLocalPlayerIndex())) { for (int i = 0; i < this.queueIconFrames.length; i++) { final QueueItemType queueItemType = simulationUnit.getBuildQueueTypes()[i]; if (queueItemType == null) { @@ -2063,24 +2064,27 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma private void clearAndRepopulateCommandCard() { clearCommandCard(); - final AbilityDataUI abilityDataUI = this.war3MapViewer.getAbilityDataUI(); - final int menuOrderId = getSubMenuOrderId(); - if ((this.activeCommand != null) && (this.draggingItem == null)) { - final IconUI cancelUI = abilityDataUI.getCancelUI(); - this.commandButton(cancelUI.getButtonPositionX(), cancelUI.getButtonPositionY(), cancelUI.getIcon(), 0, - menuOrderId, 0, false, false, true, cancelUI.getToolTip(), cancelUI.getUberTip(), 0, 0, 0); - } - else { - if (menuOrderId != 0) { - final int exitOrderId = this.subMenuOrderIdStack.size() > 1 - ? this.subMenuOrderIdStack.get(this.subMenuOrderIdStack.size() - 2) - : 0; + if (this.selectedUnit.getSimulationUnit().getPlayerIndex() == this.war3MapViewer.getLocalPlayerIndex()) { + final AbilityDataUI abilityDataUI = this.war3MapViewer.getAbilityDataUI(); + final int menuOrderId = getSubMenuOrderId(); + if ((this.activeCommand != null) && (this.draggingItem == null)) { final IconUI cancelUI = abilityDataUI.getCancelUI(); this.commandButton(cancelUI.getButtonPositionX(), cancelUI.getButtonPositionY(), cancelUI.getIcon(), 0, - exitOrderId, 0, false, false, true, cancelUI.getToolTip(), cancelUI.getUberTip(), 0, 0, 0); + menuOrderId, 0, false, false, true, cancelUI.getToolTip(), cancelUI.getUberTip(), 0, 0, 0); + } + else { + if (menuOrderId != 0) { + final int exitOrderId = this.subMenuOrderIdStack.size() > 1 + ? this.subMenuOrderIdStack.get(this.subMenuOrderIdStack.size() - 2) + : 0; + final IconUI cancelUI = abilityDataUI.getCancelUI(); + this.commandButton(cancelUI.getButtonPositionX(), cancelUI.getButtonPositionY(), cancelUI.getIcon(), + 0, exitOrderId, 0, false, false, true, cancelUI.getToolTip(), cancelUI.getUberTip(), 0, 0, + 0); + } + this.selectedUnit.populateCommandCard(this.war3MapViewer.simulation, this.rootFrame, this, + abilityDataUI, menuOrderId); } - this.selectedUnit.populateCommandCard(this.war3MapViewer.simulation, this.rootFrame, this, abilityDataUI, - menuOrderId); } } @@ -2224,7 +2228,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } else { if (button == Input.Buttons.RIGHT) { - if (getSelectedUnit() != null) { + if ((getSelectedUnit() != null) && (getSelectedUnit().getSimulationUnit() + .getPlayerIndex() == this.war3MapViewer.getLocalPlayerIndex())) { final RenderWidget rayPickUnit = this.war3MapViewer.rayPickUnit(screenX, worldScreenY); if ((rayPickUnit != null) && !rayPickUnit.getSimulationWidget().isDead()) { boolean ordered = false; @@ -2295,19 +2300,21 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma boolean ordered = false; boolean rallied = false; for (final RenderUnit unit : this.selectedUnits) { - for (final CAbility ability : unit.getSimulationUnit().getAbilities()) { - ability.checkCanUse(this.war3MapViewer.simulation, unit.getSimulationUnit(), OrderIds.smart, - BooleanAbilityActivationReceiver.INSTANCE); - if (BooleanAbilityActivationReceiver.INSTANCE.isOk()) { - ability.checkCanTarget(this.war3MapViewer.simulation, unit.getSimulationUnit(), OrderIds.smart, - clickLocationTemp2, PointAbilityTargetCheckReceiver.INSTANCE); - final Vector2 target = PointAbilityTargetCheckReceiver.INSTANCE.getTarget(); - if (target != null) { - this.unitOrderListener.issuePointOrder(unit.getSimulationUnit().getHandleId(), - ability.getHandleId(), OrderIds.smart, clickLocationTemp2.x, clickLocationTemp2.y, - isShiftDown()); - rallied |= ability instanceof CAbilityRally; - ordered = true; + if (unit.getSimulationUnit().getPlayerIndex() == this.war3MapViewer.getLocalPlayerIndex()) { + for (final CAbility ability : unit.getSimulationUnit().getAbilities()) { + ability.checkCanUse(this.war3MapViewer.simulation, unit.getSimulationUnit(), OrderIds.smart, + BooleanAbilityActivationReceiver.INSTANCE); + if (BooleanAbilityActivationReceiver.INSTANCE.isOk()) { + ability.checkCanTarget(this.war3MapViewer.simulation, unit.getSimulationUnit(), OrderIds.smart, + clickLocationTemp2, PointAbilityTargetCheckReceiver.INSTANCE); + final Vector2 target = PointAbilityTargetCheckReceiver.INSTANCE.getTarget(); + if (target != null) { + this.unitOrderListener.issuePointOrder(unit.getSimulationUnit().getHandleId(), + ability.getHandleId(), OrderIds.smart, clickLocationTemp2.x, clickLocationTemp2.y, + isShiftDown()); + rallied |= ability instanceof CAbilityRally; + ordered = true; + } } } } @@ -2345,33 +2352,38 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma if (selectionChanged) { this.selectedSoundCount = 0; } - if (unit.soundset != null) { - UnitSound ackSoundToPlay = unit.soundset.what; - int soundIndex; - final int pissedSoundCount = unit.soundset.pissed.getSoundCount(); - if (unit.getSimulationUnit().isConstructing()) { - ackSoundToPlay = this.war3MapViewer.getUiSounds() - .getSound(this.rootFrame.getSkinField("ConstructingBuilding")); - soundIndex = (int) (Math.random() * ackSoundToPlay.getSoundCount()); - } - else { - if ((this.selectedSoundCount >= 3) && (pissedSoundCount > 0)) { - soundIndex = this.selectedSoundCount - 3; - ackSoundToPlay = unit.soundset.pissed; - } - else { + if (unit.getSimulationUnit().getPlayerIndex() == this.war3MapViewer.getLocalPlayerIndex()) { + if (unit.soundset != null) { + UnitSound ackSoundToPlay = unit.soundset.what; + int soundIndex; + final int pissedSoundCount = unit.soundset.pissed.getSoundCount(); + if (unit.getSimulationUnit().isConstructing()) { + ackSoundToPlay = this.war3MapViewer.getUiSounds() + .getSound(this.rootFrame.getSkinField("ConstructingBuilding")); soundIndex = (int) (Math.random() * ackSoundToPlay.getSoundCount()); } - } - if ((ackSoundToPlay != null) && ackSoundToPlay - .playUnitResponse(this.war3MapViewer.worldScene.audioContext, unit, soundIndex)) { - this.selectedSoundCount++; - if ((this.selectedSoundCount - 3) >= pissedSoundCount) { - this.selectedSoundCount = 0; + else { + if ((this.selectedSoundCount >= 3) && (pissedSoundCount > 0)) { + soundIndex = this.selectedSoundCount - 3; + ackSoundToPlay = unit.soundset.pissed; + } + else { + soundIndex = (int) (Math.random() * ackSoundToPlay.getSoundCount()); + } + } + if ((ackSoundToPlay != null) && ackSoundToPlay + .playUnitResponse(this.war3MapViewer.worldScene.audioContext, unit, soundIndex)) { + this.selectedSoundCount++; + if ((this.selectedSoundCount - 3) >= pissedSoundCount) { + this.selectedSoundCount = 0; + } + playedNewSound = true; } - playedNewSound = true; } } + else { + this.war3MapViewer.getUiSounds().getSound("InterfaceClick").play(this.uiScene.audioContext, 0, 0, 0); + } if (selectionChanged) { selectUnit(unit); } diff --git a/jassparser/src/com/etheller/interpreter/ast/scope/TriggerExecutionScope.java b/jassparser/src/com/etheller/interpreter/ast/scope/TriggerExecutionScope.java index ded54e9..e9799ed 100644 --- a/jassparser/src/com/etheller/interpreter/ast/scope/TriggerExecutionScope.java +++ b/jassparser/src/com/etheller/interpreter/ast/scope/TriggerExecutionScope.java @@ -3,6 +3,8 @@ package com.etheller.interpreter.ast.scope; import com.etheller.interpreter.ast.scope.trigger.Trigger; public class TriggerExecutionScope { + public static final TriggerExecutionScope EMPTY = new TriggerExecutionScope(null); + private final Trigger triggeringTrigger; public TriggerExecutionScope(final Trigger triggeringTrigger) { diff --git a/jassparser/src/com/etheller/interpreter/ast/value/StringJassValue.java b/jassparser/src/com/etheller/interpreter/ast/value/StringJassValue.java index d37c98f..9cbcbb8 100644 --- a/jassparser/src/com/etheller/interpreter/ast/value/StringJassValue.java +++ b/jassparser/src/com/etheller/interpreter/ast/value/StringJassValue.java @@ -3,12 +3,18 @@ package com.etheller.interpreter.ast.value; public class StringJassValue implements JassValue { private final String value; + public static StringJassValue of(final String value) { + // later this could do that dumb thing jass does with making sure we dont create + // duplicate instances, maybe + return new StringJassValue(value); + } + public StringJassValue(final String value) { this.value = value; } public String getValue() { - return value; + return this.value; } @Override From 2890e640e0383ea96253859ba35e4a8906ab2d8a Mon Sep 17 00:00:00 2001 From: Retera Date: Mon, 22 Mar 2021 08:34:44 -0400 Subject: [PATCH 111/116] Update to allow test menu to enter a campaign screen --- .../etheller/warsmash/SingleModelScreen.java | 5 + .../warsmash/WarsmashGdxMenuScreen.java | 94 ++++- .../warsmash/parsers/fdf/GameSkin.java | 22 + .../etheller/warsmash/parsers/fdf/GameUI.java | 142 +++++-- .../fdf/frames/AbstractRenderableFrame.java | 39 ++ .../parsers/fdf/frames/AbstractUIFrame.java | 5 + .../parsers/fdf/frames/BackdropFrame.java | 17 +- .../parsers/fdf/frames/ControlFrame.java | 35 ++ .../parsers/fdf/frames/EditBoxFrame.java | 164 ++++++++ .../parsers/fdf/frames/ListBoxFrame.java | 110 +++++ .../parsers/fdf/frames/StringFrame.java | 35 +- .../warsmash/parsers/fdf/frames/UIFrame.java | 8 + .../etheller/warsmash/parsers/jass/Jass2.java | 5 +- .../warsmash/util/WarsmashConstants.java | 2 +- .../viewer5/handlers/w3x/ui/MeleeUI.java | 4 +- .../viewer5/handlers/w3x/ui/MenuUI.java | 377 ++++++++++++++++-- .../w3x/ui/command/FocusableFrame.java | 17 + .../rms/parsers/mdlx/AnimationMap.java | 2 +- 18 files changed, 977 insertions(+), 106 deletions(-) create mode 100644 core/src/com/etheller/warsmash/SingleModelScreen.java create mode 100644 core/src/com/etheller/warsmash/parsers/fdf/GameSkin.java create mode 100644 core/src/com/etheller/warsmash/parsers/fdf/frames/ControlFrame.java create mode 100644 core/src/com/etheller/warsmash/parsers/fdf/frames/EditBoxFrame.java create mode 100644 core/src/com/etheller/warsmash/parsers/fdf/frames/ListBoxFrame.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/FocusableFrame.java diff --git a/core/src/com/etheller/warsmash/SingleModelScreen.java b/core/src/com/etheller/warsmash/SingleModelScreen.java new file mode 100644 index 0000000..cf583e1 --- /dev/null +++ b/core/src/com/etheller/warsmash/SingleModelScreen.java @@ -0,0 +1,5 @@ +package com.etheller.warsmash; + +public interface SingleModelScreen { + void setModel(String path); +} diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMenuScreen.java b/core/src/com/etheller/warsmash/WarsmashGdxMenuScreen.java index 4788545..761a69c 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMenuScreen.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMenuScreen.java @@ -20,6 +20,8 @@ import com.badlogic.gdx.math.Quaternion; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Vector3; import com.badlogic.gdx.utils.viewport.ExtendViewport; +import com.badlogic.gdx.utils.viewport.FitViewport; +import com.badlogic.gdx.utils.viewport.Viewport; import com.etheller.warsmash.datasources.DataSource; import com.etheller.warsmash.parsers.fdf.GameUI; import com.etheller.warsmash.parsers.jass.Jass2.RootFrameListener; @@ -44,10 +46,11 @@ import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; import com.etheller.warsmash.viewer5.handlers.mdx.MdxViewer; import com.etheller.warsmash.viewer5.handlers.mdx.Sequence; import com.etheller.warsmash.viewer5.handlers.mdx.SequenceLoopMode; +import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; import com.etheller.warsmash.viewer5.handlers.w3x.ui.MenuUI; -public class WarsmashGdxMenuScreen implements CanvasProvider, InputProcessor, Screen { +public class WarsmashGdxMenuScreen implements CanvasProvider, InputProcessor, Screen, SingleModelScreen { private static final boolean ENABLE_AUDIO = true; private static final boolean ENABLE_MUSIC = true; private DataSource codebase; @@ -59,7 +62,7 @@ public class WarsmashGdxMenuScreen implements CanvasProvider, InputProcessor, Sc // libGDX stuff private OrthographicCamera uiCamera; private SpriteBatch batch; - private ExtendViewport uiViewport; + private Viewport uiViewport; private GlyphLayout glyphLayout; private final DataTable warsmashIni; @@ -68,6 +71,7 @@ public class WarsmashGdxMenuScreen implements CanvasProvider, InputProcessor, Sc private MenuUI menuUI; private final WarsmashGdxMultiScreenGame game; private Music currentMusic; + private boolean hasPlayedStandHack = false; public WarsmashGdxMenuScreen(final DataTable warsmashIni, final WarsmashGdxMultiScreenGame game) { this.warsmashIni = warsmashIni; @@ -94,8 +98,8 @@ public class WarsmashGdxMenuScreen implements CanvasProvider, InputProcessor, Sc this.viewer.addHandler(new MdxHandler()); this.viewer.enableAudio(); - final Scene scene = this.viewer.addSimpleScene(); - scene.enableAudio(); + this.scene = this.viewer.addSimpleScene(); + this.scene.enableAudio(); this.uiScene = this.viewer.addSimpleScene(); this.uiScene.alpha = true; @@ -121,10 +125,10 @@ public class WarsmashGdxMenuScreen implements CanvasProvider, InputProcessor, Sc aspect3By4Width = (height * 4) / 3; aspect3By4Height = height; } - this.uiViewport = new ExtendViewport(aspect3By4Width, aspect3By4Height, this.uiCamera); + this.uiViewport = new FitViewport(aspect3By4Width, aspect3By4Height, this.uiCamera); this.uiViewport.update(width, height); - this.uiCamera.position.set(this.uiViewport.getMinWorldWidth() / 2, this.uiViewport.getMinWorldHeight() / 2, 0); + this.uiCamera.position.set(getMinWorldWidth() / 2, getMinWorldHeight() / 2, 0); this.uiCamera.update(); this.batch = new SpriteBatch(); @@ -137,7 +141,7 @@ public class WarsmashGdxMenuScreen implements CanvasProvider, InputProcessor, Sc Gdx.input.setInputProcessor(this); this.cameraManager = new CameraManager(); - this.cameraManager.setupCamera(scene); + this.cameraManager.setupCamera(this.scene); // this.mainModel = (MdxModel) this.viewer.load("Doodads\\Cinematic\\ArthasIllidanFight\\ArthasIllidanFight.mdx", // this.mainModel = (MdxModel) this.viewer.load("UI\\Glues\\SinglePlayer\\NightElf_Exp\\NightElf_Exp.mdx", @@ -174,7 +178,7 @@ public class WarsmashGdxMenuScreen implements CanvasProvider, InputProcessor, Sc System.out.println("Loaded"); Gdx.gl30.glClearColor(0.0f, 0.0f, 0.0f, 1); - this.menuUI = new MenuUI(this.viewer.dataSource, this.uiViewport, this.uiScene, this.viewer, this.game, + this.menuUI = new MenuUI(this.viewer.dataSource, this.uiViewport, this.uiScene, this.viewer, this.game, this, this.warsmashIni, new RootFrameListener() { @Override public void onCreate(final GameUI rootFrame) { @@ -193,7 +197,7 @@ public class WarsmashGdxMenuScreen implements CanvasProvider, InputProcessor, Sc WarsmashGdxMenuScreen.this.currentMusic = music; } - singleModelScene(scene, + singleModelScene(WarsmashGdxMenuScreen.this.scene, War3MapViewer.mdx(rootFrame .getSkinField("GlueSpriteLayerBackground_V" + WarsmashConstants.GAME_VERSION)), "Stand"); @@ -213,6 +217,20 @@ public class WarsmashGdxMenuScreen implements CanvasProvider, InputProcessor, Sc } + private float getMinWorldWidth() { + if (this.uiViewport instanceof ExtendViewport) { + return ((ExtendViewport) this.uiViewport).getMinWorldWidth(); + } + return this.uiViewport.getWorldWidth(); + } + + private float getMinWorldHeight() { + if (this.uiViewport instanceof ExtendViewport) { + return ((ExtendViewport) this.uiViewport).getMinWorldHeight(); + } + return this.uiViewport.getWorldHeight(); + } + private void updateUIScene() { this.tempRect.x = this.uiViewport.getScreenX(); this.tempRect.y = this.uiViewport.getScreenY(); @@ -221,8 +239,8 @@ public class WarsmashGdxMenuScreen implements CanvasProvider, InputProcessor, Sc this.uiScene.camera.viewport(this.tempRect); final float worldWidth = this.uiViewport.getWorldWidth(); final float worldHeight = this.uiViewport.getWorldHeight(); - final float xScale = worldWidth / this.uiViewport.getMinWorldWidth(); - final float yScale = worldHeight / this.uiViewport.getMinWorldHeight(); + final float xScale = worldWidth / getMinWorldWidth(); + final float yScale = worldHeight / getMinWorldHeight(); final float uiSceneWidth = 0.8f * xScale; final float uiSceneHeight = 0.6f * yScale; final float uiSceneX = ((0.8f - uiSceneWidth) / 2); @@ -285,11 +303,27 @@ public class WarsmashGdxMenuScreen implements CanvasProvider, InputProcessor, Sc } instance3.setSequence(animIndex); - instance3.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); + instance3.setSequenceLoopMode(SequenceLoopMode.NEVER_LOOP); this.mainInstance = instance3; this.mainModel = model2; } + @Override + public void setModel(final String path) { + if (this.mainInstance != null) { + this.mainInstance.detach(); + } + singleModelScene(this.scene, War3MapViewer.mdx(path), "birth"); + WarsmashGdxMenuScreen.this.modelCamera = WarsmashGdxMenuScreen.this.mainModel.cameras.get(0); + // this hack is because we only have the queued animation system in RenderWidget + // which is stupid and back and needs to get moved to the model instance + // itself... our model instance class is a + // hacky replica of a model viewer tool with a bunch of irrelevant loop type + // settings instead of what it should be + this.hasPlayedStandHack = false; + + } + private void acolytesHarvestingScene(final Scene scene) { final MdxModel acolyteModel = (MdxModel) this.viewer.load("units\\undead\\acolyte\\acolyte.mdx", @@ -477,6 +511,7 @@ public class WarsmashGdxMenuScreen implements CanvasProvider, InputProcessor, Sc private final float[] cameraPositionTemp = new float[3]; private final float[] cameraTargetTemp = new float[3]; private final boolean firstFrame = true; + private Scene scene; @Override public void render(final float delta) { @@ -486,6 +521,12 @@ public class WarsmashGdxMenuScreen implements CanvasProvider, InputProcessor, Sc Gdx.gl30.glBindVertexArray(WarsmashGdxGame.VAO); this.cameraManager.updateCamera(); this.menuUI.update(deltaTime); + if (this.mainInstance.sequenceEnded + && (((this.mainModel.getSequences().get(this.mainInstance.sequence).getFlags() & 0x1) == 0) + || !this.hasPlayedStandHack)) { + SequenceUtils.randomStandSequence(this.mainInstance); + this.hasPlayedStandHack = true; + } this.viewer.updateAndRender(); Gdx.gl30.glDisable(GL30.GL_SCISSOR_TEST); @@ -516,12 +557,26 @@ public class WarsmashGdxMenuScreen implements CanvasProvider, InputProcessor, Sc public void resize(final int width, final int height) { this.tempRect.width = width; this.tempRect.height = height; + final float fourThirdsHeight = (this.tempRect.height * 4) / 3; + if (fourThirdsHeight < this.tempRect.width) { + final float dx = this.tempRect.width - fourThirdsHeight; + this.tempRect.width = fourThirdsHeight; + this.tempRect.x = dx / 2; + } + else { + final float threeFourthsWidth = (this.tempRect.width * 3) / 4; + if (threeFourthsWidth < this.tempRect.height) { + final float dy = this.tempRect.height - threeFourthsWidth; + this.tempRect.height = threeFourthsWidth; + this.tempRect.y = dy; + } + } this.cameraManager.camera.viewport(this.tempRect); // super.resize(width, height); this.uiViewport.update(width, height); - this.uiCamera.position.set(this.uiViewport.getMinWorldWidth() / 2, this.uiViewport.getMinWorldHeight() / 2, 0); + this.uiCamera.position.set(getMinWorldWidth() / 2, getMinWorldHeight() / 2, 0); this.menuUI.resize(); updateUIScene(); @@ -612,8 +667,8 @@ public class WarsmashGdxMenuScreen implements CanvasProvider, InputProcessor, Sc WarsmashGdxMenuScreen.this.cameraPositionTemp[2]); this.target.add(WarsmashGdxMenuScreen.this.cameraTargetTemp[0], WarsmashGdxMenuScreen.this.cameraTargetTemp[1], WarsmashGdxMenuScreen.this.cameraTargetTemp[2]); - this.camera.perspective(WarsmashGdxMenuScreen.this.modelCamera.fieldOfView * 0.75f, - Gdx.graphics.getWidth() / (float) Gdx.graphics.getHeight(), + this.camera.perspective(WarsmashGdxMenuScreen.this.modelCamera.fieldOfView * 0.6f, + this.camera.rect.width / this.camera.rect.height, WarsmashGdxMenuScreen.this.modelCamera.nearClippingPlane, WarsmashGdxMenuScreen.this.modelCamera.farClippingPlane); } @@ -635,20 +690,17 @@ public class WarsmashGdxMenuScreen implements CanvasProvider, InputProcessor, Sc @Override public boolean keyDown(final int keycode) { - // TODO Auto-generated method stub - return false; + return this.menuUI.keyDown(keycode); } @Override public boolean keyUp(final int keycode) { - // TODO Auto-generated method stub - return false; + return this.menuUI.keyUp(keycode); } @Override public boolean keyTyped(final char character) { - // TODO Auto-generated method stub - return false; + return this.menuUI.keyTyped(character); } @Override diff --git a/core/src/com/etheller/warsmash/parsers/fdf/GameSkin.java b/core/src/com/etheller/warsmash/parsers/fdf/GameSkin.java new file mode 100644 index 0000000..ccbcf44 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/fdf/GameSkin.java @@ -0,0 +1,22 @@ +package com.etheller.warsmash.parsers.fdf; + +import com.etheller.warsmash.units.DataTable; +import com.etheller.warsmash.units.Element; + +public class GameSkin { + private final Element skin; + private final DataTable skinsTable; + + public GameSkin(final Element skin, final DataTable skinsTable) { + this.skin = skin; + this.skinsTable = skinsTable; + } + + public Element getSkin() { + return this.skin; + } + + public DataTable getSkinsTable() { + return this.skinsTable; + } +} diff --git a/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java index 404020e..990bee2 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java @@ -34,9 +34,12 @@ import com.etheller.warsmash.parsers.fdf.datamodel.Vector2Definition; import com.etheller.warsmash.parsers.fdf.datamodel.Vector4Definition; import com.etheller.warsmash.parsers.fdf.frames.AbstractUIFrame; import com.etheller.warsmash.parsers.fdf.frames.BackdropFrame; +import com.etheller.warsmash.parsers.fdf.frames.ControlFrame; +import com.etheller.warsmash.parsers.fdf.frames.EditBoxFrame; import com.etheller.warsmash.parsers.fdf.frames.FilterModeTextureFrame; import com.etheller.warsmash.parsers.fdf.frames.GlueButtonFrame; import com.etheller.warsmash.parsers.fdf.frames.GlueTextButtonFrame; +import com.etheller.warsmash.parsers.fdf.frames.ListBoxFrame; import com.etheller.warsmash.parsers.fdf.frames.SetPoint; import com.etheller.warsmash.parsers.fdf.frames.SimpleFrame; import com.etheller.warsmash.parsers.fdf.frames.SimpleStatusBarFrame; @@ -52,6 +55,7 @@ import com.etheller.warsmash.util.StringBundle; import com.etheller.warsmash.viewer5.Scene; import com.etheller.warsmash.viewer5.handlers.AbstractMdxModelViewer; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; +import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.FocusableFrame; import com.hiveworkshop.rms.parsers.mdlx.MdlxLayer.FilterMode; public final class GameUI extends AbstractUIFrame implements UIFrame { @@ -77,11 +81,11 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { private final BitmapFont font20; private final DynamicFontGeneratorHolder dynamicFontGeneratorHolder; - public GameUI(final DataSource dataSource, final Element skin, final Viewport viewport, final Scene uiScene, + public GameUI(final DataSource dataSource, final GameSkin skin, final Viewport viewport, final Scene uiScene, final AbstractMdxModelViewer modelViewer, final int racialCommandIndex, final WTS mapStrings) { super("GameUI", null); this.dataSource = dataSource; - this.skin = skin; + this.skin = skin.getSkin(); this.viewport = viewport; this.uiScene = uiScene; this.modelViewer = modelViewer; @@ -95,7 +99,7 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { } this.templates = new FrameTemplateEnvironment(); - this.dynamicFontGeneratorHolder = new DynamicFontGeneratorHolder(this.modelViewer.dataSource, skin); + this.dynamicFontGeneratorHolder = new DynamicFontGeneratorHolder(this.modelViewer.dataSource, this.skin); this.fontGenerator = this.dynamicFontGeneratorHolder.getFontGenerator("MasterFont"); final FreeTypeFontParameter fontParam = new FreeTypeFontParameter(); fontParam.size = 32; @@ -104,32 +108,22 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { this.font20 = this.fontGenerator.generateFont(fontParam); this.fontParam = new FreeTypeFontParameter(); this.fdfCoordinateResolutionDummyViewport = new FitViewport(0.8f, 0.6f); - this.skinData = new DataTable(modelViewer.getWorldEditStrings()); - try { - try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\CommandFunc.txt")) { - this.skinData.readTXT(miscDataTxtStream, true); - } - try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\CommandStrings.txt")) { - this.skinData.readTXT(miscDataTxtStream, true); - } - if (this.dataSource.has("war3mapSkin.txt")) { - try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("war3mapSkin.txt")) { - this.skinData.readTXT(miscDataTxtStream, true); - } - } - } - catch (final IOException e) { - throw new RuntimeException(e); - } + this.skinData = skin.getSkinsTable(); this.errorStrings = this.skinData.get("Errors"); this.glyphLayout = new GlyphLayout(); this.mapStrings = mapStrings; } - public static Element loadSkin(final DataSource dataSource, final String skin) { + public static GameSkin loadSkin(final DataSource dataSource, final String skin) { final DataTable skinsTable = new DataTable(StringBundle.EMPTY); try (InputStream stream = dataSource.getResourceAsStream("UI\\war3skins.txt")) { skinsTable.readTXT(stream, true); + try (InputStream miscDataTxtStream = dataSource.getResourceAsStream("Units\\CommandFunc.txt")) { + skinsTable.readTXT(miscDataTxtStream, true); + } + try (InputStream miscDataTxtStream = dataSource.getResourceAsStream("Units\\CommandStrings.txt")) { + skinsTable.readTXT(miscDataTxtStream, true); + } } catch (final IOException e) { throw new RuntimeException(e); @@ -159,10 +153,10 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { userSkin.setField(key, customSkin.getField(key)); } } - return userSkin; + return new GameSkin(userSkin, skinsTable); } - public static Element loadSkin(final DataSource dataSource, final int skinIndex) { + public static GameSkin loadSkin(final DataSource dataSource, final int skinIndex) { final DataTable skinsTable = new DataTable(StringBundle.EMPTY); try (InputStream stream = dataSource.getResourceAsStream("UI\\war3skins.txt")) { skinsTable.readTXT(stream, true); @@ -201,7 +195,7 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { userSkin.setField(key, customSkin.getField(key)); } } - return userSkin; + return new GameSkin(userSkin, skinsTable); } public void loadTOCFile(final String tocFilePath) throws IOException { @@ -246,7 +240,9 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { public UIFrame createFrame(final String name, final UIFrame owner, final int priority, final int createContext) { final FrameDefinition frameDefinition = this.templates.getFrame(name); final UIFrame inflatedFrame = inflate(frameDefinition, owner, null, frameDefinition.has("DecorateFileNames")); - add(inflatedFrame); + if (owner == this) { + add(inflatedFrame); + } return inflatedFrame; } @@ -344,10 +340,6 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { if (this.skin.hasField(backgroundArt)) { backgroundArt = this.skin.getField(backgroundArt); } - else { - throw new IllegalStateException( - "Decorated file name lookup not available: " + backgroundArt); - } } } if (backgroundArt != null) { @@ -373,7 +365,7 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { final Float textLength = frameDefinition.getFloat("TextLength"); TextJustify justifyH = frameDefinition.getTextJustify("FontJustificationH"); if (justifyH == null) { - justifyH = TextJustify.CENTER; + justifyH = TextJustify.LEFT; } TextJustify justifyV = frameDefinition.getTextJustify("FontJustificationV"); if (justifyV == null) { @@ -402,7 +394,7 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { final FontDefinition font = frameDefinition.getFont("FrameFont"); final Float height = frameDefinition.getFloat("Height"); this.fontParam.size = (int) convertY(viewport2, - font == null ? (height == null ? 0.06f : height) : font.getFontSize()); + (font == null ? (height == null ? 0.06f : height) : font.getFontSize())); if (this.fontParam.size == 0) { this.fontParam.size = 24; } @@ -524,6 +516,78 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { } inflatedFrame = glueButtonFrame; } + else if ("EDITBOX".equals(frameDefinition.getFrameType())) { + final float editBorderSize = convertX(viewport2, frameDefinition.getFloat("EditBorderSize")); + final Vector4Definition editCursorColorDefinition = frameDefinition.getVector4("EditCursorColor"); + Color editCursorColor; + if (editCursorColorDefinition == null) { + editCursorColor = Color.WHITE; + } + else { + editCursorColor = new Color(editCursorColorDefinition.getX(), editCursorColorDefinition.getY(), + editCursorColorDefinition.getZ(), editCursorColorDefinition.getW()); + } + final EditBoxFrame editBoxFrame = new EditBoxFrame(frameDefinition.getName(), parent, editBorderSize, + editCursorColor); + // TODO: we should not need to put ourselves in this map 2x, but we do + // since there are nested inflate calls happening before the general case + // mapping + this.nameToFrame.put(frameDefinition.getName(), editBoxFrame); + final String controlBackdropKey = frameDefinition.getString("ControlBackdrop"); + final String editTextFrameKey = frameDefinition.getString("EditTextFrame"); + for (final FrameDefinition childDefinition : frameDefinition.getInnerFrames()) { + if (childDefinition.getName().equals(controlBackdropKey)) { + final UIFrame inflatedChild = inflate(childDefinition, editBoxFrame, frameDefinition, + inDecorateFileNames || childDefinition.has("DecorateFileNames")); + inflatedChild.setSetAllPoints(true); + editBoxFrame.setControlBackdrop(inflatedChild); + } + else if (childDefinition.getName().equals(editTextFrameKey)) { + final UIFrame inflatedChild = inflate(childDefinition, editBoxFrame, frameDefinition, + inDecorateFileNames || childDefinition.has("DecorateFileNames")); + final StringFrame editTextFrame = (StringFrame) inflatedChild; + inflatedChild.setSetAllPoints(true, editBorderSize); + editBoxFrame.setEditTextFrame(editTextFrame); + setText(editTextFrame, ""); + } + } + inflatedFrame = editBoxFrame; + } + else if ("CONTROL".equals(frameDefinition.getFrameType())) { + final ControlFrame controlFrame = new ControlFrame(frameDefinition.getName(), parent); + // TODO: we should not need to put ourselves in this map 2x, but we do + // since there are nested inflate calls happening before the general case + // mapping + this.nameToFrame.put(frameDefinition.getName(), controlFrame); + final String controlBackdropKey = frameDefinition.getString("ControlBackdrop"); + for (final FrameDefinition childDefinition : frameDefinition.getInnerFrames()) { + if (childDefinition.getName().equals(controlBackdropKey)) { + final UIFrame inflatedChild = inflate(childDefinition, controlFrame, frameDefinition, + inDecorateFileNames || childDefinition.has("DecorateFileNames")); + inflatedChild.setSetAllPoints(true); + controlFrame.setControlBackdrop(inflatedChild); + } + } + inflatedFrame = controlFrame; + } + else if ("LISTBOX".equals(frameDefinition.getFrameType())) { + // TODO advanced components here + final ListBoxFrame controlFrame = new ListBoxFrame(frameDefinition.getName(), parent, viewport2); + // TODO: we should not need to put ourselves in this map 2x, but we do + // since there are nested inflate calls happening before the general case + // mapping + this.nameToFrame.put(frameDefinition.getName(), controlFrame); + final String controlBackdropKey = frameDefinition.getString("ControlBackdrop"); + for (final FrameDefinition childDefinition : frameDefinition.getInnerFrames()) { + if (childDefinition.getName().equals(controlBackdropKey)) { + final UIFrame inflatedChild = inflate(childDefinition, controlFrame, frameDefinition, + inDecorateFileNames || childDefinition.has("DecorateFileNames")); + inflatedChild.setSetAllPoints(true); + controlFrame.setControlBackdrop(inflatedChild); + } + } + inflatedFrame = controlFrame; + } else if ("HIGHLIGHT".equals(frameDefinition.getFrameType())) { final String highlightType = frameDefinition.getString("HighlightType"); if (!"FILETEXTURE".equals(highlightType)) { @@ -542,6 +606,7 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { } else if ("BACKDROP".equals(frameDefinition.getFrameType())) { final boolean tileBackground = frameDefinition.has("BackdropTileBackground"); + final boolean mirrored = frameDefinition.has("BackdropMirrored"); String backgroundString = frameDefinition.getString("BackdropBackground"); String cornerFlagsString = frameDefinition.getString("BackdropCornerFlags"); if (cornerFlagsString == null) { @@ -580,7 +645,7 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { final BackdropFrame backdropFrame = new BackdropFrame(frameDefinition.getName(), parent, decorateFileNames, tileBackground, background, cornerFlags, cornerSize, backgroundSize, - backgroundInsets, edgeFile); + backgroundInsets, edgeFile, mirrored); this.nameToFrame.put(frameDefinition.getName(), backdropFrame); for (final FrameDefinition childDefinition : frameDefinition.getInnerFrames()) { backdropFrame.add(inflate(childDefinition, backdropFrame, frameDefinition, decorateFileNames)); @@ -716,9 +781,15 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { final String inherits, final int createContext) { // TODO idk what inherits is doing yet, and I didn't implement createContext yet // even though it looked like just mapping/indexing on int + final FrameDefinition baseTemplateDef = this.templates.getFrame(name); final FrameDefinition frameDefinition = new FrameDefinition(FrameClass.Frame, typeName, name); + if (baseTemplateDef != null) { + frameDefinition.inheritFrom(baseTemplateDef, "WITHCHILDREN".equals(inherits)); + } final UIFrame inflatedFrame = inflate(frameDefinition, owner, null, frameDefinition.has("DecorateFileNames")); - add(inflatedFrame); + if (this == owner) { + add(inflatedFrame); + } return inflatedFrame; } @@ -830,4 +901,9 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { public void dispose() { this.dynamicFontGeneratorHolder.dispose(); } + + public FocusableFrame getNextFocusFrame() { + // TODO to support tabbing thru menus and stuff, we will have to implement this + return null; + } } diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java index ddd3c15..a89d357 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java @@ -47,6 +47,28 @@ public abstract class AbstractRenderableFrame implements UIFrame { } } + @Override + public void setSetAllPoints(final boolean setAllPoints, final float inset) { + this.framePointToAssignment.put(FramePoint.TOPLEFT, + new SetPoint(FramePoint.TOPLEFT, this.parent, FramePoint.TOPLEFT, inset, -inset)); + this.framePointToAssignment.put(FramePoint.LEFT, + new SetPoint(FramePoint.LEFT, this.parent, FramePoint.LEFT, inset, 0)); + this.framePointToAssignment.put(FramePoint.BOTTOMLEFT, + new SetPoint(FramePoint.BOTTOMLEFT, this.parent, FramePoint.BOTTOMLEFT, inset, inset)); + this.framePointToAssignment.put(FramePoint.BOTTOM, + new SetPoint(FramePoint.BOTTOM, this.parent, FramePoint.BOTTOM, 0, inset)); + this.framePointToAssignment.put(FramePoint.BOTTOMRIGHT, + new SetPoint(FramePoint.BOTTOMRIGHT, this.parent, FramePoint.BOTTOMRIGHT, -inset, inset)); + this.framePointToAssignment.put(FramePoint.RIGHT, + new SetPoint(FramePoint.RIGHT, this.parent, FramePoint.RIGHT, -inset, 0)); + this.framePointToAssignment.put(FramePoint.TOPRIGHT, + new SetPoint(FramePoint.TOPRIGHT, this.parent, FramePoint.TOPRIGHT, -inset, -inset)); + this.framePointToAssignment.put(FramePoint.TOP, + new SetPoint(FramePoint.TOP, this.parent, FramePoint.TOP, 0, -inset)); + this.framePointToAssignment.put(FramePoint.CENTER, + new SetPoint(FramePoint.CENTER, this.parent, FramePoint.CENTER, 0, 0)); + } + @Override public void setWidth(final float width) { this.assignedWidth = width; @@ -296,6 +318,7 @@ public abstract class AbstractRenderableFrame implements UIFrame { protected abstract void innerPositionBounds(GameUI gameUI, final Viewport viewport); + @Override public boolean isVisible() { return this.visible; } @@ -320,6 +343,22 @@ public abstract class AbstractRenderableFrame implements UIFrame { } } + @Override + public UIFrame getParent() { + return this.parent; + } + + @Override + public boolean isVisibleOnScreen() { + boolean visibleOnScreen = this.visible; + UIFrame ancestor = this.parent; + while (visibleOnScreen && (ancestor != null)) { + visibleOnScreen &= ancestor.isVisible(); + ancestor = ancestor.getParent(); + } + return visibleOnScreen; + } + protected abstract void internalRender(SpriteBatch batch, BitmapFont baseFont, GlyphLayout glyphLayout); @Override diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractUIFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractUIFrame.java index ff8756f..5727e28 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractUIFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractUIFrame.java @@ -30,6 +30,11 @@ public abstract class AbstractUIFrame extends AbstractRenderableFrame implements super(name, parent); } + @Override + public void setVisible(final boolean visible) { + super.setVisible(visible); + } + @Override protected void internalRender(final SpriteBatch batch, final BitmapFont baseFont, final GlyphLayout glyphLayout) { for (final UIFrame childFrame : this.childFrames) { diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/BackdropFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/BackdropFrame.java index 2b05c29..5d1f153 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/BackdropFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/BackdropFrame.java @@ -22,14 +22,15 @@ public class BackdropFrame extends AbstractUIFrame { private final float edgeUVWidth; private final float edgeFileHeight; private final float edgeUVHeight; + private final boolean mirrored; public BackdropFrame(final String name, final UIFrame parent, final boolean decorateFileNames, final boolean tileBackground, final Texture background, final EnumSet cornerFlags, final float cornerSize, final float backgroundSize, final Vector4Definition backgroundInsets, - final Texture edgeFile) { + final Texture edgeFile, final boolean mirrored) { super(name, parent); this.decorateFileNames = decorateFileNames; - this.tileBackground = tileBackground; + this.tileBackground = tileBackground && (backgroundSize > 0); this.background = background; this.cornerFlags = cornerFlags; this.cornerSize = cornerSize; @@ -40,6 +41,7 @@ public class BackdropFrame extends AbstractUIFrame { this.edgeFileHeight = edgeFile == null ? 0.0f : edgeFile.getHeight(); this.edgeUVWidth = 1f / 8f; this.edgeUVHeight = 1f; + this.mirrored = mirrored; } @Override @@ -83,7 +85,13 @@ public class BackdropFrame extends AbstractUIFrame { backgroundHeightRemainderRatio); } else { - batch.draw(this.background, backgroundX, backgroundY, backgroundWidth, backgroundHeight); + if (this.mirrored) { + batch.draw(this.background, backgroundX, backgroundY, backgroundWidth, backgroundHeight, 0, 0, + this.background.getWidth(), this.background.getHeight(), true, false); + } + else { + batch.draw(this.background, backgroundX, backgroundY, backgroundWidth, backgroundHeight); + } } } if (this.edgeFile != null) { @@ -181,4 +189,7 @@ public class BackdropFrame extends AbstractUIFrame { super.internalRender(batch, baseFont, glyphLayout); } + public float getCornerSize() { + return this.cornerSize; + } } diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/ControlFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/ControlFrame.java new file mode 100644 index 0000000..dd6a309 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/ControlFrame.java @@ -0,0 +1,35 @@ +package com.etheller.warsmash.parsers.fdf.frames; + +import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.g2d.GlyphLayout; +import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.badlogic.gdx.utils.viewport.Viewport; +import com.etheller.warsmash.parsers.fdf.GameUI; + +public class ControlFrame extends AbstractRenderableFrame { + + private UIFrame controlBackdrop; + + public ControlFrame(final String name, final UIFrame parent) { + super(name, parent); + } + + public void setControlBackdrop(final UIFrame controlBackdrop) { + this.controlBackdrop = controlBackdrop; + } + + public UIFrame getControlBackdrop() { + return this.controlBackdrop; + } + + @Override + protected void innerPositionBounds(final GameUI gameUI, final Viewport viewport) { + this.controlBackdrop.positionBounds(gameUI, viewport); + } + + @Override + protected void internalRender(final SpriteBatch batch, final BitmapFont baseFont, final GlyphLayout glyphLayout) { + this.controlBackdrop.render(batch, baseFont, glyphLayout); + } + +} diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/EditBoxFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/EditBoxFrame.java new file mode 100644 index 0000000..5ff574e --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/EditBoxFrame.java @@ -0,0 +1,164 @@ +package com.etheller.warsmash.parsers.fdf.frames; + +import com.badlogic.gdx.Input; +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.g2d.GlyphLayout; +import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.badlogic.gdx.utils.TimeUtils; +import com.badlogic.gdx.utils.viewport.Viewport; +import com.etheller.warsmash.parsers.fdf.GameUI; +import com.etheller.warsmash.parsers.fdf.datamodel.FramePoint; +import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.FocusableFrame; + +public class EditBoxFrame extends AbstractRenderableFrame implements FocusableFrame { + + private UIFrame controlBackdrop; + private final float editBorderSize; + private final Color editCursorColor; + private StringFrame editTextFrame; + private boolean focused = false; + private int cursorIndex; + + // TODO design in such a way that references to these are not held !! very bad + // code design here + private GameUI gameUI; + private Viewport viewport; + private GlyphLayout glyphLayout; + + public EditBoxFrame(final String name, final UIFrame parent, final float editBorderSize, + final Color editCursorColor) { + super(name, parent); + this.editBorderSize = editBorderSize; + this.editCursorColor = editCursorColor; + } + + public void setControlBackdrop(final UIFrame controlBackdrop) { + this.controlBackdrop = controlBackdrop; + } + + @Override + protected void innerPositionBounds(final GameUI gameUI, final Viewport viewport) { + this.gameUI = gameUI; + this.viewport = viewport; + this.controlBackdrop.positionBounds(gameUI, viewport); + this.editTextFrame.positionBounds(gameUI, viewport); + } + + @Override + protected void internalRender(final SpriteBatch batch, final BitmapFont baseFont, final GlyphLayout glyphLayout) { + this.glyphLayout = glyphLayout; + this.controlBackdrop.render(batch, baseFont, glyphLayout); + this.editTextFrame.render(batch, baseFont, glyphLayout); + if (this.focused) { + final long time = TimeUtils.millis(); + if ((time % 500) > 250) { + final BitmapFont frameFont = this.editTextFrame.getFrameFont(); + frameFont.setColor(this.editCursorColor); + final int cursorRenderPosition = Math.min(this.cursorIndex, this.editTextFrame.getText().length()); + this.cursorIndex = cursorRenderPosition; + glyphLayout.setText(frameFont, this.editTextFrame.getText().substring(0, cursorRenderPosition)); + final float cursorXOffset = glyphLayout.width; + glyphLayout.setText(frameFont, "|"); + frameFont.draw(batch, "|", + (this.editTextFrame.getFramePointX(FramePoint.LEFT) + cursorXOffset) - (glyphLayout.width / 2), + this.editTextFrame.getFramePointY(FramePoint.LEFT) + ((frameFont.getCapHeight()) / 2)); + } + } + } + + public void setEditTextFrame(final StringFrame editTextFrame) { + this.editTextFrame = editTextFrame; + } + + @Override + public boolean isFocusable() { + return true; + } + + @Override + public void onFocusGained() { + this.focused = true; + } + + @Override + public void onFocusLost() { + this.focused = false; + } + + @Override + public boolean keyDown(final int keycode) { + switch (keycode) { + case Input.Keys.LEFT: { + this.cursorIndex = Math.max(0, this.cursorIndex - 1); + break; + } + case Input.Keys.RIGHT: { + final String text = this.editTextFrame.getText(); + this.cursorIndex = Math.min(text.length(), this.cursorIndex + 1); + break; + } + case Input.Keys.BACKSPACE: { + final String prevText = this.editTextFrame.getText(); + final int prevTextLength = prevText.length(); + final int cursorIndex = Math.min(this.cursorIndex, prevTextLength); + if (cursorIndex >= 1) { + this.cursorIndex = cursorIndex - 1; + final String newText = prevText.substring(0, cursorIndex - 1) + + prevText.substring(cursorIndex, prevTextLength); + this.editTextFrame.setText(newText, this.gameUI, this.viewport); + } + break; + } + } + return false; + } + + @Override + public boolean keyUp(final int keycode) { + return false; + } + + @Override + public boolean keyTyped(final char character) { + if (Character.isAlphabetic(character) || Character.isDigit(character)) { + final String prevText = this.editTextFrame.getText(); + final int prevTextLength = prevText.length(); + final int cursorIndex = Math.min(this.cursorIndex, prevTextLength); + final String newText = prevText.substring(0, cursorIndex) + character + + prevText.substring(cursorIndex, prevTextLength); + this.editTextFrame.setText(newText, this.gameUI, this.viewport); + this.cursorIndex++; + } + return false; + } + + @Override + public UIFrame touchDown(final float screenX, final float screenY, final int button) { + if (isVisible() && this.renderBounds.contains(screenX, screenY)) { + + final String text = this.editTextFrame.getText(); + int indexFound = -1; + final float fpXOfEditText = this.editTextFrame.getFramePointX(FramePoint.LEFT); + float lastX = 0; + for (int i = 0; i < text.length(); i++) { + final BitmapFont frameFont = this.editTextFrame.getFrameFont(); + this.glyphLayout.setText(frameFont, this.editTextFrame.getText().substring(0, i)); + final float x = fpXOfEditText + this.glyphLayout.width; + if (((x + lastX) / 2) > screenX) { + indexFound = i - 1; + break; + } + lastX = x; + } + if (indexFound == -1) { + indexFound = text.length(); + } + this.cursorIndex = indexFound; + + return this; + } + return super.touchDown(screenX, screenY, button); + } + +} diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/ListBoxFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/ListBoxFrame.java new file mode 100644 index 0000000..50d250e --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/ListBoxFrame.java @@ -0,0 +1,110 @@ +package com.etheller.warsmash.parsers.fdf.frames; + +import java.util.ArrayList; +import java.util.List; + +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.g2d.GlyphLayout; +import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.badlogic.gdx.utils.viewport.Viewport; +import com.etheller.warsmash.parsers.fdf.GameUI; +import com.etheller.warsmash.parsers.fdf.datamodel.FramePoint; +import com.etheller.warsmash.parsers.fdf.datamodel.TextJustify; + +public class ListBoxFrame extends ControlFrame { + private final List listItems = new ArrayList<>(); + private final List stringFrames = new ArrayList<>(); + private BitmapFont frameFont; + private float listBoxBorder; + + public ListBoxFrame(final String name, final UIFrame parent, final Viewport viewport) { + super(name, parent); + this.listBoxBorder = GameUI.convertX(viewport, 0.01f); + } + + public void setListBoxBorder(final float listBoxBorder) { + this.listBoxBorder = listBoxBorder; + } + + public float getListBoxBorder() { + return this.listBoxBorder; + } + + public void setFrameFont(final BitmapFont frameFont) { + this.frameFont = frameFont; + } + + @Override + protected void innerPositionBounds(final GameUI gameUI, final Viewport viewport) { + super.innerPositionBounds(gameUI, viewport); + updateUI(gameUI, viewport); + } + + private void positionChildren(final GameUI gameUI, final Viewport viewport) { + for (final SingleStringFrame frame : this.stringFrames) { + frame.positionBounds(gameUI, viewport); + } + } + + @Override + protected void internalRender(final SpriteBatch batch, final BitmapFont baseFont, final GlyphLayout glyphLayout) { + super.internalRender(batch, baseFont, glyphLayout); + for (final SingleStringFrame frame : this.stringFrames) { + frame.render(batch, baseFont, glyphLayout); + } + } + + public void addItem(final String item, final GameUI gameUI, final Viewport viewport) { + this.listItems.add(item); +// updateUI(gameUI, viewport); + } + + public void setItems(final List items, final GameUI gameUI, final Viewport viewport) { + this.listItems.clear(); + this.listItems.addAll(items); +// updateUI(gameUI, viewport); + } + + public void removeItem(final String item, final GameUI gameUI, final Viewport viewport) { + this.listItems.remove(item); +// updateUI(gameUI, viewport); + } + + public void removeItem(final int index, final GameUI gameUI, final Viewport viewport) { + this.listItems.remove(index); +// updateUI(gameUI, viewport); + } + + private void updateUI(final GameUI gameUI, final Viewport viewport) { + this.stringFrames.clear(); + SingleStringFrame prev = null; + int i = 0; + for (final String string : this.listItems) { + final SingleStringFrame stringFrame = new SingleStringFrame("LISTY" + i++, this, Color.WHITE, + TextJustify.LEFT, TextJustify.MIDDLE, this.frameFont); + stringFrame.setText(string); + stringFrame.setWidth(this.renderBounds.width); + stringFrame.setHeight(this.frameFont.getLineHeight()); + if (prev != null) { + stringFrame.addSetPoint(new SetPoint(FramePoint.TOPLEFT, prev, FramePoint.BOTTOMLEFT, 0, 0)); + } + else { + stringFrame.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this, FramePoint.TOPLEFT, this.listBoxBorder, + -this.listBoxBorder)); + } + this.stringFrames.add(stringFrame); + prev = stringFrame; + } + positionChildren(gameUI, viewport); + } + + @Override + public UIFrame touchDown(final float screenX, final float screenY, final int button) { + if (isVisible() && this.renderBounds.contains(screenX, screenY)) { + + return this; + } + return super.touchDown(screenX, screenY, button); + } +} diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/StringFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/StringFrame.java index 24cef0f..72fe1ad 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/StringFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/StringFrame.java @@ -7,6 +7,8 @@ import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.GlyphLayout; import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.badlogic.gdx.graphics.glutils.ShapeRenderer; +import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType; import com.badlogic.gdx.utils.viewport.Viewport; import com.etheller.warsmash.parsers.fdf.GameUI; import com.etheller.warsmash.parsers.fdf.datamodel.AnchorDefinition; @@ -14,6 +16,7 @@ import com.etheller.warsmash.parsers.fdf.datamodel.FramePoint; import com.etheller.warsmash.parsers.fdf.datamodel.TextJustify; public class StringFrame extends AbstractRenderableFrame { + private static final boolean DEBUG = false; private final List internalFrames = new ArrayList<>(); private Color color; private String text = "Default string"; @@ -27,6 +30,8 @@ public class StringFrame extends AbstractRenderableFrame { private final SimpleFrame internalFramesContainer; private float predictedViewportHeight; + static ShapeRenderer shapeRenderer = new ShapeRenderer(); + public StringFrame(final String name, final UIFrame parent, final Color color, final TextJustify justifyH, final TextJustify justifyV, final BitmapFont frameFont, final String text) { super(name, parent); @@ -38,6 +43,10 @@ public class StringFrame extends AbstractRenderableFrame { this.internalFramesContainer = new SimpleFrame(null, this); } + public String getText() { + return this.text; + } + public void setText(final String text, final GameUI gameUI, final Viewport viewport) { if (text == null) { throw new IllegalArgumentException(); @@ -77,11 +86,32 @@ public class StringFrame extends AbstractRenderableFrame { @Override protected void internalRender(final SpriteBatch batch, final BitmapFont baseFont, final GlyphLayout glyphLayout) { this.internalFramesContainer.render(batch, baseFont, glyphLayout); + + if (DEBUG) { + batch.end(); + shapeRenderer.setProjectionMatrix(batch.getProjectionMatrix()); + shapeRenderer.setColor(1f, 1f, 1f, 1f); + shapeRenderer.begin(ShapeType.Line); + shapeRenderer.rect(this.renderBounds.x, this.renderBounds.y, this.renderBounds.width, + this.renderBounds.height); + + shapeRenderer.end(); + + batch.begin(); + } + } + + @Override + public void positionBounds(final GameUI gameUI, final Viewport viewport) { + createInternalFrames(gameUI.getGlyphLayout()); + if (this.renderBounds.height == 0) { + this.renderBounds.height = getPredictedViewportHeight(); + } + super.positionBounds(gameUI, viewport); } @Override protected void innerPositionBounds(final GameUI gameUI, final Viewport viewport) { - createInternalFrames(gameUI.getGlyphLayout()); this.internalFramesContainer.positionBounds(gameUI, viewport); } @@ -436,4 +466,7 @@ public class StringFrame extends AbstractRenderableFrame { return this.predictedViewportHeight; } + public BitmapFont getFrameFont() { + return this.frameFont; + } } diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/UIFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/UIFrame.java index 003522a..3bb9f8c 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/UIFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/UIFrame.java @@ -31,8 +31,16 @@ public interface UIFrame { void setSetAllPoints(boolean setAllPoints); + void setSetAllPoints(boolean setAllPoints, float inset); + void setVisible(boolean visible); + UIFrame getParent(); + + boolean isVisible(); + + boolean isVisibleOnScreen(); + UIFrame touchDown(float screenX, float screenY, int button); UIFrame touchUp(float screenX, float screenY, int button); diff --git a/core/src/com/etheller/warsmash/parsers/jass/Jass2.java b/core/src/com/etheller/warsmash/parsers/jass/Jass2.java index 5d1fe78..e08320d 100644 --- a/core/src/com/etheller/warsmash/parsers/jass/Jass2.java +++ b/core/src/com/etheller/warsmash/parsers/jass/Jass2.java @@ -35,6 +35,7 @@ import com.etheller.interpreter.ast.value.visitor.RealJassValueVisitor; import com.etheller.interpreter.ast.value.visitor.StringJassValueVisitor; import com.etheller.interpreter.ast.visitors.JassProgramVisitor; import com.etheller.warsmash.datasources.DataSource; +import com.etheller.warsmash.parsers.fdf.GameSkin; import com.etheller.warsmash.parsers.fdf.GameUI; import com.etheller.warsmash.parsers.fdf.datamodel.AnchorDefinition; import com.etheller.warsmash.parsers.fdf.datamodel.FramePoint; @@ -227,11 +228,11 @@ public class Jass2 { public JassValue call(final List arguments, final GlobalScope globalScope, final TriggerExecutionScope triggerScope) { final String skinArg = arguments.get(0).visit(StringJassValueVisitor.getInstance()); - final Element skin = GameUI.loadSkin(dataSource, skinArg); + final GameSkin skin = GameUI.loadSkin(dataSource, skinArg); final GameUI gameUI = new GameUI(dataSource, skin, uiViewport, uiScene, war3MapViewer, 0, war3MapViewer.getAllObjectData().getWts()); JUIEnvironment.this.gameUI = gameUI; - JUIEnvironment.this.skin = skin; + JUIEnvironment.this.skin = skin.getSkin(); rootFrameListener.onCreate(gameUI); return new HandleJassValue(frameHandleType, gameUI); } diff --git a/core/src/com/etheller/warsmash/util/WarsmashConstants.java b/core/src/com/etheller/warsmash/util/WarsmashConstants.java index 694d5b2..6b2e4ab 100644 --- a/core/src/com/etheller/warsmash/util/WarsmashConstants.java +++ b/core/src/com/etheller/warsmash/util/WarsmashConstants.java @@ -6,7 +6,7 @@ public class WarsmashConstants { * With version, we use 0 for RoC, 1 for TFT emulation, and probably 2+ or * whatever for custom mods and other stuff */ - public static int GAME_VERSION = 0; + public static int GAME_VERSION = 1; public static final int REPLACEABLE_TEXTURE_LIMIT = 64; public static final float SIMULATION_STEP_TIME = 1 / 20f; public static final int PORT_NUMBER = 6115; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java index 20020e4..eed8296 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java @@ -715,9 +715,9 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.tooltipUberTipText = (StringFrame) this.rootFrame.getFrameByName("SmashUberTipText", 0); this.tooltipUberTipText.setWidth(GameUI.convertX(this.uiViewport, 0.274f)); this.uberTipNoResourcesSetPoint = new SetPoint(FramePoint.TOPLEFT, this.tooltipText, FramePoint.BOTTOMLEFT, 0, - GameUI.convertY(this.uiViewport, -0.014f)); + GameUI.convertY(this.uiViewport, -0.004f)); this.uberTipWithResourcesSetPoint = new SetPoint(FramePoint.TOPLEFT, this.tooltipText, FramePoint.BOTTOMLEFT, 0, - GameUI.convertY(this.uiViewport, -0.024f)); + GameUI.convertY(this.uiViewport, -0.014f)); this.tooltipUberTipText.addSetPoint(this.uberTipNoResourcesSetPoint); this.tooltipResourceFrames = new UIFrame[ResourceType.VALUES.length]; this.tooltipResourceIconFrames = new TextureFrame[ResourceType.VALUES.length]; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MenuUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MenuUI.java index 58ad74f..b33da59 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MenuUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MenuUI.java @@ -10,35 +10,43 @@ import com.badlogic.gdx.graphics.g2d.GlyphLayout; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.utils.viewport.ExtendViewport; -import com.etheller.warsmash.WarsmashGdxMapScreen; +import com.badlogic.gdx.utils.viewport.Viewport; +import com.etheller.warsmash.SingleModelScreen; import com.etheller.warsmash.WarsmashGdxMenuScreen; import com.etheller.warsmash.WarsmashGdxMultiScreenGame; import com.etheller.warsmash.datasources.DataSource; import com.etheller.warsmash.parsers.fdf.GameUI; import com.etheller.warsmash.parsers.fdf.datamodel.FramePoint; +import com.etheller.warsmash.parsers.fdf.frames.EditBoxFrame; import com.etheller.warsmash.parsers.fdf.frames.GlueButtonFrame; import com.etheller.warsmash.parsers.fdf.frames.GlueTextButtonFrame; +import com.etheller.warsmash.parsers.fdf.frames.ListBoxFrame; import com.etheller.warsmash.parsers.fdf.frames.SetPoint; +import com.etheller.warsmash.parsers.fdf.frames.SimpleFrame; import com.etheller.warsmash.parsers.fdf.frames.SpriteFrame; import com.etheller.warsmash.parsers.fdf.frames.StringFrame; import com.etheller.warsmash.parsers.fdf.frames.UIFrame; import com.etheller.warsmash.parsers.jass.Jass2.RootFrameListener; import com.etheller.warsmash.units.DataTable; +import com.etheller.warsmash.units.Element; import com.etheller.warsmash.units.custom.WTS; +import com.etheller.warsmash.util.StringBundle; import com.etheller.warsmash.util.WarsmashConstants; import com.etheller.warsmash.util.WorldEditStrings; import com.etheller.warsmash.viewer5.Scene; import com.etheller.warsmash.viewer5.handlers.mdx.MdxViewer; import com.etheller.warsmash.viewer5.handlers.w3x.UnitSound; import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.ClickableFrame; +import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.FocusableFrame; import com.etheller.warsmash.viewer5.handlers.w3x.ui.sound.KeyedSounds; public class MenuUI { private static final Vector2 screenCoordsVector = new Vector2(); + private static boolean ENABLE_NOT_YET_IMPLEMENTED_BUTTONS = false; private final DataSource dataSource; private final Scene uiScene; - private final ExtendViewport uiViewport; + private final Viewport uiViewport; private final MdxViewer viewer; private final RootFrameListener rootFrameListener; private final float widthRatioCorrection; @@ -48,6 +56,7 @@ public class MenuUI { private ClickableFrame mouseDownUIFrame; private ClickableFrame mouseOverUIFrame; + private FocusableFrame focusUIFrame; private UIFrame mainMenuFrame; @@ -74,8 +83,12 @@ public class MenuUI { private MenuState menuState; private UIFrame singlePlayerMenu; + private UIFrame singlePlayerMainPanel; + + private UIFrame skirmish; private UIFrame profilePanel; + private EditBoxFrame newProfileEditBox; private GlueButtonFrame profileButton; private GlueTextButtonFrame campaignButton; @@ -83,28 +96,61 @@ public class MenuUI { private GlueTextButtonFrame viewReplayButton; private GlueTextButtonFrame customCampaignButton; private GlueTextButtonFrame skirmishButton; - private GlueTextButtonFrame cancelButton; + private GlueTextButtonFrame singlePlayerCancelButton; private GlueButtonFrame editionButton; + private GlueTextButtonFrame skirmishCancelButton; + private final WarsmashGdxMultiScreenGame screenManager; private final DataTable warsmashIni; private UnitSound glueScreenLoop; - public MenuUI(final DataSource dataSource, final ExtendViewport uiViewport, final Scene uiScene, - final MdxViewer viewer, final WarsmashGdxMultiScreenGame screenManager, final DataTable warsmashIni, - final RootFrameListener rootFrameListener) { + private SpriteFrame warcraftIIILogo; + // Campaign + private UIFrame campaignMenu; + private SpriteFrame campaignFade; + private GlueTextButtonFrame campaignBackButton; + private UIFrame missionSelectFrame; + private UIFrame campaignSelectFrame; + private final DataTable campaignStrings; + private SpriteFrame campaignWarcraftIIILogo; + private final SingleModelScreen menuScreen; + + private String currentCampaignBackgroundModel; + private String currentCampaignAmbientSound; + private int currentCampaignCursor; + private String[] campaignList; + private Element[] campaignDatas; + private UnitSound mainMenuGlueScreenLoop; + private GlueTextButtonFrame addProfileButton; + private GlueTextButtonFrame deleteProfileButton; + private GlueTextButtonFrame selectProfileButton; + + public MenuUI(final DataSource dataSource, final Viewport uiViewport, final Scene uiScene, final MdxViewer viewer, + final WarsmashGdxMultiScreenGame screenManager, final SingleModelScreen menuScreen, + final DataTable warsmashIni, final RootFrameListener rootFrameListener) { this.dataSource = dataSource; this.uiViewport = uiViewport; this.uiScene = uiScene; this.viewer = viewer; this.screenManager = screenManager; + this.menuScreen = menuScreen; this.warsmashIni = warsmashIni; this.rootFrameListener = rootFrameListener; - this.widthRatioCorrection = this.uiViewport.getMinWorldWidth() / 1600f; - this.heightRatioCorrection = this.uiViewport.getMinWorldHeight() / 1200f; + this.widthRatioCorrection = getMinWorldWidth() / 1600f; + this.heightRatioCorrection = getMinWorldHeight() / 1200f; + + this.campaignStrings = new DataTable(StringBundle.EMPTY); + try (InputStream campaignStringStream = dataSource.getResourceAsStream( + "UI\\CampaignStrings" + (WarsmashConstants.GAME_VERSION == 1 ? "_exp" : "") + ".txt")) { + this.campaignStrings.readTXT(campaignStringStream, true); + } + catch (final IOException e) { + throw new RuntimeException(e); + } } public float getHeightRatioCorrection() { @@ -138,13 +184,13 @@ public class MenuUI { // Create main menu this.mainMenuFrame = this.rootFrame.createFrame("MainMenuFrame", this.rootFrame, 0, 0); - this.mainMenuFrame.setVisible(false); - final SpriteFrame warcraftIIILogo = (SpriteFrame) this.rootFrame.getFrameByName("WarCraftIIILogo", 0); - this.rootFrame.setSpriteFrameModel(warcraftIIILogo, + this.warcraftIIILogo = (SpriteFrame) this.rootFrame.getFrameByName("WarCraftIIILogo", 0); + this.rootFrame.setSpriteFrameModel(this.warcraftIIILogo, this.rootFrame.getSkinField("MainMenuLogo_V" + WarsmashConstants.GAME_VERSION)); - warcraftIIILogo.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.mainMenuFrame, FramePoint.TOPLEFT, + this.warcraftIIILogo.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.mainMenuFrame, FramePoint.TOPLEFT, GameUI.convertX(this.uiViewport, 0.13f), GameUI.convertY(this.uiViewport, -0.08f))); + setMainMenuVisible(false); this.rootFrame.getFrameByName("RealmSelect", 0).setVisible(false); this.glueSpriteLayerTopRight = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", @@ -170,17 +216,7 @@ public class MenuUI { this.cursorFrame.setZDepth(-1.0f); Gdx.input.setCursorCatched(true); - // Create single player - this.singlePlayerMenu = this.rootFrame.createFrame("SinglePlayerMenu", this.rootFrame, 0, 0); - this.singlePlayerMenu.setVisible(false); - - this.profilePanel = this.rootFrame.getFrameByName("ProfilePanel", 0); - this.profilePanel.setVisible(false); - - // position all - this.rootFrame.positionBounds(this.rootFrame, this.uiViewport); - - // Main Menu + // Main Menu interactivity this.singlePlayerButton = (GlueTextButtonFrame) this.rootFrame.getFrameByName("SinglePlayerButton", 0); this.battleNetButton = (GlueTextButtonFrame) this.rootFrame.getFrameByName("BattleNetButton", 0); this.realmButton = (GlueButtonFrame) this.rootFrame.getFrameByName("RealmButton", 0); @@ -197,7 +233,7 @@ public class MenuUI { WarsmashConstants.GAME_VERSION = (WarsmashConstants.GAME_VERSION == 1 ? 0 : 1); MenuUI.this.glueSpriteLayerTopLeft.setSequence("MainMenu Death"); MenuUI.this.glueSpriteLayerTopRight.setSequence("MainMenu Death"); - MenuUI.this.mainMenuFrame.setVisible(false); + setMainMenuVisible(false); MenuUI.this.menuState = MenuState.RESTARTING; } }); @@ -214,7 +250,7 @@ public class MenuUI { public void run() { MenuUI.this.glueSpriteLayerTopLeft.setSequence("MainMenu Death"); MenuUI.this.glueSpriteLayerTopRight.setSequence("MainMenu Death"); - MenuUI.this.mainMenuFrame.setVisible(false); + setMainMenuVisible(false); MenuUI.this.menuState = MenuState.QUITTING; } }); @@ -224,12 +260,37 @@ public class MenuUI { public void run() { MenuUI.this.glueSpriteLayerTopLeft.setSequence("MainMenu Death"); MenuUI.this.glueSpriteLayerTopRight.setSequence("MainMenu Death"); - MenuUI.this.mainMenuFrame.setVisible(false); + setMainMenuVisible(false); MenuUI.this.menuState = MenuState.GOING_TO_SINGLE_PLAYER; } }); - // Single Player + // Create single player + this.singlePlayerMenu = this.rootFrame.createFrame("SinglePlayerMenu", this.rootFrame, 0, 0); + this.singlePlayerMenu.setVisible(false); + + this.profilePanel = this.rootFrame.getFrameByName("ProfilePanel", 0); + this.profilePanel.setVisible(false); + + this.newProfileEditBox = (EditBoxFrame) this.rootFrame.getFrameByName("NewProfileEditBox", 0); + final StringFrame profileListText = (StringFrame) this.rootFrame.getFrameByName("ProfileListText", 0); + final SimpleFrame profileListContainer = (SimpleFrame) this.rootFrame.getFrameByName("ProfileListContainer", 0); + final ListBoxFrame profileListBox = (ListBoxFrame) this.rootFrame.createFrameByType("LISTBOX", "ListBoxWar3", + profileListContainer, "WITHCHILDREN", 0); + profileListBox.setSetAllPoints(true); + profileListBox.setFrameFont(profileListText.getFrameFont()); + profileListBox.addItem("Test1", this.rootFrame, this.uiViewport); + profileListBox.addItem("Test2", this.rootFrame, this.uiViewport); + profileListBox.addItem("Test3", this.rootFrame, this.uiViewport); + profileListContainer.add(profileListBox); + + this.addProfileButton = (GlueTextButtonFrame) this.rootFrame.getFrameByName("AddProfileButton", 0); + this.deleteProfileButton = (GlueTextButtonFrame) this.rootFrame.getFrameByName("DeleteProfileButton", 0); + this.selectProfileButton = (GlueTextButtonFrame) this.rootFrame.getFrameByName("SelectProfileButton", 0); + + this.singlePlayerMainPanel = this.rootFrame.getFrameByName("MainPanel", 0); + + // Single Player Interactivity this.profileButton = (GlueButtonFrame) this.rootFrame.getFrameByName("ProfileButton", 0); this.campaignButton = (GlueTextButtonFrame) this.rootFrame.getFrameByName("CampaignButton", 0); this.loadSavedButton = (GlueTextButtonFrame) this.rootFrame.getFrameByName("LoadSavedButton", 0); @@ -237,16 +298,21 @@ public class MenuUI { this.customCampaignButton = (GlueTextButtonFrame) this.rootFrame.getFrameByName("CustomCampaignButton", 0); this.skirmishButton = (GlueTextButtonFrame) this.rootFrame.getFrameByName("SkirmishButton", 0); - this.cancelButton = (GlueTextButtonFrame) this.rootFrame.getFrameByName("CancelButton", 0); + this.singlePlayerCancelButton = (GlueTextButtonFrame) this.rootFrame.getFrameByName("CancelButton", 0); final StringFrame profileNameText = (StringFrame) this.rootFrame.getFrameByName("ProfileNameText", 0); this.rootFrame.setText(profileNameText, "WorldEdit"); - this.profileButton.setEnabled(false); - this.loadSavedButton.setEnabled(false); - this.viewReplayButton.setEnabled(false); - this.customCampaignButton.setEnabled(false); - this.skirmishButton.setEnabled(false); + setSinglePlayerButtonsEnabled(true); + + this.profileButton.setOnClick(new Runnable() { + @Override + public void run() { + MenuUI.this.glueSpriteLayerTopLeft.setSequence("RealmSelection Birth"); + setSinglePlayerButtonsEnabled(true); + MenuUI.this.menuState = MenuState.SINGLE_PLAYER_PROFILE; + } + }); this.campaignButton.setOnClick(new Runnable() { @Override @@ -254,30 +320,125 @@ public class MenuUI { MenuUI.this.glueSpriteLayerTopLeft.setSequence("SinglePlayer Death"); MenuUI.this.glueSpriteLayerTopRight.setSequence("SinglePlayer Death"); MenuUI.this.singlePlayerMenu.setVisible(false); + MenuUI.this.profilePanel.setVisible(false); MenuUI.this.menuState = MenuState.GOING_TO_CAMPAIGN; } }); - this.cancelButton.setOnClick(new Runnable() { + this.skirmishButton.setOnClick(new Runnable() { @Override public void run() { MenuUI.this.glueSpriteLayerTopLeft.setSequence("SinglePlayer Death"); MenuUI.this.glueSpriteLayerTopRight.setSequence("SinglePlayer Death"); MenuUI.this.singlePlayerMenu.setVisible(false); + MenuUI.this.profilePanel.setVisible(false); + MenuUI.this.menuState = MenuState.GOING_TO_SINGLE_PLAYER_SKIRMISH; + } + }); + + this.singlePlayerCancelButton.setOnClick(new Runnable() { + @Override + public void run() { + if (MenuUI.this.menuState == MenuState.SINGLE_PLAYER_PROFILE) { + MenuUI.this.glueSpriteLayerTopLeft.setSequence("RealmSelection Death"); + MenuUI.this.profilePanel.setVisible(false); + } + else { + MenuUI.this.glueSpriteLayerTopLeft.setSequence("SinglePlayer Death"); + } + MenuUI.this.glueSpriteLayerTopRight.setSequence("SinglePlayer Death"); + MenuUI.this.singlePlayerMenu.setVisible(false); MenuUI.this.menuState = MenuState.GOING_TO_MAIN_MENU; + } + }); + + // Create skirmish UI + this.skirmish = this.rootFrame.createFrame("Skirmish", this.rootFrame, 0, 0); + this.skirmish.setVisible(false); + + this.skirmishCancelButton = (GlueTextButtonFrame) this.rootFrame.getFrameByName("CancelButton", 0); + this.skirmishCancelButton.setOnClick(new Runnable() { + @Override + public void run() { + MenuUI.this.glueSpriteLayerTopLeft.setSequence("SinglePlayerSkirmish Death"); + MenuUI.this.glueSpriteLayerTopRight.setSequence("SinglePlayerSkirmish Death"); + MenuUI.this.skirmish.setVisible(false); + MenuUI.this.menuState = MenuState.GOING_TO_SINGLE_PLAYER; } }); - this.menuState = MenuState.MAIN_MENU; + // Create Campaign UI + + this.campaignMenu = this.rootFrame.createFrame("CampaignMenu", this.rootFrame, 0, 0); + this.campaignMenu.setVisible(false); + this.campaignFade = (SpriteFrame) this.rootFrame.getFrameByName("SlidingDoors", 0); + this.campaignFade.setVisible(false); + this.campaignBackButton = (GlueTextButtonFrame) this.rootFrame.getFrameByName("BackButton", 0); + this.campaignBackButton.setVisible(false); + this.missionSelectFrame = this.rootFrame.getFrameByName("MissionSelectFrame", 0); + this.missionSelectFrame.setVisible(false); + this.campaignSelectFrame = this.rootFrame.getFrameByName("CampaignSelectFrame", 0); + this.campaignSelectFrame.setVisible(false); + + this.campaignWarcraftIIILogo = (SpriteFrame) this.rootFrame.getFrameByName("WarCraftIIILogo", 0); + this.rootFrame.setSpriteFrameModel(this.campaignWarcraftIIILogo, + this.rootFrame.getSkinField("MainMenuLogo_V" + WarsmashConstants.GAME_VERSION)); + this.campaignWarcraftIIILogo.setVisible(false); + + this.campaignBackButton.setOnClick(new Runnable() { + @Override + public void run() { + MenuUI.this.campaignMenu.setVisible(false); + MenuUI.this.campaignBackButton.setVisible(false); + MenuUI.this.missionSelectFrame.setVisible(false); + MenuUI.this.campaignSelectFrame.setVisible(false); + MenuUI.this.campaignFade.setSequence("Birth"); + MenuUI.this.menuState = MenuState.LEAVING_CAMPAIGN; + } + }); + final Element campaignIndex = this.campaignStrings.get("Index"); + this.campaignList = campaignIndex.getField("CampaignList").split(","); + this.campaignDatas = new Element[this.campaignList.length]; + for (int i = 0; i < this.campaignList.length; i++) { + final String campaign = this.campaignList[i]; + this.campaignDatas[i] = this.campaignStrings.get(campaign); + if ((this.campaignDatas[i] != null) && (this.currentCampaignBackgroundModel == null)) { + this.currentCampaignBackgroundModel = this.rootFrame.getSkinField( + this.campaignDatas[i].getField("Background") + "_V" + WarsmashConstants.GAME_VERSION); + this.currentCampaignAmbientSound = this.rootFrame + .trySkinField(this.campaignDatas[i].getField("AmbientSound")); + this.currentCampaignCursor = this.campaignDatas[i].getFieldValue("Cursor"); + } + } + + // position all + this.rootFrame.positionBounds(this.rootFrame, this.uiViewport); + + this.menuState = MenuState.GOING_TO_MAIN_MENU; loadSounds(); final String glueLoopField = this.rootFrame.getSkinField("GlueScreenLoop_V" + WarsmashConstants.GAME_VERSION); - this.glueScreenLoop = this.uiSounds.getSound(glueLoopField); + this.mainMenuGlueScreenLoop = this.uiSounds.getSound(glueLoopField); + this.glueScreenLoop = this.mainMenuGlueScreenLoop; this.glueScreenLoop.play(this.uiScene.audioContext, 0f, 0f, 0f); } + protected void setSinglePlayerButtonsEnabled(final boolean b) { + this.profileButton.setEnabled(b); + this.campaignButton.setEnabled(b); + this.loadSavedButton.setEnabled(b && ENABLE_NOT_YET_IMPLEMENTED_BUTTONS); + this.viewReplayButton.setEnabled(b && ENABLE_NOT_YET_IMPLEMENTED_BUTTONS); + this.customCampaignButton.setEnabled(b && ENABLE_NOT_YET_IMPLEMENTED_BUTTONS); + this.skirmishButton.setEnabled(b); + } + + private void setMainMenuVisible(final boolean visible) { + this.mainMenuFrame.setVisible(visible); + this.warcraftIIILogo.setVisible(visible); + } + public void resize() { } @@ -288,12 +449,28 @@ public class MenuUI { font.setColor(Color.YELLOW); final String fpsString = "FPS: " + Gdx.graphics.getFramesPerSecond(); glyphLayout.setText(font, fpsString); - font.draw(batch, fpsString, (this.uiViewport.getMinWorldWidth() - glyphLayout.width) / 2, - 1100 * this.heightRatioCorrection); + font.draw(batch, fpsString, (getMinWorldWidth() - glyphLayout.width) / 2, 1100 * this.heightRatioCorrection); this.rootFrame.render(batch, font20, glyphLayout); } + private float getMinWorldWidth() { + if (this.uiViewport instanceof ExtendViewport) { + return ((ExtendViewport) this.uiViewport).getMinWorldWidth(); + } + return this.uiViewport.getWorldWidth(); + } + + private float getMinWorldHeight() { + if (this.uiViewport instanceof ExtendViewport) { + return ((ExtendViewport) this.uiViewport).getMinWorldHeight(); + } + return this.uiViewport.getWorldHeight(); + } + public void update(final float deltaTime) { + if ((this.focusUIFrame != null) && !this.focusUIFrame.isVisibleOnScreen()) { + setFocusFrame(getNextFocusFrame()); + } final int baseMouseX = Gdx.input.getX(); int mouseX = baseMouseX; @@ -316,7 +493,8 @@ public class MenuUI { this.cursorFrame.setFramePointY(FramePoint.BOTTOM, screenCoordsVector.y); this.cursorFrame.setSequence("Normal"); - if (this.glueSpriteLayerTopRight.isSequenceEnded()) { + if (this.glueSpriteLayerTopRight.isSequenceEnded() && this.glueSpriteLayerTopLeft.isSequenceEnded() + && (!this.campaignFade.isVisible() || this.campaignFade.isSequenceEnded())) { switch (this.menuState) { case GOING_TO_MAIN_MENU: this.glueSpriteLayerTopLeft.setSequence("MainMenu Birth"); @@ -324,7 +502,7 @@ public class MenuUI { this.menuState = MenuState.MAIN_MENU; break; case MAIN_MENU: - this.mainMenuFrame.setVisible(true); + setMainMenuVisible(true); this.glueSpriteLayerTopLeft.setSequence("MainMenu Stand"); this.glueSpriteLayerTopRight.setSequence("MainMenu Stand"); break; @@ -333,14 +511,82 @@ public class MenuUI { this.glueSpriteLayerTopRight.setSequence("SinglePlayer Birth"); this.menuState = MenuState.SINGLE_PLAYER; break; + case LEAVING_CAMPAIGN: + this.glueSpriteLayerTopLeft.setSequence("Birth"); + this.glueSpriteLayerTopRight.setSequence("Birth"); + if (this.campaignFade.isVisible()) { + this.campaignFade.setSequence("Death"); + } + this.glueScreenLoop.stop(); + this.glueScreenLoop = this.mainMenuGlueScreenLoop; + this.glueScreenLoop.play(this.uiScene.audioContext, 0f, 0f, 0f); + this.menuScreen.setModel( + this.rootFrame.getSkinField("GlueSpriteLayerBackground_V" + WarsmashConstants.GAME_VERSION)); + this.rootFrame.setSpriteFrameModel(this.cursorFrame, this.rootFrame.getSkinField("Cursor")); + this.menuState = MenuState.GOING_TO_SINGLE_PLAYER; + break; case SINGLE_PLAYER: this.singlePlayerMenu.setVisible(true); + this.campaignFade.setVisible(false); + setSinglePlayerButtonsEnabled(true); this.glueSpriteLayerTopLeft.setSequence("SinglePlayer Stand"); this.glueSpriteLayerTopRight.setSequence("SinglePlayer Stand"); break; + case GOING_TO_SINGLE_PLAYER_SKIRMISH: + this.glueSpriteLayerTopLeft.setSequence("SinglePlayerSkirmish Birth"); + this.glueSpriteLayerTopRight.setSequence("SinglePlayerSkirmish Birth"); + this.menuState = MenuState.SINGLE_PLAYER_SKIRMISH; + break; + case SINGLE_PLAYER_SKIRMISH: + this.skirmish.setVisible(true); + this.glueSpriteLayerTopLeft.setSequence("SinglePlayerSkirmish Stand"); + this.glueSpriteLayerTopRight.setSequence("SinglePlayerSkirmish Stand"); + break; case GOING_TO_CAMPAIGN: - MenuUI.this.screenManager.setScreen(new WarsmashGdxMapScreen(MenuUI.this.warsmashIni, - this.warsmashIni.get("Map").getField("FilePath"))); + this.glueSpriteLayerTopLeft.setSequence("Death"); + this.glueSpriteLayerTopRight.setSequence("Death"); + this.campaignMenu.setVisible(true); + this.campaignFade.setVisible(true); + this.campaignFade.setSequence("Birth"); + this.menuState = MenuState.GOING_TO_CAMPAIGN_PART2; + break; + case GOING_TO_CAMPAIGN_PART2: + this.menuScreen.setModel(this.currentCampaignBackgroundModel); + this.glueScreenLoop.stop(); + this.glueScreenLoop = this.uiSounds.getSound(this.currentCampaignAmbientSound); + this.glueScreenLoop.play(this.uiScene.audioContext, 0f, 0f, 0f); + final DataTable skinData = this.rootFrame.getSkinData(); + final Element skinDataMain = skinData.get("Main"); + int currentCampaignCursor = this.currentCampaignCursor; + if (currentCampaignCursor == 3) { + currentCampaignCursor = 2; + } + else if (currentCampaignCursor == 2) { + currentCampaignCursor = 3; + } + final String cursorSkin = skinDataMain.getField("Skins", currentCampaignCursor); + this.rootFrame.setSpriteFrameModel(this.cursorFrame, skinData.get(cursorSkin).getField("Cursor")); + + this.campaignFade.setSequence("Death"); + this.menuState = MenuState.CAMPAIGN; + break; + case CAMPAIGN: + this.campaignBackButton.setVisible(true); + this.campaignWarcraftIIILogo.setVisible(true); + this.campaignSelectFrame.setVisible(true); + break; + case GOING_TO_SINGLE_PLAYER_PROFILE: + this.glueSpriteLayerTopLeft.setSequence("RealmSelection Birth"); + this.menuState = MenuState.SINGLE_PLAYER_PROFILE; + break; + case SINGLE_PLAYER_PROFILE: + this.profilePanel.setVisible(true); + this.glueSpriteLayerTopLeft.setSequence("RealmSelection Stand"); + // TODO the below should probably be some generic focusing thing when we enter a + // new view? + if ((this.newProfileEditBox != null) && this.newProfileEditBox.isFocusable()) { + setFocusFrame(this.newProfileEditBox); + } break; case QUITTING: Gdx.app.exit(); @@ -356,6 +602,10 @@ public class MenuUI { } + private FocusableFrame getNextFocusFrame() { + return this.rootFrame.getNextFocusFrame(); + } + public boolean touchDown(final int screenX, final int screenY, final float worldScreenY, final int button) { screenCoordsVector.set(screenX, screenY); this.uiViewport.unproject(screenCoordsVector); @@ -365,10 +615,26 @@ public class MenuUI { this.mouseDownUIFrame = (ClickableFrame) clickedUIFrame; this.mouseDownUIFrame.mouseDown(this.rootFrame, this.uiViewport); } + if (clickedUIFrame instanceof FocusableFrame) { + final FocusableFrame clickedFocusableFrame = (FocusableFrame) clickedUIFrame; + if (clickedFocusableFrame.isFocusable()) { + setFocusFrame(clickedFocusableFrame); + } + } } return false; } + private void setFocusFrame(final FocusableFrame clickedFocusableFrame) { + if (this.focusUIFrame != null) { + this.focusUIFrame.onFocusLost(); + } + this.focusUIFrame = clickedFocusableFrame; + if (this.focusUIFrame != null) { + this.focusUIFrame.onFocusGained(); + } + } + public boolean touchUp(final int screenX, final int screenY, final float worldScreenY, final int button) { screenCoordsVector.set(screenX, screenY); this.uiViewport.unproject(screenCoordsVector); @@ -437,9 +703,15 @@ public class MenuUI { GOING_TO_MAIN_MENU, MAIN_MENU, GOING_TO_SINGLE_PLAYER, + LEAVING_CAMPAIGN, SINGLE_PLAYER, + GOING_TO_SINGLE_PLAYER_SKIRMISH, + SINGLE_PLAYER_SKIRMISH, GOING_TO_CAMPAIGN, + GOING_TO_CAMPAIGN_PART2, CAMPAIGN, + GOING_TO_SINGLE_PLAYER_PROFILE, + SINGLE_PLAYER_PROFILE, QUITTING, RESTARTING; } @@ -453,4 +725,25 @@ public class MenuUI { this.rootFrame.dispose(); } } + + public boolean keyDown(final int keycode) { + if (this.focusUIFrame != null) { + this.focusUIFrame.keyDown(keycode); + } + return false; + } + + public boolean keyUp(final int keycode) { + if (this.focusUIFrame != null) { + this.focusUIFrame.keyUp(keycode); + } + return false; + } + + public boolean keyTyped(final char character) { + if (this.focusUIFrame != null) { + this.focusUIFrame.keyTyped(character); + } + return false; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/FocusableFrame.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/FocusableFrame.java new file mode 100644 index 0000000..0300fc3 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/FocusableFrame.java @@ -0,0 +1,17 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.ui.command; + +import com.etheller.warsmash.parsers.fdf.frames.UIFrame; + +public interface FocusableFrame extends UIFrame { + boolean isFocusable(); + + void onFocusGained(); + + void onFocusLost(); + + boolean keyDown(int keycode); + + boolean keyUp(int keycode); + + boolean keyTyped(char character); +} diff --git a/core/src/com/hiveworkshop/rms/parsers/mdlx/AnimationMap.java b/core/src/com/hiveworkshop/rms/parsers/mdlx/AnimationMap.java index 0fa82d4..9e88301 100644 --- a/core/src/com/hiveworkshop/rms/parsers/mdlx/AnimationMap.java +++ b/core/src/com/hiveworkshop/rms/parsers/mdlx/AnimationMap.java @@ -219,7 +219,7 @@ public enum AnimationMap { /** * Camera source rotation */ - KCRL(MdlUtils.TOKEN_ROTATION, MdlxTimelineDescriptor.UINT32_TIMELINE), + KCRL(MdlUtils.TOKEN_ROTATION, MdlxTimelineDescriptor.FLOAT_TIMELINE), // GenericObject /** * Generic object translation From ef61a0926c31301299e95f32247d02e4d57f4bd3 Mon Sep 17 00:00:00 2001 From: Retera Date: Mon, 22 Mar 2021 08:35:09 -0400 Subject: [PATCH 112/116] Fix blp plugin --- jars/blp-iio-plugin.jar | Bin 63630 -> 64414 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/jars/blp-iio-plugin.jar b/jars/blp-iio-plugin.jar index f2d085a59d48ce8564683c2ef4a699344ebccf4f..f72f214c7d4d46a1f14b89c52c656ad61d8a4068 100644 GIT binary patch delta 19601 zcmZ6yV{l+iv^F}Ei6^#g+nm_8ZJT>yCle3|aaTjPyYL;zW21(Z)ZTAPyOieXiOQ#eRm zLHSsbVApU~9dWbrZ2sYT>0|vh!h)c{p=%i!erf6Z@7@%9EzD{NH2mrN<13v`Q;t&~ zFK27;-y)Wwa{~+-&*u}ueGD_WqU459kHjfeF6x-;YUFxj<0?}jl6;NH(={hbAa=}- zxmo0-3joVS_yq5hX)1P15E0tsadZr#RH>p(>*a?4)ZoiNC4Sq#52iEajpYcf!}q~V zgPUUsFg>}d({nG{km)___~;%DEtE z8Fb8dD6=i--OEJSYg?kgH{*?j2;{ZKu?f`x+gHMYSu~d!dweJ7JTdV(oQ`9t9Vne7 zT3}JCBz=(7GCFTch!Up>5n+~RD4U#j597973a2mg-rs2cj?PJ#*CNPh{I1d=GB4zj z#AvBPyG*qfD_Emc(@-g+R)h90tk&@?hPn$kTwjPxH0*j#Wg6;R3Ss6;mfNp1%*71V zJnI5$hs$miH|_B${Jhux!v{mq49GudBtXRv!r4CHq)EJJCYFZ?{OFc%Kbt52-gYbF1!0RIqi zml?usLV}rirpZw#GB2qnfhLKk?0wuZ>dYxR`w-X1-aILV8NUmM;D(G6xzqdrkDN=j z@B+P3ft7NBB&wzIh84S_H#YhMWk2T4WNMW>eGFXlU^R9x?>8jzR(f40g_13KDG12s z?J2M-kZ<3R!M=U_|9ks?d-^X9U3}>UO5HHw2FQP1{vU!}cT3X$A5zy!K8FEH_U{DL z*r9;_ZxL(9QP%~E3i7|3_{~Db*ldV81c{h3f{}3Ueh`euQ7Z8H1grNa zZ|&l-vZHCk)y)gPlJ?_sGYyjrLSl6)_FzJ#1% zcFq>3KE8DRW6>LcJQ94F;M%|||-rRhy zsfz}(a3jG6*1ADQ%3k6v!JOM^TVbr>FW7+W`i1nvpMu93WAA>xhDLYC>4zrA2+nO1 zh;rkkpuv)o*KBm#i8V2vI&Yper7%413|Rz~K9u6d=uH6tr=XJe&$@A&&COLj@~ftORS#VVf8(-3~&BZX|9QeHh+q4Lu7RQSm@UN zK)>J7mNE&w`Kw5jMN?r~bSs6ha2%P2bSYR3L>i`soLHbD(XVl%?u+tkt#;L3>!v@F zAt(8{H_#$hd{oQl8nn61ioVgIj>nMq;$_-%F{YdXHGLndT04H*7K&d zUyhcZnu5mJL6#W^$|ccY^aGaV>^0x9dFfDu~&KBX!F*P`lCM ztQ&TLn?USKLe(X>m;&TW8B*|G3FtbW-xc@cw$y(Y>acQ9tZF9uUA1N0q}LvOJBidv z_Qr*Z6f>22aNMH8be3ggEASdp^_`4Y`O*+Ard+GlD9Z&rnPO4_W>N zu*=8}cMPo)_Ovr5@>b!2*#{OR8q>jfjqTu3qVg*1`sIT6=JC z(6Y}QW0BHT#)5O*-I}5Rvt4=Aabt+tI-o3Z+}W_s^YtS015;*>cODy$@y4P8(&Qc- z0IUodF0tH|t0}4q6rODYE*pVOG&-?4j~%}8*MlpVq4OWEG&nNLC)KJ}Z-3%l(|RNF z#$eM;cktIZz!?3fFx=Bc7_;K6o=U!FcE>9Ze5YnJw>w)nETHWYoBUl<&ld$SU9Tt{ ze*};8^Y39M-E9h6b{kf-d;4cvZav%D`pfc!#;X_bU2`I54c(plL!<-EMM$lhTHo~a zeU}eN43jKL(vM+d9EJ<44-{c{0#voV^J04CZ(I5)PGh(4v|~DNf7;Sz zQM;Sm#VyXPRqnBA$<_x00Tq?)L%d~adzMPQ1 zg7l`-;9lp~PI_I_E0BtA72{-?hrZZj5cgg`T}t@KN}AKqiNAodkSS-qWdnVfK||@> zuKrTRcknLeYpq)jteUVbD{-_!$~cgZnf|P6Gt(cXIx*mRAcStpal0uyta##>zdVREB)Km$9^U2Ki$udQChtwRu{ z+9=lPq}YWJbY;xBRjT%2W8=tL)rgAz@K|a@;R4|5vF~52swr)ul9(#%*%5CjI;WyC z0Iqa627+tDWKviEQZeeuec{Q@<@tNVV{(04iaA#3;Qc(^HG#Hv;g4(2`^dKR$sZd& zxr{{tCR4kDT#tMo4P%_ob{E4gO)l7#SWZb97?V-iiXR1?M@{PIZOZU%(krxH&2v^q zj)u^uo<$dy0X@&^Eb)j)$Uy78G`(X5<#1H_?79zTvB1!s*g>0-c*8bP%+L0Pp4vwZvf?+%E|&Kj29O~ z+ngl~l`^3?F&|d06-qyv#NS&gi5Cr@kO8Fbo8BAWYVL#P)jw|p69DNfX(eUAs@f>w z2bcLM;ni8SFD~PO=m!;7v4ygkuF776m(xoeTYfRMpk!8vGNC2yQPw`e z-5n*!>}JTaN-#>6cU3rKmiKD&4j)4{>KwH3NlrA&8@>*%n&2aI@BhlhsNxsIGOVX>;V z#wg|OXopl68L3Ql#mHao@&>v-XvZ>|lFxJ>*=WN8(_EE!npbUsUwJo(oEF1HVhxM3 z?*fKh8`HM1&SJE*s~xKFbI%4!c!`9C!+v#2{`C71u`4IUPj=dK=#At^ZxHdw_fbcy z#~UZ_LJ@o;z{t~Lw4RNL(cDS1>s`4arve>*mP#Og!r95NbwpjAb(F?Q{iC4iXGIX9 zoU!d~QNqTbqT|!}0454=eVYBS>~8{urpJ1*9Dj2FqYIiN^!}O%gHpRVg@Gm&{g5WLDr_~9^X2*I{gG{;t zq{Z306mwI#*PvSuQwE2SbeE;-jFE|Ql5MiQQ44Ol(qeK3E^iL$M{>HEY3<&S6PUvd zEh7zDKun&;Nu9bXyT6N$lw6()L!=DVw1jp7sZ(Oy4AieT<_j(*Jx45DFpQq|N`v!b zb=4;fR}i7)x^iF0Ds{F%waoxh;!wwirVLlWe;k1P6ZFMH!=U}F#k$z=kwbX zvuCaC`DN0eeMsx>sY~ri$zR+jY2@kd?1x(}iLx?7Ap8QYl2C9s-oS@tKgd+*lo43g z((GJi9uy5$AvWQ1u^>HK9{$+&bk(ELkGSPv6jx%IVL=jmNCuB?!&J$ESl z1A~|5@$LY_-FT$lqJ1JB5~-W&@@+3v*CV=f|$z053CF@RSFmnm{LXyE!$HS}Nkp$=En=(A~Ae zsIoMs>5kwm1I2fbfg+A41WGnT)$DiDamMP_Qp=hfBuSWZAE-3g@iZVL5+)>@(jJE0m6;jy~oqEGXVUJ)*E$Gdyc4FpVn`y#?VBdP9yhGWwo35q$puQmneOIc2rk26TxknFc^mGH919sZRv;*3POMW$)AQ z`cIUdH}#Pl%Xb8|ZF2RHEX9Pc`+oJ4x+Kzmwl6KHfH3FB297RE6XD zEC%JtOn;rhBf7a1k|_v)Oj1Rs&u(pwL3t zPGL|bKU$i!RIKMpC)sXryr=tNm##&U&8DxF{UWnGNQBZ}^VAL}Pt}PJ~z>l3=G?K&bYZX2+w{ zoU%x|j#7!#R1&qcBnu2D%f#3qIyQ(?G)dez-TSPgm$S4o%iTWm&z*P~8GKV}pN%A~ zj;6yBV|?^L2iF{Pg0Vx)f^JGE`XMU}=}NHRRc4{x^|g9{o^ZQVar!U{cvZ5=-fPg* z+}#nv(2O?~jk7MmRNf+Ygg4;y-mFJne?OxyRQpbsU{O$>fT~+UqD74wdbNP7!H4~` z4$X#U)@JOq>}Od76ROJJwD7Ym5tWa*-p}09Pf87LlCw);2IX}Wj%d9F>dA-Jodj!Z z==qTFko{7^6+GzYD<#K?P-hp|vChJ{bAn?;NU&d|Iop(ghsejD_S%w!sZFwYxgrO$ zteQb4kwnPE^n`joxZ}NQK3yGo1S;IPCDsW=qA+=RLVsvP<p~WY;wB$TE_2b56OldR~!6Xb$ZGjC~y>i(?FhJ$WZ*6;k$4$?CIX`ATU9v}1uw-Vg;+^JSM_)!}m)4lLqf8M|62%8zJ z^IYn@lU9pPKg{k+LNIH^g`8ZszKRFZn?s+8h5se3SAjH`z9J z?7-E7{lNjYWPNzymt>2&9a0Lf&f(nCuvaRUt!-YTa6y~nIm;LJ?7;_Lw_ZlO1VH&i-6tF{9*JORxS>qdFG?1TR;utls*a zhFCIAaVpxc=OH6bZ}}~H9`Y4G+8%>=defWjEt;87g!qd6KkA>vaUGgH^0#k2l;7b0 zul#p8Mi;Ml1_dbDThJL<8#p;ts7k0|iy?esNkj$?{p{5T)dk(`Hym*fL$5~u-is%O zBupH@7?vm)Ie=k<35yZ$I1TTq{d2p*br} zU?ZKwY-+8BH)1g`X-cv?`u*C3q|mNZXB@7Px935TiVO-U9o>ezBlfFLk^xg5W2r4M z2);0CvF@3zU93;iUCgj!d7v?Tj)5;w*@*)X_1mJ3B4F-_&gd zldIDV%y1h6;ez{IG-{=F#uAVUjPJ&hs8Eo&D7AKZFl8_-ew`EP{)#7EkWmCHWU-n7 z3+`ajtd{BZ4c2);$+%9EZ1DgwlkP(j*>&$=as~9s!4TMLJ)Jq3tI`~7_$uzg58^aE zHJM>M>8R7hghA3dBp2GGb=#Ru*k$h(G=tyCwK2LGp#oq6(zd8#A#)j%#*7^`FnuS5 zGE*W&{BML=FrZ3sPMaeM1*_Y0bTd-qD-$1ON1X%8;6xH`bBc`91(}81STNKIg`ikv zn*heR(7@fE8+{&%>6Q&n%%hf#&7tM6pFOZ*#@gGNt-qFbYqpJUGLvp#*w1XORsFCN z83#XH6F;b8w+p64oBu?Hoidz`Q`sk*P^?!J?LRV_PmMyV4NOCpgRu#l;0%%&4S8d4 zb&JXbgw(Pw9Euj8uWv6ukc*u{{R%VHrvFG7k8dAL)pd(F_w5xCYrfOq4jLi?hSKYm_r$7vkrtYEWyA%~zZvg1_ z$Q1Wm<@Y>es%HE;EudJ~AMM1mhJ}TtFeON>9F|EjEsQZpEVF**BVVXn^bk3|Umjv| z`A8a}(=r%HP$Ym)9GYleNsX!JaHV<@ChN`=xzq`ynsOYksUKq%_W3}Qw^8lERfPn6 zbIx7W9;Qu`=x`-UI+ON8k(RukxdxKRAXc~?3lg}P)~R6+zlcQ^lxS&NP-bp(3#8oJ zOyd$##NFLKf}w1@XT~?^WPWc*INBtVcMuJ-$B<1QuPz2DJcKdO>;zQIXB%($w>^g@ zKN?|`cE0XgKR+W(%@kB=dNwMT;W|GIJ^Y$w>1Q_JP&93`#yt6*;^diXGXd1O?*8Y!C|pLy zVIhN@xmc@iSHvn~9*0}s^s}OVuW*6neESQIjsMqL`NT%$UV;Iyeg?3EXtftV_t%G4>s~TXzPal2eL`%i3jHJk5lOe_jyR^v z9a8N-A(}*E)b1D21dYI;-&I6ZyClBNYSJZwIVXO78=3oldr;7Yce@p4BHtC}1cM>|(PJ&f}^*PjtuOUA3-pgZK##>)k+s98WO; z>Bn=i>W8q`?_$`v0S9RF(0pXbf7dhUa}XaOa*w8RnksV6`4R@q5zUDs1e@fHgg$nG z5V1J`LKQAc%F111o*(d2yQV7W6{7TjmDsfp4}av^%r6zRwg{?CHF_^D|#;XmYSTV zZ>nwKuYzb%1~V-Cy?jmDiA13H^UdxP?0+VMmvfZk=08s;1QF){8bh&mjCEDna{oC% z|2FYe{~R8!yzC$UYZBFM7E=8`a|ov70RKPZ=)YM4QHN)``Taj+vyd+SdK@2c$5C_n zyy9bd!{E(usZFvTi_Rz~(Q-r*b08TCBN-dBhkN z0hLhiklg?)kx`f*r9`x%nhR=f5s&oCLMo?fT9Kk&fU>>-6zQRhxotHHk6P%&%;Rv9 z_bJ_L63~6Rl(N%K2dP$YrnCnjts@JOZ1M&PKkTWT7`XpRHo*==<%u;&M+P`+abV63 z-7mWmXH3s>GnW)+P82dhYlEDPIZYZOHjh`ExIA5)OAIKbUX-NZp*J^Hwd2q=B~aWt zL%WC68(VjCl$wFz1Q_^*Pl(PkG(k51+9~Aqey}fqmsKvk&Xw%Ov|I)Df&`+V*{Rx# z`%IvdXe`0pu4F`&7>PM+-^4Z55W5w>leQaKU3G3&fY?M>>5Xok>l_vfPBqYjY`*u9Z}1xx z|IU7*hLEQ9v~Npl+@B-)u994R##Ysx;Q>?MXPOo}EqdR0xqb~)XU-TIyewEK3|Ys; zTGo+0nZhV-m6+5A%Gi#jiniNuBu7f#uX!p8^W~UvrGdABc{AINP&Mo7hYb*lJWRCg8dLDw;iP>DHWV^6n`Qvyn1E!XhTahtK?li2tN8%a?xlskPLsW0Prb6{uwoUU+vyF`WtI7!_wdsq>O*$d8 zs!MDdJzBq7VECyC}fvlN{cbQ^O?Dw7xpgZqH1f_V|ndhU{NJ+DtG=75vke&`KJA z(a~WpL;*)$KY>Y;5hLV)13zT~^=*3+e9omh zr%`bX_+oc?&m}vrrZfn@^7OCJaJ?Zho2=EfvGr;#OLeRpY${)5_%#b_`ahL0A$R*? z%HA*lJ~^ir33Z(H_vL^sRR>8B^;CF>HBd0KL)Ai}mbcK6d!8t{Qg@V?l@rPm*V#H7 zj;MY}r$TRXeWoIl33T-qaiwjzJWvwbmImv&+;MBNRyQ@l!`|#QYA;9+pVa<3HCTHU zp%#68LYxEOP#4UIgQkEB_Qg7Vg7s;-NZk?`w!NH)A<3gJG@IxRDZ1`?F_$p>xmW`& z5f7~yP_c2htc3LU#RQ39s@N2N9>aP4xiOWVHP38C{^7mIhiawL1m3Za_?=%A+^P~m zPHM{=W7m%XAXvQ_yEugYq`^=88657P3?-1NgWAs2WRZ{r%UYP6S$zo&>pF#=(dG=$ znnIXyXn$ffs3Y+_1M55J9H@zE5~^!m$~S7YI3u_u$K1|mE^tVPRg@;%#|2X5dT z7StAY!k^qKF1&p$F4!4KPWvt899%h5B^#af)g>uz?Dz3da8ij#GLjYC_dPVqRhuW9Dq|SAeLhc*<6HsUzk53{9eQF zNe;N2(w~s9hHTaA{tH(Xq&UqT-9#HYufao;N<{9U%_PFr?)b4?*9oULjeQ@rH+hF~ zdcDNe<%LIl6niWm&`GIC9q{SU9uPof#vN1+sR~{i09a1r--DHE1*l9qq`dPl0_B>2 zw$r$nKU}oZMwv>dzn(--H(CAkd^%SiU~IGcePVx;XAdVDf?U-ka@&@XP40cd0fK*b z1bMcn>j6YIW)E3T!=Q%)jZc)aI=;n;Tlw5j z9fo1tmHF(EC=ilOpFhC#&y6=k>$U47oa3SFG0=?dU_NXB`E+~62Xpty-~0AJf#>fR z^76?6w~fpZI@EHDNy0FBBQp8HILq_W5fC!L(isNR8ATK0M%Fp?1n&UJKd7+N&)YlD zg#Hwc)h?y&=N4|#EXT69v1{5cW<9{j*!|?&u9k1Fy?@Q;0KZ9(xBc;!*Q}S`4|RjV z2M}a-Lkz?}|8Wn$a&i~Y-uHrr4^O@}xMAWW>J;s?`}5^BpTarXacW|S;+T-P)uTc7 z2ZuL$D-m;i2KkwnOfBdckuQimEMM|-YY4xZj}~#OSBXQ6Cpk>*hUHGHhw4V`)J=ANzY=?AX#j>mK8Y?-^C=1bTD^)rl52~thOGmThc{}~G3kOHbg8$MtxF|v*K znvcqA7>W1FBi7JwOZyp^$MsHWAb$tRySjTHD|>Rf zZk6)TDB-=P>xOaHvCED!0@K@nr`?|pWm@E2nLa+|xS>e^!+-U{ssyjgB|s6A1>GX6ySerfV~&&c$|s1}R?G&mfJ#a+=j z{dyd#x0#U!cmkKMl`uX2{o^c+jXMNgGi9!d8El-E^4M`P_NPV0i38O6ZP{P+PImaJ zkPvsheYo^SDjspoCL{E9t7?%pQiBl*gkFp_0Im;o8SGoBRC7xWSVwU+3Xe2ei9q}r zXO3`sytlY}KJh6wv~LQ4u<3XAtAoH2G_&@7(KzGVy0@+|oLsKo6?N(mVQ0ZZRO2+} zSsIO05Nli#SMj)_!^?W8zD8 zZjqE%D3$4XVMHPGW7DES?XT9NOel6lXI1q%T9UXZQ>r_{By&Yjb@AmPl*3#Rnqf{N zyB1v60r35QsPWGeD6vGiZ{Kq8|1UF+zZ{$XUwRv7E(T~o=_?;(eEBf{yRYgaVLHkX z{EmwN6%J)w_X8FEC4eL-@H5_69K2ULMf#6?#IlKlR@p+und(}lqDxJi=8fcX_)u%v zrKT0VRnvw}TifN1?HOLHSeg0j8*nud8!)Z1)qdqO^!5G`L*My0(v%5Nt$cCYom1UR z?O?B9cMjO!4+`IQ3)tlX3TAre$1~!!X#t-iG8I~VH(DtLu$9M#O6F-@t0zss2Way#kJ5$&I>y?i|77fPDJDk|L-$Z6nnb znc{en@)_n&t^GZp!aP`V6%o!4rw?dBy_Lo+apATNa0j&(#pAapaUKzJUO+x|YrCZ+ zW~2+wGoo+~M4cloYi9JnsH4(hrg^DRaTXINby+W70@e7O*(=darpQibr@36dolULT zH9$~c$2W_5Avb{JKtGByS{Vfo^Y}y_S7PqgLZ-(@kT9l{uXZE`b(lgiqz(2IuFjLB zS%6TFI@iCZG@{;a=CR-ke@H!!S+ww~&a|31dV1JTA+QzM8{<)oFq#OREMrnq{a#0{ z-JoM2YDI%*gUz#yDdQ^4^;T>vXq8Jn3pDY@MUFJ_o?MNlEeZrr@Uut+?BT z4vM?Clw-n*iqX;&4JlgU=47>fk)*SWz{AQnff!W^6MpFt_a44o|MpQ}pBUzi0zi5u zrK$TR1`c%^?`}83%G?_qTqc!n(E7gWH-xdOmoa24Yv41daS7cYzK00ysv9%`K^x1>Zed;b9Ka>t|R4v*kuPgsv#@7B9vu z*~D0(v-tQbWSg*P3Y#!Trrsjp;U`6azsd|hs?^ENj4^xqo8tEyKI8Z?TPo)<7l~!4 z4QT`B*T^@r+-0$tv-XvCU4xkc)?r?`8}1n^qTml`zvb@L`k#$I@o1OMZvlTe*aNcY z$RZ|&DCT=dw`Glnc6zKUi3glhF))kOF7*I`JBrL??ToF-KXScApiKd+xP0M;6k5j_ZW(I|S z9q$LTJ@xlTd0WX)v?W45EwcGUG46paF zO+D^{&K4h5wF6I4HlP_NKeyfK(_3Ed2vT;=w8Hbd%F8}H7^7(o27P@hzpaSX<71`>U!>U>cgJW1AHGCt#G*$9pLV$RwE+sQF*_wc|M zmVoe9207*@gXyD`Jm5?+*dJ}eF}f+XW{m>_)ygixsEjPGu@#1 zj`v!aEZR7ut92+|_O((%?vj)FQnd`BW2-N9ib1!He@2CMCGzzqN-i_=GvBxw77B17 z7C8NJxSPn!1AN;9fZJ_+D1RCTHZjF=>aR$^01=ZTL0W=VLvH|@Rfp?*{j-N2S?lov zJJeCh*UkBNg{<2pcwW3|Hr~y;M3r=9%!Sc_JZ)}1MOLAq^BI_)+UvJO19vEQFeI6B z4Wu7mM!P@4{482ll+2|blCVh9d`9#lR+pqd0r?8c5PpS*H+7xeG}2}X9Mo4kjYO4PRsrOI@diAcca;1onGBI&Dj#x= zf|F7YJ8QqyC~Vp=?YFz6b1mn!3tU02Yh`iqy9}6pDTop#6+vEVuE#Y9nN3OJj&qmHidlls2Qlm5kCT(iUW%SV9$8oVl7k(J`&Oz@*ZRFov?`98Ht@hj^|f z_TlS$!?JR68L6r*?%bd8Om$($vPcf!l9Hqnz;l--`3i%`sgNcz2^6-lRT6}M3fg-_ zE>LU-EZ-D88*AUJphR_qQLyUrK|`)!BcXw{P%{mVDXZM9Y1uw;9;}CUo+kzZ1AmQNBpYrbq}bn zM3yp+x8ptU9$DdP%z~=^N6Y&hLt?8Ol3(u^`9ZDbIkqUbNo?qE)!%s4BXw!^e5_b; z?OEZne>j|rf_OPVBC1~a@Zjzx0u+8IdHM)V+YGitQO`)x+rBV6h&U$PY=K1_ZqaA- zPFL9NhoKK|w0*f@_P_Be=DPpPct+!Rl63wf=?1XyLQb}1|M-~CH`@E0G^G34JlkKT zF`m}@!x2X5P)_@O8Tt0CV!jtUT>GffS$sZ+6q4IYG@Ov0)gu9_F$vt;0MBhA&mCW2 z^4ARt{TLhM8fE-2d)_`@bEKG5B*eBO@2Rgd&a!P@iXK7VTFEvp%G)GP^+Y#XMP;aA z_YPXnSPg4m_p}0YMJrjt)%$>kHnYD~*0dnue$m-L$je&@iJN9TZ4=x!*ZI6^df`R> zy?I1qF2k}x-wSQHvHisXaQd6Qp4}NgeEsO1JLCIRQbzE5^Rv@mRQKL}SvPYjn`2$~ z$T^1e$!U#q$LQ@>%LkZ8o-lE94Bn@eX+DDKObj~KDt+IHJ5EA-dDAj04kxrQGX{68 z?~b8FA+o zt)zWqj(;(@vJobk@qP?bsLL8#Qxr{mpbcUhyMQ4$?HVazW@VhLCmpINtx6^jRKs<0 z+6gBLT7YS;@}5Nk7!TUhZkaLE&ohM{y!`3VzRZmwc+)Yyt9e zob2xk*HM?$=$GW$2e!|nQ{k@OwCUB1<<;SEJqPie;p;#gV8M8~$h2^J>=1cd9Pg?> zj-@*tj1wK_$9p6>H6zRO>E&EeFbHc*5ItT+M`{jqRV0absAUX%m@%>4TIqGl8RGA| z^YIfa`HB{LYmvcE@r-91zih8tq%9zQsQHcxj`8%~XCCNhe3FpUyW5;fUbZ~GG~^#m zuWVbugfZg-*n_DKmCCX*#z_r~xx`Q) zYO|yzQJ$ZN+0*jY-M&N0N}k)of*DLQcptz_1QQC&7i5LSW5q4}>qmqt05-%*)2)GM z$xJlQB;6&n=$cr$^G6L#D~W7vc8*4ahmMSrIPKqayah?8@eEa&;+E}Ut-hZj+P>#3 zoYFB;a*l2fkezDC&@E4y18Fw9g9h&lJ`5(p>+ddUd7IUyTa|98?>+(ZS98aZp8l1a zo_4wca_FZmEYF$Q?8jSe&1}bd?Nx+T(`qSdDNS8V_*q)n@ zJCDkSfuEkIQNQ#8(oA@d3B!7hsrqppOs2pRa8;{k z7`uP z4%|LZlI3Dz_A*;@n%oo0aoishPja%>-NiE@uaKK8N|`b;oplh@#r|Z{ijT%M6^vK` zd1N4dc2Cry7!7oTRk|z{m{CBxGx~lNyH_?^YpzQ~q2xdo2wGU?JFXm}e)*b;0hbea zrPyh9#~X6h;;mPz$mvKoJlsMMFbT%S1@NZi!I~_XV4d^8TcwKGq+TtAf|ZuiQYJd3 zETK!%`>$e2)0Y|G6=Tys^5~cbZ!3yndJ91o%k3d`2AncOmKcp&3_10ekA`X#L>2%0 zcv@2g=Aj4;LB_-!?Z%WUMN|t0Y%9H>E>HEADUUTvJ>^Z&yk&dM!_Kn4xW?jTfT6ba zxqxkHV?n{oG`&TR$zxqI8HmQcvPpPyC5z&gujmZbbRj?G=uE$&ezBF;oAsn=AQ?WM zNnv!$v$2g%84?>N$5*iozEyCV86M*C!u!cv&X}v*t$M1XRk9d_S-?i0uV_Q$TY{zf z5+z!p#9g?IU_$3`c&wprWvpfjtXR^Gm$rms1idmXv?p8o23@{j#X9rH-hT_?7E!qx zB6=XW+Y?eRz%%vrhCF+QyYp%ABoRAUj2x{f9foUD#YKk0(hpLd4^zY{PjN~bwI09a z8>A{dS0L3q`&04Y6c{D1!&pK+l*3SA{adyocJl1bYlzF~d(_VdjD~kIpn*roj4#aa z0cCGrf<`|=cPxByn2fw^B0Q3fnn^(0;74p;T%&au7nYeY7 zFju0MVRV(W9u}NU&H|%zAD$PW2I0TGg8&!5{#}L#-vQ@ z8N!cyo)M3`aE=@I!BqA&wTzYm#X;3olX8~r4vEeKyslC%WwzYKsJg#-2)1H~k~jmL ziB`6v!7Yt>8`7>rKm!b^^iGp8RWf??UHNKjfiKUGAg^Ysu%g^x4coh4N2q2hbvhao zLBZZ(0@g)i{(~5?(19I$OwBky^|`2)4^G3evYy{fV7IHfjix~dprxjI#@1f{l>UZ!4wZ4& z6g1g_P04zNO}ik7UbXgM3+uus=vEOglNA4wZs8pwV(3)UVK{5vl*v;`@dv+`GB9->S1f%>U>7;3bN`ks0K z+K{I8zE^x60HuXfHgjn{NGJC5@fz15aAvO=%VCHNXP0}Ru8D)q%1PJG!ZwZhcmC(D1zqOP9Uwy7lRXYaizm&>foMX`c zs#Eud|5wEd-2T7hN{A50e`%I~iXL4&LmpH-emeeljXM5x|L?%t$p9!{&o7uK&~@&Q zmfZ{j%v8~iO?%<|R5*tU`B7?56P=MVtbkYLejhpp7;|hZtCb6aM}d5^<}!*b*a*CE z_dvw&@n_+g8_LvwWrxzM_EmH=RGfqI>e}f=ya#RzWrz3VK&7 zqR4nQ0pw4okL2tv=Iz4t2^UXA`yhw}-Y@%HJ_3|1@J+vs+z>vuM+-@zgLVs!Qlr`b zuZ`=BYGQ4|OK$-Q-OxgjA`l=TQlv@|K~ND8yi^56lqyY1=%6%Zu~4L35#cIGk>W+g zN|P!bq}NbGk8;(!*>LVh^xJdtXWnOKo|)a5>^b|sgC6hGePi0O72q4Sv+0m|%~G~& zq*^Y(?T6Y!G2Y?PpY1$2uYr8s$(VJuPjMy2l|I`{y#D*-y3S zp3CVat#qB@Y=;N87BgBJGPpf9URfkYWnj$)Q?E=(yhxlfX?sx6iL;)J5p45Nm{07z zR9o<|zB=4wI4;X|;C_J(e_ntGZJ$CSvfTLstB9Ej>$P$Nj~uZ~dTeo=v_JBml;~|s z9j-;+i&L!i%}D%BtK{R>m20Kf>~=W8&cijjBj=uH|A?X)&hxy%o5cB#Wn$-wY6Pz3 zA-X@Eji_iqw-tvzFpRr16Otd&sKxWBCdF8|3n`qlLwwhen!=6c5`LnU{f)TAy|&Wt z0XjOSPMIUSSWU+yQfmwnjq?ks_DMyRB~bC+BxWy2>9Y78$#*EYD)sFggJ1N;_O<3! zG|H;{PBQCFmi`YsHbYYhsg^UBQi=v-wO4uln~SW8tJ^{yF&MoLeFo)-94D_1yZd>i zMtP<2&-+-)`{_9F;rgX12N~4HzHZ@2h2S==o}o$$0KQDW)&U5e!mp5M+C9V%&Ie*Z zcLH}s_1Vnbr=E1s-*AP$0%0V>h$IaI0QOwEptq$6n7fVyUDr4&HbQaaq5+GdbSkRD zmv>93DC+a>hA>>53m19Q*Lln z^RKn0(o%Li9ayHD__x9J{!eF7#`@By4@he)_XWh}>(@46O7Fc9jq?;5&D=l3yqqR+ zshFl4Rq}J>o$ES~tW19y-C+R;XJH@UcE0s2SX&d~+X#|~DNTQ6`O&kSjkJu9Z|ejp zr~vP%Dlbp1=!fG9BI>HDgbV(V(KIl43XQfHGK>@}>m8udGw&#(NmqN%&)3rxem;NBn~yH( zeW=#?K4DURJD}Gt-%5!ko+LYZRS@n&Q z5PvRx?^%C(#O;yV_;BQci-<+GsF+rJX3v!iJvsPv9?_x9FzjgV+wdY)$CD4g$RHK`o#qq3|v0?eq zmD>h#RNVJ+m~cvc7U+F~F$pn^VVvztkE;%B%l~7roxinhtR^wrv+r;5HVnZw|GO-< z?6ukcUau5eQ(FOz$4A@+@yTtyX=$~NS?XfhOP%j?{#ComDacoulwtTF`KFTAv)BiG( zV$5L6_jNmN;)e@m7ddmW|9a;HGBnr!^Q^cwmw?qNN1v-k$_^XGv@ObuO~xloq%1;2 zSi#nkGHn`^R$t>6NrBwnXjjw##>mJYIagG0N_ILbb2zCgd-){3|HZ(!X{nVvL}n}F zbmGi0nzdHd@y?<*>tN^IwiG1qL!In`yVf$q7C*7NHViJ+(9TV~NZbd8%HcP+6PakE1d-`7!Z6$@D0VAX}QM{Syx-qHz49u+Thd!HN*V+-m=6 ze0jr#jbj*dCrp>JX%}MFFg4zM^*r7;E7E1gg&B*ue zqEIgl0H{zjBU1H~I}7*-pGIy5Ie0bDlgI?(=51+7Gj_n?c|EuX?HyHu1r-+LK^6iU zP3lg{R!8h!LPJafpbe{PdpZ>Wc+r5XOG=>C(ryRxSVB>Q^swyJ5*Ms22c(bs@BzRv zB>*@~p`6bI0S(Zk@g}c+CZ*Rx^otn*K%N2=EDDY@N`sdcxX1%e!WSS960qzwEhE^v zfFR3TYPlARk+_F%F8Uo#9u|Wg`f55NzDwB=_yn4HUJZ z2`uAz%t67Olzs6YMk;1=F`A$n?H%qNu&YAOmPnpoIf8*&-X*eG!D;163#t z7~mkV7znP4kPX}-ps0zdu&kXRMwaC+qNo$HV546aC1FNOlZIwzp^W~35(Ygid#->y zSeZYF17uo5kPWCWqo@@Mp!lW0m1R1xMTZZ}T;5%K0FEau_z?o?PymJBLqIH89nStA7~dVre+jzx z>-4{ZPu0Up1$~c&nEDfxQoTu>I{8-5P;{ z=L9QitOxzD6dv*x!D75J$VVgtr=u&Y|36TB2?EMMopC??@DjlZ}^6fY${z`{$fgK+f6JPrL>4zwep~$Yp_%{$v03 ze;wROK%%5cy?0nmyi_$n|9$M5S`3g*YR~KmItCQ_8&AXp8CWqmKSD@y00{~R3OJzB zH^Wm>jGSdL1B2e%q9n}r+K?emEvlv@tj2%|$e6FQs@1M-)UE4}^{s8&_V!Mgb=vpe zoh&T!r1;`*8_3)5-JhS&pPS2{*~g#HHI!d!!gH7vNXt9Mxef5FqT#i^-B{<>3heDQ5hZx2HnF#?cvANo zXQ<}gPWwnwjawk43LVRM(8#58jK@8~`K<8nkKzw)kI|hOGjW>}HPJImOD_WSy4mar z?x!B^Y~;xX+_-ZHD@UjlZ1DhH0h|-K-i#gNZrlgHP=c!EGbx2kX2YL2;|{Dl^7(zC z!iGHOA}IIG0bGGn7-Fg&bZx|xR?;F@zru$_*iM|ZX$?i$&5NHxF5FU_?txK;DWMGb z(J{-(CILkgva}){eYd2%FXf>#7F##i*jFveJT*J%bmeCFwHIJ{m>`#I$OHX_<_BtBN@h%;_rH#Aw%X zBXnUli{KE2wMO)r*e%4Gc1B>>V8e>fWGVOA1+C9JCSf_~AZbwayd2v|QS0Ao)te2t zJmwSv^cb|^6oK@)2HXIqWa!O)AR4@@mTM12TOJ^t$YpA=qqgMpz=%>LL+nKk}6S$Sy*n&hWg-8-hYy z0(~on3>YO1)!rIXa@Vaz8@<#>k`3`%GuC3Q&tok&cV^YkuwAp?&(tQIY0?jCSsGxv z#w*a&vY^<*O0S!+5<;^rsvGPn@ztU@TAA#^z&k2kykxG1`K2VJZ(EfdwOpFe;)a&c zf(j(tyrcE(55NIiV`rs9NVAWi&xgQAyz8@r{ggLg&Sv?$Zf@%p%~=BJMBK1*%xUuU zw&Gh2%8oQAWKA_{*`!K9B6BrymvADM<>#eKlfamLe#^cx4o)ADxxfxW_}ZlvMd#XC z37C0G`Z(~i<#mFjs#oezCn4m};-a~KaacQ;{ZT_vkAVOI&p)N(P#x|WJf!LR3hR#- z2?XLAT9PLV`CYiM+V=*;_&ig*7)t{t;@u9ahCvZ33Wr-TD`-HtLsrj=tLYwedZWKf z^X$UIP>{qXX9gud&P)p|Q4_!DWDeR6*g@*dseiKAGdUwU7!`Gzd-<^(5;T8BVIH1|Mwu%eio0Y31X zT%{bab~f*Anl;w#U^Y>)rG+m+l-GSfuZYs8m&Ajb41gbDw|Uh=$$7{&Ml8grrw|#=oJNKL)^f*THo5Cv<#B-~cQ2u}8#e&Zwr_ zDKOV+_sz~OP>$DMS)+#k&J#ty@9`=Sq6pxqPUg}AFVHjgRZo){u34F2P{^RM7e6gH zS%4h~QPcjCUx~D`XEB2)lD<;pziBW76LV^Lt5-MqT^Yf!Ke;jEGd|OSaG& z0LY@m*G09H$LBK=XYcf?C&^Xt^GPzCp`&?dt9G~&o`L30A=o`3Dkz?jA*p`}{asCaswc7hkI6 zD%{{0bl8>|DIzTv?>de`$?XMGIEV?k(plF`@7QVcTR;5`;VW^+Wv*yP5~KCdCWSL4 zEzu>D>#u-kH0J7So%}P4Vq?aVl7%A&5 zDAYtTlbC8QPv#2?V91`o@od3Jj_70_|6|C($=wK5f}GI#Ja4cX{Ziview4 zQ#HnIBR`gT{;1(d4#GjrEd3yfc=qqLTOJ##^TSN7XJ2%BoitQjw2(ZJIImnQ6{@@Gb6iXB$>LRlnL@=!h0qv&Fs$}E=5p= zs44kvp^2GW(+ip9Gy~o{_$9#Lx*qpcuCO_)!H7$ks&SKL5TY#QYlf(yDnG zb6|6Mc1)=bb(&(qmG7plTS)6>&-L@9dO1urdx_*{+yrcV zROYhI^YDyVE#tA&enu~`ddnu2$Y!5Hw)mz|rk#G(gJTMn`lQyyjPSJ}v>oiVb&k$R zZ0z`E-MFg3*kndj=`z5r;|RD>PD|EOIVEG>#hZ0Fy0oe?60VdsK7@P2WWwzHPAkQY z=enJP+sl93b7yN$k|ja-`0J|MEt#%yIbNu)a%s&KgoU#DZYSPJfaTpW-F zQX2{T5!llr3n`JyPHXU2v+3p3(uhu9yvU;6WT238+)LhEr4GL&s!!GRWiD9n&M>b< zD_Ck3A_)?H^g@6+>=3$S<^#2)r>1Y%Fmm60;k{oy51MN;(d%z5sSW{;30tRR>^u7TMr&J; zdCWyUJeIn@S2zS1U|-Z79GMT~o=xujt5Z!xX4a#^Az=U^&CR^JvKbkw%(nCgHP0+> zf12Mc_C~B56cd}Wo?)#$s_)|sbOnZoa|6SYu8?ATNAyz9aPX~NWJV)BYJT^7D@<#7 z+wOX+#~GwTKaPUZ9iveZTb>TpKaHwNd$iGccUYs@4RIOODi|y%JuY5xpI761)0}f5 z%IPH1#2^4Wv5~WR!cxA?kB@Fe1o6=B&3JFsNDJ=q9X+-WM|_$i6tK1#t{n5Ak?U6{ z=q2sRfeEdAqZ3z`BmoAk?#lUJkN1qaFZeMh?Ey?FgH%qd9Q{fRama*P|!q zCTC{1`v9G8KptREERUlooQtS$1QCHcVmSZxv-@#T+{ z7BuZ;{-%wTd)U6iHH$r1$c;=P(ReO)xd1FJYB+ODxIUq-xZsb1?Hr|ajMIe8CkE)G zW$Cm%b84mXMIApEY3w##!ES((tQ7<243KFuuTo;=5H@oxRiZgL1H85Qy_(X#f}_ z`|>=XIDPlWXJp=0z8H)|4GRKsf{?qwD-Y+kBEI2!?UI-`-DeK8~#>G7rS>^E2m9Gw(gCj~=}~HsDwT zMHin6!P%|m!^klEoJ7twKO2A~Z2-d1T?ynzh{&A5Ae_-8U$0g@_WjcyAma8T){Yc* z!`tm}qYkKfM^{i=aw)xN^82HWjxq-ze!&bW%0XIQ z5o~ZW!n!xsQ+)Da@~MYeKPYFo>WWG&qwZiOkW0M*GmuA&Y_e2jkO4M1r_iprgz=4q z_oD!c1sTxXy>4)YG>l;iYk)xXhGO~xlQ1O|TesLA+D{2nEA8XJJhWB}aC2MSBmZ;k7P2+O_nI^V4Bj`}UN?refUv6v z(u9F}|2g^=K~bK350FN24KDot;yS4@wPSx*l^+Jg?5$!)2nYo2Le{?2@Woa=IqzeA zIfCb$iBj*O>P`6N+S1oj`N98PB*sNsW~idVPd#8cc-=3TN2lj>d&zJfu{L^u@Dn}< zNcZ@O>we{ee}I&Hkbu0%CqK+u4C>coF)a8q7!sTg!qP1F0kq$4`KzFB@Eof3MDDgrRzvcF+uNxb5iYspvirFYU7iww~eFGpbljtA9!ntM=eB04n zptco(JuPBz0a_$ssSpN@3Boj;-k~~Yg>jvtSQ^O;B9tT&j5KrgwEW^8J=9ue%B5V% z704_l(927+Lb0<3W+wz`ghFqSJQy(^K?V&&k3S;U04N{c7?GsN5tCcIa`+w5iVvTb7Slme~WAHMP1* zMCO8W;>swHFYd}{m2XdGqoJqnBQ05CITWSHZz5o5q&q^HkaW6fXCGO#(T0^(6;IsB zN^>=Jcve-_G<9`YR@K$1%G%0Wv$~(q6E3zUKow0DjnxV4(!NdoEl^q z+G#_R{1U<>9@}4(j;SZx6Y??Wjg|yA-SKKiE<8UpA=)jPV`!b}gKcVcu@V0UEE&3dfGM9b&^-%A(9{t+kAlf&Ix4+R>Lx)ayxe2;&*f{?7 zfN{NU!p}QpPcT0!j=fbI$e9V6VFr#dnTGT>FRV>ND9PZPU7AU*Rm$&1CGaEYhzYF@ zN%k?n^W^lac){}%@3^535^BkTZm|X47oOTUzum`<>NuyPcS`XGIZuo~E>SvE?h{=V zahDRuX0_;w6J%a#eOKpW|0G|i`*e}?rJTtMlLrc%zt>HJXP}}KkT(GrV zACa2`Z_RT3KHmqRSeccwuA`tK4wT-Z947+tGa9t9#gjhZ&X@?I<)2pWq@ZjxGuX>`Lw^I>xoKaH3QqgbQyNfPzzM zNmrvo<`3MtBS2t6Kc`z1Kn0@V8)#Nxg)stR&CywKg6o z;u1V&HzO@V)*QWEB+P#jw>eGw=~LqPUED%RPc{Q5OW_3PLFVSWE) zeBks+63;kI2ibX`|5872p{72NZQy^YA2=My|Db*x|4=`orah<<%>O#^mp=UK&RW6w zFD~3~bAkNt3mvMN{vm|mNw@qk08RBxHN?O4tqDX@C^CdV3(3`Mv~CNe)4*h|^^(aO zp~N9%&5j|8(SdYbjfB##eA}qt$!5tH)j5@*EYZ%6FJ1Pv1EQu zyPMzS%;y1q9%%eg-qeIpgaOoPaC8|}BQe>3bTlT8g<-HBUu?JaNQnrz0R&mu%uk|& zYLBI$9KyG*zN)=g%uQY!jLro@JFrwbwH{1k%JUAS+$p$GA*w0;7>CkASld!zDx>T* zPmF^v4cV#=E|VE6EHt4Qbg;fdq#Dzt?IXd#KJ^2^hFOW}-PL3*9pjXvd3Df?sq-*k z1NMs3%_TfEt^IR~r;G~%04e^S3@TBQ+>2tRgNgZ#Q=Iwd*ka~VQ|=;IdVoP@KVPag zE9sTdnqm-LLDIg1Y%1>NTvLe%m73qMVu;`X{zR&VyxgH#C}LMcTD^uzr&#G+@eOT@ zzhPapnoUxDoS`|LD1(=D$rf(_OY3M znO2)@ZCcDU2{Dk~lj1YIQu0oQ6D9+2a;kB^F&$=Ke6V0>|FnFH1n4~Gq-hhEv{?LO z{3u1~fgxQ6Lt&Xowh)W3JaIME;(T%C zyk#`Sp&}hFl(k_1VCH*fjC1n)#cq!R9@HH-;hr^a2`^{4bO7fdC+Qy6{nGMEEr5r? zIC}J&epK~fA785fddE?)~!i z_n(q}(e+GA=duMjyZWn%B9dpw0udI6)G1)RkF^zVs^{cUfKn$KlhTs7h;%-`YR*eU z$fei>7QBw3Ma+&2mgi`3ELFfjOPwpMY2kwz4mTr>G5irj8Qkd?-DrBfXxr5jgi%VY zQUk&x+U%qUkZp57wH-qnA(ZYw)G}Q80lhI0Rp%R%=%UfQ2`iG~Klo7>hX{wcmPl3y z`m+7%g+mJwke>S6*;rrqLxlXmYeR-AbdU>23x?`%6$#xBFf^|?3;9IcDga=QVeI<+4=HbQ%YJj+W zB4jr#hjmF|)G^bX5GV_E6J=}Z6c5mzVP7mQF|S+zT#h?|vljov5vnS=J}o)v%tpP? z?_awrmkdb_bXk|lYP68?xEHa)xs`;6D(8MgVjlaL7l?_sn@OwY!3-kvuSxMq?bNgf z#d%~1lmcDW$*OFJNt|0#|6ogN$HmMeR{1aD%#S%Y++$W8|EJ?3OGMYK@5 zix=}LbkRTD7AzH#t_s|F=EdxNigo>QzHSX3g_~zPeDE%^H`}yx9^X2b^5zcx!bitH zK*v7;sJ|i3+fs7-kX#B8!h+F2-P?h!igHK+AG}wuc=zv7XAHzDLrMJhNPMDK?r4sU z;ftcM7)(-9?cowg7?JHR2=_mQE{ksdegoKqp7A!q#bA!OL`bUi!+oT=DEDRg3sNyA z9F}QZDCmS4MJ5n`e~-FLAbiEfzU}!=F_NA!$$z(oSG_i@1w&|Lk}SG{H*N*Fu52Fz z{$eq(`Nj#G^a$n(56t?$R-5M(Og?UMZ@Z=qxX*XTMReZPL@QhU9wN&o-6*q^HXkzv zg&4a86+3s2{!7vx?A%Np4Qh#f{uq8hJsw5Muo%rx%6_$vLv5cW-FDwt?u=Zwrz$1L zV|I_Ng!&1p`t8~+DCv>J`#SK9(BDM{V1%-lJd3x0inwk5CQE$B->H)&WO9ypXyxt8 z>GR}ZF8Eufv6klrVv4s>jF z;3o?J^q(E3RMO&e671J6H`xDohxxz6qF>AjKAZrhMFrHq^mMirX*FXjjZ1il*j4$& zQNirP+`8%HYI6iKTFk6#5aDXu|13-z&G{txh5wtvCemR= z&XfSUZSciL$8+Van82nb@${*f#2wOQ8&;Vs{m{bp6Rok@oy*jh&#g#82pKyHBX8ab zi9{M|QdX@RkO3@ji4mkAv#8RZnwvCZo}#9hA8Y4len3jQrp)Vj5U&)pd9H&8mE;%; zPE>!&tjI)XZjaGq+;)Wy}G8 ziZ?8U^3gfLd6S16ae&07oM62dSYnZ8yM~x9BpEnI_->iSmy*;tjdI;%jDdZdDYzDy zp|8b;Ev&ps>^QTm)StaH?K5NPqXSgi4jFycj@J56UAHSUMqwkie!5XMrs}QQvQ$Zo zxliGBT`98P-w##tnmoTTg*kwW!6X5fLg7z$`X6))mM)}mTtUw7{(C{;I(wlKWeFWG zhW+B8b}Dxb)^CmhQPB7(?NDhvEiy)t#HEs-u)U9Rcdeg12VFg5x~{7P9d)^f1-mSq z57F_b61nE55{P_6UQ*2xPuXW=^E8Fa40f@u9~9-%3NwNi4vC#vC35KHQKSm4^&-m* z$|cq+B{FE#D%;i^3-9do7fInP2iwm2MEdx64l#xt!QQ`$85HrLLYor)uwvLXn6zpQkdhvOzmt6(fk`ZXgA`$TvJUuL%h=$yMc@TBt#dlc zY-rWU!n#H0GdX6{%UyH3Wi`8Q3`s89=r(T1W*&%s&-&VJECNL1`K`Td=f(pw2h*CLVO;8#B9%x?GF}+Fc5%aYxAr^82{N1 zsydkWFtY9Dz;zu|pxnmbMzn(6&b5ht3VR8~VW~rd-q_$#v%A@9wpLX)Uk%l@RHDkzK=aN&>CPliDG)tvbyn8y+F8~S|ucDkm?ryEB0sK)e@agtv(v~*5 z7XgHWN38q}@Sw$&u7!Sn3IS7s5M{WI5Hm_RVg?g;okj;UMsx2}ENAouHqet9X61-% z%qa-af@Fse_8K6MR4m-v#E}Cx4jNae4vVCz3S!Gq-);#;sH;iMpV@4ho)Qf@9FY-I1IdJC&mlV$}% zZr4Z4AX|=d^$g6_&f)>L#N!5PI~GHp?J&NmgF^n`#Du`n1sGRdl9yO~(U9-vK;G;+ zpZoQ_6AcD^v)9@BVtZ|iy7m6E(K{qP93?{w&Xr7nrIXR<2h*`+?P(f@LkF$8QEfrl z+Qx(#6Z#rjNQ2@X_95By62!M3U_y%jXwWgxJl)V=5D?%&jCGTf;AR$tA>E*~wN$BK zBCZCrnJ$Ak2|%8tvnAiU^Oz6N-qMg2@!D2kMz+<-kO+o{%Y-}ti58Og*;81>$F3c0 zV4=+7kv$p^of_|fEOK8Q%Ee5(a|xJVB*1ovN*>Rg$Y4d+Z(aeF+?ZDcE1Pjp3OiSvfT1DL1q($rwf@`8P z&&S{=CQddEdjz{5fNP4#UG?jQ7$vxRD(*SeaoUnxaZoeAb# zC2XjdEfbN!hlAudBYZAi`eRZUcw*qQn@MCvPtat1bjmp|&Qa zln4k~yvOUU ztUn*3b{oP^{3!I2yRwBbwgo2TeAqXu474;sM#m`R{E=}`;y+761p9Hy#~c~q2MX=R9)Zn!1jxSWkiXL)f#W3 zNqjDr%j>F^^*eI_5VhNJFK~1VXcw(W|I0v6C4-0KpG@&VU!=x5C~!tLf#sW0-dnAQ@`MM;T3G zeQfr|m%WkEOEouhU`9#Slw3{P?0O0Aqb^y!CsJ0G81lGp4o^UsDyOcShDEhwY)bl5 z9`wQHqV?*ogqzrxjHS{wtVM5{aal%n*pyGPkZ``TPQ&yM&Y^Hbu7kM94cu^ATNYd+ zgmZ>VH|RaL0;M`IzibLVAli8sa=kqPaER*%d(^IzUGux8 zIbPk#t;)e><8Ei z_f!#wQlb5fFD%rbJFL{)F$n+)BTszmNxQAre#NWi!xn!{{bRSO8TykypQZ1l@Og!+ zw%ijO_?s?7_xC3kpc*Tb0nq~<&c}!R+8;jG{Gd$AX28cr45zD6Zfb9l_{!jNck&j~ zZkTZMQ}_)r)(}JP?v(;*V9*1&Z)_LipVrC{1S%JuGVgB+o*5WyaZY7__yYey zA>$NsClH0>j|O-9c*JT(-;!1X1Q+~4xP0%9usxUai5C2(40>~X+869R;6%m^Yd^K) zEyi=MGHtw0_gUEc1yWINEMT`vR6`URXUYdDJSPIGtTIqo(c*r%q}J)0eB=u|HdWtVZ*FAP zUBdsT9h_BKDWY}H6X(J=S9}WuN316;TSuLvSNJ%`d_6g%a10P0bpEYs?eMy>H>2Rc zRN~pfa6l3T2bUm{;jWro63S%HJ3*}*7*=sBxiVCS*|_NPl0>U#u0b%%EHP`<{79#2 z+58y(g21}r`Ok$-GyT#bf+Ugf;mhwoQ5|-G1!{3SF1K8t5c`rb0)*_*?-sva>sK%z zpB&jcul=@!#jEC|vUSzBIia?qb(ZL9(OhD6edk*Hg4Ove9I+H~fL(&UyK;r%k@ZH? z9PJBw*6>NsO~{Thzv>ZS;3G6yf_<>jyZGH%5#B8q zS#Q63Mkij#X{KK@2zN`m2^c;7hJxRid|y!-U*NXlewKFl%-)Hww+}snS)>GVmY|YH zpS)D4@t^VMsEiao2|LK3Tu5HdMVyfUxP~71$(^ge@Jc5g$E_=;2PqaMl`aM1 z1du}Mnl#k4h=8h(`&qN~3sFdSo$t;XBcqe-3$^yrg72+gfb0|e>=&=?@5}uFS<63k zGN&5D_u#QjTxy3L>XNz1gTSqWK3m@6+@3l@16C!DKN&HfsOF+lvVB4o#UtD!T8tHi zsY^DP2t4^&3DQqrJWm&7*!S?u`h{!m8h!K$1h@0SuT0`ZFm|%ob}=(pC3!bX4pC!O zjt-}gI(i$n?E)q;WDu)MzIb|oLLT4wrq<@?=EJG7sd&TB zVkg6CobI*zL#5Ts_32?)$ta;t{bjsk>UzmEC@ItkK(TOE%yw$;7KYB9*~T=Z+MM9a z$<+__uembs^p_tzSOixOr;RtD0%EO zE=IYgCOMr{Rx_n+gDCs((Rmf?Q2$Lich$l1Lb1Cd4VnCvCzEa5DfUbBVZQA;sg{~I zo41vPy#LG6d@gu$rI;Q79-A@fP$sr%S!byt%VPGcYAxwtk2ou6)pT~%BFuCay(d-8 zt}$H%B4z7&?&ad++Z}tn z)Ns)0+U>)~t%p@tl*7$kt36s}i6^W>cUeVRO|fql!Ih(?ZjJ|-yc!q3i;+p7N-iwm z-dD56I-);0`k6oIF?KEVABZn_vDPubs;)TE>NnWy^+!^dT_D8ZBf72>v zKe4oGBV{=n*Bd3yW*s~GWw5zCqWM3>L2t?N|-66TBNII z^gynb7Lqq{zrP`Q9PjU|OZLJylVYooejaKumtD)-E+c%5%S_4T-k!-jL0Yc*sm2$J zeHZu2U*023Ek0iet)}Ry2pf{hh?HgPKP|PnHRlY=U|%O!?E?-1&wTMI2w;})U{_Gp zMfJaKtp>LNTaWl%AXbCrj9OJ2!yJLtvcD8;Ax3K>12Y^MzwB&P_w}(~m#%Z^4-is3i@AvG4R+{kz4@}I zYPw?++H06%HGYA${6o1XC(VtlC8n(9W0uY1sYMy!G`g|E`Ro!GHckiSjZx#4&fi(i3{q9|_G2m_yyj z$G0;PAJzWc!Nxta&(w<~@6qtgzr-u%XC}+-3DrE6vnz#5aE(&a9|HdYd@-}3e?t*} z{kkXmfB8q!PZNDo-7{L#Sb@`jC8Q?JzPbPEK}q@}zng3aZ~j$>^hXx{B_(VVrl|iP zDNzH;Xd0~!1Of~{83E@R5QTaJ-{kz&xtEKNmMPM+=OkW`32RrQJVyJ|SbL-zBj`(Q zlmG(@vN5uu<;D}vhj@;4Lp@LCE_>|Qi*#0rH&;YPl;-d4tv$c*RdQXl$>vwvN!?7q%-NXe z>+4Sw7(j>Ja?WGlXXc^6TB&BscOYb2c=9Rr1Y#d71eCQBKSOP?$iC; zO2mGcL3~~bRMA?${8c~|?3(9KR0s#RA)aUIB5~!bKJ7kyd70{@c8gZ2wX+~=#9&M- zUl;jfgINfU?=9+Kera@4D=AKgBt6M{qH~$$6JXiHFsunDn!AW?qt>$Fo^DU+m8eXU zz0`6=>?{YTpAFVHxEfPaI6O%fB*!LU^K`5Lr?&k@e_~S9>F6eq+2P7pNwL_H<5&- zJD`M%fQq0o?o(-Nv^efOJZ{$4XWw{y6dEfsPL)93*XI-Z-QA{5l95sJp)C9yoZJCL za*KF`RB6c{BDsHU*dIpJXVx`2KZF~9fnkvy^}8mBd!WhO^VkCsj%HnI6nq%W#we=V z_Ca)<+PQgj$tE@L4Tk4CQ_|i&d=rbP;J+F0UkgN2y?Fk=v9BUc z{qNc}^?$su7`3I3Z6|5%zh8ixK|cc`XMB78zl>h+8N9}w|BUtSO;)!P#Qd{mY* ze)2qzW@PN}LBX+*83`@$r+9-P*Qq7Ax0(xZKHEPM8Yn2=;fJXWe^l zeP(~&XMCQYDae6rP;0PtfN@9w;~tOUu4}xBG6WORq{`DlOqZ>j(JskBD$blpY@<^a zaVe2F6*=c7LsKS+mT>q1&%<2i^^mj;xSDv|JUmGaCY=;mY{tN=E*NQp$5Iiz@>%|X zISG-^>dmQWe``Wog~nkijTbc_dEItD1pOQxEPotR?k`FVht=iVd*OV5QWyrApNe5b z=rD{{|8^I0g2|E;>@9yLvnyzW%5Rw4wUw4S>#X080~;6*ZD8&9%y>k!Wz=e=e5u$> z+_XGRqv4v0;-BBxR-kGGtnfI~Oe9QOxUs`ih9oPw`1mndP+iuvMx{118Fz*yeGA&i z8S^UC8LiV6H3=xIsK40&Rwd_Epnf?A9 zXWyLx^-kWRZ7GcI(IXJkPj<$PX3#3G6eMCcaS5$Q?rsalaT?J&lfW}sJntrNWpHSS z(SDkFwj%J)m{eqWsw6RtR|&^X$uX%7OIPM;l{*i zD<}Jn#_1ffjc}f$6UEI)HiQ^*;)T!Y97!*JyVA`I2yRf|MZR#9qd<9%Aoq{iLqL-1x6jU!r6f3&6!7%M)1ITuTViWX z#r*7n&u9;7GqaMqY`Q=Ga`%Sas)O2~sk6OO;v8PMN%qV&`$*dTk{S;$7i(0O{0lEy zirylr%k+4@YqFu?32SB(tR|T^X<$v_qk2~;lsPW}GIH_&-S6KTgML;#!V#Kmt8s1T z9$i~Zzmbko2`S4E&O9Jsg_5EtWAJEU8KN5fh#12-T{aSIj-a7S@(+ACsU2L)5+q2k zH;(dcH6k{mGsZNgvNpn1DpT(ic#`EttRK-4`4EcK2D?1C=^&$cB6w+weOf^scy>qv z?%~aT>!z=8Izk@G;oT0}BfgPXCM{FIIZ0X15RWARRFSqy4mo#bjh77lj9*})r-YIQ zSQ&_U0App`v*BmP6f&jh2QE?l+C>}a%URQQ^mH*4oTW~ zsmRO&U`;u*$p9*&qj!#pES`e-`zTHGMDjPBR!u^3`)#gY z@Nzr=G~x=mZKTd^HJ|hnb=(+Dd8&vm;53F`Q(K0?@DUaD=zN@%EJ?9f+bKz0y^A?1 zIear+akrU9`{Y~EcLLT$!J62qK6cXl*5*#rb~UUm3BhClH&QGRMHb^zexDA68F6SS zb&V~Bh2lV~`Wj>Sa!QGwE9P_H-SGWpVHp~LozLP^S}r>aZ|l@mne)plW`^)q{4(9h zSpJC&1S=)y0@8?2Yr_NBY99L!vc_~+w$G<`j`$Rtk)9ll`;GhoXb=S zpi742qFA3(r(08#a{Bg@%>50_EAD_=lJ{E~4?qqij@~1j(pR{D>CARF-1@jL`jG{o zl?|blZRIlKXi)Kab2ifmLz*U<)JLv)oOZt56J#-hxLbY0f!jsaP?;;HuyNLCB?3g* zH-*FFG$=N?hmT8~UFNn#i~aHMO$r;52Ci{_I&Crs?=-*YC+|U>J-l`O0{NSeVj^-> z!BMoEr-$7qaOjQQlf@z-TE~~e9{T`@|K|7m4U}MVQNE;U3>~a0?g9Nno$^({?ZATy z{eyF}aCf_9DmI5(oEWnsPdW*Je46G?g!VTB>f6+hHwq3!617fJk52U160}bnx=9&U zPVw**aWgf+8F#a*g=O+N99V7DJmn5~pK3t;W7(MHBnm;rn#1RtHt|U74yO#@mRKVq zr9V7Z)i;n%pM=xaHENv_-e69XRMH`MZ4IUM3R@!qo%bmS0U9XEnn%p_N!3+ig`QTc zAdzw@fXbU{lxa&TAXr_QPDucJ88fvlAtrQ@O>PYFCK1Ij4P14~OKH(ko8XOwuHTQ~ z#(~_@;27k?$~tN{_TfyxZutZ(4FRjOlP|2*2`w=AyLo>ZH9%S!K{1RQ1BFV|eSn~vkE?KI1iR31C z%(0`xNZgj^V34I$rIm6(>Udi6CK~3@uh3X8wHhurwHGmGFnF3D=uu}hGzo}tmNot} z5L}y;@tNiXf2t0#hi<r@l~C9e7)PSE39{D!pGgo;>F+`;+T8VYp=ci^a!Jy>|*sN)80 zilMlz-(m!uCa|sGi>$gs0rGt#rElL`*7-^+#kW2?+R(XAyz&$kI$H0w@Q668I$vROcIf$ zc7jv&MH$EUvlWSQ+9$7QeKqW~W72(D9C6fHz1VlQuHrbt5*N3YEH}aBXt8)`DH;}s z*z$qahw<$7XQKc#5FPn~TWft!NE7{V+GVUNn>+TJTnI$GN|pXq>(vpsGs$N|d%wcFx z2-JGY_d`xuz#|xwPX(KlGqNlXrtex^e{zXcsa^5TUT2b5kyUHgrk)*eYoq0kvM6V5 zr_?xy_gu2Zr5L&QH|dmw-ae91zKck$G)n_ZxcQj69zm*-@3s)xRCoL`aB?|AWj)54p)QOnJr%he*H0L za%1VYGyHQcjQ3&;^5}-*>;khB>$Vm72lsXqD4h{;kWdV%zl$-LhiNM68|V|X$h=qZ<#gO#*9erTu$Z>;2F!JXB$VVZ2 zBy32ydn|7l>K>~{VDk0UF^_Zf)%c4)E-w7kBkl(oA90uT$DQ)jyF0TBx1-j66u@pC)x#eg1_cK`VX``xMI1CwVOVy!`@ zH+0JRFkhs;_gMUXo&fvTf9HPjZ_rbGoP-<2_ZMZa)cL$$_MyaI!Uh{1M0 z%Cb`R1ILM~{4~F4$D5O~IzNA?Y;pe`FDMK^xhj?K3vjN1j;q%Vl+ICkv>#6l1s8SM z>rKD1lvK1|-ch+yv1)Q=eK@~PlsRYR0LB?O-(^799ql!_QG`1o>u@s^rNc(`ejFQb zU4gQ&h_Hr(oh*7;?O|RZ1FG&7oer&DfIYOfA{T?OfOpQ17WLt8?|fbat;`_XKXjZ2VY^KFH_3`u_0`e5Rh>g%{XE z*jn9Qm9zg_Uist99<(PoUTS(P@mWSFE7-oi^*lC3(1vBiI>x;aV40(`=iC<>Fn?>8 z%^TNyarp|$_MY44PQPc0fqi@`;HnQ8s`xuHG7j}6UB#e+{LrA}w{+1PKK+fVkMz*h zF&Gz-8eoaLPo+M7ie>g5G?mDrF*KnuQrVu3)t<`M5V7n4GO9bZU}yHlfAU4>%rjCg zcb8*GKDo$rtij^TjXxrPpT~U3>4o@HGBY#yW^Y!k9rHJgBo2FA>&ZzUu+QWJA3vJBQRH7qrf6o40V zxqV5&5T1fxF6{i>ZC=VvPLLCOW3_C_Ctk-J2W{jIsc&Y|8)*IB6T9xo*Q9u{y?4c3 z^QzXMrBm>%z-9;JNbe5Pg}gE;3{tg|LYet!T)HBf0~GzdUuxoZGh7buE0RA zW&ZiYK-d{OK-M0I1M$zLI;_E&Qn8hn?(eX*1`sqhS{15nVTPjZd3q%J4x*04W&PcZ zeLsl?96}6Z(V1>fpU2QAI9^O?hdZD-t@dwcn5=0O(8l!gKoJHgBj}N3#}T>6Nb%;<#9lt2PUydDfE_^MHXqclk+n9J8uT`en0M>7 z_H0!>LiD?c`_!MU?w`aaQr`}3$NgTsn+)Fv8BQ<^dhi zDJKR>l)LL=8gAthb&7)e8_Z2@b$@iHdN%sXb5)KG3Nj_qKa-8`gq1We?lGwgUn_|m z7{Ua2_Ec%*%|{zmXE@jP6qftkyzETNi&9Q@@pn$mdo0s@t+aAmtHE#;>j}li zVp^{zhZFM5>x8eOzUmPBIQ#C)Xs=+ut7`r{tD!Pwlj}32}=bpV4W{Hp9sgiD@ND2Aug3Gq#_Q+PDrWh70ypDli+gejx#jt#+7uR; znoaK?^%)!TGv0}(&2=*!z%wX~%tDIOT3bWj>6EQ!qg-~4+({Z0NVL3R#O%>?H2s&I zzh^YHv>~Z;3zvvfHu5tIyu17`AiO1T#rDPirN86)-O5QC^hK=t*yl?R)aMfEjq}LV zL1Z!H(Io*|=1^ra;l3B|35A=cvqXnM)wC5%{Ui%#&-h9T-$Me=%s3!rFXrbPx0?V_ zr3?fa!}eTbSW|B^jN{=og(D0*TJ5A3Fc72xDssl+%uu2vOs0MXW)Gy{2p(QkV7tU4 zU^}GN?K%j7_;iFJil9wq9Vi7G&MAlpaEk=&byZ;31alL*2!XkmjsG8@H}E3p4YWcL zBQ=t%Kkw4j5lI4tX2AQe+72h zWrkpt$SOdPC5qtiX++rqR)$v>)`O=$Prwd_B6k0I7399fpH6&@BH)Jsprz0m0nWU4 zB6)*_vL+2dW+>oL0>b#PSYgBc@kU~=go3E<2H`)5a>`4A5B_ruVUEcnSF{398vxm( zKoOS!$P|u#BENxo;#G=DI9S>9AYW*w{4~VLeOXl$x%0~eY)mGw?Sq-i3W%b385*6h zfqal`B0RpdL*!gMN5GyhLTsElC6R3n*cR>~HoCJc?8;w{t{5TNwDXe%0-2bUCp82kVtj0S#eN1}UBuoGc4-@7V%0R$-%e9)K9 zeg=S=pz5uT0LA}PSfip^`y2sQQ&hh!BTM>e(_0{#z8jL8F$ln54~}SUNI|dlx4H!& zXM)WTq>WObzFl|P1T#msq8x>XnE3IAcBF6+$Fgxe+`fX-6oQxB>z>=dW+;t hz|3iLnD|Kq Date: Wed, 24 Mar 2021 08:34:41 -0400 Subject: [PATCH 113/116] Some list box changes --- .../fdf/frames/AbstractRenderableFrame.java | 3 + .../parsers/fdf/frames/ListBoxFrame.java | 90 ++++++++++++++++++- 2 files changed, 91 insertions(+), 2 deletions(-) diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java index a89d357..aeb61a4 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java @@ -381,4 +381,7 @@ public abstract class AbstractRenderableFrame implements UIFrame { return this.name; } + public Rectangle getRenderBounds() { + return this.renderBounds; + } } diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/ListBoxFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/ListBoxFrame.java index 50d250e..a1d46f0 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/ListBoxFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/ListBoxFrame.java @@ -4,6 +4,9 @@ import java.util.ArrayList; import java.util.List; import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.Pixmap; +import com.badlogic.gdx.graphics.Pixmap.Format; +import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.GlyphLayout; import com.badlogic.gdx.graphics.g2d.SpriteBatch; @@ -13,14 +16,35 @@ import com.etheller.warsmash.parsers.fdf.datamodel.FramePoint; import com.etheller.warsmash.parsers.fdf.datamodel.TextJustify; public class ListBoxFrame extends ControlFrame { + // TODO where are these colors in the UI definition files? + private static final Color SELECT_COLOR = Color.BLUE; + private static final Color MOUSE_OVER_HIGHLIGHT_COLOR = new Color(0.3f, 0.3f, 1.0f, 0.25f); + private final List listItems = new ArrayList<>(); private final List stringFrames = new ArrayList<>(); private BitmapFont frameFont; private float listBoxBorder; + private int selectedIndex = -1; + private int mouseOverIndex = -1; + + private final TextureFrame selectionFrame; + private final TextureFrame mouseHighlightFrame; + private GameUI gameUI; + private Viewport viewport; public ListBoxFrame(final String name, final UIFrame parent, final Viewport viewport) { super(name, parent); this.listBoxBorder = GameUI.convertX(viewport, 0.01f); + this.selectionFrame = new TextureFrame(null, this, false, null); + this.mouseHighlightFrame = new TextureFrame(null, this, false, null); + final Pixmap pixmap = new Pixmap(1, 1, Format.RGBA8888); + pixmap.setColor(SELECT_COLOR); + pixmap.fill(); + this.selectionFrame.setTexture(new Texture(pixmap)); + final Pixmap mousePixmap = new Pixmap(1, 1, Format.RGBA8888); + mousePixmap.setColor(MOUSE_OVER_HIGHLIGHT_COLOR); + mousePixmap.fill(); + this.mouseHighlightFrame.setTexture(new Texture(mousePixmap)); } public void setListBoxBorder(final float listBoxBorder) { @@ -37,6 +61,8 @@ public class ListBoxFrame extends ControlFrame { @Override protected void innerPositionBounds(final GameUI gameUI, final Viewport viewport) { + this.gameUI = gameUI; + this.viewport = viewport; super.innerPositionBounds(gameUI, viewport); updateUI(gameUI, viewport); } @@ -45,11 +71,15 @@ public class ListBoxFrame extends ControlFrame { for (final SingleStringFrame frame : this.stringFrames) { frame.positionBounds(gameUI, viewport); } + this.selectionFrame.positionBounds(gameUI, viewport); + this.mouseHighlightFrame.positionBounds(gameUI, viewport); } @Override protected void internalRender(final SpriteBatch batch, final BitmapFont baseFont, final GlyphLayout glyphLayout) { super.internalRender(batch, baseFont, glyphLayout); + this.selectionFrame.render(batch, baseFont, glyphLayout); + this.mouseHighlightFrame.render(batch, baseFont, glyphLayout); for (final SingleStringFrame frame : this.stringFrames) { frame.render(batch, baseFont, glyphLayout); } @@ -76,15 +106,27 @@ public class ListBoxFrame extends ControlFrame { // updateUI(gameUI, viewport); } + public void setSelectedIndex(final int selectedIndex) { + this.selectedIndex = selectedIndex; + } + + public int getSelectedIndex() { + return this.selectedIndex; + } + private void updateUI(final GameUI gameUI, final Viewport viewport) { this.stringFrames.clear(); SingleStringFrame prev = null; int i = 0; + boolean foundSelected = false; + boolean foundMouseOver = false; for (final String string : this.listItems) { + final boolean selected = (i == this.selectedIndex); + final boolean mousedOver = (i == this.mouseOverIndex); final SingleStringFrame stringFrame = new SingleStringFrame("LISTY" + i++, this, Color.WHITE, TextJustify.LEFT, TextJustify.MIDDLE, this.frameFont); stringFrame.setText(string); - stringFrame.setWidth(this.renderBounds.width); + stringFrame.setWidth(this.renderBounds.width - (this.listBoxBorder * 2)); stringFrame.setHeight(this.frameFont.getLineHeight()); if (prev != null) { stringFrame.addSetPoint(new SetPoint(FramePoint.TOPLEFT, prev, FramePoint.BOTTOMLEFT, 0, 0)); @@ -95,16 +137,60 @@ public class ListBoxFrame extends ControlFrame { } this.stringFrames.add(stringFrame); prev = stringFrame; + if (selected) { + this.selectionFrame + .addSetPoint(new SetPoint(FramePoint.TOPLEFT, stringFrame, FramePoint.TOPLEFT, 0, 0)); + this.selectionFrame + .addSetPoint(new SetPoint(FramePoint.BOTTOMRIGHT, stringFrame, FramePoint.BOTTOMRIGHT, 0, 0)); + foundSelected = true; + } + else if (mousedOver) { + this.mouseHighlightFrame + .addSetPoint(new SetPoint(FramePoint.TOPLEFT, stringFrame, FramePoint.TOPLEFT, 0, 0)); + this.mouseHighlightFrame + .addSetPoint(new SetPoint(FramePoint.BOTTOMRIGHT, stringFrame, FramePoint.BOTTOMRIGHT, 0, 0)); + foundMouseOver = true; + } } + this.selectionFrame.setVisible(foundSelected); + this.mouseHighlightFrame.setVisible(foundMouseOver); positionChildren(gameUI, viewport); } @Override public UIFrame touchDown(final float screenX, final float screenY, final int button) { if (isVisible() && this.renderBounds.contains(screenX, screenY)) { - + int index = 0; + for (final SingleStringFrame stringFrame : this.stringFrames) { + if (stringFrame.getRenderBounds().contains(screenX, screenY)) { + this.selectedIndex = index; + System.out.println("selected: " + index); + } + index++; + } + updateUI(this.gameUI, this.viewport); return this; } return super.touchDown(screenX, screenY, button); } + + @Override + public UIFrame getFrameChildUnderMouse(final float screenX, final float screenY) { + if (isVisible() && this.renderBounds.contains(screenX, screenY)) { + int index = 0; + int mouseOverIndex = -1; + for (final SingleStringFrame stringFrame : this.stringFrames) { + if (stringFrame.getRenderBounds().contains(screenX, screenY)) { + mouseOverIndex = index; + System.out.println("moused over: " + index); + } + index++; + } + if (this.mouseOverIndex != mouseOverIndex) { + this.mouseOverIndex = mouseOverIndex; + updateUI(this.gameUI, this.viewport); + } + } + return super.getFrameChildUnderMouse(screenX, screenY); + } } From 94192c783c59679c675b0b1fce2f08d1892e2656 Mon Sep 17 00:00:00 2001 From: Retera Date: Thu, 25 Mar 2021 08:30:41 -0400 Subject: [PATCH 114/116] Profile selection functionality --- .../etheller/warsmash/parsers/fdf/GameUI.java | 3 +- .../parsers/fdf/frames/EditBoxFrame.java | 15 +++ .../parsers/fdf/frames/ListBoxFrame.java | 18 ++-- .../warsmash/util/WarsmashConstants.java | 2 +- .../viewer5/handlers/w3x/ui/MenuUI.java | 99 +++++++++++++++++-- .../handlers/w3x/ui/PlayerProfile.java | 13 +++ .../handlers/w3x/ui/PlayerProfileManager.java | 93 +++++++++++++++++ 7 files changed, 229 insertions(+), 14 deletions(-) create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/PlayerProfile.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/PlayerProfileManager.java diff --git a/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java index 990bee2..b4164d9 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java @@ -349,7 +349,8 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { // a good idea? inflatedFrame = spriteFrame; } - else if ("FRAME".equals(frameDefinition.getFrameType())) { + else if ("FRAME".equals(frameDefinition.getFrameType()) + || "DIALOG".equals(frameDefinition.getFrameType())) { final SimpleFrame simpleFrame = new SimpleFrame(frameDefinition.getName(), parent); // TODO: we should not need to put ourselves in this map 2x, but we do // since there are nested inflate calls happening before the general case diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/EditBoxFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/EditBoxFrame.java index 5ff574e..db1fbcd 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/EditBoxFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/EditBoxFrame.java @@ -25,6 +25,7 @@ public class EditBoxFrame extends AbstractRenderableFrame implements FocusableFr private GameUI gameUI; private Viewport viewport; private GlyphLayout glyphLayout; + private Runnable onChange; public EditBoxFrame(final String name, final UIFrame parent, final float editBorderSize, final Color editCursorColor) { @@ -107,6 +108,9 @@ public class EditBoxFrame extends AbstractRenderableFrame implements FocusableFr final String newText = prevText.substring(0, cursorIndex - 1) + prevText.substring(cursorIndex, prevTextLength); this.editTextFrame.setText(newText, this.gameUI, this.viewport); + if (this.onChange != null) { + this.onChange.run(); + } } break; } @@ -129,6 +133,9 @@ public class EditBoxFrame extends AbstractRenderableFrame implements FocusableFr + prevText.substring(cursorIndex, prevTextLength); this.editTextFrame.setText(newText, this.gameUI, this.viewport); this.cursorIndex++; + if (this.onChange != null) { + this.onChange.run(); + } } return false; } @@ -161,4 +168,12 @@ public class EditBoxFrame extends AbstractRenderableFrame implements FocusableFr return super.touchDown(screenX, screenY, button); } + public String getText() { + return this.editTextFrame.getText(); + } + + public void setOnChange(final Runnable onChange) { + this.onChange = onChange; + } + } diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/ListBoxFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/ListBoxFrame.java index a1d46f0..3816045 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/ListBoxFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/ListBoxFrame.java @@ -31,6 +31,7 @@ public class ListBoxFrame extends ControlFrame { private final TextureFrame mouseHighlightFrame; private GameUI gameUI; private Viewport viewport; + private Runnable onSelect; public ListBoxFrame(final String name, final UIFrame parent, final Viewport viewport) { super(name, parent); @@ -87,23 +88,23 @@ public class ListBoxFrame extends ControlFrame { public void addItem(final String item, final GameUI gameUI, final Viewport viewport) { this.listItems.add(item); -// updateUI(gameUI, viewport); + updateUI(gameUI, viewport); } public void setItems(final List items, final GameUI gameUI, final Viewport viewport) { this.listItems.clear(); this.listItems.addAll(items); -// updateUI(gameUI, viewport); + updateUI(gameUI, viewport); } public void removeItem(final String item, final GameUI gameUI, final Viewport viewport) { this.listItems.remove(item); -// updateUI(gameUI, viewport); + updateUI(gameUI, viewport); } public void removeItem(final int index, final GameUI gameUI, final Viewport viewport) { this.listItems.remove(index); -// updateUI(gameUI, viewport); + updateUI(gameUI, viewport); } public void setSelectedIndex(final int selectedIndex) { @@ -164,11 +165,13 @@ public class ListBoxFrame extends ControlFrame { for (final SingleStringFrame stringFrame : this.stringFrames) { if (stringFrame.getRenderBounds().contains(screenX, screenY)) { this.selectedIndex = index; - System.out.println("selected: " + index); } index++; } updateUI(this.gameUI, this.viewport); + if (this.onSelect != null) { + this.onSelect.run(); + } return this; } return super.touchDown(screenX, screenY, button); @@ -182,7 +185,6 @@ public class ListBoxFrame extends ControlFrame { for (final SingleStringFrame stringFrame : this.stringFrames) { if (stringFrame.getRenderBounds().contains(screenX, screenY)) { mouseOverIndex = index; - System.out.println("moused over: " + index); } index++; } @@ -193,4 +195,8 @@ public class ListBoxFrame extends ControlFrame { } return super.getFrameChildUnderMouse(screenX, screenY); } + + public void setOnSelect(final Runnable onSelect) { + this.onSelect = onSelect; + } } diff --git a/core/src/com/etheller/warsmash/util/WarsmashConstants.java b/core/src/com/etheller/warsmash/util/WarsmashConstants.java index 6b2e4ab..694d5b2 100644 --- a/core/src/com/etheller/warsmash/util/WarsmashConstants.java +++ b/core/src/com/etheller/warsmash/util/WarsmashConstants.java @@ -6,7 +6,7 @@ public class WarsmashConstants { * With version, we use 0 for RoC, 1 for TFT emulation, and probably 2+ or * whatever for custom mods and other stuff */ - public static int GAME_VERSION = 1; + public static int GAME_VERSION = 0; public static final int REPLACEABLE_TEXTURE_LIMIT = 64; public static final float SIMULATION_STEP_TIME = 1 / 20f; public static final int PORT_NUMBER = 6115; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MenuUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MenuUI.java index b33da59..c9b791e 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MenuUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MenuUI.java @@ -127,6 +127,9 @@ public class MenuUI { private GlueTextButtonFrame addProfileButton; private GlueTextButtonFrame deleteProfileButton; private GlueTextButtonFrame selectProfileButton; + private final PlayerProfileManager profileManager; + private StringFrame profileNameText; + private UIFrame confirmDialog; public MenuUI(final DataSource dataSource, final Viewport uiViewport, final Scene uiScene, final MdxViewer viewer, final WarsmashGdxMultiScreenGame screenManager, final SingleModelScreen menuScreen, @@ -151,6 +154,8 @@ public class MenuUI { catch (final IOException e) { throw new RuntimeException(e); } + + this.profileManager = PlayerProfileManager.loadFromGdx(); } public float getHeightRatioCorrection() { @@ -273,20 +278,92 @@ public class MenuUI { this.profilePanel.setVisible(false); this.newProfileEditBox = (EditBoxFrame) this.rootFrame.getFrameByName("NewProfileEditBox", 0); + this.newProfileEditBox.setOnChange(new Runnable() { + @Override + public void run() { + MenuUI.this.addProfileButton + .setEnabled(!MenuUI.this.profileManager.hasProfile(MenuUI.this.newProfileEditBox.getText())); + } + }); final StringFrame profileListText = (StringFrame) this.rootFrame.getFrameByName("ProfileListText", 0); final SimpleFrame profileListContainer = (SimpleFrame) this.rootFrame.getFrameByName("ProfileListContainer", 0); final ListBoxFrame profileListBox = (ListBoxFrame) this.rootFrame.createFrameByType("LISTBOX", "ListBoxWar3", profileListContainer, "WITHCHILDREN", 0); profileListBox.setSetAllPoints(true); profileListBox.setFrameFont(profileListText.getFrameFont()); - profileListBox.addItem("Test1", this.rootFrame, this.uiViewport); - profileListBox.addItem("Test2", this.rootFrame, this.uiViewport); - profileListBox.addItem("Test3", this.rootFrame, this.uiViewport); + for (final PlayerProfile profile : this.profileManager.getProfiles()) { + profileListBox.addItem(profile.getName(), this.rootFrame, this.uiViewport); + } profileListContainer.add(profileListBox); this.addProfileButton = (GlueTextButtonFrame) this.rootFrame.getFrameByName("AddProfileButton", 0); this.deleteProfileButton = (GlueTextButtonFrame) this.rootFrame.getFrameByName("DeleteProfileButton", 0); this.selectProfileButton = (GlueTextButtonFrame) this.rootFrame.getFrameByName("SelectProfileButton", 0); + this.selectProfileButton.setEnabled(false); + this.deleteProfileButton.setEnabled(false); + this.addProfileButton.setOnClick(new Runnable() { + @Override + public void run() { + final String newProfileName = MenuUI.this.newProfileEditBox.getText(); + if (!newProfileName.isEmpty() && !MenuUI.this.profileManager.hasProfile(newProfileName)) { + MenuUI.this.profileManager.addProfile(newProfileName); + profileListBox.addItem(newProfileName, MenuUI.this.rootFrame, MenuUI.this.uiViewport); + MenuUI.this.addProfileButton.setEnabled(false); + } + } + }); + this.deleteProfileButton.setOnClick(new Runnable() { + @Override + public void run() { + final int selectedIndex = profileListBox.getSelectedIndex(); + final boolean validSelect = (selectedIndex >= 0) + && (selectedIndex < MenuUI.this.profileManager.getProfiles().size()); + if (validSelect) { + if (MenuUI.this.profileManager.getProfiles().size() > 1) { + final PlayerProfile profileToRemove = MenuUI.this.profileManager.getProfiles() + .get(selectedIndex); + final String removeProfileName = profileToRemove.getName(); + final boolean deletingCurrentProfile = removeProfileName + .equals(MenuUI.this.profileManager.getCurrentProfile()); + MenuUI.this.profileManager.removeProfile(profileToRemove); + profileListBox.removeItem(selectedIndex, MenuUI.this.rootFrame, MenuUI.this.uiViewport); + if (deletingCurrentProfile) { + setCurrentProfile(MenuUI.this.profileManager.getProfiles().get(0).getName()); + } + } + } + } + }); + this.selectProfileButton.setOnClick(new Runnable() { + @Override + public void run() { + final int selectedIndex = profileListBox.getSelectedIndex(); + final boolean validSelect = (selectedIndex >= 0) + && (selectedIndex < MenuUI.this.profileManager.getProfiles().size()); + if (validSelect) { + final PlayerProfile profileToSelect = MenuUI.this.profileManager.getProfiles().get(selectedIndex); + final String selectedProfileName = profileToSelect.getName(); + setCurrentProfile(selectedProfileName); + + MenuUI.this.glueSpriteLayerTopLeft.setSequence("RealmSelection Death"); + MenuUI.this.profilePanel.setVisible(false); + MenuUI.this.menuState = MenuState.SINGLE_PLAYER; + setSinglePlayerButtonsEnabled(false); + } + + } + + }); + profileListBox.setOnSelect(new Runnable() { + @Override + public void run() { + final int selectedIndex = profileListBox.getSelectedIndex(); + final boolean validSelect = (selectedIndex >= 0) + && (selectedIndex < MenuUI.this.profileManager.getProfiles().size()); + MenuUI.this.selectProfileButton.setEnabled(validSelect); + MenuUI.this.deleteProfileButton.setEnabled(validSelect); + } + }); this.singlePlayerMainPanel = this.rootFrame.getFrameByName("MainPanel", 0); @@ -300,8 +377,8 @@ public class MenuUI { this.singlePlayerCancelButton = (GlueTextButtonFrame) this.rootFrame.getFrameByName("CancelButton", 0); - final StringFrame profileNameText = (StringFrame) this.rootFrame.getFrameByName("ProfileNameText", 0); - this.rootFrame.setText(profileNameText, "WorldEdit"); + this.profileNameText = (StringFrame) this.rootFrame.getFrameByName("ProfileNameText", 0); + this.rootFrame.setText(this.profileNameText, this.profileManager.getCurrentProfile()); setSinglePlayerButtonsEnabled(true); @@ -309,7 +386,7 @@ public class MenuUI { @Override public void run() { MenuUI.this.glueSpriteLayerTopLeft.setSequence("RealmSelection Birth"); - setSinglePlayerButtonsEnabled(true); + setSinglePlayerButtonsEnabled(false); MenuUI.this.menuState = MenuState.SINGLE_PLAYER_PROFILE; } }); @@ -412,6 +489,9 @@ public class MenuUI { } } + this.confirmDialog = this.rootFrame.createFrame("DialogWar3", this.rootFrame, 0, 0); + this.confirmDialog.setVisible(false); + // position all this.rootFrame.positionBounds(this.rootFrame, this.uiViewport); @@ -425,6 +505,11 @@ public class MenuUI { this.glueScreenLoop.play(this.uiScene.audioContext, 0f, 0f, 0f); } + private void setCurrentProfile(final String selectedProfileName) { + this.profileManager.setCurrentProfile(selectedProfileName); + this.rootFrame.setText(MenuUI.this.profileNameText, selectedProfileName); + } + protected void setSinglePlayerButtonsEnabled(final boolean b) { this.profileButton.setEnabled(b); this.campaignButton.setEnabled(b); @@ -432,6 +517,7 @@ public class MenuUI { this.viewReplayButton.setEnabled(b && ENABLE_NOT_YET_IMPLEMENTED_BUTTONS); this.customCampaignButton.setEnabled(b && ENABLE_NOT_YET_IMPLEMENTED_BUTTONS); this.skirmishButton.setEnabled(b); + this.singlePlayerCancelButton.setEnabled(b); } private void setMainMenuVisible(final boolean visible) { @@ -581,6 +667,7 @@ public class MenuUI { break; case SINGLE_PLAYER_PROFILE: this.profilePanel.setVisible(true); + setSinglePlayerButtonsEnabled(true); this.glueSpriteLayerTopLeft.setSequence("RealmSelection Stand"); // TODO the below should probably be some generic focusing thing when we enter a // new view? diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/PlayerProfile.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/PlayerProfile.java new file mode 100644 index 0000000..932da41 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/PlayerProfile.java @@ -0,0 +1,13 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.ui; + +public class PlayerProfile { + private final String name; + + public PlayerProfile(final String name) { + this.name = name; + } + + public String getName() { + return this.name; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/PlayerProfileManager.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/PlayerProfileManager.java new file mode 100644 index 0000000..5314e74 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/PlayerProfileManager.java @@ -0,0 +1,93 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.ui; + +import java.util.ArrayList; +import java.util.List; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.Preferences; + +public class PlayerProfileManager { + private static final String CURRENT_PROFILE = "CurrentProfile"; + private static final String PROFILE_COUNT = "ProfileCount"; + private final Preferences preferences; + private final List profiles; + private String currentProfile; + + public static PlayerProfileManager loadFromGdx() { + final Preferences preferences = Gdx.app.getPreferences("WarsmashWC3Engine"); + final int profileCount = preferences.getInteger(PROFILE_COUNT); + final List profiles = new ArrayList<>(); + for (int i = 0; i < profileCount; i++) { + final String name = preferences.getString("Profile" + i + "_Name"); + profiles.add(new PlayerProfile(name)); + } + final String currentProfile = preferences.getString(CURRENT_PROFILE, "WorldEdit"); + if (profiles.isEmpty()) { + final PlayerProfile worldEditDefaultProfile = new PlayerProfile("WorldEdit"); + saveProfile(preferences, profiles.size(), worldEditDefaultProfile); + profiles.add(worldEditDefaultProfile); + preferences.putInteger(PROFILE_COUNT, profiles.size()); + preferences.flush(); + } + return new PlayerProfileManager(preferences, profiles, currentProfile); + } + + public PlayerProfileManager(final Preferences preferences, final List profiles, + final String currentProfile) { + this.preferences = preferences; + this.profiles = profiles; + this.currentProfile = currentProfile; + } + + public List getProfiles() { + return this.profiles; + } + + public PlayerProfile addProfile(final String name) { + final PlayerProfile playerProfile = new PlayerProfile(name); + saveProfile(this.preferences, this.profiles.size(), playerProfile); + this.profiles.add(playerProfile); + this.preferences.putInteger(PROFILE_COUNT, this.profiles.size()); + this.preferences.flush(); + return playerProfile; + } + + public void setCurrentProfile(final String currentProfile) { + this.currentProfile = currentProfile; + this.preferences.putString(CURRENT_PROFILE, this.currentProfile); + this.preferences.flush(); + } + + public String getCurrentProfile() { + return this.currentProfile; + } + + public void saveAll() { + final int size = this.profiles.size(); + this.preferences.putInteger(PROFILE_COUNT, size); + this.preferences.putString(CURRENT_PROFILE, this.currentProfile); + for (int i = 0; i < size; i++) { + final PlayerProfile playerProfile = this.profiles.get(i); + saveProfile(this.preferences, i, playerProfile); + } + this.preferences.flush(); + } + + private static void saveProfile(final Preferences preferences, final int i, final PlayerProfile playerProfile) { + preferences.putString("Profile" + i + "_Name", playerProfile.getName()); + } + + public boolean hasProfile(final String text) { + for (final PlayerProfile profile : this.profiles) { + if (profile.getName().equals(text)) { + return true; + } + } + return false; + } + + public void removeProfile(final PlayerProfile profileToRemove) { + this.profiles.remove(profileToRemove); + saveAll(); + } +} From b5ab73fb47c0051dfd5a4301c3abf59dceac0f94 Mon Sep 17 00:00:00 2001 From: Retera Date: Mon, 29 Mar 2021 08:34:56 -0400 Subject: [PATCH 115/116] Multiple maps in one process --- .../etheller/warsmash/WarsmashGdxGame.java | 2 +- .../warsmash/WarsmashGdxMapScreen.java | 74 ++-- .../warsmash/WarsmashGdxMenuScreen.java | 214 +++++------ .../warsmash/WarsmashGdxMultiScreenGame.java | 14 +- .../warsmash/WarsmashPreviewApplication.java | 3 +- .../etheller/warsmash/parsers/fdf/GameUI.java | 181 +++++++++- .../fdf/frames/AbstractRenderableFrame.java | 10 + .../parsers/fdf/frames/BackdropFrame.java | 4 +- .../parsers/fdf/frames/GlueButtonFrame.java | 3 + .../parsers/fdf/frames/SimpleButtonFrame.java | 217 ++++++++++++ .../parsers/fdf/frames/SpriteFrame.java | 5 +- .../parsers/fdf/frames/StringFrame.java | 7 +- .../parsers/fdf/frames/TextButtonFrame.java | 18 + .../parsers/fdf/frames/TextureFrame.java | 2 +- .../warsmash/parsers/fdf/frames/UIFrame.java | 4 + .../warsmash/parsers/w3x/War3Map.java | 6 + .../objectdata/Warcraft3MapObjectData.java | 14 +- .../etheller/warsmash/util/ImageUtils.java | 3 + .../handlers/mdx/MdxComplexInstance.java | 2 +- .../viewer5/handlers/mdx/MdxViewer.java | 6 +- .../viewer5/handlers/w3x/War3MapViewer.java | 51 ++- .../w3x/simulation/data/CUnitData.java | 3 - .../viewer5/handlers/w3x/ui/MeleeUI.java | 218 ++++++++++-- .../viewer5/handlers/w3x/ui/MenuUI.java | 334 ++++++++++++++++-- .../w3x/ui/menu/CampaignButtonUI.java | 104 ++++++ .../w3x/ui/menu/CampaignMenuData.java | 118 +++++++ .../handlers/w3x/ui/menu/CampaignMenuUI.java | 84 +++++ .../handlers/w3x/ui/menu/CampaignMission.java | 25 ++ .../warsmash/desktop/DesktopLauncher.java | 10 +- .../fdf/datamodel/FrameDefinition.java | 10 + .../visitor/GetStringPairFieldVisitor.java | 56 +++ 31 files changed, 1552 insertions(+), 250 deletions(-) create mode 100644 core/src/com/etheller/warsmash/parsers/fdf/frames/SimpleButtonFrame.java create mode 100644 core/src/com/etheller/warsmash/parsers/fdf/frames/TextButtonFrame.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/menu/CampaignButtonUI.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/menu/CampaignMenuData.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/menu/CampaignMenuUI.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/menu/CampaignMission.java create mode 100644 fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/visitor/GetStringPairFieldVisitor.java diff --git a/core/src/com/etheller/warsmash/WarsmashGdxGame.java b/core/src/com/etheller/warsmash/WarsmashGdxGame.java index e8f9c1e..9ff816f 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxGame.java @@ -63,7 +63,7 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide System.err.println("Renderer: " + renderer); this.codebase = WarsmashGdxMapScreen.parseDataSources(this.warsmashIni); - this.viewer = new MdxViewer(this.codebase, this); + this.viewer = new MdxViewer(this.codebase, this, new Vector3(0.3f, 0.3f, -0.25f)); this.viewer.addHandler(new MdxHandler()); this.viewer.enableAudio(); diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMapScreen.java b/core/src/com/etheller/warsmash/WarsmashGdxMapScreen.java index 086b7dc..9f4c9b1 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMapScreen.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMapScreen.java @@ -36,7 +36,6 @@ import com.etheller.warsmash.units.Element; import com.etheller.warsmash.util.DataSourceFileHandle; import com.etheller.warsmash.util.ImageUtils; import com.etheller.warsmash.util.WarsmashConstants; -import com.etheller.warsmash.viewer5.CanvasProvider; import com.etheller.warsmash.viewer5.Model; import com.etheller.warsmash.viewer5.ModelInstance; import com.etheller.warsmash.viewer5.ModelViewer; @@ -49,16 +48,13 @@ import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; import com.etheller.warsmash.viewer5.handlers.w3x.camera.CameraPreset; import com.etheller.warsmash.viewer5.handlers.w3x.camera.CameraRates; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.config.War3MapConfig; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayerUnitOrderExecutor; import com.etheller.warsmash.viewer5.handlers.w3x.ui.MeleeUI; -import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.SettableCommandErrorListener; -public class WarsmashGdxMapScreen implements CanvasProvider, InputProcessor, Screen { - private static final boolean ENABLE_AUDIO = true; - private static final boolean ENABLE_MUSIC = false; - private DataSource codebase; - private War3MapViewer viewer; +public class WarsmashGdxMapScreen implements InputProcessor, Screen { + public static final boolean ENABLE_AUDIO = true; + private static final boolean ENABLE_MUSIC = true; + private final War3MapViewer viewer; private final Rectangle tempRect = new Rectangle(); // libGDX stuff @@ -72,17 +68,19 @@ public class WarsmashGdxMapScreen implements CanvasProvider, InputProcessor, Scr private ShapeRenderer shapeRenderer; private MdxModel timeIndicator; - private final DataTable warsmashIni; private Scene uiScene; private MeleeUI meleeUI; private Music currentMusic; - private final String fileToLoad; + private final WarsmashGdxMultiScreenGame screenManager; + private final WarsmashGdxMenuScreen menuScreen; - public WarsmashGdxMapScreen(final DataTable warsmashIni, final String fileToLoad) { - this.warsmashIni = warsmashIni; - this.fileToLoad = fileToLoad; + public WarsmashGdxMapScreen(final War3MapViewer mapViewer, final WarsmashGdxMultiScreenGame screenManager, + final WarsmashGdxMenuScreen menuScreen) { + this.viewer = mapViewer; + this.screenManager = screenManager; + this.menuScreen = menuScreen; } /* @@ -105,22 +103,6 @@ public class WarsmashGdxMapScreen implements CanvasProvider, InputProcessor, Scr final String renderer = Gdx.gl.glGetString(GL20.GL_RENDERER); System.err.println("Renderer: " + renderer); - final SettableCommandErrorListener commandErrorListener = new SettableCommandErrorListener(); - this.codebase = parseDataSources(this.warsmashIni); - this.viewer = new War3MapViewer(this.codebase, this, commandErrorListener, - new War3MapConfig(WarsmashConstants.MAX_PLAYERS)); - - if (ENABLE_AUDIO) { - this.viewer.worldScene.enableAudio(); - this.viewer.enableAudio(); - } - try { - this.viewer.loadMap(this.fileToLoad, 0); - } - catch (final IOException e) { - throw new RuntimeException(e); - } - final Element cameraData = this.viewer.miscData.get("Camera"); Element cameraListenerData = this.viewer.miscData.get("Listener"); if (cameraListenerData == null) { @@ -207,7 +189,8 @@ public class WarsmashGdxMapScreen implements CanvasProvider, InputProcessor, Scr WarsmashGdxMapScreen.this.viewer.setGameUI(rootFrame); if (ENABLE_MUSIC) { - final String musicField = rootFrame.getSkinField("Music_V1"); + final String musicField = rootFrame + .getSkinField("Music_V" + WarsmashConstants.GAME_VERSION); final String[] musics = musicField.split(";"); String musicPath = musics[(int) (Math.random() * musics.length)]; if (false) { @@ -215,15 +198,22 @@ public class WarsmashGdxMapScreen implements CanvasProvider, InputProcessor, Scr } final Music music = Gdx.audio.newMusic( new DataSourceFileHandle(WarsmashGdxMapScreen.this.viewer.dataSource, musicPath)); - music.setVolume(0.2f); + music.setVolume(1.0f); music.setLooping(true); music.play(); WarsmashGdxMapScreen.this.currentMusic = music; } } }, new CPlayerUnitOrderExecutor(this.viewer.simulation, this.viewer.getLocalPlayerIndex(), - commandErrorListener)); - commandErrorListener.setDelegate(this.meleeUI); + this.viewer.getCommandErrorListener()), + new Runnable() { + @Override + public void run() { + WarsmashGdxMapScreen.this.menuScreen.onReturnFromGame(); + WarsmashGdxMapScreen.this.screenManager.setScreen(WarsmashGdxMapScreen.this.menuScreen); + } + }); + this.viewer.getCommandErrorListener().setDelegate(this.meleeUI); final ModelInstance libgdxContentInstance = new LibGDXContentLayerModel(null, this.viewer, "", this.viewer.mapPathSolver, "").addInstance(); libgdxContentInstance.setScene(this.uiScene); @@ -329,16 +319,6 @@ public class WarsmashGdxMapScreen implements CanvasProvider, InputProcessor, Scr this.meleeUI.dispose(); } - @Override - public float getWidth() { - return Gdx.graphics.getWidth(); - } - - @Override - public float getHeight() { - return Gdx.graphics.getHeight(); - } - @Override public void resize(final int width, final int height) { // super.resize(width, height); @@ -383,7 +363,7 @@ public class WarsmashGdxMapScreen implements CanvasProvider, InputProcessor, Scr @Override public boolean touchDown(final int screenX, final int screenY, final int pointer, final int button) { - final float worldScreenY = getHeight() - screenY; + final float worldScreenY = this.viewer.canvas.getHeight() - screenY; if (this.meleeUI.touchDown(screenX, screenY, worldScreenY, button)) { return false; @@ -393,7 +373,7 @@ public class WarsmashGdxMapScreen implements CanvasProvider, InputProcessor, Scr @Override public boolean touchUp(final int screenX, final int screenY, final int pointer, final int button) { - final float worldScreenY = getHeight() - screenY; + final float worldScreenY = this.viewer.canvas.getHeight() - screenY; if (this.meleeUI.touchUp(screenX, screenY, worldScreenY, button)) { return false; @@ -403,7 +383,7 @@ public class WarsmashGdxMapScreen implements CanvasProvider, InputProcessor, Scr @Override public boolean touchDragged(final int screenX, final int screenY, final int pointer) { - final float worldScreenY = getHeight() - screenY; + final float worldScreenY = this.viewer.canvas.getHeight() - screenY; if (this.meleeUI.touchDragged(screenX, screenY, worldScreenY, pointer)) { return false; } @@ -412,7 +392,7 @@ public class WarsmashGdxMapScreen implements CanvasProvider, InputProcessor, Scr @Override public boolean mouseMoved(final int screenX, final int screenY) { - final float worldScreenY = getHeight() - screenY; + final float worldScreenY = this.viewer.canvas.getHeight() - screenY; if (this.meleeUI.mouseMoved(screenX, screenY, worldScreenY)) { return false; } diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMenuScreen.java b/core/src/com/etheller/warsmash/WarsmashGdxMenuScreen.java index 761a69c..2faf476 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMenuScreen.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMenuScreen.java @@ -50,7 +50,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; import com.etheller.warsmash.viewer5.handlers.w3x.ui.MenuUI; -public class WarsmashGdxMenuScreen implements CanvasProvider, InputProcessor, Screen, SingleModelScreen { +public class WarsmashGdxMenuScreen implements InputProcessor, Screen, SingleModelScreen { private static final boolean ENABLE_AUDIO = true; private static final boolean ENABLE_MUSIC = true; private DataSource codebase; @@ -72,6 +72,7 @@ public class WarsmashGdxMenuScreen implements CanvasProvider, InputProcessor, Sc private final WarsmashGdxMultiScreenGame game; private Music currentMusic; private boolean hasPlayedStandHack = false; + private boolean loaded = false; public WarsmashGdxMenuScreen(final DataTable warsmashIni, final WarsmashGdxMultiScreenGame game) { this.warsmashIni = warsmashIni; @@ -80,68 +81,68 @@ public class WarsmashGdxMenuScreen implements CanvasProvider, InputProcessor, Sc @Override public void show() { - final ByteBuffer tempByteBuffer = ByteBuffer.allocateDirect(4); - tempByteBuffer.order(ByteOrder.LITTLE_ENDIAN); - final IntBuffer temp = tempByteBuffer.asIntBuffer(); + if (!this.loaded) { + this.loaded = true; + final ByteBuffer tempByteBuffer = ByteBuffer.allocateDirect(4); + tempByteBuffer.order(ByteOrder.LITTLE_ENDIAN); + final IntBuffer temp = tempByteBuffer.asIntBuffer(); // - Gdx.gl30.glGenVertexArrays(1, temp); - WarsmashGdxGame.VAO = temp.get(0); + Gdx.gl30.glGenVertexArrays(1, temp); + WarsmashGdxGame.VAO = temp.get(0); - Gdx.gl30.glBindVertexArray(WarsmashGdxGame.VAO); + Gdx.gl30.glBindVertexArray(WarsmashGdxGame.VAO); - final String renderer = Gdx.gl.glGetString(GL20.GL_RENDERER); - System.err.println("Renderer: " + renderer); + final String renderer = Gdx.gl.glGetString(GL20.GL_RENDERER); + System.err.println("Renderer: " + renderer); - this.codebase = WarsmashGdxMapScreen.parseDataSources(this.warsmashIni); - this.viewer = new MdxViewer(this.codebase, this); + this.codebase = WarsmashGdxMapScreen.parseDataSources(this.warsmashIni); + this.viewer = new MdxViewer(this.codebase, this.game, Vector3.Zero); - this.viewer.addHandler(new MdxHandler()); - this.viewer.enableAudio(); + this.viewer.addHandler(new MdxHandler()); + this.viewer.enableAudio(); - this.scene = this.viewer.addSimpleScene(); - this.scene.enableAudio(); + this.scene = this.viewer.addSimpleScene(); + this.scene.enableAudio(); - this.uiScene = this.viewer.addSimpleScene(); - this.uiScene.alpha = true; - if (ENABLE_AUDIO) { - this.uiScene.enableAudio(); - } - final int width = Gdx.graphics.getWidth(); - final int height = Gdx.graphics.getHeight(); + this.uiScene = this.viewer.addSimpleScene(); + this.uiScene.alpha = true; + if (ENABLE_AUDIO) { + this.uiScene.enableAudio(); + } + final int width = Gdx.graphics.getWidth(); + final int height = Gdx.graphics.getHeight(); - this.glyphLayout = new GlyphLayout(); + this.glyphLayout = new GlyphLayout(); - // Constructs a new OrthographicCamera, using the given viewport width and - // height - // Height is multiplied by aspect ratio. - this.uiCamera = new OrthographicCamera(); - int aspect3By4Width; - int aspect3By4Height; - if (width < ((height * 4) / 3)) { - aspect3By4Width = width; - aspect3By4Height = (width * 3) / 4; - } - else { - aspect3By4Width = (height * 4) / 3; - aspect3By4Height = height; - } - this.uiViewport = new FitViewport(aspect3By4Width, aspect3By4Height, this.uiCamera); - this.uiViewport.update(width, height); + // Constructs a new OrthographicCamera, using the given viewport width and + // height + // Height is multiplied by aspect ratio. + this.uiCamera = new OrthographicCamera(); + int aspect3By4Width; + int aspect3By4Height; + if (width < ((height * 4) / 3)) { + aspect3By4Width = width; + aspect3By4Height = (width * 3) / 4; + } + else { + aspect3By4Width = (height * 4) / 3; + aspect3By4Height = height; + } + this.uiViewport = new FitViewport(aspect3By4Width, aspect3By4Height, this.uiCamera); + this.uiViewport.update(width, height); - this.uiCamera.position.set(getMinWorldWidth() / 2, getMinWorldHeight() / 2, 0); - this.uiCamera.update(); + this.uiCamera.position.set(getMinWorldWidth() / 2, getMinWorldHeight() / 2, 0); + this.uiCamera.update(); - this.batch = new SpriteBatch(); + this.batch = new SpriteBatch(); // this.consoleUITexture = new Texture(new DataSourceFileHandle(this.viewer.dataSource, "AlphaUi.png")); - this.solidGreenTexture = ImageUtils.getAnyExtensionTexture(this.viewer.dataSource, - "ReplaceableTextures\\TeamColor\\TeamColor06.blp"); + this.solidGreenTexture = ImageUtils.getAnyExtensionTexture(this.viewer.dataSource, + "ReplaceableTextures\\TeamColor\\TeamColor06.blp"); - Gdx.input.setInputProcessor(this); - - this.cameraManager = new CameraManager(); - this.cameraManager.setupCamera(this.scene); + this.cameraManager = new CameraManager(); + this.cameraManager.setupCamera(this.scene); // this.mainModel = (MdxModel) this.viewer.load("Doodads\\Cinematic\\ArthasIllidanFight\\ArthasIllidanFight.mdx", // this.mainModel = (MdxModel) this.viewer.load("UI\\Glues\\SinglePlayer\\NightElf_Exp\\NightElf_Exp.mdx", @@ -175,45 +176,51 @@ public class WarsmashGdxMenuScreen implements CanvasProvider, InputProcessor, Sc // singleModelScene(scene, "Buildings\\Undead\\Necropolis\\Necropolis.mdx", "birth"); // singleModelScene(scene, "Units\\Orc\\KotoBeast\\KotoBeast.mdx", "spell slam"); - System.out.println("Loaded"); - Gdx.gl30.glClearColor(0.0f, 0.0f, 0.0f, 1); + System.out.println("Loaded"); + Gdx.gl30.glClearColor(0.0f, 0.0f, 0.0f, 1); - this.menuUI = new MenuUI(this.viewer.dataSource, this.uiViewport, this.uiScene, this.viewer, this.game, this, - this.warsmashIni, new RootFrameListener() { - @Override - public void onCreate(final GameUI rootFrame) { + this.menuUI = new MenuUI(this.viewer.dataSource, this.uiViewport, this.uiScene, this.viewer, this.game, + this, this.warsmashIni, new RootFrameListener() { + @Override + public void onCreate(final GameUI rootFrame) { // WarsmashGdxMapGame.this.viewer.setGameUI(rootFrame); - if (ENABLE_MUSIC) { - final String musicField = rootFrame - .getSkinField("GlueMusic_V" + WarsmashConstants.GAME_VERSION); - final String[] musics = musicField.split(";"); - final String musicPath = musics[(int) (Math.random() * musics.length)]; - final Music music = Gdx.audio.newMusic( - new DataSourceFileHandle(WarsmashGdxMenuScreen.this.viewer.dataSource, musicPath)); + if (ENABLE_MUSIC) { + final String musicField = rootFrame + .getSkinField("GlueMusic_V" + WarsmashConstants.GAME_VERSION); + final String[] musics = musicField.split(";"); + final String musicPath = musics[(int) (Math.random() * musics.length)]; + final Music music = Gdx.audio.newMusic(new DataSourceFileHandle( + WarsmashGdxMenuScreen.this.viewer.dataSource, musicPath)); // music.setVolume(0.2f); - music.setLooping(true); - music.play(); - WarsmashGdxMenuScreen.this.currentMusic = music; + music.setLooping(true); + music.play(); + WarsmashGdxMenuScreen.this.currentMusic = music; + } + + singleModelScene(WarsmashGdxMenuScreen.this.scene, War3MapViewer.mdx(rootFrame + .getSkinField("GlueSpriteLayerBackground_V" + WarsmashConstants.GAME_VERSION)), + "Stand"); + WarsmashGdxMenuScreen.this.modelCamera = WarsmashGdxMenuScreen.this.mainModel.cameras + .get(0); } + }); - singleModelScene(WarsmashGdxMenuScreen.this.scene, - War3MapViewer.mdx(rootFrame - .getSkinField("GlueSpriteLayerBackground_V" + WarsmashConstants.GAME_VERSION)), - "Stand"); - WarsmashGdxMenuScreen.this.modelCamera = WarsmashGdxMenuScreen.this.mainModel.cameras.get(0); - } - }); + final ModelInstance libgdxContentInstance = new LibGDXContentLayerModel(null, this.viewer, "", + PathSolver.DEFAULT, "").addInstance(); + libgdxContentInstance.setLocation(0f, 0f, -0.5f); + libgdxContentInstance.setScene(this.uiScene); + this.menuUI.main(); - final ModelInstance libgdxContentInstance = new LibGDXContentLayerModel(null, this.viewer, "", - PathSolver.DEFAULT, "").addInstance(); - libgdxContentInstance.setLocation(0f, 0f, -0.5f); - libgdxContentInstance.setScene(this.uiScene); - this.menuUI.main(); + updateUIScene(); - updateUIScene(); + resize(width, height); + } - resize(width, height); + Gdx.input.setInputProcessor(this); + if (this.currentMusic != null) { + this.currentMusic.play(); + } } @@ -313,14 +320,21 @@ public class WarsmashGdxMenuScreen implements CanvasProvider, InputProcessor, Sc if (this.mainInstance != null) { this.mainInstance.detach(); } - singleModelScene(this.scene, War3MapViewer.mdx(path), "birth"); - WarsmashGdxMenuScreen.this.modelCamera = WarsmashGdxMenuScreen.this.mainModel.cameras.get(0); - // this hack is because we only have the queued animation system in RenderWidget - // which is stupid and back and needs to get moved to the model instance - // itself... our model instance class is a - // hacky replica of a model viewer tool with a bunch of irrelevant loop type - // settings instead of what it should be - this.hasPlayedStandHack = false; + if (path == null) { + this.modelCamera = null; + this.mainInstance = null; + this.mainModel = null; + } + else { + singleModelScene(this.scene, War3MapViewer.mdx(path), "birth"); + WarsmashGdxMenuScreen.this.modelCamera = WarsmashGdxMenuScreen.this.mainModel.cameras.get(0); + // this hack is because we only have the queued animation system in RenderWidget + // which is stupid and back and needs to get moved to the model instance + // itself... our model instance class is a + // hacky replica of a model viewer tool with a bunch of irrelevant loop type + // settings instead of what it should be + this.hasPlayedStandHack = false; + } } @@ -521,7 +535,7 @@ public class WarsmashGdxMenuScreen implements CanvasProvider, InputProcessor, Sc Gdx.gl30.glBindVertexArray(WarsmashGdxGame.VAO); this.cameraManager.updateCamera(); this.menuUI.update(deltaTime); - if (this.mainInstance.sequenceEnded + if ((this.mainInstance != null) && this.mainInstance.sequenceEnded && (((this.mainModel.getSequences().get(this.mainInstance.sequence).getFlags() & 0x1) == 0) || !this.hasPlayedStandHack)) { SequenceUtils.randomStandSequence(this.mainInstance); @@ -543,16 +557,6 @@ public class WarsmashGdxMenuScreen implements CanvasProvider, InputProcessor, Sc this.menuUI.dispose(); } - @Override - public float getWidth() { - return Gdx.graphics.getWidth(); - } - - @Override - public float getHeight() { - return Gdx.graphics.getHeight(); - } - @Override public void resize(final int width, final int height) { this.tempRect.width = width; @@ -705,7 +709,7 @@ public class WarsmashGdxMenuScreen implements CanvasProvider, InputProcessor, Sc @Override public boolean touchDown(final int screenX, final int screenY, final int pointer, final int button) { - final float worldScreenY = getHeight() - screenY; + final float worldScreenY = this.game.getHeight() - screenY; if (this.menuUI.touchDown(screenX, screenY, worldScreenY, button)) { return false; @@ -715,7 +719,7 @@ public class WarsmashGdxMenuScreen implements CanvasProvider, InputProcessor, Sc @Override public boolean touchUp(final int screenX, final int screenY, final int pointer, final int button) { - final float worldScreenY = getHeight() - screenY; + final float worldScreenY = this.game.getHeight() - screenY; if (this.menuUI.touchUp(screenX, screenY, worldScreenY, button)) { return false; @@ -725,7 +729,7 @@ public class WarsmashGdxMenuScreen implements CanvasProvider, InputProcessor, Sc @Override public boolean touchDragged(final int screenX, final int screenY, final int pointer) { - final float worldScreenY = getHeight() - screenY; + final float worldScreenY = this.game.getHeight() - screenY; if (this.menuUI.touchDragged(screenX, screenY, worldScreenY, pointer)) { return false; } @@ -734,7 +738,7 @@ public class WarsmashGdxMenuScreen implements CanvasProvider, InputProcessor, Sc @Override public boolean mouseMoved(final int screenX, final int screenY) { - final float worldScreenY = getHeight() - screenY; + final float worldScreenY = this.game.getHeight() - screenY; if (this.menuUI.mouseMoved(screenX, screenY, worldScreenY)) { return false; } @@ -867,4 +871,12 @@ public class WarsmashGdxMenuScreen implements CanvasProvider, InputProcessor, Sc @Override public void resume() { } + + public void startMap(final String finalFileToLoad) { + this.menuUI.startMap(finalFileToLoad); + } + + public void onReturnFromGame() { + this.menuUI.onReturnFromGame(); + } } diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMultiScreenGame.java b/core/src/com/etheller/warsmash/WarsmashGdxMultiScreenGame.java index b6234c8..2ba3bb4 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMultiScreenGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMultiScreenGame.java @@ -1,11 +1,23 @@ package com.etheller.warsmash; import com.badlogic.gdx.Game; +import com.badlogic.gdx.Gdx; +import com.etheller.warsmash.viewer5.CanvasProvider; -public class WarsmashGdxMultiScreenGame extends Game { +public class WarsmashGdxMultiScreenGame extends Game implements CanvasProvider { @Override public void create() { } + @Override + public float getWidth() { + return Gdx.graphics.getWidth(); + } + + @Override + public float getHeight() { + return Gdx.graphics.getHeight(); + } + } diff --git a/core/src/com/etheller/warsmash/WarsmashPreviewApplication.java b/core/src/com/etheller/warsmash/WarsmashPreviewApplication.java index 14cd086..36d628a 100644 --- a/core/src/com/etheller/warsmash/WarsmashPreviewApplication.java +++ b/core/src/com/etheller/warsmash/WarsmashPreviewApplication.java @@ -13,6 +13,7 @@ import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.math.Rectangle; +import com.badlogic.gdx.math.Vector3; import com.etheller.warsmash.datasources.DataSource; import com.etheller.warsmash.units.DataTable; import com.etheller.warsmash.viewer5.CanvasProvider; @@ -60,7 +61,7 @@ public class WarsmashPreviewApplication extends ApplicationAdapter implements Ca System.err.println("Renderer: " + renderer); this.codebase = WarsmashGdxMapScreen.parseDataSources(this.warsmashIni); - this.viewer = new MdxViewer(this.codebase, this); + this.viewer = new MdxViewer(this.codebase, this, new Vector3(0.3f, 0.3f, -0.25f)); this.mdxHandler = new MdxHandler(); this.viewer.addHandler(this.mdxHandler); diff --git a/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java index b4164d9..34fa256 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java @@ -27,11 +27,13 @@ import com.etheller.warsmash.parsers.fdf.datamodel.ControlStyle; import com.etheller.warsmash.parsers.fdf.datamodel.FontDefinition; import com.etheller.warsmash.parsers.fdf.datamodel.FrameClass; import com.etheller.warsmash.parsers.fdf.datamodel.FrameDefinition; +import com.etheller.warsmash.parsers.fdf.datamodel.FramePoint; import com.etheller.warsmash.parsers.fdf.datamodel.FrameTemplateEnvironment; import com.etheller.warsmash.parsers.fdf.datamodel.SetPointDefinition; import com.etheller.warsmash.parsers.fdf.datamodel.TextJustify; import com.etheller.warsmash.parsers.fdf.datamodel.Vector2Definition; import com.etheller.warsmash.parsers.fdf.datamodel.Vector4Definition; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.StringPairFrameDefinitionField; import com.etheller.warsmash.parsers.fdf.frames.AbstractUIFrame; import com.etheller.warsmash.parsers.fdf.frames.BackdropFrame; import com.etheller.warsmash.parsers.fdf.frames.ControlFrame; @@ -41,10 +43,12 @@ import com.etheller.warsmash.parsers.fdf.frames.GlueButtonFrame; import com.etheller.warsmash.parsers.fdf.frames.GlueTextButtonFrame; import com.etheller.warsmash.parsers.fdf.frames.ListBoxFrame; import com.etheller.warsmash.parsers.fdf.frames.SetPoint; +import com.etheller.warsmash.parsers.fdf.frames.SimpleButtonFrame; import com.etheller.warsmash.parsers.fdf.frames.SimpleFrame; import com.etheller.warsmash.parsers.fdf.frames.SimpleStatusBarFrame; import com.etheller.warsmash.parsers.fdf.frames.SpriteFrame; import com.etheller.warsmash.parsers.fdf.frames.StringFrame; +import com.etheller.warsmash.parsers.fdf.frames.TextButtonFrame; import com.etheller.warsmash.parsers.fdf.frames.TextureFrame; import com.etheller.warsmash.parsers.fdf.frames.UIFrame; import com.etheller.warsmash.units.DataTable; @@ -59,7 +63,8 @@ import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.FocusableFrame; import com.hiveworkshop.rms.parsers.mdlx.MdlxLayer.FilterMode; public final class GameUI extends AbstractUIFrame implements UIFrame { - private static final boolean PIN_FAIL_IS_FATAL = false; + public static final boolean DEBUG = false; + private static final boolean PIN_FAIL_IS_FATAL = true; private final DataSource dataSource; private final Element skin; private final Viewport viewport; @@ -347,6 +352,11 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { } viewport2 = this.viewport; // TODO was fdfCoordinateResolutionDummyViewport here previously, but is that // a good idea? + this.nameToFrame.put(frameDefinition.getName(), spriteFrame); + for (final FrameDefinition childDefinition : frameDefinition.getInnerFrames()) { + spriteFrame.add(inflate(childDefinition, spriteFrame, frameDefinition, + inDecorateFileNames || childDefinition.has("DecorateFileNames"))); + } inflatedFrame = spriteFrame; } else if ("FRAME".equals(frameDefinition.getFrameType()) @@ -372,6 +382,13 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { if (justifyV == null) { justifyV = TextJustify.MIDDLE; } + for (final SetPointDefinition setPoint : frameDefinition.getSetPoints()) { + if (((setPoint.getMyPoint() == FramePoint.TOP) && (setPoint.getOtherPoint() == FramePoint.TOP)) + || ((setPoint.getMyPoint() == FramePoint.BOTTOM) + && (setPoint.getOtherPoint() == FramePoint.BOTTOM))) { + justifyH = TextJustify.CENTER; + } + } Color fontColor; final Vector4Definition fontColorDefinition = frameDefinition.getVector4("FontColor"); @@ -472,6 +489,88 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { } inflatedFrame = glueButtonFrame; } + else if ("SIMPLEBUTTON".equals(frameDefinition.getFrameType())) { + // ButtonText & ControlBackdrop + final SimpleButtonFrame simpleButtonFrame = new SimpleButtonFrame(frameDefinition.getName(), parent); + // TODO: we should not need to put ourselves in this map 2x, but we do + // since there are nested inflate calls happening before the general case + // mapping + this.nameToFrame.put(frameDefinition.getName(), simpleButtonFrame); + final StringPairFrameDefinitionField normalTextDefinition = frameDefinition.getStringPair("NormalText"); + final StringPairFrameDefinitionField disabledTextDefinition = frameDefinition + .getStringPair("DisabledText"); + final StringPairFrameDefinitionField highlightTextDefinition = frameDefinition + .getStringPair("HighlightText"); + final String normalTextureDefinition = frameDefinition.getString("NormalTexture"); + final String pushedTextureDefinition = frameDefinition.getString("PushedTexture"); + final String disabledTextureDefinition = frameDefinition.getString("DisabledTexture"); + final String useHighlightDefinition = frameDefinition.getString("UseHighlight"); + + final boolean decorateFileNamesOnThisFrame = frameDefinition.has("DecorateFileNames") + || inDecorateFileNames; + final UIFrame normalText = inflate(this.templates.getFrame(normalTextDefinition.getFirst()), + simpleButtonFrame, frameDefinition, decorateFileNamesOnThisFrame); + setDecoratedText((StringFrame) normalText, normalTextDefinition.getSecond()); + normalText.setSetAllPoints(true); + final UIFrame disabledText = inflate(this.templates.getFrame(disabledTextDefinition.getFirst()), + simpleButtonFrame, frameDefinition, decorateFileNamesOnThisFrame); + setDecoratedText((StringFrame) disabledText, disabledTextDefinition.getSecond()); + disabledText.setSetAllPoints(true); + final UIFrame highlightText = inflate(this.templates.getFrame(highlightTextDefinition.getFirst()), + simpleButtonFrame, frameDefinition, decorateFileNamesOnThisFrame); + setDecoratedText((StringFrame) highlightText, highlightTextDefinition.getSecond()); + highlightText.setSetAllPoints(true); + final UIFrame normalTexture = inflate(this.templates.getFrame(normalTextureDefinition), + simpleButtonFrame, frameDefinition, decorateFileNamesOnThisFrame); + normalTexture.setSetAllPoints(true); + final UIFrame pushedTexture = inflate(this.templates.getFrame(pushedTextureDefinition), + simpleButtonFrame, frameDefinition, decorateFileNamesOnThisFrame); + pushedTexture.setSetAllPoints(true); + final UIFrame disabledTexture = inflate(this.templates.getFrame(disabledTextureDefinition), + simpleButtonFrame, frameDefinition, decorateFileNamesOnThisFrame); + disabledTexture.setSetAllPoints(true); + final UIFrame useHighlight = inflate(this.templates.getFrame(useHighlightDefinition), simpleButtonFrame, + frameDefinition, decorateFileNamesOnThisFrame); + useHighlight.setSetAllPoints(true); + simpleButtonFrame.setButtonText(normalText); + simpleButtonFrame.setDisabledText(disabledText); + simpleButtonFrame.setHighlightText(highlightText); + simpleButtonFrame.setControlBackdrop(normalTexture); + simpleButtonFrame.setControlDisabledBackdrop(disabledTexture); + simpleButtonFrame.setControlMouseOverHighlight(useHighlight); + simpleButtonFrame.setControlPushedBackdrop(pushedTexture); + + final Vector2Definition pushedTextOffset = frameDefinition.getVector2("ButtonPushedTextOffset"); + if (pushedTextOffset != null) { + final UIFrame pushedNormalText = inflate(this.templates.getFrame(normalTextDefinition.getFirst()), + simpleButtonFrame, frameDefinition, decorateFileNamesOnThisFrame); + setDecoratedText((StringFrame) pushedNormalText, normalTextDefinition.getSecond()); + final UIFrame pushedHighlightText = inflate( + this.templates.getFrame(highlightTextDefinition.getFirst()), simpleButtonFrame, + frameDefinition, decorateFileNamesOnThisFrame); + setDecoratedText((StringFrame) pushedHighlightText, highlightTextDefinition.getSecond()); + pushedNormalText.addSetPoint(new SetPoint(FramePoint.TOPLEFT, simpleButtonFrame, FramePoint.TOPLEFT, + GameUI.convertX(viewport2, pushedTextOffset.getX()), + GameUI.convertY(viewport2, pushedTextOffset.getY()))); + pushedNormalText.addSetPoint(new SetPoint(FramePoint.BOTTOMRIGHT, simpleButtonFrame, + FramePoint.BOTTOMRIGHT, GameUI.convertX(viewport2, pushedTextOffset.getX()), + GameUI.convertY(viewport2, pushedTextOffset.getY()))); + pushedHighlightText.addSetPoint(new SetPoint(FramePoint.TOPLEFT, simpleButtonFrame, + FramePoint.TOPLEFT, GameUI.convertX(viewport2, pushedTextOffset.getX()), + GameUI.convertY(viewport2, pushedTextOffset.getY()))); + pushedHighlightText.addSetPoint(new SetPoint(FramePoint.BOTTOMRIGHT, simpleButtonFrame, + FramePoint.BOTTOMRIGHT, GameUI.convertX(viewport2, pushedTextOffset.getX()), + GameUI.convertY(viewport2, pushedTextOffset.getY()))); + simpleButtonFrame.setPushedText(pushedNormalText); + simpleButtonFrame.setPushedHighlightText(pushedHighlightText); + } + else { + simpleButtonFrame.setPushedText(normalText); + simpleButtonFrame.setPushedHighlightText(highlightText); + } + + inflatedFrame = simpleButtonFrame; + } else if ("GLUEBUTTON".equals(frameDefinition.getFrameType())) { // ButtonText & ControlBackdrop final GlueButtonFrame glueButtonFrame = new GlueButtonFrame(frameDefinition.getName(), parent); @@ -517,6 +616,56 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { } inflatedFrame = glueButtonFrame; } + else if ("TEXTBUTTON".equals(frameDefinition.getFrameType())) { + // ButtonText & ControlBackdrop + final TextButtonFrame glueButtonFrame = new TextButtonFrame(frameDefinition.getName(), parent); + // TODO: we should not need to put ourselves in this map 2x, but we do + // since there are nested inflate calls happening before the general case + // mapping + this.nameToFrame.put(frameDefinition.getName(), glueButtonFrame); + final String controlBackdropKey = frameDefinition.getString("ControlBackdrop"); + final String controlPushedBackdropKey = frameDefinition.getString("ControlPushedBackdrop"); + final String controlDisabledBackdropKey = frameDefinition.getString("ControlDisabledBackdrop"); + final String controlMouseOverHighlightKey = frameDefinition.getString("ControlMouseOverHighlight"); + final Vector2Definition pushedTextOffset = frameDefinition.getVector2("ButtonPushedTextOffset"); + if (pushedTextOffset != null) { + glueButtonFrame.setButtonPushedTextOffsetX(pushedTextOffset.getX()); + glueButtonFrame.setButtonPushedTextOffsetY(pushedTextOffset.getY()); + } + for (final FrameDefinition childDefinition : frameDefinition.getInnerFrames()) { + if (childDefinition.getName().equals(controlBackdropKey)) { + final UIFrame inflatedChild = inflate(childDefinition, glueButtonFrame, frameDefinition, + inDecorateFileNames || childDefinition.has("DecorateFileNames")); + inflatedChild.setSetAllPoints(true); + glueButtonFrame.setControlBackdrop(inflatedChild); + } + else if (childDefinition.getName().equals(controlPushedBackdropKey)) { + final UIFrame inflatedChild = inflate(childDefinition, glueButtonFrame, frameDefinition, + inDecorateFileNames || childDefinition.has("DecorateFileNames")); + inflatedChild.setSetAllPoints(true); + glueButtonFrame.setControlPushedBackdrop(inflatedChild); + } + else if (childDefinition.getName().equals(controlDisabledBackdropKey)) { + final UIFrame inflatedChild = inflate(childDefinition, glueButtonFrame, frameDefinition, + inDecorateFileNames || childDefinition.has("DecorateFileNames")); + inflatedChild.setSetAllPoints(true); + glueButtonFrame.setControlDisabledBackdrop(inflatedChild); + } + else if (childDefinition.getName().equals(controlMouseOverHighlightKey)) { + final UIFrame inflatedChild = inflate(childDefinition, glueButtonFrame, frameDefinition, + inDecorateFileNames || childDefinition.has("DecorateFileNames")); + inflatedChild.setSetAllPoints(true); + glueButtonFrame.setControlMouseOverHighlight(inflatedChild); + } + } + final EnumSet controlStyle = ControlStyle + .parseControlStyle(frameDefinition.getString("ControlStyle")); + if (controlStyle.contains(ControlStyle.AUTOTRACK) + && controlStyle.contains(ControlStyle.HIGHLIGHTONMOUSEOVER)) { + glueButtonFrame.setHighlightOnMouseOver(true); + } + inflatedFrame = glueButtonFrame; + } else if ("EDITBOX".equals(frameDefinition.getFrameType())) { final float editBorderSize = convertX(viewport2, frameDefinition.getFloat("EditBorderSize")); final Vector4Definition editCursorColorDefinition = frameDefinition.getVector4("EditCursorColor"); @@ -598,7 +747,7 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { final String highlightAlphaFile = frameDefinition.getString("HighlightAlphaFile"); final String highlightAlphaMode = frameDefinition.getString("HighlightAlphaMode"); final FilterModeTextureFrame textureFrame = new FilterModeTextureFrame(frameDefinition.getName(), - parent, false, null); + parent, inDecorateFileNames || frameDefinition.has("DecorateFileNames"), null); textureFrame.setTexture(highlightAlphaFile, this); if ("ADD".equals(highlightAlphaMode)) { textureFrame.setFilterMode(FilterMode.ADDALPHA); @@ -623,10 +772,10 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { backgroundSizeNullable == null ? 0.0f : backgroundSizeNullable); Vector4Definition backgroundInsets = frameDefinition.getVector4("BackdropBackgroundInsets"); if (backgroundInsets != null) { - backgroundInsets.setX(GameUI.convertX(viewport2, backgroundInsets.getX())); - backgroundInsets.setY(GameUI.convertY(viewport2, backgroundInsets.getY())); - backgroundInsets.setZ(GameUI.convertX(viewport2, backgroundInsets.getZ())); - backgroundInsets.setW(GameUI.convertY(viewport2, backgroundInsets.getW())); + backgroundInsets = new Vector4Definition(GameUI.convertX(viewport2, backgroundInsets.getX()), + GameUI.convertY(viewport2, backgroundInsets.getY()), + GameUI.convertX(viewport2, backgroundInsets.getZ()), + GameUI.convertY(viewport2, backgroundInsets.getW())); } else { backgroundInsets = new Vector4Definition(0, 0, 0, 0); @@ -708,8 +857,17 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { final String file = frameDefinition.getString("File"); final boolean decorateFileNames = frameDefinition.has("DecorateFileNames") || inDecorateFileNames; final Vector4Definition texCoord = frameDefinition.getVector4("TexCoord"); - final TextureFrame textureFrame = new TextureFrame(frameDefinition.getName(), parent, decorateFileNames, - texCoord); + TextureFrame textureFrame; + final String alphaMode = frameDefinition.getString("AlphaMode"); + if ((alphaMode != null) && alphaMode.equals("ADD")) { + final FilterModeTextureFrame filterModeTextureFrame = new FilterModeTextureFrame( + frameDefinition.getName(), parent, decorateFileNames, texCoord); + filterModeTextureFrame.setFilterMode(FilterMode.ADDALPHA); + textureFrame = filterModeTextureFrame; + } + else { + textureFrame = new TextureFrame(frameDefinition.getName(), parent, decorateFileNames, texCoord); + } textureFrame.setTexture(file, this); inflatedFrame = textureFrame; break; @@ -736,9 +894,6 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { if (height != null) { inflatedFrame.setHeight(convertY(viewport2, height)); } - else if (frameDefinition.getFont("Font") != null) { - inflatedFrame.setHeight(convertY(viewport2, frameDefinition.getFont("Font").getFontSize())); - } for (final AnchorDefinition anchor : frameDefinition.getAnchors()) { inflatedFrame.addAnchor(new AnchorDefinition(anchor.getMyPoint(), convertX(this.viewport, anchor.getX()), convertY(this.viewport, anchor.getY()))); @@ -887,6 +1042,10 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { stringFrame.setText(text, this, this.viewport); } + public void setDecoratedText(final StringFrame stringFrame, final String text) { + stringFrame.setText(this.templates.getDecoratedString(text), this, this.viewport); + } + public BitmapFont getFont() { return this.font; } diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java index aeb61a4..1ca76a6 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java @@ -75,6 +75,16 @@ public abstract class AbstractRenderableFrame implements UIFrame { this.renderBounds.width = width; } + @Override + public float getAssignedWidth() { + return this.assignedWidth; + } + + @Override + public float getAssignedHeight() { + return this.assignedHeight; + } + @Override public void setHeight(final float height) { this.assignedHeight = height; diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/BackdropFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/BackdropFrame.java index 5d1f153..9e59928 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/BackdropFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/BackdropFrame.java @@ -15,7 +15,7 @@ public class BackdropFrame extends AbstractUIFrame { private final Texture background; private final EnumSet cornerFlags; private final float cornerSize; - private final float backgroundSize; + private float backgroundSize; private final Vector4Definition backgroundInsets; private final Texture edgeFile; private final float edgeFileWidth; @@ -42,6 +42,8 @@ public class BackdropFrame extends AbstractUIFrame { this.edgeUVWidth = 1f / 8f; this.edgeUVHeight = 1f; this.mirrored = mirrored; + this.backgroundSize -= (backgroundInsets.getX() + backgroundInsets.getY() + backgroundInsets.getZ() + + backgroundInsets.getW()) / 2f; } @Override diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/GlueButtonFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/GlueButtonFrame.java index baed87c..de20d4a 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/GlueButtonFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/GlueButtonFrame.java @@ -27,6 +27,9 @@ public class GlueButtonFrame extends AbstractRenderableFrame implements Clickabl public void setControlBackdrop(final UIFrame controlBackdrop) { this.controlBackdrop = controlBackdrop; + if (this.activeChild == null) { + this.activeChild = controlBackdrop; + } } public void setControlPushedBackdrop(final UIFrame controlPushedBackdrop) { diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/SimpleButtonFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/SimpleButtonFrame.java new file mode 100644 index 0000000..610a8c8 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/SimpleButtonFrame.java @@ -0,0 +1,217 @@ +package com.etheller.warsmash.parsers.fdf.frames; + +import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.g2d.GlyphLayout; +import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.badlogic.gdx.utils.viewport.Viewport; +import com.etheller.warsmash.parsers.fdf.GameUI; +import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.ClickableFrame; + +public class SimpleButtonFrame extends AbstractRenderableFrame implements ClickableFrame { + + private UIFrame controlBackdrop; + private UIFrame controlPushedBackdrop; + private UIFrame controlDisabledBackdrop; + private UIFrame controlMouseOverHighlight; + + private boolean enabled = true; + private boolean highlightOnMouseOver; + private boolean mouseOver = false; + private boolean pushed = false; + + private UIFrame activeChild; + private UIFrame activeTextChild; + + private UIFrame buttonText; + private UIFrame disabledText; + private UIFrame highlightText; + + private UIFrame pushedText; + private UIFrame pushedHighlightText; + + private Runnable onClick; + + public SimpleButtonFrame(final String name, final UIFrame parent) { + super(name, parent); + } + + public void setControlBackdrop(final UIFrame controlBackdrop) { + this.controlBackdrop = controlBackdrop; + if (this.activeChild == null) { + this.activeChild = controlBackdrop; + } + } + + public void setControlPushedBackdrop(final UIFrame controlPushedBackdrop) { + this.controlPushedBackdrop = controlPushedBackdrop; + } + + public void setControlDisabledBackdrop(final UIFrame controlDisabledBackdrop) { + this.controlDisabledBackdrop = controlDisabledBackdrop; + } + + public void setControlMouseOverHighlight(final UIFrame controlMouseOverHighlight) { + this.controlMouseOverHighlight = controlMouseOverHighlight; + this.highlightOnMouseOver |= controlMouseOverHighlight != null; + } + + public void setEnabled(final boolean enabled) { + this.enabled = enabled; + if (this.enabled) { + this.activeChild = this.controlBackdrop; + this.activeTextChild = this.buttonText; + } + else { + this.activeChild = this.controlDisabledBackdrop; + this.activeTextChild = this.disabledText; + } + } + + public void setHighlightOnMouseOver(final boolean highlightOnMouseOver) { + this.highlightOnMouseOver = highlightOnMouseOver; + } + + public void setOnClick(final Runnable onClick) { + this.onClick = onClick; + } + + @Override + protected void innerPositionBounds(final GameUI gameUI, final Viewport viewport) { + if (this.controlBackdrop != null) { + this.controlBackdrop.positionBounds(gameUI, viewport); + } + if (this.controlPushedBackdrop != null) { + this.controlPushedBackdrop.positionBounds(gameUI, viewport); + } + if (this.controlDisabledBackdrop != null) { + this.controlDisabledBackdrop.positionBounds(gameUI, viewport); + } + if (this.controlMouseOverHighlight != null) { + this.controlMouseOverHighlight.positionBounds(gameUI, viewport); + } + if (this.buttonText != null) { + this.buttonText.positionBounds(gameUI, viewport); + } + if (this.pushedText != null) { + this.pushedText.positionBounds(gameUI, viewport); + } + if (this.disabledText != null) { + this.disabledText.positionBounds(gameUI, viewport); + } + if (this.highlightText != null) { + this.highlightText.positionBounds(gameUI, viewport); + } + if (this.pushedHighlightText != null) { + this.pushedHighlightText.positionBounds(gameUI, viewport); + } + if (this.enabled) { + this.activeChild = this.controlBackdrop; + this.activeTextChild = this.buttonText; + } + else { + this.activeChild = this.controlDisabledBackdrop; + this.activeTextChild = this.disabledText; + } + } + + @Override + protected void internalRender(final SpriteBatch batch, final BitmapFont baseFont, final GlyphLayout glyphLayout) { + if (this.activeChild != null) { + this.activeChild.render(batch, baseFont, glyphLayout); + } + if (this.activeTextChild != null) { + this.activeTextChild.render(batch, baseFont, glyphLayout); + } + if (this.mouseOver) { + this.controlMouseOverHighlight.render(batch, baseFont, glyphLayout); + } + } + + @Override + public void mouseDown(final GameUI gameUI, final Viewport uiViewport) { + if (this.enabled) { + this.activeChild = this.controlPushedBackdrop; + this.pushed = true; + this.activeTextChild = this.mouseOver ? this.pushedHighlightText : this.pushedText; + } + } + + @Override + public void mouseUp(final GameUI gameUI, final Viewport uiViewport) { + if (this.enabled) { + this.activeChild = this.controlBackdrop; + this.activeTextChild = this.mouseOver ? this.highlightText : this.buttonText; + } + this.pushed = false; + } + + @Override + public void mouseEnter(final GameUI gameUI, final Viewport uiViewport) { + if (this.highlightOnMouseOver) { + this.mouseOver = true; + if (this.enabled) { + this.activeTextChild = this.pushed ? this.pushedHighlightText : this.highlightText; + } + } + } + + @Override + public void mouseExit(final GameUI gameUI, final Viewport uiViewport) { + this.mouseOver = false; + if (this.enabled) { + this.activeTextChild = this.pushed ? this.pushedText : this.buttonText; + } + } + + @Override + public void onClick(final int button) { + if (this.onClick != null) { + this.onClick.run(); + } + } + + @Override + public UIFrame touchUp(final float screenX, final float screenY, final int button) { + if (isVisible() && this.enabled && this.renderBounds.contains(screenX, screenY)) { + return this; + } + return super.touchUp(screenX, screenY, button); + } + + @Override + public UIFrame touchDown(final float screenX, final float screenY, final int button) { + if (isVisible() && this.enabled && this.renderBounds.contains(screenX, screenY)) { + return this; + } + return super.touchDown(screenX, screenY, button); + } + + @Override + public UIFrame getFrameChildUnderMouse(final float screenX, final float screenY) { + if (isVisible() && this.enabled && this.renderBounds.contains(screenX, screenY)) { + return this; + } + return super.getFrameChildUnderMouse(screenX, screenY); + } + + public void setButtonText(final UIFrame buttonText) { + this.buttonText = buttonText; + } + + public void setPushedHighlightText(final UIFrame pushedHighlightText) { + this.pushedHighlightText = pushedHighlightText; + } + + public void setPushedText(final UIFrame pushedText) { + this.pushedText = pushedText; + } + + public void setHighlightText(final UIFrame highlightText) { + this.highlightText = highlightText; + this.highlightOnMouseOver |= highlightText != null; + } + + public void setDisabledText(final UIFrame disabledText) { + this.disabledText = disabledText; + } +} diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/SpriteFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/SpriteFrame.java index ba042ba..79e32f2 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/SpriteFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/SpriteFrame.java @@ -13,7 +13,7 @@ import com.etheller.warsmash.viewer5.handlers.mdx.SequenceLoopMode; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; -public class SpriteFrame extends AbstractRenderableFrame { +public class SpriteFrame extends AbstractUIFrame { protected final Scene scene; protected final Viewport uiViewport; @@ -49,7 +49,7 @@ public class SpriteFrame extends AbstractRenderableFrame { @Override protected void internalRender(final SpriteBatch batch, final BitmapFont baseFont, final GlyphLayout glyphLayout) { - + super.internalRender(batch, baseFont, glyphLayout); } @Override @@ -71,6 +71,7 @@ public class SpriteFrame extends AbstractRenderableFrame { @Override protected void innerPositionBounds(final GameUI gameUI, final Viewport viewport) { + super.innerPositionBounds(gameUI, viewport); updateInstanceLocation(viewport); } diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/StringFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/StringFrame.java index 72fe1ad..ad8e2ed 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/StringFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/StringFrame.java @@ -16,7 +16,6 @@ import com.etheller.warsmash.parsers.fdf.datamodel.FramePoint; import com.etheller.warsmash.parsers.fdf.datamodel.TextJustify; public class StringFrame extends AbstractRenderableFrame { - private static final boolean DEBUG = false; private final List internalFrames = new ArrayList<>(); private Color color; private String text = "Default string"; @@ -62,6 +61,10 @@ public class StringFrame extends AbstractRenderableFrame { } } + public Color getColor() { + return this.color; + } + public void setFontShadowColor(final Color fontShadowColor) { this.fontShadowColor = fontShadowColor; for (final SingleStringFrame internalFrame : this.internalFrames) { @@ -87,7 +90,7 @@ public class StringFrame extends AbstractRenderableFrame { protected void internalRender(final SpriteBatch batch, final BitmapFont baseFont, final GlyphLayout glyphLayout) { this.internalFramesContainer.render(batch, baseFont, glyphLayout); - if (DEBUG) { + if (GameUI.DEBUG) { batch.end(); shapeRenderer.setProjectionMatrix(batch.getProjectionMatrix()); shapeRenderer.setColor(1f, 1f, 1f, 1f); diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/TextButtonFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/TextButtonFrame.java new file mode 100644 index 0000000..6e08134 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/TextButtonFrame.java @@ -0,0 +1,18 @@ +package com.etheller.warsmash.parsers.fdf.frames; + +public class TextButtonFrame extends GlueTextButtonFrame { + private float buttonPushedTextOffsetX; + private float buttonPushedTextOffsetY; + + public TextButtonFrame(final String name, final UIFrame parent) { + super(name, parent); + } + + public void setButtonPushedTextOffsetX(final float buttonPushedTextOffsetX) { + this.buttonPushedTextOffsetX = buttonPushedTextOffsetX; + } + + public void setButtonPushedTextOffsetY(final float buttonPushedTextOffsetY) { + this.buttonPushedTextOffsetY = buttonPushedTextOffsetY; + } +} diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/TextureFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/TextureFrame.java index 96ee8ca..22b77a6 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/TextureFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/TextureFrame.java @@ -48,7 +48,7 @@ public class TextureFrame extends AbstractRenderableFrame { public void setTexture(String file, final GameUI gameUI) { if (this.decorateFileNames) { - file = gameUI.getSkinField(file); + file = gameUI.trySkinField(file); } final Texture texture = gameUI.loadTexture(file); if (texture != null) { diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/UIFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/UIFrame.java index 3bb9f8c..3bd83b1 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/UIFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/UIFrame.java @@ -29,6 +29,10 @@ public interface UIFrame { void setHeight(final float height); + float getAssignedWidth(); + + float getAssignedHeight(); + void setSetAllPoints(boolean setAllPoints); void setSetAllPoints(boolean setAllPoints, float inset); diff --git a/core/src/com/etheller/warsmash/parsers/w3x/War3Map.java b/core/src/com/etheller/warsmash/parsers/w3x/War3Map.java index a647f4b..409d53a 100644 --- a/core/src/com/etheller/warsmash/parsers/w3x/War3Map.java +++ b/core/src/com/etheller/warsmash/parsers/w3x/War3Map.java @@ -20,6 +20,7 @@ import com.etheller.warsmash.parsers.w3x.unitsdoo.War3MapUnitsDoo; import com.etheller.warsmash.parsers.w3x.w3e.War3MapW3e; import com.etheller.warsmash.parsers.w3x.w3i.War3MapW3i; import com.etheller.warsmash.parsers.w3x.wpm.War3MapWpm; +import com.etheller.warsmash.units.custom.WTS; import com.google.common.io.LittleEndianDataInputStream; import mpq.MPQArchive; @@ -105,6 +106,11 @@ public class War3Map implements DataSource { return changes; } + public Warcraft3MapObjectData readModifications(final WTS preloadedWTS) throws IOException { + final Warcraft3MapObjectData changes = Warcraft3MapObjectData.load(this.dataSource, true, preloadedWTS); + return changes; + } + @Override public InputStream getResourceAsStream(final String filepath) throws IOException { return this.dataSource.getResourceAsStream(filepath); diff --git a/core/src/com/etheller/warsmash/parsers/w3x/objectdata/Warcraft3MapObjectData.java b/core/src/com/etheller/warsmash/parsers/w3x/objectdata/Warcraft3MapObjectData.java index adc5708..8236c53 100644 --- a/core/src/com/etheller/warsmash/parsers/w3x/objectdata/Warcraft3MapObjectData.java +++ b/core/src/com/etheller/warsmash/parsers/w3x/objectdata/Warcraft3MapObjectData.java @@ -94,7 +94,19 @@ public final class Warcraft3MapObjectData { return this.wts; } + public static WTS loadWTS(final DataSource dataSource) throws IOException { + final WTS wts = dataSource.has("war3map.wts") ? new WTSFile(dataSource.getResourceAsStream("war3map.wts")) + : WTS.DO_NOTHING; + return wts; + } + public static Warcraft3MapObjectData load(final DataSource dataSource, final boolean inlineWTS) throws IOException { + final WTS wts = loadWTS(dataSource); + return load(dataSource, inlineWTS, wts); + } + + public static Warcraft3MapObjectData load(final DataSource dataSource, final boolean inlineWTS, final WTS wts) + throws IOException { final StandardObjectData standardObjectData = new StandardObjectData(dataSource); final WarcraftData standardUnits = standardObjectData.getStandardUnits(); @@ -120,8 +132,6 @@ public final class Warcraft3MapObjectData { final War3ObjectDataChangeset buffChangeset = new War3ObjectDataChangeset('h'); final War3ObjectDataChangeset upgradeChangeset = new War3ObjectDataChangeset('q'); - final WTS wts = dataSource.has("war3map.wts") ? new WTSFile(dataSource.getResourceAsStream("war3map.wts")) - : WTS.DO_NOTHING; if (dataSource.has("war3map.w3u")) { unitChangeset.load(new LittleEndianDataInputStream(dataSource.getResourceAsStream("war3map.w3u")), wts, inlineWTS); diff --git a/core/src/com/etheller/warsmash/util/ImageUtils.java b/core/src/com/etheller/warsmash/util/ImageUtils.java index c9c6844..80358b1 100644 --- a/core/src/com/etheller/warsmash/util/ImageUtils.java +++ b/core/src/com/etheller/warsmash/util/ImageUtils.java @@ -18,6 +18,7 @@ import com.badlogic.gdx.graphics.GL30; import com.badlogic.gdx.graphics.Pixmap; import com.badlogic.gdx.graphics.Pixmap.Format; import com.badlogic.gdx.graphics.Texture; +import com.badlogic.gdx.graphics.Texture.TextureFilter; import com.etheller.warsmash.datasources.DataSource; import com.etheller.warsmash.viewer5.handlers.tga.TgaFile; @@ -153,6 +154,7 @@ public final class ImageUtils { } } final Texture texture = new Texture(pixmap); + texture.setFilter(TextureFilter.Linear, TextureFilter.Linear); return texture; } @@ -175,6 +177,7 @@ public final class ImageUtils { } } final Texture texture = new Texture(pixmap); + texture.setFilter(TextureFilter.Linear, TextureFilter.Linear); return texture; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java index 7336f62..33f6cf9 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java @@ -542,7 +542,7 @@ public class MdxComplexInstance extends ModelInstance { final MdxModel model = (MdxModel) this.model; final int sequenceId = this.sequence; - if (sequenceId != -1) { + if ((sequenceId != -1) && (model.sequences.size() != 0)) { final Sequence sequence = model.sequences.get(sequenceId); final long[] interval = sequence.getInterval(); final float frameTime = (dt * 1000 * this.animationSpeed); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxViewer.java index 2ddb277..ea4fe18 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxViewer.java @@ -12,15 +12,17 @@ import com.etheller.warsmash.viewer5.handlers.w3x.W3xScenePortraitLightManager; public class MdxViewer extends AbstractMdxModelViewer { private final WorldEditStrings worldEditStrings; + private final Vector3 defaultLighting; - public MdxViewer(final DataSource dataSource, final CanvasProvider canvas) { + public MdxViewer(final DataSource dataSource, final CanvasProvider canvas, final Vector3 defaultLighting) { super(dataSource, canvas); + this.defaultLighting = defaultLighting; this.worldEditStrings = new WorldEditStrings(this.dataSource); } @Override public SceneLightManager createLightManager(final boolean simple) { - return new W3xScenePortraitLightManager(this, new Vector3(0.3f, 0.3f, -0.25f)); + return new W3xScenePortraitLightManager(this, this.defaultLighting); } @Override diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index 4b4c67b..7778f4f 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -48,6 +48,7 @@ import com.etheller.warsmash.parsers.w3x.wpm.War3MapWpm; import com.etheller.warsmash.units.DataTable; import com.etheller.warsmash.units.Element; import com.etheller.warsmash.units.StandardObjectData; +import com.etheller.warsmash.units.custom.WTS; import com.etheller.warsmash.units.manager.MutableObjectData; import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; import com.etheller.warsmash.units.manager.MutableObjectData.WorldEditorDataType; @@ -110,7 +111,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CMapControl import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CRacePreference; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.ResourceType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.SimulationRenderController; -import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.CommandErrorListener; +import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.SettableCommandErrorListener; import com.etheller.warsmash.viewer5.handlers.w3x.ui.sound.KeyedSounds; import mpq.MPQArchive; @@ -220,14 +221,13 @@ public class War3MapViewer extends AbstractMdxModelViewer { private KeyedSounds uiSounds; private int localPlayerIndex; - private final CommandErrorListener commandErrorListener; + private final SettableCommandErrorListener commandErrorListener; public final List textTags = new ArrayList<>(); private final War3MapConfig mapConfig; - public War3MapViewer(final DataSource dataSource, final CanvasProvider canvas, - final CommandErrorListener errorListener, final War3MapConfig mapConfig) { + public War3MapViewer(final DataSource dataSource, final CanvasProvider canvas, final War3MapConfig mapConfig) { super(dataSource, canvas); this.gameDataSource = dataSource; @@ -243,7 +243,7 @@ public class War3MapViewer extends AbstractMdxModelViewer { throw new IllegalStateException("FrameBuffer setup failed"); } - this.commandErrorListener = errorListener; + this.commandErrorListener = new SettableCommandErrorListener(); this.mapConfig = mapConfig; } @@ -384,9 +384,29 @@ public class War3MapViewer extends AbstractMdxModelViewer { } } - public void loadMap(final String mapFilePath, final int localPlayerIndex) throws IOException { + public War3Map beginLoadingMap(final String mapFilePath) throws IOException { + return new War3Map(this.gameDataSource, mapFilePath); + } + + public DataTable loadWorldEditData(final War3Map map) { + final StandardObjectData standardObjectData = new StandardObjectData(map); + this.worldEditData = standardObjectData.getWorldEditData(); + return this.worldEditData; + } + + public WTS preloadWTS(final War3Map map) { + try { + this.preloadedWTS = Warcraft3MapObjectData.loadWTS(map); + return this.preloadedWTS; + } + catch (final IOException e) { + throw new RuntimeException(e); + } + } + + public void loadMap(final War3Map war3Map, final War3MapW3i w3iFile, final int localPlayerIndex) + throws IOException { this.localPlayerIndex = localPlayerIndex; - final War3Map war3Map = new War3Map(this.gameDataSource, mapFilePath); this.mapMpq = war3Map; @@ -394,8 +414,6 @@ public class War3MapViewer extends AbstractMdxModelViewer { char tileset = 'A'; - final War3MapW3i w3iFile = this.mapMpq.readMapInformation(); - if (ENABLE_WORLDEDIT_AS_GAMEPLAY_DATA_HACK) { int playerIndex = 0; for (final Player player : w3iFile.getPlayers()) { @@ -450,9 +468,6 @@ public class War3MapViewer extends AbstractMdxModelViewer { final War3MapWpm terrainPathing = this.mapMpq.readPathing(); - final StandardObjectData standardObjectData = new StandardObjectData(this.dataSource); - this.worldEditData = standardObjectData.getWorldEditData(); - this.terrain = new Terrain(terrainData, terrainPathing, w3iFile, this.webGL, this.dataSource, this.worldEditStrings, this, this.worldEditData); @@ -474,7 +489,12 @@ public class War3MapViewer extends AbstractMdxModelViewer { this.confirmationInstance.setSequence(0); this.confirmationInstance.setScene(this.worldScene); - this.allObjectData = this.mapMpq.readModifications(); + if (this.preloadedWTS != null) { + this.allObjectData = this.mapMpq.readModifications(this.preloadedWTS); + } + else { + this.allObjectData = this.mapMpq.readModifications(); + } this.simulation = new CSimulation(this.mapConfig, this.miscData, this.allObjectData.getUnits(), this.allObjectData.getItems(), this.allObjectData.getDestructibles(), this.allObjectData.getAbilities(), new SimulationRenderController() { @@ -1650,6 +1670,7 @@ public class War3MapViewer extends AbstractMdxModelViewer { private AbilityDataUI abilityDataUI; private Map soundsetNameToSoundset; public int imageWalkableZOffset; + private WTS preloadedWTS; /** * Returns a power of two size for the given target capacity. @@ -1872,6 +1893,10 @@ public class War3MapViewer extends AbstractMdxModelViewer { this.textTags.add(textTag); } + public SettableCommandErrorListener getCommandErrorListener() { + return this.commandErrorListener; + } + public War3MapConfig getMapConfig() { return this.mapConfig; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java index 7df7d6a..642c525 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java @@ -292,9 +292,6 @@ public class CUnitData { final int strength = unitType.getFieldAsInteger(STR, 0); final int agility = unitType.getFieldAsInteger(AGI, 0); final int intelligence = unitType.getFieldAsInteger(INT, 0); - if (typeId.toString().equals("Hjai")) { - System.out.println("bp"); - } final CPrimaryAttribute primaryAttribute = CPrimaryAttribute .parsePrimaryAttribute(unitType.getFieldAsString(PRIMARY_ATTRIBUTE, 0)); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java index eed8296..bf5aee9 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java @@ -36,7 +36,9 @@ import com.etheller.warsmash.parsers.fdf.datamodel.TextJustify; import com.etheller.warsmash.parsers.fdf.datamodel.Vector4Definition; import com.etheller.warsmash.parsers.fdf.frames.AbstractUIFrame; import com.etheller.warsmash.parsers.fdf.frames.FilterModeTextureFrame; +import com.etheller.warsmash.parsers.fdf.frames.GlueTextButtonFrame; import com.etheller.warsmash.parsers.fdf.frames.SetPoint; +import com.etheller.warsmash.parsers.fdf.frames.SimpleButtonFrame; import com.etheller.warsmash.parsers.fdf.frames.SimpleFrame; import com.etheller.warsmash.parsers.fdf.frames.SimpleStatusBarFrame; import com.etheller.warsmash.parsers.fdf.frames.SpriteFrame; @@ -137,6 +139,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.MeleeUIAbility import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.PointAbilityTargetCheckReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.ResourceType; import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.ClickableActionFrame; +import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.ClickableFrame; import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.CommandCardCommandListener; import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.CommandErrorListener; import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.QueueIconListener; @@ -263,8 +266,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma // probably remove them later private final float widthRatioCorrection; private final float heightRatioCorrection; - private ClickableActionFrame mouseDownUIFrame; - private ClickableActionFrame mouseOverUIFrame; + private ClickableFrame mouseDownUIFrame; + private ClickableFrame mouseOverUIFrame; private UIFrame smashSimpleInfoPanel; private SimpleFrame smashAttack1IconWrapper; private SimpleFrame smashAttack2IconWrapper; @@ -287,11 +290,16 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma private final StringBuilder recycleStringBuilder = new StringBuilder(); private CItem draggingItem; private final ItemCommandCardCommandListener itemCommandCardCommandListener; + private SimpleButtonFrame questsButton; + private SimpleButtonFrame menuButton; + private SimpleButtonFrame alliesButton; + private SimpleButtonFrame chatButton; + private final Runnable exitGameRunnable; public MeleeUI(final DataSource dataSource, final ExtendViewport uiViewport, final Scene uiScene, final Scene portraitScene, final CameraPreset[] cameraPresets, final CameraRates cameraRates, final War3MapViewer war3MapViewer, final RootFrameListener rootFrameListener, - final CPlayerUnitOrderListener unitOrderListener) { + final CPlayerUnitOrderListener unitOrderListener, final Runnable exitGameRunnable) { this.dataSource = dataSource; this.uiViewport = uiViewport; this.uiScene = uiScene; @@ -299,6 +307,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.war3MapViewer = war3MapViewer; this.rootFrameListener = rootFrameListener; this.unitOrderListener = unitOrderListener; + this.exitGameRunnable = exitGameRunnable; this.cameraManager = new GameCameraManager(cameraPresets, cameraRates); @@ -428,6 +437,131 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.resourceBarUpkeepText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarUpkeepText", 0); upkeepChanged(); + final UIFrame upperButtonBar = this.rootFrame.createSimpleFrame("UpperButtonBarFrame", this.consoleUI, 0); + upperButtonBar.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.consoleUI, FramePoint.TOPLEFT, 0, 0)); + + this.questsButton = (SimpleButtonFrame) this.rootFrame.getFrameByName("UpperButtonBarQuestsButton", 0); + this.questsButton.setEnabled(false); + this.menuButton = (SimpleButtonFrame) this.rootFrame.getFrameByName("UpperButtonBarMenuButton", 0); + this.alliesButton = (SimpleButtonFrame) this.rootFrame.getFrameByName("UpperButtonBarAlliesButton", 0); + this.alliesButton.setEnabled(false); + this.chatButton = (SimpleButtonFrame) this.rootFrame.getFrameByName("UpperButtonBarChatButton", 0); + this.chatButton.setEnabled(false); + + final UIFrame escMenuBackdrop = this.rootFrame.createFrame("EscMenuBackdrop", this.rootFrame, 0, 0); + escMenuBackdrop.setVisible(false); + escMenuBackdrop.addAnchor(new AnchorDefinition(FramePoint.TOP, 0, GameUI.convertY(this.uiViewport, -0.05f))); + final UIFrame escMenuMainPanel = this.rootFrame.createFrame("EscMenuMainPanel", this.rootFrame, 0, 0); + escMenuMainPanel.setVisible(false); + escMenuMainPanel.addAnchor(new AnchorDefinition(FramePoint.TOP, 0, GameUI.convertY(this.uiViewport, -0.05f))); + + final UIFrame escMenuInnerMainPanel = this.rootFrame.getFrameByName("MainPanel", 0); + final GlueTextButtonFrame pauseButton = (GlueTextButtonFrame) this.rootFrame.getFrameByName("PauseButton", 0); + pauseButton.setEnabled(false); + final GlueTextButtonFrame saveGameButton = (GlueTextButtonFrame) this.rootFrame.getFrameByName("SaveGameButton", + 0); + saveGameButton.setEnabled(false); + final GlueTextButtonFrame loadGameButton = (GlueTextButtonFrame) this.rootFrame.getFrameByName("LoadGameButton", + 0); + loadGameButton.setEnabled(false); + final GlueTextButtonFrame optionsButton = (GlueTextButtonFrame) this.rootFrame.getFrameByName("OptionsButton", + 0); + optionsButton.setEnabled(false); + final GlueTextButtonFrame helpButton = (GlueTextButtonFrame) this.rootFrame.getFrameByName("HelpButton", 0); + helpButton.setEnabled(false); + final GlueTextButtonFrame tipsButton = (GlueTextButtonFrame) this.rootFrame.getFrameByName("TipsButton", 0); + tipsButton.setEnabled(false); + final GlueTextButtonFrame endGameButton = (GlueTextButtonFrame) this.rootFrame.getFrameByName("EndGameButton", + 0); + final GlueTextButtonFrame returnButton = (GlueTextButtonFrame) this.rootFrame.getFrameByName("ReturnButton", 0); + + final UIFrame escMenuInnerEndGamePanel = this.rootFrame.getFrameByName("EndGamePanel", 0); + final GlueTextButtonFrame endGamePreviousButton = (GlueTextButtonFrame) this.rootFrame + .getFrameByName("PreviousButton", 0); + final GlueTextButtonFrame endGameQuitButton = (GlueTextButtonFrame) this.rootFrame.getFrameByName("QuitButton", + 0); + final GlueTextButtonFrame endGameRestartButton = (GlueTextButtonFrame) this.rootFrame + .getFrameByName("RestartButton", 0); + endGameRestartButton.setEnabled(false); + final GlueTextButtonFrame endGameExitButton = (GlueTextButtonFrame) this.rootFrame.getFrameByName("ExitButton", + 0); + + final UIFrame escMenuInnerConfirmQuitPanel = this.rootFrame.getFrameByName("ConfirmQuitPanel", 0); + final GlueTextButtonFrame confirmQuitCancelButton = (GlueTextButtonFrame) this.rootFrame + .getFrameByName("ConfirmQuitCancelButton", 0); + final GlueTextButtonFrame confirmQuitQuitButton = (GlueTextButtonFrame) this.rootFrame + .getFrameByName("ConfirmQuitQuitButton", 0); + final UIFrame escMenuInnerHelpPanel = this.rootFrame.getFrameByName("HelpPanel", 0); + final UIFrame escMenuInnerTipsPanel = this.rootFrame.getFrameByName("TipsPanel", 0); + escMenuInnerMainPanel.setVisible(false); + escMenuInnerEndGamePanel.setVisible(false); + escMenuInnerConfirmQuitPanel.setVisible(false); + escMenuInnerHelpPanel.setVisible(false); + escMenuInnerTipsPanel.setVisible(false); + + this.menuButton.setOnClick(new Runnable() { + @Override + public void run() { + escMenuBackdrop.setVisible(true); + escMenuMainPanel.setVisible(true); + escMenuInnerMainPanel.setVisible(true); + updateEscMenuCurrentPanel(escMenuBackdrop, escMenuMainPanel, escMenuInnerMainPanel); + } + }); + returnButton.setOnClick(new Runnable() { + @Override + public void run() { + escMenuBackdrop.setVisible(false); + escMenuMainPanel.setVisible(false); + escMenuInnerMainPanel.setVisible(false); + } + }); + endGameButton.setOnClick(new Runnable() { + @Override + public void run() { + escMenuInnerMainPanel.setVisible(false); + escMenuInnerEndGamePanel.setVisible(true); + updateEscMenuCurrentPanel(escMenuBackdrop, escMenuMainPanel, escMenuInnerEndGamePanel); + } + }); + endGamePreviousButton.setOnClick(new Runnable() { + @Override + public void run() { + escMenuInnerEndGamePanel.setVisible(false); + escMenuInnerMainPanel.setVisible(true); + updateEscMenuCurrentPanel(escMenuBackdrop, escMenuMainPanel, escMenuInnerMainPanel); + } + }); + endGameQuitButton.setOnClick(new Runnable() { + @Override + public void run() { + escMenuInnerEndGamePanel.setVisible(false); + MeleeUI.this.exitGameRunnable.run(); + } + }); + endGameExitButton.setOnClick(new Runnable() { + @Override + public void run() { + escMenuInnerEndGamePanel.setVisible(false); + escMenuInnerConfirmQuitPanel.setVisible(true); + updateEscMenuCurrentPanel(escMenuBackdrop, escMenuMainPanel, escMenuInnerConfirmQuitPanel); + } + }); + confirmQuitCancelButton.setOnClick(new Runnable() { + @Override + public void run() { + escMenuInnerEndGamePanel.setVisible(true); + escMenuInnerConfirmQuitPanel.setVisible(false); + updateEscMenuCurrentPanel(escMenuBackdrop, escMenuMainPanel, escMenuInnerEndGamePanel); + } + }); + confirmQuitQuitButton.setOnClick(new Runnable() { + @Override + public void run() { + Gdx.app.exit(); + } + }); + // Create the Time Indicator (clock) this.timeIndicator = (SpriteFrame) this.rootFrame.createFrame("TimeOfDayIndicator", this.rootFrame, 0, 0); this.timeIndicator.setSequence(0); // play the stand @@ -594,10 +728,19 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.inventoryBarFrame = (SimpleFrame) this.rootFrame.createSimpleFrame("SmashSimpleInventoryBar", this.rootFrame, 0); - this.inventoryBarFrame.setWidth(GameUI.convertX(this.uiViewport, 0.064f)); + this.inventoryBarFrame.setWidth(GameUI.convertX(this.uiViewport, 0.079f)); this.inventoryBarFrame.setHeight(GameUI.convertY(this.uiViewport, 0.115f)); this.inventoryBarFrame.addSetPoint(new SetPoint(FramePoint.BOTTOMRIGHT, this.consoleUI, FramePoint.BOTTOMLEFT, - GameUI.convertX(this.uiViewport, 0.496f), GameUI.convertY(this.uiViewport, 0.0f))); + GameUI.convertX(this.uiViewport, 0.591f), GameUI.convertY(this.uiViewport, 0.0f))); + + if (GameUI.DEBUG) { + final FilterModeTextureFrame placeholderPreview = new FilterModeTextureFrame(null, this.inventoryBarFrame, + false, null); + placeholderPreview.setFilterMode(FilterMode.ADDALPHA); + placeholderPreview.setTexture("ReplaceableTextures\\TeamColor\\TeamColor06.blp", this.rootFrame); + placeholderPreview.setSetAllPoints(true); + this.inventoryBarFrame.add(placeholderPreview); + } int commandButtonIndex = 0; for (int j = 0; j < INVENTORY_HEIGHT; j++) { @@ -610,8 +753,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma "SmashInventoryButton_" + (commandButtonIndex) + "_Icon", this.rootFrame, false, null); final SpriteFrame cooldownFrame = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", "SmashInventoryButton_" + (commandButtonIndex) + "_Cooldown", this.rootFrame, "", 0); - commandCardIcon.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.inventoryBarFrame, - FramePoint.TOPRIGHT, GameUI.convertX(this.uiViewport, 0.0187f + (0.04f * i)), + commandCardIcon.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.inventoryBarFrame, FramePoint.TOPLEFT, + GameUI.convertX(this.uiViewport, 0.0037f + (0.04f * i)), GameUI.convertY(this.uiViewport, -0.0021f - (0.03815f * j)))); commandCardIcon.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_INVENTORY_ICON_WIDTH)); commandCardIcon.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_INVENTORY_ICON_WIDTH)); @@ -630,12 +773,12 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } } this.inventoryTitleFrame = this.rootFrame.createStringFrame("SmashInventoryText", this.inventoryBarFrame, - new Color(0xFCDE12FF), TextJustify.LEFT, TextJustify.MIDDLE, 0.0109f); + new Color(0xFCDE12FF), TextJustify.CENTER, TextJustify.MIDDLE, 0.0109f); this.rootFrame.setText(this.inventoryTitleFrame, this.rootFrame.getTemplates().getDecoratedString("INVENTORY")); this.inventoryTitleFrame - .addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.inventoryBarFrame, FramePoint.TOPRIGHT, - GameUI.convertX(this.uiViewport, 0.029f), GameUI.convertY(this.uiViewport, 0.0165625f))); - this.inventoryTitleFrame.setWidth(GameUI.convertX(this.uiViewport, 0.076875f)); + .addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.inventoryBarFrame, FramePoint.TOPLEFT, + GameUI.convertX(this.uiViewport, 0.004f), GameUI.convertY(this.uiViewport, 0.0165625f))); + this.inventoryTitleFrame.setWidth(GameUI.convertX(this.uiViewport, 0.071f)); this.inventoryTitleFrame.setHeight(GameUI.convertX(this.uiViewport, 0.01125f)); this.inventoryTitleFrame.setFontShadowColor(new Color(0f, 0f, 0f, 0.9f)); this.inventoryTitleFrame.setFontShadowOffsetX(GameUI.convertX(this.uiViewport, 0.001f)); @@ -731,7 +874,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma .getFrameByName("SmashToolTipIconResourceLabel", i); this.tooltipResourceFrames[i].addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.tooltipText, FramePoint.BOTTOMLEFT, GameUI.convertX(this.uiViewport, 0.004f + (0.032f * i)), - GameUI.convertY(this.uiViewport, -0.011f))); + GameUI.convertY(this.uiViewport, -0.001f))); // have we really no better API than the below??? ((AbstractUIFrame) this.tooltipFrame).add(this.tooltipResourceFrames[i]); this.rootFrame.remove(this.tooltipResourceFrames[i]); @@ -779,6 +922,16 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } + private void updateEscMenuCurrentPanel(final UIFrame escMenuBackdrop, final UIFrame escMenuMainPanel, + final UIFrame escMenuInnerMainPanel) { + escMenuMainPanel.setHeight(escMenuInnerMainPanel.getAssignedHeight()); + escMenuMainPanel.setWidth(escMenuInnerMainPanel.getAssignedWidth()); + escMenuBackdrop.setHeight(escMenuInnerMainPanel.getAssignedHeight()); + escMenuBackdrop.setWidth(escMenuInnerMainPanel.getAssignedWidth()); + escMenuMainPanel.positionBounds(MeleeUI.this.rootFrame, MeleeUI.this.uiViewport); + escMenuBackdrop.positionBounds(MeleeUI.this.rootFrame, MeleeUI.this.uiViewport); + } + @Override public void onClick(final int abilityHandleId, final int orderId, final boolean rightClick) { // TODO not O(N) @@ -2043,7 +2196,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma public void ordersChanged() { reloadSelectedUnitUI(this.selectedUnit); if (this.mouseOverUIFrame instanceof ClickableActionFrame) { - loadTooltip(); + loadTooltip((ClickableActionFrame) this.mouseOverUIFrame); } } @@ -2284,8 +2437,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } } else { - if (clickedUIFrame instanceof ClickableActionFrame) { - this.mouseDownUIFrame = (ClickableActionFrame) clickedUIFrame; + if (clickedUIFrame instanceof ClickableFrame) { + this.mouseDownUIFrame = (ClickableFrame) clickedUIFrame; this.mouseDownUIFrame.mouseDown(this.rootFrame, this.uiViewport); } } @@ -2403,7 +2556,14 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma if (this.mouseDownUIFrame != null) { if (clickedUIFrame == this.mouseDownUIFrame) { this.mouseDownUIFrame.onClick(button); - this.war3MapViewer.getUiSounds().getSound("InterfaceClick").play(this.uiScene.audioContext, 0, 0, 0); + if (this.mouseDownUIFrame instanceof ClickableActionFrame) { + this.war3MapViewer.getUiSounds().getSound("InterfaceClick").play(this.uiScene.audioContext, 0, 0, + 0); + } + else { + this.war3MapViewer.getUiSounds().getSound("MenuButtonClick").play(this.uiScene.audioContext, 0, 0, + 0); + } } this.mouseDownUIFrame.mouseUp(this.rootFrame, this.uiViewport); } @@ -2434,9 +2594,17 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma final UIFrame mousedUIFrame = this.rootFrame.getFrameChildUnderMouse(screenCoordsVector.x, screenCoordsVector.y); if (mousedUIFrame != this.mouseOverUIFrame) { - if (mousedUIFrame instanceof ClickableActionFrame) { - this.mouseOverUIFrame = (ClickableActionFrame) mousedUIFrame; - loadTooltip(); + if (this.mouseOverUIFrame != null) { + this.mouseOverUIFrame.mouseExit(this.rootFrame, this.uiViewport); + } + if (mousedUIFrame instanceof ClickableFrame) { + this.mouseOverUIFrame = (ClickableFrame) mousedUIFrame; + if (this.mouseOverUIFrame != null) { + this.mouseOverUIFrame.mouseEnter(this.rootFrame, this.uiViewport); + } + if (mousedUIFrame instanceof ClickableActionFrame) { + loadTooltip((ClickableActionFrame) mousedUIFrame); + } } else { this.mouseOverUIFrame = null; @@ -2446,12 +2614,12 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma return false; } - private void loadTooltip() { - final int goldCost = this.mouseOverUIFrame.getToolTipGoldCost(); - final int lumberCost = this.mouseOverUIFrame.getToolTipLumberCost(); - final int foodCost = this.mouseOverUIFrame.getToolTipFoodCost(); - final String toolTip = this.mouseOverUIFrame.getToolTip(); - final String uberTip = this.mouseOverUIFrame.getUberTip(); + private void loadTooltip(final ClickableActionFrame mousedUIFrame) { + final int goldCost = mousedUIFrame.getToolTipGoldCost(); + final int lumberCost = mousedUIFrame.getToolTipLumberCost(); + final int foodCost = mousedUIFrame.getToolTipFoodCost(); + final String toolTip = mousedUIFrame.getToolTip(); + final String uberTip = mousedUIFrame.getUberTip(); if ((toolTip == null) || (uberTip == null)) { this.tooltipFrame.setVisible(false); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MenuUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MenuUI.java index c9b791e..1d41c7f 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MenuUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MenuUI.java @@ -12,6 +12,7 @@ import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.utils.viewport.ExtendViewport; import com.badlogic.gdx.utils.viewport.Viewport; import com.etheller.warsmash.SingleModelScreen; +import com.etheller.warsmash.WarsmashGdxMapScreen; import com.etheller.warsmash.WarsmashGdxMenuScreen; import com.etheller.warsmash.WarsmashGdxMultiScreenGame; import com.etheller.warsmash.datasources.DataSource; @@ -27,6 +28,8 @@ import com.etheller.warsmash.parsers.fdf.frames.SpriteFrame; import com.etheller.warsmash.parsers.fdf.frames.StringFrame; import com.etheller.warsmash.parsers.fdf.frames.UIFrame; import com.etheller.warsmash.parsers.jass.Jass2.RootFrameListener; +import com.etheller.warsmash.parsers.w3x.War3Map; +import com.etheller.warsmash.parsers.w3x.w3i.War3MapW3i; import com.etheller.warsmash.units.DataTable; import com.etheller.warsmash.units.Element; import com.etheller.warsmash.units.custom.WTS; @@ -36,8 +39,14 @@ import com.etheller.warsmash.util.WorldEditStrings; import com.etheller.warsmash.viewer5.Scene; import com.etheller.warsmash.viewer5.handlers.mdx.MdxViewer; import com.etheller.warsmash.viewer5.handlers.w3x.UnitSound; +import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.config.War3MapConfig; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CRace; import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.ClickableFrame; import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.FocusableFrame; +import com.etheller.warsmash.viewer5.handlers.w3x.ui.menu.CampaignMenuData; +import com.etheller.warsmash.viewer5.handlers.w3x.ui.menu.CampaignMenuUI; +import com.etheller.warsmash.viewer5.handlers.w3x.ui.menu.CampaignMission; import com.etheller.warsmash.viewer5.handlers.w3x.ui.sound.KeyedSounds; public class MenuUI { @@ -118,11 +127,9 @@ public class MenuUI { private SpriteFrame campaignWarcraftIIILogo; private final SingleModelScreen menuScreen; - private String currentCampaignBackgroundModel; - private String currentCampaignAmbientSound; - private int currentCampaignCursor; + private CampaignMenuData currentCampaign; private String[] campaignList; - private Element[] campaignDatas; + private CampaignMenuData[] campaignDatas; private UnitSound mainMenuGlueScreenLoop; private GlueTextButtonFrame addProfileButton; private GlueTextButtonFrame deleteProfileButton; @@ -130,6 +137,18 @@ public class MenuUI { private final PlayerProfileManager profileManager; private StringFrame profileNameText; private UIFrame confirmDialog; + private CampaignMenuUI campaignRootMenuUI; + private CampaignMenuUI currentMissionSelectMenuUI; + private UIFrame loadingFrame; + private UIFrame loadingCustomPanel; + private UIFrame loadingMeleePanel; + private StringFrame loadingTitleText; + private StringFrame loadingSubtitleText; + private StringFrame loadingText; + private SpriteFrame loadingBar; + private String mapFilepathToStart; + private LoadingMap loadingMap; + private SpriteFrame loadingBackground; public MenuUI(final DataSource dataSource, final Viewport uiViewport, final Scene uiScene, final MdxViewer viewer, final WarsmashGdxMultiScreenGame screenManager, final SingleModelScreen menuScreen, @@ -455,6 +474,9 @@ public class MenuUI { this.campaignBackButton.setVisible(false); this.missionSelectFrame = this.rootFrame.getFrameByName("MissionSelectFrame", 0); this.missionSelectFrame.setVisible(false); + final StringFrame missionName = (StringFrame) this.rootFrame.getFrameByName("MissionName", 0); + final StringFrame missionNameHeader = (StringFrame) this.rootFrame.getFrameByName("MissionNameHeader", 0); + this.campaignSelectFrame = this.rootFrame.getFrameByName("CampaignSelectFrame", 0); this.campaignSelectFrame.setVisible(false); @@ -462,36 +484,130 @@ public class MenuUI { this.rootFrame.setSpriteFrameModel(this.campaignWarcraftIIILogo, this.rootFrame.getSkinField("MainMenuLogo_V" + WarsmashConstants.GAME_VERSION)); this.campaignWarcraftIIILogo.setVisible(false); + this.campaignWarcraftIIILogo + .addSetPoint(new SetPoint(FramePoint.TOPRIGHT, this.campaignMenu, FramePoint.TOPRIGHT, + GameUI.convertX(this.uiViewport, -0.13f), GameUI.convertY(this.uiViewport, -0.08f))); + this.campaignRootMenuUI = new CampaignMenuUI(null, this.campaignMenu, this.rootFrame, this.uiViewport); + this.campaignRootMenuUI.setVisible(false); + this.campaignRootMenuUI.addSetPoint(new SetPoint(FramePoint.TOPRIGHT, this.campaignMenu, FramePoint.TOPRIGHT, + GameUI.convertX(this.uiViewport, -0.0f), GameUI.convertY(this.uiViewport, -0.12f))); + this.campaignRootMenuUI.setWidth(GameUI.convertX(this.uiViewport, 0.30f)); + this.campaignRootMenuUI.setHeight(GameUI.convertY(this.uiViewport, 0.42f)); + this.rootFrame.add(this.campaignRootMenuUI); this.campaignBackButton.setOnClick(new Runnable() { @Override public void run() { - MenuUI.this.campaignMenu.setVisible(false); - MenuUI.this.campaignBackButton.setVisible(false); - MenuUI.this.missionSelectFrame.setVisible(false); - MenuUI.this.campaignSelectFrame.setVisible(false); - MenuUI.this.campaignFade.setSequence("Birth"); - MenuUI.this.menuState = MenuState.LEAVING_CAMPAIGN; + if (MenuUI.this.currentMissionSelectMenuUI != null) { + MenuUI.this.currentMissionSelectMenuUI.setVisible(false); + MenuUI.this.missionSelectFrame.setVisible(false); + MenuUI.this.menuState = MenuState.CAMPAIGN; + MenuUI.this.currentMissionSelectMenuUI = null; + } + else { + MenuUI.this.campaignMenu.setVisible(false); + MenuUI.this.campaignBackButton.setVisible(false); + MenuUI.this.missionSelectFrame.setVisible(false); + MenuUI.this.campaignSelectFrame.setVisible(false); + MenuUI.this.campaignWarcraftIIILogo.setVisible(false); + MenuUI.this.campaignRootMenuUI.setVisible(false); + MenuUI.this.campaignFade.setSequence("Birth"); + MenuUI.this.menuState = MenuState.LEAVING_CAMPAIGN; + } } }); final Element campaignIndex = this.campaignStrings.get("Index"); this.campaignList = campaignIndex.getField("CampaignList").split(","); - this.campaignDatas = new Element[this.campaignList.length]; + this.campaignDatas = new CampaignMenuData[this.campaignList.length]; for (int i = 0; i < this.campaignList.length; i++) { final String campaign = this.campaignList[i]; - this.campaignDatas[i] = this.campaignStrings.get(campaign); - if ((this.campaignDatas[i] != null) && (this.currentCampaignBackgroundModel == null)) { - this.currentCampaignBackgroundModel = this.rootFrame.getSkinField( - this.campaignDatas[i].getField("Background") + "_V" + WarsmashConstants.GAME_VERSION); - this.currentCampaignAmbientSound = this.rootFrame - .trySkinField(this.campaignDatas[i].getField("AmbientSound")); - this.currentCampaignCursor = this.campaignDatas[i].getFieldValue("Cursor"); + final Element campaignElement = this.campaignStrings.get(campaign); + if (campaignElement != null) { + final CampaignMenuData newCampaign = new CampaignMenuData(campaignElement); + this.campaignDatas[i] = newCampaign; + if (this.currentCampaign == null) { + this.currentCampaign = newCampaign; + } + + } + } + for (final CampaignMenuData campaign : this.campaignDatas) { + if (campaign != null) { + final CampaignMenuUI missionSelectMenuUI = new CampaignMenuUI(null, this.campaignMenu, this.rootFrame, + this.uiViewport); + missionSelectMenuUI.setVisible(false); + missionSelectMenuUI + .addSetPoint(new SetPoint(FramePoint.TOPRIGHT, this.campaignMenu, FramePoint.TOPRIGHT, + GameUI.convertX(this.uiViewport, -0.0f), GameUI.convertY(this.uiViewport, -0.12f))); + missionSelectMenuUI.setWidth(GameUI.convertX(this.uiViewport, 0.30f)); + missionSelectMenuUI.setHeight(GameUI.convertY(this.uiViewport, 0.42f)); + this.rootFrame.add(missionSelectMenuUI); + + for (final CampaignMission mission : campaign.getMissions()) { + missionSelectMenuUI.addButton(mission.getHeader(), mission.getMissionName(), new Runnable() { + @Override + public void run() { + MenuUI.this.campaignMenu.setVisible(false); + MenuUI.this.campaignBackButton.setVisible(false); + MenuUI.this.missionSelectFrame.setVisible(false); + MenuUI.this.campaignSelectFrame.setVisible(false); + MenuUI.this.campaignWarcraftIIILogo.setVisible(false); + MenuUI.this.campaignRootMenuUI.setVisible(false); + MenuUI.this.currentMissionSelectMenuUI.setVisible(false); + MenuUI.this.campaignFade.setSequence("Birth"); + MenuUI.this.mapFilepathToStart = mission.getMapFilename(); + } + }); + } + + this.campaignRootMenuUI.addButton(campaign.getHeader(), campaign.getName(), new Runnable() { + @Override + public void run() { + if (campaign != MenuUI.this.currentCampaign) { + MenuUI.this.campaignMenu.setVisible(false); + MenuUI.this.campaignBackButton.setVisible(false); + MenuUI.this.missionSelectFrame.setVisible(false); + MenuUI.this.campaignSelectFrame.setVisible(false); + MenuUI.this.campaignWarcraftIIILogo.setVisible(false); + MenuUI.this.campaignRootMenuUI.setVisible(false); + MenuUI.this.campaignFade.setSequence("Birth"); + MenuUI.this.currentCampaign = campaign; + MenuUI.this.currentMissionSelectMenuUI = missionSelectMenuUI; + MenuUI.this.menuState = MenuState.GOING_TO_MISSION_SELECT; + } + else { + MenuUI.this.campaignSelectFrame.setVisible(false); + MenuUI.this.campaignRootMenuUI.setVisible(false); + MenuUI.this.currentMissionSelectMenuUI.setVisible(true); + MenuUI.this.missionSelectFrame.setVisible(true); + MenuUI.this.menuState = MenuState.MISSION_SELECT; + } + MenuUI.this.rootFrame.setText(missionName, campaign.getName()); + MenuUI.this.rootFrame.setText(missionNameHeader, campaign.getHeader()); + } + }); + if (campaign == MenuUI.this.currentCampaign) { + MenuUI.this.currentMissionSelectMenuUI = missionSelectMenuUI; + } } } this.confirmDialog = this.rootFrame.createFrame("DialogWar3", this.rootFrame, 0, 0); this.confirmDialog.setVisible(false); + this.loadingFrame = this.rootFrame.createFrame("Loading", this.rootFrame, 0, 0); + this.loadingFrame.setVisible(false); + this.loadingCustomPanel = this.rootFrame.getFrameByName("LoadingCustomPanel", 0); + this.loadingCustomPanel.setVisible(false); + this.loadingTitleText = (StringFrame) this.rootFrame.getFrameByName("LoadingTitleText", 0); + this.loadingSubtitleText = (StringFrame) this.rootFrame.getFrameByName("LoadingSubtitleText", 0); + this.loadingText = (StringFrame) this.rootFrame.getFrameByName("LoadingText", 0); + this.loadingBar = (SpriteFrame) this.rootFrame.getFrameByName("LoadingBar", 0); + this.loadingBackground = (SpriteFrame) this.rootFrame.getFrameByName("LoadingBackground", 0); + + this.loadingMeleePanel = this.rootFrame.getFrameByName("LoadingMeleePanel", 0); + this.loadingMeleePanel.setVisible(false); + // position all this.rootFrame.positionBounds(this.rootFrame, this.uiViewport); @@ -505,6 +621,62 @@ public class MenuUI { this.glueScreenLoop.play(this.uiScene.audioContext, 0f, 0f, 0f); } + private void internalStartMap(final String mapFilename) { + this.loadingFrame.setVisible(true); + this.loadingBar.setVisible(true); + this.loadingCustomPanel.setVisible(true); + final DataSource codebase = WarsmashGdxMapScreen.parseDataSources(this.warsmashIni); + final War3MapViewer viewer = new War3MapViewer(codebase, this.screenManager, + new War3MapConfig(WarsmashConstants.MAX_PLAYERS)); + + if (WarsmashGdxMapScreen.ENABLE_AUDIO) { + viewer.worldScene.enableAudio(); + viewer.enableAudio(); + } + try { + final War3Map map = viewer.beginLoadingMap(mapFilename); + final War3MapW3i mapInfo = map.readMapInformation(); + final DataTable worldEditData = viewer.loadWorldEditData(map); + final WTS wts = viewer.preloadWTS(map); + + final int loadingScreen = mapInfo.getLoadingScreen(); + System.out.println("LOADING SCREEN INT: " + loadingScreen); + final int campaignBackground = mapInfo.getCampaignBackground(); + final Element loadingScreens = worldEditData.get("LoadingScreens"); + final String key = String.format("%2s", Integer.toString(campaignBackground)).replace(' ', '0'); + final int animationSequenceIndex = loadingScreens.getFieldValue(key, 2); + final String campaignScreenModel = loadingScreens.getField(key, 3); + + this.menuScreen.setModel(null); + this.rootFrame.setSpriteFrameModel(this.loadingBackground, campaignScreenModel); + this.loadingBackground.setSequence(animationSequenceIndex); + this.rootFrame.setSpriteFrameModel(this.loadingBar, this.rootFrame.getSkinField("LoadingProgressBar")); + this.loadingBar.setSequence(0); + this.loadingBar.setFrameByRatio(0.5f); + this.loadingBar.setZDepth(1.0f); + this.rootFrame.setText(this.loadingTitleText, getStringWithWTS(wts, mapInfo.getLoadingScreenTitle())); + this.rootFrame.setText(this.loadingSubtitleText, getStringWithWTS(wts, mapInfo.getLoadingScreenSubtitle())); + this.rootFrame.setText(this.loadingText, getStringWithWTS(wts, mapInfo.getLoadingScreenText())); + this.loadingMap = new LoadingMap(viewer, map, mapInfo); + + } + catch (final IOException e) { + throw new RuntimeException(e); + } + } + + private static String getStringWithWTS(final WTS wts, String string) { + if (string.startsWith("TRIGSTR_")) { + string = wts.get(Integer.parseInt(string.substring(8))); + } + return string; + } + + public void startMap(final String mapFilename) { + this.mainMenuFrame.setVisible(false); + internalStartMap(mapFilename); + } + private void setCurrentProfile(final String selectedProfileName) { this.profileManager.setCurrentProfile(selectedProfileName); this.rootFrame.setText(MenuUI.this.profileNameText, selectedProfileName); @@ -554,6 +726,29 @@ public class MenuUI { } public void update(final float deltaTime) { + if (this.mapFilepathToStart != null) { + this.campaignFade.setVisible(false); + internalStartMap(this.mapFilepathToStart); + this.mapFilepathToStart = null; + return; + } + else if (this.loadingMap != null) { + try { + this.loadingMap.viewer.loadMap(this.loadingMap.map, this.loadingMap.mapInfo, 0); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + // TODO not cast menu screen + MenuUI.this.screenManager.setScreen(new WarsmashGdxMapScreen(this.loadingMap.viewer, this.screenManager, + (WarsmashGdxMenuScreen) this.menuScreen)); + this.loadingMap = null; + + this.loadingBar.setVisible(false); + this.loadingFrame.setVisible(false); + this.loadingBackground.setVisible(false); + return; + } if ((this.focusUIFrame != null) && !this.focusUIFrame.isVisibleOnScreen()) { setFocusFrame(getNextFocusFrame()); } @@ -636,30 +831,53 @@ public class MenuUI { this.campaignFade.setSequence("Birth"); this.menuState = MenuState.GOING_TO_CAMPAIGN_PART2; break; - case GOING_TO_CAMPAIGN_PART2: - this.menuScreen.setModel(this.currentCampaignBackgroundModel); + case GOING_TO_CAMPAIGN_PART2: { + final String currentCampaignBackgroundModel = this.rootFrame + .getSkinField(this.currentCampaign.getBackground() + "_V" + WarsmashConstants.GAME_VERSION); + final String currentCampaignAmbientSound = this.rootFrame + .trySkinField(this.currentCampaign.getAmbientSound()); + this.menuScreen.setModel(currentCampaignBackgroundModel); this.glueScreenLoop.stop(); - this.glueScreenLoop = this.uiSounds.getSound(this.currentCampaignAmbientSound); + this.glueScreenLoop = this.uiSounds.getSound(currentCampaignAmbientSound); this.glueScreenLoop.play(this.uiScene.audioContext, 0f, 0f, 0f); final DataTable skinData = this.rootFrame.getSkinData(); - final Element skinDataMain = skinData.get("Main"); - int currentCampaignCursor = this.currentCampaignCursor; - if (currentCampaignCursor == 3) { - currentCampaignCursor = 2; - } - else if (currentCampaignCursor == 2) { - currentCampaignCursor = 3; - } - final String cursorSkin = skinDataMain.getField("Skins", currentCampaignCursor); + final String cursorSkin = CRace.VALUES[this.currentCampaign.getCursor()].name(); this.rootFrame.setSpriteFrameModel(this.cursorFrame, skinData.get(cursorSkin).getField("Cursor")); this.campaignFade.setSequence("Death"); this.menuState = MenuState.CAMPAIGN; break; + } case CAMPAIGN: + this.campaignMenu.setVisible(true); this.campaignBackButton.setVisible(true); this.campaignWarcraftIIILogo.setVisible(true); this.campaignSelectFrame.setVisible(true); + this.campaignRootMenuUI.setVisible(true); + break; + case GOING_TO_MISSION_SELECT: { + final String currentCampaignBackgroundModel = this.rootFrame + .getSkinField(this.currentCampaign.getBackground() + "_V" + WarsmashConstants.GAME_VERSION); + final String currentCampaignAmbientSound = this.rootFrame + .trySkinField(this.currentCampaign.getAmbientSound()); + this.menuScreen.setModel(currentCampaignBackgroundModel); + this.glueScreenLoop.stop(); + this.glueScreenLoop = this.uiSounds.getSound(currentCampaignAmbientSound); + this.glueScreenLoop.play(this.uiScene.audioContext, 0f, 0f, 0f); + final DataTable skinData = this.rootFrame.getSkinData(); + final String cursorSkin = CRace.VALUES[this.currentCampaign.getCursor()].name(); + this.rootFrame.setSpriteFrameModel(this.cursorFrame, skinData.get(cursorSkin).getField("Cursor")); + + this.campaignFade.setSequence("Death"); + this.menuState = MenuState.MISSION_SELECT; + break; + } + case MISSION_SELECT: + this.campaignMenu.setVisible(true); + this.campaignBackButton.setVisible(true); + this.campaignWarcraftIIILogo.setVisible(true); + this.currentMissionSelectMenuUI.setVisible(true); + this.missionSelectFrame.setVisible(true); break; case GOING_TO_SINGLE_PLAYER_PROFILE: this.glueSpriteLayerTopLeft.setSequence("RealmSelection Birth"); @@ -796,9 +1014,12 @@ public class MenuUI { SINGLE_PLAYER_SKIRMISH, GOING_TO_CAMPAIGN, GOING_TO_CAMPAIGN_PART2, + GOING_TO_MISSION_SELECT, + MISSION_SELECT, CAMPAIGN, GOING_TO_SINGLE_PLAYER_PROFILE, SINGLE_PLAYER_PROFILE, + GOING_TO_LOADING_SCREEN, QUITTING, RESTARTING; } @@ -833,4 +1054,57 @@ public class MenuUI { } return false; } + + public void onReturnFromGame() { +// MenuUI.this.campaignMenu.setVisible(true); +// MenuUI.this.campaignBackButton.setVisible(true); +// MenuUI.this.missionSelectFrame.setVisible(true); +// MenuUI.this.campaignSelectFrame.setVisible(false); +// MenuUI.this.campaignWarcraftIIILogo.setVisible(true); +// MenuUI.this.campaignRootMenuUI.setVisible(false); +// MenuUI.this.currentMissionSelectMenuUI.setVisible(true); + switch (this.menuState) { + default: + case GOING_TO_MAIN_MENU: + case MAIN_MENU: + this.glueScreenLoop.stop(); + this.glueScreenLoop = this.mainMenuGlueScreenLoop; + this.glueScreenLoop.play(this.uiScene.audioContext, 0f, 0f, 0f); + this.menuScreen.setModel( + this.rootFrame.getSkinField("GlueSpriteLayerBackground_V" + WarsmashConstants.GAME_VERSION)); + this.rootFrame.setSpriteFrameModel(this.cursorFrame, this.rootFrame.getSkinField("Cursor")); + break; + case CAMPAIGN: + case MISSION_SELECT: + final String currentCampaignBackgroundModel = this.rootFrame + .getSkinField(this.currentCampaign.getBackground() + "_V" + WarsmashConstants.GAME_VERSION); + final String currentCampaignAmbientSound = this.rootFrame + .trySkinField(this.currentCampaign.getAmbientSound()); + this.menuScreen.setModel(currentCampaignBackgroundModel); + this.glueScreenLoop.stop(); + this.glueScreenLoop = this.uiSounds.getSound(currentCampaignAmbientSound); + this.glueScreenLoop.play(this.uiScene.audioContext, 0f, 0f, 0f); + final DataTable skinData = this.rootFrame.getSkinData(); + final String cursorSkin = CRace.VALUES[this.currentCampaign.getCursor()].name(); + this.rootFrame.setSpriteFrameModel(this.cursorFrame, skinData.get(cursorSkin).getField("Cursor")); + break; + } +// MenuUI.this.campaignFade.setSequence("Death"); +// this.campaignFade.setVisible(true); +// this.menuState = MenuState.MISSION_SELECT; + } + + private static final class LoadingMap { + + private final War3MapViewer viewer; + private final War3Map map; + private final War3MapW3i mapInfo; + + public LoadingMap(final War3MapViewer viewer, final War3Map map, final War3MapW3i mapInfo) { + this.viewer = viewer; + this.map = map; + this.mapInfo = mapInfo; + } + + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/menu/CampaignButtonUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/menu/CampaignButtonUI.java new file mode 100644 index 0000000..becd2b1 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/menu/CampaignButtonUI.java @@ -0,0 +1,104 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.ui.menu; + +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.utils.viewport.Viewport; +import com.etheller.warsmash.parsers.fdf.GameUI; +import com.etheller.warsmash.parsers.fdf.frames.AbstractUIFrame; +import com.etheller.warsmash.parsers.fdf.frames.GlueButtonFrame; +import com.etheller.warsmash.parsers.fdf.frames.StringFrame; +import com.etheller.warsmash.parsers.fdf.frames.UIFrame; +import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.ClickableFrame; + +public class CampaignButtonUI extends AbstractUIFrame implements ClickableFrame { + + private GlueButtonFrame buttonArt; + private boolean enabled = true; + private StringFrame headerText; + private StringFrame nameText; + private Color defaultNameColor; + private Color defaultHeaderColor; + private boolean artHighlight; + + public CampaignButtonUI(final String name, final UIFrame parent) { + super(name, parent); + } + + public void setButtonArt(final GlueButtonFrame buttonArt) { + this.buttonArt = buttonArt; + } + + public void setEnabled(final boolean enabled) { + this.enabled = enabled; + this.buttonArt.setEnabled(enabled); + } + + public void setOnClick(final Runnable onClick) { + this.buttonArt.setOnClick(onClick); + } + + @Override + public UIFrame touchUp(final float screenX, final float screenY, final int button) { + if (isVisible() && this.enabled && this.renderBounds.contains(screenX, screenY)) { + return this; + } + return super.touchUp(screenX, screenY, button); + } + + @Override + public UIFrame touchDown(final float screenX, final float screenY, final int button) { + if (isVisible() && this.enabled && this.renderBounds.contains(screenX, screenY)) { + return this; + } + return super.touchDown(screenX, screenY, button); + } + + @Override + public UIFrame getFrameChildUnderMouse(final float screenX, final float screenY) { + if (isVisible() && this.enabled && this.renderBounds.contains(screenX, screenY)) { + final UIFrame childResult = this.buttonArt.getFrameChildUnderMouse(screenX, screenY); + if (childResult != null) { + return childResult; + } + return this; + } + return super.getFrameChildUnderMouse(screenX, screenY); + } + + @Override + public void mouseDown(final GameUI gameUI, final Viewport uiViewport) { + this.buttonArt.mouseDown(gameUI, uiViewport); + } + + @Override + public void mouseUp(final GameUI gameUI, final Viewport uiViewport) { + this.buttonArt.mouseUp(gameUI, uiViewport); + } + + @Override + public void mouseEnter(final GameUI gameUI, final Viewport uiViewport) { + this.headerText.setColor(Color.WHITE); + this.nameText.setColor(Color.WHITE); + } + + @Override + public void mouseExit(final GameUI gameUI, final Viewport uiViewport) { + this.headerText.setColor(this.defaultHeaderColor); + this.nameText.setColor(this.defaultNameColor); + } + + @Override + public void onClick(final int button) { + this.buttonArt.onClick(button); + } + + public void setHeaderText(final StringFrame headerText) { + this.headerText = headerText; + this.defaultHeaderColor = headerText.getColor(); + } + + public void setNameText(final StringFrame nameText) { + this.nameText = nameText; + this.defaultNameColor = nameText.getColor(); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/menu/CampaignMenuData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/menu/CampaignMenuData.java new file mode 100644 index 0000000..f92d18e --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/menu/CampaignMenuData.java @@ -0,0 +1,118 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.ui.menu; + +import java.util.ArrayList; +import java.util.List; + +import com.badlogic.gdx.graphics.Color; +import com.etheller.warsmash.units.Element; + +public class CampaignMenuData { + private final String header; + private final String name; + private final boolean defaultOpen; + private final String background; + private final int backgroundFogStyle; + private final Color backgroundFogColor; + private final float backgroundFogDensity; + private final float backgroundFogStart; + private final float backgroundFogEnd; + private final int cursor; + private final String ambientSound; + private final CampaignMission introCinematic; + private final CampaignMission openCinematic; + private final CampaignMission endCinematic; + private final List missions = new ArrayList<>(); + + public CampaignMenuData(final Element element) { + this.header = element.getField("Header"); + this.name = element.getField("Name"); + this.defaultOpen = element.getFieldValue("DefaultOpen") == 1; + this.background = element.getField("Background"); + this.backgroundFogStyle = element.getFieldValue("BackgroundFogStyle"); + this.backgroundFogColor = new Color(element.getFieldFloatValue("BackgroundFogColor", 1) / 255f, + element.getFieldFloatValue("BackgroundFogColor", 2) / 255f, + element.getFieldFloatValue("BackgroundFogColor", 3) / 255f, + element.getFieldFloatValue("BackgroundFogColor", 0) / 255f); + this.backgroundFogDensity = element.getFieldFloatValue("BackgroundFogDensity"); + this.backgroundFogStart = element.getFieldFloatValue("BackgroundFogStart"); + this.backgroundFogEnd = element.getFieldFloatValue("BackgroundFogEnd"); + this.cursor = element.getFieldValue("Cursor"); + this.ambientSound = element.getField("AmbientSound"); + this.introCinematic = readMission(element, "IntroCinematic"); + this.openCinematic = readMission(element, "OpenCinematic"); + this.endCinematic = readMission(element, "EndCinematic"); + int missionIndex = 0; + CampaignMission currentMission; + while ((currentMission = readMission(element, "Mission" + missionIndex)) != null) { + this.missions.add(currentMission); + missionIndex++; + } + } + + public String getHeader() { + return this.header; + } + + public String getName() { + return this.name; + } + + public boolean isDefaultOpen() { + return this.defaultOpen; + } + + public String getBackground() { + return this.background; + } + + public int getBackgroundFogStyle() { + return this.backgroundFogStyle; + } + + public Color getBackgroundFogColor() { + return this.backgroundFogColor; + } + + public float getBackgroundFogDensity() { + return this.backgroundFogDensity; + } + + public float getBackgroundFogStart() { + return this.backgroundFogStart; + } + + public float getBackgroundFogEnd() { + return this.backgroundFogEnd; + } + + public int getCursor() { + return this.cursor; + } + + public String getAmbientSound() { + return this.ambientSound; + } + + public CampaignMission getIntroCinematic() { + return this.introCinematic; + } + + public CampaignMission getOpenCinematic() { + return this.openCinematic; + } + + public CampaignMission getEndCinematic() { + return this.endCinematic; + } + + public List getMissions() { + return this.missions; + } + + private static CampaignMission readMission(final Element element, final String field) { + if ("".equals(element.getField(field, 0))) { + return null; + } + return new CampaignMission(element.getField(field, 0), element.getField(field, 1), element.getField(field, 2)); + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/menu/CampaignMenuUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/menu/CampaignMenuUI.java new file mode 100644 index 0000000..335e241 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/menu/CampaignMenuUI.java @@ -0,0 +1,84 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.ui.menu; + +import java.util.ArrayList; +import java.util.List; + +import com.badlogic.gdx.utils.viewport.Viewport; +import com.etheller.warsmash.parsers.fdf.GameUI; +import com.etheller.warsmash.parsers.fdf.datamodel.FramePoint; +import com.etheller.warsmash.parsers.fdf.frames.SetPoint; +import com.etheller.warsmash.parsers.fdf.frames.SimpleFrame; +import com.etheller.warsmash.parsers.fdf.frames.StringFrame; +import com.etheller.warsmash.parsers.fdf.frames.TextButtonFrame; +import com.etheller.warsmash.parsers.fdf.frames.UIFrame; + +public class CampaignMenuUI extends SimpleFrame { + private final GameUI rootFrame; + private final Viewport uiViewport; + private final List buttonUIs = new ArrayList<>(); + + public CampaignMenuUI(final String name, final UIFrame parent, final GameUI rootFrame, final Viewport uiViewport) { + super(name, parent); + this.rootFrame = rootFrame; + this.uiViewport = uiViewport; + } + + public void addButton(final String header, final String name, final Runnable onClick) { + final CampaignButtonUI campaignButtonUI = new CampaignButtonUI(null, this); + final TextButtonFrame campaignArrowButton = (TextButtonFrame) this.rootFrame + .createFrame("CampaignArrowButtonTemplate", campaignButtonUI, 0, 0); + campaignButtonUI.setButtonArt(campaignArrowButton); + campaignButtonUI.add(campaignArrowButton); + campaignArrowButton.addSetPoint(new SetPoint(FramePoint.TOPLEFT, campaignButtonUI, FramePoint.TOPLEFT, 0, 0)); + campaignArrowButton.setOnClick(onClick); + + final StringFrame headerText = (StringFrame) this.rootFrame.createFrame("StandardSmallTextTemplate", + campaignButtonUI, 0, 0); + this.rootFrame.setText(headerText, header); + campaignButtonUI.add(headerText); + final StringFrame nameText = (StringFrame) this.rootFrame.createFrame("StandardValueTextTemplate", + campaignButtonUI, 0, 0); + this.rootFrame.setText(nameText, name); + headerText.addSetPoint(new SetPoint(FramePoint.TOPLEFT, campaignArrowButton, FramePoint.TOPRIGHT, 0, 0)); + nameText.addSetPoint(new SetPoint(FramePoint.TOPLEFT, headerText, FramePoint.BOTTOMLEFT, 0, 0)); + campaignButtonUI.add(nameText); + campaignButtonUI.setHeaderText(headerText); + campaignButtonUI.setNameText(nameText); + campaignButtonUI.setHeight(GameUI.convertY(this.uiViewport, 0.032f)); + + add(campaignButtonUI); + this.buttonUIs.add(campaignButtonUI); + + } + + @Override + protected void innerPositionBounds(final GameUI gameUI, final Viewport viewport) { + super.innerPositionBounds(gameUI, viewport); + final float myHeight = this.renderBounds.height; + final int buttonCount = this.buttonUIs.size(); + System.out.println("myheight / count is " + myHeight + "/" + buttonCount); + final float buttonSpacing = Math.min(myHeight / buttonCount, GameUI.convertY(this.uiViewport, 0.056f)); + final float buttonsHeight = buttonSpacing * buttonCount; + final float expectedHeight = Math.min(buttonsHeight, myHeight); + final float yOffset = (myHeight - expectedHeight) / 2; + + UIFrame lastButton = null; + for (final CampaignButtonUI campaignButtonUI : this.buttonUIs) { + if (lastButton == null) { + System.out.println("offsetting from this " + getName() + " by " + yOffset); + campaignButtonUI.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this, FramePoint.TOPLEFT, 0, -yOffset)); + campaignButtonUI.addSetPoint(new SetPoint(FramePoint.TOPRIGHT, this, FramePoint.TOPRIGHT, 0, -yOffset)); + } + else { + System.out.println("offsetting from bs " + lastButton.getName() + " by " + buttonSpacing); + campaignButtonUI.addSetPoint( + new SetPoint(FramePoint.TOPLEFT, lastButton, FramePoint.TOPLEFT, 0, -buttonSpacing)); + campaignButtonUI.addSetPoint( + new SetPoint(FramePoint.TOPRIGHT, lastButton, FramePoint.TOPRIGHT, 0, -buttonSpacing)); + } + lastButton = campaignButtonUI; + } + super.innerPositionBounds(gameUI, viewport); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/menu/CampaignMission.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/menu/CampaignMission.java new file mode 100644 index 0000000..d69f149 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/menu/CampaignMission.java @@ -0,0 +1,25 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.ui.menu; + +public class CampaignMission { + private final String header; + private final String missionName; + private final String mapFilename; + + public CampaignMission(final String header, final String missionName, final String mapFilename) { + this.header = header; + this.missionName = missionName; + this.mapFilename = mapFilename; + } + + public String getHeader() { + return this.header; + } + + public String getMissionName() { + return this.missionName; + } + + public String getMapFilename() { + return this.mapFilename; + } +} diff --git a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java index 98f99d7..01b5234 100644 --- a/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java +++ b/desktop/src/com/etheller/warsmash/desktop/DesktopLauncher.java @@ -21,7 +21,6 @@ import com.badlogic.gdx.audio.Sound; import com.badlogic.gdx.backends.lwjgl.LwjglApplication; import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration; import com.badlogic.gdx.backends.lwjgl.LwjglNativesLoader; -import com.etheller.warsmash.WarsmashGdxMapScreen; import com.etheller.warsmash.WarsmashGdxMenuScreen; import com.etheller.warsmash.WarsmashGdxMultiScreenGame; import com.etheller.warsmash.audio.OpenALSound; @@ -68,12 +67,11 @@ public class DesktopLauncher { Gdx.app.postRunnable(new Runnable() { @Override public void run() { + final WarsmashGdxMenuScreen menuScreen = new WarsmashGdxMenuScreen(warsmashIni, + warsmashGdxMultiScreenGame); + warsmashGdxMultiScreenGame.setScreen(menuScreen); if (finalFileToLoad != null) { - warsmashGdxMultiScreenGame.setScreen(new WarsmashGdxMapScreen(warsmashIni, finalFileToLoad)); - } - else { - warsmashGdxMultiScreenGame - .setScreen(new WarsmashGdxMenuScreen(warsmashIni, warsmashGdxMultiScreenGame)); + menuScreen.startMap(finalFileToLoad); } } }); diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FrameDefinition.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FrameDefinition.java index dc0b1cf..14ecf1d 100644 --- a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FrameDefinition.java +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FrameDefinition.java @@ -8,9 +8,11 @@ import java.util.Map; import java.util.Set; import com.etheller.warsmash.parsers.fdf.datamodel.fields.FrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.StringPairFrameDefinitionField; import com.etheller.warsmash.parsers.fdf.datamodel.fields.visitor.GetFloatFieldVisitor; import com.etheller.warsmash.parsers.fdf.datamodel.fields.visitor.GetFontFieldVisitor; import com.etheller.warsmash.parsers.fdf.datamodel.fields.visitor.GetStringFieldVisitor; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.visitor.GetStringPairFieldVisitor; import com.etheller.warsmash.parsers.fdf.datamodel.fields.visitor.GetTextJustifyFieldVisitor; import com.etheller.warsmash.parsers.fdf.datamodel.fields.visitor.GetVector2FieldVisitor; import com.etheller.warsmash.parsers.fdf.datamodel.fields.visitor.GetVector4FieldVisitor; @@ -111,6 +113,14 @@ public class FrameDefinition { return null; } + public StringPairFrameDefinitionField getStringPair(final String id) { + final FrameDefinitionField frameDefinitionField = this.nameToField.get(id); + if (frameDefinitionField != null) { + return frameDefinitionField.visit(GetStringPairFieldVisitor.INSTANCE); + } + return null; + } + public Float getFloat(final String id) { final FrameDefinitionField frameDefinitionField = this.nameToField.get(id); if (frameDefinitionField != null) { diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/visitor/GetStringPairFieldVisitor.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/visitor/GetStringPairFieldVisitor.java new file mode 100644 index 0000000..0b9f34c --- /dev/null +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/fields/visitor/GetStringPairFieldVisitor.java @@ -0,0 +1,56 @@ +package com.etheller.warsmash.parsers.fdf.datamodel.fields.visitor; + +import com.etheller.warsmash.parsers.fdf.datamodel.fields.FloatFrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.FontFrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.FrameDefinitionFieldVisitor; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.StringFrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.StringPairFrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.TextJustifyFrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.Vector2FrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.Vector3FrameDefinitionField; +import com.etheller.warsmash.parsers.fdf.datamodel.fields.Vector4FrameDefinitionField; + +public class GetStringPairFieldVisitor implements FrameDefinitionFieldVisitor { + public static GetStringPairFieldVisitor INSTANCE = new GetStringPairFieldVisitor(); + + @Override + public StringPairFrameDefinitionField accept(final StringFrameDefinitionField field) { + return null; + } + + @Override + public StringPairFrameDefinitionField accept(final StringPairFrameDefinitionField field) { + return field; + } + + @Override + public StringPairFrameDefinitionField accept(final FloatFrameDefinitionField field) { + return null; + } + + @Override + public StringPairFrameDefinitionField accept(final Vector3FrameDefinitionField field) { + return null; + } + + @Override + public StringPairFrameDefinitionField accept(final Vector4FrameDefinitionField field) { + return null; + } + + @Override + public StringPairFrameDefinitionField accept(final Vector2FrameDefinitionField field) { + return null; + } + + @Override + public StringPairFrameDefinitionField accept(final FontFrameDefinitionField field) { + return null; + } + + @Override + public StringPairFrameDefinitionField accept(final TextJustifyFrameDefinitionField field) { + return null; + } + +} From f1bcb4ae54f0ff4a8d5e4bd5892b6a6622441b15 Mon Sep 17 00:00:00 2001 From: Retera Date: Sun, 4 Apr 2021 10:28:40 -0400 Subject: [PATCH 116/116] Update campaign menu behaviors and make some progress on untested common j apis --- .../warsmash/WarsmashGdxMapScreen.java | 2 +- .../warsmash/WarsmashGdxMenuScreen.java | 2 +- .../etheller/warsmash/parsers/fdf/GameUI.java | 29 +- .../fdf/frames/AbstractRenderableFrame.java | 2 +- .../parsers/fdf/frames/GlueButtonFrame.java | 12 + .../fdf/frames/GlueTextButtonFrame.java | 42 + .../parsers/fdf/frames/SingleStringFrame.java | 4 + .../parsers/fdf/frames/StringFrame.java | 27 +- .../etheller/warsmash/parsers/jass/Jass2.java | 978 +++++++++++++++++- .../scope/CommonTriggerExecutionScope.java | 64 ++ .../com/etheller/warsmash/util/Quadtree.java | 3 +- .../warsmash/util/WarsmashConstants.java | 2 +- .../viewer5/handlers/mdx/BatchGroup.java | 2 +- .../viewer5/handlers/mdx/SdSequence.java | 36 +- .../viewer5/handlers/w3x/War3MapViewer.java | 50 +- .../handlers/w3x/simulation/CSimulation.java | 7 + .../handlers/w3x/simulation/CUnit.java | 136 ++- .../w3x/simulation/CWorldCollision.java | 3 + .../simulation/behaviors/CBehaviorMove.java | 2 +- .../behaviors/build/CBehaviorUndeadBuild.java | 2 +- .../w3x/simulation/players/CPlayerJass.java | 2 + .../w3x/simulation/region/CRegion.java | 125 +++ .../region/CRegionEnumFunction.java | 11 + .../w3x/simulation/region/CRegionManager.java | 189 ++++ .../viewer5/handlers/w3x/ui/MeleeUI.java | 23 +- .../viewer5/handlers/w3x/ui/MenuUI.java | 21 +- .../handlers/w3x/ui/menu/CampaignMenuUI.java | 3 - 27 files changed, 1710 insertions(+), 69 deletions(-) create mode 100644 core/src/com/etheller/warsmash/parsers/jass/scope/CommonTriggerExecutionScope.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/region/CRegion.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/region/CRegionEnumFunction.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/region/CRegionManager.java diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMapScreen.java b/core/src/com/etheller/warsmash/WarsmashGdxMapScreen.java index 9f4c9b1..bc3601c 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMapScreen.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMapScreen.java @@ -53,7 +53,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.ui.MeleeUI; public class WarsmashGdxMapScreen implements InputProcessor, Screen { public static final boolean ENABLE_AUDIO = true; - private static final boolean ENABLE_MUSIC = true; + private static final boolean ENABLE_MUSIC = false; private final War3MapViewer viewer; private final Rectangle tempRect = new Rectangle(); diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMenuScreen.java b/core/src/com/etheller/warsmash/WarsmashGdxMenuScreen.java index 2faf476..84d7674 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMenuScreen.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMenuScreen.java @@ -52,7 +52,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.ui.MenuUI; public class WarsmashGdxMenuScreen implements InputProcessor, Screen, SingleModelScreen { private static final boolean ENABLE_AUDIO = true; - private static final boolean ENABLE_MUSIC = true; + private static final boolean ENABLE_MUSIC = false; private DataSource codebase; private MdxViewer viewer; private MdxModel model; diff --git a/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java index 34fa256..dd5fd4c 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java @@ -294,7 +294,8 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { this.fontParam.size = 128; } final BitmapFont frameFont = this.fontGenerator.generateFont(this.fontParam); - final StringFrame stringFrame = new StringFrame(name, parent, color, justifyH, justifyV, frameFont, name); + final StringFrame stringFrame = new StringFrame(name, parent, color, justifyH, justifyV, frameFont, name, null, + null); this.nameToFrame.put(name, stringFrame); add(stringFrame); return stringFrame; @@ -400,6 +401,28 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { fontColorDefinition.getZ(), fontColorDefinition.getW()); } + Color fontHighlightColor; + final Vector4Definition fontHighlightColorDefinition = frameDefinition.getVector4("FontHighlightColor"); + if (fontHighlightColorDefinition == null) { + fontHighlightColor = null; + } + else { + fontHighlightColor = new Color(fontHighlightColorDefinition.getX(), + fontHighlightColorDefinition.getY(), fontHighlightColorDefinition.getZ(), + fontHighlightColorDefinition.getW()); + } + + Color fontDisabledColor; + final Vector4Definition fontDisabledColorDefinition = frameDefinition.getVector4("FontDisabledColor"); + if (fontDisabledColorDefinition == null) { + fontDisabledColor = null; + } + else { + fontDisabledColor = new Color(fontDisabledColorDefinition.getX(), + fontDisabledColorDefinition.getY(), fontDisabledColorDefinition.getZ(), + fontDisabledColorDefinition.getW()); + } + Color fontShadowColor; final Vector4Definition fontShadowColorDefinition = frameDefinition.getVector4("FontShadowColor"); if (fontShadowColorDefinition == null) { @@ -428,7 +451,7 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { textString = text; } final StringFrame stringFrame = new StringFrame(frameDefinition.getName(), parent, fontColor, justifyH, - justifyV, frameFont, textString); + justifyV, frameFont, textString, fontHighlightColor, fontDisabledColor); if (fontShadowColor != null) { final Vector2Definition shadowOffset = frameDefinition.getVector2("FontShadowOffset"); stringFrame.setFontShadowColor(fontShadowColor); @@ -850,7 +873,7 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { textString = text; } final StringFrame stringFrame = new StringFrame(frameDefinition.getName(), parent, fontColor, justifyH, - justifyV, frameFont, textString); + justifyV, frameFont, textString, null, null); inflatedFrame = stringFrame; break; case Texture: diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java index 1ca76a6..418840f 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java @@ -23,7 +23,7 @@ public abstract class AbstractRenderableFrame implements UIFrame { private static final FramePoint[] TOP_ANCHOR_PRIORITY = { FramePoint.TOP, FramePoint.TOPLEFT, FramePoint.TOPRIGHT }; private static final FramePoint[] BOTTOM_ANCHOR_PRIORITY = { FramePoint.BOTTOM, FramePoint.BOTTOMLEFT, FramePoint.BOTTOMRIGHT }; - private static final boolean DEBUG_LOG = true; + private static final boolean DEBUG_LOG = false; protected String name; protected UIFrame parent; protected boolean visible = true; diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/GlueButtonFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/GlueButtonFrame.java index de20d4a..f71be58 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/GlueButtonFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/GlueButtonFrame.java @@ -54,6 +54,10 @@ public class GlueButtonFrame extends AbstractRenderableFrame implements Clickabl } } + public boolean isEnabled() { + return this.enabled; + } + public void setHighlightOnMouseOver(final boolean highlightOnMouseOver) { this.highlightOnMouseOver = highlightOnMouseOver; } @@ -112,12 +116,20 @@ public class GlueButtonFrame extends AbstractRenderableFrame implements Clickabl public void mouseEnter(final GameUI gameUI, final Viewport uiViewport) { if (this.highlightOnMouseOver) { this.mouseOver = true; + onMouseEnter(); } } + protected void onMouseEnter() { + } + @Override public void mouseExit(final GameUI gameUI, final Viewport uiViewport) { this.mouseOver = false; + onMouseExit(); + } + + protected void onMouseExit() { } @Override diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/GlueTextButtonFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/GlueTextButtonFrame.java index a64db3c..fc0ba43 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/GlueTextButtonFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/GlueTextButtonFrame.java @@ -1,5 +1,6 @@ package com.etheller.warsmash.parsers.fdf.frames; +import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.GlyphLayout; import com.badlogic.gdx.graphics.g2d.SpriteBatch; @@ -32,4 +33,45 @@ public class GlueTextButtonFrame extends GlueButtonFrame { this.buttonText.render(batch, baseFont, glyphLayout); } } + + @Override + public void setEnabled(final boolean enabled) { + super.setEnabled(enabled); + if (this.buttonText instanceof StringFrame) { + final StringFrame stringButtonText = (StringFrame) this.buttonText; + final Color fontColor = enabled ? stringButtonText.getFontOriginalColor() + : stringButtonText.getFontDisabledColor(); + if (fontColor != null) { + stringButtonText.setColor(fontColor); + } + } + } + + @Override + protected void onMouseEnter() { + super.onMouseEnter(); + if (isEnabled()) { + if (this.buttonText instanceof StringFrame) { + final StringFrame stringFrame = (StringFrame) this.buttonText; + final Color fontHighlightColor = stringFrame.getFontHighlightColor(); + if (fontHighlightColor != null) { + stringFrame.setColor(fontHighlightColor); + } + } + } + } + + @Override + protected void onMouseExit() { + super.onMouseExit(); + if (isEnabled()) { + if (this.buttonText instanceof StringFrame) { + final StringFrame stringFrame = (StringFrame) this.buttonText; + final Color fontOriginalColor = stringFrame.getFontOriginalColor(); + if (fontOriginalColor != null) { + stringFrame.setColor(fontOriginalColor); + } + } + } + } } diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/SingleStringFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/SingleStringFrame.java index 4ea4756..b6968d4 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/SingleStringFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/SingleStringFrame.java @@ -40,6 +40,10 @@ public class SingleStringFrame extends AbstractRenderableFrame { this.color = color; } + public Color getColor() { + return this.color; + } + public void setFontShadowColor(final Color fontShadowColor) { this.fontShadowColor = fontShadowColor; } diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/StringFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/StringFrame.java index ad8e2ed..11cfeb1 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/StringFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/StringFrame.java @@ -30,15 +30,22 @@ public class StringFrame extends AbstractRenderableFrame { private float predictedViewportHeight; static ShapeRenderer shapeRenderer = new ShapeRenderer(); + private final Color fontHighlightColor; + private final Color fontDisabledColor; + private final Color fontColor; public StringFrame(final String name, final UIFrame parent, final Color color, final TextJustify justifyH, - final TextJustify justifyV, final BitmapFont frameFont, final String text) { + final TextJustify justifyV, final BitmapFont frameFont, final String text, final Color fontHighlightColor, + final Color fontDisabledColor) { super(name, parent); + this.fontColor = color; this.color = color; this.justifyH = justifyH; this.justifyV = justifyV; this.frameFont = frameFont; this.text = text; + this.fontHighlightColor = fontHighlightColor; + this.fontDisabledColor = fontDisabledColor; this.internalFramesContainer = new SimpleFrame(null, this); } @@ -55,16 +62,30 @@ public class StringFrame extends AbstractRenderableFrame { } public void setColor(final Color color) { - this.color = color; for (final SingleStringFrame internalFrame : this.internalFrames) { - internalFrame.setColor(color); + if (internalFrame.getColor() == this.color) { + internalFrame.setColor(color); + } } + this.color = color; } public Color getColor() { return this.color; } + public Color getFontOriginalColor() { + return this.fontColor; + } + + public Color getFontDisabledColor() { + return this.fontDisabledColor; + } + + public Color getFontHighlightColor() { + return this.fontHighlightColor; + } + public void setFontShadowColor(final Color fontShadowColor) { this.fontShadowColor = fontShadowColor; for (final SingleStringFrame internalFrame : this.internalFrames) { diff --git a/core/src/com/etheller/warsmash/parsers/jass/Jass2.java b/core/src/com/etheller/warsmash/parsers/jass/Jass2.java index e08320d..6246602 100644 --- a/core/src/com/etheller/warsmash/parsers/jass/Jass2.java +++ b/core/src/com/etheller/warsmash/parsers/jass/Jass2.java @@ -2,6 +2,8 @@ package com.etheller.warsmash.parsers.jass; import java.awt.geom.Point2D; import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Locale; @@ -12,6 +14,8 @@ import org.antlr.v4.runtime.RecognitionException; import org.antlr.v4.runtime.Recognizer; import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.math.Rectangle; +import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.utils.viewport.Viewport; import com.etheller.interpreter.JassLexer; import com.etheller.interpreter.JassParser; @@ -42,6 +46,7 @@ import com.etheller.warsmash.parsers.fdf.datamodel.FramePoint; import com.etheller.warsmash.parsers.fdf.frames.SetPoint; import com.etheller.warsmash.parsers.fdf.frames.StringFrame; import com.etheller.warsmash.parsers.fdf.frames.UIFrame; +import com.etheller.warsmash.parsers.jass.scope.CommonTriggerExecutionScope; import com.etheller.warsmash.parsers.jass.triggers.BoolExprAnd; import com.etheller.warsmash.parsers.jass.triggers.BoolExprCondition; import com.etheller.warsmash.parsers.jass.triggers.BoolExprFilter; @@ -52,12 +57,17 @@ import com.etheller.warsmash.parsers.jass.triggers.TriggerCondition; import com.etheller.warsmash.units.Element; import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.util.WarsmashConstants; import com.etheller.warsmash.viewer5.Scene; import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.ability.ItemUI; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CDestructableType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitEnumFunction; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.ai.AIDifficulty; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.config.CPlayerAPI; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.config.War3MapConfig; @@ -75,6 +85,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayerStat import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CRace; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CRacePreference; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CStartLocPrio; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.region.CRegion; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.state.CGameState; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.state.CUnitState; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.timers.CTimerJass; @@ -557,6 +568,8 @@ public class Jass2 { public CommonEnvironment(final JassProgramVisitor jassProgramVisitor, final DataSource dataSource, final Viewport uiViewport, final Scene uiScene, final War3MapViewer war3MapViewer, final RootFrameListener rootFrameListener) { + final Rectangle tempRect = new Rectangle(); + final CSimulation simulation = war3MapViewer.simulation; final GlobalScope globals = jassProgramVisitor.getGlobals(); final HandleJassType agentType = globals.registerHandleType("agent"); final HandleJassType eventType = globals.registerHandleType("event"); @@ -1022,8 +1035,7 @@ public class Jass2 { public JassValue call(final List arguments, final GlobalScope globalScope, final TriggerExecutionScope triggerScope) { final String idString = arguments.get(0).visit(StringJassValueVisitor.getInstance()); - final CUnitType unitType = war3MapViewer.simulation.getUnitData() - .getUnitTypeByJassLegacyName(idString); + final CUnitType unitType = simulation.getUnitData().getUnitTypeByJassLegacyName(idString); if (unitType == null) { return new IntegerJassValue(0); } @@ -1036,8 +1048,7 @@ public class Jass2 { final TriggerExecutionScope triggerScope) { final Integer id = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); final War3ID war3id = new War3ID(id); - return new StringJassValue( - war3MapViewer.simulation.getUnitData().getUnitType(war3id).getLegacyName()); + return new StringJassValue(simulation.getUnitData().getUnitType(war3id).getLegacyName()); } }); jassProgramVisitor.getJassNativeManager().createNative("AbilityId", new JassFunction() { @@ -1060,7 +1071,6 @@ public class Jass2 { final TriggerExecutionScope triggerScope) { final Integer id = arguments.get(0).visit(IntegerJassValueVisitor.getInstance()); final War3ID war3id = new War3ID(id); - final CSimulation simulation = war3MapViewer.simulation; final CUnitType unitType = simulation.getUnitData().getUnitType(war3id); if (unitType != null) { return new StringJassValue(unitType.getName()); @@ -1328,8 +1338,11 @@ public class Jass2 { final War3MapConfig mapConfig = war3MapViewer.getMapConfig(); registerConfigNatives(jassProgramVisitor, mapConfig, startlocprioType, gametypeType, placementType, gamespeedType, gamedifficultyType, mapdensityType, locationType, playerType, playercolorType, - mapcontrolType, playerslotstateType, war3MapViewer.simulation); + mapcontrolType, playerslotstateType, simulation); + // ============================================================================ + // Timer API + // jassProgramVisitor.getJassNativeManager().createNative("CreateTimer", new JassFunction() { @Override public JassValue call(final List arguments, final GlobalScope globalScope, @@ -1342,7 +1355,7 @@ public class Jass2 { public JassValue call(final List arguments, final GlobalScope globalScope, final TriggerExecutionScope triggerScope) { final CTimerJass timer = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); - war3MapViewer.simulation.unregisterTimer(timer); + simulation.unregisterTimer(timer); return null; } }); @@ -1358,7 +1371,7 @@ public class Jass2 { timer.setTimeoutTime(timeout.floatValue()); timer.setRepeats(periodic); timer.setHandlerFunc(handlerFunc); - timer.start(war3MapViewer.simulation); + timer.start(simulation); } return null; } @@ -1368,7 +1381,7 @@ public class Jass2 { public JassValue call(final List arguments, final GlobalScope globalScope, final TriggerExecutionScope triggerScope) { final CTimerJass timer = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); - return new RealJassValue(timer.getElapsed(war3MapViewer.simulation)); + return new RealJassValue(timer.getElapsed(simulation)); } }); jassProgramVisitor.getJassNativeManager().createNative("TimerGetRemaining", new JassFunction() { @@ -1376,7 +1389,7 @@ public class Jass2 { public JassValue call(final List arguments, final GlobalScope globalScope, final TriggerExecutionScope triggerScope) { final CTimerJass timer = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); - return new RealJassValue(timer.getRemaining(war3MapViewer.simulation)); + return new RealJassValue(timer.getRemaining(simulation)); } }); jassProgramVisitor.getJassNativeManager().createNative("TimerGetTimeout", new JassFunction() { @@ -1392,7 +1405,7 @@ public class Jass2 { public JassValue call(final List arguments, final GlobalScope globalScope, final TriggerExecutionScope triggerScope) { final CTimerJass timer = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); - timer.pause(war3MapViewer.simulation); + timer.pause(simulation); return null; } }); @@ -1401,10 +1414,951 @@ public class Jass2 { public JassValue call(final List arguments, final GlobalScope globalScope, final TriggerExecutionScope triggerScope) { final CTimerJass timer = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); - timer.resume(war3MapViewer.simulation); + timer.resume(simulation); return null; } }); + + // ============================================================================ + // Group API + // + jassProgramVisitor.getJassNativeManager().createNative("CreateGroup", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + return new HandleJassValue(groupType, new ArrayList()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("DestroyGroup", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final List group = arguments.get(0).visit(ObjectJassValueVisitor.>getInstance()); + System.err.println( + "DestroyGroup called but in Java we don't have a destructor, so we need to unregister later when that is implemented"); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GroupAddUnit", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final List group = arguments.get(0).visit(ObjectJassValueVisitor.>getInstance()); + final CUnit whichUnit = arguments.get(1).visit(ObjectJassValueVisitor.getInstance()); + group.add(whichUnit); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GroupRemoveUnit", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final List group = arguments.get(0).visit(ObjectJassValueVisitor.>getInstance()); + final CUnit whichUnit = arguments.get(1).visit(ObjectJassValueVisitor.getInstance()); + group.remove(whichUnit); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GroupClear", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final List group = arguments.get(0).visit(ObjectJassValueVisitor.>getInstance()); + group.clear(); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GroupEnumUnitsOfType", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final List group = arguments.get(0).visit(ObjectJassValueVisitor.>getInstance()); + final String unitname = arguments.get(1).visit(StringJassValueVisitor.getInstance()); + final TriggerBooleanExpression filter = arguments.get(2) + .visit(ObjectJassValueVisitor.getInstance()); + for (final CUnit unit : simulation.getUnits()) { + if (unitname.equals(unit.getUnitType().getLegacyName())) { + if (filter.evaluate(globalScope, + CommonTriggerExecutionScope.filterScope(triggerScope, unit))) { + // TODO the trigger scope for evaluation here might need to be a clean one? + group.add(unit); + } + } + } + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GroupEnumUnitsOfPlayer", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final List group = arguments.get(0).visit(ObjectJassValueVisitor.>getInstance()); + final CPlayerJass player = arguments.get(1) + .visit(ObjectJassValueVisitor.getInstance()); + final TriggerBooleanExpression filter = arguments.get(2) + .visit(ObjectJassValueVisitor.getInstance()); + for (final CUnit unit : simulation.getUnits()) { + if (unit.getPlayerIndex() == player.getId()) { + if (filter.evaluate(globalScope, + CommonTriggerExecutionScope.filterScope(triggerScope, unit))) { + // TODO the trigger scope for evaluation here might need to be a clean one? + group.add(unit); + } + } + } + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GroupEnumUnitsOfTypeCounted", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final List group = arguments.get(0).visit(ObjectJassValueVisitor.>getInstance()); + final String unitname = arguments.get(1).visit(StringJassValueVisitor.getInstance()); + final TriggerBooleanExpression filter = arguments.get(2) + .visit(ObjectJassValueVisitor.getInstance()); + final Integer countLimit = arguments.get(3).visit(IntegerJassValueVisitor.getInstance()); + int count = 0; + for (final CUnit unit : simulation.getUnits()) { + if (unitname.equals(unit.getUnitType().getLegacyName())) { + if (filter.evaluate(globalScope, + CommonTriggerExecutionScope.filterScope(triggerScope, unit))) { + // TODO the trigger scope for evaluation here might need to be a clean one? + group.add(unit); + count++; + if (count >= countLimit) { + break; + } + } + } + } + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GroupEnumUnitsInRect", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final List group = arguments.get(0).visit(ObjectJassValueVisitor.>getInstance()); + final Rectangle rect = arguments.get(1).visit(ObjectJassValueVisitor.getInstance()); + final TriggerBooleanExpression filter = arguments.get(2) + .visit(ObjectJassValueVisitor.getInstance()); + simulation.getWorldCollision().enumUnitsInRect(rect, new CUnitEnumFunction() { + @Override + public boolean call(final CUnit unit) { + if (filter.evaluate(globalScope, + CommonTriggerExecutionScope.filterScope(triggerScope, unit))) { + // TODO the trigger scope for evaluation here might need to be a clean one? + group.add(unit); + } + return false; + } + }); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GroupEnumUnitsInRectCounted", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final List group = arguments.get(0).visit(ObjectJassValueVisitor.>getInstance()); + final Rectangle rect = arguments.get(1).visit(ObjectJassValueVisitor.getInstance()); + final TriggerBooleanExpression filter = arguments.get(2) + .visit(ObjectJassValueVisitor.getInstance()); + final Integer countLimit = arguments.get(3).visit(IntegerJassValueVisitor.getInstance()); + simulation.getWorldCollision().enumUnitsInRect(rect, new CUnitEnumFunction() { + int count = 0; + + @Override + public boolean call(final CUnit unit) { + if (filter.evaluate(globalScope, + CommonTriggerExecutionScope.filterScope(triggerScope, unit))) { + // TODO the trigger scope for evaluation here might need to be a clean one? + group.add(unit); + this.count++; + if (this.count >= countLimit) { + return true; + } + } + return false; + } + }); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GroupEnumUnitsInRange", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final List group = arguments.get(0).visit(ObjectJassValueVisitor.>getInstance()); + final float x = arguments.get(1).visit(RealJassValueVisitor.getInstance()).floatValue(); + final float y = arguments.get(2).visit(RealJassValueVisitor.getInstance()).floatValue(); + final float radius = arguments.get(3).visit(RealJassValueVisitor.getInstance()).floatValue(); + final TriggerBooleanExpression filter = arguments.get(4) + .visit(ObjectJassValueVisitor.getInstance()); + simulation.getWorldCollision().enumUnitsInRect(tempRect.set(x - radius, y - radius, radius, radius), + new CUnitEnumFunction() { + + @Override + public boolean call(final CUnit unit) { + if (unit.distance(x, y) <= radius) { + if (filter.evaluate(globalScope, + CommonTriggerExecutionScope.filterScope(triggerScope, unit))) { + // TODO the trigger scope for evaluation here might need to be a clean one? + group.add(unit); + } + } + return false; + } + }); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GroupEnumUnitsInRangeOfLoc", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final List group = arguments.get(0).visit(ObjectJassValueVisitor.>getInstance()); + final Point2D.Double whichLocation = arguments.get(1) + .visit(ObjectJassValueVisitor.getInstance()); + final float x = (float) whichLocation.x; + final float y = (float) whichLocation.y; + final float radius = arguments.get(2).visit(RealJassValueVisitor.getInstance()).floatValue(); + final TriggerBooleanExpression filter = arguments.get(3) + .visit(ObjectJassValueVisitor.getInstance()); + simulation.getWorldCollision().enumUnitsInRect(tempRect.set(x - radius, y - radius, radius, radius), + new CUnitEnumFunction() { + + @Override + public boolean call(final CUnit unit) { + if (unit.distance(x, y) <= radius) { + if (filter.evaluate(globalScope, + CommonTriggerExecutionScope.filterScope(triggerScope, unit))) { + // TODO the trigger scope for evaluation here might need to be a clean one? + group.add(unit); + } + } + return false; + } + }); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GroupEnumUnitsInRangeCounted", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final List group = arguments.get(0).visit(ObjectJassValueVisitor.>getInstance()); + final float x = arguments.get(1).visit(RealJassValueVisitor.getInstance()).floatValue(); + final float y = arguments.get(2).visit(RealJassValueVisitor.getInstance()).floatValue(); + final float radius = arguments.get(3).visit(RealJassValueVisitor.getInstance()).floatValue(); + final TriggerBooleanExpression filter = arguments.get(4) + .visit(ObjectJassValueVisitor.getInstance()); + final Integer countLimit = arguments.get(5).visit(IntegerJassValueVisitor.getInstance()); + simulation.getWorldCollision().enumUnitsInRect(tempRect.set(x - radius, y - radius, radius, radius), + new CUnitEnumFunction() { + int count = 0; + + @Override + public boolean call(final CUnit unit) { + if (unit.distance(x, y) <= radius) { + if (filter.evaluate(globalScope, + CommonTriggerExecutionScope.filterScope(triggerScope, unit))) { + // TODO the trigger scope for evaluation here might need to be a clean one? + group.add(unit); + this.count++; + if (this.count >= countLimit) { + return true; + } + } + } + return false; + } + }); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GroupEnumUnitsInRangeOfLocCounted", + new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final List group = arguments.get(0) + .visit(ObjectJassValueVisitor.>getInstance()); + final Point2D.Double whichLocation = arguments.get(1) + .visit(ObjectJassValueVisitor.getInstance()); + final float x = (float) whichLocation.x; + final float y = (float) whichLocation.y; + final float radius = arguments.get(2).visit(RealJassValueVisitor.getInstance()) + .floatValue(); + final TriggerBooleanExpression filter = arguments.get(3) + .visit(ObjectJassValueVisitor.getInstance()); + final Integer countLimit = arguments.get(4).visit(IntegerJassValueVisitor.getInstance()); + simulation.getWorldCollision().enumUnitsInRect( + tempRect.set(x - radius, y - radius, radius, radius), new CUnitEnumFunction() { + int count = 0; + + @Override + public boolean call(final CUnit unit) { + if (unit.distance(x, y) <= radius) { + if (filter.evaluate(globalScope, + CommonTriggerExecutionScope.filterScope(triggerScope, unit))) { + // TODO the trigger scope for evaluation here might need to be a + // clean one? + group.add(unit); + this.count++; + if (this.count >= countLimit) { + return true; + } + } + } + return false; + } + }); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GroupEnumUnitsSelected", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final List group = arguments.get(0).visit(ObjectJassValueVisitor.>getInstance()); + final CPlayerJass whyichPlayer = arguments.get(1) + .visit(ObjectJassValueVisitor.getInstance()); + final TriggerBooleanExpression filter = arguments.get(2) + .visit(ObjectJassValueVisitor.getInstance()); + throw new UnsupportedOperationException("GroupEnumUnitsSelected not supported yet."); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GroupImmediateOrder", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final List group = arguments.get(0).visit(ObjectJassValueVisitor.>getInstance()); + final String order = arguments.get(1).visit(StringJassValueVisitor.getInstance()); + final int orderId = OrderIdUtils.getOrderId(order); + boolean success = true; + for (final CUnit unit : group) { + success &= unit.order(simulation, orderId, null); + } + return BooleanJassValue.of(success); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GroupImmediateOrderById", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final List group = arguments.get(0).visit(ObjectJassValueVisitor.>getInstance()); + final int order = arguments.get(1).visit(IntegerJassValueVisitor.getInstance()); + boolean success = true; + for (final CUnit unit : group) { + success &= unit.order(simulation, order, null); + } + return BooleanJassValue.of(success); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GroupPointOrder", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final List group = arguments.get(0).visit(ObjectJassValueVisitor.>getInstance()); + final String order = arguments.get(1).visit(StringJassValueVisitor.getInstance()); + final Double x = arguments.get(2).visit(RealJassValueVisitor.getInstance()); + final Double y = arguments.get(3).visit(RealJassValueVisitor.getInstance()); + final AbilityPointTarget target = new AbilityPointTarget(x.floatValue(), y.floatValue()); + final int orderId = OrderIdUtils.getOrderId(order); + boolean success = true; + for (final CUnit unit : group) { + success &= unit.order(simulation, orderId, target); + } + return BooleanJassValue.of(success); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GroupPointOrderLoc", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final List group = arguments.get(0).visit(ObjectJassValueVisitor.>getInstance()); + final String order = arguments.get(1).visit(StringJassValueVisitor.getInstance()); + final Point2D.Double whichLocation = arguments.get(2) + .visit(ObjectJassValueVisitor.getInstance()); + final AbilityPointTarget target = new AbilityPointTarget((float) whichLocation.x, + (float) whichLocation.y); + final int orderId = OrderIdUtils.getOrderId(order); + boolean success = true; + for (final CUnit unit : group) { + success &= unit.order(simulation, orderId, target); + } + return BooleanJassValue.of(success); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GroupPointOrderById", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final List group = arguments.get(0).visit(ObjectJassValueVisitor.>getInstance()); + final int orderId = arguments.get(1).visit(IntegerJassValueVisitor.getInstance()); + final Double x = arguments.get(2).visit(RealJassValueVisitor.getInstance()); + final Double y = arguments.get(3).visit(RealJassValueVisitor.getInstance()); + final AbilityPointTarget target = new AbilityPointTarget(x.floatValue(), y.floatValue()); + boolean success = true; + for (final CUnit unit : group) { + success &= unit.order(simulation, orderId, target); + } + return BooleanJassValue.of(success); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GroupPointOrderByIdLoc", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final List group = arguments.get(0).visit(ObjectJassValueVisitor.>getInstance()); + final int orderId = arguments.get(1).visit(IntegerJassValueVisitor.getInstance()); + final Point2D.Double whichLocation = arguments.get(2) + .visit(ObjectJassValueVisitor.getInstance()); + final AbilityPointTarget target = new AbilityPointTarget((float) whichLocation.x, + (float) whichLocation.y); + boolean success = true; + for (final CUnit unit : group) { + success &= unit.order(simulation, orderId, target); + } + return BooleanJassValue.of(success); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GroupTargetOrder", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final List group = arguments.get(0).visit(ObjectJassValueVisitor.>getInstance()); + final String order = arguments.get(1).visit(StringJassValueVisitor.getInstance()); + final CWidget target = arguments.get(2).visit(ObjectJassValueVisitor.getInstance()); + final int orderId = OrderIdUtils.getOrderId(order); + boolean success = true; + for (final CUnit unit : group) { + success &= unit.order(simulation, orderId, target); + } + return BooleanJassValue.of(success); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GroupTargetOrderById", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final List group = arguments.get(0).visit(ObjectJassValueVisitor.>getInstance()); + final int orderId = arguments.get(1).visit(IntegerJassValueVisitor.getInstance()); + final CWidget target = arguments.get(2).visit(ObjectJassValueVisitor.getInstance()); + boolean success = true; + for (final CUnit unit : group) { + success &= unit.order(simulation, orderId, target); + } + return BooleanJassValue.of(success); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ForGroup", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final List group = arguments.get(0).visit(ObjectJassValueVisitor.>getInstance()); + final JassFunction callback = arguments.get(1).visit(JassFunctionJassValueVisitor.getInstance()); + for (final CUnit unit : group) { + callback.call(Collections.emptyList(), globalScope, + CommonTriggerExecutionScope.enumScope(triggerScope, unit)); + } + return new HandleJassValue(unitType, group.get(0)); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("FirstOfGroup", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final List group = arguments.get(0).visit(ObjectJassValueVisitor.>getInstance()); + return new HandleJassValue(unitType, group.get(0)); + } + }); + // ============================================================================ + // Force API + // + jassProgramVisitor.getJassNativeManager().createNative("CreateForce", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + return new HandleJassValue(forceType, new ArrayList()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("DestroyForce", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final List force = arguments.get(0) + .visit(ObjectJassValueVisitor.>getInstance()); + System.err.println( + "DestroyForce called but in Java we don't have a destructor, so we need to unregister later when that is implemented"); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ForceAddPlayer", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final List force = arguments.get(0) + .visit(ObjectJassValueVisitor.>getInstance()); + final CPlayerJass player = arguments.get(1) + .visit(ObjectJassValueVisitor.getInstance()); + force.add(player); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ForceRemovePlayer", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final List force = arguments.get(0) + .visit(ObjectJassValueVisitor.>getInstance()); + final CPlayerJass player = arguments.get(1) + .visit(ObjectJassValueVisitor.getInstance()); + force.remove(player); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ForceClear", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final List force = arguments.get(0) + .visit(ObjectJassValueVisitor.>getInstance()); + force.clear(); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ForceEnumPlayers", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final List force = arguments.get(0) + .visit(ObjectJassValueVisitor.>getInstance()); + final TriggerBooleanExpression filter = arguments.get(1) + .visit(ObjectJassValueVisitor.getInstance()); + for (int i = 0; i < WarsmashConstants.MAX_PLAYERS; i++) { + final CPlayerJass jassPlayer = simulation.getPlayer(i); + if (filter.evaluate(globalScope, + CommonTriggerExecutionScope.filterScope(triggerScope, jassPlayer))) { + force.add(jassPlayer); + } + } + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ForceEnumPlayersCounted", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final List force = arguments.get(0) + .visit(ObjectJassValueVisitor.>getInstance()); + final TriggerBooleanExpression filter = arguments.get(1) + .visit(ObjectJassValueVisitor.getInstance()); + final Integer countLimit = arguments.get(2).visit(IntegerJassValueVisitor.getInstance()); + int count = 0; + for (int i = 0; (i < WarsmashConstants.MAX_PLAYERS) && (count < countLimit); i++) { + final CPlayerJass jassPlayer = simulation.getPlayer(i); + if (filter.evaluate(globalScope, + CommonTriggerExecutionScope.filterScope(triggerScope, jassPlayer))) { + force.add(jassPlayer); + count++; + } + } + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ForceEnumAllies", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final List force = arguments.get(0) + .visit(ObjectJassValueVisitor.>getInstance()); + final CPlayerJass player = arguments.get(1) + .visit(ObjectJassValueVisitor.getInstance()); + final TriggerBooleanExpression filter = arguments.get(2) + .visit(ObjectJassValueVisitor.getInstance()); + for (int i = 0; i < WarsmashConstants.MAX_PLAYERS; i++) { + final CPlayerJass jassPlayer = simulation.getPlayer(i); + if (player.hasAlliance(i, CAllianceType.PASSIVE)) { + if (filter.evaluate(globalScope, + CommonTriggerExecutionScope.filterScope(triggerScope, jassPlayer))) { + force.add(jassPlayer); + } + } + } + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ForceEnumEnemies", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final List force = arguments.get(0) + .visit(ObjectJassValueVisitor.>getInstance()); + final CPlayerJass player = arguments.get(1) + .visit(ObjectJassValueVisitor.getInstance()); + final TriggerBooleanExpression filter = arguments.get(2) + .visit(ObjectJassValueVisitor.getInstance()); + for (int i = 0; i < WarsmashConstants.MAX_PLAYERS; i++) { + final CPlayerJass jassPlayer = simulation.getPlayer(i); + if (!player.hasAlliance(i, CAllianceType.PASSIVE)) { + if (filter.evaluate(globalScope, + CommonTriggerExecutionScope.filterScope(triggerScope, jassPlayer))) { + force.add(jassPlayer); + } + } + } + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("ForForce", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final List force = arguments.get(0) + .visit(ObjectJassValueVisitor.>getInstance()); + final JassFunction callback = arguments.get(1).visit(JassFunctionJassValueVisitor.getInstance()); + for (final CPlayerJass player : force) { + callback.call(Collections.emptyList(), globalScope, + CommonTriggerExecutionScope.enumScope(triggerScope, player)); + } + return null; + } + }); + // ============================================================================ + // Region and Location API + // + jassProgramVisitor.getJassNativeManager().createNative("Rect", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final float minx = arguments.get(0).visit(RealJassValueVisitor.getInstance()).floatValue(); + final float miny = arguments.get(1).visit(RealJassValueVisitor.getInstance()).floatValue(); + final float maxx = arguments.get(2).visit(RealJassValueVisitor.getInstance()).floatValue(); + final float maxy = arguments.get(3).visit(RealJassValueVisitor.getInstance()).floatValue(); + return new HandleJassValue(rectType, new Rectangle(minx, miny, maxx - minx, maxy - miny)); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("RectFromLoc", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Point2D.Double min = arguments.get(0) + .visit(ObjectJassValueVisitor.getInstance()); + final Point2D.Double max = arguments.get(1) + .visit(ObjectJassValueVisitor.getInstance()); + final float minx = (float) min.x; + final float miny = (float) min.y; + final float maxx = (float) max.x; + final float maxy = (float) max.y; + return new HandleJassValue(rectType, new Rectangle(minx, miny, maxx - minx, maxy - miny)); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("RemoveRect", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Rectangle rect = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + System.err.println( + "RemoveRect called but in Java we don't have a destructor, so we need to unregister later when that is implemented"); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("SetRect", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Rectangle rect = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final float minx = arguments.get(1).visit(RealJassValueVisitor.getInstance()).floatValue(); + final float miny = arguments.get(2).visit(RealJassValueVisitor.getInstance()).floatValue(); + final float maxx = arguments.get(3).visit(RealJassValueVisitor.getInstance()).floatValue(); + final float maxy = arguments.get(4).visit(RealJassValueVisitor.getInstance()).floatValue(); + rect.set(minx, miny, maxx - minx, maxy - miny); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("SetRectFromLoc", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Rectangle rect = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final Point2D.Double min = arguments.get(1) + .visit(ObjectJassValueVisitor.getInstance()); + final Point2D.Double max = arguments.get(2) + .visit(ObjectJassValueVisitor.getInstance()); + final float minx = (float) min.x; + final float miny = (float) min.y; + final float maxx = (float) max.x; + final float maxy = (float) max.y; + rect.set(minx, miny, maxx - minx, maxy - miny); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("MoveRectTo", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Rectangle rect = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final float newCenterX = arguments.get(1).visit(RealJassValueVisitor.getInstance()).floatValue(); + final float newCenterY = arguments.get(2).visit(RealJassValueVisitor.getInstance()).floatValue(); + rect.setCenter(newCenterX, newCenterY); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("MoveRectToLoc", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Rectangle rect = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final Point2D.Double newCenterLoc = arguments.get(1) + .visit(ObjectJassValueVisitor.getInstance()); + rect.setCenter((float) newCenterLoc.x, (float) newCenterLoc.y); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetRectCenterX", new JassFunction() { + Vector2 centerHeap = new Vector2(); + + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Rectangle rect = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + return new RealJassValue(rect.getCenter(this.centerHeap).x); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetRectCenterY", new JassFunction() { + Vector2 centerHeap = new Vector2(); + + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Rectangle rect = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + return new RealJassValue(rect.getCenter(this.centerHeap).y); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetRectMinX", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Rectangle rect = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + return new RealJassValue(rect.getX()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetRectMinY", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Rectangle rect = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + return new RealJassValue(rect.getY()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetRectMaxX", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Rectangle rect = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + return new RealJassValue(rect.getX() + rect.getWidth()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetRectMaxY", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Rectangle rect = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + return new RealJassValue(rect.getY() + rect.getHeight()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("CreateRegion", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + return new HandleJassValue(regionType, new CRegion()); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("RemoveRegion", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CRegion region = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + region.remove(simulation.getRegionManager()); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("RegionAddRect", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CRegion region = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final Rectangle rect = arguments.get(1).visit(ObjectJassValueVisitor.getInstance()); + region.addRect(rect, simulation.getRegionManager()); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("RegionClearRect", new JassFunction() { + + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CRegion region = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final Rectangle rect = arguments.get(1).visit(ObjectJassValueVisitor.getInstance()); + region.clearRect(rect, simulation.getRegionManager()); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("RegionAddCell", new JassFunction() { + + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CRegion region = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final float x = arguments.get(1).visit(RealJassValueVisitor.getInstance()).floatValue(); + final float y = arguments.get(2).visit(RealJassValueVisitor.getInstance()).floatValue(); + region.addCell(x, y, simulation.getRegionManager()); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("RegionAddCellAtLoc", new JassFunction() { + + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CRegion region = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final Point2D.Double whichLocation = arguments.get(1) + .visit(ObjectJassValueVisitor.getInstance()); + region.addCell((float) whichLocation.x, (float) whichLocation.y, simulation.getRegionManager()); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("RegionClearCell", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CRegion region = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final float x = arguments.get(1).visit(RealJassValueVisitor.getInstance()).floatValue(); + final float y = arguments.get(2).visit(RealJassValueVisitor.getInstance()).floatValue(); + region.clearCell(x, y, simulation.getRegionManager()); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("RegionClearCellAtLoc", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CRegion region = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final Point2D.Double whichLocation = arguments.get(1) + .visit(ObjectJassValueVisitor.getInstance()); + region.clearCell((float) whichLocation.x, (float) whichLocation.y, simulation.getRegionManager()); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("Location", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final float x = arguments.get(0).visit(RealJassValueVisitor.getInstance()).floatValue(); + final float y = arguments.get(1).visit(RealJassValueVisitor.getInstance()).floatValue(); + return new HandleJassValue(locationType, new Point2D.Double(x, y)); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("RemoveLocation", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Point2D.Double whichLocation = arguments.get(0) + .visit(ObjectJassValueVisitor.getInstance()); + System.err.println( + "RemoveRect called but in Java we don't have a destructor, so we need to unregister later when that is implemented"); + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("MoveLocation", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Point2D.Double whichLocation = arguments.get(0) + .visit(ObjectJassValueVisitor.getInstance()); + final float x = arguments.get(1).visit(RealJassValueVisitor.getInstance()).floatValue(); + final float y = arguments.get(2).visit(RealJassValueVisitor.getInstance()).floatValue(); + whichLocation.x = x; + whichLocation.y = y; + return null; + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetLocationX", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Point2D.Double whichLocation = arguments.get(0) + .visit(ObjectJassValueVisitor.getInstance()); + return new RealJassValue(whichLocation.x); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetLocationY", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Point2D.Double whichLocation = arguments.get(0) + .visit(ObjectJassValueVisitor.getInstance()); + return new RealJassValue(whichLocation.y); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetLocationZ", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final Point2D.Double whichLocation = arguments.get(0) + .visit(ObjectJassValueVisitor.getInstance()); + return new RealJassValue( + war3MapViewer.terrain.getGroundHeight((float) whichLocation.x, (float) whichLocation.y)); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("IsUnitInRegion", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CRegion whichRegion = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final CUnit whichUnit = arguments.get(1).visit(ObjectJassValueVisitor.getInstance()); + return BooleanJassValue.of(whichUnit.isInRegion(whichRegion)); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("IsPointInRegion", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CRegion whichRegion = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final float x = arguments.get(1).visit(RealJassValueVisitor.getInstance()).floatValue(); + final float y = arguments.get(2).visit(RealJassValueVisitor.getInstance()).floatValue(); + return BooleanJassValue.of(whichRegion.contains(x, y, simulation.getRegionManager())); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("IsLocationInRegion", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CRegion whichRegion = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final Point2D.Double whichLocation = arguments.get(1) + .visit(ObjectJassValueVisitor.getInstance()); + return BooleanJassValue.of(whichRegion.contains((float) whichLocation.x, (float) whichLocation.y, + simulation.getRegionManager())); + } + }); + jassProgramVisitor.getJassNativeManager().createNative("GetWorldBounds", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final float worldMinX = simulation.getPathingGrid().getWorldX(0) - 16f; + final float worldMinY = simulation.getPathingGrid().getWorldY(0) - 16f; + final float worldMaxX = simulation.getPathingGrid() + .getWorldX(simulation.getPathingGrid().getWidth() - 1) + 16f; + final float worldMaxY = simulation.getPathingGrid() + .getWorldY(simulation.getPathingGrid().getHeight() - 1) + 16f; + return new HandleJassValue(rectType, + new Rectangle(worldMinX, worldMinY, worldMaxX - worldMinX, worldMaxY - worldMinY)); + } + }); + } private void registerConfigNatives(final JassProgramVisitor jassProgramVisitor, final War3MapConfig mapConfig, diff --git a/core/src/com/etheller/warsmash/parsers/jass/scope/CommonTriggerExecutionScope.java b/core/src/com/etheller/warsmash/parsers/jass/scope/CommonTriggerExecutionScope.java new file mode 100644 index 0000000..c6d6564 --- /dev/null +++ b/core/src/com/etheller/warsmash/parsers/jass/scope/CommonTriggerExecutionScope.java @@ -0,0 +1,64 @@ +package com.etheller.warsmash.parsers.jass.scope; + +import com.etheller.interpreter.ast.scope.TriggerExecutionScope; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayerJass; + +public class CommonTriggerExecutionScope extends TriggerExecutionScope { + private final CUnit triggeringUnit; + private final CUnit filterUnit; + private final CUnit enumUnit; + private final CPlayerJass filterPlayer; + private final CPlayerJass enumPlayer; + + public CommonTriggerExecutionScope(final TriggerExecutionScope parentScope, final CUnit triggeringUnit, + final CUnit filterUnit, final CUnit enumUnit, final CPlayerJass filterPlayer, + final CPlayerJass enumPlayer) { + super(parentScope.getTriggeringTrigger()); + this.triggeringUnit = triggeringUnit; + this.filterUnit = filterUnit; + this.enumUnit = enumUnit; + this.filterPlayer = filterPlayer; + this.enumPlayer = enumPlayer; + } + + public CUnit getEnumUnit() { + return this.enumUnit; + } + + public CUnit getTriggeringUnit() { + return this.triggeringUnit; + } + + public CUnit getFilterUnit() { + return this.filterUnit; + } + + public CPlayerJass getFilterPlayer() { + return this.filterPlayer; + } + + public CPlayerJass getEnumPlayer() { + return this.enumPlayer; + } + + public static CommonTriggerExecutionScope filterScope(final TriggerExecutionScope parentScope, + final CUnit filterUnit) { + return new CommonTriggerExecutionScope(parentScope, null, filterUnit, null, null, null); + } + + public static CommonTriggerExecutionScope enumScope(final TriggerExecutionScope parentScope, final CUnit enumUnit) { + return new CommonTriggerExecutionScope(parentScope, null, null, enumUnit, null, null); + } + + public static CommonTriggerExecutionScope filterScope(final TriggerExecutionScope parentScope, + final CPlayerJass filterPlayer) { + return new CommonTriggerExecutionScope(parentScope, null, null, null, filterPlayer, null); + } + + public static CommonTriggerExecutionScope enumScope(final TriggerExecutionScope parentScope, + final CPlayerJass enumPlayer) { + return new CommonTriggerExecutionScope(parentScope, null, null, null, null, enumPlayer); + } + +} diff --git a/core/src/com/etheller/warsmash/util/Quadtree.java b/core/src/com/etheller/warsmash/util/Quadtree.java index 6ab8595..81a9308 100644 --- a/core/src/com/etheller/warsmash/util/Quadtree.java +++ b/core/src/com/etheller/warsmash/util/Quadtree.java @@ -24,7 +24,8 @@ public class Quadtree { } public void add(final T object, final Rectangle bounds) { - add(new Node(object, bounds), 0); + final Node node = new Node(object, bounds); + add(node, 0); } public void remove(final T object, final Rectangle bounds) { diff --git a/core/src/com/etheller/warsmash/util/WarsmashConstants.java b/core/src/com/etheller/warsmash/util/WarsmashConstants.java index 694d5b2..6b2e4ab 100644 --- a/core/src/com/etheller/warsmash/util/WarsmashConstants.java +++ b/core/src/com/etheller/warsmash/util/WarsmashConstants.java @@ -6,7 +6,7 @@ public class WarsmashConstants { * With version, we use 0 for RoC, 1 for TFT emulation, and probably 2+ or * whatever for custom mods and other stuff */ - public static int GAME_VERSION = 0; + public static int GAME_VERSION = 1; public static final int REPLACEABLE_TEXTURE_LIMIT = 64; public static final float SIMULATION_STEP_TIME = 1 / 20f; public static final int PORT_NUMBER = 6115; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/BatchGroup.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/BatchGroup.java index 0295e3c..d80a550 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/BatchGroup.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/BatchGroup.java @@ -97,7 +97,7 @@ public class BatchGroup extends GenericGroup { final float[] geosetColor = instance.geosetColors[geosetIndex]; final float layerAlpha = instance.layerAlphas[layerIndex]; - if ((geosetColor[3] > 0) && (layerAlpha > 0)) { + if ((geosetColor[3] > 0.01) && (layerAlpha > 0.01)) { // BELOW: I updated it to "Math.max(0," because MDL and MDX parser for PRSCMOD // menu screen behaved differently, // the MDL case was getting "no data" for default value when unanimated, and "no diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SdSequence.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SdSequence.java index 4708c73..5ec445c 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SdSequence.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/SdSequence.java @@ -167,7 +167,7 @@ public final class SdSequence { } public int getValue(final TYPE out, final long frame) { - final int l = this.frames.length; + final int length = this.frames.length; if (this.constant || (frame < this.start)) { this.sd.copy(out, this.values[0]); @@ -175,34 +175,34 @@ public final class SdSequence { return -1; } else { - int startFrame = -1; - int endFrame = -1; - final int l1 = l - 1; - if ((frame < this.frames[0]) || (frame >= this.frames[l1])) { - startFrame = l1; - endFrame = 0; + int startFrameIndex = -1; + int endFrameIndex = -1; + final int lengthLessOne = length - 1; + if ((frame < this.frames[0]) || (frame >= this.frames[lengthLessOne])) { + startFrameIndex = lengthLessOne; + endFrameIndex = 0; } else { - for (int i = 1; i < l; i++) { + for (int i = 1; i < length; i++) { if (this.frames[i] > frame) { - startFrame = i - 1; - endFrame = i; + startFrameIndex = i - 1; + endFrameIndex = i; break; } } } - long start = this.frames[startFrame]; - final long end = this.frames[endFrame]; - long timeBetweenFrames = end - start; + long startFrame = this.frames[startFrameIndex]; + final long endFrame = this.frames[endFrameIndex]; + long timeBetweenFrames = endFrame - startFrame; if (timeBetweenFrames < 0) { timeBetweenFrames += (this.end - this.start); - if (frame < start) { - start = end; + if (frame < startFrame) { + startFrame = endFrame; } } - final float t = ((timeBetweenFrames) == 0 ? 0 : ((frame - start) / (float) (timeBetweenFrames))); - this.sd.interpolate(out, this.values, this.inTans, this.outTans, startFrame, endFrame, t); - return startFrame; + final float t = ((timeBetweenFrames) == 0 ? 0 : ((frame - startFrame) / (float) (timeBetweenFrames))); + this.sd.interpolate(out, this.values, this.inTans, this.outTans, startFrameIndex, endFrameIndex, t); + return startFrameIndex; } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index 7778f4f..dbd628d 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -939,11 +939,10 @@ public class War3MapViewer extends AbstractMdxModelViewer { final RenderDestructable renderDestructable = new RenderDestructable(this, model, row, doodad, type, maxPitch, maxRoll, doodad.getLife(), destructableShadow, simulationDestructable); if (row.readSLKTagBoolean("walkable")) { + final float angle = doodad.getAngle(); final BoundingBox boundingBox = model.bounds.getBoundingBox(); - final float minX = boundingBox.min.x + x; - final float minY = boundingBox.min.y + y; - final Rectangle renderDestructableBounds = new Rectangle(minX, minY, boundingBox.getWidth(), - boundingBox.getHeight()); + final Rectangle renderDestructableBounds = getRotatedBoundingBox(x, y, angle, boundingBox); + System.out.println("ROTATED BOUNDS TO: " + renderDestructableBounds); this.walkableObjectsTree.add((MdxComplexInstance) renderDestructable.instance, renderDestructableBounds); renderDestructable.walkableBounds = renderDestructableBounds; @@ -1020,6 +1019,39 @@ public class War3MapViewer extends AbstractMdxModelViewer { this.anyReady = true; } + private Rectangle getRotatedBoundingBox(final float x, final float y, final float angle, + final BoundingBox boundingBox) { + final float x1 = boundingBox.min.x; + final float y1 = boundingBox.min.y; + final float x2 = boundingBox.min.x + boundingBox.getWidth(); + final float y2 = boundingBox.min.y; + final float x3 = boundingBox.min.x + boundingBox.getWidth(); + final float y3 = boundingBox.min.y + boundingBox.getHeight(); + final float x4 = boundingBox.min.x; + final float y4 = boundingBox.min.y + boundingBox.getHeight(); + final float angle1 = (float) StrictMath.atan2(y1, x1) + angle; + final float len1 = (float) StrictMath.sqrt((x1 * x1) + (y1 * y1)); + final float angle2 = (float) StrictMath.atan2(y2, x2) + angle; + final float len2 = (float) StrictMath.sqrt((x2 * x2) + (y2 * y2)); + final float angle3 = (float) StrictMath.atan2(y3, x3) + angle; + final float len3 = (float) StrictMath.sqrt((x3 * x3) + (y3 * y3)); + final float angle4 = (float) StrictMath.atan2(y4, x4) + angle; + final float len4 = (float) StrictMath.sqrt((x4 * x4) + (y4 * y4)); + final double x1prime = StrictMath.cos(angle1) * len1; + final double x2prime = StrictMath.cos(angle2) * len2; + final double x3prime = StrictMath.cos(angle3) * len3; + final double x4prime = StrictMath.cos(angle4) * len4; + final double y1prime = StrictMath.sin(angle1) * len1; + final double y2prime = StrictMath.sin(angle2) * len2; + final double y3prime = StrictMath.sin(angle3) * len3; + final double y4prime = StrictMath.sin(angle4) * len4; + final float minX = (float) StrictMath.min(StrictMath.min(x1prime, x2prime), StrictMath.min(x3prime, x4prime)); + final float minY = (float) StrictMath.min(StrictMath.min(y1prime, y2prime), StrictMath.min(y3prime, y4prime)); + final float maxX = (float) StrictMath.max(StrictMath.max(x1prime, x2prime), StrictMath.max(x3prime, x4prime)); + final float maxY = (float) StrictMath.max(StrictMath.max(y1prime, y2prime), StrictMath.max(y3prime, y4prime)); + return new Rectangle(x + minX, y + minY, maxX - minX, maxY - minY); + } + private void applyModificationFile(final MappedData doodadsData2, final MappedData doodadMetaData2, final MutableObjectData destructibles, final WorldEditorDataType dataType) { // TODO condense ported MappedData from Ghostwolf and MutableObjectData from @@ -1732,6 +1764,16 @@ public class War3MapViewer extends AbstractMdxModelViewer { return mdxPath; } + public static String mdl(String mdxPath) { + if (mdxPath.toLowerCase().endsWith(".mdx")) { + mdxPath = mdxPath.substring(0, mdxPath.length() - 4); + } + if (!mdxPath.toLowerCase().endsWith(".mdl")) { + mdxPath += ".mdl"; + } + return mdxPath; + } + public String blp(String iconPath) { final int lastDotIndex = iconPath.lastIndexOf('.'); if (lastDotIndex != -1) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java index 24e23c3..e472ce9 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java @@ -37,6 +37,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.pathing.CPathfindin import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CAllianceType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayer; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CRace; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.region.CRegionManager; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.timers.CTimer; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.ResourceType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.SimulationRenderController; @@ -70,6 +71,7 @@ public class CSimulation implements CPlayerAPI { private final Map handleIdToAbility = new HashMap<>(); private final LinkedList activeTimers = new LinkedList<>(); private transient CommandErrorListener commandErrorListener; + private final CRegionManager regionManager; public CSimulation(final War3MapConfig config, final DataTable miscData, final MutableObjectData parsedUnitData, final MutableObjectData parsedItemData, final MutableObjectData parsedDestructableData, @@ -92,6 +94,7 @@ public class CSimulation implements CPlayerAPI { this.newProjectiles = new ArrayList<>(); this.handleIdAllocator = new HandleIdAllocator(); this.worldCollision = new CWorldCollision(entireMapBounds, this.gameplayConstants.getMaxCollisionRadius()); + this.regionManager = new CRegionManager(entireMapBounds, pathingGrid); this.pathfindingProcessors = new CPathfindingProcessor[WarsmashConstants.MAX_PLAYERS]; this.playerHeroes = new ArrayList[WarsmashConstants.MAX_PLAYERS]; for (int i = 0; i < WarsmashConstants.MAX_PLAYERS; i++) { @@ -293,6 +296,10 @@ public class CSimulation implements CPlayerAPI { return this.worldCollision; } + public CRegionManager getRegionManager() { + return this.regionManager; + } + public CGameplayConstants getGameplayConstants() { return this.gameplayConstants; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java index 442321b..48fd227 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnit.java @@ -4,9 +4,11 @@ import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.EnumSet; import java.util.Iterator; +import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Queue; +import java.util.Set; import com.badlogic.gdx.math.Rectangle; import com.etheller.warsmash.util.War3ID; @@ -37,15 +39,21 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.COrder; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.COrderNoTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.COrderTargetPoint; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.COrderTargetWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CAllianceType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayer; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.region.CRegion; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.region.CRegionEnumFunction; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.region.CRegionManager; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.BooleanAbilityActivationReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.BooleanAbilityTargetCheckReceiver; public class CUnit extends CWidget { + private static RegionCheckerImpl regionCheckerImpl = new RegionCheckerImpl(); + private War3ID typeId; private float facing; // degrees private float mana; @@ -111,6 +119,8 @@ public class CUnit extends CWidget { private int foodUsed; private List unitSpecificAttacks; + private transient Set containingRegions = new LinkedHashSet<>(); + private transient Set priorContainingRegions = new LinkedHashSet<>(); public CUnit(final int handleId, final int playerIndex, final float x, final float y, final float life, final War3ID typeId, final float facing, final float mana, final int maximumLife, final int maximumMana, @@ -474,6 +484,74 @@ public class CUnit extends CWidget { } } + public boolean order(final CSimulation simulation, final int orderId, final AbilityTarget target) { + if (orderId == OrderIds.stop) { + order(simulation, new COrderNoTarget(0, orderId, false), false); + return true; + } + for (final CAbility ability : this.abilities) { + final BooleanAbilityActivationReceiver activationReceiver = BooleanAbilityActivationReceiver.INSTANCE; + ability.checkCanUse(simulation, this, orderId, activationReceiver); + if (activationReceiver.isOk()) { + if (target == null) { + final BooleanAbilityTargetCheckReceiver booleanTargetReceiver = BooleanAbilityTargetCheckReceiver + .getInstance().reset(); + ability.checkCanTargetNoTarget(simulation, this, orderId, booleanTargetReceiver); + if (booleanTargetReceiver.isTargetable()) { + order(simulation, new COrderNoTarget(ability.getHandleId(), orderId, false), false); + return true; + } + } + final boolean targetable = target.visit(new AbilityTargetVisitor() { + @Override + public Boolean accept(final AbilityPointTarget target) { + final BooleanAbilityTargetCheckReceiver booleanTargetReceiver = BooleanAbilityTargetCheckReceiver + .getInstance().reset(); + ability.checkCanTarget(simulation, CUnit.this, orderId, target, booleanTargetReceiver); + final boolean pointTargetable = booleanTargetReceiver.isTargetable(); + if (pointTargetable) { + order(simulation, new COrderTargetPoint(ability.getHandleId(), orderId, target, false), + false); + } + return pointTargetable; + } + + public Boolean acceptWidget(final CWidget target) { + final BooleanAbilityTargetCheckReceiver booleanTargetReceiver = BooleanAbilityTargetCheckReceiver + .getInstance().reset(); + ability.checkCanTarget(simulation, CUnit.this, orderId, target, booleanTargetReceiver); + final boolean widgetTargetable = booleanTargetReceiver.isTargetable(); + if (widgetTargetable) { + order(simulation, + new COrderTargetWidget(ability.getHandleId(), orderId, target.getHandleId(), false), + false); + } + return widgetTargetable; + } + + @Override + public Boolean accept(final CUnit target) { + return acceptWidget(target); + } + + @Override + public Boolean accept(final CDestructable target) { + return acceptWidget(target); + } + + @Override + public Boolean accept(final CItem target) { + return acceptWidget(target); + } + }); + if (targetable) { + return true; + } + } + } + return false; + } + private CBehavior beginOrder(final CSimulation game, final COrder order) { this.lastStartedOrder = order; CBehavior nextBehavior; @@ -550,20 +628,22 @@ public class CUnit extends CWidget { return this.collisionRectangle; } - public void setX(final float newX, final CWorldCollision collision) { + public void setX(final float newX, final CWorldCollision collision, final CRegionManager regionManager) { final float prevX = getX(); if (!this.unitType.isBuilding()) { setX(newX); collision.translate(this, newX - prevX, 0); } + checkRegionEvents(regionManager); } - public void setY(final float newY, final CWorldCollision collision) { + public void setY(final float newY, final CWorldCollision collision, final CRegionManager regionManager) { final float prevY = getY(); if (!this.unitType.isBuilding()) { setY(newY); collision.translate(this, 0, newY - prevY); } + checkRegionEvents(regionManager); } public void setPointAndCheckUnstuck(final float newX, final float newY, final CSimulation game) { @@ -602,11 +682,12 @@ public class CUnit extends CWidget { checkX -= (int) Math.cos(angle); checkY -= (int) Math.sin(angle); } - setPoint(outputX, outputY, collision); + setPoint(outputX, outputY, collision, game.getRegionManager()); game.unitRepositioned(this); } - public void setPoint(final float newX, final float newY, final CWorldCollision collision) { + public void setPoint(final float newX, final float newY, final CWorldCollision collision, + final CRegionManager regionManager) { final float prevX = getX(); final float prevY = getY(); setX(newX); @@ -614,6 +695,29 @@ public class CUnit extends CWidget { if (!this.unitType.isBuilding()) { collision.translate(this, newX - prevX, newY - prevY); } + checkRegionEvents(regionManager); + } + + private void checkRegionEvents(final CRegionManager regionManager) { + final Set temp = this.containingRegions; + this.containingRegions = this.priorContainingRegions; + this.priorContainingRegions = temp; + this.containingRegions.clear(); + regionManager.checkRegions(this.collisionRectangle == null ? tempRect.set(this.getX(), this.getY(), 0, 0) + : this.collisionRectangle, regionCheckerImpl.reset(this)); + for (final CRegion region : this.priorContainingRegions) { + if (!this.containingRegions.contains(region)) { + onLeaveRegion(region); + } + } + } + + private void onEnterRegion(final CRegion region) { + + } + + private void onLeaveRegion(final CRegion region) { + } public EnumSet getClassifications() { @@ -1428,4 +1532,28 @@ public class CUnit extends CWidget { game.unitDropItemEvent(this, droppedItem); } } + + public boolean isInRegion(final CRegion region) { + return this.containingRegions.contains(region); + } + + private static final class RegionCheckerImpl implements CRegionEnumFunction { + private CUnit unit; + + public RegionCheckerImpl reset(final CUnit unit) { + this.unit = unit; + return this; + } + + @Override + public boolean call(final CRegion region) { + if (this.unit.containingRegions.add(region)) { + if (!this.unit.priorContainingRegions.contains(region)) { + this.unit.onEnterRegion(region); + } + } + return false; + } + + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWorldCollision.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWorldCollision.java index fdcffd5..8f6600d 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWorldCollision.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CWorldCollision.java @@ -225,6 +225,9 @@ public class CWorldCollision { return false; } if (this.done) { + // This check is because we may use the intersector for multiple intersect + // calls, see "enumUnitsInRect" and how it uses this intersector first on the + // ground unit layer, then the flying unit layer, without recycling return true; } if (this.intersectedUnits.add(intersectingObject)) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorMove.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorMove.java index 4cc32fa..15099b1 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorMove.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorMove.java @@ -240,7 +240,7 @@ public class CBehaviorMove implements CBehavior { // / 16) // * 16 && !worldCollision.intersectsAnythingOtherThan(tempRect, this.unit, movementType))) { - this.unit.setPoint(nextX, nextY, worldCollision); + this.unit.setPoint(nextX, nextY, worldCollision, simulation.getRegionManager()); if (done) { // if we're making headway along the path then it's OK to start thinking fast // again diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorUndeadBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorUndeadBuild.java index 6fd5b1c..897d2c1 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorUndeadBuild.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorUndeadBuild.java @@ -95,7 +95,7 @@ public class CBehaviorUndeadBuild extends CAbstractRangedBehavior { final float delta = (float) Math.sqrt((deltaX * deltaX) + (deltaY * deltaY)); this.unit.setPoint(this.target.getX() + ((deltaX / delta) * unitTypeToCreate.getCollisionSize()), this.target.getY() + ((deltaY / delta) * unitTypeToCreate.getCollisionSize()), - simulation.getWorldCollision()); + simulation.getWorldCollision(), simulation.getRegionManager()); simulation.unitRepositioned(this.unit); simulation.unitConstructedEvent(this.unit, constructedStructure); this.doneTick = simulation.getGameTurnTick() + delayAnimationTicks; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerJass.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerJass.java index 57cc14d..9a14f0b 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerJass.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerJass.java @@ -15,6 +15,8 @@ public interface CPlayerJass { void setAlliance(int otherPlayerIndex, CAllianceType whichAllianceSetting, boolean value); + boolean hasAlliance(int otherPlayerIndex, CAllianceType allianceType); + void setTaxRate(int otherPlayerIndex, CPlayerState whichResource, int rate); void setRacePref(CRacePreference whichRacePreference); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/region/CRegion.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/region/CRegion.java new file mode 100644 index 0000000..f215fac --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/region/CRegion.java @@ -0,0 +1,125 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.region; + +import com.badlogic.gdx.math.Rectangle; + +public class CRegion { + private Rectangle currentBounds; + private boolean complexRegion; + + public void addRect(final Rectangle rect, final CRegionManager regionManager) { + if (this.currentBounds == null) { + this.currentBounds = new Rectangle(rect); + regionManager.addRectForRegion(this, this.currentBounds); + } + else { + if (!this.complexRegion) { + convertToComplexRegionAndAddRect(rect, regionManager); + } + else { + complexRegionAddRect(rect, regionManager); + } + } + } + + public void clearRect(final Rectangle rect, final CRegionManager regionManager) { + if (this.currentBounds == null) { + return; + } + if (this.complexRegion) { + regionManager.removeRectForRegion(this, this.currentBounds); + regionManager.removeComplexRegionCells(this, rect); + regionManager.computeNewMinimumComplexRegionBounds(this, this.currentBounds); + regionManager.addRectForRegion(this, this.currentBounds); + } + else { + this.complexRegion = true; + regionManager.addComplexRegionCells(this, this.currentBounds); + regionManager.removeComplexRegionCells(this, rect); + } + } + + public void remove(final CRegionManager regionManager) { + if (this.currentBounds == null) { + return; + } + if (this.complexRegion) { + regionManager.removeComplexRegionCells(this, this.currentBounds); + } + regionManager.removeRectForRegion(this, this.currentBounds); + } + + public void addCell(final float x, final float y, final CRegionManager regionManager) { + if (this.currentBounds == null) { + this.complexRegion = true; + this.currentBounds = new Rectangle(x, y, 0, 0); + regionManager.addComplexRegionCell(this, x, y, this.currentBounds); + regionManager.addRectForRegion(this, this.currentBounds); + } + else { + regionManager.removeRectForRegion(this, this.currentBounds); + if (!this.complexRegion) { + regionManager.addComplexRegionCells(this, this.currentBounds); + this.complexRegion = true; + } + regionManager.addComplexRegionCell(this, x, y, this.currentBounds); + regionManager.addRectForRegion(this, this.currentBounds); + } + } + + public void clearCell(final float x, final float y, final CRegionManager regionManager) { + if (this.currentBounds == null) { + return; + } + else { + regionManager.removeRectForRegion(this, this.currentBounds); + if (!this.complexRegion) { + regionManager.addComplexRegionCells(this, this.currentBounds); + this.complexRegion = true; + } + regionManager.clearComplexRegionCell(this, x, y, this.currentBounds); + regionManager.addRectForRegion(this, this.currentBounds); + } + } + + private void complexRegionAddRect(final Rectangle rect, final CRegionManager regionManager) { + regionManager.removeRectForRegion(this, this.currentBounds); + regionManager.addComplexRegionCells(this, rect); + this.currentBounds = this.currentBounds.merge(rect); + regionManager.addRectForRegion(this, this.currentBounds); + } + + private void convertToComplexRegionAndAddRect(final Rectangle rect, final CRegionManager regionManager) { + regionManager.removeRectForRegion(this, this.currentBounds); + this.complexRegion = true; + regionManager.addComplexRegionCells(this, this.currentBounds); + regionManager.addComplexRegionCells(this, rect); + this.currentBounds = this.currentBounds.merge(rect); + regionManager.addRectForRegion(this, this.currentBounds); + } + + public Rectangle getCurrentBounds() { + return this.currentBounds; + } + + public void setCurrentBounds(final Rectangle currentBounds) { + this.currentBounds = currentBounds; + } + + public boolean isComplexRegion() { + return this.complexRegion; + } + + public void setComplexRegion(final boolean complexRegion) { + this.complexRegion = complexRegion; + } + + public boolean contains(final float x, final float y, final CRegionManager regionManager) { + if (this.currentBounds == null) { + return false; + } + if (this.complexRegion) { + return regionManager.isPointInComplexRegion(this, x, y); + } + return this.currentBounds.contains(x, y); + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/region/CRegionEnumFunction.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/region/CRegionEnumFunction.java new file mode 100644 index 0000000..8cf6135 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/region/CRegionEnumFunction.java @@ -0,0 +1,11 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.region; + +public interface CRegionEnumFunction { + /** + * Operates on a region, returning true if we should stop execution. + * + * @param region + * @return + */ + boolean call(CRegion region); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/region/CRegionManager.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/region/CRegionManager.java new file mode 100644 index 0000000..6ed9c65 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/region/CRegionManager.java @@ -0,0 +1,189 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.region; + +import java.util.ArrayList; +import java.util.List; + +import com.badlogic.gdx.math.Rectangle; +import com.etheller.warsmash.util.Quadtree; +import com.etheller.warsmash.util.QuadtreeIntersector; +import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid; + +public class CRegionManager { + private static Rectangle tempRect = new Rectangle(); + private final Quadtree regionTree; + private final RegionChecker regionChecker = new RegionChecker(); + private final List[][] cellRegions; + private final PathingGrid pathingGrid; + + public CRegionManager(final Rectangle entireMapBounds, final PathingGrid pathingGrid) { + this.regionTree = new Quadtree<>(entireMapBounds); + this.cellRegions = new List[pathingGrid.getHeight()][pathingGrid.getWidth()]; + this.pathingGrid = pathingGrid; + } + + public void addRectForRegion(final CRegion region, final Rectangle rect) { + this.regionTree.add(region, rect); + } + + public void removeRectForRegion(final CRegion region, final Rectangle rect) { + this.regionTree.remove(region, rect); + } + + /** + * Calls back on the enum function for every region that touches the given area. + * Sometimes, for performance, this algorithm is designed to call the enum + * function twice for the same region, because our expected use case is to store + * the regions in a set that guarantees uniqueness anyway (see CUnit and/or + * other uses of this method). + */ + public void checkRegions(final Rectangle area, final CRegionEnumFunction enumFunction) { + this.regionTree.intersect(area, this.regionChecker.reset(enumFunction)); + if (this.regionChecker.includesComplex) { + final int minX = this.pathingGrid.getCellX(area.x); + final int minY = this.pathingGrid.getCellY(area.y); + final int maxX = this.pathingGrid.getCellX(area.x + area.width); + final int maxY = this.pathingGrid.getCellY(area.y + area.height); + for (int x = minX; x <= maxX; x++) { + for (int y = minY; y <= maxY; y++) { + final List cellRegionsAtPoint = this.cellRegions[y][x]; + if (cellRegionsAtPoint != null) { + for (final CRegion region : cellRegionsAtPoint) { + if (enumFunction.call(region)) { + return; + } + } + } + } + } + } + } + + private static final class RegionChecker implements QuadtreeIntersector { + private CRegionEnumFunction delegate; + private boolean includesComplex = false; + + public RegionChecker reset(final CRegionEnumFunction delegate) { + this.delegate = delegate; + return this; + } + + @Override + public boolean onIntersect(final CRegion intersectingObject) { + if (intersectingObject.isComplexRegion()) { + this.includesComplex = true; + // handle this type of region differently + return false; + } + return this.delegate.call(intersectingObject); + } + + } + + public void addComplexRegionCells(final CRegion region, final Rectangle currentBounds) { + final int minX = this.pathingGrid.getCellX(currentBounds.x); + final int minY = this.pathingGrid.getCellY(currentBounds.y); + final int maxX = this.pathingGrid.getCellX(currentBounds.x + currentBounds.width); + final int maxY = this.pathingGrid.getCellY(currentBounds.y + currentBounds.height); + for (int x = minX; x <= maxX; x++) { + for (int y = minY; y <= maxY; y++) { + List list = this.cellRegions[y][x]; + if (list == null) { + this.cellRegions[y][x] = list = new ArrayList<>(); + } + list.add(region); + } + } + } + + public void removeComplexRegionCells(final CRegion region, final Rectangle currentBounds) { + final int minX = this.pathingGrid.getCellX(currentBounds.x); + final int minY = this.pathingGrid.getCellY(currentBounds.y); + final int maxX = this.pathingGrid.getCellX(currentBounds.x + currentBounds.width); + final int maxY = this.pathingGrid.getCellY(currentBounds.y + currentBounds.height); + for (int x = minX; x <= maxX; x++) { + for (int y = minY; y <= maxY; y++) { + final List list = this.cellRegions[y][x]; + if (list != null) { + list.remove(region); + } + } + } + } + + public void computeNewMinimumComplexRegionBounds(final CRegion region, final Rectangle complexRegionBounds) { + final int minX = this.pathingGrid.getCellX(complexRegionBounds.x); + final int minY = this.pathingGrid.getCellY(complexRegionBounds.y); + final int maxX = this.pathingGrid.getCellX(complexRegionBounds.x + complexRegionBounds.width); + final int maxY = this.pathingGrid.getCellY(complexRegionBounds.y + complexRegionBounds.height); + float newMinX = this.pathingGrid.getWorldX(this.pathingGrid.getWidth() - 1); + float newMaxX = this.pathingGrid.getWorldX(0); + float newMinY = this.pathingGrid.getWorldY(this.pathingGrid.getHeight() - 1); + float newMaxY = this.pathingGrid.getWorldY(0); + for (int x = minX; x <= maxX; x++) { + for (int y = minY; y <= maxY; y++) { + final List list = this.cellRegions[y][x]; + if (list != null) { + if (list.contains(region)) { + final float worldX = this.pathingGrid.getWorldX(x); + final float worldY = this.pathingGrid.getWorldY(y); + final float wMinX = worldX - 16f; + final float wMinY = worldY - 16f; + final float wMaxX = worldX + 15f; + final float wMaxY = worldY + 15f; + if (wMinX < newMinX) { + newMinX = wMinX; + } + if (wMinY < newMinY) { + newMinY = wMinY; + } + if (wMaxX > newMaxX) { + newMaxX = wMaxX; + } + if (wMaxY > newMaxY) { + newMaxY = wMaxY; + } + } + } + } + } + complexRegionBounds.set(newMinX, newMinY, newMaxX - newMinX, newMaxY - newMinY); + } + + public void addComplexRegionCell(final CRegion region, final float x, final float y, + final Rectangle boundsToUpdate) { + final int cellX = this.pathingGrid.getCellX(x); + final int cellY = this.pathingGrid.getCellY(y); + List list = this.cellRegions[cellY][cellX]; + if (list == null) { + this.cellRegions[cellY][cellX] = list = new ArrayList<>(); + } + list.add(region); + final float worldX = this.pathingGrid.getWorldX(cellX); + final float worldY = this.pathingGrid.getWorldY(cellY); + final float wMinX = worldX - 16f; + final float wMinY = worldY - 16f; + boundsToUpdate.merge(tempRect.set(wMinX, wMinY, 31f, 31f)); + } + + public void clearComplexRegionCell(final CRegion region, final float x, final float y, + final Rectangle boundsToUpdate) { + final int cellX = this.pathingGrid.getCellX(x); + final int cellY = this.pathingGrid.getCellY(y); + final List list = this.cellRegions[cellY][cellX]; + if (list != null) { + list.remove(region); + } + computeNewMinimumComplexRegionBounds(region, boundsToUpdate); + } + + public boolean isPointInComplexRegion(final CRegion region, final float x, final float y) { + final int cellX = this.pathingGrid.getCellX(x); + final int cellY = this.pathingGrid.getCellY(y); + final List list = this.cellRegions[cellY][cellX]; + if (list != null) { + return list.contains(region); + } + return false; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java index bf5aee9..84064df 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java @@ -295,6 +295,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma private SimpleButtonFrame alliesButton; private SimpleButtonFrame chatButton; private final Runnable exitGameRunnable; + private SimpleFrame smashEscMenu; public MeleeUI(final DataSource dataSource, final ExtendViewport uiViewport, final Scene uiScene, final Scene portraitScene, final CameraPreset[] cameraPresets, final CameraRates cameraRates, @@ -448,12 +449,14 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.chatButton = (SimpleButtonFrame) this.rootFrame.getFrameByName("UpperButtonBarChatButton", 0); this.chatButton.setEnabled(false); - final UIFrame escMenuBackdrop = this.rootFrame.createFrame("EscMenuBackdrop", this.rootFrame, 0, 0); + this.smashEscMenu = (SimpleFrame) this.rootFrame.createSimpleFrame("SmashEscMenu", this.rootFrame, 0); + this.smashEscMenu.addAnchor(new AnchorDefinition(FramePoint.TOP, 0, GameUI.convertY(this.uiViewport, -0.05f))); + final UIFrame escMenuBackdrop = this.rootFrame.createFrame("EscMenuBackdrop", this.smashEscMenu, 0, 0); escMenuBackdrop.setVisible(false); - escMenuBackdrop.addAnchor(new AnchorDefinition(FramePoint.TOP, 0, GameUI.convertY(this.uiViewport, -0.05f))); - final UIFrame escMenuMainPanel = this.rootFrame.createFrame("EscMenuMainPanel", this.rootFrame, 0, 0); + final UIFrame escMenuMainPanel = this.rootFrame.createFrame("EscMenuMainPanel", this.smashEscMenu, 0, 0); escMenuMainPanel.setVisible(false); - escMenuMainPanel.addAnchor(new AnchorDefinition(FramePoint.TOP, 0, GameUI.convertY(this.uiViewport, -0.05f))); + this.smashEscMenu.add(escMenuBackdrop); + this.smashEscMenu.add(escMenuMainPanel); final UIFrame escMenuInnerMainPanel = this.rootFrame.getFrameByName("MainPanel", 0); final GlueTextButtonFrame pauseButton = (GlueTextButtonFrame) this.rootFrame.getFrameByName("PauseButton", 0); @@ -504,6 +507,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma public void run() { escMenuBackdrop.setVisible(true); escMenuMainPanel.setVisible(true); + MeleeUI.this.smashEscMenu.setVisible(true); escMenuInnerMainPanel.setVisible(true); updateEscMenuCurrentPanel(escMenuBackdrop, escMenuMainPanel, escMenuInnerMainPanel); } @@ -513,6 +517,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma public void run() { escMenuBackdrop.setVisible(false); escMenuMainPanel.setVisible(false); + MeleeUI.this.smashEscMenu.setVisible(false); escMenuInnerMainPanel.setVisible(false); } }); @@ -924,12 +929,12 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma private void updateEscMenuCurrentPanel(final UIFrame escMenuBackdrop, final UIFrame escMenuMainPanel, final UIFrame escMenuInnerMainPanel) { - escMenuMainPanel.setHeight(escMenuInnerMainPanel.getAssignedHeight()); - escMenuMainPanel.setWidth(escMenuInnerMainPanel.getAssignedWidth()); - escMenuBackdrop.setHeight(escMenuInnerMainPanel.getAssignedHeight()); + this.smashEscMenu.setWidth(escMenuInnerMainPanel.getAssignedWidth()); + this.smashEscMenu.setHeight(escMenuInnerMainPanel.getAssignedHeight()); escMenuBackdrop.setWidth(escMenuInnerMainPanel.getAssignedWidth()); - escMenuMainPanel.positionBounds(MeleeUI.this.rootFrame, MeleeUI.this.uiViewport); - escMenuBackdrop.positionBounds(MeleeUI.this.rootFrame, MeleeUI.this.uiViewport); + escMenuBackdrop.setHeight(escMenuInnerMainPanel.getAssignedHeight()); + this.smashEscMenu.positionBounds(this.rootFrame, this.uiViewport); + } @Override diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MenuUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MenuUI.java index 1d41c7f..32308c8 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MenuUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MenuUI.java @@ -166,12 +166,23 @@ public class MenuUI { this.heightRatioCorrection = getMinWorldHeight() / 1200f; this.campaignStrings = new DataTable(StringBundle.EMPTY); - try (InputStream campaignStringStream = dataSource.getResourceAsStream( - "UI\\CampaignStrings" + (WarsmashConstants.GAME_VERSION == 1 ? "_exp" : "") + ".txt")) { - this.campaignStrings.readTXT(campaignStringStream, true); + final String campaignStringPath = "UI\\CampaignStrings" + (WarsmashConstants.GAME_VERSION == 1 ? "_exp" : "") + + ".txt"; + if (dataSource.has(campaignStringPath)) { + try (InputStream campaignStringStream = dataSource.getResourceAsStream(campaignStringPath)) { + this.campaignStrings.readTXT(campaignStringStream, true); + } + catch (final IOException e) { + throw new RuntimeException(e); + } } - catch (final IOException e) { - throw new RuntimeException(e); + else { + try (InputStream campaignStringStream = dataSource.getResourceAsStream("UI\\CampaignInfoClassic.txt")) { + this.campaignStrings.readTXT(campaignStringStream, true); + } + catch (final IOException e) { + throw new RuntimeException(e); + } } this.profileManager = PlayerProfileManager.loadFromGdx(); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/menu/CampaignMenuUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/menu/CampaignMenuUI.java index 335e241..8aeee26 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/menu/CampaignMenuUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/menu/CampaignMenuUI.java @@ -56,7 +56,6 @@ public class CampaignMenuUI extends SimpleFrame { super.innerPositionBounds(gameUI, viewport); final float myHeight = this.renderBounds.height; final int buttonCount = this.buttonUIs.size(); - System.out.println("myheight / count is " + myHeight + "/" + buttonCount); final float buttonSpacing = Math.min(myHeight / buttonCount, GameUI.convertY(this.uiViewport, 0.056f)); final float buttonsHeight = buttonSpacing * buttonCount; final float expectedHeight = Math.min(buttonsHeight, myHeight); @@ -65,12 +64,10 @@ public class CampaignMenuUI extends SimpleFrame { UIFrame lastButton = null; for (final CampaignButtonUI campaignButtonUI : this.buttonUIs) { if (lastButton == null) { - System.out.println("offsetting from this " + getName() + " by " + yOffset); campaignButtonUI.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this, FramePoint.TOPLEFT, 0, -yOffset)); campaignButtonUI.addSetPoint(new SetPoint(FramePoint.TOPRIGHT, this, FramePoint.TOPRIGHT, 0, -yOffset)); } else { - System.out.println("offsetting from bs " + lastButton.getName() + " by " + buttonSpacing); campaignButtonUI.addSetPoint( new SetPoint(FramePoint.TOPLEFT, lastButton, FramePoint.TOPLEFT, 0, -buttonSpacing)); campaignButtonUI.addSetPoint(